Support variable anywhere in formula

This commit is contained in:
thepaperpilot 2023-01-06 00:38:11 -06:00
parent 3c2d3a5cea
commit f3f9e7ea5b
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);
}
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 {
public readonly invertible: boolean;
public readonly hasVariable: boolean;
@ -533,7 +549,8 @@ export default class Formula {
public neg() {
return new Formula(
() => 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(value: FormulaSource) {
const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula(
() => this.evaluate().add(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible
? undefined
: value => Decimal.sub(value, unrefFormulaSource(v))
invertible
? value =>
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(value: FormulaSource) {
const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula(
() => this.evaluate().sub(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible
? undefined
: value => Decimal.add(value, unrefFormulaSource(v))
invertible
? value =>
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(value: FormulaSource) {
const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula(
() => this.evaluate().mul(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible
? undefined
: value => Decimal.div(value, unrefFormulaSource(v))
invertible
? value =>
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(value: FormulaSource) {
const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula(
() => this.evaluate().div(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible
? undefined
: value => Decimal.mul(value, unrefFormulaSource(v))
invertible
? value =>
Decimal.mul(
value,
isVariableFormula(this) ? unrefFormulaSource(v) : this.evaluate()
)
: undefined,
hasVariable
);
}
@ -674,7 +715,8 @@ export default class Formula {
public recip() {
return new Formula(
() => 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() {
return new Formula(
() => 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(value: FormulaSource) {
const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula(
() => this.evaluate().log(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible
? undefined
: value => Decimal.pow(unrefFormulaSource(v), value)
invertible
? 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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(value: FormulaSource) {
const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula(
() => this.evaluate().pow(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible
? undefined
: value => Decimal.root(value, unrefFormulaSource(v))
invertible
? value =>
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() {
return new Formula(
() => 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(value: FormulaSource) {
const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula(
() => this.evaluate().pow_base(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible
? undefined
: value => Decimal.root(unrefFormulaSource(v), value)
invertible
? 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(value: FormulaSource) {
const v = processFormulaSource(value);
const { invertible, hasVariable } = calculateInvertibility(this, value);
return new Formula(
() => this.evaluate().root(unrefFormulaSource(v)),
(v instanceof Formula && !v.invertible) || !this.invertible
? undefined
: value => Decimal.pow(value, unrefFormulaSource(v))
invertible
? value =>
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() {
return new Formula(
() => 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 payloadValue = processFormulaSource(payload);
const { invertible, hasVariable } = calculateInvertibility(this, height, payload);
return new Formula(
() =>
this.evaluate().tetrate(
Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber(),
unrefFormulaSource(payloadValue)
),
(heightValue instanceof Formula && !heightValue.invertible) ||
(payloadValue instanceof Formula && !payloadValue.invertible) ||
!this.invertible
? undefined
: value =>
invertible
? value =>
Decimal.slog(
value,
Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber()
)
: undefined,
hasVariable
);
}
@ -916,22 +983,22 @@ export default class Formula {
) {
const heightValue = processFormulaSource(height);
const payloadValue = processFormulaSource(payload);
const { invertible, hasVariable } = calculateInvertibility(this, height, payload);
return new Formula(
() =>
this.evaluate().iteratedexp(
Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber(),
new Decimal(unrefFormulaSource(payloadValue))
),
(heightValue instanceof Formula && !heightValue.invertible) ||
(payloadValue instanceof Formula && !payloadValue.invertible) ||
!this.invertible
? undefined
: value =>
invertible
? value =>
Decimal.iteratedlog(
value,
Math.E,
Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber()
)
: undefined,
hasVariable
);
}
@ -948,16 +1015,18 @@ export default class Formula {
public slog(base: FormulaSource = 10) {
const baseValue = processFormulaSource(base);
const { invertible, hasVariable } = calculateInvertibility(this, base);
return new Formula(
() =>
this.evaluate().slog(Decimal.min(1e308, unrefFormulaSource(baseValue)).toNumber()),
(baseValue instanceof Formula && !baseValue.invertible) || !this.invertible
? undefined
: value =>
invertible
? value =>
Decimal.tetrate(
value,
Decimal.min(1e308, unrefFormulaSource(baseValue)).toNumber()
)
: undefined,
hasVariable
);
}
@ -975,22 +1044,22 @@ export default class Formula {
public layeradd(diff: FormulaSource, base: FormulaSource) {
const diffValue = processFormulaSource(diff);
const baseValue = processFormulaSource(base);
const { invertible, hasVariable } = calculateInvertibility(this, diff, base);
return new Formula(
() =>
this.evaluate().layeradd(
Decimal.min(1e308, unrefFormulaSource(diffValue)).toNumber(),
unrefFormulaSource(baseValue)
),
(diffValue instanceof Formula && !diffValue.invertible) ||
(diffValue instanceof Formula && !diffValue.invertible) ||
!this.invertible
? undefined
: value =>
invertible
? value =>
Decimal.layeradd(
value,
Decimal.min(1e308, unrefFormulaSource(diffValue)).negate().toNumber(),
unrefFormulaSource(baseValue)
)
: undefined,
hasVariable
);
}
@ -999,7 +1068,8 @@ export default class Formula {
public lambertw() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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() {
return new Formula(
() => 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"]
] as const;
const invertibleTwoParamFunctionNames = [["tetrate"]] as const;
const invertibleTwoParamFunctionNames = [["tetrate"], ["layeradd"]] as const;
const nonInvertibleTwoParamFunctionNames = [
["clamp"],
["iteratedexp"],
["iteratedlog"],
["layeradd"],
["pentate"]
] 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));
});
})
);
});
});
});