ltbams 1.0.8__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 +192 -26
- ams/io/psse.py +278 -1
- ams/io/pypower.py +14 -0
- ams/main.py +2 -2
- ams/models/group.py +2 -70
- ams/models/static/pq.py +7 -3
- ams/opt/param.py +1 -2
- ams/report.py +3 -4
- 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 +30 -44
- ams/system.py +118 -2
- docs/source/api.rst +2 -0
- docs/source/getting_started/formats/matpower.rst +135 -0
- docs/source/getting_started/formats/pypower.rst +1 -2
- 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 +29 -47
- {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/METADATA +87 -47
- {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/RECORD +58 -75
- {ltbams-1.0.8.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 +125 -1
- 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.8.dist-info → ltbams-1.0.10.dist-info}/entry_points.txt +0 -0
- {ltbams-1.0.8.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
@@ -6,8 +6,12 @@ import re
|
|
6
6
|
import numpy as np
|
7
7
|
|
8
8
|
from andes.io import read_file_like
|
9
|
+
from andes.io.xlsx import confirm_overwrite
|
9
10
|
from andes.shared import deg2rad, rad2deg
|
10
11
|
|
12
|
+
from ams import __version__ as version
|
13
|
+
from ams.shared import copyright_msg, nowarranty_msg, report_time
|
14
|
+
|
11
15
|
logger = logging.getLogger(__name__)
|
12
16
|
|
13
17
|
|
@@ -184,6 +188,9 @@ def mpc2system(mpc: dict, system) -> bool:
|
|
184
188
|
Note that `mbase` in mpc is converted to `Sn`, but it is not actually used in
|
185
189
|
MATPOWER nor AMS.
|
186
190
|
|
191
|
+
In converted AMS system, StaticGen idxes are 1-based, while the sequence follow
|
192
|
+
the order of the original MATPOWER data.
|
193
|
+
|
187
194
|
Parameters
|
188
195
|
----------
|
189
196
|
system : ams.system.System
|
@@ -396,14 +403,29 @@ def mpc2system(mpc: dict, system) -> bool:
|
|
396
403
|
c2=c2, c1=c1, c0=c0
|
397
404
|
)
|
398
405
|
|
406
|
+
# --- 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]
|
415
|
+
|
399
416
|
# --- Zone ---
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
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]
|
428
|
+
|
407
429
|
return True
|
408
430
|
|
409
431
|
|
@@ -429,25 +451,29 @@ def _get_bus_id_caller(bus):
|
|
429
451
|
|
430
452
|
def system2mpc(system) -> dict:
|
431
453
|
"""
|
432
|
-
Convert
|
454
|
+
Convert a **setup** AMS system to a MATPOWER mpc dictionary.
|
455
|
+
|
456
|
+
This function is revised from ``andes.io.matpower.system2mpc``.
|
433
457
|
|
434
|
-
In the ``gen`` section, slack generators
|
458
|
+
In the ``gen`` section, slack generators are listed before PV generators.
|
435
459
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
460
|
+
In the converted MPC, the indices of area (bus[:, 6]) and zone (bus[:, 10])
|
461
|
+
may differ from the original MPC. However, the mapping relationship is preserved.
|
462
|
+
For example, if the original MPC numbers areas starting from 1, the converted
|
463
|
+
MPC may number them starting from 0.
|
464
|
+
|
465
|
+
The coefficients ``c2`` and ``c1`` in the generator cost data are scaled by
|
466
|
+
``base_mva`` to match MATPOWER's unit convention (MW).
|
441
467
|
|
442
468
|
Parameters
|
443
469
|
----------
|
444
470
|
system : ams.core.system.System
|
445
|
-
AMS system
|
471
|
+
The AMS system to be converted.
|
446
472
|
|
447
473
|
Returns
|
448
474
|
-------
|
449
|
-
mpc: dict
|
450
|
-
MATPOWER
|
475
|
+
mpc : dict
|
476
|
+
A dictionary in MATPOWER format representing the converted AMS system.
|
451
477
|
"""
|
452
478
|
|
453
479
|
mpc = dict(version='2',
|
@@ -458,12 +484,16 @@ def system2mpc(system) -> dict:
|
|
458
484
|
gencost=np.zeros((system.GCost.n, 7), dtype=np.float64),
|
459
485
|
)
|
460
486
|
|
487
|
+
if not system.is_setup:
|
488
|
+
logger.warning("System is not setup and will be setup now.")
|
489
|
+
system.setup()
|
490
|
+
|
461
491
|
if system.Bus.name.v is not None:
|
462
492
|
mpc['bus_name'] = system.Bus.name.v
|
463
493
|
|
464
494
|
base_mva = system.config.mva
|
465
495
|
|
466
|
-
# ---
|
496
|
+
# --- Bus ---
|
467
497
|
bus = mpc['bus']
|
468
498
|
gen = mpc['gen']
|
469
499
|
|
@@ -471,23 +501,22 @@ def system2mpc(system) -> dict:
|
|
471
501
|
|
472
502
|
bus[:, 0] = to_busid(system.Bus.idx.v)
|
473
503
|
bus[:, 1] = 1
|
504
|
+
if system.Area.n > 0:
|
505
|
+
bus[:, 6] = system.Area.idx2uid(system.Bus.area.v)
|
474
506
|
bus[:, 7] = system.Bus.v0.v
|
475
507
|
bus[:, 8] = system.Bus.a0.v * rad2deg
|
476
508
|
bus[:, 9] = system.Bus.Vn.v
|
509
|
+
if system.Zone.n > 0:
|
510
|
+
bus[:, 10] = system.Zone.idx2uid(system.Bus.zone.v)
|
477
511
|
bus[:, 11] = system.Bus.vmax.v
|
478
512
|
bus[:, 12] = system.Bus.vmin.v
|
479
513
|
|
480
|
-
# --- zone ---
|
481
|
-
ZONE_I = system.Zone.idx.v
|
482
|
-
if len(ZONE_I) > 0:
|
483
|
-
mapping = {busi0: i for i, busi0 in enumerate(ZONE_I)}
|
484
|
-
bus[:, 10] = np.array([mapping[busi0] for busi0 in system.Bus.zone.v])
|
485
|
-
|
486
514
|
# --- PQ ---
|
487
515
|
if system.PQ.n > 0:
|
488
516
|
pq_pos = system.Bus.idx2uid(system.PQ.bus.v)
|
489
|
-
|
490
|
-
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
|
491
520
|
|
492
521
|
# --- Shunt ---
|
493
522
|
if system.Shunt.n > 0:
|
@@ -568,4 +597,141 @@ def system2mpc(system) -> dict:
|
|
568
597
|
else:
|
569
598
|
mpc.pop('gencost')
|
570
599
|
|
600
|
+
# --- gentype ---
|
601
|
+
stg = system.StaticGen.get_all_idxes()
|
602
|
+
gentype = system.StaticGen.get(src='gentype', attr='v', idx=stg)
|
603
|
+
if any(gentype):
|
604
|
+
mpc['gentype'] = np.array(gentype)
|
605
|
+
|
606
|
+
# --- genfuel ---
|
607
|
+
genfuel = system.StaticGen.get(src='genfuel', attr='v', idx=stg)
|
608
|
+
if any(genfuel):
|
609
|
+
mpc['genfuel'] = np.array(genfuel)
|
610
|
+
|
611
|
+
# --- Bus Name ---
|
612
|
+
if any(system.Bus.name.v):
|
613
|
+
mpc['bus_name'] = np.array(system.Bus.name.v)
|
614
|
+
|
571
615
|
return mpc
|
616
|
+
|
617
|
+
|
618
|
+
def mpc2m(mpc: dict, outfile: str) -> str:
|
619
|
+
"""
|
620
|
+
Write a MATPOWER mpc dict to a M-file.
|
621
|
+
|
622
|
+
Parameters
|
623
|
+
----------
|
624
|
+
mpc : dict
|
625
|
+
MATPOWER mpc dictionary.
|
626
|
+
outfile : str
|
627
|
+
Path to the output M-file.
|
628
|
+
"""
|
629
|
+
with open(outfile, 'w') as f:
|
630
|
+
# Add version info
|
631
|
+
f.write(f"%% Converted by AMS {version}\n")
|
632
|
+
f.write(f"%% {copyright_msg}\n\n")
|
633
|
+
f.write(f"%% {nowarranty_msg}\n")
|
634
|
+
f.write(f"%% Convert time: {report_time}\n\n")
|
635
|
+
|
636
|
+
f.write("function mpc = mpc_case\n")
|
637
|
+
f.write("mpc.version = '2';\n\n")
|
638
|
+
|
639
|
+
# Write baseMVA
|
640
|
+
f.write(f"%% system MVA base\nmpc.baseMVA = {mpc['baseMVA']};\n\n")
|
641
|
+
|
642
|
+
# Write bus data
|
643
|
+
f.write("%% bus data\n")
|
644
|
+
f.write("%% bus_i type Pd Qd Gs Bs area Vm Va baseKV zone Vmax Vmin\n")
|
645
|
+
f.write("mpc.bus = [\n")
|
646
|
+
for row in mpc['bus']:
|
647
|
+
f.write(" " + "\t".join(f"{val:.6g}" for val in row) + ";\n")
|
648
|
+
f.write("];\n\n")
|
649
|
+
|
650
|
+
# Write generator data
|
651
|
+
f.write("%% generator data\n")
|
652
|
+
f.write("%% bus Pg Qg Qmax Qmin Vg mBase status Pmax Pmin\n")
|
653
|
+
f.write("%% Pc1 Pc2 Qc1min Qc1max Qc2min Qc2max ramp_agc ramp_10 ramp_30 ramp_q apf\n")
|
654
|
+
f.write("mpc.gen = [\n")
|
655
|
+
for row in mpc['gen']:
|
656
|
+
f.write(" " + "\t".join(f"{val:.6g}" for val in row) + ";\n")
|
657
|
+
f.write("];\n\n")
|
658
|
+
|
659
|
+
# Write branch data
|
660
|
+
f.write("%% branch data\n")
|
661
|
+
f.write("%% fbus tbus r x b rateA rateB rateC ratio angle status angmin angmax PF QF PT QT\n")
|
662
|
+
f.write("mpc.branch = [\n")
|
663
|
+
for row in mpc['branch']:
|
664
|
+
f.write(" " + "\t".join(f"{val:.6g}" for val in row) + ";\n")
|
665
|
+
f.write("];\n\n")
|
666
|
+
|
667
|
+
# Write generator cost data if available
|
668
|
+
if 'gencost' in mpc:
|
669
|
+
f.write("%% generator cost data\n")
|
670
|
+
f.write("%% 1 startup shutdown n x1 y1 ... xn yn\n")
|
671
|
+
f.write("%% 2 startup shutdown n c(n-1) ... c0\n")
|
672
|
+
f.write("mpc.gencost = [\n")
|
673
|
+
for row in mpc['gencost']:
|
674
|
+
f.write(" " + "\t".join(f"{val:.6g}" for val in row) + ";\n")
|
675
|
+
f.write("];\n\n")
|
676
|
+
|
677
|
+
# Write bus names if available and not all None
|
678
|
+
if 'bus_name' in mpc and any(mpc['bus_name']):
|
679
|
+
f.write("%% bus names\n")
|
680
|
+
f.write("mpc.bus_name = {\n")
|
681
|
+
for name in mpc['bus_name']:
|
682
|
+
f.write(f" '{name}';\n")
|
683
|
+
f.write("};\n\n")
|
684
|
+
|
685
|
+
# Write generator types if available and not all None
|
686
|
+
if 'gentype' in mpc and any(mpc['gentype']):
|
687
|
+
f.write("%% generator types\n")
|
688
|
+
f.write("mpc.gentype = {\n")
|
689
|
+
for gentype in mpc['gentype']:
|
690
|
+
f.write(f" '{gentype}';\n")
|
691
|
+
f.write("};\n\n")
|
692
|
+
|
693
|
+
# Write generator fuels if available and not all None
|
694
|
+
if 'genfuel' in mpc and any(mpc['genfuel']):
|
695
|
+
f.write("%% generator fuels\n")
|
696
|
+
f.write("mpc.genfuel = {\n")
|
697
|
+
for genfuel in mpc['genfuel']:
|
698
|
+
f.write(f" '{genfuel}';\n")
|
699
|
+
f.write("};\n\n")
|
700
|
+
|
701
|
+
logger.info(f"Finished writing MATPOWER case to {outfile}")
|
702
|
+
return outfile
|
703
|
+
|
704
|
+
|
705
|
+
def write(system, outfile: str, overwrite: bool = None) -> bool:
|
706
|
+
"""
|
707
|
+
Export an AMS system to a MATPOWER M-file.
|
708
|
+
|
709
|
+
This function converts an AMS system object into a MATPOWER-compatible
|
710
|
+
mpc dictionary and writes it to a specified output file in MATPOWER format.
|
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
|
+
|
717
|
+
Parameters
|
718
|
+
----------
|
719
|
+
system : ams.system.System
|
720
|
+
A loaded system.
|
721
|
+
outfile : str
|
722
|
+
Path to the output file.
|
723
|
+
overwrite : bool, optional
|
724
|
+
None to prompt for overwrite selection; True to overwrite; False to not overwrite.
|
725
|
+
|
726
|
+
Returns
|
727
|
+
-------
|
728
|
+
bool
|
729
|
+
True if the file was successfully written, False otherwise.
|
730
|
+
"""
|
731
|
+
if not confirm_overwrite(outfile, overwrite=overwrite):
|
732
|
+
return False
|
733
|
+
|
734
|
+
mpc = system2mpc(system)
|
735
|
+
mpc2m(mpc, outfile)
|
736
|
+
logger.info('MATPOWER m case file written to "%s"', outfile)
|
737
|
+
return True
|