"use strict"; class ParseError extends Error { constructor(message) { super(message); this.name = "ParseError"; } } const OPEN_TO_CLOSE = { "(": ")", "[": "]", "{": "}", "<": ">", "\u00ab": "\u00bb", }; const OPEN_BRACKETS = new Set(Object.keys(OPEN_TO_CLOSE)); const CLOSE_BRACKETS = new Set(Object.values(OPEN_TO_CLOSE)); const BRACKET_RE = /\u00ab|\u00bb|[()[\]{}<>]|[^\s()[\]{}<>\u00ab\u00bb]+/g; function isConst(e) { return e instanceof Const; } function isZero(e) { return isConst(e) && e._value === 0; } function isOne(e) { return isConst(e) && e._value === 1; } class Expression { diff(v) { throw new Error("diff not implemented"); } simplify() { return this; } isConst() { return false; } } class Const extends Expression { constructor(value) { super(); this._value = value; } evaluate(_x, _y, _z) { return this._value; } toString() { return String(this._value); } prefix() { return String(this._value); } postfix() { return String(this._value); } diff(_v) { return ZERO; } isConst() { return true; } contains(_v) { return false; } } const ZERO = new Const(0); const ONE = new Const(1); const TWO = new Const(2); const VAR_INDEX = { x: 0, y: 1, z: 2 }; class Variable extends Expression { constructor(name) { super(); this._name = name; } evaluate(x, y, z) { return [x, y, z][VAR_INDEX[this._name]]; } toString() { return this._name; } prefix() { return this._name; } postfix() { return this._name; } diff(v) { return v === this._name ? ONE : ZERO; } contains(v) { return v === this._name; } } class Operation extends Expression { constructor(...args) { super(); this._args = args; } evaluate(x, y, z) { return this._calc(...this._args.map((a) => a.evaluate(x, y, z))); } toString() { return ( this._args.map((a) => a.toString()).join(" ") + " " + this.constructor.symbol ); } postfix() { return ( "(" + this._args.map((a) => a.postfix()).join(" ") + " " + this.constructor.symbol + ")" ); } prefix() { return ( "(" + this.constructor.symbol + " " + this._args.map((a) => a.prefix()).join(" ") + ")" ); } isConst() { return this._args.every((a) => a.isConst()); } simplify() { const args = this._args.map((a) => a.simplify()); const ruled = this._simplifyArgs(args); if (!(ruled instanceof Operation)) return ruled; if (ruled._args.every((a) => a.isConst())) { const val = ruled._calc(...ruled._args.map((a) => a._value)); return new Const(val); } return ruled; } contains(v) { return this._args.some((a) => a.contains(v)); } diff(v) { if (this.isConst()) return ZERO; if (!this.contains(v)) return ZERO; return this._diff(v); } _diff(v) { throw new Error("_diff not implemented"); } _simplifyArgs(args) { return new this.constructor(...args); } } function makeOperation(symbol, calc, diffFn, simplifyArgsFn) { class Op extends Operation { _calc(...vals) { return calc(...vals); } _diff(v) { return diffFn(v, ...this._args); } _simplifyArgs(args) { if (simplifyArgsFn) { const result = simplifyArgsFn(...args); if (result !== undefined) return result; } return new Op(...args); } } Op.symbol = symbol; return Op; } function exprEqual(a, b) { if (a instanceof Const && b instanceof Const) return a._value === b._value; if (a instanceof Variable && b instanceof Variable) return a._name === b._name; if (a.constructor !== b.constructor) return false; if (!(a instanceof Operation)) return false; if (a._args.length !== b._args.length) return false; return a._args.every((arg, i) => exprEqual(arg, b._args[i])); } function factors(e) { if (e.constructor === Multiply) { return [...factors(e._args[0]), ...factors(e._args[1])]; } return [e]; } function cancelDivide(f, g) { const numFactors = factors(f); const denFactors = factors(g); for (let i = 0; i < numFactors.length; i++) { for (let j = 0; j < denFactors.length; j++) { if (exprEqual(numFactors[i], denFactors[j])) { const newNum = rebuildProduct(numFactors.filter((_, k) => k !== i)); const newDen = rebuildProduct(denFactors.filter((_, k) => k !== j)); if (isOne(newDen)) return newNum; const further = cancelDivide(newNum, newDen); if (further !== undefined) return further; return new Divide(newNum, newDen); } } } return undefined; } function rebuildProduct(fs) { if (fs.length === 0) return ONE; return fs.reduce((acc, f) => new Multiply(acc, f)); } const Add = makeOperation( "+", (a, b) => a + b, (v, f, g) => new Add(f.diff(v), g.diff(v)), (f, g) => (isZero(f) ? g : isZero(g) ? f : undefined), ); const Subtract = makeOperation( "-", (a, b) => a - b, (v, f, g) => new Subtract(f.diff(v), g.diff(v)), (f, g) => (isZero(g) ? f : isZero(f) ? new Negate(g) : undefined), ); const Multiply = makeOperation( "*", (a, b) => a * b, (v, f, g) => new Add(new Multiply(f.diff(v), g), new Multiply(f, g.diff(v))), (f, g) => { if (isZero(f) || isZero(g)) return ZERO; if (isOne(f)) return g; if (isOne(g)) return f; if (g.constructor === Divide && isOne(g._args[0])) return new Divide(f, g._args[1]); if (f.constructor === Divide && isOne(f._args[0])) return new Divide(g, f._args[1]); return undefined; }, ); const Divide = makeOperation( "/", (a, b) => a / b, (v, f, g) => new Divide( new Subtract(new Multiply(f.diff(v), g), new Multiply(f, g.diff(v))), new Multiply(g, g), ), (f, g) => { if (isZero(f)) return ZERO; if (isOne(g)) return f; if (exprEqual(f, g)) return ONE; if (f.constructor === Divide) { return new Divide(f._args[0], new Multiply(f._args[1], g)); } if (g.constructor === Divide) { return new Divide(new Multiply(f, g._args[1]), g._args[0]); } const cancelled = cancelDivide(f, g); if (cancelled) return cancelled; return undefined; }, ); const Negate = makeOperation( "negate", (a) => -a, (v, f) => new Negate(f.diff(v)), (f) => (isZero(f) ? ZERO : undefined), ); const Power = makeOperation( "pow", (a, b) => Math.pow(a, b), (v, f, g) => new Add( new Multiply( new Multiply(g, new Power(f, new Subtract(g, ONE))), f.diff(v), ), new Multiply( new Multiply(new Power(f, g), new Log(new Const(Math.E), f)), g.diff(v), ), ), ); Power.symbol = "pow"; const Log = makeOperation( "log", (a, b) => Math.log(Math.abs(b)) / Math.log(Math.abs(a)), (v, base, x) => new Divide( new Subtract( new Divide(x.diff(v), x), new Multiply(new Log(base, x), new Divide(base.diff(v), base)), ), new Log(new Const(Math.E), base), ), ); Log.symbol = "log"; function makeSumN(n) { class SumNOp extends Operation { _calc(...vals) { return vals.reduce((s, v) => s + v, 0); } _diff(v) { return this._args .map((a) => a.diff(v)) .reduce((acc, d) => new Add(acc, d)); } } SumNOp.symbol = "sum" + n; return SumNOp; } const Sum1 = makeSumN(1); const Sum2 = makeSumN(2); const Sum3 = makeSumN(3); const Sum4 = makeSumN(4); const Sum5 = makeSumN(5); function makeAvgN(n) { class AvgNOp extends Operation { _calc(...vals) { return vals.reduce((s, v) => s + v, 0) / n; } _diff(v) { const sumDiff = this._args .map((a) => a.diff(v)) .reduce((acc, d) => new Add(acc, d)); return new Divide(sumDiff, new Const(n)); } } AvgNOp.symbol = "avg" + n; return AvgNOp; } const Avg1 = makeAvgN(1); const Avg2 = makeAvgN(2); const Avg3 = makeAvgN(3); const Avg4 = makeAvgN(4); const Avg5 = makeAvgN(5); const Gauss = makeOperation( "gauss", (a, b, c, x) => a * Math.exp(-((x - b) ** 2) / (2 * c * c)), (v, a, b, c, x) => { if (a.isConst() && isZero(a)) return ZERO; const gauss = new Gauss(a, b, c, x); const xmb = new Subtract(x, b); const c2 = new Multiply(c, c); const da = new Divide(a.diff(v), a); const dxb = new Divide( new Multiply(xmb, new Subtract(x.diff(v), b.diff(v))), c2, ); const dc = new Divide( new Multiply(new Multiply(xmb, xmb), c.diff(v)), new Multiply(c2, c), ); return new Multiply(gauss, new Add(da, new Subtract(dc, dxb))); }, (a, b, c, x) => (isZero(a) ? ZERO : undefined), ); Gauss.symbol = "gauss"; function wrapCalc(x, mn, mx) { const range = mx - mn; if (range === 0) return mn; return mn + ((((x - mn) % range) + range) % range); } const Floor = makeOperation( "__floor__", (a) => Math.floor(a), () => ZERO, ); Floor.symbol = "__floor__"; const Wrap = makeOperation("wrap", wrapCalc, (v, x, mn, mx) => { const q = new Floor(new Divide(new Subtract(x, mn), new Subtract(mx, mn))); return new Subtract( x.diff(v), new Multiply(q, new Subtract(mx.diff(v), mn.diff(v))), ); }); Wrap.symbol = "wrap"; const Sin = makeOperation( "__sin__", (a) => Math.sin(a), (v, f) => new Multiply(new Cos(f), f.diff(v)), ); Sin.symbol = "__sin__"; const Cos = makeOperation( "__cos__", (a) => Math.cos(a), (v, f) => new Negate(new Multiply(new Sin(f), f.diff(v))), ); Cos.symbol = "__cos__"; const Asin = makeOperation( "__asin__", (a) => Math.asin(a), (v, f) => new Divide( f.diff(v), new Power(new Subtract(ONE, new Multiply(f, f)), new Const(0.5)), ), ); Asin.symbol = "__asin__"; const Tanh = makeOperation( "__tanh__", (a) => Math.tanh(a), (v, f) => { const th = new Tanh(f); return new Multiply(new Subtract(ONE, new Multiply(th, th)), f.diff(v)); }, ); Tanh.symbol = "__tanh__"; function softWrapCalc(x, mn, mx, lam) { const range = mx - mn; if (range === 0) return mn; const a = (Math.PI * (x - mn)) / range; return ( (Math.asin(Math.cos(a) * Math.tanh(lam * -Math.sin(a))) * range) / Math.PI + (mx + mn) / 2 ); } class SoftWrap extends Operation { _calc(...vals) { return softWrapCalc(...vals); } _tree() { const [x, mn, mx, lam] = this._args; const PI = new Const(Math.PI); const range = new Subtract(mx, mn); const a = new Divide(new Multiply(PI, new Subtract(x, mn)), range); const th = new Tanh(new Multiply(lam, new Negate(new Sin(a)))); const inner = new Asin(new Multiply(new Cos(a), th)); return new Add( new Multiply(inner, new Divide(range, PI)), new Divide(new Add(mx, mn), TWO), ); } _diff(v) { return this._tree().diff(v); } } SoftWrap.symbol = "softWrap"; class ArcTan12 extends Operation { _calc(...vals) { return vals.length === 1 ? Math.atan(vals[0]) : Math.atan2(vals[0], vals[1]); } _diff(v) { if (this._args.length === 1) { const [x] = this._args; return new Divide(x.diff(v), new Add(ONE, new Multiply(x, x))); } const [y, x] = this._args; return new Divide( new Subtract(new Multiply(x, y.diff(v)), new Multiply(y, x.diff(v))), new Add(new Multiply(x, x), new Multiply(y, y)), ); } } ArcTan12.symbol = "atan12"; const Exp = makeOperation( "exp", (a) => Math.exp(a), (v, f) => new Multiply(new Exp(f), f.diff(v)), ); Exp.symbol = "exp"; class MeanExp extends Operation { _calc(...vals) { return vals.reduce((s, v) => s + Math.exp(v), 0) / vals.length; } _diff(v) { const n = this._args.length; const sumDiff = this._args .map((a) => new Multiply(new Exp(a), a.diff(v))) .reduce((acc, d) => new Add(acc, d)); return new Divide(sumDiff, new Const(n)); } } MeanExp.symbol = "meanExp"; class SumExp extends Operation { _calc(...vals) { return vals.reduce((s, v) => s + Math.exp(v), 0); } _diff(v) { return this._args .map((a) => new Multiply(new Exp(a), a.diff(v))) .reduce((acc, d) => new Add(acc, d)); } } SumExp.symbol = "sumExp"; class Lse extends Operation { _calc(...vals) { return Math.log(vals.reduce((s, v) => s + Math.exp(v), 0)); } _diff(v) { const sumExpNode = new SumExp(...this._args); const sumDiff = this._args .map((a) => new Multiply(new Exp(a), a.diff(v))) .reduce((acc, d) => new Add(acc, d)); return new Divide(sumDiff, sumExpNode); } } Lse.symbol = "lse"; class Lme extends Operation { _calc(...vals) { return Math.log(vals.reduce((s, v) => s + Math.exp(v), 0) / vals.length); } _diff(v) { const n = this._args.length; const sumExpNode = new SumExp(...this._args); const sumDiff = this._args .map((a) => new Multiply(new Exp(a), a.diff(v))) .reduce((acc, d) => new Add(acc, d)); return new Divide(sumDiff, sumExpNode); } } Lme.symbol = "lme"; const OPERATIONS = { "+": [Add, 2], "-": [Subtract, 2], "*": [Multiply, 2], "/": [Divide, 2], negate: [Negate, 1], pow: [Power, 2], log: [Log, 2], sum1: [Sum1, 1], sum2: [Sum2, 2], sum3: [Sum3, 3], sum4: [Sum4, 4], sum5: [Sum5, 5], avg1: [Avg1, 1], avg2: [Avg2, 2], avg3: [Avg3, 3], avg4: [Avg4, 4], avg5: [Avg5, 5], gauss: [Gauss, 4], wrap: [Wrap, 3], softWrap: [SoftWrap, 4], __floor__: [Floor, 1], __sin__: [Sin, 1], __cos__: [Cos, 1], __asin__: [Asin, 1], __tanh__: [Tanh, 1], exp: [Exp, 1], atan12: [ArcTan12, null], meanExp: [MeanExp, null], sumExp: [SumExp, null], lse: [Lse, null], lme: [Lme, null], }; function parse(expr) { const tokens = expr.trim().split(/\s+/); const stack = []; for (const tok of tokens) { if (tok in OPERATIONS) { const [Ctor, arity] = OPERATIONS[tok]; const args = stack.splice(stack.length - arity, arity); stack.push(new Ctor(...args)); } else if (tok in VAR_INDEX) { stack.push(new Variable(tok)); } else { stack.push(new Const(Number(tok))); } } return stack[0]; } function parsePostfix(expr) { const tokens = expr.trim().match(BRACKET_RE) || []; if (tokens.length === 0) { throw new ParseError("Empty expression"); } let pos = 0; function peek() { return tokens[pos]; } function consume() { return tokens[pos++]; } function parseArg() { const val = parseExpr(); if (typeof val === "string") { throw new ParseError(`Unexpected operator '${val}' in argument position`); } return val; } function parseExpr() { if (pos >= tokens.length) { throw new ParseError("Unexpected end of input"); } const tok = peek(); if (OPEN_BRACKETS.has(tok)) { const openBracket = consume(); const closeBracket = OPEN_TO_CLOSE[openBracket]; const args = []; while (peek() !== closeBracket) { if (peek() === undefined) { throw new ParseError(`Missing closing '${closeBracket}'`); } const item = parseExpr(); args.push(item); } consume(); if (args.length === 0) { throw new ParseError("Empty parenthesised expression"); } const opSym = args[args.length - 1]; if (typeof opSym !== "string") { throw new ParseError( "Expected operator at end of parenthesised expression, got expression", ); } if (!(opSym in OPERATIONS)) { throw new ParseError(`Unknown operator: '${opSym}'`); } const exprArgs = args.slice(0, args.length - 1); for (let i = 0; i < exprArgs.length; i++) { if (typeof exprArgs[i] === "string") { throw new ParseError( `Unexpected operator '${exprArgs[i]}' in argument position`, ); } } const [Ctor, arity] = OPERATIONS[opSym]; if (arity !== null && exprArgs.length !== arity) { throw new ParseError( `Operator '${opSym}' expects ${arity} argument(s), got ${exprArgs.length}`, ); } return new Ctor(...exprArgs); } if (CLOSE_BRACKETS.has(tok)) { throw new ParseError(`Unexpected closing bracket: '${tok}'`); } consume(); if (tok in OPERATIONS) { return tok; } if (tok in VAR_INDEX) { return new Variable(tok); } const n = Number(tok); if (isNaN(n)) { throw new ParseError(`Unknown token: '${tok}'`); } return new Const(n); } const result = parseExpr(); if (typeof result === "string") { throw new ParseError(`Unexpected operator token: '${result}'`); } if (pos < tokens.length) { throw new ParseError(`Unexpected token after expression: '${tokens[pos]}'`); } return result; } function parsePrefix(expr) { const tokens = expr.trim().match(BRACKET_RE) || []; let pos = 0; function peek() { return tokens[pos]; } function consume() { return tokens[pos++]; } function parseExpr() { if (pos >= tokens.length) { throw new ParseError("Unexpected end of input"); } const tok = peek(); if (OPEN_BRACKETS.has(tok)) { const openBracket = consume(); const closeBracket = OPEN_TO_CLOSE[openBracket]; const op = consume(); if (op === undefined) { throw new ParseError(`Expected operator after '${openBracket}'`); } if (!(op in OPERATIONS)) { throw new ParseError(`Unknown operator: '${op}'`); } const [Ctor, arity] = OPERATIONS[op]; const args = []; if (arity === null) { while (peek() !== closeBracket) { if (peek() === undefined) { throw new ParseError( `Expected '${closeBracket}' but reached end of input`, ); } args.push(parseExpr()); } } else { for (let i = 0; i < arity; i++) { args.push(parseExpr()); } if (peek() !== closeBracket) { throw new ParseError( `Expected '${closeBracket}' after ${arity} argument(s) of '${op}', ` + `got ${peek() === undefined ? "end of input" : "'" + peek() + "'"}`, ); } } consume(); return new Ctor(...args); } consume(); if (tok in VAR_INDEX) { return new Variable(tok); } if (CLOSE_BRACKETS.has(tok)) { throw new ParseError(`Unexpected closing bracket: '${tok}'`); } const n = Number(tok); if (!isNaN(n)) { return new Const(n); } throw new ParseError(`Unexpected token: '${tok}'`); } const result = parseExpr(); if (pos < tokens.length) { throw new ParseError(`Unexpected token after expression: '${tokens[pos]}'`); } return result; } if (typeof module !== "undefined") { module.exports = { Const, Variable, Add, Subtract, Multiply, Divide, Negate, Power, Log, Sum1, Sum2, Sum3, Sum4, Sum5, Avg1, Avg2, Avg3, Avg4, Avg5, Gauss, Wrap, SoftWrap, ArcTan12, MeanExp, SumExp, Lse, Lme, parse, parsePrefix, parsePostfix, }; }