68 lines
2.6 KiB
JavaScript
68 lines
2.6 KiB
JavaScript
|
|
const test = require('node:test');
|
||
|
|
const assert = require('node:assert/strict');
|
||
|
|
|
||
|
|
const { calcBestCombination } = require('../../src/optimizer/bestCombination');
|
||
|
|
|
||
|
|
function makeMachine({ id, fMin = 0, fMax = 100, NCog = 0.5, costFn } = {}) {
|
||
|
|
return {
|
||
|
|
config: { general: { id } },
|
||
|
|
NCog,
|
||
|
|
predictFlow: { currentFxyYMin: fMin, currentFxyYMax: fMax },
|
||
|
|
predictPower: { currentFxyYMin: 0, currentFxyYMax: fMax * 2 },
|
||
|
|
// Power model: caller picks the cost function so we can shape who wins.
|
||
|
|
inputFlowCalcPower: costFn ?? ((flow) => flow * 1.0),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function mkCtx(machines) {
|
||
|
|
return {
|
||
|
|
machines,
|
||
|
|
groupCurves: {
|
||
|
|
groupFlow: (m) => m.predictFlow,
|
||
|
|
groupPower: (m) => m.predictPower,
|
||
|
|
groupNCog: (m) => m.NCog ?? 0,
|
||
|
|
groupCalcPower: (m, f) => m.inputFlowCalcPower(f),
|
||
|
|
},
|
||
|
|
logger: { debug: () => {} },
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
test('calcBestCombination: 1 machine in combination receives Qd clamped to its range', () => {
|
||
|
|
const machines = { a: makeMachine({ id: 'a', fMin: 5, fMax: 60 }) };
|
||
|
|
const ctx = mkCtx(machines);
|
||
|
|
|
||
|
|
const res = calcBestCombination([['a']], 40, ctx);
|
||
|
|
assert.ok(res.bestCombination);
|
||
|
|
assert.equal(res.bestCombination.length, 1);
|
||
|
|
assert.equal(res.bestCombination[0].flow, 40);
|
||
|
|
|
||
|
|
// Above max — clamps to max.
|
||
|
|
const high = calcBestCombination([['a']], 200, ctx);
|
||
|
|
assert.equal(high.bestCombination[0].flow, 60);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('calcBestCombination: 2 machines with equal NCog split flow evenly', () => {
|
||
|
|
const machines = {
|
||
|
|
a: makeMachine({ id: 'a', NCog: 0.5, fMin: 0, fMax: 100 }),
|
||
|
|
b: makeMachine({ id: 'b', NCog: 0.5, fMin: 0, fMax: 100 }),
|
||
|
|
};
|
||
|
|
const ctx = mkCtx(machines);
|
||
|
|
const res = calcBestCombination([['a', 'b']], 40, ctx);
|
||
|
|
const aFlow = res.bestCombination.find(e => e.machineId === 'a').flow;
|
||
|
|
const bFlow = res.bestCombination.find(e => e.machineId === 'b').flow;
|
||
|
|
assert.ok(Math.abs(aFlow - bFlow) < 1e-6, `expected even split, got a=${aFlow} b=${bFlow}`);
|
||
|
|
assert.ok(Math.abs(aFlow + bFlow - 40) < 1e-6);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('calcBestCombination: returns combination with the lowest total power', () => {
|
||
|
|
// Two combinations: [a] (expensive) vs [b] (cheap). Both can deliver Qd=20.
|
||
|
|
const machines = {
|
||
|
|
a: makeMachine({ id: 'a', fMin: 0, fMax: 100, costFn: (f) => f * 10 }),
|
||
|
|
b: makeMachine({ id: 'b', fMin: 0, fMax: 100, costFn: (f) => f * 1 }),
|
||
|
|
};
|
||
|
|
const ctx = mkCtx(machines);
|
||
|
|
const res = calcBestCombination([['a'], ['b']], 20, ctx);
|
||
|
|
assert.equal(res.bestCombination[0].machineId, 'b');
|
||
|
|
assert.equal(res.bestPower, 20);
|
||
|
|
});
|