1use std::cell::RefCell;
2
3use thiserror::Error;
4
5use crate::arena::{ExprArena, ExprId, ExprNode, ParamId, VarId};
6
7#[derive(Debug, Error)]
8pub enum EvalError {
9 #[error("variable {0:?} has no value bound in the evaluation context")]
10 UnboundVar(VarId),
11 #[error("parameter {0:?} has no value bound in the evaluation context")]
12 UnboundParam(ParamId),
13}
14
15pub trait EvalContext {
17 fn var(&self, v: VarId) -> Option<f64>;
18 fn param(&self, p: ParamId) -> Option<f64>;
19}
20
21impl EvalContext for &[f64] {
22 fn var(&self, v: VarId) -> Option<f64> {
23 self.get(v.index()).copied()
24 }
25 fn param(&self, _p: ParamId) -> Option<f64> {
26 None
27 }
28}
29
30#[inline]
40pub fn evaluate<C: EvalContext>(arena: &ExprArena, id: ExprId, ctx: &C) -> Result<f64, EvalError> {
41 match arena.get(id) {
42 ExprNode::Const(c) => Ok(*c),
43 ExprNode::Var(v) => ctx.var(*v).ok_or(EvalError::UnboundVar(*v)),
44 ExprNode::Param(p) => {
45 ctx.param(*p).or_else(|| arena.try_param_value(*p)).ok_or(EvalError::UnboundParam(*p))
46 }
47 ExprNode::Linear { coeffs, constant } => eval_linear(coeffs, *constant, ctx),
48 _ => eval_compound(arena, id, ctx),
49 }
50}
51
52#[inline]
53fn eval_linear<C: EvalContext>(
54 coeffs: &[(VarId, f64)],
55 mut value: f64,
56 ctx: &C,
57) -> Result<f64, EvalError> {
58 for &(v, c) in coeffs {
59 value += c * ctx.var(v).ok_or(EvalError::UnboundVar(v))?;
60 }
61 Ok(value)
62}
63
64#[inline]
65fn eval_leaf<C: EvalContext>(
66 arena: &ExprArena,
67 node: &ExprNode,
68 ctx: &C,
69) -> Option<Result<f64, EvalError>> {
70 Some(match node {
71 ExprNode::Const(c) => Ok(*c),
72 ExprNode::Var(v) => ctx.var(*v).ok_or(EvalError::UnboundVar(*v)),
73 ExprNode::Param(p) => {
74 ctx.param(*p).or_else(|| arena.try_param_value(*p)).ok_or(EvalError::UnboundParam(*p))
75 }
76 ExprNode::Linear { coeffs, constant } => eval_linear(coeffs, *constant, ctx),
77 _ => return None,
78 })
79}
80
81fn eval_compound<C: EvalContext>(
82 arena: &ExprArena,
83 mut id: ExprId,
84 ctx: &C,
85) -> Result<f64, EvalError> {
86 let mut negate = false;
88 while let ExprNode::Unary(crate::UnaryOp::Neg, inner) = arena.get(id) {
89 negate = !negate;
90 id = *inner;
91 }
92 let value = eval_shallow(arena, id, ctx).unwrap_or_else(|| eval_general(arena, id, ctx))?;
93 Ok(if negate { -value } else { value })
94}
95
96fn eval_shallow<C: EvalContext>(
97 arena: &ExprArena,
98 id: ExprId,
99 ctx: &C,
100) -> Option<Result<f64, EvalError>> {
101 let node = arena.get(id);
102 if let Some(value) = eval_leaf(arena, node, ctx) {
103 return Some(value);
104 }
105 if let ExprNode::Unary(op, inner) = node {
106 return eval_leaf(arena, arena.get(*inner), ctx).map(|v| v.map(|v| op.apply(v)));
107 }
108 let (children, mut value) = match node {
109 ExprNode::Add(c) => (c, 0.0),
110 ExprNode::Mul(c) => (c, 1.0),
111 ExprNode::Min(c) => (c, f64::INFINITY),
112 ExprNode::Max(c) => (c, f64::NEG_INFINITY),
113 _ => return None,
114 };
115 let mut previous = None;
119 if !children.iter().all(|&child| {
120 let distinct = previous.is_none_or(|p| child.index() > p);
121 previous = Some(child.index());
122 distinct
123 && matches!(
124 arena.get(child),
125 ExprNode::Const(_)
126 | ExprNode::Var(_)
127 | ExprNode::Param(_)
128 | ExprNode::Linear { .. }
129 )
130 }) {
131 return None;
132 }
133 for &child in children {
134 let x = match eval_leaf(arena, arena.get(child), ctx)? {
135 Ok(x) => x,
136 Err(error) => return Some(Err(error)),
137 };
138 value = match node {
139 ExprNode::Add(_) => value + x,
140 ExprNode::Mul(_) => value * x,
141 ExprNode::Min(_) => value.min(x),
142 ExprNode::Max(_) => value.max(x),
143 _ => unreachable!(),
144 };
145 }
146 Some(Ok(value))
147}
148
149fn eval_general<C: EvalContext>(arena: &ExprArena, id: ExprId, ctx: &C) -> Result<f64, EvalError> {
150 if let Some(result) = eval_array(arena, id, ctx) {
151 return result;
152 }
153 eval_hashed(arena, id, ctx)
154}
155
156#[derive(Default)]
157struct EvalScratch {
158 values: Vec<f64>,
159 seen: Vec<u32>,
160 stack: Vec<(ExprId, usize)>,
161 generation: u32,
162}
163
164thread_local! {
165 static SCRATCH: RefCell<EvalScratch> = const { RefCell::new(EvalScratch {
166 values: Vec::new(), seen: Vec::new(), stack: Vec::new(), generation: 0,
167 }) };
168}
169
170const ARRAY_PATH_MAX_NODES: usize = 1 << 16;
171
172fn eval_array<C: EvalContext>(
177 arena: &ExprArena,
178 id: ExprId,
179 ctx: &C,
180) -> Option<Result<f64, EvalError>> {
181 let n = arena.len();
182 if n > ARRAY_PATH_MAX_NODES || id.index() >= n {
183 return None;
184 }
185 SCRATCH.with(|scratch| {
186 let mut scratch = scratch.try_borrow_mut().ok()?;
188 let EvalScratch { values, seen, stack, generation } = &mut *scratch;
189 values.resize(values.len().max(n), 0.0);
190 seen.resize(seen.len().max(n), 0);
191 stack.clear();
192 *generation = generation.wrapping_add(1);
193 if *generation == 0 {
194 seen.fill(0);
195 *generation = 1;
196 }
197 stack.push((id, 0));
198 seen[id.index()] = *generation;
199 while let Some((cur, next)) = stack.last_mut() {
200 let cur = *cur;
201 if let Some(child) = eval_child(arena, cur, *next) {
202 *next += 1;
203 if child.index() >= n {
204 return None;
205 }
206 if seen[child.index()] != *generation {
207 seen[child.index()] = *generation;
208 stack.push((child, 0));
209 }
210 } else {
211 match eval_node_array(arena, ctx, values, cur) {
212 Ok(value) => values[cur.index()] = value,
213 Err(error) => return Some(Err(error)),
214 }
215 stack.pop();
216 }
217 }
218 Some(Ok(values[id.index()]))
219 })
220}
221
222fn eval_node_array<C: EvalContext>(
224 arena: &ExprArena,
225 ctx: &C,
226 values: &[f64],
227 cur: ExprId,
228) -> Result<f64, EvalError> {
229 Ok(match arena.get(cur) {
230 ExprNode::Const(c) => *c,
231 ExprNode::Var(v) => ctx.var(*v).ok_or(EvalError::UnboundVar(*v))?,
232 ExprNode::Param(p) => ctx
233 .param(*p)
234 .or_else(|| arena.try_param_value(*p))
235 .ok_or(EvalError::UnboundParam(*p))?,
236 ExprNode::Add(children) => {
237 let mut acc = 0.0;
238 for c in children {
239 acc += values[c.index()];
240 }
241 acc
242 }
243 ExprNode::Mul(children) => {
244 let mut acc = 1.0;
245 for c in children {
246 acc *= values[c.index()];
247 }
248 acc
249 }
250 ExprNode::Unary(op, inner) => op.apply(values[inner.index()]),
251 ExprNode::Pow(base, exp) => values[base.index()].powf(values[exp.index()]),
252 ExprNode::Div(num, den) => values[num.index()] / values[den.index()],
253 ExprNode::Atan2(y, x) => values[y.index()].atan2(values[x.index()]),
254 ExprNode::Min(children) => {
255 let mut acc = f64::INFINITY;
256 for c in children {
257 acc = acc.min(values[c.index()]);
258 }
259 acc
260 }
261 ExprNode::Max(children) => {
262 let mut acc = f64::NEG_INFINITY;
263 for c in children {
264 acc = acc.max(values[c.index()]);
265 }
266 acc
267 }
268 ExprNode::Linear { coeffs, constant } => {
269 let mut acc = *constant;
270 for (v, c) in coeffs {
271 acc += c * ctx.var(*v).ok_or(EvalError::UnboundVar(*v))?;
272 }
273 acc
274 }
275 })
276}
277
278fn eval_hashed<C: EvalContext>(arena: &ExprArena, id: ExprId, ctx: &C) -> Result<f64, EvalError> {
281 let mut seen = rustc_hash::FxHashSet::default();
282 let mut order = Vec::new();
283 seen.insert(id);
284 let mut stack: Vec<(ExprId, usize)> = vec![(id, 0)];
285 while let Some((cur, next)) = stack.last_mut() {
286 let cur = *cur;
287 let Some(child) = eval_child(arena, cur, *next) else {
288 order.push(cur);
289 stack.pop();
290 continue;
291 };
292 *next += 1;
293 if seen.insert(child) {
294 stack.push((child, 0));
295 }
296 }
297
298 let mut values =
299 rustc_hash::FxHashMap::with_capacity_and_hasher(order.len(), rustc_hash::FxBuildHasher);
300 for cur in order {
301 let value = match arena.get(cur) {
302 ExprNode::Const(c) => *c,
303 ExprNode::Var(v) => ctx.var(*v).ok_or(EvalError::UnboundVar(*v))?,
304 ExprNode::Param(p) => ctx
305 .param(*p)
306 .or_else(|| arena.try_param_value(*p))
307 .ok_or(EvalError::UnboundParam(*p))?,
308 ExprNode::Add(children) => {
309 let mut acc = 0.0;
310 for c in children {
311 acc += values[c];
312 }
313 acc
314 }
315 ExprNode::Mul(children) => {
316 let mut acc = 1.0;
317 for c in children {
318 acc *= values[c];
319 }
320 acc
321 }
322 ExprNode::Unary(op, inner) => op.apply(values[inner]),
323 ExprNode::Pow(base, exp) => values[base].powf(values[exp]),
324 ExprNode::Div(num, den) => values[num] / values[den],
325 ExprNode::Atan2(y, x) => values[y].atan2(values[x]),
326 ExprNode::Min(children) => {
327 let mut acc = f64::INFINITY;
328 for c in children {
329 acc = acc.min(values[c]);
330 }
331 acc
332 }
333 ExprNode::Max(children) => {
334 let mut acc = f64::NEG_INFINITY;
335 for c in children {
336 acc = acc.max(values[c]);
337 }
338 acc
339 }
340 ExprNode::Linear { coeffs, constant } => {
341 let mut acc = *constant;
342 for (v, c) in coeffs {
343 acc += c * ctx.var(*v).ok_or(EvalError::UnboundVar(*v))?;
344 }
345 acc
346 }
347 };
348 values.insert(cur, value);
349 }
350 Ok(values[&id])
351}
352
353fn eval_child(arena: &ExprArena, id: ExprId, next: usize) -> Option<ExprId> {
356 match arena.get(id) {
357 ExprNode::Add(children)
358 | ExprNode::Mul(children)
359 | ExprNode::Min(children)
360 | ExprNode::Max(children) => children.get(next).copied(),
361 ExprNode::Unary(_, inner) => (next == 0).then_some(*inner),
362 ExprNode::Pow(base, exp) | ExprNode::Div(base, exp) | ExprNode::Atan2(base, exp) => {
363 match next {
364 0 => Some(*base),
365 1 => Some(*exp),
366 _ => None,
367 }
368 }
369 ExprNode::Const(_) | ExprNode::Var(_) | ExprNode::Param(_) | ExprNode::Linear { .. } => {
370 None
371 }
372 }
373}