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
ams/routines/acopf.py ADDED
@@ -0,0 +1,117 @@
1
+ """
2
+ ACOPF routines using PYPOWER.
3
+ """
4
+ import logging
5
+ from collections import OrderedDict
6
+
7
+ from ams.pypower import runopf
8
+ from ams.pypower.core import ppoption
9
+
10
+ from ams.io.pypower import system2ppc
11
+ from ams.core.param import RParam
12
+
13
+ from ams.routines.dcpf0 import DCPF0
14
+ from ams.opt import Var, Constraint, Objective
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ACOPF(DCPF0):
20
+ """
21
+ Standard AC optimal power flow.
22
+
23
+ Notes
24
+ -----
25
+ 1. ACOPF is solved with PYPOWER ``runopf`` function.
26
+ 2. ACOPF formulation in AMS style is NOT DONE YET,
27
+ but this does not affect the results
28
+ because the data are passed to PYPOWER for solving.
29
+ """
30
+
31
+ def __init__(self, system, config):
32
+ DCPF0.__init__(self, system, config)
33
+ self.info = 'AC Optimal Power Flow'
34
+ self.type = 'ACED'
35
+
36
+ self.map1 = OrderedDict() # ACOPF does not receive
37
+ self.map2.update({
38
+ 'vBus': ('Bus', 'v0'),
39
+ 'ug': ('StaticGen', 'u'),
40
+ 'pg': ('StaticGen', 'p0'),
41
+ })
42
+
43
+ # --- params ---
44
+ self.c2 = RParam(info='Gen cost coefficient 2',
45
+ name='c2', tex_name=r'c_{2}',
46
+ unit=r'$/(p.u.^2)', model='GCost',
47
+ indexer='gen', imodel='StaticGen',
48
+ nonneg=True)
49
+ self.c1 = RParam(info='Gen cost coefficient 1',
50
+ name='c1', tex_name=r'c_{1}',
51
+ unit=r'$/(p.u.)', model='GCost',
52
+ indexer='gen', imodel='StaticGen',)
53
+ self.c0 = RParam(info='Gen cost coefficient 0',
54
+ name='c0', tex_name=r'c_{0}',
55
+ unit=r'$', model='GCost',
56
+ indexer='gen', imodel='StaticGen',
57
+ no_parse=True)
58
+ self.qd = RParam(info='reactive demand',
59
+ name='qd', tex_name=r'q_{d}',
60
+ model='StaticLoad', src='q0',
61
+ unit='p.u.',)
62
+ # --- bus ---
63
+ self.vBus = Var(info='Bus voltage magnitude',
64
+ unit='p.u.',
65
+ name='vBus', tex_name=r'v_{Bus}',
66
+ src='v', model='Bus',)
67
+ # --- gen ---
68
+ self.qg = Var(info='Gen reactive power',
69
+ unit='p.u.',
70
+ name='qg', tex_name=r'q_{g}',
71
+ model='StaticGen', src='q',)
72
+ # --- constraints ---
73
+ self.pb = Constraint(name='pb',
74
+ info='power balance',
75
+ e_str='sum(pd) - sum(pg)',
76
+ is_eq=True,)
77
+ # TODO: ACOPF formulation
78
+ # --- objective ---
79
+ self.obj = Objective(name='obj',
80
+ info='total cost',
81
+ e_str='sum(c2 * pg**2 + c1 * pg + c0)',
82
+ sense='min',)
83
+
84
+ def solve(self, method=None, **kwargs):
85
+ """
86
+ Solve ACOPF using PYPOWER with PIPS.
87
+ """
88
+ ppc = system2ppc(self.system)
89
+ ppopt = ppoption()
90
+ res, sstats = runopf(casedata=ppc, ppopt=ppopt, **kwargs)
91
+ return res, sstats
92
+
93
+ def run(self, **kwargs):
94
+ """
95
+ Run ACOPF using PYPOWER with PIPS.
96
+ *args and **kwargs go to `self.solve()`, which are not used yet.
97
+
98
+ Examples
99
+ --------
100
+ >>> ss = ams.load(ams.get_case('matpower/case14.m'))
101
+ >>> ss.ACOPF.run()
102
+
103
+ Parameters
104
+ ----------
105
+ force_init : bool
106
+ Force initialization.
107
+ no_code : bool
108
+ Disable showing code.
109
+ method : str
110
+ Placeholder for future use.
111
+
112
+ Returns
113
+ -------
114
+ exit_code : int
115
+ Exit code of the routine.
116
+ """
117
+ super().run(**kwargs)
ams/routines/cpf.py ADDED
@@ -0,0 +1,65 @@
1
+ """
2
+ Continuous power flow routine.
3
+ """
4
+ import logging
5
+
6
+ from ams.pypower import runcpf
7
+
8
+ from ams.io.pypower import system2ppc
9
+ from ams.pypower.core import ppoption
10
+
11
+ from ams.routines.pflow import PFlow
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class CPF(PFlow):
17
+ """
18
+ Continuous power flow.
19
+
20
+ Still under development, not ready for use.
21
+ """
22
+
23
+ def __init__(self, system, config):
24
+ PFlow.__init__(self, system, config)
25
+ self.info = 'AC continuous power flow'
26
+ self.type = 'PF'
27
+
28
+ def solve(self, method=None, **kwargs):
29
+ """
30
+ Solve the CPF using PYPOWER.
31
+ """
32
+ ppc = system2ppc(self.system)
33
+ ppopt = ppoption()
34
+ res, success, sstats = runcpf(casedata=ppc, ppopt=ppopt, **kwargs)
35
+ return res, success, sstats
36
+
37
+ # FIXME: unpack results?
38
+
39
+ def run(self, force_init=False, no_code=True,
40
+ method='newton', **kwargs):
41
+ """
42
+ Run continuous power flow using PYPOWER.
43
+
44
+ Examples
45
+ --------
46
+ >>> ss = ams.load(ams.get_case('matpower/case14.m'))
47
+ >>> ss.CPF.run()
48
+
49
+ Parameters
50
+ ----------
51
+ force_init : bool
52
+ Force initialization.
53
+ no_code : bool
54
+ Disable showing code.
55
+ method : str
56
+ Method for solving the power flow.
57
+
58
+ Returns
59
+ -------
60
+ exit_code : int
61
+ Exit code of the routine.
62
+ """
63
+ super().run(force_init=force_init,
64
+ no_code=no_code, method=method,
65
+ **kwargs, )
ams/routines/dcopf.py ADDED
@@ -0,0 +1,241 @@
1
+ """
2
+ DCOPF routines.
3
+ """
4
+ import logging
5
+
6
+ import numpy as np
7
+ from ams.core.param import RParam
8
+ from ams.core.service import NumOp, NumOpDual
9
+
10
+ from ams.routines.dcpf import DCPFBase
11
+
12
+ from ams.opt import Constraint, Objective, ExpressionCalc, Expression
13
+
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class DCOPF(DCPFBase):
19
+ """
20
+ DC optimal power flow (DCOPF).
21
+
22
+ The nodal price is calculated as ``pi`` in ``pic``.
23
+
24
+ References
25
+ ----------
26
+ 1. R. D. Zimmerman, C. E. Murillo-Sanchez, and R. J. Thomas, “MATPOWER: Steady-State Operations, Planning, and
27
+ Analysis Tools for Power Systems Research and Education,” IEEE Trans. Power Syst., vol. 26, no. 1, pp. 12–19,
28
+ Feb. 2011
29
+ """
30
+
31
+ def __init__(self, system, config):
32
+ DCPFBase.__init__(self, system, config)
33
+ self.info = 'DC Optimal Power Flow'
34
+ self.type = 'DCED'
35
+
36
+ # --- Mapping Section ---
37
+ # --- from map ---
38
+ self.map1.update({
39
+ 'ug': ('StaticGen', 'u'),
40
+ })
41
+ # --- to map ---
42
+ self.map2.update({
43
+ 'vBus': ('Bus', 'v0'),
44
+ 'ug': ('StaticGen', 'u'),
45
+ 'pg': ('StaticGen', 'p0'),
46
+ })
47
+
48
+ # --- Data Section ---
49
+ # --- generator cost ---
50
+ self.c2 = RParam(info='Gen cost coefficient 2',
51
+ name='c2', tex_name=r'c_{2}',
52
+ unit=r'$/(p.u.^2)', model='GCost',
53
+ indexer='gen', imodel='StaticGen',
54
+ nonneg=True, no_parse=True)
55
+ self.c1 = RParam(info='Gen cost coefficient 1',
56
+ name='c1', tex_name=r'c_{1}',
57
+ unit=r'$/(p.u.)', model='GCost',
58
+ indexer='gen', imodel='StaticGen',)
59
+ self.c0 = RParam(info='Gen cost coefficient 0',
60
+ name='c0', tex_name=r'c_{0}',
61
+ unit=r'$', model='GCost',
62
+ indexer='gen', imodel='StaticGen',
63
+ no_parse=True)
64
+ # --- generator ---
65
+ self.ctrl = RParam(info='Gen controllability',
66
+ name='ctrl', tex_name=r'c_{trl}',
67
+ model='StaticGen', src='ctrl',
68
+ no_parse=True)
69
+ self.ctrle = NumOpDual(info='Effective Gen controllability',
70
+ name='ctrle', tex_name=r'c_{trl, e}',
71
+ u=self.ctrl, u2=self.ug,
72
+ fun=np.multiply, no_parse=True)
73
+ self.nctrl = NumOp(u=self.ctrl, fun=np.logical_not,
74
+ name='nctrl', tex_name=r'c_{trl,n}',
75
+ info='Effective Gen uncontrollability',
76
+ no_parse=True,)
77
+ self.nctrle = NumOpDual(info='Effective Gen uncontrollability',
78
+ name='nctrle', tex_name=r'c_{trl,n,e}',
79
+ u=self.nctrl, u2=self.ug,
80
+ fun=np.multiply, no_parse=True)
81
+ self.pmax = RParam(info='Gen maximum active power',
82
+ name='pmax', tex_name=r'p_{g, max}',
83
+ unit='p.u.', model='StaticGen',
84
+ no_parse=False,)
85
+ self.pmin = RParam(info='Gen minimum active power',
86
+ name='pmin', tex_name=r'p_{g, min}',
87
+ unit='p.u.', model='StaticGen',
88
+ no_parse=False,)
89
+
90
+ # --- line ---
91
+ self.ul = RParam(info='Line connection status',
92
+ name='ul', tex_name=r'u_{l}',
93
+ model='Line', src='u',
94
+ no_parse=True,)
95
+ self.rate_a = RParam(info='long-term flow limit',
96
+ name='rate_a', tex_name=r'R_{ATEA}',
97
+ unit='p.u.', model='Line',)
98
+ # --- line angle difference ---
99
+ self.amax = RParam(model='Line', src='amax',
100
+ name='amax', tex_name=r'\theta_{bus, max}',
101
+ info='max line angle difference',
102
+ no_parse=True,)
103
+ self.amin = RParam(model='Line', src='amin',
104
+ name='amin', tex_name=r'\theta_{bus, min}',
105
+ info='min line angle difference',
106
+ no_parse=True,)
107
+
108
+ # --- Model Section ---
109
+ self.pmaxe = Expression(info='Effective pmax',
110
+ name='pmaxe', tex_name=r'p_{g, max, e}',
111
+ e_str='mul(nctrle, pg0) + mul(ctrle, pmax)',
112
+ model='StaticGen', src=None, unit='p.u.',)
113
+ self.pmine = Expression(info='Effective pmin',
114
+ name='pmine', tex_name=r'p_{g, min, e}',
115
+ e_str='mul(nctrle, pg0) + mul(ctrle, pmin)',
116
+ model='StaticGen', src=None, unit='p.u.',)
117
+ self.pglb = Constraint(name='pglb', info='pg min',
118
+ e_str='-pg + pmine', is_eq=False,)
119
+ self.pgub = Constraint(name='pgub', info='pg max',
120
+ e_str='pg - pmaxe', is_eq=False,)
121
+
122
+ # --- line flow ---
123
+ self.plflb = Constraint(info='line flow lower bound',
124
+ name='plflb', is_eq=False,
125
+ e_str='-plf - mul(ul, rate_a)',)
126
+ self.plfub = Constraint(info='line flow upper bound',
127
+ name='plfub', is_eq=False,
128
+ e_str='plf - mul(ul, rate_a)',)
129
+ self.alflb = Constraint(info='line angle difference lower bound',
130
+ name='alflb', is_eq=False,
131
+ e_str='-CftT@aBus + amin',)
132
+ self.alfub = Constraint(info='line angle difference upper bound',
133
+ name='alfub', is_eq=False,
134
+ e_str='CftT@aBus - amax',)
135
+ # NOTE: in CVXPY, dual_variables returns a list
136
+ self.pi = ExpressionCalc(info='LMP, dual of <pb>',
137
+ name='pi', unit='$/p.u.',
138
+ model='Bus', src=None,
139
+ e_str='pb.dual_variables[0]')
140
+
141
+ # --- objective ---
142
+ obj = 'sum(mul(c2, pg**2))'
143
+ obj += '+ sum(mul(c1, pg))'
144
+ obj += '+ sum(mul(ug, c0))'
145
+ self.obj = Objective(name='obj',
146
+ info='total cost', unit='$',
147
+ sense='min', e_str=obj,)
148
+
149
+ def dc2ac(self, kloss=1.0, **kwargs):
150
+ """
151
+ Convert the RTED results with ACOPF.
152
+
153
+ Parameters
154
+ ----------
155
+ kloss : float, optional
156
+ The loss factor for the conversion. Defaults to 1.2.
157
+ """
158
+ exec_time = self.exec_time
159
+ if self.exec_time == 0 or self.exit_code != 0:
160
+ logger.warning(f'{self.class_name} is not executed successfully, quit conversion.')
161
+ return False
162
+
163
+ # --- ACOPF ---
164
+ # scale up load
165
+ pq_idx = self.system.StaticLoad.get_all_idxes()
166
+ pd0 = self.system.StaticLoad.get(src='p0', attr='v', idx=pq_idx).copy()
167
+ qd0 = self.system.StaticLoad.get(src='q0', attr='v', idx=pq_idx).copy()
168
+ self.system.StaticLoad.set(src='p0', idx=pq_idx, attr='v', value=pd0 * kloss)
169
+ self.system.StaticLoad.set(src='q0', idx=pq_idx, attr='v', value=qd0 * kloss)
170
+ # run ACOPF
171
+ ACOPF = self.system.ACOPF
172
+ ACOPF.run()
173
+ # scale load back
174
+ self.system.StaticLoad.set(src='p0', idx=pq_idx, attr='v', value=pd0)
175
+ self.system.StaticLoad.set(src='q0', idx=pq_idx, attr='v', value=qd0)
176
+ if not ACOPF.exit_code == 0:
177
+ logger.warning('<ACOPF> did not converge, conversion failed.')
178
+ # NOTE: mock results to fit interface with ANDES
179
+ self.vBus = ACOPF.vBus
180
+ self.vBus.optz.value = np.ones(self.system.Bus.n)
181
+ self.aBus.optz.value = np.zeros(self.system.Bus.n)
182
+ return False
183
+ self.pg.optz.value = ACOPF.pg.v
184
+
185
+ # NOTE: mock results to fit interface with ANDES
186
+ self.vBus.optz.value = ACOPF.vBus.v
187
+ self.aBus.optz.value = ACOPF.aBus.v
188
+ self.exec_time = exec_time
189
+
190
+ # --- set status ---
191
+ self.system.recent = self
192
+ self.converted = True
193
+ logger.warning(f'<{self.class_name}> converted to AC.')
194
+ return True
195
+
196
+ def run(self, **kwargs):
197
+ """
198
+ Run the routine.
199
+
200
+ Following kwargs go to `self.init()`: `force`, `force_mats`, `force_constr`, `force_om`.
201
+
202
+ Following kwargs go to `self.solve()`: `solver`, `verbose`, `gp`, `qcp`, `requires_grad`,
203
+ `enforce_dpp`, `ignore_dpp`, `method`, and all rest.
204
+
205
+ Parameters
206
+ ----------
207
+ force : bool, optional
208
+ If True, force re-initialization. Defaults to False.
209
+ force_mats : bool, optional
210
+ If True, force re-generating matrices. Defaults to False.
211
+ force_constr : bool, optional
212
+ Whether to turn on all constraints.
213
+ force_om : bool, optional
214
+ If True, force re-generating optimization model. Defaults to False.
215
+ solver: str, optional
216
+ The solver to use. For example, 'GUROBI', 'ECOS', 'SCS', or 'OSQP'.
217
+ verbose : bool, optional
218
+ Overrides the default of hiding solver output and prints logging
219
+ information describing CVXPY's compilation process.
220
+ gp : bool, optional
221
+ If True, parses the problem as a disciplined geometric program
222
+ instead of a disciplined convex program.
223
+ qcp : bool, optional
224
+ If True, parses the problem as a disciplined quasiconvex program
225
+ instead of a disciplined convex program.
226
+ requires_grad : bool, optional
227
+ Makes it possible to compute gradients of a solution with respect to Parameters
228
+ by calling problem.backward() after solving, or to compute perturbations to the variables
229
+ given perturbations to Parameters by calling problem.derivative().
230
+ Gradients are only supported for DCP and DGP problems, not quasiconvex problems.
231
+ When computing gradients (i.e., when this argument is True), the problem must satisfy the DPP rules.
232
+ enforce_dpp : bool, optional
233
+ When True, a DPPError will be thrown when trying to solve a
234
+ non-DPP problem (instead of just a warning).
235
+ Only relevant for problems involving Parameters. Defaults to False.
236
+ ignore_dpp : bool, optional
237
+ When True, DPP problems will be treated as non-DPP, which may speed up compilation. Defaults to False.
238
+ method : function, optional
239
+ A custom solve method to use.
240
+ """
241
+ return super().run(**kwargs)
ams/routines/dcpf.py ADDED
@@ -0,0 +1,209 @@
1
+ """
2
+ Power flow routines.
3
+ """
4
+ import logging
5
+
6
+ from ams.opt import Var, Constraint, Expression, Objective
7
+ from ams.routines.routine import RoutineBase
8
+
9
+ from ams.core.param import RParam
10
+ from ams.core.service import VarSelect
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class DCPFBase(RoutineBase):
16
+ """
17
+ Base class for DC power flow.
18
+ """
19
+
20
+ def __init__(self, system, config):
21
+ RoutineBase.__init__(self, system, config)
22
+
23
+ self.ug = RParam(info='Gen connection status',
24
+ name='ug', tex_name=r'u_{g}',
25
+ model='StaticGen', src='u',
26
+ no_parse=True)
27
+ self.pg0 = RParam(info='Gen initial active power',
28
+ name='pg0', tex_name=r'p_{g, 0}',
29
+ unit='p.u.', model='StaticGen',
30
+ src='p0', no_parse=False,)
31
+ # --- shunt ---
32
+ self.gsh = RParam(info='shunt conductance',
33
+ name='gsh', tex_name=r'g_{sh}',
34
+ model='Shunt', src='g',
35
+ no_parse=True,)
36
+
37
+ self.buss = RParam(info='Bus slack',
38
+ name='buss', tex_name=r'B_{us,s}',
39
+ model='Slack', src='bus',
40
+ no_parse=True,)
41
+ # --- load ---
42
+ self.pd = RParam(info='active demand',
43
+ name='pd', tex_name=r'p_{d}',
44
+ model='StaticLoad', src='p0',
45
+ unit='p.u.',)
46
+
47
+ # --- connection matrix ---
48
+ self.Cg = RParam(info='Gen connection matrix',
49
+ name='Cg', tex_name=r'C_{g}',
50
+ model='mats', src='Cg',
51
+ no_parse=True, sparse=True,)
52
+ self.Cl = RParam(info='Load connection matrix',
53
+ name='Cl', tex_name=r'C_{l}',
54
+ model='mats', src='Cl',
55
+ no_parse=True, sparse=True,)
56
+ self.CftT = RParam(info='Transpose of line connection matrix',
57
+ name='CftT', tex_name=r'C_{ft}^T',
58
+ model='mats', src='CftT',
59
+ no_parse=True, sparse=True,)
60
+ self.Csh = RParam(info='Shunt connection matrix',
61
+ name='Csh', tex_name=r'C_{sh}',
62
+ model='mats', src='Csh',
63
+ no_parse=True, sparse=True,)
64
+
65
+ # --- system matrix ---
66
+ self.Bbus = RParam(info='Bus admittance matrix',
67
+ name='Bbus', tex_name=r'B_{bus}',
68
+ model='mats', src='Bbus',
69
+ no_parse=True, sparse=True,)
70
+ self.Bf = RParam(info='Bf matrix',
71
+ name='Bf', tex_name=r'B_{f}',
72
+ model='mats', src='Bf',
73
+ no_parse=True, sparse=True,)
74
+ self.Pbusinj = RParam(info='Bus power injection vector',
75
+ name='Pbusinj', tex_name=r'P_{bus}^{inj}',
76
+ model='mats', src='Pbusinj',
77
+ no_parse=True,)
78
+ self.Pfinj = RParam(info='Line power injection vector',
79
+ name='Pfinj', tex_name=r'P_{f}^{inj}',
80
+ model='mats', src='Pfinj',
81
+ no_parse=True,)
82
+
83
+ # --- generation ---
84
+ self.pg = Var(info='Gen active power',
85
+ unit='p.u.',
86
+ name='pg', tex_name=r'p_g',
87
+ model='StaticGen', src='p',
88
+ v0=self.pg0)
89
+
90
+ # --- bus ---
91
+ self.vBus = Var(info='Bus voltage magnitude, placeholder',
92
+ unit='p.u.',
93
+ name='vBus', tex_name=r'v_{Bus}',
94
+ src='v', model='Bus',)
95
+ self.aBus = Var(info='Bus voltage angle',
96
+ unit='rad',
97
+ name='aBus', tex_name=r'\theta_{bus}',
98
+ model='Bus', src='a',)
99
+
100
+ # --- power balance ---
101
+ pb = 'Bbus@aBus + Pbusinj + Cl@pd + Csh@gsh - Cg@pg'
102
+ self.pb = Constraint(name='pb', info='power balance',
103
+ e_str=pb, is_eq=True,)
104
+
105
+ # --- bus ---
106
+ self.csb = VarSelect(info='select slack bus',
107
+ name='csb', tex_name=r'c_{sb}',
108
+ u=self.aBus, indexer='buss',
109
+ no_parse=True,)
110
+ self.sba = Constraint(info='align slack bus angle',
111
+ name='sbus', is_eq=True,
112
+ e_str='csb@aBus',)
113
+
114
+ # --- line flow ---
115
+ self.plf = Expression(info='Line flow',
116
+ name='plf', tex_name=r'p_{lf}',
117
+ unit='p.u.',
118
+ e_str='Bf@aBus + Pfinj',
119
+ model='Line', src=None,)
120
+
121
+ def solve(self, **kwargs):
122
+ """
123
+ Solve the routine optimization model.
124
+ args and kwargs go to `self.om.prob.solve()` (`cvxpy.Problem.solve()`).
125
+ """
126
+ return self.om.prob.solve(**kwargs)
127
+
128
+ def unpack(self, **kwargs):
129
+ """
130
+ Unpack the results from CVXPY model.
131
+ """
132
+ # --- solver Var results to routine algeb ---
133
+ for _, var in self.vars.items():
134
+ # --- copy results from routine algeb into system algeb ---
135
+ if var.model is None: # if no owner
136
+ continue
137
+ if var.src is None: # if no source
138
+ continue
139
+ else:
140
+ try:
141
+ idx = var.owner.get_all_idxes()
142
+ except AttributeError:
143
+ idx = var.owner.idx.v
144
+
145
+ # NOTE: only unpack the variables that are in the model or group
146
+ try:
147
+ var.owner.set(src=var.src, idx=idx, attr='v', value=var.v)
148
+ except (KeyError, TypeError):
149
+ logger.error(f'Failed to unpack <{var}> to <{var.owner.class_name}>.')
150
+
151
+ # --- solver ExpressionCalc results to routine algeb ---
152
+ for _, exprc in self.exprcs.items():
153
+ if exprc.model is None:
154
+ continue
155
+ if exprc.src is None:
156
+ continue
157
+ else:
158
+ try:
159
+ idx = exprc.owner.get_all_idxes()
160
+ except AttributeError:
161
+ idx = exprc.owner.idx.v
162
+
163
+ try:
164
+ exprc.owner.set(src=exprc.src, idx=idx, attr='v', value=exprc.v)
165
+ except (KeyError, TypeError):
166
+ logger.error(f'Failed to unpack <{exprc}> to <{exprc.owner.class_name}>.')
167
+
168
+ # label the most recent solved routine
169
+ self.system.recent = self.system.routines[self.class_name]
170
+ return True
171
+
172
+ def _post_solve(self):
173
+ """
174
+ Post-solve calculations.
175
+ """
176
+ # NOTE: unpack Expressions if owner and arc are available
177
+ for expr in self.exprs.values():
178
+ if expr.owner and expr.src:
179
+ expr.owner.set(src=expr.src, attr='v',
180
+ idx=expr.get_all_idxes(), value=expr.v)
181
+ return True
182
+
183
+
184
+ class DCPF(DCPFBase):
185
+ """
186
+ DC power flow.
187
+ """
188
+
189
+ def __init__(self, system, config):
190
+ DCPFBase.__init__(self, system, config)
191
+ self.info = 'DC Power Flow'
192
+ self.type = 'PF'
193
+
194
+ self.genpv = RParam(info='gen of PV',
195
+ name='genpv', tex_name=r'g_{DG}',
196
+ model='PV', src='idx',
197
+ no_parse=True,)
198
+ self.cpv = VarSelect(u=self.pg, indexer='genpv',
199
+ name='cpv', tex_name=r'C_{PV}',
200
+ info='Select PV from pg',
201
+ no_parse=True,)
202
+
203
+ self.pvb = Constraint(name='pvb', info='PV generator',
204
+ e_str='cpv @ (pg - mul(ug, pg0))',
205
+ is_eq=True,)
206
+
207
+ self.obj = Objective(name='obj',
208
+ info='place holder', unit='$',
209
+ sense='min', e_str='0',)