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/routines/grbopt.py
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
"""
|
2
|
+
Routines using gurobi-optimods.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
|
6
|
+
import io
|
7
|
+
|
8
|
+
import scipy.io
|
9
|
+
|
10
|
+
from andes.shared import np, pd
|
11
|
+
from andes.utils.misc import elapsed
|
12
|
+
|
13
|
+
from ams.io.pypower import system2ppc
|
14
|
+
|
15
|
+
from ams.opt import Var, Objective
|
16
|
+
|
17
|
+
from ams.routines.pypower import DCPF1
|
18
|
+
from ams.shared import opf
|
19
|
+
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
|
23
|
+
class OPF(DCPF1):
|
24
|
+
"""
|
25
|
+
Optimal Power Flow (OPF) routine using gurobi-optimods.
|
26
|
+
|
27
|
+
This class provides an interface for performing optimal power flow analysis
|
28
|
+
with gurobi-optimods, supporting both AC and DC OPF formulations.
|
29
|
+
|
30
|
+
In addition to optimizing generator dispatch, this routine can also optimize
|
31
|
+
transmission line statuses (branch switching), enabling topology optimization.
|
32
|
+
Refer to the gurobi-optimods documentation for further details:
|
33
|
+
|
34
|
+
https://gurobi-optimods.readthedocs.io/en/stable/mods/opf/opf.html
|
35
|
+
"""
|
36
|
+
|
37
|
+
def __init__(self, system, config):
|
38
|
+
DCPF1.__init__(self, system, config)
|
39
|
+
self.info = 'Optimal Power Flow'
|
40
|
+
self.type = 'ACED'
|
41
|
+
|
42
|
+
self.obj = Objective(name='obj',
|
43
|
+
info='total cost, placeholder',
|
44
|
+
e_str='sum(c2 * pg**2 + c1 * pg + c0)',
|
45
|
+
sense='min',)
|
46
|
+
|
47
|
+
self.pi = Var(info='Lagrange multiplier on real power mismatch',
|
48
|
+
name='pi', unit='$/p.u.',
|
49
|
+
model='Bus', src=None,)
|
50
|
+
|
51
|
+
self.uld = Var(info='Line commitment decision',
|
52
|
+
name='uld', tex_name=r'u_{l,d}',
|
53
|
+
model='Line', src='u',)
|
54
|
+
|
55
|
+
def solve(self, **kwargs):
|
56
|
+
ppc = system2ppc(self.system)
|
57
|
+
mat = io.BytesIO()
|
58
|
+
scipy.io.savemat(mat, {'mpc': ppc})
|
59
|
+
mat.seek(0)
|
60
|
+
res = opf.solve_opf(opf.read_case_matpower(mat), **kwargs)
|
61
|
+
return res
|
62
|
+
|
63
|
+
def unpack(self, res, **kwargs):
|
64
|
+
"""
|
65
|
+
Unpack the results from the gurobi-optimods.
|
66
|
+
"""
|
67
|
+
# NOTE: Map gurobi-optimods results to PPC-compatible format.
|
68
|
+
# Only relevant columns are populated, as required by `DCOPF.unpack()`.
|
69
|
+
# If future versions of gurobi-optimods provide additional outputs,
|
70
|
+
# this mapping may need to be updated to extract and assign new fields.
|
71
|
+
|
72
|
+
res_new = dict()
|
73
|
+
res_new['success'] = res['success']
|
74
|
+
res_new['et'] = res['et']
|
75
|
+
res_new['f'] = res['f']
|
76
|
+
res_new['baseMVA'] = res['baseMVA']
|
77
|
+
|
78
|
+
bus = pd.DataFrame(res['bus'])
|
79
|
+
res_new['bus'] = np.zeros((self.system.Bus.n, 17))
|
80
|
+
res_new['bus'][:, 7] = bus['Vm'].values
|
81
|
+
res_new['bus'][:, 8] = bus['Va'].values
|
82
|
+
# NOTE: As of v2.3.2, gurobi-optimods does not return LMP
|
83
|
+
|
84
|
+
gen = pd.DataFrame(res['gen'])
|
85
|
+
res_new['gen'] = np.zeros((self.system.StaticGen.n, 14))
|
86
|
+
res_new['gen'][:, 1] = gen['Pg'].values
|
87
|
+
res_new['gen'][:, 2] = gen['Qg'].values
|
88
|
+
|
89
|
+
branch = pd.DataFrame(res['branch'])
|
90
|
+
res_new['branch'] = np.zeros((self.system.Line.n, 14))
|
91
|
+
res_new['branch'][:, 13] = branch['Pf'].values
|
92
|
+
# NOTE: unpack branch_switching decision
|
93
|
+
res_new['branch'][:, 10] = branch['switching'].values
|
94
|
+
return super().unpack(res_new)
|
95
|
+
|
96
|
+
def run(self, **kwargs):
|
97
|
+
"""
|
98
|
+
Run the OPF routine using gurobi-optimods.
|
99
|
+
|
100
|
+
This method invokes `self.solve(**kwargs)`, which internally utilizes
|
101
|
+
`gurobi-optimods` to solve the OPF problem.
|
102
|
+
|
103
|
+
Keyword arguments
|
104
|
+
-------------------
|
105
|
+
- ``opftype`` : str
|
106
|
+
Type of OPF to solve (default: 'AC').
|
107
|
+
- ``branch_switching`` : bool
|
108
|
+
Enable branch switching (default: False).
|
109
|
+
- ``min_active_branches`` : float
|
110
|
+
Defines the minimum number of branches that must be turned on when
|
111
|
+
branch switching is active, i.e. the minimum number of turned on
|
112
|
+
branches is equal to ``numbranches * min_active_branches``. Has no
|
113
|
+
effect if ``branch_switching`` is set to False.
|
114
|
+
- ``use_mip_start`` : bool
|
115
|
+
Use MIP start (default: False).
|
116
|
+
- ``time_limit`` : float
|
117
|
+
Time limit for the solver (default: 0.0, no limit).
|
118
|
+
"""
|
119
|
+
if not self.initialized:
|
120
|
+
self.init()
|
121
|
+
t0, _ = elapsed()
|
122
|
+
|
123
|
+
# --- solve optimization ---
|
124
|
+
t0, _ = elapsed()
|
125
|
+
res = self.solve(**kwargs)
|
126
|
+
self.converged = res['success']
|
127
|
+
self.exit_code = 0 if res['success'] else 1
|
128
|
+
_, s = elapsed(t0)
|
129
|
+
self.exec_time = float(s.split(" ")[0])
|
130
|
+
try:
|
131
|
+
n_iter = res['raw']['output']['iterations']
|
132
|
+
except Exception:
|
133
|
+
n_iter = -1
|
134
|
+
n_iter_str = f"{n_iter} iterations " if n_iter > 1 else f"{n_iter} iteration "
|
135
|
+
if self.exit_code == 0:
|
136
|
+
msg = f"<{self.class_name}> converged in {s}, "
|
137
|
+
msg += n_iter_str + "with gurobi-optimods."
|
138
|
+
logger.warning(msg)
|
139
|
+
try:
|
140
|
+
self.unpack(res)
|
141
|
+
except Exception as e:
|
142
|
+
logger.error(f"Failed to unpack results from {self.class_name}.\n{e}")
|
143
|
+
return False
|
144
|
+
self.system.report()
|
145
|
+
return True
|
146
|
+
else:
|
147
|
+
msg = f"{self.class_name} failed to converge in {s}, "
|
148
|
+
msg += n_iter_str + "with gurobi-optimods."
|
149
|
+
logger.warning(msg)
|
150
|
+
return False
|
ams/routines/pflow.py
CHANGED
@@ -130,7 +130,7 @@ class PFlow(RoutineBase):
|
|
130
130
|
_, s = elapsed(t0)
|
131
131
|
self.exec_time = float(s.split(" ")[0])
|
132
132
|
|
133
|
-
self.unpack()
|
133
|
+
self.unpack(res=None)
|
134
134
|
return self.converged
|
135
135
|
|
136
136
|
def _post_solve(self):
|
@@ -139,7 +139,7 @@ class PFlow(RoutineBase):
|
|
139
139
|
"""
|
140
140
|
return True
|
141
141
|
|
142
|
-
def unpack(self, **kwargs):
|
142
|
+
def unpack(self, res, **kwargs):
|
143
143
|
"""
|
144
144
|
Unpack the results from ANDES PFlow routine.
|
145
145
|
"""
|