diff --git a/src/game/formulas.ts b/src/game/formulas.ts index 10b3fcd..253e0db 100644 --- a/src/game/formulas.ts +++ b/src/game/formulas.ts @@ -829,7 +829,9 @@ export default class Formula { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.internalInvertIntegral = - this.internalHasVariable && variable?.isIntegralInvertible() ? invert : undefined; + this.internalHasVariable && variable?.isIntegralInvertible() + ? invertIntegral + : undefined; } /** Type predicate that this formula can be inverted. */ @@ -847,7 +849,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; + return ( + this.internalHasVariable && + (this.internalInvertIntegral != null || this.internalEvaluate == null) + ); } /** Whether or not this formula has a singular variable inside it, which can be accessed via {@link innermostVariable}. */ @@ -892,11 +897,12 @@ export default class Formula { * @see {@link isIntegrable} */ evaluateIntegral(variable?: DecimalSource): DecimalSource { - return ( - this.internalIntegrate?.call(this, variable, ...this.inputs) ?? - variable ?? - unrefFormulaSource(this.inputs[0]) - ); + if (this.internalIntegrate) { + return this.internalIntegrate.call(this, variable, ...this.inputs); + } else if (this.inputs.length === 1 && this.internalHasVariable) { + return variable ?? unrefFormulaSource(this.inputs[0]); + } + throw "Cannot integrate formula without variable"; } /** @@ -907,7 +913,12 @@ export default class Formula { invertIntegral(value: DecimalSource): DecimalSource { // This is nearly completely non-functional // Proper nesting will require somehow using integration by substitution or integration by parts - return this.internalInvertIntegral?.call(this, value, ...this.inputs) ?? value; + if (this.internalInvertIntegral) { + return this.internalInvertIntegral.call(this, value, ...this.inputs); + } else if (this.inputs.length === 1 && this.internalHasVariable) { + return value; + } + throw "Cannot invert integral of formula without invertible integral"; } /** @@ -962,10 +973,12 @@ export default class Formula { public static step( value: FormulaSource, start: Computable, - formulaModifier: (value: Ref) => GenericFormula + formulaModifier: ( + value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula + ) => GenericFormula ): GenericFormula { const lhsRef = ref(0); - const formula = formulaModifier(lhsRef); + const formula = formulaModifier(Formula.variable(lhsRef)); const processedStart = convertComputable(start); function evalStep(lhs: DecimalSource) { if (Decimal.lt(lhs, unref(processedStart))) { @@ -1002,10 +1015,12 @@ export default class Formula { public static if( value: FormulaSource, condition: Computable, - formulaModifier: (value: Ref) => GenericFormula + formulaModifier: ( + value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula + ) => GenericFormula ): GenericFormula { const lhsRef = ref(0); - const formula = formulaModifier(lhsRef); + const formula = formulaModifier(Formula.variable(lhsRef)); const processedCondition = convertComputable(condition); function evalStep(lhs: DecimalSource) { if (unref(processedCondition)) { @@ -1035,7 +1050,9 @@ export default class Formula { public static conditional( value: FormulaSource, condition: Computable, - formulaModifier: (value: Ref) => GenericFormula + formulaModifier: ( + value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula + ) => GenericFormula ) { return Formula.if(value, condition, formulaModifier); } @@ -1632,20 +1649,26 @@ export default class Formula { public step( start: Computable, - formulaModifier: (value: Ref) => GenericFormula + formulaModifier: ( + value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula + ) => GenericFormula ) { return Formula.step(this, start, formulaModifier); } public if( condition: Computable, - formulaModifier: (value: Ref) => GenericFormula + formulaModifier: ( + value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula + ) => GenericFormula ) { return Formula.if(this, condition, formulaModifier); } public conditional( condition: Computable, - formulaModifier: (value: Ref) => GenericFormula + formulaModifier: ( + value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula + ) => GenericFormula ) { return Formula.if(this, condition, formulaModifier); } diff --git a/tests/game/formulas.test.ts b/tests/game/formulas.test.ts index 47f8d26..c00d43b 100644 --- a/tests/game/formulas.test.ts +++ b/tests/game/formulas.test.ts @@ -569,8 +569,8 @@ describe("Integrating", () => { checkFormula(Formula[name](variable, constant))); test(`${name}(const, var) is marked as integrable`, () => checkFormula(Formula[name](constant, variable))); - test(`${name}(var, var) is marked as integrable`, () => - checkFormula(Formula[name](variable, variable))); + test(`${name}(var, var) is marked as not integrable`, () => + expect(Formula[name](variable, variable).isIntegrable()).toBe(false)); }); }); }); @@ -872,20 +872,29 @@ describe("Custom Formulas", () => { describe("Formula with invert", () => { test("Zero input inverts correctly", () => expect( - new Formula({ inputs: [], evaluate: () => 6, invert: value => value }).invert(10) + new Formula({ + inputs: [], + evaluate: () => 6, + invert: value => value, + variable: ref(10) + }).invert(10) ).compare_tolerance(10)); test("One input inverts correctly", () => expect( - new Formula({ inputs: [1], evaluate: () => 10, invert: (value, v1) => v1 }).invert( - 10 - ) + new Formula({ + inputs: [1], + evaluate: () => 10, + invert: (value, v1) => v1, + variable: ref(10) + }).invert(10) ).compare_tolerance(1)); test("Two inputs inverts correctly", () => expect( new Formula({ inputs: [1, 2], evaluate: () => 10, - invert: (value, v1, v2) => v2 + invert: (value, v1, v2) => v2, + variable: ref(10) }).invert(10) ).compare_tolerance(2)); }); @@ -923,7 +932,8 @@ describe("Custom Formulas", () => { new Formula({ inputs: [], evaluate: () => 10, - invertIntegral: () => 1 + invertIntegral: () => 1, + variable: ref(10) }).invertIntegral(8) ).compare_tolerance(1)); test("One input inverts integral correctly", () => @@ -931,7 +941,8 @@ describe("Custom Formulas", () => { new Formula({ inputs: [1], evaluate: () => 10, - invertIntegral: val => 1 + invertIntegral: val => 1, + variable: ref(10) }).invertIntegral(8) ).compare_tolerance(1)); test("Two inputs inverts integral correctly", () => @@ -939,7 +950,8 @@ describe("Custom Formulas", () => { new Formula({ inputs: [1, 2], evaluate: (v1, v2) => 10, - invertIntegral: (v1, v2) => 1 + invertIntegral: (v1, v2) => 1, + variable: ref(10) }).invertIntegral(8) ).compare_tolerance(1)); });