Skip to main content

prima_core/
simplify.rs

1use num_bigint::BigInt;
2use num_rational::BigRational;
3
4use crate::builtins::BuiltinSymbols;
5use crate::expr_pool::{ExprData, ExprId, ExprPool};
6use crate::number::Number;
7
8/// Full simplification (spec §8.3 levels 2/3): fold recursively, then apply rules on demand —
9/// `Pow(sqrt(x), 2) → x`, Euler's `e^{iθ} → cos + i·sin`, constant folding for `sin/cos/exp/log/ln/abs/sqrt`,
10/// `Pow(x, 1/2) → \sqrt{x}`. Simplification never changes the mathematical value.
11pub fn simplify(pool: &ExprPool, builtins: &BuiltinSymbols, id: ExprId) -> ExprId {
12    let node = match pool.get(id) {
13        Some(n) => n,
14        None => return id,
15    };
16    match node {
17        ExprData::Add(items) => {
18            if items.is_empty() {
19                return id;
20            }
21            let mut acc = simplify(pool, builtins, items[0]);
22            for &it in &items[1..] {
23                let s = simplify(pool, builtins, it);
24                acc = pool.add2(acc, s);
25            }
26            acc
27        }
28        ExprData::Mul(items) => {
29            if items.is_empty() {
30                return id;
31            }
32            let mut acc = simplify(pool, builtins, items[0]);
33            for &it in &items[1..] {
34                let s = simplify(pool, builtins, it);
35                acc = pool.mul2(acc, s);
36            }
37            acc
38        }
39        ExprData::Pow { base, exp } => {
40            let b = simplify(pool, builtins, base);
41            let e = simplify(pool, builtins, exp);
42            if let Some(ExprData::Apply { f, args }) = pool.get(b)
43                && f == pool.symbol(builtins.sqrt)
44                && args.len() == 1
45                && e == pool.integer(2)
46            {
47                return simplify(pool, builtins, args[0]);
48            }
49            if b == pool.symbol(builtins.e) && let Some(r) = euler(pool, builtins, e) {
50                return r;
51            }
52            pool.pow2(b, e)
53        }
54        ExprData::Apply { f, args } => {
55            let mut new_args = Vec::with_capacity(args.len());
56            for &a in args.iter() {
57                new_args.push(simplify(pool, builtins, a));
58            }
59            if let Some(r) = apply_rule(pool, builtins, f, &new_args) {
60                r
61            } else {
62                pool.apply(f, &new_args)
63            }
64        }
65        _ => id,
66    }
67}
68
69fn apply_rule(pool: &ExprPool, builtins: &BuiltinSymbols, f: ExprId, args: &[ExprId]) -> Option<ExprId> {
70    if args.len() != 1 {
71        return None;
72    }
73    let arg = args[0];
74    if f == pool.symbol(builtins.sqrt) {
75        if let Some(n) = pool.const_number(arg) && let Some(s) = n.sqrt() {
76            return Some(pool.number(&s));
77        }
78        return None;
79    }
80    let sin = pool.symbol(builtins.sin);
81    let cos = pool.symbol(builtins.cos);
82    let tan = pool.symbol(builtins.tan);
83    if f == sin || f == cos || f == tan {
84        if let Some((c, s)) = trig_of_angle(pool, builtins, arg) {
85            if f == sin {
86                return Some(pool.number(&s));
87            }
88            if f == cos {
89                return Some(pool.number(&c));
90            }
91            if !c.is_zero() {
92                return Some(pool.number(&(s / c)));
93            }
94        }
95        return None;
96    }
97    if f == pool.symbol(builtins.exp) {
98        if arg == pool.integer(0) {
99            return Some(pool.integer(1));
100        }
101        return None;
102    }
103    if f == pool.symbol(builtins.log) || f == pool.symbol(builtins.ln) {
104        if arg == pool.integer(1) {
105            return Some(pool.integer(0));
106        }
107        if arg == pool.symbol(builtins.e) {
108            return Some(pool.integer(1));
109        }
110        return None;
111    }
112    if f == pool.symbol(builtins.abs) {
113        if let Some(n) = pool.const_number(arg) {
114            return Some(pool.number(&n.abs()));
115        }
116        return None;
117    }
118    None
119}
120
121/// An angle of the form `k·\pi`: extract the rational coefficient k, then consult the exact trig table (spec §7 built-in symbol simplification).
122fn trig_of_angle(pool: &ExprPool, builtins: &BuiltinSymbols, expr: ExprId) -> Option<(Number, Number)> {
123    let c = rational_pi_coefficient(pool, builtins, expr)?;
124    exact_trig(&c)
125}
126
127fn rational_pi_coefficient(pool: &ExprPool, builtins: &BuiltinSymbols, expr: ExprId) -> Option<BigRational> {
128    let node = pool.get(expr)?;
129    match node {
130        ExprData::Symbol(s) if s == builtins.pi => Some(BigRational::new(BigInt::from(1), BigInt::from(1))),
131        ExprData::Integer(_) | ExprData::Rational(_) | ExprData::Real(_) => {
132            let n = pool.const_number(expr)?;
133            if n.is_zero() {
134                Some(BigRational::new(BigInt::from(0), BigInt::from(1)))
135            } else {
136                None
137            }
138        }
139        ExprData::Mul(items) => {
140            let mut coeff: Option<BigRational> = None;
141            let mut found_pi = false;
142            for &it in items.iter() {
143                match pool.get(it)? {
144                    ExprData::Symbol(s) if s == builtins.pi => found_pi = true,
145                    ExprData::Integer(_) | ExprData::Rational(_) => {
146                        let c = match pool.const_number(it)? {
147                            Number::Integer(i) => BigRational::from_integer(i),
148                            Number::Rational(r) => r,
149                            _ => return None,
150                        };
151                        coeff = Some(match coeff {
152                            Some(acc) => acc * c,
153                            None => c,
154                        });
155                    }
156                    _ => return None,
157                }
158            }
159            if !found_pi {
160                return None;
161            }
162            Some(coeff.unwrap_or_else(|| BigRational::new(BigInt::from(1), BigInt::from(1))))
163        }
164        _ => None,
165    }
166}
167
168fn exact_trig(c: &BigRational) -> Option<(Number, Number)> {
169    let two = BigRational::new(BigInt::from(2), BigInt::from(1));
170    let mut c = c % two.clone();
171    if c < BigRational::new(BigInt::from(0), BigInt::from(1)) {
172        c += two;
173    }
174    let zero = BigRational::new(BigInt::from(0), BigInt::from(1));
175    let half = BigRational::new(BigInt::from(1), BigInt::from(2));
176    let one = BigRational::new(BigInt::from(1), BigInt::from(1));
177    let three_halves = BigRational::new(BigInt::from(3), BigInt::from(2));
178    if c == zero {
179        Some((Number::from(1), Number::from(0)))
180    } else if c == half {
181        Some((Number::from(0), Number::from(1)))
182    } else if c == one {
183        Some((Number::from(-1), Number::from(0)))
184    } else if c == three_halves {
185        Some((Number::from(0), Number::from(-1)))
186    } else {
187        None
188    }
189}
190
191/// Euler's formula (spec §7.4): fold `e^{iθ}` into `cosθ + i·sinθ` using the exact trig values of θ.
192fn euler(pool: &ExprPool, builtins: &BuiltinSymbols, z: ExprId) -> Option<ExprId> {
193    let i = pool.symbol(builtins.i);
194    let theta = match pool.get(z)? {
195        ExprData::Symbol(s) if s == builtins.i => return None,
196        ExprData::Mul(items) => {
197            let mut theta_items = Vec::new();
198            let mut has_i = false;
199            for &it in items.iter() {
200                if it == i {
201                    has_i = true;
202                } else {
203                    theta_items.push(it);
204                }
205            }
206            if !has_i || theta_items.is_empty() {
207                return None;
208            }
209            let mut acc = theta_items[0];
210            for &it in &theta_items[1..] {
211                acc = pool.mul2(acc, it);
212            }
213            acc
214        }
215        _ => return None,
216    };
217    let (c, s) = trig_of_angle(pool, builtins, theta)?;
218    if s == Number::from(0) {
219        Some(pool.number(&c))
220    } else if c == Number::from(0) && s == Number::from(1) {
221        Some(i)
222    } else if c == Number::from(0) && s == Number::from(-1) {
223        Some(pool.mul2(pool.integer(-1), i))
224    } else {
225        None
226    }
227}