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/simulator.py
ADDED
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Python interface to the RAMSES dynamic power-system simulator.
|
|
4
|
+
|
|
5
|
+
Provides :class:`sim`, a ctypes-based wrapper around the RAMSES shared library
|
|
6
|
+
(``ramses.dll`` on Windows, ``ramses.so`` on Linux and macOS). Function
|
|
7
|
+
signatures are read automatically from the bundled ``ramses.h`` C header at
|
|
8
|
+
instantiation time, so the wrapper is self-describing and does not hard-code
|
|
9
|
+
argument types.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import ctypes
|
|
13
|
+
import _ctypes
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import warnings
|
|
17
|
+
import datetime
|
|
18
|
+
import numpy as np
|
|
19
|
+
from scipy.sparse import coo_matrix
|
|
20
|
+
|
|
21
|
+
from .cases import cfg
|
|
22
|
+
from .globals import RAMSESError, CustomWarning, __libdir__, __platlibdir__, wrapToList
|
|
23
|
+
|
|
24
|
+
class sim(object):
|
|
25
|
+
"""Interface to a RAMSES solver instance.
|
|
26
|
+
|
|
27
|
+
Each :class:`sim` object loads the RAMSES shared library into the current
|
|
28
|
+
process and exposes the solver's C API through Python methods. The library
|
|
29
|
+
is unloaded (via :func:`ctypes.FreeLibrary` / :func:`ctypes._ctypes.dlclose`)
|
|
30
|
+
when the instance is garbage-collected.
|
|
31
|
+
|
|
32
|
+
**Thread safety:** RAMSES maintains internal global state; running multiple
|
|
33
|
+
:class:`sim` instances concurrently in separate threads is not supported.
|
|
34
|
+
Multiple sequential instances within the same process are safe.
|
|
35
|
+
|
|
36
|
+
**Class attributes:**
|
|
37
|
+
|
|
38
|
+
- ``ramsesCount`` (*int*) — number of :class:`sim` instances currently alive.
|
|
39
|
+
Incremented in :meth:`__init__` and decremented in :meth:`__del__`.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
warnings.showwarning = CustomWarning
|
|
43
|
+
|
|
44
|
+
ramsesCount = 0 # number of active sim instances
|
|
45
|
+
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
|
|
46
|
+
|
|
47
|
+
def __init__(self, custLibDir = None):
|
|
48
|
+
"""Load the RAMSES shared library and initialise C function signatures.
|
|
49
|
+
|
|
50
|
+
On Windows the library is ``ramses.dll``; on all other platforms
|
|
51
|
+
(Linux and macOS) it is ``ramses.so``. By default the library bundled
|
|
52
|
+
with the package for the current platform is used
|
|
53
|
+
(``src/stepss/libs/win/``, ``libs/lin/`` or ``libs/mac/``). A custom
|
|
54
|
+
directory can be supplied via *custLibDir* to override that library —
|
|
55
|
+
useful for testing pre-release solver builds; the directory must
|
|
56
|
+
contain the library file directly (no platform subfolder).
|
|
57
|
+
|
|
58
|
+
After loading the library, :meth:`_setcalls` is invoked to parse
|
|
59
|
+
``ramses.h`` and configure the ctypes argument/return types for every
|
|
60
|
+
exported function.
|
|
61
|
+
|
|
62
|
+
:param custLibDir: path to a directory containing an alternative
|
|
63
|
+
``ramses.dll`` / ``ramses.so``. The directory must
|
|
64
|
+
exist. When provided, the library file in that
|
|
65
|
+
location will be locked by the OS until this instance
|
|
66
|
+
is deleted.
|
|
67
|
+
:type custLibDir: str or None
|
|
68
|
+
:raises RAMSESError: if *custLibDir* is given but does not exist or is
|
|
69
|
+
not a directory.
|
|
70
|
+
:raises ImportError: if the shared library cannot be loaded (e.g. missing
|
|
71
|
+
system runtime libraries such as OpenBLAS on Linux).
|
|
72
|
+
"""
|
|
73
|
+
if custLibDir is None:
|
|
74
|
+
ramLibDir = __platlibdir__
|
|
75
|
+
else:
|
|
76
|
+
try:
|
|
77
|
+
if os.path.exists(custLibDir) and os.path.isdir(custLibDir):
|
|
78
|
+
ramLibDir = custLibDir
|
|
79
|
+
warnings.warn("Overwriting the internal DLL with the one at %s. The DLL in that location will be locked. To unlock it, you need to del() this instance of stepss.sim() or restart the kernel." % ramLibDir)
|
|
80
|
+
else:
|
|
81
|
+
raise RAMSESError('RAMSES: The path %s does not exist or it is not a directory.' % (custLibDir))
|
|
82
|
+
except OSError as e:
|
|
83
|
+
raise RAMSESError('RAMSES: The path %s gave an error.' % custLibDir) from e
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
if sys.platform in ('win32', 'cygwin'):
|
|
87
|
+
self._ramseslib = ctypes.CDLL(os.path.join(ramLibDir, "ramses.dll"))
|
|
88
|
+
else:
|
|
89
|
+
self._ramseslib = ctypes.CDLL(os.path.join(ramLibDir, "ramses.so"))
|
|
90
|
+
except OSError as e:
|
|
91
|
+
raise ImportError('RAMSES: Could not load shared library from %s.' % ramLibDir) from e
|
|
92
|
+
|
|
93
|
+
sim.ramsesCount += 1
|
|
94
|
+
self._ramsesNum = sim.ramsesCount # This is the current instance of Ramses
|
|
95
|
+
self._setcalls()
|
|
96
|
+
|
|
97
|
+
def __del__(self):
|
|
98
|
+
"""Unload the RAMSES shared library and decrement the instance counter.
|
|
99
|
+
|
|
100
|
+
Called automatically by the garbage collector. Releases the OS-level
|
|
101
|
+
handle to the DLL/SO so the file can be replaced or deleted afterwards.
|
|
102
|
+
Guards against partially-initialised instances (e.g. if ``__init__``
|
|
103
|
+
raised before the library was loaded).
|
|
104
|
+
"""
|
|
105
|
+
if not hasattr(self, '_ramseslib'):
|
|
106
|
+
return
|
|
107
|
+
warnings.warn("Simulator with number %i was deleted." % getattr(self, '_ramsesNum', -1))
|
|
108
|
+
if sys.platform in ('win32', 'cygwin'):
|
|
109
|
+
_ctypes.FreeLibrary(self._ramseslib._handle)
|
|
110
|
+
else:
|
|
111
|
+
_ctypes.dlclose(self._ramseslib._handle)
|
|
112
|
+
sim.ramsesCount -= 1
|
|
113
|
+
|
|
114
|
+
def _c_func_wrapper(self, cdecl_text):
|
|
115
|
+
"""Parse a single C function declaration and configure its ctypes binding.
|
|
116
|
+
|
|
117
|
+
Reads one line from ``ramses.h``, extracts the return type, function
|
|
118
|
+
name, and parameter types, then sets ``restype`` and ``argtypes`` on the
|
|
119
|
+
corresponding function object retrieved from ``self._ramseslib``.
|
|
120
|
+
|
|
121
|
+
:param str cdecl_text: a single-line C function declaration in the form
|
|
122
|
+
``<return_type> <name>(<param_type> <param_name>, ...)``
|
|
123
|
+
:raises KeyError: (internally caught) if a C type has no ctypes mapping.
|
|
124
|
+
|
|
125
|
+
.. note:: Pointer qualifiers (``*``) attached to the variable name are
|
|
126
|
+
moved to the type string before lookup, so both ``char* x``
|
|
127
|
+
and ``char *x`` are handled correctly.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def move_pointer_and_strip(type_def, name):
|
|
131
|
+
"""Move any trailing ``*`` from *name* onto *type_def* and strip whitespace."""
|
|
132
|
+
if '*' in name:
|
|
133
|
+
type_def += ' ' + name[:name.rindex('*') + 1]
|
|
134
|
+
name = name.rsplit('*', 1)[1]
|
|
135
|
+
return type_def.strip(), name.strip()
|
|
136
|
+
|
|
137
|
+
def type_lookup(type_def):
|
|
138
|
+
'''Supported C variable types and their ctypes equivalents.'''
|
|
139
|
+
types = {
|
|
140
|
+
'void': None,
|
|
141
|
+
'char *': ctypes.c_char_p,
|
|
142
|
+
'int': ctypes.c_int,
|
|
143
|
+
'int *': ctypes.POINTER(ctypes.c_int),
|
|
144
|
+
'void *': ctypes.c_void_p,
|
|
145
|
+
'size_t': ctypes.c_size_t,
|
|
146
|
+
'size_t *': ctypes.POINTER(ctypes.c_size_t),
|
|
147
|
+
'double': ctypes.c_double,
|
|
148
|
+
'double *': ctypes.POINTER(ctypes.c_double)
|
|
149
|
+
}
|
|
150
|
+
type_def_without_const = type_def.replace('const ', '')
|
|
151
|
+
if type_def_without_const in types:
|
|
152
|
+
return types[type_def_without_const]
|
|
153
|
+
elif (type_def_without_const.endswith('*') and type_def_without_const[:-1] in types):
|
|
154
|
+
return ctypes.POINTER(types[type_def_without_const[:-1]])
|
|
155
|
+
else:
|
|
156
|
+
raise KeyError(type_def)
|
|
157
|
+
|
|
158
|
+
a, b = [i.strip() for i in cdecl_text.split('(', 1)]
|
|
159
|
+
params, _ = b.rsplit(')', 1)
|
|
160
|
+
rtn_type, name = move_pointer_and_strip(*a.rsplit(' ', 1))
|
|
161
|
+
param_spec = []
|
|
162
|
+
for param in params.split(','):
|
|
163
|
+
if param != 'void':
|
|
164
|
+
param_spec.append(move_pointer_and_strip(*param.rsplit(' ', 1)))
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
func = getattr(self._ramseslib, name) # get the function from the dll
|
|
168
|
+
setattr(func, 'restype', type_lookup(rtn_type)) # set the return type
|
|
169
|
+
setattr(func, 'argtypes', [type_lookup(type_def) for type_def, _ in param_spec]) # set the argument types
|
|
170
|
+
except AttributeError as e:
|
|
171
|
+
warnings.warn('RAMSES: Function %s is listed in ramses.h but cannot be found in the library.' % (name))
|
|
172
|
+
warnings.warn(str(e))
|
|
173
|
+
|
|
174
|
+
def _setcalls(self):
|
|
175
|
+
"""Parse ``ramses.h`` and configure ctypes bindings for all exported functions.
|
|
176
|
+
|
|
177
|
+
Reads the C header file bundled with the package line by line, skipping
|
|
178
|
+
blank lines and ``//`` comments, and delegates each declaration to
|
|
179
|
+
:meth:`_c_func_wrapper`.
|
|
180
|
+
|
|
181
|
+
:raises IOError: if ``ramses.h`` cannot be opened.
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
with open(os.path.join(__libdir__, "ramses.h"), 'r') as f:
|
|
185
|
+
_C_HEADER = f.read()
|
|
186
|
+
for cdecl_text in _C_HEADER.splitlines():
|
|
187
|
+
if cdecl_text.strip():
|
|
188
|
+
if not cdecl_text.startswith("//"):
|
|
189
|
+
self._c_func_wrapper(cdecl_text)
|
|
190
|
+
except IOError as e:
|
|
191
|
+
raise IOError("RAMSES: Cannot open ramses.h files", e)
|
|
192
|
+
|
|
193
|
+
def getLastErr(self):
|
|
194
|
+
"""Return the last error message issued by RAMSES.
|
|
195
|
+
|
|
196
|
+
:rtype: str
|
|
197
|
+
|
|
198
|
+
:Example:
|
|
199
|
+
|
|
200
|
+
>>> import stepss
|
|
201
|
+
>>> ram = stepss.sim()
|
|
202
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
203
|
+
>>> ram.execSim(case, 0) # start simulation paused
|
|
204
|
+
>>> ram.getLastErr()
|
|
205
|
+
'This is an error msg'
|
|
206
|
+
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
errMsg = ctypes.create_string_buffer(1024)
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
retval = self._ramseslib.get_last_err_log(errMsg)
|
|
213
|
+
except KeyError:
|
|
214
|
+
retval = 1
|
|
215
|
+
|
|
216
|
+
if (retval != 0):
|
|
217
|
+
raise RAMSESError('RAMSES: Function getLastErr failed.')
|
|
218
|
+
|
|
219
|
+
return errMsg.value.decode()
|
|
220
|
+
|
|
221
|
+
def getJac(self):
|
|
222
|
+
"""Return the system Jacobian matrices written by RAMSES to temporary files.
|
|
223
|
+
|
|
224
|
+
Triggers the RAMSES ``get_Jac`` routine, which writes two sparse matrix
|
|
225
|
+
files to the current working directory (``py_eqs.dat`` for the structural
|
|
226
|
+
incidence matrix *E* and ``py_val.dat`` for the Jacobian values *A*).
|
|
227
|
+
Both files are then parsed and returned as SciPy CSC sparse matrices.
|
|
228
|
+
|
|
229
|
+
The simulation must have been initialised (i.e. :meth:`execSim` called
|
|
230
|
+
with ``pause=0.0``) before this method is called.
|
|
231
|
+
|
|
232
|
+
:returns: tuple ``(A, E)`` where *A* is the Jacobian value matrix and
|
|
233
|
+
*E* is the structural incidence matrix, both as
|
|
234
|
+
:class:`scipy.sparse.csc_matrix` of shape ``(N, N)``.
|
|
235
|
+
:rtype: tuple
|
|
236
|
+
|
|
237
|
+
:Example:
|
|
238
|
+
|
|
239
|
+
>>> import stepss
|
|
240
|
+
>>> ram = stepss.sim()
|
|
241
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
242
|
+
>>> ram.execSim(case, 0) # start simulation paused
|
|
243
|
+
>>> ram.getJac()
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
try:
|
|
247
|
+
retval = self._ramseslib.get_Jac()
|
|
248
|
+
except KeyError:
|
|
249
|
+
retval = 1
|
|
250
|
+
|
|
251
|
+
if (retval != 0) and (retval != 112):
|
|
252
|
+
raise RAMSESError('RAMSES: Function get_Jac failed.')
|
|
253
|
+
|
|
254
|
+
Nmat = 0
|
|
255
|
+
try:
|
|
256
|
+
with open('py_eqs.dat', 'r') as f:
|
|
257
|
+
lines = f.readlines()
|
|
258
|
+
rowl = []
|
|
259
|
+
coll = []
|
|
260
|
+
datal = []
|
|
261
|
+
for x in lines:
|
|
262
|
+
xsplit = x.split()
|
|
263
|
+
if int(xsplit[0]) > Nmat:
|
|
264
|
+
Nmat = int(xsplit[0])
|
|
265
|
+
if int(xsplit[5]) > 0:
|
|
266
|
+
rowl.append(int(xsplit[0])-1)
|
|
267
|
+
coll.append(int(xsplit[5])-1)
|
|
268
|
+
datal.append(1)
|
|
269
|
+
row = np.array( rowl, dtype=int )
|
|
270
|
+
col = np.array( coll, dtype=int )
|
|
271
|
+
data = np.array( datal, dtype=float )
|
|
272
|
+
E = coo_matrix((data,(row,col)), shape=(Nmat,Nmat)).tocsc()
|
|
273
|
+
except Exception:
|
|
274
|
+
raise RAMSESError('RAMSES: Function get_Jac failed while reading E.')
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
with open('py_val.dat', 'r') as f:
|
|
278
|
+
lines = f.readlines()
|
|
279
|
+
row = np.array( [ int(x.split()[0])-1 for x in lines ], dtype=int )
|
|
280
|
+
col = np.array( [ int(x.split()[1])-1 for x in lines ], dtype=int )
|
|
281
|
+
data = np.array( [ x.split()[2] for x in lines ], dtype=float )
|
|
282
|
+
A = coo_matrix((data,(row,col)), shape=(Nmat,Nmat)).tocsc()
|
|
283
|
+
except Exception:
|
|
284
|
+
raise RAMSESError('RAMSES: Function get_Jac failed while reading A.')
|
|
285
|
+
|
|
286
|
+
return A, E
|
|
287
|
+
|
|
288
|
+
def getCompName(self, comp_type, num):
|
|
289
|
+
"""Return the name of the *num*-th component of the given type.
|
|
290
|
+
|
|
291
|
+
:param str comp_type: component type string — one of ``'BUS'``,
|
|
292
|
+
``'SYNC'``, ``'INJ'``, ``'DCTL'``, ``'BRANCH'``,
|
|
293
|
+
``'TWOP'``, ``'SHUNT'``, ``'LOAD'``.
|
|
294
|
+
:param int num: 1-based index of the component.
|
|
295
|
+
:returns: component name as returned by RAMSES (trailing whitespace stripped).
|
|
296
|
+
:rtype: str
|
|
297
|
+
:raises RAMSESError: if the component does not exist or the call fails.
|
|
298
|
+
|
|
299
|
+
:Example:
|
|
300
|
+
|
|
301
|
+
>>> import stepss
|
|
302
|
+
>>> ram = stepss.sim()
|
|
303
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
304
|
+
>>> ram.execSim(case, 0) # start simulation paused
|
|
305
|
+
>>> ram.getCompName("BUS",1)
|
|
306
|
+
'B1'
|
|
307
|
+
|
|
308
|
+
"""
|
|
309
|
+
get_name_func = {
|
|
310
|
+
'BUS': self._ramseslib.get_bus_name,
|
|
311
|
+
'SYNC': self._ramseslib.get_sync_name,
|
|
312
|
+
'INJ': self._ramseslib.get_inj_name,
|
|
313
|
+
'DCTL': self._ramseslib.get_dctl_name,
|
|
314
|
+
'BRANCH': self._ramseslib.get_branch_name,
|
|
315
|
+
'TWOP': self._ramseslib.get_twop_name,
|
|
316
|
+
'SHUNT': self._ramseslib.get_shunt_name,
|
|
317
|
+
'LOAD': self._ramseslib.get_load_name
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
name = ctypes.create_string_buffer(21)
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
retval = get_name_func[comp_type](num, name)
|
|
324
|
+
except KeyError:
|
|
325
|
+
retval = 1
|
|
326
|
+
|
|
327
|
+
if (retval != 0):
|
|
328
|
+
raise RAMSESError(
|
|
329
|
+
'RAMSES: Function getCompName(%s,%i) failed. Does this component exist?' % (comp_type, num))
|
|
330
|
+
|
|
331
|
+
return name.value.decode()
|
|
332
|
+
|
|
333
|
+
def getAllCompNames(self, comp_type):
|
|
334
|
+
"""Return the names of all components of the given type.
|
|
335
|
+
|
|
336
|
+
:param str comp_type: component type string — one of ``'BUS'``,
|
|
337
|
+
``'SYNC'``, ``'INJ'``, ``'DCTL'``, ``'BRANCH'``,
|
|
338
|
+
``'TWOP'``, ``'SHUNT'``, ``'LOAD'``.
|
|
339
|
+
:returns: list of component names (empty list if none exist).
|
|
340
|
+
:rtype: list of str
|
|
341
|
+
:raises RAMSESError: if *comp_type* is not recognised.
|
|
342
|
+
|
|
343
|
+
:Example:
|
|
344
|
+
|
|
345
|
+
>>> import stepss
|
|
346
|
+
>>> ram = stepss.sim()
|
|
347
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
348
|
+
>>> ram.execSim(case, 0) # start simulation paused
|
|
349
|
+
>>> ram.getAllCompNames("BUS")
|
|
350
|
+
['B1',
|
|
351
|
+
'B2',
|
|
352
|
+
'B3']
|
|
353
|
+
|
|
354
|
+
"""
|
|
355
|
+
get_nb_func = {
|
|
356
|
+
'BUS': self._ramseslib.get_nbbus,
|
|
357
|
+
'SYNC': self._ramseslib.get_nbsync,
|
|
358
|
+
'INJ': self._ramseslib.get_nbinj,
|
|
359
|
+
'DCTL': self._ramseslib.get_nbdctl,
|
|
360
|
+
'BRANCH': self._ramseslib.get_nbbra,
|
|
361
|
+
'TWOP': self._ramseslib.get_nbtwop,
|
|
362
|
+
'SHUNT': self._ramseslib.get_nbshunt,
|
|
363
|
+
'LOAD': self._ramseslib.get_nbload
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
Nb = get_nb_func[comp_type]()
|
|
368
|
+
except KeyError:
|
|
369
|
+
raise RAMSESError(
|
|
370
|
+
'RAMSES: Function getAllCompName(%s) failed. Does this component type exists?' % (comp_type))
|
|
371
|
+
|
|
372
|
+
names = []
|
|
373
|
+
if Nb > 0:
|
|
374
|
+
for comp in range(1, Nb + 1):
|
|
375
|
+
names.append(self.getCompName(comp_type, comp))
|
|
376
|
+
return names
|
|
377
|
+
|
|
378
|
+
def execSim(self, cmd, pause=None):
|
|
379
|
+
"""Execute a RAMSES simulation.
|
|
380
|
+
|
|
381
|
+
Serialises *cmd* to a RAMSES command file (via :meth:`~stepss.cfg.writeCmdFile`),
|
|
382
|
+
then calls the RAMSES solver. If *pause* is supplied, the simulation is
|
|
383
|
+
scheduled to stop at that simulated time and can be resumed later with
|
|
384
|
+
:meth:`contSim`. Pass ``pause=0.0`` to initialise only (power-flow
|
|
385
|
+
solution) without running the dynamic simulation.
|
|
386
|
+
|
|
387
|
+
:param cmd: case configuration describing all input and output files.
|
|
388
|
+
:type cmd: :class:`stepss.cfg`
|
|
389
|
+
:param pause: simulated time (seconds) at which to pause, or ``None``
|
|
390
|
+
to run until the end of the disturbance scenario.
|
|
391
|
+
:type pause: float or None
|
|
392
|
+
:returns: ``0`` on success.
|
|
393
|
+
:rtype: int
|
|
394
|
+
:raises TypeError: if *cmd* is not a :class:`stepss.cfg` instance.
|
|
395
|
+
:raises RAMSESError: if the solver returns a non-zero, non-112 flag.
|
|
396
|
+
|
|
397
|
+
.. note:: Return code ``112`` means the simulation paused normally; it
|
|
398
|
+
is treated as success by this method.
|
|
399
|
+
|
|
400
|
+
:Example:
|
|
401
|
+
|
|
402
|
+
>>> import stepss
|
|
403
|
+
>>> ram = stepss.sim()
|
|
404
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
405
|
+
>>> ram.execSim(case) # run to end
|
|
406
|
+
>>> ram.execSim(case, pause=0.0) # initialise only, then pause
|
|
407
|
+
|
|
408
|
+
.. note:: If you have an existing command file, you can load it with
|
|
409
|
+
``stepss.cfg("cmd.txt")`` before passing it here.
|
|
410
|
+
"""
|
|
411
|
+
if not isinstance(cmd, cfg):
|
|
412
|
+
raise TypeError('RAMSES: Function execSim failed because the command file is not of type stepss.cfg()')
|
|
413
|
+
if pause is not None:
|
|
414
|
+
self.pauseSim(pause)
|
|
415
|
+
cmdfilename = cmd.writeCmdFile()
|
|
416
|
+
if not cmd._out:
|
|
417
|
+
outfilename = os.path.join(os.getcwd(),'output '+datetime.datetime.now().strftime("%d.%m.%Y-%H.%M.%S")+'.trace')
|
|
418
|
+
else:
|
|
419
|
+
outfilename = cmd._out[0]
|
|
420
|
+
retval = self._ramseslib.ramses(cmdfilename.encode('utf-8'), outfilename.encode('utf-8'))
|
|
421
|
+
if (retval != 0) and (retval != 112):
|
|
422
|
+
raise RAMSESError('RAMSES: Function execSim() failed with the flag %i. Last message was: %s' % (retval, self.getLastErr()))
|
|
423
|
+
return 0
|
|
424
|
+
|
|
425
|
+
def contSim(self, pause=None):
|
|
426
|
+
"""Continue a paused simulation until *pause* seconds of simulated time.
|
|
427
|
+
|
|
428
|
+
If *pause* is given, :meth:`pauseSim` is called first to schedule the
|
|
429
|
+
next stop. Pass ``self.getInfTime()`` to run until the end of the
|
|
430
|
+
disturbance scenario.
|
|
431
|
+
|
|
432
|
+
:param pause: simulated time (in seconds) at which to pause again, or
|
|
433
|
+
``None`` to keep the currently scheduled pause time.
|
|
434
|
+
:type pause: float or None
|
|
435
|
+
:returns: ``0`` on success.
|
|
436
|
+
:rtype: int
|
|
437
|
+
:raises RAMSESError: if the RAMSES call returns a non-zero, non-112 flag.
|
|
438
|
+
|
|
439
|
+
:Example:
|
|
440
|
+
|
|
441
|
+
>>> import stepss
|
|
442
|
+
>>> ram = stepss.sim()
|
|
443
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
444
|
+
>>> ram.execSim(case, 0.0) # initialise and pause at t=0
|
|
445
|
+
>>> ram.contSim(ram.getInfTime()) # run to end of scenario
|
|
446
|
+
"""
|
|
447
|
+
if pause is not None:
|
|
448
|
+
self.pauseSim(pause)
|
|
449
|
+
retval = self._ramseslib.continue_simul()
|
|
450
|
+
if (retval != 0) and (retval != 112):
|
|
451
|
+
raise RAMSESError('RAMSES: Function contSim() failed with the flag %i. Last message was: %s' % (retval, self.getLastErr()))
|
|
452
|
+
return 0
|
|
453
|
+
|
|
454
|
+
def getBusVolt(self, busNames):
|
|
455
|
+
"""Return the voltage magnitude of a list of buses
|
|
456
|
+
|
|
457
|
+
:param busNames: the names of buses
|
|
458
|
+
:type busNames: list of str
|
|
459
|
+
:returns: list of bus voltage magnitudes
|
|
460
|
+
:rtype: list of floats
|
|
461
|
+
|
|
462
|
+
:Example:
|
|
463
|
+
|
|
464
|
+
>>> import stepss
|
|
465
|
+
>>> ram = stepss.sim()
|
|
466
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
467
|
+
>>> ram.execSim(case, 10.0) # simulate until 10 seconds and pause
|
|
468
|
+
>>> buses = ['g1','g2','g3']
|
|
469
|
+
>>> ram.getBusVolt(buses)
|
|
470
|
+
[1.0736851673414456,
|
|
471
|
+
1.0615442362180327,
|
|
472
|
+
1.064702686997689]
|
|
473
|
+
"""
|
|
474
|
+
volts = []
|
|
475
|
+
bus_volt = ctypes.c_double()
|
|
476
|
+
for bus in busNames:
|
|
477
|
+
retval = self._ramseslib.get_volt_mag(bus.encode('utf-8'), bus_volt)
|
|
478
|
+
if (retval != 0):
|
|
479
|
+
raise RAMSESError(
|
|
480
|
+
'RAMSES: Function getBusVolt(%s) failed with the flag %i. Does the bus exist? Last message was: %s' % (bus, retval, self.getLastErr()))
|
|
481
|
+
volts.append(bus_volt.value)
|
|
482
|
+
return volts
|
|
483
|
+
|
|
484
|
+
def getBranchPow(self, branchName):
|
|
485
|
+
"""Return the active and reactive powers of a list of branches
|
|
486
|
+
|
|
487
|
+
:param branchName: the names of branches
|
|
488
|
+
:type branchName: list of str
|
|
489
|
+
:returns: list of branch powers. These are active and reactive power at the origin and extremity respectively (p_orig, q_orig, p_extr, q_extr)
|
|
490
|
+
:rtype: list of floats
|
|
491
|
+
|
|
492
|
+
:Example:
|
|
493
|
+
|
|
494
|
+
>>> import stepss
|
|
495
|
+
>>> ram = stepss.sim()
|
|
496
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
497
|
+
>>> ram.execSim(case, 10.0) # simulate until 10 seconds and pause
|
|
498
|
+
>>> branchName = ['1011-1013','1012-1014','1021-1022']
|
|
499
|
+
>>> ram.getBranchPow(branchName)
|
|
500
|
+
"""
|
|
501
|
+
pows = []
|
|
502
|
+
p_orig = ctypes.c_double()
|
|
503
|
+
q_orig = ctypes.c_double()
|
|
504
|
+
p_extr = ctypes.c_double()
|
|
505
|
+
q_extr = ctypes.c_double()
|
|
506
|
+
for branch in branchName:
|
|
507
|
+
retval = self._ramseslib.get_line_pow(branch.encode('utf-8'), p_orig, q_orig, p_extr, q_extr)
|
|
508
|
+
if (retval != 0):
|
|
509
|
+
raise RAMSESError(
|
|
510
|
+
'RAMSES: Function get_line_pow(%s) failed with the flag %i. Does the branch exist? Last message was: %s' % (branch, retval, self.getLastErr()))
|
|
511
|
+
thisBranch = [p_orig.value, q_orig.value, p_extr.value, q_extr.value]
|
|
512
|
+
pows.append(thisBranch)
|
|
513
|
+
return pows
|
|
514
|
+
|
|
515
|
+
def getBranchCur(self, branchName):
|
|
516
|
+
"""Return the currents of a list of branches
|
|
517
|
+
|
|
518
|
+
:param branchName: the names of branches
|
|
519
|
+
:type branchName: list of str
|
|
520
|
+
:returns: list of branch currents. These are x-y components at the origin and extremity respectively (ix_orig, iy_orig, ix_extr, iy_extr)
|
|
521
|
+
:rtype: list of floats
|
|
522
|
+
|
|
523
|
+
:Example:
|
|
524
|
+
|
|
525
|
+
>>> import stepss
|
|
526
|
+
>>> ram = stepss.sim()
|
|
527
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
528
|
+
>>> ram.execSim(case, 10.0) # simulate until 10 seconds and pause
|
|
529
|
+
>>> branchName = ['1011-1013','1012-1014','1021-1022']
|
|
530
|
+
>>> ram.getBranchCur(branchName)
|
|
531
|
+
|
|
532
|
+
"""
|
|
533
|
+
curs = []
|
|
534
|
+
ix_orig = ctypes.c_double()
|
|
535
|
+
iy_orig = ctypes.c_double()
|
|
536
|
+
ix_extr = ctypes.c_double()
|
|
537
|
+
iy_extr = ctypes.c_double()
|
|
538
|
+
for branch in branchName:
|
|
539
|
+
retval = self._ramseslib.get_line_cur(branch.encode('utf-8'), ix_orig, iy_orig, ix_extr, iy_extr)
|
|
540
|
+
if (retval != 0):
|
|
541
|
+
raise RAMSESError(
|
|
542
|
+
'RAMSES: Function get_line_cur(%s) failed with the flag %i. Does the branch exist? Last message was: %s' % (branch, retval, self.getLastErr()))
|
|
543
|
+
thisBranch = [ix_orig.value, iy_orig.value, ix_extr.value, iy_extr.value]
|
|
544
|
+
curs.append(thisBranch)
|
|
545
|
+
return curs
|
|
546
|
+
|
|
547
|
+
def getBusPha(self, busNames):
|
|
548
|
+
"""Return the voltage phase of a list of buses
|
|
549
|
+
|
|
550
|
+
:param busNames: the names of buses
|
|
551
|
+
:type busNames: list of str
|
|
552
|
+
:returns: list of bus voltage phase
|
|
553
|
+
:rtype: list of floats
|
|
554
|
+
|
|
555
|
+
:Example:
|
|
556
|
+
|
|
557
|
+
>>> import stepss
|
|
558
|
+
>>> ram = stepss.sim()
|
|
559
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
560
|
+
>>> ram.execSim(case, 10.0) # simulate until 10 seconds and pause
|
|
561
|
+
>>> buses = ['g1','g2','g3']
|
|
562
|
+
>>> ram.getBusPha(buses)
|
|
563
|
+
[0.0000000000000000,
|
|
564
|
+
10.0615442362180327,
|
|
565
|
+
11.064702686997689]
|
|
566
|
+
|
|
567
|
+
"""
|
|
568
|
+
pha = []
|
|
569
|
+
bus_pha = ctypes.c_double()
|
|
570
|
+
for bus in busNames:
|
|
571
|
+
retval = self._ramseslib.get_volt_pha(bus.encode('utf-8'), bus_pha)
|
|
572
|
+
if (retval != 0):
|
|
573
|
+
raise RAMSESError(
|
|
574
|
+
'RAMSES: Function getBusPha(%s) failed with the flag %i. Does the bus exist? Last message was: %s' % (bus, retval, self.getLastErr()))
|
|
575
|
+
pha.append(bus_pha.value)
|
|
576
|
+
return pha
|
|
577
|
+
|
|
578
|
+
def endSim(self):
|
|
579
|
+
"""Signal RAMSES to end the simulation and block until it terminates.
|
|
580
|
+
|
|
581
|
+
Sets the internal RAMSES end-of-simulation flag and then calls
|
|
582
|
+
:meth:`contSim` with an infinite time horizon so that the solver
|
|
583
|
+
flushes its output and cleans up.
|
|
584
|
+
|
|
585
|
+
:returns: return value of the underlying :meth:`contSim` call (``0`` on success).
|
|
586
|
+
:rtype: int
|
|
587
|
+
"""
|
|
588
|
+
|
|
589
|
+
self._ramseslib.set_end_simul()
|
|
590
|
+
return self.contSim(self.getInfTime())
|
|
591
|
+
|
|
592
|
+
def getEndSim(self):
|
|
593
|
+
"""Check whether the simulation has ended.
|
|
594
|
+
|
|
595
|
+
:returns: ``0`` if the simulation is still running; ``1`` if it has ended.
|
|
596
|
+
:rtype: int
|
|
597
|
+
"""
|
|
598
|
+
|
|
599
|
+
return self._ramseslib.get_end_simul()
|
|
600
|
+
|
|
601
|
+
def getSimTime(self):
|
|
602
|
+
"""Return the current simulated time reported by the RAMSES solver.
|
|
603
|
+
|
|
604
|
+
Only meaningful while a simulation is paused (between :meth:`execSim`
|
|
605
|
+
and :meth:`contSim` calls).
|
|
606
|
+
|
|
607
|
+
:returns: current simulation time in seconds.
|
|
608
|
+
:rtype: float
|
|
609
|
+
"""
|
|
610
|
+
return self._ramseslib.get_sim_time()
|
|
611
|
+
|
|
612
|
+
def getInfTime(self):
|
|
613
|
+
"""Return the largest representable double recognised by the RAMSES solver.
|
|
614
|
+
|
|
615
|
+
Passing this value to :meth:`contSim` or :meth:`execSim` instructs
|
|
616
|
+
RAMSES to run until the end of the disturbance scenario.
|
|
617
|
+
|
|
618
|
+
:returns: maximum double value used internally by RAMSES as a sentinel
|
|
619
|
+
for "simulate to the end".
|
|
620
|
+
:rtype: float
|
|
621
|
+
"""
|
|
622
|
+
return self._ramseslib.get_huge_double()
|
|
623
|
+
|
|
624
|
+
def initObserv(self, traj_filenm):
|
|
625
|
+
"""Initialise the runtime observable recording system.
|
|
626
|
+
|
|
627
|
+
Must be called after :meth:`execSim` (with ``pause=0.0``) and before
|
|
628
|
+
:meth:`addObserv` / :meth:`finalObserv`. Sets the path of the output
|
|
629
|
+
trajectory file and prepares the internal observable structures.
|
|
630
|
+
|
|
631
|
+
:param str traj_filenm: path of the ``.trj`` file where recorded
|
|
632
|
+
timeseries will be written.
|
|
633
|
+
:returns: return value from the underlying RAMSES call (non-zero on error).
|
|
634
|
+
|
|
635
|
+
:Example:
|
|
636
|
+
|
|
637
|
+
>>> import stepss
|
|
638
|
+
>>> ram = stepss.sim()
|
|
639
|
+
>>> case = stepss.cfg("cmd.txt") # command file without any observables
|
|
640
|
+
>>> ram.execSim(case, 0.0) # start
|
|
641
|
+
>>> traj_filenm = 'obs.trj'
|
|
642
|
+
>>> ram.initObserv(traj_filenm)
|
|
643
|
+
"""
|
|
644
|
+
|
|
645
|
+
return self._ramseslib.initObserv(traj_filenm.encode('utf-8'))
|
|
646
|
+
|
|
647
|
+
def addObserv(self, string):
|
|
648
|
+
"""Register one observable element for runtime recording.
|
|
649
|
+
|
|
650
|
+
Must be called after :meth:`initObserv` and before :meth:`finalObserv`.
|
|
651
|
+
Accepts RAMSES observable selector strings such as ``'BUS *'`` (all
|
|
652
|
+
buses) or ``'SYNC g1'`` (a specific generator).
|
|
653
|
+
|
|
654
|
+
:param str string: observable selector in RAMSES format.
|
|
655
|
+
:returns: return value from the underlying RAMSES call (non-zero on error).
|
|
656
|
+
|
|
657
|
+
:Example:
|
|
658
|
+
|
|
659
|
+
>>> import stepss
|
|
660
|
+
>>> ram = stepss.sim()
|
|
661
|
+
>>> case = stepss.cfg("cmd.txt") # command file without any observables
|
|
662
|
+
>>> ram.execSim(case, 0.0) # start
|
|
663
|
+
>>> traj_filenm = 'obs.trj'
|
|
664
|
+
>>> ram.initObserv(traj_filenm)
|
|
665
|
+
>>> string = 'BUS *' # monitor all buses
|
|
666
|
+
>>> ram.addObserv(string)
|
|
667
|
+
"""
|
|
668
|
+
|
|
669
|
+
return self._ramseslib.addObserv(string.encode('utf-8'))
|
|
670
|
+
|
|
671
|
+
def finalObserv(self):
|
|
672
|
+
"""Finalise observable selection, allocate recording buffers, and write the trajectory file header.
|
|
673
|
+
|
|
674
|
+
Must be called after all :meth:`addObserv` calls. RAMSES will write
|
|
675
|
+
timeseries data to the ``.trj`` file specified in :meth:`initObserv`
|
|
676
|
+
as the simulation advances.
|
|
677
|
+
|
|
678
|
+
:returns: return value from the underlying RAMSES call (non-zero on error).
|
|
679
|
+
|
|
680
|
+
:Example:
|
|
681
|
+
|
|
682
|
+
>>> import stepss
|
|
683
|
+
>>> ram = stepss.sim()
|
|
684
|
+
>>> case = stepss.cfg("cmd.txt") # command file without any observables
|
|
685
|
+
>>> ram.execSim(case, 0.0) # start
|
|
686
|
+
>>> traj_filenm = 'obs.trj'
|
|
687
|
+
>>> ram.initObserv(traj_filenm)
|
|
688
|
+
>>> string = 'BUS *' # monitor all buses
|
|
689
|
+
>>> ram.addObserv(string)
|
|
690
|
+
>>> ram.finalObserv()
|
|
691
|
+
"""
|
|
692
|
+
|
|
693
|
+
return self._ramseslib.finalObserv()
|
|
694
|
+
|
|
695
|
+
def getPrm(self, comp_type, comp_name, prm_name):
|
|
696
|
+
"""Get the value of a named parameter
|
|
697
|
+
|
|
698
|
+
:param comp_type: the types of components (EXC, TOR, INJ, DCTL, TWOP)
|
|
699
|
+
:type comp_type: list of str
|
|
700
|
+
:param comp_name: the names of components
|
|
701
|
+
:type comp_name: list of str
|
|
702
|
+
:param prm_name: the names of parameters
|
|
703
|
+
:type prm_name: list of str
|
|
704
|
+
|
|
705
|
+
:returns: list of parameter values
|
|
706
|
+
:rtype: list of floats
|
|
707
|
+
|
|
708
|
+
:Example:
|
|
709
|
+
|
|
710
|
+
>>> import stepss
|
|
711
|
+
>>> ram = stepss.sim()
|
|
712
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
713
|
+
>>> ram.execSim(case, 10.0) # simulate until 10 seconds and pause
|
|
714
|
+
>>> comp_type = ['EXC','EXC','EXC']
|
|
715
|
+
>>> comp_name = ['g1','g2','g3']
|
|
716
|
+
>>> prm_name = ['V0','V0','V0']
|
|
717
|
+
>>> ram.getPrm(comp_type,comp_name, prm_name)
|
|
718
|
+
[1.0736851673414456,
|
|
719
|
+
1.0615442362180327,
|
|
720
|
+
1.064702686997689]
|
|
721
|
+
|
|
722
|
+
"""
|
|
723
|
+
|
|
724
|
+
comp_type = wrapToList(comp_type)
|
|
725
|
+
comp_name = wrapToList(comp_name)
|
|
726
|
+
prm_name = wrapToList(prm_name)
|
|
727
|
+
|
|
728
|
+
if not (len(comp_type) == len(comp_name) == len(prm_name)):
|
|
729
|
+
raise ValueError('RAMSES: Function getPrm failed because the lists are not equal!')
|
|
730
|
+
prm_values = []
|
|
731
|
+
for a, b, c in zip(comp_type, comp_name, prm_name):
|
|
732
|
+
prm_value = ctypes.c_double()
|
|
733
|
+
retval = self._ramseslib.get_named_prm(a.encode('utf-8'), b.encode('utf-8'), c.encode('utf-8'),
|
|
734
|
+
prm_value)
|
|
735
|
+
if retval != 0:
|
|
736
|
+
raise RAMSESError(
|
|
737
|
+
'RAMSES: Function getPrm(%s,%s,%s) failed with the flag %i. Does the parameter or equipment exist? Last message was: %s'
|
|
738
|
+
% (a, b, c, retval, self.getLastErr()))
|
|
739
|
+
prm_values.append(prm_value.value)
|
|
740
|
+
if len(prm_values) == 1:
|
|
741
|
+
return prm_values[0]
|
|
742
|
+
else:
|
|
743
|
+
return prm_values
|
|
744
|
+
|
|
745
|
+
def defineSS(self, ssID, filter1, filter2, filter3):
|
|
746
|
+
"""Define a subsytem using three filters. The resulting list is an intersection of the filters.
|
|
747
|
+
|
|
748
|
+
:param ssID: Number of the SS
|
|
749
|
+
:type ssID: int
|
|
750
|
+
:param filter1: Voltage levels to be included
|
|
751
|
+
:type filter1: list of str or str
|
|
752
|
+
:param filter2: Zones (which zone/zones will be included)
|
|
753
|
+
:type filter2: list of str or str
|
|
754
|
+
:param filter3: Bus names to be included
|
|
755
|
+
:type filter3: list of str or str
|
|
756
|
+
|
|
757
|
+
:Example:
|
|
758
|
+
|
|
759
|
+
>>> import stepss
|
|
760
|
+
>>> ram = stepss.sim()
|
|
761
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
762
|
+
>>> ram.execSim(case, 0.0)
|
|
763
|
+
>>> ram.defineSS(1, ['735'], [], []) # SS 1 with all buses at 735 kV, no zones, no list of buses
|
|
764
|
+
|
|
765
|
+
:raises RAMSESError: if *ssID*, *location*, or *in_service* are not valid, or the RAMSES call fails.
|
|
766
|
+
:returns: ``None``
|
|
767
|
+
|
|
768
|
+
.. note:: An empty filter means it is deactivated and discarded.
|
|
769
|
+
|
|
770
|
+
"""
|
|
771
|
+
filter1 = wrapToList(filter1)
|
|
772
|
+
filter2 = wrapToList(filter2)
|
|
773
|
+
filter3 = wrapToList(filter3)
|
|
774
|
+
|
|
775
|
+
strF1 = ' '.join(filter1)
|
|
776
|
+
strF2 = ' '.join(filter2)
|
|
777
|
+
strF3 = ' '.join(filter3)
|
|
778
|
+
|
|
779
|
+
retval = self._ramseslib.define_SS(ctypes.c_int(ssID), strF1.encode('utf-8'), ctypes.c_int(len(filter1)),
|
|
780
|
+
strF2.encode('utf-8'), ctypes.c_int(len(filter2)), strF3.encode('utf-8'),
|
|
781
|
+
ctypes.c_int(len(filter3)))
|
|
782
|
+
if retval != 0:
|
|
783
|
+
raise RAMSESError('RAMSES: Function define_SS(%i,...) failed with the flag %i. Last message was: %s' % (ssID, retval, self.getLastErr()))
|
|
784
|
+
|
|
785
|
+
def getSS(self, ssID):
|
|
786
|
+
"""Retrieve the buses of a subsytem.
|
|
787
|
+
|
|
788
|
+
:param ssID: Number of the SS
|
|
789
|
+
:type ssID: int
|
|
790
|
+
|
|
791
|
+
:returns: list of buses
|
|
792
|
+
:rtype: list of str
|
|
793
|
+
|
|
794
|
+
:Example:
|
|
795
|
+
|
|
796
|
+
>>> import stepss
|
|
797
|
+
>>> ram = stepss.sim()
|
|
798
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
799
|
+
>>> ram.execSim(case, 0.0)
|
|
800
|
+
>>> ram.defineSS(1, ['735'], [], []) # SS 1 with all buses at 735 kV
|
|
801
|
+
>>> ram.getSS(1) # get list of buses in SS 1
|
|
802
|
+
|
|
803
|
+
"""
|
|
804
|
+
|
|
805
|
+
mxreclen = self._ramseslib.get_nbbus()
|
|
806
|
+
string_buffer = ctypes.create_string_buffer(19 * mxreclen)
|
|
807
|
+
|
|
808
|
+
retval = self._ramseslib.get_SS(ctypes.c_int(ssID), ctypes.c_int(mxreclen), string_buffer)
|
|
809
|
+
if retval != 0:
|
|
810
|
+
raise RAMSESError('RAMSES: Function getSS(%i) failed with the flag %i. Last message was: %s' % (ssID, retval, self.getLastErr()))
|
|
811
|
+
|
|
812
|
+
return string_buffer.value.split()
|
|
813
|
+
|
|
814
|
+
def getTrfoSS(self, ssID, location, in_service, rettype):
|
|
815
|
+
"""Retrieve transformer information of subsystem after applying some filters
|
|
816
|
+
|
|
817
|
+
:param ssID: Number of the SS
|
|
818
|
+
:type ssID: int
|
|
819
|
+
:param location: 1 – both buses inside SS, 2 - tie transformers, 3 – 1 & 2
|
|
820
|
+
:type location: int
|
|
821
|
+
:param in_service: 1 – transformers in service, 2 - all transformers
|
|
822
|
+
:type in_service: int
|
|
823
|
+
:param rettype: Type of response (NAME, From, To, Status, Tap, Currentf, Currentt, Pf, Qf, Pt, Qt).
|
|
824
|
+
:type rettype: str
|
|
825
|
+
|
|
826
|
+
:returns: list of transformer names
|
|
827
|
+
:rtype: list of str
|
|
828
|
+
|
|
829
|
+
:Example:
|
|
830
|
+
|
|
831
|
+
>>> import stepss
|
|
832
|
+
>>> ram = stepss.sim()
|
|
833
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
834
|
+
>>> ram.execSim(case, 0.0)
|
|
835
|
+
>>> ram.defineSS(1, ['735'], [], []) # SS 1 with all buses at 735 kV
|
|
836
|
+
>>> ram.getTrfoSS(1,3,2,'Status')
|
|
837
|
+
|
|
838
|
+
.. note:: Tap is not implemented yet.
|
|
839
|
+
|
|
840
|
+
.. todo:: Implement Taps after discussing with Lester.
|
|
841
|
+
"""
|
|
842
|
+
|
|
843
|
+
if rettype not in ['NAME', 'From', 'To', 'Status', 'Tap', 'Currentf', 'Currentt', 'Pf', 'Qf', 'Pt', 'Qt']:
|
|
844
|
+
raise RAMSESError('RAMSES: Function getTrfoSS(%i,%i,%i,%s): rettype is not valid!' % (
|
|
845
|
+
ssID, location, in_service, rettype))
|
|
846
|
+
elif location not in [1, 2, 3]:
|
|
847
|
+
raise RAMSESError('RAMSES: Function getTrfoSS(%i,%i,%i,%s): location is not valid!' % (
|
|
848
|
+
ssID, location, in_service, rettype))
|
|
849
|
+
elif in_service not in [1, 2]:
|
|
850
|
+
raise RAMSESError('RAMSES: Function getTrfoSS(%i,%i,%i,%s): in_service is not valid!' % (
|
|
851
|
+
ssID, location, in_service, rettype))
|
|
852
|
+
|
|
853
|
+
mxreclen = self._ramseslib.get_nbbra()
|
|
854
|
+
string_buffer = ctypes.create_string_buffer(21 * mxreclen)
|
|
855
|
+
dp_vec = (ctypes.c_double * mxreclen)()
|
|
856
|
+
dp_int = (ctypes.c_int * mxreclen)()
|
|
857
|
+
elem = ctypes.c_int()
|
|
858
|
+
|
|
859
|
+
retval = self._ramseslib.get_transformer_in_SS(ctypes.c_int(ssID), ctypes.c_int(location),
|
|
860
|
+
ctypes.c_int(in_service), rettype.encode('utf-8'),
|
|
861
|
+
ctypes.c_int(mxreclen), string_buffer, dp_vec, dp_int, elem)
|
|
862
|
+
if retval != 0:
|
|
863
|
+
raise RAMSESError('RAMSES: Function getTrfoSS(%i,%i,%i,%s) failed with the flag %i.' % (
|
|
864
|
+
ssID, location, in_service, rettype, retval))
|
|
865
|
+
|
|
866
|
+
if elem == 0:
|
|
867
|
+
return []
|
|
868
|
+
elif rettype in ['Tap', 'Status']:
|
|
869
|
+
return [dp_int[i] for i in range(0, elem.value)]
|
|
870
|
+
elif rettype in ['Currentf', 'Currentt', 'Pf', 'Qf', 'Pt', 'Qt']:
|
|
871
|
+
return [dp_vec[i] for i in range(0, elem.value)]
|
|
872
|
+
else:
|
|
873
|
+
return string_buffer.value.split()
|
|
874
|
+
|
|
875
|
+
def getPrmNames(self, comp_type, comp_name):
|
|
876
|
+
"""Get the named parameters of a model
|
|
877
|
+
|
|
878
|
+
:param comp_type: the types of components (EXC, TOR, INJ, DCTL, TWOP)
|
|
879
|
+
:type comp_type: list of str or str
|
|
880
|
+
:param comp_name: the names of component instances
|
|
881
|
+
:type comp_name: list of str or str
|
|
882
|
+
|
|
883
|
+
:returns: list of parameter names
|
|
884
|
+
:rtype: list of lists of strings
|
|
885
|
+
|
|
886
|
+
:Example:
|
|
887
|
+
|
|
888
|
+
>>> import stepss
|
|
889
|
+
>>> ram = stepss.sim()
|
|
890
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
891
|
+
>>> ram.execSim(case, 0.0) # initialize and wait
|
|
892
|
+
>>> comp_type = ['EXC','EXC','EXC']
|
|
893
|
+
>>> comp_name = ['g1','g2','g3'] # name of synchronous machines
|
|
894
|
+
>>> ram.getPrmNames(comp_type,comp_name)
|
|
895
|
+
|
|
896
|
+
"""
|
|
897
|
+
comp_type = wrapToList(comp_type)
|
|
898
|
+
comp_name = wrapToList(comp_name)
|
|
899
|
+
|
|
900
|
+
if not (len(comp_type) == len(comp_name)):
|
|
901
|
+
raise ValueError('RAMSES: Function getPrmNames failed because the lists are not equal!')
|
|
902
|
+
prm_names = []
|
|
903
|
+
mxprm = self._ramseslib.get_mxprm() # get maximum number of parameters in any model
|
|
904
|
+
for a, b in zip(comp_type, comp_name):
|
|
905
|
+
string_buffer = ctypes.create_string_buffer(11 * mxprm)
|
|
906
|
+
retval = self._ramseslib.get_comp_prm_names(a.encode('utf-8'), b.encode('utf-8'), ctypes.c_int(mxprm),
|
|
907
|
+
string_buffer)
|
|
908
|
+
if retval != 0:
|
|
909
|
+
raise RAMSESError(
|
|
910
|
+
'RAMSES: Function getPrm(%s,%s) failed with the flag %i. Does the parameter or equipment exist?'
|
|
911
|
+
% (a, b, retval))
|
|
912
|
+
decodedstring = string_buffer.value.decode()
|
|
913
|
+
prm_names.append(decodedstring.split())
|
|
914
|
+
if len(prm_names) == 1:
|
|
915
|
+
return prm_names[0]
|
|
916
|
+
else:
|
|
917
|
+
return prm_names
|
|
918
|
+
|
|
919
|
+
def getObs(self, comp_type, comp_name, obs_name):
|
|
920
|
+
"""Get the value of a named observable.
|
|
921
|
+
|
|
922
|
+
:param comp_type: the types of components ('EXC','TOR','INJ','TWOP','DCTL','SYN')
|
|
923
|
+
:type comp_type: list of str
|
|
924
|
+
:param comp_name: the names of components
|
|
925
|
+
:type comp_name: list of str
|
|
926
|
+
:param obs_name: the names of observables
|
|
927
|
+
:type obs_name: list of str
|
|
928
|
+
|
|
929
|
+
:returns: list of observable values
|
|
930
|
+
:rtype: list of floats
|
|
931
|
+
|
|
932
|
+
.. note::
|
|
933
|
+
|
|
934
|
+
For the synchronous generator ('SYN') the accepted obs_name values are
|
|
935
|
+
- 'P': Active power (MW)
|
|
936
|
+
- 'Q': Reactive power (MVAr)
|
|
937
|
+
- ``'Omega'``: Machine speed (pu)
|
|
938
|
+
- 'S': Apparent power (MVA)
|
|
939
|
+
- 'SNOM': Nominal apparent power (MVA)
|
|
940
|
+
- 'PNOM': Nominal active power (MW)
|
|
941
|
+
|
|
942
|
+
:Example:
|
|
943
|
+
|
|
944
|
+
>>> import stepss
|
|
945
|
+
>>> ram = stepss.sim()
|
|
946
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
947
|
+
>>> ram.execSim(case, 10.0) # simulate until 10 seconds and pause
|
|
948
|
+
>>> comp_type = ['INJ','EXC','TOR']
|
|
949
|
+
>>> comp_name = ['L_11','g2','g3']
|
|
950
|
+
>>> obs_name = ['P','vf','Pm']
|
|
951
|
+
>>> ram.getObs(comp_type,comp_name, obs_name)
|
|
952
|
+
[199.73704732259995,
|
|
953
|
+
1.3372945282585218,
|
|
954
|
+
0.816671075203357]
|
|
955
|
+
|
|
956
|
+
"""
|
|
957
|
+
comp_type = wrapToList(comp_type)
|
|
958
|
+
comp_name = wrapToList(comp_name)
|
|
959
|
+
obs_name = wrapToList(obs_name)
|
|
960
|
+
|
|
961
|
+
if not (len(comp_type) == len(comp_name) == len(obs_name)):
|
|
962
|
+
raise ValueError('RAMSES: Function getObs failed because the lists are not equal!')
|
|
963
|
+
obs_values = []
|
|
964
|
+
for a, b, c in zip(comp_type, comp_name, obs_name):
|
|
965
|
+
obs_value = ctypes.c_double()
|
|
966
|
+
retval = self._ramseslib.get_named_obs(a.encode('utf-8'), b.encode('utf-8'), c.encode('utf-8'),
|
|
967
|
+
obs_value)
|
|
968
|
+
if retval != 0:
|
|
969
|
+
raise RAMSESError(
|
|
970
|
+
'RAMSES: Function getObs(%s,%s,%s) failed with the flag %i. Does the observable or equipment exist?'
|
|
971
|
+
% (a, b, c, retval))
|
|
972
|
+
obs_values.append(obs_value.value)
|
|
973
|
+
return obs_values
|
|
974
|
+
|
|
975
|
+
def pauseSim(self, t_pause):
|
|
976
|
+
"""Schedule a pause at *t_pause* seconds of simulated time.
|
|
977
|
+
|
|
978
|
+
The pause is registered in the solver's internal state and takes effect
|
|
979
|
+
during the next :meth:`execSim` or :meth:`contSim` call.
|
|
980
|
+
|
|
981
|
+
:param float t_pause: simulated time (seconds) at which to pause.
|
|
982
|
+
:returns: return value from the underlying RAMSES call (non-zero on error).
|
|
983
|
+
:rtype: int
|
|
984
|
+
|
|
985
|
+
:Example:
|
|
986
|
+
|
|
987
|
+
>>> import stepss
|
|
988
|
+
>>> ram = stepss.sim()
|
|
989
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
990
|
+
>>> ram.pauseSim(10.0)
|
|
991
|
+
>>> ram.execSim(case) # will pause at t=10 s
|
|
992
|
+
"""
|
|
993
|
+
return self._ramseslib.set_pause_time(t_pause)
|
|
994
|
+
|
|
995
|
+
def addDisturb(self, t_dist, disturb):
|
|
996
|
+
"""Add a new disturbance at a specific time. Follows the same structure as the disturbances in the dst files.
|
|
997
|
+
|
|
998
|
+
:param t_dist: time of the disturbance
|
|
999
|
+
:type t_dist: float
|
|
1000
|
+
:param disturb: description of disturbance
|
|
1001
|
+
:type disturb: str
|
|
1002
|
+
|
|
1003
|
+
:Example:
|
|
1004
|
+
|
|
1005
|
+
>>> import stepss
|
|
1006
|
+
>>> ram = stepss.sim()
|
|
1007
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
1008
|
+
>>> ram.execSim(case, 80.0) # simulate until 80 seconds and pause
|
|
1009
|
+
>>> ram.addDisturb(100.000, 'CHGPRM DCTL 1-1041 Vsetpt -0.05 0') # Decrease the setpoint of the DCTL by 0.015 pu, at t=100 s
|
|
1010
|
+
>>> ram.addDisturb(100.000, 'CHGPRM DCTL 2-1042 Vsetpt -0.05 0')
|
|
1011
|
+
>>> ram.addDisturb(100.000, 'CHGPRM DCTL 3-1043 Vsetpt -0.05 0')
|
|
1012
|
+
>>> ram.addDisturb(100.000, 'CHGPRM DCTL 4-1044 Vsetpt -0.05 0')
|
|
1013
|
+
>>> ram.addDisturb(100.000, 'CHGPRM DCTL 5-1045 Vsetpt -0.05 0')
|
|
1014
|
+
>>> ram.contSim(ram.getInfTime()) # continue the simulation
|
|
1015
|
+
"""
|
|
1016
|
+
return self._ramseslib.add_disturb(t_dist, disturb.encode('utf-8'))
|
|
1017
|
+
|
|
1018
|
+
def load_MDL(self, MDLName):
|
|
1019
|
+
"""Load an external shared library containing user-defined RAMSES models.
|
|
1020
|
+
|
|
1021
|
+
The library must expose the standard RAMSES user-model interface. Once
|
|
1022
|
+
loaded, the models it defines become available to the solver for the
|
|
1023
|
+
lifetime of this :class:`sim` instance.
|
|
1024
|
+
|
|
1025
|
+
:param str MDLName: path to the model library (``*.dll`` on Windows,
|
|
1026
|
+
``*.so`` on Linux). Use the current directory or
|
|
1027
|
+
an absolute path.
|
|
1028
|
+
:returns: return value from the underlying RAMSES call (non-zero on error).
|
|
1029
|
+
|
|
1030
|
+
:Example:
|
|
1031
|
+
|
|
1032
|
+
>>> import stepss
|
|
1033
|
+
>>> stepss.sim.load_MDL("MDLs.dll")
|
|
1034
|
+
>>> ram = stepss.sim()
|
|
1035
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
1036
|
+
>>> ram.execSim(case)
|
|
1037
|
+
"""
|
|
1038
|
+
return self._ramseslib.c_load_MDL(MDLName.encode('utf-8'))
|
|
1039
|
+
|
|
1040
|
+
def unload_MDL(self, MDLName):
|
|
1041
|
+
"""Unload a previously loaded user-model shared library.
|
|
1042
|
+
|
|
1043
|
+
:param str MDLName: path to the model library that was passed to
|
|
1044
|
+
:meth:`load_MDL`.
|
|
1045
|
+
:returns: return value from the underlying RAMSES call (non-zero on error).
|
|
1046
|
+
|
|
1047
|
+
:Example:
|
|
1048
|
+
|
|
1049
|
+
>>> import stepss
|
|
1050
|
+
>>> stepss.sim.load_MDL("MDLs.dll")
|
|
1051
|
+
>>> ram = stepss.sim()
|
|
1052
|
+
>>> case = stepss.cfg("cmd.txt")
|
|
1053
|
+
>>> ram.execSim(case)
|
|
1054
|
+
>>> stepss.sim.unload_MDL("MDLs.dll")
|
|
1055
|
+
"""
|
|
1056
|
+
return self._ramseslib.c_unload_MDL(MDLName.encode('utf-8'))
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
def get_MDL_no(self):
|
|
1060
|
+
"""Return the number of user-model libraries currently loaded.
|
|
1061
|
+
|
|
1062
|
+
:returns: count of loaded user-model libraries.
|
|
1063
|
+
:rtype: int
|
|
1064
|
+
|
|
1065
|
+
:Example:
|
|
1066
|
+
|
|
1067
|
+
>>> import stepss
|
|
1068
|
+
>>> stepss.sim.load_MDL("MDLs.dll")
|
|
1069
|
+
>>> stepss.sim.get_MDL_no()
|
|
1070
|
+
1
|
|
1071
|
+
"""
|
|
1072
|
+
return self._ramseslib.c_get_MDL_no()
|