Skip to content

Commit 4bda287

Browse files
authored
Merge pull request #137 from CURENT/develop
Prep v1.0.8
2 parents ba5d62c + 357641a commit 4bda287

File tree

16 files changed

+2014
-241
lines changed

16 files changed

+2014
-241
lines changed

.github/workflows/publish-pypi.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
name: Publish
22

3-
on: [push, pull_request]
3+
on:
4+
push:
5+
tags:
6+
- '*'
47

58
jobs:
69
func:

ams/cases/README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ Western Electricity Coordinating Council system [SciData](#references)
6464
- `wecc.m`: Matpower case for the WECC system.
6565
- `wecc_uced.xlsx`: UCED case for the WECC system.
6666

67+
## hawaii
68+
69+
Hawaii Synthetic Grid – 37 Buses [synthetic](#references), source from [Hawaii Synthetic Grid – 37 Buses](https://electricgrids.engr.tamu.edu/hawaii40/)
70+
71+
- `Hawaii40.m`: Matpower case for the synthetic Hawaii system.
72+
- `Hawaii40.AUX`: [Auxiliary File Format](https://www.powerworld.com/knowledge-base/auxiliary-file-format-10), can contain power flow, dynamics, contingencies, economic studies, etc. data.
73+
6774
## pglib
6875

6976
Cases from the Power Grid Lib - Optimal Power Flow [pglib](#references)
@@ -82,4 +89,6 @@ Cases from the Power Grid Lib - Optimal Power Flow [pglib](#references)
8289

8390
[SciData]: Q. Zhang and F. Li, “A Dataset for Electricity Market Studies on Western and Northeastern Power Grids in the United States,” Scientific Data, vol. 10, no. 1, p. 646, Sep. 2023, doi: 10.1038/s41597-023-02448-w.
8491

85-
[pglib]: S. Babaeinejadsarookolaee et al., “The Power Grid Library for Benchmarking AC Optimal Power Flow Algorithms,” arXiv.org, 2019. https://arxiv.org/abs/1908.02788
92+
[pglib]: S. Babaeinejadsarookolaee et al., “The Power Grid Library for Benchmarking AC Optimal Power Flow Algorithms,” arXiv.org, 2019. https://arxiv.org/abs/1908.02788
93+
94+
[synthetic] A. B. Birchfield, T. Xu, K. M. Gegner, K. S. Shetye and T. J. Overbye, "Grid Structural Characteristics as Validation Criteria for Synthetic Networks," in IEEE Transactions on Power Systems, vol. 32, no. 4, pp. 3258-3265, July 2017, doi: 10.1109/TPWRS.2016.2616385.

ams/cases/hawaii40/Hawaii40.AUX

Lines changed: 1318 additions & 0 deletions
Large diffs are not rendered by default.

ams/cases/hawaii40/Hawaii40.m

Lines changed: 375 additions & 0 deletions
Large diffs are not rendered by default.

ams/cases/matpower/case5.m

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
% Created by Rui Bo in 2006, modified in 2010, 2014.
1010
% Distributed with permission.
11+
% Make up two sections gentype, genfuel, and bus_name for testing purposes.
1112

1213
% MATPOWER
1314

@@ -60,3 +61,27 @@
6061
2 0 0 2 40 0;
6162
2 0 0 2 10 0;
6263
];
64+
65+
mpc.gentype = {
66+
'ST';
67+
'ST';
68+
'ST';
69+
'ST';
70+
'ST';
71+
};
72+
73+
mpc.genfuel = {
74+
'coal';
75+
'coal';
76+
'coal';
77+
'coal';
78+
'coal';
79+
};
80+
81+
mpc.bus_name = {
82+
'A';
83+
'B';
84+
'C';
85+
'D';
86+
'E';
87+
};

ams/io/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
'matpower': ('m', ),
2626
'psse': ('raw', 'dyr'),
2727
'pypower': ('py',),
28+
'powerworld': ('aux',),
2829
}
2930

3031
# Output formats is a dictionary of supported output formats and their extensions

ams/io/matpower.py

Lines changed: 165 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
MATPOWER parser.
33
"""
44
import logging
5+
import re
56
import numpy as np
67

7-
from andes.io.matpower import m2mpc
8+
from andes.io import read_file_like
89
from andes.shared import deg2rad, rad2deg
910

1011
logger = logging.getLogger(__name__)
@@ -29,6 +30,151 @@ def read(system, file):
2930
return mpc2system(mpc, system)
3031

3132

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+
32178
def mpc2system(mpc: dict, system) -> bool:
33179
"""
34180
Load an mpc dict into an empty AMS system.
@@ -97,7 +243,20 @@ def mpc2system(mpc: dict, system) -> bool:
97243
mpc_gen[:, 19] = system.PV.Rq.default * base_mva / 60
98244
else:
99245
mpc_gen = mpc['gen']
100-
for data in mpc_gen:
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):
101260
# bus pg qg qmax qmin vg mbase status pmax pmin
102261
# 0 1 2 3 4 5 6 7 8 9
103262
# pc1 pc2 qc1min qc1max qc2min qc2max ramp_agc ramp_10
@@ -142,7 +301,7 @@ def mpc2system(mpc: dict, system) -> bool:
142301
Qc2min=qc2min, Qc2max=qc2max,
143302
Ragc=ramp_agc, R10=ramp_10,
144303
R30=ramp_30, Rq=ramp_q,
145-
apf=apf)
304+
apf=apf, gentype=gt, genfuel=gf)
146305
else:
147306
system.add('PV', idx=gen_idx, bus=bus_idx, busr=bus_idx,
148307
name=None,
@@ -155,7 +314,7 @@ def mpc2system(mpc: dict, system) -> bool:
155314
Qc2min=qc2min, Qc2max=qc2max,
156315
Ragc=ramp_agc, R10=ramp_10,
157316
R30=ramp_30, Rq=ramp_q,
158-
apf=apf)
317+
apf=apf, gentype=gt, genfuel=gf)
159318

160319
for data in mpc['branch']:
161320
# fbus tbus r x b rateA rateB rateC ratio angle
@@ -204,9 +363,8 @@ def mpc2system(mpc: dict, system) -> bool:
204363
gen_idx = np.arange(mpc['gen'].shape[0]) + 1
205364
mpc_cost = mpc['gencost']
206365
if mpc_cost[0, 0] == 1:
207-
logger.warning("Type 1 gencost detected. "
208-
"This is not supported in AMS. "
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"
210368
"It is recommended to manually convert the gencost data to type 2.")
211369
mpc_cost = np.repeat(np.array([[2, 0, 0, 3, 0, 0, 0]]),
212370
mpc_cost.shape[0], axis=0)

ams/io/psse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Excel reader and writer for AMS.
2+
PSS/E .raw reader for AMS.
33
This module is the existing module in ``andes.io.psse``.
44
"""
55

ams/models/group.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,20 +175,91 @@ def __init__(self):
175175

176176
class StaticGen(GroupBase):
177177
"""
178-
Generator group.
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
179250
180251
Notes
181252
-----
182-
For co-simulation with ANDES, check
183-
`ANDES StaticGen <https://docs.andes.app/en/latest/groupdoc/StaticGen.html#staticgen>`_
184-
for replacing static generators with dynamic generators.
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.
185256
"""
186257

187258
def __init__(self):
188259
super().__init__()
189260
self.common_params.extend(('bus', 'Sn', 'Vn', 'p0', 'q0', 'ra', 'xs', 'subidx',
190261
'pmax', 'pmin', 'pg0', 'ctrl', 'R10', 'td1', 'td2',
191-
'area', 'zone'))
262+
'area', 'zone', 'gentype', 'genfuel'))
192263
self.common_vars.extend(('p', 'q'))
193264

194265

0 commit comments

Comments
 (0)