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 { branchedResetPropagation, createTree } from "features/trees/tree";
|
||||
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 { createLayer } from "game/layers";
|
||||
import type { Player } from "game/player";
|
||||
|
@ -18,6 +22,7 @@ import prestige from "./layers/prestige";
|
|||
window.Formula = Formula;
|
||||
window.calculateMaxAffordable = calculateMaxAffordable;
|
||||
window.calculateCost = calculateCost;
|
||||
window.findNonInvertible = findNonInvertible;
|
||||
window.unref = unref;
|
||||
window.ref = ref;
|
||||
window.createResource = createResource;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Resource } from "features/resources/resource";
|
||||
import Decimal, { DecimalSource } from "util/bignum";
|
||||
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 {
|
||||
EvaluateFunction,
|
||||
FormulaOptions,
|
||||
|
@ -18,7 +19,6 @@ import type {
|
|||
SubstitutionFunction,
|
||||
SubstitutionStack
|
||||
} from "./types";
|
||||
import * as ops from "./operations";
|
||||
|
||||
export function hasVariable(value: FormulaSource): value is InvertibleFormula {
|
||||
return value instanceof Formula && value.hasVariable();
|
||||
|
@ -32,13 +32,6 @@ function integrateVariable(this: GenericFormula) {
|
|||
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.
|
||||
* 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 internalIntegrateInner: IntegrateFunction<T> | undefined;
|
||||
private readonly applySubstitution: SubstitutionFunction<T> | undefined;
|
||||
private readonly internalHasVariable: boolean;
|
||||
private readonly internalVariables: number;
|
||||
|
||||
public readonly innermostVariable: ProcessedComputable<DecimalSource> | undefined;
|
||||
|
||||
|
@ -69,7 +62,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
|||
readonlyProperties = this.setupFormula(options);
|
||||
}
|
||||
this.inputs = readonlyProperties.inputs;
|
||||
this.internalHasVariable = readonlyProperties.internalHasVariable;
|
||||
this.internalVariables = readonlyProperties.internalVariables;
|
||||
this.innermostVariable = readonlyProperties.innermostVariable;
|
||||
this.internalEvaluate = readonlyProperties.internalEvaluate;
|
||||
this.internalInvert = readonlyProperties.internalInvert;
|
||||
|
@ -85,10 +78,9 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
|||
}): InternalFormulaProperties<T> {
|
||||
return {
|
||||
inputs: [variable] as T,
|
||||
internalHasVariable: true,
|
||||
internalVariables: 1,
|
||||
innermostVariable: variable,
|
||||
internalIntegrate: integrateVariable as unknown as IntegrateFunction<T>,
|
||||
internalIntegrateInner: integrateVariableInner as unknown as IntegrateFunction<T>,
|
||||
internalIntegrate: integrateVariable,
|
||||
applySubstitution: ops.passthrough as unknown as SubstitutionFunction<T>
|
||||
};
|
||||
}
|
||||
|
@ -99,68 +91,49 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
|||
}
|
||||
return {
|
||||
inputs: inputs as T,
|
||||
internalHasVariable: false
|
||||
internalVariables: 0
|
||||
};
|
||||
}
|
||||
|
||||
private setupFormula(options: GeneralFormulaOptions<T>): InternalFormulaProperties<T> {
|
||||
const {
|
||||
inputs,
|
||||
evaluate,
|
||||
invert,
|
||||
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 { inputs, evaluate, invert, integrate, integrateInner, applySubstitution } = options;
|
||||
const numVariables = inputs.reduce<number>(
|
||||
(acc, input) => acc + (input instanceof Formula ? input.internalVariables : 0),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
const numVariables = inputs.filter(
|
||||
input => input instanceof Formula && input.hasVariable()
|
||||
).length;
|
||||
const variable = inputs.find(input => input instanceof Formula && input.hasVariable()) as
|
||||
| GenericFormula
|
||||
| undefined;
|
||||
|
||||
const internalHasVariable =
|
||||
numVariables === 1 || (numVariables === 0 && hasVariable === true);
|
||||
const innermostVariable = internalHasVariable ? variable?.innermostVariable : undefined;
|
||||
const internalInvert = internalHasVariable && variable?.isInvertible() ? invert : undefined;
|
||||
const innermostVariable = numVariables === 1 ? variable?.innermostVariable : undefined;
|
||||
|
||||
return {
|
||||
inputs,
|
||||
internalEvaluate: evaluate,
|
||||
internalInvert,
|
||||
internalInvert: invert,
|
||||
internalIntegrate: integrate,
|
||||
internalIntegrateInner: integrateInner,
|
||||
applySubstitution,
|
||||
innermostVariable,
|
||||
internalHasVariable
|
||||
internalVariables: numVariables
|
||||
};
|
||||
}
|
||||
|
||||
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 actualCost = this.evaluate(0);
|
||||
const actualCost = Decimal.add(this.evaluate(0), this.evaluate(1)).div(2);
|
||||
return Decimal.sub(actualCost, integral);
|
||||
}
|
||||
|
||||
/** Type predicate that this formula can be inverted. */
|
||||
isInvertible(): this is InvertibleFormula {
|
||||
return (
|
||||
this.internalHasVariable &&
|
||||
(this.internalInvert != null || this.internalEvaluate == null)
|
||||
);
|
||||
return this.hasVariable() && (this.internalInvert != null || this.internalEvaluate == null);
|
||||
}
|
||||
|
||||
/** Type predicate that this formula can be integrated. */
|
||||
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. */
|
||||
|
@ -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}. */
|
||||
hasVariable(): boolean {
|
||||
return this.internalHasVariable;
|
||||
return this.internalVariables === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -188,7 +161,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
|||
unrefFormulaSource(input, variable)
|
||||
) as GuardedFormulasToDecimals<T>)
|
||||
) ??
|
||||
(this.internalHasVariable ? variable : null) ??
|
||||
(this.hasVariable() ? variable : null) ??
|
||||
unrefFormulaSource(this.inputs[0])
|
||||
);
|
||||
}
|
||||
|
@ -199,9 +172,9 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
|||
* @see {@link isInvertible}
|
||||
*/
|
||||
invert(value: DecimalSource): DecimalSource {
|
||||
if (this.internalInvert) {
|
||||
if (this.internalInvert && this.hasVariable()) {
|
||||
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;
|
||||
}
|
||||
throw new Error("Cannot invert non-invertible formula");
|
||||
|
@ -228,7 +201,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
|||
* @see {@link isIntegralInvertible}
|
||||
*/
|
||||
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");
|
||||
}
|
||||
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.
|
||||
* @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(
|
||||
variable?: ProcessedComputable<DecimalSource>,
|
||||
stack?: SubstitutionStack
|
||||
): GenericFormula {
|
||||
if (variable == null && this.integralFormula != null) {
|
||||
getIntegralFormula(stack?: SubstitutionStack): GenericFormula {
|
||||
if (this.integralFormula != null) {
|
||||
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) {
|
||||
// "Outer" part of the formula
|
||||
if (this.applySubstitution == null) {
|
||||
|
@ -262,21 +223,24 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
|||
if (this.internalIntegrate == null) {
|
||||
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)));
|
||||
formula = value;
|
||||
this.integralFormula = value;
|
||||
} else {
|
||||
// Continue digging into the formula
|
||||
if (this.internalIntegrate) {
|
||||
formula = this.internalIntegrate.call(
|
||||
this.integralFormula = this.internalIntegrate.call(
|
||||
this,
|
||||
variable,
|
||||
undefined,
|
||||
...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
|
||||
formula = this;
|
||||
this.integralFormula = this;
|
||||
} else {
|
||||
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)
|
||||
);
|
||||
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) {
|
||||
formula = this.internalIntegrate.call(this, variable, stack, ...this.inputs);
|
||||
} else if (this.inputs.length === 1 && this.internalHasVariable) {
|
||||
this.integralFormula = this.internalIntegrate.call(this, stack, ...this.inputs);
|
||||
} else if (
|
||||
this.inputs.length === 1 &&
|
||||
this.internalEvaluate == null &&
|
||||
this.hasVariable()
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
formula = this;
|
||||
this.integralFormula = this;
|
||||
} else {
|
||||
throw new Error("Cannot integrate formula without variable");
|
||||
}
|
||||
}
|
||||
if (!variablePresent) {
|
||||
this.integralFormula = formula;
|
||||
}
|
||||
return formula;
|
||||
return this.integralFormula;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -324,7 +293,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
|||
this.internalEvaluate === other.internalEvaluate &&
|
||||
this.internalInvert === other.internalInvert &&
|
||||
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 reciprocate = Formula.recip;
|
||||
|
||||
public static max(value: FormulaSource, other: FormulaSource): GenericFormula {
|
||||
return new Formula({
|
||||
inputs: [value, other],
|
||||
evaluate: Decimal.max,
|
||||
invert: ops.passthrough as (
|
||||
value: DecimalSource,
|
||||
...inputs: [FormulaSource, FormulaSource]
|
||||
) => DecimalSource
|
||||
});
|
||||
}
|
||||
|
||||
// TODO these functions should ostensibly be integrable, and the integrals should be invertible
|
||||
public static max = ops.createPassthroughBinaryFormula(Decimal.max);
|
||||
public static min = ops.createPassthroughBinaryFormula(Decimal.min);
|
||||
public static minabs = ops.createPassthroughBinaryFormula(Decimal.minabs);
|
||||
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.
|
||||
* @param formula The formula to use for calculating buy max from
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import Decimal, { DecimalSource } from "util/bignum";
|
||||
import { Ref } from "vue";
|
||||
import Formula, { hasVariable, unrefFormulaSource } from "./formulas";
|
||||
import { FormulaSource, GenericFormula, InvertFunction, SubstitutionStack } from "./types";
|
||||
|
||||
const ln10 = Decimal.ln(10);
|
||||
|
||||
export function passthrough<T extends GenericFormula | DecimalSource>(value: T): T {
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateNeg(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateNeg(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
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");
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
export function integrateAdd(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
export function integrateAdd(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
return Formula.times(rhs, variable ?? lhs.innermostVariable ?? 0).add(x);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.times(rhs, lhs.innermostVariable ?? 0).add(x);
|
||||
} else if (hasVariable(rhs)) {
|
||||
const x = rhs.getIntegralFormula(variable, stack);
|
||||
return Formula.times(lhs, variable ?? rhs.innermostVariable ?? 0).add(x);
|
||||
const x = rhs.getIntegralFormula(stack);
|
||||
return Formula.times(lhs, rhs.innermostVariable ?? 0).add(x);
|
||||
}
|
||||
throw new Error("Could not integrate due to no input being a variable");
|
||||
}
|
||||
|
||||
export function integrateInnerAdd(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.add(x, rhs);
|
||||
} else if (hasVariable(rhs)) {
|
||||
const x = rhs.getIntegralFormula(variable, stack);
|
||||
const x = rhs.getIntegralFormula(stack);
|
||||
return Formula.add(x, lhs);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateSub(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
export function integrateSub(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
return Formula.sub(x, Formula.times(rhs, variable ?? lhs.innermostVariable ?? 0));
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.sub(x, Formula.times(rhs, lhs.innermostVariable ?? 0));
|
||||
} else if (hasVariable(rhs)) {
|
||||
const x = rhs.getIntegralFormula(variable, stack);
|
||||
return Formula.times(lhs, variable ?? rhs.innermostVariable ?? 0).sub(x);
|
||||
const x = rhs.getIntegralFormula(stack);
|
||||
return Formula.times(lhs, rhs.innermostVariable ?? 0).sub(x);
|
||||
}
|
||||
throw new Error("Could not integrate due to no input being a variable");
|
||||
}
|
||||
|
||||
export function integrateInnerSub(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.sub(x, rhs);
|
||||
} else if (hasVariable(rhs)) {
|
||||
const x = rhs.getIntegralFormula(variable, stack);
|
||||
const x = rhs.getIntegralFormula(stack);
|
||||
return Formula.sub(x, lhs);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateMul(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
export function integrateMul(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.times(x, rhs);
|
||||
} else if (hasVariable(rhs)) {
|
||||
const x = rhs.getIntegralFormula(variable, stack);
|
||||
const x = rhs.getIntegralFormula(stack);
|
||||
return Formula.times(x, lhs);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateDiv(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
export function integrateDiv(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.div(x, rhs);
|
||||
} else if (hasVariable(rhs)) {
|
||||
const x = rhs.getIntegralFormula(variable, stack);
|
||||
const x = rhs.getIntegralFormula(stack);
|
||||
return Formula.div(lhs, x);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateRecip(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateRecip(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.ln(x);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateLog10(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
function internalIntegrateLog10(lhs: DecimalSource) {
|
||||
return Decimal.ln(lhs).sub(1).times(lhs).div(ln10);
|
||||
}
|
||||
|
||||
function internalInvertIntegralLog10(value: DecimalSource, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
return Formula.ln(x).sub(1).times(x).div(Formula.ln(10));
|
||||
const numerator = ln10.times(value);
|
||||
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");
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
export function integrateLog(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
function internalIntegrateLog(lhs: DecimalSource, rhs: DecimalSource) {
|
||||
return Decimal.ln(lhs).sub(1).times(lhs).div(Decimal.ln(rhs));
|
||||
}
|
||||
|
||||
function internalInvertIntegralLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
return Formula.ln(x).sub(1).times(x).div(Formula.ln(rhs));
|
||||
const numerator = Decimal.ln(unrefFormulaSource(rhs)).times(value);
|
||||
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");
|
||||
}
|
||||
|
@ -254,14 +246,25 @@ export function invertLog2(value: DecimalSource, lhs: FormulaSource) {
|
|||
throw new Error("Could not invert due to no input being a variable");
|
||||
}
|
||||
|
||||
export function integrateLog2(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
function internalIntegrateLog2(lhs: DecimalSource) {
|
||||
return Decimal.ln(lhs).sub(1).times(lhs).div(Decimal.ln(2));
|
||||
}
|
||||
|
||||
function internalInvertIntegralLog2(value: DecimalSource, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
return Formula.ln(x).sub(1).times(x).div(Formula.ln(2));
|
||||
const numerator = Decimal.ln(2).times(value);
|
||||
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");
|
||||
}
|
||||
|
@ -273,14 +276,24 @@ export function invertLn(value: DecimalSource, lhs: FormulaSource) {
|
|||
throw new Error("Could not invert due to no input being a variable");
|
||||
}
|
||||
|
||||
export function integrateLn(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
function internalIntegrateLn(lhs: DecimalSource) {
|
||||
return Decimal.ln(lhs).sub(1).times(lhs);
|
||||
}
|
||||
|
||||
function internalInvertIntegralLn(value: DecimalSource, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
return Formula.ln(x).sub(1).times(x);
|
||||
return lhs.invert(Decimal.div(value, Decimal.div(value, Math.E).lambertw()));
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
export function integratePow(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
export function integratePow(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
const pow = Formula.add(rhs, 1);
|
||||
return Formula.pow(x, pow).div(pow);
|
||||
} else if (hasVariable(rhs)) {
|
||||
const x = rhs.getIntegralFormula(variable, stack);
|
||||
const x = rhs.getIntegralFormula(stack);
|
||||
return Formula.pow(lhs, x).div(Formula.ln(lhs));
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integratePow10(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
function internalIntegratePow10(lhs: DecimalSource) {
|
||||
return Decimal.pow10(lhs).div(Decimal.ln(lhs));
|
||||
}
|
||||
|
||||
function internalInvertIntegralPow10(value: DecimalSource, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
return Formula.ln(x).sub(1).times(x).div(Decimal.ln(10));
|
||||
return lhs.invert(ln10.times(value).ln().div(ln10));
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
export function integratePowBase(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
export function integratePowBase(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.pow(rhs, x).div(Formula.ln(rhs));
|
||||
} else if (hasVariable(rhs)) {
|
||||
const x = rhs.getIntegralFormula(variable, stack);
|
||||
const x = rhs.getIntegralFormula(stack);
|
||||
const denominator = Formula.add(lhs, 1);
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateRoot(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource,
|
||||
rhs: FormulaSource
|
||||
) {
|
||||
export function integrateRoot(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||
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));
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateExp(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateExp(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.exp(x);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateSin(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateSin(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.cos(x).neg();
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateCos(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateCos(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.sin(x);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateTan(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateTan(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.cos(x).ln().neg();
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateAsin(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateAsin(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.asin(x)
|
||||
.times(x)
|
||||
.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");
|
||||
}
|
||||
|
||||
export function integrateAcos(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateAcos(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.acos(x)
|
||||
.times(x)
|
||||
.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");
|
||||
}
|
||||
|
||||
export function integrateAtan(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateAtan(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.atan(x)
|
||||
.times(x)
|
||||
.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");
|
||||
}
|
||||
|
||||
export function integrateSinh(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateSinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.cosh(x);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateCosh(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateCosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.sinh(x);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateTanh(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateTanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.cosh(x).ln();
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateAsinh(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateAsinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
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());
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
export function integrateAcosh(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateAcosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.acosh(x)
|
||||
.times(x)
|
||||
.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");
|
||||
}
|
||||
|
||||
export function integrateAtanh(
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack,
|
||||
lhs: FormulaSource
|
||||
) {
|
||||
export function integrateAtanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||
if (hasVariable(lhs)) {
|
||||
const x = lhs.getIntegralFormula(variable, stack);
|
||||
const x = lhs.getIntegralFormula(stack);
|
||||
return Formula.atanh(x)
|
||||
.times(x)
|
||||
.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 IntegrateFunction<T> = (
|
||||
this: Formula<T>,
|
||||
variable: Ref<DecimalSource>,
|
||||
stack: SubstitutionStack | undefined,
|
||||
...inputs: T
|
||||
) => GenericFormula;
|
||||
|
@ -43,7 +42,6 @@ type GeneralFormulaOptions<T extends [FormulaSource] | FormulaSource[]> = {
|
|||
integrate?: IntegrateFunction<T>;
|
||||
integrateInner?: IntegrateFunction<T>;
|
||||
applySubstitution?: SubstitutionFunction<T>;
|
||||
hasVariable?: boolean;
|
||||
};
|
||||
type FormulaOptions<T extends [FormulaSource] | FormulaSource[]> =
|
||||
| VariableFormulaOptions
|
||||
|
@ -52,7 +50,7 @@ type FormulaOptions<T extends [FormulaSource] | FormulaSource[]> =
|
|||
|
||||
type InternalFormulaProperties<T extends [FormulaSource] | FormulaSource[]> = {
|
||||
inputs: T;
|
||||
internalHasVariable: boolean;
|
||||
internalVariables: number;
|
||||
internalEvaluate?: EvaluateFunction<T>;
|
||||
internalInvert?: InvertFunction<T>;
|
||||
internalIntegrate?: IntegrateFunction<T>;
|
||||
|
|
|
@ -96,21 +96,21 @@ const invertibleIntegralZeroPramFunctionNames = [
|
|||
"sqr",
|
||||
"sqrt",
|
||||
"cube",
|
||||
"cbrt"
|
||||
] as const;
|
||||
const nonInvertibleIntegralZeroPramFunctionNames = [
|
||||
...nonIntegrableZeroParamFunctionNames,
|
||||
"cbrt",
|
||||
"neg",
|
||||
"exp",
|
||||
"sin",
|
||||
"cos",
|
||||
"tan",
|
||||
"sinh",
|
||||
"cosh",
|
||||
"tanh"
|
||||
] as const;
|
||||
const nonInvertibleIntegralZeroPramFunctionNames = [
|
||||
...nonIntegrableZeroParamFunctionNames,
|
||||
"asin",
|
||||
"acos",
|
||||
"atan",
|
||||
"sinh",
|
||||
"cosh",
|
||||
"tanh",
|
||||
"asinh",
|
||||
"acosh",
|
||||
"atanh"
|
||||
|
@ -493,8 +493,8 @@ describe("Integrating", () => {
|
|||
|
||||
test("variable.evaluateIntegral() calculates correctly", () =>
|
||||
expect(variable.evaluateIntegral()).compare_tolerance(Decimal.pow(10, 2).div(2)));
|
||||
test("evaluateIntegral(variable) overrides variable value", () =>
|
||||
expect(variable.add(10).evaluateIntegral(20)).compare_tolerance(400));
|
||||
test("variable.evaluateIntegral(variable) overrides variable value", () =>
|
||||
expect(variable.evaluateIntegral(20)).compare_tolerance(Decimal.pow(20, 2).div(2)));
|
||||
|
||||
describe("Integrable functions marked as such", () => {
|
||||
function checkFormula(formula: GenericFormula) {
|
||||
|
@ -668,32 +668,13 @@ describe("Inverting integrals", () => {
|
|||
|
||||
test("Inverting integral of nested formulas", () => {
|
||||
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", () => {
|
||||
const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
|
||||
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", () => {
|
||||
|
@ -914,8 +895,7 @@ describe("Custom Formulas", () => {
|
|||
new Formula({
|
||||
inputs: [],
|
||||
evaluate: () => 6,
|
||||
invert: value => value,
|
||||
hasVariable: true
|
||||
invert: value => value
|
||||
}).invert(10)
|
||||
).toThrow());
|
||||
test("One input inverts correctly", () =>
|
||||
|
@ -923,8 +903,7 @@ describe("Custom Formulas", () => {
|
|||
new Formula({
|
||||
inputs: [variable],
|
||||
evaluate: () => 10,
|
||||
invert: (value, v1) => v1.evaluate(),
|
||||
hasVariable: true
|
||||
invert: (value, v1) => v1.evaluate()
|
||||
}).invert(10)
|
||||
).compare_tolerance(1));
|
||||
test("Two inputs inverts correctly", () =>
|
||||
|
@ -932,37 +911,36 @@ describe("Custom Formulas", () => {
|
|||
new Formula({
|
||||
inputs: [variable, 2],
|
||||
evaluate: () => 10,
|
||||
invert: (value, v1, v2) => v2,
|
||||
hasVariable: true
|
||||
invert: (value, v1, v2) => v2
|
||||
}).invert(10)
|
||||
).compare_tolerance(2));
|
||||
});
|
||||
|
||||
describe("Formula with integrate", () => {
|
||||
test("Zero input integrates correctly", () =>
|
||||
expect(
|
||||
test("Zero input cannot integrate", () =>
|
||||
expect(() =>
|
||||
new Formula({
|
||||
inputs: [],
|
||||
evaluate: () => 10,
|
||||
integrate: variable => variable
|
||||
evaluate: () => 0,
|
||||
integrate: stack => variable
|
||||
}).evaluateIntegral()
|
||||
).compare_tolerance(20));
|
||||
).toThrow());
|
||||
test("One input integrates correctly", () =>
|
||||
expect(
|
||||
new Formula({
|
||||
inputs: [variable],
|
||||
evaluate: () => 10,
|
||||
integrate: (variable, stack, v1) => Formula.add(variable, v1)
|
||||
evaluate: v1 => Decimal.add(v1, 19.5),
|
||||
integrate: (stack, v1) => Formula.add(v1, 10)
|
||||
}).evaluateIntegral()
|
||||
).compare_tolerance(20));
|
||||
test("Two inputs integrates correctly", () =>
|
||||
expect(
|
||||
new Formula({
|
||||
inputs: [variable, 2],
|
||||
evaluate: (v1, v2) => 10,
|
||||
integrate: (variable, v1, v2) => variable
|
||||
inputs: [variable, 10],
|
||||
evaluate: v1 => Decimal.add(v1, 19.5),
|
||||
integrate: (stack, v1, v2) => Formula.add(v1, v2)
|
||||
}).evaluateIntegral()
|
||||
).compare_tolerance(3));
|
||||
).compare_tolerance(20));
|
||||
});
|
||||
|
||||
describe("Formula with invertIntegral", () => {
|
||||
|
@ -970,29 +948,26 @@ describe("Custom Formulas", () => {
|
|||
expect(() =>
|
||||
new Formula({
|
||||
inputs: [],
|
||||
evaluate: () => 10,
|
||||
integrate: variable => variable,
|
||||
hasVariable: true
|
||||
}).invertIntegral(8)
|
||||
evaluate: () => 0,
|
||||
integrate: stack => variable
|
||||
}).invertIntegral(20)
|
||||
).toThrow());
|
||||
test("One input inverts integral correctly", () =>
|
||||
expect(
|
||||
new Formula({
|
||||
inputs: [variable],
|
||||
evaluate: () => 10,
|
||||
integrate: (variable, stack, v1) => variable,
|
||||
hasVariable: true
|
||||
}).invertIntegral(8)
|
||||
).compare_tolerance(1));
|
||||
evaluate: v1 => Decimal.add(v1, 19.5),
|
||||
integrate: (stack, v1) => Formula.add(v1, 10)
|
||||
}).invertIntegral(20)
|
||||
).compare_tolerance(10));
|
||||
test("Two inputs inverts integral correctly", () =>
|
||||
expect(
|
||||
new Formula({
|
||||
inputs: [variable, 2],
|
||||
evaluate: (v1, v2) => 10,
|
||||
integrate: (variable, v1, v2) => variable,
|
||||
hasVariable: true
|
||||
}).invertIntegral(8)
|
||||
).compare_tolerance(1));
|
||||
inputs: [variable, 10],
|
||||
evaluate: v1 => Decimal.add(v1, 19.5),
|
||||
integrate: (stack, v1, v2) => Formula.add(v1, v2)
|
||||
}).invertIntegral(20)
|
||||
).compare_tolerance(10));
|
||||
});
|
||||
|
||||
describe.todo("Formula as input");
|
||||
|
|
|
@ -2,7 +2,7 @@ import Decimal, { DecimalSource, format } from "util/bignum";
|
|||
import { expect } from "vitest";
|
||||
|
||||
interface CustomMatchers<R = unknown> {
|
||||
compare_tolerance(expected: DecimalSource): R;
|
||||
compare_tolerance(expected: DecimalSource, tolerance?: number): R;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -16,7 +16,7 @@ declare global {
|
|||
}
|
||||
|
||||
expect.extend({
|
||||
compare_tolerance(received: DecimalSource, expected: DecimalSource) {
|
||||
compare_tolerance(received: DecimalSource, expected: DecimalSource, tolerance?: number) {
|
||||
const { isNot } = this;
|
||||
let pass = false;
|
||||
if (!Decimal.isFinite(expected)) {
|
||||
|
@ -24,7 +24,7 @@ expect.extend({
|
|||
} else if (Decimal.isNaN(expected)) {
|
||||
pass = Decimal.isNaN(received);
|
||||
} else {
|
||||
pass = Decimal.eq_tolerance(received, expected);
|
||||
pass = Decimal.eq_tolerance(received, expected, tolerance);
|
||||
}
|
||||
return {
|
||||
// do not alter your "pass" based on isNot. Vitest does it for you
|
||||
|
|
Loading…
Reference in a new issue