# Julia OPF API (`eDisGo_OPF`) ```{note} This page is generated automatically from the docstrings in the `eDisGo_OPF` Julia source (`edisgo/opf/eDisGo_OPF.jl/src/`). Only documented symbols are listed, so the reference fills in on its own as docstrings are added at the source. ``` The optimal power flow (OPF) is implemented in Julia, in the `eDisGo_OPF` package, which extends [PowerModels.jl](https://github.com/lanl-ansi/PowerModels.jl) with a branch-flow formulation for distribution grids and eDisGo's flexibilities (storage, electromobility, heat pumps, demand-side management, curtailment). From Python the OPF is run via {meth}`~edisgo.edisgo.EDisGo.pm_optimize`, which serialises the grid and flexibility data to JSON, hands it to `eDisGo_OPF` and reads the results back. For the modelling background — branch-flow physics, the SOC vs. non-convex relaxations and the `opf_version` variants — see {ref}`flexibility-opf`. The three exported problem formulations are `BFPowerModelEdisgo` (base branch-flow), `SOCBFPowerModelEdisgo` (second-order-cone relaxation, convex) and `NCBFPowerModelEdisgo` (non-convex, exact). ## Problem formulations *Source: `edisgo/opf/eDisGo_OPF.jl/src/core/types.jl`* ### `AbstractBFModelEdisgo` ```julia abstract type AbstractBFModelEdisgo <: AbstractBFQPModel ``` Abstract supertype for all eDisGo branch-flow OPF models. Concrete subtypes (`BFPowerModelEdisgo`, `SOCBFPowerModelEdisgo`, `NCBFPowerModelEdisgo`) select how the branch-flow equations are treated (base, second-order-cone relaxation, or non-convex/exact). ### `BFPowerModelEdisgo` ```julia mutable struct BFPowerModelEdisgo <: AbstractBFModelEdisgo ``` Base radial branch-flow model for the eDisGo OPF — the eDisGo extension of PowerModels' branch-flow (`DistFlow`) formulation. Applicable to problem formulations whose name ends in `_bf` (e.g. `build_mn_opf_bf_flex`). The branch-flow model is the natural formulation for the tree-shaped (radial) distribution grids eDisGo works with. For every branch `i → j` it couples the sending-end active and reactive power `(P, Q)`, the squared branch current `ccm` (`= I²`) and the squared bus voltages `w` (`= V²`), together with a nodal power balance at each bus, thermal/current limits, voltage limits and the flexibility power/energy bands. The defining coupling between power, current and voltage is the quadratic relation `P² + Q² = V²·I²`, which is what makes an exact AC OPF non-convex. This type carries the shared model structure; *how* that quadratic coupling is enforced is fixed by the concrete subtypes `SOCBFPowerModelEdisgo` (convex relaxation) and `NCBFPowerModelEdisgo` (exact, non-convex) — one of those is what you actually solve. All quantities are in per-unit on the common base power `s_base`. ### `SOCBFPowerModelEdisgo` ```julia mutable struct SOCBFPowerModelEdisgo <: AbstractSOCBFModelEdisgo ``` Second-order-cone (SOC) relaxation of the radial branch-flow model. Applicable to problem formulations whose name ends in `_bf`. The non-convex coupling `P² + Q² = V²·I²` is relaxed to the convex inequality `P² + Q² ≤ V²·I²` — a second-order cone (see `constraint_model_current` for `AbstractSOCBFModelEdisgo`). This turns the OPF into a convex program, solved with **Gurobi**: fast, reliable and globally optimal. For radial grids the relaxation is usually *exact* — the inequality is tight at the optimum, so the solution is also feasible for the original AC problem. `check_SOC_equality` flags any branches and time steps where it is not tight; there, running with `warm_start=true` recovers a feasible AC solution by polishing with the non-convex model (`NCBFPowerModelEdisgo`). This is the model behind the default `method="soc"`. ### `NCBFPowerModelEdisgo` ```julia mutable struct NCBFPowerModelEdisgo <: AbstractNCBFModelEdisgo ``` Non-convex (NC), exact radial branch-flow model. Applicable to problem formulations whose name ends in `_bf`. Keeps the exact quadratic equality `P² + Q² = V²·I²` (added as a nonlinear `@NLconstraint`; see `constraint_model_current` for `AbstractNCBFModelEdisgo`), so no relaxation is involved. The resulting non-convex program is solved with the **Ipopt** interior-point solver: more accurate in principle, but slower and only guaranteed to find a *local* optimum. Selected via `method="nc"` (cold start), or used automatically as the warm-started polishing step when `method="soc"` is run with `warm_start=true`, starting from the exact Gurobi SOC solution. ## Problem definition & build *Source: `edisgo/opf/eDisGo_OPF.jl/src/prob/opf_bf.jl`* ### `solve_mn_opf_bf_flex` ```julia function solve_mn_opf_bf_flex(file, model_type::Type{T}, optimizer; kwargs...) where T <: AbstractBFModel ``` Solve multinetwork branch flow OPF with multiple flexibilities ### `build_mn_opf_bf_flex` ```julia function build_mn_opf_bf_flex(pm::AbstractBFModelEdisgo) ``` Build multinetwork branch flow OPF with multiple flexibilities ## Model setup *Source: `edisgo/opf/eDisGo_OPF.jl/src/core/base.jl`* ### `solve_model` ```julia function solve_model(data::Dict{String,<:Any}, model_type::Type, optimizer, build_method; ref_extensions=[], solution_processors=[], relax_integrality=false, multinetwork=false, multiconductor=false, kwargs...) ``` Instantiate and solve an eDisGo OPF model. Builds a model of type `model_type` from the network `data` using `build_method` (e.g. `build_mn_opf_bf_flex`), optimises it with the given JuMP `optimizer` (Gurobi or Ipopt), and returns the tuple `(result, pm)`, where `result` is the PowerModels solution dict and `pm` is the constructed model object. The keyword arguments mirror PowerModels' `solve_model`/`instantiate_model` (`ref_extensions`, `solution_processors`, `relax_integrality`, `multinetwork`, `multiconductor`). ### `instantiate_model` ```julia function instantiate_model(data::Dict{String,<:Any}, model_type::Type, build_method; kwargs...) ``` Instantiate an eDisGo OPF model without solving it — a thin wrapper around `InfrastructureModels.instantiate_model` with eDisGo's `ref_add_core!` reference extension and the global keys. Used internally by `solve_model`. ### `ref_add_core!` ```julia function ref_add_core!(ref::Dict{Symbol,Any}) ``` Returns a dict that stores commonly used pre-computed data from of the data dictionary, primarily for converting data-types, filtering out deactivated components, and storing system-wide values that need to be computed globally. Some of the common keys include: * `:off_angmin` and `:off_angmax` (see `calc_theta_delta_bounds(data)`), * `:bus` -- the set `{(i, bus) in ref[:bus] : bus["bus_type"] != 4}`, * `:gen` -- the set `{(i, gen) in ref[:gen] : gen["gen_status"] == 1 && gen["gen_bus"] in keys(ref[:bus])}`, * `:branch` -- the set of branches that are active in the network (based on the component status values), * `:arcs_from` -- the set `[(i,b["f_bus"],b["t_bus"]) for (i,b) in ref[:branch]]`, * `:arcs_to` -- the set `[(i,b["t_bus"],b["f_bus"]) for (i,b) in ref[:branch]]`, * `:arcs` -- the set of arcs from both `arcs_from` and `arcs_to`, * `:bus_arcs` -- the mapping `Dict(i => [(l,i,j) for (l,i,j) in ref[:arcs]])`, * `:bus_arcs_to` -- the mapping `Dict(j => [(l,i,j) for (l,i,j) in ref[:arcs_from]])`, * `:bus_arcs_from` -- the mapping `Dict(i => [(l,i,j) for (l,i,j) in ref[:arcs_from]])`, * `:bus_lines_to` -- the mapping `Dict(j => [l for (l,i,j) in ref[:arcs_from]])`, * `:buspairs` -- (see `buspair_parameters(ref[:arcs_from], ref[:branch], ref[:bus])`), * `:bus_gens` -- the mapping `Dict(i => [gen["gen_bus"] for (i,gen) in ref[:gen]])`. * `:bus_gens_nd` -- the mapping `Dict(i => [gen_nd["gen_bus"] for (i,gen) in ref[:gen_nd]])`. * `:bus_gens_slack` -- the mapping `Dict(i => [gen_slack["gen_bus"] for (i,gen) in ref[:gen_slack]])`. * `:bus_loads` -- the mapping `Dict(i => [load["load_bus"] for (i,load) in ref[:load]])`. * `:bus_shunts` -- the mapping `Dict(i => [shunt["shunt_bus"] for (i,shunt) in ref[:shunt]])`. * `:bus_dsm` -- the mapping `Dict(i => [dsm["dsm_bus"] for (i,dsm) in ref[:dsm]])`. * `:bus_cps` -- the mapping `Dict(i => [cp["cp_bus"] for (i,cp) in ref[:electromobility]])`. * `:bus_hps` -- the mapping `Dict(i => [hp["hp_bus"] for (i,hp) in ref[:heatpumps]])`. * `:arcs_from_dc` -- the set `[(i,b["f_bus"],b["t_bus"]) for (i,b) in ref[:dcline]]`, * `:arcs_to_dc` -- the set `[(i,b["t_bus"],b["f_bus"]) for (i,b) in ref[:dcline]]`, * `:arcs_dc` -- the set of arcs from both `arcs_from_dc` and `arcs_to_dc`, * `:bus_arcs_dc` -- the mapping `Dict(i => [(l,i,j) for (l,i,j) in ref[:arcs_dc]])`, and * `:buspairs_dc` -- (see `buspair_parameters(ref[:arcs_from_dc], ref[:dcline], ref[:bus])`), If `:ne_branch` exists, then the following keys are also available with similar semantics: * `:ne_branch`, `:ne_arcs_from`, `:ne_arcs_to`, `:ne_arcs`, `:ne_bus_arcs`, `:ne_buspairs`. Added keys for eDisGo OPF include: * `:bus_arcs_to` -- the mapping `Dict(j => [(l,i,j) for (l,i,j) in ref[:arcs_from]])`, * `:bus_arcs_from` -- the mapping `Dict(i => [(l,i,j) for (l,i,j) in ref[:arcs_from]])`, * `:bus_lines_to` -- the mapping `Dict(j => [l for (l,i,j) in ref[:arcs_from]])`, * `:bus_gens_nd` -- the mapping `Dict(i => [gen_nd["gen_bus"] for (i,gen) in ref[:gen_nd]])`. * `:bus_gens_slack` -- the mapping `Dict(i => [gen_slack["gen_bus"] for (i,gen) in ref[:gen_slack]])`. * `:bus_dsm` -- the mapping `Dict(i => [dsm["dsm_bus"] for (i,dsm) in ref[:dsm]])`. * `:bus_cps` -- the mapping `Dict(i => [cp["cp_bus"] for (i,cp) in ref[:electromobility]])`. * `:bus_hps` -- the mapping `Dict(i => [hp["hp_bus"] for (i,hp) in ref[:heatpumps]])`. ## Variables *Source: `edisgo/opf/eDisGo_OPF.jl/src/core/variables.jl`* ### `variable_branch_power_radial` ```julia function variable_branch_power_radial(pm::AbstractPowerModel; kwargs...) ``` Create the branch power-flow variables for the radial branch-flow model: both the active (`p`) and reactive (`q`) flow on every branch. Wrapper around `variable_branch_power_real_radial` and `variable_branch_power_imaginary_radial`. ### `variable_branch_power_real_radial` ```julia function variable_branch_power_real_radial(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` variable: `p[l,i,j]` for `(l,i,j)` in `arcs_from` ### `variable_branch_power_imaginary_radial` ```julia function variable_branch_power_imaginary_radial(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` variable: `q[l,i,j]` for `(l,i,j)` in `arcs` ### `variable_bus_voltage_magnitude_sqr` ```julia function variable_bus_voltage_magnitude_sqr(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `w[i]` for each non-storage bus `i`: the squared voltage magnitude (`= V²`), bounded by `vmin²`/`vmax²`. ### `variable_max_line_loading` ```julia function variable_max_line_loading(pm::AbstractPowerModel; kwargs...) ``` Create the maximum-line-loading variable `ll` (wrapper around `variable_line_loading_max`), used by the line-loading-minimising objectives (`opf_version` 1 and 3). ### `variable_line_loading_max` ```julia function variable_line_loading_max(pm::AbstractPowerModel; nw::Int=nw_id_default, report::Bool=true) ``` variable: `ll[l,i,j]` for `(l,i,j)` in `arcs_from` ### `variable_gen_power_curt` ```julia function variable_gen_power_curt(pm::AbstractPowerModel; kwargs...) ``` generates variables for both `active` and `reactive` non-dispatchable power generation curtailment ### `variable_gen_power_curt_real` ```julia function variable_gen_power_curt_real(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` variable: `pgc[j]` for `j` in `gen_nd` ### `variable_gen_power_curt_imaginary` ```julia function variable_gen_power_curt_imaginary(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` variable: `qgc[j]` for `j` in `gen_nd` ### `variable_battery_storage` ```julia function variable_battery_storage(pm::AbstractPowerModel; kwargs...) ``` variables for modeling storage units, includes grid injection and internal variables ### `variable_battery_storage_power_real` ```julia function variable_battery_storage_power_real(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `ps[i]` for `i` in `storage`: the active power of each battery storage unit (charging/discharging), bounded by the unit's `pmin` and `pmax`. ### `variable_battery_storage_power_imaginary` ```julia function variable_battery_storage_power_imaginary(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `qs[i]` for `i` in `storage`: the reactive power of each battery storage unit, bounded by the unit's `qmin` and `qmax`. (Optional; by default reactive storage power is derived from the power factor in the power-balance constraint.) ### `variable_dsm_storage_power` ```julia function variable_dsm_storage_power(pm::AbstractPowerModel; kwargs...) ``` variables for modeling dsm storage units, includes grid injection and internal variables ### `variable_dsm_storage_power_real` ```julia function variable_dsm_storage_power_real(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `pdsm[i]` for `i` in `dsm`: the demand-side-management load-shift power of each DSM unit, bounded by `p_min`/`p_max`. ### `variable_dsm_storage_power_imaginary` ```julia function variable_dsm_storage_power_imaginary(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `qdsm[i]` for `i` in `dsm`: the reactive power of each DSM unit, bounded by `q_min`/`q_max`. (Optional; by default derived from the power factor.) ### `variable_dsm_storage_energy` ```julia function variable_dsm_storage_energy(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `dsme[i]` for `i` in `dsm`: the accumulated shifted energy of each DSM unit, bounded by `e_min`/`e_max`. Coupled to `pdsm` over time by the DSM storage constraints. ### `variable_heat_storage` ```julia function variable_heat_storage(pm::AbstractPowerModel; kwargs...) ``` variables for modeling heat storage units, includes grid injection and internal variables ### `variable_heat_storage_power` ```julia function variable_heat_storage_power(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `phs[i]` for `i` in `heat_storage`: the (dis)charging thermal power of each heat storage unit, bounded by ±`capacity`. ### `variable_heat_storage_energy` ```julia function variable_heat_storage_energy(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `hse[i]` for `i` in `heat_storage`: the stored thermal energy (state of charge) of each heat storage unit, between 0 and its `capacity`. ### `variable_heat_pump_power` ```julia function variable_heat_pump_power(pm::AbstractPowerModel; kwargs...) ``` variables for modeling heat pumps, includes grid injection and internal variables ### `variable_heat_pump_power_real` ```julia function variable_heat_pump_power_real(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `php[i]` for `i` in `heatpumps`: the electrical power drawn by each heat pump, bounded by `p_min`/`p_max`. ### `variable_heat_pump_power_imaginary` ```julia function variable_heat_pump_power_imaginary(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `qhp[i]` for `i` in `heatpumps`: the reactive power of each heat pump, bounded by `q_min`/`q_max`. (Optional; by default derived from the power factor.) ### `variable_cp_power` ```julia function variable_cp_power(pm::AbstractPowerModel; kwargs...) ``` variables for modeling charging points, includes grid injection and internal variables ### `variable_cp_power_real` ```julia function variable_cp_power_real(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `pcp[i]` for `i` in `electromobility`: the charging power of each charging park, bounded by `p_min`/`p_max`. ### `variable_cp_power_imaginary` ```julia function variable_cp_power_imaginary(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `qcp[i]` for `i` in `electromobility`: the reactive power of each charging park, bounded by `q_min`/`q_max`. (Optional; by default derived from the power factor.) ### `variable_cp_energy` ```julia function variable_cp_energy(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `cpe[i]` for `i` in `electromobility`: the accumulated charged energy of each charging park, bounded by `e_min`/`e_max` (the flexibility band). Coupled to `pcp` over time by the charging-point constraints. ### `variable_slack_grid_restrictions` ```julia function variable_slack_grid_restrictions(pm::AbstractBFModelEdisgo; kwargs...) ``` slack variables for grid restrictions ### `variable_slack_heat_pump_storage` ```julia function variable_slack_heat_pump_storage(pm::AbstractBFModelEdisgo; kwargs...) ``` Create the heat-pump / heat-storage operation slack variables (wrapper around `variable_hs_slack` and `variable_hp2_slack`), which keep the heat-pump energy balance feasible at a penalty. ### `variable_hs_slack` ```julia function variable_hs_slack(pm::AbstractBFModelEdisgo; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` heat storage slack variable ### `variable_hp2_slack` ```julia function variable_hp2_slack(pm::AbstractBFModelEdisgo; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` heat pump operation slack variable ### `variable_hp_slack` ```julia function variable_hp_slack(pm::AbstractBFModelEdisgo; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` heat pump slack variable ### `variable_load_slack` ```julia function variable_load_slack(pm::AbstractBFModelEdisgo; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` load slack variable ### `variable_gen_slack` ```julia function variable_gen_slack(pm::AbstractBFModelEdisgo; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` gen slack variable ### `variable_ev_slack` ```julia function variable_ev_slack(pm::AbstractBFModelEdisgo; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` EV slack variable ### `variable_slack_gen` ```julia function variable_slack_gen(pm::AbstractBFModelEdisgo; kwargs...) ``` slack generator variables ### `variable_slack_gen_real` ```julia function variable_slack_gen_real(pm::AbstractBFModelEdisgo; nw::Int=nw_id_default, report::Bool=true) ``` Variable `pgs[i]` for `i` in `gen_slack`: the active power of each slack generator (the substation / feed-in node), left unbounded. ### `variable_slack_gen_imaginary` ```julia function variable_slack_gen_imaginary(pm::AbstractBFModelEdisgo; nw::Int=nw_id_default, report::Bool=true) ``` Variable `qgs[i]` for `i` in `gen_slack`: the reactive power of each slack generator (the substation / feed-in node), left unbounded. ### `variable_slack_HV_requirements` ```julia function variable_slack_HV_requirements(pm::AbstractPowerModel; kwargs...) ``` slack variables for HV requirement constraints ### `variable_slack_HV_requirements_real` ```julia function variable_slack_HV_requirements_real(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `phvs[i]` for `i` in `HV_requirements`: the active-power slack on each overlying-grid (HV) flexibility requirement, allowing the dispatch requested by the higher voltage level to be missed at a penalty (`opf_version` 3 and 4). ### `variable_slack_HV_requirements_imaginary` ```julia function variable_slack_HV_requirements_imaginary(pm::AbstractPowerModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `qhvs[i]` for `i` in `HV_requirements`: the reactive-power counterpart of `phvs`. (Optional; not used by default.) ## Constraints — flexibilities & HV requirements *Source: `edisgo/opf/eDisGo_OPF.jl/src/core/constraint.jl`* ### `constraint_store_state_initial` ```julia function constraint_store_state_initial(pm::AbstractBFModelEdisgo, n::Int, i::Int, energy, charge_eff, discharge_eff, time_elapsed, kind, p_loss) ``` Initial-time-step state constraint for a storage `kind` (`"storage"` battery, `"heat_storage"`, or `"dsm"`). Sets the stored energy after the first time step from the unit's initial state of charge and its (dis)charging power, and pins the final state of charge to the required end value — Eq. (3.9)/(3.10) for batteries, (3.22)/(3.23) for heat storage and (3.32)/(3.33) for DSM in the eDisGo OPF formulation. ### `constraint_store_state` ```julia function constraint_store_state(pm::AbstractBFModelEdisgo, n_1::Int, n_2::Int, i::Int, charge_eff, discharge_eff, time_elapsed, kind, p_loss) ``` Inter-time-step state-of-charge coupling for a storage `kind` between networks `n_1` and `n_2`: the stored energy in `n_2` equals that in `n_1` plus the energy (dis)charged over the elapsed time — Eq. (3.10), (3.23), (3.33) of the eDisGo OPF formulation. For heat storage the previous energy is reduced by the standing-loss factor. ### `constraint_cp_state_initial` ```julia function constraint_cp_state_initial(pm::AbstractBFModelEdisgo, n::Int, i::Int, eta) ``` Initial-time-step state constraint for charging park `i`: sets the charged energy after the first time step from the midpoint of the flexibility band `(e_min+e_max)/2` and the charging power, scaled by the charging efficiency `eta` — Eq. (3.25) of the eDisGo OPF formulation. ### `constraint_cp_state` ```julia function constraint_cp_state(pm::AbstractBFModelEdisgo, n_1::Int, n_2::Int, i::Int, eta) ``` Inter-time-step energy coupling for charging park `i` between networks `n_1` and `n_2`: the charged energy in `n_2` equals that in `n_1` plus the energy charged over the elapsed time (scaled by the efficiency `eta`) — Eq. (3.26). In the last time step the energy is pinned back to the midpoint of the flexibility band (Eq. (3.25)). ### `constraint_hp_operation` ```julia function constraint_hp_operation(pm::AbstractBFModelEdisgo, i::Int, nw::Int=nw_id_default) ``` Heat-pump energy balance for heat pump `i` in network `nw`: the electrical power drawn (`php`, plus the operation slack `phps2`) times the coefficient of performance `cop` must cover the heat demand `pd` plus the net heat-storage (dis)charge (`phss - phs`) — Eq. (3.19) of the eDisGo OPF formulation. ### `constraint_HV_requirements` ```julia function constraint_HV_requirements(pm::AbstractBFModelEdisgo, i::Int, nw::Int=nw_id_default) ``` Overlying-grid requirement constraint `i` in network `nw` (`opf_version` 3 and 4): forces the total dispatch of the addressed flexibility (DSM, curtailment, storage, heat pumps or charging points) to meet the active power `P` requested by the higher voltage level, up to the slack `phvs`. ## Constraint templates *Source: `edisgo/opf/eDisGo_OPF.jl/src/core/constraint_template.jl`* ### `constraint_power_balance_bf` ```julia function constraint_power_balance_bf(pm::AbstractBFModelEdisgo, i::Int; nw::Int=nw_id_default) ``` power balance for radial branch flow model ### `constraint_voltage_magnitude_difference_radial` ```julia function constraint_voltage_magnitude_difference_radial(pm::AbstractBFModelEdisgo, i::Int; nw::Int=nw_id_default) ``` Apply the branch-flow voltage-magnitude-difference equation for branch `i` in network `nw`. Relates the squared voltage magnitudes at the two ends of the branch to the active/reactive power flows and the squared current magnitude (Eq. (3.5) of the eDisGo OPF formulation). Virtual storage branches are skipped, as they do not represent a physical line. ### `constraint_store_state` ```julia function constraint_store_state(pm::AbstractBFModelEdisgo, i::Int; nw::Int=nw_id_default, kind::String) ``` First-time-step storage state constraint for unit `i` of type `kind` (`"storage"`, `"heat_storage"` or `"dsm"`) in network `nw`. Looks up the unit's parameters (initial energy, charge/discharge efficiency, standing loss, elapsed time) and forwards to `constraint_store_state_initial` — Eq. (3.9)/(3.22)/(3.32). ### `constraint_store_state` ```julia function constraint_store_state(pm::AbstractBFModelEdisgo, i::Int, nw_1::Int, nw_2::Int, kind::String) ``` Couple the state of charge of storage unit `i` of type `kind` between two consecutive time steps (networks `nw_1` and `nw_2`). `kind` is one of `"storage"` (battery), `"heat_storage"` or `"dsm"`. Links the stored energy in `nw_2` to that in `nw_1` via the (dis)charging power and the elapsed time (Eq. (3.10), (3.23), (3.33) of the eDisGo OPF formulation; heat storage additionally accounts for standing losses). If the unit is inactive in `nw_1`, the initial-state constraint is applied from `nw_2`'s data instead. ### `constraint_model_current` ```julia function constraint_model_current(pm::AbstractPowerModel; nw::Int=nw_id_default) ``` Template for the current/power/voltage coupling constraint: forwards to the formulation-specific method for network `nw` — the convex second-order-cone inequality (`AbstractSOCBFModelEdisgo`) or the exact nonlinear equality (`AbstractNCBFModelEdisgo`). ## Branch-flow formulation *Source: `edisgo/opf/eDisGo_OPF.jl/src/form/bf.jl`* ### `variable_branch_current` ```julia function variable_branch_current(pm::AbstractBFModel; kwargs...) ``` Create the squared branch-current-magnitude variables for the branch-flow model (wrapper around `variable_buspair_current_magnitude_sqr`, which defines `ccm[i]`). ### `variable_bus_voltage` ```julia function variable_bus_voltage(pm::AbstractBFModel; kwargs...) ``` Create the bus-voltage variable for the branch-flow model (wrapper around `variable_bus_voltage_magnitude_sqr`, which defines `w[i] = V²`). ### `variable_buspair_current_magnitude_sqr` ```julia function variable_buspair_current_magnitude_sqr(pm::AbstractBFModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true) ``` Variable `ccm[i]` for each branch `i`: the squared branch current magnitude (`= I²`). Lower-bounded by 0 and upper-bounded from the branch rating and the sending-bus voltage (`(rate_a·tap / vmin)²`); in the unbounded model only storage (virtual) branches receive the upper bound. ### `constraint_voltage_magnitude_difference` ```julia function constraint_voltage_magnitude_difference(pm::AbstractBFModelEdisgo, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, tm) ``` Low-level branch-flow voltage-drop equation for branch `i` (network `n`): relates the squared voltages at the two ends to the active/reactive flows, the squared current `ccm` and the branch impedance `r, x` (with tap `tm`) — Eq. (3.5) of the eDisGo OPF formulation. At the slack bus the squared voltage is fixed to 1 p.u. Called by `constraint_voltage_magnitude_difference_radial`. ### `constraint_model_current` ```julia function constraint_model_current(pm::AbstractSOCBFModelEdisgo, n::Int) # Eq. (3.9) ``` Current/power/voltage coupling for the **SOC** (convex) branch-flow model in network `n`: the second-order-cone inequality `p² + q² ≤ (w/tm²)·ccm` on every branch — the convex relaxation (Eq. (3.6i) of the eDisGo OPF formulation) of the exact equality. ### `constraint_model_current` ```julia function constraint_model_current(pm::AbstractNCBFModelEdisgo, n::Int) # Eq. (3.5) ``` Current/power/voltage coupling for the **non-convex** branch-flow model in network `n`: the exact nonlinear equality `p² + q² = (w/tm²)·ccm` (a JuMP `@NLconstraint`) on every non-storage branch — Eq. (3.6) of the eDisGo OPF formulation. ### `constraint_max_line_loading` ```julia function constraint_max_line_loading(pm::AbstractSOCBFModelEdisgo, n::Int) ``` Maximum-line-loading constraint for network `n`: bounds the squared apparent power on every non-storage branch by the loading variable `ll`, `(p² + q²)/s_nom² ≤ ll` — Eq. (3.40) of the eDisGo OPF formulation. Together with the line-loading objective this minimises the worst-case loading (`opf_version` 1 and 3). ### `constraint_max_line_loading` ```julia function constraint_max_line_loading(pm::AbstractNCBFModelEdisgo, n::Int) ``` Non-convex-model version of the maximum-line-loading constraint `(p² + q²)/s_nom² ≤ ll` (identical formulation to the SOC method) — Eq. (3.40). ### `constraint_power_balance` ```julia function constraint_power_balance(pm::AbstractBFModelEdisgo, n::Int, i, bus_gens, bus_gens_nd, bus_gens_slack, bus_loads, bus_arcs_to, bus_arcs_from, bus_lines_to, bus_storage, bus_pg, bus_qg, bus_pg_nd, bus_qg_nd, bus_pd, bus_qd, branch_r, branch_x, bus_dsm, bus_hps, bus_cps, bus_storage_pf, bus_dsm_pf, bus_hps_pf, bus_cps_pf, bus_gen_nd_pf, bus_gen_d_pf, bus_loads_pf, branch_strg_pf) ``` Nodal active- and reactive-power balance (Kirchhoff's current law) at bus `i` in network `n` for the radial branch-flow model — Eq. (3.3)/(3.4) of the eDisGo OPF formulation. Equates the power flowing into the bus on incoming branches to the power leaving on outgoing branches plus the net injection of every component connected to the bus: conventional and non-dispatchable generators, slack generator, loads, battery storage, DSM, heat pumps and charging points, plus the ohmic branch losses (`ccm·r` for active, `ccm·x` for reactive power). For `opf_version` 2 and 4 the balance additionally includes the load-shedding / curtailment slack variables (generation curtailment `pgc`, dispatchable-generation slack `pgens`, load slack `pds`, charging-point slack `pcps`, heat-pump slack `phps`). Reactive injections of the flexibilities are derived from their active power via the power factor (`tan(acos(pf))`). This low-level method receives the pre-collected per-bus component maps from `constraint_power_balance_bf`. ## Objective functions *Source: `edisgo/opf/eDisGo_OPF.jl/src/core/objective.jl`* ### `objective_min_losses` ```julia function objective_min_losses(pm::AbstractBFModelEdisgo) ``` Objective: minimise total ohmic line losses, summed over all time steps, as `Σ ccm[b]·r[b]` over the non-storage branches. (Defined as an alternative objective; the multi-network build uses `objective_min_line_loading_max` for `opf_version` 1.) ### `objective_min_losses_slacks` ```julia function objective_min_losses_slacks(pm::AbstractBFModelEdisgo) ``` Objective for `opf_version` 2 — Eq. (3.2 iii) of the eDisGo OPF formulation: minimise a weighted sum of line losses and the grid-restriction slacks. Combines ohmic losses (`Σ ccm·r`) with penalties on non-dispatchable curtailment (`pgc`), dispatchable-generation curtailment (`pgens`), load shedding (`pds`), charging-point shedding (`pcps`) and heat-pump shedding (`phps`), weighted by `factor_slacks`, plus a large penalty on the heat-storage / heat-pump operation slacks (`phss`, `phps2`). ### `objective_min_line_loading_max` ```julia function objective_min_line_loading_max(pm::AbstractBFModelEdisgo) ``` Objective for `opf_version` 1 — Eq. (3.2 ii) of the eDisGo OPF formulation: minimise a weighted sum of line losses (`Σ ccm·r`) and the maximum line loading `ll` (weighted by `factor_ll`, scaled by branch cost × length) over the non-storage branches. ### `objective_min_losses_slacks_OG` ```julia function objective_min_losses_slacks_OG(pm::AbstractBFModelEdisgo) ``` Objective for `opf_version` 4 (with overlying grid): like `objective_min_losses_slacks`, but additionally penalises the overlying-grid (HV) requirement slacks `phvs` (with a reduced weight for DSM requirements). The HV penalty factor is scaled from the branch resistances. ### `objective_min_line_loading_max_OG` ```julia function objective_min_line_loading_max_OG(pm::AbstractBFModelEdisgo) ``` Objective for `opf_version` 3 (with overlying grid): like `objective_min_line_loading_max` (losses + maximum line loading), but additionally penalises the overlying-grid (HV) requirement slacks `phvs` (reduced weight for DSM). ## Data preparation *Source: `edisgo/opf/eDisGo_OPF.jl/src/core/data.jl`* ### `set_ac_bf_start_values!` ```julia function set_ac_bf_start_values!(network::Dict{String,<:Any}) ``` Seed warm-start values on a (single-network) data dict: copy each flexibility's optimised power/energy results into the corresponding `*_start` fields, so a subsequent non-convex (Ipopt) solve can be warm-started from a previous solution. Used in the SOC → NC `warm_start` workflow. ### `correct_bus_types!` ```julia function correct_bus_types!(data::Dict{String,<:Any}) ``` checks bus types are suitable for a power flow study, if not, fixes them. the primary checks are that all type 2 buses (i.e., PV) have a connected and active generator and there is a single type 3 bus (i.e., slack bus) with an active connected generator. assumes that the network is a single connected component ### `_correct_bus_types!` ```julia function _correct_bus_types!(pm_data::Dict{String,<:Any}) ``` Internal worker for `correct_bus_types!`, operating on a single (non-multinetwork) PowerModels data dict. Demotes PV buses (type 2) without an active generator to PQ (type 1), validates the slack bus (type 3), and — if no slack bus is found — promotes the bus of the largest generator to slack. See `correct_bus_types!` for the public entry point. ## Solution processing *Source: `edisgo/opf/eDisGo_OPF.jl/src/core/solution.jl`* ### `sol_component_value_radial` ```julia function sol_component_value_radial(aim::AbstractPowerModel, n::Int, comp_name::Symbol, field_name_to::Symbol, comp_ids_to, variables) ``` Solution processor: write a per-branch variable indexed by the directed arc `(l, i, j)` (e.g. the branch flows) back into the PowerModels solution dict under `field_name_to`, for the radial branch-flow model. The arc-indexed counterpart of PowerModels' `sol_component_value`. ### `check_SOC_equality` ```julia function check_SOC_equality(result, data_edisgo) ``` Check whether the second-order-cone relaxation is *tight* in a solved SOC result. For every non-storage branch and time step it compares `pf² + qf²` against `ccm · w` (the SOC inequality); a gap below `-1e-1` means the cone is not tight there. Returns `(soc_tight, soc_eq_dict)`, where `soc_tight::Bool` and `soc_eq_dict` maps each time step to its offending branches. Used to decide whether a `warm_start` AC polishing step is needed (see `SOCBFPowerModelEdisgo`). ## I/O — network validation *Source: `edisgo/opf/eDisGo_OPF.jl/src/io/common.jl`* ### `correct_network_data!` ```julia function correct_network_data!(data::Dict{String,<:Any}) ``` Validate and normalise the parsed PowerModels network data before the OPF is built: runs the connectivity / status / limit checks, converts everything to per-unit (`make_per_unit!`), corrects transformer / voltage-angle / thermal / current limits and branch directions, checks storage / switch / voltage setpoints, and simplifies the cost terms. Called by `parse_json` when validation is enabled. ## I/O — JSON parsing *Source: `edisgo/opf/eDisGo_OPF.jl/src/io/json.jl`* ### `parse_json` ```julia function parse_json(io::Union{IO,String}; kwargs...)::Dict{String,Any} ``` Parses json from iostream or string