ltbams 1.0.9__py3-none-any.whl → 1.0.11__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/documenter.py +130 -62
  7. ams/core/model.py +1 -1
  8. ams/core/symprocessor.py +1 -1
  9. ams/extension/eva.py +1 -1
  10. ams/interface.py +42 -26
  11. ams/io/matpower.py +31 -17
  12. ams/io/psse.py +278 -1
  13. ams/main.py +2 -2
  14. ams/models/group.py +2 -1
  15. ams/models/static/pq.py +7 -3
  16. ams/opt/param.py +1 -2
  17. ams/routines/__init__.py +2 -3
  18. ams/routines/acopf.py +5 -108
  19. ams/routines/dcopf.py +8 -0
  20. ams/routines/dcpf.py +1 -1
  21. ams/routines/ed.py +4 -2
  22. ams/routines/grbopt.py +155 -0
  23. ams/routines/pflow.py +2 -2
  24. ams/routines/pypower.py +622 -0
  25. ams/routines/routine.py +4 -10
  26. ams/routines/uc.py +2 -2
  27. ams/shared.py +26 -43
  28. ams/system.py +120 -4
  29. ams/utils/paths.py +3 -3
  30. docs/source/api.rst +2 -0
  31. docs/source/examples/index.rst +2 -1
  32. docs/source/genroutineref.py +1 -1
  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 +37 -57
  37. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/METADATA +85 -48
  38. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/RECORD +58 -75
  39. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/WHEEL +1 -1
  40. tests/test_1st_system.py +2 -2
  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 +50 -0
  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.9.dist-info → ltbams-1.0.11.dist-info}/entry_points.txt +0 -0
  83. {ltbams-1.0.9.dist-info → ltbams-1.0.11.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/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
@@ -220,7 +220,8 @@ class StaticShunt(GroupBase):
220
220
  """
221
221
  Static shunt compensator group.
222
222
  """
223
- pass
223
+ def __init__(self):
224
+ super().__init__()
224
225
 
225
226
 
226
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/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.