forked from profectus/Profectus
Make clamping functions pass-throughs for inverting
This commit is contained in:
parent
fd925071e5
commit
553c6a4554
2 changed files with 153 additions and 58 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue