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.
Files changed (83) hide show
  1. ams/__init__.py +0 -1
  2. ams/_version.py +3 -3
  3. ams/cases/5bus/pjm5bus_demo.json +1324 -0
  4. ams/core/__init__.py +1 -0
  5. ams/core/common.py +30 -0
  6. ams/core/documenter.py +130 -62
  7. ams/core/model.py +1 -1
  8. ams/core/symprocessor.py +1 -1
  9. ams/extension/eva.py +1 -1
  10. ams/interface.py +42 -26
  11. ams/io/matpower.py +31 -17
  12. ams/io/psse.py +278 -1
  13. ams/main.py +2 -2
  14. ams/models/group.py +2 -1
  15. ams/models/static/pq.py +7 -3
  16. ams/opt/param.py +1 -2
  17. ams/routines/__init__.py +2 -3
  18. ams/routines/acopf.py +5 -108
  19. ams/routines/dcopf.py +8 -0
  20. ams/routines/dcpf.py +1 -1
  21. ams/routines/ed.py +4 -2
  22. ams/routines/grbopt.py +155 -0
  23. ams/routines/pflow.py +2 -2
  24. ams/routines/pypower.py +622 -0
  25. ams/routines/routine.py +4 -10
  26. ams/routines/uc.py +2 -2
  27. ams/shared.py +26 -43
  28. ams/system.py +120 -4
  29. ams/utils/paths.py +3 -3
  30. docs/source/api.rst +2 -0
  31. docs/source/examples/index.rst +2 -1
  32. docs/source/genroutineref.py +1 -1
  33. docs/source/getting_started/install.rst +9 -6
  34. docs/source/images/dcopf_time.png +0 -0
  35. docs/source/images/educ_pie.png +0 -0
  36. docs/source/release-notes.rst +37 -57
  37. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/METADATA +85 -48
  38. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/RECORD +58 -75
  39. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/WHEEL +1 -1
  40. tests/test_1st_system.py +2 -2
  41. tests/test_case.py +14 -14
  42. tests/test_export_csv.py +1 -1
  43. tests/test_interface.py +24 -2
  44. tests/test_io.py +50 -0
  45. tests/test_omodel.py +1 -1
  46. tests/test_report.py +6 -6
  47. tests/test_routine.py +2 -2
  48. tests/test_rtn_acopf.py +75 -0
  49. tests/test_rtn_dcopf.py +1 -1
  50. tests/test_rtn_dcopf2.py +1 -1
  51. tests/test_rtn_ed.py +9 -9
  52. tests/test_rtn_opf.py +142 -0
  53. tests/test_rtn_pflow.py +0 -72
  54. tests/test_rtn_pypower.py +315 -0
  55. tests/test_rtn_rted.py +8 -8
  56. tests/test_rtn_uc.py +18 -18
  57. ams/pypower/__init__.py +0 -8
  58. ams/pypower/_compat.py +0 -9
  59. ams/pypower/core/__init__.py +0 -8
  60. ams/pypower/core/pips.py +0 -894
  61. ams/pypower/core/ppoption.py +0 -244
  62. ams/pypower/core/ppver.py +0 -18
  63. ams/pypower/core/solver.py +0 -2451
  64. ams/pypower/eps.py +0 -6
  65. ams/pypower/idx.py +0 -174
  66. ams/pypower/io.py +0 -604
  67. ams/pypower/make/__init__.py +0 -11
  68. ams/pypower/make/matrices.py +0 -665
  69. ams/pypower/make/pdv.py +0 -506
  70. ams/pypower/routines/__init__.py +0 -7
  71. ams/pypower/routines/cpf.py +0 -513
  72. ams/pypower/routines/cpf_callbacks.py +0 -114
  73. ams/pypower/routines/opf.py +0 -1803
  74. ams/pypower/routines/opffcns.py +0 -1946
  75. ams/pypower/routines/pflow.py +0 -852
  76. ams/pypower/toggle.py +0 -1098
  77. ams/pypower/utils.py +0 -293
  78. ams/routines/cpf.py +0 -65
  79. ams/routines/dcpf0.py +0 -196
  80. ams/routines/pflow0.py +0 -113
  81. tests/test_rtn_dcpf.py +0 -77
  82. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/entry_points.txt +0 -0
  83. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/top_level.txt +0 -0
ams/core/__init__.py CHANGED
@@ -1,2 +1,3 @@
1
1
  from ams.core.model import Model # NOQA
2
2
  from ams.core.var import Algeb # NOQA
3
+ from ams.core.common import Config # NOQA
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
- def disable_method(func):
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 + f'Model <{self.class_name}> in Group <{self.parent.group}>\n' + 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
- if len(self.services) == 0:
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 : Model
176
- The `Model` instance to document
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 + f'Routine <{self.class_name}> in Type <{self.parent.type}>\n' + 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, constraint in self.parent.constrs.items():
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 andes_System
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 = andes_System(no_outpu=no_output, default_config=default_config, **kwargs)
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
- if key != 'xlsx':
246
- logger.error('Addfile format "%s" is not supported yet.', add_format)
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)
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
- reader = pd.ExcelFile(addfile)
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 reader.sheet_names:
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 = pd.read_excel(addfile,
273
- sheet_name=pflow_mdl_nonempty,
274
- index_col=0,
275
- engine='openpyxl',
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(reader.sheet_names) - set(pflow_mdl))
310
+ mdl_to_keep = list(set(reads.keys()) - set(pflow_mdl))
294
311
  mdl_to_keep.sort(key=str.lower)
295
- df_models = pd.read_excel(addfile,
296
- sheet_name=mdl_to_keep,
297
- index_col=0,
298
- engine='openpyxl',
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
- area_id = np.unique(system.Bus.area.v).astype(int)
405
- for area in area_id:
406
- area_idx = f'AREA_{area}'
407
- system.add('Area', idx=area_idx, name=area)
408
- bus_area = system.Bus.area.v
409
- bus_area = [f'AREA_{int(area)}' for area in bus_area]
410
- system.Bus.area.v = bus_area
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
- zone_id = np.unique(system.Bus.zone.v).astype(int)
414
- for zone in zone_id:
415
- zone_idx = f'ZONE_{zone}'
416
- system.add('Zone', idx=zone_idx, name=zone)
417
- bus_zone = system.Bus.zone.v
418
- bus_zone = [f'ZONE_{int(zone)}' for zone in bus_zone]
419
- system.Bus.zone.v = bus_zone
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
- bus[pq_pos, 2] = system.PQ.p0.v * base_mva
510
- bus[pq_pos, 3] = system.PQ.q0.v * base_mva
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 mpc file.
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