Make clamping functions pass-throughs for inverting

This commit is contained in:
thepaperpilot 2023-02-14 02:41:33 -06:00
parent ba19f4a727
commit f3ed96f8cf
2 changed files with 153 additions and 58 deletions

View file

@ -56,6 +56,10 @@ export function unrefFormulaSource(value: FormulaSource, variable?: DecimalSourc
return value instanceof Formula ? value.evaluate(variable) : unref(value);
}
function passthrough(value: DecimalSource) {
return value;
}
function invertNeg(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) {
return lhs.invert(Decimal.neg(value));
@ -1216,19 +1220,63 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
}
public static max(value: FormulaSource, other: FormulaSource): GenericFormula {
return new Formula({ inputs: [value, other], evaluate: Decimal.max });
return new Formula({
inputs: [value, other],
evaluate: Decimal.max,
invert: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource,
invertIntegral: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource
});
}
public static min(value: FormulaSource, other: FormulaSource): GenericFormula {
return new Formula({ inputs: [value, other], evaluate: Decimal.min });
return new Formula({
inputs: [value, other],
evaluate: Decimal.min,
invert: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource,
invertIntegral: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource
});
}
public static minabs(value: FormulaSource, other: FormulaSource): GenericFormula {
return new Formula({ inputs: [value, other], evaluate: Decimal.minabs });
return new Formula({
inputs: [value, other],
evaluate: Decimal.minabs,
invert: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource,
invertIntegral: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource
});
}
public static maxabs(value: FormulaSource, other: FormulaSource): GenericFormula {
return new Formula({ inputs: [value, other], evaluate: Decimal.maxabs });
return new Formula({
inputs: [value, other],
evaluate: Decimal.maxabs,
invert: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource,
invertIntegral: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource
});
}
public static clamp(
@ -1236,15 +1284,48 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
min: FormulaSource,
max: FormulaSource
): GenericFormula {
return new Formula({ inputs: [value, min, max], evaluate: Decimal.clamp });
return new Formula({
inputs: [value, min, max],
evaluate: Decimal.clamp,
invert: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource, FormulaSource]
) => DecimalSource,
invertIntegral: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource, FormulaSource]
) => DecimalSource
});
}
public static clampMin(value: FormulaSource, min: FormulaSource): GenericFormula {
return new Formula({ inputs: [value, min], evaluate: Decimal.clampMin });
return new Formula({
inputs: [value, min],
evaluate: Decimal.clampMin,
invert: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource,
invertIntegral: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource
});
}
public static clampMax(value: FormulaSource, max: FormulaSource): GenericFormula {
return new Formula({ inputs: [value, max], evaluate: Decimal.clampMax });
return new Formula({
inputs: [value, max],
evaluate: Decimal.clampMax,
invert: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource,
invertIntegral: passthrough as (
value: DecimalSource,
...inputs: [FormulaSource, FormulaSource]
) => DecimalSource
});
}
public static pLog10(value: FormulaSource): GenericFormula {

View file

@ -124,22 +124,14 @@ const invertibleOneParamFunctionNames = [
"root",
"slog"
] as const;
const nonInvertibleOneParamFunctionNames = [
"max",
"min",
"maxabs",
"minabs",
"clampMin",
"clampMax",
"layeradd10"
] as const;
const nonInvertibleOneParamFunctionNames = ["layeradd10"] as const;
const integrableOneParamFunctionNames = ["add", "sub", "mul", "div", "log", "pow", "root"] as const;
const nonIntegrableOneParamFunctionNames = [...nonInvertibleOneParamFunctionNames, "slog"] as const;
const invertibleIntegralOneParamFunctionNames = integrableOneParamFunctionNames;
const nonInvertibleIntegralOneParamFunctionNames = nonIntegrableOneParamFunctionNames;
const invertibleTwoParamFunctionNames = ["tetrate", "layeradd", "iteratedexp"] as const;
const nonInvertibleTwoParamFunctionNames = ["clamp", "iteratedlog", "pentate"] as const;
const nonInvertibleTwoParamFunctionNames = ["iteratedlog", "pentate"] as const;
const nonIntegrableTwoParamFunctionNames = [
...invertibleTwoParamFunctionNames,
...nonInvertibleZeroParamFunctionNames
@ -308,24 +300,28 @@ describe("Creating Formulas", () => {
});
}
describe("Invertible 0-param", () => {
invertibleZeroParamFunctionNames.forEach(names =>
describe(names, () => {
checkFormula(names, [0] as const);
testValues.forEach(i => testFormulaCall(names, [i] as const));
})
describe("0-param", () => {
[...invertibleZeroParamFunctionNames, ...nonInvertibleZeroParamFunctionNames].forEach(
names =>
describe(names, () => {
checkFormula(names, [0] as const);
testValues.forEach(i => testFormulaCall(names, [i] as const));
})
);
});
describe("Non-Invertible 0-param", () => {
nonInvertibleZeroParamFunctionNames.forEach(names =>
describe(names, () => {
checkFormula(names, [0] as const);
testValues.forEach(i => testFormulaCall(names, [i] as const));
})
);
});
describe("Invertible 1-param", () => {
invertibleOneParamFunctionNames.forEach(names =>
describe("1-param", () => {
(
[
...invertibleOneParamFunctionNames,
...nonInvertibleOneParamFunctionNames,
"max",
"min",
"maxabs",
"minabs",
"clampMin",
"clampMax"
] as const
).forEach(names =>
describe(names, () => {
checkFormula(names, [0, 0] as const);
testValues.forEach(i =>
@ -334,30 +330,14 @@ describe("Creating Formulas", () => {
})
);
});
describe("Non-Invertible 1-param", () => {
nonInvertibleOneParamFunctionNames.forEach(names =>
describe(names, () => {
checkFormula(names, [0, 0] as const);
testValues.forEach(i =>
testValues.forEach(j => testFormulaCall(names, [i, j] as const))
);
})
);
});
describe("Invertible 2-param", () => {
invertibleTwoParamFunctionNames.forEach(names =>
describe(names, () => {
checkFormula(names, [0, 0, 0] as const);
testValues.forEach(i =>
testValues.forEach(j =>
testValues.forEach(k => testFormulaCall(names, [i, j, k] as const))
)
);
})
);
});
describe("Non-Invertible 2-param", () => {
nonInvertibleTwoParamFunctionNames.forEach(names =>
describe("2-param", () => {
(
[
...invertibleTwoParamFunctionNames,
...nonInvertibleTwoParamFunctionNames,
"clamp"
] as const
).forEach(names =>
describe(names, () => {
checkFormula(names, [0, 0, 0] as const);
testValues.forEach(i =>
@ -527,6 +507,21 @@ describe("Inverting", () => {
);
});
describe("Inverting pass-throughs", () => {
test("max", () => expect(Formula.max(variable, constant).invert(10)).compare_tolerance(10));
test("min", () => expect(Formula.min(variable, constant).invert(10)).compare_tolerance(10));
test("minabs", () =>
expect(Formula.minabs(variable, constant).invert(10)).compare_tolerance(10));
test("maxabs", () =>
expect(Formula.maxabs(variable, constant).invert(10)).compare_tolerance(10));
test("clampMax", () =>
expect(Formula.clampMax(variable, constant).invert(10)).compare_tolerance(10));
test("clampMin", () =>
expect(Formula.clampMin(variable, constant).invert(10)).compare_tolerance(10));
test("clamp", () =>
expect(Formula.clamp(variable, constant, constant).invert(10)).compare_tolerance(10));
});
test("Inverting nested formulas", () => {
const formula = Formula.add(variable, constant).times(constant);
expect(formula.invert(100)).compare_tolerance(0);
@ -705,6 +700,25 @@ describe("Inverting integrals", () => {
const formula = Formula.add(variable, constant).times(constant);
expect(formula.invertIntegral(1500)).compare_tolerance(10);
});
describe("Inverting integral pass-throughs", () => {
test("max", () =>
expect(Formula.max(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("min", () =>
expect(Formula.min(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("minabs", () =>
expect(Formula.minabs(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("maxabs", () =>
expect(Formula.maxabs(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("clampMax", () =>
expect(Formula.clampMax(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("clampMin", () =>
expect(Formula.clampMin(variable, constant).invertIntegral(10)).compare_tolerance(10));
test("clamp", () =>
expect(
Formula.clamp(variable, constant, constant).invertIntegral(10)
).compare_tolerance(10));
});
});
describe("Step-wise", () => {
@ -980,7 +994,7 @@ describe("Buy Max", () => {
});
describe("Without spending", () => {
test("Throws on non-invertible formula", () => {
const maxAffordable = calculateMaxAffordable(Formula.abs(10), resource, false);
const maxAffordable = calculateMaxAffordable(Formula.abs(10), resource);
expect(() => maxAffordable.value).toThrow();
});
// https://www.desmos.com/calculator/5vgletdc1p