From a91efffd5c1bc37513f88bb5b55344b2f5eab6cb Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sat, 1 Apr 2023 15:55:17 -0500 Subject: [PATCH] WIP integral rework --- src/game/formulas/formulas.ts | 268 ++++++++++----------- src/game/formulas/operations.ts | 399 +++++++++++--------------------- src/game/formulas/types.d.ts | 17 +- tests/game/formulas.test.ts | 30 ++- 4 files changed, 283 insertions(+), 431 deletions(-) diff --git a/src/game/formulas/formulas.ts b/src/game/formulas/formulas.ts index d6eabcd..305ad5b 100644 --- a/src/game/formulas/formulas.ts +++ b/src/game/formulas/formulas.ts @@ -1,7 +1,7 @@ import { Resource } from "features/resources/resource"; import Decimal, { DecimalSource } from "util/bignum"; import { Computable, convertComputable, ProcessedComputable } from "util/computed"; -import { computed, ComputedRef, ref, unref } from "vue"; +import { computed, ComputedRef, Ref, ref, unref } from "vue"; import type { EvaluateFunction, FormulaOptions, @@ -15,7 +15,6 @@ import type { InvertFunction, InvertibleFormula, InvertibleIntegralFormula, - InvertIntegralFunction, SubstitutionFunction, SubstitutionStack } from "./types"; @@ -29,8 +28,8 @@ export function unrefFormulaSource(value: FormulaSource, variable?: DecimalSourc return value instanceof Formula ? value.evaluate(variable) : unref(value); } -function integrateVariable(variable: DecimalSource) { - return Decimal.pow(variable, 2).div(2); +function integrateVariable(this: GenericFormula) { + return Formula.pow(this, 2).div(2); } function integrateVariableInner(this: GenericFormula, variable?: DecimalSource) { @@ -54,11 +53,12 @@ export default class Formula { private readonly internalIntegrate: IntegrateFunction | undefined; private readonly internalIntegrateInner: IntegrateFunction | undefined; private readonly applySubstitution: SubstitutionFunction | undefined; - private readonly internalInvertIntegral: InvertIntegralFunction | undefined; private readonly internalHasVariable: boolean; public readonly innermostVariable: ProcessedComputable | undefined; + private integralFormula: GenericFormula | undefined; + constructor(options: FormulaOptions) { let readonlyProperties; if ("variable" in options) { @@ -75,7 +75,6 @@ export default class Formula { this.internalInvert = readonlyProperties.internalInvert; this.internalIntegrate = readonlyProperties.internalIntegrate; this.internalIntegrateInner = readonlyProperties.internalIntegrateInner; - this.internalInvertIntegral = readonlyProperties.internalInvertIntegral; this.applySubstitution = readonlyProperties.applySubstitution; } @@ -112,10 +111,9 @@ export default class Formula { integrate, integrateInner, applySubstitution, - invertIntegral, hasVariable } = options; - if (invert == null && invertIntegral == null && hasVariable) { + if (invert == null && hasVariable) { throw new Error( "A formula cannot be marked as having a variable if it is not invertible" ); @@ -132,8 +130,6 @@ export default class Formula { numVariables === 1 || (numVariables === 0 && hasVariable === true); const innermostVariable = internalHasVariable ? variable?.innermostVariable : undefined; const internalInvert = internalHasVariable && variable?.isInvertible() ? invert : undefined; - const internalInvertIntegral = - internalHasVariable && variable?.isIntegralInvertible() ? invertIntegral : undefined; return { inputs, @@ -141,13 +137,19 @@ export default class Formula { internalInvert, internalIntegrate: integrate, internalIntegrateInner: integrateInner, - internalInvertIntegral, applySubstitution, innermostVariable, internalHasVariable }; } + private calculateConstantOfIntegration() { + // Calculate C based on the knowledge that at 1 purchase, the total sum would be the cost of that one purchase + const integral = this.getIntegralFormula().evaluate(1); + const actualCost = this.evaluate(0); + return Decimal.sub(actualCost, integral); + } + /** Type predicate that this formula can be inverted. */ isInvertible(): this is InvertibleFormula { return ( @@ -163,10 +165,10 @@ export default class Formula { /** Type predicate that this formula has an integral function that can be inverted. */ isIntegralInvertible(): this is InvertibleIntegralFormula { - return ( - this.internalHasVariable && - (this.internalInvertIntegral != null || this.internalEvaluate == null) - ); + if (!this.isIntegrable()) { + return false; + } + return this.getIntegralFormula().isInvertible(); } /** Whether or not this formula has a singular variable inside it, which can be accessed via {@link innermostVariable}. */ @@ -208,71 +210,101 @@ export default class Formula { /** * Evaluate the result of the indefinite integral (sans the constant of integration). Only works if there's a single variable and the formula is integrable. The formula can only have one "complex" operation (anything besides +,-,*,/). * @param variable Optionally override the value of the variable while evaluating - * @param stack The list of callbacks to run to handle simple operations inside the complex operation. Used in nested formulas * @see {@link isIntegrable} */ - evaluateIntegral(variable?: DecimalSource, stack?: SubstitutionStack): DecimalSource { + evaluateIntegral(variable?: DecimalSource): DecimalSource { + if (!this.isIntegrable()) { + throw new Error("Cannot evaluate integral of formula without integral"); + } + return Decimal.add( + this.getIntegralFormula().evaluate(variable), + this.calculateConstantOfIntegration() + ); + } + + /** + * Given the potential result of the formula's integral (and the constant of integration), calculate what value the variable inside the formula would have to be for that result to occur. Only works if there's a single variable and if the formula's integral is invertible. + * @param value The result of the integral. + * @see {@link isIntegralInvertible} + */ + invertIntegral(value: DecimalSource): DecimalSource { + if (this.integralFormula?.isInvertible()) { + throw new Error("Cannot invert integral of formula without invertible integral"); + } + return this.getIntegralFormula().invert(value); + } + + /** + * 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 + */ + getIntegralFormula( + variable?: ProcessedComputable, + stack?: SubstitutionStack + ): GenericFormula { + if (variable == null && 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) { // We're the complex operation of this formula stack = []; if (this.internalIntegrate == null) { - throw new Error("Cannot integrate formula with non-existent operation"); + throw new Error("Cannot integrate formula with non-integrable operation"); } let value = this.internalIntegrate.call(this, variable, stack, ...this.inputs); stack.forEach(func => (value = func(value))); - return value; + formula = value; } else { // Continue digging into the formula if (this.internalIntegrate) { - return this.internalIntegrate.call(this, variable, undefined, ...this.inputs); + formula = this.internalIntegrate.call( + this, + variable, + undefined, + ...this.inputs + ); } else if (this.inputs.length === 1 && this.internalHasVariable) { - return integrateVariable(variable ?? unrefFormulaSource(this.inputs[0])); + // eslint-disable-next-line @typescript-eslint/no-this-alias + formula = this; + } else { + throw new Error("Cannot integrate formula without variable"); } - throw new Error("Cannot integrate formula without variable"); } } else { // "Inner" part of the formula if (this.applySubstitution == null) { throw new Error("Cannot have two complex operations in an integrable formula"); } - stack.push((variable: DecimalSource) => + stack.push((variable: GenericFormula) => // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.applySubstitution!.call(this, variable, ...this.inputs) ); if (this.internalIntegrateInner) { - return this.internalIntegrateInner.call(this, variable, stack, ...this.inputs); + formula = this.internalIntegrateInner.call(this, variable, stack, ...this.inputs); } else if (this.internalIntegrate) { - return this.internalIntegrate.call(this, variable, stack, ...this.inputs); + formula = this.internalIntegrate.call(this, variable, stack, ...this.inputs); } else if (this.inputs.length === 1 && this.internalHasVariable) { - return variable ?? unrefFormulaSource(this.inputs[0]); + // eslint-disable-next-line @typescript-eslint/no-this-alias + formula = this; + } else { + throw new Error("Cannot integrate formula without variable"); } - throw new Error("Cannot integrate formula without variable"); } - } - - calculateConstantOfIntegration() { - // Calculate C based on the knowledge that at 1 purchase, the total sum would be the cost of that one purchase - const integral = this.evaluateIntegral(1); - const actualCost = this.evaluate(0); - return Decimal.sub(actualCost, integral); - } - - /** - * Given the potential result of the formula's integral (sand the constant of integration), calculate what value the variable inside the formula would have to be for that result to occur. Only works if there's a single variable and if the formula's integral is invertible. - * @param value The result of the integral. - * @see {@link isIntegralInvertible} - */ - invertIntegral(value: DecimalSource): DecimalSource { - // This is nearly completely non-functional - // Proper nesting will require somehow using integration by substitution or integration by parts - if (this.internalInvertIntegral) { - return this.internalInvertIntegral.call(this, value, ...this.inputs); - } else if (this.inputs.length === 1 && this.internalHasVariable) { - return value; + if (!variablePresent) { + this.integralFormula = formula; } - throw new Error("Cannot invert integral of formula without invertible integral"); + return formula; } /** @@ -292,7 +324,6 @@ export default class Formula { this.internalEvaluate === other.internalEvaluate && this.internalInvert === other.internalInvert && this.internalIntegrate === other.internalIntegrate && - this.internalInvertIntegral === other.internalInvertIntegral && this.internalHasVariable === other.internalHasVariable ); } @@ -415,7 +446,7 @@ export default class Formula { return new Formula({ inputs: [value], evaluate: Decimal.abs }); } - public static neg(value: T): Omit; + public static neg(value: T): T; public static neg(value: FormulaSource): GenericFormula; public static neg(value: FormulaSource) { return new Formula({ @@ -460,8 +491,7 @@ export default class Formula { invert: ops.invertAdd, integrate: ops.integrateAdd, integrateInner: ops.integrateInnerAdd, - applySubstitution: ops.passthrough, - invertIntegral: ops.invertIntegrateAdd + applySubstitution: ops.passthrough }); } public static plus = Formula.add; @@ -476,8 +506,7 @@ export default class Formula { invert: ops.invertSub, integrate: ops.integrateSub, integrateInner: ops.integrateInnerSub, - applySubstitution: ops.passthrough, - invertIntegral: ops.invertIntegrateSub + applySubstitution: ops.passthrough }); } public static subtract = Formula.sub; @@ -492,8 +521,7 @@ export default class Formula { evaluate: Decimal.mul, invert: ops.invertMul, integrate: ops.integrateMul, - applySubstitution: ops.applySubstitutionMul, - invertIntegral: ops.invertIntegrateMul + applySubstitution: ops.applySubstitutionMul }); } public static multiply = Formula.mul; @@ -508,8 +536,7 @@ export default class Formula { evaluate: Decimal.div, invert: ops.invertDiv, integrate: ops.integrateDiv, - applySubstitution: ops.applySubstitutionDiv, - invertIntegral: ops.invertIntegrateDiv + applySubstitution: ops.applySubstitutionDiv }); } public static divide = Formula.div; @@ -523,8 +550,7 @@ export default class Formula { inputs: [value], evaluate: Decimal.recip, invert: ops.invertRecip, - integrate: ops.integrateRecip, - invertIntegral: ops.invertIntegrateRecip + integrate: ops.integrateRecip }); } public static reciprocal = Formula.recip; @@ -537,10 +563,6 @@ export default class Formula { invert: ops.passthrough as ( value: DecimalSource, ...inputs: [FormulaSource, FormulaSource] - ) => DecimalSource, - invertIntegral: ops.passthrough as ( - value: DecimalSource, - ...inputs: [FormulaSource, FormulaSource] ) => DecimalSource }); } @@ -559,12 +581,7 @@ export default class Formula { return new Formula({ inputs: [value, min, max], evaluate: Decimal.clamp, - invert: ops.passthrough as InvertFunction< - [FormulaSource, FormulaSource, FormulaSource] - >, - invertIntegral: ops.passthrough as InvertFunction< - [FormulaSource, FormulaSource, FormulaSource] - > + invert: ops.passthrough as InvertFunction<[FormulaSource, FormulaSource, FormulaSource]> }); } @@ -583,8 +600,7 @@ export default class Formula { inputs: [value], evaluate: Decimal.log10, invert: ops.invertLog10, - integrate: ops.integrateLog10, - invertIntegral: ops.invertIntegrateLog10 + integrate: ops.integrateLog10 }); } @@ -596,8 +612,7 @@ export default class Formula { inputs: [value, base], evaluate: Decimal.log, invert: ops.invertLog, - integrate: ops.integrateLog, - invertIntegral: ops.invertIntegrateLog + integrate: ops.integrateLog }); } public static logarithm = Formula.log; @@ -609,8 +624,7 @@ export default class Formula { inputs: [value], evaluate: Decimal.log2, invert: ops.invertLog2, - integrate: ops.integrateLog2, - invertIntegral: ops.invertIntegrateLog2 + integrate: ops.integrateLog2 }); } @@ -621,8 +635,7 @@ export default class Formula { inputs: [value], evaluate: Decimal.ln, invert: ops.invertLn, - integrate: ops.integrateLn, - invertIntegral: ops.invertIntegrateLn + integrate: ops.integrateLn }); } @@ -634,8 +647,7 @@ export default class Formula { inputs: [value, other], evaluate: Decimal.pow, invert: ops.invertPow, - integrate: ops.integratePow, - invertIntegral: ops.invertIntegratePow + integrate: ops.integratePow }); } @@ -646,8 +658,7 @@ export default class Formula { inputs: [value], evaluate: Decimal.pow10, invert: ops.invertPow10, - integrate: ops.integratePow10, - invertIntegral: ops.invertIntegratePow10 + integrate: ops.integratePow10 }); } @@ -659,8 +670,7 @@ export default class Formula { inputs: [value, other], evaluate: Decimal.pow_base, invert: ops.invertPowBase, - integrate: ops.integratePowBase, - invertIntegral: ops.invertIntegratePowBase + integrate: ops.integratePowBase }); } @@ -672,8 +682,7 @@ export default class Formula { inputs: [value, other], evaluate: Decimal.root, invert: ops.invertRoot, - integrate: ops.integrateRoot, - invertIntegral: ops.invertIntegrateRoot + integrate: ops.integrateRoot }); } @@ -689,7 +698,7 @@ export default class Formula { return new Formula({ inputs: [value], evaluate: Decimal.lngamma }); } - public static exp(value: T): Omit; + public static exp(value: T): T; public static exp(value: FormulaSource): GenericFormula; public static exp(value: FormulaSource) { return new Formula({ @@ -728,7 +737,7 @@ export default class Formula { value: T, height?: FormulaSource, payload?: FormulaSource - ): Omit; + ): Omit; public static tetrate( value: FormulaSource, height?: FormulaSource, @@ -750,7 +759,7 @@ export default class Formula { value: T, height?: FormulaSource, payload?: FormulaSource - ): Omit; + ): Omit; public static iteratedexp( value: FormulaSource, height?: FormulaSource, @@ -779,7 +788,7 @@ export default class Formula { public static slog( value: T, base?: FormulaSource - ): Omit; + ): Omit; public static slog(value: FormulaSource, base?: FormulaSource): GenericFormula; public static slog(value: FormulaSource, base: FormulaSource = 10) { return new Formula({ inputs: [value, base], evaluate: ops.slog, invert: ops.invertSlog }); @@ -793,7 +802,7 @@ export default class Formula { value: T, diff: FormulaSource, base?: FormulaSource - ): Omit; + ): Omit; public static layeradd( value: FormulaSource, diff: FormulaSource, @@ -807,9 +816,7 @@ export default class Formula { }); } - public static lambertw( - value: T - ): Omit; + public static lambertw(value: T): Omit; public static lambertw(value: FormulaSource): GenericFormula; public static lambertw(value: FormulaSource) { return new Formula({ @@ -819,9 +826,7 @@ export default class Formula { }); } - public static ssqrt( - value: T - ): Omit; + public static ssqrt(value: T): Omit; public static ssqrt(value: FormulaSource): GenericFormula; public static ssqrt(value: FormulaSource) { return new Formula({ inputs: [value], evaluate: Decimal.ssqrt, invert: ops.invertSsqrt }); @@ -835,7 +840,7 @@ export default class Formula { return new Formula({ inputs: [value, height, payload], evaluate: ops.pentate }); } - public static sin(value: T): Omit; + public static sin(value: T): T; public static sin(value: FormulaSource): GenericFormula; public static sin(value: FormulaSource) { return new Formula({ @@ -846,7 +851,7 @@ export default class Formula { }); } - public static cos(value: T): Omit; + public static cos(value: T): T; public static cos(value: FormulaSource): GenericFormula; public static cos(value: FormulaSource) { return new Formula({ @@ -857,7 +862,7 @@ export default class Formula { }); } - public static tan(value: T): Omit; + public static tan(value: T): T; public static tan(value: FormulaSource): GenericFormula; public static tan(value: FormulaSource) { return new Formula({ @@ -868,7 +873,7 @@ export default class Formula { }); } - public static asin(value: T): Omit; + public static asin(value: T): T; public static asin(value: FormulaSource): GenericFormula; public static asin(value: FormulaSource) { return new Formula({ @@ -879,7 +884,7 @@ export default class Formula { }); } - public static acos(value: T): Omit; + public static acos(value: T): T; public static acos(value: FormulaSource): GenericFormula; public static acos(value: FormulaSource) { return new Formula({ @@ -890,7 +895,7 @@ export default class Formula { }); } - public static atan(value: T): Omit; + public static atan(value: T): T; public static atan(value: FormulaSource): GenericFormula; public static atan(value: FormulaSource) { return new Formula({ @@ -901,7 +906,7 @@ export default class Formula { }); } - public static sinh(value: T): Omit; + public static sinh(value: T): T; public static sinh(value: FormulaSource): GenericFormula; public static sinh(value: FormulaSource) { return new Formula({ @@ -912,7 +917,7 @@ export default class Formula { }); } - public static cosh(value: T): Omit; + public static cosh(value: T): T; public static cosh(value: FormulaSource): GenericFormula; public static cosh(value: FormulaSource) { return new Formula({ @@ -923,7 +928,7 @@ export default class Formula { }); } - public static tanh(value: T): Omit; + public static tanh(value: T): T; public static tanh(value: FormulaSource): GenericFormula; public static tanh(value: FormulaSource) { return new Formula({ @@ -934,7 +939,7 @@ export default class Formula { }); } - public static asinh(value: T): Omit; + public static asinh(value: T): T; public static asinh(value: FormulaSource): GenericFormula; public static asinh(value: FormulaSource) { return new Formula({ @@ -945,7 +950,7 @@ export default class Formula { }); } - public static acosh(value: T): Omit; + public static acosh(value: T): T; public static acosh(value: FormulaSource): GenericFormula; public static acosh(value: FormulaSource) { return new Formula({ @@ -956,7 +961,7 @@ export default class Formula { }); } - public static atanh(value: T): Omit; + public static atanh(value: T): T; public static atanh(value: FormulaSource): GenericFormula; public static atanh(value: FormulaSource) { return new Formula({ @@ -997,7 +1002,7 @@ export default class Formula { return Formula.abs(this); } - public neg(this: T): Omit; + public neg(this: T): T; public neg(this: GenericFormula): GenericFormula; public neg(this: GenericFormula) { return Formula.neg(this); @@ -1170,7 +1175,7 @@ export default class Formula { return Formula.lngamma(this); } - public exp(this: T): Omit; + public exp(this: T): T; public exp(this: FormulaSource): GenericFormula; public exp(this: FormulaSource) { return Formula.exp(this); @@ -1203,7 +1208,7 @@ export default class Formula { this: T, height?: FormulaSource, payload?: FormulaSource - ): Omit; + ): Omit; public tetrate( this: FormulaSource, height?: FormulaSource, @@ -1221,7 +1226,7 @@ export default class Formula { this: T, height?: FormulaSource, payload?: FormulaSource - ): Omit; + ): Omit; public iteratedexp( this: FormulaSource, height?: FormulaSource, @@ -1239,10 +1244,7 @@ export default class Formula { return Formula.iteratedlog(this, base, times); } - public slog( - this: T, - base?: FormulaSource - ): Omit; + public slog(this: T, base?: FormulaSource): Omit; public slog(this: FormulaSource, base?: FormulaSource): GenericFormula; public slog(this: FormulaSource, base: FormulaSource = 10) { return Formula.slog(this, base); @@ -1256,19 +1258,19 @@ export default class Formula { this: T, diff: FormulaSource, base?: FormulaSource - ): Omit; + ): Omit; public layeradd(this: FormulaSource, diff: FormulaSource, base?: FormulaSource): GenericFormula; public layeradd(this: FormulaSource, diff: FormulaSource, base: FormulaSource) { return Formula.layeradd(this, diff, base); } - public lambertw(this: T): Omit; + public lambertw(this: T): Omit; public lambertw(this: FormulaSource): GenericFormula; public lambertw(this: FormulaSource) { return Formula.lambertw(this); } - public ssqrt(this: T): Omit; + public ssqrt(this: T): Omit; public ssqrt(this: FormulaSource): GenericFormula; public ssqrt(this: FormulaSource) { return Formula.ssqrt(this); @@ -1281,73 +1283,73 @@ export default class Formula { return Formula.pentate(this, height, payload); } - public sin(this: T): Omit; + public sin(this: T): T; public sin(this: FormulaSource): GenericFormula; public sin(this: FormulaSource) { return Formula.sin(this); } - public cos(this: T): Omit; + public cos(this: T): T; public cos(this: FormulaSource): GenericFormula; public cos(this: FormulaSource) { return Formula.cos(this); } - public tan(this: T): Omit; + public tan(this: T): T; public tan(this: FormulaSource): GenericFormula; public tan(this: FormulaSource) { return Formula.tan(this); } - public asin(this: T): Omit; + public asin(this: T): T; public asin(this: FormulaSource): GenericFormula; public asin(this: FormulaSource) { return Formula.asin(this); } - public acos(this: T): Omit; + public acos(this: T): T; public acos(this: FormulaSource): GenericFormula; public acos(this: FormulaSource) { return Formula.acos(this); } - public atan(this: T): Omit; + public atan(this: T): T; public atan(this: FormulaSource): GenericFormula; public atan(this: FormulaSource) { return Formula.atan(this); } - public sinh(this: T): Omit; + public sinh(this: T): T; public sinh(this: FormulaSource): GenericFormula; public sinh(this: FormulaSource) { return Formula.sinh(this); } - public cosh(this: T): Omit; + public cosh(this: T): T; public cosh(this: FormulaSource): GenericFormula; public cosh(this: FormulaSource) { return Formula.cosh(this); } - public tanh(this: T): Omit; + public tanh(this: T): T; public tanh(this: FormulaSource): GenericFormula; public tanh(this: FormulaSource) { return Formula.tanh(this); } - public asinh(this: T): Omit; + public asinh(this: T): T; public asinh(this: FormulaSource): GenericFormula; public asinh(this: FormulaSource) { return Formula.asinh(this); } - public acosh(this: T): Omit; + public acosh(this: T): T; public acosh(this: FormulaSource): GenericFormula; public acosh(this: FormulaSource) { return Formula.acosh(this); } - public atanh(this: T): Omit; + public atanh(this: T): T; public atanh(this: FormulaSource): GenericFormula; public atanh(this: FormulaSource) { return Formula.atanh(this); diff --git a/src/game/formulas/operations.ts b/src/game/formulas/operations.ts index f4dd1f0..4512f55 100644 --- a/src/game/formulas/operations.ts +++ b/src/game/formulas/operations.ts @@ -1,9 +1,9 @@ import Decimal, { DecimalSource } from "util/bignum"; -import { unref } from "vue"; +import { Ref } from "vue"; import Formula, { hasVariable, unrefFormulaSource } from "./formulas"; -import { FormulaSource, InvertFunction, SubstitutionStack } from "./types"; +import { FormulaSource, GenericFormula, InvertFunction, SubstitutionStack } from "./types"; -export function passthrough(value: DecimalSource) { +export function passthrough(value: T): T { return value; } @@ -15,18 +15,18 @@ export function invertNeg(value: DecimalSource, lhs: FormulaSource) { } export function integrateNeg( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - return Decimal.neg(lhs.evaluateIntegral(variable, stack)); + return Formula.neg(lhs.getIntegralFormula(variable, stack)); } throw new Error("Could not integrate due to no input being a variable"); } -export function applySubstitutionNeg(value: DecimalSource) { - return Decimal.neg(value); +export function applySubstitutionNeg(value: GenericFormula) { + return Formula.neg(value); } export function invertAdd(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { @@ -39,54 +39,37 @@ export function invertAdd(value: DecimalSource, lhs: FormulaSource, rhs: Formula } export function integrateAdd( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.times( - unrefFormulaSource(rhs), - variable ?? unref(lhs.innermostVariable) ?? 0 - ).add(x); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.times(rhs, variable ?? lhs.innermostVariable ?? 0).add(x); } else if (hasVariable(rhs)) { - const x = rhs.evaluateIntegral(variable, stack); - return Decimal.times( - unrefFormulaSource(lhs), - variable ?? unref(rhs.innermostVariable) ?? 0 - ).add(x); + const x = rhs.getIntegralFormula(variable, stack); + return Formula.times(lhs, variable ?? rhs.innermostVariable ?? 0).add(x); } throw new Error("Could not integrate due to no input being a variable"); } export function integrateInnerAdd( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.add(x, unrefFormulaSource(rhs)); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.add(x, rhs); } else if (hasVariable(rhs)) { - const x = rhs.evaluateIntegral(variable, stack); - return Decimal.add(x, unrefFormulaSource(lhs)); + const x = rhs.getIntegralFormula(variable, stack); + return Formula.add(x, lhs); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegrateAdd(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { - if (hasVariable(lhs)) { - const b = unrefFormulaSource(rhs); - return lhs.invert(Decimal.pow(b, 2).add(Decimal.times(value, 2)).sub(b)); - } else if (hasVariable(rhs)) { - const b = unrefFormulaSource(lhs); - return rhs.invert(Decimal.pow(b, 2).add(Decimal.times(value, 2)).sub(b)); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.add(value, unrefFormulaSource(rhs))); @@ -97,54 +80,37 @@ export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: Formula } export function integrateSub( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.sub( - x, - Decimal.times(unrefFormulaSource(rhs), variable ?? unref(lhs.innermostVariable) ?? 0) - ); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.sub(x, Formula.times(rhs, variable ?? lhs.innermostVariable ?? 0)); } else if (hasVariable(rhs)) { - const x = rhs.evaluateIntegral(variable, stack); - return Decimal.times( - unrefFormulaSource(lhs), - variable ?? unref(rhs.innermostVariable) ?? 0 - ).sub(x); + const x = rhs.getIntegralFormula(variable, stack); + return Formula.times(lhs, variable ?? rhs.innermostVariable ?? 0).sub(x); } throw new Error("Could not integrate due to no input being a variable"); } export function integrateInnerSub( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.sub(x, unrefFormulaSource(rhs)); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.sub(x, rhs); } else if (hasVariable(rhs)) { - const x = rhs.evaluateIntegral(variable, stack); - return Decimal.sub(x, unrefFormulaSource(lhs)); + const x = rhs.getIntegralFormula(variable, stack); + return Formula.sub(x, lhs); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegrateSub(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { - if (hasVariable(lhs)) { - const b = unrefFormulaSource(rhs); - return lhs.invert(Decimal.pow(b, 2).add(Decimal.times(value, 2)).sqrt().sub(b)); - } else if (hasVariable(rhs)) { - const b = unrefFormulaSource(lhs); - return rhs.invert(Decimal.pow(b, 2).add(Decimal.times(value, 2)).sqrt().sub(b)); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.div(value, unrefFormulaSource(rhs))); @@ -155,41 +121,34 @@ export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: Formula } export function integrateMul( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.times(x, unrefFormulaSource(rhs)); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.times(x, rhs); } else if (hasVariable(rhs)) { - const x = rhs.evaluateIntegral(variable, stack); - return Decimal.times(x, unrefFormulaSource(lhs)); + const x = rhs.getIntegralFormula(variable, stack); + return Formula.times(x, lhs); } throw new Error("Could not integrate due to no input being a variable"); } -export function applySubstitutionMul(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { +export function applySubstitutionMul( + value: GenericFormula, + lhs: FormulaSource, + rhs: FormulaSource +) { if (hasVariable(lhs)) { - return Decimal.div(value, unrefFormulaSource(rhs)); + return Formula.div(value, rhs); } else if (hasVariable(rhs)) { - return Decimal.div(value, unrefFormulaSource(lhs)); + return Formula.div(value, lhs); } throw new Error("Could not apply substitution due to no input being a variable"); } -export function invertIntegrateMul(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { - if (hasVariable(lhs)) { - const b = unrefFormulaSource(rhs); - return lhs.invert(Decimal.sqrt(value).times(Decimal.sqrt(2)).div(Decimal.sqrt(b))); - } else if (hasVariable(rhs)) { - const b = unrefFormulaSource(lhs); - return rhs.invert(Decimal.sqrt(value).times(Decimal.sqrt(2)).div(Decimal.sqrt(b))); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.mul(value, unrefFormulaSource(rhs))); @@ -200,41 +159,34 @@ export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: Formula } export function integrateDiv( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.div(x, unrefFormulaSource(rhs)); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.div(x, rhs); } else if (hasVariable(rhs)) { - const x = rhs.evaluateIntegral(variable, stack); - return Decimal.div(unrefFormulaSource(lhs), x); + const x = rhs.getIntegralFormula(variable, stack); + return Formula.div(lhs, x); } throw new Error("Could not integrate due to no input being a variable"); } -export function applySubstitutionDiv(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { +export function applySubstitutionDiv( + value: GenericFormula, + lhs: FormulaSource, + rhs: FormulaSource +) { if (hasVariable(lhs)) { - return Decimal.mul(value, unrefFormulaSource(rhs)); + return Formula.mul(value, rhs); } else if (hasVariable(rhs)) { - return Decimal.mul(value, unrefFormulaSource(lhs)); + return Formula.mul(value, lhs); } throw new Error("Could not apply substitution due to no input being a variable"); } -export function invertIntegrateDiv(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { - if (hasVariable(lhs)) { - const b = unrefFormulaSource(rhs); - return lhs.invert(Decimal.sqrt(value).times(Decimal.sqrt(2)).times(Decimal.sqrt(b))); - } else if (hasVariable(rhs)) { - const b = unrefFormulaSource(lhs); - return rhs.invert(Decimal.sqrt(value).times(Decimal.sqrt(2)).times(Decimal.sqrt(b))); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertRecip(value: DecimalSource, lhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.recip(value)); @@ -243,24 +195,17 @@ export function invertRecip(value: DecimalSource, lhs: FormulaSource) { } export function integrateRecip( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.ln(x); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.ln(x); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegrateRecip(value: DecimalSource, lhs: FormulaSource) { - if (hasVariable(lhs)) { - return lhs.invert(Decimal.exp(value)); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertLog10(value: DecimalSource, lhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.pow10(value)); @@ -269,26 +214,17 @@ export function invertLog10(value: DecimalSource, lhs: FormulaSource) { } export function integrateLog10( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.ln(x).sub(1).times(x).div(Decimal.ln(10)); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.ln(x).sub(1).times(x).div(Formula.ln(10)); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegrateLog10(value: DecimalSource, lhs: FormulaSource) { - if (hasVariable(lhs)) { - return lhs.invert( - Decimal.exp(Decimal.ln(2).add(Decimal.ln(5)).times(value).div(Math.E).lambertw().add(1)) - ); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.pow(unrefFormulaSource(rhs), value)); @@ -299,29 +235,18 @@ export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: Formula } export function integrateLog( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.ln(x) - .sub(1) - .times(x) - .div(Decimal.ln(unrefFormulaSource(rhs))); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.ln(x).sub(1).times(x).div(Formula.ln(rhs)); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegrateLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { - if (hasVariable(lhs)) { - 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 invertLog2(value: DecimalSource, lhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.pow(2, value)); @@ -330,24 +255,17 @@ export function invertLog2(value: DecimalSource, lhs: FormulaSource) { } export function integrateLog2( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.ln(x).sub(1).times(x).div(Decimal.ln(2)); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.ln(x).sub(1).times(x).div(Formula.ln(2)); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegrateLog2(value: DecimalSource, lhs: FormulaSource) { - if (hasVariable(lhs)) { - return lhs.invert(Decimal.exp(Decimal.ln(2).times(value).div(Math.E).lambertw().add(1))); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertLn(value: DecimalSource, lhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.exp(value)); @@ -356,24 +274,17 @@ export function invertLn(value: DecimalSource, lhs: FormulaSource) { } export function integrateLn( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.ln(x).sub(1).times(x); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.ln(x).sub(1).times(x); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegrateLn(value: DecimalSource, lhs: FormulaSource) { - if (hasVariable(lhs)) { - return lhs.invert(Decimal.exp(Decimal.div(value, Math.E).lambertw().add(1))); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.root(value, unrefFormulaSource(rhs))); @@ -384,34 +295,22 @@ export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: Formula } export function integratePow( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - const pow = Decimal.add(unrefFormulaSource(rhs), 1); - return Decimal.pow(x, pow).div(pow); + const x = lhs.getIntegralFormula(variable, stack); + const pow = Formula.add(rhs, 1); + return Formula.pow(x, pow).div(pow); } else if (hasVariable(rhs)) { - const x = rhs.evaluateIntegral(variable, stack); - const b = unrefFormulaSource(lhs); - return Decimal.pow(b, x).div(Decimal.ln(b)); + const x = rhs.getIntegralFormula(variable, stack); + return Formula.pow(lhs, x).div(Formula.ln(lhs)); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegratePow(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { - if (hasVariable(lhs)) { - const b = unrefFormulaSource(rhs); - return lhs.invert(Decimal.negate(b).sub(1).negate().times(value).root(Decimal.add(b, 1))); - } else if (hasVariable(rhs)) { - const denominator = Decimal.ln(unrefFormulaSource(lhs)); - return rhs.invert(Decimal.times(denominator, value).ln().div(denominator)); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertPow10(value: DecimalSource, lhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.root(value, 10)); @@ -420,26 +319,17 @@ export function invertPow10(value: DecimalSource, lhs: FormulaSource) { } export function integratePow10( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.ln(x).sub(1).times(x).div(Decimal.ln(10)); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.ln(x).sub(1).times(x).div(Decimal.ln(10)); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegratePow10(value: DecimalSource, lhs: FormulaSource) { - if (hasVariable(lhs)) { - return lhs.invert( - Decimal.ln(2).add(Decimal.ln(5)).times(value).div(Math.E).lambertw().add(1).exp() - ); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.ln(value).div(unrefFormulaSource(rhs))); @@ -450,38 +340,22 @@ export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: For } export function integratePowBase( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - const b = unrefFormulaSource(rhs); - return Decimal.pow(b, x).div(Decimal.ln(b)); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.pow(rhs, x).div(Formula.ln(rhs)); } else if (hasVariable(rhs)) { - const x = rhs.evaluateIntegral(variable, stack); - const denominator = Decimal.add(unrefFormulaSource(lhs), 1); - return Decimal.pow(x, denominator).div(denominator); + const x = rhs.getIntegralFormula(variable, stack); + const denominator = Formula.add(lhs, 1); + return Formula.pow(x, denominator).div(denominator); } throw new Error("Could not integrate due to no input being a variable"); } -export function invertIntegratePowBase( - value: DecimalSource, - lhs: FormulaSource, - rhs: FormulaSource -) { - if (hasVariable(lhs)) { - const b = unrefFormulaSource(rhs); - return lhs.invert(Decimal.ln(b).times(value).ln().div(Decimal.ln(b))); - } else if (hasVariable(rhs)) { - const b = unrefFormulaSource(lhs); - return rhs.invert(Decimal.neg(b).sub(1).negate().times(value).root(Decimal.add(b, 1))); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.root(value, Decimal.recip(unrefFormulaSource(rhs)))); @@ -492,32 +366,18 @@ export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: Formul } export function integrateRoot( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - const a = unrefFormulaSource(rhs); - return Decimal.pow(x, Decimal.recip(a).add(1)).times(a).div(Decimal.add(a, 1)); + const x = lhs.getIntegralFormula(variable, 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"); } -export function invertIntegrateRoot(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { - if (hasVariable(lhs)) { - const b = unrefFormulaSource(rhs); - return lhs.invert( - Decimal.add(b, 1) - .times(value) - .div(b) - .pow(Decimal.div(b, Decimal.add(b, 1))) - ); - } - throw new Error("Could not invert due to no input being a variable"); -} - export function invertExp(value: DecimalSource, lhs: FormulaSource) { if (hasVariable(lhs)) { return lhs.invert(Decimal.ln(value)); @@ -526,13 +386,13 @@ export function invertExp(value: DecimalSource, lhs: FormulaSource) { } export function integrateExp( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.exp(x); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.exp(x); } throw new Error("Could not integrate due to no input being a variable"); } @@ -661,13 +521,13 @@ export function invertSin(value: DecimalSource, lhs: FormulaSource) { } export function integrateSin( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.cos(x).neg(); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.cos(x).neg(); } throw new Error("Could not integrate due to no input being a variable"); } @@ -680,13 +540,13 @@ export function invertCos(value: DecimalSource, lhs: FormulaSource) { } export function integrateCos( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.sin(x); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.sin(x); } throw new Error("Could not integrate due to no input being a variable"); } @@ -699,13 +559,13 @@ export function invertTan(value: DecimalSource, lhs: FormulaSource) { } export function integrateTan( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.cos(x).ln().neg(); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.cos(x).ln().neg(); } throw new Error("Could not integrate due to no input being a variable"); } @@ -718,15 +578,15 @@ export function invertAsin(value: DecimalSource, lhs: FormulaSource) { } export function integrateAsin( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.asin(x) + const x = lhs.getIntegralFormula(variable, stack); + return Formula.asin(x) .times(x) - .add(Decimal.sqrt(Decimal.sub(1, Decimal.pow(x, 2)))); + .add(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2)))); } throw new Error("Could not integrate due to no input being a variable"); } @@ -739,15 +599,15 @@ export function invertAcos(value: DecimalSource, lhs: FormulaSource) { } export function integrateAcos( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.acos(x) + const x = lhs.getIntegralFormula(variable, stack); + return Formula.acos(x) .times(x) - .sub(Decimal.sqrt(Decimal.sub(1, Decimal.pow(x, 2)))); + .sub(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2)))); } throw new Error("Could not integrate due to no input being a variable"); } @@ -760,15 +620,15 @@ export function invertAtan(value: DecimalSource, lhs: FormulaSource) { } export function integrateAtan( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.atan(x) + const x = lhs.getIntegralFormula(variable, stack); + return Formula.atan(x) .times(x) - .sub(Decimal.ln(Decimal.pow(x, 2).add(1)).div(2)); + .sub(Formula.ln(Formula.pow(x, 2).add(1)).div(2)); } throw new Error("Could not integrate due to no input being a variable"); } @@ -781,13 +641,13 @@ export function invertSinh(value: DecimalSource, lhs: FormulaSource) { } export function integrateSinh( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.cosh(x); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.cosh(x); } throw new Error("Could not integrate due to no input being a variable"); } @@ -800,13 +660,13 @@ export function invertCosh(value: DecimalSource, lhs: FormulaSource) { } export function integrateCosh( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.sinh(x); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.sinh(x); } throw new Error("Could not integrate due to no input being a variable"); } @@ -819,13 +679,13 @@ export function invertTanh(value: DecimalSource, lhs: FormulaSource) { } export function integrateTanh( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.cosh(x).ln(); + const x = lhs.getIntegralFormula(variable, stack); + return Formula.cosh(x).ln(); } throw new Error("Could not integrate due to no input being a variable"); } @@ -838,13 +698,13 @@ export function invertAsinh(value: DecimalSource, lhs: FormulaSource) { } export function integrateAsinh( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.asinh(x).times(x).sub(Decimal.pow(x, 2).add(1).sqrt()); + const x = lhs.getIntegralFormula(variable, 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"); } @@ -857,15 +717,15 @@ export function invertAcosh(value: DecimalSource, lhs: FormulaSource) { } export function integrateAcosh( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.acosh(x) + const x = lhs.getIntegralFormula(variable, stack); + return Formula.acosh(x) .times(x) - .sub(Decimal.add(x, 1).sqrt().times(Decimal.sub(x, 1).sqrt())); + .sub(Formula.add(x, 1).sqrt().times(Formula.sub(x, 1).sqrt())); } throw new Error("Could not integrate due to no input being a variable"); } @@ -878,15 +738,15 @@ export function invertAtanh(value: DecimalSource, lhs: FormulaSource) { } export function integrateAtanh( - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack, lhs: FormulaSource ) { if (hasVariable(lhs)) { - const x = lhs.evaluateIntegral(variable, stack); - return Decimal.atanh(x) + const x = lhs.getIntegralFormula(variable, stack); + return Formula.atanh(x) .times(x) - .add(Decimal.sub(1, Decimal.pow(x, 2)).ln().div(2)); + .add(Formula.sub(1, Formula.pow(x, 2)).ln().div(2)); } throw new Error("Could not integrate due to no input being a variable"); } @@ -898,7 +758,6 @@ export function createPassthroughBinaryFormula( new Formula({ inputs: [value, other], evaluate: operation, - invert: passthrough as InvertFunction<[FormulaSource, FormulaSource]>, - invertIntegral: passthrough as InvertFunction<[FormulaSource, FormulaSource]> + invert: passthrough as InvertFunction<[FormulaSource, FormulaSource]> }); } diff --git a/src/game/formulas/types.d.ts b/src/game/formulas/types.d.ts index baaf2bb..3d3bc83 100644 --- a/src/game/formulas/types.d.ts +++ b/src/game/formulas/types.d.ts @@ -22,20 +22,15 @@ type EvaluateFunction = ( type InvertFunction = (this: Formula, value: DecimalSource, ...inputs: T) => DecimalSource; type IntegrateFunction = ( this: Formula, - variable: DecimalSource | undefined, + variable: Ref, stack: SubstitutionStack | undefined, ...inputs: T -) => DecimalSource; +) => GenericFormula; type SubstitutionFunction = ( this: Formula, - variable: DecimalSource, + variable: GenericFormula, ...inputs: T -) => DecimalSource; -type InvertIntegralFunction = ( - this: Formula, - value: DecimalSource, - ...inputs: T -) => DecimalSource; +) => GenericFormula; type VariableFormulaOptions = { variable: ProcessedComputable }; type ConstantFormulaOptions = { @@ -48,7 +43,6 @@ type GeneralFormulaOptions = { integrate?: IntegrateFunction; integrateInner?: IntegrateFunction; applySubstitution?: SubstitutionFunction; - invertIntegral?: InvertIntegralFunction; hasVariable?: boolean; }; type FormulaOptions = @@ -63,12 +57,11 @@ type InternalFormulaProperties = { internalInvert?: InvertFunction; internalIntegrate?: IntegrateFunction; internalIntegrateInner?: IntegrateFunction; - internalInvertIntegral?: InvertIntegralFunction; applySubstitution?: SubstitutionFunction; innermostVariable?: ProcessedComputable; }; -type SubstitutionStack = ((value: DecimalSource) => DecimalSource)[] | undefined; +type SubstitutionStack = ((value: GenericFormula) => GenericFormula)[] | undefined; // It's really hard to type mapped tuples, but these classes seem to manage type FormulasToDecimals = { diff --git a/tests/game/formulas.test.ts b/tests/game/formulas.test.ts index 4003051..ce0120b 100644 --- a/tests/game/formulas.test.ts +++ b/tests/game/formulas.test.ts @@ -491,8 +491,8 @@ describe("Integrating", () => { constant = Formula.constant(10); }); - test("evaluateIntegral() returns variable's value", () => - expect(variable.evaluate()).compare_tolerance(10)); + 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)); @@ -569,14 +569,10 @@ describe("Integrating", () => { const actualCost = new Array(10) .fill(null) .reduce((acc, _, i) => acc.add(formula.evaluate(i)), new Decimal(0)); - const calculatedCost = Decimal.add( - formula.evaluateIntegral(), - formula.calculateConstantOfIntegration() - ); // Check if the calculated cost is within 10% of the actual cost, // because this is an approximation expect( - Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber() + Decimal.sub(actualCost, formula.evaluateIntegral()).abs().div(actualCost).toNumber() ).toBeLessThan(0.1); }); @@ -594,8 +590,10 @@ describe("Inverting integrals", () => { constant = Formula.constant(10); }); - test("variable.invertIntegral() is pass-through", () => - expect(variable.invertIntegral(20)).compare_tolerance(20)); + test("variable.invertIntegral() calculates correctly", () => + expect(variable.invertIntegral(20)).compare_tolerance( + Decimal.sqrt(20).times(Decimal.sqrt(2)) + )); describe("Invertible Integral functions marked as such", () => { function checkFormula(formula: GenericFormula) { @@ -670,7 +668,7 @@ describe("Inverting integrals", () => { test("Inverting integral of nested formulas", () => { const formula = Formula.add(variable, constant).times(constant).pow(2).times(30); - expect(formula.invertIntegral(7000000)).compare_tolerance(10); + expect(formula.invertIntegral(formula.evaluateIntegral())).compare_tolerance(10); }); test("Inverting integral of nested complex formulas", () => { @@ -946,7 +944,7 @@ describe("Custom Formulas", () => { new Formula({ inputs: [], evaluate: () => 10, - integrate: () => 20 + integrate: variable => variable }).evaluateIntegral() ).compare_tolerance(20)); test("One input integrates correctly", () => @@ -954,7 +952,7 @@ describe("Custom Formulas", () => { new Formula({ inputs: [variable], evaluate: () => 10, - integrate: (val, stack, v1) => val ?? 20 + integrate: (variable, stack, v1) => Formula.add(variable, v1) }).evaluateIntegral() ).compare_tolerance(20)); test("Two inputs integrates correctly", () => @@ -962,7 +960,7 @@ describe("Custom Formulas", () => { new Formula({ inputs: [variable, 2], evaluate: (v1, v2) => 10, - integrate: (v1, v2) => 3 + integrate: (variable, v1, v2) => variable }).evaluateIntegral() ).compare_tolerance(3)); }); @@ -973,7 +971,7 @@ describe("Custom Formulas", () => { new Formula({ inputs: [], evaluate: () => 10, - invertIntegral: () => 1, + integrate: variable => variable, hasVariable: true }).invertIntegral(8) ).toThrow()); @@ -982,7 +980,7 @@ describe("Custom Formulas", () => { new Formula({ inputs: [variable], evaluate: () => 10, - invertIntegral: (val, v1) => 1, + integrate: (variable, stack, v1) => variable, hasVariable: true }).invertIntegral(8) ).compare_tolerance(1)); @@ -991,7 +989,7 @@ describe("Custom Formulas", () => { new Formula({ inputs: [variable, 2], evaluate: (v1, v2) => 10, - invertIntegral: (v1, v2) => 1, + integrate: (variable, v1, v2) => variable, hasVariable: true }).invertIntegral(8) ).compare_tolerance(1));