Fix passthroughs for inversions and make more operations invertible

This commit is contained in:
thepaperpilot 2023-05-30 22:52:24 -05:00
parent 9edda4d957
commit eee5ac3e2d
3 changed files with 51 additions and 16 deletions

View file

@ -345,19 +345,35 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
public static sgn = InternalFormula.sign;
public static round(value: FormulaSource) {
return new Formula({ inputs: [value], evaluate: Decimal.round });
return new Formula({
inputs: [value],
evaluate: Decimal.round,
invert: ops.invertPassthrough
});
}
public static floor(value: FormulaSource) {
return new Formula({ inputs: [value], evaluate: Decimal.floor });
return new Formula({
inputs: [value],
evaluate: Decimal.floor,
invert: ops.invertPassthrough
});
}
public static ceil(value: FormulaSource) {
return new Formula({ inputs: [value], evaluate: Decimal.ceil });
return new Formula({
inputs: [value],
evaluate: Decimal.ceil,
invert: ops.invertPassthrough
});
}
public static trunc(value: FormulaSource) {
return new Formula({ inputs: [value], evaluate: Decimal.trunc });
return new Formula({
inputs: [value],
evaluate: Decimal.trunc,
invert: ops.invertPassthrough
});
}
public static add<T extends GenericFormula>(value: T, other: FormulaSource): T;
@ -459,7 +475,7 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
return new Formula({
inputs: [value, min, max],
evaluate: Decimal.clamp,
invert: ops.passthrough as InvertFunction<[FormulaSource, FormulaSource, FormulaSource]>
invert: ops.invertPassthrough
});
}

View file

@ -1,6 +1,12 @@
import Decimal, { DecimalSource } from "util/bignum";
import Formula, { hasVariable, unrefFormulaSource } from "./formulas";
import { FormulaSource, GenericFormula, InvertFunction, SubstitutionStack } from "./types";
import {
FormulaSource,
GenericFormula,
InvertFunction,
InvertibleFormula,
SubstitutionStack
} from "./types";
const ln10 = Decimal.ln(10);
@ -8,6 +14,15 @@ export function passthrough<T extends GenericFormula | DecimalSource>(value: T):
return value;
}
export function invertPassthrough(value: DecimalSource, ...inputs: FormulaSource[]) {
const variable = inputs.find(input => hasVariable(input)) as InvertibleFormula | undefined;
if (variable == null) {
console.error("Could not invert due to no input being a variable");
return 0;
}
return variable.invert(value);
}
export function invertNeg(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) {
return lhs.invert(Decimal.neg(value));

View file

@ -16,6 +16,10 @@ type FormulaFunctions = keyof GenericFormula & keyof typeof Formula & keyof type
const testValues = [-2, "0", new Decimal(10.5)] as const;
const invertibleZeroParamFunctionNames = [
"round",
"floor",
"ceil",
"trunc",
"neg",
"recip",
"log10",
@ -48,10 +52,6 @@ const invertibleZeroParamFunctionNames = [
const nonInvertibleZeroParamFunctionNames = [
"abs",
"sign",
"round",
"floor",
"ceil",
"trunc",
"pLog10",
"absLog10",
"factorial",
@ -85,6 +85,10 @@ const integrableZeroParamFunctionNames = [
] as const;
const nonIntegrableZeroParamFunctionNames = [
...nonInvertibleZeroParamFunctionNames,
"round",
"floor",
"ceil",
"trunc",
"lambertw",
"ssqrt"
] as const;
@ -151,7 +155,7 @@ describe("Formula Equality Checking", () => {
describe("Formula aliases", () => {
function testAliases<T extends FormulaFunctions>(
aliases: T[],
args: Parameters<(typeof Formula)[T]>
args: Parameters<typeof Formula[T]>
) {
describe(aliases[0], () => {
let formula: GenericFormula;
@ -246,7 +250,7 @@ describe("Creating Formulas", () => {
function checkFormula<T extends FormulaFunctions>(
functionName: T,
args: Readonly<Parameters<(typeof Formula)[T]>>
args: Readonly<Parameters<typeof Formula[T]>>
) {
let formula: GenericFormula;
beforeAll(() => {
@ -270,7 +274,7 @@ describe("Creating Formulas", () => {
// It's a lot of tests, but I'd rather be exhaustive
function testFormulaCall<T extends FormulaFunctions>(
functionName: T,
args: Readonly<Parameters<(typeof Formula)[T]>>
args: Readonly<Parameters<typeof Formula[T]>>
) {
if ((functionName === "slog" || functionName === "layeradd") && args[0] === -1) {
// These cases in particular take a long time, so skip them
@ -488,18 +492,18 @@ describe("Inverting", () => {
});
test("Inverting nested formulas", () => {
const formula = Formula.add(variable, constant).times(constant);
const formula = Formula.add(variable, constant).times(constant).floor();
expect(formula.invert(100)).compare_tolerance(0);
});
describe("Inverting with non-invertible sections", () => {
test("Non-invertible constant", () => {
const formula = Formula.add(variable, constant.ceil());
const formula = Formula.add(variable, constant.sign());
expect(formula.isInvertible()).toBe(true);
expect(() => formula.invert(10)).not.toLogError();
});
test("Non-invertible variable", () => {
const formula = Formula.add(variable.ceil(), constant);
const formula = Formula.add(variable.sign(), constant);
expect(formula.isInvertible()).toBe(false);
expect(() => formula.invert(10)).toLogError();
});