1use crate::custom_ops::CustomOperationBody;
3use crate::data_types::{Type, INT64, UINT64};
4use crate::errors::Result;
5use crate::graphs::{Context, Graph};
6
7use serde::{Deserialize, Serialize};
8
9use super::utils::{constant_scalar, inverse_initial_approximation, multiply_fixed_point};
10
11#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
49pub struct NewtonInversion {
50 pub iterations: u64,
52 pub denominator_cap_2k: u64,
54}
55
56#[typetag::serde]
57impl CustomOperationBody for NewtonInversion {
58 fn instantiate(&self, context: Context, arguments_types: Vec<Type>) -> Result<Graph> {
59 if arguments_types.len() != 1 && arguments_types.len() != 2 {
60 return Err(runtime_error!(
61 "Invalid number of arguments for NewtonDivision"
62 ));
63 }
64 let t = arguments_types[0].clone();
65 if !t.is_scalar() && !t.is_array() {
66 return Err(runtime_error!(
67 "Divisor in NewtonDivision must be a scalar or an array"
68 ));
69 }
70 let sc = t.get_scalar_type();
71 if sc != UINT64 && sc != INT64 {
72 return Err(runtime_error!(
73 "Divisor in NewtonDivision must consist of either INT64s or UINT64s"
74 ));
75 }
76 let has_initial_approximation = arguments_types.len() == 2;
77 if has_initial_approximation {
78 let divisor_t = arguments_types[1].clone();
79 if divisor_t != t {
80 return Err(runtime_error!(
81 "Divisor and initial approximation must have the same type."
82 ));
83 }
84 }
85
86 let g_initial_approximation =
87 inverse_initial_approximation(&context, t.clone(), self.denominator_cap_2k)?;
88 let g = context.create_graph()?;
89 let divisor = g.input(t.clone())?;
90 let mut approximation = if has_initial_approximation {
91 g.input(t)?
92 } else if self.denominator_cap_2k == 0 {
93 g.ones(t)?
94 } else {
95 g.call(g_initial_approximation, vec![divisor.clone()])?
96 };
97 let two_power_cap_plus_one = constant_scalar(&g, 1 << (self.denominator_cap_2k + 1), sc)?;
100 for _ in 0..self.iterations {
101 let x = approximation;
102 let mult = two_power_cap_plus_one.subtract(x.multiply(divisor.clone())?)?;
103 approximation = multiply_fixed_point(mult, x, self.denominator_cap_2k)?;
104 }
105 approximation.set_as_output()?;
106 g.finalize()?;
107 Ok(g)
108 }
109
110 fn get_name(&self) -> String {
111 format!(
112 "NewtonDivision(iterations={}, cap=2**{})",
113 self.iterations, self.denominator_cap_2k
114 )
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 use crate::custom_ops::run_instantiation_pass;
123 use crate::custom_ops::CustomOperation;
124 use crate::data_types::{array_type, scalar_type, ScalarType};
125 use crate::data_values::Value;
126 use crate::evaluators::random_evaluate;
127 use crate::graphs::util::simple_context;
128 use crate::inline::inline_common::DepthOptimizationLevel;
129 use crate::inline::inline_ops::inline_operations;
130 use crate::inline::inline_ops::InlineConfig;
131 use crate::inline::inline_ops::InlineMode;
132 use crate::mpc::mpc_compiler::prepare_for_mpc_evaluation;
133 use crate::mpc::mpc_compiler::IOStatus;
134
135 fn scalar_division_helper(
136 divisor: u64,
137 initial_approximation: Option<u64>,
138 st: ScalarType,
139 ) -> Result<Value> {
140 let c = simple_context(|g| {
141 let i = g.input(scalar_type(st))?;
142 if let Some(approx) = initial_approximation {
143 let approx_const = constant_scalar(&g, approx, st)?;
144 g.custom_op(
145 CustomOperation::new(NewtonInversion {
146 iterations: 5,
147 denominator_cap_2k: 10,
148 }),
149 vec![i, approx_const],
150 )
151 } else {
152 g.custom_op(
153 CustomOperation::new(NewtonInversion {
154 iterations: 5,
155 denominator_cap_2k: 10,
156 }),
157 vec![i],
158 )
159 }
160 })?;
161 let mapped_c = run_instantiation_pass(c)?;
162 let result = random_evaluate(
163 mapped_c.get_context().get_main_graph()?,
164 vec![Value::from_scalar(divisor, st)?],
165 )?;
166 Ok(result)
167 }
168
169 fn array_division_helper(divisor: Vec<u64>, st: ScalarType) -> Result<Vec<u64>> {
170 let array_t = array_type(vec![divisor.len() as u64], st);
171 let c = simple_context(|g| {
172 let i = g.input(array_t.clone())?;
173 g.custom_op(
174 CustomOperation::new(NewtonInversion {
175 iterations: 5,
176 denominator_cap_2k: 10,
177 }),
178 vec![i],
179 )
180 })?;
181 let mapped_c = run_instantiation_pass(c)?;
182 let result = random_evaluate(
183 mapped_c.get_context().get_main_graph()?,
184 vec![Value::from_flattened_array(&divisor, st)?],
185 )?;
186 result.to_flattened_array_u64(array_t)
187 }
188
189 #[test]
190 fn test_newton_division_scalar() {
191 let div_v = vec![1, 2, 3, 123, 300, 500, 700];
192 for i in div_v.clone() {
193 assert!(
194 (scalar_division_helper(i, None, UINT64)
195 .unwrap()
196 .to_u64(UINT64)
197 .unwrap() as i64
198 - 1024 / i as i64)
199 .abs()
200 <= 1
201 );
202
203 assert!(
204 (scalar_division_helper(i, None, INT64)
205 .unwrap()
206 .to_i64(INT64)
207 .unwrap() as i64
208 - 1024 / i as i64)
209 .abs()
210 <= 1
211 );
212 }
213 }
214
215 #[test]
216 fn test_newton_division_array() {
217 let arr = vec![23, 32, 57, 71, 183, 555];
218 let div = array_division_helper(arr.clone(), UINT64).unwrap();
219 let i_div = array_division_helper(arr.clone(), INT64).unwrap();
220 for i in 0..arr.len() {
221 assert!((div[i] as i64 - 1024 / arr[i] as i64).abs() <= 1);
222 assert!((i_div[i] as i64 - 1024 / arr[i] as i64).abs() <= 1);
223 }
224 }
225
226 #[test]
227 fn test_newton_division_with_initial_guess() {
228 for i in vec![1, 2, 3, 123, 300, 500, 700] {
229 let mut initial_guess = 1;
230 while initial_guess * i * 2 < 1024 {
231 initial_guess *= 2;
232 }
233 assert!(
234 (scalar_division_helper(i, Some(initial_guess), UINT64)
235 .unwrap()
236 .to_u64(UINT64)
237 .unwrap() as i64
238 - 1024 / i as i64)
239 .abs()
240 <= 1
241 );
242 assert!(
243 (scalar_division_helper(i, Some(initial_guess), INT64)
244 .unwrap()
245 .to_i64(INT64)
246 .unwrap() as i64
247 - 1024 / i as i64)
248 .abs()
249 <= 1
250 );
251 }
252 }
253
254 #[test]
255 fn test_newton_inversion_compiles_end2end() -> Result<()> {
256 let c = simple_context(|g| {
257 let i = g.input(scalar_type(INT64))?;
258 g.custom_op(
259 CustomOperation::new(NewtonInversion {
260 iterations: 5,
261 denominator_cap_2k: 10,
262 }),
263 vec![i],
264 )
265 })?;
266 let inline_config = InlineConfig {
267 default_mode: InlineMode::DepthOptimized(DepthOptimizationLevel::Default),
268 ..Default::default()
269 };
270 let instantiated_context = run_instantiation_pass(c)?.get_context();
271 let inlined_context = inline_operations(instantiated_context, inline_config.clone())?;
272 let _unused = prepare_for_mpc_evaluation(
273 inlined_context,
274 vec![vec![IOStatus::Shared]],
275 vec![vec![]],
276 inline_config,
277 )?;
278 Ok(())
279 }
280}