WIP integral rework

This commit is contained in:
thepaperpilot 2023-04-01 15:55:17 -05:00
parent d7e2658304
commit a91efffd5c
4 changed files with 283 additions and 431 deletions

View file

@ -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<T extends [FormulaSource] | FormulaSource[]> {
private readonly internalIntegrate: IntegrateFunction<T> | undefined;
private readonly internalIntegrateInner: IntegrateFunction<T> | undefined;
private readonly applySubstitution: SubstitutionFunction<T> | undefined;
private readonly internalInvertIntegral: InvertIntegralFunction<T> | undefined;
private readonly internalHasVariable: boolean;
public readonly innermostVariable: ProcessedComputable<DecimalSource> | undefined;
private integralFormula: GenericFormula | undefined;
constructor(options: FormulaOptions<T>) {
let readonlyProperties;
if ("variable" in options) {
@ -75,7 +75,6 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
/** 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<T extends [FormulaSource] | FormulaSource[]> {
/**
* 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<DecimalSource>,
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");
}
}
} 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");
}
}
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);
if (!variablePresent) {
this.integralFormula = formula;
}
/**
* 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;
}
throw new Error("Cannot invert integral of formula without invertible integral");
return formula;
}
/**
@ -292,7 +324,6 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
return new Formula({ inputs: [value], evaluate: Decimal.abs });
}
public static neg<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static neg<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
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<T extends [FormulaSource] | FormulaSource[]> {
return new Formula({ inputs: [value], evaluate: Decimal.lngamma });
}
public static exp<T extends GenericFormula>(value: T): Omit<T, "invertsIntegral">;
public static exp<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
value: T,
height?: FormulaSource,
payload?: FormulaSource
): Omit<T, "integrate" | "invertIntegral">;
): Omit<T, "integrate">;
public static tetrate(
value: FormulaSource,
height?: FormulaSource,
@ -750,7 +759,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
value: T,
height?: FormulaSource,
payload?: FormulaSource
): Omit<T, "integrate" | "invertIntegral">;
): Omit<T, "integrate">;
public static iteratedexp(
value: FormulaSource,
height?: FormulaSource,
@ -779,7 +788,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
public static slog<T extends GenericFormula>(
value: T,
base?: FormulaSource
): Omit<T, "integrate" | "invertIntegral">;
): Omit<T, "integrate">;
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<T extends [FormulaSource] | FormulaSource[]> {
value: T,
diff: FormulaSource,
base?: FormulaSource
): Omit<T, "integrate" | "invertIntegral">;
): Omit<T, "integrate">;
public static layeradd(
value: FormulaSource,
diff: FormulaSource,
@ -807,9 +816,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static lambertw<T extends GenericFormula>(
value: T
): Omit<T, "integrate" | "invertIntegral">;
public static lambertw<T extends GenericFormula>(value: T): Omit<T, "integrate">;
public static lambertw(value: FormulaSource): GenericFormula;
public static lambertw(value: FormulaSource) {
return new Formula({
@ -819,9 +826,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static ssqrt<T extends GenericFormula>(
value: T
): Omit<T, "integrate" | "invertIntegral">;
public static ssqrt<T extends GenericFormula>(value: T): Omit<T, "integrate">;
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<T extends [FormulaSource] | FormulaSource[]> {
return new Formula({ inputs: [value, height, payload], evaluate: ops.pentate });
}
public static sin<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static sin<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static cos<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static cos<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static tan<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static tan<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static asin<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static asin<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static acos<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static acos<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static atan<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static atan<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static sinh<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static sinh<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static cosh<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static cosh<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static tanh<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static tanh<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static asinh<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static asinh<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static acosh<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static acosh<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
});
}
public static atanh<T extends GenericFormula>(value: T): Omit<T, "invertIntegral">;
public static atanh<T extends GenericFormula>(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<T extends [FormulaSource] | FormulaSource[]> {
return Formula.abs(this);
}
public neg<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public neg<T extends GenericFormula>(this: T): T;
public neg(this: GenericFormula): GenericFormula;
public neg(this: GenericFormula) {
return Formula.neg(this);
@ -1170,7 +1175,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
return Formula.lngamma(this);
}
public exp<T extends GenericFormula>(this: T): Omit<T, "invertsIntegral">;
public exp<T extends GenericFormula>(this: T): T;
public exp(this: FormulaSource): GenericFormula;
public exp(this: FormulaSource) {
return Formula.exp(this);
@ -1203,7 +1208,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
this: T,
height?: FormulaSource,
payload?: FormulaSource
): Omit<T, "integrate" | "invertIntegral">;
): Omit<T, "integrate">;
public tetrate(
this: FormulaSource,
height?: FormulaSource,
@ -1221,7 +1226,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
this: T,
height?: FormulaSource,
payload?: FormulaSource
): Omit<T, "integrate" | "invertIntegral">;
): Omit<T, "integrate">;
public iteratedexp(
this: FormulaSource,
height?: FormulaSource,
@ -1239,10 +1244,7 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
return Formula.iteratedlog(this, base, times);
}
public slog<T extends GenericFormula>(
this: T,
base?: FormulaSource
): Omit<T, "integrate" | "invertIntegral">;
public slog<T extends GenericFormula>(this: T, base?: FormulaSource): Omit<T, "integrate">;
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<T extends [FormulaSource] | FormulaSource[]> {
this: T,
diff: FormulaSource,
base?: FormulaSource
): Omit<T, "integrate" | "invertIntegral">;
): Omit<T, "integrate">;
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<T extends GenericFormula>(this: T): Omit<T, "integrate" | "invertIntegral">;
public lambertw<T extends GenericFormula>(this: T): Omit<T, "integrate">;
public lambertw(this: FormulaSource): GenericFormula;
public lambertw(this: FormulaSource) {
return Formula.lambertw(this);
}
public ssqrt<T extends GenericFormula>(this: T): Omit<T, "integrate" | "invertIntegral">;
public ssqrt<T extends GenericFormula>(this: T): Omit<T, "integrate">;
public ssqrt(this: FormulaSource): GenericFormula;
public ssqrt(this: FormulaSource) {
return Formula.ssqrt(this);
@ -1281,73 +1283,73 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
return Formula.pentate(this, height, payload);
}
public sin<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public sin<T extends GenericFormula>(this: T): T;
public sin(this: FormulaSource): GenericFormula;
public sin(this: FormulaSource) {
return Formula.sin(this);
}
public cos<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public cos<T extends GenericFormula>(this: T): T;
public cos(this: FormulaSource): GenericFormula;
public cos(this: FormulaSource) {
return Formula.cos(this);
}
public tan<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public tan<T extends GenericFormula>(this: T): T;
public tan(this: FormulaSource): GenericFormula;
public tan(this: FormulaSource) {
return Formula.tan(this);
}
public asin<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public asin<T extends GenericFormula>(this: T): T;
public asin(this: FormulaSource): GenericFormula;
public asin(this: FormulaSource) {
return Formula.asin(this);
}
public acos<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public acos<T extends GenericFormula>(this: T): T;
public acos(this: FormulaSource): GenericFormula;
public acos(this: FormulaSource) {
return Formula.acos(this);
}
public atan<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public atan<T extends GenericFormula>(this: T): T;
public atan(this: FormulaSource): GenericFormula;
public atan(this: FormulaSource) {
return Formula.atan(this);
}
public sinh<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public sinh<T extends GenericFormula>(this: T): T;
public sinh(this: FormulaSource): GenericFormula;
public sinh(this: FormulaSource) {
return Formula.sinh(this);
}
public cosh<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public cosh<T extends GenericFormula>(this: T): T;
public cosh(this: FormulaSource): GenericFormula;
public cosh(this: FormulaSource) {
return Formula.cosh(this);
}
public tanh<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public tanh<T extends GenericFormula>(this: T): T;
public tanh(this: FormulaSource): GenericFormula;
public tanh(this: FormulaSource) {
return Formula.tanh(this);
}
public asinh<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public asinh<T extends GenericFormula>(this: T): T;
public asinh(this: FormulaSource): GenericFormula;
public asinh(this: FormulaSource) {
return Formula.asinh(this);
}
public acosh<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public acosh<T extends GenericFormula>(this: T): T;
public acosh(this: FormulaSource): GenericFormula;
public acosh(this: FormulaSource) {
return Formula.acosh(this);
}
public atanh<T extends GenericFormula>(this: T): Omit<T, "invertIntegral">;
public atanh<T extends GenericFormula>(this: T): T;
public atanh(this: FormulaSource): GenericFormula;
public atanh(this: FormulaSource) {
return Formula.atanh(this);

View file

@ -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<T extends GenericFormula | DecimalSource>(value: T): T {
return value;
}
@ -15,18 +15,18 @@ export function invertNeg(value: DecimalSource, lhs: FormulaSource) {
}
export function integrateNeg(
variable: DecimalSource | undefined,
variable: Ref<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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<DecimalSource>,
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]>
});
}

View file

@ -22,20 +22,15 @@ type EvaluateFunction<T> = (
type InvertFunction<T> = (this: Formula<T>, value: DecimalSource, ...inputs: T) => DecimalSource;
type IntegrateFunction<T> = (
this: Formula<T>,
variable: DecimalSource | undefined,
variable: Ref<DecimalSource>,
stack: SubstitutionStack | undefined,
...inputs: T
) => DecimalSource;
) => GenericFormula;
type SubstitutionFunction<T> = (
this: Formula<T>,
variable: DecimalSource,
variable: GenericFormula,
...inputs: T
) => DecimalSource;
type InvertIntegralFunction<T> = (
this: Formula<T>,
value: DecimalSource,
...inputs: T
) => DecimalSource;
) => GenericFormula;
type VariableFormulaOptions = { variable: ProcessedComputable<DecimalSource> };
type ConstantFormulaOptions = {
@ -48,7 +43,6 @@ type GeneralFormulaOptions<T extends [FormulaSource] | FormulaSource[]> = {
integrate?: IntegrateFunction<T>;
integrateInner?: IntegrateFunction<T>;
applySubstitution?: SubstitutionFunction<T>;
invertIntegral?: InvertIntegralFunction<T>;
hasVariable?: boolean;
};
type FormulaOptions<T extends [FormulaSource] | FormulaSource[]> =
@ -63,12 +57,11 @@ type InternalFormulaProperties<T extends [FormulaSource] | FormulaSource[]> = {
internalInvert?: InvertFunction<T>;
internalIntegrate?: IntegrateFunction<T>;
internalIntegrateInner?: IntegrateFunction<T>;
internalInvertIntegral?: InvertIntegralFunction<T>;
applySubstitution?: SubstitutionFunction<T>;
innermostVariable?: ProcessedComputable<DecimalSource>;
};
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<T extends FormulaSource[]> = {

View file

@ -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));