3434Ensures that the net electrical load (charging point load - virtual generator support)
3535stays above the §14a minimum power level (typically 4.2 kW = 0.0042 MW).
3636
37+ For flexible CPs (in electromobility dict): uses optimization variable `pcp`.
38+ For fixed CPs (in load dict): uses fixed load parameter `load["pd"]`.
39+
40+ Big-M formulation:
41+ p_cp - p_cp14a >= p_min_14a - M * (1 - z_cp14a)
42+ When z=0 (inactive): constraint relaxed
43+ When z=1 (active): p_cp - p_cp14a >= p_min_14a (net load >= 4.2 kW)
44+
3745# Arguments
3846- `pm::AbstractBFModelEdisgo`: PowerModels model
3947- `i::Int`: Virtual generator index
@@ -42,75 +50,69 @@ stays above the §14a minimum power level (typically 4.2 kW = 0.0042 MW).
4250function constraint_cp_14a_min_net_load (pm:: AbstractBFModelEdisgo , i:: Int , nw:: Int = nw_id_default)
4351 gen_cp14a = PowerModels. ref (pm, nw, :gen_cp_14a , i)
4452 cp_idx = gen_cp14a[" cp_index" ]
45-
53+
54+ # Virtual generator support variable
55+ p_cp14a = PowerModels. var (pm, nw, :p_cp14a , i)
56+
57+ # §14a minimum power (per unit)
58+ p_min_14a = gen_cp14a[" p_min_14a" ]
59+
60+ # Maximum support capacity
61+ p_max_support = gen_cp14a[" pmax" ]
62+
63+ if p_max_support < 1e-6
64+ # Charging point too small for §14a curtailment, disable virtual generator
65+ JuMP. @constraint (pm. model, p_cp14a == 0.0 )
66+ return
67+ end
68+
69+ # Binary variable for Big-M formulation
70+ z_cp14a = PowerModels. var (pm, nw, :z_cp14a , i)
71+ M = p_max_support + p_min_14a
72+
4673 # Check if CP is flexible (in electromobility dict) or simple load
47- p_cp_load = nothing
4874 if haskey (PowerModels. ref (pm, nw), :electromobility ) && haskey (PowerModels. ref (pm, nw, :electromobility ), cp_idx)
49- # Flexible CP: use electromobility variable
50- cp = PowerModels. ref (pm, nw, :electromobility , cp_idx)
51- p_cp_load = cp[ " pcp" ] # Charging power variable (optimization variable )
75+ # Flexible CP: use optimization VARIABLE
76+ pcp = PowerModels. var (pm, nw, :pcp , cp_idx)
77+ JuMP . @constraint (pm . model, pcp - p_cp14a >= p_min_14a - M * ( 1 - z_cp14a) )
5278 else
5379 # Non-flexible CP: use fixed load timeseries
54- # Find the load by CP name
80+ # ============================================
81+ # NOTE: This is an O(n) linear search through all loads
82+ #
83+ # Why this search is necessary:
84+ # - cp_index may reference electromobility dict (flexible CPs with optimization vars)
85+ # - OR it may reference load dict (fixed CPs with constant power draw)
86+ # - No unified index mapping exists between these two data structures
87+ # - Load name matching is the only reliable way to find the correct load
88+ #
89+ # Performance impact:
90+ # - Acceptable for <1,000 loads per network
91+ # - For larger networks, consider pre-indexing loads by name in Python
92+ # before serializing to PowerModels dict
93+ # ============================================
5594 cp_name = gen_cp14a[" cp_name" ]
56- # Search for load with matching name
5795 load_found = false
5896 for (load_id, load) in PowerModels. ref (pm, nw, :load )
5997 if haskey (load, " name" ) && load[" name" ] == cp_name
60- p_cp_load = load[" pd" ] # Fixed load value (parameter, not variable)
98+ p_cp_load = load[" pd" ]
6199 load_found = true
100+ if p_cp_load > 1e-6
101+ # Fixed load with Big-M: when z=1, enforce min net load
102+ JuMP. @constraint (pm. model, p_cp_load - p_cp14a >= p_min_14a - M * (1 - z_cp14a))
103+ else
104+ # CP is off (p_cp_load ≈ 0), no support needed
105+ JuMP. @constraint (pm. model, p_cp14a == 0.0 )
106+ end
62107 break
63108 end
64109 end
65-
110+
66111 if ! load_found
67112 @warn " Could not find load for charging point $(cp_name) , skipping constraint"
68113 return
69114 end
70115 end
71-
72- # Virtual generator support
73- p_cp14a = PowerModels. var (pm, nw, :p_cp14a , i)
74-
75- # §14a minimum power (per unit)
76- p_min_14a = gen_cp14a[" p_min_14a" ]
77-
78- # Maximum support capacity (matches Python field name "pmax")
79- p_max_support = gen_cp14a[" pmax" ]
80-
81- if i == 3
82- println (" [DEBUG NW $nw HP $i ]" )
83- println (" > p_cp_load: $(round (p_cp_load, digits= 6 )) " )
84- println (" > p_cp14a (Var): $(p_cp14a) " ) # Zeigt die JuMP-Variable
85- println (" > p_min_14a: $(round (p_min_14a, digits= 6 )) " )
86- println (" > p_max_support: $(round (p_max_support, digits= 6 )) " )
87-
88- # Hilfswert für die Logik unten berechnen
89- p_min_net_debug = min (p_cp_load, p_min_14a)
90- println (" > p_min_net: $(round (p_min_net_debug, digits= 6 )) " )
91- println (" " * " -" ^ 20 )
92- end
93-
94- # Net load must stay ≥ minimum net load allowed
95- # The minimum is the LOWER of: current load or §14a limit
96- # This handles cases where CP draws less than 4.2 kW (e.g., 3 kW due to low charging demand)
97- # p_cp_load - p_cp14a ≥ min(p_cp_load, p_min_14a)
98- #
99- # Special cases:
100- # - If p_max_support ≈ 0 (CP too small), force virtual gen to zero
101- # - If CP is off (p_cp_load ≈ 0), no support needed
102- if p_max_support < 1e-6
103- # Charging point too small for §14a curtailment, disable virtual generator
104- JuMP. @constraint (pm. model, p_cp14a == 0.0 )
105- elseif p_cp_load > 1e-6
106- # Normal case: enforce minimum net load
107- # Net load cannot go below current load or §14a minimum, whichever is lower
108- p_min_net = min (p_cp_load, p_min_14a)
109- JuMP. @constraint (pm. model, p_cp14a <= p_cp_load - p_min_net)
110- else
111- # Charging point is off, no support needed
112- JuMP. @constraint (pm. model, p_cp14a == 0.0 )
113- end
114116end
115117
116118
0 commit comments