| 50 | // --------------------------------------------------------------------------- |
| 51 | |
| 52 | /** Structural ReplaceAll: return `u` with every subexpression structurally |
| 53 | * equal to `target` replaced by `replacement` (Mathematica `u /. target -> |
| 54 | * replacement`). Used by the general form of Rubi's `Subst` (see the Subst |
| 55 | * case in build). If `target` does not occur, `u` is returned unchanged — a |
| 56 | * safe outcome for the back-substitution rules, whose pre-substitution form |
| 57 | * differs from the intended one only by a branch-constant offset in the log |
| 58 | * argument (equal on the positive-real verification domain). */ |
| 59 | function replaceSubexpr( |
| 60 | u: Expression, |
| 61 | target: Expression, |
| 62 | replacement: Expression |
| 63 | ): Expression { |
| 64 | if (u.isSame(target)) return replacement; |
| 65 | const ops = u.ops; |
| 66 | if (!ops) return u; |
| 67 | let changed = false; |
| 68 | const newOps = ops.map((op) => { |
| 69 | const r = replaceSubexpr(op, target, replacement); |
| 70 | if (r !== op) changed = true; |
| 71 | return r; |
| 72 | }); |
| 73 | if (!changed) return u; |
| 74 | return u.engine.function(u.operator, newOps); |
| 75 | } |
| 76 | |
| 77 | export function build(json: Json, ctx: Ctx): Expression { |
| 78 | const { ce, env } = ctx; |
| 79 | if (typeof json === 'number') return ce.number(json); |
| 80 | if (typeof json === 'string') { |
| 81 | const bound = env.get(json); |
| 82 | if (bound !== undefined) return bound; |
| 83 | return ce.symbol(json); |
| 84 | } |
| 85 | const [head, ...args] = json; |
| 86 | if (typeof head !== 'string') return fail('non-symbol head in RHS'); |
| 87 | |
| 88 | switch (head) { |
| 89 | case 'Int': { |
| 90 | // Int[f, x] — recursive integration; an unsolved subproblem stays |
| 91 | // inert (reduction formulas legitimately leave residual integrals). |
| 92 | const f = build(args[0], ctx); |
| 93 | return ctx.hooks.int(f) ?? ce._fn('Integrate', [f, ce.symbol(ctx.x)]); |
| 94 | } |
| 95 | case 'Subst': { |
| 96 | // Subst[u, y, v]: integrate/transform u, then substitute y → v. |
| 97 | // When u is itself Int[…], a failed inner integration fails the |
| 98 | // rule (substituting into an inert integral is not useful). |
| 99 | if (args.length !== 3) return fail('Subst arity'); |
| 100 | let u: Expression; |
| 101 | if (Array.isArray(args[0]) && args[0][0] === 'Int') { |
| 102 | const f = build((args[0] as Json[])[1], ctx); |
| 103 | const F = ctx.hooks.int(f); |
| 104 | // a residual inert Integrate inside F would have its integration |
| 105 | // variable substituted too, producing a malformed integral |
| 106 | if (F === null || F.has('Integrate')) |
| 107 | return fail('Subst: inner Int unsolved'); |
| 108 | u = F; |
| 109 | } else u = build(args[0], ctx); |