Skip to main content

handwired_rs/
wire.rs

1//! Wires and affine forms.
2//!
3//! A [`Wire`] is the position of a number in a program: a source cell or an
4//! operation's result. It is its own type, so a plain integer can't stand in
5//! for one. A [`Lin`] is an affine form, a bias plus wires times coefficients,
6//! built with ordinary operators, e.g. `x * 0.5 - 26.0` or `a - b`. Building a
7//! `Lin` appends nothing; passing it to [`Circuit::affine`](crate::Circuit::affine)
8//! or another builder method does.
9
10use std::ops::{Add, Mul, Neg, Sub};
11
12/// The position of a source cell or of an operation's result.
13#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct Wire(pub(crate) u32);
15
16impl Wire {
17    /// A wire at a position counted by hand, like handwired's `asWire`.
18    pub fn from_index(index: usize) -> Wire {
19        Wire(u32::try_from(index).expect("wire position must fit in u32"))
20    }
21
22    pub fn index(self) -> usize {
23        self.0 as usize
24    }
25}
26
27/// One term of an affine form: a wire and its coefficient. Coefficients are
28/// kept in f64 like handwired's, and rounded to f32 when evaluated.
29pub type Term = (Wire, f64);
30
31/// An affine form: `bias + Σ coefficient × wire`, with terms in the order
32/// they were written. Ordinary builder methods sort and merge terms; the
33/// rounding-preserving ones, e.g. [`Circuit::round_affine`](crate::Circuit::round_affine),
34/// keep this order.
35#[derive(Clone, Debug, Default, PartialEq)]
36pub struct Lin {
37    pub terms: Vec<Term>,
38    pub bias: f64,
39}
40
41impl Lin {
42    pub fn new(terms: Vec<Term>, bias: f64) -> Lin {
43        Lin { terms, bias }
44    }
45
46    pub fn constant(bias: f64) -> Lin {
47        Lin { terms: Vec::new(), bias }
48    }
49
50    fn scaled(mut self, factor: f64) -> Lin {
51        for term in &mut self.terms {
52            term.1 *= factor;
53        }
54        self.bias *= factor;
55        self
56    }
57
58    fn plus(mut self, other: Lin) -> Lin {
59        self.terms.extend(other.terms);
60        self.bias += other.bias;
61        self
62    }
63}
64
65impl From<Wire> for Lin {
66    fn from(wire: Wire) -> Lin {
67        Lin { terms: vec![(wire, 1.0)], bias: 0.0 }
68    }
69}
70
71impl From<f64> for Lin {
72    fn from(bias: f64) -> Lin {
73        Lin::constant(bias)
74    }
75}
76
77impl Neg for Lin {
78    type Output = Lin;
79    fn neg(self) -> Lin {
80        self.scaled(-1.0)
81    }
82}
83
84impl Neg for Wire {
85    type Output = Lin;
86    fn neg(self) -> Lin {
87        Lin::from(self).scaled(-1.0)
88    }
89}
90
91impl Mul<f64> for Lin {
92    type Output = Lin;
93    fn mul(self, factor: f64) -> Lin {
94        self.scaled(factor)
95    }
96}
97
98impl Mul<f64> for Wire {
99    type Output = Lin;
100    fn mul(self, factor: f64) -> Lin {
101        Lin::from(self).scaled(factor)
102    }
103}
104
105impl Mul<Wire> for f64 {
106    type Output = Lin;
107    fn mul(self, wire: Wire) -> Lin {
108        Lin::from(wire).scaled(self)
109    }
110}
111
112impl Mul<Lin> for f64 {
113    type Output = Lin;
114    fn mul(self, form: Lin) -> Lin {
115        form.scaled(self)
116    }
117}
118
119// Addition and subtraction between any two of Wire, Lin and f64, except f64 with f64.
120macro_rules! affine_arithmetic {
121    ($left:ty, $right:ty) => {
122        impl Add<$right> for $left {
123            type Output = Lin;
124            fn add(self, right: $right) -> Lin {
125                Lin::from(self).plus(Lin::from(right))
126            }
127        }
128        impl Sub<$right> for $left {
129            type Output = Lin;
130            fn sub(self, right: $right) -> Lin {
131                Lin::from(self).plus(Lin::from(right).scaled(-1.0))
132            }
133        }
134    };
135}
136
137affine_arithmetic!(Wire, Wire);
138affine_arithmetic!(Wire, Lin);
139affine_arithmetic!(Wire, f64);
140affine_arithmetic!(Lin, Wire);
141affine_arithmetic!(Lin, Lin);
142affine_arithmetic!(Lin, f64);
143affine_arithmetic!(f64, Wire);
144affine_arithmetic!(f64, Lin);
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn operators_build_forms_in_written_order() {
152        let a = Wire(3);
153        let b = Wire(1);
154        let form = a * 0.5 - b + 2.0;
155        assert_eq!(form, Lin::new(vec![(a, 0.5), (b, -1.0)], 2.0));
156        assert_eq!(1.0 - a, Lin::new(vec![(a, -1.0)], 1.0));
157    }
158}