* Check that `original` and `transformed` are numerically equal at a battery of * sample points. `vars` are the free variables to substitute; each is assigned * a value from `points`.
(
original: BoxedExpression,
transformed: BoxedExpression,
vars: string[],
{ complex = false, tol = 1e-9 }: { complex?: boolean; tol?: number } = {}
)
| 11 | * a value from `points`. |
| 12 | */ |
| 13 | function assertNumericallyEqual( |
| 14 | original: BoxedExpression, |
| 15 | transformed: BoxedExpression, |
| 16 | vars: string[], |
| 17 | { complex = false, tol = 1e-9 }: { complex?: boolean; tol?: number } = {} |
| 18 | ) { |
| 19 | // A handful of deterministic "random-ish" sample points, chosen to avoid |
| 20 | // poles of tan/sec/csc/cot. |
| 21 | const reals = [0.3, 0.7, 1.1, -0.5, 2.2, -1.7]; |
| 22 | const points: { re: number; im: number }[] = reals.map((re) => ({ |
| 23 | re, |
| 24 | im: 0, |
| 25 | })); |
| 26 | if (complex) |
| 27 | points.push( |
| 28 | { re: 0.4, im: 0.6 }, |
| 29 | { re: -0.8, im: 0.3 }, |
| 30 | { re: 1.2, im: -0.7 } |
| 31 | ); |
| 32 | |
| 33 | for (let i = 0; i < points.length; i++) { |
| 34 | // Assign each variable a distinct point (rotate through the list). |
| 35 | const sub: Record<string, BoxedExpression> = {}; |
| 36 | for (let v = 0; v < vars.length; v++) { |
| 37 | const p = points[(i + v) % points.length]; |
| 38 | sub[vars[v]] = |
| 39 | p.im === 0 ? ce.number(p.re) : ce.number(ce.complex(p.re, p.im)); |
| 40 | } |
| 41 | |
| 42 | const a = original.subs(sub).N(); |
| 43 | const b = transformed.subs(sub).N(); |
| 44 | |
| 45 | const ar = a.re ?? NaN; |
| 46 | const ai = a.im ?? 0; |
| 47 | const br = b.re ?? NaN; |
| 48 | const bi = b.im ?? 0; |
| 49 | |
| 50 | const ok = |
| 51 | Number.isFinite(ar) && |
| 52 | Number.isFinite(br) && |
| 53 | Math.abs(ar - br) <= tol && |
| 54 | Math.abs(ai - bi) <= tol; |
| 55 | |
| 56 | if (!ok) { |
| 57 | throw new Error( |
| 58 | `Mismatch at ${JSON.stringify(sub)}: ` + |
| 59 | `original=${a.toString()} (${ar}+${ai}i) vs ` + |
| 60 | `transformed=${b.toString()} (${br}+${bi}i)\n` + |
| 61 | ` original: ${original.toString()}\n` + |
| 62 | ` transformed: ${transformed.toString()}` |
| 63 | ); |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | function apply(op: string, latex: string): BoxedExpression { |
| 69 | return ce.function(op, [ce.parse(latex)]).evaluate(); |