ltbams 1.0.8__py3-none-any.whl → 1.0.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. ams/__init__.py +0 -1
  2. ams/_version.py +3 -3
  3. ams/cases/5bus/pjm5bus_demo.json +1324 -0
  4. ams/core/__init__.py +1 -0
  5. ams/core/common.py +30 -0
  6. ams/core/model.py +1 -1
  7. ams/core/symprocessor.py +1 -1
  8. ams/extension/eva.py +1 -1
  9. ams/interface.py +40 -24
  10. ams/io/matpower.py +192 -26
  11. ams/io/psse.py +278 -1
  12. ams/io/pypower.py +14 -0
  13. ams/main.py +2 -2
  14. ams/models/group.py +2 -70
  15. ams/models/static/pq.py +7 -3
  16. ams/opt/param.py +1 -2
  17. ams/report.py +3 -4
  18. ams/routines/__init__.py +2 -3
  19. ams/routines/acopf.py +5 -108
  20. ams/routines/dcopf.py +8 -0
  21. ams/routines/dcpf.py +1 -1
  22. ams/routines/ed.py +4 -2
  23. ams/routines/grbopt.py +150 -0
  24. ams/routines/pflow.py +2 -2
  25. ams/routines/pypower.py +631 -0
  26. ams/routines/routine.py +4 -10
  27. ams/routines/uc.py +2 -2
  28. ams/shared.py +30 -44
  29. ams/system.py +118 -2
  30. docs/source/api.rst +2 -0
  31. docs/source/getting_started/formats/matpower.rst +135 -0
  32. docs/source/getting_started/formats/pypower.rst +1 -2
  33. docs/source/getting_started/install.rst +9 -6
  34. docs/source/images/dcopf_time.png +0 -0
  35. docs/source/images/educ_pie.png +0 -0
  36. docs/source/release-notes.rst +29 -47
  37. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/METADATA +87 -47
  38. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/RECORD +58 -75
  39. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/WHEEL +1 -1
  40. tests/test_1st_system.py +1 -1
  41. tests/test_case.py +14 -14
  42. tests/test_export_csv.py +1 -1
  43. tests/test_interface.py +24 -2
  44. tests/test_io.py +125 -1
  45. tests/test_omodel.py +1 -1
  46. tests/test_report.py +6 -6
  47. tests/test_routine.py +2 -2
  48. tests/test_rtn_acopf.py +75 -0
  49. tests/test_rtn_dcopf.py +1 -1
  50. tests/test_rtn_dcopf2.py +1 -1
  51. tests/test_rtn_ed.py +9 -9
  52. tests/test_rtn_opf.py +142 -0
  53. tests/test_rtn_pflow.py +0 -72
  54. tests/test_rtn_pypower.py +315 -0
  55. tests/test_rtn_rted.py +8 -8
  56. tests/test_rtn_uc.py +18 -18
  57. ams/pypower/__init__.py +0 -8
  58. ams/pypower/_compat.py +0 -9
  59. ams/pypower/core/__init__.py +0 -8
  60. ams/pypower/core/pips.py +0 -894
  61. ams/pypower/core/ppoption.py +0 -244
  62. ams/pypower/core/ppver.py +0 -18
  63. ams/pypower/core/solver.py +0 -2451
  64. ams/pypower/eps.py +0 -6
  65. ams/pypower/idx.py +0 -174
  66. ams/pypower/io.py +0 -604
  67. ams/pypower/make/__init__.py +0 -11
  68. ams/pypower/make/matrices.py +0 -665
  69. ams/pypower/make/pdv.py +0 -506
  70. ams/pypower/routines/__init__.py +0 -7
  71. ams/pypower/routines/cpf.py +0 -513
  72. ams/pypower/routines/cpf_callbacks.py +0 -114
  73. ams/pypower/routines/opf.py +0 -1803
  74. ams/pypower/routines/opffcns.py +0 -1946
  75. ams/pypower/routines/pflow.py +0 -852
  76. ams/pypower/toggle.py +0 -1098
  77. ams/pypower/utils.py +0 -293
  78. ams/routines/cpf.py +0 -65
  79. ams/routines/dcpf0.py +0 -196
  80. ams/routines/pflow0.py +0 -113
  81. tests/test_rtn_dcpf.py +0 -77
  82. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/entry_points.txt +0 -0
  83. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/top_level.txt +0 -0
ams/io/psse.py CHANGED
@@ -3,4 +3,281 @@ PSS/E .raw reader for AMS.
3
3
  This module is the existing module in ``andes.io.psse``.
4
4
  """
5
5
 
6
- from andes.io.psse import (read, testlines) # NOQA
6
+ from andes.io.psse import testlines # NOQA
7
+ from andes.io.psse import read as ad_read
8
+ from andes.io.xlsx import confirm_overwrite
9
+ from andes.shared import rad2deg, pd
10
+
11
+ from ams import __version__ as version
12
+ from ams.shared import copyright_msg, nowarranty_msg, report_time
13
+
14
+
15
+ def read(system, file):
16
+ """
17
+ Read PSS/E RAW file v32/v33 formats.
18
+
19
+ Revised from ``andes.io.psse.read`` to complete model ``Zone`` when necessary.
20
+ """
21
+ ret = ad_read(system, file)
22
+ # Extract zone data
23
+ zone = system.Bus.zone.v
24
+ zone_map = {}
25
+
26
+ # Check if there are zones to process
27
+ # NOTE: since `Zone` and `Area` below to group `Collection`, we add
28
+ # numerical Zone idx after the last Area idx.
29
+ if zone:
30
+ n_zone = system.Area.n
31
+ for z in set(zone):
32
+ # Add new zone and update the mapping
33
+ z_new = system.add(
34
+ 'Zone',
35
+ param_dict=dict(idx=n_zone + 1, name=f'{n_zone + 1}')
36
+ )
37
+ zone_map[z] = z_new
38
+ n_zone += 1
39
+
40
+ # Update the zone values in the system
41
+ system.Bus.zone.v = [zone_map[z] for z in zone]
42
+ return ret
43
+
44
+
45
+ def write_raw(system, outfile: str, overwrite: bool = None):
46
+ """
47
+ Convert AMS system to PSS/E RAW file.
48
+
49
+ Parameters
50
+ ----------
51
+ system : System
52
+ The AMS system to be converted.
53
+ outfile : str
54
+ The output file path.
55
+ overwrite : bool, optional
56
+ If True, overwrite the file if it exists. If False, do not overwrite.
57
+ """
58
+ if not confirm_overwrite(outfile, overwrite=overwrite):
59
+ return False
60
+
61
+ mva = system.config.mva
62
+ freq = system.config.freq
63
+ name = system.files.name
64
+
65
+ with open(outfile, 'w') as f:
66
+ # PSS/E version and date
67
+ f.write(f"0, {mva:.2f}, 33, 0, 1, {freq:.2f} ")
68
+
69
+ f.write(f"/ PSS/E 33 RAW, {report_time}\n")
70
+ f.write(f"Created by AMS {version}\n")
71
+ f.write(f"{copyright_msg}\n")
72
+ f.write(f"{nowarranty_msg}\n")
73
+ f.write(f"{name.upper()} \n")
74
+
75
+ # --- Bus ---
76
+ bus = system.Bus.cache.df_in
77
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
78
+ # ID, Name, BaseKV, Type, Area, Zone, Owner, Vm, Va, Vmax, Vmin
79
+ # Define column widths for alignment
80
+ column_widths = [6, 8, 8, 2, 3, 3, 3, 8, 8, 8, 8]
81
+ bus_owner = {}
82
+ for i, o in enumerate(set(bus['owner'])):
83
+ bus_owner[o] = i + 1
84
+ for row in bus.itertuples():
85
+ # Prepare the data for each column
86
+ data = [
87
+ int(row.idx), row.name, row.Vn, int(row.type),
88
+ int(system.Collection.idx2uid(row.area) + 1),
89
+ int(system.Collection.idx2uid(row.zone) + 1),
90
+ int(bus_owner[row.owner]),
91
+ float(row.v0), float(row.a0 * rad2deg),
92
+ float(row.vmax), float(row.vmin)]
93
+ # Format each column with ',' as the delimiter
94
+ formatted_row = ",".join(
95
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
96
+ for value, width in zip(data, column_widths)
97
+ ) + "\n"
98
+ # Write the formatted row to the file
99
+ f.write(formatted_row)
100
+
101
+ # --- Load ---
102
+ f.write("0 / END OF BUS DATA, BEGIN LOAD DATA\n")
103
+ load = system.PQ.cache.df_in
104
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
105
+ # Bus, Id, Status, Area, Zone, PL(MW), QL (MW), IP, IQ, YP, YQ, Owner
106
+ # NOTE: load are converted to constant load, IP, IQ, YP, YQ are ignored by setting to 0
107
+ column_widths = [6, 8, 2, 3, 3, 10, 10, 2, 2, 10, 10, 3]
108
+ for row in load.itertuples():
109
+ # Prepare the data for each column
110
+ data = [
111
+ int(row.bus), # Bus number
112
+ int(system.PQ.idx2uid(row.idx) + 1), # Load ID (unique index + 1)
113
+ int(row.u), # Status
114
+ int(system.Collection.idx2uid(system.PQ.get('area', row.idx, 'v')) + 1), # Area number
115
+ int(system.Collection.idx2uid(system.PQ.get('zone', row.idx, 'v')) + 1), # Zone number
116
+ float(row.p0 * mva), # PL (MW)
117
+ float(row.q0 * mva), # QL (MVar)
118
+ 0.0, # IP (ignored, set to 0)
119
+ 0.0, # IQ (ignored, set to 0)
120
+ 0.0, # YP (ignored, set to 0)
121
+ 0.0, # YQ (ignored, set to 0)
122
+ int(bus_owner[row.owner]) # Owner
123
+ ]
124
+ # Format each column with ',' as the delimiter
125
+ formatted_row = ",".join(
126
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
127
+ for value, width in zip(data, column_widths)
128
+ ) + "\n"
129
+ # Write the formatted row to the file
130
+ f.write(formatted_row)
131
+
132
+ # --- Fixed Shunt ---
133
+ f.write("0 / END OF LOAD DATA, BEGIN FIXED SHUNT DATA\n")
134
+ shunt = system.Shunt.cache.df_in
135
+ # 0, 1, 2, 3, 4
136
+ # Bus, Id, Status, G (MW), B (Mvar)
137
+ # NOTE: ANDES parse v33 swshunt into fixed shunt
138
+ column_widths = [6, 8, 2, 10, 10]
139
+ for row in shunt.itertuples():
140
+ # Prepare the data for each column
141
+ data = [
142
+ int(row.bus), # Bus number
143
+ int(system.Shunt.idx2uid(row.idx) + 1), # Shunt ID (unique index + 1)
144
+ int(row.u), # Status
145
+ float(row.g * mva), # Conductance (MW at system base)
146
+ float(row.b * mva) # Susceptance (Mvar at system base)
147
+ ]
148
+
149
+ # Format each column with ',' as the delimiter
150
+ formatted_row = ",".join(
151
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
152
+ for value, width in zip(data, column_widths)
153
+ ) + "\n"
154
+ # Write the formatted row to the file
155
+ f.write(formatted_row)
156
+
157
+ # --- Generator ---
158
+ f.write("0 / END OF FIXED SHUNT DATA, BEGIN GENERATOR DATA\n")
159
+ pv = system.PV.cache.df_in
160
+ slack = system.Slack.cache.df_in
161
+
162
+ gen = pd.concat([pv, slack.drop(columns=['a0'])], axis=0)
163
+ gen["subidx"] = gen.groupby('bus').cumcount() + 1
164
+
165
+ column_widths = [6, 8, 2, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8]
166
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
167
+ # I, ID, PG, QG, QT, QB, VS, IREG, MBASE, ZR, ZX, RT, XT, GTAP, STAT,
168
+ # 15, 16, 17, 18, 19, ..., 26, 27
169
+ # RMPCT, PT, PB, O1, F1, ..., O4, F4, WMOD, WPF
170
+ # The columns above for v33 is different from the manual of v34.5, which includes two new columns:
171
+ # `NREG`` at 8 and `BSLOD` before `O1`
172
+ for row in gen.itertuples():
173
+ # Prepare the data for each column
174
+ data = [
175
+ int(row.bus), # I: Bus number
176
+ int(row.subidx), # ID: Generator ID (subindex)
177
+ float(row.p0 * mva), # PG: Generated MW
178
+ float(row.q0 * mva), # QG: Generated MVar
179
+ float(row.qmax * mva), # QT: Max Q (MVar)
180
+ float(row.qmin * mva), # QB: Min Q (MVar)
181
+ float(row.v0), # VS: Setpoint voltage (p.u.)
182
+ int(0), # IREG: Regulated bus (not tracked)
183
+ float(row.Sn if hasattr(row, 'Sn') else mva), # MBASE: Machine base MVA
184
+ float(row.ra), # ZR: Armature resistance (p.u.)
185
+ float(row.xs), # ZX: Synchronous reactance (p.u.)
186
+ float(0), # RT: Step-up transformer resistance (not tracked)
187
+ float(0), # XT: Step-up transformer reactance (not tracked)
188
+ int(0), # GTAP: Step-up transformer off-nominal turns ratio (not tracked)
189
+ int(row.u) # STAT: Status
190
+ ]
191
+ # Format each column with ',' as the delimiter
192
+ formatted_row = ",".join(
193
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"'{value:>{width - 2}}'"
194
+ for value, width in zip(data, column_widths)
195
+ ) + "\n"
196
+ # Write the formatted row to the file
197
+ f.write(formatted_row)
198
+
199
+ # --- Line ---
200
+ f.write("0 / END OF GENERATOR DATA, BEGIN BRANCH DATA\n")
201
+ line = system.Line.cache.df_in
202
+ branch = line[line['trans'] == 0].reset_index(drop=True)
203
+ transf = line[line['trans'] == 1].reset_index(drop=True)
204
+ # 1) branch
205
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
206
+ # I, J, CKT, R, X, B, RATEA, RATEB, RATEC, GI, BI, GJ, BJ, ST, LEN, O1, F1, ..., O4, F4
207
+ column_widths = [6, 6, 4, 10, 10, 10, 8, 8, 8, 8, 8, 8, 8, 3, 8]
208
+ for row in branch.itertuples():
209
+ data = [
210
+ int(row.bus1), # I: From bus number
211
+ int(row.bus2), # J: To bus number
212
+ "'1'", # CKT: Circuit ID (default '1')
213
+ float(row.r), # R: Resistance (p.u.)
214
+ float(row.x), # X: Reactance (p.u.)
215
+ float(row.b), # B: Total line charging (p.u.)
216
+ float(row.rate_a), # RATEA: Rating A (MVA)
217
+ float(row.rate_b), # RATEB: Rating B (MVA)
218
+ float(row.rate_c), # RATEC: Rating C (MVA)
219
+ 0.0, # GI: From bus conductance (not tracked)
220
+ 0.0, # BI: From bus susceptance (not tracked)
221
+ 0.0, # GJ: To bus conductance (not tracked)
222
+ 0.0, # BJ: To bus susceptance (not tracked)
223
+ int(row.u), # ST: Status
224
+ 0.0 # LEN: Line length (not tracked)
225
+ # O1, F1, ..., O4, F4 omitted for v33
226
+ ]
227
+ formatted_row = ",".join(
228
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"{value:>{width}}"
229
+ for value, width in zip(data, column_widths)
230
+ ) + "\n"
231
+ f.write(formatted_row)
232
+ # 2) transformer
233
+ f.write("0 / END OF BRANCH DATA, BEGIN TRANSFORMER DATA\n")
234
+ for row in transf.itertuples():
235
+ # Map AMS Line (trans) fields to PSSE RAW transformer columns
236
+ # Only 2-winding transformers are supported here
237
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
238
+ # I, J, CKT, R, X, B, RATEA, RATEB, RATEC, GI, BI, GJ, BJ, ST, LEN
239
+ data = [
240
+ int(row.bus1), # I: From bus number
241
+ int(row.bus2), # J: To bus number
242
+ "'1'", # CKT: Circuit ID (default '1')
243
+ float(row.r), # R: Resistance (p.u.)
244
+ float(row.x), # X: Reactance (p.u.)
245
+ float(row.b), # B: Total line charging (p.u.)
246
+ float(row.rate_a), # RATEA: Rating A (MVA)
247
+ float(row.rate_b), # RATEB: Rating B (MVA)
248
+ float(row.rate_c), # RATEC: Rating C (MVA)
249
+ 0.0, # GI: From bus conductance (not tracked)
250
+ 0.0, # BI: From bus susceptance (not tracked)
251
+ 0.0, # GJ: To bus conductance (not tracked)
252
+ 0.0, # BJ: To bus susceptance (not tracked)
253
+ int(row.u), # ST: Status
254
+ 0.0 # LEN: Line length (not tracked)
255
+ # O1, F1, ..., O4, F4 omitted for v33
256
+ ]
257
+ formatted_row = ",".join(
258
+ f"{value:>{width}}" if isinstance(value, (int, float)) else f"{value:>{width}}"
259
+ for value, width in zip(data, column_widths)
260
+ ) + "\n"
261
+ f.write(formatted_row)
262
+
263
+ # --- Area ---
264
+ f.write("0 / END OF TRANSFORMER DATA, BEGIN AREA DATA\n")
265
+ area = system.Area.cache.df_in
266
+ for row in area.itertuples():
267
+ # PSSE expects: ID, ISW, PDES, PTOL, NAME
268
+ # Here, ISW, PDES, PTOL are set to 0 by default
269
+ f.write(f"{int(system.Area.idx2uid(row.idx) + 1):6d}, 0, 0.0, 0.0, '{row.name}'\n")
270
+
271
+ # --- Zone ---
272
+ f.write("0 / END OF AREA DATA, BEGIN ZONE DATA\n")
273
+ zone = system.Zone.cache.df_in
274
+ # 0, 1
275
+ # ID, Name
276
+ for row in zone.itertuples():
277
+ f.write(f"{int(system.Zone.idx2uid(row.idx) + 1):6d}, '{row.name}'\n")
278
+ f.write("0 / END OF ZONE DATA\n")
279
+
280
+ # End of file
281
+ f.write("Q\n")
282
+
283
+ return True
ams/io/pypower.py CHANGED
@@ -101,3 +101,17 @@ def system2ppc(system) -> dict:
101
101
  mpc['branch'][:, 0] = np.array([bus_map[busi0] for busi0 in mpc['branch'][:, 0].astype(int)])
102
102
  mpc['branch'][:, 1] = np.array([bus_map[busi0] for busi0 in mpc['branch'][:, 1].astype(int)])
103
103
  return mpc
104
+
105
+
106
+ def ppc2py(ppc: dict, outfile: str) -> str:
107
+ """
108
+ Placeholder.
109
+ """
110
+ return NotImplemented
111
+
112
+
113
+ def write(system, outfile: str) -> bool:
114
+ """
115
+ Placeholder.
116
+ """
117
+ return NotImplemented
ams/main.py CHANGED
@@ -105,8 +105,8 @@ def config_logger(stream_level=logging.INFO, *,
105
105
 
106
106
  else:
107
107
  # update the handlers
108
- set_logger_level(logger, logging.StreamHandler, stream_level)
109
- set_logger_level(logger, logging.FileHandler, file_level)
108
+ set_logger_level(lg, logging.StreamHandler, stream_level)
109
+ set_logger_level(lg, logging.FileHandler, file_level)
110
110
 
111
111
  if not is_interactive():
112
112
  coloredlogs.install(logger=lg, level=stream_level, fmt=sh_formatter_str)
ams/models/group.py CHANGED
@@ -179,75 +179,6 @@ class StaticGen(GroupBase):
179
179
 
180
180
  The generator types and fuel types are referenced from MATPOWER.
181
181
 
182
- Generator Types
183
- ---------------
184
- The following codes represent the types of generators:
185
- - BA : Energy Storage, Battery
186
- - CE : Energy Storage, Compressed Air
187
- - CP : Energy Storage, Concentrated Solar Power
188
- - FW : Energy Storage, Flywheel
189
- - PS : Hydraulic Turbine, Reversible (pumped storage)
190
- - ES : Energy Storage, Other
191
- - ST : Steam Turbine (includes nuclear, geothermal, and solar steam)
192
- - GT : Combustion (Gas) Turbine
193
- - IC : Internal Combustion Engine (diesel, piston, reciprocating)
194
- - CA : Combined Cycle Steam Part
195
- - CT : Combined Cycle Combustion Turbine Part
196
- - CS : Combined Cycle Single Shaft
197
- - CC : Combined Cycle Total Unit
198
- - HA : Hydrokinetic, Axial Flow Turbine
199
- - HB : Hydrokinetic, Wave Buoy
200
- - HK : Hydrokinetic, Other
201
- - HY : Hydroelectric Turbine
202
- - BT : Turbines Used in a Binary Cycle
203
- - PV : Photovoltaic
204
- - WT : Wind Turbine, Onshore
205
- - WS : Wind Turbine, Offshore
206
- - FC : Fuel Cell
207
- - OT : Other
208
- - UN : Unknown
209
- - JE : Jet Engine
210
- - NB : ST - Boiling Water Nuclear Reactor
211
- - NG : ST - Graphite Nuclear Reactor
212
- - NH : ST - High Temperature Gas Nuclear Reactor
213
- - NP : ST - Pressurized Water Nuclear Reactor
214
- - IT : Internal Combustion Turbo Charged
215
- - SC : Synchronous Condenser
216
- - DC : DC ties
217
- - MP : Motor/Pump
218
- - W1 : Wind Turbine, Type 1
219
- - W2 : Wind Turbine, Type 2
220
- - W3 : Wind Turbine, Type 3
221
- - W4 : Wind Turbine, Type 4
222
- - SV : Static Var Compensator
223
- - DL : Dispatchable Load
224
-
225
- Fuel Types
226
- ----------
227
- The following codes represent the fuel types:
228
- - biomass : Biomass
229
- - coal : Coal
230
- - dfo : Distillate Fuel Oil
231
- - geothermal : Geothermal
232
- - hydro : Hydro
233
- - hydrops : Hydro Pumped Storage
234
- - jetfuel : Jet Fuel
235
- - lng : Liquefied Natural Gas
236
- - ng : Natural Gas
237
- - nuclear : Nuclear
238
- - oil : Unspecified Oil
239
- - refuse : Refuse, Municipal Solid Waste
240
- - rfo : Residual Fuel Oil
241
- - solar : Solar
242
- - syncgen : Synchronous Condenser
243
- - wasteheat : Waste Heat
244
- - wind : Wind
245
- - wood : Wood or Wood Waste
246
- - other : Other
247
- - unknown : Unknown
248
- - dl : Dispatchable Load
249
- - ess : Energy Storage System
250
-
251
182
  Notes
252
183
  -----
253
184
  For co-simulation with ANDES, refer to the `ANDES StaticGen Documentation
@@ -289,7 +220,8 @@ class StaticShunt(GroupBase):
289
220
  """
290
221
  Static shunt compensator group.
291
222
  """
292
- pass
223
+ def __init__(self):
224
+ super().__init__()
293
225
 
294
226
 
295
227
  class Information(GroupBase):
ams/models/static/pq.py CHANGED
@@ -53,9 +53,13 @@ class PQ(PQData, Model):
53
53
  q2z=r"\gamma_{q2z}",
54
54
  )
55
55
 
56
- self.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False,
57
- info='Retrieved area idx', vtype=str, default=None,
58
- )
59
56
  self.ctrl = NumParam(default=1,
60
57
  info="load controllability",
61
58
  tex_name=r'c_{trl}',)
59
+
60
+ self.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False,
61
+ info='Retrieved area idx', vtype=str, default=None,
62
+ )
63
+ self.zone = ExtParam(model='Bus', src='zone', indexer=self.bus, export=False,
64
+ info='Retrieved zone idx', vtype=str, default=None,
65
+ )
ams/opt/param.py CHANGED
@@ -9,10 +9,9 @@ import re
9
9
 
10
10
  import numpy as np
11
11
 
12
- from andes.core.common import Config
13
-
14
12
  import cvxpy as cp
15
13
 
14
+ from ams.core import Config
16
15
  from ams.shared import sps
17
16
 
18
17
  from ams.opt import OptzBase, ensure_symbols, ensure_mats_and_parsed
ams/report.py CHANGED
@@ -3,7 +3,6 @@ Module for report generation.
3
3
  """
4
4
  import logging
5
5
  from collections import OrderedDict
6
- from time import strftime
7
6
  from typing import List, Dict, Optional
8
7
 
9
8
  from andes.io.txt import dump_data
@@ -11,7 +10,7 @@ from andes.shared import np
11
10
  from andes.utils.misc import elapsed
12
11
 
13
12
  from ams import __version__ as version
14
- from ams.shared import copyright_msg
13
+ from ams.shared import copyright_msg, nowarranty_msg, report_time
15
14
 
16
15
  logger = logging.getLogger(__name__)
17
16
 
@@ -23,9 +22,9 @@ def report_info(system) -> list:
23
22
  info = list()
24
23
  info.append('AMS' + ' ' + version + '\n')
25
24
  info.append(f'{copyright_msg}\n\n')
26
- info.append('AMS comes with ABSOLUTELY NO WARRANTY\n')
25
+ info.append(f"{nowarranty_msg}\n")
27
26
  info.append('Case file: ' + str(system.files.case) + '\n')
28
- info.append('Report time: ' + strftime("%m/%d/%Y %I:%M:%S %p") + '\n\n')
27
+ info.append(f'Report time: {report_time}\n\n')
29
28
  return info
30
29
 
31
30
 
ams/routines/__init__.py CHANGED
@@ -8,7 +8,6 @@ from andes.utils.func import list_flatten
8
8
  all_routines = OrderedDict([
9
9
  ('dcpf', ['DCPF']),
10
10
  ('pflow', ['PFlow']),
11
- ('cpf', ['CPF']),
12
11
  ('acopf', ['ACOPF']),
13
12
  ('dcopf', ['DCOPF']),
14
13
  ('dcopf2', ['DCOPF2']),
@@ -16,8 +15,8 @@ all_routines = OrderedDict([
16
15
  ('rted', ['RTED', 'RTEDDG', 'RTEDES', 'RTEDVIS']),
17
16
  ('uc', ['UC', 'UCDG', 'UCES']),
18
17
  ('dopf', ['DOPF', 'DOPFVIS']),
19
- ('pflow0', ['PFlow0']),
20
- ('dcpf0', ['DCPF0']),
18
+ ('pypower', ['DCPF1', 'PFlow1', 'DCOPF1', 'ACOPF1']),
19
+ ('grbopt', ['OPF']),
21
20
  ])
22
21
 
23
22
  class_names = list_flatten(list(all_routines.values()))
ams/routines/acopf.py CHANGED
@@ -1,117 +1,14 @@
1
1
  """
2
- ACOPF routines using PYPOWER.
2
+ ACOPF routines.
3
3
  """
4
- import logging
5
- from collections import OrderedDict
6
4
 
7
- from ams.pypower import runopf
8
- from ams.pypower.core import ppoption
5
+ from ams.routines.pypower import ACOPF1
9
6
 
10
- from ams.io.pypower import system2ppc
11
- from ams.core.param import RParam
12
7
 
13
- from ams.routines.dcpf0 import DCPF0
14
- from ams.opt import Var, Constraint, Objective
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- class ACOPF(DCPF0):
8
+ class ACOPF(ACOPF1):
20
9
  """
21
- Standard AC optimal power flow.
22
-
23
- Notes
24
- -----
25
- 1. ACOPF is solved with PYPOWER ``runopf`` function.
26
- 2. ACOPF formulation in AMS style is NOT DONE YET,
27
- but this does not affect the results
28
- because the data are passed to PYPOWER for solving.
10
+ Alias for ACOPF1.
29
11
  """
30
12
 
31
13
  def __init__(self, system, config):
32
- DCPF0.__init__(self, system, config)
33
- self.info = 'AC Optimal Power Flow'
34
- self.type = 'ACED'
35
-
36
- self.map1 = OrderedDict() # ACOPF does not receive
37
- self.map2.update({
38
- 'vBus': ('Bus', 'v0'),
39
- 'ug': ('StaticGen', 'u'),
40
- 'pg': ('StaticGen', 'p0'),
41
- })
42
-
43
- # --- params ---
44
- self.c2 = RParam(info='Gen cost coefficient 2',
45
- name='c2', tex_name=r'c_{2}',
46
- unit=r'$/(p.u.^2)', model='GCost',
47
- indexer='gen', imodel='StaticGen',
48
- nonneg=True)
49
- self.c1 = RParam(info='Gen cost coefficient 1',
50
- name='c1', tex_name=r'c_{1}',
51
- unit=r'$/(p.u.)', model='GCost',
52
- indexer='gen', imodel='StaticGen',)
53
- self.c0 = RParam(info='Gen cost coefficient 0',
54
- name='c0', tex_name=r'c_{0}',
55
- unit=r'$', model='GCost',
56
- indexer='gen', imodel='StaticGen',
57
- no_parse=True)
58
- self.qd = RParam(info='reactive demand',
59
- name='qd', tex_name=r'q_{d}',
60
- model='StaticLoad', src='q0',
61
- unit='p.u.',)
62
- # --- bus ---
63
- self.vBus = Var(info='Bus voltage magnitude',
64
- unit='p.u.',
65
- name='vBus', tex_name=r'v_{Bus}',
66
- src='v', model='Bus',)
67
- # --- gen ---
68
- self.qg = Var(info='Gen reactive power',
69
- unit='p.u.',
70
- name='qg', tex_name=r'q_{g}',
71
- model='StaticGen', src='q',)
72
- # --- constraints ---
73
- self.pb = Constraint(name='pb',
74
- info='power balance',
75
- e_str='sum(pd) - sum(pg)',
76
- is_eq=True,)
77
- # TODO: ACOPF formulation
78
- # --- objective ---
79
- self.obj = Objective(name='obj',
80
- info='total cost',
81
- e_str='sum(c2 * pg**2 + c1 * pg + c0)',
82
- sense='min',)
83
-
84
- def solve(self, method=None, **kwargs):
85
- """
86
- Solve ACOPF using PYPOWER with PIPS.
87
- """
88
- ppc = system2ppc(self.system)
89
- ppopt = ppoption()
90
- res, sstats = runopf(casedata=ppc, ppopt=ppopt, **kwargs)
91
- return res, sstats
92
-
93
- def run(self, **kwargs):
94
- """
95
- Run ACOPF using PYPOWER with PIPS.
96
- *args and **kwargs go to `self.solve()`, which are not used yet.
97
-
98
- Examples
99
- --------
100
- >>> ss = ams.load(ams.get_case('matpower/case14.m'))
101
- >>> ss.ACOPF.run()
102
-
103
- Parameters
104
- ----------
105
- force_init : bool
106
- Force initialization.
107
- no_code : bool
108
- Disable showing code.
109
- method : str
110
- Placeholder for future use.
111
-
112
- Returns
113
- -------
114
- exit_code : int
115
- Exit code of the routine.
116
- """
117
- super().run(**kwargs)
14
+ super().__init__(system, config)
ams/routines/dcopf.py CHANGED
@@ -141,6 +141,14 @@ class DCOPF(DCPFBase):
141
141
  name='pi', unit='$/p.u.',
142
142
  model='Bus', src=None,
143
143
  e_str='pb.dual_variables[0]')
144
+ self.mu1 = ExpressionCalc(info='Lagrange multipliers, dual of <plflb>',
145
+ name='mu1', unit='$/p.u.',
146
+ model='Line', src=None,
147
+ e_str='plflb.dual_variables[0]')
148
+ self.mu2 = ExpressionCalc(info='Lagrange multipliers, dual of <plfub>',
149
+ name='mu2', unit='$/p.u.',
150
+ model='Line', src=None,
151
+ e_str='plfub.dual_variables[0]')
144
152
 
145
153
  # --- objective ---
146
154
  obj = 'sum(mul(c2, pg**2))'
ams/routines/dcpf.py CHANGED
@@ -125,7 +125,7 @@ class DCPFBase(RoutineBase):
125
125
  """
126
126
  return self.om.prob.solve(**kwargs)
127
127
 
128
- def unpack(self, **kwargs):
128
+ def unpack(self, res, **kwargs):
129
129
  """
130
130
  Unpack the results from CVXPY model.
131
131
  """
ams/routines/ed.py CHANGED
@@ -104,6 +104,8 @@ class MPBase:
104
104
  self.vBus.horizon = self.timeslot
105
105
  self.vBus.info = '2D Bus voltage'
106
106
  self.pi.horizon = self.timeslot
107
+ self.mu1.horizon = self.timeslot
108
+ self.mu2.horizon = self.timeslot
107
109
 
108
110
 
109
111
  class ED(RTED, MPBase, SRBase):
@@ -214,14 +216,14 @@ class ED(RTED, MPBase, SRBase):
214
216
  cost += '+ sum(mul(ugt, mul(c0, tlv)))'
215
217
  self.obj.e_str = cost
216
218
 
217
- def dc2ac(self, **kwargs):
219
+ def dc2ac(self, kloss=1.0, **kwargs):
218
220
  """
219
221
  AC conversion ``dc2ac`` is not implemented yet for
220
222
  multi-period scheduling.
221
223
  """
222
224
  return NotImplementedError
223
225
 
224
- def unpack(self, **kwargs):
226
+ def unpack(self, res, **kwargs):
225
227
  """
226
228
  Multi-period scheduling will not unpack results from
227
229
  solver into devices.