ltbams 1.0.9__py3-none-any.whl → 1.0.10__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 (79) 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/model.py +1 -1
  7. ams/core/symprocessor.py +1 -1
  8. ams/extension/eva.py +1 -1
  9. ams/interface.py +40 -24
  10. ams/io/matpower.py +31 -17
  11. ams/io/psse.py +278 -1
  12. ams/main.py +2 -2
  13. ams/models/group.py +2 -1
  14. ams/models/static/pq.py +7 -3
  15. ams/opt/param.py +1 -2
  16. ams/routines/__init__.py +2 -3
  17. ams/routines/acopf.py +5 -108
  18. ams/routines/dcopf.py +8 -0
  19. ams/routines/dcpf.py +1 -1
  20. ams/routines/ed.py +4 -2
  21. ams/routines/grbopt.py +150 -0
  22. ams/routines/pflow.py +2 -2
  23. ams/routines/pypower.py +631 -0
  24. ams/routines/routine.py +4 -10
  25. ams/routines/uc.py +2 -2
  26. ams/shared.py +26 -43
  27. ams/system.py +118 -2
  28. docs/source/api.rst +2 -0
  29. docs/source/getting_started/install.rst +9 -6
  30. docs/source/images/dcopf_time.png +0 -0
  31. docs/source/images/educ_pie.png +0 -0
  32. docs/source/release-notes.rst +21 -47
  33. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/METADATA +87 -47
  34. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/RECORD +54 -71
  35. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/WHEEL +1 -1
  36. tests/test_1st_system.py +1 -1
  37. tests/test_case.py +14 -14
  38. tests/test_export_csv.py +1 -1
  39. tests/test_interface.py +24 -2
  40. tests/test_io.py +50 -0
  41. tests/test_omodel.py +1 -1
  42. tests/test_report.py +6 -6
  43. tests/test_routine.py +2 -2
  44. tests/test_rtn_acopf.py +75 -0
  45. tests/test_rtn_dcopf.py +1 -1
  46. tests/test_rtn_dcopf2.py +1 -1
  47. tests/test_rtn_ed.py +9 -9
  48. tests/test_rtn_opf.py +142 -0
  49. tests/test_rtn_pflow.py +0 -72
  50. tests/test_rtn_pypower.py +315 -0
  51. tests/test_rtn_rted.py +8 -8
  52. tests/test_rtn_uc.py +18 -18
  53. ams/pypower/__init__.py +0 -8
  54. ams/pypower/_compat.py +0 -9
  55. ams/pypower/core/__init__.py +0 -8
  56. ams/pypower/core/pips.py +0 -894
  57. ams/pypower/core/ppoption.py +0 -244
  58. ams/pypower/core/ppver.py +0 -18
  59. ams/pypower/core/solver.py +0 -2451
  60. ams/pypower/eps.py +0 -6
  61. ams/pypower/idx.py +0 -174
  62. ams/pypower/io.py +0 -604
  63. ams/pypower/make/__init__.py +0 -11
  64. ams/pypower/make/matrices.py +0 -665
  65. ams/pypower/make/pdv.py +0 -506
  66. ams/pypower/routines/__init__.py +0 -7
  67. ams/pypower/routines/cpf.py +0 -513
  68. ams/pypower/routines/cpf_callbacks.py +0 -114
  69. ams/pypower/routines/opf.py +0 -1803
  70. ams/pypower/routines/opffcns.py +0 -1946
  71. ams/pypower/routines/pflow.py +0 -852
  72. ams/pypower/toggle.py +0 -1098
  73. ams/pypower/utils.py +0 -293
  74. ams/routines/cpf.py +0 -65
  75. ams/routines/dcpf0.py +0 -196
  76. ams/routines/pflow0.py +0 -113
  77. tests/test_rtn_dcpf.py +0 -77
  78. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/entry_points.txt +0 -0
  79. {ltbams-1.0.9.dist-info → ltbams-1.0.10.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/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,6 +3,7 @@ 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
 
@@ -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
 
@@ -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
ams/io/psse.py CHANGED
@@ -3,4 +3,281 @@ PSS/E .raw reader for AMS.
3
3
  This module is the existing module in ``andes.io.psse``.
4
4
  """
5
5
 
6
- from andes.io.psse import (read, testlines) # NOQA
6
+ from andes.io.psse import testlines # NOQA
7
+ from andes.io.psse import read as ad_read
8
+ from andes.io.xlsx import confirm_overwrite
9
+ from andes.shared import rad2deg, pd
10
+
11
+ from ams import __version__ as version
12
+ from ams.shared import copyright_msg, nowarranty_msg, report_time
13
+
14
+
15
+ def read(system, file):
16
+ """
17
+ Read PSS/E RAW file v32/v33 formats.
18
+
19
+ Revised from ``andes.io.psse.read`` to complete model ``Zone`` when necessary.
20
+ """
21
+ ret = ad_read(system, file)
22
+ # Extract zone data
23
+ zone = system.Bus.zone.v
24
+ zone_map = {}
25
+
26
+ # Check if there are zones to process
27
+ # NOTE: since `Zone` and `Area` below to group `Collection`, we add
28
+ # numerical Zone idx after the last Area idx.
29
+ if zone:
30
+ n_zone = system.Area.n
31
+ for z in set(zone):
32
+ # Add new zone and update the mapping
33
+ z_new = system.add(
34
+ 'Zone',
35
+ param_dict=dict(idx=n_zone + 1, name=f'{n_zone + 1}')
36
+ )
37
+ zone_map[z] = z_new
38
+ n_zone += 1
39
+
40
+ # Update the zone values in the system
41
+ system.Bus.zone.v = [zone_map[z] for z in zone]
42
+ return ret
43
+
44
+
45
+ def write_raw(system, outfile: str, overwrite: bool = None):
46
+ """
47
+ Convert AMS system to PSS/E RAW file.
48
+
49
+ Parameters
50
+ ----------
51
+ system : System
52
+ The AMS system to be converted.
53
+ outfile : str
54
+ The output file path.
55
+ overwrite : bool, optional
56
+ If True, overwrite the file if it exists. If False, do not overwrite.
57
+ """
58
+ if not confirm_overwrite(outfile, overwrite=overwrite):
59
+ return False
60
+
61
+ mva = system.config.mva
62
+ freq = system.config.freq
63
+ name = system.files.name
64
+
65
+ with open(outfile, 'w') as f:
66
+ # PSS/E version and date
67
+ f.write(f"0, {mva:.2f}, 33, 0, 1, {freq:.2f} ")
68
+
69
+ f.write(f"/ PSS/E 33 RAW, {report_time}\n")
70
+ f.write(f"Created by AMS {version}\n")
71
+ f.write(f"{copyright_msg}\n")
72
+ f.write(f"{nowarranty_msg}\n")
73
+ f.write(f"{name.upper()} \n")
74
+
75
+ # --- Bus ---
76
+ bus = system.Bus.cache.df_in
77
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
78
+ # ID, Name, BaseKV, Type, Area, Zone, Owner, Vm, Va, Vmax, Vmin
79
+ # Define column widths for alignment
80
+ column_widths = [6, 8, 8, 2, 3, 3, 3, 8, 8, 8, 8]
81
+ bus_owner = {}
82
+ for i, o in enumerate(set(bus['owner'])):
83
+ bus_owner[o] = i + 1
84
+ for row in bus.itertuples():
85
+ # Prepare the data for each column
86
+ data = [
87
+ int(row.idx), row.name, row.Vn, int(row.type),
88
+ int(system.Collection.idx2uid(row.area) + 1),
89
+ int(system.Collection.idx2uid(row.zone) + 1),
90
+ int(bus_owner[row.owner]),
91
+ float(row.v0), float(row.a0 * rad2deg),
92
+ float(row.vmax), float(row.vmin)]
93
+ # Format each column with ',' as the delimiter
94
+ formatted_row = ",".join(
95
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
96
+ for value, width in zip(data, column_widths)
97
+ ) + "\n"
98
+ # Write the formatted row to the file
99
+ f.write(formatted_row)
100
+
101
+ # --- Load ---
102
+ f.write("0 / END OF BUS DATA, BEGIN LOAD DATA\n")
103
+ load = system.PQ.cache.df_in
104
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
105
+ # Bus, Id, Status, Area, Zone, PL(MW), QL (MW), IP, IQ, YP, YQ, Owner
106
+ # NOTE: load are converted to constant load, IP, IQ, YP, YQ are ignored by setting to 0
107
+ column_widths = [6, 8, 2, 3, 3, 10, 10, 2, 2, 10, 10, 3]
108
+ for row in load.itertuples():
109
+ # Prepare the data for each column
110
+ data = [
111
+ int(row.bus), # Bus number
112
+ int(system.PQ.idx2uid(row.idx) + 1), # Load ID (unique index + 1)
113
+ int(row.u), # Status
114
+ int(system.Collection.idx2uid(system.PQ.get('area', row.idx, 'v')) + 1), # Area number
115
+ int(system.Collection.idx2uid(system.PQ.get('zone', row.idx, 'v')) + 1), # Zone number
116
+ float(row.p0 * mva), # PL (MW)
117
+ float(row.q0 * mva), # QL (MVar)
118
+ 0.0, # IP (ignored, set to 0)
119
+ 0.0, # IQ (ignored, set to 0)
120
+ 0.0, # YP (ignored, set to 0)
121
+ 0.0, # YQ (ignored, set to 0)
122
+ int(bus_owner[row.owner]) # Owner
123
+ ]
124
+ # Format each column with ',' as the delimiter
125
+ formatted_row = ",".join(
126
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
127
+ for value, width in zip(data, column_widths)
128
+ ) + "\n"
129
+ # Write the formatted row to the file
130
+ f.write(formatted_row)
131
+
132
+ # --- Fixed Shunt ---
133
+ f.write("0 / END OF LOAD DATA, BEGIN FIXED SHUNT DATA\n")
134
+ shunt = system.Shunt.cache.df_in
135
+ # 0, 1, 2, 3, 4
136
+ # Bus, Id, Status, G (MW), B (Mvar)
137
+ # NOTE: ANDES parse v33 swshunt into fixed shunt
138
+ column_widths = [6, 8, 2, 10, 10]
139
+ for row in shunt.itertuples():
140
+ # Prepare the data for each column
141
+ data = [
142
+ int(row.bus), # Bus number
143
+ int(system.Shunt.idx2uid(row.idx) + 1), # Shunt ID (unique index + 1)
144
+ int(row.u), # Status
145
+ float(row.g * mva), # Conductance (MW at system base)
146
+ float(row.b * mva) # Susceptance (Mvar at system base)
147
+ ]
148
+
149
+ # Format each column with ',' as the delimiter
150
+ formatted_row = ",".join(
151
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
152
+ for value, width in zip(data, column_widths)
153
+ ) + "\n"
154
+ # Write the formatted row to the file
155
+ f.write(formatted_row)
156
+
157
+ # --- Generator ---
158
+ f.write("0 / END OF FIXED SHUNT DATA, BEGIN GENERATOR DATA\n")
159
+ pv = system.PV.cache.df_in
160
+ slack = system.Slack.cache.df_in
161
+
162
+ gen = pd.concat([pv, slack.drop(columns=['a0'])], axis=0)
163
+ gen["subidx"] = gen.groupby('bus').cumcount() + 1
164
+
165
+ column_widths = [6, 8, 2, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8]
166
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
167
+ # I, ID, PG, QG, QT, QB, VS, IREG, MBASE, ZR, ZX, RT, XT, GTAP, STAT,
168
+ # 15, 16, 17, 18, 19, ..., 26, 27
169
+ # RMPCT, PT, PB, O1, F1, ..., O4, F4, WMOD, WPF
170
+ # The columns above for v33 is different from the manual of v34.5, which includes two new columns:
171
+ # `NREG`` at 8 and `BSLOD` before `O1`
172
+ for row in gen.itertuples():
173
+ # Prepare the data for each column
174
+ data = [
175
+ int(row.bus), # I: Bus number
176
+ int(row.subidx), # ID: Generator ID (subindex)
177
+ float(row.p0 * mva), # PG: Generated MW
178
+ float(row.q0 * mva), # QG: Generated MVar
179
+ float(row.qmax * mva), # QT: Max Q (MVar)
180
+ float(row.qmin * mva), # QB: Min Q (MVar)
181
+ float(row.v0), # VS: Setpoint voltage (p.u.)
182
+ int(0), # IREG: Regulated bus (not tracked)
183
+ float(row.Sn if hasattr(row, 'Sn') else mva), # MBASE: Machine base MVA
184
+ float(row.ra), # ZR: Armature resistance (p.u.)
185
+ float(row.xs), # ZX: Synchronous reactance (p.u.)
186
+ float(0), # RT: Step-up transformer resistance (not tracked)
187
+ float(0), # XT: Step-up transformer reactance (not tracked)
188
+ int(0), # GTAP: Step-up transformer off-nominal turns ratio (not tracked)
189
+ int(row.u) # STAT: Status
190
+ ]
191
+ # Format each column with ',' as the delimiter
192
+ formatted_row = ",".join(
193
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
194
+ for value, width in zip(data, column_widths)
195
+ ) + "\n"
196
+ # Write the formatted row to the file
197
+ f.write(formatted_row)
198
+
199
+ # --- Line ---
200
+ f.write("0 / END OF GENERATOR DATA, BEGIN BRANCH DATA\n")
201
+ line = system.Line.cache.df_in
202
+ branch = line[line['trans'] == 0].reset_index(drop=True)
203
+ transf = line[line['trans'] == 1].reset_index(drop=True)
204
+ # 1) branch
205
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
206
+ # I, J, CKT, R, X, B, RATEA, RATEB, RATEC, GI, BI, GJ, BJ, ST, LEN, O1, F1, ..., O4, F4
207
+ column_widths = [6, 6, 4, 10, 10, 10, 8, 8, 8, 8, 8, 8, 8, 3, 8]
208
+ for row in branch.itertuples():
209
+ data = [
210
+ int(row.bus1), # I: From bus number
211
+ int(row.bus2), # J: To bus number
212
+ "'1'", # CKT: Circuit ID (default '1')
213
+ float(row.r), # R: Resistance (p.u.)
214
+ float(row.x), # X: Reactance (p.u.)
215
+ float(row.b), # B: Total line charging (p.u.)
216
+ float(row.rate_a), # RATEA: Rating A (MVA)
217
+ float(row.rate_b), # RATEB: Rating B (MVA)
218
+ float(row.rate_c), # RATEC: Rating C (MVA)
219
+ 0.0, # GI: From bus conductance (not tracked)
220
+ 0.0, # BI: From bus susceptance (not tracked)
221
+ 0.0, # GJ: To bus conductance (not tracked)
222
+ 0.0, # BJ: To bus susceptance (not tracked)
223
+ int(row.u), # ST: Status
224
+ 0.0 # LEN: Line length (not tracked)
225
+ # O1, F1, ..., O4, F4 omitted for v33
226
+ ]
227
+ formatted_row = ",".join(
228
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"{value:>{width}}"
229
+ for value, width in zip(data, column_widths)
230
+ ) + "\n"
231
+ f.write(formatted_row)
232
+ # 2) transformer
233
+ f.write("0 / END OF BRANCH DATA, BEGIN TRANSFORMER DATA\n")
234
+ for row in transf.itertuples():
235
+ # Map AMS Line (trans) fields to PSSE RAW transformer columns
236
+ # Only 2-winding transformers are supported here
237
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
238
+ # I, J, CKT, R, X, B, RATEA, RATEB, RATEC, GI, BI, GJ, BJ, ST, LEN
239
+ data = [
240
+ int(row.bus1), # I: From bus number
241
+ int(row.bus2), # J: To bus number
242
+ "'1'", # CKT: Circuit ID (default '1')
243
+ float(row.r), # R: Resistance (p.u.)
244
+ float(row.x), # X: Reactance (p.u.)
245
+ float(row.b), # B: Total line charging (p.u.)
246
+ float(row.rate_a), # RATEA: Rating A (MVA)
247
+ float(row.rate_b), # RATEB: Rating B (MVA)
248
+ float(row.rate_c), # RATEC: Rating C (MVA)
249
+ 0.0, # GI: From bus conductance (not tracked)
250
+ 0.0, # BI: From bus susceptance (not tracked)
251
+ 0.0, # GJ: To bus conductance (not tracked)
252
+ 0.0, # BJ: To bus susceptance (not tracked)
253
+ int(row.u), # ST: Status
254
+ 0.0 # LEN: Line length (not tracked)
255
+ # O1, F1, ..., O4, F4 omitted for v33
256
+ ]
257
+ formatted_row = ",".join(
258
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"{value:>{width}}"
259
+ for value, width in zip(data, column_widths)
260
+ ) + "\n"
261
+ f.write(formatted_row)
262
+
263
+ # --- Area ---
264
+ f.write("0 / END OF TRANSFORMER DATA, BEGIN AREA DATA\n")
265
+ area = system.Area.cache.df_in
266
+ for row in area.itertuples():
267
+ # PSSE expects: ID, ISW, PDES, PTOL, NAME
268
+ # Here, ISW, PDES, PTOL are set to 0 by default
269
+ f.write(f"{int(system.Area.idx2uid(row.idx) + 1):6d}, 0, 0.0, 0.0, '{row.name}'\n")
270
+
271
+ # --- Zone ---
272
+ f.write("0 / END OF AREA DATA, BEGIN ZONE DATA\n")
273
+ zone = system.Zone.cache.df_in
274
+ # 0, 1
275
+ # ID, Name
276
+ for row in zone.itertuples():
277
+ f.write(f"{int(system.Zone.idx2uid(row.idx) + 1):6d}, '{row.name}'\n")
278
+ f.write("0 / END OF ZONE DATA\n")
279
+
280
+ # End of file
281
+ f.write("Q\n")
282
+
283
+ return True
ams/main.py CHANGED
@@ -105,8 +105,8 @@ def config_logger(stream_level=logging.INFO, *,
105
105
 
106
106
  else:
107
107
  # update the handlers
108
- set_logger_level(logger, logging.StreamHandler, stream_level)
109
- set_logger_level(logger, logging.FileHandler, file_level)
108
+ set_logger_level(lg, logging.StreamHandler, stream_level)
109
+ set_logger_level(lg, logging.FileHandler, file_level)
110
110
 
111
111
  if not is_interactive():
112
112
  coloredlogs.install(logger=lg, level=stream_level, fmt=sh_formatter_str)
ams/models/group.py CHANGED
@@ -220,7 +220,8 @@ class StaticShunt(GroupBase):
220
220
  """
221
221
  Static shunt compensator group.
222
222
  """
223
- pass
223
+ def __init__(self):
224
+ super().__init__()
224
225
 
225
226
 
226
227
  class Information(GroupBase):
ams/models/static/pq.py CHANGED
@@ -53,9 +53,13 @@ class PQ(PQData, Model):
53
53
  q2z=r"\gamma_{q2z}",
54
54
  )
55
55
 
56
- self.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False,
57
- info='Retrieved area idx', vtype=str, default=None,
58
- )
59
56
  self.ctrl = NumParam(default=1,
60
57
  info="load controllability",
61
58
  tex_name=r'c_{trl}',)
59
+
60
+ self.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False,
61
+ info='Retrieved area idx', vtype=str, default=None,
62
+ )
63
+ self.zone = ExtParam(model='Bus', src='zone', indexer=self.bus, export=False,
64
+ info='Retrieved zone idx', vtype=str, default=None,
65
+ )
ams/opt/param.py CHANGED
@@ -9,10 +9,9 @@ import re
9
9
 
10
10
  import numpy as np
11
11
 
12
- from andes.core.common import Config
13
-
14
12
  import cvxpy as cp
15
13
 
14
+ from ams.core import Config
16
15
  from ams.shared import sps
17
16
 
18
17
  from ams.opt import OptzBase, ensure_symbols, ensure_mats_and_parsed
ams/routines/__init__.py CHANGED
@@ -8,7 +8,6 @@ from andes.utils.func import list_flatten
8
8
  all_routines = OrderedDict([
9
9
  ('dcpf', ['DCPF']),
10
10
  ('pflow', ['PFlow']),
11
- ('cpf', ['CPF']),
12
11
  ('acopf', ['ACOPF']),
13
12
  ('dcopf', ['DCOPF']),
14
13
  ('dcopf2', ['DCOPF2']),
@@ -16,8 +15,8 @@ all_routines = OrderedDict([
16
15
  ('rted', ['RTED', 'RTEDDG', 'RTEDES', 'RTEDVIS']),
17
16
  ('uc', ['UC', 'UCDG', 'UCES']),
18
17
  ('dopf', ['DOPF', 'DOPFVIS']),
19
- ('pflow0', ['PFlow0']),
20
- ('dcpf0', ['DCPF0']),
18
+ ('pypower', ['DCPF1', 'PFlow1', 'DCOPF1', 'ACOPF1']),
19
+ ('grbopt', ['OPF']),
21
20
  ])
22
21
 
23
22
  class_names = list_flatten(list(all_routines.values()))