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.
- 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/model.py +1 -1
- ams/core/symprocessor.py +1 -1
- ams/extension/eva.py +1 -1
- ams/interface.py +40 -24
- 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 +150 -0
- ams/routines/pflow.py +2 -2
- ams/routines/pypower.py +631 -0
- ams/routines/routine.py +4 -10
- ams/routines/uc.py +2 -2
- ams/shared.py +26 -43
- ams/system.py +118 -2
- docs/source/api.rst +2 -0
- 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 +21 -47
- {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/METADATA +87 -47
- {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/RECORD +54 -71
- {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/WHEEL +1 -1
- tests/test_1st_system.py +1 -1
- 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.10.dist-info}/entry_points.txt +0 -0
- {ltbams-1.0.9.dist-info → ltbams-1.0.10.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/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,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
|
-
|
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
|
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
|
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(
|
109
|
-
set_logger_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
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
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
|
-
('
|
20
|
-
('
|
18
|
+
('pypower', ['DCPF1', 'PFlow1', 'DCOPF1', 'ACOPF1']),
|
19
|
+
('grbopt', ['OPF']),
|
21
20
|
])
|
22
21
|
|
23
22
|
class_names = list_flatten(list(all_routines.values()))
|