ltbams 1.0.11__py3-none-any.whl → 1.0.13__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/_version.py +3 -3
- ams/core/matprocessor.py +183 -118
- ams/io/matpower.py +55 -20
- ams/io/psse.py +4 -0
- ams/opt/exprcalc.py +11 -0
- ams/routines/grbopt.py +2 -0
- ams/routines/pypower.py +21 -4
- ams/routines/routine.py +127 -15
- ams/shared.py +30 -2
- ams/system.py +51 -3
- ams/utils/paths.py +64 -0
- docs/source/index.rst +4 -3
- docs/source/release-notes.rst +25 -10
- {ltbams-1.0.11.dist-info → ltbams-1.0.13.dist-info}/METADATA +4 -2
- {ltbams-1.0.11.dist-info → ltbams-1.0.13.dist-info}/RECORD +18 -46
- {ltbams-1.0.11.dist-info → ltbams-1.0.13.dist-info}/WHEEL +1 -1
- {ltbams-1.0.11.dist-info → ltbams-1.0.13.dist-info}/top_level.txt +0 -1
- tests/__init__.py +0 -0
- tests/test_1st_system.py +0 -64
- tests/test_addressing.py +0 -40
- tests/test_case.py +0 -301
- tests/test_cli.py +0 -34
- tests/test_export_csv.py +0 -89
- tests/test_group.py +0 -83
- tests/test_interface.py +0 -238
- tests/test_io.py +0 -180
- tests/test_jumper.py +0 -27
- tests/test_known_good.py +0 -267
- tests/test_matp.py +0 -437
- tests/test_model.py +0 -54
- tests/test_omodel.py +0 -119
- tests/test_paths.py +0 -22
- tests/test_report.py +0 -251
- tests/test_repr.py +0 -21
- tests/test_routine.py +0 -178
- tests/test_rtn_acopf.py +0 -75
- tests/test_rtn_dcopf.py +0 -101
- tests/test_rtn_dcopf2.py +0 -103
- tests/test_rtn_ed.py +0 -279
- tests/test_rtn_opf.py +0 -142
- tests/test_rtn_pflow.py +0 -147
- tests/test_rtn_pypower.py +0 -315
- tests/test_rtn_rted.py +0 -273
- tests/test_rtn_uc.py +0 -248
- tests/test_service.py +0 -73
- {ltbams-1.0.11.dist-info → ltbams-1.0.13.dist-info}/entry_points.txt +0 -0
ams/routines/pypower.py
CHANGED
@@ -32,6 +32,8 @@ class DCPF1(RoutineBase):
|
|
32
32
|
- This class does not implement the AMS-style DC power flow formulation.
|
33
33
|
- For detailed mathematical formulations and algorithmic details, refer to the
|
34
34
|
MATPOWER User's Manual, section on Power Flow.
|
35
|
+
|
36
|
+
.. versionadded:: 1.0.10
|
35
37
|
"""
|
36
38
|
|
37
39
|
def __init__(self, system, config):
|
@@ -131,6 +133,15 @@ class DCPF1(RoutineBase):
|
|
131
133
|
unit='p.u.',
|
132
134
|
name='vBus', tex_name=r'v_{Bus}',
|
133
135
|
src='v', model='Bus',)
|
136
|
+
# --- load ---
|
137
|
+
self.pd = RParam(info='active demand',
|
138
|
+
name='pd', tex_name=r'p_{d}',
|
139
|
+
model='StaticLoad', src='p0',
|
140
|
+
unit='p.u.',)
|
141
|
+
self.qd = RParam(info='reactive demand',
|
142
|
+
name='qd', tex_name=r'q_{d}',
|
143
|
+
model='StaticLoad', src='q0',
|
144
|
+
unit='p.u.',)
|
134
145
|
# --- gen ---
|
135
146
|
self.pg = Var(info='Gen active power',
|
136
147
|
unit='p.u.',
|
@@ -267,13 +278,13 @@ class DCPF1(RoutineBase):
|
|
267
278
|
return False
|
268
279
|
|
269
280
|
def _get_off_constrs(self):
|
270
|
-
|
281
|
+
logger.debug(f"{self.class_name} does not implement _get_off_constrs.")
|
271
282
|
|
272
283
|
def _data_check(self, info=True, **kwargs):
|
273
|
-
|
284
|
+
logger.debug(f"{self.class_name} does not implement _data_check.")
|
274
285
|
|
275
286
|
def update(self, params=None, build_mats=False, **kwargs):
|
276
|
-
|
287
|
+
logger.debug(f"{self.class_name} does not implement update.")
|
277
288
|
|
278
289
|
def enable(self, name):
|
279
290
|
raise NotImplementedError
|
@@ -282,7 +293,7 @@ class DCPF1(RoutineBase):
|
|
282
293
|
raise NotImplementedError
|
283
294
|
|
284
295
|
def _post_add_check(self):
|
285
|
-
|
296
|
+
logger.debug(f"{self.class_name} does not implement _post_add_check.")
|
286
297
|
|
287
298
|
def addRParam(self,
|
288
299
|
name: str,
|
@@ -353,6 +364,8 @@ class PFlow1(DCPF1):
|
|
353
364
|
MATPOWER User's Manual, section on Power Flow.
|
354
365
|
- Fast-Decoupled (XB version) and Fast-Decoupled (BX version) algorithms are
|
355
366
|
not fully supported yet.
|
367
|
+
|
368
|
+
.. versionadded:: 1.0.10
|
356
369
|
"""
|
357
370
|
|
358
371
|
def __init__(self, system, config):
|
@@ -427,6 +440,8 @@ class DCOPF1(DCPF1):
|
|
427
440
|
- For detailed mathematical formulations and algorithmic details, refer to the
|
428
441
|
MATPOWER User's Manual, section on Optimal Power Flow.
|
429
442
|
- Algorithms 400, 500, 600, and 700 are not fully supported yet.
|
443
|
+
|
444
|
+
.. versionadded:: 1.0.10
|
430
445
|
"""
|
431
446
|
|
432
447
|
def __init__(self, system, config):
|
@@ -577,6 +592,8 @@ class ACOPF1(DCOPF1):
|
|
577
592
|
- This class does not implement the AMS-style AC optimal power flow formulation.
|
578
593
|
- For detailed mathematical formulations and algorithmic details, refer to the
|
579
594
|
MATPOWER User's Manual, section on Optimal Power Flow.
|
595
|
+
|
596
|
+
.. versionadded:: 1.0.10
|
580
597
|
"""
|
581
598
|
|
582
599
|
def __init__(self, system, config):
|
ams/routines/routine.py
CHANGED
@@ -3,9 +3,9 @@ Module for routine data.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
-
import
|
7
|
-
from typing import Optional, Union, Type, Iterable, Dict
|
6
|
+
import json
|
8
7
|
from collections import OrderedDict
|
8
|
+
from typing import Optional, Union, Type, Iterable, Dict
|
9
9
|
|
10
10
|
import numpy as np
|
11
11
|
|
@@ -19,6 +19,8 @@ from ams.core.service import RBaseService, ValueService
|
|
19
19
|
from ams.opt import OModel
|
20
20
|
from ams.opt import Param, Var, Constraint, Objective, ExpressionCalc, Expression
|
21
21
|
|
22
|
+
from ams.utils.paths import get_export_path
|
23
|
+
|
22
24
|
from ams.shared import pd
|
23
25
|
|
24
26
|
logger = logging.getLogger(__name__)
|
@@ -430,6 +432,90 @@ class RoutineBase:
|
|
430
432
|
logger.warning(msg)
|
431
433
|
return False
|
432
434
|
|
435
|
+
def load_json(self, path):
|
436
|
+
"""
|
437
|
+
Load scheduling results from a json file.
|
438
|
+
|
439
|
+
Parameters
|
440
|
+
----------
|
441
|
+
path : str
|
442
|
+
Path of the json file to load.
|
443
|
+
|
444
|
+
Returns
|
445
|
+
-------
|
446
|
+
bool
|
447
|
+
True if the loading is successful, False otherwise.
|
448
|
+
"""
|
449
|
+
try:
|
450
|
+
with open(path, 'r') as f:
|
451
|
+
data = json.load(f)
|
452
|
+
except Exception as e:
|
453
|
+
logger.error(f"Failed to load JSON file: {e}")
|
454
|
+
return False
|
455
|
+
|
456
|
+
if not self.initialized:
|
457
|
+
self.init()
|
458
|
+
|
459
|
+
# Unpack variables and expressions from JSON
|
460
|
+
for group, group_data in data.items():
|
461
|
+
if not isinstance(group_data, dict):
|
462
|
+
continue
|
463
|
+
for key, values in group_data.items():
|
464
|
+
if key == 'idx':
|
465
|
+
continue
|
466
|
+
# Find the corresponding variable or expression
|
467
|
+
if key in self.vars:
|
468
|
+
var = self.vars[key]
|
469
|
+
# Assign values to the variable
|
470
|
+
try:
|
471
|
+
var.v = np.array(values)
|
472
|
+
except Exception as e:
|
473
|
+
logger.warning(f"Failed to assign values to var '{key}': {e}")
|
474
|
+
elif key in self.exprs:
|
475
|
+
continue
|
476
|
+
elif key in self.exprcs:
|
477
|
+
exprc = self.exprcs[key]
|
478
|
+
# Assign values to the expression calculation
|
479
|
+
try:
|
480
|
+
exprc.v = np.array(values)
|
481
|
+
except Exception as e:
|
482
|
+
logger.warning(f"Failed to assign values to exprc '{key}': {e}")
|
483
|
+
logger.info(f"Loaded results from {path}")
|
484
|
+
return True
|
485
|
+
|
486
|
+
def export_json(self, path=None):
|
487
|
+
"""
|
488
|
+
Export scheduling results to a json file.
|
489
|
+
|
490
|
+
Parameters
|
491
|
+
----------
|
492
|
+
path : str, optional
|
493
|
+
Path of the json file to export.
|
494
|
+
|
495
|
+
Returns
|
496
|
+
-------
|
497
|
+
str
|
498
|
+
The exported json file name
|
499
|
+
"""
|
500
|
+
if not self.converged:
|
501
|
+
logger.warning("Routine did not converge, aborting export.")
|
502
|
+
return None
|
503
|
+
|
504
|
+
path, file_name = get_export_path(self.system, self.class_name,
|
505
|
+
path=path, fmt='json')
|
506
|
+
|
507
|
+
data_dict = dict()
|
508
|
+
|
509
|
+
group_data(self, data_dict, self.vars, 'v')
|
510
|
+
group_data(self, data_dict, self.exprs, 'v')
|
511
|
+
group_data(self, data_dict, self.exprcs, 'v')
|
512
|
+
|
513
|
+
with open(path, 'w') as f:
|
514
|
+
json.dump(data_dict, f, indent=4,
|
515
|
+
default=lambda x: x.tolist() if isinstance(x, np.ndarray) else x)
|
516
|
+
|
517
|
+
return file_name
|
518
|
+
|
433
519
|
def export_csv(self, path=None):
|
434
520
|
"""
|
435
521
|
Export scheduling results to a csv file.
|
@@ -441,26 +527,20 @@ class RoutineBase:
|
|
441
527
|
|
442
528
|
Parameters
|
443
529
|
----------
|
444
|
-
path : str
|
445
|
-
|
530
|
+
path : str, optional
|
531
|
+
Path of the csv file to export.
|
446
532
|
|
447
533
|
Returns
|
448
534
|
-------
|
449
|
-
|
450
|
-
The
|
535
|
+
str
|
536
|
+
The exported csv file name
|
451
537
|
"""
|
452
538
|
if not self.converged:
|
453
539
|
logger.warning("Routine did not converge, aborting export.")
|
454
540
|
return None
|
455
541
|
|
456
|
-
|
457
|
-
|
458
|
-
logger.info("Input file name not detacted. Using `Untitled`.")
|
459
|
-
file_name = f'Untitled_{self.class_name}'
|
460
|
-
else:
|
461
|
-
file_name = os.path.splitext(self.system.files.fullname)[0]
|
462
|
-
file_name += f'_{self.class_name}'
|
463
|
-
path = os.path.join(os.getcwd(), file_name + '.csv')
|
542
|
+
path, file_name = get_export_path(self.system, self.class_name,
|
543
|
+
path=path, fmt='csv')
|
464
544
|
|
465
545
|
data_dict = initialize_data_dict(self)
|
466
546
|
|
@@ -473,7 +553,7 @@ class RoutineBase:
|
|
473
553
|
|
474
554
|
pd.DataFrame(data_dict).to_csv(path, index=False)
|
475
555
|
|
476
|
-
return file_name
|
556
|
+
return file_name
|
477
557
|
|
478
558
|
def summary(self, **kwargs):
|
479
559
|
"""
|
@@ -1027,3 +1107,35 @@ def collect_data(rtn: RoutineBase, data_dict: Dict, items: Dict, attr: str):
|
|
1027
1107
|
logger.debug(f"Error with collecting data for '{key}': {e}")
|
1028
1108
|
data_v = [np.nan] * len(idx_v)
|
1029
1109
|
data_dict.update(OrderedDict(zip([f'{key} {dev}' for dev in idx_v], data_v)))
|
1110
|
+
|
1111
|
+
|
1112
|
+
def group_data(rtn: RoutineBase, data_dict: Dict, items: Dict, attr: str):
|
1113
|
+
"""
|
1114
|
+
Collect data for export from grouped items.
|
1115
|
+
|
1116
|
+
Parameters
|
1117
|
+
----------
|
1118
|
+
rtn : ams.routines.routine.RoutineBase
|
1119
|
+
The routine to collect data from.
|
1120
|
+
data_dict : Dict
|
1121
|
+
The data dictionary to populate.
|
1122
|
+
items : dict
|
1123
|
+
Dictionary of items to collect data from.
|
1124
|
+
attr : str
|
1125
|
+
Attribute to collect data for.
|
1126
|
+
"""
|
1127
|
+
for key, item in items.items():
|
1128
|
+
if item.owner is None:
|
1129
|
+
continue
|
1130
|
+
if item.owner.class_name not in data_dict.keys():
|
1131
|
+
idx_v = item.get_all_idxes()
|
1132
|
+
data_dict[item.owner.class_name] = dict(idx=idx_v)
|
1133
|
+
else:
|
1134
|
+
idx_v = data_dict[item.owner.class_name]['idx']
|
1135
|
+
try:
|
1136
|
+
data_v = rtn.get(src=key, attr=attr, idx=idx_v,
|
1137
|
+
horizon=rtn.timeslot.v if hasattr(rtn, 'timeslot') else None)
|
1138
|
+
except Exception as e:
|
1139
|
+
logger.warning(f"Error with collecting data for '{key}': {e}")
|
1140
|
+
data_v = [np.nan] * item.owner.n
|
1141
|
+
data_dict[item.owner.class_name][key] = data_v
|
ams/shared.py
CHANGED
@@ -4,6 +4,7 @@ Shared constants and delayed imports.
|
|
4
4
|
This module is supplementary to the ``andes.shared`` module.
|
5
5
|
"""
|
6
6
|
import logging
|
7
|
+
import sys
|
7
8
|
import unittest
|
8
9
|
from functools import wraps
|
9
10
|
from time import strftime
|
@@ -24,6 +25,7 @@ ppoption = LazyImport('from pypower.ppoption import ppoption')
|
|
24
25
|
runpf = LazyImport('from pypower.runpf import runpf')
|
25
26
|
runopf = LazyImport('from pypower.runopf import runopf')
|
26
27
|
opf = LazyImport('from gurobi_optimods import opf')
|
28
|
+
tqdm = LazyImport('from tqdm.auto import tqdm')
|
27
29
|
|
28
30
|
# --- an empty ANDES system ---
|
29
31
|
empty_adsys = adSystem(autogen_stale=False)
|
@@ -118,7 +120,7 @@ def skip_unittest_without_PYPOWER(f):
|
|
118
120
|
try:
|
119
121
|
import pypower # NOQA
|
120
122
|
except ImportError:
|
121
|
-
raise unittest.SkipTest("PYPOWER is not
|
123
|
+
raise unittest.SkipTest("PYPOWER is not available.")
|
122
124
|
return f(*args, **kwargs)
|
123
125
|
return wrapper
|
124
126
|
|
@@ -131,6 +133,32 @@ def skip_unittest_without_gurobi_optimods(f):
|
|
131
133
|
try:
|
132
134
|
import gurobi_optimods # NOQA
|
133
135
|
except ImportError:
|
134
|
-
raise unittest.SkipTest("
|
136
|
+
raise unittest.SkipTest("gurobi_optimods is not available.")
|
135
137
|
return f(*args, **kwargs)
|
136
138
|
return wrapper
|
139
|
+
|
140
|
+
|
141
|
+
def _init_pbar(total, unit, no_tqdm):
|
142
|
+
"""Initializes and returns a tqdm progress bar."""
|
143
|
+
pbar = tqdm(total=total, unit=unit, ncols=80, ascii=True,
|
144
|
+
file=sys.stdout, disable=no_tqdm)
|
145
|
+
pbar.update(0)
|
146
|
+
return pbar
|
147
|
+
|
148
|
+
|
149
|
+
def _update_pbar(pbar, current, total):
|
150
|
+
"""Updates and closes the progress bar."""
|
151
|
+
perc = np.round(min((current / total) * 100, 100), 2)
|
152
|
+
if pbar.total is not None: # Check if pbar is still valid
|
153
|
+
last_pc = pbar.n / pbar.total * 100 # Get current percentage based on updated value
|
154
|
+
else:
|
155
|
+
last_pc = 0
|
156
|
+
|
157
|
+
perc_diff = perc - last_pc
|
158
|
+
if perc_diff >= 1:
|
159
|
+
pbar.update(perc_diff)
|
160
|
+
|
161
|
+
# Ensure pbar finishes at 100% and closes
|
162
|
+
if pbar.n < pbar.total: # Check if it's not already at total
|
163
|
+
pbar.update(pbar.total - pbar.n) # Update remaining
|
164
|
+
pbar.close()
|
ams/system.py
CHANGED
@@ -29,7 +29,7 @@ from ams.report import Report
|
|
29
29
|
from ams.shared import ad_dyn_models
|
30
30
|
|
31
31
|
from ams.io.matpower import system2mpc
|
32
|
-
from ams.io.matpower import write as
|
32
|
+
from ams.io.matpower import write as write_m
|
33
33
|
from ams.io.xlsx import write as write_xlsx
|
34
34
|
from ams.io.json import write as write_json
|
35
35
|
from ams.io.psse import write_raw
|
@@ -602,6 +602,7 @@ class System(adSystem):
|
|
602
602
|
**kwargs):
|
603
603
|
"""
|
604
604
|
Convert the AMS system to an ANDES system.
|
605
|
+
Wrapper method for `ams.interface.to_andes`.
|
605
606
|
|
606
607
|
A preferred dynamic system file to be added has following features:
|
607
608
|
1. The file contains both power flow and dynamic models.
|
@@ -718,17 +719,36 @@ class System(adSystem):
|
|
718
719
|
def to_mpc(self):
|
719
720
|
"""
|
720
721
|
Export an AMS system to a MATPOWER dict.
|
722
|
+
Wrapper method for `ams.io.matpower.system2mpc`.
|
721
723
|
|
722
724
|
Returns
|
723
725
|
-------
|
724
726
|
dict
|
725
727
|
A dictionary representing the MATPOWER case.
|
728
|
+
|
729
|
+
Notes
|
730
|
+
-----
|
731
|
+
- In the `gen` section, slack generators are listed before PV generators.
|
732
|
+
- For uncontrolled generators (`ctrl.v == 0`), their max and min power
|
733
|
+
limits are set to their initial power (`p0.v`) in the converted MPC.
|
734
|
+
- In the converted MPC, the indices of area (`bus[:, 6]`) and zone (`bus[:, 10]`)
|
735
|
+
may differ from the original MPC. However, the mapping relationship is preserved.
|
736
|
+
For example, if the original MPC numbers areas starting from 1, the converted
|
737
|
+
MPC may number them starting from 0.
|
738
|
+
- The coefficients `c2` and `c1` in the generator cost data are scaled by
|
739
|
+
`baseMVA`.
|
740
|
+
- Unlike the XLSX and JSON converters, this implementation uses value providers
|
741
|
+
(`v`) instead of vin. As a result, any changes made through `model.set` will be
|
742
|
+
reflected in the generated MPC.
|
743
|
+
|
744
|
+
.. versionadded:: 1.0.10
|
726
745
|
"""
|
727
746
|
return system2mpc(self)
|
728
747
|
|
729
748
|
def to_m(self, outfile: str, overwrite: bool = None):
|
730
749
|
"""
|
731
750
|
Export an AMS system to a MATPOWER M-file.
|
751
|
+
Wrapper method for `ams.io.matpower.write`.
|
732
752
|
|
733
753
|
Parameters
|
734
754
|
----------
|
@@ -736,12 +756,30 @@ class System(adSystem):
|
|
736
756
|
The output file name.
|
737
757
|
overwrite : bool, optional
|
738
758
|
If True, overwrite the existing file. Default is None.
|
739
|
-
|
740
|
-
|
759
|
+
|
760
|
+
Notes
|
761
|
+
-----
|
762
|
+
- In the `gen` section, slack generators are listed before PV generators.
|
763
|
+
- For uncontrolled generators (`ctrl.v == 0`), their max and min power
|
764
|
+
limits are set to their initial power (`p0.v`) in the converted MPC.
|
765
|
+
- In the converted MPC, the indices of area (`bus[:, 6]`) and zone (`bus[:, 10]`)
|
766
|
+
may differ from the original MPC. However, the mapping relationship is preserved.
|
767
|
+
For example, if the original MPC numbers areas starting from 1, the converted
|
768
|
+
MPC may number them starting from 0.
|
769
|
+
- The coefficients `c2` and `c1` in the generator cost data are scaled by
|
770
|
+
`baseMVA`.
|
771
|
+
- Unlike the XLSX and JSON converters, this implementation uses value providers
|
772
|
+
(`v`) instead of vin. As a result, any changes made through `model.set` will be
|
773
|
+
reflected in the generated MPC.
|
774
|
+
|
775
|
+
.. versionadded:: 1.0.10
|
776
|
+
"""
|
777
|
+
return write_m(self, outfile=outfile, overwrite=overwrite)
|
741
778
|
|
742
779
|
def to_xlsx(self, outfile: str, overwrite: bool = None):
|
743
780
|
"""
|
744
781
|
Export an AMS system to an Excel file.
|
782
|
+
Wrapper method for `ams.io.xlsx.write`.
|
745
783
|
|
746
784
|
Parameters
|
747
785
|
----------
|
@@ -749,12 +787,15 @@ class System(adSystem):
|
|
749
787
|
The output file name.
|
750
788
|
overwrite : bool, optional
|
751
789
|
If True, overwrite the existing file. Default is None.
|
790
|
+
|
791
|
+
.. versionadded:: 1.0.10
|
752
792
|
"""
|
753
793
|
return write_xlsx(self, outfile=outfile, overwrite=overwrite)
|
754
794
|
|
755
795
|
def to_json(self, outfile: str, overwrite: bool = None):
|
756
796
|
"""
|
757
797
|
Export an AMS system to a JSON file.
|
798
|
+
Wrapper method for `ams.io.json.write`.
|
758
799
|
|
759
800
|
Parameters
|
760
801
|
----------
|
@@ -762,12 +803,17 @@ class System(adSystem):
|
|
762
803
|
The output file name.
|
763
804
|
overwrite : bool, optional
|
764
805
|
If True, overwrite the existing file. Default is None.
|
806
|
+
|
807
|
+
.. versionadded:: 1.0.10
|
765
808
|
"""
|
766
809
|
return write_json(self, outfile=outfile, overwrite=overwrite)
|
767
810
|
|
768
811
|
def to_raw(self, outfile: str, overwrite: bool = None):
|
769
812
|
"""
|
770
813
|
Export an AMS system to a v33 PSS/E RAW file.
|
814
|
+
Wrapper method for `ams.io.psse.write_raw`.
|
815
|
+
|
816
|
+
This method has not been fully benchmarked yet!
|
771
817
|
|
772
818
|
Parameters
|
773
819
|
----------
|
@@ -775,6 +821,8 @@ class System(adSystem):
|
|
775
821
|
The output file name.
|
776
822
|
overwrite : bool, optional
|
777
823
|
If True, overwrite the existing file. Default is None.
|
824
|
+
|
825
|
+
.. versionadded:: 1.0.10
|
778
826
|
"""
|
779
827
|
return write_raw(self, outfile=outfile, overwrite=overwrite)
|
780
828
|
|
ams/utils/paths.py
CHANGED
@@ -255,3 +255,67 @@ def confirm_overwrite(outfile, overwrite=None):
|
|
255
255
|
pass
|
256
256
|
|
257
257
|
return True
|
258
|
+
|
259
|
+
|
260
|
+
def get_export_path(system, fname, path=None, fmt='csv'):
|
261
|
+
"""
|
262
|
+
Get the absolute export path and the derived file name for
|
263
|
+
an AMS system export.
|
264
|
+
|
265
|
+
This function is not intended to be used directly by users.
|
266
|
+
|
267
|
+
Parameters
|
268
|
+
----------
|
269
|
+
system : ams.system.System
|
270
|
+
The AMS system to export. (Mocked in example)
|
271
|
+
fname : str
|
272
|
+
The descriptive file name, e.g., 'PTDF', or 'DCOPF'.
|
273
|
+
path : str, optional
|
274
|
+
The desired output path.
|
275
|
+
- If it's a directory, the file name will be generated.
|
276
|
+
- If it's a full file path (with extension), the filename part will be used
|
277
|
+
as the base, and the `fmt` argument will determine the final extension.
|
278
|
+
- If None, the current working directory will be used, and the filename will
|
279
|
+
be generated.
|
280
|
+
fmt : str, optional
|
281
|
+
The file format to export, e.g., 'csv', 'json'. Default is 'csv'.
|
282
|
+
|
283
|
+
Returns
|
284
|
+
-------
|
285
|
+
tuple
|
286
|
+
(str, str): A tuple containing:
|
287
|
+
- The **absolute export path** (e.g., '/home/user/project/data_Routine.csv').
|
288
|
+
- The **export file name** (e.g., 'data_Routine.csv'), including the format extension.
|
289
|
+
"""
|
290
|
+
# Determine the base name from system.files.fullname or default to "Untitled"
|
291
|
+
if system.files.fullname is None:
|
292
|
+
logger.info("Input file name not detected. Using `Untitled`.")
|
293
|
+
base_name_prefix = f'Untitled_{fname}'
|
294
|
+
else:
|
295
|
+
base_name_prefix = os.path.splitext(os.path.basename(system.files.fullname))[0]
|
296
|
+
base_name_prefix += f'_{fname}'
|
297
|
+
|
298
|
+
target_extension = fmt.lower() # Ensure consistent extension casing
|
299
|
+
|
300
|
+
if path:
|
301
|
+
abs_path = os.path.abspath(path) # Resolve to absolute path early
|
302
|
+
|
303
|
+
# Check if the provided path is likely intended as a directory
|
304
|
+
if not os.path.splitext(abs_path)[1]: # No extension implies it's a directory
|
305
|
+
dir_path = abs_path
|
306
|
+
final_file_name = f"{base_name_prefix}.{target_extension}"
|
307
|
+
full_export_path = os.path.join(dir_path, final_file_name)
|
308
|
+
else:
|
309
|
+
# Path includes a filename. Use its directory, and its base name.
|
310
|
+
dir_path = os.path.dirname(abs_path)
|
311
|
+
# Use the provided filename's base, but enforce the target_extension
|
312
|
+
provided_base_filename = os.path.splitext(os.path.basename(abs_path))[0]
|
313
|
+
final_file_name = f"{provided_base_filename}.{target_extension}"
|
314
|
+
full_export_path = os.path.join(dir_path, final_file_name)
|
315
|
+
else:
|
316
|
+
# No path provided, use current working directory
|
317
|
+
dir_path = os.getcwd()
|
318
|
+
final_file_name = f"{base_name_prefix}.{target_extension}"
|
319
|
+
full_export_path = os.path.join(dir_path, final_file_name)
|
320
|
+
|
321
|
+
return full_export_path, final_file_name
|
docs/source/index.rst
CHANGED
@@ -89,9 +89,10 @@ dynamic simulator ANDES.
|
|
89
89
|
publication.
|
90
90
|
|
91
91
|
|
92
|
-
.. [Wang2025] J. Wang et al., "Dynamics-
|
93
|
-
Constrained Scheduling Under High-
|
94
|
-
Transactions on Sustainable Energy,
|
92
|
+
.. [Wang2025] J. Wang et al., "Dynamics-Incorporated Modeling Framework for Stability
|
93
|
+
Constrained Scheduling Under High-Penetration of Renewable Energy," in IEEE
|
94
|
+
Transactions on Sustainable Energy, vol. 16, no. 3, pp. 1673-1685, July 2025,
|
95
|
+
doi: 10.1109/TSTE.2025.3528027.
|
95
96
|
|
96
97
|
|
97
98
|
.. toctree::
|
docs/source/release-notes.rst
CHANGED
@@ -9,6 +9,21 @@ The APIs before v3.0.0 are in beta and may change without prior notice.
|
|
9
9
|
v1.0
|
10
10
|
==========
|
11
11
|
|
12
|
+
v1.0.13 (2025-08-18)
|
13
|
+
----------------------
|
14
|
+
|
15
|
+
- Add methods ``export_npz``, ``load_npz``, ``load_csv`` in ``MatProcessor``
|
16
|
+
to save and load matrices from files
|
17
|
+
- Add methods ``export_json``, ``load_json`` in ``RoutineBase``
|
18
|
+
to save and load routine results from JSON files
|
19
|
+
|
20
|
+
v1.0.12 (2025-05-29)
|
21
|
+
----------------------
|
22
|
+
|
23
|
+
- Add RParam pd and qd in ``DCPF1`` for easy access to load
|
24
|
+
- Bug fix in ``RoutineBase.export_csv`` when path is specified
|
25
|
+
- Fix bug in ``io.matpower.system2mpc`` with multiple PQ at one bus
|
26
|
+
|
12
27
|
v1.0.11 (2025-05-23)
|
13
28
|
----------------------
|
14
29
|
|
@@ -18,8 +33,8 @@ v1.0.11 (2025-05-23)
|
|
18
33
|
v1.0.10 (2025-05-23)
|
19
34
|
----------------------
|
20
35
|
|
21
|
-
- Add bus type correction in ``system.System.setup
|
22
|
-
- Revise ``
|
36
|
+
- Add bus type correction in ``system.System.setup``
|
37
|
+
- Revise ``io.psse.read`` to complete model Zone when necessary
|
23
38
|
- Use numerical Area and Zone idx in MATPOWER and PSSE RAW file conversion
|
24
39
|
- Support JSON format addfile when converting to ANDES case
|
25
40
|
- Add PSS/E v33 RAW file writer
|
@@ -34,7 +49,7 @@ v1.0.10 (2025-05-23)
|
|
34
49
|
- Revise ``andes.common.config.Config.update`` to ensure configuration parameters
|
35
50
|
are consistently updated in both the object and its internal ``_dict``
|
36
51
|
- Remove legacy revised PYPOWER module
|
37
|
-
- Remove function ``
|
52
|
+
- Remove function ``shared.ppc2df``
|
38
53
|
|
39
54
|
v1.0.9 (2025-04-23)
|
40
55
|
--------------------
|
@@ -62,7 +77,7 @@ v1.0.7 (2025-04-14)
|
|
62
77
|
- Extend common parameters in groups ``StaticGen`` and ``StaticLoad`` with ``area``
|
63
78
|
- Set case ``pjm5bus_demo.xlsx`` as a all-inclusive case
|
64
79
|
- Include module ``MatProcessor`` in the API documentation
|
65
|
-
- Improve Line parameters correction in ``System.setup
|
80
|
+
- Improve Line parameters correction in ``System.setup``
|
66
81
|
- Make func ``interface._to_andes_pflow`` public
|
67
82
|
- Discard ``sync_adsys`` step in func ``to_andes_pflow`` to fix mistake in
|
68
83
|
parameters conversion
|
@@ -91,7 +106,7 @@ v1.0.4 (2025-04-05)
|
|
91
106
|
v1.0.3 (2025-03-17)
|
92
107
|
--------------------
|
93
108
|
|
94
|
-
- Bug fix in function ``
|
109
|
+
- Bug fix in function ``interface.parse_addfile``, released in v1.0.3a1
|
95
110
|
|
96
111
|
v1.0.2 (2025-02-01)
|
97
112
|
--------------------
|
@@ -136,10 +151,10 @@ v0.9.12 (2024-11-23)
|
|
136
151
|
|
137
152
|
- Refactor ``OModel.initialized`` as a property method
|
138
153
|
- Add a demo to show using ``Constraint.e`` for debugging
|
139
|
-
- Fix ``
|
140
|
-
- Improve ``
|
141
|
-
- Refactor module ``
|
142
|
-
- Add class ``
|
154
|
+
- Fix ``opt.omodel.Param.evaluate`` when its value is a number
|
155
|
+
- Improve ``opt.omodel.ExpressionCalc`` for better performance
|
156
|
+
- Refactor module ``opt``
|
157
|
+
- Add class ``opt.Expression``
|
143
158
|
- Switch from PYPOWER to ANDES in routine ``PFlow``
|
144
159
|
- Switch from PYPOWER to regular formulation in routine ``DCPF``
|
145
160
|
- Refactor routines ``DCPF`` and ``DCOPF``
|
@@ -382,7 +397,7 @@ v0.6.5 (2023-06-27)
|
|
382
397
|
-------------------
|
383
398
|
|
384
399
|
- Update documentation with auto-generated model and routine reference
|
385
|
-
- Add interface with ANDES ``
|
400
|
+
- Add interface with ANDES ``interop.andes``
|
386
401
|
- Add routine RTED and example of RTED-TDS co-simulation
|
387
402
|
- Draft development documentation
|
388
403
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ltbams
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.13
|
4
4
|
Summary: Python software for scheduling modeling and co-simulation with dynamics.
|
5
5
|
Home-page: https://github.com/CURENT/ams
|
6
6
|
Author: Jinning Wang
|
@@ -23,6 +23,7 @@ Requires-Dist: openpyxl
|
|
23
23
|
Requires-Dist: andes>=1.9.3
|
24
24
|
Requires-Dist: pybind11
|
25
25
|
Requires-Dist: cvxpy
|
26
|
+
Requires-Dist: cffi
|
26
27
|
Provides-Extra: dev
|
27
28
|
Requires-Dist: pytest; extra == "dev"
|
28
29
|
Requires-Dist: pytest-cov; extra == "dev"
|
@@ -60,6 +61,7 @@ Python Software for Power System Scheduling Modeling and Co-Simulation with Dyna
|
|
60
61
|
| Badges | | |
|
61
62
|
|---|---|---|
|
62
63
|
| Repo |  |  |
|
64
|
+
| Python | [](https://www.python.org/) |
|
63
65
|
| Version | [](https://pypi.org/project/ltbams/) | [](https://anaconda.org/conda-forge/ltbams) |
|
64
66
|
| Tag | [](https://github.com/CURENT/ams/tags) |  |
|
65
67
|
| Documentation | [](https://ltb.readthedocs.io/projects/ams/en/stable/?badge=stable) | [](https://ltb.readthedocs.io/projects/ams/en/develop/?badge=develop) |
|
@@ -217,7 +219,7 @@ sa
|
|
217
219
|
# Citing AMS
|
218
220
|
If you use AMS for research or consulting, please cite the following paper in your publication that uses AMS:
|
219
221
|
|
220
|
-
> J. Wang et al., "Dynamics-
|
222
|
+
> J. Wang et al., "Dynamics-Incorporated Modeling Framework for Stability Constrained Scheduling Under High-Penetration of Renewable Energy," in IEEE Transactions on Sustainable Energy, vol. 16, no. 3, pp. 1673-1685, July 2025, doi: 10.1109/TSTE.2025.3528027.
|
221
223
|
|
222
224
|
# Sponsors and Contributors
|
223
225
|
AMS is the scheduling simulation engine for the CURENT Largescale Testbed (LTB).
|