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/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))