Skip to contents

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:

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 w=0w = 0, the cost is minimized
  • If w=1w = 1, the net power is minimized
  • If 0<w<10 \lt w \lt 1, 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 (0,1)(0, 1).

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:

PI¯=1Tt=1TPIt \overline{PI} = \frac{1}{T}\sum_{t=1}^{T} PI_t

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:

minOt,It,Ett=1T[w((Ot+LStGt)PI¯)2+(1w)(ItPItEtPEt+(PTDtPTUt)Ot)]+λt=1T1(Ot+1Ot)2 \min_{O_t,\, I_t,\, E_t} \; \sum_{t=1}^{T} \Big[ w \cdot \big((O_t + LS_t - G_t) \cdot \overline{PI}\big)^2 + (1-w) \cdot \big(I_t \cdot PI_t - E_t \cdot PE_t + (PTD_t - PTU_t)\, O_t \big) \Big] + \lambda \sum_{t=1}^{T-1}\left(O_{t+1} - O_t\right)^{2}

Where:

  • TT : Number of time intervals within the optimization window
  • GtG_t : Local power generation time-series vector
  • LStLS_t : Non-flexible (static) load time-series vector
  • LFtLF_t : Flexible load time-series vector (if not optimized)
  • OtO_t : Optimal flexible load time-series vector (decision variable)
  • ItI_t : Imported power (decision variable, non-negative)
  • EtE_t : Exported power (decision variable, non-negative)
  • PItPI_t : Imported energy price
  • PEtPE_t : Exported energy price
  • PTUtPTU_t : Balancing price for turn-up power
  • PTDtPTD_t : Balancing price for turn-down power
  • ww : Weight of the net power objective over the cost objective
  • λ\lambda : Ramping penalty weight

As in the cost article, the balancing term (PTDtPTUt)Ot(PTD_t - PTU_t)\,O_t is the equivalent linear form of PTUt(OtLFt)PTDt(LFtOt)- PTU_t(O_t-LF_t) - PTD_t(LF_t-O_t), both differing only by a constant. Note also that λ\lambda penalizes slot-to-slot changes in the optimal load (ramping) and is not scaled by ww or (1w)(1-w).

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 ItEt=Ot+LStGtI_t - E_t = O_t + LS_t - G_t, the combined load and grid capacity box bound on OtO_t, the import and export capacity bounds, and the forward/backward time horizon constraints.

Solver. The decision variables are X=[Ot,It,Et]X = [O_t, I_t, E_t], i.e. 3T3T continuous variables. Because the quadratic net power term is always present for 0<w<10 \lt w \lt 1, 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 ItEtI_t - E_t 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 PEtmin(PEt,PIt)PE_t \leftarrow \min(PE_t, PI_t) 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:

minBt,It,Ett=1T[w((Lt+BtGt)PI¯)2+(1w)(ItPItEtPEt)]+λt=1T1(Bt+1Bt)2 \min_{B_t,\, I_t,\, E_t} \; \sum_{t=1}^{T} \Big[ w \cdot \big((L_t + B_t - G_t) \cdot \overline{PI}\big)^2 + (1-w) \cdot \big(I_t \cdot PI_t - E_t \cdot PE_t\big) \Big] + \lambda \sum_{t=1}^{T-1}\left(B_{t+1} - B_t\right)^{2}

Where:

  • TT : Number of time intervals within the optimization window
  • GtG_t : Local power generation time-series vector
  • LtL_t : Power load time-series vector
  • BtB_t : Net battery power exchange (decision variable, positive = charging, negative = discharging)
  • ItI_t : Imported power (decision variable, non-negative)
  • EtE_t : Exported power (decision variable, non-negative)
  • PItPI_t : Imported energy price
  • PEtPE_t : Exported energy price
  • ww : Weight of the net power objective over the cost objective
  • λ\lambda : Ramping penalty weight

Additionally, this optimization problem also counts with the following parameters used in the constraints below:

  • BcapB_{cap} : Battery capacity
  • BcB_c : Maximum charging power
  • BdB_d : Maximum discharging power
  • SOCminSOC_{min} : Minimum state of charge of the battery (%)
  • SOCmaxSOC_{max} : Maximum state of charge of the battery (%)
  • SOCiniSOC_{ini} : State of charge at the beginning/end of the optimization window (%)
  • ICtIC_t, ECtEC_t : 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 BtB_t rather than separate charging and discharging variables. The decision variables are X=[Bt,It,Et]X = [B_t, I_t, E_t], i.e. 3T3T continuous variables, solved as a QP with OSQP with no binary variables of any kind.

Optimization constraints:

  • Energy balance behind-the-meter:

ItEt=Bt+LtGttT I_t - E_t = B_t + L_t - G_t \quad t \in T

  • Battery power limits:

BdBtBctT -B_d \;\le\; B_t \;\le\; B_c \quad t \in T

  • State of charge limits:

SOCminSOCini+100Bcapk=1tBkΔtSOCmaxt=1,,T SOC_{min} \le SOC_{ini} + \frac{100}{B_{cap}} \sum_{k=1}^t B_k \, \Delta t \le SOC_{max} \quad t = 1, \dots, T

  • The balance of stored energy must be 0 at the end of the optimization window to keep the same initial state of charge:

t=1TBtΔt=0 \sum_{t=1}^T B_t \, \Delta t = 0

  • 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:

0Itmin(ICt,max(LtGt+Bc,0))tT 0 \le I_t \le \min\!\left(IC_t,\; \max\!\left(L_t - G_t + B_c,\; 0\right)\right) \quad t \in T

0Etmin(ECt,max(GtLt+Bd,0))tT 0 \le E_t \le \min\!\left(EC_t,\; \max\!\left(G_t - L_t + B_d,\; 0\right)\right) \quad t \in T

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 PEtmin(PEt,PIt)PE_t \leftarrow \min(PE_t, PI_t), with a warning emitted once when clipping occurs.

Unsupported parameters. For 0<w<10 \lt w \lt 1 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.