Coverage for tests/pyomo_models/test_pyomo_lindist.py: 99%
165 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-13 18:07 -0800
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-13 18:07 -0800
1import pytest
2import pandas as pd
3import pyomo.environ as pyo
4import distopf as opf
5from distopf.pyomo_models.lindist import create_lindist_model
6from distopf.importer import Case, create_case
9@pytest.fixture
10def ieee13_case():
11 """Fixture to load IEEE 13 test case"""
12 # return opf.DistOPFCase(
13 # data_path=opf.CASES_DIR / "csv" / "ieee13",
14 # objective_functions=opf.cp_obj_loss,
15 # control_variable="PQ",
16 # )
17 return create_case(data_path=opf.CASES_DIR / "csv" / "ieee13")
20@pytest.fixture
21def ieee123_30der_case():
22 """Fixture to load IEEE 123 with 30 DER test case"""
23 # return opf.DistOPFCase(
24 # data_path=opf.CASES_DIR / "csv" / "ieee123_30der",
25 # objective_functions=opf.cp_obj_loss,
26 # control_variable="PQ",
27 # )
28 return create_case(data_path=opf.CASES_DIR / "csv" / "ieee123_30der")
31@pytest.fixture
32def simple_case_data():
33 """Fixture with minimal test data"""
34 branch_data = pd.DataFrame(
35 {
36 "fb": [1, 2],
37 "tb": [2, 3],
38 "raa": [0.01, 0.02],
39 "rab": [0.0, 0.0],
40 "rac": [0.0, 0.0],
41 "rbb": [0.01, 0.02],
42 "rbc": [0.0, 0.0],
43 "rcc": [0.01, 0.02],
44 "xaa": [0.02, 0.04],
45 "xab": [0.0, 0.0],
46 "xac": [0.0, 0.0],
47 "xbb": [0.02, 0.04],
48 "xbc": [0.0, 0.0],
49 "xcc": [0.02, 0.04],
50 "phases": ["abc", "abc"],
51 "status": ["", ""],
52 }
53 )
55 bus_data = pd.DataFrame(
56 {
57 "id": [1, 2, 3],
58 "name": ["bus1", "bus2", "bus3"],
59 "bus_type": ["SWING", "PQ", "PQ"],
60 "v_min": [0.95, 0.95, 0.95],
61 "v_max": [1.05, 1.05, 1.05],
62 "phases": ["abc", "abc", "abc"],
63 }
64 )
66 gen_data = pd.DataFrame(
67 {
68 "id": [2],
69 "name": ["gen1"],
70 "pa": [0.5],
71 "pb": [0.4],
72 "pc": [0.3],
73 "qa": [0.1],
74 "qb": [0.1],
75 "qc": [0.1],
76 "sa_max": [1.0],
77 "sb_max": [1.0],
78 "sc_max": [1.0],
79 "qa_max": [0.8],
80 "qb_max": [0.8],
81 "qc_max": [0.8],
82 "qa_min": [-0.8],
83 "qb_min": [-0.8],
84 "qc_min": [-0.8],
85 "control_variable": ["PQ"],
86 "phases": ["abc"],
87 }
88 )
90 cap_data = pd.DataFrame(
91 {
92 "id": [3],
93 "name": ["cap1"],
94 "qa": [0.2],
95 "qb": [0.2],
96 "qc": [0.2],
97 "phases": ["abc"],
98 }
99 )
101 reg_data = pd.DataFrame(
102 {
103 "fb": [1],
104 "tb": [2],
105 "ratio_a": [1.0],
106 "ratio_b": [1.0],
107 "ratio_c": [1.0],
108 "phases": ["abc"],
109 }
110 )
112 return Case(
113 branch_data=branch_data,
114 bus_data=bus_data,
115 gen_data=gen_data,
116 cap_data=cap_data,
117 reg_data=reg_data,
118 )
121class TestCreateLinDistModel:
122 """Test the main model creation function"""
124 def test_model_creation_ieee13(self, ieee13_case):
125 """Test model creation with IEEE 13 case"""
126 case = Case(
127 branch_data=ieee13_case.branch_data,
128 bus_data=ieee13_case.bus_data,
129 gen_data=ieee13_case.gen_data,
130 cap_data=ieee13_case.cap_data,
131 reg_data=ieee13_case.reg_data,
132 )
134 model = create_lindist_model(case)
136 # Check that model is created
137 assert isinstance(model, pyo.ConcreteModel)
139 # ==================== SETS ====================
140 assert hasattr(model, "time_set")
141 assert hasattr(model, "bus_set")
142 assert hasattr(model, "swing_bus_set")
143 assert hasattr(model, "swing_phase_set")
144 assert hasattr(model, "branch_set")
145 assert hasattr(model, "phase_pair_set")
146 assert hasattr(model, "bus_phase_set")
147 assert hasattr(model, "branch_phase_set")
148 assert hasattr(model, "gen_phase_set")
149 assert hasattr(model, "cap_phase_set")
150 assert hasattr(model, "reg_phase_set")
151 assert hasattr(model, "bat_phase_set")
152 assert hasattr(model, "bat_set")
154 # ==================== PARAMETERS ====================
155 assert hasattr(model, "delta_t")
156 assert hasattr(model, "start_step")
157 assert hasattr(model, "n_steps")
158 assert hasattr(model, "r")
159 assert hasattr(model, "x")
160 assert hasattr(model, "p_load_nom")
161 assert hasattr(model, "q_load_nom")
162 assert hasattr(model, "cvr_p")
163 assert hasattr(model, "cvr_q")
164 assert hasattr(model, "p_gen_nom")
165 assert hasattr(model, "q_gen_nom")
166 assert hasattr(model, "s_rated")
167 assert hasattr(model, "q_gen_max")
168 assert hasattr(model, "q_gen_min")
169 assert hasattr(model, "gen_control_type")
170 assert hasattr(model, "q_cap_nom")
171 assert hasattr(model, "reg_ratio")
172 assert hasattr(model, "v_swing")
173 assert hasattr(model, "v_min")
174 assert hasattr(model, "v_max")
175 assert hasattr(model, "p_bat_nom")
176 assert hasattr(model, "q_bat_nom")
177 assert hasattr(model, "s_bat_rated")
178 assert hasattr(model, "q_bat_max")
179 assert hasattr(model, "q_bat_min")
180 assert hasattr(model, "bat_control_type")
181 assert hasattr(model, "energy_capacity")
182 assert hasattr(model, "soc_min")
183 assert hasattr(model, "soc_max")
184 assert hasattr(model, "start_soc")
185 assert hasattr(model, "charge_efficiency")
186 assert hasattr(model, "discharge_efficiency")
187 assert hasattr(model, "annual_cycle_limit")
188 assert hasattr(model, "battery_has_a_phase")
189 assert hasattr(model, "battery_has_b_phase")
190 assert hasattr(model, "battery_has_c_phase")
191 assert hasattr(model, "battery_has_phase")
192 assert hasattr(model, "battery_n_phases")
193 # ==================== VARIABLES ====================
194 assert hasattr(model, "v2")
195 assert hasattr(model, "v2_reg")
196 assert hasattr(model, "p_flow")
197 assert hasattr(model, "q_flow")
198 assert hasattr(model, "p_gen")
199 assert hasattr(model, "q_gen")
200 assert hasattr(model, "p_load")
201 assert hasattr(model, "q_load")
202 assert hasattr(model, "q_cap")
203 assert hasattr(model, "p_charge")
204 assert hasattr(model, "p_discharge")
205 assert hasattr(model, "p_bat")
206 assert hasattr(model, "q_bat")
207 assert hasattr(model, "soc")
209 def test_model_creation_ieee123_30der(self, ieee123_30der_case):
210 """Test model creation with IEEE 123 + 30 DER case"""
211 case = Case(
212 branch_data=ieee123_30der_case.branch_data,
213 bus_data=ieee123_30der_case.bus_data,
214 gen_data=ieee123_30der_case.gen_data,
215 cap_data=ieee123_30der_case.cap_data,
216 reg_data=ieee123_30der_case.reg_data,
217 )
219 model = create_lindist_model(case)
221 # Check that model is created
222 assert isinstance(model, pyo.ConcreteModel)
224 # Check that generators exist in this case
225 assert len(model.gen_phase_set) > 0
227 def test_model_creation_simple_case(self, simple_case_data):
228 """Test model creation with simple test data"""
229 model = create_lindist_model(simple_case_data)
231 # Check basic structure
232 assert isinstance(model, pyo.ConcreteModel)
233 assert len(model.bus_set) == 3
234 assert len(model.swing_bus_set) == 1
235 assert len(model.branch_set) == 2
238class TestSets:
239 """Test set creation"""
241 def test_bus_phase_set_ieee13(self, ieee13_case):
242 """Test bus-phase set creation for IEEE 13"""
243 case = Case(
244 branch_data=ieee13_case.branch_data,
245 bus_data=ieee13_case.bus_data,
246 gen_data=ieee13_case.gen_data,
247 cap_data=ieee13_case.cap_data,
248 reg_data=ieee13_case.reg_data,
249 )
251 model = create_lindist_model(case)
253 # Check that all buses with phases are represented
254 bus_phase_list = list(model.bus_phase_set)
256 # Check some specific bus-phase combinations from the CSV
257 assert (1, "a") in bus_phase_list # sourcebus has abc
258 assert (1, "b") in bus_phase_list
259 assert (1, "c") in bus_phase_list
260 assert (7, "b") in bus_phase_list # bus 645 has bc phases
261 assert (7, "c") in bus_phase_list
262 assert (7, "a") not in bus_phase_list # bus 645 doesn't have a phase
263 assert (11, "c") in bus_phase_list # bus 611 has only c phase
264 assert (11, "a") not in bus_phase_list
265 assert (11, "b") not in bus_phase_list
267 def test_branch_phase_set_ieee13(self, ieee13_case):
268 """Test branch-phase set creation for IEEE 13"""
269 case = Case(
270 branch_data=ieee13_case.branch_data,
271 bus_data=ieee13_case.bus_data,
272 gen_data=ieee13_case.gen_data,
273 cap_data=ieee13_case.cap_data,
274 reg_data=ieee13_case.reg_data,
275 )
277 model = create_lindist_model(case)
279 branch_phase_list = list(model.branch_phase_set)
281 # Check specific branch-phase combinations (branches identified by to_bus)
282 assert (2, "a") in branch_phase_list # branch to bus 2 has abc phases
283 assert (7, "b") in branch_phase_list # branch to bus 7 has cb phases
284 assert (7, "c") in branch_phase_list
285 assert (7, "a") not in branch_phase_list # branch to bus 7 doesn't have a phase
287 def test_gen_phase_set_empty(self, ieee13_case):
288 """Test generator phase set when no generators exist"""
289 case = Case(
290 branch_data=ieee13_case.branch_data,
291 bus_data=ieee13_case.bus_data,
292 gen_data=ieee13_case.gen_data, # IEEE 13 has no generators
293 cap_data=ieee13_case.cap_data,
294 reg_data=ieee13_case.reg_data,
295 )
297 model = create_lindist_model(case)
299 # Check that gen_phase_set is empty for IEEE 13
300 assert len(model.gen_phase_set) == 0
302 def test_cap_phase_set_ieee13(self, ieee13_case):
303 """Test capacitor phase set for IEEE 13"""
304 case = Case(
305 branch_data=ieee13_case.branch_data,
306 bus_data=ieee13_case.bus_data,
307 gen_data=ieee13_case.gen_data,
308 cap_data=ieee13_case.cap_data,
309 reg_data=ieee13_case.reg_data,
310 )
312 model = create_lindist_model(case)
314 cap_phase_list = list(model.cap_phase_set)
316 # From the cap_data CSV: bus 10 (675) has abc, bus 11 (611) has c
317 assert (10, "a") in cap_phase_list
318 assert (10, "b") in cap_phase_list
319 assert (10, "c") in cap_phase_list
320 assert (11, "c") in cap_phase_list
323class TestParameters:
324 """Test parameter creation"""
326 def test_impedance_parameters_ieee13(self, ieee13_case):
327 """Test resistance and reactance parameters for IEEE 13"""
328 case = Case(
329 branch_data=ieee13_case.branch_data,
330 bus_data=ieee13_case.bus_data,
331 gen_data=ieee13_case.gen_data,
332 cap_data=ieee13_case.cap_data,
333 reg_data=ieee13_case.reg_data,
334 )
336 model = create_lindist_model(case)
338 # Check that parameters exist and have expected values
339 # From CSV: branch 1->2 has raa=0.0008786982248520712
340 assert pyo.value(model.r[2, "aa"]) == pytest.approx(
341 0.0008786982248520712, rel=1e-10
342 )
343 assert pyo.value(model.x[2, "aa"]) == pytest.approx(
344 0.0015976331360946748, rel=1e-10
345 )
347 # Check off-diagonal terms
348 assert pyo.value(model.r[2, "ab"]) == 0.0
349 assert pyo.value(model.x[2, "ab"]) == 0.0
352class TestModelIntegrity:
353 """Test overall model integrity"""
355 def test_model_variables_match_sets(self, ieee13_case):
356 """Test that variable dimensions match set dimensions"""
357 case = Case(
358 branch_data=ieee13_case.branch_data,
359 bus_data=ieee13_case.bus_data,
360 gen_data=ieee13_case.gen_data,
361 cap_data=ieee13_case.cap_data,
362 reg_data=ieee13_case.reg_data,
363 )
365 model = create_lindist_model(case)
367 # Check that variable keys match set contents
368 v_keys = set(model.v2.keys())
369 bus_phase_set = set(model.bus_phase_set * model.time_set)
370 assert v_keys == bus_phase_set
372 p_flow_keys = set(model.p_flow.keys())
373 q_flow_keys = set(model.q_flow.keys())
374 branch_phase_set = set(model.branch_phase_set * model.time_set)
375 assert p_flow_keys == branch_phase_set
376 assert q_flow_keys == branch_phase_set
378 p_gen_keys = set(model.p_gen.keys())
379 q_gen_keys = set(model.q_gen.keys())
380 gen_phase_set = set(model.gen_phase_set * model.time_set)
381 assert p_gen_keys == gen_phase_set
382 assert q_gen_keys == gen_phase_set
384 q_cap_keys = set(model.q_cap.keys())
385 cap_phase_set = set(model.cap_phase_set * model.time_set)
386 assert q_cap_keys == cap_phase_set
389if __name__ == "__main__":
390 pytest.main([__file__, "-v"])