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.
- 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 +203 -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 +234 -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.2a1.dist-info/METADATA +210 -0
- ltbams-1.0.2a1.dist-info/RECORD +188 -0
- {ltbams-0.9.9.dist-info → ltbams-1.0.2a1.dist-info}/WHEEL +1 -1
- ltbams-1.0.2a1.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.2a1.dist-info}/entry_points.txt +0 -0
ams/opt/omodel.py
ADDED
@@ -0,0 +1,432 @@
|
|
1
|
+
"""
|
2
|
+
Module for optimization OModel.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
|
6
|
+
from typing import Any
|
7
|
+
from collections import OrderedDict
|
8
|
+
|
9
|
+
from andes.utils.misc import elapsed
|
10
|
+
|
11
|
+
import cvxpy as cp
|
12
|
+
|
13
|
+
from ams.opt.optzbase import ensure_symbols, ensure_mats_and_parsed
|
14
|
+
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
class OModelBase:
|
20
|
+
"""
|
21
|
+
Template class for optimization models.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self, routine):
|
25
|
+
self.rtn = routine
|
26
|
+
self.prob = None
|
27
|
+
self.exprs = OrderedDict()
|
28
|
+
self.params = OrderedDict()
|
29
|
+
self.vars = OrderedDict()
|
30
|
+
self.constrs = OrderedDict()
|
31
|
+
self.obj = None
|
32
|
+
self.parsed = False
|
33
|
+
self.evaluated = False
|
34
|
+
self.finalized = False
|
35
|
+
|
36
|
+
@property
|
37
|
+
def initialized(self):
|
38
|
+
"""
|
39
|
+
Return the initialization status.
|
40
|
+
"""
|
41
|
+
return self.parsed and self.evaluated and self.finalized
|
42
|
+
|
43
|
+
def parse(self, force=False):
|
44
|
+
self.parsed = True
|
45
|
+
return self.parsed
|
46
|
+
|
47
|
+
def _evaluate_params(self):
|
48
|
+
return True
|
49
|
+
|
50
|
+
def _evaluate_vars(self):
|
51
|
+
return True
|
52
|
+
|
53
|
+
def _evaluate_constrs(self):
|
54
|
+
return True
|
55
|
+
|
56
|
+
def _evaluate_obj(self):
|
57
|
+
return True
|
58
|
+
|
59
|
+
def _evaluate_exprs(self):
|
60
|
+
return True
|
61
|
+
|
62
|
+
def _evaluate_exprcs(self):
|
63
|
+
return True
|
64
|
+
|
65
|
+
def evaluate(self, force=False):
|
66
|
+
self._evaluate_params()
|
67
|
+
self._evaluate_vars()
|
68
|
+
self._evaluate_exprs()
|
69
|
+
self._evaluate_constrs()
|
70
|
+
self._evaluate_obj()
|
71
|
+
self._evaluate_exprcs()
|
72
|
+
self.evaluated = True
|
73
|
+
return self.evaluated
|
74
|
+
|
75
|
+
def finalize(self, force=False):
|
76
|
+
self.finalized = True
|
77
|
+
return True
|
78
|
+
|
79
|
+
def init(self, force=False):
|
80
|
+
self.parse(force)
|
81
|
+
self.evaluate(force)
|
82
|
+
self.finalize(force)
|
83
|
+
return self.initialized
|
84
|
+
|
85
|
+
@property
|
86
|
+
def class_name(self):
|
87
|
+
return self.__class__.__name__
|
88
|
+
|
89
|
+
def _register_attribute(self, key, value):
|
90
|
+
"""
|
91
|
+
Register a pair of attributes to OModel instance.
|
92
|
+
|
93
|
+
Called within ``__setattr__``, this is where the magic happens.
|
94
|
+
Subclass attributes are automatically registered based on the variable type.
|
95
|
+
"""
|
96
|
+
if isinstance(value, cp.Variable):
|
97
|
+
self.vars[key] = value
|
98
|
+
elif isinstance(value, cp.Constraint):
|
99
|
+
self.constrs[key] = value
|
100
|
+
elif isinstance(value, cp.Parameter):
|
101
|
+
self.params[key] = value
|
102
|
+
elif isinstance(value, cp.Expression):
|
103
|
+
self.exprs[key] = value
|
104
|
+
|
105
|
+
def __setattr__(self, name: str, value: Any):
|
106
|
+
super().__setattr__(name, value)
|
107
|
+
self._register_attribute(name, value)
|
108
|
+
|
109
|
+
def update(self, params):
|
110
|
+
return True
|
111
|
+
|
112
|
+
def __repr__(self) -> str:
|
113
|
+
return f'{self.rtn.class_name}.{self.__class__.__name__} at {hex(id(self))}'
|
114
|
+
|
115
|
+
|
116
|
+
class OModel(OModelBase):
|
117
|
+
"""
|
118
|
+
Base class for optimization models.
|
119
|
+
|
120
|
+
Parameters
|
121
|
+
----------
|
122
|
+
routine: Routine
|
123
|
+
Routine that to be modeled.
|
124
|
+
|
125
|
+
Attributes
|
126
|
+
----------
|
127
|
+
prob: cvxpy.Problem
|
128
|
+
Optimization model.
|
129
|
+
exprs: OrderedDict
|
130
|
+
Expressions registry.
|
131
|
+
params: OrderedDict
|
132
|
+
Parameters registry.
|
133
|
+
vars: OrderedDict
|
134
|
+
Decision variables registry.
|
135
|
+
constrs: OrderedDict
|
136
|
+
Constraints registry.
|
137
|
+
obj: Objective
|
138
|
+
Objective function.
|
139
|
+
initialized: bool
|
140
|
+
Flag indicating if the model is initialized.
|
141
|
+
parsed: bool
|
142
|
+
Flag indicating if the model is parsed.
|
143
|
+
evaluated: bool
|
144
|
+
Flag indicating if the model is evaluated.
|
145
|
+
finalized: bool
|
146
|
+
Flag indicating if the model is finalized.
|
147
|
+
"""
|
148
|
+
|
149
|
+
def __init__(self, routine):
|
150
|
+
OModelBase.__init__(self, routine)
|
151
|
+
|
152
|
+
@ensure_symbols
|
153
|
+
def parse(self, force=False):
|
154
|
+
"""
|
155
|
+
Parse the optimization model from the symbolic description.
|
156
|
+
|
157
|
+
This method should be called after the routine symbols are generated
|
158
|
+
`self.rtn.syms.generate_symbols()`. It parses the following components
|
159
|
+
of the optimization model: parameters, decision variables, constraints,
|
160
|
+
objective function, and expressions.
|
161
|
+
|
162
|
+
Parameters
|
163
|
+
----------
|
164
|
+
force : bool, optional
|
165
|
+
Flag indicating if to force the parsing.
|
166
|
+
|
167
|
+
Returns
|
168
|
+
-------
|
169
|
+
bool
|
170
|
+
Returns True if the parsing is successful, False otherwise.
|
171
|
+
"""
|
172
|
+
if self.parsed and not force:
|
173
|
+
logger.debug("Model is already parsed.")
|
174
|
+
return self.parsed
|
175
|
+
|
176
|
+
t, _ = elapsed()
|
177
|
+
logger.warning(f'Parsing OModel for <{self.rtn.class_name}>')
|
178
|
+
# --- add expressions ---
|
179
|
+
for key, val in self.rtn.exprs.items():
|
180
|
+
val.parse()
|
181
|
+
|
182
|
+
# --- add RParams and Services as parameters ---
|
183
|
+
for key, val in self.rtn.params.items():
|
184
|
+
if not val.no_parse:
|
185
|
+
val.parse()
|
186
|
+
|
187
|
+
# --- add decision variables ---
|
188
|
+
for key, val in self.rtn.vars.items():
|
189
|
+
val.parse()
|
190
|
+
|
191
|
+
# --- add constraints ---
|
192
|
+
for key, val in self.rtn.constrs.items():
|
193
|
+
val.parse()
|
194
|
+
|
195
|
+
# --- add ExpressionCalcs ---
|
196
|
+
for key, val in self.rtn.exprcs.items():
|
197
|
+
val.parse()
|
198
|
+
|
199
|
+
# --- parse objective functions ---
|
200
|
+
if self.rtn.obj is not None:
|
201
|
+
try:
|
202
|
+
self.rtn.obj.parse()
|
203
|
+
except Exception as e:
|
204
|
+
raise Exception(f"Failed to parse Objective <{self.rtn.obj.name}>.\n{e}")
|
205
|
+
elif self.rtn.class_name not in ['DCPF0']:
|
206
|
+
logger.warning(f"{self.rtn.class_name} has no objective function!")
|
207
|
+
self.parsed = False
|
208
|
+
return self.parsed
|
209
|
+
|
210
|
+
# --- parse expressions ---
|
211
|
+
for key, val in self.rtn.exprs.items():
|
212
|
+
try:
|
213
|
+
val.parse()
|
214
|
+
except Exception as e:
|
215
|
+
raise Exception(f"Failed to parse ExpressionCalc <{key}>.\n{e}")
|
216
|
+
_, s = elapsed(t)
|
217
|
+
logger.debug(f" -> Parsed in {s}")
|
218
|
+
|
219
|
+
self.parsed = True
|
220
|
+
return self.parsed
|
221
|
+
|
222
|
+
def _evaluate_params(self):
|
223
|
+
"""
|
224
|
+
Evaluate the parameters.
|
225
|
+
"""
|
226
|
+
for key, val in self.rtn.params.items():
|
227
|
+
try:
|
228
|
+
val.evaluate()
|
229
|
+
setattr(self, key, val.optz)
|
230
|
+
except Exception as e:
|
231
|
+
raise Exception(f"Failed to evaluate Param <{key}>.\n{e}")
|
232
|
+
|
233
|
+
def _evaluate_vars(self):
|
234
|
+
"""
|
235
|
+
Evaluate the decision variables.
|
236
|
+
"""
|
237
|
+
for key, val in self.rtn.vars.items():
|
238
|
+
try:
|
239
|
+
val.evaluate()
|
240
|
+
setattr(self, key, val.optz)
|
241
|
+
except Exception as e:
|
242
|
+
raise Exception(f"Failed to evaluate Var <{key}>.\n{e}")
|
243
|
+
|
244
|
+
def _evaluate_constrs(self):
|
245
|
+
"""
|
246
|
+
Evaluate the constraints.
|
247
|
+
"""
|
248
|
+
for key, val in self.rtn.constrs.items():
|
249
|
+
try:
|
250
|
+
val.evaluate()
|
251
|
+
setattr(self, key, val.optz)
|
252
|
+
except Exception as e:
|
253
|
+
raise Exception(f"Failed to evaluate Constr <{key}>.\n{e}")
|
254
|
+
|
255
|
+
def _evaluate_obj(self):
|
256
|
+
"""
|
257
|
+
Evaluate the objective function.
|
258
|
+
"""
|
259
|
+
# NOTE: since we already have the attribute `obj`,
|
260
|
+
# we can update it rather than setting it
|
261
|
+
if self.rtn.obj is not None:
|
262
|
+
self.rtn.obj.evaluate()
|
263
|
+
self.obj = self.rtn.obj.optz
|
264
|
+
|
265
|
+
def _evaluate_exprs(self):
|
266
|
+
"""
|
267
|
+
Evaluate the expressions.
|
268
|
+
"""
|
269
|
+
for key, val in self.rtn.exprs.items():
|
270
|
+
try:
|
271
|
+
val.evaluate()
|
272
|
+
setattr(self, key, val.optz)
|
273
|
+
except Exception as e:
|
274
|
+
raise Exception(f"Failed to evaluate Expression <{key}>.\n{e}")
|
275
|
+
|
276
|
+
def _evaluate_exprcs(self):
|
277
|
+
"""
|
278
|
+
Evaluate the expressions.
|
279
|
+
"""
|
280
|
+
for key, val in self.rtn.exprcs.items():
|
281
|
+
try:
|
282
|
+
val.evaluate()
|
283
|
+
except Exception as e:
|
284
|
+
raise Exception(f"Failed to evaluate ExpressionCalc <{key}>.\n{e}")
|
285
|
+
|
286
|
+
@ensure_mats_and_parsed
|
287
|
+
def evaluate(self, force=False):
|
288
|
+
"""
|
289
|
+
Evaluate the optimization model.
|
290
|
+
|
291
|
+
This method should be called after `self.parse()`. It evaluates the following
|
292
|
+
components of the optimization model: parameters, decision variables, constraints,
|
293
|
+
objective function, and expressions.
|
294
|
+
|
295
|
+
Parameters
|
296
|
+
----------
|
297
|
+
force : bool, optional
|
298
|
+
Flag indicating if to force the evaluation
|
299
|
+
|
300
|
+
Returns
|
301
|
+
-------
|
302
|
+
bool
|
303
|
+
Returns True if the evaluation is successful, False otherwise.
|
304
|
+
"""
|
305
|
+
if self.evaluated and not force:
|
306
|
+
logger.debug("Model is already evaluated.")
|
307
|
+
return self.evaluated
|
308
|
+
logger.warning(f"Evaluating OModel for <{self.rtn.class_name}>")
|
309
|
+
t, _ = elapsed()
|
310
|
+
|
311
|
+
# NOTE: should evaluate in sequence
|
312
|
+
self._evaluate_params()
|
313
|
+
self._evaluate_vars()
|
314
|
+
self._evaluate_exprs()
|
315
|
+
self._evaluate_constrs()
|
316
|
+
self._evaluate_obj()
|
317
|
+
self._evaluate_exprcs()
|
318
|
+
|
319
|
+
self.evaluated = True
|
320
|
+
_, s = elapsed(t)
|
321
|
+
logger.debug(f" -> Evaluated in {s}")
|
322
|
+
return self.evaluated
|
323
|
+
|
324
|
+
def finalize(self, force=False):
|
325
|
+
"""
|
326
|
+
Finalize the optimization model.
|
327
|
+
|
328
|
+
This method should be called after `self.evaluate()`. It assemble the optimization
|
329
|
+
problem from the evaluated components.
|
330
|
+
|
331
|
+
Returns
|
332
|
+
-------
|
333
|
+
bool
|
334
|
+
Returns True if the finalization is successful, False otherwise.
|
335
|
+
"""
|
336
|
+
# NOTE: for power flow type, we skip the finalization
|
337
|
+
if self.rtn.class_name in ['DCPF0']:
|
338
|
+
self.finalized = True
|
339
|
+
return self.finalized
|
340
|
+
if self.finalized and not force:
|
341
|
+
logger.debug("Model is already finalized.")
|
342
|
+
return self.finalized
|
343
|
+
logger.warning(f"Finalizing OModel for <{self.rtn.class_name}>")
|
344
|
+
t, _ = elapsed()
|
345
|
+
|
346
|
+
# Collect constraints that are not disabled
|
347
|
+
constrs_add = [val.optz for key, val in self.rtn.constrs.items(
|
348
|
+
) if not val.is_disabled and val is not None]
|
349
|
+
# Construct the problem using cvxpy.Problem
|
350
|
+
self.prob = cp.Problem(self.obj, constrs_add)
|
351
|
+
|
352
|
+
_, s = elapsed(t)
|
353
|
+
logger.debug(f" -> Finalized in {s}")
|
354
|
+
self.finalized = True
|
355
|
+
return self.finalized
|
356
|
+
|
357
|
+
def init(self, force=False):
|
358
|
+
"""
|
359
|
+
Set up the optimization model from the symbolic description.
|
360
|
+
|
361
|
+
This method initializes the optimization model by parsing decision variables,
|
362
|
+
constraints, and the objective function from the associated routine.
|
363
|
+
|
364
|
+
Parameters
|
365
|
+
----------
|
366
|
+
force : bool, optional
|
367
|
+
Flag indicating if to force the OModel initialization.
|
368
|
+
If True, following methods will be called by force: `self.parse()`,
|
369
|
+
`self.evaluate()`, `self.finalize()`
|
370
|
+
|
371
|
+
Returns
|
372
|
+
-------
|
373
|
+
bool
|
374
|
+
Returns True if the setup is successful, False otherwise.
|
375
|
+
"""
|
376
|
+
if self.initialized and not force:
|
377
|
+
logger.debug("OModel is already initialized.")
|
378
|
+
return self.initialized
|
379
|
+
|
380
|
+
t, _ = elapsed()
|
381
|
+
|
382
|
+
self.parse(force=force)
|
383
|
+
self.evaluate(force=force)
|
384
|
+
self.finalize(force=force)
|
385
|
+
|
386
|
+
_, s = elapsed(t)
|
387
|
+
logger.debug(f"OModel for <{self.rtn.class_name}> initialized in {s}")
|
388
|
+
|
389
|
+
return self.initialized
|
390
|
+
|
391
|
+
@property
|
392
|
+
def class_name(self):
|
393
|
+
"""
|
394
|
+
Return the class name
|
395
|
+
"""
|
396
|
+
return self.__class__.__name__
|
397
|
+
|
398
|
+
def _register_attribute(self, key, value):
|
399
|
+
"""
|
400
|
+
Register a pair of attributes to OModel instance.
|
401
|
+
|
402
|
+
Called within ``__setattr__``, this is where the magic happens.
|
403
|
+
Subclass attributes are automatically registered based on the variable type.
|
404
|
+
"""
|
405
|
+
if isinstance(value, cp.Variable):
|
406
|
+
self.vars[key] = value
|
407
|
+
elif isinstance(value, cp.Constraint):
|
408
|
+
self.constrs[key] = value
|
409
|
+
elif isinstance(value, cp.Parameter):
|
410
|
+
self.params[key] = value
|
411
|
+
elif isinstance(value, cp.Expression):
|
412
|
+
self.exprs[key] = value
|
413
|
+
|
414
|
+
def __setattr__(self, name: str, value: Any):
|
415
|
+
super().__setattr__(name, value)
|
416
|
+
self._register_attribute(name, value)
|
417
|
+
|
418
|
+
def update(self, params):
|
419
|
+
"""
|
420
|
+
Update the Parameter values.
|
421
|
+
|
422
|
+
Parameters
|
423
|
+
----------
|
424
|
+
params: list
|
425
|
+
List of parameters to be updated.
|
426
|
+
"""
|
427
|
+
for param in params:
|
428
|
+
param.update()
|
429
|
+
return True
|
430
|
+
|
431
|
+
def __repr__(self) -> str:
|
432
|
+
return f'{self.rtn.class_name}.{self.__class__.__name__} at {hex(id(self))}'
|
ams/opt/optzbase.py
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
"""
|
2
|
+
Module for optimization base classes.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
from ams.utils.misc import deprec_get_idx
|
9
|
+
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
def ensure_symbols(func):
|
15
|
+
"""
|
16
|
+
Decorator to ensure that symbols are generated before parsing.
|
17
|
+
If not, it runs self.rtn.syms.generate_symbols().
|
18
|
+
|
19
|
+
Designed to be used on the `parse` method of the optimization elements (`OptzBase`)
|
20
|
+
and optimization model (`OModel`), i.e., `Var`, `Param`, `Constraint`, `Objective`,
|
21
|
+
and `ExpressionCalc`.
|
22
|
+
|
23
|
+
Parsing before symbol generation can give wrong results. Ensure that symbols
|
24
|
+
are generated before calling the `parse` method.
|
25
|
+
"""
|
26
|
+
|
27
|
+
def wrapper(self, *args, **kwargs):
|
28
|
+
if not self.rtn._syms:
|
29
|
+
logger.debug(f"<{self.rtn.class_name}> symbols are not generated yet. Generating now...")
|
30
|
+
self.rtn.syms.generate_symbols()
|
31
|
+
return func(self, *args, **kwargs)
|
32
|
+
return wrapper
|
33
|
+
|
34
|
+
|
35
|
+
def ensure_mats_and_parsed(func):
|
36
|
+
"""
|
37
|
+
Decorator to ensure that system matrices are built and the OModel is parsed
|
38
|
+
before evaluation. If not, it runs the necessary methods to initialize them.
|
39
|
+
|
40
|
+
Designed to be used on the `evaluate` method of optimization elements (`OptzBase`)
|
41
|
+
and the optimization model (`OModel`), i.e., `Var`, `Param`, `Constraint`, `Objective`,
|
42
|
+
and `ExpressionCalc`.
|
43
|
+
|
44
|
+
Evaluation before building matrices and parsing the OModel can lead to errors. Ensure that
|
45
|
+
system matrices are built and the OModel is parsed before calling the `evaluate` method.
|
46
|
+
"""
|
47
|
+
|
48
|
+
def wrapper(self, *args, **kwargs):
|
49
|
+
try:
|
50
|
+
if not self.rtn.system.mats.initialized:
|
51
|
+
logger.debug("System matrices are not built yet. Building now...")
|
52
|
+
self.rtn.system.mats.build()
|
53
|
+
if isinstance(self, (OptzBase)):
|
54
|
+
if not self.om.parsed:
|
55
|
+
logger.debug("OModel is not parsed yet. Parsing now...")
|
56
|
+
self.om.parse()
|
57
|
+
else:
|
58
|
+
if not self.parsed:
|
59
|
+
logger.debug("OModel is not parsed yet. Parsing now...")
|
60
|
+
self.parse()
|
61
|
+
except Exception as e:
|
62
|
+
logger.error(f"Error during initialization or parsing: {e}")
|
63
|
+
raise e
|
64
|
+
return func(self, *args, **kwargs)
|
65
|
+
return wrapper
|
66
|
+
|
67
|
+
|
68
|
+
class OptzBase:
|
69
|
+
"""
|
70
|
+
Base class for optimization elements.
|
71
|
+
Ensure that symbols are generated before calling the `parse` method. Parsing
|
72
|
+
before symbol generation can lead to incorrect results.
|
73
|
+
|
74
|
+
Parameters
|
75
|
+
----------
|
76
|
+
name : str, optional
|
77
|
+
Name of the optimization element.
|
78
|
+
info : str, optional
|
79
|
+
Descriptive information about the optimization element.
|
80
|
+
unit : str, optional
|
81
|
+
Unit of measurement for the optimization element.
|
82
|
+
|
83
|
+
Attributes
|
84
|
+
----------
|
85
|
+
rtn : ams.routines.Routine
|
86
|
+
The owner routine instance.
|
87
|
+
"""
|
88
|
+
|
89
|
+
def __init__(self,
|
90
|
+
name: Optional[str] = None,
|
91
|
+
info: Optional[str] = None,
|
92
|
+
unit: Optional[str] = None,
|
93
|
+
model: Optional[str] = None,
|
94
|
+
):
|
95
|
+
self.om = None
|
96
|
+
self.name = name
|
97
|
+
self.info = info
|
98
|
+
self.unit = unit
|
99
|
+
self.is_disabled = False
|
100
|
+
self.rtn = None
|
101
|
+
self.optz = None # corresponding optimization element
|
102
|
+
self.code = None
|
103
|
+
self.model = model # indicate if this element belongs to a model or group
|
104
|
+
self.owner = None # instance of the owner model or group
|
105
|
+
self.is_group = False
|
106
|
+
|
107
|
+
@ensure_symbols
|
108
|
+
def parse(self):
|
109
|
+
"""
|
110
|
+
Parse the object.
|
111
|
+
"""
|
112
|
+
raise NotImplementedError
|
113
|
+
|
114
|
+
@ensure_mats_and_parsed
|
115
|
+
def evaluate(self):
|
116
|
+
"""
|
117
|
+
Evaluate the object.
|
118
|
+
"""
|
119
|
+
raise NotImplementedError
|
120
|
+
|
121
|
+
@property
|
122
|
+
def class_name(self):
|
123
|
+
"""
|
124
|
+
Return the class name
|
125
|
+
"""
|
126
|
+
return self.__class__.__name__
|
127
|
+
|
128
|
+
@property
|
129
|
+
def n(self):
|
130
|
+
"""
|
131
|
+
Return the number of elements.
|
132
|
+
"""
|
133
|
+
if self.owner is None:
|
134
|
+
return len(self.v)
|
135
|
+
else:
|
136
|
+
return self.owner.n
|
137
|
+
|
138
|
+
@property
|
139
|
+
def shape(self):
|
140
|
+
"""
|
141
|
+
Return the shape.
|
142
|
+
"""
|
143
|
+
try:
|
144
|
+
return self.om.__dict__[self.name].shape
|
145
|
+
except KeyError:
|
146
|
+
logger.warning('Shape info is not ready before initialization.')
|
147
|
+
return None
|
148
|
+
|
149
|
+
@property
|
150
|
+
def size(self):
|
151
|
+
"""
|
152
|
+
Return the size.
|
153
|
+
"""
|
154
|
+
if self.rtn.initialized:
|
155
|
+
return self.om.__dict__[self.name].size
|
156
|
+
else:
|
157
|
+
logger.warning(f'Routine <{self.rtn.class_name}> is not initialized yet.')
|
158
|
+
return None
|
159
|
+
|
160
|
+
def __repr__(self):
|
161
|
+
return f'{self.__class__.__name__}: {self.name}'
|
162
|
+
|
163
|
+
@deprec_get_idx
|
164
|
+
def get_idx(self):
|
165
|
+
if self.is_group:
|
166
|
+
return self.owner.get_all_idxes()
|
167
|
+
elif self.owner is None:
|
168
|
+
logger.info(f'{self.class_name} <{self.name}> has no owner.')
|
169
|
+
return None
|
170
|
+
else:
|
171
|
+
return self.owner.idx.v
|
172
|
+
|
173
|
+
def get_all_idxes(self):
|
174
|
+
"""
|
175
|
+
Return all the indexes of this item.
|
176
|
+
|
177
|
+
.. note::
|
178
|
+
New in version 1.0.0.
|
179
|
+
|
180
|
+
Returns
|
181
|
+
-------
|
182
|
+
list
|
183
|
+
A list of indexes.
|
184
|
+
"""
|
185
|
+
|
186
|
+
if self.is_group:
|
187
|
+
return self.owner.get_all_idxes()
|
188
|
+
elif self.owner is None:
|
189
|
+
logger.info(f'{self.class_name} <{self.name}> has no owner.')
|
190
|
+
return None
|
191
|
+
else:
|
192
|
+
return self.owner.idx.v
|