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
8pub fn simplify(pool: &ExprPool, builtins: &BuiltinSymbols, id: ExprId) -> ExprId {
12 simplify_at(pool, builtins, id, 3)
13}
14
15pub fn simplify_at(pool: &ExprPool, builtins: &BuiltinSymbols, id: ExprId, level: u8) -> ExprId {
20 let node = match pool.get(id) {
21 Some(n) => n,
22 None => return id,
23 };
24 match node {
25 ExprData::Add(items) => {
26 if items.is_empty() {
27 return id;
28 }
29 let mut acc = simplify_at(pool, builtins, items[0], level);
30 for &it in &items[1..] {
31 let s = simplify_at(pool, builtins, it, level);
32 acc = pool.add2(acc, s);
33 }
34 acc
35 }
36 ExprData::Mul(items) => {
37 if items.is_empty() {
38 return id;
39 }
40 let mut acc = simplify_at(pool, builtins, items[0], level);
41 for &it in &items[1..] {
42 let s = simplify_at(pool, builtins, it, level);
43 acc = pool.mul2(acc, s);
44 }
45 acc
46 }
47 ExprData::Pow { base, exp } => {
48 let b = simplify_at(pool, builtins, base, level);
49 let e = simplify_at(pool, builtins, exp, level);
50 if level >= 2 {
51 if let Some(ExprData::Apply { f, args }) = pool.get(b)
52 && f == pool.symbol(builtins.sqrt)
53 && args.len() == 1
54 && e == pool.integer(2)
55 {
56 return simplify_at(pool, builtins, args[0], level);
57 }
58 if b == pool.symbol(builtins.e)
59 && let Some(r) = euler(pool, builtins, e)
60 {
61 return r;
62 }
63 }
64 pool.pow2(b, e)
65 }
66 ExprData::Apply { f, args } => {
67 let mut new_args = Vec::with_capacity(args.len());
68 for &a in args.iter() {
69 new_args.push(simplify_at(pool, builtins, a, level));
70 }
71 if level >= 2
72 && let Some(r) = apply_rule(pool, builtins, f, &new_args)
73 {
74 return r;
75 }
76 pool.apply(f, &new_args)
77 }
78 _ => id,
79 }
80}
81
82fn apply_rule(
83 pool: &ExprPool,
84 builtins: &BuiltinSymbols,
85 f: ExprId,
86 args: &[ExprId],
87) -> Option<ExprId> {
88 if args.len() != 1 {
89 return None;
90 }
91 let arg = args[0];
92 if f == pool.symbol(builtins.sqrt) {
93 if let Some(n) = pool.const_number(arg)
94 && let Some(s) = n.sqrt()
95 {
96 return Some(pool.number(&s));
97 }
98 return None;
99 }
100 let sin = pool.symbol(builtins.sin);
101 let cos = pool.symbol(builtins.cos);
102 let tan = pool.symbol(builtins.tan);
103 if f == sin || f == cos || f == tan {
104 if let Some((c, s)) = trig_of_angle(pool, builtins, arg) {
105 if f == sin {
106 return Some(pool.number(&s));
107 }
108 if f == cos {
109 return Some(pool.number(&c));
110 }
111 if !c.is_zero() {
112 return Some(pool.number(&(s / c)));
113 }
114 }
115 return None;
116 }
117 if f == pool.symbol(builtins.exp) {
118 if arg == pool.integer(0) {
119 return Some(pool.integer(1));
120 }
121 return None;
122 }
123 if f == pool.symbol(builtins.log) || f == pool.symbol(builtins.ln) {
124 if arg == pool.integer(1) {
125 return Some(pool.integer(0));
126 }
127 if arg == pool.symbol(builtins.e) {
128 return Some(pool.integer(1));
129 }
130 return None;
131 }
132 if f == pool.symbol(builtins.abs) {
133 if let Some(n) = pool.const_number(arg) {
134 return Some(pool.number(&n.abs()));
135 }
136 return None;
137 }
138 None
139}
140
141fn trig_of_angle(
143 pool: &ExprPool,
144 builtins: &BuiltinSymbols,
145 expr: ExprId,
146) -> Option<(Number, Number)> {
147 let c = rational_pi_coefficient(pool, builtins, expr)?;
148 exact_trig(&c)
149}
150
151fn rational_pi_coefficient(
152 pool: &ExprPool,
153 builtins: &BuiltinSymbols,
154 expr: ExprId,
155) -> Option<BigRational> {
156 let node = pool.get(expr)?;
157 match node {
158 ExprData::Symbol(s) if s == builtins.pi => {
159 Some(BigRational::new(BigInt::from(1), BigInt::from(1)))
160 }
161 ExprData::Integer(_) | ExprData::Rational(_) | ExprData::Real(_) => {
162 let n = pool.const_number(expr)?;
163 if n.is_zero() {
164 Some(BigRational::new(BigInt::from(0), BigInt::from(1)))
165 } else {
166 None
167 }
168 }
169 ExprData::Mul(items) => {
170 let mut coeff: Option<BigRational> = None;
171 let mut found_pi = false;
172 for &it in items.iter() {
173 match pool.get(it)? {
174 ExprData::Symbol(s) if s == builtins.pi => found_pi = true,
175 ExprData::Integer(_) | ExprData::Rational(_) => {
176 let c = match pool.const_number(it)? {
177 Number::Integer(i) => BigRational::from_integer(i),
178 Number::Rational(r) => r,
179 _ => return None,
180 };
181 coeff = Some(match coeff {
182 Some(acc) => acc * c,
183 None => c,
184 });
185 }
186 _ => return None,
187 }
188 }
189 if !found_pi {
190 return None;
191 }
192 Some(coeff.unwrap_or_else(|| BigRational::new(BigInt::from(1), BigInt::from(1))))
193 }
194 _ => None,
195 }
196}
197
198fn exact_trig(c: &BigRational) -> Option<(Number, Number)> {
199 let two = BigRational::new(BigInt::from(2), BigInt::from(1));
200 let mut c = c % two.clone();
201 if c < BigRational::new(BigInt::from(0), BigInt::from(1)) {
202 c += two;
203 }
204 let zero = BigRational::new(BigInt::from(0), BigInt::from(1));
205 let half = BigRational::new(BigInt::from(1), BigInt::from(2));
206 let one = BigRational::new(BigInt::from(1), BigInt::from(1));
207 let three_halves = BigRational::new(BigInt::from(3), BigInt::from(2));
208 if c == zero {
209 Some((Number::from(1), Number::from(0)))
210 } else if c == half {
211 Some((Number::from(0), Number::from(1)))
212 } else if c == one {
213 Some((Number::from(-1), Number::from(0)))
214 } else if c == three_halves {
215 Some((Number::from(0), Number::from(-1)))
216 } else {
217 None
218 }
219}
220
221fn euler(pool: &ExprPool, builtins: &BuiltinSymbols, z: ExprId) -> Option<ExprId> {
223 let i = pool.symbol(builtins.i);
224 let theta = match pool.get(z)? {
225 ExprData::Symbol(s) if s == builtins.i => return None,
226 ExprData::Mul(items) => {
227 let mut theta_items = Vec::new();
228 let mut has_i = false;
229 for &it in items.iter() {
230 if it == i {
231 has_i = true;
232 } else {
233 theta_items.push(it);
234 }
235 }
236 if !has_i || theta_items.is_empty() {
237 return None;
238 }
239 let mut acc = theta_items[0];
240 for &it in &theta_items[1..] {
241 acc = pool.mul2(acc, it);
242 }
243 acc
244 }
245 _ => return None,
246 };
247 let (c, s) = trig_of_angle(pool, builtins, theta)?;
248 if s == Number::from(0) {
249 Some(pool.number(&c))
250 } else if c == Number::from(0) && s == Number::from(1) {
251 Some(i)
252 } else if c == Number::from(0) && s == Number::from(-1) {
253 Some(pool.mul2(pool.integer(-1), i))
254 } else {
255 None
256 }
257}