ltbams 1.0.11__py3-none-any.whl → 1.0.12__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 +13 -14
- ams/io/matpower.py +51 -20
- ams/io/psse.py +2 -0
- ams/routines/pypower.py +13 -4
- ams/routines/routine.py +9 -14
- ams/system.py +41 -3
- ams/utils/paths.py +64 -0
- docs/source/release-notes.rst +17 -10
- {ltbams-1.0.11.dist-info → ltbams-1.0.12.dist-info}/METADATA +1 -1
- {ltbams-1.0.11.dist-info → ltbams-1.0.12.dist-info}/RECORD +17 -17
- {ltbams-1.0.11.dist-info → ltbams-1.0.12.dist-info}/WHEEL +1 -1
- tests/test_io.py +10 -0
- tests/test_paths.py +68 -1
- tests/test_rtn_dcopf.py +20 -0
- {ltbams-1.0.11.dist-info → ltbams-1.0.12.dist-info}/entry_points.txt +0 -0
- {ltbams-1.0.11.dist-info → ltbams-1.0.12.dist-info}/top_level.txt +0 -0
ams/_version.py
CHANGED
@@ -8,11 +8,11 @@ import json
|
|
8
8
|
|
9
9
|
version_json = '''
|
10
10
|
{
|
11
|
-
"date": "2025-05-
|
11
|
+
"date": "2025-05-29T20:16:06-0400",
|
12
12
|
"dirty": false,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "1.0.
|
14
|
+
"full-revisionid": "2166f4368af3d382720af38408153a70502eb9f5",
|
15
|
+
"version": "1.0.12"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|
ams/core/matprocessor.py
CHANGED
@@ -3,7 +3,6 @@ Module for system matrix make.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
-
import os
|
7
6
|
import sys
|
8
7
|
from typing import Optional
|
9
8
|
|
@@ -14,6 +13,9 @@ from andes.shared import tqdm, tqdm_nb
|
|
14
13
|
from andes.utils.misc import elapsed, is_notebook
|
15
14
|
|
16
15
|
from ams.opt import Param
|
16
|
+
|
17
|
+
from ams.utils.paths import get_export_path
|
18
|
+
|
17
19
|
from ams.shared import pd, sps
|
18
20
|
|
19
21
|
logger = logging.getLogger(__name__)
|
@@ -75,31 +77,28 @@ class MParam(Param):
|
|
75
77
|
"""
|
76
78
|
Export the matrix to a CSV file.
|
77
79
|
|
80
|
+
In the exported CSV, columns are the bus idxes, and Line idxes are
|
81
|
+
used as row indexes.
|
82
|
+
|
78
83
|
Parameters
|
79
84
|
----------
|
80
85
|
path : str, optional
|
81
|
-
Path
|
86
|
+
Path of the csv file to export.
|
82
87
|
|
83
88
|
Returns
|
84
89
|
-------
|
85
90
|
str
|
86
|
-
The
|
91
|
+
The exported csv file name
|
87
92
|
"""
|
88
93
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
else:
|
94
|
-
file_name = os.path.splitext(self.owner.system.files.fullname)[0]
|
95
|
-
file_name += f'_{self.name}'
|
96
|
-
path = os.path.join(os.getcwd(), file_name + '.csv')
|
97
|
-
else:
|
98
|
-
file_name = os.path.splitext(os.path.basename(path))[0]
|
94
|
+
path, file_name = get_export_path(self.owner.system,
|
95
|
+
self.name,
|
96
|
+
path=path,
|
97
|
+
fmt='csv')
|
99
98
|
|
100
99
|
pd.DataFrame(data=self.v, columns=self.col_names, index=self.row_names).to_csv(path)
|
101
100
|
|
102
|
-
return file_name
|
101
|
+
return file_name
|
103
102
|
|
104
103
|
@property
|
105
104
|
def v(self):
|
ams/io/matpower.py
CHANGED
@@ -455,16 +455,6 @@ def system2mpc(system) -> dict:
|
|
455
455
|
|
456
456
|
This function is revised from ``andes.io.matpower.system2mpc``.
|
457
457
|
|
458
|
-
In the ``gen`` section, slack generators are listed before PV generators.
|
459
|
-
|
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).
|
467
|
-
|
468
458
|
Parameters
|
469
459
|
----------
|
470
460
|
system : ams.core.system.System
|
@@ -474,6 +464,21 @@ def system2mpc(system) -> dict:
|
|
474
464
|
-------
|
475
465
|
mpc : dict
|
476
466
|
A dictionary in MATPOWER format representing the converted AMS system.
|
467
|
+
|
468
|
+
Notes
|
469
|
+
-----
|
470
|
+
- In the `gen` section, slack generators are listed before PV generators.
|
471
|
+
- For uncontrolled generators (`ctrl.v == 0`), their max and min power
|
472
|
+
limits are set to their initial power (`p0.v`) in the converted MPC.
|
473
|
+
- In the converted MPC, the indices of area (`bus[:, 6]`) and zone (`bus[:, 10]`)
|
474
|
+
may differ from the original MPC. However, the mapping relationship is preserved.
|
475
|
+
For example, if the original MPC numbers areas starting from 1, the converted
|
476
|
+
MPC may number them starting from 0.
|
477
|
+
- The coefficients `c2` and `c1` in the generator cost data are scaled by
|
478
|
+
`baseMVA`.
|
479
|
+
- Unlike the XLSX and JSON converters, this implementation uses value providers
|
480
|
+
(`v`) instead of vin. As a result, any changes made through `model.set` will be
|
481
|
+
reflected in the generated MPC.
|
477
482
|
"""
|
478
483
|
|
479
484
|
mpc = dict(version='2',
|
@@ -514,15 +519,31 @@ def system2mpc(system) -> dict:
|
|
514
519
|
# --- PQ ---
|
515
520
|
if system.PQ.n > 0:
|
516
521
|
pq_pos = system.Bus.idx2uid(system.PQ.bus.v)
|
517
|
-
|
518
|
-
|
519
|
-
|
522
|
+
|
523
|
+
p0e = system.PQ.u.v * system.PQ.p0.v
|
524
|
+
q0e = system.PQ.u.v * system.PQ.q0.v
|
525
|
+
|
526
|
+
# NOTE: ensure multiple PQ on the same bus are summed
|
527
|
+
# rather than overwritten. Same for Shunt.
|
528
|
+
p = np.zeros(system.Bus.n)
|
529
|
+
q = np.zeros(system.Bus.n)
|
530
|
+
np.add.at(p, pq_pos, p0e)
|
531
|
+
np.add.at(q, pq_pos, q0e)
|
532
|
+
bus[:, 2] = p * base_mva
|
533
|
+
bus[:, 3] = q * base_mva
|
520
534
|
|
521
535
|
# --- Shunt ---
|
522
536
|
if system.Shunt.n > 0:
|
523
537
|
shunt_pos = system.Bus.idx2uid(system.Shunt.bus.v)
|
524
|
-
|
525
|
-
|
538
|
+
|
539
|
+
ge = system.Shunt.u.v * system.Shunt.g.v
|
540
|
+
be = system.Shunt.u.v * system.Shunt.b.v
|
541
|
+
g = np.zeros(system.Bus.n)
|
542
|
+
b = np.zeros(system.Bus.n)
|
543
|
+
np.add.at(g, shunt_pos, ge)
|
544
|
+
np.add.at(b, shunt_pos, be)
|
545
|
+
bus[:, 4] = g * base_mva
|
546
|
+
bus[:, 5] = b * base_mva
|
526
547
|
|
527
548
|
# --- PV ---
|
528
549
|
if system.PV.n > 0:
|
@@ -709,11 +730,6 @@ def write(system, outfile: str, overwrite: bool = None) -> bool:
|
|
709
730
|
This function converts an AMS system object into a MATPOWER-compatible
|
710
731
|
mpc dictionary and writes it to a specified output file in MATPOWER format.
|
711
732
|
|
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
733
|
Parameters
|
718
734
|
----------
|
719
735
|
system : ams.system.System
|
@@ -727,6 +743,21 @@ def write(system, outfile: str, overwrite: bool = None) -> bool:
|
|
727
743
|
-------
|
728
744
|
bool
|
729
745
|
True if the file was successfully written, False otherwise.
|
746
|
+
|
747
|
+
Notes
|
748
|
+
-----
|
749
|
+
- In the `gen` section, slack generators are listed before PV generators.
|
750
|
+
- For uncontrolled generators (`ctrl.v == 0`), their max and min power
|
751
|
+
limits are set to their initial power (`p0.v`) in the converted MPC.
|
752
|
+
- In the converted MPC, the indices of area (`bus[:, 6]`) and zone (`bus[:, 10]`)
|
753
|
+
may differ from the original MPC. However, the mapping relationship is preserved.
|
754
|
+
For example, if the original MPC numbers areas starting from 1, the converted
|
755
|
+
MPC may number them starting from 0.
|
756
|
+
- The coefficients `c2` and `c1` in the generator cost data are scaled by
|
757
|
+
`baseMVA`.
|
758
|
+
- Unlike the XLSX and JSON converters, this implementation uses value providers
|
759
|
+
(`v`) instead of vin. As a result, any changes made through `model.set` will be
|
760
|
+
reflected in the generated MPC.
|
730
761
|
"""
|
731
762
|
if not confirm_overwrite(outfile, overwrite=overwrite):
|
732
763
|
return False
|
ams/io/psse.py
CHANGED
ams/routines/pypower.py
CHANGED
@@ -131,6 +131,15 @@ class DCPF1(RoutineBase):
|
|
131
131
|
unit='p.u.',
|
132
132
|
name='vBus', tex_name=r'v_{Bus}',
|
133
133
|
src='v', model='Bus',)
|
134
|
+
# --- load ---
|
135
|
+
self.pd = RParam(info='active demand',
|
136
|
+
name='pd', tex_name=r'p_{d}',
|
137
|
+
model='StaticLoad', src='p0',
|
138
|
+
unit='p.u.',)
|
139
|
+
self.qd = RParam(info='reactive demand',
|
140
|
+
name='qd', tex_name=r'q_{d}',
|
141
|
+
model='StaticLoad', src='q0',
|
142
|
+
unit='p.u.',)
|
134
143
|
# --- gen ---
|
135
144
|
self.pg = Var(info='Gen active power',
|
136
145
|
unit='p.u.',
|
@@ -267,13 +276,13 @@ class DCPF1(RoutineBase):
|
|
267
276
|
return False
|
268
277
|
|
269
278
|
def _get_off_constrs(self):
|
270
|
-
|
279
|
+
logger.debug(f"{self.class_name} does not implement _get_off_constrs.")
|
271
280
|
|
272
281
|
def _data_check(self, info=True, **kwargs):
|
273
|
-
|
282
|
+
logger.debug(f"{self.class_name} does not implement _data_check.")
|
274
283
|
|
275
284
|
def update(self, params=None, build_mats=False, **kwargs):
|
276
|
-
|
285
|
+
logger.debug(f"{self.class_name} does not implement update.")
|
277
286
|
|
278
287
|
def enable(self, name):
|
279
288
|
raise NotImplementedError
|
@@ -282,7 +291,7 @@ class DCPF1(RoutineBase):
|
|
282
291
|
raise NotImplementedError
|
283
292
|
|
284
293
|
def _post_add_check(self):
|
285
|
-
|
294
|
+
logger.debug(f"{self.class_name} does not implement _post_add_check.")
|
286
295
|
|
287
296
|
def addRParam(self,
|
288
297
|
name: str,
|
ams/routines/routine.py
CHANGED
@@ -3,7 +3,6 @@ Module for routine data.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
-
import os
|
7
6
|
from typing import Optional, Union, Type, Iterable, Dict
|
8
7
|
from collections import OrderedDict
|
9
8
|
|
@@ -19,6 +18,8 @@ from ams.core.service import RBaseService, ValueService
|
|
19
18
|
from ams.opt import OModel
|
20
19
|
from ams.opt import Param, Var, Constraint, Objective, ExpressionCalc, Expression
|
21
20
|
|
21
|
+
from ams.utils.paths import get_export_path
|
22
|
+
|
22
23
|
from ams.shared import pd
|
23
24
|
|
24
25
|
logger = logging.getLogger(__name__)
|
@@ -441,26 +442,20 @@ class RoutineBase:
|
|
441
442
|
|
442
443
|
Parameters
|
443
444
|
----------
|
444
|
-
path : str
|
445
|
-
|
445
|
+
path : str, optional
|
446
|
+
Path of the csv file to export.
|
446
447
|
|
447
448
|
Returns
|
448
449
|
-------
|
449
|
-
|
450
|
-
The
|
450
|
+
str
|
451
|
+
The exported csv file name
|
451
452
|
"""
|
452
453
|
if not self.converged:
|
453
454
|
logger.warning("Routine did not converge, aborting export.")
|
454
455
|
return None
|
455
456
|
|
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')
|
457
|
+
path, file_name = get_export_path(self.system, self.class_name,
|
458
|
+
path=path, fmt='csv')
|
464
459
|
|
465
460
|
data_dict = initialize_data_dict(self)
|
466
461
|
|
@@ -473,7 +468,7 @@ class RoutineBase:
|
|
473
468
|
|
474
469
|
pd.DataFrame(data_dict).to_csv(path, index=False)
|
475
470
|
|
476
|
-
return file_name
|
471
|
+
return file_name
|
477
472
|
|
478
473
|
def summary(self, **kwargs):
|
479
474
|
"""
|
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,34 @@ 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.
|
726
743
|
"""
|
727
744
|
return system2mpc(self)
|
728
745
|
|
729
746
|
def to_m(self, outfile: str, overwrite: bool = None):
|
730
747
|
"""
|
731
748
|
Export an AMS system to a MATPOWER M-file.
|
749
|
+
Wrapper method for `ams.io.matpower.write`.
|
732
750
|
|
733
751
|
Parameters
|
734
752
|
----------
|
@@ -736,12 +754,28 @@ class System(adSystem):
|
|
736
754
|
The output file name.
|
737
755
|
overwrite : bool, optional
|
738
756
|
If True, overwrite the existing file. Default is None.
|
739
|
-
|
740
|
-
|
757
|
+
|
758
|
+
Notes
|
759
|
+
-----
|
760
|
+
- In the `gen` section, slack generators are listed before PV generators.
|
761
|
+
- For uncontrolled generators (`ctrl.v == 0`), their max and min power
|
762
|
+
limits are set to their initial power (`p0.v`) in the converted MPC.
|
763
|
+
- In the converted MPC, the indices of area (`bus[:, 6]`) and zone (`bus[:, 10]`)
|
764
|
+
may differ from the original MPC. However, the mapping relationship is preserved.
|
765
|
+
For example, if the original MPC numbers areas starting from 1, the converted
|
766
|
+
MPC may number them starting from 0.
|
767
|
+
- The coefficients `c2` and `c1` in the generator cost data are scaled by
|
768
|
+
`baseMVA`.
|
769
|
+
- Unlike the XLSX and JSON converters, this implementation uses value providers
|
770
|
+
(`v`) instead of vin. As a result, any changes made through `model.set` will be
|
771
|
+
reflected in the generated MPC.
|
772
|
+
"""
|
773
|
+
return write_m(self, outfile=outfile, overwrite=overwrite)
|
741
774
|
|
742
775
|
def to_xlsx(self, outfile: str, overwrite: bool = None):
|
743
776
|
"""
|
744
777
|
Export an AMS system to an Excel file.
|
778
|
+
Wrapper method for `ams.io.xlsx.write`.
|
745
779
|
|
746
780
|
Parameters
|
747
781
|
----------
|
@@ -755,6 +789,7 @@ class System(adSystem):
|
|
755
789
|
def to_json(self, outfile: str, overwrite: bool = None):
|
756
790
|
"""
|
757
791
|
Export an AMS system to a JSON file.
|
792
|
+
Wrapper method for `ams.io.json.write`.
|
758
793
|
|
759
794
|
Parameters
|
760
795
|
----------
|
@@ -768,6 +803,9 @@ class System(adSystem):
|
|
768
803
|
def to_raw(self, outfile: str, overwrite: bool = None):
|
769
804
|
"""
|
770
805
|
Export an AMS system to a v33 PSS/E RAW file.
|
806
|
+
Wrapper method for `ams.io.psse.write_raw`.
|
807
|
+
|
808
|
+
This method has not been fully benchmarked yet!
|
771
809
|
|
772
810
|
Parameters
|
773
811
|
----------
|
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/release-notes.rst
CHANGED
@@ -9,6 +9,13 @@ 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.12 (2025-05-29)
|
13
|
+
----------------------
|
14
|
+
|
15
|
+
- Add RParam pd and qd in ``DCPF1`` for easy access to load
|
16
|
+
- Bug fix in ``RoutineBase.export_csv`` when path is specified
|
17
|
+
- Fix bug in ``io.matpower.system2mpc`` with multiple PQ at one bus
|
18
|
+
|
12
19
|
v1.0.11 (2025-05-23)
|
13
20
|
----------------------
|
14
21
|
|
@@ -18,8 +25,8 @@ v1.0.11 (2025-05-23)
|
|
18
25
|
v1.0.10 (2025-05-23)
|
19
26
|
----------------------
|
20
27
|
|
21
|
-
- Add bus type correction in ``system.System.setup
|
22
|
-
- Revise ``
|
28
|
+
- Add bus type correction in ``system.System.setup``
|
29
|
+
- Revise ``io.psse.read`` to complete model Zone when necessary
|
23
30
|
- Use numerical Area and Zone idx in MATPOWER and PSSE RAW file conversion
|
24
31
|
- Support JSON format addfile when converting to ANDES case
|
25
32
|
- Add PSS/E v33 RAW file writer
|
@@ -34,7 +41,7 @@ v1.0.10 (2025-05-23)
|
|
34
41
|
- Revise ``andes.common.config.Config.update`` to ensure configuration parameters
|
35
42
|
are consistently updated in both the object and its internal ``_dict``
|
36
43
|
- Remove legacy revised PYPOWER module
|
37
|
-
- Remove function ``
|
44
|
+
- Remove function ``shared.ppc2df``
|
38
45
|
|
39
46
|
v1.0.9 (2025-04-23)
|
40
47
|
--------------------
|
@@ -62,7 +69,7 @@ v1.0.7 (2025-04-14)
|
|
62
69
|
- Extend common parameters in groups ``StaticGen`` and ``StaticLoad`` with ``area``
|
63
70
|
- Set case ``pjm5bus_demo.xlsx`` as a all-inclusive case
|
64
71
|
- Include module ``MatProcessor`` in the API documentation
|
65
|
-
- Improve Line parameters correction in ``System.setup
|
72
|
+
- Improve Line parameters correction in ``System.setup``
|
66
73
|
- Make func ``interface._to_andes_pflow`` public
|
67
74
|
- Discard ``sync_adsys`` step in func ``to_andes_pflow`` to fix mistake in
|
68
75
|
parameters conversion
|
@@ -91,7 +98,7 @@ v1.0.4 (2025-04-05)
|
|
91
98
|
v1.0.3 (2025-03-17)
|
92
99
|
--------------------
|
93
100
|
|
94
|
-
- Bug fix in function ``
|
101
|
+
- Bug fix in function ``interface.parse_addfile``, released in v1.0.3a1
|
95
102
|
|
96
103
|
v1.0.2 (2025-02-01)
|
97
104
|
--------------------
|
@@ -136,10 +143,10 @@ v0.9.12 (2024-11-23)
|
|
136
143
|
|
137
144
|
- Refactor ``OModel.initialized`` as a property method
|
138
145
|
- Add a demo to show using ``Constraint.e`` for debugging
|
139
|
-
- Fix ``
|
140
|
-
- Improve ``
|
141
|
-
- Refactor module ``
|
142
|
-
- Add class ``
|
146
|
+
- Fix ``opt.omodel.Param.evaluate`` when its value is a number
|
147
|
+
- Improve ``opt.omodel.ExpressionCalc`` for better performance
|
148
|
+
- Refactor module ``opt``
|
149
|
+
- Add class ``opt.Expression``
|
143
150
|
- Switch from PYPOWER to ANDES in routine ``PFlow``
|
144
151
|
- Switch from PYPOWER to regular formulation in routine ``DCPF``
|
145
152
|
- Refactor routines ``DCPF`` and ``DCOPF``
|
@@ -382,7 +389,7 @@ v0.6.5 (2023-06-27)
|
|
382
389
|
-------------------
|
383
390
|
|
384
391
|
- Update documentation with auto-generated model and routine reference
|
385
|
-
- Add interface with ANDES ``
|
392
|
+
- Add interface with ANDES ``interop.andes``
|
386
393
|
- Add routine RTED and example of RTED-TDS co-simulation
|
387
394
|
- Draft development documentation
|
388
395
|
|
@@ -1,12 +1,12 @@
|
|
1
1
|
ams/__init__.py,sha256=q-9-f0YCg6aaTW19VCkY6VosfvRA2ObI9mF1f_3016Y,313
|
2
2
|
ams/__main__.py,sha256=EB4GfGiKgvnQ_psNr0QwPoziYvjmGvQ2yVsBwQtfrLw,170
|
3
|
-
ams/_version.py,sha256=
|
3
|
+
ams/_version.py,sha256=jNLhv-DaZngBxRAi3mCJd0jmQgAAbgAoAn-N8TbLgbQ,498
|
4
4
|
ams/cli.py,sha256=EyNFXn565gFCppTxpyTZviBdPgUuKtgAPZ4WE6xewRk,6164
|
5
5
|
ams/interface.py,sha256=-UN7-TiSt7mOS0W7526LuOk4KCIGDZSguRaVmh_KMRM,45240
|
6
6
|
ams/main.py,sha256=lIqC16TO0pye75Wv8l_6EemNtm15iyMdvu8YQFkdd_4,23599
|
7
7
|
ams/report.py,sha256=ewcffopOzT5o45eJNmcV8pxeQqPIjKbarGN33-yHGA8,10961
|
8
8
|
ams/shared.py,sha256=4sY2U0sghs_S2V6C7Z0OyQ6WZRfpCXk2_Cf_Mg24Vr4,3900
|
9
|
-
ams/system.py,sha256=
|
9
|
+
ams/system.py,sha256=bGbZQrN5N2tx1tnxoPz6JSGE568EGH1Oi1zQIyLFLug,31948
|
10
10
|
ams/cases/5bus/pjm5bus_demo.json,sha256=IpxO2vzB9-9Kg9xjOXREKeXEz9wjFE7cuQbcUu8VORA,23152
|
11
11
|
ams/cases/5bus/pjm5bus_demo.xlsx,sha256=OWIUprkg8_aQ_bTCaEFMl7Bhfa1R20zxAHXRQtXBix0,32607
|
12
12
|
ams/cases/5bus/pjm5bus_ev.xlsx,sha256=vR8dJv5jIxib1pgcMonhzvraoqZVJWhBSJdVXDL0qsI,19498
|
@@ -38,7 +38,7 @@ ams/cases/wecc/wecc_uced.xlsx,sha256=R3tZgxEqz_ctKcjA1wwFecxn-QZXutvf7NzgnCg_078
|
|
38
38
|
ams/core/__init__.py,sha256=lIvrAvYf2yrHWqYi0rVuJgJt8KNA8nTmN2iuZ125z9Q,123
|
39
39
|
ams/core/common.py,sha256=934J7xq5JvC14yKp2Z4hWKUFJFhxzAnxB_8_99CChY0,704
|
40
40
|
ams/core/documenter.py,sha256=3-jUYrtN8zDZXd8tQZlmZouJluJPH9_xIDbK9ZEEnRU,25762
|
41
|
-
ams/core/matprocessor.py,sha256=
|
41
|
+
ams/core/matprocessor.py,sha256=gOEqg5xYpzmj4aMgTdzYeOPaL-5Bp493TrxUVmMaiTw,26453
|
42
42
|
ams/core/model.py,sha256=LNZtzyf2A7Tz3pn9IDs35JYaHSkQRqhqZicTpZGSqsc,10926
|
43
43
|
ams/core/param.py,sha256=LPH48xUHyqWqODD6IsiamUtkJDDSgGCEMAo6vroFoHE,11130
|
44
44
|
ams/core/service.py,sha256=Q4aeaYFWycIEH7I8DSO8Itah2CJxc3oAW46dtKCQEyA,28041
|
@@ -48,8 +48,8 @@ ams/extension/__init__.py,sha256=5IFTNirDL0uDaUsg05_oociVT9VCy2rhPx1ad4LGveM,65
|
|
48
48
|
ams/extension/eva.py,sha256=4_q4ME0WrQIcd205SBTjv0-rMRZZIdQ07QJuEHwLIC8,16340
|
49
49
|
ams/io/__init__.py,sha256=GIfF7X44olHaySL8EzOBU1eXUnunr1owzzdPfPvvHZU,3802
|
50
50
|
ams/io/json.py,sha256=IurwcZDuKczSbRTpcbQZiIi0ARCrMK6kJ0E3wS8ENy8,2585
|
51
|
-
ams/io/matpower.py,sha256=
|
52
|
-
ams/io/psse.py,sha256=
|
51
|
+
ams/io/matpower.py,sha256=R8ynl88bWJAzISYZKfMQi7AFRUINwqk_e-Zsh_pnVCE,27541
|
52
|
+
ams/io/psse.py,sha256=S2JqpEFaXEiazbVtU77i93BA4ZnoKP7jDZ1rQGg24sE,13684
|
53
53
|
ams/io/pypower.py,sha256=Fe0sxzC5DZegtYksTixadXk_LcfJ1GNetWu9EuHCkG8,2790
|
54
54
|
ams/io/xlsx.py,sha256=7ATW1seyxsGn7d5p5IuSRFHcoCHVVjMu3E7mP1Mc74U,2460
|
55
55
|
ams/models/__init__.py,sha256=EGkViLkVX_We9FAGuEkgfleMNmPw_vGp2Nq1OQimL7U,691
|
@@ -90,14 +90,14 @@ ams/routines/dopf.py,sha256=8D36-FkPORYGaMnwGTqwz8HxAXk5ywo3mk8NlGq327g,6289
|
|
90
90
|
ams/routines/ed.py,sha256=9Hf_ZqD6poIxCIBfsTMC0DGoPNEja1ZtVxqhb4ijhgE,11875
|
91
91
|
ams/routines/grbopt.py,sha256=RjrMq6XPHeBEbf-Pt9sLk2D1vnXZYYwaic4BP8lbACg,5480
|
92
92
|
ams/routines/pflow.py,sha256=5_9n10r_PfsVXIRkaBgKxVITumImZ8mvpHnwxX_ECdw,9432
|
93
|
-
ams/routines/pypower.py,sha256=
|
94
|
-
ams/routines/routine.py,sha256=
|
93
|
+
ams/routines/pypower.py,sha256=3nKm7VoVXWsiiBK0fEdHv0W2T2AsJ4a6Nqb9ts-1u1A,27214
|
94
|
+
ams/routines/routine.py,sha256=teafNQ7DmXntwNP-RbtJJ9Ixa5xOEioO-nXFhY1psmA,35574
|
95
95
|
ams/routines/rted.py,sha256=GOHRxo0-HS5HhwQg8lv7-2VcGr_M_TdUvvomgJ31fDQ,22070
|
96
96
|
ams/routines/type.py,sha256=lvTWSnCYIbRJXIm3HX6jA0Hv-WWYusTOUPfoW8DITlU,3877
|
97
97
|
ams/routines/uc.py,sha256=VcuNy2TnBjsewKEGIqeo2EFTyuhpx5QsEvgpAtscDIQ,15648
|
98
98
|
ams/utils/__init__.py,sha256=2hAQmWRgmnE-bCGT9cJoW9FkPDMGRiGkbBcUgj-bgjI,122
|
99
99
|
ams/utils/misc.py,sha256=Y6tPKpUKJa7bL9alroJuG2UNW9vdQjnfAmKb2EbIls8,2027
|
100
|
-
ams/utils/paths.py,sha256=
|
100
|
+
ams/utils/paths.py,sha256=6iTux5UaHzRi6mGHnpbYUp-MoD-IFny4IuRsV8FKDcQ,9481
|
101
101
|
docs/Makefile,sha256=UKXBFAbKGPt1Xw9J84343v0Mh8ylAZ-tE0uCd74DrQU,725
|
102
102
|
docs/make.bat,sha256=9UgKGb4SdP006622fJiFxeFT1BeycYAs6hDbV1xwPy8,804
|
103
103
|
docs/source/api.rst,sha256=BRzdDFDzDwVL7Jr_Xj-O5Icgx0gt5hNNd1OjvPl7ap0,1490
|
@@ -105,7 +105,7 @@ docs/source/conf.py,sha256=UyoWogZTUNSJU7pQS_JaR28nKddW94zr01LYoIciZZw,6688
|
|
105
105
|
docs/source/genmodelref.py,sha256=IhmF7bDw8BXPvLD8V3WjQNrfc-H07r5iS-_4DHbbp-8,1420
|
106
106
|
docs/source/genroutineref.py,sha256=glHhbWUXfZzMDrkque9CBZu8QtdxlxPojInERzAAOwA,1063
|
107
107
|
docs/source/index.rst,sha256=N5phQS5RIyYs-NZo_5yYB8LjvHzOKLeXzRA-M8i-g3Q,2688
|
108
|
-
docs/source/release-notes.rst,sha256=
|
108
|
+
docs/source/release-notes.rst,sha256=zBHZ9dALCLSKKz5lwwalCQJiNCD2l2d3iJjISWtUiPg,14031
|
109
109
|
docs/source/_templates/autosummary/base.rst,sha256=zl3U4baR4a6YjsHyT-x9zCOrHwKZOVUdWn1NPX2u3bc,106
|
110
110
|
docs/source/_templates/autosummary/class.rst,sha256=Hv_igCsLsUpM62_zN0nqj6FSfKnS5xLyu8ZldMbfOAk,668
|
111
111
|
docs/source/_templates/autosummary/module.rst,sha256=YdbpCudOrEU-JbuSlzGvcOI2hn_KrCM6FW5HcGqkaEE,1113
|
@@ -143,18 +143,18 @@ tests/test_cli.py,sha256=TtCGBy2e7Ll_2gJTFo9juZtzhaakho_MqkcqhG2w2dk,870
|
|
143
143
|
tests/test_export_csv.py,sha256=NTULXfTexgI1jf5koUMOYi3RLrSQouS7zxRjXo3ng50,2921
|
144
144
|
tests/test_group.py,sha256=Tq0s9gtenqrv4ws5YNzWxbiF4WgyhtMEAXZfJtew6M4,2699
|
145
145
|
tests/test_interface.py,sha256=8hOZ1caRfoyoEdy1lnh8Y4rIL97FLb2oUKfX_N1lEDo,8838
|
146
|
-
tests/test_io.py,sha256=
|
146
|
+
tests/test_io.py,sha256=OXt1C6F_RAt0RoRj3xLKdbA3raKEAjvVyWlKD7Ju7BA,6947
|
147
147
|
tests/test_jumper.py,sha256=bdOknplEGnO_tiJc7p3xQvgTe2b6Dz53bOgbFaXKMAI,537
|
148
148
|
tests/test_known_good.py,sha256=NBrlAxnVMxIHXR2cWps-Kwjh36fiU4Y-eupspZkM0ks,9670
|
149
149
|
tests/test_matp.py,sha256=LkjhqxSFr6oY_ENpduDQ77rhLLBl6RzIHZ2d0m_8i-8,17262
|
150
150
|
tests/test_model.py,sha256=ZgghNYmEeALf8_1sPjq9lIJwuYDQ2SelivJNEYpHxJU,1621
|
151
151
|
tests/test_omodel.py,sha256=niVdTZJEZNSVHz-ocA0nnVx84Dt3-8P5FUrwKkNiA0I,4305
|
152
|
-
tests/test_paths.py,sha256=
|
152
|
+
tests/test_paths.py,sha256=j_D9-Cjg73mwhTS3sUHDfQ8mCebD8enCQxjyRaR2ZH4,3020
|
153
153
|
tests/test_report.py,sha256=RR23by4G-cyleaHTy9A7SEVet0eOVS-Tm0kk1GXY5dM,8134
|
154
154
|
tests/test_repr.py,sha256=g7MRdxLcZRI1PlREFdUG_npp0LkcQiJZfOPw1aq0EFM,570
|
155
155
|
tests/test_routine.py,sha256=DP5CwKtzliw2gNLW2W_3kp0Ihy4rCaxeBxMpgyN5AXA,6234
|
156
156
|
tests/test_rtn_acopf.py,sha256=Kg-RHflrrwyX2uHr16xSAze9XAdYcK7eg1LHMyr1uyA,2354
|
157
|
-
tests/test_rtn_dcopf.py,sha256=
|
157
|
+
tests/test_rtn_dcopf.py,sha256=qFY9mKTAIG-qDlU9-loHZddKDDszdGQwRjL-ZDnESu8,4204
|
158
158
|
tests/test_rtn_dcopf2.py,sha256=57_62TnHW-cS6iK2zXT_eXChnh83YiYoPypJwub3VmQ,3747
|
159
159
|
tests/test_rtn_ed.py,sha256=SeSuzqpTZZnBph41PV2Piplo9yxR3qpbhQUa6UCYnSw,10267
|
160
160
|
tests/test_rtn_opf.py,sha256=MmMNwb9-G0KzHcrrP4uHTvs9DBU71_XPIJqNnkQQPHo,4846
|
@@ -163,8 +163,8 @@ tests/test_rtn_pypower.py,sha256=KO5VOZxETxVH2mY1mgNvzj1gz1Gdak1sAWxYleDU4E8,104
|
|
163
163
|
tests/test_rtn_rted.py,sha256=QHDUymorCqQAJKFlDgTy40JyLTGDvNVNU3tjbjDl3-0,9850
|
164
164
|
tests/test_rtn_uc.py,sha256=UbMeaam3dZwgq2LAJokGOl3LT5B3TWKMjCp4dRcLs40,8497
|
165
165
|
tests/test_service.py,sha256=6IP6CAH2xHxGHZM4-R8LjZxVJ2L10LcGaPDyRIqKLmc,2438
|
166
|
-
ltbams-1.0.
|
167
|
-
ltbams-1.0.
|
168
|
-
ltbams-1.0.
|
169
|
-
ltbams-1.0.
|
170
|
-
ltbams-1.0.
|
166
|
+
ltbams-1.0.12.dist-info/METADATA,sha256=ZKZlrfSVEg8RkdZ-YjEWGZfY4p7kdEGppQfhHV6hgpg,13808
|
167
|
+
ltbams-1.0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
168
|
+
ltbams-1.0.12.dist-info/entry_points.txt,sha256=FA56FlhO_yVNeEf810SrorVQb7_Xsmo3_EW-W-ijUfA,37
|
169
|
+
ltbams-1.0.12.dist-info/top_level.txt,sha256=pyKDqG2kj13F9-BYd_wkruRdBSqLXw8Nwc-cmljqrxg,15
|
170
|
+
ltbams-1.0.12.dist-info/RECORD,,
|
tests/test_io.py
CHANGED
@@ -85,6 +85,16 @@ class TestMATPOWER(unittest.TestCase):
|
|
85
85
|
self.assertEqual(np.unique(mpc5['bus'][:, 10]).shape[0],
|
86
86
|
np.unique(self.mpc5['bus'][:, 10]).shape[0])
|
87
87
|
|
88
|
+
def test_system2mpc_pq(self):
|
89
|
+
"""Test AMS System to MPC conversion with multiple PQ at one bus."""
|
90
|
+
system = ams.load(ams.get_case('5bus/pjm5bus_demo.json'),
|
91
|
+
setup=False, no_output=True)
|
92
|
+
system.add('PQ', dict(bus=1, p0=0.2))
|
93
|
+
|
94
|
+
mpc = system.to_mpc()
|
95
|
+
np.testing.assert_almost_equal(mpc['bus'][:, 2].sum(),
|
96
|
+
system.config.mva * system.PQ.p0.v.sum())
|
97
|
+
|
88
98
|
def test_gencost1(self):
|
89
99
|
"""Test when gencost is type 1."""
|
90
100
|
mpcgc1 = self.mpc14.copy()
|
tests/test_paths.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import unittest
|
2
2
|
|
3
|
+
import os
|
4
|
+
|
3
5
|
import ams
|
4
|
-
from ams.utils.paths import list_cases
|
6
|
+
from ams.utils.paths import list_cases, get_export_path
|
5
7
|
|
6
8
|
|
7
9
|
class TestPaths(unittest.TestCase):
|
@@ -20,3 +22,68 @@ class TestPaths(unittest.TestCase):
|
|
20
22
|
no_output=True, default_config=True,
|
21
23
|
)
|
22
24
|
self.assertNotEqual(ss, None)
|
25
|
+
|
26
|
+
|
27
|
+
class TestExportPath(unittest.TestCase):
|
28
|
+
|
29
|
+
def setUp(self) -> None:
|
30
|
+
self.ss = ams.load(ams.get_case('5bus/pjm5bus_demo.json'),
|
31
|
+
setup=True, no_output=True)
|
32
|
+
|
33
|
+
def test_no_fullname(self):
|
34
|
+
"""
|
35
|
+
Test export path with no full name specified.
|
36
|
+
"""
|
37
|
+
self.ss.files.full_name = None
|
38
|
+
|
39
|
+
path, file_name = get_export_path(self.ss,
|
40
|
+
'DCOPF',
|
41
|
+
path=None,
|
42
|
+
fmt='csv')
|
43
|
+
|
44
|
+
dir_path, only_file_name = os.path.split(path)
|
45
|
+
self.assertTrue(os.path.exists(dir_path))
|
46
|
+
self.assertIsNotNone(only_file_name)
|
47
|
+
self.assertEqual(only_file_name, file_name)
|
48
|
+
|
49
|
+
def test_no_path(self):
|
50
|
+
"""
|
51
|
+
Test export path with no path specified.
|
52
|
+
"""
|
53
|
+
path, file_name = get_export_path(self.ss,
|
54
|
+
'DCOPF',
|
55
|
+
path=None,
|
56
|
+
fmt='csv')
|
57
|
+
|
58
|
+
dir_path, only_file_name = os.path.split(path)
|
59
|
+
self.assertTrue(os.path.exists(dir_path))
|
60
|
+
self.assertIsNotNone(only_file_name)
|
61
|
+
self.assertEqual(only_file_name, file_name)
|
62
|
+
|
63
|
+
def test_current_path(self):
|
64
|
+
"""
|
65
|
+
Test export path with current path specified.
|
66
|
+
"""
|
67
|
+
path, file_name = get_export_path(self.ss,
|
68
|
+
'DCOPF',
|
69
|
+
path='.',
|
70
|
+
fmt='csv')
|
71
|
+
|
72
|
+
dir_path, only_file_name = os.path.split(path)
|
73
|
+
self.assertTrue(os.path.exists(dir_path))
|
74
|
+
self.assertIsNotNone(only_file_name)
|
75
|
+
self.assertEqual(only_file_name, file_name)
|
76
|
+
|
77
|
+
def test_path_with_file_name(self):
|
78
|
+
"""
|
79
|
+
Test export path with path and file name specified.
|
80
|
+
"""
|
81
|
+
path, file_name = get_export_path(self.ss,
|
82
|
+
'DCOPF',
|
83
|
+
path='./test_export.csv',
|
84
|
+
fmt='csv',)
|
85
|
+
|
86
|
+
dir_path, only_file_name = os.path.split(path)
|
87
|
+
self.assertTrue(os.path.exists(dir_path))
|
88
|
+
self.assertEqual(only_file_name, 'test_export.csv')
|
89
|
+
self.assertEqual(only_file_name, file_name)
|
tests/test_rtn_dcopf.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
import os
|
2
|
+
|
1
3
|
import unittest
|
2
4
|
import numpy as np
|
3
5
|
|
@@ -99,3 +101,21 @@ class TestDCOPF(unittest.TestCase):
|
|
99
101
|
a_dcopf = self.ss.DCOPF.get(src='aBus', attr='v', idx=bus_idx)
|
100
102
|
a_acopf = self.ss.ACOPF.get(src='aBus', attr='v', idx=bus_idx)
|
101
103
|
np.testing.assert_almost_equal(a_dcopf, a_acopf, decimal=3)
|
104
|
+
|
105
|
+
def test_export_csv(self):
|
106
|
+
"""
|
107
|
+
Test `Routine.export_csv()` method.
|
108
|
+
"""
|
109
|
+
self.ss.DCOPF.run(solver='CLARABEL')
|
110
|
+
|
111
|
+
# test when path is none
|
112
|
+
out = self.ss.DCOPF.export_csv()
|
113
|
+
self.assertTrue(os.path.exists(out), "CSV export failed!")
|
114
|
+
|
115
|
+
# test when path is not none
|
116
|
+
out2 = self.ss.DCOPF.export_csv(path='test_dcopf.csv')
|
117
|
+
self.assertTrue(os.path.exists(out2), "CSV export with path failed!")
|
118
|
+
|
119
|
+
# Clean up
|
120
|
+
os.remove(out)
|
121
|
+
os.remove(out2)
|
File without changes
|
File without changes
|