forked from profectus/Profectus
Fixing more tests with integral rework
This commit is contained in:
parent
a91efffd5c
commit
6115b6687d
6 changed files with 264 additions and 352 deletions
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
4
src/game/formulas/types.d.ts
vendored
4
src/game/formulas/types.d.ts
vendored
|
@ -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>;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue