Skip to main content

oximo_expr/
ops.rs

1use std::ops::{Add, Div, Mul, Neg, Sub};
2
3use crate::arena::{Children, ExprId, ExprNode};
4use crate::handle::Expr;
5use crate::linear::{add_into, add_n, div_into, mul_into, neg_into, sub_into};
6
7// -----------------------------------------------------------------------------
8// Expr <op> Expr
9// -----------------------------------------------------------------------------
10
11impl<'a> Add for Expr<'a> {
12    type Output = Self;
13    fn add(self, rhs: Self) -> Self {
14        self.assert_same_arena(rhs);
15        let id = self.arena.with_mut(|arena| add_into(arena, self.id, rhs.id));
16        Self::new(id, self.arena)
17    }
18}
19
20impl<'a> Sub for Expr<'a> {
21    type Output = Self;
22    fn sub(self, rhs: Self) -> Self {
23        self.assert_same_arena(rhs);
24        let id = self.arena.with_mut(|arena| sub_into(arena, self.id, rhs.id));
25        Self::new(id, self.arena)
26    }
27}
28
29impl<'a> Mul for Expr<'a> {
30    type Output = Self;
31    fn mul(self, rhs: Self) -> Self {
32        self.assert_same_arena(rhs);
33        let id = self.arena.with_mut(|arena| mul_into(arena, self.id, rhs.id));
34        Self::new(id, self.arena)
35    }
36}
37
38impl<'a> Div for Expr<'a> {
39    type Output = Self;
40    fn div(self, rhs: Self) -> Self {
41        self.assert_same_arena(rhs);
42        let id = self.arena.with_mut(|arena| div_into(arena, self.id, rhs.id));
43        Self::new(id, self.arena)
44    }
45}
46
47impl<'a> Neg for Expr<'a> {
48    type Output = Self;
49    fn neg(self) -> Self {
50        let id = self.arena.with_mut(|arena| neg_into(arena, self.id));
51        Self::new(id, self.arena)
52    }
53}
54
55// -----------------------------------------------------------------------------
56// Expr <op> f64 / f64 <op> Expr, and the same for i32 because `2 * x`
57// without type annotation is the most common ergonomic case.
58// -----------------------------------------------------------------------------
59
60macro_rules! impl_scalar_ops {
61    ($scalar:ty, $to_f64:expr) => {
62        impl<'a> Add<$scalar> for Expr<'a> {
63            type Output = Self;
64            fn add(self, rhs: $scalar) -> Self {
65                let id = self.arena.with_mut(|arena| {
66                    let rhs_id = arena.constant($to_f64(rhs));
67                    add_into(arena, self.id, rhs_id)
68                });
69                Self::new(id, self.arena)
70            }
71        }
72
73        impl<'a> Add<Expr<'a>> for $scalar {
74            type Output = Expr<'a>;
75            fn add(self, rhs: Expr<'a>) -> Expr<'a> {
76                rhs + self
77            }
78        }
79
80        impl<'a> Sub<$scalar> for Expr<'a> {
81            type Output = Self;
82            fn sub(self, rhs: $scalar) -> Self {
83                let id = self.arena.with_mut(|arena| {
84                    let rhs_id = arena.constant($to_f64(rhs));
85                    sub_into(arena, self.id, rhs_id)
86                });
87                Self::new(id, self.arena)
88            }
89        }
90
91        impl<'a> Sub<Expr<'a>> for $scalar {
92            type Output = Expr<'a>;
93            fn sub(self, rhs: Expr<'a>) -> Expr<'a> {
94                let id = rhs.arena.with_mut(|arena| {
95                    let lhs_id = arena.constant($to_f64(self));
96                    sub_into(arena, lhs_id, rhs.id)
97                });
98                Expr::new(id, rhs.arena)
99            }
100        }
101
102        impl<'a> Mul<$scalar> for Expr<'a> {
103            type Output = Self;
104            fn mul(self, rhs: $scalar) -> Self {
105                let id = self.arena.with_mut(|arena| {
106                    let rhs_id = arena.constant($to_f64(rhs));
107                    mul_into(arena, self.id, rhs_id)
108                });
109                Self::new(id, self.arena)
110            }
111        }
112
113        impl<'a> Mul<Expr<'a>> for $scalar {
114            type Output = Expr<'a>;
115            fn mul(self, rhs: Expr<'a>) -> Expr<'a> {
116                rhs * self
117            }
118        }
119
120        impl<'a> Div<$scalar> for Expr<'a> {
121            type Output = Self;
122            fn div(self, rhs: $scalar) -> Self {
123                let id = self.arena.with_mut(|arena| {
124                    let rhs_id = arena.constant($to_f64(rhs));
125                    div_into(arena, self.id, rhs_id)
126                });
127                Self::new(id, self.arena)
128            }
129        }
130
131        impl<'a> Div<Expr<'a>> for $scalar {
132            type Output = Expr<'a>;
133            fn div(self, rhs: Expr<'a>) -> Expr<'a> {
134                let id = rhs.arena.with_mut(|arena| {
135                    let lhs_id = arena.constant($to_f64(self));
136                    div_into(arena, lhs_id, rhs.id)
137                });
138                Expr::new(id, rhs.arena)
139            }
140        }
141    };
142}
143
144impl_scalar_ops!(f64, core::convert::identity);
145impl_scalar_ops!(i32, f64::from);
146
147// -----------------------------------------------------------------------------
148// std::iter::Sum: the first element of the iterator carries the arena handle,
149// so no external zero is required. Collected into a single flat n-ary `Add`.
150// -----------------------------------------------------------------------------
151
152fn sum_children(first: ExprId, rest: impl Iterator<Item = ExprId>) -> Children {
153    let capacity = rest.size_hint().0.saturating_add(1);
154    let mut children = Children::new();
155    if capacity > children.inline_size() {
156        // We use Vec's extend loop to avoid checking SmallVec's storage mode per term.
157        let mut ids = Vec::with_capacity(capacity);
158        ids.push(first);
159        ids.extend(rest);
160        Children::from_vec(ids)
161    } else {
162        children.push(first);
163        children.extend(rest);
164        children
165    }
166}
167
168impl<'a> std::iter::Sum for Expr<'a> {
169    fn sum<I: Iterator<Item = Self>>(mut iter: I) -> Self {
170        let first = iter.next().expect("Expr::sum on empty iterator");
171        let ids = sum_children(
172            first.id,
173            iter.map(|expr| {
174                first.assert_same_arena(expr);
175                expr.id
176            }),
177        );
178        let id = first.arena.with_mut(|arena| add_n(arena, ids));
179        Self::new(id, first.arena)
180    }
181}
182
183impl<'a> Expr<'a> {
184    /// Macro-helper summation that evaluates every term before checking arena
185    /// ownership, matching the former collect-then-sum behavior. Returns `None`
186    /// for an empty domain so the caller can preserve its own diagnostic.
187    #[doc(hidden)]
188    pub fn __sum_terms(mut iter: impl Iterator<Item = Self>) -> Option<Self> {
189        let first = iter.next()?;
190        let mut same_arena = true;
191        let ids = sum_children(
192            first.id,
193            iter.map(|expr| {
194                same_arena &= std::ptr::eq(first.arena, expr.arena);
195                expr.id
196            }),
197        );
198        assert!(same_arena, "expressions belong to different arenas");
199        let id = first.arena.with_mut(|arena| add_n(arena, ids));
200        Some(Self::new(id, first.arena))
201    }
202
203    /// Model-anchored summation. Validate all term owners before emitting the
204    /// sum, and construct zero in the supplied arena when there are no terms.
205    #[doc(hidden)]
206    pub fn __sum_terms_in(
207        arena: &'a crate::ExprArenaCell,
208        mut iter: impl Iterator<Item = Self>,
209    ) -> Self {
210        let Some(first) = iter.next() else {
211            return Self::constant(arena, 0.0);
212        };
213        let mut same_arena = std::ptr::eq(arena, first.arena);
214        let ids = sum_children(
215            first.id,
216            iter.map(|expr| {
217                same_arena &= std::ptr::eq(arena, expr.arena);
218                expr.id
219            }),
220        );
221        assert!(same_arena, "sum! terms belong to a different model");
222        let id = arena.with_mut(|arena| add_n(arena, ids));
223        Self::new(id, arena)
224    }
225
226    fn extrema_terms(mut iter: impl Iterator<Item = Self>, is_min: bool) -> Option<Self> {
227        let first = iter.next()?;
228        let mut same_arena = true;
229        let mut ids = Children::new();
230        let mut append = |expr: Self| {
231            same_arena &= std::ptr::eq(first.arena, expr.arena);
232            expr.arena.with_ref(|arena| match arena.get(expr.id) {
233                ExprNode::Min(children) if is_min => ids.extend_from_slice(children),
234                ExprNode::Max(children) if !is_min => ids.extend_from_slice(children),
235                _ => ids.push(expr.id),
236            });
237        };
238        append(first);
239        for expr in iter {
240            append(expr);
241        }
242        assert!(same_arena, "expressions belong to different arenas");
243        if ids.len() == 1 {
244            return Some(Self::new(ids[0], first.arena));
245        }
246        let id = first.arena.with_mut(|arena| {
247            arena.push(if is_min { ExprNode::Min(ids) } else { ExprNode::Max(ids) })
248        });
249        Some(Self::new(id, first.arena))
250    }
251
252    /// Macro support for constructing a flat n-ary minimum.
253    #[doc(hidden)]
254    pub fn __min_terms(iter: impl Iterator<Item = Self>) -> Option<Self> {
255        Self::extrema_terms(iter, true)
256    }
257
258    /// Macro support for constructing a flat n-ary maximum.
259    #[doc(hidden)]
260    pub fn __max_terms(iter: impl Iterator<Item = Self>) -> Option<Self> {
261        Self::extrema_terms(iter, false)
262    }
263}
264
265impl<'a, 'b> std::iter::Sum<&'b Expr<'a>> for Expr<'a> {
266    fn sum<I: Iterator<Item = &'b Expr<'a>>>(iter: I) -> Self {
267        iter.copied().sum()
268    }
269}
270
271/// Dot product of expressions with scalar coefficients: `sum_{i} c_i * e_i`.
272///
273/// Both arguments are slices. Pass owned containers by reference:
274/// `&vec`, `vec.as_slice()`, or `&array`.
275///
276/// # Panics
277/// Panics if `exprs` and `coeffs` have different lengths, or if `exprs`
278/// is empty (the result needs an arena handle).
279pub fn dot<'a>(exprs: &[Expr<'a>], coeffs: &[f64]) -> Expr<'a> {
280    assert_eq!(
281        exprs.len(),
282        coeffs.len(),
283        "dot: length mismatch (exprs.len() = {}, coeffs.len() = {})",
284        exprs.len(),
285        coeffs.len(),
286    );
287    exprs.iter().zip(coeffs).map(|(e, c)| *c * *e).sum()
288}