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.
- ams/__init__.py +0 -1
- ams/_version.py +3 -3
- ams/cases/5bus/pjm5bus_demo.json +1324 -0
- ams/core/__init__.py +1 -0
- ams/core/common.py +30 -0
- ams/core/model.py +1 -1
- ams/core/symprocessor.py +1 -1
- ams/extension/eva.py +1 -1
- ams/interface.py +40 -24
- ams/io/matpower.py +31 -17
- ams/io/psse.py +278 -1
- ams/main.py +2 -2
- ams/models/group.py +2 -1
- ams/models/static/pq.py +7 -3
- ams/opt/param.py +1 -2
- ams/routines/__init__.py +2 -3
- ams/routines/acopf.py +5 -108
- ams/routines/dcopf.py +8 -0
- ams/routines/dcpf.py +1 -1
- ams/routines/ed.py +4 -2
- ams/routines/grbopt.py +150 -0
- ams/routines/pflow.py +2 -2
- ams/routines/pypower.py +631 -0
- ams/routines/routine.py +4 -10
- ams/routines/uc.py +2 -2
- ams/shared.py +26 -43
- ams/system.py +118 -2
- docs/source/api.rst +2 -0
- docs/source/getting_started/install.rst +9 -6
- docs/source/images/dcopf_time.png +0 -0
- docs/source/images/educ_pie.png +0 -0
- docs/source/release-notes.rst +21 -47
- {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/METADATA +87 -47
- {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/RECORD +54 -71
- {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/WHEEL +1 -1
- tests/test_1st_system.py +1 -1
- tests/test_case.py +14 -14
- tests/test_export_csv.py +1 -1
- tests/test_interface.py +24 -2
- tests/test_io.py +50 -0
- tests/test_omodel.py +1 -1
- tests/test_report.py +6 -6
- tests/test_routine.py +2 -2
- tests/test_rtn_acopf.py +75 -0
- tests/test_rtn_dcopf.py +1 -1
- tests/test_rtn_dcopf2.py +1 -1
- tests/test_rtn_ed.py +9 -9
- tests/test_rtn_opf.py +142 -0
- tests/test_rtn_pflow.py +0 -72
- tests/test_rtn_pypower.py +315 -0
- tests/test_rtn_rted.py +8 -8
- tests/test_rtn_uc.py +18 -18
- ams/pypower/__init__.py +0 -8
- ams/pypower/_compat.py +0 -9
- ams/pypower/core/__init__.py +0 -8
- ams/pypower/core/pips.py +0 -894
- ams/pypower/core/ppoption.py +0 -244
- ams/pypower/core/ppver.py +0 -18
- ams/pypower/core/solver.py +0 -2451
- ams/pypower/eps.py +0 -6
- ams/pypower/idx.py +0 -174
- ams/pypower/io.py +0 -604
- ams/pypower/make/__init__.py +0 -11
- ams/pypower/make/matrices.py +0 -665
- ams/pypower/make/pdv.py +0 -506
- ams/pypower/routines/__init__.py +0 -7
- ams/pypower/routines/cpf.py +0 -513
- ams/pypower/routines/cpf_callbacks.py +0 -114
- ams/pypower/routines/opf.py +0 -1803
- ams/pypower/routines/opffcns.py +0 -1946
- ams/pypower/routines/pflow.py +0 -852
- ams/pypower/toggle.py +0 -1098
- ams/pypower/utils.py +0 -293
- ams/routines/cpf.py +0 -65
- ams/routines/dcpf0.py +0 -196
- ams/routines/pflow0.py +0 -113
- tests/test_rtn_dcpf.py +0 -77
- {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/entry_points.txt +0 -0
- {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,)
|