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

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 

7 

8 

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") 

18 

19 

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") 

29 

30 

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 ) 

54 

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 ) 

65 

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 ) 

89 

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 ) 

100 

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 ) 

111 

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 ) 

119 

120 

121class TestCreateLinDistModel: 

122 """Test the main model creation function""" 

123 

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 ) 

133 

134 model = create_lindist_model(case) 

135 

136 # Check that model is created 

137 assert isinstance(model, pyo.ConcreteModel) 

138 

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") 

153 

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") 

208 

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 ) 

218 

219 model = create_lindist_model(case) 

220 

221 # Check that model is created 

222 assert isinstance(model, pyo.ConcreteModel) 

223 

224 # Check that generators exist in this case 

225 assert len(model.gen_phase_set) > 0 

226 

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) 

230 

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 

236 

237 

238class TestSets: 

239 """Test set creation""" 

240 

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 ) 

250 

251 model = create_lindist_model(case) 

252 

253 # Check that all buses with phases are represented 

254 bus_phase_list = list(model.bus_phase_set) 

255 

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 

266 

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 ) 

276 

277 model = create_lindist_model(case) 

278 

279 branch_phase_list = list(model.branch_phase_set) 

280 

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 

286 

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 ) 

296 

297 model = create_lindist_model(case) 

298 

299 # Check that gen_phase_set is empty for IEEE 13 

300 assert len(model.gen_phase_set) == 0 

301 

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 ) 

311 

312 model = create_lindist_model(case) 

313 

314 cap_phase_list = list(model.cap_phase_set) 

315 

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 

321 

322 

323class TestParameters: 

324 """Test parameter creation""" 

325 

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 ) 

335 

336 model = create_lindist_model(case) 

337 

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 ) 

346 

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 

350 

351 

352class TestModelIntegrity: 

353 """Test overall model integrity""" 

354 

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 ) 

364 

365 model = create_lindist_model(case) 

366 

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 

371 

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 

377 

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 

383 

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 

387 

388 

389if __name__ == "__main__": 

390 pytest.main([__file__, "-v"])