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::{
  7        GLWE, LWEInfos,
  8        prepared::{GGSWPrepared, GGSWPreparedToRef},
  9    },
 10};
 11use poulpy_hal::layouts::{Backend, DataMut, DataRef, Module, Scratch, ZnxZero};
 12
 13use crate::tfhe::bdd_arithmetic::UnsignedInteger;
 14
 15pub trait BitCircuitInfo {
 16    fn info(&self) -> (&[Node], usize);
 17}
 18
 19pub trait GetBitCircuitInfo<T: UnsignedInteger> {
 20    fn input_size(&self) -> usize;
 21    fn output_size(&self) -> usize;
 22    fn get_circuit(&self, bit: usize) -> (&[Node], usize);
 23}
 24
 25pub(crate) struct BitCircuit<const N: usize> {
 26    pub(crate) nodes: [Node; N],
 27    pub(crate) max_inter_state: usize,
 28}
 29
 30pub struct Circuit<C: BitCircuitInfo, const N: usize>(pub [C; N]);
 31
 32pub trait ExecuteBDDCircuit<T: UnsignedInteger, BE: Backend> {
 33    fn execute_bdd_circuit<C, O>(
 34        &self,
 35        out: &mut [GLWE<O>],
 36        inputs: &[&dyn GGSWPreparedToRef<BE>],
 37        circuit: &C,
 38        scratch: &mut Scratch<BE>,
 39    ) where
 40        C: GetBitCircuitInfo<T>,
 41        O: DataMut;
 42}
 43
 44impl<T: UnsignedInteger, BE: Backend> ExecuteBDDCircuit<T, BE> for Module<BE>
 45where
 46    Self: Cmux<BE> + GLWECopy,
 47    Scratch<BE>: ScratchTakeCore<BE>,
 48{
 49    fn execute_bdd_circuit<C, O>(
 50        &self,
 51        out: &mut [GLWE<O>],
 52        inputs: &[&dyn GGSWPreparedToRef<BE>],
 53        circuit: &C,
 54        scratch: &mut Scratch<BE>,
 55    ) where
 56        C: GetBitCircuitInfo<T>,
 57        O: DataMut,
 58    {
 59        #[cfg(debug_assertions)]
 60        {
 61            assert_eq!(inputs.len(), circuit.input_size());
 62            assert!(out.len() >= circuit.output_size());
 63        }
 64
 65        for (i, out_i) in out.iter_mut().enumerate().take(circuit.output_size()) {
 66            let (nodes, max_inter_state) = circuit.get_circuit(i);
 67
 68            assert!(nodes.len().is_multiple_of(max_inter_state));
 69
 70            let (mut level, scratch_1) = scratch.take_glwe_slice(max_inter_state * 2, out_i);
 71
 72            level.iter_mut().for_each(|ct| ct.data_mut().zero());
 73
 74            // TODO: implement API on GLWE
 75            level[1]
 76                .data_mut()
 77                .encode_coeff_i64(out_i.base2k().into(), 0, 2, 0, 1);
 78
 79            let mut level_ref = level.iter_mut().collect_vec();
 80            let (mut prev_level, mut next_level) = level_ref.split_at_mut(max_inter_state);
 81
 82            let (all_but_last, last) = nodes.split_at(nodes.len() - max_inter_state);
 83
 84            for nodes_lvl in all_but_last.chunks_exact(max_inter_state) {
 85                for (j, node) in nodes_lvl.iter().enumerate() {
 86                    match node {
 87                        Node::Cmux(in_idx, hi_idx, lo_idx) => {
 88                            self.cmux(
 89                                next_level[j],
 90                                prev_level[*hi_idx],
 91                                prev_level[*lo_idx],
 92                                &inputs[*in_idx].to_ref(),
 93                                scratch_1,
 94                            );
 95                        }
 96                        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 */
 97                        Node::None => {}
 98                    }
 99                }
100
101                (prev_level, next_level) = (next_level, prev_level);
102            }
103
104            // Last chunck of max_inter_state Nodes is always structured as
105            // [CMUX, NONE, NONE, ..., NONE]
106            match &last[0] {
107                Node::Cmux(in_idx, hi_idx, lo_idx) => {
108                    self.cmux(
109                        out_i,
110                        prev_level[*hi_idx],
111                        prev_level[*lo_idx],
112                        &inputs[*in_idx].to_ref(),
113                        scratch_1,
114                    );
115                }
116                _ => {
117                    panic!("invalid last node, should be CMUX")
118                }
119            }
120        }
121
122        for out_i in out.iter_mut().skip(circuit.output_size()) {
123            out_i.data_mut().zero();
124        }
125    }
126}
127
128impl<const N: usize> BitCircuit<N> {
129    pub(crate) const fn new(nodes: [Node; N], max_inter_state: usize) -> Self {
130        Self {
131            nodes,
132            max_inter_state,
133        }
134    }
135}
136impl<const N: usize> BitCircuitInfo for BitCircuit<N> {
137    fn info(&self) -> (&[Node], usize) {
138        (self.nodes.as_ref(), self.max_inter_state)
139    }
140}
141
142#[derive(Debug)]
143pub enum Node {
144    Cmux(usize, usize, usize),
145    Copy,
146    None,
147}
148
149pub trait Cmux<BE: Backend> {
150    fn cmux<O, T, F, S>(&self, out: &mut GLWE<O>, t: &GLWE<T>, f: &GLWE<F>, s: &GGSWPrepared<S, BE>, scratch: &mut Scratch<BE>)
151    where
152        O: DataMut,
153        T: DataRef,
154        F: DataRef,
155        S: DataRef;
156}
157
158impl<BE: Backend> Cmux<BE> for Module<BE>
159where
160    Module<BE>: GLWEExternalProduct<BE> + GLWESub + GLWEAdd,
161    Scratch<BE>: ScratchTakeCore<BE>,
162{
163    fn cmux<O, T, F, S>(&self, out: &mut GLWE<O>, t: &GLWE<T>, f: &GLWE<F>, s: &GGSWPrepared<S, BE>, scratch: &mut Scratch<BE>)
164    where
165        O: DataMut,
166        T: DataRef,
167        F: DataRef,
168        S: DataRef,
169    {
170        // let mut out: GLWECiphertext<&mut [u8]> = out.to_mut();
171        self.glwe_sub(out, t, f);
172        self.glwe_external_product_inplace(out, s, scratch);
173        self.glwe_add_inplace(out, f);
174    }
175}