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