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);
|
return value instanceof Formula ? value.evaluate(variable) : unref(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function passthrough(value: DecimalSource) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
function invertNeg(value: DecimalSource, lhs: FormulaSource) {
|
function invertNeg(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.neg(value));
|
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 {
|
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 {
|
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 {
|
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 {
|
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(
|
public static clamp(
|
||||||
|
@ -1236,15 +1284,48 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
|
||||||
min: FormulaSource,
|
min: FormulaSource,
|
||||||
max: FormulaSource
|
max: FormulaSource
|
||||||
): GenericFormula {
|
): 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 {
|
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 {
|
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 {
|
public static pLog10(value: FormulaSource): GenericFormula {
|
||||||
|
|
|
@ -124,22 +124,14 @@ const invertibleOneParamFunctionNames = [
|
||||||
"root",
|
"root",
|
||||||
"slog"
|
"slog"
|
||||||
] as const;
|
] as const;
|
||||||
const nonInvertibleOneParamFunctionNames = [
|
const nonInvertibleOneParamFunctionNames = ["layeradd10"] as const;
|
||||||
"max",
|
|
||||||
"min",
|
|
||||||
"maxabs",
|
|
||||||
"minabs",
|
|
||||||
"clampMin",
|
|
||||||
"clampMax",
|
|
||||||
"layeradd10"
|
|
||||||
] as const;
|
|
||||||
const integrableOneParamFunctionNames = ["add", "sub", "mul", "div", "log", "pow", "root"] as const;
|
const integrableOneParamFunctionNames = ["add", "sub", "mul", "div", "log", "pow", "root"] as const;
|
||||||
const nonIntegrableOneParamFunctionNames = [...nonInvertibleOneParamFunctionNames, "slog"] as const;
|
const nonIntegrableOneParamFunctionNames = [...nonInvertibleOneParamFunctionNames, "slog"] as const;
|
||||||
const invertibleIntegralOneParamFunctionNames = integrableOneParamFunctionNames;
|
const invertibleIntegralOneParamFunctionNames = integrableOneParamFunctionNames;
|
||||||
const nonInvertibleIntegralOneParamFunctionNames = nonIntegrableOneParamFunctionNames;
|
const nonInvertibleIntegralOneParamFunctionNames = nonIntegrableOneParamFunctionNames;
|
||||||
|
|
||||||
const invertibleTwoParamFunctionNames = ["tetrate", "layeradd", "iteratedexp"] as const;
|
const invertibleTwoParamFunctionNames = ["tetrate", "layeradd", "iteratedexp"] as const;
|
||||||
const nonInvertibleTwoParamFunctionNames = ["clamp", "iteratedlog", "pentate"] as const;
|
const nonInvertibleTwoParamFunctionNames = ["iteratedlog", "pentate"] as const;
|
||||||
const nonIntegrableTwoParamFunctionNames = [
|
const nonIntegrableTwoParamFunctionNames = [
|
||||||
...invertibleTwoParamFunctionNames,
|
...invertibleTwoParamFunctionNames,
|
||||||
...nonInvertibleZeroParamFunctionNames
|
...nonInvertibleZeroParamFunctionNames
|
||||||
|
@ -308,24 +300,28 @@ describe("Creating Formulas", () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Invertible 0-param", () => {
|
describe("0-param", () => {
|
||||||
invertibleZeroParamFunctionNames.forEach(names =>
|
[...invertibleZeroParamFunctionNames, ...nonInvertibleZeroParamFunctionNames].forEach(
|
||||||
describe(names, () => {
|
names =>
|
||||||
checkFormula(names, [0] as const);
|
describe(names, () => {
|
||||||
testValues.forEach(i => testFormulaCall(names, [i] as const));
|
checkFormula(names, [0] as const);
|
||||||
})
|
testValues.forEach(i => testFormulaCall(names, [i] as const));
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
describe("Non-Invertible 0-param", () => {
|
describe("1-param", () => {
|
||||||
nonInvertibleZeroParamFunctionNames.forEach(names =>
|
(
|
||||||
describe(names, () => {
|
[
|
||||||
checkFormula(names, [0] as const);
|
...invertibleOneParamFunctionNames,
|
||||||
testValues.forEach(i => testFormulaCall(names, [i] as const));
|
...nonInvertibleOneParamFunctionNames,
|
||||||
})
|
"max",
|
||||||
);
|
"min",
|
||||||
});
|
"maxabs",
|
||||||
describe("Invertible 1-param", () => {
|
"minabs",
|
||||||
invertibleOneParamFunctionNames.forEach(names =>
|
"clampMin",
|
||||||
|
"clampMax"
|
||||||
|
] as const
|
||||||
|
).forEach(names =>
|
||||||
describe(names, () => {
|
describe(names, () => {
|
||||||
checkFormula(names, [0, 0] as const);
|
checkFormula(names, [0, 0] as const);
|
||||||
testValues.forEach(i =>
|
testValues.forEach(i =>
|
||||||
|
@ -334,30 +330,14 @@ describe("Creating Formulas", () => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
describe("Non-Invertible 1-param", () => {
|
describe("2-param", () => {
|
||||||
nonInvertibleOneParamFunctionNames.forEach(names =>
|
(
|
||||||
describe(names, () => {
|
[
|
||||||
checkFormula(names, [0, 0] as const);
|
...invertibleTwoParamFunctionNames,
|
||||||
testValues.forEach(i =>
|
...nonInvertibleTwoParamFunctionNames,
|
||||||
testValues.forEach(j => testFormulaCall(names, [i, j] as const))
|
"clamp"
|
||||||
);
|
] as const
|
||||||
})
|
).forEach(names =>
|
||||||
);
|
|
||||||
});
|
|
||||||
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(names, () => {
|
describe(names, () => {
|
||||||
checkFormula(names, [0, 0, 0] as const);
|
checkFormula(names, [0, 0, 0] as const);
|
||||||
testValues.forEach(i =>
|
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", () => {
|
test("Inverting nested formulas", () => {
|
||||||
const formula = Formula.add(variable, constant).times(constant);
|
const formula = Formula.add(variable, constant).times(constant);
|
||||||
expect(formula.invert(100)).compare_tolerance(0);
|
expect(formula.invert(100)).compare_tolerance(0);
|
||||||
|
@ -705,6 +700,25 @@ describe("Inverting integrals", () => {
|
||||||
const formula = Formula.add(variable, constant).times(constant);
|
const formula = Formula.add(variable, constant).times(constant);
|
||||||
expect(formula.invertIntegral(1500)).compare_tolerance(10);
|
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", () => {
|
describe("Step-wise", () => {
|
||||||
|
@ -980,7 +994,7 @@ describe("Buy Max", () => {
|
||||||
});
|
});
|
||||||
describe("Without spending", () => {
|
describe("Without spending", () => {
|
||||||
test("Throws on non-invertible formula", () => {
|
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();
|
expect(() => maxAffordable.value).toThrow();
|
||||||
});
|
});
|
||||||
// https://www.desmos.com/calculator/5vgletdc1p
|
// https://www.desmos.com/calculator/5vgletdc1p
|
||||||
|
|
Loading…
Reference in a new issue