ltbams 0.9.9__py3-none-any.whl → 1.0.2__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 +4 -11
- ams/_version.py +3 -3
- ams/cases/5bus/pjm5bus_demo.xlsx +0 -0
- ams/cases/5bus/pjm5bus_jumper.xlsx +0 -0
- ams/cases/5bus/pjm5bus_uced.json +1062 -0
- 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
- ams/cases/ieee123/ieee123.xlsx +0 -0
- ams/cases/ieee123/ieee123_regcv1.xlsx +0 -0
- ams/cases/ieee14/ieee14.json +1166 -0
- ams/cases/ieee14/ieee14.raw +92 -0
- ams/cases/ieee14/ieee14_conn.xlsx +0 -0
- ams/cases/ieee14/ieee14_uced.xlsx +0 -0
- ams/cases/ieee39/ieee39.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/benchmark.json +1594 -0
- ams/cases/matpower/case118.m +787 -0
- ams/cases/matpower/case14.m +129 -0
- ams/cases/matpower/case300.m +1315 -0
- ams/cases/matpower/case39.m +205 -0
- ams/cases/matpower/case5.m +62 -0
- ams/cases/matpower/case_ACTIVSg2000.m +9460 -0
- ams/cases/npcc/npcc.m +644 -0
- ams/cases/npcc/npcc_uced.xlsx +0 -0
- ams/cases/pglib/pglib_opf_case39_epri__api.m +243 -0
- ams/cases/wecc/wecc.m +714 -0
- ams/cases/wecc/wecc_uced.xlsx +0 -0
- ams/cli.py +6 -0
- ams/core/__init__.py +2 -0
- ams/core/documenter.py +652 -0
- ams/core/matprocessor.py +782 -0
- ams/core/model.py +330 -0
- ams/core/param.py +322 -0
- ams/core/service.py +918 -0
- ams/core/symprocessor.py +224 -0
- ams/core/var.py +59 -0
- ams/extension/__init__.py +5 -0
- ams/extension/eva.py +401 -0
- ams/interface.py +1085 -0
- ams/io/__init__.py +133 -0
- ams/io/json.py +82 -0
- ams/io/matpower.py +406 -0
- ams/io/psse.py +6 -0
- ams/io/pypower.py +103 -0
- ams/io/xlsx.py +80 -0
- ams/main.py +81 -4
- ams/models/__init__.py +24 -0
- ams/models/area.py +40 -0
- ams/models/bus.py +52 -0
- ams/models/cost.py +169 -0
- ams/models/distributed/__init__.py +3 -0
- ams/models/distributed/esd1.py +71 -0
- ams/models/distributed/ev.py +60 -0
- ams/models/distributed/pvd1.py +67 -0
- ams/models/group.py +231 -0
- ams/models/info.py +26 -0
- ams/models/line.py +238 -0
- ams/models/renewable/__init__.py +5 -0
- ams/models/renewable/regc.py +119 -0
- ams/models/reserve.py +94 -0
- ams/models/shunt.py +14 -0
- ams/models/static/__init__.py +2 -0
- ams/models/static/gen.py +165 -0
- ams/models/static/pq.py +61 -0
- ams/models/timeslot.py +69 -0
- ams/models/zone.py +49 -0
- ams/opt/__init__.py +12 -0
- ams/opt/constraint.py +175 -0
- ams/opt/exprcalc.py +127 -0
- ams/opt/expression.py +188 -0
- ams/opt/objective.py +174 -0
- ams/opt/omodel.py +432 -0
- ams/opt/optzbase.py +192 -0
- ams/opt/param.py +156 -0
- ams/opt/var.py +233 -0
- ams/pypower/__init__.py +8 -0
- ams/pypower/_compat.py +9 -0
- ams/pypower/core/__init__.py +8 -0
- ams/pypower/core/pips.py +894 -0
- ams/pypower/core/ppoption.py +244 -0
- ams/pypower/core/ppver.py +18 -0
- ams/pypower/core/solver.py +2451 -0
- ams/pypower/eps.py +6 -0
- ams/pypower/idx.py +174 -0
- ams/pypower/io.py +604 -0
- ams/pypower/make/__init__.py +11 -0
- ams/pypower/make/matrices.py +665 -0
- ams/pypower/make/pdv.py +506 -0
- ams/pypower/routines/__init__.py +7 -0
- ams/pypower/routines/cpf.py +513 -0
- ams/pypower/routines/cpf_callbacks.py +114 -0
- ams/pypower/routines/opf.py +1803 -0
- ams/pypower/routines/opffcns.py +1946 -0
- ams/pypower/routines/pflow.py +852 -0
- ams/pypower/toggle.py +1098 -0
- ams/pypower/utils.py +293 -0
- ams/report.py +212 -50
- ams/routines/__init__.py +23 -0
- ams/routines/acopf.py +117 -0
- ams/routines/cpf.py +65 -0
- ams/routines/dcopf.py +241 -0
- ams/routines/dcpf.py +209 -0
- ams/routines/dcpf0.py +196 -0
- ams/routines/dopf.py +150 -0
- ams/routines/ed.py +312 -0
- ams/routines/pflow.py +255 -0
- ams/routines/pflow0.py +113 -0
- ams/routines/routine.py +1033 -0
- ams/routines/rted.py +519 -0
- ams/routines/type.py +160 -0
- ams/routines/uc.py +376 -0
- ams/shared.py +63 -9
- ams/system.py +61 -22
- ams/utils/__init__.py +3 -0
- ams/utils/misc.py +77 -0
- ams/utils/paths.py +257 -0
- docs/Makefile +21 -0
- docs/make.bat +35 -0
- docs/source/_templates/autosummary/base.rst +5 -0
- docs/source/_templates/autosummary/class.rst +35 -0
- docs/source/_templates/autosummary/module.rst +65 -0
- docs/source/_templates/autosummary/module_toctree.rst +66 -0
- docs/source/api.rst +102 -0
- docs/source/conf.py +206 -0
- docs/source/examples/index.rst +34 -0
- docs/source/genmodelref.py +61 -0
- docs/source/genroutineref.py +47 -0
- docs/source/getting_started/copyright.rst +20 -0
- docs/source/getting_started/formats/index.rst +20 -0
- docs/source/getting_started/formats/matpower.rst +183 -0
- docs/source/getting_started/formats/psse.rst +46 -0
- docs/source/getting_started/formats/pypower.rst +223 -0
- docs/source/getting_started/formats/xlsx.png +0 -0
- docs/source/getting_started/formats/xlsx.rst +23 -0
- docs/source/getting_started/index.rst +76 -0
- docs/source/getting_started/install.rst +231 -0
- docs/source/getting_started/overview.rst +26 -0
- docs/source/getting_started/testcase.rst +45 -0
- docs/source/getting_started/verification.rst +13 -0
- docs/source/images/curent.ico +0 -0
- docs/source/images/dcopf_time.png +0 -0
- docs/source/images/sponsors/CURENT_Logo_NameOnTrans.png +0 -0
- docs/source/images/sponsors/CURENT_Logo_Transparent.png +0 -0
- docs/source/images/sponsors/CURENT_Logo_Transparent_Name.png +0 -0
- docs/source/images/sponsors/doe.png +0 -0
- docs/source/index.rst +108 -0
- docs/source/modeling/example.rst +159 -0
- docs/source/modeling/index.rst +17 -0
- docs/source/modeling/model.rst +210 -0
- docs/source/modeling/routine.rst +122 -0
- docs/source/modeling/system.rst +51 -0
- docs/source/release-notes.rst +398 -0
- ltbams-1.0.2.dist-info/METADATA +215 -0
- ltbams-1.0.2.dist-info/RECORD +188 -0
- {ltbams-0.9.9.dist-info → ltbams-1.0.2.dist-info}/WHEEL +1 -1
- ltbams-1.0.2.dist-info/top_level.txt +3 -0
- tests/__init__.py +0 -0
- tests/test_1st_system.py +33 -0
- tests/test_addressing.py +40 -0
- tests/test_andes_mats.py +61 -0
- tests/test_case.py +266 -0
- tests/test_cli.py +34 -0
- tests/test_export_csv.py +89 -0
- tests/test_group.py +83 -0
- tests/test_interface.py +216 -0
- tests/test_io.py +32 -0
- tests/test_jumper.py +27 -0
- tests/test_known_good.py +267 -0
- tests/test_matp.py +437 -0
- tests/test_model.py +54 -0
- tests/test_omodel.py +119 -0
- tests/test_paths.py +22 -0
- tests/test_report.py +251 -0
- tests/test_repr.py +21 -0
- tests/test_routine.py +178 -0
- tests/test_rtn_dcopf.py +101 -0
- tests/test_rtn_dcpf.py +77 -0
- tests/test_rtn_ed.py +279 -0
- tests/test_rtn_pflow.py +219 -0
- tests/test_rtn_rted.py +273 -0
- tests/test_rtn_uc.py +248 -0
- tests/test_service.py +73 -0
- ltbams-0.9.9.dist-info/LICENSE +0 -692
- ltbams-0.9.9.dist-info/METADATA +0 -859
- ltbams-0.9.9.dist-info/RECORD +0 -14
- ltbams-0.9.9.dist-info/top_level.txt +0 -1
- {ltbams-0.9.9.dist-info → ltbams-1.0.2.dist-info}/entry_points.txt +0 -0
ams/io/__init__.py
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
"""
|
2
|
+
AMS input parsers and output formatters.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import importlib
|
6
|
+
import logging
|
7
|
+
|
8
|
+
import os
|
9
|
+
|
10
|
+
from andes.utils.misc import elapsed
|
11
|
+
from andes.io import dump # NOQA
|
12
|
+
|
13
|
+
from ams.io import xlsx, psse, matpower, pypower, json # NOQA
|
14
|
+
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
# Input formats is a dictionary of supported format names and the accepted file extensions
|
19
|
+
# The first file will be parsed by read() function and the addfile will be parsed by read_add()
|
20
|
+
# Typically, column based formats, such as IEEE CDF and PSS/E RAW, are faster to parse
|
21
|
+
|
22
|
+
input_formats = {
|
23
|
+
'xlsx': ('xlsx',),
|
24
|
+
'json': ('json',),
|
25
|
+
'matpower': ('m', ),
|
26
|
+
'psse': ('raw', 'dyr'),
|
27
|
+
'pypower': ('py',),
|
28
|
+
}
|
29
|
+
|
30
|
+
# Output formats is a dictionary of supported output formats and their extensions
|
31
|
+
# The static data will be written by write() function and the addfile by writeadd()
|
32
|
+
|
33
|
+
output_formats = {
|
34
|
+
'xlsx': ('xlsx',),
|
35
|
+
'json': ('json',),
|
36
|
+
}
|
37
|
+
|
38
|
+
|
39
|
+
def guess(system):
|
40
|
+
"""
|
41
|
+
Guess the input format based on extension and content.
|
42
|
+
|
43
|
+
Also stores the format name to `system.files.input_format`.
|
44
|
+
|
45
|
+
Parameters
|
46
|
+
----------
|
47
|
+
system : System
|
48
|
+
System instance with the file name set to `system.files`
|
49
|
+
|
50
|
+
Returns
|
51
|
+
-------
|
52
|
+
str
|
53
|
+
format name
|
54
|
+
"""
|
55
|
+
files = system.files
|
56
|
+
maybe = []
|
57
|
+
if files.input_format:
|
58
|
+
maybe.append(files.input_format)
|
59
|
+
# first, guess by extension
|
60
|
+
for key, val in input_formats.items():
|
61
|
+
if files.ext.strip('.').lower() in val:
|
62
|
+
maybe.append(key)
|
63
|
+
|
64
|
+
# second, guess by lines
|
65
|
+
true_format = ''
|
66
|
+
|
67
|
+
for item in maybe:
|
68
|
+
parser = importlib.import_module('.' + item, __name__)
|
69
|
+
testlines = getattr(parser, 'testlines')
|
70
|
+
if testlines(files.case):
|
71
|
+
true_format = item
|
72
|
+
files.input_format = true_format
|
73
|
+
logger.debug('Input format guessed as %s.', true_format)
|
74
|
+
break
|
75
|
+
|
76
|
+
if not true_format:
|
77
|
+
logger.error('Unable to determine case format.')
|
78
|
+
|
79
|
+
# guess addfile format
|
80
|
+
if files.addfile:
|
81
|
+
_, add_ext = os.path.splitext(files.addfile)
|
82
|
+
for key, val in input_formats.items():
|
83
|
+
if add_ext[1:] in val:
|
84
|
+
files.add_format = key
|
85
|
+
logger.debug('Addfile format guessed as %s.', key)
|
86
|
+
break
|
87
|
+
|
88
|
+
return true_format
|
89
|
+
|
90
|
+
|
91
|
+
def parse(system):
|
92
|
+
"""
|
93
|
+
Parse input file with the given format in `system.files.input_format`.
|
94
|
+
|
95
|
+
Returns
|
96
|
+
-------
|
97
|
+
bool
|
98
|
+
True if successful; False otherwise.
|
99
|
+
"""
|
100
|
+
|
101
|
+
t, _ = elapsed()
|
102
|
+
|
103
|
+
# exit when no input format is given
|
104
|
+
if not system.files.input_format:
|
105
|
+
if not guess(system):
|
106
|
+
logger.error('Input format unknown for file "%s".', system.files.case)
|
107
|
+
return False
|
108
|
+
|
109
|
+
# try parsing the base case file
|
110
|
+
logger.info('Parsing input file "%s"...', system.files.case)
|
111
|
+
input_format = system.files.input_format
|
112
|
+
parser = importlib.import_module('.' + input_format, __name__)
|
113
|
+
if not parser.read(system, system.files.case):
|
114
|
+
logger.error('Error parsing file "%s" with <%s> parser.', system.files.fullname, input_format)
|
115
|
+
return False
|
116
|
+
|
117
|
+
_, s = elapsed(t)
|
118
|
+
logger.info('Input file parsed in %s.', s)
|
119
|
+
|
120
|
+
# Try parsing the addfile
|
121
|
+
t, _ = elapsed()
|
122
|
+
|
123
|
+
if system.files.addfile:
|
124
|
+
logger.info('Parsing additional file "%s"...', system.files.addfile)
|
125
|
+
add_format = system.files.add_format
|
126
|
+
add_parser = importlib.import_module('.' + add_format, __name__)
|
127
|
+
if not add_parser.read_add(system, system.files.addfile):
|
128
|
+
logger.error('Error parsing addfile "%s" with %s parser.', system.files.addfile, input_format)
|
129
|
+
return False
|
130
|
+
_, s = elapsed(t)
|
131
|
+
logger.info('Addfile parsed in %s.', s)
|
132
|
+
|
133
|
+
return True
|
ams/io/json.py
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
"""
|
2
|
+
Json reader and writer for AMS.
|
3
|
+
|
4
|
+
This module leverages the existing parser and writer in andes.io.json.
|
5
|
+
"""
|
6
|
+
import json
|
7
|
+
import logging
|
8
|
+
|
9
|
+
from collections import OrderedDict
|
10
|
+
|
11
|
+
from andes.io.json import (testlines, read) # NOQA
|
12
|
+
from andes.utils.paths import confirm_overwrite
|
13
|
+
|
14
|
+
from ams.shared import empty_adsys, ad_models
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
def write(system, outfile, skip_empty=True, overwrite=None,
|
20
|
+
to_andes=False,
|
21
|
+
):
|
22
|
+
"""
|
23
|
+
Write loaded AMS system data into an json file.
|
24
|
+
If to_andes is True, only write models that are in ANDES,
|
25
|
+
but the outfile might not be able to be read back into AMS.
|
26
|
+
|
27
|
+
Revise function ``andes.io.json.write`` to skip non-andes models.
|
28
|
+
|
29
|
+
Parameters
|
30
|
+
----------
|
31
|
+
system : System
|
32
|
+
A loaded system with parameters
|
33
|
+
outfile : str
|
34
|
+
Path to the output file
|
35
|
+
skip_empty : bool
|
36
|
+
Skip output of empty models (n = 0)
|
37
|
+
overwrite : bool, optional
|
38
|
+
None to prompt for overwrite selection; True to overwrite; False to not overwrite
|
39
|
+
to_andes : bool, optional
|
40
|
+
Write to an ANDES system, where non-ANDES models are skipped
|
41
|
+
|
42
|
+
Returns
|
43
|
+
-------
|
44
|
+
bool
|
45
|
+
True if file written; False otherwise
|
46
|
+
"""
|
47
|
+
if not confirm_overwrite(outfile, overwrite):
|
48
|
+
return False
|
49
|
+
|
50
|
+
if hasattr(outfile, 'write'):
|
51
|
+
outfile.write(_dump_system(system, skip_empty, to_andes=to_andes))
|
52
|
+
else:
|
53
|
+
with open(outfile, 'w') as writer:
|
54
|
+
writer.write(_dump_system(system, skip_empty, to_andes=to_andes))
|
55
|
+
logger.info('JSON file written to "%s"', outfile)
|
56
|
+
|
57
|
+
return True
|
58
|
+
|
59
|
+
|
60
|
+
def _dump_system(system, skip_empty, orient='records', to_andes=False):
|
61
|
+
"""
|
62
|
+
Dump parameters of each model into a json string and return
|
63
|
+
them all in an OrderedDict.
|
64
|
+
"""
|
65
|
+
out = OrderedDict()
|
66
|
+
for name, instance in system.models.items():
|
67
|
+
if skip_empty and instance.n == 0:
|
68
|
+
continue
|
69
|
+
if to_andes:
|
70
|
+
if name not in ad_models:
|
71
|
+
continue
|
72
|
+
# NOTE: ommit parameters that are not in ANDES
|
73
|
+
skip_params = []
|
74
|
+
ams_params = list(instance.params.keys())
|
75
|
+
andes_params = list(empty_adsys.models[name].params.keys())
|
76
|
+
skip_params = list(set(ams_params) - set(andes_params))
|
77
|
+
df = instance.cache.df_in.drop(skip_params, axis=1, errors='ignore')
|
78
|
+
out[name] = df.to_dict(orient=orient)
|
79
|
+
else:
|
80
|
+
df = instance.cache.df_in
|
81
|
+
out[name] = df.to_dict(orient=orient)
|
82
|
+
return json.dumps(out, indent=2)
|
ams/io/matpower.py
ADDED
@@ -0,0 +1,406 @@
|
|
1
|
+
"""
|
2
|
+
MATPOWER parser.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
import numpy as np
|
6
|
+
|
7
|
+
from andes.io.matpower import m2mpc
|
8
|
+
from andes.shared import deg2rad, rad2deg
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
def testlines(infile):
|
14
|
+
"""
|
15
|
+
Test if this file is in the MATPOWER format.
|
16
|
+
|
17
|
+
NOT YET IMPLEMENTED.
|
18
|
+
"""
|
19
|
+
|
20
|
+
return True # hard coded
|
21
|
+
|
22
|
+
|
23
|
+
def read(system, file):
|
24
|
+
"""
|
25
|
+
Read a MATPOWER data file into mpc, and build AMS device elements.
|
26
|
+
"""
|
27
|
+
|
28
|
+
mpc = m2mpc(file)
|
29
|
+
return mpc2system(mpc, system)
|
30
|
+
|
31
|
+
|
32
|
+
def mpc2system(mpc: dict, system) -> bool:
|
33
|
+
"""
|
34
|
+
Load an mpc dict into an empty AMS system.
|
35
|
+
|
36
|
+
Revised from ``andes.io.matpower.mpc2system``.
|
37
|
+
|
38
|
+
Note that `mbase` in mpc is converted to `Sn`, but it is not actually used in
|
39
|
+
MATPOWER nor AMS.
|
40
|
+
|
41
|
+
Parameters
|
42
|
+
----------
|
43
|
+
system : ams.system.System
|
44
|
+
Empty system to load the data into.
|
45
|
+
mpc : dict
|
46
|
+
mpc struct names : numpy arrays
|
47
|
+
|
48
|
+
Returns
|
49
|
+
-------
|
50
|
+
bool
|
51
|
+
True if successful, False otherwise.
|
52
|
+
"""
|
53
|
+
|
54
|
+
# list of buses with slack gen
|
55
|
+
sw = []
|
56
|
+
|
57
|
+
system.config.mva = base_mva = mpc['baseMVA']
|
58
|
+
|
59
|
+
for data in mpc['bus']:
|
60
|
+
# idx ty pd qd gs bs area vmag vang baseKV zone vmax vmin
|
61
|
+
# 0 1 2 3 4 5 6 7 8 9 10 11 12
|
62
|
+
idx = int(data[0])
|
63
|
+
ty = data[1]
|
64
|
+
if ty == 3:
|
65
|
+
sw.append(idx)
|
66
|
+
pd = data[2] / base_mva
|
67
|
+
qd = data[3] / base_mva
|
68
|
+
gs = data[4] / base_mva
|
69
|
+
bs = data[5] / base_mva
|
70
|
+
area = data[6]
|
71
|
+
vmag = data[7]
|
72
|
+
vang = data[8] * deg2rad
|
73
|
+
baseKV = data[9]
|
74
|
+
if baseKV == 0:
|
75
|
+
baseKV = 110
|
76
|
+
zone = data[10]
|
77
|
+
vmax = data[11]
|
78
|
+
vmin = data[12]
|
79
|
+
|
80
|
+
system.add('Bus', idx=idx, name=None,
|
81
|
+
type=ty, Vn=baseKV,
|
82
|
+
v0=vmag, a0=vang,
|
83
|
+
vmax=vmax, vmin=vmin,
|
84
|
+
area=area, zone=zone)
|
85
|
+
if pd != 0 or qd != 0:
|
86
|
+
system.add('PQ', bus=idx, name=None, Vn=baseKV, p0=pd, q0=qd)
|
87
|
+
if gs or bs:
|
88
|
+
system.add('Shunt', bus=idx, name=None, Vn=baseKV, g=gs, b=bs)
|
89
|
+
|
90
|
+
gen_idx = 0
|
91
|
+
if mpc['gen'].shape[1] <= 10: # missing data
|
92
|
+
mpc_gen = np.zeros((mpc['gen'].shape[0], 21), dtype=np.float64)
|
93
|
+
mpc_gen[:, :10] = mpc['gen']
|
94
|
+
mpc_gen[:, 16] = system.PV.Ragc.default * base_mva / 60
|
95
|
+
mpc_gen[:, 17] = system.PV.R10.default * base_mva
|
96
|
+
mpc_gen[:, 18] = system.PV.R30.default * base_mva
|
97
|
+
mpc_gen[:, 19] = system.PV.Rq.default * base_mva / 60
|
98
|
+
else:
|
99
|
+
mpc_gen = mpc['gen']
|
100
|
+
for data in mpc_gen:
|
101
|
+
# bus pg qg qmax qmin vg mbase status pmax pmin
|
102
|
+
# 0 1 2 3 4 5 6 7 8 9
|
103
|
+
# pc1 pc2 qc1min qc1max qc2min qc2max ramp_agc ramp_10
|
104
|
+
# 10 11 12 13 14 15 16 17
|
105
|
+
# ramp_30 ramp_q apf
|
106
|
+
# 18 19 20
|
107
|
+
bus_idx = int(data[0])
|
108
|
+
gen_idx += 1
|
109
|
+
vg = data[5]
|
110
|
+
status = int(data[7])
|
111
|
+
pg = data[1] / base_mva
|
112
|
+
qg = data[2] / base_mva
|
113
|
+
qmax = data[3] / base_mva
|
114
|
+
qmin = data[4] / base_mva
|
115
|
+
pmax = data[8] / base_mva
|
116
|
+
pmin = data[9] / base_mva
|
117
|
+
pc1 = data[10] / base_mva
|
118
|
+
pc2 = data[11] / base_mva
|
119
|
+
qc1min = data[12] / base_mva
|
120
|
+
qc1max = data[13] / base_mva
|
121
|
+
qc2min = data[14] / base_mva
|
122
|
+
qc2max = data[15] / base_mva
|
123
|
+
ramp_agc = 60 * data[16] / base_mva # MW/min -> MW/h
|
124
|
+
ramp_10 = data[17] / base_mva # MW -> MW/h
|
125
|
+
ramp_30 = data[18] / base_mva # MW -> MW/h
|
126
|
+
ramp_q = 60 * data[19] / base_mva # MVAr/min -> MVAr/h
|
127
|
+
apf = data[20]
|
128
|
+
|
129
|
+
uid = system.Bus.idx2uid(bus_idx)
|
130
|
+
vn = system.Bus.Vn.v[uid]
|
131
|
+
a0 = system.Bus.a0.v[uid]
|
132
|
+
|
133
|
+
if bus_idx in sw:
|
134
|
+
system.add('Slack', idx=gen_idx, bus=bus_idx, busr=bus_idx,
|
135
|
+
name=None,
|
136
|
+
u=status, Sn=data[6],
|
137
|
+
Vn=vn, v0=vg, p0=pg, q0=qg, a0=a0,
|
138
|
+
pmax=pmax, pmin=pmin,
|
139
|
+
qmax=qmax, qmin=qmin,
|
140
|
+
Pc1=pc1, Pc2=pc2,
|
141
|
+
Qc1min=qc1min, Qc1max=qc1max,
|
142
|
+
Qc2min=qc2min, Qc2max=qc2max,
|
143
|
+
Ragc=ramp_agc, R10=ramp_10,
|
144
|
+
R30=ramp_30, Rq=ramp_q,
|
145
|
+
apf=apf)
|
146
|
+
else:
|
147
|
+
system.add('PV', idx=gen_idx, bus=bus_idx, busr=bus_idx,
|
148
|
+
name=None,
|
149
|
+
u=status, Sn=data[6],
|
150
|
+
Vn=vn, v0=vg, p0=pg, q0=qg,
|
151
|
+
pmax=pmax, pmin=pmin,
|
152
|
+
qmax=qmax, qmin=qmin,
|
153
|
+
Pc1=pc1, Pc2=pc2,
|
154
|
+
Qc1min=qc1min, Qc1max=qc1max,
|
155
|
+
Qc2min=qc2min, Qc2max=qc2max,
|
156
|
+
Ragc=ramp_agc, R10=ramp_10,
|
157
|
+
R30=ramp_30, Rq=ramp_q,
|
158
|
+
apf=apf)
|
159
|
+
|
160
|
+
for data in mpc['branch']:
|
161
|
+
# fbus tbus r x b rateA rateB rateC ratio angle
|
162
|
+
# 0 1 2 3 4 5 6 7 8 9
|
163
|
+
# status angmin angmax Pf Qf Pt Qt
|
164
|
+
# 10 11 12 13 14 15 16
|
165
|
+
fbus = int(data[0])
|
166
|
+
tbus = int(data[1])
|
167
|
+
r = data[2]
|
168
|
+
x = data[3]
|
169
|
+
b = data[4]
|
170
|
+
rate_a = data[5] / base_mva
|
171
|
+
rate_b = data[6] / base_mva
|
172
|
+
rate_c = data[7] / base_mva
|
173
|
+
amin = data[11] * deg2rad
|
174
|
+
amax = data[12] * deg2rad
|
175
|
+
|
176
|
+
status = int(data[10])
|
177
|
+
|
178
|
+
if (data[8] == 0.0) or (data[8] == 1.0 and data[9] == 0.0):
|
179
|
+
# not a transformer
|
180
|
+
tf = False
|
181
|
+
ratio = 1
|
182
|
+
angle = 0
|
183
|
+
else:
|
184
|
+
tf = True
|
185
|
+
ratio = data[8]
|
186
|
+
angle = data[9] * deg2rad
|
187
|
+
|
188
|
+
vf = system.Bus.Vn.v[system.Bus.idx2uid(fbus)]
|
189
|
+
vt = system.Bus.Vn.v[system.Bus.idx2uid(tbus)]
|
190
|
+
system.add('Line', u=status, name=f'Line {fbus:.0f}-{tbus:.0f}',
|
191
|
+
Vn1=vf, Vn2=vt,
|
192
|
+
bus1=fbus, bus2=tbus,
|
193
|
+
r=r, x=x, b=b,
|
194
|
+
trans=tf, tap=ratio, phi=angle,
|
195
|
+
rate_a=rate_a, rate_b=rate_b, rate_c=rate_c,
|
196
|
+
amin=amin, amax=amax)
|
197
|
+
|
198
|
+
if ('bus_name' in mpc) and (len(mpc['bus_name']) == len(system.Bus.name.v)):
|
199
|
+
system.Bus.name.v[:] = mpc['bus_name']
|
200
|
+
|
201
|
+
# --- gencost ---
|
202
|
+
if 'gencost' in mpc:
|
203
|
+
gcost_idx = 0
|
204
|
+
gen_idx = np.arange(mpc['gen'].shape[0]) + 1
|
205
|
+
mpc_cost = mpc['gencost']
|
206
|
+
for data, gen in zip(mpc_cost, gen_idx):
|
207
|
+
# NOTE: only type 2 costs are supported for now
|
208
|
+
# type startup shutdown n c2 c1 c0
|
209
|
+
# 0 1 2 3 4 5 6
|
210
|
+
if data[0] != 2:
|
211
|
+
raise ValueError('Only MODEL 2 costs are supported')
|
212
|
+
gcost_idx += 1
|
213
|
+
gctype = int(data[0])
|
214
|
+
startup = data[1]
|
215
|
+
shutdown = data[2]
|
216
|
+
if data[3] == 3:
|
217
|
+
c2 = data[4] * base_mva ** 2
|
218
|
+
c1 = data[5] * base_mva
|
219
|
+
c0 = data[6]
|
220
|
+
elif data[3] == 2:
|
221
|
+
c2 = 0
|
222
|
+
c1 = data[4] * base_mva
|
223
|
+
c0 = data[5]
|
224
|
+
else:
|
225
|
+
raise ValueError('Unrecognized gencost model, please use eighter quadratic or linear cost model')
|
226
|
+
system.add('GCost', gen=int(gen),
|
227
|
+
u=1, type=gctype,
|
228
|
+
idx=gcost_idx,
|
229
|
+
name=None,
|
230
|
+
csu=startup, csd=shutdown,
|
231
|
+
c2=c2, c1=c1, c0=c0
|
232
|
+
)
|
233
|
+
|
234
|
+
# --- Zone ---
|
235
|
+
zone_id = np.unique(system.Bus.zone.v).astype(int)
|
236
|
+
for zone in zone_id:
|
237
|
+
zone_idx = f'ZONE_{zone}'
|
238
|
+
system.add('Zone', idx=zone_idx, name=None)
|
239
|
+
bus_zone = system.Bus.zone.v
|
240
|
+
bus_zone = [f'ZONE_{int(zone)}' for zone in bus_zone]
|
241
|
+
system.Bus.zone.v = bus_zone
|
242
|
+
return True
|
243
|
+
|
244
|
+
|
245
|
+
def _get_bus_id_caller(bus):
|
246
|
+
"""
|
247
|
+
Helper function to get the bus id. Force bus id to be uid+1.
|
248
|
+
|
249
|
+
Parameters
|
250
|
+
----------
|
251
|
+
bus : andes.models.bus.Bus
|
252
|
+
Bus object
|
253
|
+
|
254
|
+
Returns
|
255
|
+
-------
|
256
|
+
lambda function to that takes bus idx and returns bus id for matpower case
|
257
|
+
"""
|
258
|
+
|
259
|
+
if np.array(bus.idx.v).dtype in ['int', 'float']:
|
260
|
+
return lambda x: x
|
261
|
+
else:
|
262
|
+
return lambda x: list(np.array(bus.idx2uid(x)) + 1)
|
263
|
+
|
264
|
+
|
265
|
+
def system2mpc(system) -> dict:
|
266
|
+
"""
|
267
|
+
Convert data from an AMS system to an mpc dict.
|
268
|
+
|
269
|
+
In the ``gen`` section, slack generators preceeds PV generators.
|
270
|
+
|
271
|
+
Compared to the ``andes.io.matpower.system2mpc``,
|
272
|
+
this function includes the generator cost data in the ``gencost``
|
273
|
+
section.
|
274
|
+
Additionally, ``c2`` and ``c1`` are scaled by ``base_mva`` to match
|
275
|
+
MATPOWER unit ``MW``.
|
276
|
+
|
277
|
+
Parameters
|
278
|
+
----------
|
279
|
+
system : ams.core.system.System
|
280
|
+
AMS system
|
281
|
+
|
282
|
+
Returns
|
283
|
+
-------
|
284
|
+
mpc: dict
|
285
|
+
MATPOWER mpc dict
|
286
|
+
"""
|
287
|
+
|
288
|
+
mpc = dict(version='2',
|
289
|
+
baseMVA=system.config.mva,
|
290
|
+
bus=np.zeros((system.Bus.n, 13), dtype=np.float64),
|
291
|
+
gen=np.zeros((system.PV.n + system.Slack.n, 21), dtype=np.float64),
|
292
|
+
branch=np.zeros((system.Line.n, 17), dtype=np.float64),
|
293
|
+
gencost=np.zeros((system.GCost.n, 7), dtype=np.float64),
|
294
|
+
)
|
295
|
+
|
296
|
+
if system.Bus.name.v is not None:
|
297
|
+
mpc['bus_name'] = system.Bus.name.v
|
298
|
+
|
299
|
+
base_mva = system.config.mva
|
300
|
+
|
301
|
+
# --- bus ---
|
302
|
+
bus = mpc['bus']
|
303
|
+
gen = mpc['gen']
|
304
|
+
|
305
|
+
to_busid = _get_bus_id_caller(system.Bus)
|
306
|
+
|
307
|
+
bus[:, 0] = to_busid(system.Bus.idx.v)
|
308
|
+
bus[:, 1] = 1
|
309
|
+
bus[:, 7] = system.Bus.v0.v
|
310
|
+
bus[:, 8] = system.Bus.a0.v * rad2deg
|
311
|
+
bus[:, 9] = system.Bus.Vn.v
|
312
|
+
bus[:, 11] = system.Bus.vmax.v
|
313
|
+
bus[:, 12] = system.Bus.vmin.v
|
314
|
+
|
315
|
+
# --- zone ---
|
316
|
+
ZONE_I = system.Zone.idx.v
|
317
|
+
if len(ZONE_I) > 0:
|
318
|
+
mapping = {busi0: i for i, busi0 in enumerate(ZONE_I)}
|
319
|
+
bus[:, 10] = np.array([mapping[busi0] for busi0 in system.Bus.zone.v])
|
320
|
+
|
321
|
+
# --- PQ ---
|
322
|
+
if system.PQ.n > 0:
|
323
|
+
pq_pos = system.Bus.idx2uid(system.PQ.bus.v)
|
324
|
+
bus[pq_pos, 2] = system.PQ.p0.v * base_mva
|
325
|
+
bus[pq_pos, 3] = system.PQ.q0.v * base_mva
|
326
|
+
|
327
|
+
# --- Shunt ---
|
328
|
+
if system.Shunt.n > 0:
|
329
|
+
shunt_pos = system.Bus.idx2uid(system.Shunt.bus.v)
|
330
|
+
bus[shunt_pos, 4] = system.Shunt.g.v * base_mva
|
331
|
+
bus[shunt_pos, 5] = system.Shunt.b.v * base_mva
|
332
|
+
|
333
|
+
# --- PV ---
|
334
|
+
if system.PV.n > 0:
|
335
|
+
PV = system.PV
|
336
|
+
pv_pos = system.Bus.idx2uid(PV.bus.v)
|
337
|
+
bus[pv_pos, 1] = 2
|
338
|
+
gen[system.Slack.n:, 0] = to_busid(PV.bus.v)
|
339
|
+
gen[system.Slack.n:, 1] = PV.p0.v * base_mva
|
340
|
+
gen[system.Slack.n:, 2] = PV.q0.v * base_mva
|
341
|
+
gen[system.Slack.n:, 3] = PV.qmax.v * base_mva
|
342
|
+
gen[system.Slack.n:, 4] = PV.qmin.v * base_mva
|
343
|
+
gen[system.Slack.n:, 5] = PV.v0.v
|
344
|
+
gen[system.Slack.n:, 6] = PV.Sn.v
|
345
|
+
gen[system.Slack.n:, 7] = PV.u.v
|
346
|
+
gen[system.Slack.n:, 8] = (PV.ctrl.v * PV.pmax.v + (1 - PV.ctrl.v) * PV.p0.v) * base_mva
|
347
|
+
gen[system.Slack.n:, 9] = (PV.ctrl.v * PV.pmin.v + (1 - PV.ctrl.v) * PV.p0.v) * base_mva
|
348
|
+
gen[system.Slack.n:, 16] = PV.Ragc.v * base_mva * 60 # MW/h -> MW/min
|
349
|
+
gen[system.Slack.n:, 17] = PV.R10.v * base_mva
|
350
|
+
gen[system.Slack.n:, 18] = PV.R30.v * base_mva
|
351
|
+
gen[system.Slack.n:, 19] = PV.Rq.v * base_mva * 60 # MVAr/h -> MVAr/min
|
352
|
+
|
353
|
+
# --- Slack ---
|
354
|
+
if system.Slack.n > 0:
|
355
|
+
slack_pos = system.Bus.idx2uid(system.Slack.bus.v)
|
356
|
+
bus[slack_pos, 1] = 3
|
357
|
+
bus[slack_pos, 8] = system.Slack.a0.v * rad2deg
|
358
|
+
gen[:system.Slack.n, 0] = to_busid(system.Slack.bus.v)
|
359
|
+
gen[:system.Slack.n, 1] = system.Slack.p0.v * base_mva
|
360
|
+
gen[:system.Slack.n, 2] = system.Slack.q0.v * base_mva
|
361
|
+
gen[:system.Slack.n, 3] = system.Slack.qmax.v * base_mva
|
362
|
+
gen[:system.Slack.n, 4] = system.Slack.qmin.v * base_mva
|
363
|
+
gen[:system.Slack.n, 5] = system.Slack.v0.v
|
364
|
+
gen[:system.Slack.n, 6] = system.Slack.Sn.v
|
365
|
+
gen[:system.Slack.n, 7] = system.Slack.u.v
|
366
|
+
gen[:system.Slack.n, 8] = system.Slack.pmax.v * base_mva
|
367
|
+
gen[:system.Slack.n, 9] = system.Slack.pmin.v * base_mva
|
368
|
+
gen[:system.Slack.n, 16] = system.Slack.Ragc.v * base_mva * 60 # MW/h -> MW/min
|
369
|
+
gen[:system.Slack.n, 17] = system.Slack.R10.v * base_mva
|
370
|
+
gen[:system.Slack.n, 18] = system.Slack.R30.v * base_mva
|
371
|
+
gen[:system.Slack.n, 19] = system.Slack.Rq.v * base_mva * 60 # MVAr/h -> MVAr/min
|
372
|
+
|
373
|
+
if system.Line.n > 0:
|
374
|
+
branch = mpc['branch']
|
375
|
+
branch[:, 0] = to_busid(system.Line.bus1.v)
|
376
|
+
branch[:, 1] = to_busid(system.Line.bus2.v)
|
377
|
+
branch[:, 2] = system.Line.r.v
|
378
|
+
branch[:, 3] = system.Line.x.v
|
379
|
+
branch[:, 4] = system.Line.b.v
|
380
|
+
branch[:, 5] = system.Line.rate_a.v * base_mva
|
381
|
+
branch[:, 6] = system.Line.rate_b.v * base_mva
|
382
|
+
branch[:, 7] = system.Line.rate_c.v * base_mva
|
383
|
+
branch[:, 8] = system.Line.tap.v
|
384
|
+
branch[:, 9] = system.Line.phi.v * rad2deg
|
385
|
+
branch[:, 10] = system.Line.u.v
|
386
|
+
branch[:, 11] = system.Line.amin.v * rad2deg
|
387
|
+
branch[:, 12] = system.Line.amax.v * rad2deg
|
388
|
+
|
389
|
+
# --- GCost ---
|
390
|
+
# NOTE: adjust GCost sequence to match the generator sequence
|
391
|
+
if system.GCost.n > 0:
|
392
|
+
stg_idx = system.Slack.idx.v + system.PV.idx.v
|
393
|
+
gcost_idx = system.GCost.find_idx(keys=['gen'], values=[stg_idx])
|
394
|
+
gcost_uid = system.GCost.idx2uid(gcost_idx)
|
395
|
+
gencost = mpc['gencost']
|
396
|
+
gencost[:, 0] = system.GCost.type.v[gcost_uid]
|
397
|
+
gencost[:, 1] = system.GCost.csu.v[gcost_uid]
|
398
|
+
gencost[:, 2] = system.GCost.csd.v[gcost_uid]
|
399
|
+
gencost[:, 3] = 3
|
400
|
+
gencost[:, 4] = system.GCost.c2.v[gcost_uid] / base_mva / base_mva
|
401
|
+
gencost[:, 5] = system.GCost.c1.v[gcost_uid] / base_mva
|
402
|
+
gencost[:, 6] = system.GCost.c0.v[gcost_uid]
|
403
|
+
else:
|
404
|
+
mpc.pop('gencost')
|
405
|
+
|
406
|
+
return mpc
|