"use strict"; class Expression { evaluate() { throw new Error("Not implemented"); } toString() { throw new Error("Not implemented"); } diff() { throw new Error("Not implemented"); } simplify() { return this; } } class Const extends Expression { constructor(value) { super(); this._value = value; } evaluate(_x, _y, _z) { return this._value; } toString() { return String(this._value); } diff(_varName) { return ZERO; } simplify() { return this; } } const ZERO = new Const(0); const ONE = new Const(1); const VAR_INDEX = { x: 0, y: 1, z: 2 }; class Variable extends Expression { constructor(name) { super(); this._name = name; this._index = VAR_INDEX[name]; } evaluate(...args) { return args[this._index]; } toString() { return this._name; } diff(varName) { return varName === this._name ? ONE : ZERO; } simplify() { return this; } } function exprEquals(a, b) { if (a === b) return true; if (a.constructor !== b.constructor) return false; if (a instanceof Const) return a._value === b._value; if (a instanceof Variable) return a._name === b._name; if (a instanceof BinaryOp) return exprEquals(a._left, b._left) && exprEquals(a._right, b._right); if (a instanceof UnaryOp) return exprEquals(a._operand, b._operand); if (a instanceof NaryOp) { if (a._args.length !== b._args.length) return false; return a._args.every((arg, i) => exprEquals(arg, b._args[i])); } return false; } class BinaryOp extends Expression { constructor(left, right) { super(); this._left = left; this._right = right; } evaluate(x, y, z) { return this._op( this._left.evaluate(x, y, z), this._right.evaluate(x, y, z), ); } toString() { return `${this._left.toString()} ${this._right.toString()} ${this._symbol}`; } simplify() { const l = this._left.simplify(); const r = this._right.simplify(); const lConst = l instanceof Const; const rConst = r instanceof Const; const special = this._simplifySpecial(l, r, lConst, rConst); if (special !== null) return special; if (lConst && rConst) { return new Const(this._op(l._value, r._value)); } return this._rebuild(l, r); } _rebuild(l, r) { return new this.constructor(l, r); } _simplifySpecial(l, r, lConst, rConst) { return null; } } class UnaryOp extends Expression { constructor(operand) { super(); this._operand = operand; } evaluate(x, y, z) { return this._op(this._operand.evaluate(x, y, z)); } toString() { return `${this._operand.toString()} ${this._symbol}`; } simplify() { const inner = this._operand.simplify(); if (inner instanceof Const) { return new Const(this._op(inner._value)); } return new this.constructor(inner); } } // Base class for N-ary operations (N = 1..5) class NaryOp extends Expression { constructor(...args) { super(); this._args = args; } evaluate(x, y, z) { return this._op(...this._args.map((a) => a.evaluate(x, y, z))); } toString() { return `${this._args.map((a) => a.toString()).join(" ")} ${this._symbol}`; } simplify() { const simplified = this._args.map((a) => a.simplify()); if (simplified.every((a) => a instanceof Const)) { return new Const(this._op(...simplified.map((a) => a._value))); } return new this.constructor(...simplified); } } class Add extends BinaryOp { get _symbol() { return "+"; } _op(a, b) { return a + b; } diff(varName) { return new Add(this._left.diff(varName), this._right.diff(varName)); } _simplifySpecial(l, r, lConst, rConst) { if (lConst && l._value === 0) return r; if (rConst && r._value === 0) return l; return null; } } class Subtract extends BinaryOp { get _symbol() { return "-"; } _op(a, b) { return a - b; } diff(varName) { return new Subtract(this._left.diff(varName), this._right.diff(varName)); } _simplifySpecial(l, r, lConst, rConst) { if (rConst && r._value === 0) return l; if (lConst && l._value === 0) return new Negate(r).simplify(); if (exprEquals(l, r)) return ZERO; return null; } } function cancelCommonFactor(num, den) { if (exprEquals(num, den)) return [ONE, ONE]; if (den instanceof Multiply) { const [dA, dB] = [den._left, den._right]; if (exprEquals(num, dA)) return [ONE, dB]; if (exprEquals(num, dB)) return [ONE, dA]; if (num instanceof Multiply) { const [nA, nB] = [num._left, num._right]; if (exprEquals(nA, dA)) return [nB, dB]; if (exprEquals(nA, dB)) return [nB, dA]; if (exprEquals(nB, dA)) return [nA, dB]; if (exprEquals(nB, dB)) return [nA, dA]; } } if (num instanceof Multiply) { const [nA, nB] = [num._left, num._right]; if (exprEquals(den, nA)) return [nB, ONE]; if (exprEquals(den, nB)) return [nA, ONE]; } return null; } class Multiply extends BinaryOp { get _symbol() { return "*"; } _op(a, b) { return a * b; } diff(varName) { return new Add( new Multiply(this._left.diff(varName), this._right), new Multiply(this._left, this._right.diff(varName)), ); } _simplifySpecial(l, r, lConst, rConst) { if ((lConst && l._value === 0) || (rConst && r._value === 0)) return ZERO; if (lConst && l._value === 1) return r; if (rConst && r._value === 1) return l; return null; } } class Divide extends BinaryOp { get _symbol() { return "/"; } _op(a, b) { return a / b; } diff(varName) { return new Divide( new Subtract( new Multiply(this._left.diff(varName), this._right), new Multiply(this._left, this._right.diff(varName)), ), new Multiply(this._right, this._right), ); } _simplifySpecial(l, r, lConst, rConst) { if (lConst && l._value === 0) return ZERO; if (rConst && r._value === 1) return l; const cancelled = cancelCommonFactor(l, r); if (cancelled !== null) { const [newNum, newDen] = cancelled; return new Divide(newNum, newDen).simplify(); } return null; } } class Negate extends UnaryOp { get _symbol() { return "negate"; } _op(a) { return -a; } diff(varName) { return new Negate(this._operand.diff(varName)); } simplify() { const inner = this._operand.simplify(); if (inner instanceof Const) return new Const(-inner._value); return new Negate(inner); } } // ── Power ────────────────────────────────────────────────────────────────── // pow(f, g) = f ^ g // (f ^ g)' = f ^ g * (g' * ln|f| + g * f' / f) class Power extends BinaryOp { get _symbol() { return "pow"; } _op(a, b) { return Math.pow(a, b); } diff(varName) { const f = this._left; const g = this._right; // Simplify f and g first so that e.g. Negate(Const(493)) is treated as Const(-493) const gs = g.simplify(); const fs = f.simplify(); // Also compute derivatives and simplify them to detect zero const fdiff = f.diff(varName).simplify(); const gdiff = g.diff(varName).simplify(); const fdiffZero = fdiff instanceof Const && fdiff._value === 0; const gdiffZero = gdiff instanceof Const && gdiff._value === 0; if (gs instanceof Const || gdiffZero) { // Power rule: (f^n)' = n * f^(n-1) * f' // Works when exponent is constant OR doesn't depend on varName const n = gs instanceof Const ? gs._value : g; const nExpr = gs instanceof Const ? new Const(gs._value) : g; const nMinus1 = gs instanceof Const ? new Const(gs._value - 1) : new Subtract(g, ONE); return new Multiply( new Multiply(nExpr, new Power(f, nMinus1)), fdiff, ); } if (fs instanceof Const || fdiffZero) { // Exponential rule: (a^g)' = a^g * ln(|a|) * g' // Works when base is constant OR doesn't depend on varName return new Multiply( new Multiply(new Power(f, g), new Log(new Const(Math.E), f)), gdiff, ); } // General case: both base and exponent truly depend on variable // (f^g)' = f^g * (g' * ln|f| + g * f'/f) return new Multiply( new Power(f, g), new Add( new Multiply(gdiff, new Log(new Const(Math.E), f)), new Multiply(g, new Divide(fdiff, f)), ), ); } _simplifySpecial(l, r, lConst, rConst) { if (rConst && r._value === 0) return ONE; if (rConst && r._value === 1) return l; if (lConst && l._value === 1) return ONE; if (lConst && l._value === 0) return ZERO; return null; } } // ── Log ──────────────────────────────────────────────────────────────────── // log(base, x) = ln|x| / ln|base| // Matches the postfix token "log": `base x log` // // Full quotient-rule derivative (base may depend on the variable): // log_b(f) = ln|f| / ln|b| // d/dx = (f'/f * ln|b| - b'/b * ln|f|) / (ln|b|)^2 // = f'/(f * ln|b|) - b' * ln|f| / (b * (ln|b|)^2) class Log extends BinaryOp { get _symbol() { return "log"; } _op(base, x) { return Math.log(Math.abs(x)) / Math.log(Math.abs(base)); } diff(varName) { const b = this._left; // base expression const f = this._right; // argument expression // Compute derivatives and simplify to detect zeros const bdiff = b.diff(varName).simplify(); const fdiff = f.diff(varName).simplify(); const bdiffZero = bdiff instanceof Const && bdiff._value === 0; const fdiffZero = fdiff instanceof Const && fdiff._value === 0; // log_b(f) = ln|f| / ln|b| // Quotient rule: d/dx = (f'/f * ln|b| - b'/b * ln|f|) / (ln|b|)^2 // = f'/(f * ln|b|) - b'*ln|f| / (b * (ln|b|)^2) // Special case: log_b(b) = 1 always => d/dx = 0 if (exprEquals(b, f)) return ZERO; const lnB = new Log(new Const(Math.E), b); // Special case: base is constant (b' = 0) => d/dx = f'/(f * ln|b|) if (bdiffZero) { return new Divide(fdiff, new Multiply(f, lnB)); } // Special case: argument is constant (f' = 0) => d/dx = -log_b(f) / (b * ln|b|) // derived from general formula with fdiff=0: -(b'/b * log_b(f)) / lnB // restructured to avoid 1/b factor: -Log(b,f) / (b * lnB) if (fdiffZero) { return new Negate(new Divide(new Log(b, f), new Multiply(b, lnB))); } // General case: use formula (f'/f - b'/b * log_b(f)) / ln|b| // This avoids introducing ln|f| separately (which would create structurally // different but numerically equal subexpressions that don't cancel). const inner = new Subtract( new Divide(fdiff, f), new Multiply(new Divide(bdiff, b), new Log(b, f)), ); return new Divide(inner, lnB); } _simplifySpecial(l, r, lConst, rConst) { // log_b(1) = 0 if (rConst && r._value === 1) return ZERO; // log_b(b) = 1 (when base and argument are structurally equal) if (exprEquals(l, r)) return ONE; return null; } } // ── SumN ─────────────────────────────────────────────────────────────────── // sum1..sum5 – sum of N arguments // Derivative: sum of derivatives of each argument. function makeSumN(n) { return class extends NaryOp { get _symbol() { return `sum${n}`; } _op(...vals) { return vals.reduce((acc, v) => acc + v, 0); } diff(varName) { return this._args .map((a) => a.diff(varName)) .reduce((acc, d) => new Add(acc, d)); } simplify() { const simplified = this._args.map((a) => a.simplify()); // Drop zero summands const nonZero = simplified.filter( (a) => !(a instanceof Const && a._value === 0), ); if (nonZero.length === 0) return ZERO; if (nonZero.length === 1) return nonZero[0]; if (nonZero.every((a) => a instanceof Const)) { return new Const(nonZero.reduce((acc, a) => acc + a._value, 0)); } return new this.constructor(...simplified); } }; } const Sum1 = makeSumN(1); const Sum2 = makeSumN(2); const Sum3 = makeSumN(3); const Sum4 = makeSumN(4); const Sum5 = makeSumN(5); // ── Operation registry & parser ──────────────────────────────────────────── const OPERATIONS = { "+": Add, "-": Subtract, "*": Multiply, "/": Divide, negate: Negate, pow: Power, log: Log, sum1: Sum1, sum2: Sum2, sum3: Sum3, sum4: Sum4, sum5: Sum5, }; // Arity for each token (BinaryOp = 2, UnaryOp = 1, NaryOp = N) const ARITY = { "+": 2, "-": 2, "*": 2, "/": 2, negate: 1, pow: 2, log: 2, sum1: 1, sum2: 2, sum3: 3, sum4: 4, sum5: 5, }; function parse(expr) { const tokens = expr .trim() .split(/\s+/) .filter((t) => t.length > 0); const stack = []; for (const token of tokens) { if (token in OPERATIONS) { const Cls = OPERATIONS[token]; const arity = ARITY[token]; const operands = stack.splice(stack.length - arity, arity); stack.push(new Cls(...operands)); } else if (token in VAR_INDEX) { stack.push(new Variable(token)); } else { stack.push(new Const(Number(token))); } } return stack.pop(); } if (typeof module !== "undefined") { module.exports = { Const, Variable, Add, Subtract, Multiply, Divide, Negate, Power, Log, Sum1, Sum2, Sum3, Sum4, Sum5, parse, }; }