Skip to main content

prima_core/
collapse.rs

1//! Foundation for explicit symbolic → numeric collapse (spec §9).
2//!
3//! Numeric evaluation only lowers determinate nodes to `Number`: built-in constants (`\e`/`\pi`),
4//! exact nodes, unary numeric operators, and the four arithmetic operations plus powers. It returns `None`
5//! for unresolved symbols or unsupported operators; the caller (the collapse function family, spec §9.2)
6//! decides whether to raise an error or keep the symbolic form.
7
8use crate::builtins::BuiltinSymbols;
9use crate::expr_pool::{ExprData, ExprId, ExprPool};
10use crate::number::{Number, Real};
11use crate::value::Value;
12
13/// Collapse a `Value` to a number (spec §9): `Number` is returned as-is, `Expr` is evaluated numerically,
14/// everything else (including `Undefined`/`Indeterminate`/arrays) yields `None`.
15pub fn collapse_value(pool: &ExprPool, builtins: &BuiltinSymbols, v: &Value) -> Option<Number> {
16    match v {
17        Value::Number(n) => Some(n.clone()),
18        Value::Expr(id) => numeric_value(pool, builtins, *id),
19        _ => None,
20    }
21}
22
23/// ExprDAG → number (spec §9).
24pub fn numeric_value(pool: &ExprPool, builtins: &BuiltinSymbols, id: ExprId) -> Option<Number> {
25    match pool.get(id)? {
26        ExprData::Symbol(s) => symbol_value(builtins, s),
27        ExprData::Integer(_) | ExprData::Rational(_) | ExprData::Real(_) => pool.const_number(id),
28        ExprData::Add(items) => fold(
29            items.iter().copied(),
30            Number::from(0),
31            |a, b| a + b,
32            pool,
33            builtins,
34        ),
35        ExprData::Mul(items) => fold(
36            items.iter().copied(),
37            Number::from(1),
38            |a, b| a * b,
39            pool,
40            builtins,
41        ),
42        ExprData::Pow { base, exp } => {
43            let b = numeric_value(pool, builtins, base)?;
44            let e = numeric_value(pool, builtins, exp)?;
45            match b.pow(&e) {
46                Some(r) => Some(r),
47                None => Some(Number::Real(Real::F64(
48                    b.to_f64_lossy().powf(e.to_f64_lossy()),
49                ))),
50            }
51        }
52        ExprData::Apply { f, args } => apply_value(pool, builtins, f, &args),
53        ExprData::Indeterminate(_) => None,
54    }
55}
56
57fn fold(
58    items: impl Iterator<Item = ExprId>,
59    init: Number,
60    f: impl Fn(Number, Number) -> Number,
61    pool: &ExprPool,
62    builtins: &BuiltinSymbols,
63) -> Option<Number> {
64    let mut acc = init;
65    for it in items {
66        acc = f(acc, numeric_value(pool, builtins, it)?);
67    }
68    Some(acc)
69}
70
71fn symbol_value(builtins: &BuiltinSymbols, s: crate::symbol::SymbolId) -> Option<Number> {
72    if s == builtins.e {
73        Some(Number::Real(Real::F64(std::f64::consts::E)))
74    } else if s == builtins.pi {
75        Some(Number::Real(Real::F64(std::f64::consts::PI)))
76    } else {
77        None
78    }
79}
80
81fn apply_value(
82    pool: &ExprPool,
83    builtins: &BuiltinSymbols,
84    f: ExprId,
85    args: &[ExprId],
86) -> Option<Number> {
87    if args.len() != 1 {
88        return None;
89    }
90    let arg = numeric_value(pool, builtins, args[0])?;
91    if f == pool.symbol(builtins.sqrt) {
92        return match arg.sqrt() {
93            Some(r) => Some(r),
94            None => Some(Number::Real(Real::F64(arg.to_f64_lossy().sqrt()))),
95        };
96    }
97    if f == pool.symbol(builtins.abs) {
98        return Some(arg.abs());
99    }
100    let x = arg.to_f64_lossy();
101    let v = if f == pool.symbol(builtins.exp) {
102        Some(x.exp())
103    } else if f == pool.symbol(builtins.log) || f == pool.symbol(builtins.ln) {
104        Some(x.ln())
105    } else if f == pool.symbol(builtins.sin) {
106        Some(x.sin())
107    } else if f == pool.symbol(builtins.cos) {
108        Some(x.cos())
109    } else if f == pool.symbol(builtins.tan) {
110        Some(x.tan())
111    } else {
112        None
113    }?;
114    Some(Number::Real(Real::F64(v)))
115}