ltbams 1.0.9__py3-none-any.whl → 1.0.11__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/documenter.py +130 -62
- ams/core/model.py +1 -1
- ams/core/symprocessor.py +1 -1
- ams/extension/eva.py +1 -1
- ams/interface.py +42 -26
- ams/io/matpower.py +31 -17
- ams/io/psse.py +278 -1
- ams/main.py +2 -2
- ams/models/group.py +2 -1
- ams/models/static/pq.py +7 -3
- ams/opt/param.py +1 -2
- 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 +155 -0
- ams/routines/pflow.py +2 -2
- ams/routines/pypower.py +622 -0
- ams/routines/routine.py +4 -10
- ams/routines/uc.py +2 -2
- ams/shared.py +26 -43
- ams/system.py +120 -4
- ams/utils/paths.py +3 -3
- docs/source/api.rst +2 -0
- docs/source/examples/index.rst +2 -1
- docs/source/genroutineref.py +1 -1
- 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 +37 -57
- {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/METADATA +85 -48
- {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/RECORD +58 -75
- {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/WHEEL +1 -1
- tests/test_1st_system.py +2 -2
- tests/test_case.py +14 -14
- tests/test_export_csv.py +1 -1
- tests/test_interface.py +24 -2
- tests/test_io.py +50 -0
- 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.9.dist-info → ltbams-1.0.11.dist-info}/entry_points.txt +0 -0
- {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/top_level.txt +0 -0
ams/io/psse.py
CHANGED
@@ -3,4 +3,281 @@ PSS/E .raw reader for AMS.
|
|
3
3
|
This module is the existing module in ``andes.io.psse``.
|
4
4
|
"""
|
5
5
|
|
6
|
-
from andes.io.psse import
|
6
|
+
from andes.io.psse import testlines # NOQA
|
7
|
+
from andes.io.psse import read as ad_read
|
8
|
+
from andes.io.xlsx import confirm_overwrite
|
9
|
+
from andes.shared import rad2deg, pd
|
10
|
+
|
11
|
+
from ams import __version__ as version
|
12
|
+
from ams.shared import copyright_msg, nowarranty_msg, report_time
|
13
|
+
|
14
|
+
|
15
|
+
def read(system, file):
|
16
|
+
"""
|
17
|
+
Read PSS/E RAW file v32/v33 formats.
|
18
|
+
|
19
|
+
Revised from ``andes.io.psse.read`` to complete model ``Zone`` when necessary.
|
20
|
+
"""
|
21
|
+
ret = ad_read(system, file)
|
22
|
+
# Extract zone data
|
23
|
+
zone = system.Bus.zone.v
|
24
|
+
zone_map = {}
|
25
|
+
|
26
|
+
# Check if there are zones to process
|
27
|
+
# NOTE: since `Zone` and `Area` below to group `Collection`, we add
|
28
|
+
# numerical Zone idx after the last Area idx.
|
29
|
+
if zone:
|
30
|
+
n_zone = system.Area.n
|
31
|
+
for z in set(zone):
|
32
|
+
# Add new zone and update the mapping
|
33
|
+
z_new = system.add(
|
34
|
+
'Zone',
|
35
|
+
param_dict=dict(idx=n_zone + 1, name=f'{n_zone + 1}')
|
36
|
+
)
|
37
|
+
zone_map[z] = z_new
|
38
|
+
n_zone += 1
|
39
|
+
|
40
|
+
# Update the zone values in the system
|
41
|
+
system.Bus.zone.v = [zone_map[z] for z in zone]
|
42
|
+
return ret
|
43
|
+
|
44
|
+
|
45
|
+
def write_raw(system, outfile: str, overwrite: bool = None):
|
46
|
+
"""
|
47
|
+
Convert AMS system to PSS/E RAW file.
|
48
|
+
|
49
|
+
Parameters
|
50
|
+
----------
|
51
|
+
system : System
|
52
|
+
The AMS system to be converted.
|
53
|
+
outfile : str
|
54
|
+
The output file path.
|
55
|
+
overwrite : bool, optional
|
56
|
+
If True, overwrite the file if it exists. If False, do not overwrite.
|
57
|
+
"""
|
58
|
+
if not confirm_overwrite(outfile, overwrite=overwrite):
|
59
|
+
return False
|
60
|
+
|
61
|
+
mva = system.config.mva
|
62
|
+
freq = system.config.freq
|
63
|
+
name = system.files.name
|
64
|
+
|
65
|
+
with open(outfile, 'w') as f:
|
66
|
+
# PSS/E version and date
|
67
|
+
f.write(f"0, {mva:.2f}, 33, 0, 1, {freq:.2f} ")
|
68
|
+
|
69
|
+
f.write(f"/ PSS/E 33 RAW, {report_time}\n")
|
70
|
+
f.write(f"Created by AMS {version}\n")
|
71
|
+
f.write(f"{copyright_msg}\n")
|
72
|
+
f.write(f"{nowarranty_msg}\n")
|
73
|
+
f.write(f"{name.upper()} \n")
|
74
|
+
|
75
|
+
# --- Bus ---
|
76
|
+
bus = system.Bus.cache.df_in
|
77
|
+
# 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
|
78
|
+
# ID, Name, BaseKV, Type, Area, Zone, Owner, Vm, Va, Vmax, Vmin
|
79
|
+
# Define column widths for alignment
|
80
|
+
column_widths = [6, 8, 8, 2, 3, 3, 3, 8, 8, 8, 8]
|
81
|
+
bus_owner = {}
|
82
|
+
for i, o in enumerate(set(bus['owner'])):
|
83
|
+
bus_owner[o] = i + 1
|
84
|
+
for row in bus.itertuples():
|
85
|
+
# Prepare the data for each column
|
86
|
+
data = [
|
87
|
+
int(row.idx), row.name, row.Vn, int(row.type),
|
88
|
+
int(system.Collection.idx2uid(row.area) + 1),
|
89
|
+
int(system.Collection.idx2uid(row.zone) + 1),
|
90
|
+
int(bus_owner[row.owner]),
|
91
|
+
float(row.v0), float(row.a0 * rad2deg),
|
92
|
+
float(row.vmax), float(row.vmin)]
|
93
|
+
# Format each column with ',' as the delimiter
|
94
|
+
formatted_row = ",".join(
|
95
|
+
f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
|
96
|
+
for value, width in zip(data, column_widths)
|
97
|
+
) + "\n"
|
98
|
+
# Write the formatted row to the file
|
99
|
+
f.write(formatted_row)
|
100
|
+
|
101
|
+
# --- Load ---
|
102
|
+
f.write("0 / END OF BUS DATA, BEGIN LOAD DATA\n")
|
103
|
+
load = system.PQ.cache.df_in
|
104
|
+
# 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
|
105
|
+
# Bus, Id, Status, Area, Zone, PL(MW), QL (MW), IP, IQ, YP, YQ, Owner
|
106
|
+
# NOTE: load are converted to constant load, IP, IQ, YP, YQ are ignored by setting to 0
|
107
|
+
column_widths = [6, 8, 2, 3, 3, 10, 10, 2, 2, 10, 10, 3]
|
108
|
+
for row in load.itertuples():
|
109
|
+
# Prepare the data for each column
|
110
|
+
data = [
|
111
|
+
int(row.bus), # Bus number
|
112
|
+
int(system.PQ.idx2uid(row.idx) + 1), # Load ID (unique index + 1)
|
113
|
+
int(row.u), # Status
|
114
|
+
int(system.Collection.idx2uid(system.PQ.get('area', row.idx, 'v')) + 1), # Area number
|
115
|
+
int(system.Collection.idx2uid(system.PQ.get('zone', row.idx, 'v')) + 1), # Zone number
|
116
|
+
float(row.p0 * mva), # PL (MW)
|
117
|
+
float(row.q0 * mva), # QL (MVar)
|
118
|
+
0.0, # IP (ignored, set to 0)
|
119
|
+
0.0, # IQ (ignored, set to 0)
|
120
|
+
0.0, # YP (ignored, set to 0)
|
121
|
+
0.0, # YQ (ignored, set to 0)
|
122
|
+
int(bus_owner[row.owner]) # Owner
|
123
|
+
]
|
124
|
+
# Format each column with ',' as the delimiter
|
125
|
+
formatted_row = ",".join(
|
126
|
+
f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
|
127
|
+
for value, width in zip(data, column_widths)
|
128
|
+
) + "\n"
|
129
|
+
# Write the formatted row to the file
|
130
|
+
f.write(formatted_row)
|
131
|
+
|
132
|
+
# --- Fixed Shunt ---
|
133
|
+
f.write("0 / END OF LOAD DATA, BEGIN FIXED SHUNT DATA\n")
|
134
|
+
shunt = system.Shunt.cache.df_in
|
135
|
+
# 0, 1, 2, 3, 4
|
136
|
+
# Bus, Id, Status, G (MW), B (Mvar)
|
137
|
+
# NOTE: ANDES parse v33 swshunt into fixed shunt
|
138
|
+
column_widths = [6, 8, 2, 10, 10]
|
139
|
+
for row in shunt.itertuples():
|
140
|
+
# Prepare the data for each column
|
141
|
+
data = [
|
142
|
+
int(row.bus), # Bus number
|
143
|
+
int(system.Shunt.idx2uid(row.idx) + 1), # Shunt ID (unique index + 1)
|
144
|
+
int(row.u), # Status
|
145
|
+
float(row.g * mva), # Conductance (MW at system base)
|
146
|
+
float(row.b * mva) # Susceptance (Mvar at system base)
|
147
|
+
]
|
148
|
+
|
149
|
+
# Format each column with ',' as the delimiter
|
150
|
+
formatted_row = ",".join(
|
151
|
+
f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
|
152
|
+
for value, width in zip(data, column_widths)
|
153
|
+
) + "\n"
|
154
|
+
# Write the formatted row to the file
|
155
|
+
f.write(formatted_row)
|
156
|
+
|
157
|
+
# --- Generator ---
|
158
|
+
f.write("0 / END OF FIXED SHUNT DATA, BEGIN GENERATOR DATA\n")
|
159
|
+
pv = system.PV.cache.df_in
|
160
|
+
slack = system.Slack.cache.df_in
|
161
|
+
|
162
|
+
gen = pd.concat([pv, slack.drop(columns=['a0'])], axis=0)
|
163
|
+
gen["subidx"] = gen.groupby('bus').cumcount() + 1
|
164
|
+
|
165
|
+
column_widths = [6, 8, 2, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8]
|
166
|
+
# 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
167
|
+
# I, ID, PG, QG, QT, QB, VS, IREG, MBASE, ZR, ZX, RT, XT, GTAP, STAT,
|
168
|
+
# 15, 16, 17, 18, 19, ..., 26, 27
|
169
|
+
# RMPCT, PT, PB, O1, F1, ..., O4, F4, WMOD, WPF
|
170
|
+
# The columns above for v33 is different from the manual of v34.5, which includes two new columns:
|
171
|
+
# `NREG`` at 8 and `BSLOD` before `O1`
|
172
|
+
for row in gen.itertuples():
|
173
|
+
# Prepare the data for each column
|
174
|
+
data = [
|
175
|
+
int(row.bus), # I: Bus number
|
176
|
+
int(row.subidx), # ID: Generator ID (subindex)
|
177
|
+
float(row.p0 * mva), # PG: Generated MW
|
178
|
+
float(row.q0 * mva), # QG: Generated MVar
|
179
|
+
float(row.qmax * mva), # QT: Max Q (MVar)
|
180
|
+
float(row.qmin * mva), # QB: Min Q (MVar)
|
181
|
+
float(row.v0), # VS: Setpoint voltage (p.u.)
|
182
|
+
int(0), # IREG: Regulated bus (not tracked)
|
183
|
+
float(row.Sn if hasattr(row, 'Sn') else mva), # MBASE: Machine base MVA
|
184
|
+
float(row.ra), # ZR: Armature resistance (p.u.)
|
185
|
+
float(row.xs), # ZX: Synchronous reactance (p.u.)
|
186
|
+
float(0), # RT: Step-up transformer resistance (not tracked)
|
187
|
+
float(0), # XT: Step-up transformer reactance (not tracked)
|
188
|
+
int(0), # GTAP: Step-up transformer off-nominal turns ratio (not tracked)
|
189
|
+
int(row.u) # STAT: Status
|
190
|
+
]
|
191
|
+
# Format each column with ',' as the delimiter
|
192
|
+
formatted_row = ",".join(
|
193
|
+
f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
|
194
|
+
for value, width in zip(data, column_widths)
|
195
|
+
) + "\n"
|
196
|
+
# Write the formatted row to the file
|
197
|
+
f.write(formatted_row)
|
198
|
+
|
199
|
+
# --- Line ---
|
200
|
+
f.write("0 / END OF GENERATOR DATA, BEGIN BRANCH DATA\n")
|
201
|
+
line = system.Line.cache.df_in
|
202
|
+
branch = line[line['trans'] == 0].reset_index(drop=True)
|
203
|
+
transf = line[line['trans'] == 1].reset_index(drop=True)
|
204
|
+
# 1) branch
|
205
|
+
# 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
|
206
|
+
# I, J, CKT, R, X, B, RATEA, RATEB, RATEC, GI, BI, GJ, BJ, ST, LEN, O1, F1, ..., O4, F4
|
207
|
+
column_widths = [6, 6, 4, 10, 10, 10, 8, 8, 8, 8, 8, 8, 8, 3, 8]
|
208
|
+
for row in branch.itertuples():
|
209
|
+
data = [
|
210
|
+
int(row.bus1), # I: From bus number
|
211
|
+
int(row.bus2), # J: To bus number
|
212
|
+
"'1'", # CKT: Circuit ID (default '1')
|
213
|
+
float(row.r), # R: Resistance (p.u.)
|
214
|
+
float(row.x), # X: Reactance (p.u.)
|
215
|
+
float(row.b), # B: Total line charging (p.u.)
|
216
|
+
float(row.rate_a), # RATEA: Rating A (MVA)
|
217
|
+
float(row.rate_b), # RATEB: Rating B (MVA)
|
218
|
+
float(row.rate_c), # RATEC: Rating C (MVA)
|
219
|
+
0.0, # GI: From bus conductance (not tracked)
|
220
|
+
0.0, # BI: From bus susceptance (not tracked)
|
221
|
+
0.0, # GJ: To bus conductance (not tracked)
|
222
|
+
0.0, # BJ: To bus susceptance (not tracked)
|
223
|
+
int(row.u), # ST: Status
|
224
|
+
0.0 # LEN: Line length (not tracked)
|
225
|
+
# O1, F1, ..., O4, F4 omitted for v33
|
226
|
+
]
|
227
|
+
formatted_row = ",".join(
|
228
|
+
f"{value:>{width}}" if isinstance(value, (int, float)) else f"{value:>{width}}"
|
229
|
+
for value, width in zip(data, column_widths)
|
230
|
+
) + "\n"
|
231
|
+
f.write(formatted_row)
|
232
|
+
# 2) transformer
|
233
|
+
f.write("0 / END OF BRANCH DATA, BEGIN TRANSFORMER DATA\n")
|
234
|
+
for row in transf.itertuples():
|
235
|
+
# Map AMS Line (trans) fields to PSSE RAW transformer columns
|
236
|
+
# Only 2-winding transformers are supported here
|
237
|
+
# 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
|
238
|
+
# I, J, CKT, R, X, B, RATEA, RATEB, RATEC, GI, BI, GJ, BJ, ST, LEN
|
239
|
+
data = [
|
240
|
+
int(row.bus1), # I: From bus number
|
241
|
+
int(row.bus2), # J: To bus number
|
242
|
+
"'1'", # CKT: Circuit ID (default '1')
|
243
|
+
float(row.r), # R: Resistance (p.u.)
|
244
|
+
float(row.x), # X: Reactance (p.u.)
|
245
|
+
float(row.b), # B: Total line charging (p.u.)
|
246
|
+
float(row.rate_a), # RATEA: Rating A (MVA)
|
247
|
+
float(row.rate_b), # RATEB: Rating B (MVA)
|
248
|
+
float(row.rate_c), # RATEC: Rating C (MVA)
|
249
|
+
0.0, # GI: From bus conductance (not tracked)
|
250
|
+
0.0, # BI: From bus susceptance (not tracked)
|
251
|
+
0.0, # GJ: To bus conductance (not tracked)
|
252
|
+
0.0, # BJ: To bus susceptance (not tracked)
|
253
|
+
int(row.u), # ST: Status
|
254
|
+
0.0 # LEN: Line length (not tracked)
|
255
|
+
# O1, F1, ..., O4, F4 omitted for v33
|
256
|
+
]
|
257
|
+
formatted_row = ",".join(
|
258
|
+
f"{value:>{width}}" if isinstance(value, (int, float)) else f"{value:>{width}}"
|
259
|
+
for value, width in zip(data, column_widths)
|
260
|
+
) + "\n"
|
261
|
+
f.write(formatted_row)
|
262
|
+
|
263
|
+
# --- Area ---
|
264
|
+
f.write("0 / END OF TRANSFORMER DATA, BEGIN AREA DATA\n")
|
265
|
+
area = system.Area.cache.df_in
|
266
|
+
for row in area.itertuples():
|
267
|
+
# PSSE expects: ID, ISW, PDES, PTOL, NAME
|
268
|
+
# Here, ISW, PDES, PTOL are set to 0 by default
|
269
|
+
f.write(f"{int(system.Area.idx2uid(row.idx) + 1):6d}, 0, 0.0, 0.0, '{row.name}'\n")
|
270
|
+
|
271
|
+
# --- Zone ---
|
272
|
+
f.write("0 / END OF AREA DATA, BEGIN ZONE DATA\n")
|
273
|
+
zone = system.Zone.cache.df_in
|
274
|
+
# 0, 1
|
275
|
+
# ID, Name
|
276
|
+
for row in zone.itertuples():
|
277
|
+
f.write(f"{int(system.Zone.idx2uid(row.idx) + 1):6d}, '{row.name}'\n")
|
278
|
+
f.write("0 / END OF ZONE DATA\n")
|
279
|
+
|
280
|
+
# End of file
|
281
|
+
f.write("Q\n")
|
282
|
+
|
283
|
+
return True
|
ams/main.py
CHANGED
@@ -105,8 +105,8 @@ def config_logger(stream_level=logging.INFO, *,
|
|
105
105
|
|
106
106
|
else:
|
107
107
|
# update the handlers
|
108
|
-
set_logger_level(
|
109
|
-
set_logger_level(
|
108
|
+
set_logger_level(lg, logging.StreamHandler, stream_level)
|
109
|
+
set_logger_level(lg, logging.FileHandler, file_level)
|
110
110
|
|
111
111
|
if not is_interactive():
|
112
112
|
coloredlogs.install(logger=lg, level=stream_level, fmt=sh_formatter_str)
|
ams/models/group.py
CHANGED
ams/models/static/pq.py
CHANGED
@@ -53,9 +53,13 @@ class PQ(PQData, Model):
|
|
53
53
|
q2z=r"\gamma_{q2z}",
|
54
54
|
)
|
55
55
|
|
56
|
-
self.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False,
|
57
|
-
info='Retrieved area idx', vtype=str, default=None,
|
58
|
-
)
|
59
56
|
self.ctrl = NumParam(default=1,
|
60
57
|
info="load controllability",
|
61
58
|
tex_name=r'c_{trl}',)
|
59
|
+
|
60
|
+
self.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False,
|
61
|
+
info='Retrieved area idx', vtype=str, default=None,
|
62
|
+
)
|
63
|
+
self.zone = ExtParam(model='Bus', src='zone', indexer=self.bus, export=False,
|
64
|
+
info='Retrieved zone idx', vtype=str, default=None,
|
65
|
+
)
|
ams/opt/param.py
CHANGED
ams/routines/__init__.py
CHANGED
@@ -8,7 +8,6 @@ from andes.utils.func import list_flatten
|
|
8
8
|
all_routines = OrderedDict([
|
9
9
|
('dcpf', ['DCPF']),
|
10
10
|
('pflow', ['PFlow']),
|
11
|
-
('cpf', ['CPF']),
|
12
11
|
('acopf', ['ACOPF']),
|
13
12
|
('dcopf', ['DCOPF']),
|
14
13
|
('dcopf2', ['DCOPF2']),
|
@@ -16,8 +15,8 @@ all_routines = OrderedDict([
|
|
16
15
|
('rted', ['RTED', 'RTEDDG', 'RTEDES', 'RTEDVIS']),
|
17
16
|
('uc', ['UC', 'UCDG', 'UCES']),
|
18
17
|
('dopf', ['DOPF', 'DOPFVIS']),
|
19
|
-
('
|
20
|
-
('
|
18
|
+
('pypower', ['DCPF1', 'PFlow1', 'DCOPF1', 'ACOPF1']),
|
19
|
+
('grbopt', ['OPF']),
|
21
20
|
])
|
22
21
|
|
23
22
|
class_names = list_flatten(list(all_routines.values()))
|
ams/routines/acopf.py
CHANGED
@@ -1,117 +1,14 @@
|
|
1
1
|
"""
|
2
|
-
ACOPF routines
|
2
|
+
ACOPF routines.
|
3
3
|
"""
|
4
|
-
import logging
|
5
|
-
from collections import OrderedDict
|
6
4
|
|
7
|
-
from ams.pypower import
|
8
|
-
from ams.pypower.core import ppoption
|
5
|
+
from ams.routines.pypower import ACOPF1
|
9
6
|
|
10
|
-
from ams.io.pypower import system2ppc
|
11
|
-
from ams.core.param import RParam
|
12
7
|
|
13
|
-
|
14
|
-
from ams.opt import Var, Constraint, Objective
|
15
|
-
|
16
|
-
logger = logging.getLogger(__name__)
|
17
|
-
|
18
|
-
|
19
|
-
class ACOPF(DCPF0):
|
8
|
+
class ACOPF(ACOPF1):
|
20
9
|
"""
|
21
|
-
|
22
|
-
|
23
|
-
Notes
|
24
|
-
-----
|
25
|
-
1. ACOPF is solved with PYPOWER ``runopf`` function.
|
26
|
-
2. ACOPF formulation in AMS style is NOT DONE YET,
|
27
|
-
but this does not affect the results
|
28
|
-
because the data are passed to PYPOWER for solving.
|
10
|
+
Alias for ACOPF1.
|
29
11
|
"""
|
30
12
|
|
31
13
|
def __init__(self, system, config):
|
32
|
-
|
33
|
-
self.info = 'AC Optimal Power Flow'
|
34
|
-
self.type = 'ACED'
|
35
|
-
|
36
|
-
self.map1 = OrderedDict() # ACOPF does not receive
|
37
|
-
self.map2.update({
|
38
|
-
'vBus': ('Bus', 'v0'),
|
39
|
-
'ug': ('StaticGen', 'u'),
|
40
|
-
'pg': ('StaticGen', 'p0'),
|
41
|
-
})
|
42
|
-
|
43
|
-
# --- params ---
|
44
|
-
self.c2 = RParam(info='Gen cost coefficient 2',
|
45
|
-
name='c2', tex_name=r'c_{2}',
|
46
|
-
unit=r'$/(p.u.^2)', model='GCost',
|
47
|
-
indexer='gen', imodel='StaticGen',
|
48
|
-
nonneg=True)
|
49
|
-
self.c1 = RParam(info='Gen cost coefficient 1',
|
50
|
-
name='c1', tex_name=r'c_{1}',
|
51
|
-
unit=r'$/(p.u.)', model='GCost',
|
52
|
-
indexer='gen', imodel='StaticGen',)
|
53
|
-
self.c0 = RParam(info='Gen cost coefficient 0',
|
54
|
-
name='c0', tex_name=r'c_{0}',
|
55
|
-
unit=r'$', model='GCost',
|
56
|
-
indexer='gen', imodel='StaticGen',
|
57
|
-
no_parse=True)
|
58
|
-
self.qd = RParam(info='reactive demand',
|
59
|
-
name='qd', tex_name=r'q_{d}',
|
60
|
-
model='StaticLoad', src='q0',
|
61
|
-
unit='p.u.',)
|
62
|
-
# --- bus ---
|
63
|
-
self.vBus = Var(info='Bus voltage magnitude',
|
64
|
-
unit='p.u.',
|
65
|
-
name='vBus', tex_name=r'v_{Bus}',
|
66
|
-
src='v', model='Bus',)
|
67
|
-
# --- gen ---
|
68
|
-
self.qg = Var(info='Gen reactive power',
|
69
|
-
unit='p.u.',
|
70
|
-
name='qg', tex_name=r'q_{g}',
|
71
|
-
model='StaticGen', src='q',)
|
72
|
-
# --- constraints ---
|
73
|
-
self.pb = Constraint(name='pb',
|
74
|
-
info='power balance',
|
75
|
-
e_str='sum(pd) - sum(pg)',
|
76
|
-
is_eq=True,)
|
77
|
-
# TODO: ACOPF formulation
|
78
|
-
# --- objective ---
|
79
|
-
self.obj = Objective(name='obj',
|
80
|
-
info='total cost',
|
81
|
-
e_str='sum(c2 * pg**2 + c1 * pg + c0)',
|
82
|
-
sense='min',)
|
83
|
-
|
84
|
-
def solve(self, method=None, **kwargs):
|
85
|
-
"""
|
86
|
-
Solve ACOPF using PYPOWER with PIPS.
|
87
|
-
"""
|
88
|
-
ppc = system2ppc(self.system)
|
89
|
-
ppopt = ppoption()
|
90
|
-
res, sstats = runopf(casedata=ppc, ppopt=ppopt, **kwargs)
|
91
|
-
return res, sstats
|
92
|
-
|
93
|
-
def run(self, **kwargs):
|
94
|
-
"""
|
95
|
-
Run ACOPF using PYPOWER with PIPS.
|
96
|
-
*args and **kwargs go to `self.solve()`, which are not used yet.
|
97
|
-
|
98
|
-
Examples
|
99
|
-
--------
|
100
|
-
>>> ss = ams.load(ams.get_case('matpower/case14.m'))
|
101
|
-
>>> ss.ACOPF.run()
|
102
|
-
|
103
|
-
Parameters
|
104
|
-
----------
|
105
|
-
force_init : bool
|
106
|
-
Force initialization.
|
107
|
-
no_code : bool
|
108
|
-
Disable showing code.
|
109
|
-
method : str
|
110
|
-
Placeholder for future use.
|
111
|
-
|
112
|
-
Returns
|
113
|
-
-------
|
114
|
-
exit_code : int
|
115
|
-
Exit code of the routine.
|
116
|
-
"""
|
117
|
-
super().run(**kwargs)
|
14
|
+
super().__init__(system, config)
|
ams/routines/dcopf.py
CHANGED
@@ -141,6 +141,14 @@ class DCOPF(DCPFBase):
|
|
141
141
|
name='pi', unit='$/p.u.',
|
142
142
|
model='Bus', src=None,
|
143
143
|
e_str='pb.dual_variables[0]')
|
144
|
+
self.mu1 = ExpressionCalc(info='Lagrange multipliers, dual of <plflb>',
|
145
|
+
name='mu1', unit='$/p.u.',
|
146
|
+
model='Line', src=None,
|
147
|
+
e_str='plflb.dual_variables[0]')
|
148
|
+
self.mu2 = ExpressionCalc(info='Lagrange multipliers, dual of <plfub>',
|
149
|
+
name='mu2', unit='$/p.u.',
|
150
|
+
model='Line', src=None,
|
151
|
+
e_str='plfub.dual_variables[0]')
|
144
152
|
|
145
153
|
# --- objective ---
|
146
154
|
obj = 'sum(mul(c2, pg**2))'
|
ams/routines/dcpf.py
CHANGED
ams/routines/ed.py
CHANGED
@@ -104,6 +104,8 @@ class MPBase:
|
|
104
104
|
self.vBus.horizon = self.timeslot
|
105
105
|
self.vBus.info = '2D Bus voltage'
|
106
106
|
self.pi.horizon = self.timeslot
|
107
|
+
self.mu1.horizon = self.timeslot
|
108
|
+
self.mu2.horizon = self.timeslot
|
107
109
|
|
108
110
|
|
109
111
|
class ED(RTED, MPBase, SRBase):
|
@@ -214,14 +216,14 @@ class ED(RTED, MPBase, SRBase):
|
|
214
216
|
cost += '+ sum(mul(ugt, mul(c0, tlv)))'
|
215
217
|
self.obj.e_str = cost
|
216
218
|
|
217
|
-
def dc2ac(self, **kwargs):
|
219
|
+
def dc2ac(self, kloss=1.0, **kwargs):
|
218
220
|
"""
|
219
221
|
AC conversion ``dc2ac`` is not implemented yet for
|
220
222
|
multi-period scheduling.
|
221
223
|
"""
|
222
224
|
return NotImplementedError
|
223
225
|
|
224
|
-
def unpack(self, **kwargs):
|
226
|
+
def unpack(self, res, **kwargs):
|
225
227
|
"""
|
226
228
|
Multi-period scheduling will not unpack results from
|
227
229
|
solver into devices.
|