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 sgn = InternalFormula.sign;
public static round(value: FormulaSource) { 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) { 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) { 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) { 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; 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({ return new Formula({
inputs: [value, min, max], inputs: [value, min, max],
evaluate: Decimal.clamp, 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 Decimal, { DecimalSource } from "util/bignum";
import Formula, { hasVariable, unrefFormulaSource } from "./formulas"; 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); const ln10 = Decimal.ln(10);
@ -8,6 +14,15 @@ export function passthrough<T extends GenericFormula | DecimalSource>(value: T):
return value; 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) { export function invertNeg(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.neg(value)); 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 testValues = [-2, "0", new Decimal(10.5)] as const;
const invertibleZeroParamFunctionNames = [ const invertibleZeroParamFunctionNames = [
"round",
"floor",
"ceil",
"trunc",
"neg", "neg",
"recip", "recip",
"log10", "log10",
@ -48,10 +52,6 @@ const invertibleZeroParamFunctionNames = [
const nonInvertibleZeroParamFunctionNames = [ const nonInvertibleZeroParamFunctionNames = [
"abs", "abs",
"sign", "sign",
"round",
"floor",
"ceil",
"trunc",
"pLog10", "pLog10",
"absLog10", "absLog10",
"factorial", "factorial",
@ -85,6 +85,10 @@ const integrableZeroParamFunctionNames = [
] as const; ] as const;
const nonIntegrableZeroParamFunctionNames = [ const nonIntegrableZeroParamFunctionNames = [
...nonInvertibleZeroParamFunctionNames, ...nonInvertibleZeroParamFunctionNames,
"round",
"floor",
"ceil",
"trunc",
"lambertw", "lambertw",
"ssqrt" "ssqrt"
] as const; ] as const;
@ -151,7 +155,7 @@ describe("Formula Equality Checking", () => {
describe("Formula aliases", () => { describe("Formula aliases", () => {
function testAliases<T extends FormulaFunctions>( function testAliases<T extends FormulaFunctions>(
aliases: T[], aliases: T[],
args: Parameters<(typeof Formula)[T]> args: Parameters<typeof Formula[T]>
) { ) {
describe(aliases[0], () => { describe(aliases[0], () => {
let formula: GenericFormula; let formula: GenericFormula;
@ -246,7 +250,7 @@ describe("Creating Formulas", () => {
function checkFormula<T extends FormulaFunctions>( function checkFormula<T extends FormulaFunctions>(
functionName: T, functionName: T,
args: Readonly<Parameters<(typeof Formula)[T]>> args: Readonly<Parameters<typeof Formula[T]>>
) { ) {
let formula: GenericFormula; let formula: GenericFormula;
beforeAll(() => { beforeAll(() => {
@ -270,7 +274,7 @@ describe("Creating Formulas", () => {
// It's a lot of tests, but I'd rather be exhaustive // It's a lot of tests, but I'd rather be exhaustive
function testFormulaCall<T extends FormulaFunctions>( function testFormulaCall<T extends FormulaFunctions>(
functionName: T, functionName: T,
args: Readonly<Parameters<(typeof Formula)[T]>> args: Readonly<Parameters<typeof Formula[T]>>
) { ) {
if ((functionName === "slog" || functionName === "layeradd") && args[0] === -1) { if ((functionName === "slog" || functionName === "layeradd") && args[0] === -1) {
// These cases in particular take a long time, so skip them // These cases in particular take a long time, so skip them
@ -488,18 +492,18 @@ describe("Inverting", () => {
}); });
test("Inverting nested formulas", () => { 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); expect(formula.invert(100)).compare_tolerance(0);
}); });
describe("Inverting with non-invertible sections", () => { describe("Inverting with non-invertible sections", () => {
test("Non-invertible constant", () => { 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.isInvertible()).toBe(true);
expect(() => formula.invert(10)).not.toLogError(); expect(() => formula.invert(10)).not.toLogError();
}); });
test("Non-invertible variable", () => { 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.isInvertible()).toBe(false);
expect(() => formula.invert(10)).toLogError(); expect(() => formula.invert(10)).toLogError();
}); });