From c9086e9ea229e17b3508d195f5a84ca577ee7fc3 Mon Sep 17 00:00:00 2001 From: Johan Enell Date: Tue, 7 Mar 2023 14:54:08 +0100 Subject: [PATCH] fix: improved toSchedule --- src/fitness.js | 60 +++++++++------- src/strategy-battery-charging-functions.js | 72 ++++++++----------- test/fitness.test.js | 42 +++-------- ...-battery-charging-functions-mutate.test.js | 8 +-- ...trategy-battery-charging-functions.test.js | 4 +- 5 files changed, 82 insertions(+), 104 deletions(-) diff --git a/src/fitness.js b/src/fitness.js index 8048839..e6c7d11 100644 --- a/src/fitness.js +++ b/src/fitness.js @@ -34,20 +34,17 @@ function* fillInNormalPeriodsGenerator(totalDuration, p) { p[i - 1] ?? { start: 0, duration: 0 }, p[i] ) - for (const interval of splitIntoHourIntervalsGenerator(normalPeriod)) { - yield interval - } - for (const interval of splitIntoHourIntervalsGenerator(p[i])) { - yield interval - } + if (normalPeriod.duration > 0) yield normalPeriod + yield p[i] } + const normalPeriod = calculateNormalPeriod( p.at(-1) ?? { start: 0, duration: 0 }, - { start: totalDuration } + { + start: totalDuration, + } ) - for (const interval of splitIntoHourIntervalsGenerator(normalPeriod)) { - yield interval - } + if (normalPeriod.duration > 0) yield normalPeriod } const fillInNormalPeriods = (totalDuration, p) => { @@ -133,7 +130,7 @@ const calculateChargeScore = (props) => { return [cost, charge] } -const calculatePeriodScore = (props) => { +const calculateIntervalScore = (props) => { switch (props.activity) { case -1: return calculateDischargeScore(props) @@ -144,17 +141,12 @@ const calculatePeriodScore = (props) => { } } -const fitnessFunction = (props) => (phenotype) => { - const { totalDuration, input, batteryMaxEnergy, batteryMaxInputPower, soc } = +const calculatePeriodScore = (props, period, _currentCharge) => { + const { input, batteryMaxEnergy, batteryMaxInputPower, excessPvEnergyUse } = props - - let score = 0 - let currentCharge = soc * batteryMaxEnergy - - for (const interval of fillInNormalPeriodsGenerator( - totalDuration, - phenotype.periods - )) { + let cost = 0 + let currentCharge = _currentCharge + for (const interval of splitIntoHourIntervals(period)) { const duration = interval.duration / 60 const maxCharge = Math.min( batteryMaxInputPower, @@ -164,7 +156,7 @@ const fitnessFunction = (props) => (phenotype) => { const { importPrice, exportPrice, consumption, production } = input[Math.floor(interval.start / 60)] - const v = calculatePeriodScore({ + const v = calculateIntervalScore({ activity: interval.activity, importPrice, exportPrice, @@ -172,18 +164,36 @@ const fitnessFunction = (props) => (phenotype) => { production: production * duration, maxCharge, maxDischarge, - excessPvEnergyUse: phenotype.excessPvEnergyUse, + excessPvEnergyUse: excessPvEnergyUse, }) - score -= v[0] + cost += v[0] currentCharge += v[1] } + return [cost, currentCharge - _currentCharge] +} + +const fitnessFunction = (props) => (phenotype) => { + const { totalDuration, batteryMaxEnergy, soc } = props + + let cost = 0 + let currentCharge = soc * batteryMaxEnergy + + for (const period of fillInNormalPeriodsGenerator( + totalDuration, + phenotype.periods + )) { + const score = calculatePeriodScore(props, period, currentCharge) + cost -= score[0] + currentCharge += score[1] + } - return score + return cost } module.exports = { fitnessFunction, splitIntoHourIntervals, + fillInNormalPeriodsGenerator, fillInNormalPeriods, calculateDischargeScore, calculateChargeScore, diff --git a/src/strategy-battery-charging-functions.js b/src/strategy-battery-charging-functions.js index 6913022..8b6d0fc 100644 --- a/src/strategy-battery-charging-functions.js +++ b/src/strategy-battery-charging-functions.js @@ -1,5 +1,5 @@ const geneticAlgorithmConstructor = require('geneticalgorithm') -const { fitnessFunction } = require('./fitness') +const { fitnessFunction, fillInNormalPeriodsGenerator } = require('./fitness') const random = (min, max) => { return Math.floor(Math.random() * (max - min)) + min @@ -138,54 +138,40 @@ const generatePopulation = ( return population } -const toSchedule = (p, start) => { +const toSchedule = (p, start, totalDuration) => { const addMinutes = (date, minutes) => { return new Date(date.getTime() + minutes * 60000) } + const activityToName = (activity) => { + switch (activity) { + case -1: + return 'discharging' + case 1: + return 'charging' + default: + return 'idle' + } + } + const schedule = [] - p.forEach((g) => { - if (g.duration > 0) { - if ( - schedule.length > 0 && - g.activity === schedule[schedule.length - 1].activity - ) { - schedule[schedule.length - 1].duration += g.duration - } else { - let emptyPeriodStart = new Date(start) - if (schedule.length > 0) { - emptyPeriodStart = addMinutes( - schedule[schedule.length - 1].start, - schedule[schedule.length - 1].duration - ) - } - schedule.push({ - start: emptyPeriodStart, - activity: 0, - name: 'none', - }) - let periodStart = new Date(start) - periodStart = addMinutes(periodStart, g.start) - const name = g.activity === 1 ? 'charging' : 'discharging' - schedule.push({ - start: periodStart, - activity: g.activity, - duration: g.duration, - name, - }) - } + for (const period of fillInNormalPeriodsGenerator(totalDuration, p)) { + if (period.duration <= 0) { + continue + } + let periodStart = new Date(start) + if (schedule.length && period.activity === schedule.at(-1).activity) { + schedule[schedule.length - 1].duration += period.duration + } else { + schedule.push({ + start: addMinutes(periodStart, period.start), + activity: period.activity, + duration: period.duration, + name: activityToName(period.activity), + }) } - }) - - let emptyPeriodStart = new Date(start) - if (schedule.length > 0) { - emptyPeriodStart = addMinutes( - schedule[schedule.length - 1].start, - schedule[schedule.length - 1].duration - ) } - schedule.push({ start: emptyPeriodStart, activity: 0, name: 'none' }) return schedule } @@ -272,12 +258,12 @@ const calculateBatteryChargingStrategy = (config) => { const noBattery = { periods: [], excessPvEnergyUse: 0 } return { best: { - schedule: toSchedule(best.periods, input[0].start), + schedule: toSchedule(best.periods, input[0].start, totalDuration), excessPvEnergyUse: best.excessPvEnergyUse, cost: f(best) * -1, }, noBattery: { - schedule: toSchedule(noBattery.periods, input[0].start), + schedule: toSchedule(noBattery.periods, input[0].start, totalDuration), excessPvEnergyUse: noBattery.excessPvEnergyUse, cost: f(noBattery) * -1, }, diff --git a/test/fitness.test.js b/test/fitness.test.js index cf6d676..f65c5d1 100644 --- a/test/fitness.test.js +++ b/test/fitness.test.js @@ -44,35 +44,23 @@ describe('Fitness - splitIntoHourIntervals', () => { describe('Fitness - fillInNormalPeriods', () => { test('should test fillInNormalPeriods empty', () => { expect(fillInNormalPeriods(300, [])).toMatchObject([ - { start: 0, duration: 60, activity: 0 }, - { start: 60, duration: 60, activity: 0 }, - { start: 120, duration: 60, activity: 0 }, - { start: 180, duration: 60, activity: 0 }, - { start: 240, duration: 60, activity: 0 }, + { start: 0, duration: 300, activity: 0 }, ]) }) test('should test fillInNormalPeriods one activity', () => { expect( fillInNormalPeriods(300, [{ start: 0, duration: 300, activity: 1 }]) - ).toMatchObject([ - { start: 0, duration: 60, activity: 1 }, - { start: 60, duration: 60, activity: 1 }, - { start: 120, duration: 60, activity: 1 }, - { start: 180, duration: 60, activity: 1 }, - { start: 240, duration: 60, activity: 1 }, - ]) + ).toMatchObject([{ start: 0, duration: 300, activity: 1 }]) }) test('should test fillInNormalPeriods one in the middle', () => { expect( fillInNormalPeriods(300, [{ start: 120, duration: 60, activity: 1 }]) ).toMatchObject([ - { start: 0, duration: 60, activity: 0 }, - { start: 60, duration: 60, activity: 0 }, + { start: 0, duration: 120, activity: 0 }, { start: 120, duration: 60, activity: 1 }, - { start: 180, duration: 60, activity: 0 }, - { start: 240, duration: 60, activity: 0 }, + { start: 180, duration: 120, activity: 0 }, ]) }) @@ -80,13 +68,9 @@ describe('Fitness - fillInNormalPeriods', () => { expect( fillInNormalPeriods(300, [{ start: 100, duration: 100, activity: 1 }]) ).toMatchObject([ - { start: 0, duration: 60, activity: 0 }, - { start: 60, duration: 40, activity: 0 }, - { start: 100, duration: 20, activity: 1 }, - { start: 120, duration: 60, activity: 1 }, - { start: 180, duration: 20, activity: 1 }, - { start: 200, duration: 40, activity: 0 }, - { start: 240, duration: 60, activity: 0 }, + { start: 0, duration: 100, activity: 0 }, + { start: 100, duration: 100, activity: 1 }, + { start: 200, duration: 100, activity: 0 }, ]) }) @@ -97,15 +81,11 @@ describe('Fitness - fillInNormalPeriods', () => { { start: 160, activity: -1, duration: 30 }, ]) ).toMatchObject([ - { start: 0, duration: 60, activity: 0 }, - { start: 60, duration: 10, activity: 0 }, - { start: 70, duration: 50, activity: 1 }, - { start: 120, duration: 30, activity: 1 }, + { start: 0, duration: 70, activity: 0 }, + { start: 70, duration: 80, activity: 1 }, { start: 150, duration: 10, activity: 0 }, - { start: 160, duration: 20, activity: -1 }, - { start: 180, duration: 10, activity: -1 }, - { start: 190, duration: 50, activity: 0 }, - { start: 240, duration: 60, activity: 0 }, + { start: 160, duration: 30, activity: -1 }, + { start: 190, duration: 110, activity: 0 }, ]) }) }) diff --git a/test/strategy-battery-charging-functions-mutate.test.js b/test/strategy-battery-charging-functions-mutate.test.js index 8ad4a51..c77fe68 100644 --- a/test/strategy-battery-charging-functions-mutate.test.js +++ b/test/strategy-battery-charging-functions-mutate.test.js @@ -7,7 +7,7 @@ describe('Mutation', () => { mockRandomForEach(0.4) test('should mutate', () => { - const mutate = mutationFunction(120, 1) + const mutate = mutationFunction(120, 1, 0) const p = mutate({ periods: [ @@ -19,10 +19,10 @@ describe('Mutation', () => { expect(p).toMatchObject({ periods: [ - { start: 0, activity: -1, duration: 0 }, - { start: 70, activity: 1, duration: 10 }, + { start: 0, activity: -1, duration: 4 }, + { start: 84, activity: 1, duration: 10 }, ], - excessPvEnergyUse: 1, + excessPvEnergyUse: 0, }) }) }) diff --git a/test/strategy-battery-charging-functions.test.js b/test/strategy-battery-charging-functions.test.js index 98fdad7..0cec830 100644 --- a/test/strategy-battery-charging-functions.test.js +++ b/test/strategy-battery-charging-functions.test.js @@ -83,6 +83,7 @@ describe('Calculate', () => { const averageConsumption = 1.5 // kW const averageProduction = 0 // kW const soc = 0 + const excessPvEnergyUse = 0 const config = { priceData, @@ -98,6 +99,7 @@ describe('Calculate', () => { productionForecast, consumptionForecast, soc, + excessPvEnergyUse, } const strategy = calculateBatteryChargingStrategy(config) const bestSchedule = strategy.best.schedule @@ -114,7 +116,7 @@ describe('Calculate', () => { expect(bestSchedule[2]).toMatchObject({ activity: 0, }) - expect(strategy.best.excessPvEnergyUse).toEqual(0) + expect(strategy.best.excessPvEnergyUse).toEqual(excessPvEnergyUse) console.log(`best: ${strategy.best.cost}`) console.log(`no battery: ${strategy.noBattery.cost}`)