Optimizer: consider planner precondition (late charging) (#32872)
This commit is contained in:
parent
4f169c5812
commit
0bbf4dc5c7
2 changed files with 82 additions and 2 deletions
|
|
@ -832,13 +832,15 @@ func (site *Site) loadpointRequest(lp loadpoint.API, minLen int, firstSlotDurati
|
|||
case api.ModeMinPV:
|
||||
// forced min charging
|
||||
demand = continuousDemand(lp, minLen)
|
||||
// add smartcost limit and plan goal, if configured
|
||||
// add smartcost limit, precondition and plan goal, if configured
|
||||
demand = applySmartCostLimit(lp, demand, grid, minLen)
|
||||
demand = applyPrecondition(lp, demand, minLen)
|
||||
site.applyPlanGoal(lp, &bat, minLen)
|
||||
|
||||
case api.ModePV:
|
||||
// add smartcost limit and plan goal, if configured
|
||||
// add smartcost limit, precondition and plan goal, if configured
|
||||
demand = applySmartCostLimit(lp, nil, grid, minLen)
|
||||
demand = applyPrecondition(lp, demand, minLen)
|
||||
site.applyPlanGoal(lp, &bat, minLen)
|
||||
}
|
||||
|
||||
|
|
@ -1222,6 +1224,50 @@ func applySmartCostLimit(lp loadpoint.API, demand []float32, grid api.Rates, min
|
|||
return demand
|
||||
}
|
||||
|
||||
// applyPrecondition forces max charging power during the planner's precondition window
|
||||
// ("late charging"), i.e. the last precondition duration before the plan time
|
||||
func applyPrecondition(lp loadpoint.API, demand []float32, minLen int) []float32 {
|
||||
precondition := lp.EffectivePlanStrategy().Precondition
|
||||
if precondition <= 0 {
|
||||
return demand
|
||||
}
|
||||
|
||||
ts := lp.EffectivePlanTime()
|
||||
if ts.IsZero() {
|
||||
return demand
|
||||
}
|
||||
|
||||
// TODO precise slot placement
|
||||
end := time.Until(ts)
|
||||
start := end - precondition
|
||||
if end <= 0 {
|
||||
return demand
|
||||
}
|
||||
|
||||
first := max(int(start/tariff.SlotDuration), 0)
|
||||
if first >= minLen {
|
||||
return demand
|
||||
}
|
||||
|
||||
if demand == nil {
|
||||
demand = make([]float32, minLen)
|
||||
}
|
||||
|
||||
energy := float32(lp.EffectiveMaxPower() / slotsPerHour)
|
||||
|
||||
for i := first; i < minLen; i++ {
|
||||
slotStart := time.Duration(i) * tariff.SlotDuration
|
||||
overlap := min(end, slotStart+tariff.SlotDuration) - max(start, slotStart)
|
||||
if overlap <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
demand[i] = max(demand[i], energy*float32(overlap)/float32(tariff.SlotDuration))
|
||||
}
|
||||
|
||||
return demand
|
||||
}
|
||||
|
||||
func (site *Site) applyBatteryGridChargeLimit(cMax float32, grid api.Rates, minLen int) []float32 {
|
||||
limit := site.GetBatteryGridChargeLimit()
|
||||
if limit == nil {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,40 @@ func TestLoadpointProfile(t *testing.T) {
|
|||
require.Equal(t, []float64{250, 250, 250, 250, 250, 250, 250, 50}, loadpointProfile(lp, 8))
|
||||
}
|
||||
|
||||
func TestApplyPrecondition(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
lp := loadpoint.NewMockAPI(ctrl)
|
||||
lp.EXPECT().EffectiveMaxPower().Return(8000.0).AnyTimes() // 2 kWh per slot
|
||||
|
||||
// no precondition configured
|
||||
lp.EXPECT().EffectivePlanStrategy().Return(api.PlanStrategy{}).Times(1)
|
||||
assert.Nil(t, applyPrecondition(lp, nil, 8))
|
||||
|
||||
// no plan
|
||||
lp.EXPECT().EffectivePlanStrategy().Return(api.PlanStrategy{Precondition: time.Hour}).Times(1)
|
||||
lp.EXPECT().EffectivePlanTime().Return(time.Time{}).Times(1)
|
||||
assert.Nil(t, applyPrecondition(lp, nil, 8))
|
||||
|
||||
// plan in 1h, 40min precondition: slots 1 (10min) and 2, 3 (full)
|
||||
lp.EXPECT().EffectivePlanTime().Return(time.Now().Add(time.Hour)).Times(1)
|
||||
lp.EXPECT().EffectivePlanStrategy().Return(api.PlanStrategy{Precondition: 40 * time.Minute}).Times(1)
|
||||
res := applyPrecondition(lp, nil, 8)
|
||||
require.Len(t, res, 8)
|
||||
assert.InDeltaSlice(t, []float32{0, 2000. / 1.5, 2000, 2000, 0, 0, 0, 0}, res, 1)
|
||||
|
||||
// existing demand is kept where higher
|
||||
lp.EXPECT().EffectivePlanTime().Return(time.Now().Add(time.Hour)).Times(1)
|
||||
lp.EXPECT().EffectivePlanStrategy().Return(api.PlanStrategy{Precondition: 30 * time.Minute}).Times(1)
|
||||
res = applyPrecondition(lp, []float32{3000, 3000, 3000, 3000, 0, 0, 0, 0}, 8)
|
||||
assert.InDeltaSlice(t, []float32{3000, 3000, 3000, 3000, 0, 0, 0, 0}, res, 1)
|
||||
|
||||
// plan beyond horizon
|
||||
lp.EXPECT().EffectivePlanTime().Return(time.Now().Add(24 * time.Hour)).Times(1)
|
||||
lp.EXPECT().EffectivePlanStrategy().Return(api.PlanStrategy{Precondition: time.Hour}).Times(1)
|
||||
assert.Nil(t, applyPrecondition(lp, nil, 8))
|
||||
}
|
||||
|
||||
func TestLoadpointCurrentAction(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue