Functions optimize_demand(),
add_battery_optimization() and
smart_charging() solve one independent optimization problem
per optimization window (typically one day). Depending
on the objective and the variables involved, the window problem is
solved either as a quadratic program (QP) with the OSQP backend, or as a linear or
mixed-integer linear program (LP/MILP) with the HiGHS backend. Currently, the
flextools package allows to optimize a time-series power
load considering the following objectives:
- Minimize the power exchanged with the grid (net power), with
opt_objective = "grid" - Minimize only the power that exceeds the grid connection capacity,
with
opt_objective = "capacity" - Minimize the energy cost, with
opt_objective = "cost" - Minimize a weighted combination of net power and cost, with a
numeric
opt_objectivebetween0and1
In this article, we’ll cover the optimization problem for the first two objectives, net power minimization and grid capacity minimization, for both the flexible demand (e.g. heatpumps, electric vehicles, etc.) and the battery.
Demand optimization
Net power objective
To minimize the energy exchanged with the distribution grid while maximizing the use of local generation, using the flexibility from a power demand profile, the objective function of the optimization problem has been raised in the following way:
Where:
- : Number of time intervals within the optimization window
- : Local power generation time-series vector
- : Non-flexible (static) load time-series vector
- : Flexible load time-series vector (if not optimized)
- : Optimal flexible load time-series vector (decision variable)
-
: Maximum power that the flexible load can consume
(
load_capacity) - , : Grid import and export capacity
- : Time horizon, in number of time slots
- : Ramping penalty weight
The squared term penalizes high values of net power, so the optimization always results in the most flat as possible net power profile (peak shaving). The second term penalizes slot-to-slot changes in the optimal load, so a positive results in a smoother flexible load profile.
The only decision variable is the optimal load , so this objective is solved as a QP over variables. Grid import and export are not explicit variables here: they are fully determined by the energy balance .
This optimization problem has the following constraints:
- The energy consumed by the flexible load must remain the same than the expected behavior:
- The optimal flexible load must respect both its own maximum power and the grid connection capacity. Since is the only decision variable, the grid limits
are combined with into a single box bound:
Note the lower bound: when the local generation exceeds the static load by more than the export capacity, the flexible load is forced to consume the surplus that cannot be exported.
At the same time, we can optimize the flexible energy demand with two opposite approaches:
- Postpone the consumption to later time-slots (shift the energy forward)
- Consume now (store) the energy that will be consumed later (shift the energy backward)
We consider both approaches for different objectives or applications. The energy shift is always done within a maximum time horizon () to consider realistic scenarios. For example, the time horizon for the energy demand of a water boiler could have a time horizon of 6 hours, because it wouldn’t make sense to heat the water more than 6 hours before the final consumption. Each one of these approaches brings extra constraints to the optimization problem:
If the energy can only be shifted forward:
- The cumulative sum of the optimal load must be higher than the cumulative sum of the original flexible load except the last time slots, and lower than the total cumulative sum of the original flexible load (energy can only be shifted forwards):
- The maximum values for the optimal demand will depend on the time horizon :
If the energy can only be shifted backward:
- The cumulative sum of the optimal demand must be higher than the total cumulative sum of the original demand (energy can only be shifted backwards), and lower than the cumulative sum of the original demand except the following time slots:
- The maximum values for the optimal demand will depend on the time horizon :
In both cases these horizon caps are intersected with the box bound above, so the effective upper bound on is the minimum of the horizon cap and .
Infeasible windows. When the grid capacities make a window infeasible, the grid limits are not simply dropped. Instead each capacity is raised only as far as the original, unshifted profile already requires, i.e. and . Slots that were within their capacity keep the true limit, so the optimizer can never create a violation worse than the input profile already had. With these limits the do-nothing point is feasible by construction. If the solver still fails, the original profile is returned unchanged.
Grid capacity objective
The "capacity" objective does not flatten the whole net
power profile. Instead, it minimizes the amount of flexible
energy that has to be moved in order to respect the grid
connection limits, and only that moved energy is then reshaped. This is
useful when the flexible load should be disturbed as little as possible
and the only real requirement is to stay within the grid connection
capacity.
It is solved in two stages:
Stage 1 — select the minimal slice (LP, HiGHS). With decision variables (the slice of flexible load removed from slot ) and (where that slice is placed):
subject to , the same forward/backward horizon and energy conservation constraints as above applied to the pair , and the requirement that the resulting profile respects the grid connection box:
If the optimal slice is zero (the original profile already respects the grid limits), the flexible load is returned unchanged.
Stage 2 — reshape only that slice (QP, OSQP). The untouched part is added to the static load, and the net power formulation of the previous section is applied to the slice alone, with its maximum power reduced accordingly. The final result is the sum of the fixed part and the optimized slice.
If Stage 1 is infeasible under the true capacities, the same minimal relaxation described above is applied and the plain net power formulation is used on the full flexible load.
Battery optimization
Net power objective
To minimize the energy exchanged with the distribution grid while maximizing the use of local generation, using the flexibility from a battery, the objective function of the optimization problem has been raised in the following way:
Where:
- : Number of time intervals within the optimization window
- : Local power generation time-series vector
- : Power load time-series vector
- : Net battery power exchange (decision variable, positive = charging, negative = discharging)
- : Ramping penalty weight
Additionally, this optimization problem also counts with the following parameters used in the constraints below:
- : Battery capacity
- : Maximum charging power
- : Maximum discharging power
- : Minimum state of charge of the battery (%)
- : Maximum state of charge of the battery (%)
- : State of charge at the beginning/end of the optimization window (%)
- , : Grid import and export capacity
Note that, unlike the cost objective, the net power objective uses a single net battery variable instead of separate charging and discharging variables. The problem is therefore a QP over continuous variables, with no binary variables: since charging and discharging are the two signs of one variable, they cannot happen simultaneously by construction.
Optimization constraints:
- Battery power limits, combined with the grid connection capacity. The battery power is bounded by its own charging and discharging limits and, at the same time, the net site power must respect the grid connection:
Both restrictions are imposed as a single box bound:
- State of charge limits:
- The balance of stored energy must be 0 at the end of the optimization window to keep the same initial state of charge:
Efficiencies. The charge_eff and
discharge_eff parameters of
add_battery_optimization() are not applied
by the net power and grid capacity objectives, which assume a lossless
battery
().
Round-trip losses are only embedded in the state-of-charge constraints
of the cost
objective.
Infeasible windows. As in the demand case, when the grid capacities make a window infeasible each capacity is raised only as far as the pre-battery profile already requires, i.e. and , which guarantees that is a feasible point. If OSQP does not converge, a greedy forward-pass heuristic clipped to the feasible box is used, and as a last resort the battery is disabled for that window.
Grid capacity objective
Like for the demand, the "capacity" objective for the
battery only targets the power that exceeds the grid connection
capacity, rather than flattening the whole net power profile. It reuses
exactly the net power formulation above, but first curtails the
usable battery capacity to the amount of energy that actually
has to be displaced:
The two sums are the total energy exported above the export capacity and imported above the import capacity by the original (pre-battery) profile. The 1% headroom ensures the resulting problem is strictly feasible. If the original profile causes no violation at all, and the battery is left idle for that window.
