ltbams 0.9.9__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. ams/__init__.py +4 -11
  2. ams/_version.py +3 -3
  3. ams/cases/5bus/pjm5bus_demo.xlsx +0 -0
  4. ams/cases/5bus/pjm5bus_jumper.xlsx +0 -0
  5. ams/cases/5bus/pjm5bus_uced.json +1062 -0
  6. ams/cases/5bus/pjm5bus_uced.xlsx +0 -0
  7. ams/cases/5bus/pjm5bus_uced_esd1.xlsx +0 -0
  8. ams/cases/5bus/pjm5bus_uced_ev.xlsx +0 -0
  9. ams/cases/ieee123/ieee123.xlsx +0 -0
  10. ams/cases/ieee123/ieee123_regcv1.xlsx +0 -0
  11. ams/cases/ieee14/ieee14.json +1166 -0
  12. ams/cases/ieee14/ieee14.raw +92 -0
  13. ams/cases/ieee14/ieee14_conn.xlsx +0 -0
  14. ams/cases/ieee14/ieee14_uced.xlsx +0 -0
  15. ams/cases/ieee39/ieee39.xlsx +0 -0
  16. ams/cases/ieee39/ieee39_uced.xlsx +0 -0
  17. ams/cases/ieee39/ieee39_uced_esd1.xlsx +0 -0
  18. ams/cases/ieee39/ieee39_uced_pvd1.xlsx +0 -0
  19. ams/cases/ieee39/ieee39_uced_vis.xlsx +0 -0
  20. ams/cases/matpower/benchmark.json +1594 -0
  21. ams/cases/matpower/case118.m +787 -0
  22. ams/cases/matpower/case14.m +129 -0
  23. ams/cases/matpower/case300.m +1315 -0
  24. ams/cases/matpower/case39.m +205 -0
  25. ams/cases/matpower/case5.m +62 -0
  26. ams/cases/matpower/case_ACTIVSg2000.m +9460 -0
  27. ams/cases/npcc/npcc.m +644 -0
  28. ams/cases/npcc/npcc_uced.xlsx +0 -0
  29. ams/cases/pglib/pglib_opf_case39_epri__api.m +243 -0
  30. ams/cases/wecc/wecc.m +714 -0
  31. ams/cases/wecc/wecc_uced.xlsx +0 -0
  32. ams/cli.py +6 -0
  33. ams/core/__init__.py +2 -0
  34. ams/core/documenter.py +652 -0
  35. ams/core/matprocessor.py +782 -0
  36. ams/core/model.py +330 -0
  37. ams/core/param.py +322 -0
  38. ams/core/service.py +918 -0
  39. ams/core/symprocessor.py +224 -0
  40. ams/core/var.py +59 -0
  41. ams/extension/__init__.py +5 -0
  42. ams/extension/eva.py +401 -0
  43. ams/interface.py +1085 -0
  44. ams/io/__init__.py +133 -0
  45. ams/io/json.py +82 -0
  46. ams/io/matpower.py +406 -0
  47. ams/io/psse.py +6 -0
  48. ams/io/pypower.py +103 -0
  49. ams/io/xlsx.py +80 -0
  50. ams/main.py +81 -4
  51. ams/models/__init__.py +24 -0
  52. ams/models/area.py +40 -0
  53. ams/models/bus.py +52 -0
  54. ams/models/cost.py +169 -0
  55. ams/models/distributed/__init__.py +3 -0
  56. ams/models/distributed/esd1.py +71 -0
  57. ams/models/distributed/ev.py +60 -0
  58. ams/models/distributed/pvd1.py +67 -0
  59. ams/models/group.py +231 -0
  60. ams/models/info.py +26 -0
  61. ams/models/line.py +238 -0
  62. ams/models/renewable/__init__.py +5 -0
  63. ams/models/renewable/regc.py +119 -0
  64. ams/models/reserve.py +94 -0
  65. ams/models/shunt.py +14 -0
  66. ams/models/static/__init__.py +2 -0
  67. ams/models/static/gen.py +165 -0
  68. ams/models/static/pq.py +61 -0
  69. ams/models/timeslot.py +69 -0
  70. ams/models/zone.py +49 -0
  71. ams/opt/__init__.py +12 -0
  72. ams/opt/constraint.py +175 -0
  73. ams/opt/exprcalc.py +127 -0
  74. ams/opt/expression.py +188 -0
  75. ams/opt/objective.py +174 -0
  76. ams/opt/omodel.py +432 -0
  77. ams/opt/optzbase.py +192 -0
  78. ams/opt/param.py +156 -0
  79. ams/opt/var.py +233 -0
  80. ams/pypower/__init__.py +8 -0
  81. ams/pypower/_compat.py +9 -0
  82. ams/pypower/core/__init__.py +8 -0
  83. ams/pypower/core/pips.py +894 -0
  84. ams/pypower/core/ppoption.py +244 -0
  85. ams/pypower/core/ppver.py +18 -0
  86. ams/pypower/core/solver.py +2451 -0
  87. ams/pypower/eps.py +6 -0
  88. ams/pypower/idx.py +174 -0
  89. ams/pypower/io.py +604 -0
  90. ams/pypower/make/__init__.py +11 -0
  91. ams/pypower/make/matrices.py +665 -0
  92. ams/pypower/make/pdv.py +506 -0
  93. ams/pypower/routines/__init__.py +7 -0
  94. ams/pypower/routines/cpf.py +513 -0
  95. ams/pypower/routines/cpf_callbacks.py +114 -0
  96. ams/pypower/routines/opf.py +1803 -0
  97. ams/pypower/routines/opffcns.py +1946 -0
  98. ams/pypower/routines/pflow.py +852 -0
  99. ams/pypower/toggle.py +1098 -0
  100. ams/pypower/utils.py +293 -0
  101. ams/report.py +212 -50
  102. ams/routines/__init__.py +23 -0
  103. ams/routines/acopf.py +117 -0
  104. ams/routines/cpf.py +65 -0
  105. ams/routines/dcopf.py +241 -0
  106. ams/routines/dcpf.py +209 -0
  107. ams/routines/dcpf0.py +196 -0
  108. ams/routines/dopf.py +150 -0
  109. ams/routines/ed.py +312 -0
  110. ams/routines/pflow.py +255 -0
  111. ams/routines/pflow0.py +113 -0
  112. ams/routines/routine.py +1033 -0
  113. ams/routines/rted.py +519 -0
  114. ams/routines/type.py +160 -0
  115. ams/routines/uc.py +376 -0
  116. ams/shared.py +63 -9
  117. ams/system.py +61 -22
  118. ams/utils/__init__.py +3 -0
  119. ams/utils/misc.py +77 -0
  120. ams/utils/paths.py +257 -0
  121. docs/Makefile +21 -0
  122. docs/make.bat +35 -0
  123. docs/source/_templates/autosummary/base.rst +5 -0
  124. docs/source/_templates/autosummary/class.rst +35 -0
  125. docs/source/_templates/autosummary/module.rst +65 -0
  126. docs/source/_templates/autosummary/module_toctree.rst +66 -0
  127. docs/source/api.rst +102 -0
  128. docs/source/conf.py +206 -0
  129. docs/source/examples/index.rst +34 -0
  130. docs/source/genmodelref.py +61 -0
  131. docs/source/genroutineref.py +47 -0
  132. docs/source/getting_started/copyright.rst +20 -0
  133. docs/source/getting_started/formats/index.rst +20 -0
  134. docs/source/getting_started/formats/matpower.rst +183 -0
  135. docs/source/getting_started/formats/psse.rst +46 -0
  136. docs/source/getting_started/formats/pypower.rst +223 -0
  137. docs/source/getting_started/formats/xlsx.png +0 -0
  138. docs/source/getting_started/formats/xlsx.rst +23 -0
  139. docs/source/getting_started/index.rst +76 -0
  140. docs/source/getting_started/install.rst +231 -0
  141. docs/source/getting_started/overview.rst +26 -0
  142. docs/source/getting_started/testcase.rst +45 -0
  143. docs/source/getting_started/verification.rst +13 -0
  144. docs/source/images/curent.ico +0 -0
  145. docs/source/images/dcopf_time.png +0 -0
  146. docs/source/images/sponsors/CURENT_Logo_NameOnTrans.png +0 -0
  147. docs/source/images/sponsors/CURENT_Logo_Transparent.png +0 -0
  148. docs/source/images/sponsors/CURENT_Logo_Transparent_Name.png +0 -0
  149. docs/source/images/sponsors/doe.png +0 -0
  150. docs/source/index.rst +108 -0
  151. docs/source/modeling/example.rst +159 -0
  152. docs/source/modeling/index.rst +17 -0
  153. docs/source/modeling/model.rst +210 -0
  154. docs/source/modeling/routine.rst +122 -0
  155. docs/source/modeling/system.rst +51 -0
  156. docs/source/release-notes.rst +398 -0
  157. ltbams-1.0.2.dist-info/METADATA +215 -0
  158. ltbams-1.0.2.dist-info/RECORD +188 -0
  159. {ltbams-0.9.9.dist-info → ltbams-1.0.2.dist-info}/WHEEL +1 -1
  160. ltbams-1.0.2.dist-info/top_level.txt +3 -0
  161. tests/__init__.py +0 -0
  162. tests/test_1st_system.py +33 -0
  163. tests/test_addressing.py +40 -0
  164. tests/test_andes_mats.py +61 -0
  165. tests/test_case.py +266 -0
  166. tests/test_cli.py +34 -0
  167. tests/test_export_csv.py +89 -0
  168. tests/test_group.py +83 -0
  169. tests/test_interface.py +216 -0
  170. tests/test_io.py +32 -0
  171. tests/test_jumper.py +27 -0
  172. tests/test_known_good.py +267 -0
  173. tests/test_matp.py +437 -0
  174. tests/test_model.py +54 -0
  175. tests/test_omodel.py +119 -0
  176. tests/test_paths.py +22 -0
  177. tests/test_report.py +251 -0
  178. tests/test_repr.py +21 -0
  179. tests/test_routine.py +178 -0
  180. tests/test_rtn_dcopf.py +101 -0
  181. tests/test_rtn_dcpf.py +77 -0
  182. tests/test_rtn_ed.py +279 -0
  183. tests/test_rtn_pflow.py +219 -0
  184. tests/test_rtn_rted.py +273 -0
  185. tests/test_rtn_uc.py +248 -0
  186. tests/test_service.py +73 -0
  187. ltbams-0.9.9.dist-info/LICENSE +0 -692
  188. ltbams-0.9.9.dist-info/METADATA +0 -859
  189. ltbams-0.9.9.dist-info/RECORD +0 -14
  190. ltbams-0.9.9.dist-info/top_level.txt +0 -1
  191. {ltbams-0.9.9.dist-info → ltbams-1.0.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,782 @@
1
+ """
2
+ Module for system matrix make.
3
+ """
4
+
5
+ import logging
6
+ import os
7
+ import sys
8
+ from typing import Optional
9
+
10
+ import numpy as np
11
+
12
+ from andes.thirdparty.npfunc import safe_div
13
+ from andes.shared import tqdm, tqdm_nb
14
+ from andes.utils.misc import elapsed, is_notebook
15
+
16
+ from ams.opt import Param
17
+ from ams.shared import pd, sps
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class MParam(Param):
23
+ """
24
+ Class for matrix parameters built from the system.
25
+
26
+ MParam is designed to be a subclass of RParam for routine parameters
27
+ management.
28
+
29
+ Parameters
30
+ ----------
31
+ name : str, optional
32
+ Name of this parameter. If not provided, `name` will be set
33
+ to the attribute name.
34
+ tex_name : str, optional
35
+ LaTeX-formatted parameter name. If not provided, `tex_name`
36
+ will be assigned the same as `name`.
37
+ info : str, optional
38
+ A description of this parameter
39
+ unit : str, optional
40
+ Unit of the parameter.
41
+ v : np.ndarray, optional
42
+ Matrix value of the parameter.
43
+ owner : object, optional
44
+ Owner of the MParam, usually the MatProcessor instance.
45
+ sparse : bool, optional
46
+ If True, the matrix is stored in sparse format.
47
+ col_names : list, optional
48
+ Column names of the matrix.
49
+ row_names : list, optional
50
+ Row names of the matrix.
51
+ """
52
+
53
+ def __init__(self,
54
+ name: Optional[str] = None,
55
+ tex_name: Optional[str] = None,
56
+ info: Optional[str] = None,
57
+ unit: Optional[str] = None,
58
+ v: Optional[np.ndarray] = None,
59
+ owner: Optional[object] = None,
60
+ sparse: Optional[bool] = False,
61
+ col_names: Optional[list] = None,
62
+ row_names: Optional[list] = None,
63
+ ):
64
+ self.name = name
65
+ self.tex_name = tex_name if (tex_name is not None) else name
66
+ self.info = info
67
+ self.unit = unit
68
+ self._v = v
69
+ self.sparse = sparse
70
+ self.owner = owner
71
+ self.col_names = col_names
72
+ self.row_names = row_names
73
+
74
+ def export_csv(self, path=None):
75
+ """
76
+ Export the matrix to a CSV file.
77
+
78
+ Parameters
79
+ ----------
80
+ path : str, optional
81
+ Path to the output CSV file.
82
+
83
+ Returns
84
+ -------
85
+ str
86
+ The path of the exported csv file
87
+ """
88
+
89
+ if path is None:
90
+ if self.owner.system.files.fullname is None:
91
+ logger.info("Input file name not detacted. Using `Untitled`.")
92
+ file_name = f'Untitled_{self.name}'
93
+ else:
94
+ file_name = os.path.splitext(self.owner.system.files.fullname)[0]
95
+ file_name += f'_{self.name}'
96
+ path = os.path.join(os.getcwd(), file_name + '.csv')
97
+ else:
98
+ file_name = os.path.splitext(os.path.basename(path))[0]
99
+
100
+ pd.DataFrame(data=self.v, columns=self.col_names, index=self.row_names).to_csv(path)
101
+
102
+ return file_name + '.csv'
103
+
104
+ @property
105
+ def v(self):
106
+ """
107
+ Return the value of the parameter.
108
+ """
109
+ # NOTE: scipy.sparse matrix will return 2D array
110
+ # so we squeeze it here if only one row
111
+ if isinstance(self._v, (sps.csr_matrix, sps.lil_matrix, sps.csc_matrix)):
112
+ out = self._v.toarray()
113
+ if out.shape[0] == 1:
114
+ return np.squeeze(out)
115
+ else:
116
+ return out
117
+ return self._v
118
+
119
+ @property
120
+ def shape(self):
121
+ """
122
+ Return the shape of the parameter.
123
+ """
124
+ return self.v.shape
125
+
126
+ @property
127
+ def n(self):
128
+ """
129
+ Return the szie of the parameter.
130
+ """
131
+ return len(self.v)
132
+
133
+ @property
134
+ def class_name(self):
135
+ """
136
+ Return the class name
137
+ """
138
+ return self.__class__.__name__
139
+
140
+
141
+ class MatProcessor:
142
+ """
143
+ Class for matrices processing in AMS system.
144
+ The connectivity matrices `Cft`, `Cg`, `Cl`, and `Csh` ***have taken*** the
145
+ devices connectivity into account.
146
+
147
+ The MParams' row names and col names are assigned in `System.setup()`.
148
+ """
149
+
150
+ def __init__(self, system):
151
+ self.system = system
152
+ self.initialized = False
153
+ self.pbar = None
154
+
155
+ self.Cft = MParam(name='Cft', tex_name=r'C_{ft}',
156
+ info='Line connectivity matrix',
157
+ v=None, sparse=True, owner=self)
158
+ self.CftT = MParam(name='CftT', tex_name=r'C_{ft}^{T}',
159
+ info='Line connectivity matrix transpose',
160
+ v=None, sparse=True, owner=self)
161
+ self.Cg = MParam(name='Cg', tex_name=r'C_g',
162
+ info='Generator connectivity matrix',
163
+ v=None, sparse=True, owner=self)
164
+ self.Cl = MParam(name='Cl', tex_name=r'Cl',
165
+ info='Load connectivity matrix',
166
+ v=None, sparse=True, owner=self)
167
+ self.Csh = MParam(name='Csh', tex_name=r'C_{sh}',
168
+ info='Shunt connectivity matrix',
169
+ v=None, sparse=True, owner=self)
170
+
171
+ self.Bbus = MParam(name='Bbus', tex_name=r'B_{bus}',
172
+ info='Bus admittance matrix',
173
+ v=None, sparse=True, owner=self)
174
+ self.Bf = MParam(name='Bf', tex_name=r'B_{f}',
175
+ info='Bf matrix',
176
+ v=None, sparse=True, owner=self)
177
+ self.Pbusinj = MParam(name='Pbusinj', tex_name=r'P_{bus}^{inj}',
178
+ info='Bus power injection vector',
179
+ v=None, sparse=False, owner=self)
180
+ self.Pfinj = MParam(name='Pfinj', tex_name=r'P_{f}^{inj}',
181
+ info='Line power injection vector',
182
+ v=None, sparse=False, owner=self)
183
+
184
+ self.PTDF = MParam(name='PTDF', tex_name=r'P_{TDF}',
185
+ info='Power transfer distribution factor',
186
+ v=None, sparse=False, owner=self)
187
+ self.LODF = MParam(name='LODF', tex_name=r'O_{TDF}',
188
+ info='Line outage distribution factor',
189
+ v=None, sparse=False, owner=self)
190
+
191
+ def build(self, force=False):
192
+ """
193
+ Build the system matrices.
194
+ It build connectivity matrices first: Cg, Cl, Csh, Cft, and CftT.
195
+ Then build bus matrices: Bf, Bbus, Pfinj, and Pbusinj.
196
+
197
+ Parameters
198
+ ----------
199
+ force : bool, optional
200
+ If True, force to rebuild the matrices. Default is False.
201
+
202
+ Notes
203
+ -----
204
+ Generator online status is NOT considered in its connectivity matrix.
205
+ The same applies for load, line, and shunt.
206
+
207
+ Returns
208
+ -------
209
+ initialized : bool
210
+ True if the matrices are built successfully.
211
+ """
212
+ if not force and self.initialized:
213
+ logger.debug("System matrices are already built.")
214
+ return self.initialized
215
+
216
+ t_mat, _ = elapsed()
217
+ logger.warning("Building system matrices")
218
+ # --- connectivity matrices ---
219
+ _ = self.build_cg()
220
+ _ = self.build_cl()
221
+ _ = self.build_csh()
222
+ _ = self.build_cft()
223
+
224
+ # --- bus matrices ---
225
+ _ = self.build_bf()
226
+ _ = self.build_bbus()
227
+ _ = self.build_pfinj()
228
+ _ = self.build_pbusinj()
229
+ _, s_mat = elapsed(t_mat)
230
+
231
+ logger.debug(f" -> System matrices built in {s_mat}")
232
+ self.initialized = True
233
+ return self.initialized
234
+
235
+ @property
236
+ def class_name(self):
237
+ """
238
+ Return the class name
239
+ """
240
+ return self.__class__.__name__
241
+
242
+ @property
243
+ def n(self):
244
+ """
245
+ To fit the RParam style.
246
+ """
247
+ return 2
248
+
249
+ def build_cg(self):
250
+ """
251
+ Build generator connectivity matrix Cg, and store it in the MParam `Cg`.
252
+
253
+ Returns
254
+ -------
255
+ Cg : scipy.sparse.csr_matrix
256
+ Generator connectivity matrix.
257
+ """
258
+ system = self.system
259
+
260
+ # common variables
261
+ nb = system.Bus.n
262
+ ng = system.StaticGen.n
263
+
264
+ # bus indices: idx -> uid
265
+ idx_gen = system.StaticGen.get_all_idxes()
266
+ u_gen = system.StaticGen.get(src='u', attr='v', idx=idx_gen)
267
+ on_gen = np.flatnonzero(u_gen) # uid of online generators
268
+ on_gen_idx = [idx_gen[i] for i in on_gen] # idx of online generators
269
+ on_gen_bus = system.StaticGen.get(src='bus', attr='v', idx=on_gen_idx)
270
+
271
+ row = np.array([system.Bus.idx2uid(x) for x in on_gen_bus])
272
+ col = np.array([idx_gen.index(x) for x in on_gen_idx])
273
+ self.Cg._v = sps.csr_matrix((np.ones(len(on_gen_idx)), (row, col)), (nb, ng))
274
+ return self.Cg._v
275
+
276
+ def build_cl(self):
277
+ """
278
+ Build load connectivity matrix Cl, and store it in the MParam `Cl`.
279
+
280
+ Returns
281
+ -------
282
+ Cl : scipy.sparse.csr_matrix
283
+ Load connectivity matrix.
284
+ """
285
+ system = self.system
286
+
287
+ # common variables
288
+ nb = system.Bus.n
289
+ npq = system.PQ.n
290
+
291
+ # load indices: idx -> uid
292
+ idx_load = system.PQ.idx.v
293
+ u_load = system.PQ.get(src='u', attr='v', idx=idx_load)
294
+ on_load = np.flatnonzero(u_load)
295
+ on_load_idx = [idx_load[i] for i in on_load]
296
+ on_load_bus = system.PQ.get(src='bus', attr='v', idx=on_load_idx)
297
+
298
+ row = np.array([system.Bus.idx2uid(x) for x in on_load_bus])
299
+ col = np.array([system.PQ.idx2uid(x) for x in on_load_idx])
300
+ self.Cl._v = sps.csr_matrix((np.ones(len(on_load_idx)), (row, col)), (nb, npq))
301
+ return self.Cl._v
302
+
303
+ def build_csh(self):
304
+ """
305
+ Build shunt connectivity matrix Csh, and store it in the MParam `Csh`.
306
+
307
+ Returns
308
+ -------
309
+ Csh : spmatrix
310
+ Shunt connectivity matrix.
311
+ """
312
+ system = self.system
313
+
314
+ # common variables
315
+ nb = system.Bus.n
316
+ nsh = system.Shunt.n
317
+
318
+ # shunt indices: idx -> uid
319
+ idx_shunt = system.Shunt.idx.v
320
+ u_shunt = system.Shunt.get(src='u', attr='v', idx=idx_shunt)
321
+ on_shunt = np.flatnonzero(u_shunt)
322
+ on_shunt_idx = [idx_shunt[i] for i in on_shunt]
323
+ on_shunt_bus = system.Shunt.get(src='bus', attr='v', idx=on_shunt_idx)
324
+
325
+ row = np.array([system.Bus.idx2uid(x) for x in on_shunt_bus])
326
+ col = np.array([system.Shunt.idx2uid(x) for x in on_shunt_idx])
327
+ self.Csh._v = sps.csr_matrix((np.ones(len(on_shunt_idx)), (row, col)), (nb, nsh))
328
+ return self.Csh._v
329
+
330
+ def build_cft(self):
331
+ """
332
+ Build line connectivity matrix Cft and its transpose CftT.
333
+ The Cft and CftT are stored in the MParam `Cft` and `CftT`, respectively.
334
+
335
+ Returns
336
+ -------
337
+ Cft : scipy.sparse.csr_matrix
338
+ Line connectivity matrix.
339
+ """
340
+ system = self.system
341
+
342
+ # common variables
343
+ nb = system.Bus.n
344
+ nl = system.Line.n
345
+
346
+ # line indices: idx -> uid
347
+ idx_line = system.Line.idx.v
348
+ u_line = system.Line.get(src='u', attr='v', idx=idx_line)
349
+ on_line = np.flatnonzero(u_line)
350
+ on_line_idx = [idx_line[i] for i in on_line]
351
+ on_line_bus1 = system.Line.get(src='bus1', attr='v', idx=on_line_idx)
352
+ on_line_bus2 = system.Line.get(src='bus2', attr='v', idx=on_line_idx)
353
+
354
+ data_line = np.ones(2*len(on_line_idx))
355
+ data_line[len(on_line_idx):] = -1
356
+ row_line = np.array([system.Bus.idx2uid(x) for x in on_line_bus1 + on_line_bus2])
357
+ col_line = np.array([system.Line.idx2uid(x) for x in on_line_idx + on_line_idx])
358
+ self.Cft._v = sps.csr_matrix((data_line, (row_line, col_line)), (nb, nl))
359
+ self.CftT._v = self.Cft._v.T
360
+ return self.Cft._v
361
+
362
+ def build_bf(self):
363
+ """
364
+ Build DC Bf matrix and store it in the MParam `Bf`.
365
+
366
+ Returns
367
+ -------
368
+ Bf : scipy.sparse.csr_matrix
369
+ Bf matrix.
370
+ """
371
+ system = self.system
372
+
373
+ # common variables
374
+ nb = system.Bus.n
375
+ nl = system.Line.n
376
+
377
+ # line parameters
378
+ idx_line = system.Line.idx.v
379
+ b = self._calc_b()
380
+
381
+ # build Bf such that Bf * Va is the vector of real branch powers injected
382
+ # at each branch's "from" bus
383
+ f = system.Bus.idx2uid(system.Line.get(src='bus1', attr='v', idx=idx_line))
384
+ t = system.Bus.idx2uid(system.Line.get(src='bus2', attr='v', idx=idx_line))
385
+ ir = np.r_[range(nl), range(nl)] # double set of row indices
386
+ self.Bf._v = sps.csr_matrix((np.r_[b, -b], (ir, np.r_[f, t])), (nl, nb))
387
+ return self.Bf._v
388
+
389
+ def build_bbus(self):
390
+ """
391
+ Build Bdc matrix and store it in the MParam `Bbus`.
392
+
393
+ Returns
394
+ -------
395
+ Bdc : scipy.sparse.csr_matrix
396
+ DC bus admittance matrix.
397
+ """
398
+ self.Bbus._v = self.Cft._v * self.Bf._v
399
+ return self.Bbus._v
400
+
401
+ def build_pfinj(self):
402
+ """
403
+ Build DC Pfinj vector and store it in the MParam `Pfinj`.
404
+
405
+ Returns
406
+ -------
407
+ Pfinj : np.ndarray
408
+ Line power injection vector.
409
+ """
410
+ idx_line = self.system.Line.idx.v
411
+ b = self._calc_b()
412
+ phi = self.system.Line.get(src='phi', attr='v', idx=idx_line)
413
+ self.Pfinj._v = b * (-phi)
414
+ return self.Pfinj._v
415
+
416
+ def build_pbusinj(self):
417
+ """
418
+ Build DC Pbusinj vector and store it in the MParam `Pbusinj`.
419
+
420
+ Returns
421
+ -------
422
+ Pbusinj : np.ndarray
423
+ Bus power injection vector.
424
+ """
425
+ self.Pbusinj._v = self.Cft._v * self.Pfinj._v
426
+ return self.Pbusinj._v
427
+
428
+ def _calc_b(self):
429
+ """
430
+ Calculate DC series susceptance for each line.
431
+
432
+ Returns
433
+ -------
434
+ b : np.ndarray
435
+ Series susceptance for each line.
436
+ """
437
+ system = self.system
438
+ nl = system.Line.n
439
+
440
+ # line parameters
441
+ idx_line = system.Line.idx.v
442
+ x = system.Line.get(src='x', attr='v', idx=idx_line)
443
+ u_line = system.Line.get(src='u', attr='v', idx=idx_line)
444
+ b = u_line / x # series susceptance
445
+
446
+ # in DC, tap is assumed to be 1
447
+ tap0 = system.Line.get(src='tap', attr='v', idx=idx_line)
448
+ tap = np.ones(nl)
449
+ i = np.flatnonzero(tap0)
450
+ tap[i] = tap0[i] # assign non-zero tap ratios
451
+ b = b / tap # adjusted series susceptance
452
+
453
+ return b
454
+
455
+ def build_ptdf(self, line=None, no_store=False,
456
+ incremental=False, step=1000, no_tqdm=False,
457
+ permc_spec=None, use_umfpack=True):
458
+ """
459
+ Build the Power Transfer Distribution Factor (PTDF) matrix and optionally store it in `MParam.PTDF`.
460
+
461
+ PTDF[m, n] represents the increased line flow on line `m` for a 1 p.u. power injection at bus `n`.
462
+ It is similar to the Generation Shift Factor (GSF).
463
+
464
+ Note: There may be minor discrepancies between PTDF-based line flow and DCOPF-calculated line flow.
465
+
466
+ For large cases, use `incremental=True` to calculate the sparse PTDF in chunks, which will be stored
467
+ as a `scipy.sparse.lil_matrix`. In this mode, the PTDF is calculated in chunks, and a progress bar
468
+ will be shown unless `no_tqdm=True`.
469
+
470
+ Parameters
471
+ ----------
472
+ line : int, str, list, optional
473
+ Line indices for which the PTDF is calculated. If specified, the PTDF will not be stored in `MParam`.
474
+ no_store : bool, optional
475
+ If False, store the PTDF in `MatProcessor.PTDF._v`. Default is False.
476
+ incremental : bool, optional
477
+ If True, calculate the sparse PTDF in chunks to save memory. Default is False.
478
+ step : int, optional
479
+ Step size for incremental calculation. Default is 1000.
480
+ no_tqdm : bool, optional
481
+ If True, disable the progress bar. Default is False.
482
+ permc_spec : str, optional
483
+ Column permutation strategy for sparsity preservation. Default is 'COLAMD'.
484
+ use_umfpack : bool, optional
485
+ If True, use UMFPACK as the solver. Effective only when (`incremental=True`)
486
+ & (`line` contains a single line or `step` is 1). Default is True.
487
+
488
+ Parameters
489
+ ----------
490
+ line: int, str, list, optional
491
+ Lines index for which the PTDF is calculated. It takes both single
492
+ or multiple line indices. Note that if `line` is given, the PTDF will
493
+ not be stored in the MParam.
494
+ no_store : bool, optional
495
+ If False, the PTDF will be stored into `MatProcessor.PTDF._v`.
496
+ incremental : bool, optional
497
+ If True, the sparse PTDF will be calculated in chunks to save memory.
498
+ step : int, optional
499
+ Step for incremental calculation.
500
+ no_tqdm : bool, optional
501
+ If True, the progress bar will be disabled.
502
+ permc_spec : str, optional
503
+ How to permute the columns of the matrix for sparsity preservation. (default: 'COLAMD')
504
+ use_umfpack : bool, optional
505
+ If True, use UMFPACK as the solver. (default: True)
506
+
507
+ Returns
508
+ -------
509
+ PTDF : np.ndarray or scipy.sparse.lil_matrix
510
+ Power transfer distribution factor.
511
+
512
+ References
513
+ ----------
514
+ [1] PowerWorld Documentation, Power Transfer Distribution Factors, [Online]
515
+
516
+ Available:
517
+
518
+ https://www.powerworld.com/WebHelp/Content/MainDocumentation_HTML/Power_Transfer_Distribution_Factors.htm
519
+ """
520
+ system = self.system
521
+
522
+ # use first slack bus as reference slack bus
523
+ slack = system.Slack.bus.v[0]
524
+ noslack = [system.Bus.idx2uid(bus) for bus in system.Bus.idx.v if bus != slack]
525
+
526
+ # use first bus for voltage angle reference
527
+ noref_idx = system.Bus.idx.v[1:]
528
+ noref = system.Bus.idx2uid(noref_idx)
529
+
530
+ if line is None:
531
+ luid = system.Line.idx2uid(system.Line.idx.v)
532
+ elif isinstance(line, (int, str)):
533
+ try:
534
+ luid = [system.Line.idx2uid(line)]
535
+ except ValueError:
536
+ raise ValueError(f"Line {line} not found.")
537
+ elif isinstance(line, list):
538
+ luid = system.Line.idx2uid(line)
539
+
540
+ # build other matrices if not built
541
+ if not self.initialized:
542
+ logger.debug("System matrices are not built. Building now.")
543
+ self.build()
544
+
545
+ nbus = system.Bus.n
546
+ nline = len(luid)
547
+
548
+ Bbus = self.Bbus._v
549
+ Bf = self.Bf._v
550
+
551
+ if incremental:
552
+ # initialize progress bar
553
+ if is_notebook():
554
+ self.pbar = tqdm_nb(total=100, unit='%', file=sys.stdout,
555
+ disable=no_tqdm)
556
+ else:
557
+ self.pbar = tqdm(total=100, unit='%', ncols=80, ascii=True,
558
+ file=sys.stdout, disable=no_tqdm)
559
+
560
+ self.pbar.update(0)
561
+ last_pc = 0
562
+
563
+ H = sps.lil_matrix((nline, system.Bus.n))
564
+
565
+ # NOTE: for PTDF, we are building rows by rows
566
+ for start in range(0, nline, step):
567
+ end = min(start + step, nline)
568
+ sol = sps.linalg.spsolve(A=Bbus[np.ix_(noslack, noref)].T,
569
+ b=Bf[np.ix_(luid[start:end], noref)].T,
570
+ permc_spec=permc_spec,
571
+ use_umfpack=use_umfpack).T
572
+ H[start:end, noslack] = sol
573
+
574
+ # show progress in percentage
575
+ perc = np.round(min((end / nline) * 100, 100), 2)
576
+
577
+ perc_diff = perc - last_pc
578
+ if perc_diff >= 1:
579
+ self.pbar.update(perc_diff)
580
+ last_pc = perc
581
+
582
+ # finish progress bar
583
+ self.pbar.update(100 - last_pc)
584
+ # removed `pbar` so that System object can be serialized
585
+ self.pbar.close()
586
+ self.pbar = None
587
+ else:
588
+ H = np.zeros((nline, nbus))
589
+ H[:, noslack] = np.linalg.solve(Bbus.todense()[np.ix_(noslack, noref)].T,
590
+ Bf.todense()[np.ix_(luid, noref)].T).T
591
+
592
+ # reshape results into 1D array if only one line
593
+ if isinstance(line, (int, str)):
594
+ H = H[0, :]
595
+
596
+ if (not no_store) & (line is None):
597
+ self.PTDF._v = H
598
+
599
+ return H
600
+
601
+ def build_lodf(self, line=None, no_store=False,
602
+ incremental=False, step=1000, no_tqdm=False):
603
+ """
604
+ Build the Line Outage Distribution Factor matrix and store it in the
605
+ MParam `LODF`.
606
+
607
+ `LODF[m, n]` means the increased line flow on line `m` when there is
608
+ 1 p.u. line flow decrease on line `n` due to line `n` outage.
609
+ It is also referred to as Branch Outage Distribution Factor (BODF).
610
+
611
+ It requires DC PTDF and Cft.
612
+
613
+ For large cases where memory is a concern, use `incremental=True` to
614
+ calculate the sparse LODF in chunks in the format of scipy.sparse.lil_matrix.
615
+
616
+ Parameters
617
+ ----------
618
+ line: int, str, list, optional
619
+ Lines index for which the LODF is calculated. It takes both single
620
+ or multiple line indices. Note that if `line` is given, the LODF will
621
+ not be stored in the MParam.
622
+ no_store : bool, optional
623
+ If False, the LODF will be stored into `MatProcessor.LODF._v`.
624
+ incremental : bool, optional
625
+ If True, the sparse LODF will be calculated in chunks to save memory.
626
+ step : int, optional
627
+ Step for incremental calculation.
628
+ no_tqdm : bool, optional
629
+ If True, the progress bar will be disabled.
630
+
631
+ Returns
632
+ -------
633
+ LODF : np.ndarray, scipy.sparse.lil_matrix
634
+ Line outage distribution factor.
635
+
636
+ References
637
+ ----------
638
+ [1] PowerWorld Documentation, Line Outage Distribution Factors, [Online]
639
+
640
+ Available:
641
+
642
+ https://www.powerworld.com/WebHelp/Content/MainDocumentation_HTML/Line_Outage_Distribution_Factors_LODFs.htm
643
+ """
644
+ system = self.system
645
+
646
+ if line is None:
647
+ luid = system.Line.idx2uid(system.Line.idx.v)
648
+ elif isinstance(line, (int, str)):
649
+ try:
650
+ luid = [system.Line.idx2uid(line)]
651
+ except ValueError:
652
+ raise ValueError(f"Line {line} not found.")
653
+ elif isinstance(line, list):
654
+ luid = system.Line.idx2uid(line)
655
+
656
+ # NOTE: here we use nbranch to differentiate it with nline
657
+ nbranch = system.Line.n
658
+ nline = len(luid)
659
+
660
+ ptdf = self.PTDF._v
661
+ # build PTDF if not built
662
+ if self.PTDF._v is None:
663
+ ptdf = self.build_ptdf(no_store=True, incremental=incremental, step=step)
664
+ if incremental and isinstance(self.PTDF._v, np.ndarray):
665
+ ptdf = sps.lil_matrix(self.PTDF._v)
666
+
667
+ if incremental | (isinstance(ptdf, sps.spmatrix)):
668
+ # initialize progress bar
669
+ if is_notebook():
670
+ self.pbar = tqdm_nb(total=100, unit='%', file=sys.stdout,
671
+ disable=no_tqdm)
672
+ else:
673
+ self.pbar = tqdm(total=100, unit='%', ncols=80, ascii=True,
674
+ file=sys.stdout, disable=no_tqdm)
675
+
676
+ self.pbar.update(0)
677
+ last_pc = 0
678
+
679
+ LODF = sps.lil_matrix((nbranch, nline))
680
+
681
+ # NOTE: for LODF, we are doing it columns by columns
682
+ # reshape luid to list of list by step
683
+ luidp = [luid[i:i + step] for i in range(0, len(luid), step)]
684
+ for luidi in luidp:
685
+ H_chunk = ptdf @ self.Cft._v[:, luidi]
686
+ h_chunk = H_chunk.diagonal(-luidi[0])
687
+ rden = safe_div(np.ones(H_chunk.shape),
688
+ np.tile(np.ones_like(h_chunk) - h_chunk, (nbranch, 1)))
689
+ H_chunk = H_chunk.multiply(rden).tolil()
690
+ # NOTE: use lil_matrix to set diagonal values as -1
691
+ rsid = sps.diags(H_chunk.diagonal(-luidi[0])) + sps.eye(H_chunk.shape[1])
692
+ if H_chunk.shape[0] > rsid.shape[0]:
693
+ Rsid = sps.lil_matrix(H_chunk.shape)
694
+ Rsid[luidi, :] = rsid
695
+ else:
696
+ Rsid = rsid
697
+ H_chunk = H_chunk - Rsid
698
+ LODF[:, [luid.index(i) for i in luidi]] = H_chunk
699
+
700
+ # show progress in percentage
701
+ perc = np.round(min((luid.index(luidi[-1]) / nline) * 100, 100), 2)
702
+
703
+ perc_diff = perc - last_pc
704
+ if perc_diff >= 1:
705
+ self.pbar.update(perc_diff)
706
+ last_pc = perc
707
+
708
+ # finish progress bar
709
+ self.pbar.update(100 - last_pc)
710
+ # removed `pbar` so that System object can be serialized
711
+ self.pbar.close()
712
+ self.pbar = None
713
+ else:
714
+ H = ptdf @ self.Cft._v[:, luid]
715
+ h = np.diag(H, -luid[0])
716
+ LODF = safe_div(H,
717
+ np.tile(np.ones_like(h) - h, (nbranch, 1)))
718
+ # # NOTE: reset the diagonal elements to -1
719
+ rsid = np.diag(np.diag(LODF, -luid[0])) + np.eye(nline, nline)
720
+ if LODF.shape[0] > rsid.shape[0]:
721
+ Rsid = np.zeros_like(LODF)
722
+ Rsid[luid, :] = rsid
723
+ else:
724
+ Rsid = rsid
725
+ LODF = LODF - Rsid
726
+
727
+ # reshape results into 1D array if only one line
728
+ if isinstance(line, (int, str)):
729
+ LODF = LODF[:, 0]
730
+
731
+ if (not no_store) & (line is None):
732
+ self.LODF._v = LODF
733
+ return LODF
734
+
735
+ def build_otdf(self, line=None):
736
+ """
737
+ Build the Outrage Transfer Distribution Factor (OTDF) matrix for line
738
+ k outage: :math:`OTDF_k = PTDF + LODF[:, k] @ PTDF[k, ]`.
739
+
740
+ OTDF_k[m, n] means the increased line flow on line `m` when there is
741
+ 1 p.u. power injection at bus `n` when line `k` is outage.
742
+
743
+ Note that the OTDF is not stored in the MatProcessor.
744
+
745
+ Parameters
746
+ ----------
747
+ line : int, str, list, optional
748
+ Lines index for which the OTDF is calculated. It takes both single
749
+ or multiple line indices.
750
+ If not given, the first line is used by default.
751
+
752
+ Returns
753
+ -------
754
+ OTDF : np.ndarray, scipy.sparse.csr_matrix
755
+ Line outage distribution factor.
756
+
757
+ References
758
+ ----------
759
+ [1] PowerWorld Documentation, Line Outage Distribution Factors, [Online]
760
+
761
+ Available:
762
+
763
+ https://www.powerworld.com/WebHelp/Content/MainDocumentation_HTML/Line_Outage_Distribution_Factors_LODFs.htm
764
+ """
765
+ if (self.PTDF._v is None) or (self.LODF._v is None):
766
+ raise ValueError("Internal PTDF and LODF are not available. Please build them first.")
767
+
768
+ ptdf = self.PTDF._v
769
+ lodf = self.LODF._v
770
+
771
+ if line is None:
772
+ luid = [0]
773
+ elif isinstance(line, (int, str)):
774
+ try:
775
+ luid = [self.system.Line.idx2uid(line)]
776
+ except ValueError:
777
+ raise ValueError(f"Line {line} not found.")
778
+ elif isinstance(line, list):
779
+ luid = self.system.Line.idx2uid(line)
780
+
781
+ otdf = ptdf + lodf[:, luid] @ ptdf[luid, :]
782
+ return otdf