ltbams 0.9.9__py3-none-any.whl → 1.0.2a1__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.
Files changed (191) hide show
  1. ams/__init__.py +4 -11
  2. ams/_version.py +3 -3
  3. ams/cases/5bus/pjm5bus_demo.xlsx +0 -0
  4. ams/cases/5bus/pjm5bus_jumper.xlsx +0 -0
  5. ams/cases/5bus/pjm5bus_uced.json +1062 -0
  6. ams/cases/5bus/pjm5bus_uced.xlsx +0 -0
  7. ams/cases/5bus/pjm5bus_uced_esd1.xlsx +0 -0
  8. ams/cases/5bus/pjm5bus_uced_ev.xlsx +0 -0
  9. ams/cases/ieee123/ieee123.xlsx +0 -0
  10. ams/cases/ieee123/ieee123_regcv1.xlsx +0 -0
  11. ams/cases/ieee14/ieee14.json +1166 -0
  12. ams/cases/ieee14/ieee14.raw +92 -0
  13. ams/cases/ieee14/ieee14_conn.xlsx +0 -0
  14. ams/cases/ieee14/ieee14_uced.xlsx +0 -0
  15. ams/cases/ieee39/ieee39.xlsx +0 -0
  16. ams/cases/ieee39/ieee39_uced.xlsx +0 -0
  17. ams/cases/ieee39/ieee39_uced_esd1.xlsx +0 -0
  18. ams/cases/ieee39/ieee39_uced_pvd1.xlsx +0 -0
  19. ams/cases/ieee39/ieee39_uced_vis.xlsx +0 -0
  20. ams/cases/matpower/benchmark.json +1594 -0
  21. ams/cases/matpower/case118.m +787 -0
  22. ams/cases/matpower/case14.m +129 -0
  23. ams/cases/matpower/case300.m +1315 -0
  24. ams/cases/matpower/case39.m +205 -0
  25. ams/cases/matpower/case5.m +62 -0
  26. ams/cases/matpower/case_ACTIVSg2000.m +9460 -0
  27. ams/cases/npcc/npcc.m +644 -0
  28. ams/cases/npcc/npcc_uced.xlsx +0 -0
  29. ams/cases/pglib/pglib_opf_case39_epri__api.m +243 -0
  30. ams/cases/wecc/wecc.m +714 -0
  31. ams/cases/wecc/wecc_uced.xlsx +0 -0
  32. ams/cli.py +6 -0
  33. ams/core/__init__.py +2 -0
  34. ams/core/documenter.py +652 -0
  35. ams/core/matprocessor.py +782 -0
  36. ams/core/model.py +330 -0
  37. ams/core/param.py +322 -0
  38. ams/core/service.py +918 -0
  39. ams/core/symprocessor.py +224 -0
  40. ams/core/var.py +59 -0
  41. ams/extension/__init__.py +5 -0
  42. ams/extension/eva.py +401 -0
  43. ams/interface.py +1085 -0
  44. ams/io/__init__.py +133 -0
  45. ams/io/json.py +82 -0
  46. ams/io/matpower.py +406 -0
  47. ams/io/psse.py +6 -0
  48. ams/io/pypower.py +103 -0
  49. ams/io/xlsx.py +80 -0
  50. ams/main.py +81 -4
  51. ams/models/__init__.py +24 -0
  52. ams/models/area.py +40 -0
  53. ams/models/bus.py +52 -0
  54. ams/models/cost.py +169 -0
  55. ams/models/distributed/__init__.py +3 -0
  56. ams/models/distributed/esd1.py +71 -0
  57. ams/models/distributed/ev.py +60 -0
  58. ams/models/distributed/pvd1.py +67 -0
  59. ams/models/group.py +231 -0
  60. ams/models/info.py +26 -0
  61. ams/models/line.py +238 -0
  62. ams/models/renewable/__init__.py +5 -0
  63. ams/models/renewable/regc.py +119 -0
  64. ams/models/reserve.py +94 -0
  65. ams/models/shunt.py +14 -0
  66. ams/models/static/__init__.py +2 -0
  67. ams/models/static/gen.py +165 -0
  68. ams/models/static/pq.py +61 -0
  69. ams/models/timeslot.py +69 -0
  70. ams/models/zone.py +49 -0
  71. ams/opt/__init__.py +12 -0
  72. ams/opt/constraint.py +175 -0
  73. ams/opt/exprcalc.py +127 -0
  74. ams/opt/expression.py +188 -0
  75. ams/opt/objective.py +174 -0
  76. ams/opt/omodel.py +432 -0
  77. ams/opt/optzbase.py +192 -0
  78. ams/opt/param.py +156 -0
  79. ams/opt/var.py +233 -0
  80. ams/pypower/__init__.py +8 -0
  81. ams/pypower/_compat.py +9 -0
  82. ams/pypower/core/__init__.py +8 -0
  83. ams/pypower/core/pips.py +894 -0
  84. ams/pypower/core/ppoption.py +244 -0
  85. ams/pypower/core/ppver.py +18 -0
  86. ams/pypower/core/solver.py +2451 -0
  87. ams/pypower/eps.py +6 -0
  88. ams/pypower/idx.py +174 -0
  89. ams/pypower/io.py +604 -0
  90. ams/pypower/make/__init__.py +11 -0
  91. ams/pypower/make/matrices.py +665 -0
  92. ams/pypower/make/pdv.py +506 -0
  93. ams/pypower/routines/__init__.py +7 -0
  94. ams/pypower/routines/cpf.py +513 -0
  95. ams/pypower/routines/cpf_callbacks.py +114 -0
  96. ams/pypower/routines/opf.py +1803 -0
  97. ams/pypower/routines/opffcns.py +1946 -0
  98. ams/pypower/routines/pflow.py +852 -0
  99. ams/pypower/toggle.py +1098 -0
  100. ams/pypower/utils.py +293 -0
  101. ams/report.py +212 -50
  102. ams/routines/__init__.py +23 -0
  103. ams/routines/acopf.py +117 -0
  104. ams/routines/cpf.py +65 -0
  105. ams/routines/dcopf.py +241 -0
  106. ams/routines/dcpf.py +209 -0
  107. ams/routines/dcpf0.py +196 -0
  108. ams/routines/dopf.py +150 -0
  109. ams/routines/ed.py +312 -0
  110. ams/routines/pflow.py +255 -0
  111. ams/routines/pflow0.py +113 -0
  112. ams/routines/routine.py +1033 -0
  113. ams/routines/rted.py +519 -0
  114. ams/routines/type.py +160 -0
  115. ams/routines/uc.py +376 -0
  116. ams/shared.py +63 -9
  117. ams/system.py +61 -22
  118. ams/utils/__init__.py +3 -0
  119. ams/utils/misc.py +77 -0
  120. ams/utils/paths.py +257 -0
  121. docs/Makefile +21 -0
  122. docs/make.bat +35 -0
  123. docs/source/_templates/autosummary/base.rst +5 -0
  124. docs/source/_templates/autosummary/class.rst +35 -0
  125. docs/source/_templates/autosummary/module.rst +65 -0
  126. docs/source/_templates/autosummary/module_toctree.rst +66 -0
  127. docs/source/api.rst +102 -0
  128. docs/source/conf.py +203 -0
  129. docs/source/examples/index.rst +34 -0
  130. docs/source/genmodelref.py +61 -0
  131. docs/source/genroutineref.py +47 -0
  132. docs/source/getting_started/copyright.rst +20 -0
  133. docs/source/getting_started/formats/index.rst +20 -0
  134. docs/source/getting_started/formats/matpower.rst +183 -0
  135. docs/source/getting_started/formats/psse.rst +46 -0
  136. docs/source/getting_started/formats/pypower.rst +223 -0
  137. docs/source/getting_started/formats/xlsx.png +0 -0
  138. docs/source/getting_started/formats/xlsx.rst +23 -0
  139. docs/source/getting_started/index.rst +76 -0
  140. docs/source/getting_started/install.rst +234 -0
  141. docs/source/getting_started/overview.rst +26 -0
  142. docs/source/getting_started/testcase.rst +45 -0
  143. docs/source/getting_started/verification.rst +13 -0
  144. docs/source/images/curent.ico +0 -0
  145. docs/source/images/dcopf_time.png +0 -0
  146. docs/source/images/sponsors/CURENT_Logo_NameOnTrans.png +0 -0
  147. docs/source/images/sponsors/CURENT_Logo_Transparent.png +0 -0
  148. docs/source/images/sponsors/CURENT_Logo_Transparent_Name.png +0 -0
  149. docs/source/images/sponsors/doe.png +0 -0
  150. docs/source/index.rst +108 -0
  151. docs/source/modeling/example.rst +159 -0
  152. docs/source/modeling/index.rst +17 -0
  153. docs/source/modeling/model.rst +210 -0
  154. docs/source/modeling/routine.rst +122 -0
  155. docs/source/modeling/system.rst +51 -0
  156. docs/source/release-notes.rst +398 -0
  157. ltbams-1.0.2a1.dist-info/METADATA +210 -0
  158. ltbams-1.0.2a1.dist-info/RECORD +188 -0
  159. {ltbams-0.9.9.dist-info → ltbams-1.0.2a1.dist-info}/WHEEL +1 -1
  160. ltbams-1.0.2a1.dist-info/top_level.txt +3 -0
  161. tests/__init__.py +0 -0
  162. tests/test_1st_system.py +33 -0
  163. tests/test_addressing.py +40 -0
  164. tests/test_andes_mats.py +61 -0
  165. tests/test_case.py +266 -0
  166. tests/test_cli.py +34 -0
  167. tests/test_export_csv.py +89 -0
  168. tests/test_group.py +83 -0
  169. tests/test_interface.py +216 -0
  170. tests/test_io.py +32 -0
  171. tests/test_jumper.py +27 -0
  172. tests/test_known_good.py +267 -0
  173. tests/test_matp.py +437 -0
  174. tests/test_model.py +54 -0
  175. tests/test_omodel.py +119 -0
  176. tests/test_paths.py +22 -0
  177. tests/test_report.py +251 -0
  178. tests/test_repr.py +21 -0
  179. tests/test_routine.py +178 -0
  180. tests/test_rtn_dcopf.py +101 -0
  181. tests/test_rtn_dcpf.py +77 -0
  182. tests/test_rtn_ed.py +279 -0
  183. tests/test_rtn_pflow.py +219 -0
  184. tests/test_rtn_rted.py +273 -0
  185. tests/test_rtn_uc.py +248 -0
  186. tests/test_service.py +73 -0
  187. ltbams-0.9.9.dist-info/LICENSE +0 -692
  188. ltbams-0.9.9.dist-info/METADATA +0 -859
  189. ltbams-0.9.9.dist-info/RECORD +0 -14
  190. ltbams-0.9.9.dist-info/top_level.txt +0 -1
  191. {ltbams-0.9.9.dist-info → ltbams-1.0.2a1.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
ams/io/psse.py ADDED
@@ -0,0 +1,6 @@
1
+ """
2
+ Excel reader and writer for AMS.
3
+ This module is the existing module in ``andes.io.psse``.
4
+ """
5
+
6
+ from andes.io.psse import (read, testlines) # NOQA