use nebu::Goldilocks;
use crate::data::{Reduction, Order};
use crate::reduce::{Outcome, ErrorKind, evaluate_unary_field};
use crate::call::CallProvider;
use crate::trace::{Tracer, TraceRow};
use crate::jets::registry::JetRegistry;
use crate::data::NIL;
const P_MINUS_2: u64 = 0xFFFFFFFEFFFFFFFF;
pub fn inv<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order, body: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
row: &mut TraceRow, registry: &JetRegistry<N>,
) -> Outcome {
let (v, budget) = match evaluate_unary_field(reduction, object, body, budget, hints, tracer, depth, registry) {
Ok(v) => v,
Err(o) => {
row.r[3] = NIL as u64;
row.r[9] = 0;
row.r[10] = match &o {
Outcome::Error(k) => *k as u64,
_ => 0,
};
tracer.record(*row);
return o;
}
};
if v == Goldilocks::ZERO {
row.r[4] = 0;
row.r[3] = NIL as u64;
row.r[10] = ErrorKind::InvZero as u64;
tracer.record(*row);
return Outcome::Error(ErrorKind::InvZero);
}
let mut acc = v;
row.r[4] = v.as_u64();
row.r[10] = acc.as_u64();
row.r[11] = 1; row.r[12] = 0; tracer.record(*row);
for step in 1u64..=63 {
let bit_pos = 63 - step;
let bit = (P_MINUS_2 >> bit_pos) & 1;
acc = acc * acc;
if bit == 1 {
acc *= v;
}
let mut step_row = TraceRow::default();
step_row.r[0] = row.r[0];
step_row.r[1] = row.r[1];
step_row.r[2] = row.r[2];
step_row.r[8] = row.r[8];
step_row.r[4] = v.as_u64();
step_row.r[10] = acc.as_u64();
step_row.r[11] = bit;
step_row.r[12] = step;
if step == 63 {
match reduction.atom(acc) {
Some(result) => {
step_row.r[3] = result as u64;
step_row.r[6] = acc.as_u64();
step_row.r[9] = budget;
tracer.record(step_row);
return Outcome::Ok(result, budget);
}
None => {
step_row.r[3] = NIL as u64;
step_row.r[10] = ErrorKind::Unavailable as u64;
tracer.record(step_row);
return Outcome::Error(ErrorKind::Unavailable);
}
}
} else {
tracer.record(step_row);
}
}
Outcome::Error(ErrorKind::Malformed)
}
#[cfg(test)]
mod tests {
use crate::reduce::{reduce, Outcome, ErrorKind};
use crate::call::NullCalls;
use crate::trace::{NoTrace, VecTrace};
use crate::data::{Reduction};
use nebu::Goldilocks;
fn g(v: u64) -> Goldilocks { Goldilocks::new(v) }
fn make_inv<const N: usize>(ar: &mut Reduction<N>, v: u64) -> crate::data::Order {
let t8 = ar.atom(g(8)).unwrap();
let t1 = ar.atom(g(1)).unwrap();
let val = ar.atom(g(v)).unwrap();
let body = ar.pair(t1, val).unwrap();
ar.pair(t8, body).unwrap()
}
#[test]
fn inv_nonzero() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_inv(&mut ar, 2);
match reduce(&mut ar, obj, formula, 10000, &NullCalls, &mut NoTrace) {
Outcome::Ok(r, _) => {
let inv2 = ar.atom_value(r).unwrap();
assert_eq!(inv2 * g(2), g(1));
}
o => panic!("{:?}", o),
}
}
#[test]
fn inv_zero_errors() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_inv(&mut ar, 0);
match reduce(&mut ar, obj, formula, 10000, &NullCalls, &mut NoTrace) {
Outcome::Error(ErrorKind::InvZero) => {}
o => panic!("expected InvZero, got {:?}", o),
}
}
#[test]
fn inv_round_trip_property() {
const P: u64 = 0xFFFF_FFFF_0000_0001;
let samples = [
1u64,
2,
3,
42,
0xDEAD_BEEF,
P - 2, P - 1, (P - 1) / 2,
];
for &k in &samples {
let mut ar = Reduction::<2048>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_inv(&mut ar, k);
match reduce(&mut ar, obj, formula, 10_000, &NullCalls, &mut NoTrace) {
Outcome::Ok(r, _) => {
let inv_k = ar.atom_value(r).unwrap();
assert_eq!(inv_k * g(k), g(1), "inv({}) * {} != 1", k, k);
}
o => panic!("inv({}) failed: {:?}", k, o),
}
}
}
#[test]
fn inv_neg_one_is_self() {
const P: u64 = 0xFFFF_FFFF_0000_0001;
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_inv(&mut ar, P - 1);
match reduce(&mut ar, obj, formula, 10_000, &NullCalls, &mut NoTrace) {
Outcome::Ok(r, _) => {
let inv_neg1 = ar.atom_value(r).unwrap();
assert_eq!(inv_neg1, g(P - 1));
}
o => panic!("{:?}", o),
}
}
#[test]
fn inv_round_trip_for_boundary_values() {
const P: u64 = 0xFFFF_FFFF_0000_0001;
let cases: &[(u64, &str)] = &[
(1, "inv(1) * 1 == 1"),
(P - 1, "inv(p-1) * (p-1) == 1"),
(2, "inv(2) * 2 == 1"),
];
for &(k, desc) in cases {
let mut ar = Reduction::<2048>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_inv(&mut ar, k);
match reduce(&mut ar, obj, formula, 10_000, &NullCalls, &mut NoTrace) {
Outcome::Ok(r, _) => {
let inv_k = ar.atom_value(r).unwrap();
assert_eq!(inv_k * g(k), g(1), "{}", desc);
}
o => panic!("{}: got {:?}", desc, o),
}
}
}
#[test]
fn inv_emits_64_rows() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_inv(&mut ar, 3);
let mut tracer = VecTrace::default();
match reduce(&mut ar, obj, formula, 10000, &NullCalls, &mut tracer) {
Outcome::Ok(_, _) => {}
o => panic!("{:?}", o),
}
let inv_rows = tracer.0.iter().filter(|r| r.r[0] == 8).count();
assert_eq!(inv_rows, 64, "inv emits 64 rows for its own computation");
}
}