Fixing more tests with integral rework

This commit is contained in:
thepaperpilot 2023-04-01 23:42:12 -05:00
parent a91efffd5c
commit 6115b6687d
6 changed files with 264 additions and 352 deletions

View file

@ -4,7 +4,11 @@ import { createResource, trackBest, trackOOMPS, trackTotal } from "features/reso
import type { GenericTree } from "features/trees/tree"; import type { GenericTree } from "features/trees/tree";
import { branchedResetPropagation, createTree } from "features/trees/tree"; import { branchedResetPropagation, createTree } from "features/trees/tree";
import { globalBus } from "game/events"; import { globalBus } from "game/events";
import Formula, { calculateCost, calculateMaxAffordable } from "game/formulas/formulas"; import Formula, {
calculateCost,
calculateMaxAffordable,
findNonInvertible
} from "game/formulas/formulas";
import type { BaseLayer, GenericLayer } from "game/layers"; import type { BaseLayer, GenericLayer } from "game/layers";
import { createLayer } from "game/layers"; import { createLayer } from "game/layers";
import type { Player } from "game/player"; import type { Player } from "game/player";
@ -18,6 +22,7 @@ import prestige from "./layers/prestige";
window.Formula = Formula; window.Formula = Formula;
window.calculateMaxAffordable = calculateMaxAffordable; window.calculateMaxAffordable = calculateMaxAffordable;
window.calculateCost = calculateCost; window.calculateCost = calculateCost;
window.findNonInvertible = findNonInvertible;
window.unref = unref; window.unref = unref;
window.ref = ref; window.ref = ref;
window.createResource = createResource; window.createResource = createResource;

View file

@ -1,7 +1,8 @@
import { Resource } from "features/resources/resource"; import { Resource } from "features/resources/resource";
import Decimal, { DecimalSource } from "util/bignum"; import Decimal, { DecimalSource } from "util/bignum";
import { Computable, convertComputable, ProcessedComputable } from "util/computed"; import { Computable, convertComputable, ProcessedComputable } from "util/computed";
import { computed, ComputedRef, Ref, ref, unref } from "vue"; import { computed, ComputedRef, ref, unref } from "vue";
import * as ops from "./operations";
import type { import type {
EvaluateFunction, EvaluateFunction,
FormulaOptions, FormulaOptions,
@ -18,7 +19,6 @@ import type {
SubstitutionFunction, SubstitutionFunction,
SubstitutionStack SubstitutionStack
} from "./types"; } from "./types";
import * as ops from "./operations";
export function hasVariable(value: FormulaSource): value is InvertibleFormula { export function hasVariable(value: FormulaSource): value is InvertibleFormula {
return value instanceof Formula && value.hasVariable(); return value instanceof Formula && value.hasVariable();
@ -32,13 +32,6 @@ function integrateVariable(this: GenericFormula) {
return Formula.pow(this, 2).div(2); return Formula.pow(this, 2).div(2);
} }
function integrateVariableInner(this: GenericFormula, variable?: DecimalSource) {
if (variable == null && this.innermostVariable == null) {
throw new Error("Cannot integrate non-existent variable");
}
return variable ?? unref(this.innermostVariable);
}
/** /**
* A class that can be used for cost/goal functions. It can be evaluated similar to a cost function, but also provides extra features for supported formulas. For example, a lot of math functions can be inverted. * A class that can be used for cost/goal functions. It can be evaluated similar to a cost function, but also provides extra features for supported formulas. For example, a lot of math functions can be inverted.
* Typically, the use of these extra features is to support cost/goal functions that have multiple levels purchased/completed at once efficiently. * Typically, the use of these extra features is to support cost/goal functions that have multiple levels purchased/completed at once efficiently.
@ -53,7 +46,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
private readonly internalIntegrate: IntegrateFunction<T> | undefined; private readonly internalIntegrate: IntegrateFunction<T> | undefined;
private readonly internalIntegrateInner: IntegrateFunction<T> | undefined; private readonly internalIntegrateInner: IntegrateFunction<T> | undefined;
private readonly applySubstitution: SubstitutionFunction<T> | undefined; private readonly applySubstitution: SubstitutionFunction<T> | undefined;
private readonly internalHasVariable: boolean; private readonly internalVariables: number;
public readonly innermostVariable: ProcessedComputable<DecimalSource> | undefined; public readonly innermostVariable: ProcessedComputable<DecimalSource> | undefined;
@ -69,7 +62,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
readonlyProperties = this.setupFormula(options); readonlyProperties = this.setupFormula(options);
} }
this.inputs = readonlyProperties.inputs; this.inputs = readonlyProperties.inputs;
this.internalHasVariable = readonlyProperties.internalHasVariable; this.internalVariables = readonlyProperties.internalVariables;
this.innermostVariable = readonlyProperties.innermostVariable; this.innermostVariable = readonlyProperties.innermostVariable;
this.internalEvaluate = readonlyProperties.internalEvaluate; this.internalEvaluate = readonlyProperties.internalEvaluate;
this.internalInvert = readonlyProperties.internalInvert; this.internalInvert = readonlyProperties.internalInvert;
@ -85,10 +78,9 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
}): InternalFormulaProperties<T> { }): InternalFormulaProperties<T> {
return { return {
inputs: [variable] as T, inputs: [variable] as T,
internalHasVariable: true, internalVariables: 1,
innermostVariable: variable, innermostVariable: variable,
internalIntegrate: integrateVariable as unknown as IntegrateFunction<T>, internalIntegrate: integrateVariable,
internalIntegrateInner: integrateVariableInner as unknown as IntegrateFunction<T>,
applySubstitution: ops.passthrough as unknown as SubstitutionFunction<T> applySubstitution: ops.passthrough as unknown as SubstitutionFunction<T>
}; };
} }
@ -99,68 +91,49 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
} }
return { return {
inputs: inputs as T, inputs: inputs as T,
internalHasVariable: false internalVariables: 0
}; };
} }
private setupFormula(options: GeneralFormulaOptions<T>): InternalFormulaProperties<T> { private setupFormula(options: GeneralFormulaOptions<T>): InternalFormulaProperties<T> {
const { const { inputs, evaluate, invert, integrate, integrateInner, applySubstitution } = options;
inputs, const numVariables = inputs.reduce<number>(
evaluate, (acc, input) => acc + (input instanceof Formula ? input.internalVariables : 0),
invert, 0
integrate, );
integrateInner,
applySubstitution,
hasVariable
} = options;
if (invert == null && hasVariable) {
throw new Error(
"A formula cannot be marked as having a variable if it is not invertible"
);
}
const numVariables = inputs.filter(
input => input instanceof Formula && input.hasVariable()
).length;
const variable = inputs.find(input => input instanceof Formula && input.hasVariable()) as const variable = inputs.find(input => input instanceof Formula && input.hasVariable()) as
| GenericFormula | GenericFormula
| undefined; | undefined;
const internalHasVariable = const innermostVariable = numVariables === 1 ? variable?.innermostVariable : undefined;
numVariables === 1 || (numVariables === 0 && hasVariable === true);
const innermostVariable = internalHasVariable ? variable?.innermostVariable : undefined;
const internalInvert = internalHasVariable && variable?.isInvertible() ? invert : undefined;
return { return {
inputs, inputs,
internalEvaluate: evaluate, internalEvaluate: evaluate,
internalInvert, internalInvert: invert,
internalIntegrate: integrate, internalIntegrate: integrate,
internalIntegrateInner: integrateInner, internalIntegrateInner: integrateInner,
applySubstitution, applySubstitution,
innermostVariable, innermostVariable,
internalHasVariable internalVariables: numVariables
}; };
} }
private calculateConstantOfIntegration() { private calculateConstantOfIntegration() {
// Calculate C based on the knowledge that at 1 purchase, the total sum would be the cost of that one purchase // Calculate C based on the knowledge that at x=1, the integral should be the average between f(0) and f(1)
const integral = this.getIntegralFormula().evaluate(1); const integral = this.getIntegralFormula().evaluate(1);
const actualCost = this.evaluate(0); const actualCost = Decimal.add(this.evaluate(0), this.evaluate(1)).div(2);
return Decimal.sub(actualCost, integral); return Decimal.sub(actualCost, integral);
} }
/** Type predicate that this formula can be inverted. */ /** Type predicate that this formula can be inverted. */
isInvertible(): this is InvertibleFormula { isInvertible(): this is InvertibleFormula {
return ( return this.hasVariable() && (this.internalInvert != null || this.internalEvaluate == null);
this.internalHasVariable &&
(this.internalInvert != null || this.internalEvaluate == null)
);
} }
/** Type predicate that this formula can be integrated. */ /** Type predicate that this formula can be integrated. */
isIntegrable(): this is IntegrableFormula { isIntegrable(): this is IntegrableFormula {
return this.internalHasVariable && this.internalIntegrate != null; return this.hasVariable() && this.internalIntegrate != null;
} }
/** Type predicate that this formula has an integral function that can be inverted. */ /** Type predicate that this formula has an integral function that can be inverted. */
@ -173,7 +146,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
/** Whether or not this formula has a singular variable inside it, which can be accessed via {@link innermostVariable}. */ /** Whether or not this formula has a singular variable inside it, which can be accessed via {@link innermostVariable}. */
hasVariable(): boolean { hasVariable(): boolean {
return this.internalHasVariable; return this.internalVariables === 1;
} }
/** /**
@ -188,7 +161,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
unrefFormulaSource(input, variable) unrefFormulaSource(input, variable)
) as GuardedFormulasToDecimals<T>) ) as GuardedFormulasToDecimals<T>)
) ?? ) ??
(this.internalHasVariable ? variable : null) ?? (this.hasVariable() ? variable : null) ??
unrefFormulaSource(this.inputs[0]) unrefFormulaSource(this.inputs[0])
); );
} }
@ -199,9 +172,9 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
* @see {@link isInvertible} * @see {@link isInvertible}
*/ */
invert(value: DecimalSource): DecimalSource { invert(value: DecimalSource): DecimalSource {
if (this.internalInvert) { if (this.internalInvert && this.hasVariable()) {
return this.internalInvert.call(this, value, ...this.inputs); return this.internalInvert.call(this, value, ...this.inputs);
} else if (this.inputs.length === 1 && this.internalHasVariable) { } else if (this.inputs.length === 1 && this.hasVariable()) {
return value; return value;
} }
throw new Error("Cannot invert non-invertible formula"); throw new Error("Cannot invert non-invertible formula");
@ -228,7 +201,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
* @see {@link isIntegralInvertible} * @see {@link isIntegralInvertible}
*/ */
invertIntegral(value: DecimalSource): DecimalSource { invertIntegral(value: DecimalSource): DecimalSource {
if (this.integralFormula?.isInvertible()) { if (!this.isIntegrable() || !this.getIntegralFormula().isInvertible()) {
throw new Error("Cannot invert integral of formula without invertible integral"); throw new Error("Cannot invert integral of formula without invertible integral");
} }
return this.getIntegralFormula().invert(value); return this.getIntegralFormula().invert(value);
@ -236,24 +209,12 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
/** /**
* Get a formula that will evaluate to the integral of this formula. May also be invertible. * Get a formula that will evaluate to the integral of this formula. May also be invertible.
* @param variable The variable that will be used to evaluate this integral at a given x value * @param stack For nested formulas, a stack of operations that occur outside the complex operation.
* @param stack For nested formulas, a stack of operations that occur outside the complex operation
*/ */
getIntegralFormula( getIntegralFormula(stack?: SubstitutionStack): GenericFormula {
variable?: ProcessedComputable<DecimalSource>, if (this.integralFormula != null) {
stack?: SubstitutionStack
): GenericFormula {
if (variable == null && this.integralFormula != null) {
return this.integralFormula; return this.integralFormula;
} }
let formula;
const variablePresent = variable != null;
if (variable == null) {
variable = this.innermostVariable;
if (variable == null) {
throw new Error("Cannot integrate formula without variable");
}
}
if (stack == null) { if (stack == null) {
// "Outer" part of the formula // "Outer" part of the formula
if (this.applySubstitution == null) { if (this.applySubstitution == null) {
@ -262,21 +223,24 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
if (this.internalIntegrate == null) { if (this.internalIntegrate == null) {
throw new Error("Cannot integrate formula with non-integrable operation"); throw new Error("Cannot integrate formula with non-integrable operation");
} }
let value = this.internalIntegrate.call(this, variable, stack, ...this.inputs); let value = this.internalIntegrate.call(this, stack, ...this.inputs);
stack.forEach(func => (value = func(value))); stack.forEach(func => (value = func(value)));
formula = value; this.integralFormula = value;
} else { } else {
// Continue digging into the formula // Continue digging into the formula
if (this.internalIntegrate) { if (this.internalIntegrate) {
formula = this.internalIntegrate.call( this.integralFormula = this.internalIntegrate.call(
this, this,
variable,
undefined, undefined,
...this.inputs ...this.inputs
); );
} else if (this.inputs.length === 1 && this.internalHasVariable) { } else if (
this.inputs.length === 1 &&
this.internalEvaluate == null &&
this.hasVariable()
) {
// eslint-disable-next-line @typescript-eslint/no-this-alias // eslint-disable-next-line @typescript-eslint/no-this-alias
formula = this; this.integralFormula = this;
} else { } else {
throw new Error("Cannot integrate formula without variable"); throw new Error("Cannot integrate formula without variable");
} }
@ -291,20 +255,25 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
this.applySubstitution!.call(this, variable, ...this.inputs) this.applySubstitution!.call(this, variable, ...this.inputs)
); );
if (this.internalIntegrateInner) { if (this.internalIntegrateInner) {
formula = this.internalIntegrateInner.call(this, variable, stack, ...this.inputs); this.integralFormula = this.internalIntegrateInner.call(
this,
stack,
...this.inputs
);
} else if (this.internalIntegrate) { } else if (this.internalIntegrate) {
formula = this.internalIntegrate.call(this, variable, stack, ...this.inputs); this.integralFormula = this.internalIntegrate.call(this, stack, ...this.inputs);
} else if (this.inputs.length === 1 && this.internalHasVariable) { } else if (
this.inputs.length === 1 &&
this.internalEvaluate == null &&
this.hasVariable()
) {
// eslint-disable-next-line @typescript-eslint/no-this-alias // eslint-disable-next-line @typescript-eslint/no-this-alias
formula = this; this.integralFormula = this;
} else { } else {
throw new Error("Cannot integrate formula without variable"); throw new Error("Cannot integrate formula without variable");
} }
} }
if (!variablePresent) { return this.integralFormula;
this.integralFormula = formula;
}
return formula;
} }
/** /**
@ -324,7 +293,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
this.internalEvaluate === other.internalEvaluate && this.internalEvaluate === other.internalEvaluate &&
this.internalInvert === other.internalInvert && this.internalInvert === other.internalInvert &&
this.internalIntegrate === other.internalIntegrate && this.internalIntegrate === other.internalIntegrate &&
this.internalHasVariable === other.internalHasVariable this.internalVariables === other.internalVariables
); );
} }
@ -556,17 +525,8 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
public static reciprocal = Formula.recip; public static reciprocal = Formula.recip;
public static reciprocate = Formula.recip; public static reciprocate = Formula.recip;
public static max(value: FormulaSource, other: FormulaSource): GenericFormula { // TODO these functions should ostensibly be integrable, and the integrals should be invertible
return new Formula({ public static max = ops.createPassthroughBinaryFormula(Decimal.max);
inputs: [value, other],
evaluate: Decimal.max,
invert: ops.passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource
});
}
public static min = ops.createPassthroughBinaryFormula(Decimal.min); public static min = ops.createPassthroughBinaryFormula(Decimal.min);
public static minabs = ops.createPassthroughBinaryFormula(Decimal.minabs); public static minabs = ops.createPassthroughBinaryFormula(Decimal.minabs);
public static maxabs = ops.createPassthroughBinaryFormula(Decimal.maxabs); public static maxabs = ops.createPassthroughBinaryFormula(Decimal.maxabs);
@ -1356,6 +1316,24 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
} }
} }
/**
* Utility for recursively searching through a formula for the cause of non-invertibility.
* @param formula The formula to search for a non-invertible formula within
*/
export function findNonInvertible(formula: GenericFormula): GenericFormula | null {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (formula.internalInvert == null && formula.internalEvaluate != null) {
return formula;
}
for (const input of formula.inputs) {
if (hasVariable(input)) {
return findNonInvertible(input);
}
}
return null;
}
/** /**
* Utility for calculating the maximum amount of purchases possible with a given formula and resource. If {@ref spendResources} is changed to false, the calculation will be much faster with higher numbers. * Utility for calculating the maximum amount of purchases possible with a given formula and resource. If {@ref spendResources} is changed to false, the calculation will be much faster with higher numbers.
* @param formula The formula to use for calculating buy max from * @param formula The formula to use for calculating buy max from

View file

@ -1,8 +1,9 @@
import Decimal, { DecimalSource } from "util/bignum"; import Decimal, { DecimalSource } from "util/bignum";
import { Ref } from "vue";
import Formula, { hasVariable, unrefFormulaSource } from "./formulas"; import Formula, { hasVariable, unrefFormulaSource } from "./formulas";
import { FormulaSource, GenericFormula, InvertFunction, SubstitutionStack } from "./types"; import { FormulaSource, GenericFormula, InvertFunction, SubstitutionStack } from "./types";
const ln10 = Decimal.ln(10);
export function passthrough<T extends GenericFormula | DecimalSource>(value: T): T { export function passthrough<T extends GenericFormula | DecimalSource>(value: T): T {
return value; return value;
} }
@ -14,13 +15,9 @@ export function invertNeg(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateNeg( export function integrateNeg(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return Formula.neg(lhs.getIntegralFormula(variable, stack)); return Formula.neg(lhs.getIntegralFormula(stack));
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
} }
@ -38,33 +35,27 @@ export function invertAdd(value: DecimalSource, lhs: FormulaSource, rhs: Formula
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateAdd( export function integrateAdd(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource,
rhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.times(rhs, variable ?? lhs.innermostVariable ?? 0).add(x); return Formula.times(rhs, lhs.innermostVariable ?? 0).add(x);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
const x = rhs.getIntegralFormula(variable, stack); const x = rhs.getIntegralFormula(stack);
return Formula.times(lhs, variable ?? rhs.innermostVariable ?? 0).add(x); return Formula.times(lhs, rhs.innermostVariable ?? 0).add(x);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
} }
export function integrateInnerAdd( export function integrateInnerAdd(
variable: Ref<DecimalSource>,
stack: SubstitutionStack, stack: SubstitutionStack,
lhs: FormulaSource, lhs: FormulaSource,
rhs: FormulaSource rhs: FormulaSource
) { ) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.add(x, rhs); return Formula.add(x, rhs);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
const x = rhs.getIntegralFormula(variable, stack); const x = rhs.getIntegralFormula(stack);
return Formula.add(x, lhs); return Formula.add(x, lhs);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -79,33 +70,27 @@ export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: Formula
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateSub( export function integrateSub(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource,
rhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.sub(x, Formula.times(rhs, variable ?? lhs.innermostVariable ?? 0)); return Formula.sub(x, Formula.times(rhs, lhs.innermostVariable ?? 0));
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
const x = rhs.getIntegralFormula(variable, stack); const x = rhs.getIntegralFormula(stack);
return Formula.times(lhs, variable ?? rhs.innermostVariable ?? 0).sub(x); return Formula.times(lhs, rhs.innermostVariable ?? 0).sub(x);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
} }
export function integrateInnerSub( export function integrateInnerSub(
variable: Ref<DecimalSource>,
stack: SubstitutionStack, stack: SubstitutionStack,
lhs: FormulaSource, lhs: FormulaSource,
rhs: FormulaSource rhs: FormulaSource
) { ) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.sub(x, rhs); return Formula.sub(x, rhs);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
const x = rhs.getIntegralFormula(variable, stack); const x = rhs.getIntegralFormula(stack);
return Formula.sub(x, lhs); return Formula.sub(x, lhs);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -120,17 +105,12 @@ export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: Formula
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateMul( export function integrateMul(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource,
rhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.times(x, rhs); return Formula.times(x, rhs);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
const x = rhs.getIntegralFormula(variable, stack); const x = rhs.getIntegralFormula(stack);
return Formula.times(x, lhs); return Formula.times(x, lhs);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -158,17 +138,12 @@ export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: Formula
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateDiv( export function integrateDiv(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource,
rhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.div(x, rhs); return Formula.div(x, rhs);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
const x = rhs.getIntegralFormula(variable, stack); const x = rhs.getIntegralFormula(stack);
return Formula.div(lhs, x); return Formula.div(lhs, x);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -194,13 +169,9 @@ export function invertRecip(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateRecip( export function integrateRecip(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.ln(x); return Formula.ln(x);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -213,14 +184,25 @@ export function invertLog10(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateLog10( function internalIntegrateLog10(lhs: DecimalSource) {
variable: Ref<DecimalSource>, return Decimal.ln(lhs).sub(1).times(lhs).div(ln10);
stack: SubstitutionStack, }
lhs: FormulaSource
) { function internalInvertIntegralLog10(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const numerator = ln10.times(value);
return Formula.ln(x).sub(1).times(x).div(Formula.ln(10)); return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
}
throw new Error("Could not invert due to no input being a variable");
}
export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) {
return new Formula({
inputs: [lhs.getIntegralFormula(stack)],
evaluate: internalIntegrateLog10,
invert: internalInvertIntegralLog10
});
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
} }
@ -234,15 +216,25 @@ export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: Formula
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateLog( function internalIntegrateLog(lhs: DecimalSource, rhs: DecimalSource) {
variable: Ref<DecimalSource>, return Decimal.ln(lhs).sub(1).times(lhs).div(Decimal.ln(rhs));
stack: SubstitutionStack, }
lhs: FormulaSource,
rhs: FormulaSource function internalInvertIntegralLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const numerator = Decimal.ln(unrefFormulaSource(rhs)).times(value);
return Formula.ln(x).sub(1).times(x).div(Formula.ln(rhs)); return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
}
throw new Error("Could not invert due to no input being a variable");
}
export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
if (hasVariable(lhs)) {
return new Formula({
inputs: [lhs.getIntegralFormula(stack), rhs],
evaluate: internalIntegrateLog,
invert: internalInvertIntegralLog
});
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
} }
@ -254,14 +246,25 @@ export function invertLog2(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateLog2( function internalIntegrateLog2(lhs: DecimalSource) {
variable: Ref<DecimalSource>, return Decimal.ln(lhs).sub(1).times(lhs).div(Decimal.ln(2));
stack: SubstitutionStack, }
lhs: FormulaSource
) { function internalInvertIntegralLog2(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const numerator = Decimal.ln(2).times(value);
return Formula.ln(x).sub(1).times(x).div(Formula.ln(2)); return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
}
throw new Error("Could not invert due to no input being a variable");
}
export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) {
return new Formula({
inputs: [lhs.getIntegralFormula(stack)],
evaluate: internalIntegrateLog2,
invert: internalInvertIntegralLog2
});
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
} }
@ -273,14 +276,24 @@ export function invertLn(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateLn( function internalIntegrateLn(lhs: DecimalSource) {
variable: Ref<DecimalSource>, return Decimal.ln(lhs).sub(1).times(lhs);
stack: SubstitutionStack, }
lhs: FormulaSource
) { function internalInvertIntegralLn(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); return lhs.invert(Decimal.div(value, Decimal.div(value, Math.E).lambertw()));
return Formula.ln(x).sub(1).times(x); }
throw new Error("Could not invert due to no input being a variable");
}
export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) {
return new Formula({
inputs: [lhs.getIntegralFormula(stack)],
evaluate: internalIntegrateLn,
invert: internalInvertIntegralLn
});
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
} }
@ -294,18 +307,13 @@ export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: Formula
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integratePow( export function integratePow(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource,
rhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
const pow = Formula.add(rhs, 1); const pow = Formula.add(rhs, 1);
return Formula.pow(x, pow).div(pow); return Formula.pow(x, pow).div(pow);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
const x = rhs.getIntegralFormula(variable, stack); const x = rhs.getIntegralFormula(stack);
return Formula.pow(lhs, x).div(Formula.ln(lhs)); return Formula.pow(lhs, x).div(Formula.ln(lhs));
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -318,14 +326,24 @@ export function invertPow10(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integratePow10( function internalIntegratePow10(lhs: DecimalSource) {
variable: Ref<DecimalSource>, return Decimal.pow10(lhs).div(Decimal.ln(lhs));
stack: SubstitutionStack, }
lhs: FormulaSource
) { function internalInvertIntegralPow10(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); return lhs.invert(ln10.times(value).ln().div(ln10));
return Formula.ln(x).sub(1).times(x).div(Decimal.ln(10)); }
throw new Error("Could not invert due to no input being a variable");
}
export function integratePow10(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) {
return new Formula({
inputs: [lhs.getIntegralFormula(stack)],
evaluate: internalIntegratePow10,
invert: internalInvertIntegralPow10
});
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
} }
@ -339,17 +357,12 @@ export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: For
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integratePowBase( export function integratePowBase(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource,
rhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.pow(rhs, x).div(Formula.ln(rhs)); return Formula.pow(rhs, x).div(Formula.ln(rhs));
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
const x = rhs.getIntegralFormula(variable, stack); const x = rhs.getIntegralFormula(stack);
const denominator = Formula.add(lhs, 1); const denominator = Formula.add(lhs, 1);
return Formula.pow(x, denominator).div(denominator); return Formula.pow(x, denominator).div(denominator);
} }
@ -365,14 +378,9 @@ export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: Formul
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateRoot( export function integrateRoot(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource,
rhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.pow(x, Formula.recip(rhs).add(1)).times(rhs).div(Formula.add(rhs, 1)); return Formula.pow(x, Formula.recip(rhs).add(1)).times(rhs).div(Formula.add(rhs, 1));
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -385,13 +393,9 @@ export function invertExp(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateExp( export function integrateExp(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.exp(x); return Formula.exp(x);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -520,13 +524,9 @@ export function invertSin(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateSin( export function integrateSin(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.cos(x).neg(); return Formula.cos(x).neg();
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -539,13 +539,9 @@ export function invertCos(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateCos( export function integrateCos(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.sin(x); return Formula.sin(x);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -558,13 +554,9 @@ export function invertTan(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateTan( export function integrateTan(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.cos(x).ln().neg(); return Formula.cos(x).ln().neg();
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -577,13 +569,9 @@ export function invertAsin(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateAsin( export function integrateAsin(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.asin(x) return Formula.asin(x)
.times(x) .times(x)
.add(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2)))); .add(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
@ -598,13 +586,9 @@ export function invertAcos(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateAcos( export function integrateAcos(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.acos(x) return Formula.acos(x)
.times(x) .times(x)
.sub(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2)))); .sub(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
@ -619,13 +603,9 @@ export function invertAtan(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateAtan( export function integrateAtan(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.atan(x) return Formula.atan(x)
.times(x) .times(x)
.sub(Formula.ln(Formula.pow(x, 2).add(1)).div(2)); .sub(Formula.ln(Formula.pow(x, 2).add(1)).div(2));
@ -640,13 +620,9 @@ export function invertSinh(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateSinh( export function integrateSinh(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.cosh(x); return Formula.cosh(x);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -659,13 +635,9 @@ export function invertCosh(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateCosh( export function integrateCosh(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.sinh(x); return Formula.sinh(x);
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -678,13 +650,9 @@ export function invertTanh(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateTanh( export function integrateTanh(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.cosh(x).ln(); return Formula.cosh(x).ln();
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -697,13 +665,9 @@ export function invertAsinh(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateAsinh( export function integrateAsinh(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.asinh(x).times(x).sub(Formula.pow(x, 2).add(1).sqrt()); return Formula.asinh(x).times(x).sub(Formula.pow(x, 2).add(1).sqrt());
} }
throw new Error("Could not integrate due to no input being a variable"); throw new Error("Could not integrate due to no input being a variable");
@ -716,13 +680,9 @@ export function invertAcosh(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateAcosh( export function integrateAcosh(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.acosh(x) return Formula.acosh(x)
.times(x) .times(x)
.sub(Formula.add(x, 1).sqrt().times(Formula.sub(x, 1).sqrt())); .sub(Formula.add(x, 1).sqrt().times(Formula.sub(x, 1).sqrt()));
@ -737,13 +697,9 @@ export function invertAtanh(value: DecimalSource, lhs: FormulaSource) {
throw new Error("Could not invert due to no input being a variable"); throw new Error("Could not invert due to no input being a variable");
} }
export function integrateAtanh( export function integrateAtanh(stack: SubstitutionStack, lhs: FormulaSource) {
variable: Ref<DecimalSource>,
stack: SubstitutionStack,
lhs: FormulaSource
) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
const x = lhs.getIntegralFormula(variable, stack); const x = lhs.getIntegralFormula(stack);
return Formula.atanh(x) return Formula.atanh(x)
.times(x) .times(x)
.add(Formula.sub(1, Formula.pow(x, 2)).ln().div(2)); .add(Formula.sub(1, Formula.pow(x, 2)).ln().div(2));

View file

@ -22,7 +22,6 @@ type EvaluateFunction<T> = (
type InvertFunction<T> = (this: Formula<T>, value: DecimalSource, ...inputs: T) => DecimalSource; type InvertFunction<T> = (this: Formula<T>, value: DecimalSource, ...inputs: T) => DecimalSource;
type IntegrateFunction<T> = ( type IntegrateFunction<T> = (
this: Formula<T>, this: Formula<T>,
variable: Ref<DecimalSource>,
stack: SubstitutionStack | undefined, stack: SubstitutionStack | undefined,
...inputs: T ...inputs: T
) => GenericFormula; ) => GenericFormula;
@ -43,7 +42,6 @@ type GeneralFormulaOptions<T extends [FormulaSource] | FormulaSource[]> = {
integrate?: IntegrateFunction<T>; integrate?: IntegrateFunction<T>;
integrateInner?: IntegrateFunction<T>; integrateInner?: IntegrateFunction<T>;
applySubstitution?: SubstitutionFunction<T>; applySubstitution?: SubstitutionFunction<T>;
hasVariable?: boolean;
}; };
type FormulaOptions<T extends [FormulaSource] | FormulaSource[]> = type FormulaOptions<T extends [FormulaSource] | FormulaSource[]> =
| VariableFormulaOptions | VariableFormulaOptions
@ -52,7 +50,7 @@ type FormulaOptions<T extends [FormulaSource] | FormulaSource[]> =
type InternalFormulaProperties<T extends [FormulaSource] | FormulaSource[]> = { type InternalFormulaProperties<T extends [FormulaSource] | FormulaSource[]> = {
inputs: T; inputs: T;
internalHasVariable: boolean; internalVariables: number;
internalEvaluate?: EvaluateFunction<T>; internalEvaluate?: EvaluateFunction<T>;
internalInvert?: InvertFunction<T>; internalInvert?: InvertFunction<T>;
internalIntegrate?: IntegrateFunction<T>; internalIntegrate?: IntegrateFunction<T>;

View file

@ -96,21 +96,21 @@ const invertibleIntegralZeroPramFunctionNames = [
"sqr", "sqr",
"sqrt", "sqrt",
"cube", "cube",
"cbrt" "cbrt",
] as const;
const nonInvertibleIntegralZeroPramFunctionNames = [
...nonIntegrableZeroParamFunctionNames,
"neg", "neg",
"exp", "exp",
"sin", "sin",
"cos", "cos",
"tan", "tan",
"sinh",
"cosh",
"tanh"
] as const;
const nonInvertibleIntegralZeroPramFunctionNames = [
...nonIntegrableZeroParamFunctionNames,
"asin", "asin",
"acos", "acos",
"atan", "atan",
"sinh",
"cosh",
"tanh",
"asinh", "asinh",
"acosh", "acosh",
"atanh" "atanh"
@ -493,8 +493,8 @@ describe("Integrating", () => {
test("variable.evaluateIntegral() calculates correctly", () => test("variable.evaluateIntegral() calculates correctly", () =>
expect(variable.evaluateIntegral()).compare_tolerance(Decimal.pow(10, 2).div(2))); expect(variable.evaluateIntegral()).compare_tolerance(Decimal.pow(10, 2).div(2)));
test("evaluateIntegral(variable) overrides variable value", () => test("variable.evaluateIntegral(variable) overrides variable value", () =>
expect(variable.add(10).evaluateIntegral(20)).compare_tolerance(400)); expect(variable.evaluateIntegral(20)).compare_tolerance(Decimal.pow(20, 2).div(2)));
describe("Integrable functions marked as such", () => { describe("Integrable functions marked as such", () => {
function checkFormula(formula: GenericFormula) { function checkFormula(formula: GenericFormula) {
@ -668,32 +668,13 @@ describe("Inverting integrals", () => {
test("Inverting integral of nested formulas", () => { test("Inverting integral of nested formulas", () => {
const formula = Formula.add(variable, constant).times(constant).pow(2).times(30); const formula = Formula.add(variable, constant).times(constant).pow(2).times(30);
expect(formula.invertIntegral(formula.evaluateIntegral())).compare_tolerance(10); expect(formula.invertIntegral(formula.evaluateIntegral())).compare_tolerance(10, 0.01);
}); });
test("Inverting integral of nested complex formulas", () => { test("Inverting integral of nested complex formulas", () => {
const formula = Formula.pow(1.05, variable).times(100).pow(0.5); const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
expect(() => formula.invertIntegral(100)).toThrow(); expect(() => formula.invertIntegral(100)).toThrow();
}); });
describe("Inverting integral pass-throughs", () => {
test("max", () =>
expect(Formula.max(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("min", () =>
expect(Formula.min(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("minabs", () =>
expect(Formula.minabs(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("maxabs", () =>
expect(Formula.maxabs(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("clampMax", () =>
expect(Formula.clampMax(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("clampMin", () =>
expect(Formula.clampMin(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("clamp", () =>
expect(
Formula.clamp(variable, constant, constant).invertIntegral(10)
).compare_tolerance(10));
});
}); });
describe("Step-wise", () => { describe("Step-wise", () => {
@ -914,8 +895,7 @@ describe("Custom Formulas", () => {
new Formula({ new Formula({
inputs: [], inputs: [],
evaluate: () => 6, evaluate: () => 6,
invert: value => value, invert: value => value
hasVariable: true
}).invert(10) }).invert(10)
).toThrow()); ).toThrow());
test("One input inverts correctly", () => test("One input inverts correctly", () =>
@ -923,8 +903,7 @@ describe("Custom Formulas", () => {
new Formula({ new Formula({
inputs: [variable], inputs: [variable],
evaluate: () => 10, evaluate: () => 10,
invert: (value, v1) => v1.evaluate(), invert: (value, v1) => v1.evaluate()
hasVariable: true
}).invert(10) }).invert(10)
).compare_tolerance(1)); ).compare_tolerance(1));
test("Two inputs inverts correctly", () => test("Two inputs inverts correctly", () =>
@ -932,37 +911,36 @@ describe("Custom Formulas", () => {
new Formula({ new Formula({
inputs: [variable, 2], inputs: [variable, 2],
evaluate: () => 10, evaluate: () => 10,
invert: (value, v1, v2) => v2, invert: (value, v1, v2) => v2
hasVariable: true
}).invert(10) }).invert(10)
).compare_tolerance(2)); ).compare_tolerance(2));
}); });
describe("Formula with integrate", () => { describe("Formula with integrate", () => {
test("Zero input integrates correctly", () => test("Zero input cannot integrate", () =>
expect( expect(() =>
new Formula({ new Formula({
inputs: [], inputs: [],
evaluate: () => 10, evaluate: () => 0,
integrate: variable => variable integrate: stack => variable
}).evaluateIntegral() }).evaluateIntegral()
).compare_tolerance(20)); ).toThrow());
test("One input integrates correctly", () => test("One input integrates correctly", () =>
expect( expect(
new Formula({ new Formula({
inputs: [variable], inputs: [variable],
evaluate: () => 10, evaluate: v1 => Decimal.add(v1, 19.5),
integrate: (variable, stack, v1) => Formula.add(variable, v1) integrate: (stack, v1) => Formula.add(v1, 10)
}).evaluateIntegral() }).evaluateIntegral()
).compare_tolerance(20)); ).compare_tolerance(20));
test("Two inputs integrates correctly", () => test("Two inputs integrates correctly", () =>
expect( expect(
new Formula({ new Formula({
inputs: [variable, 2], inputs: [variable, 10],
evaluate: (v1, v2) => 10, evaluate: v1 => Decimal.add(v1, 19.5),
integrate: (variable, v1, v2) => variable integrate: (stack, v1, v2) => Formula.add(v1, v2)
}).evaluateIntegral() }).evaluateIntegral()
).compare_tolerance(3)); ).compare_tolerance(20));
}); });
describe("Formula with invertIntegral", () => { describe("Formula with invertIntegral", () => {
@ -970,29 +948,26 @@ describe("Custom Formulas", () => {
expect(() => expect(() =>
new Formula({ new Formula({
inputs: [], inputs: [],
evaluate: () => 10, evaluate: () => 0,
integrate: variable => variable, integrate: stack => variable
hasVariable: true }).invertIntegral(20)
}).invertIntegral(8)
).toThrow()); ).toThrow());
test("One input inverts integral correctly", () => test("One input inverts integral correctly", () =>
expect( expect(
new Formula({ new Formula({
inputs: [variable], inputs: [variable],
evaluate: () => 10, evaluate: v1 => Decimal.add(v1, 19.5),
integrate: (variable, stack, v1) => variable, integrate: (stack, v1) => Formula.add(v1, 10)
hasVariable: true }).invertIntegral(20)
}).invertIntegral(8) ).compare_tolerance(10));
).compare_tolerance(1));
test("Two inputs inverts integral correctly", () => test("Two inputs inverts integral correctly", () =>
expect( expect(
new Formula({ new Formula({
inputs: [variable, 2], inputs: [variable, 10],
evaluate: (v1, v2) => 10, evaluate: v1 => Decimal.add(v1, 19.5),
integrate: (variable, v1, v2) => variable, integrate: (stack, v1, v2) => Formula.add(v1, v2)
hasVariable: true }).invertIntegral(20)
}).invertIntegral(8) ).compare_tolerance(10));
).compare_tolerance(1));
}); });
describe.todo("Formula as input"); describe.todo("Formula as input");

View file

@ -2,7 +2,7 @@ import Decimal, { DecimalSource, format } from "util/bignum";
import { expect } from "vitest"; import { expect } from "vitest";
interface CustomMatchers<R = unknown> { interface CustomMatchers<R = unknown> {
compare_tolerance(expected: DecimalSource): R; compare_tolerance(expected: DecimalSource, tolerance?: number): R;
} }
declare global { declare global {
@ -16,7 +16,7 @@ declare global {
} }
expect.extend({ expect.extend({
compare_tolerance(received: DecimalSource, expected: DecimalSource) { compare_tolerance(received: DecimalSource, expected: DecimalSource, tolerance?: number) {
const { isNot } = this; const { isNot } = this;
let pass = false; let pass = false;
if (!Decimal.isFinite(expected)) { if (!Decimal.isFinite(expected)) {
@ -24,7 +24,7 @@ expect.extend({
} else if (Decimal.isNaN(expected)) { } else if (Decimal.isNaN(expected)) {
pass = Decimal.isNaN(received); pass = Decimal.isNaN(received);
} else { } else {
pass = Decimal.eq_tolerance(received, expected); pass = Decimal.eq_tolerance(received, expected, tolerance);
} }
return { return {
// do not alter your "pass" based on isNot. Vitest does it for you // do not alter your "pass" based on isNot. Vitest does it for you