Skip to main content

relp_num/traits/
absorb.rs

1//! # Acting on a wide accumulator with a narrow value
2//!
3//! Exact linear programs are stored in narrow number types and computed in wide ones. The
4//! constraint matrix of a network problem holds nothing but `1`, `-1` and `0`; the basis inverse
5//! the simplex method builds from it needs arbitrary precision. Widening every coefficient before
6//! using it throws away exactly the information that makes the narrow type worth having:
7//! multiplying by [`One`](crate::fixed::One) is a clone, adding [`Zero`](crate::fixed::Zero) is
8//! nothing at all, and adding an integer to a fraction leaves its denominator alone.
9//!
10//! [`Absorb`] is the one relation between the two. It is written from the wide side, and that is
11//! the whole point: an implementation names both types concretely, so it can reach the operator
12//! that was written for exactly this pair. A trait written from the narrow side and generic over
13//! the wide type cannot, because inside such a body the wide type is opaque and converting is the
14//! only thing left to do.
15//!
16//! [`Narrow`](crate::Narrow) is the same boundary in the other direction, and is written from the
17//! wide side for the same reason.
18
19use crate::Field;
20
21/// What a wide accumulator does with a narrow value.
22///
23/// The wide type is `Self`, so every implementation sees both types concretely and can be written
24/// in terms of the operators that exist for the pair.
25///
26/// # No defaults
27///
28/// Every method has to be written out. A default body would have to be phrased in terms of
29/// [`from_narrow`](Absorb::from_narrow), which is the slow path: it materialises a wide value and
30/// hands it to the general routine, so a forgotten override would cost orders of magnitude at the
31/// larger widths and cost nothing at the smallest, which is where it would be tested. Requiring
32/// each body makes leaving one out a compile error rather than a benchmark result.
33///
34/// Two macros write the bodies, so the burden is one line per pair in practice:
35/// `absorb_by_ops!` for a pair that has operators, `absorb_by_widening!` for one that does not.
36///
37/// # Extending it
38///
39/// A crate downstream can implement this for its own narrow type against a wide type from here:
40/// `impl<const S: usize> Absorb<MyCoefficient> for Big<S>` is a foreign trait for a foreign `Self`
41/// with a local type parameter, which the orphan rule permits. Such an implementation sees `Big`
42/// concretely and can be as fast as one written here.
43///
44/// A crate that brings its own wide type implements this for it against the narrow types it wants
45/// to read, which is always allowed because `Self` is then local.
46///
47/// # Agreement
48///
49/// The four operating methods have to agree with [`from_narrow`](Absorb::from_narrow):
50/// `w.add_narrow(n)` equals `w += W::from_narrow(n)`, `w.sub_narrow(n)` equals
51/// `w -= W::from_narrow(n)`, `w.mul_narrow(n)` equals `w * W::from_narrow(n)`, and
52/// `w.add_mul_narrow(n, v)` equals `w += v * W::from_narrow(n)`. The tests in this crate check
53/// every implementation against those four statements.
54///
55/// # Example
56///
57/// ```
58/// use relp_num::fixed::One;
59/// use relp_num::{Absorb, RationalBig, RB};
60///
61/// let mut accumulator = RB!(1, 2);
62/// // No `RationalBig` is materialised, and no gcd is taken: the denominator is added into the
63/// // numerator where it stands.
64/// accumulator.add_narrow(&One);
65/// assert_eq!(accumulator, RB!(3, 2));
66///
67/// assert_eq!(RB!(7, 2).mul_narrow(&One), RB!(7, 2));
68/// assert_eq!(<RationalBig as Absorb<One>>::from_narrow(&One), RB!(1));
69/// ```
70pub trait Absorb<N>: Sized {
71    /// The narrow value in the wide type.
72    fn from_narrow(narrow: &N) -> Self;
73
74    /// Add a narrow value to this one.
75    fn add_narrow(&mut self, narrow: &N);
76
77    /// Subtract a narrow value from this one.
78    fn sub_narrow(&mut self, narrow: &N);
79
80    /// Multiply this value by a narrow one.
81    fn mul_narrow(&self, narrow: &N) -> Self;
82
83    /// Add the product of a narrow value and a wide one to this value.
84    ///
85    /// This is the operation an inner product is made of, and the reason it is a method of its own
86    /// rather than a multiplication followed by an addition: for a narrow value that is one, the
87    /// multiplication is not a cheap multiplication but no multiplication, and the intermediate it
88    /// would have produced is not a cheap allocation but no allocation.
89    fn add_mul_narrow(&mut self, narrow: &N, other: &Self);
90}
91
92/// A wide accumulator, together with the narrow values it reads.
93///
94/// Stating `W: AbsorbAll<N>` is stating `W: Field + Absorb<N>`, which is what generic code over a
95/// matrix provider's coefficient type wants: a field to compute in, and the ability to act on it
96/// with what the provider hands over.
97pub trait AbsorbAll<N>: Field + Absorb<N> {}
98impl<N, W: Field + Absorb<N>> AbsorbAll<N> for W {}
99
100#[cfg(test)]
101mod test {
102    use crate::{Absorb, AbsorbAll, Field, FieldRef, Rational8, Rational16, Rational32, Rational64, Rational128, RationalBig, RB};
103    use crate::fixed::{Binary, One, SignedOne, Zero};
104
105    /// Every implementation has to agree with converting and then operating.
106    ///
107    /// This is the contract that makes the whole table trustworthy: the fast bodies are written by
108    /// hand against the operators of a pair, and nothing but this check ties them back to what the
109    /// narrow value means.
110    fn check<T>(value: &T, wide: RationalBig)
111    where
112        RationalBig: Absorb<T>,
113    {
114        assert_eq!(RationalBig::from_narrow(value), wide, "from_narrow");
115
116        let start = RB!(5, 3);
117
118        let mut by_override = start.clone();
119        by_override.add_narrow(value);
120        assert_eq!(by_override, start.clone() + wide.clone(), "add_narrow");
121
122        let mut by_override = start.clone();
123        by_override.sub_narrow(value);
124        assert_eq!(by_override, start.clone() - wide.clone(), "sub_narrow");
125
126        assert_eq!(start.mul_narrow(value), wide.clone() * start.clone(), "mul_narrow");
127        assert_eq!(RB!(0).mul_narrow(value), RB!(0), "mul_narrow by zero");
128
129        // The fused operation, against the two steps it replaces.
130        for other in [RB!(0), RB!(1), RB!(-7, 5), start.clone()] {
131            let mut fused = start.clone();
132            fused.add_mul_narrow(value, &other);
133            assert_eq!(
134                fused,
135                start.clone() + wide.clone() * other.clone(),
136                "add_mul_narrow by {other}",
137            );
138        }
139    }
140
141    #[test]
142    fn markers_agree_with_converting() {
143        check(&One, RB!(1));
144        check(&Zero, RB!(0));
145        check(&Binary::Zero, RB!(0));
146        check(&Binary::One, RB!(1));
147        check(&SignedOne::PlusOne, RB!(1));
148        check(&SignedOne::MinusOne, RB!(-1));
149    }
150
151    #[test]
152    fn options_and_references_agree_with_converting() {
153        check(&Some(One), RB!(1));
154        check(&None::<One>, RB!(0));
155        check(&Some(SignedOne::MinusOne), RB!(-1));
156        check(&&One, RB!(1));
157        check(&Some(&SignedOne::MinusOne), RB!(-1));
158        check(&None::<Rational64>, RB!(0));
159        check(&Some(Rational64::new(3, 4).unwrap()), RB!(3, 4));
160    }
161
162    #[test]
163    fn integers_and_rationals_agree_with_converting() {
164        check(&7_i32, RB!(7));
165        check(&-7_i64, RB!(-7));
166        check(&0_u8, RB!(0));
167        check(&0_i16, RB!(0));
168        check(&Rational8::new(3, 4).unwrap(), RB!(3, 4));
169        check(&Rational16::new(-3, 4).unwrap(), RB!(-3, 4));
170        check(&Rational32::new(3, 4).unwrap(), RB!(3, 4));
171        check(&Rational64::new(3, 4).unwrap(), RB!(3, 4));
172        check(&Rational128::new(3, 4).unwrap(), RB!(3, 4));
173        check(&RB!(-9, 4), RB!(-9, 4));
174    }
175
176    /// The widths that only became reachable once the conversion bound went away.
177    ///
178    /// These used to be covered by an implementation whose `for<'r> From<&'r Self>` bound
179    /// `RationalBig` does not satisfy, so the implementation existed and the trait did not hold,
180    /// which showed up at a use site as an error about a conversion nobody asked for.
181    #[test]
182    fn pointer_widths() {
183        check(&7_usize, RB!(7));
184        check(&7_isize, RB!(7));
185        check(&(-7_isize), RB!(-7));
186    }
187
188    /// Generic code over a field, stated the way it has to be stated with [`FieldRef`].
189    ///
190    /// The bound on the reference is a higher ranked one on a type that is not `Self`, so it is a
191    /// requirement on the caller rather than something a `F: Field` bound implies. Moving it onto
192    /// the definition of `Field` does not help: it stays a requirement, and one that then has to be
193    /// discharged at every use of `Field` instead of every use of the reference operations.
194    fn with_field_ref<F: Field>(a: &F, b: &F) -> F
195    where
196        for<'r> &'r F: FieldRef<F>,
197    {
198        &(a * b) + a
199    }
200
201    /// The same function over [`AbsorbAll`], which is one bound and no higher ranked anything.
202    ///
203    /// `Absorb<F> for F` is the reference arithmetic: every method already takes its operands by
204    /// reference, so there is no reference type left to put a bound on.
205    fn with_absorb<F: AbsorbAll<F>>(a: &F, b: &F) -> F {
206        let mut result = a.mul_narrow(b);
207        result.add_narrow(a);
208        result
209    }
210
211    #[test]
212    fn one_bound_replaces_the_higher_ranked_pair() {
213        let a = RB!(3, 2);
214        let b = RB!(5, 7);
215        assert_eq!(with_field_ref(&a, &b), with_absorb(&a, &b));
216        assert_eq!(with_absorb(&a, &b), RB!(3, 2) * RB!(5, 7) + RB!(3, 2));
217    }
218
219    /// Mixed narrow and wide arithmetic behind the same single bound.
220    ///
221    /// This is the shape a sparse inner product has, and the bound is stated once on the
222    /// accumulator rather than once per narrow type the caller might bring.
223    fn inner_product<W: AbsorbAll<N>, N>(column: &[(usize, N)], pi: &[W]) -> W {
224        let mut total = W::zero();
225        for (index, value) in column {
226            total.add_mul_narrow(value, &pi[*index]);
227        }
228        total
229    }
230
231    #[test]
232    fn inner_product_over_any_narrow_type() {
233        let pi = [RB!(5), RB!(4), RB!(-1, 2)];
234
235        // A marker column: no multiplication happens at all.
236        assert_eq!(inner_product(&[(0, One), (2, One)], &pi), RB!(9, 2));
237        assert_eq!(
238            inner_product(&[(0, SignedOne::MinusOne), (1, SignedOne::PlusOne)], &pi),
239            RB!(-1),
240        );
241        // A small rational column.
242        let column = [(0, Rational64::new(1, 5).unwrap()), (1, Rational64::new(1, 2).unwrap())];
243        assert_eq!(inner_product(&column, &pi), RB!(3));
244        // An integer column.
245        assert_eq!(inner_product(&[(1, 3_i64)], &pi), RB!(12));
246        // A column of the wide type itself.
247        assert_eq!(inner_product(&[(0, RB!(2))], &pi), RB!(10));
248    }
249
250    /// The trait is usable through a reference and an `Option`, which is how a sparse column of a
251    /// matrix provider arrives.
252    #[test]
253    fn references_and_absent_values() {
254        let pi = [RB!(5), RB!(4)];
255        let column: Vec<(usize, Option<One>)> = vec![(0, Some(One)), (1, None)];
256        assert_eq!(inner_product(&column, &pi), RB!(5));
257
258        let stored = [One, One];
259        let column: Vec<(usize, &One)> = vec![(0, &stored[0]), (1, &stored[1])];
260        assert_eq!(inner_product(&column, &pi), RB!(9));
261    }
262}