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/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