Skip to content

Commit

Permalink
fix: improved toSchedule
Browse files Browse the repository at this point in the history
  • Loading branch information
enell committed Mar 7, 2023
1 parent 9023e86 commit c9086e9
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 104 deletions.
60 changes: 35 additions & 25 deletions src/fitness.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -133,7 +130,7 @@ const calculateChargeScore = (props) => {
return [cost, charge]
}

const calculatePeriodScore = (props) => {
const calculateIntervalScore = (props) => {
switch (props.activity) {
case -1:
return calculateDischargeScore(props)
Expand All @@ -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,
Expand All @@ -164,26 +156,44 @@ 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,
consumption: consumption * duration,
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,
Expand Down
72 changes: 29 additions & 43 deletions src/strategy-battery-charging-functions.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
},
Expand Down
42 changes: 11 additions & 31 deletions test/fitness.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,49 +44,33 @@ 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 },
])
})

test('should test fillInNormalPeriods one long activity', () => {
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 },
])
})

Expand All @@ -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 },
])
})
})
Expand Down
8 changes: 4 additions & 4 deletions test/strategy-battery-charging-functions-mutate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -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,
})
})
})
4 changes: 3 additions & 1 deletion test/strategy-battery-charging-functions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe('Calculate', () => {
const averageConsumption = 1.5 // kW
const averageProduction = 0 // kW
const soc = 0
const excessPvEnergyUse = 0

const config = {
priceData,
Expand All @@ -98,6 +99,7 @@ describe('Calculate', () => {
productionForecast,
consumptionForecast,
soc,
excessPvEnergyUse,
}
const strategy = calculateBatteryChargingStrategy(config)
const bestSchedule = strategy.best.schedule
Expand All @@ -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}`)
Expand Down

0 comments on commit c9086e9

Please sign in to comment.