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/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/io/pypower.py
CHANGED
@@ -101,3 +101,17 @@ def system2ppc(system) -> dict:
|
|
101
101
|
mpc['branch'][:, 0] = np.array([bus_map[busi0] for busi0 in mpc['branch'][:, 0].astype(int)])
|
102
102
|
mpc['branch'][:, 1] = np.array([bus_map[busi0] for busi0 in mpc['branch'][:, 1].astype(int)])
|
103
103
|
return mpc
|
104
|
+
|
105
|
+
|
106
|
+
def ppc2py(ppc: dict, outfile: str) -> str:
|
107
|
+
"""
|
108
|
+
Placeholder.
|
109
|
+
"""
|
110
|
+
return NotImplemented
|
111
|
+
|
112
|
+
|
113
|
+
def write(system, outfile: str) -> bool:
|
114
|
+
"""
|
115
|
+
Placeholder.
|
116
|
+
"""
|
117
|
+
return NotImplemented
|
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
@@ -179,75 +179,6 @@ class StaticGen(GroupBase):
|
|
179
179
|
|
180
180
|
The generator types and fuel types are referenced from MATPOWER.
|
181
181
|
|
182
|
-
Generator Types
|
183
|
-
---------------
|
184
|
-
The following codes represent the types of generators:
|
185
|
-
- BA : Energy Storage, Battery
|
186
|
-
- CE : Energy Storage, Compressed Air
|
187
|
-
- CP : Energy Storage, Concentrated Solar Power
|
188
|
-
- FW : Energy Storage, Flywheel
|
189
|
-
- PS : Hydraulic Turbine, Reversible (pumped storage)
|
190
|
-
- ES : Energy Storage, Other
|
191
|
-
- ST : Steam Turbine (includes nuclear, geothermal, and solar steam)
|
192
|
-
- GT : Combustion (Gas) Turbine
|
193
|
-
- IC : Internal Combustion Engine (diesel, piston, reciprocating)
|
194
|
-
- CA : Combined Cycle Steam Part
|
195
|
-
- CT : Combined Cycle Combustion Turbine Part
|
196
|
-
- CS : Combined Cycle Single Shaft
|
197
|
-
- CC : Combined Cycle Total Unit
|
198
|
-
- HA : Hydrokinetic, Axial Flow Turbine
|
199
|
-
- HB : Hydrokinetic, Wave Buoy
|
200
|
-
- HK : Hydrokinetic, Other
|
201
|
-
- HY : Hydroelectric Turbine
|
202
|
-
- BT : Turbines Used in a Binary Cycle
|
203
|
-
- PV : Photovoltaic
|
204
|
-
- WT : Wind Turbine, Onshore
|
205
|
-
- WS : Wind Turbine, Offshore
|
206
|
-
- FC : Fuel Cell
|
207
|
-
- OT : Other
|
208
|
-
- UN : Unknown
|
209
|
-
- JE : Jet Engine
|
210
|
-
- NB : ST - Boiling Water Nuclear Reactor
|
211
|
-
- NG : ST - Graphite Nuclear Reactor
|
212
|
-
- NH : ST - High Temperature Gas Nuclear Reactor
|
213
|
-
- NP : ST - Pressurized Water Nuclear Reactor
|
214
|
-
- IT : Internal Combustion Turbo Charged
|
215
|
-
- SC : Synchronous Condenser
|
216
|
-
- DC : DC ties
|
217
|
-
- MP : Motor/Pump
|
218
|
-
- W1 : Wind Turbine, Type 1
|
219
|
-
- W2 : Wind Turbine, Type 2
|
220
|
-
- W3 : Wind Turbine, Type 3
|
221
|
-
- W4 : Wind Turbine, Type 4
|
222
|
-
- SV : Static Var Compensator
|
223
|
-
- DL : Dispatchable Load
|
224
|
-
|
225
|
-
Fuel Types
|
226
|
-
----------
|
227
|
-
The following codes represent the fuel types:
|
228
|
-
- biomass : Biomass
|
229
|
-
- coal : Coal
|
230
|
-
- dfo : Distillate Fuel Oil
|
231
|
-
- geothermal : Geothermal
|
232
|
-
- hydro : Hydro
|
233
|
-
- hydrops : Hydro Pumped Storage
|
234
|
-
- jetfuel : Jet Fuel
|
235
|
-
- lng : Liquefied Natural Gas
|
236
|
-
- ng : Natural Gas
|
237
|
-
- nuclear : Nuclear
|
238
|
-
- oil : Unspecified Oil
|
239
|
-
- refuse : Refuse, Municipal Solid Waste
|
240
|
-
- rfo : Residual Fuel Oil
|
241
|
-
- solar : Solar
|
242
|
-
- syncgen : Synchronous Condenser
|
243
|
-
- wasteheat : Waste Heat
|
244
|
-
- wind : Wind
|
245
|
-
- wood : Wood or Wood Waste
|
246
|
-
- other : Other
|
247
|
-
- unknown : Unknown
|
248
|
-
- dl : Dispatchable Load
|
249
|
-
- ess : Energy Storage System
|
250
|
-
|
251
182
|
Notes
|
252
183
|
-----
|
253
184
|
For co-simulation with ANDES, refer to the `ANDES StaticGen Documentation
|
@@ -289,7 +220,8 @@ class StaticShunt(GroupBase):
|
|
289
220
|
"""
|
290
221
|
Static shunt compensator group.
|
291
222
|
"""
|
292
|
-
|
223
|
+
def __init__(self):
|
224
|
+
super().__init__()
|
293
225
|
|
294
226
|
|
295
227
|
class Information(GroupBase):
|
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/report.py
CHANGED
@@ -3,7 +3,6 @@ Module for report generation.
|
|
3
3
|
"""
|
4
4
|
import logging
|
5
5
|
from collections import OrderedDict
|
6
|
-
from time import strftime
|
7
6
|
from typing import List, Dict, Optional
|
8
7
|
|
9
8
|
from andes.io.txt import dump_data
|
@@ -11,7 +10,7 @@ from andes.shared import np
|
|
11
10
|
from andes.utils.misc import elapsed
|
12
11
|
|
13
12
|
from ams import __version__ as version
|
14
|
-
from ams.shared import copyright_msg
|
13
|
+
from ams.shared import copyright_msg, nowarranty_msg, report_time
|
15
14
|
|
16
15
|
logger = logging.getLogger(__name__)
|
17
16
|
|
@@ -23,9 +22,9 @@ def report_info(system) -> list:
|
|
23
22
|
info = list()
|
24
23
|
info.append('AMS' + ' ' + version + '\n')
|
25
24
|
info.append(f'{copyright_msg}\n\n')
|
26
|
-
info.append(
|
25
|
+
info.append(f"{nowarranty_msg}\n")
|
27
26
|
info.append('Case file: ' + str(system.files.case) + '\n')
|
28
|
-
info.append('Report time:
|
27
|
+
info.append(f'Report time: {report_time}\n\n')
|
29
28
|
return info
|
30
29
|
|
31
30
|
|
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.
|