ltbams 1.0.6__py3-none-any.whl → 1.0.8__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/cases/5bus/pjm5bus_demo.xlsx +0 -0
- ams/cases/5bus/pjm5bus_ev.xlsx +0 -0
- ams/cases/5bus/pjm5bus_jumper.xlsx +0 -0
- ams/cases/hawaii40/Hawaii40.m +375 -0
- ams/cases/ieee14/ieee14_uced.xlsx +0 -0
- ams/cases/ieee39/ieee39_uced.xlsx +0 -0
- ams/cases/ieee39/ieee39_uced_esd1.xlsx +0 -0
- ams/cases/ieee39/ieee39_uced_pvd1.xlsx +0 -0
- ams/cases/ieee39/ieee39_uced_vis.xlsx +0 -0
- ams/cases/matpower/case5.m +25 -0
- ams/core/matprocessor.py +7 -16
- ams/core/service.py +4 -3
- ams/interface.py +17 -4
- ams/io/__init__.py +1 -0
- ams/io/matpower.py +165 -7
- ams/io/psse.py +1 -1
- ams/models/__init__.py +1 -1
- ams/models/group.py +77 -6
- ams/models/line.py +3 -164
- ams/models/renewable/regc.py +1 -5
- ams/models/reserve.py +2 -2
- ams/models/static/gen.py +11 -3
- ams/models/static/pq.py +2 -2
- ams/models/timeslot.py +1 -1
- ams/routines/dcopf.py +1 -1
- ams/routines/dcpf0.py +1 -1
- ams/routines/dopf.py +10 -10
- ams/routines/ed.py +3 -4
- ams/routines/pflow.py +9 -10
- ams/routines/rted.py +20 -29
- ams/routines/uc.py +1 -1
- ams/system.py +11 -18
- docs/source/api.rst +2 -0
- docs/source/getting_started/copyright.rst +1 -1
- docs/source/release-notes.rst +24 -0
- {ltbams-1.0.6.dist-info → ltbams-1.0.8.dist-info}/METADATA +1 -1
- {ltbams-1.0.6.dist-info → ltbams-1.0.8.dist-info}/RECORD +48 -51
- {ltbams-1.0.6.dist-info → ltbams-1.0.8.dist-info}/WHEEL +1 -1
- tests/test_1st_system.py +31 -0
- tests/test_case.py +27 -19
- tests/test_io.py +15 -0
- tests/test_rtn_ed.py +4 -4
- tests/test_rtn_rted.py +4 -4
- tests/test_rtn_uc.py +4 -4
- tests/test_service.py +2 -2
- ams/cases/5bus/pjm5bus_uced.json +0 -1062
- ams/cases/5bus/pjm5bus_uced.xlsx +0 -0
- ams/cases/5bus/pjm5bus_uced_esd1.xlsx +0 -0
- ams/cases/5bus/pjm5bus_uced_ev.xlsx +0 -0
- tests/test_andes_mats.py +0 -61
- {ltbams-1.0.6.dist-info → ltbams-1.0.8.dist-info}/entry_points.txt +0 -0
- {ltbams-1.0.6.dist-info → ltbams-1.0.8.dist-info}/top_level.txt +0 -0
ams/io/matpower.py
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
MATPOWER parser.
|
3
3
|
"""
|
4
4
|
import logging
|
5
|
+
import re
|
5
6
|
import numpy as np
|
6
7
|
|
7
|
-
from andes.io
|
8
|
+
from andes.io import read_file_like
|
8
9
|
from andes.shared import deg2rad, rad2deg
|
9
10
|
|
10
11
|
logger = logging.getLogger(__name__)
|
@@ -29,6 +30,151 @@ def read(system, file):
|
|
29
30
|
return mpc2system(mpc, system)
|
30
31
|
|
31
32
|
|
33
|
+
def m2mpc(infile: str) -> dict:
|
34
|
+
"""
|
35
|
+
Parse a MATPOWER file and return a dictionary containing the parsed data.
|
36
|
+
|
37
|
+
This function processes MATPOWER case files and extracts relevant fields
|
38
|
+
into a structured dictionary. It is revised from ``andes.io.matpower.m2mpc``.
|
39
|
+
|
40
|
+
Supported fields include:
|
41
|
+
- `baseMVA`: The system base power in MVA.
|
42
|
+
- `bus`: Bus data, including voltage, load, and generation information.
|
43
|
+
- `bus_name`: Names of the buses (if available).
|
44
|
+
- `gen`: Generator data, including power limits and voltage setpoints.
|
45
|
+
- `branch`: Branch data, including line impedances and ratings.
|
46
|
+
- `gencost`: Generator cost data (parsed but not used in this implementation).
|
47
|
+
- `areas`: Area data (parsed but not used in this implementation).
|
48
|
+
- `gentype`: Generator type information (if available).
|
49
|
+
- `genfuel`: Generator fuel type information (if available).
|
50
|
+
|
51
|
+
Parameters
|
52
|
+
----------
|
53
|
+
infile : str
|
54
|
+
Path to the MATPOWER file to be parsed.
|
55
|
+
|
56
|
+
Returns
|
57
|
+
-------
|
58
|
+
dict
|
59
|
+
A dictionary containing the parsed MATPOWER data, where keys correspond
|
60
|
+
to MATPOWER struct names and values are numpy arrays or lists.
|
61
|
+
"""
|
62
|
+
|
63
|
+
func = re.compile(r'function\s')
|
64
|
+
mva = re.compile(r'\s*mpc.baseMVA\s*=\s*')
|
65
|
+
bus = re.compile(r'\s*mpc.bus\s*=\s*\[?')
|
66
|
+
gen = re.compile(r'\s*mpc.gen\s*=\s*\[')
|
67
|
+
branch = re.compile(r'\s*mpc.branch\s*=\s*\[')
|
68
|
+
area = re.compile(r'\s*mpc.areas\s*=\s*\[')
|
69
|
+
gencost = re.compile(r'\s*mpc.gencost\s*=\s*\[')
|
70
|
+
bus_name = re.compile(r'\s*mpc.bus_name\s*=\s*{')
|
71
|
+
gentype = re.compile(r'\s*mpc.gentype\s*=\s*{')
|
72
|
+
genfuel = re.compile(r'\s*mpc.genfuel\s*=\s*{')
|
73
|
+
end = re.compile(r'\s*[\];}]')
|
74
|
+
has_digit = re.compile(r'.*\d+\s*]?;?')
|
75
|
+
|
76
|
+
field = None
|
77
|
+
info = True
|
78
|
+
|
79
|
+
mpc = {
|
80
|
+
'version': 2, # not in use
|
81
|
+
'baseMVA': 100,
|
82
|
+
'bus': [],
|
83
|
+
'gen': [],
|
84
|
+
'branch': [],
|
85
|
+
'area': [],
|
86
|
+
'gencost': [],
|
87
|
+
'bus_name': [],
|
88
|
+
'gentype': [],
|
89
|
+
'genfuel': [],
|
90
|
+
}
|
91
|
+
|
92
|
+
input_list = read_file_like(infile)
|
93
|
+
|
94
|
+
for line in input_list:
|
95
|
+
line = line.strip().rstrip(';')
|
96
|
+
if not line:
|
97
|
+
continue
|
98
|
+
elif func.search(line): # skip function declaration
|
99
|
+
continue
|
100
|
+
elif len(line.split('%')[0]) == 0:
|
101
|
+
if info is True:
|
102
|
+
logger.info(line[1:])
|
103
|
+
info = False
|
104
|
+
else:
|
105
|
+
continue
|
106
|
+
elif mva.search(line):
|
107
|
+
mpc["baseMVA"] = float(line.split('=')[1])
|
108
|
+
|
109
|
+
if not field:
|
110
|
+
if bus.search(line):
|
111
|
+
field = 'bus'
|
112
|
+
elif gen.search(line):
|
113
|
+
field = 'gen'
|
114
|
+
elif branch.search(line):
|
115
|
+
field = 'branch'
|
116
|
+
elif area.search(line):
|
117
|
+
field = 'area'
|
118
|
+
elif gencost.search(line):
|
119
|
+
field = 'gencost'
|
120
|
+
elif bus_name.search(line):
|
121
|
+
field = 'bus_name'
|
122
|
+
elif gentype.search(line):
|
123
|
+
field = 'gentype'
|
124
|
+
elif genfuel.search(line):
|
125
|
+
field = 'genfuel'
|
126
|
+
else:
|
127
|
+
continue
|
128
|
+
elif end.search(line):
|
129
|
+
field = None
|
130
|
+
continue
|
131
|
+
|
132
|
+
# parse mpc sections
|
133
|
+
if field:
|
134
|
+
if line.find('=') >= 0:
|
135
|
+
line = line.split('=')[1]
|
136
|
+
if line.find('[') >= 0:
|
137
|
+
line = re.sub(r'\[', '', line)
|
138
|
+
elif line.find('{') >= 0:
|
139
|
+
line = re.sub(r'{', '', line)
|
140
|
+
|
141
|
+
if field in ['bus_name', 'gentype', 'genfuel']:
|
142
|
+
# Handle string-based fields
|
143
|
+
line = line.split(';')
|
144
|
+
data = [i.strip('\'').strip() for i in line if i.strip()]
|
145
|
+
mpc[field].extend(data)
|
146
|
+
else:
|
147
|
+
if not has_digit.search(line):
|
148
|
+
continue
|
149
|
+
line = line.split('%')[0].strip()
|
150
|
+
line = line.split(';')
|
151
|
+
for item in line:
|
152
|
+
if not has_digit.search(item):
|
153
|
+
continue
|
154
|
+
try:
|
155
|
+
data = np.array([float(val) for val in item.split()])
|
156
|
+
except Exception as e:
|
157
|
+
logger.error('Error parsing "%s"', infile)
|
158
|
+
raise e
|
159
|
+
mpc[field].append(data)
|
160
|
+
|
161
|
+
# convert mpc to np array
|
162
|
+
mpc_array = dict()
|
163
|
+
for key, val in mpc.items():
|
164
|
+
if isinstance(val, (float, int)):
|
165
|
+
mpc_array[key] = val
|
166
|
+
elif isinstance(val, list):
|
167
|
+
if len(val) == 0:
|
168
|
+
continue
|
169
|
+
if key in ['bus_name', 'gentype', 'genfuel']:
|
170
|
+
mpc_array[key] = np.array(val, dtype=object)
|
171
|
+
else:
|
172
|
+
mpc_array[key] = np.array(val)
|
173
|
+
else:
|
174
|
+
raise NotImplementedError("Unknown type for mpc, ", type(val))
|
175
|
+
return mpc_array
|
176
|
+
|
177
|
+
|
32
178
|
def mpc2system(mpc: dict, system) -> bool:
|
33
179
|
"""
|
34
180
|
Load an mpc dict into an empty AMS system.
|
@@ -97,7 +243,20 @@ def mpc2system(mpc: dict, system) -> bool:
|
|
97
243
|
mpc_gen[:, 19] = system.PV.Rq.default * base_mva / 60
|
98
244
|
else:
|
99
245
|
mpc_gen = mpc['gen']
|
100
|
-
|
246
|
+
|
247
|
+
# Ensure 'gentype' and 'genfuel' keys exist in mpc, with default values if missing
|
248
|
+
gentype = mpc.get('gentype', [''] * mpc_gen.shape[0])
|
249
|
+
genfuel = mpc.get('genfuel', [''] * mpc_gen.shape[0])
|
250
|
+
|
251
|
+
# Validate lengths of 'gentype' and 'genfuel' against the number of generators
|
252
|
+
if len(gentype) != mpc_gen.shape[0]:
|
253
|
+
raise ValueError(
|
254
|
+
f"'gentype' length ({len(gentype)}) does not match the number of generators ({mpc_gen.shape[0]})")
|
255
|
+
if len(genfuel) != mpc_gen.shape[0]:
|
256
|
+
raise ValueError(
|
257
|
+
f"'genfuel' length ({len(genfuel)}) does not match the number of generators ({mpc_gen.shape[0]})")
|
258
|
+
|
259
|
+
for data, gt, gf in zip(mpc_gen, gentype, genfuel):
|
101
260
|
# bus pg qg qmax qmin vg mbase status pmax pmin
|
102
261
|
# 0 1 2 3 4 5 6 7 8 9
|
103
262
|
# pc1 pc2 qc1min qc1max qc2min qc2max ramp_agc ramp_10
|
@@ -142,7 +301,7 @@ def mpc2system(mpc: dict, system) -> bool:
|
|
142
301
|
Qc2min=qc2min, Qc2max=qc2max,
|
143
302
|
Ragc=ramp_agc, R10=ramp_10,
|
144
303
|
R30=ramp_30, Rq=ramp_q,
|
145
|
-
apf=apf)
|
304
|
+
apf=apf, gentype=gt, genfuel=gf)
|
146
305
|
else:
|
147
306
|
system.add('PV', idx=gen_idx, bus=bus_idx, busr=bus_idx,
|
148
307
|
name=None,
|
@@ -155,7 +314,7 @@ def mpc2system(mpc: dict, system) -> bool:
|
|
155
314
|
Qc2min=qc2min, Qc2max=qc2max,
|
156
315
|
Ragc=ramp_agc, R10=ramp_10,
|
157
316
|
R30=ramp_30, Rq=ramp_q,
|
158
|
-
apf=apf)
|
317
|
+
apf=apf, gentype=gt, genfuel=gf)
|
159
318
|
|
160
319
|
for data in mpc['branch']:
|
161
320
|
# fbus tbus r x b rateA rateB rateC ratio angle
|
@@ -204,9 +363,8 @@ def mpc2system(mpc: dict, system) -> bool:
|
|
204
363
|
gen_idx = np.arange(mpc['gen'].shape[0]) + 1
|
205
364
|
mpc_cost = mpc['gencost']
|
206
365
|
if mpc_cost[0, 0] == 1:
|
207
|
-
logger.warning("Type 1 gencost detected
|
208
|
-
"
|
209
|
-
"Default type 2 cost parameters will be used as a fallback."
|
366
|
+
logger.warning("Type 1 gencost detected, which is not supported in AMS.\n"
|
367
|
+
"Default type 2 cost parameters will be used as a fallback.\n"
|
210
368
|
"It is recommended to manually convert the gencost data to type 2.")
|
211
369
|
mpc_cost = np.repeat(np.array([[2, 0, 0, 3, 0, 0, 0]]),
|
212
370
|
mpc_cost.shape[0], axis=0)
|
ams/io/psse.py
CHANGED
ams/models/__init__.py
CHANGED
@@ -18,7 +18,7 @@ ams_file_classes = list([
|
|
18
18
|
('reserve', ['SFR', 'SR', 'NSR', 'VSGR']),
|
19
19
|
('cost', ['GCost', 'SFRCost', 'SRCost', 'NSRCost', 'VSGCost']),
|
20
20
|
('cost', ['DCost']),
|
21
|
-
('timeslot', ['
|
21
|
+
('timeslot', ['EDTSlot', 'UCTSlot']),
|
22
22
|
])
|
23
23
|
|
24
24
|
file_classes = ams_file_classes
|
ams/models/group.py
CHANGED
@@ -175,20 +175,91 @@ class Reserve(GroupBase):
|
|
175
175
|
|
176
176
|
class StaticGen(GroupBase):
|
177
177
|
"""
|
178
|
-
Generator
|
178
|
+
Static Generator Group.
|
179
|
+
|
180
|
+
The generator types and fuel types are referenced from MATPOWER.
|
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
|
179
250
|
|
180
251
|
Notes
|
181
252
|
-----
|
182
|
-
For co-simulation with ANDES,
|
183
|
-
|
184
|
-
|
253
|
+
For co-simulation with ANDES, refer to the `ANDES StaticGen Documentation
|
254
|
+
<https://docs.andes.app/en/latest/groupdoc/StaticGen.html#staticgen>`_ for
|
255
|
+
replacing static generators with dynamic generators.
|
185
256
|
"""
|
186
257
|
|
187
258
|
def __init__(self):
|
188
259
|
super().__init__()
|
189
260
|
self.common_params.extend(('bus', 'Sn', 'Vn', 'p0', 'q0', 'ra', 'xs', 'subidx',
|
190
261
|
'pmax', 'pmin', 'pg0', 'ctrl', 'R10', 'td1', 'td2',
|
191
|
-
'zone'))
|
262
|
+
'area', 'zone', 'gentype', 'genfuel'))
|
192
263
|
self.common_vars.extend(('p', 'q'))
|
193
264
|
|
194
265
|
|
@@ -211,7 +282,7 @@ class StaticLoad(GroupBase):
|
|
211
282
|
|
212
283
|
def __init__(self):
|
213
284
|
super().__init__()
|
214
|
-
self.common_params.extend(('bus', 'p0', 'q0', 'ctrl', 'zone'))
|
285
|
+
self.common_params.extend(('bus', 'p0', 'q0', 'ctrl', 'area', 'zone'))
|
215
286
|
|
216
287
|
|
217
288
|
class StaticShunt(GroupBase):
|
ams/models/line.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from andes.models.line.line import LineData
|
2
2
|
from andes.models.line.jumper import JumperData
|
3
3
|
from andes.core.param import NumParam
|
4
|
-
from andes.shared import deg2rad
|
4
|
+
from andes.shared import deg2rad
|
5
5
|
|
6
6
|
from ams.core.model import Model
|
7
7
|
|
@@ -18,8 +18,8 @@ class Line(LineData, Model):
|
|
18
18
|
|
19
19
|
Notes
|
20
20
|
-----
|
21
|
-
|
22
|
-
|
21
|
+
1. Adding Algeb ``ud`` causes Line.algebs to encounter an AttributeError: 'NoneType'
|
22
|
+
object has no attribute 'n'. The root cause is still under investigation.
|
23
23
|
"""
|
24
24
|
|
25
25
|
def __init__(self, system=None, config=None) -> None:
|
@@ -49,167 +49,6 @@ class Line(LineData, Model):
|
|
49
49
|
self.a1a = None
|
50
50
|
self.a2a = None
|
51
51
|
|
52
|
-
# NOTE: following code are minly copied from `andes.models.line.Line`
|
53
|
-
# and they are not fully verified
|
54
|
-
# potential issues:
|
55
|
-
# `build_Bp` contains 'fdxb', which is not included in the input parameters,
|
56
|
-
# and the results are the negative of `Bbus` from `makeBdc` in PYPOWER
|
57
|
-
# `build_Bpp` ignores the line resistance for all three methods
|
58
|
-
# `build_Bdc` results are the negative of `Bbus` from `makeBdc` in PYPOWER
|
59
|
-
# `build_y` results have inignorable differences at diagonal elements with `makeYbus` in PYPOWER
|
60
|
-
|
61
|
-
def build_y(self):
|
62
|
-
"""
|
63
|
-
Build bus admittance matrix. Copied from ``andes.models.line.line.Line``.
|
64
|
-
|
65
|
-
Returns
|
66
|
-
-------
|
67
|
-
Y : spmatrix
|
68
|
-
Bus admittance matrix.
|
69
|
-
"""
|
70
|
-
|
71
|
-
nb = self.system.Bus.n
|
72
|
-
|
73
|
-
y1 = self.u.v * (self.g1.v + self.b1.v * 1j)
|
74
|
-
y2 = self.u.v * (self.g2.v + self.b2.v * 1j)
|
75
|
-
y12 = self.u.v / (self.r.v + self.x.v * 1j)
|
76
|
-
m = self.tap.v * np.exp(1j * self.phi.v)
|
77
|
-
m2 = self.tap.v**2
|
78
|
-
mconj = np.conj(m)
|
79
|
-
|
80
|
-
# build self and mutual admittances into Y
|
81
|
-
Y = spmatrix((y12 + y1 / m2), self.a1a, self.a1a, (nb, nb), 'z')
|
82
|
-
Y -= spmatrix(y12 / mconj, self.a1a, self.a2a, (nb, nb), 'z')
|
83
|
-
Y -= spmatrix(y12 / m, self.a2a, self.a1a, (nb, nb), 'z')
|
84
|
-
Y += spmatrix(y12 + y2, self.a2a, self.a2a, (nb, nb), 'z')
|
85
|
-
|
86
|
-
return Y
|
87
|
-
|
88
|
-
def build_Bp(self, method='fdpf'):
|
89
|
-
"""
|
90
|
-
Function for building B' matrix.
|
91
|
-
|
92
|
-
Parameters
|
93
|
-
----------
|
94
|
-
method : str
|
95
|
-
Method for building B' matrix. Choose from 'fdpf', 'fdbx', 'dcpf'.
|
96
|
-
|
97
|
-
Returns
|
98
|
-
-------
|
99
|
-
Bp : spmatrix
|
100
|
-
B' matrix.
|
101
|
-
"""
|
102
|
-
nb = self.system.Bus.n
|
103
|
-
|
104
|
-
if method not in ("fdpf", "fdbx", "dcpf"):
|
105
|
-
raise ValueError(f"Invalid method {method}; choose from 'fdpf', 'fdbx', 'dcpf'")
|
106
|
-
|
107
|
-
# Build B prime matrix -- FDPF
|
108
|
-
# `y1`` neglects line charging shunt, and g1 is usually 0 in HV lines
|
109
|
-
# `y2`` neglects line charging shunt, and g2 is usually 0 in HV lines
|
110
|
-
y1 = self.u.v * self.g1.v
|
111
|
-
y2 = self.u.v * self.g2.v
|
112
|
-
|
113
|
-
# `m` neglected tap ratio
|
114
|
-
m = np.exp(self.phi.v * 1j)
|
115
|
-
mconj = np.conj(m)
|
116
|
-
m2 = np.ones(self.n)
|
117
|
-
|
118
|
-
if method in ('fdxb', 'dcpf'):
|
119
|
-
# neglect line resistance in Bp in XB method
|
120
|
-
y12 = self.u.v / (self.x.v * 1j)
|
121
|
-
else:
|
122
|
-
y12 = self.u.v / (self.r.v + self.x.v * 1j)
|
123
|
-
|
124
|
-
Bdc = spmatrix((y12 + y1) / m2, self.a1a, self.a1a, (nb, nb), 'z')
|
125
|
-
Bdc -= spmatrix(y12 / mconj, self.a1a, self.a2a, (nb, nb), 'z')
|
126
|
-
Bdc -= spmatrix(y12 / m, self.a2a, self.a1a, (nb, nb), 'z')
|
127
|
-
Bdc += spmatrix(y12 + y2, self.a2a, self.a2a, (nb, nb), 'z')
|
128
|
-
Bdc = Bdc.imag()
|
129
|
-
|
130
|
-
for item in range(nb):
|
131
|
-
if abs(Bdc[item, item]) == 0:
|
132
|
-
Bdc[item, item] = 1e-6 + 0j
|
133
|
-
|
134
|
-
return Bdc
|
135
|
-
|
136
|
-
def build_Bpp(self, method='fdpf'):
|
137
|
-
"""
|
138
|
-
Function for building B'' matrix.
|
139
|
-
|
140
|
-
Parameters
|
141
|
-
----------
|
142
|
-
method : str
|
143
|
-
Method for building B'' matrix. Choose from 'fdpf', 'fdbx', 'dcpf'.
|
144
|
-
|
145
|
-
Returns
|
146
|
-
-------
|
147
|
-
Bpp : spmatrix
|
148
|
-
B'' matrix.
|
149
|
-
"""
|
150
|
-
|
151
|
-
nb = self.system.Bus.n
|
152
|
-
|
153
|
-
if method not in ("fdpf", "fdbx", "dcpf"):
|
154
|
-
raise ValueError(f"Invalid method {method}; choose from 'fdpf', 'fdbx', 'dcpf'")
|
155
|
-
|
156
|
-
# Build B double prime matrix
|
157
|
-
# y1 neglected line charging shunt, and g1 is usually 0 in HV lines
|
158
|
-
# y2 neglected line charging shunt, and g2 is usually 0 in HV lines
|
159
|
-
# m neglected phase shifter
|
160
|
-
y1 = self.u.v * (self.g1.v + self.b1.v * 1j)
|
161
|
-
y2 = self.u.v * (self.g2.v + self.b2.v * 1j)
|
162
|
-
|
163
|
-
m = self.tap.v
|
164
|
-
m2 = abs(m)**2
|
165
|
-
|
166
|
-
if method in ('fdbx', 'fdpf', 'dcpf'):
|
167
|
-
# neglect line resistance in Bpp in BX method
|
168
|
-
y12 = self.u.v / (self.x.v * 1j)
|
169
|
-
else:
|
170
|
-
y12 = self.u.v / (self.r.v + self.x.v * 1j)
|
171
|
-
|
172
|
-
Bpp = spmatrix((y12 + y1) / m2, self.a1a, self.a1a, (nb, nb), 'z')
|
173
|
-
Bpp -= spmatrix(y12 / np.conj(m), self.a1a, self.a2a, (nb, nb), 'z')
|
174
|
-
Bpp -= spmatrix(y12 / m, self.a2a, self.a1a, (nb, nb), 'z')
|
175
|
-
Bpp += spmatrix(y12 + y2, self.a2a, self.a2a, (nb, nb), 'z')
|
176
|
-
Bpp = Bpp.imag()
|
177
|
-
|
178
|
-
for item in range(nb):
|
179
|
-
if abs(Bpp[item, item]) == 0:
|
180
|
-
Bpp[item, item] = 1e-6 + 0j
|
181
|
-
|
182
|
-
return Bpp
|
183
|
-
|
184
|
-
def build_Bdc(self):
|
185
|
-
"""
|
186
|
-
The MATPOWER-flavor Bdc matrix for DC power flow.
|
187
|
-
|
188
|
-
The method neglects line charging and line resistance. It retains tap ratio.
|
189
|
-
|
190
|
-
Returns
|
191
|
-
-------
|
192
|
-
Bdc : spmatrix
|
193
|
-
Bdc matrix.
|
194
|
-
"""
|
195
|
-
|
196
|
-
nb = self.system.Bus.n
|
197
|
-
|
198
|
-
y12 = self.u.v / (self.x.v * 1j)
|
199
|
-
y12 = y12 / self.tap.v
|
200
|
-
|
201
|
-
Bdc = spmatrix(y12, self.a1a, self.a1a, (nb, nb), 'z')
|
202
|
-
Bdc -= spmatrix(y12, self.a1a, self.a2a, (nb, nb), 'z')
|
203
|
-
Bdc -= spmatrix(y12, self.a2a, self.a1a, (nb, nb), 'z')
|
204
|
-
Bdc += spmatrix(y12, self.a2a, self.a2a, (nb, nb), 'z')
|
205
|
-
Bdc = Bdc.imag()
|
206
|
-
|
207
|
-
for item in range(nb):
|
208
|
-
if abs(Bdc[item, item]) == 0:
|
209
|
-
Bdc[item, item] = 1e-6
|
210
|
-
|
211
|
-
return Bdc
|
212
|
-
|
213
52
|
|
214
53
|
class Jumper(JumperData, Model):
|
215
54
|
"""
|
ams/models/renewable/regc.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
RenGen scheduling model.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from andes.core.param import NumParam, IdxParam
|
5
|
+
from andes.core.param import NumParam, IdxParam
|
6
6
|
from andes.core.model import ModelData
|
7
7
|
from ams.core.model import Model
|
8
8
|
|
@@ -80,10 +80,6 @@ class REGCV1(REGCData, Model):
|
|
80
80
|
REGCData.__init__(self)
|
81
81
|
Model.__init__(self, system, config)
|
82
82
|
self.group = 'VSG'
|
83
|
-
self.zone = ExtParam(model='Bus', src='zone',
|
84
|
-
indexer=self.bus, export=False,
|
85
|
-
info='Retrieved zone idx',
|
86
|
-
vtype=str, default=None)
|
87
83
|
self.M = NumParam(default=10, tex_name='M',
|
88
84
|
info='Inertia emulation',
|
89
85
|
unit='s',
|
ams/models/reserve.py
CHANGED
@@ -10,8 +10,8 @@ from ams.core.model import Model
|
|
10
10
|
class ReserveData(ModelData):
|
11
11
|
def __init__(self):
|
12
12
|
super().__init__()
|
13
|
-
self.
|
14
|
-
default=None, info="
|
13
|
+
self.area = IdxParam(model='Area',
|
14
|
+
default=None, info="Area idx",)
|
15
15
|
|
16
16
|
|
17
17
|
class SFR(ReserveData, Model):
|
ams/models/static/gen.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from collections import OrderedDict
|
2
2
|
|
3
|
-
from andes.core.param import NumParam, ExtParam
|
3
|
+
from andes.core.param import NumParam, ExtParam, DataParam
|
4
4
|
from andes.models.static.pv import PVData
|
5
5
|
from andes.models.static.slack import SlackData
|
6
6
|
|
@@ -81,6 +81,14 @@ class GenParam:
|
|
81
81
|
tex_name=r't_{d2}',
|
82
82
|
unit='h',
|
83
83
|
)
|
84
|
+
self.gentype = DataParam(default=None,
|
85
|
+
info='generator type',
|
86
|
+
tex_name=r'g_{type}',
|
87
|
+
)
|
88
|
+
self.genfuel = DataParam(default=None,
|
89
|
+
info='generator fuel type',
|
90
|
+
tex_name=r'g_{fuel}',
|
91
|
+
)
|
84
92
|
|
85
93
|
|
86
94
|
class PVModel(Model):
|
@@ -120,8 +128,8 @@ class PVModel(Model):
|
|
120
128
|
err_tol=r"\epsilon_{tol}"
|
121
129
|
)
|
122
130
|
|
123
|
-
self.
|
124
|
-
info='Retrieved
|
131
|
+
self.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False,
|
132
|
+
info='Retrieved area idx', vtype=str, default=None,
|
125
133
|
)
|
126
134
|
|
127
135
|
self.ud = Algeb(info='commitment decision',
|
ams/models/static/pq.py
CHANGED
@@ -53,8 +53,8 @@ class PQ(PQData, Model):
|
|
53
53
|
q2z=r"\gamma_{q2z}",
|
54
54
|
)
|
55
55
|
|
56
|
-
self.
|
57
|
-
info='Retrieved
|
56
|
+
self.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False,
|
57
|
+
info='Retrieved area idx', vtype=str, default=None,
|
58
58
|
)
|
59
59
|
self.ctrl = NumParam(default=1,
|
60
60
|
info="load controllability",
|
ams/models/timeslot.py
CHANGED
ams/routines/dcopf.py
CHANGED
ams/routines/dcpf0.py
CHANGED
@@ -168,7 +168,7 @@ class DCPF0(RoutineBase):
|
|
168
168
|
if self.exit_code == 0:
|
169
169
|
msg = f"<{self.class_name}> solved in {s}, converged in "
|
170
170
|
msg += n_iter_str + f"with {sstats['solver_name']}."
|
171
|
-
logger.
|
171
|
+
logger.warning(msg)
|
172
172
|
try:
|
173
173
|
self.unpack(res)
|
174
174
|
except Exception as e:
|
ams/routines/dopf.py
CHANGED
@@ -16,11 +16,11 @@ class DOPF(DCOPF):
|
|
16
16
|
|
17
17
|
UNDER DEVELOPMENT!
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
References
|
20
|
+
-----------------
|
21
|
+
1. L. Bai, J. Wang, C. Wang, C. Chen, and F. Li, “Distribution Locational Marginal Pricing (DLMP)
|
22
|
+
for Congestion Management and Voltage Support,” IEEE Trans. Power Syst., vol. 33, no. 4,
|
23
|
+
pp. 4061-4073, Jul. 2018, doi: 10.1109/TPWRS.2017.2767632.
|
24
24
|
"""
|
25
25
|
|
26
26
|
def __init__(self, system, config):
|
@@ -115,11 +115,11 @@ class DOPFVIS(DOPF):
|
|
115
115
|
|
116
116
|
UNDER DEVELOPMENT!
|
117
117
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
118
|
+
References
|
119
|
+
-----------------
|
120
|
+
1. L. Bai, J. Wang, C. Wang, C. Chen, and F. Li, “Distribution Locational Marginal Pricing (DLMP)
|
121
|
+
for Congestion Management and Voltage Support,” IEEE Trans. Power Syst., vol. 33, no. 4,
|
122
|
+
pp. 4061-4073, Jul. 2018, doi: 10.1109/TPWRS.2017.2767632.
|
123
123
|
"""
|
124
124
|
|
125
125
|
def __init__(self, system, config):
|