node-red-contrib-genetic-charging-strategy
Version:
A module for Node-RED that adds a battery charging strategy to node-red-contrib-power-saver. It uses genetic algorithms to find the best schedule
146 lines (145 loc) • 7.02 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.fitnessFunction = exports.cost = exports.calculatePeriodScore = exports.calculateIntervalScore = exports.calculateChargeScore = exports.calculateNormalScore = exports.calculateDischargeScore = exports.allPeriods = exports.splitIntoHourIntervals = void 0;
exports.splitIntoHourIntervalsGenerator = splitIntoHourIntervalsGenerator;
exports.allPeriodsGenerator = allPeriodsGenerator;
function* splitIntoHourIntervalsGenerator(seed) {
var _a;
let remainingDuration = (_a = seed.duration) !== null && _a !== void 0 ? _a : 0;
let start = seed.start;
while (remainingDuration > 0) {
const i = Math.min(60 - (start % 60), remainingDuration);
yield {
start: start,
duration: i,
activity: seed.activity,
};
start += i;
remainingDuration -= i;
}
return;
}
const splitIntoHourIntervals = (seed) => [
...splitIntoHourIntervalsGenerator(seed),
];
exports.splitIntoHourIntervals = splitIntoHourIntervals;
function* allPeriodsGenerator(props, phenotype) {
const { batteryMaxEnergy, soc, totalDuration } = props;
const { excessPvEnergyUse, periods } = phenotype;
let currentCharge = soc * batteryMaxEnergy;
const addCosts = (period) => {
const score = (0, exports.calculatePeriodScore)(props, period, excessPvEnergyUse, currentCharge);
currentCharge += score[1];
period.cost = score[0];
period.charge = score[1];
return period;
};
if (periods.isEmpty()) {
return;
}
let node = periods.head;
while (node) {
const end = node.next ? node.next.data.start : totalDuration;
const period = { ...node.data };
period.duration = end - period.start;
yield addCosts(period);
node = node.next;
}
}
const allPeriods = (props, phenotype) => {
return [...allPeriodsGenerator(props, phenotype)];
};
exports.allPeriods = allPeriods;
const CHARGE = 1;
const calculateDischargeScore = (props) => {
const { exportPrice, importPrice, consumption, production, maxDischarge, maxCharge, excessPvEnergyUse, batteryIdleLoss, } = props;
const consumedFromProduction = Math.min(consumption, production);
const batteryChargeFromProduction = excessPvEnergyUse == CHARGE ? Math.min(production - consumedFromProduction, maxCharge) : 0;
const consumedFromBattery = Math.min(consumption - consumedFromProduction, maxDischarge);
const lossFromBattery = Math.min(batteryIdleLoss, maxDischarge);
const soldFromProduction = production - consumedFromProduction - batteryChargeFromProduction;
const consumedFromGrid = consumption - consumedFromProduction - consumedFromBattery;
const cost = consumedFromGrid * importPrice - soldFromProduction * exportPrice;
const charge = batteryChargeFromProduction - consumedFromBattery - lossFromBattery;
return [cost, charge];
};
exports.calculateDischargeScore = calculateDischargeScore;
const calculateNormalScore = (props) => {
const { exportPrice, importPrice, maxDischarge, maxCharge, consumption, production, excessPvEnergyUse, batteryIdleLoss, } = props;
const consumedFromProduction = Math.min(consumption, production);
const batteryChargeFromProduction = excessPvEnergyUse == CHARGE ? Math.min(production - consumedFromProduction, maxCharge) : 0;
const lossFromBattery = Math.min(batteryIdleLoss, maxDischarge);
const soldFromProduction = production - consumedFromProduction - batteryChargeFromProduction;
const consumedFromGrid = consumption - consumedFromProduction;
const cost = importPrice * consumedFromGrid - exportPrice * soldFromProduction;
const charge = batteryChargeFromProduction - lossFromBattery;
return [cost, charge];
};
exports.calculateNormalScore = calculateNormalScore;
const calculateChargeScore = (props) => {
const { exportPrice, importPrice, consumption, production, maxCharge } = props;
const consumedFromProduction = Math.min(consumption, production);
const batteryChargeFromProduction = Math.min(production - consumedFromProduction, maxCharge);
const soldFromProduction = production - consumedFromProduction - batteryChargeFromProduction;
const consumedFromGrid = consumption - consumedFromProduction;
const chargedFromGrid = maxCharge - batteryChargeFromProduction;
const cost = (consumedFromGrid + chargedFromGrid) * importPrice - soldFromProduction * exportPrice;
const charge = batteryChargeFromProduction + chargedFromGrid;
return [cost, charge];
};
exports.calculateChargeScore = calculateChargeScore;
const calculateIntervalScore = (props) => {
switch (props.activity) {
case -1:
return (0, exports.calculateDischargeScore)(props);
case 1:
return (0, exports.calculateChargeScore)(props);
case 0:
default:
return (0, exports.calculateNormalScore)(props);
}
};
exports.calculateIntervalScore = calculateIntervalScore;
const calculatePeriodScore = (props, period, excessPvEnergyUse, _currentCharge) => {
const { input, batteryMaxEnergy, batteryMaxInputPower, batteryMaxOutputPower, batteryIdleLoss } = props;
let cost = 0;
let currentCharge = _currentCharge;
for (const interval of (0, exports.splitIntoHourIntervals)(period)) {
const duration = interval.duration / 60;
const maxCharge = Math.min(batteryMaxInputPower * duration, batteryMaxEnergy - currentCharge);
const maxDischarge = Math.min(batteryMaxOutputPower * duration, currentCharge);
const { importPrice, exportPrice, consumption, production } = input[Math.floor(interval.start / 60)];
const v = (0, exports.calculateIntervalScore)({
activity: interval.activity,
importPrice,
exportPrice,
consumption: consumption * duration,
production: production * duration,
maxCharge,
maxDischarge,
excessPvEnergyUse,
batteryIdleLoss: batteryIdleLoss * duration,
});
cost += v[0];
currentCharge += v[1];
}
return [cost, currentCharge - _currentCharge];
};
exports.calculatePeriodScore = calculatePeriodScore;
const cost = (periods) => {
return periods.reduce((acc, cur) => { var _a; return acc + ((_a = cur.cost) !== null && _a !== void 0 ? _a : 0); }, 0);
};
exports.cost = cost;
const fitnessFunction = (props) => (phenotype) => {
const periods = (0, exports.allPeriods)(props, phenotype);
let score = -(0, exports.cost)(periods);
const averagePrice = props.input.reduce((acc, cur) => acc + cur.importPrice, 0) / props.input.length;
score -= periods.reduce((acc, cur) => {
if (cur.activity != 0 && cur.charge == 0 && cur.duration !== undefined)
return acc + (cur.duration * averagePrice) / 60;
else
return acc;
}, 0);
return score;
};
exports.fitnessFunction = fitnessFunction;