| 134 | const valueFn = VALUE_FNS[head]; |
| 135 | if (valueFn) return valueFn(args, ctx); |
| 136 | if (PRED_FNS[head] || head === 'And' || head === 'Or' || head === 'Not') |
| 137 | return ce.symbol(evalCondition(json, ctx) ? 'True' : 'False'); |
| 138 | |
| 139 | // ordinary mathematical head (already CE-named by the translator) |
| 140 | return ce.function( |
| 141 | head, |
| 142 | args.map((a) => build(a, ctx)) |
| 143 | ); |
| 144 | } |
| 145 | |
| 146 | // --------------------------------------------------------------------------- |
| 147 | // numeric helpers |
| 148 | // --------------------------------------------------------------------------- |
| 149 | |
| 150 | /** real machine value of an expression, or null if not a real number */ |
| 151 | function realNum(e: Expression): number | null { |
| 152 | const n = e.N(); |
| 153 | if (!isNumber(n)) return null; |
| 154 | const re = n.re; |
| 155 | const im = n.im; |
| 156 | if (typeof re !== 'number' || typeof im !== 'number') return null; |
| 157 | if (!Number.isFinite(re) || im !== 0) return null; |
| 158 | return re; |
| 159 | } |
| 160 | |
| 161 | // --------------------------------------------------------------------------- |
| 162 | // rational collapse |
| 163 | // |
| 164 | // Several Rubi normalization rules (e.g. 1.1.1.2 #46) rewrite coefficients |
| 165 | // into nested rationals like c′ = b·c/(b·c−a·d) and rely on Mathematica's |
| 166 | // automatic Together to collapse follow-up guards (b/(b·c′−a·d′) = 1) so |
| 167 | // the terminating rule fires. CE's simplify has no multivariate rational |
| 168 | // cancellation, so without this the normalization rule refires on its own |
| 169 | // output until the cycle guard kills it. |
| 170 | // --------------------------------------------------------------------------- |
| 171 | |
| 172 | function asNumDen(e: Expression): { num: Expression; den: Expression } { |
| 173 | const ce = e.engine; |
| 174 | const ops = e.ops; |
| 175 | switch (e.operator) { |
| 176 | case 'Divide': { |
| 177 | const u = asNumDen(ops![0]); |
| 178 | const v = asNumDen(ops![1]); |
| 179 | return { num: u.num.mul(v.den), den: u.den.mul(v.num) }; |
| 180 | } |
| 181 | case 'Negate': { |
| 182 | const u = asNumDen(ops![0]); |
| 183 | return { num: u.num.neg(), den: u.den }; |
| 184 | } |
| 185 | case 'Multiply': { |
| 186 | let num = ce.One; |
| 187 | let den = ce.One; |
| 188 | for (const op of ops!) { |
| 189 | const r = asNumDen(op); |
| 190 | num = num.mul(r.num); |
| 191 | den = den.mul(r.den); |
| 192 | } |
| 193 | return { num, den }; |