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)
- Minimize the energy cost
- A weighted combination of both
In this article, we’ll cover the optimization problem for the combination of both objectives, net power minimization and energy cost minimization, for both the flexible demand (e.g. heatpumps, electric vehicles, etc.) and the battery.
Below, the problem formulations are described, similarly to the
corresponding articles for net power and costs minimization, but with an
extra parameter w. The term w
is the weight for the net power minimization over the cost optimization,
so:
- If , the cost is minimized
- If , the net power is minimized
- If , both net power and cost are minimized
Note that the endpoints are aliases of the pure objectives:
opt_objective = 0 is routed directly to the cost
formulation and opt_objective = 1 to the net
power formulation. The combined formulation described in this
article is only used strictly inside
.
In both the demand and the battery case, the net power term is multiplied by the average imported energy price of the window to convert power units into cost units, so that the two terms of the objective have comparable value ranges:
Demand optimization
To minimize both the net power and the energy cost 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)
- : Imported power (decision variable, non-negative)
- : Exported power (decision variable, non-negative)
- : Imported energy price
- : Exported energy price
- : Balancing price for turn-up power
- : Balancing price for turn-down power
- : Weight of the net power objective over the cost objective
- : Ramping penalty weight
As in the cost article, the balancing term is the equivalent linear form of , both differing only by a constant. Note also that penalizes slot-to-slot changes in the optimal load (ramping) and is not scaled by or .
Moreover, this optimization problem has exactly the constraints described in the Energy cost optimization article (demand section): energy conservation of the flexible load, the behind-the-meter balance , the combined load and grid capacity box bound on , the import and export capacity bounds, and the forward/backward time horizon constraints.
Solver. The decision variables are
,
i.e.
continuous variables. Because the quadratic net power term is always
present for
,
the combined demand problem is always solved as a QP
with OSQP, for any value of lambda. This has two
consequences with respect to the pure cost objective with
lambda = 0:
- There is no binary grid mode variable, so per-slot exclusivity of import and export is not enforced as a hard constraint. Only the net flow is physically meaningful and it is always preserved, so the reported import and export profiles are collapsed to a single direction per slot afterwards.
- Export prices are clipped to and the import and export upper bounds are tightened to their physically achievable per-slot values, which keeps the QP bounded below. A warning is emitted once when clipping occurs.
The same minimal grid capacity relaxation described in the net power article applies to infeasible windows.
Battery optimization
To minimize both the net power and the energy cost 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)
- : Imported power (decision variable, non-negative)
- : Exported power (decision variable, non-negative)
- : Imported energy price
- : Exported energy price
- : Weight of the net power objective over the cost objective
- : 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
Like the net power objective and unlike the cost objective, the combined battery problem uses a single net battery variable rather than separate charging and discharging variables. The decision variables are , i.e. continuous variables, solved as a QP with OSQP with no binary variables of any kind.
Optimization constraints:
- Energy balance behind-the-meter:
- Battery power limits:
- 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:
- The imported and exported power must remain between 0 and the grid import and export capacity, tightened to the maximum physically achievable flow in each slot:
These tightened bounds are what keep the QP bounded below when import prices are negative. As on the other QP paths, export prices are also clipped to , with a warning emitted once when clipping occurs.
Unsupported parameters. For
the battery is modelled as lossless and without degradation
cost: the charge_eff, discharge_eff
and cycle_cost parameters of
add_battery_optimization() are ignored, consistently with
the net power objective. Note that this creates a discontinuity at the
endpoints, since opt_objective = 0 is routed to the cost
formulation, which does apply all three. If round-trip
efficiencies or a cycle cost are essential for your use case, use
opt_objective = "cost".
Infeasible windows. There is no fallback for this objective: if OSQP does not converge, the battery is disabled for that window (a zero profile is returned) and a warning is emitted.
