arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/poulpy-schemes/src/tfhe/bdd_arithmetic/eval.rs
poulpy-schemes/src/tfhe/bdd_arithmetic/eval.rsRaw
  1use core::panic;
  2
  3use itertools::Itertools;
  4use poulpy_core::{
  5    GLWEAdd, GLWECopy, GLWEExternalProduct, GLWESub, ScratchTakeCore,
  6    layouts::{GGSWInfos, GLWE, GLWEInfos, GLWEToMut, GLWEToRef, LWEInfos, prepared::GGSWPreparedToRef},
  7};
  8use poulpy_hal::layouts::{Backend, DataMut, Module, Scratch, ZnxZero};
  9
 10use crate::tfhe::bdd_arithmetic::{GetGGSWBit, UnsignedInteger};
 11
 12pub trait BitCircuitInfo {
 13    fn info(&self) -> (&[Node], usize);
 14}
 15
 16pub trait GetBitCircuitInfo<T: UnsignedInteger> {
 17    fn input_size(&self) -> usize;
 18    fn output_size(&self) -> usize;
 19    fn get_circuit(&self, bit: usize) -> (&[Node], usize);
 20}
 21
 22pub struct BitCircuit<const N: usize> {
 23    pub nodes: [Node; N],
 24    pub max_inter_state: usize,
 25}
 26
 27pub trait BitCircuitFamily {
 28    const INPUT_BITS: usize;
 29    const OUTPUT_BITS: usize;
 30}
 31
 32pub struct Circuit<C: BitCircuitInfo, const N: usize>(pub [C; N]);
 33
 34impl<C, T: UnsignedInteger, const N: usize> GetBitCircuitInfo<T> for Circuit<C, N>
 35where
 36    C: BitCircuitInfo + BitCircuitFamily,
 37{
 38    fn input_size(&self) -> usize {
 39        C::INPUT_BITS
 40    }
 41    fn output_size(&self) -> usize {
 42        C::OUTPUT_BITS
 43    }
 44    fn get_circuit(&self, bit: usize) -> (&[Node], usize) {
 45        self.0[bit].info()
 46    }
 47}
 48
 49pub trait ExecuteBDDCircuit<T: UnsignedInteger, BE: Backend> {
 50    fn execute_bdd_circuit<C, G, O>(&self, out: &mut [GLWE<O>], inputs: &G, circuit: &C, scratch: &mut Scratch<BE>)
 51    where
 52        G: GetGGSWBit<BE> + BitSize,
 53        C: GetBitCircuitInfo<T>,
 54        O: DataMut;
 55}
 56
 57pub trait BitSize {
 58    fn bit_size(&self) -> usize;
 59}
 60
 61impl<T: UnsignedInteger, BE: Backend> ExecuteBDDCircuit<T, BE> for Module<BE>
 62where
 63    Self: Cmux<BE> + GLWECopy,
 64    Scratch<BE>: ScratchTakeCore<BE>,
 65{
 66    fn execute_bdd_circuit<C, G, O>(&self, out: &mut [GLWE<O>], inputs: &G, circuit: &C, scratch: &mut Scratch<BE>)
 67    where
 68        G: GetGGSWBit<BE> + BitSize,
 69        C: GetBitCircuitInfo<T>,
 70        O: DataMut,
 71    {
 72        #[cfg(debug_assertions)]
 73        {
 74            assert!(inputs.bit_size() >= circuit.input_size());
 75            assert!(out.len() >= circuit.output_size());
 76        }
 77
 78        for (i, out_i) in out.iter_mut().enumerate().take(circuit.output_size()) {
 79            let (nodes, max_inter_state) = circuit.get_circuit(i);
 80
 81            if max_inter_state == 0 {
 82                out_i.data_mut().zero();
 83            } else {
 84                assert!(nodes.len().is_multiple_of(max_inter_state));
 85
 86                let (mut level, scratch_1) = scratch.take_glwe_slice(max_inter_state * 2, out_i);
 87
 88                level.iter_mut().for_each(|ct| ct.data_mut().zero());
 89
 90                // TODO: implement API on GLWE
 91                level[1]
 92                    .data_mut()
 93                    .encode_coeff_i64(out_i.base2k().into(), 0, 2, 0, 1);
 94
 95                let mut level_ref = level.iter_mut().collect_vec();
 96                let (mut prev_level, mut next_level) = level_ref.split_at_mut(max_inter_state);
 97
 98                let (all_but_last, last) = nodes.split_at(nodes.len() - max_inter_state);
 99
100                for nodes_lvl in all_but_last.chunks_exact(max_inter_state) {
101                    for (j, node) in nodes_lvl.iter().enumerate() {
102                        match node {
103                            Node::Cmux(in_idx, hi_idx, lo_idx) => {
104                                self.cmux(
105                                    next_level[j],
106                                    prev_level[*hi_idx],
107                                    prev_level[*lo_idx],
108                                    &inputs.get_bit(*in_idx),
109                                    scratch_1,
110                                );
111                            }
112                            Node::Copy => self.glwe_copy(next_level[j], prev_level[j]), /* Update BDD circuits to order Cmux -> Copy -> None so that mem swap can be used */
113                            Node::None => {}
114                        }
115                    }
116
117                    (prev_level, next_level) = (next_level, prev_level);
118                }
119
120                // Last chunck of max_inter_state Nodes is always structured as
121                // [CMUX, NONE, NONE, ..., NONE]
122                match &last[0] {
123                    Node::Cmux(in_idx, hi_idx, lo_idx) => {
124                        self.cmux(
125                            out_i,
126                            prev_level[*hi_idx],
127                            prev_level[*lo_idx],
128                            &inputs.get_bit(*in_idx),
129                            scratch_1,
130                        );
131                    }
132                    _ => {
133                        panic!("invalid last node, should be CMUX")
134                    }
135                }
136            }
137        }
138
139        for out_i in out.iter_mut().skip(circuit.output_size()) {
140            out_i.data_mut().zero();
141        }
142    }
143}
144
145impl<const N: usize> BitCircuit<N> {
146    pub const fn new(nodes: [Node; N], max_inter_state: usize) -> Self {
147        Self {
148            nodes,
149            max_inter_state,
150        }
151    }
152}
153impl<const N: usize> BitCircuitInfo for BitCircuit<N> {
154    fn info(&self) -> (&[Node], usize) {
155        (self.nodes.as_ref(), self.max_inter_state)
156    }
157}
158
159#[derive(Debug)]
160pub enum Node {
161    Cmux(usize, usize, usize),
162    Copy,
163    None,
164}
165
166pub trait Cmux<BE: Backend>
167where
168    Self: GLWEExternalProduct<BE> + GLWESub + GLWEAdd,
169{
170    fn cmux_tmp_bytes<R, A, B>(&self, res_infos: &R, a_infos: &A, b_infos: &B) -> usize
171    where
172        R: GLWEInfos,
173        A: GLWEInfos,
174        B: GGSWInfos,
175    {
176        self.glwe_external_product_tmp_bytes(res_infos, a_infos, b_infos)
177    }
178
179    fn cmux<R, T, F, S>(&self, res: &mut R, t: &T, f: &F, s: &S, scratch: &mut Scratch<BE>)
180    where
181        R: GLWEToMut,
182        T: GLWEToRef,
183        F: GLWEToRef,
184        S: GGSWPreparedToRef<BE>,
185        Scratch<BE>: ScratchTakeCore<BE>,
186    {
187        self.glwe_sub(res, t, f);
188        self.glwe_external_product_inplace(res, s, scratch);
189        self.glwe_add_inplace(res, f);
190    }
191
192    fn cmux_inplace<R, A, S>(&self, res: &mut R, a: &A, s: &S, scratch: &mut Scratch<BE>)
193    where
194        R: GLWEToMut,
195        A: GLWEToRef,
196        S: GGSWPreparedToRef<BE>,
197        Scratch<BE>: ScratchTakeCore<BE>,
198    {
199        self.glwe_sub_inplace(res, a);
200        self.glwe_external_product_inplace(res, s, scratch);
201        self.glwe_add_inplace(res, a);
202    }
203}
204
205impl<BE: Backend> Cmux<BE> for Module<BE>
206where
207    Self: GLWEExternalProduct<BE> + GLWESub + GLWEAdd,
208    Scratch<BE>: ScratchTakeCore<BE>,
209{
210}