Support variable anywhere in formula

This commit is contained in:
thepaperpilot 2023-01-06 00:38:11 -06:00
parent c8283a7043
commit 5f3dd1162d
2 changed files with 189 additions and 65 deletions

View file

@ -27,6 +27,22 @@ function unrefFormulaSource(value: Formula | ProcessedComputable<DecimalSource>)
return value instanceof Formula ? value.evaluate() : unref(value); return value instanceof Formula ? value.evaluate() : unref(value);
} }
function isVariableFormula(value: FormulaSource): value is VariableFormula {
return value instanceof Formula && value.hasVariable;
}
function calculateInvertibility(...inputs: FormulaSource[]) {
const invertible = !inputs.some(input => input instanceof Formula && !input.invertible);
const hasVariable =
invertible &&
inputs.filter(input => input instanceof Formula && input.invertible && input.hasVariable)
.length === 1;
return {
invertible,
hasVariable
};
}
export default class Formula { export default class Formula {
public readonly invertible: boolean; public readonly invertible: boolean;
public readonly hasVariable: boolean; public readonly hasVariable: boolean;
@ -533,7 +549,8 @@ export default class Formula {
public neg() { public neg() {
return new Formula( return new Formula(
() => this.evaluate().neg(), () => this.evaluate().neg(),
value => Decimal.neg(value) value => Decimal.neg(value),
this.hasVariable
); );
} }
@ -577,11 +594,17 @@ export default class Formula {
public add(this: Formula, value: FormulaSource): Formula; public add(this: Formula, value: FormulaSource): Formula;
public add(value: FormulaSource) { public add(value: FormulaSource) {
const v = processFormulaSource(value); const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula( return new Formula(
() => this.evaluate().add(unrefFormulaSource(v)), () => this.evaluate().add(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible invertible
? undefined ? value =>
: value => Decimal.sub(value, unrefFormulaSource(v)) Decimal.sub(
value,
isVariableFormula(this) ? unrefFormulaSource(v) : this.evaluate()
)
: undefined,
hasVariable
); );
} }
@ -595,11 +618,17 @@ export default class Formula {
public sub(this: Formula, value: FormulaSource): Formula; public sub(this: Formula, value: FormulaSource): Formula;
public sub(value: FormulaSource) { public sub(value: FormulaSource) {
const v = processFormulaSource(value); const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula( return new Formula(
() => this.evaluate().sub(unrefFormulaSource(v)), () => this.evaluate().sub(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible invertible
? undefined ? value =>
: value => Decimal.add(value, unrefFormulaSource(v)) Decimal.add(
value,
isVariableFormula(this) ? unrefFormulaSource(v) : this.evaluate()
)
: undefined,
hasVariable
); );
} }
@ -619,11 +648,17 @@ export default class Formula {
public mul(this: Formula, value: FormulaSource): Formula; public mul(this: Formula, value: FormulaSource): Formula;
public mul(value: FormulaSource) { public mul(value: FormulaSource) {
const v = processFormulaSource(value); const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula( return new Formula(
() => this.evaluate().mul(unrefFormulaSource(v)), () => this.evaluate().mul(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible invertible
? undefined ? value =>
: value => Decimal.div(value, unrefFormulaSource(v)) Decimal.div(
value,
isVariableFormula(this) ? unrefFormulaSource(v) : this.evaluate()
)
: undefined,
hasVariable
); );
} }
@ -643,11 +678,17 @@ export default class Formula {
public div(this: Formula, value: FormulaSource): Formula; public div(this: Formula, value: FormulaSource): Formula;
public div(value: FormulaSource) { public div(value: FormulaSource) {
const v = processFormulaSource(value); const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula( return new Formula(
() => this.evaluate().div(unrefFormulaSource(v)), () => this.evaluate().div(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible invertible
? undefined ? value =>
: value => Decimal.mul(value, unrefFormulaSource(v)) Decimal.mul(
value,
isVariableFormula(this) ? unrefFormulaSource(v) : this.evaluate()
)
: undefined,
hasVariable
); );
} }
@ -674,7 +715,8 @@ export default class Formula {
public recip() { public recip() {
return new Formula( return new Formula(
() => this.evaluate().recip(), () => this.evaluate().recip(),
!this.invertible ? undefined : value => Decimal.recip(value) this.invertible ? value => Decimal.recip(value) : undefined,
this.hasVariable
); );
} }
@ -745,7 +787,8 @@ export default class Formula {
public log10() { public log10() {
return new Formula( return new Formula(
() => this.evaluate().log10(), () => this.evaluate().log10(),
!this.invertible ? undefined : value => Decimal.pow10(value) this.invertible ? value => Decimal.pow10(value) : undefined,
this.hasVariable
); );
} }
@ -753,11 +796,16 @@ export default class Formula {
public log(this: Formula, value: FormulaSource): Formula; public log(this: Formula, value: FormulaSource): Formula;
public log(value: FormulaSource) { public log(value: FormulaSource) {
const v = processFormulaSource(value); const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula( return new Formula(
() => this.evaluate().log(unrefFormulaSource(v)), () => this.evaluate().log(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible invertible
? undefined ? value =>
: value => Decimal.pow(unrefFormulaSource(v), value) isVariableFormula(this)
? Decimal.pow(unrefFormulaSource(v), value)
: Decimal.root(this.evaluate(), value)
: undefined,
hasVariable
); );
} }
@ -772,7 +820,8 @@ export default class Formula {
public log2() { public log2() {
return new Formula( return new Formula(
() => this.evaluate().log2(), () => this.evaluate().log2(),
!this.invertible ? undefined : value => Decimal.pow(2, value) this.invertible ? value => Decimal.pow(2, value) : undefined,
this.hasVariable
); );
} }
@ -781,7 +830,8 @@ export default class Formula {
public ln() { public ln() {
return new Formula( return new Formula(
() => this.evaluate().ln(), () => this.evaluate().ln(),
!this.invertible ? undefined : value => Decimal.exp(value) this.invertible ? value => Decimal.exp(value) : undefined,
this.hasVariable
); );
} }
@ -789,11 +839,16 @@ export default class Formula {
public pow(this: Formula, value: FormulaSource): Formula; public pow(this: Formula, value: FormulaSource): Formula;
public pow(value: FormulaSource) { public pow(value: FormulaSource) {
const v = processFormulaSource(value); const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula( return new Formula(
() => this.evaluate().pow(unrefFormulaSource(v)), () => this.evaluate().pow(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible invertible
? undefined ? value =>
: value => Decimal.root(value, unrefFormulaSource(v)) isVariableFormula(this)
? Decimal.root(value, unrefFormulaSource(v))
: Decimal.ln(value).div(Decimal.ln(this.evaluate()))
: undefined,
hasVariable
); );
} }
@ -802,7 +857,8 @@ export default class Formula {
public pow10() { public pow10() {
return new Formula( return new Formula(
() => this.evaluate().pow10(), () => this.evaluate().pow10(),
!this.invertible ? undefined : value => Decimal.root(value, 10) this.invertible ? value => Decimal.root(value, 10) : undefined,
this.hasVariable
); );
} }
@ -810,11 +866,16 @@ export default class Formula {
public pow_base(this: Formula, value: FormulaSource): Formula; public pow_base(this: Formula, value: FormulaSource): Formula;
public pow_base(value: FormulaSource) { public pow_base(value: FormulaSource) {
const v = processFormulaSource(value); const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula( return new Formula(
() => this.evaluate().pow_base(unrefFormulaSource(v)), () => this.evaluate().pow_base(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible invertible
? undefined ? value =>
: value => Decimal.root(unrefFormulaSource(v), value) isVariableFormula(this)
? Decimal.ln(value).div(unrefFormulaSource(v))
: Decimal.root(unrefFormulaSource(v), value)
: undefined,
hasVariable
); );
} }
@ -822,11 +883,16 @@ export default class Formula {
public root(this: Formula, value: FormulaSource): Formula; public root(this: Formula, value: FormulaSource): Formula;
public root(value: FormulaSource) { public root(value: FormulaSource) {
const v = processFormulaSource(value); const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula( return new Formula(
() => this.evaluate().root(unrefFormulaSource(v)), () => this.evaluate().root(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible invertible
? undefined ? value =>
: value => Decimal.pow(value, unrefFormulaSource(v)) isVariableFormula(this)
? Decimal.root(value, Decimal.recip(unrefFormulaSource(v)))
: Decimal.ln(value).div(Decimal.ln(this.evaluate()).recip())
: undefined,
hasVariable
); );
} }
@ -846,7 +912,8 @@ export default class Formula {
public exp() { public exp() {
return new Formula( return new Formula(
() => this.evaluate().exp(), () => this.evaluate().exp(),
!this.invertible ? undefined : value => Decimal.ln(value) this.invertible ? value => Decimal.ln(value) : undefined,
this.hasVariable
); );
} }
@ -886,21 +953,21 @@ export default class Formula {
) { ) {
const heightValue = processFormulaSource(height); const heightValue = processFormulaSource(height);
const payloadValue = processFormulaSource(payload); const payloadValue = processFormulaSource(payload);
const { invertible, hasVariable } = calculateInvertibility(this, height, payload);
return new Formula( return new Formula(
() => () =>
this.evaluate().tetrate( this.evaluate().tetrate(
Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber(), Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber(),
unrefFormulaSource(payloadValue) unrefFormulaSource(payloadValue)
), ),
(heightValue instanceof Formula && !heightValue.invertible) || invertible
(payloadValue instanceof Formula && !payloadValue.invertible) || ? value =>
!this.invertible
? undefined
: value =>
Decimal.slog( Decimal.slog(
value, value,
Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber() Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber()
) )
: undefined,
hasVariable
); );
} }
@ -916,22 +983,22 @@ export default class Formula {
) { ) {
const heightValue = processFormulaSource(height); const heightValue = processFormulaSource(height);
const payloadValue = processFormulaSource(payload); const payloadValue = processFormulaSource(payload);
const { invertible, hasVariable } = calculateInvertibility(this, height, payload);
return new Formula( return new Formula(
() => () =>
this.evaluate().iteratedexp( this.evaluate().iteratedexp(
Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber(), Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber(),
new Decimal(unrefFormulaSource(payloadValue)) new Decimal(unrefFormulaSource(payloadValue))
), ),
(heightValue instanceof Formula && !heightValue.invertible) || invertible
(payloadValue instanceof Formula && !payloadValue.invertible) || ? value =>
!this.invertible
? undefined
: value =>
Decimal.iteratedlog( Decimal.iteratedlog(
value, value,
Math.E, Math.E,
Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber() Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber()
) )
: undefined,
hasVariable
); );
} }
@ -948,16 +1015,18 @@ export default class Formula {
public slog(base: FormulaSource = 10) { public slog(base: FormulaSource = 10) {
const baseValue = processFormulaSource(base); const baseValue = processFormulaSource(base);
const { invertible, hasVariable } = calculateInvertibility(this, base);
return new Formula( return new Formula(
() => () =>
this.evaluate().slog(Decimal.min(1e308, unrefFormulaSource(baseValue)).toNumber()), this.evaluate().slog(Decimal.min(1e308, unrefFormulaSource(baseValue)).toNumber()),
(baseValue instanceof Formula && !baseValue.invertible) || !this.invertible invertible
? undefined ? value =>
: value =>
Decimal.tetrate( Decimal.tetrate(
value, value,
Decimal.min(1e308, unrefFormulaSource(baseValue)).toNumber() Decimal.min(1e308, unrefFormulaSource(baseValue)).toNumber()
) )
: undefined,
hasVariable
); );
} }
@ -975,22 +1044,22 @@ export default class Formula {
public layeradd(diff: FormulaSource, base: FormulaSource) { public layeradd(diff: FormulaSource, base: FormulaSource) {
const diffValue = processFormulaSource(diff); const diffValue = processFormulaSource(diff);
const baseValue = processFormulaSource(base); const baseValue = processFormulaSource(base);
const { invertible, hasVariable } = calculateInvertibility(this, diff, base);
return new Formula( return new Formula(
() => () =>
this.evaluate().layeradd( this.evaluate().layeradd(
Decimal.min(1e308, unrefFormulaSource(diffValue)).toNumber(), Decimal.min(1e308, unrefFormulaSource(diffValue)).toNumber(),
unrefFormulaSource(baseValue) unrefFormulaSource(baseValue)
), ),
(diffValue instanceof Formula && !diffValue.invertible) || invertible
(diffValue instanceof Formula && !diffValue.invertible) || ? value =>
!this.invertible
? undefined
: value =>
Decimal.layeradd( Decimal.layeradd(
value, value,
Decimal.min(1e308, unrefFormulaSource(diffValue)).negate().toNumber(), Decimal.min(1e308, unrefFormulaSource(diffValue)).negate().toNumber(),
unrefFormulaSource(baseValue) unrefFormulaSource(baseValue)
) )
: undefined,
hasVariable
); );
} }
@ -999,7 +1068,8 @@ export default class Formula {
public lambertw() { public lambertw() {
return new Formula( return new Formula(
() => this.evaluate().lambertw(), () => this.evaluate().lambertw(),
!this.invertible ? undefined : value => Decimal.pow(Math.E, value).times(value) this.invertible ? value => Decimal.pow(Math.E, value).times(value) : undefined,
this.hasVariable
); );
} }
@ -1008,7 +1078,8 @@ export default class Formula {
public ssqrt() { public ssqrt() {
return new Formula( return new Formula(
() => this.evaluate().ssqrt(), () => this.evaluate().ssqrt(),
!this.invertible ? undefined : value => Decimal.tetrate(value, 2) this.invertible ? value => Decimal.tetrate(value, 2) : undefined,
this.hasVariable
); );
} }
@ -1031,7 +1102,8 @@ export default class Formula {
public sin() { public sin() {
return new Formula( return new Formula(
() => this.evaluate().sin(), () => this.evaluate().sin(),
!this.invertible ? undefined : value => Decimal.asin(value) this.invertible ? value => Decimal.asin(value) : undefined,
this.hasVariable
); );
} }
@ -1040,7 +1112,8 @@ export default class Formula {
public cos() { public cos() {
return new Formula( return new Formula(
() => this.evaluate().cos(), () => this.evaluate().cos(),
!this.invertible ? undefined : value => Decimal.acos(value) this.invertible ? value => Decimal.acos(value) : undefined,
this.hasVariable
); );
} }
@ -1049,7 +1122,8 @@ export default class Formula {
public tan() { public tan() {
return new Formula( return new Formula(
() => this.evaluate().tan(), () => this.evaluate().tan(),
!this.invertible ? undefined : value => Decimal.atan(value) this.invertible ? value => Decimal.atan(value) : undefined,
this.hasVariable
); );
} }
@ -1058,7 +1132,8 @@ export default class Formula {
public asin() { public asin() {
return new Formula( return new Formula(
() => this.evaluate().asin(), () => this.evaluate().asin(),
!this.invertible ? undefined : value => Decimal.sin(value) this.invertible ? value => Decimal.sin(value) : undefined,
this.hasVariable
); );
} }
@ -1067,7 +1142,8 @@ export default class Formula {
public acos() { public acos() {
return new Formula( return new Formula(
() => this.evaluate().acos(), () => this.evaluate().acos(),
!this.invertible ? undefined : value => Decimal.cos(value) this.invertible ? value => Decimal.cos(value) : undefined,
this.hasVariable
); );
} }
@ -1076,7 +1152,8 @@ export default class Formula {
public atan() { public atan() {
return new Formula( return new Formula(
() => this.evaluate().atan(), () => this.evaluate().atan(),
!this.invertible ? undefined : value => Decimal.tan(value) this.invertible ? value => Decimal.tan(value) : undefined,
this.hasVariable
); );
} }
@ -1085,7 +1162,8 @@ export default class Formula {
public sinh() { public sinh() {
return new Formula( return new Formula(
() => this.evaluate().sinh(), () => this.evaluate().sinh(),
!this.invertible ? undefined : value => Decimal.asinh(value) this.invertible ? value => Decimal.asinh(value) : undefined,
this.hasVariable
); );
} }
@ -1094,7 +1172,8 @@ export default class Formula {
public cosh() { public cosh() {
return new Formula( return new Formula(
() => this.evaluate().cosh(), () => this.evaluate().cosh(),
!this.invertible ? undefined : value => Decimal.acosh(value) this.invertible ? value => Decimal.acosh(value) : undefined,
this.hasVariable
); );
} }
@ -1103,7 +1182,8 @@ export default class Formula {
public tanh() { public tanh() {
return new Formula( return new Formula(
() => this.evaluate().tanh(), () => this.evaluate().tanh(),
!this.invertible ? undefined : value => Decimal.atanh(value) this.invertible ? value => Decimal.atanh(value) : undefined,
this.hasVariable
); );
} }
@ -1112,7 +1192,8 @@ export default class Formula {
public asinh() { public asinh() {
return new Formula( return new Formula(
() => this.evaluate().asinh(), () => this.evaluate().asinh(),
!this.invertible ? undefined : value => Decimal.sinh(value) this.invertible ? value => Decimal.sinh(value) : undefined,
this.hasVariable
); );
} }
@ -1121,7 +1202,8 @@ export default class Formula {
public acosh() { public acosh() {
return new Formula( return new Formula(
() => this.evaluate().acosh(), () => this.evaluate().acosh(),
!this.invertible ? undefined : value => Decimal.cosh(value) this.invertible ? value => Decimal.cosh(value) : undefined,
this.hasVariable
); );
} }
@ -1130,7 +1212,8 @@ export default class Formula {
public atanh() { public atanh() {
return new Formula( return new Formula(
() => this.evaluate().atanh(), () => this.evaluate().atanh(),
!this.invertible ? undefined : value => Decimal.tanh(value) this.invertible ? value => Decimal.tanh(value) : undefined,
this.hasVariable
); );
} }
} }

View file

@ -182,13 +182,12 @@ const nonInvertibleOneParamFunctionNames = [
["layeradd10"] ["layeradd10"]
] as const; ] as const;
const invertibleTwoParamFunctionNames = [["tetrate"]] as const; const invertibleTwoParamFunctionNames = [["tetrate"], ["layeradd"]] as const;
const nonInvertibleTwoParamFunctionNames = [ const nonInvertibleTwoParamFunctionNames = [
["clamp"], ["clamp"],
["iteratedexp"], ["iteratedexp"],
["iteratedlog"], ["iteratedlog"],
["layeradd"],
["pentate"] ["pentate"]
] as const; ] as const;
@ -361,5 +360,47 @@ describe("Creating Formulas", () => {
}); });
}); });
}); });
describe("Inverting calculates the value of the variable", () => {
let variable: Formula;
let constant: Formula;
beforeAll(() => {
variable = Formula.variable(2);
constant = Formula.constant(3);
});
invertibleOneParamFunctionNames.flat().forEach(name =>
describe(name, () => {
test(`${name}(var, const).invert()`, () => {
const formula = Formula[name](variable, constant);
const result = formula.evaluate();
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
});
test(`${name}(const, var).invert()`, () => {
const formula = Formula[name](constant, variable);
const result = formula.evaluate();
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
});
})
);
invertibleTwoParamFunctionNames.flat().forEach(name =>
describe(name, () => {
test(`${name}(var, const, const).invert()`, () => {
const formula = Formula[name](variable, constant, constant);
const result = formula.evaluate();
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
});
test(`${name}(const, var, const).invert()`, () => {
const formula = Formula[name](constant, variable, constant);
const result = formula.evaluate();
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
});
test(`${name}(const, const, var).invert()`, () => {
const formula = Formula[name](constant, constant, variable);
const result = formula.evaluate();
expect(formula.invert(result)).toSatisfy(compare_tolerance(2));
});
})
);
});
}); });
}); });