
Battery optimal charging/discharging profile
Source:R/battery_optimization.R
add_battery_optimization.RdSee the formulation of the optimization problems in the documentation website.
Usage
add_battery_optimization(
opt_data,
opt_objective = "grid",
Bcap,
Bc,
Bd,
SOCmin = 0,
SOCmax = 100,
SOCini = NULL,
window_days = 1,
window_start_hour = 0,
flex_window_hours = 24,
lambda = 0,
charge_eff = 1,
discharge_eff = 1,
cycle_cost = 0
)Arguments
- opt_data
tibble, optimization contextual data. The first column must be named
datetime(mandatory). Optional columns:static: static power demand (kW)production: local generation (kW)import_capacity: max grid import (kW)export_capacity: max grid export (kW)price_imported: energy import price (required for cost/combined)price_exported: energy export price (required for cost/combined)
import_capacityandexport_capacityconstrain the net grid flow. A negative value is therefore an obligation to flow the other way:import_capacity = -100requires at least 100 kW of export in that slot, as a congestion contract might. Such an obligation outranksopt_objective— the battery meets the capacity first and optimises second.A capacity the battery cannot physically reach is approached, not dropped: the battery operates at its limit and a warning is emitted once. The result is still guaranteed never to be worse than the profile without a battery, and slots that can meet their capacity remain strictly capped. The unavoidable miss is concentrated: the capacity is met exactly for as many slots as the battery's energy covers, rather than spread thinly so that every slot ends marginally over. Both leave the same volume unserved, but only the former reduces the number of slots in violation (see
get_energy_kpis()'scongestion_time).- opt_objective
character or numeric.
"grid"(default),"capacity","cost", or a numeric weightwwherew=1is pure grid andw=0is pure cost."grid"minimises the net grid flow, so it flattens the profile with whatever battery it is given."capacity"instead minimises the battery usage that keeps the net flow insideimport_capacityandexport_capacity: windows within both capacities are left untouched, and in the windows that are not, the battery discharges onto the capacity line rather than below it and recharges at the lowest power the window allows. Use it to size the minimum battery a congestion limit needs; use"grid"to minimise the peak itself.- Bcap
numeric, battery capacity (kWh)
- Bc
numeric, maximum charging power (kW)
- Bd
numeric, maximum discharging power (kW)
- SOCmin
numeric, minimum State-of-Charge (%)
- SOCmax
numeric, maximum State-of-Charge (%)
- SOCini
numeric, initial State-of-Charge (%). Defaults to
SOCmin.- window_days
integer, optimization window length in days.
- window_start_hour
integer, start hour of each optimization window.
- flex_window_hours
numeric, flexibility window length (hours).
- lambda
numeric, ramping penalty weight. Penalises rapid changes in battery power between consecutive time slots.
- charge_eff
numeric, charging efficiency in (0, 1]. Default 1 (lossless). Embeds round-trip losses in the SOC constraints for accurate energy accounting.
- discharge_eff
numeric, discharging efficiency in (0, 1]. Default 1 (lossless). See
charge_eff.- cycle_cost
numeric, degradation cost of one full cycle, in Euro per
BcapkWh discharged (NOT Euro/kWh). Default 0. Divided byBcapinternally to obtain the per-kWh penalty, so a battery costingpEuro/kWh and rated forncycles hascycle_cost = p * Bcap / n. Adds a linear penalty on battery discharge so the optimizer trades off energy cost savings against battery wear. When positive, the problem is solved as a pure LP (no binary variables) which is substantially faster than the default MILP.
Examples
library(dplyr)
#>
#> Attaching package: ‘dplyr’
#> The following objects are masked from ‘package:stats’:
#>
#> filter, lag
#> The following objects are masked from ‘package:base’:
#>
#> intersect, setdiff, setequal, union
opt_data <- flextools::energy_profiles %>%
filter(lubridate::isoweek(datetime) == 18) %>%
rename(production = "solar", static = "building") %>%
select(any_of(c(
"datetime", "production", "static", "price_imported", "price_exported"
)))
opt_battery <- opt_data %>%
add_battery_optimization(
opt_objective = "grid",
Bcap = 50, Bc = 4, Bd = 4,
window_start_hour = 5
)