ltbams 1.0.9__py3-none-any.whl → 1.0.10__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 (79) hide show
  1. ams/__init__.py +0 -1
  2. ams/_version.py +3 -3
  3. ams/cases/5bus/pjm5bus_demo.json +1324 -0
  4. ams/core/__init__.py +1 -0
  5. ams/core/common.py +30 -0
  6. ams/core/model.py +1 -1
  7. ams/core/symprocessor.py +1 -1
  8. ams/extension/eva.py +1 -1
  9. ams/interface.py +40 -24
  10. ams/io/matpower.py +31 -17
  11. ams/io/psse.py +278 -1
  12. ams/main.py +2 -2
  13. ams/models/group.py +2 -1
  14. ams/models/static/pq.py +7 -3
  15. ams/opt/param.py +1 -2
  16. ams/routines/__init__.py +2 -3
  17. ams/routines/acopf.py +5 -108
  18. ams/routines/dcopf.py +8 -0
  19. ams/routines/dcpf.py +1 -1
  20. ams/routines/ed.py +4 -2
  21. ams/routines/grbopt.py +150 -0
  22. ams/routines/pflow.py +2 -2
  23. ams/routines/pypower.py +631 -0
  24. ams/routines/routine.py +4 -10
  25. ams/routines/uc.py +2 -2
  26. ams/shared.py +26 -43
  27. ams/system.py +118 -2
  28. docs/source/api.rst +2 -0
  29. docs/source/getting_started/install.rst +9 -6
  30. docs/source/images/dcopf_time.png +0 -0
  31. docs/source/images/educ_pie.png +0 -0
  32. docs/source/release-notes.rst +21 -47
  33. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/METADATA +87 -47
  34. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/RECORD +54 -71
  35. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/WHEEL +1 -1
  36. tests/test_1st_system.py +1 -1
  37. tests/test_case.py +14 -14
  38. tests/test_export_csv.py +1 -1
  39. tests/test_interface.py +24 -2
  40. tests/test_io.py +50 -0
  41. tests/test_omodel.py +1 -1
  42. tests/test_report.py +6 -6
  43. tests/test_routine.py +2 -2
  44. tests/test_rtn_acopf.py +75 -0
  45. tests/test_rtn_dcopf.py +1 -1
  46. tests/test_rtn_dcopf2.py +1 -1
  47. tests/test_rtn_ed.py +9 -9
  48. tests/test_rtn_opf.py +142 -0
  49. tests/test_rtn_pflow.py +0 -72
  50. tests/test_rtn_pypower.py +315 -0
  51. tests/test_rtn_rted.py +8 -8
  52. tests/test_rtn_uc.py +18 -18
  53. ams/pypower/__init__.py +0 -8
  54. ams/pypower/_compat.py +0 -9
  55. ams/pypower/core/__init__.py +0 -8
  56. ams/pypower/core/pips.py +0 -894
  57. ams/pypower/core/ppoption.py +0 -244
  58. ams/pypower/core/ppver.py +0 -18
  59. ams/pypower/core/solver.py +0 -2451
  60. ams/pypower/eps.py +0 -6
  61. ams/pypower/idx.py +0 -174
  62. ams/pypower/io.py +0 -604
  63. ams/pypower/make/__init__.py +0 -11
  64. ams/pypower/make/matrices.py +0 -665
  65. ams/pypower/make/pdv.py +0 -506
  66. ams/pypower/routines/__init__.py +0 -7
  67. ams/pypower/routines/cpf.py +0 -513
  68. ams/pypower/routines/cpf_callbacks.py +0 -114
  69. ams/pypower/routines/opf.py +0 -1803
  70. ams/pypower/routines/opffcns.py +0 -1946
  71. ams/pypower/routines/pflow.py +0 -852
  72. ams/pypower/toggle.py +0 -1098
  73. ams/pypower/utils.py +0 -293
  74. ams/routines/cpf.py +0 -65
  75. ams/routines/dcpf0.py +0 -196
  76. ams/routines/pflow0.py +0 -113
  77. tests/test_rtn_dcpf.py +0 -77
  78. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/entry_points.txt +0 -0
  79. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/top_level.txt +0 -0
ams/pypower/utils.py DELETED
@@ -1,293 +0,0 @@
1
- """
2
- PYPOWER utility functions.
3
- """
4
- import logging # NOQA
5
- from copy import deepcopy # NOQA
6
-
7
- import numpy as np # NOQA
8
- from numpy import flatnonzero as find # NOQA
9
- import scipy.sparse as sp # NOQA
10
- from scipy.sparse import csr_matrix as c_sparse # NOQA
11
-
12
- from ams.pypower.idx import IDX # NOQA
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- def bustypes(bus, gen):
18
- """
19
- Builds index lists of each type of bus (REF, PV, PQ).
20
-
21
- Generators with "out-of-service" status are treated as PQ buses with
22
- zero generation (regardless of Pg/Qg values in gen). Expects bus
23
- and gen have been converted to use internal consecutive bus numbering.
24
-
25
- Parameters
26
- ----------
27
- bus : ndarray
28
- Bus data.
29
- gen : ndarray
30
- Generator data.
31
-
32
- Returns
33
- -------
34
- ref : ndarray
35
- Index list of reference (REF) buses.
36
- pv : ndarray
37
- Index list of PV buses.
38
- pq : ndarray
39
- Index list of PQ buses.
40
-
41
- Author
42
- ------
43
- Ray Zimmerman (PSERC Cornell)
44
- """
45
- # get generator status
46
- nb = bus.shape[0]
47
- ng = gen.shape[0]
48
- # gen connection matrix, element i, j is 1 if, generator j at bus i is ON
49
- Cg = c_sparse((gen[:, IDX.gen.GEN_STATUS] > 0,
50
- (gen[:, IDX.gen.GEN_BUS], range(ng))), (nb, ng))
51
- # number of generators at each bus that are ON
52
- bus_gen_status = (Cg * np.ones(ng, int)).astype(bool)
53
-
54
- # form index lists for slack, PV, and PQ buses
55
- ref = find((bus[:, IDX.bus.BUS_TYPE] == IDX.bus.REF) & bus_gen_status) # ref bus index
56
- pv = find((bus[:, IDX.bus.BUS_TYPE] == IDX.bus.PV) & bus_gen_status) # PV bus indices
57
- pq = find((bus[:, IDX.bus.BUS_TYPE] == IDX.bus.PQ) | ~bus_gen_status) # PQ bus indices
58
-
59
- # pick a new reference bus if for some reason there is none (may have been
60
- # shut down)
61
- if (len(ref) == 0) & (len(pv) > 0):
62
- ref = np.zeros(1, dtype=int)
63
- ref[0] = pv[0] # use the first PV bus
64
- pv = pv[1:] # take it off PV list
65
- return ref, pv, pq
66
-
67
-
68
- def sub2ind(shape, I, J, row_major=False):
69
- """
70
- Returns the linear indices of subscripts.
71
-
72
- Parameters
73
- ----------
74
- shape : tuple
75
- Shape of the grid or matrix.
76
- I : int
77
- Row subscript.
78
- J : int
79
- Column subscript.
80
- row_major : bool, optional
81
- If True, uses row-major order (default is False, using column-major order).
82
-
83
- Returns
84
- -------
85
- ind : int
86
- Linear index corresponding to the subscripts (I, J).
87
- """
88
- if row_major:
89
- ind = (I % shape[0]) * shape[1] + (J % shape[1])
90
- else:
91
- ind = (J % shape[1]) * shape[0] + (I % shape[0])
92
-
93
- return ind.astype(int)
94
-
95
-
96
- def feval(func, *args, **kw_args):
97
- """
98
- Evaluates the function func using positional arguments args
99
- and keyword arguments kw_args.
100
-
101
- Parameters
102
- ----------
103
- func : str
104
- Name of the function to evaluate.
105
- *args : list
106
- Positional arguments for the function.
107
- **kw_args : dict
108
- Keyword arguments for the function.
109
-
110
- Returns
111
- -------
112
- result : any
113
- Result of evaluating the function.
114
- """
115
- return eval(func)(*args, **kw_args)
116
-
117
-
118
- def have_fcn(name):
119
- """
120
- Checks if a Python module with the given name exists.
121
-
122
- Parameters
123
- ----------
124
- name : str
125
- Name of the Python module.
126
-
127
- Returns
128
- -------
129
- bool
130
- True if the module exists, False otherwise.
131
- """
132
- try:
133
- __import__(name)
134
- return True
135
- except ImportError:
136
- return False
137
-
138
-
139
- def get_reorder(A, idx, dim=0):
140
- """
141
- Returns A with one of its dimensions indexed::
142
-
143
- B = get_reorder(A, idx, dim)
144
-
145
- Returns A[:, ..., :, idx, :, ..., :], where dim determines
146
- in which dimension to place the idx.
147
-
148
- @author: Ray Zimmerman (PSERC Cornell)
149
- """
150
- ndims = np.ndim(A)
151
- if ndims == 1:
152
- B = A[idx].copy()
153
- elif ndims == 2:
154
- if dim == 0:
155
- B = A[idx, :].copy()
156
- elif dim == 1:
157
- B = A[:, idx].copy()
158
- else:
159
- raise ValueError('dim (%d) may be 0 or 1' % dim)
160
- else:
161
- raise ValueError('number of dimensions (%d) may be 1 or 2' % dim)
162
-
163
- return B
164
-
165
-
166
- def set_reorder(A, B, idx, dim=0):
167
- """
168
- Assigns B to A with one of the dimensions of A indexed.
169
-
170
- @return: A after doing A(:, ..., :, IDX, :, ..., :) = B
171
- where DIM determines in which dimension to place the IDX.
172
-
173
- @see: L{get_reorder}
174
-
175
- @author: Ray Zimmerman (PSERC Cornell)
176
- """
177
- A = A.copy()
178
- ndims = np.ndim(A)
179
- A = A.astype(B.dtype)
180
- if ndims == 1:
181
- A[idx] = B
182
- elif ndims == 2:
183
- if dim == 0:
184
- A[idx, :] = B
185
- elif dim == 1:
186
- A[:, idx] = B
187
- else:
188
- raise ValueError('dim (%d) may be 0 or 1' % dim)
189
- else:
190
- raise ValueError('number of dimensions (%d) may be 1 or 2' % dim)
191
-
192
- return A
193
-
194
-
195
- def isload(gen):
196
- """
197
- Checks for dispatchable loads.
198
-
199
- Parameters
200
- ----------
201
- gen: np.ndarray
202
- The generator matrix.
203
-
204
- Returns
205
- -------
206
- array
207
- A column vector of 1's and 0's. The 1's correspond to rows of the
208
- C{gen} matrix which represent dispatchable loads. The current test is
209
- C{Pmin < 0 and Pmax == 0}. This may need to be revised to allow sensible
210
- specification of both elastic demand and pumped storage units.
211
- """
212
- return (gen[:, IDX.gen.PMIN] < 0) & (gen[:, IDX.gen.PMAX] == 0)
213
-
214
-
215
- def hasPQcap(gen, hilo='B'):
216
- """
217
- Checks for P-Q capability curve constraints.
218
-
219
- Parameters
220
- ----------
221
- gen: np.ndarray
222
- The generator matrix.
223
- hilo : str, optional
224
- If 'U' this function returns C{True} only for rows corresponding to
225
- generators that require the upper constraint on Q.
226
- If 'L', only for those requiring the lower constraint.
227
- If not specified or has any other value it returns true for rows
228
- corresponding to gens that require either or both of the constraints.
229
-
230
- Returns
231
- -------
232
- array
233
- A column vector of 1's and 0's. The 1's correspond to rows of the
234
- C{gen} matrix which correspond to generators which have defined a
235
- capability curve (with sloped upper and/or lower bound on Q) and require
236
- that additional linear constraints be added to the OPF.
237
-
238
- Notes
239
- -----
240
- The C{gen} matrix in version 2 of the PYPOWER case format includes columns
241
- for specifying a P-Q capability curve for a generator defined as the
242
- intersection of two half-planes and the box constraints on P and Q.
243
- The two half planes are defined respectively as the area below the line
244
- connecting (Pc1, Qc1max) and (Pc2, Qc2max) and the area above the line
245
- connecting (Pc1, Qc1min) and (Pc2, Qc2min).
246
-
247
- It is smart enough to return C{True} only if the corresponding linear
248
- constraint is not redundant w.r.t the box constraints.
249
- """
250
- # check for errors capability curve data
251
- if np.any(gen[:, IDX.gen.PC1] > gen[:, IDX.gen.PC2]):
252
- logger.debug('hasPQcap: Pc1 > Pc2')
253
- if np.any(gen[:, IDX.gen.QC2MAX] > gen[:, IDX.gen.QC1MAX]):
254
- logger.debug('hasPQcap: Qc2max > Qc1max')
255
- if np.any(gen[:, IDX.gen.QC2MIN] < gen[:, IDX.gen.QC1MIN]):
256
- logger.debug('hasPQcap: Qc2min < Qc1min')
257
-
258
- L = np.zeros(gen.shape[0], bool)
259
- U = np.zeros(gen.shape[0], bool)
260
- k = np.nonzero(gen[:, IDX.gen.PC1] != gen[:, IDX.gen.PC2])
261
-
262
- if hilo != 'U': # include lower constraint
263
- Qmin_at_Pmax = gen[k, IDX.gen.QC1MIN] + (gen[k, IDX.gen.PMAX] - gen[k, IDX.gen.PC1]) * (
264
- gen[k, IDX.gen.QC2MIN] - gen[k, IDX.gen.QC1MIN]) / (gen[k, IDX.gen.PC2] - gen[k, IDX.gen.PC1])
265
- L[k] = Qmin_at_Pmax > gen[k, IDX.gen.QMIN]
266
-
267
- if hilo != 'L': # include upper constraint
268
- Qmax_at_Pmax = gen[k, IDX.gen.QC1MAX] + (gen[k, IDX.gen.PMAX] - gen[k, IDX.gen.PC1]) * (
269
- gen[k, IDX.gen.QC2MAX] - gen[k, IDX.gen.QC1MAX]) / (gen[k, IDX.gen.PC2] - gen[k, IDX.gen.PC1])
270
- U[k] = Qmax_at_Pmax < gen[k, IDX.gen.QMAX]
271
-
272
- return L | U
273
-
274
-
275
- def fairmax(x):
276
- """
277
- Same as built-in C{max}, except breaks ties randomly.
278
-
279
- Takes a vector as an argument and returns the same output as the
280
- built-in function C{max} with two output parameters, except that
281
- where the maximum value occurs at more than one position in the
282
- vector, the index is chosen randomly from these positions as opposed
283
- to just choosing the first occurance.
284
-
285
- @see: C{max}
286
-
287
- @author: Ray Zimmerman (PSERC Cornell)
288
- """
289
- val = max(x) # find max value
290
- i = np.nonzero(x == val) # find all positions where this occurs
291
- n = len(i) # number of occurences
292
- idx = i(np.fix(n * np.random()) + 1) # select index randomly among occurances
293
- return val, idx
ams/routines/cpf.py DELETED
@@ -1,65 +0,0 @@
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/dcpf0.py DELETED
@@ -1,196 +0,0 @@
1
- """
2
- DC power flow routines using PYPOWER.
3
- """
4
- import logging
5
-
6
- from andes.shared import deg2rad
7
- from andes.utils.misc import elapsed
8
-
9
- from ams.routines.routine import RoutineBase
10
- from ams.opt import Var
11
- from ams.pypower import runpf
12
- from ams.pypower.core import ppoption
13
-
14
- from ams.io.pypower import system2ppc
15
- from ams.core.param import RParam
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- class DCPF0(RoutineBase):
21
- """
22
- DC power flow using PYPOWER.
23
-
24
- This class is deprecated as of version 0.9.12 and will be removed in 1.1.0.
25
-
26
- Notes
27
- -----
28
- 1. DCPF is solved with PYPOWER ``runpf`` function.
29
- 2. DCPF formulation is not complete yet, but this does not affect the
30
- results because the data are passed to PYPOWER for solving.
31
- """
32
-
33
- def __init__(self, system, config):
34
- RoutineBase.__init__(self, system, config)
35
- self.info = 'DC Power Flow'
36
- self.type = 'PF'
37
-
38
- self.ug = RParam(info='Gen connection status',
39
- name='ug', tex_name=r'u_{g}',
40
- model='StaticGen', src='u',
41
- no_parse=True)
42
-
43
- # --- routine data ---
44
- self.x = RParam(info="line reactance",
45
- name='x', tex_name='x',
46
- unit='p.u.',
47
- model='Line', src='x',)
48
- self.tap = RParam(info="transformer branch tap ratio",
49
- name='tap', tex_name=r't_{ap}',
50
- model='Line', src='tap',
51
- unit='float',)
52
- self.phi = RParam(info="transformer branch phase shift in rad",
53
- name='phi', tex_name=r'\phi',
54
- model='Line', src='phi',
55
- unit='radian',)
56
-
57
- # --- load ---
58
- self.pd = RParam(info='active deman',
59
- name='pd', tex_name=r'p_{d}',
60
- unit='p.u.',
61
- model='StaticLoad', src='p0')
62
- # --- gen ---
63
- self.pg = Var(info='Gen active power',
64
- unit='p.u.',
65
- name='pg', tex_name=r'p_{g}',
66
- model='StaticGen', src='p',)
67
-
68
- # --- bus ---
69
- self.aBus = Var(info='bus voltage angle',
70
- unit='rad',
71
- name='aBus', tex_name=r'a_{Bus}',
72
- model='Bus', src='a',)
73
-
74
- # --- line flow ---
75
- self.plf = Var(info='Line flow',
76
- unit='p.u.',
77
- name='plf', tex_name=r'p_{lf}',
78
- model='Line',)
79
-
80
- def unpack(self, res):
81
- """
82
- Unpack results from PYPOWER.
83
- """
84
- system = self.system
85
- mva = res['baseMVA']
86
-
87
- # --- copy results from ppc into system algeb ---
88
- # --- Bus ---
89
- system.Bus.v.v = res['bus'][:, 7] # voltage magnitude
90
- system.Bus.a.v = res['bus'][:, 8] * deg2rad # voltage angle
91
-
92
- # --- PV ---
93
- system.PV.p.v = res['gen'][system.Slack.n:, 1] / mva # active power
94
- system.PV.q.v = res['gen'][system.Slack.n:, 2] / mva # reactive power
95
-
96
- # --- Slack ---
97
- system.Slack.p.v = res['gen'][:system.Slack.n, 1] / mva # active power
98
- system.Slack.q.v = res['gen'][:system.Slack.n, 2] / mva # reactive power
99
-
100
- # --- Line ---
101
- self.plf.optz.value = res['branch'][:, 13] / mva # line flow
102
-
103
- # --- copy results from system algeb into routine algeb ---
104
- for vname, var in self.vars.items():
105
- owner = getattr(system, var.model) # instance of owner, Model or Group
106
- if var.src is None: # skip if no source variable is specified
107
- continue
108
- elif hasattr(owner, 'group'): # if owner is a Model instance
109
- grp = getattr(system, owner.group)
110
- idx = grp.get_all_idxes()
111
- elif hasattr(owner, 'get_idx'): # if owner is a Group instance
112
- idx = owner.get_all_idxes()
113
- else:
114
- msg = f"Failed to find valid source variable `{owner.class_name}.{var.src}` for "
115
- msg += f"{self.class_name}.{vname}, skip unpacking."
116
- logger.warning(msg)
117
- continue
118
- try:
119
- logger.debug(f"Unpacking {vname} into {owner.class_name}.{var.src}.")
120
- var.optz.value = owner.get(src=var.src, attr='v', idx=idx)
121
- except AttributeError:
122
- logger.debug(f"Failed to unpack {vname} into {owner.class_name}.{var.src}.")
123
- continue
124
- self.system.recent = self.system.routines[self.class_name]
125
- return True
126
-
127
- def solve(self, method=None):
128
- """
129
- Solve DC power flow using PYPOWER.
130
- """
131
- ppc = system2ppc(self.system)
132
- ppopt = ppoption(PF_DC=True)
133
-
134
- res, sstats = runpf(casedata=ppc, ppopt=ppopt)
135
- return res, sstats
136
-
137
- def run(self, **kwargs):
138
- """
139
- Run DC pwoer flow.
140
- *args and **kwargs go to `self.solve()`, which are not used yet.
141
-
142
- Examples
143
- --------
144
- >>> ss = ams.load(ams.get_case('matpower/case14.m'))
145
- >>> ss.DCPF.run()
146
-
147
- Parameters
148
- ----------
149
- method : str
150
- Placeholder for future use.
151
-
152
- Returns
153
- -------
154
- exit_code : int
155
- Exit code of the routine.
156
- """
157
- if not self.initialized:
158
- self.init()
159
- t0, _ = elapsed()
160
-
161
- res, sstats = self.solve(**kwargs)
162
- self.converged = res['success']
163
- self.exit_code = 0 if res['success'] else 1
164
- _, s = elapsed(t0)
165
- self.exec_time = float(s.split(' ')[0])
166
- n_iter = int(sstats['num_iters'])
167
- n_iter_str = f"{n_iter} iterations " if n_iter > 1 else f"{n_iter} iteration "
168
- if self.exit_code == 0:
169
- msg = f"<{self.class_name}> solved in {s}, converged in "
170
- msg += n_iter_str + f"with {sstats['solver_name']}."
171
- logger.warning(msg)
172
- try:
173
- self.unpack(res)
174
- except Exception as e:
175
- logger.error(f"Failed to unpack results from {self.class_name}.\n{e}")
176
- return False
177
- self.system.report()
178
- return True
179
- else:
180
- msg = f"{self.class_name} failed in "
181
- msg += f"{int(sstats['num_iters'])} iterations with "
182
- msg += f"{sstats['solver_name']}!"
183
- logger.warning(msg)
184
- return False
185
-
186
- def summary(self, **kwargs):
187
- """
188
- # TODO: Print power flow summary.
189
- """
190
- raise NotImplementedError
191
-
192
- def enable(self, name):
193
- raise NotImplementedError
194
-
195
- def disable(self, name):
196
- raise NotImplementedError
ams/routines/pflow0.py DELETED
@@ -1,113 +0,0 @@
1
- """
2
- Power flow routines using PYPOWER.
3
- """
4
- import logging
5
- from collections import OrderedDict
6
-
7
- from ams.pypower import runpf
8
-
9
- from ams.io.pypower import system2ppc
10
- from ams.pypower.core import ppoption
11
- from ams.core.param import RParam
12
-
13
- from ams.routines.dcpf0 import DCPF0
14
- from ams.opt import Var
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- class PFlow0(DCPF0):
20
- """
21
- AC Power Flow using PYPOWER.
22
-
23
- This class is deprecated as of version 0.9.12 and will be removed in 1.1.0.
24
-
25
- Notes
26
- -----
27
- 1. AC pwoer flow is solved with PYPOWER ``runpf`` function.
28
- 2. AC power flow formulation in AMS style is NOT DONE YET,
29
- but this does not affect the results
30
- because the data are passed to PYPOWER for solving.
31
- """
32
-
33
- def __init__(self, system, config):
34
- DCPF0.__init__(self, system, config)
35
- self.info = "AC Power Flow"
36
- self.type = "PF"
37
-
38
- self.config.add(OrderedDict((('qlim', 0),
39
- )))
40
- self.config.add_extra("_help",
41
- qlim="Enforce generator q limits",
42
- )
43
- self.config.add_extra("_alt",
44
- qlim=(0, 1, 2),
45
- )
46
-
47
- self.qd = RParam(info="reactive power load in system base",
48
- name="qd", tex_name=r"q_{d}",
49
- unit="p.u.",
50
- model="StaticLoad", src="q0",)
51
-
52
- # --- bus ---
53
- self.vBus = Var(info="bus voltage magnitude",
54
- unit="p.u.",
55
- name="vBus", tex_name=r"v_{Bus}",
56
- model="Bus", src="v",)
57
- # --- gen ---
58
- self.qg = Var(info="reactive power generation",
59
- unit="p.u.",
60
- name="qg", tex_name=r"q_{g}",
61
- model="StaticGen", src="q",)
62
- # NOTE: omit AC power flow formulation here
63
-
64
- def solve(self, method="newton", **kwargs):
65
- """
66
- Solve the AC power flow using PYPOWER.
67
- """
68
- ppc = system2ppc(self.system)
69
-
70
- method_map = dict(newton=1, fdxb=2, fdbx=3, gauss=4)
71
- alg = method_map.get(method)
72
- if alg == 4:
73
- msg = "Gauss method is not fully tested yet, not recommended!"
74
- logger.warning(msg)
75
- if alg is None:
76
- msg = f"Invalid method `{method}` for PFlow."
77
- raise ValueError(msg)
78
- ppopt = ppoption(PF_ALG=alg, ENFORCE_Q_LIMS=self.config.qlim, **kwargs)
79
-
80
- res, sstats = runpf(casedata=ppc, ppopt=ppopt)
81
- return res, sstats
82
-
83
- def run(self, **kwargs):
84
- """
85
- Run AC power flow using PYPOWER.
86
-
87
- Currently, four methods are supported: 'newton', 'fdxb', 'fdbx', 'gauss',
88
- for Newton's method, fast-decoupled, XB, fast-decoupled, BX, and Gauss-Seidel,
89
- respectively.
90
-
91
- Note that gauss method is not recommended because it seems to be much
92
- more slower than the other three methods and not fully tested yet.
93
-
94
- Examples
95
- --------
96
- >>> ss = ams.load(ams.get_case('matpower/case14.m'))
97
- >>> ss.PFlow.run()
98
-
99
- Parameters
100
- ----------
101
- force_init : bool
102
- Force initialization.
103
- no_code : bool
104
- Disable showing code.
105
- method : str
106
- Method for solving the power flow.
107
-
108
- Returns
109
- -------
110
- exit_code : int
111
- Exit code of the routine.
112
- """
113
- return super().run(**kwargs,)