ltbams 1.0.12__py3-none-any.whl → 1.0.14__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/cli.py +2 -7
- ams/core/common.py +7 -3
- ams/core/documenter.py +2 -1
- ams/core/matprocessor.py +174 -108
- ams/core/model.py +14 -6
- ams/core/param.py +5 -3
- ams/core/symprocessor.py +8 -2
- ams/core/var.py +1 -1
- ams/extension/eva.py +11 -7
- ams/interface.py +7 -7
- ams/io/json.py +20 -16
- ams/io/matpower.py +10 -6
- ams/io/psse.py +4 -1
- ams/io/xlsx.py +21 -16
- ams/main.py +53 -45
- ams/models/distributed/esd1.py +4 -7
- ams/models/distributed/ev.py +10 -6
- ams/models/distributed/pvd1.py +4 -7
- ams/models/group.py +17 -18
- ams/models/renewable/regc.py +14 -22
- ams/models/timeslot.py +30 -0
- ams/models/zone.py +2 -4
- ams/opt/exprcalc.py +11 -0
- ams/opt/optzbase.py +4 -3
- ams/report.py +2 -7
- ams/routines/dcopf.py +7 -4
- ams/routines/dcopf2.py +14 -4
- ams/routines/dopf.py +2 -2
- ams/routines/ed.py +5 -5
- ams/routines/grbopt.py +2 -0
- ams/routines/pflow.py +1 -1
- ams/routines/pypower.py +8 -0
- ams/routines/routine.py +125 -1
- ams/routines/rted.py +5 -5
- ams/routines/uc.py +2 -2
- ams/shared.py +99 -2
- ams/system.py +103 -18
- ams/utils/paths.py +6 -10
- docs/source/genroutineref.py +12 -0
- docs/source/index.rst +4 -3
- docs/source/release-notes.rst +14 -0
- {ltbams-1.0.12.dist-info → ltbams-1.0.14.dist-info}/METADATA +21 -23
- {ltbams-1.0.12.dist-info → ltbams-1.0.14.dist-info}/RECORD +47 -75
- {ltbams-1.0.12.dist-info → ltbams-1.0.14.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 -190
- 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 -89
- 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 -121
- 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.12.dist-info → ltbams-1.0.14.dist-info}/WHEEL +0 -0
- {ltbams-1.0.12.dist-info → ltbams-1.0.14.dist-info}/entry_points.txt +0 -0
ams/models/timeslot.py
CHANGED
@@ -10,12 +10,42 @@ from ams.core.model import Model
|
|
10
10
|
def str_list_oconv(x):
|
11
11
|
"""
|
12
12
|
Convert list into a list literal.
|
13
|
+
|
14
|
+
Revised from `andes.models.timeseries.str_list_oconv`, where
|
15
|
+
the output type is converted to a list of strings.
|
13
16
|
"""
|
14
17
|
# NOTE: convert elements to string from number first, then join them
|
15
18
|
str_x = [str(i) for i in x]
|
16
19
|
return ','.join(str_x)
|
17
20
|
|
18
21
|
|
22
|
+
class GCommit(ModelData, Model):
|
23
|
+
"""
|
24
|
+
Time slot model for generator commitment decisions.
|
25
|
+
|
26
|
+
This class holds commitment decisions for generators,
|
27
|
+
and should be used in multi-period scheduling routines that need
|
28
|
+
generator commitment decisions.
|
29
|
+
|
30
|
+
For example, in Unit Commitment (UC) problems, there is a variable
|
31
|
+
`ugd` representing the unit commitment decisions for each generator.
|
32
|
+
After solving the UC problem, the `ugd` values can be used for
|
33
|
+
Economic Dispatch (ED) as a parameter.
|
34
|
+
|
35
|
+
.. versionadded:: 1.0.13
|
36
|
+
"""
|
37
|
+
|
38
|
+
def __init__(self, system=None, config=None):
|
39
|
+
ModelData.__init__(self)
|
40
|
+
Model.__init__(self, system, config)
|
41
|
+
|
42
|
+
self.ug = NumParam(info='unit commitment decisions',
|
43
|
+
tex_name=r'u_{g}',
|
44
|
+
iconvert=str_list_iconv,
|
45
|
+
oconvert=str_list_oconv,
|
46
|
+
vtype=int)
|
47
|
+
|
48
|
+
|
19
49
|
class TimeSlot(ModelData, Model):
|
20
50
|
"""
|
21
51
|
Base model for time slot data used in multi-interval scheduling.
|
ams/models/zone.py
CHANGED
@@ -17,9 +17,8 @@ class Zone(ModelData, Model):
|
|
17
17
|
|
18
18
|
Notes
|
19
19
|
-----
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
- A zone is a collection of buses.
|
21
|
+
- The ``Zone`` model is not defined in ANDES up to version 1.9.3.
|
23
22
|
"""
|
24
23
|
|
25
24
|
def __init__(self, system, config):
|
@@ -39,7 +38,6 @@ class Zone(ModelData, Model):
|
|
39
38
|
-------
|
40
39
|
str
|
41
40
|
Formatted table
|
42
|
-
|
43
41
|
"""
|
44
42
|
if self.n:
|
45
43
|
header = ['Zone ID', 'Bus ID']
|
ams/opt/exprcalc.py
CHANGED
@@ -102,6 +102,17 @@ class ExpressionCalc(OptzBase):
|
|
102
102
|
else:
|
103
103
|
return self.optz.value
|
104
104
|
|
105
|
+
@v.setter
|
106
|
+
def v(self, value):
|
107
|
+
"""
|
108
|
+
Set the ExpressionCalc value.
|
109
|
+
"""
|
110
|
+
if self.optz is None:
|
111
|
+
raise ValueError("ExpressionCalc is not evaluated yet.")
|
112
|
+
if not isinstance(value, (int, float, np.ndarray)):
|
113
|
+
raise TypeError(f"Value must be a number or numpy array, got {type(value)}.")
|
114
|
+
self.optz.value = value
|
115
|
+
|
105
116
|
@property
|
106
117
|
def e(self):
|
107
118
|
"""
|
ams/opt/optzbase.py
CHANGED
@@ -174,13 +174,14 @@ class OptzBase:
|
|
174
174
|
"""
|
175
175
|
Return all the indexes of this item.
|
176
176
|
|
177
|
-
.. note::
|
178
|
-
New in version 1.0.0.
|
179
|
-
|
180
177
|
Returns
|
181
178
|
-------
|
182
179
|
list
|
183
180
|
A list of indexes.
|
181
|
+
|
182
|
+
Notes
|
183
|
+
-----
|
184
|
+
.. versionadded:: 1.0.0
|
184
185
|
"""
|
185
186
|
|
186
187
|
if self.is_group:
|
ams/report.py
CHANGED
@@ -30,13 +30,8 @@ def report_info(system) -> list:
|
|
30
30
|
|
31
31
|
class Report:
|
32
32
|
"""
|
33
|
-
Report class to store routine analysis reports
|
34
|
-
|
35
|
-
Notes
|
36
|
-
-----
|
37
|
-
Revised from the ANDES project (https://github.com/CURENT/andes).
|
38
|
-
Original author: Hantao Cui
|
39
|
-
License: GPL3
|
33
|
+
Report class to store routine analysis reports,
|
34
|
+
revised from `andes.report.Report`.
|
40
35
|
"""
|
41
36
|
|
42
37
|
def __init__(self, system):
|
ams/routines/dcopf.py
CHANGED
@@ -17,19 +17,22 @@ logger = logging.getLogger(__name__)
|
|
17
17
|
|
18
18
|
class DCOPF(DCPFBase):
|
19
19
|
"""
|
20
|
-
DC optimal power flow (DCOPF).
|
20
|
+
DC optimal power flow (DCOPF) using B-theta formulation.
|
21
21
|
|
22
22
|
Notes
|
23
23
|
-----
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
- The nodal price is calculated as ``pi`` in ``pic``.
|
25
|
+
- Devices online status of ``StaticGen``, ``StaticLoad``, and ``Shunt`` are considered in the connectivity
|
26
|
+
matrices ``Cft``, ``Cg``, ``Cl``, and ``Csh``.
|
27
27
|
|
28
28
|
References
|
29
29
|
----------
|
30
30
|
1. R. D. Zimmerman, C. E. Murillo-Sanchez, and R. J. Thomas, “MATPOWER: Steady-State
|
31
31
|
Operations, Planning, and Analysis Tools for Power Systems Research and Education,” IEEE
|
32
32
|
Trans. Power Syst., vol. 26, no. 1, pp. 12-19, Feb. 2011
|
33
|
+
2. Y. Chen et al., "Security-Constrained Unit Commitment for Electricity Market: Modeling,
|
34
|
+
Solution Methods, and Future Challenges," in IEEE Transactions on Power Systems, vol. 38, no. 5,
|
35
|
+
pp. 4668-4681, Sept. 2023
|
33
36
|
"""
|
34
37
|
|
35
38
|
def __init__(self, system, config):
|
ams/routines/dcopf2.py
CHANGED
@@ -18,15 +18,25 @@ logger = logging.getLogger(__name__)
|
|
18
18
|
|
19
19
|
class DCOPF2(DCOPF):
|
20
20
|
"""
|
21
|
-
DC optimal power flow (DCOPF) using PTDF.
|
21
|
+
DC optimal power flow (DCOPF) using PTDF formulation.
|
22
|
+
|
22
23
|
For large cases, it is recommended to build the PTDF first, especially when incremental
|
23
24
|
build is necessary.
|
24
25
|
|
25
26
|
Notes
|
26
27
|
-----
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
- This routine requires PTDF matrix.
|
29
|
+
- Nodal price ``pi`` is calculated with three parts.
|
30
|
+
- Bus angle ``aBus`` is calculated after solving the problem.
|
31
|
+
|
32
|
+
References
|
33
|
+
----------
|
34
|
+
1. R. D. Zimmerman, C. E. Murillo-Sanchez, and R. J. Thomas, “MATPOWER: Steady-State
|
35
|
+
Operations, Planning, and Analysis Tools for Power Systems Research and Education,” IEEE
|
36
|
+
Trans. Power Syst., vol. 26, no. 1, pp. 12-19, Feb. 2011
|
37
|
+
2. Y. Chen et al., "Security-Constrained Unit Commitment for Electricity Market: Modeling,
|
38
|
+
Solution Methods, and Future Challenges," in IEEE Transactions on Power Systems, vol. 38, no. 5,
|
39
|
+
pp. 4668-4681, Sept. 2023
|
30
40
|
"""
|
31
41
|
|
32
42
|
def __init__(self, system, config):
|
ams/routines/dopf.py
CHANGED
@@ -17,7 +17,7 @@ class DOPF(DCOPF):
|
|
17
17
|
UNDER DEVELOPMENT!
|
18
18
|
|
19
19
|
References
|
20
|
-
|
20
|
+
----------
|
21
21
|
1. L. Bai, J. Wang, C. Wang, C. Chen, and F. Li, “Distribution Locational Marginal Pricing (DLMP)
|
22
22
|
for Congestion Management and Voltage Support,” IEEE Trans. Power Syst., vol. 33, no. 4,
|
23
23
|
pp. 4061-4073, Jul. 2018, doi: 10.1109/TPWRS.2017.2767632.
|
@@ -116,7 +116,7 @@ class DOPFVIS(DOPF):
|
|
116
116
|
UNDER DEVELOPMENT!
|
117
117
|
|
118
118
|
References
|
119
|
-
|
119
|
+
----------
|
120
120
|
1. L. Bai, J. Wang, C. Wang, C. Chen, and F. Li, “Distribution Locational Marginal Pricing (DLMP)
|
121
121
|
for Congestion Management and Voltage Support,” IEEE Trans. Power Syst., vol. 33, no. 4,
|
122
122
|
pp. 4061-4073, Jul. 2018, doi: 10.1109/TPWRS.2017.2767632.
|
ams/routines/ed.py
CHANGED
@@ -120,11 +120,11 @@ class ED(RTED, MPBase, SRBase):
|
|
120
120
|
|
121
121
|
Notes
|
122
122
|
-----
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
123
|
+
- Formulations has been adjusted with interval ``config.t``
|
124
|
+
- The tie-line flow is not implemented in this model.
|
125
|
+
- ``EDTSlot.ug`` is used instead of ``StaticGen.u`` for generator commitment.
|
126
|
+
- Following reserves are balanced for each "Area": RegUp reserve ``rbu``,
|
127
|
+
RegDn reserve ``rbd``, and Spinning reserve ``rsr``.
|
128
128
|
"""
|
129
129
|
|
130
130
|
def __init__(self, system, config):
|
ams/routines/grbopt.py
CHANGED
ams/routines/pflow.py
CHANGED
@@ -30,7 +30,7 @@ class PFlow(RoutineBase):
|
|
30
30
|
----------
|
31
31
|
1. M. L. Crow, Computational methods for electric power systems. 2015.
|
32
32
|
2. ANDES Documentation - Simulation and Plot.
|
33
|
-
https://
|
33
|
+
https://andes.readthedocs.io/en/stable/_examples/ex1.html
|
34
34
|
"""
|
35
35
|
|
36
36
|
def __init__(self, system, config):
|
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):
|
@@ -362,6 +364,8 @@ class PFlow1(DCPF1):
|
|
362
364
|
MATPOWER User's Manual, section on Power Flow.
|
363
365
|
- Fast-Decoupled (XB version) and Fast-Decoupled (BX version) algorithms are
|
364
366
|
not fully supported yet.
|
367
|
+
|
368
|
+
.. versionadded:: 1.0.10
|
365
369
|
"""
|
366
370
|
|
367
371
|
def __init__(self, system, config):
|
@@ -436,6 +440,8 @@ class DCOPF1(DCPF1):
|
|
436
440
|
- For detailed mathematical formulations and algorithmic details, refer to the
|
437
441
|
MATPOWER User's Manual, section on Optimal Power Flow.
|
438
442
|
- Algorithms 400, 500, 600, and 700 are not fully supported yet.
|
443
|
+
|
444
|
+
.. versionadded:: 1.0.10
|
439
445
|
"""
|
440
446
|
|
441
447
|
def __init__(self, system, config):
|
@@ -586,6 +592,8 @@ class ACOPF1(DCOPF1):
|
|
586
592
|
- This class does not implement the AMS-style AC optimal power flow formulation.
|
587
593
|
- For detailed mathematical formulations and algorithmic details, refer to the
|
588
594
|
MATPOWER User's Manual, section on Optimal Power Flow.
|
595
|
+
|
596
|
+
.. versionadded:: 1.0.10
|
589
597
|
"""
|
590
598
|
|
591
599
|
def __init__(self, system, config):
|
ams/routines/routine.py
CHANGED
@@ -3,8 +3,9 @@ Module for routine data.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
-
|
6
|
+
import json
|
7
7
|
from collections import OrderedDict
|
8
|
+
from typing import Optional, Union, Type, Iterable, Dict
|
8
9
|
|
9
10
|
import numpy as np
|
10
11
|
|
@@ -431,6 +432,95 @@ class RoutineBase:
|
|
431
432
|
logger.warning(msg)
|
432
433
|
return False
|
433
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
|
+
.. versionadded:: 1.0.13
|
450
|
+
"""
|
451
|
+
try:
|
452
|
+
with open(path, 'r') as f:
|
453
|
+
data = json.load(f)
|
454
|
+
except Exception as e:
|
455
|
+
logger.error(f"Failed to load JSON file: {e}")
|
456
|
+
return False
|
457
|
+
|
458
|
+
if not self.initialized:
|
459
|
+
self.init()
|
460
|
+
|
461
|
+
# Unpack variables and expressions from JSON
|
462
|
+
for group, group_data in data.items():
|
463
|
+
if not isinstance(group_data, dict):
|
464
|
+
continue
|
465
|
+
for key, values in group_data.items():
|
466
|
+
if key == 'idx':
|
467
|
+
continue
|
468
|
+
# Find the corresponding variable or expression
|
469
|
+
if key in self.vars:
|
470
|
+
var = self.vars[key]
|
471
|
+
# Assign values to the variable
|
472
|
+
try:
|
473
|
+
var.v = np.array(values)
|
474
|
+
except Exception as e:
|
475
|
+
logger.warning(f"Failed to assign values to var '{key}': {e}")
|
476
|
+
elif key in self.exprs:
|
477
|
+
continue
|
478
|
+
elif key in self.exprcs:
|
479
|
+
exprc = self.exprcs[key]
|
480
|
+
# Assign values to the expression calculation
|
481
|
+
try:
|
482
|
+
exprc.v = np.array(values)
|
483
|
+
except Exception as e:
|
484
|
+
logger.warning(f"Failed to assign values to exprc '{key}': {e}")
|
485
|
+
logger.info(f"Loaded results from {path}")
|
486
|
+
return True
|
487
|
+
|
488
|
+
def export_json(self, path=None):
|
489
|
+
"""
|
490
|
+
Export scheduling results to a json file.
|
491
|
+
|
492
|
+
Parameters
|
493
|
+
----------
|
494
|
+
path : str, optional
|
495
|
+
Path of the json file to export.
|
496
|
+
|
497
|
+
Returns
|
498
|
+
-------
|
499
|
+
str
|
500
|
+
The exported json file name
|
501
|
+
|
502
|
+
.. versionadded:: 1.0.13
|
503
|
+
"""
|
504
|
+
if not self.converged:
|
505
|
+
logger.warning("Routine did not converge, aborting export.")
|
506
|
+
return None
|
507
|
+
|
508
|
+
path, file_name = get_export_path(self.system,
|
509
|
+
self.class_name + '_out',
|
510
|
+
path=path, fmt='json')
|
511
|
+
|
512
|
+
data_dict = dict()
|
513
|
+
|
514
|
+
group_data(self, data_dict, self.vars, 'v')
|
515
|
+
group_data(self, data_dict, self.exprs, 'v')
|
516
|
+
group_data(self, data_dict, self.exprcs, 'v')
|
517
|
+
|
518
|
+
with open(path, 'w') as f:
|
519
|
+
json.dump(data_dict, f, indent=4,
|
520
|
+
default=lambda x: x.tolist() if isinstance(x, np.ndarray) else x)
|
521
|
+
|
522
|
+
return file_name
|
523
|
+
|
434
524
|
def export_csv(self, path=None):
|
435
525
|
"""
|
436
526
|
Export scheduling results to a csv file.
|
@@ -1022,3 +1112,37 @@ def collect_data(rtn: RoutineBase, data_dict: Dict, items: Dict, attr: str):
|
|
1022
1112
|
logger.debug(f"Error with collecting data for '{key}': {e}")
|
1023
1113
|
data_v = [np.nan] * len(idx_v)
|
1024
1114
|
data_dict.update(OrderedDict(zip([f'{key} {dev}' for dev in idx_v], data_v)))
|
1115
|
+
|
1116
|
+
|
1117
|
+
def group_data(rtn: RoutineBase, data_dict: Dict, items: Dict, attr: str):
|
1118
|
+
"""
|
1119
|
+
Collect data for export from grouped items.
|
1120
|
+
|
1121
|
+
Parameters
|
1122
|
+
----------
|
1123
|
+
rtn : ams.routines.routine.RoutineBase
|
1124
|
+
The routine to collect data from.
|
1125
|
+
data_dict : Dict
|
1126
|
+
The data dictionary to populate.
|
1127
|
+
items : dict
|
1128
|
+
Dictionary of items to collect data from.
|
1129
|
+
attr : str
|
1130
|
+
Attribute to collect data for.
|
1131
|
+
|
1132
|
+
.. versionadded:: 1.0.13
|
1133
|
+
"""
|
1134
|
+
for key, item in items.items():
|
1135
|
+
if item.owner is None:
|
1136
|
+
continue
|
1137
|
+
if item.owner.class_name not in data_dict.keys():
|
1138
|
+
idx_v = item.get_all_idxes()
|
1139
|
+
data_dict[item.owner.class_name] = dict(idx=idx_v)
|
1140
|
+
else:
|
1141
|
+
idx_v = data_dict[item.owner.class_name]['idx']
|
1142
|
+
try:
|
1143
|
+
data_v = rtn.get(src=key, attr=attr, idx=idx_v,
|
1144
|
+
horizon=rtn.timeslot.v if hasattr(rtn, 'timeslot') else None)
|
1145
|
+
except Exception as e:
|
1146
|
+
logger.warning(f"Error with collecting data for '{key}': {e}")
|
1147
|
+
data_v = [np.nan] * item.owner.n
|
1148
|
+
data_dict[item.owner.class_name][key] = data_v
|
ams/routines/rted.py
CHANGED
@@ -125,10 +125,10 @@ class RTED(DCOPF, RTEDBase, SFRBase):
|
|
125
125
|
|
126
126
|
Notes
|
127
127
|
-----
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
128
|
+
- Formulations has been adjusted with interval ``config.t``, 5/60 [Hour] by default.
|
129
|
+
- The tie-line flow related constraints are ommited in this formulation.
|
130
|
+
- Power generation is balanced for the entire system.
|
131
|
+
- SFR is balanced for each area.
|
132
132
|
"""
|
133
133
|
|
134
134
|
def __init__(self, system, config):
|
@@ -487,7 +487,7 @@ class RTEDVIS(RTED, VISBase):
|
|
487
487
|
Please ensure that the parameters `dvm` and `dvd` are set according to the system base.
|
488
488
|
|
489
489
|
References
|
490
|
-
|
490
|
+
----------
|
491
491
|
1. B. She, F. Li, H. Cui, J. Wang, Q. Zhang and R. Bo, "Virtual Inertia Scheduling (VIS) for
|
492
492
|
Real-Time Economic Dispatch of IBR-Penetrated Power Systems," in IEEE Transactions on
|
493
493
|
Sustainable Energy, vol. 15, no. 2, pp. 938-951, April 2024, doi: 10.1109/TSTE.2023.3319307.
|
ams/routines/uc.py
CHANGED
@@ -69,8 +69,8 @@ class UC(DCOPF, RTEDBase, MPBase, SRBase, NSRBase):
|
|
69
69
|
|
70
70
|
Notes
|
71
71
|
-----
|
72
|
-
|
73
|
-
|
72
|
+
- The formulations has been adjusted with interval ``config.t``
|
73
|
+
- The tie-line flow has not been implemented in formulations.
|
74
74
|
|
75
75
|
References
|
76
76
|
----------
|
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
|
@@ -14,6 +15,7 @@ from andes.utils.lazyimport import LazyImport
|
|
14
15
|
|
15
16
|
from andes.system import System as adSystem
|
16
17
|
|
18
|
+
from ._version import get_versions
|
17
19
|
|
18
20
|
logger = logging.getLogger(__name__)
|
19
21
|
|
@@ -24,6 +26,7 @@ ppoption = LazyImport('from pypower.ppoption import ppoption')
|
|
24
26
|
runpf = LazyImport('from pypower.runpf import runpf')
|
25
27
|
runopf = LazyImport('from pypower.runopf import runopf')
|
26
28
|
opf = LazyImport('from gurobi_optimods import opf')
|
29
|
+
tqdm = LazyImport('from tqdm.auto import tqdm')
|
27
30
|
|
28
31
|
# --- an empty ANDES system ---
|
29
32
|
empty_adsys = adSystem(autogen_stale=False)
|
@@ -44,6 +47,15 @@ _max_length = 80 # NOQA
|
|
44
47
|
copyright_msg = "Copyright (C) 2023-2025 Jinning Wang"
|
45
48
|
nowarranty_msg = "AMS comes with ABSOLUTELY NO WARRANTY"
|
46
49
|
report_time = strftime("%m/%d/%Y %I:%M:%S %p")
|
50
|
+
version_msg = f"AMS {get_versions()['version']}"
|
51
|
+
|
52
|
+
summary_row = {'field': 'Info',
|
53
|
+
'comment': version_msg,
|
54
|
+
'comment2': report_time,
|
55
|
+
'comment3': nowarranty_msg,
|
56
|
+
'comment4': copyright_msg}
|
57
|
+
|
58
|
+
summary_name = "Summary" # ensure model Summary's name is consistent
|
47
59
|
|
48
60
|
# NOTE: copied from CVXPY documentation, last checked on 2024/10/30, v1.5
|
49
61
|
mip_solvers = ['CBC', 'COPT', 'GLPK_MI', 'CPLEX', 'GUROBI',
|
@@ -118,7 +130,7 @@ def skip_unittest_without_PYPOWER(f):
|
|
118
130
|
try:
|
119
131
|
import pypower # NOQA
|
120
132
|
except ImportError:
|
121
|
-
raise unittest.SkipTest("PYPOWER is not
|
133
|
+
raise unittest.SkipTest("PYPOWER is not available.")
|
122
134
|
return f(*args, **kwargs)
|
123
135
|
return wrapper
|
124
136
|
|
@@ -131,6 +143,91 @@ def skip_unittest_without_gurobi_optimods(f):
|
|
131
143
|
try:
|
132
144
|
import gurobi_optimods # NOQA
|
133
145
|
except ImportError:
|
134
|
-
raise unittest.SkipTest("
|
146
|
+
raise unittest.SkipTest("gurobi_optimods is not available.")
|
135
147
|
return f(*args, **kwargs)
|
136
148
|
return wrapper
|
149
|
+
|
150
|
+
|
151
|
+
def _init_pbar(total, unit, no_tqdm):
|
152
|
+
"""Initializes and returns a tqdm progress bar."""
|
153
|
+
pbar = tqdm(total=total, unit=unit, ncols=80, ascii=True,
|
154
|
+
file=sys.stdout, disable=no_tqdm)
|
155
|
+
pbar.update(0)
|
156
|
+
return pbar
|
157
|
+
|
158
|
+
|
159
|
+
def _update_pbar(pbar, current, total):
|
160
|
+
"""Updates and closes the progress bar."""
|
161
|
+
perc = np.round(min((current / total) * 100, 100), 2)
|
162
|
+
if pbar.total is not None: # Check if pbar is still valid
|
163
|
+
last_pc = pbar.n / pbar.total * 100 # Get current percentage based on updated value
|
164
|
+
else:
|
165
|
+
last_pc = 0
|
166
|
+
|
167
|
+
perc_diff = perc - last_pc
|
168
|
+
if perc_diff >= 1:
|
169
|
+
pbar.update(perc_diff)
|
170
|
+
|
171
|
+
# Ensure pbar finishes at 100% and closes
|
172
|
+
if pbar.n < pbar.total: # Check if it's not already at total
|
173
|
+
pbar.update(pbar.total - pbar.n) # Update remaining
|
174
|
+
pbar.close()
|
175
|
+
|
176
|
+
|
177
|
+
def ams_params_not_in_andes(mdl_name, am_params):
|
178
|
+
"""
|
179
|
+
Helper function to return parameters not in the ANDES model.
|
180
|
+
If the model is not in the ANDES system, it returns an empty list.
|
181
|
+
|
182
|
+
Parameters
|
183
|
+
----------
|
184
|
+
mdl_name : str
|
185
|
+
The name of the model.
|
186
|
+
am_params : list
|
187
|
+
A list of parameters from the AMS model.
|
188
|
+
|
189
|
+
Returns
|
190
|
+
-------
|
191
|
+
list
|
192
|
+
A list of parameters that are not in the ANDES model.
|
193
|
+
"""
|
194
|
+
if mdl_name not in ad_models:
|
195
|
+
return []
|
196
|
+
ad_params = list(empty_adsys.models[mdl_name].params.keys())
|
197
|
+
return list(set(am_params) - set(ad_params))
|
198
|
+
|
199
|
+
|
200
|
+
def model2df(instance, skip_empty, to_andes):
|
201
|
+
"""
|
202
|
+
Prepare a DataFrame from the model instance for output.
|
203
|
+
|
204
|
+
Parameters
|
205
|
+
----------
|
206
|
+
instance : ams.model.Model
|
207
|
+
The model instance to prepare.
|
208
|
+
skip_empty : bool
|
209
|
+
Whether to skip empty models.
|
210
|
+
to_andes : bool
|
211
|
+
Whether to prepare the DataFrame for ANDES.
|
212
|
+
|
213
|
+
Returns
|
214
|
+
-------
|
215
|
+
pd.DataFrame
|
216
|
+
The prepared DataFrame.
|
217
|
+
"""
|
218
|
+
name = instance.class_name
|
219
|
+
|
220
|
+
if skip_empty and instance.n == 0:
|
221
|
+
return None
|
222
|
+
|
223
|
+
if name not in ad_models and to_andes:
|
224
|
+
return None
|
225
|
+
|
226
|
+
instance.cache.refresh("df_in")
|
227
|
+
df = instance.cache.df_in
|
228
|
+
|
229
|
+
if to_andes:
|
230
|
+
skipped_params = ams_params_not_in_andes(name, df.columns.tolist())
|
231
|
+
df = df.drop(skipped_params, axis=1, errors='ignore')
|
232
|
+
|
233
|
+
return df
|