first commit

This commit is contained in:
me
2026-04-08 21:25:17 +03:00
parent 3681b8eccd
commit 371b14c5e3
173 changed files with 14126 additions and 0 deletions

View File

@@ -0,0 +1,552 @@
"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,
};
}