ltbams 1.0.9__py3-none-any.whl → 1.0.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ams/__init__.py +0 -1
- ams/_version.py +3 -3
- ams/cases/5bus/pjm5bus_demo.json +1324 -0
- ams/core/__init__.py +1 -0
- ams/core/common.py +30 -0
- ams/core/documenter.py +130 -62
- ams/core/model.py +1 -1
- ams/core/symprocessor.py +1 -1
- ams/extension/eva.py +1 -1
- ams/interface.py +42 -26
- ams/io/matpower.py +31 -17
- ams/io/psse.py +278 -1
- ams/main.py +2 -2
- ams/models/group.py +2 -1
- ams/models/static/pq.py +7 -3
- ams/opt/param.py +1 -2
- ams/routines/__init__.py +2 -3
- ams/routines/acopf.py +5 -108
- ams/routines/dcopf.py +8 -0
- ams/routines/dcpf.py +1 -1
- ams/routines/ed.py +4 -2
- ams/routines/grbopt.py +155 -0
- ams/routines/pflow.py +2 -2
- ams/routines/pypower.py +622 -0
- ams/routines/routine.py +4 -10
- ams/routines/uc.py +2 -2
- ams/shared.py +26 -43
- ams/system.py +120 -4
- ams/utils/paths.py +3 -3
- docs/source/api.rst +2 -0
- docs/source/examples/index.rst +2 -1
- docs/source/genroutineref.py +1 -1
- docs/source/getting_started/install.rst +9 -6
- docs/source/images/dcopf_time.png +0 -0
- docs/source/images/educ_pie.png +0 -0
- docs/source/release-notes.rst +37 -57
- {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/METADATA +85 -48
- {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/RECORD +58 -75
- {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/WHEEL +1 -1
- tests/test_1st_system.py +2 -2
- tests/test_case.py +14 -14
- tests/test_export_csv.py +1 -1
- tests/test_interface.py +24 -2
- tests/test_io.py +50 -0
- tests/test_omodel.py +1 -1
- tests/test_report.py +6 -6
- tests/test_routine.py +2 -2
- tests/test_rtn_acopf.py +75 -0
- tests/test_rtn_dcopf.py +1 -1
- tests/test_rtn_dcopf2.py +1 -1
- tests/test_rtn_ed.py +9 -9
- tests/test_rtn_opf.py +142 -0
- tests/test_rtn_pflow.py +0 -72
- tests/test_rtn_pypower.py +315 -0
- tests/test_rtn_rted.py +8 -8
- tests/test_rtn_uc.py +18 -18
- ams/pypower/__init__.py +0 -8
- ams/pypower/_compat.py +0 -9
- ams/pypower/core/__init__.py +0 -8
- ams/pypower/core/pips.py +0 -894
- ams/pypower/core/ppoption.py +0 -244
- ams/pypower/core/ppver.py +0 -18
- ams/pypower/core/solver.py +0 -2451
- ams/pypower/eps.py +0 -6
- ams/pypower/idx.py +0 -174
- ams/pypower/io.py +0 -604
- ams/pypower/make/__init__.py +0 -11
- ams/pypower/make/matrices.py +0 -665
- ams/pypower/make/pdv.py +0 -506
- ams/pypower/routines/__init__.py +0 -7
- ams/pypower/routines/cpf.py +0 -513
- ams/pypower/routines/cpf_callbacks.py +0 -114
- ams/pypower/routines/opf.py +0 -1803
- ams/pypower/routines/opffcns.py +0 -1946
- ams/pypower/routines/pflow.py +0 -852
- ams/pypower/toggle.py +0 -1098
- ams/pypower/utils.py +0 -293
- ams/routines/cpf.py +0 -65
- ams/routines/dcpf0.py +0 -196
- ams/routines/pflow0.py +0 -113
- tests/test_rtn_dcpf.py +0 -77
- {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/entry_points.txt +0 -0
- {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/top_level.txt +0 -0
ams/core/__init__.py
CHANGED
ams/core/common.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from andes.core.common import Config as AndesConfig
|
4
|
+
|
5
|
+
logger = logging.getLogger(__name__)
|
6
|
+
|
7
|
+
|
8
|
+
class Config(AndesConfig):
|
9
|
+
"""
|
10
|
+
A class for storing configuration, can be used in system,
|
11
|
+
model, routine, and other modules.
|
12
|
+
|
13
|
+
Revised from `andes.core.common.Config`.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self, name, dct=None, **kwargs):
|
17
|
+
super().__init__(name, dct, **kwargs)
|
18
|
+
|
19
|
+
def update(self, dct: dict = None, **kwargs):
|
20
|
+
"""
|
21
|
+
Update the configuration from a file.
|
22
|
+
"""
|
23
|
+
if dct is not None:
|
24
|
+
kwargs.update(dct)
|
25
|
+
|
26
|
+
for key, val in kwargs.items():
|
27
|
+
self._set(key, val)
|
28
|
+
self._dict[key] = val
|
29
|
+
|
30
|
+
self.check()
|
ams/core/documenter.py
CHANGED
@@ -6,26 +6,12 @@ import re
|
|
6
6
|
|
7
7
|
import logging
|
8
8
|
from collections import OrderedDict
|
9
|
-
from andes.core.documenter import Documenter as andes_Documenter
|
10
9
|
from andes.utils.tab import make_doc_table, math_wrap
|
11
10
|
|
12
11
|
logger = logging.getLogger(__name__)
|
13
12
|
|
14
13
|
|
15
|
-
|
16
|
-
def wrapper(*args, **kwargs):
|
17
|
-
msg = f"Method `{func.__name__}` is included in ANDES Documenter but not supported in AMS Documenter."
|
18
|
-
logger.warning(msg)
|
19
|
-
return None
|
20
|
-
return wrapper
|
21
|
-
|
22
|
-
|
23
|
-
def disable_methods(methods):
|
24
|
-
for method in methods:
|
25
|
-
setattr(Documenter, method, disable_method(getattr(Documenter, method)))
|
26
|
-
|
27
|
-
|
28
|
-
class Documenter(andes_Documenter):
|
14
|
+
class Documenter:
|
29
15
|
"""
|
30
16
|
Helper class for documenting models.
|
31
17
|
|
@@ -37,16 +23,6 @@ class Documenter(andes_Documenter):
|
|
37
23
|
|
38
24
|
def __init__(self, parent):
|
39
25
|
self.parent = parent
|
40
|
-
self.system = parent.system
|
41
|
-
self.class_name = parent.class_name
|
42
|
-
self.config = parent.config
|
43
|
-
self.params = parent.params
|
44
|
-
self.algebs = parent.algebs
|
45
|
-
self.services = parent.services
|
46
|
-
|
47
|
-
func_to_disable = ['_init_doc', '_eq_doc',
|
48
|
-
'_discrete_doc', '_block_doc']
|
49
|
-
disable_methods(func_to_disable)
|
50
26
|
|
51
27
|
def get(self, max_width=78, export='plain'):
|
52
28
|
"""
|
@@ -68,14 +44,15 @@ class Documenter(andes_Documenter):
|
|
68
44
|
if export == 'rest':
|
69
45
|
max_width = 0
|
70
46
|
model_header = '-' * 80 + '\n'
|
71
|
-
out += f'.. _{self.class_name}:\n\n'
|
47
|
+
out += f'.. _{self.parent.class_name}:\n\n'
|
72
48
|
else:
|
73
49
|
model_header = ''
|
74
50
|
|
75
51
|
if export == 'rest':
|
76
|
-
out += model_header + f'{self.class_name}\n' + model_header
|
52
|
+
out += model_header + f'{self.parent.class_name}\n' + model_header
|
77
53
|
else:
|
78
|
-
out += model_header +
|
54
|
+
out += model_header + \
|
55
|
+
f'Model <{self.parent.class_name}> in Group <{self.parent.group}>\n' + model_header
|
79
56
|
|
80
57
|
if self.parent.__doc__ is not None:
|
81
58
|
out += inspect.cleandoc(self.parent.__doc__)
|
@@ -86,20 +63,35 @@ class Documenter(andes_Documenter):
|
|
86
63
|
out += self._var_doc(max_width=max_width, export=export)
|
87
64
|
out += self._service_doc(max_width=max_width, export=export)
|
88
65
|
# TODO: fix and add the config doc later on
|
89
|
-
# out += self.config.doc(max_width=max_width, export=export)
|
66
|
+
# out += self.parent.config.doc(max_width=max_width, export=export)
|
90
67
|
|
91
68
|
return out
|
92
69
|
|
93
70
|
def _var_doc(self, max_width=78, export='plain'):
|
71
|
+
"""
|
72
|
+
Export formatted model variable documentation as a string.
|
73
|
+
|
74
|
+
Parameters
|
75
|
+
----------
|
76
|
+
max_width : int, optional = 80
|
77
|
+
Maximum table width. If export format is ``rest`` it will be unlimited.
|
78
|
+
export : str, optional = 'plain'
|
79
|
+
Export format, 'plain' for plain text, 'rest' for restructuredText.
|
80
|
+
|
81
|
+
Returns
|
82
|
+
-------
|
83
|
+
str
|
84
|
+
Tabulated output in a string
|
85
|
+
"""
|
94
86
|
# variable documentation
|
95
|
-
if len(self.algebs) == 0:
|
87
|
+
if len(self.parent.algebs) == 0:
|
96
88
|
return ''
|
97
89
|
|
98
90
|
names, symbols, units = list(), list(), list()
|
99
91
|
info = list()
|
100
92
|
units_rest, ty = list(), list()
|
101
93
|
|
102
|
-
for p in self.algebs.values():
|
94
|
+
for p in self.parent.algebs.values():
|
103
95
|
names.append(p.name)
|
104
96
|
ty.append(p.class_name)
|
105
97
|
info.append(p.info if p.info else '')
|
@@ -110,7 +102,7 @@ class Documenter(andes_Documenter):
|
|
110
102
|
|
111
103
|
# replace with latex math expressions if export is ``rest``
|
112
104
|
if export == 'rest':
|
113
|
-
symbols = [item.tex_name for item in self.algebs.values()]
|
105
|
+
symbols = [item.tex_name for item in self.parent.algebs.values()]
|
114
106
|
symbols = math_wrap(symbols, export=export)
|
115
107
|
title = 'Variables\n---------'
|
116
108
|
|
@@ -132,14 +124,29 @@ class Documenter(andes_Documenter):
|
|
132
124
|
rest_dict=rest_dict)
|
133
125
|
|
134
126
|
def _service_doc(self, max_width=78, export='plain'):
|
135
|
-
|
127
|
+
"""
|
128
|
+
Export formatted model service documentation as a string.
|
129
|
+
|
130
|
+
Parameters
|
131
|
+
----------
|
132
|
+
max_width : int, optional = 80
|
133
|
+
Maximum table width. If export format is ``rest`` it will be unlimited.
|
134
|
+
export : str, optional = 'plain'
|
135
|
+
Export format, 'plain' for plain text, 'rest' for restructuredText.
|
136
|
+
|
137
|
+
Returns
|
138
|
+
-------
|
139
|
+
str
|
140
|
+
Tabulated output in a string
|
141
|
+
"""
|
142
|
+
if len(self.parent.services) == 0:
|
136
143
|
return ''
|
137
144
|
|
138
145
|
names, symbols = list(), list()
|
139
146
|
info = list()
|
140
147
|
class_names = list()
|
141
148
|
|
142
|
-
for p in self.services.values():
|
149
|
+
for p in self.parent.services.values():
|
143
150
|
names.append(p.name)
|
144
151
|
class_names.append(p.class_name)
|
145
152
|
info.append(p.info if p.info else '')
|
@@ -165,6 +172,74 @@ class Documenter(andes_Documenter):
|
|
165
172
|
plain_dict=plain_dict,
|
166
173
|
rest_dict=rest_dict)
|
167
174
|
|
175
|
+
def _param_doc(self, max_width=78, export='plain'):
|
176
|
+
"""
|
177
|
+
Export formatted model parameter documentation as a string.
|
178
|
+
|
179
|
+
Parameters
|
180
|
+
----------
|
181
|
+
max_width : int, optional = 80
|
182
|
+
Maximum table width. If export format is ``rest`` it will be unlimited.
|
183
|
+
|
184
|
+
export : str, optional = 'plain'
|
185
|
+
Export format, 'plain' for plain text, 'rest' for restructuredText.
|
186
|
+
|
187
|
+
Returns
|
188
|
+
-------
|
189
|
+
str
|
190
|
+
Tabulated output in a string
|
191
|
+
"""
|
192
|
+
if len(self.parent.params) == 0:
|
193
|
+
return ''
|
194
|
+
|
195
|
+
# prepare temporary lists
|
196
|
+
names, units, class_names = list(), list(), list()
|
197
|
+
info, defaults, properties = list(), list(), list()
|
198
|
+
units_rest = list()
|
199
|
+
|
200
|
+
for p in self.parent.params.values():
|
201
|
+
names.append(p.name)
|
202
|
+
class_names.append(p.class_name)
|
203
|
+
info.append(p.info if p.info else '')
|
204
|
+
defaults.append(p.default if p.default is not None else '')
|
205
|
+
units.append(f'{p.unit}' if p.unit else '')
|
206
|
+
units_rest.append(f'*{p.unit}*' if p.unit else '')
|
207
|
+
|
208
|
+
plist = []
|
209
|
+
for key, val in p.property.items():
|
210
|
+
if val is True:
|
211
|
+
plist.append(key)
|
212
|
+
properties.append(','.join(plist))
|
213
|
+
|
214
|
+
# symbols based on output format
|
215
|
+
if export == 'rest':
|
216
|
+
symbols = [item.tex_name for item in self.parent.params.values()]
|
217
|
+
symbols = math_wrap(symbols, export=export)
|
218
|
+
title = 'Parameters\n----------'
|
219
|
+
else:
|
220
|
+
symbols = [item.name for item in self.parent.params.values()]
|
221
|
+
title = 'Parameters'
|
222
|
+
|
223
|
+
plain_dict = OrderedDict([('Name', names),
|
224
|
+
('Description', info),
|
225
|
+
('Default', defaults),
|
226
|
+
('Unit', units),
|
227
|
+
('Properties', properties)])
|
228
|
+
|
229
|
+
rest_dict = OrderedDict([('Name', names),
|
230
|
+
('Symbol', symbols),
|
231
|
+
('Description', info),
|
232
|
+
('Default', defaults),
|
233
|
+
('Unit', units_rest),
|
234
|
+
('Properties', properties)])
|
235
|
+
|
236
|
+
# convert to rows and export as table
|
237
|
+
return make_doc_table(title=title,
|
238
|
+
max_width=max_width,
|
239
|
+
export=export,
|
240
|
+
plain_dict=plain_dict,
|
241
|
+
rest_dict=rest_dict)
|
242
|
+
|
168
243
|
|
169
244
|
class RDocumenter:
|
170
245
|
"""
|
@@ -172,20 +247,12 @@ class RDocumenter:
|
|
172
247
|
|
173
248
|
Parameters
|
174
249
|
----------
|
175
|
-
parent :
|
176
|
-
The `
|
250
|
+
parent : ams.core.routine.RoutineBase
|
251
|
+
The `RoutineBase` instance to document
|
177
252
|
"""
|
178
253
|
|
179
254
|
def __init__(self, parent):
|
180
255
|
self.parent = parent
|
181
|
-
self.system = parent.system
|
182
|
-
self.class_name = parent.class_name
|
183
|
-
self.config = parent.config
|
184
|
-
self.services = parent.services
|
185
|
-
self.rparams = parent.rparams
|
186
|
-
self.vars = parent.vars
|
187
|
-
self.constrs = parent.constrs
|
188
|
-
self.obj = parent.obj
|
189
256
|
|
190
257
|
def get(self, max_width=78, export='plain'):
|
191
258
|
"""
|
@@ -207,14 +274,15 @@ class RDocumenter:
|
|
207
274
|
if export == 'rest':
|
208
275
|
max_width = 0
|
209
276
|
model_header = '-' * 80 + '\n'
|
210
|
-
out += f'.. _{self.class_name}:\n\n'
|
277
|
+
out += f'.. _{self.parent.class_name}:\n\n'
|
211
278
|
else:
|
212
279
|
model_header = ''
|
213
280
|
|
214
281
|
if export == 'rest':
|
215
|
-
out += model_header + f'{self.class_name}\n' + model_header
|
282
|
+
out += model_header + f'{self.parent.class_name}\n' + model_header
|
216
283
|
else:
|
217
|
-
out += model_header +
|
284
|
+
out += model_header + \
|
285
|
+
f'Routine <{self.parent.class_name}> in Type <{self.parent.type}>\n' + model_header
|
218
286
|
|
219
287
|
if self.parent.__doc__ is not None:
|
220
288
|
out += inspect.cleandoc(self.parent.__doc__)
|
@@ -229,20 +297,20 @@ class RDocumenter:
|
|
229
297
|
out += self._exprc_doc(max_width=max_width, export=export)
|
230
298
|
out += self._service_doc(max_width=max_width, export=export)
|
231
299
|
out += self._param_doc(max_width=max_width, export=export)
|
232
|
-
out += self.config.doc(max_width=max_width, export=export)
|
300
|
+
out += self.parent.config.doc(max_width=max_width, export=export)
|
233
301
|
|
234
302
|
return out
|
235
303
|
|
236
304
|
def _service_doc(self, max_width=78, export='plain'):
|
237
305
|
# routine service documentation
|
238
|
-
if len(self.services) == 0:
|
306
|
+
if len(self.parent.services) == 0:
|
239
307
|
return ''
|
240
308
|
|
241
309
|
names, symbols = list(), list()
|
242
310
|
info = list()
|
243
311
|
class_names = list()
|
244
312
|
|
245
|
-
for p in self.services.values():
|
313
|
+
for p in self.parent.services.values():
|
246
314
|
names.append(p.name)
|
247
315
|
info.append(p.info if p.info else '')
|
248
316
|
class_names.append(p.class_name)
|
@@ -251,7 +319,7 @@ class RDocumenter:
|
|
251
319
|
|
252
320
|
# replace with latex math expressions if export is ``rest``
|
253
321
|
if export == 'rest':
|
254
|
-
symbols = [item.tex_name for item in self.services.values()]
|
322
|
+
symbols = [item.tex_name for item in self.parent.services.values()]
|
255
323
|
symbols = math_wrap(symbols, export=export)
|
256
324
|
title = 'Services\n---------'
|
257
325
|
|
@@ -272,13 +340,13 @@ class RDocumenter:
|
|
272
340
|
|
273
341
|
def _constr_doc(self, max_width=78, export='plain'):
|
274
342
|
# constraint documentation
|
275
|
-
if len(self.constrs) == 0:
|
343
|
+
if len(self.parent.constrs) == 0:
|
276
344
|
return ''
|
277
345
|
|
278
346
|
# prepare temporary lists
|
279
347
|
names, class_names, info = list(), list(), list()
|
280
348
|
|
281
|
-
for p in self.constrs.values():
|
349
|
+
for p in self.parent.constrs.values():
|
282
350
|
names.append(p.name)
|
283
351
|
class_names.append(p.class_name)
|
284
352
|
info.append(p.info if p.info else '')
|
@@ -286,7 +354,7 @@ class RDocumenter:
|
|
286
354
|
# expressions based on output format
|
287
355
|
expressions = []
|
288
356
|
if export == 'rest':
|
289
|
-
for p in self.constrs.values():
|
357
|
+
for p in self.parent.constrs.values():
|
290
358
|
expr = _tex_pre(self, p, self.parent.syms.tex_map)
|
291
359
|
if p.is_eq:
|
292
360
|
expr = f'{expr} = 0'
|
@@ -460,7 +528,7 @@ class RDocumenter:
|
|
460
528
|
# NOTE: this is for the optimization variables
|
461
529
|
# not the _var_doc for ANDES parameters
|
462
530
|
# variable documentation
|
463
|
-
if len(self.vars) == 0:
|
531
|
+
if len(self.parent.vars) == 0:
|
464
532
|
return ''
|
465
533
|
|
466
534
|
# prepare temporary lists
|
@@ -469,7 +537,7 @@ class RDocumenter:
|
|
469
537
|
sources = list()
|
470
538
|
units_rest = list()
|
471
539
|
|
472
|
-
for p in self.vars.values():
|
540
|
+
for p in self.parent.vars.values():
|
473
541
|
names.append(p.name)
|
474
542
|
class_names.append(p.class_name)
|
475
543
|
info.append(p.info if p.info else '')
|
@@ -493,11 +561,11 @@ class RDocumenter:
|
|
493
561
|
|
494
562
|
# symbols based on output format
|
495
563
|
if export == 'rest':
|
496
|
-
symbols = [item.tex_name for item in self.vars.values()]
|
564
|
+
symbols = [item.tex_name for item in self.parent.vars.values()]
|
497
565
|
symbols = math_wrap(symbols, export=export)
|
498
566
|
title = 'Vars\n----------------------------------'
|
499
567
|
else:
|
500
|
-
symbols = [item.name for item in self.vars.values()]
|
568
|
+
symbols = [item.name for item in self.parent.vars.values()]
|
501
569
|
title = 'Vars'
|
502
570
|
|
503
571
|
plain_dict = OrderedDict([('Name', names),
|
@@ -536,7 +604,7 @@ class RDocumenter:
|
|
536
604
|
str
|
537
605
|
Tabulated output in a string
|
538
606
|
"""
|
539
|
-
if len(self.rparams) == 0:
|
607
|
+
if len(self.parent.rparams) == 0:
|
540
608
|
return ''
|
541
609
|
|
542
610
|
# prepare temporary lists
|
@@ -545,7 +613,7 @@ class RDocumenter:
|
|
545
613
|
sources = list()
|
546
614
|
units_rest = list()
|
547
615
|
|
548
|
-
for p in self.rparams.values():
|
616
|
+
for p in self.parent.rparams.values():
|
549
617
|
names.append(p.name)
|
550
618
|
class_names.append(p.class_name)
|
551
619
|
info.append(p.info if p.info else '')
|
@@ -562,11 +630,11 @@ class RDocumenter:
|
|
562
630
|
|
563
631
|
# symbols based on output format
|
564
632
|
if export == 'rest':
|
565
|
-
symbols = [item.tex_name for item in self.rparams.values()]
|
633
|
+
symbols = [item.tex_name for item in self.parent.rparams.values()]
|
566
634
|
symbols = math_wrap(symbols, export=export)
|
567
635
|
title = 'Parameters\n----------------------------------'
|
568
636
|
else:
|
569
|
-
symbols = [item.name for item in self.rparams.values()]
|
637
|
+
symbols = [item.name for item in self.parent.rparams.values()]
|
570
638
|
title = 'Parameters'
|
571
639
|
|
572
640
|
plain_dict = OrderedDict([('Name', names),
|
ams/core/model.py
CHANGED
@@ -7,13 +7,13 @@ from collections import OrderedDict
|
|
7
7
|
from typing import Iterable
|
8
8
|
|
9
9
|
import numpy as np
|
10
|
-
from andes.core.common import Config
|
11
10
|
from andes.core.param import ExtParam
|
12
11
|
from andes.core.service import BaseService, BackRef
|
13
12
|
from andes.utils.func import list_flatten
|
14
13
|
|
15
14
|
from ams.core.documenter import Documenter
|
16
15
|
from ams.core.var import Algeb
|
16
|
+
from ams.core.common import Config
|
17
17
|
|
18
18
|
from ams.utils.misc import deprec_get_idx
|
19
19
|
|
ams/core/symprocessor.py
CHANGED
@@ -174,7 +174,7 @@ class SymProcessor:
|
|
174
174
|
# Constraints
|
175
175
|
# NOTE: constraints are included in sub_map for ExpressionCalc
|
176
176
|
# thus, they don't have the suffix `.v`
|
177
|
-
for cname,
|
177
|
+
for cname, _ in self.parent.constrs.items():
|
178
178
|
self.sub_map[rf"\b{cname}\b"] = f'self.rtn.{cname}.optz'
|
179
179
|
|
180
180
|
# store tex names defined in `self.config`
|
ams/extension/eva.py
CHANGED
@@ -17,7 +17,6 @@ from collections import OrderedDict
|
|
17
17
|
|
18
18
|
import scipy.stats as stats
|
19
19
|
|
20
|
-
from andes.core import Config
|
21
20
|
from andes.core.param import NumParam
|
22
21
|
from andes.core.model import ModelData
|
23
22
|
from andes.shared import np, pd
|
@@ -25,6 +24,7 @@ from andes.utils.misc import elapsed
|
|
25
24
|
|
26
25
|
from ams.core.model import Model
|
27
26
|
from ams.utils.paths import ams_root
|
27
|
+
from ams.core import Config
|
28
28
|
|
29
29
|
logger = logging.getLogger(__name__)
|
30
30
|
|
ams/interface.py
CHANGED
@@ -3,11 +3,12 @@ Module for interfacing ANDES
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import os
|
6
|
+
import json
|
6
7
|
import logging
|
7
8
|
from collections import OrderedDict, Counter
|
8
9
|
|
9
10
|
from andes.utils.misc import elapsed
|
10
|
-
from andes.system import System as
|
11
|
+
from andes.system import System as adSystem
|
11
12
|
|
12
13
|
from ams.utils import create_entry
|
13
14
|
from ams.io import input_formats
|
@@ -81,7 +82,8 @@ def sync_adsys(amsys, adsys):
|
|
81
82
|
try:
|
82
83
|
ad_mdl.set(src=param, attr='v', idx=idx,
|
83
84
|
value=am_mdl.get(src=param, attr='v', idx=idx))
|
84
|
-
except Exception:
|
85
|
+
except Exception as e:
|
86
|
+
logger.warning(f"Failed to sync parameter '{param}' for model '{mname}': {e}")
|
85
87
|
continue
|
86
88
|
return True
|
87
89
|
|
@@ -105,7 +107,7 @@ def to_andes_pflow(system, no_output=False, default_config=True, **kwargs):
|
|
105
107
|
Additional arguments to be passed to ``andes.system.System()``.
|
106
108
|
"""
|
107
109
|
|
108
|
-
adsys =
|
110
|
+
adsys = adSystem(no_outpu=no_output, default_config=default_config, **kwargs)
|
109
111
|
# FIXME: is there a systematic way to do this? Other config might be needed
|
110
112
|
adsys.config.freq = system.config.freq
|
111
113
|
adsys.config.mva = system.config.mva
|
@@ -242,24 +244,43 @@ def parse_addfile(adsys, amsys, addfile):
|
|
242
244
|
logger.debug('Addfile format guessed as %s.', key)
|
243
245
|
break
|
244
246
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
# if not add_parser.read_add(system, addfile):
|
250
|
-
# logger.error('Error parsing addfile "%s" with %s parser.', addfile, add_format)
|
251
|
-
return adsys
|
247
|
+
# FIXME: xlsx input file with dyr addfile result into KeyError: 'Toggle'
|
248
|
+
# add_parser = importlib.import_module('andes.io.' + add_format)
|
249
|
+
# if not add_parser.read_add(system, addfile):
|
250
|
+
# logger.error('Error parsing addfile "%s" with %s parser.', addfile, add_format)
|
252
251
|
|
253
252
|
# Try parsing the addfile
|
254
253
|
logger.info('Parsing additional file "%s"...', addfile)
|
255
254
|
|
256
|
-
|
255
|
+
if add_format == 'xlsx':
|
256
|
+
reads = pd.read_excel(addfile,
|
257
|
+
sheet_name=None,
|
258
|
+
index_col=0,
|
259
|
+
engine='openpyxl',
|
260
|
+
)
|
261
|
+
elif add_format == 'json':
|
262
|
+
if isinstance(addfile, str):
|
263
|
+
f = open(addfile, 'r')
|
264
|
+
else:
|
265
|
+
f = addfile
|
266
|
+
|
267
|
+
json_in = json.load(f)
|
268
|
+
|
269
|
+
if f is not addfile:
|
270
|
+
f.close()
|
271
|
+
|
272
|
+
reads = dict()
|
273
|
+
for keys, dct in json_in.items():
|
274
|
+
reads[keys] = pd.DataFrame(dct)
|
275
|
+
else:
|
276
|
+
logger.error("Unsupported addfile format, only 'xlsx' and 'json' formats are supported.")
|
277
|
+
return adsys
|
257
278
|
|
258
279
|
pflow_mdl = list(pflow_dict.keys())
|
259
280
|
|
260
281
|
pflow_mdls_overlap = []
|
261
282
|
for mdl_name in pflow_dict.keys():
|
262
|
-
if mdl_name in
|
283
|
+
if mdl_name in reads.keys():
|
263
284
|
pflow_mdls_overlap.append(mdl_name)
|
264
285
|
|
265
286
|
if len(pflow_mdls_overlap) > 0:
|
@@ -269,14 +290,10 @@ def parse_addfile(adsys, amsys, addfile):
|
|
269
290
|
|
270
291
|
pflow_mdl_nonempty = [mdl for mdl in pflow_mdl if amsys.models[mdl].n > 0]
|
271
292
|
logger.debug(f"Non-empty PFlow models: {pflow_mdl}")
|
272
|
-
pflow_df_models =
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
)
|
277
|
-
# drop rows that all nan
|
278
|
-
for name, df in pflow_df_models.items():
|
279
|
-
df.dropna(axis=0, how='all', inplace=True)
|
293
|
+
pflow_df_models = {}
|
294
|
+
for key, df in reads.items():
|
295
|
+
if key in pflow_mdl_nonempty:
|
296
|
+
pflow_df_models[key] = df.dropna(axis=0, how='all', inplace=False)
|
280
297
|
|
281
298
|
# collect idx_map if difference exists
|
282
299
|
idx_map = OrderedDict([])
|
@@ -290,13 +307,12 @@ def parse_addfile(adsys, amsys, addfile):
|
|
290
307
|
idx_map[name] = dict(zip(ad_idx, am_idx))
|
291
308
|
|
292
309
|
# --- dynamic models to be added ---
|
293
|
-
mdl_to_keep = list(set(
|
310
|
+
mdl_to_keep = list(set(reads.keys()) - set(pflow_mdl))
|
294
311
|
mdl_to_keep.sort(key=str.lower)
|
295
|
-
df_models =
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
)
|
312
|
+
df_models = {}
|
313
|
+
for key, df in reads.items():
|
314
|
+
if key in mdl_to_keep:
|
315
|
+
df_models[key] = df.dropna(axis=0, how='all', inplace=False)
|
300
316
|
|
301
317
|
# adjust models index
|
302
318
|
for name, df in df_models.items():
|
ams/io/matpower.py
CHANGED
@@ -188,6 +188,9 @@ def mpc2system(mpc: dict, system) -> bool:
|
|
188
188
|
Note that `mbase` in mpc is converted to `Sn`, but it is not actually used in
|
189
189
|
MATPOWER nor AMS.
|
190
190
|
|
191
|
+
In converted AMS system, StaticGen idxes are 1-based, while the sequence follow
|
192
|
+
the order of the original MATPOWER data.
|
193
|
+
|
191
194
|
Parameters
|
192
195
|
----------
|
193
196
|
system : ams.system.System
|
@@ -401,22 +404,27 @@ def mpc2system(mpc: dict, system) -> bool:
|
|
401
404
|
)
|
402
405
|
|
403
406
|
# --- Area ---
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
407
|
+
area = system.Bus.area.v
|
408
|
+
area_map = {}
|
409
|
+
if area:
|
410
|
+
for a in set(area):
|
411
|
+
a_new = system.add('Area',
|
412
|
+
param_dict=dict(idx=a, name=a))
|
413
|
+
area_map[a] = a_new
|
414
|
+
system.Bus.area.v = [area_map[a] for a in area]
|
411
415
|
|
412
416
|
# --- Zone ---
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
417
|
+
zone = system.Bus.zone.v
|
418
|
+
zone_map = {}
|
419
|
+
if zone:
|
420
|
+
n_zone = system.Area.n
|
421
|
+
for z in set(zone):
|
422
|
+
z_new = system.add('Zone',
|
423
|
+
param_dict=dict(idx=int(n_zone + 1),
|
424
|
+
name=f'{n_zone + 1}'))
|
425
|
+
zone_map[z] = z_new
|
426
|
+
n_zone += 1
|
427
|
+
system.Bus.zone.v = [zone_map[z] for z in zone]
|
420
428
|
|
421
429
|
return True
|
422
430
|
|
@@ -506,8 +514,9 @@ def system2mpc(system) -> dict:
|
|
506
514
|
# --- PQ ---
|
507
515
|
if system.PQ.n > 0:
|
508
516
|
pq_pos = system.Bus.idx2uid(system.PQ.bus.v)
|
509
|
-
|
510
|
-
bus[pq_pos,
|
517
|
+
u = system.PQ.u.v
|
518
|
+
bus[pq_pos, 2] = u * system.PQ.p0.v * base_mva
|
519
|
+
bus[pq_pos, 3] = u * system.PQ.q0.v * base_mva
|
511
520
|
|
512
521
|
# --- Shunt ---
|
513
522
|
if system.Shunt.n > 0:
|
@@ -695,11 +704,16 @@ def mpc2m(mpc: dict, outfile: str) -> str:
|
|
695
704
|
|
696
705
|
def write(system, outfile: str, overwrite: bool = None) -> bool:
|
697
706
|
"""
|
698
|
-
Export an AMS system to a MATPOWER
|
707
|
+
Export an AMS system to a MATPOWER M-file.
|
699
708
|
|
700
709
|
This function converts an AMS system object into a MATPOWER-compatible
|
701
710
|
mpc dictionary and writes it to a specified output file in MATPOWER format.
|
702
711
|
|
712
|
+
In the converted MPC, the indices of area (bus[:, 6]) and zone (bus[:, 10])
|
713
|
+
may differ from the original MPC. However, the mapping relationship is preserved.
|
714
|
+
For example, if the original MPC numbers areas starting from 1, the converted
|
715
|
+
MPC may number them starting from 0.
|
716
|
+
|
703
717
|
Parameters
|
704
718
|
----------
|
705
719
|
system : ams.system.System
|