Skip to content

Commit 30e291d

Browse files
committed
Adding calculation for better bigM value.
1 parent b29bfa2 commit 30e291d

5 files changed

Lines changed: 117 additions & 188 deletions

File tree

src/monee/model/formulation/misoc/el.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,38 @@ def equations(
2727
]
2828

2929

30+
def _ell_physics_max(branch, w_max: float) -> float:
31+
"""Upper bound on per-unit squared current derived from voltage bounds alone.
32+
33+
From |I_ij| = |V_i - V_j| / |Z_ij| and |V_i|, |V_j| <= sqrt(W_max):
34+
ell_ij <= (2*sqrt(W_max))^2 / |Z_ij|^2 = 4*W_max / (r^2 + x^2).
35+
"""
36+
return 4 * w_max / (branch.br_r**2 + branch.br_x**2)
37+
38+
39+
def _big_m(w_max: float) -> float:
40+
"""Compute a tight big-M bound from the voltage bound alone.
41+
42+
Substituting the physics-based current bound ell_max = 4*W_max/|Z|^2 into
43+
the Cauchy-Schwarz result M = (sqrt(W_max) + |Z|*sqrt(ell_max))^2, the
44+
impedance cancels and M = 9*W_max, independent of branch impedance.
45+
"""
46+
return 9 * w_max
47+
48+
3049
class MISOCPElectricityBranchFormulation(BranchFormulation):
3150
def ensure_var(self, branch):
32-
branch.big_M = 20
3351
branch.current_pu = Var(1, min=0)
3452

3553
def minimize(self, branch, grid, from_node_model, to_node_model, **kwargs):
3654
return [branch.current_pu * branch.br_r]
3755

3856
def equations(self, branch, grid, from_node_model, to_node_model, **kwargs):
57+
w_max = grid.vm_pu_max**2
58+
big_m = _big_m(w_max)
59+
ell_phys = _ell_physics_max(branch, w_max)
3960
return [
61+
branch.current_pu <= ell_phys * branch.on_off,
4062
voltage_drop(
4163
from_node_model.vars["vm_pu_squared"],
4264
to_node_model.vars["vm_pu_squared"],
@@ -46,7 +68,7 @@ def equations(self, branch, grid, from_node_model, to_node_model, **kwargs):
4668
branch.br_r,
4769
branch.br_x,
4870
)
49-
<= branch.big_M * (1 - branch.on_off),
71+
<= big_m * (1 - branch.on_off),
5072
voltage_drop(
5173
from_node_model.vars["vm_pu_squared"],
5274
to_node_model.vars["vm_pu_squared"],
@@ -56,7 +78,7 @@ def equations(self, branch, grid, from_node_model, to_node_model, **kwargs):
5678
branch.br_r,
5779
branch.br_x,
5880
)
59-
>= -branch.big_M * (1 - branch.on_off),
81+
>= -big_m * (1 - branch.on_off),
6082
soc_rel(
6183
from_node_model.vars["vm_pu_squared"],
6284
branch.vars["p_from_mw"] / grid.sn_mva,
@@ -75,9 +97,4 @@ def equations(self, branch, grid, from_node_model, to_node_model, **kwargs):
7597
branch.current_pu,
7698
branch.br_x,
7799
),
78-
# Calculate the voltage/current error, However using this make the Problem non-convex
79-
# branch.gap == gap_expr(from_node_model.vars["vm_pu_squared"],
80-
# branch.vars["p_from_mw"],
81-
# branch.vars["q_from_mvar"],
82-
# branch.current_pu),
83100
]

src/monee/model/formulation/qp/gas.py

Lines changed: 0 additions & 90 deletions
This file was deleted.

src/monee/model/formulation/qp/water.py

Lines changed: 0 additions & 90 deletions
This file was deleted.

src/monee/model/grid.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class PowerGrid(Grid):
4444
"""
4545

4646
sn_mva: float = 1
47+
vm_pu_max: float = 1.5
4748

4849

4950
@model
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""
2+
Test that inactive branches (on_off=0) in the MISOCP formulation
3+
carry exactly zero current and zero power flow.
4+
5+
Topology: ring B0 → B1 → B2 → B0 where the B2→B0 tie-line is a backup
6+
branch with on_off=0. All load must flow through B0→B1→B2; the
7+
backup line should be completely quiescent.
8+
"""
9+
10+
import math
11+
12+
import monee.model as mm
13+
from monee.model import Network
14+
from monee.model.branch import PowerLine
15+
from monee.model.child import ExtPowerGrid, PowerLoad
16+
from monee.model.formulation import MISOCP_NETWORK_FORMULATION
17+
from monee.model.grid import PowerGrid
18+
from monee.model.node import Bus
19+
from monee.solver import PyomoSolver
20+
21+
22+
def create_ring_with_backup() -> Network:
23+
"""Three-bus ring; the B2→B0 branch is the inactive backup tie-line."""
24+
pn = Network(PowerGrid(name="power", sn_mva=1))
25+
26+
b0 = pn.node(
27+
Bus(base_kv=1),
28+
child_ids=[pn.child(ExtPowerGrid(p_mw=0, q_mvar=0, vm_pu=1.0, va_degree=0))],
29+
grid=mm.EL,
30+
)
31+
b1 = pn.node(
32+
Bus(base_kv=1),
33+
child_ids=[pn.child(PowerLoad(p_mw=0.5, q_mvar=0))],
34+
grid=mm.EL,
35+
)
36+
b2 = pn.node(
37+
Bus(base_kv=1),
38+
child_ids=[pn.child(PowerLoad(p_mw=0.5, q_mvar=0))],
39+
grid=mm.EL,
40+
)
41+
42+
line_kw = dict(r_ohm_per_m=0.00007, x_ohm_per_m=0.00007, parallel=1)
43+
pn.branch(PowerLine(length_m=500, **line_kw), b0, b1)
44+
pn.branch(PowerLine(length_m=500, **line_kw), b1, b2)
45+
pn.branch(PowerLine(length_m=500, backup=True, on_off=0, **line_kw), b2, b0)
46+
47+
pn.apply_formulation(MISOCP_NETWORK_FORMULATION)
48+
return pn
49+
50+
51+
def _backup_row(result):
52+
"""Return the result Series for the backup (on_off=0) line."""
53+
lines = result.get(mm.PowerLine)
54+
# The backup line has on_off=0; all others have on_off=1
55+
return lines[lines["on_off"] == 0].iloc[0]
56+
57+
58+
def test_inactive_branch_has_zero_current():
59+
result = PyomoSolver().solve(create_ring_with_backup())
60+
backup = _backup_row(result)
61+
assert math.isclose(backup["current_pu"], 0.0, abs_tol=1e-6), (
62+
f"Inactive branch current_pu should be 0, got {backup['current_pu']}"
63+
)
64+
65+
66+
def test_inactive_branch_has_zero_power_flow():
67+
result = PyomoSolver().solve(create_ring_with_backup())
68+
backup = _backup_row(result)
69+
for col in ("p_from_mw", "p_to_mw", "q_from_mvar", "q_to_mvar"):
70+
assert math.isclose(backup[col], 0.0, abs_tol=1e-4), (
71+
f"Inactive branch {col} should be 0, got {backup[col]}"
72+
)
73+
74+
75+
def test_active_lines_carry_full_load():
76+
"""Both loads must be fully served; only active lines carry current."""
77+
result = PyomoSolver().solve(create_ring_with_backup())
78+
79+
lines = result.get(mm.PowerLine)
80+
active = lines[lines["on_off"] == 1]
81+
82+
# Both active lines must carry non-zero current
83+
assert (active["current_pu"] > 1e-4).all(), (
84+
f"Active lines should carry current:\n{active['current_pu']}"
85+
)
86+
87+
# All load is served (regulation = 1 for both loads)
88+
loads = result.get(mm.PowerLoad)
89+
assert (loads["regulation"] > 0.99).all(), (
90+
f"All load should be served:\n{loads['regulation']}"
91+
)

0 commit comments

Comments
 (0)