stepss 3.59__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.
- stepss/__init__.py +66 -0
- stepss/_bundled.py +13 -0
- stepss/cases.py +555 -0
- stepss/extractor.py +750 -0
- stepss/globals.py +154 -0
- stepss/helios.py +836 -0
- stepss/libs/helios_api.h +305 -0
- stepss/libs/lin/libhelios_api.so +0 -0
- stepss/libs/lin/ramses.so +0 -0
- stepss/libs/mac/libhelios_api.dylib +0 -0
- stepss/libs/mac/ramses.so +0 -0
- stepss/libs/ramses.h +41 -0
- stepss/libs/win/helios_api.dll +0 -0
- stepss/libs/win/ramses.dll +0 -0
- stepss/scripts/__init__.py +2 -0
- stepss/scripts/exec.py +38 -0
- stepss/simulator.py +1072 -0
- stepss-3.59.dist-info/METADATA +296 -0
- stepss-3.59.dist-info/RECORD +23 -0
- stepss-3.59.dist-info/WHEEL +5 -0
- stepss-3.59.dist-info/entry_points.txt +2 -0
- stepss-3.59.dist-info/licenses/LICENSE.rst +190 -0
- stepss-3.59.dist-info/top_level.txt +1 -0
stepss/extractor.py
ADDED
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Post-simulation results extraction for stepss.
|
|
4
|
+
|
|
5
|
+
Provides:
|
|
6
|
+
|
|
7
|
+
- :class:`extractor` — parses a Fortran binary trajectory file (``.trj``)
|
|
8
|
+
produced by RAMSES and exposes per-component timeseries accessors.
|
|
9
|
+
- :class:`cur` — lightweight NamedTuple holding a ``(time, value, msg)``
|
|
10
|
+
timeseries with a convenience :meth:`~cur.plot` method.
|
|
11
|
+
- :func:`curplot` — plot one or more :class:`cur` objects on a single axes.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import warnings
|
|
15
|
+
import os
|
|
16
|
+
import errno
|
|
17
|
+
from scipy.io import FortranFile
|
|
18
|
+
from typing import NamedTuple
|
|
19
|
+
import numpy as np
|
|
20
|
+
import matplotlib.pyplot as plt
|
|
21
|
+
|
|
22
|
+
from .globals import RAMSESError, CustomWarning, wrapToList
|
|
23
|
+
|
|
24
|
+
warnings.showwarning = CustomWarning
|
|
25
|
+
|
|
26
|
+
def curplot(curves):
|
|
27
|
+
"""Plot one or more timeseries curves on a single matplotlib axes.
|
|
28
|
+
|
|
29
|
+
Each curve is drawn as a line and labelled with its ``msg`` attribute.
|
|
30
|
+
A legend is placed at the best available position and the x-axis is
|
|
31
|
+
labelled ``'time (s)'``.
|
|
32
|
+
|
|
33
|
+
:param curves: a single :class:`cur` or a list of :class:`cur` objects.
|
|
34
|
+
:type curves: :class:`cur` or list of :class:`cur`
|
|
35
|
+
:returns: None — displays the plot via :func:`matplotlib.pyplot.show`.
|
|
36
|
+
"""
|
|
37
|
+
curves = wrapToList(curves)
|
|
38
|
+
for curve in curves:
|
|
39
|
+
plt.plot(curve.time, curve.value, label=curve.msg)
|
|
40
|
+
plt.legend(loc='best',ncol=2)
|
|
41
|
+
plt.xlabel('time (s)')
|
|
42
|
+
plt.show()
|
|
43
|
+
|
|
44
|
+
class cur(NamedTuple):
|
|
45
|
+
"""Immutable container for a single timeseries result.
|
|
46
|
+
|
|
47
|
+
Fields:
|
|
48
|
+
|
|
49
|
+
- **time** (*numpy.ndarray*) — simulation timestamps in seconds.
|
|
50
|
+
- **value** (*numpy.ndarray*) — recorded values at each timestamp;
|
|
51
|
+
units depend on the observable (e.g. pu, MW, Mvar, deg).
|
|
52
|
+
- **msg** (*str*) — human-readable label describing the quantity
|
|
53
|
+
(e.g. ``'g1: active power produced (MW)'``).
|
|
54
|
+
"""
|
|
55
|
+
time: np.ndarray
|
|
56
|
+
value: np.ndarray
|
|
57
|
+
msg: str
|
|
58
|
+
|
|
59
|
+
def plot(self):
|
|
60
|
+
"""Plot this timeseries using :func:`curplot`."""
|
|
61
|
+
curplot(self)
|
|
62
|
+
|
|
63
|
+
class extractor(object):
|
|
64
|
+
"""Parse RAMSES trajectory files and expose per-component timeseries accessors.
|
|
65
|
+
|
|
66
|
+
After running a RAMSES simulation, the results are stored in a Fortran binary
|
|
67
|
+
trajectory file (``.trj``). This class reads that file and provides
|
|
68
|
+
convenience methods to retrieve timeseries for each supported component type
|
|
69
|
+
(buses, branches, synchronous machines, exciters, governors, injectors,
|
|
70
|
+
two-ports, and DCTLs).
|
|
71
|
+
|
|
72
|
+
:param str traj: path to the RAMSES trajectory file produced during the
|
|
73
|
+
simulation (as set with :meth:`~stepss.cfg.addTrj`).
|
|
74
|
+
|
|
75
|
+
:Example:
|
|
76
|
+
|
|
77
|
+
>>> import stepss
|
|
78
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
79
|
+
>>> ram = stepss.sim()
|
|
80
|
+
>>> ram.execSim(case) # run the simulation
|
|
81
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
82
|
+
>>> ext.getBus('1041').mag.plot() # plot the voltage magnitude timeseries for bus '1041'
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(self,traj):
|
|
86
|
+
"""Parse a RAMSES Fortran binary trajectory file.
|
|
87
|
+
|
|
88
|
+
The file is structured as a sequence of Fortran unformatted records
|
|
89
|
+
(read via :class:`scipy.io.FortranFile`). The header section records
|
|
90
|
+
the number and names of every monitored component type; the body section
|
|
91
|
+
contains the timeseries data written in variable-size chunks.
|
|
92
|
+
|
|
93
|
+
After construction the results are available as a 2-D NumPy array
|
|
94
|
+
``self._results`` of shape ``(n_timesteps, n_observables + 1)``
|
|
95
|
+
where column 0 is time and the remaining columns are observables in the
|
|
96
|
+
order defined by RAMSES. Per-component accessors (``getBus``,
|
|
97
|
+
``getSync``, etc.) compute the correct column offset into this array.
|
|
98
|
+
|
|
99
|
+
:param str traj: path to the ``.trj`` trajectory file produced by RAMSES.
|
|
100
|
+
:raises TypeError: if *traj* is not a string.
|
|
101
|
+
:raises FileNotFoundError: if the file does not exist.
|
|
102
|
+
"""
|
|
103
|
+
if not isinstance(traj, str):
|
|
104
|
+
raise TypeError('stepss: extractor expects a string path to the trajectory file.')
|
|
105
|
+
|
|
106
|
+
self._trajfilename = traj
|
|
107
|
+
|
|
108
|
+
if not os.path.isfile(traj):
|
|
109
|
+
raise FileNotFoundError(errno.ENOENT,
|
|
110
|
+
os.strerror(errno.ENOENT), traj)
|
|
111
|
+
|
|
112
|
+
f = FortranFile(traj, 'r')
|
|
113
|
+
|
|
114
|
+
# --- Header: component counts and names ---
|
|
115
|
+
# Each block: read a count integer, then read that many fixed-width name strings.
|
|
116
|
+
# Names are Fortran CHARACTER arrays decoded as UTF-8 and whitespace-stripped.
|
|
117
|
+
|
|
118
|
+
self._busnum = f.read_ints()[0]
|
|
119
|
+
self._busname = []
|
|
120
|
+
for i in range(self._busnum):
|
|
121
|
+
self._busname.append(f.read_record(dtype="S18")[0].decode('UTF-8').strip())
|
|
122
|
+
|
|
123
|
+
self._shunum = f.read_ints()[0]
|
|
124
|
+
self._shuname = []
|
|
125
|
+
for i in range(self._shunum):
|
|
126
|
+
self._shuname.append(f.read_record(dtype="S20")[0].decode('UTF-8').strip())
|
|
127
|
+
|
|
128
|
+
self._ldnum = f.read_ints()[0]
|
|
129
|
+
self._ldname = []
|
|
130
|
+
for i in range(self._ldnum):
|
|
131
|
+
self._ldname.append(f.read_record(dtype="S20")[0].decode('UTF-8').strip())
|
|
132
|
+
|
|
133
|
+
self._branum = f.read_ints()[0]
|
|
134
|
+
self._braname = []
|
|
135
|
+
for i in range(self._branum):
|
|
136
|
+
self._braname.append(f.read_record(dtype="S20")[0].decode('UTF-8').strip())
|
|
137
|
+
|
|
138
|
+
# Synchronous machines interleave exciter and governor observable metadata.
|
|
139
|
+
# _adexc[i] / _adtor[i] store the 1-based column offset of machine i's
|
|
140
|
+
# exciter / governor block inside the results array, enabling O(1) lookup.
|
|
141
|
+
self._syncnum = f.read_ints()[0]
|
|
142
|
+
self._syncname = []
|
|
143
|
+
self._excobsnum = []
|
|
144
|
+
self._excobsname = []
|
|
145
|
+
self._adexc = []
|
|
146
|
+
idxexc = 1
|
|
147
|
+
self._torobsnum = []
|
|
148
|
+
self._torobsname = []
|
|
149
|
+
self._adtor = []
|
|
150
|
+
idxtor = 1
|
|
151
|
+
for i in range(self._syncnum):
|
|
152
|
+
self._syncname.append(f.read_record(dtype="S20")[0].decode('UTF-8').strip())
|
|
153
|
+
self._excobsnum.append(f.read_ints()[0])
|
|
154
|
+
self._excobsname.append([])
|
|
155
|
+
self._adexc.append(idxexc)
|
|
156
|
+
for j in range(self._excobsnum[i]):
|
|
157
|
+
self._excobsname[i].append(f.read_record(dtype="S10")[0].decode('UTF-8').strip())
|
|
158
|
+
idxexc = idxexc + self._excobsnum[i]
|
|
159
|
+
self._torobsnum.append(f.read_ints()[0])
|
|
160
|
+
self._torobsname.append([])
|
|
161
|
+
self._adtor.append(idxtor)
|
|
162
|
+
for j in range(self._torobsnum[i]):
|
|
163
|
+
self._torobsname[i].append(f.read_record(dtype="S10")[0].decode('UTF-8').strip())
|
|
164
|
+
idxtor = idxtor + self._torobsnum[i]
|
|
165
|
+
self._adexc.append(idxexc)
|
|
166
|
+
self._adtor.append(idxtor)
|
|
167
|
+
|
|
168
|
+
self._injnum = f.read_ints()[0]
|
|
169
|
+
self._injobsnum = []
|
|
170
|
+
self._injobsname = []
|
|
171
|
+
self._injname = []
|
|
172
|
+
self._adinj = []
|
|
173
|
+
idxinj = 1
|
|
174
|
+
for i in range(self._injnum):
|
|
175
|
+
self._adinj.append(idxinj)
|
|
176
|
+
self._injname.append(f.read_record(dtype="S20")[0].decode('UTF-8').strip())
|
|
177
|
+
self._injobsnum.append(f.read_ints()[0])
|
|
178
|
+
self._injobsname.append([])
|
|
179
|
+
for j in range(self._injobsnum[i]):
|
|
180
|
+
self._injobsname[i].append(f.read_record(dtype="S10")[0].decode('UTF-8').strip())
|
|
181
|
+
idxinj = idxinj + self._injobsnum[i]
|
|
182
|
+
self._adinj.append(idxinj)
|
|
183
|
+
|
|
184
|
+
self._twopnum = f.read_ints()[0]
|
|
185
|
+
self._twopobsnum = []
|
|
186
|
+
self._twopobsname = []
|
|
187
|
+
self._twopname = []
|
|
188
|
+
self._adtwop = []
|
|
189
|
+
idxtwop = 1
|
|
190
|
+
for i in range(self._twopnum):
|
|
191
|
+
self._adtwop.append(idxtwop)
|
|
192
|
+
self._twopname.append(f.read_record(dtype="S20")[0].decode('UTF-8').strip())
|
|
193
|
+
self._twopobsnum.append(f.read_ints()[0])
|
|
194
|
+
self._twopobsname.append([])
|
|
195
|
+
for j in range(self._twopobsnum[i]):
|
|
196
|
+
self._twopobsname[i].append(f.read_record(dtype="S10")[0].decode('UTF-8').strip())
|
|
197
|
+
idxtwop = idxtwop + self._twopobsnum[i]
|
|
198
|
+
self._adtwop.append(idxtwop)
|
|
199
|
+
|
|
200
|
+
self._dctlnum = f.read_ints()[0]
|
|
201
|
+
self._dctlobsnum = []
|
|
202
|
+
self._dctlobsname = []
|
|
203
|
+
self._dctlname = []
|
|
204
|
+
self._addctl = []
|
|
205
|
+
idxdctl = 1
|
|
206
|
+
for i in range(self._dctlnum):
|
|
207
|
+
self._addctl.append(idxdctl)
|
|
208
|
+
self._dctlname.append(f.read_record(dtype="S20")[0].decode('UTF-8').strip())
|
|
209
|
+
self._dctlobsnum.append(f.read_ints()[0])
|
|
210
|
+
self._dctlobsname.append([])
|
|
211
|
+
for j in range(self._dctlobsnum[i]):
|
|
212
|
+
self._dctlobsname[i].append(f.read_record(dtype="S10")[0].decode('UTF-8').strip())
|
|
213
|
+
idxdctl = idxdctl + self._dctlobsnum[i]
|
|
214
|
+
self._addctl.append(idxdctl)
|
|
215
|
+
|
|
216
|
+
# Total number of scalar observables per timestep.
|
|
217
|
+
# The layout is: 2·buses + shunts + 2·loads + 6·branches +
|
|
218
|
+
# (15 + exc_obs + tor_obs)·sync + inj_obs + twop_obs + dctl_obs.
|
|
219
|
+
self._totobs = 2*self._busnum + self._shunum + 2*self._ldnum + 6*self._branum + \
|
|
220
|
+
15*self._syncnum + sum(self._excobsnum) + sum(self._torobsnum) + \
|
|
221
|
+
sum(self._injobsnum) + sum(self._twopobsnum) + sum(self._dctlobsnum)
|
|
222
|
+
|
|
223
|
+
# --- Body: timeseries data ---
|
|
224
|
+
# RAMSES writes data in variable-size chunks preceded by a 64-bit buffer-size
|
|
225
|
+
# integer. A zero size signals end of data. All chunks are concatenated
|
|
226
|
+
# and reshaped into (n_timesteps, n_observables + 1); column 0 is time.
|
|
227
|
+
# Collect all data chunks first, then concatenate once for O(N) performance.
|
|
228
|
+
chunks = []
|
|
229
|
+
buffsz = f.read_ints(np.int64)[0]
|
|
230
|
+
while buffsz > 0:
|
|
231
|
+
chunks.append(f.read_reals(dtype=np.float64))
|
|
232
|
+
buffsz = f.read_ints(np.int64)[0]
|
|
233
|
+
self._results = np.concatenate(chunks) if chunks else np.array([])
|
|
234
|
+
|
|
235
|
+
self._results = np.reshape(self._results, (-1,self._totobs+1), order='C')
|
|
236
|
+
|
|
237
|
+
self._time = self._results[:,0] # column 0 is always the simulation timestamp
|
|
238
|
+
f.close()
|
|
239
|
+
|
|
240
|
+
def __del__(self):
|
|
241
|
+
"""Emit a warning when this extractor is garbage-collected."""
|
|
242
|
+
if hasattr(self, '_trajfilename'):
|
|
243
|
+
warnings.warn("Extractor of file %s was deleted." % self._trajfilename)
|
|
244
|
+
|
|
245
|
+
def getBus(self, busname):
|
|
246
|
+
"""Returns an object that allows to extract or plot bus related variables.
|
|
247
|
+
|
|
248
|
+
:param str busname: the name of the bus
|
|
249
|
+
|
|
250
|
+
:Example:
|
|
251
|
+
|
|
252
|
+
>>> import stepss
|
|
253
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
254
|
+
>>> ram = stepss.sim()
|
|
255
|
+
>>> ram.execSim(case) # run the simulation
|
|
256
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
257
|
+
>>> ext.getBus('1041').mag.plot() # will plot the timeseries simulated for the voltage magnitude on bus '1041'
|
|
258
|
+
|
|
259
|
+
.. note:: Available data are
|
|
260
|
+
obsnames = ['mag','pha']
|
|
261
|
+
obsdesc = ['Voltage magnitude (pu)','Voltage phase angle (deg)']
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
i=self._busname.index(busname) + 1 # +1 is to go to Fortran notation
|
|
265
|
+
return self._getBusClass(self._time, self._results, 2*(i-1), busname)
|
|
266
|
+
except ValueError:
|
|
267
|
+
warnings.warn('Bus %s not found' % (busname))
|
|
268
|
+
class _getBusClass(object):
|
|
269
|
+
"""Accessor object for bus timeseries observables.
|
|
270
|
+
|
|
271
|
+
Attributes are set dynamically at construction from ``obsnames``; each
|
|
272
|
+
attribute holds a :class:`cur` object. Available attributes:
|
|
273
|
+
``mag`` (voltage magnitude, pu), ``pha`` (voltage phase angle, deg).
|
|
274
|
+
"""
|
|
275
|
+
def _getElem(self, j, msg):
|
|
276
|
+
tmp = self._shift + j
|
|
277
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
278
|
+
def __init__(self, time, results, shift, busname):
|
|
279
|
+
self._time = time
|
|
280
|
+
self._results = results
|
|
281
|
+
self._shift = shift
|
|
282
|
+
|
|
283
|
+
j=0
|
|
284
|
+
self._obsnames = ['mag','pha']
|
|
285
|
+
self._obsdesc = ['Voltage magnitude (pu)','Voltage phase angle (deg)']
|
|
286
|
+
self.obsdict = dict(zip(self._obsnames, self._obsdesc))
|
|
287
|
+
for name,msg in zip(self._obsnames, self._obsdesc):
|
|
288
|
+
j=j+1
|
|
289
|
+
setattr(self, name, self._getElem(j,busname+': '+msg))
|
|
290
|
+
|
|
291
|
+
def getShunt(self, shuname):
|
|
292
|
+
"""Returns an object that allows to extract or plot shunt related variables.
|
|
293
|
+
|
|
294
|
+
:param str shuname: the name of the shunt
|
|
295
|
+
|
|
296
|
+
:Example:
|
|
297
|
+
|
|
298
|
+
>>> import stepss
|
|
299
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
300
|
+
>>> ram = stepss.sim()
|
|
301
|
+
>>> ram.execSim(case) # run the simulation
|
|
302
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
303
|
+
>>> ext.getShunt('sh1').Q.plot() # will plot the timeseries simulated for the reactive power of shunt 'sh1'
|
|
304
|
+
|
|
305
|
+
.. note:: Available data are
|
|
306
|
+
obsnames = ['Q']
|
|
307
|
+
obsdesc = ['Reactive power produced (Mvar)']
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
try:
|
|
311
|
+
i=self._shuname.index(shuname) + 1 # +1 is to go to Fortran notation
|
|
312
|
+
return self._getShuClass(self._time, self._results, 2*self._busnum + i-1, shuname)
|
|
313
|
+
except ValueError:
|
|
314
|
+
warnings.warn('Shunt %s not found' % (shuname))
|
|
315
|
+
|
|
316
|
+
class _getShuClass(object):
|
|
317
|
+
"""Accessor object for shunt timeseries observables.
|
|
318
|
+
|
|
319
|
+
Available attributes: ``Q`` (reactive power produced, Mvar).
|
|
320
|
+
"""
|
|
321
|
+
def _getElem(self, j, msg):
|
|
322
|
+
tmp = self._shift + j
|
|
323
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
324
|
+
def __init__(self, time, results, shift, shuname):
|
|
325
|
+
self._time = time
|
|
326
|
+
self._results = results
|
|
327
|
+
self._shift = shift
|
|
328
|
+
j=0
|
|
329
|
+
self._obsnames = ['Q']
|
|
330
|
+
self._obsdesc = ['Reactive power produced (Mvar)']
|
|
331
|
+
self.obsdict = dict(zip(self._obsnames, self._obsdesc))
|
|
332
|
+
for name,msg in zip(self._obsnames, self._obsdesc):
|
|
333
|
+
j=j+1
|
|
334
|
+
setattr(self, name, self._getElem(j,shuname+': '+msg))
|
|
335
|
+
|
|
336
|
+
def getLoad(self, ldname):
|
|
337
|
+
"""Returns an object that allows to extract or plot load related variables.
|
|
338
|
+
|
|
339
|
+
:param str ldname: the name of the load
|
|
340
|
+
|
|
341
|
+
:Example:
|
|
342
|
+
|
|
343
|
+
>>> import stepss
|
|
344
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
345
|
+
>>> ram = stepss.sim()
|
|
346
|
+
>>> ram.execSim(case) # run the simulation
|
|
347
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
348
|
+
>>> ext.getLoad('L_1').P.plot() # will plot the timeseries simulated for the active power of 'L_1'
|
|
349
|
+
|
|
350
|
+
.. note:: Available data are
|
|
351
|
+
obsnames = ['P','Q']
|
|
352
|
+
obsdesc = ['Active power consumed (MW)','Reactive power consumed (Mvar)']
|
|
353
|
+
"""
|
|
354
|
+
try:
|
|
355
|
+
i=self._ldname.index(ldname) + 1 # +1 is to go to Fortran notation
|
|
356
|
+
return self._getLdClass(self._time, self._results, 2*self._busnum+self._shunum +2*(i-1), ldname)
|
|
357
|
+
except ValueError:
|
|
358
|
+
warnings.warn('Load %s not found' % (ldname))
|
|
359
|
+
|
|
360
|
+
class _getLdClass(object):
|
|
361
|
+
"""Accessor object for load timeseries observables.
|
|
362
|
+
|
|
363
|
+
Available attributes: ``P`` (active power consumed, MW),
|
|
364
|
+
``Q`` (reactive power consumed, Mvar).
|
|
365
|
+
"""
|
|
366
|
+
def _getElem(self, j, msg):
|
|
367
|
+
tmp = self._shift + j
|
|
368
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
369
|
+
def __init__(self, time, results, shift, ldname):
|
|
370
|
+
self._time = time
|
|
371
|
+
self._results = results
|
|
372
|
+
self._shift = shift
|
|
373
|
+
j=0
|
|
374
|
+
self._obsnames = ['P','Q']
|
|
375
|
+
self._obsdesc = ['Active power consumed (MW)','Reactive power consumed (Mvar)']
|
|
376
|
+
self.obsdict = dict(zip(self._obsnames, self._obsdesc))
|
|
377
|
+
for name,msg in zip(self._obsnames, self._obsdesc):
|
|
378
|
+
j=j+1
|
|
379
|
+
setattr(self, name, self._getElem(j,ldname+': '+msg))
|
|
380
|
+
|
|
381
|
+
def getBranch(self, braname):
|
|
382
|
+
"""Returns an object that allows to extract or plot branch related variables.
|
|
383
|
+
|
|
384
|
+
:param str braname: the name of the branch
|
|
385
|
+
|
|
386
|
+
:Example:
|
|
387
|
+
|
|
388
|
+
>>> import stepss
|
|
389
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
390
|
+
>>> ram = stepss.sim()
|
|
391
|
+
>>> ram.execSim(case) # run the simulation
|
|
392
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
393
|
+
>>> ext.getBranch('1041-4041').PF.plot() # will plot the timeseries simulated for the active power of line'1041-4041'
|
|
394
|
+
|
|
395
|
+
.. note::
|
|
396
|
+
|
|
397
|
+
Available observables:
|
|
398
|
+
|
|
399
|
+
- ``PF`` — P (MW) entering at FROM end
|
|
400
|
+
- ``QF`` — Q (Mvar) entering at FROM end
|
|
401
|
+
- ``PT`` — P (MW) entering at TO end
|
|
402
|
+
- ``QT`` — Q (Mvar) entering at TO end
|
|
403
|
+
- ``RM`` — magnitude of transformer ratio
|
|
404
|
+
- ``RA`` — phase angle of transformer ratio (deg)
|
|
405
|
+
"""
|
|
406
|
+
try:
|
|
407
|
+
i=self._braname.index(braname) + 1 # +1 is to go to Fortran notation
|
|
408
|
+
return self._getBraClass(self._time, self._results, 2*self._busnum+
|
|
409
|
+
self._shunum+2*self._ldnum+6*(i-1), braname)
|
|
410
|
+
except ValueError:
|
|
411
|
+
warnings.warn('Branch %s not found' % (braname))
|
|
412
|
+
|
|
413
|
+
class _getBraClass(object):
|
|
414
|
+
"""Accessor object for branch timeseries observables.
|
|
415
|
+
|
|
416
|
+
Available attributes: ``PF``, ``QF`` (P/Q entering at FROM end, MW/Mvar),
|
|
417
|
+
``PT``, ``QT`` (P/Q entering at TO end, MW/Mvar),
|
|
418
|
+
``RM`` (transformer ratio magnitude), ``RA`` (transformer ratio angle, deg).
|
|
419
|
+
"""
|
|
420
|
+
def _getElem(self, j, msg):
|
|
421
|
+
tmp = self._shift + j
|
|
422
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
423
|
+
def __init__(self, time, results, shift, braname):
|
|
424
|
+
self._time = time
|
|
425
|
+
self._results = results
|
|
426
|
+
self._shift = shift
|
|
427
|
+
j=0
|
|
428
|
+
self._obsnames = ['PF','QF','PT','QT','RM','RA']
|
|
429
|
+
self._obsdesc = ['P (MW) entering at FROM end', 'Q (Mvar) entering at FROM end',
|
|
430
|
+
'P (MW) entering at TO end', 'Q (Mvar) entering at TO end',
|
|
431
|
+
'magnitude of transformer ratio','phase angle of transformer ratio (deg)']
|
|
432
|
+
self.obsdict = dict(zip(self._obsnames, self._obsdesc))
|
|
433
|
+
for name,msg in zip(self._obsnames, self._obsdesc):
|
|
434
|
+
j=j+1
|
|
435
|
+
setattr(self, name, self._getElem(j,braname+': '+msg))
|
|
436
|
+
|
|
437
|
+
def getSync(self, syncname):
|
|
438
|
+
"""Returns an object that allows to extract or plot synchronous machine related variables.
|
|
439
|
+
|
|
440
|
+
:param str syncname: the name of the sync machine
|
|
441
|
+
|
|
442
|
+
:Example:
|
|
443
|
+
|
|
444
|
+
>>> import stepss
|
|
445
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
446
|
+
>>> ram = stepss.sim()
|
|
447
|
+
>>> ram.execSim(case) # run the simulation
|
|
448
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
449
|
+
>>> ext.getSync('g1').P.plot() # will plot the timeseries simulated for the active power of 'g1'
|
|
450
|
+
|
|
451
|
+
.. note::
|
|
452
|
+
|
|
453
|
+
Available observables:
|
|
454
|
+
|
|
455
|
+
- ``P`` — active power produced (MW)
|
|
456
|
+
- ``Q`` — reactive power produced (Mvar)
|
|
457
|
+
- ``A`` — rotor angle wrt COI (deg)
|
|
458
|
+
- ``S`` — rotor speed (pu; 1 = nominal)
|
|
459
|
+
- ``FW`` — flux in field winding (pu mach. base)
|
|
460
|
+
- ``DD`` — flux in d1 damper (pu mach. base)
|
|
461
|
+
- ``QD`` — flux in q1 damper (pu mach. base)
|
|
462
|
+
- ``QW`` — flux in q2 winding (pu mach. base)
|
|
463
|
+
- ``FC`` — field current (pu)
|
|
464
|
+
- ``FV`` — field voltage (pu)
|
|
465
|
+
- ``T`` — mechanical torque (pu)
|
|
466
|
+
- ``ET`` — electromagnetic torque (pu mach. base)
|
|
467
|
+
- ``SC`` — speed *deviation* of the COI reference (pu; 0 = nominal)
|
|
468
|
+
|
|
469
|
+
.. note::
|
|
470
|
+
|
|
471
|
+
The two speed observables use different conventions: ``S`` is the
|
|
472
|
+
whole rotor speed (1 = nominal) while ``SC`` is the deviation of the
|
|
473
|
+
centre-of-inertia speed from nominal (0 = nominal). The system
|
|
474
|
+
frequency in Hz is therefore ``fnom * (1 + SC)`` or ``fnom * S``.
|
|
475
|
+
"""
|
|
476
|
+
try:
|
|
477
|
+
i=self._syncname.index(syncname) + 1 # +1 is to go to Fortran notation
|
|
478
|
+
return self._getSyncClass(self._time, self._results,
|
|
479
|
+
2*self._busnum+self._shunum+2*self._ldnum+6*self._branum+
|
|
480
|
+
15*(i-1)+self._adexc[i-1]-1+self._adtor[i-1]-1, syncname)
|
|
481
|
+
except ValueError:
|
|
482
|
+
warnings.warn('Sync machine %s not found' % (syncname))
|
|
483
|
+
|
|
484
|
+
class _getSyncClass(object):
|
|
485
|
+
"""Accessor object for synchronous machine timeseries observables.
|
|
486
|
+
|
|
487
|
+
Available attributes (all as :class:`cur` objects):
|
|
488
|
+
``P`` (MW), ``Q`` (Mvar), ``A`` (rotor angle wrt COI, deg),
|
|
489
|
+
``S`` (rotor speed, pu; 1 = nominal), ``FW`` (field-winding flux, pu),
|
|
490
|
+
``DD`` (d1 damper flux, pu), ``QD`` (q1 damper flux, pu),
|
|
491
|
+
``QW`` (q2 winding flux, pu), ``FC`` (field current, pu),
|
|
492
|
+
``FV`` (field voltage, pu), ``T`` (mechanical torque, pu),
|
|
493
|
+
``ET`` (electromagnetic torque, pu),
|
|
494
|
+
``SC`` (COI speed deviation, pu; 0 = nominal).
|
|
495
|
+
"""
|
|
496
|
+
def _getElem(self, j, msg):
|
|
497
|
+
tmp = self._shift + j
|
|
498
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
499
|
+
|
|
500
|
+
def __init__(self, time, results, shift, syncname):
|
|
501
|
+
self._time = time
|
|
502
|
+
self._results = results
|
|
503
|
+
self._shift = shift
|
|
504
|
+
j=0
|
|
505
|
+
self._obsnames = ['P','Q','A','S','FW','DD','QD','QW','FC','FV','T','ET','SC']
|
|
506
|
+
self._obsdesc = ['active power produced (MW)',
|
|
507
|
+
'reactive power produced (Mvar)',
|
|
508
|
+
'rotor angle wrt COI (deg)',
|
|
509
|
+
'rotor speed (pu)',
|
|
510
|
+
'flux in field winding (pu mach. base) ',
|
|
511
|
+
'flux in d1 damper (pu mach. base) ',
|
|
512
|
+
'flux in q1 damper (pu mach. base) ',
|
|
513
|
+
'flux in q2 winding (pu mach. base) ',
|
|
514
|
+
'field current (pu) ',
|
|
515
|
+
'field voltage (pu) ',
|
|
516
|
+
'mechanical torque (pu) ',
|
|
517
|
+
'electromagnetic torque (pu mach. base) ',
|
|
518
|
+
'speed deviation of COI reference (pu) ']
|
|
519
|
+
self.obsdict = dict(zip(self._obsnames, self._obsdesc))
|
|
520
|
+
for name,msg in zip(self._obsnames, self._obsdesc):
|
|
521
|
+
j=j+1
|
|
522
|
+
setattr(self, name, self._getElem(j,syncname+': '+msg))
|
|
523
|
+
|
|
524
|
+
def getExc(self, syncname):
|
|
525
|
+
"""Returns an object that allows to extract or plot exciter related variables.
|
|
526
|
+
|
|
527
|
+
:param str syncname: the name of the generator that we want to check the exciter
|
|
528
|
+
|
|
529
|
+
:Example:
|
|
530
|
+
|
|
531
|
+
>>> import stepss
|
|
532
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
533
|
+
>>> ram = stepss.sim()
|
|
534
|
+
>>> ram.execSim(case) # run the simulation
|
|
535
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
536
|
+
>>> ext.getExc('g1').vf.plot() # will plot the timeseries simulated for the field voltage of 'g1'
|
|
537
|
+
"""
|
|
538
|
+
try:
|
|
539
|
+
i=self._syncname.index(syncname) + 1 # +1 is to go to Fortran notation
|
|
540
|
+
return self._getExcClass(self._time, self._results,
|
|
541
|
+
2*self._busnum+self._shunum+2*self._ldnum+6*self._branum+
|
|
542
|
+
15*(i-1)+self._adexc[i-1]-1+self._adtor[i-1]-1 + 15, self._excobsname[i-1], syncname)
|
|
543
|
+
except ValueError:
|
|
544
|
+
warnings.warn('Sync machine %s not found' % (syncname))
|
|
545
|
+
|
|
546
|
+
class _getExcClass(object):
|
|
547
|
+
"""Accessor object for exciter timeseries observables.
|
|
548
|
+
|
|
549
|
+
Attributes are set dynamically from the observable names defined in the
|
|
550
|
+
exciter model. Refer to the RAMSES user manual for the observable names
|
|
551
|
+
of each supported exciter model.
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
def _getElem(self, j, msg):
|
|
555
|
+
tmp = self._shift + j
|
|
556
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
557
|
+
|
|
558
|
+
def __init__(self, time, results, shift, excobsname, syncname):
|
|
559
|
+
self._time = time
|
|
560
|
+
self._results = results
|
|
561
|
+
self._shift = shift
|
|
562
|
+
self.obsdict = dict(zip(excobsname, ['User model, refer to the manual']*len(excobsname)))
|
|
563
|
+
j=0
|
|
564
|
+
for name in excobsname:
|
|
565
|
+
j=j+1
|
|
566
|
+
setattr(self, name, self._getElem(j,syncname+': '+name))
|
|
567
|
+
|
|
568
|
+
def getTor(self, syncname):
|
|
569
|
+
"""Returns an object that allows to extract or plot governor related variables.
|
|
570
|
+
|
|
571
|
+
:param str syncname: the name of the generator that we want to check the governor
|
|
572
|
+
|
|
573
|
+
:Example:
|
|
574
|
+
|
|
575
|
+
>>> import stepss
|
|
576
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
577
|
+
>>> ram = stepss.sim()
|
|
578
|
+
>>> ram.execSim(case) # run the simulation
|
|
579
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
580
|
+
>>> ext.getTor('g1').Tm.plot() # will plot the timeseries simulated for the torque of 'g1'
|
|
581
|
+
"""
|
|
582
|
+
try:
|
|
583
|
+
i=self._syncname.index(syncname) + 1 # +1 is to go to Fortran notation
|
|
584
|
+
return self._getTorClass(self._time, self._results,
|
|
585
|
+
2*self._busnum+self._shunum+2*self._ldnum+6*self._branum+
|
|
586
|
+
15*(i-1)+self._adexc[i]-1+self._adtor[i-1]-1 + 15, self._torobsname[i-1], syncname)
|
|
587
|
+
except ValueError:
|
|
588
|
+
warnings.warn('Sync machine %s not found' % (syncname))
|
|
589
|
+
|
|
590
|
+
class _getTorClass(object):
|
|
591
|
+
"""Accessor object for governor (torque controller) timeseries observables.
|
|
592
|
+
|
|
593
|
+
Attributes are set dynamically from the observable names defined in the
|
|
594
|
+
governor model. Refer to the RAMSES user manual for the observable names
|
|
595
|
+
of each supported governor model.
|
|
596
|
+
"""
|
|
597
|
+
|
|
598
|
+
def _getElem(self, j, msg):
|
|
599
|
+
tmp = self._shift + j
|
|
600
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
601
|
+
|
|
602
|
+
def __init__(self, time, results, shift, torobsname, syncname):
|
|
603
|
+
self._time = time
|
|
604
|
+
self._results = results
|
|
605
|
+
self._shift = shift
|
|
606
|
+
self.obsdict = dict(zip(torobsname, ['User model, refer to the manual']*len(torobsname)))
|
|
607
|
+
j=0
|
|
608
|
+
for name in torobsname:
|
|
609
|
+
j=j+1
|
|
610
|
+
setattr(self, name, self._getElem(j,syncname+': '+name))
|
|
611
|
+
|
|
612
|
+
def getInj(self, injname):
|
|
613
|
+
"""Returns an object that allows to extract or plot injector related variables.
|
|
614
|
+
|
|
615
|
+
:param str injname: the name of the injector
|
|
616
|
+
|
|
617
|
+
:Example:
|
|
618
|
+
|
|
619
|
+
>>> import stepss
|
|
620
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
621
|
+
>>> ram = stepss.sim()
|
|
622
|
+
>>> ram.execSim(case) # run the simulation
|
|
623
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
624
|
+
>>> ext.getInj('pv1').P.plot() # will plot the timeseries simulated for the active power of 'pv1'
|
|
625
|
+
"""
|
|
626
|
+
try:
|
|
627
|
+
i=self._injname.index(injname) + 1 # +1 is to go to Fortran notation
|
|
628
|
+
return self._getInjClass(self._time, self._results,
|
|
629
|
+
2*self._busnum+self._shunum+2*self._ldnum+6*self._branum+
|
|
630
|
+
15*(self._syncnum)+self._adexc[self._syncnum]-1+
|
|
631
|
+
self._adtor[self._syncnum]-1+self._adinj[i-1]-1,
|
|
632
|
+
self._injobsname[i-1], injname)
|
|
633
|
+
except ValueError:
|
|
634
|
+
warnings.warn('Injector %s not found' % (injname))
|
|
635
|
+
|
|
636
|
+
class _getInjClass(object):
|
|
637
|
+
"""Accessor object for injector timeseries observables.
|
|
638
|
+
|
|
639
|
+
Attributes are set dynamically from the observable names defined in the
|
|
640
|
+
injector model. Refer to the RAMSES user manual for the observable names
|
|
641
|
+
of each supported injector model.
|
|
642
|
+
"""
|
|
643
|
+
|
|
644
|
+
def _getElem(self, j, msg):
|
|
645
|
+
tmp = self._shift + j
|
|
646
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
647
|
+
|
|
648
|
+
def __init__(self, time, results, shift, injobsname, injname):
|
|
649
|
+
self._time = time
|
|
650
|
+
self._results = results
|
|
651
|
+
self._shift = shift
|
|
652
|
+
self.obsdict = dict(zip(injobsname, ['User model, refer to the manual']*len(injobsname)))
|
|
653
|
+
j=0
|
|
654
|
+
for name in injobsname:
|
|
655
|
+
j=j+1
|
|
656
|
+
setattr(self, name, self._getElem(j,injname+': '+name))
|
|
657
|
+
|
|
658
|
+
def getTwop(self, twopname):
|
|
659
|
+
"""Returns an object that allows to extract or plot twoport related variables.
|
|
660
|
+
|
|
661
|
+
:param str twopname: the name of the twoport
|
|
662
|
+
|
|
663
|
+
:Example:
|
|
664
|
+
|
|
665
|
+
>>> import stepss
|
|
666
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
667
|
+
>>> ram = stepss.sim()
|
|
668
|
+
>>> ram.execSim(case) # run the simulation
|
|
669
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
670
|
+
>>> ext.getTwop('lcc1').P1.plot() # will plot the timeseries simulated for the power of 'lcc1'
|
|
671
|
+
"""
|
|
672
|
+
try:
|
|
673
|
+
i=self._twopname.index(twopname) + 1 # +1 is to go to Fortran notation
|
|
674
|
+
return self._getTwopClass(self._time, self._results,
|
|
675
|
+
2*self._busnum+self._shunum+2*self._ldnum+6*self._branum+
|
|
676
|
+
15*(self._syncnum)+self._adexc[self._syncnum]-1+
|
|
677
|
+
self._adtor[self._syncnum]-1+self._adinj[self._injnum]-1+
|
|
678
|
+
self._adtwop[i-1]-1,
|
|
679
|
+
self._twopobsname[i-1], twopname)
|
|
680
|
+
except ValueError:
|
|
681
|
+
warnings.warn('Twoport %s not found' % (twopname))
|
|
682
|
+
|
|
683
|
+
class _getTwopClass(object):
|
|
684
|
+
"""Accessor object for two-port device timeseries observables.
|
|
685
|
+
|
|
686
|
+
Attributes are set dynamically from the observable names defined in the
|
|
687
|
+
two-port model. Refer to the RAMSES user manual for the observable names
|
|
688
|
+
of each supported two-port model.
|
|
689
|
+
"""
|
|
690
|
+
|
|
691
|
+
def _getElem(self, j, msg):
|
|
692
|
+
tmp = self._shift + j
|
|
693
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
694
|
+
|
|
695
|
+
def __init__(self, time, results, shift, twopobsname, twopname):
|
|
696
|
+
self._time = time
|
|
697
|
+
self._results = results
|
|
698
|
+
self._shift = shift
|
|
699
|
+
self.obsdict = dict(zip(twopobsname, ['User model, refer to the manual']*len(twopobsname)))
|
|
700
|
+
j=0
|
|
701
|
+
for name in twopobsname:
|
|
702
|
+
j=j+1
|
|
703
|
+
setattr(self, name, self._getElem(j,twopname+': '+name))
|
|
704
|
+
|
|
705
|
+
def getDctl(self, dctlname):
|
|
706
|
+
"""Returns an object that allows to extract or plot dctl related variables.
|
|
707
|
+
|
|
708
|
+
:param str dctlname: the name of the dctl
|
|
709
|
+
|
|
710
|
+
:Example:
|
|
711
|
+
|
|
712
|
+
>>> import stepss
|
|
713
|
+
>>> case = stepss.cfg("case.rcfg") # load case from a configuration file
|
|
714
|
+
>>> ram = stepss.sim()
|
|
715
|
+
>>> ram.execSim(case) # run the simulation
|
|
716
|
+
>>> ext = stepss.extractor(case.getTrj())
|
|
717
|
+
>>> ext.getDctl('agc').g5.plot() # will plot the timeseries simulated for the power of 'g5'
|
|
718
|
+
"""
|
|
719
|
+
try:
|
|
720
|
+
i=self._dctlname.index(dctlname) + 1 # +1 is to go to Fortran notation
|
|
721
|
+
return self._getDCTLClass(self._time, self._results,
|
|
722
|
+
2*self._busnum+self._shunum+2*self._ldnum+6*self._branum+
|
|
723
|
+
15*(self._syncnum)+self._adexc[self._syncnum]-1+
|
|
724
|
+
self._adtor[self._syncnum]-1+self._adinj[self._injnum]-1+
|
|
725
|
+
self._adtwop[self._twopnum]-1+self._addctl[i-1]-1,
|
|
726
|
+
self._dctlobsname[i-1],dctlname)
|
|
727
|
+
except ValueError:
|
|
728
|
+
warnings.warn('DCTL %s not found' % (dctlname))
|
|
729
|
+
|
|
730
|
+
class _getDCTLClass(object):
|
|
731
|
+
"""Accessor object for discrete controller timeseries observables.
|
|
732
|
+
|
|
733
|
+
Attributes are set dynamically from the observable names defined in the
|
|
734
|
+
DCTL model. Refer to the RAMSES user manual for the observable names
|
|
735
|
+
of each supported discrete controller model.
|
|
736
|
+
"""
|
|
737
|
+
|
|
738
|
+
def _getElem(self, j, msg):
|
|
739
|
+
tmp = self._shift + j
|
|
740
|
+
return cur(self._time, self._results[:,tmp], msg)
|
|
741
|
+
|
|
742
|
+
def __init__(self, time, results, shift, dctlobsname, dctlname):
|
|
743
|
+
self._time = time
|
|
744
|
+
self._results = results
|
|
745
|
+
self._shift = shift
|
|
746
|
+
self.obsdict = dict(zip(dctlobsname, ['User model, refer to the manual']*len(dctlobsname)))
|
|
747
|
+
j=0
|
|
748
|
+
for name in dctlobsname:
|
|
749
|
+
j=j+1
|
|
750
|
+
setattr(self, name, self._getElem(j,dctlname+': '+name))
|