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/constraint.py ADDED
@@ -0,0 +1,175 @@
1
+ """
2
+ Module for optimization Constraint.
3
+ """
4
+ import logging
5
+
6
+ from typing import Optional
7
+ import re
8
+
9
+ import numpy as np
10
+
11
+ import cvxpy as cp
12
+
13
+ from ams.utils import pretty_long_message
14
+ from ams.shared import _prefix, _max_length
15
+
16
+ from ams.opt import OptzBase, ensure_symbols, ensure_mats_and_parsed
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class Constraint(OptzBase):
22
+ """
23
+ Base class for constraints.
24
+
25
+ This class is used as a template for defining constraints. Each
26
+ instance of this class represents a single constraint.
27
+
28
+ Parameters
29
+ ----------
30
+ name : str, optional
31
+ A user-defined name for the constraint.
32
+ e_str : str, optional
33
+ A mathematical expression representing the constraint.
34
+ info : str, optional
35
+ Additional informational text about the constraint.
36
+ is_eq : str, optional
37
+ Flag indicating if the constraint is an equality constraint. False indicates
38
+ an inequality constraint in the form of `<= 0`.
39
+
40
+ Attributes
41
+ ----------
42
+ is_disabled : bool
43
+ Flag indicating if the constraint is disabled, False by default.
44
+ rtn : ams.routines.Routine
45
+ The owner routine instance.
46
+ is_disabled : bool, optional
47
+ Flag indicating if the constraint is disabled, False by default.
48
+ code : str, optional
49
+ The code string for the constraint
50
+ """
51
+
52
+ def __init__(self,
53
+ name: Optional[str] = None,
54
+ e_str: Optional[str] = None,
55
+ info: Optional[str] = None,
56
+ is_eq: Optional[bool] = False,
57
+ ):
58
+ OptzBase.__init__(self, name=name, info=info)
59
+ self.e_str = e_str
60
+ self.is_eq = is_eq
61
+ self.is_disabled = False
62
+ self.code = None
63
+
64
+ def get_idx(self):
65
+ raise NotImplementedError
66
+
67
+ def get_all_idxes(self):
68
+ raise NotImplementedError
69
+
70
+ @ensure_symbols
71
+ def parse(self):
72
+ """
73
+ Parse the constraint.
74
+ """
75
+ # parse the expression str
76
+ sub_map = self.om.rtn.syms.sub_map
77
+ code_constr = self.e_str
78
+ for pattern, replacement in sub_map.items():
79
+ try:
80
+ code_constr = re.sub(pattern, replacement, code_constr)
81
+ except TypeError as e:
82
+ raise TypeError(f"Error in parsing constr <{self.name}>.\n{e}")
83
+ # parse the constraint type
84
+ code_constr += " == 0" if self.is_eq else " <= 0"
85
+ # store the parsed expression str code
86
+ self.code = code_constr
87
+ msg = f" - Constr <{self.name}>: {self.e_str}"
88
+ logger.debug(pretty_long_message(msg, _prefix, max_length=_max_length))
89
+ return True
90
+
91
+ def _evaluate_expression(self, code, local_vars=None):
92
+ """
93
+ Helper method to evaluate the expression code.
94
+
95
+ Parameters
96
+ ----------
97
+ code : str
98
+ The code string representing the expression.
99
+
100
+ Returns
101
+ -------
102
+ cp.Expression
103
+ The evaluated cvxpy expression.
104
+ """
105
+ return eval(code, {}, local_vars)
106
+
107
+ @ensure_mats_and_parsed
108
+ def evaluate(self):
109
+ """
110
+ Evaluate the constraint.
111
+ """
112
+ msg = f" - Constr <{self.name}>: {self.code}"
113
+ logger.debug(pretty_long_message(msg, _prefix, max_length=_max_length))
114
+ try:
115
+ local_vars = {'self': self, 'cp': cp, 'sub_map': self.om.rtn.syms.val_map}
116
+ self.optz = self._evaluate_expression(self.code, local_vars=local_vars)
117
+ except Exception as e:
118
+ raise Exception(f"Error in evaluating Constraint <{self.name}>.\n{e}")
119
+
120
+ def __repr__(self):
121
+ enabled = 'OFF' if self.is_disabled else 'ON'
122
+ out = f"{self.class_name}: {self.name} [{enabled}]"
123
+ return out
124
+
125
+ @property
126
+ def e(self):
127
+ """
128
+ Return the calculated constraint LHS value.
129
+ Note that `v` should be used primarily as it is obtained
130
+ from the solver directly.
131
+
132
+ `e` is for debugging purpose. For a successfully solved problem,
133
+ `e` should equal to `v`. However, when a problem is infeasible
134
+ or unbounded, `e` can be used to check the constraint LHS value.
135
+ """
136
+ if self.code is None:
137
+ logger.info(f"Constraint <{self.name}> is not parsed yet.")
138
+ return None
139
+
140
+ val_map = self.om.rtn.syms.val_map
141
+ code = self.code
142
+ for pattern, replacement in val_map.items():
143
+ try:
144
+ code = re.sub(pattern, replacement, code)
145
+ except TypeError as e:
146
+ raise TypeError(e)
147
+
148
+ try:
149
+ logger.debug(pretty_long_message(f"Value code: {code}",
150
+ _prefix, max_length=_max_length))
151
+ local_vars = {'self': self, 'np': np, 'cp': cp, 'val_map': val_map}
152
+ return self._evaluate_expression(code, local_vars)
153
+ except Exception as e:
154
+ logger.error(f"Error in calculating constr <{self.name}>.\n{e}")
155
+ return None
156
+
157
+ @property
158
+ def v(self):
159
+ """
160
+ Return the CVXPY constraint LHS value.
161
+ """
162
+ if self.optz is None:
163
+ return None
164
+ if self.optz._expr.value is None:
165
+ try:
166
+ shape = self._expr.shape
167
+ return np.zeros(shape)
168
+ except AttributeError:
169
+ return None
170
+ else:
171
+ return self.optz._expr.value
172
+
173
+ @v.setter
174
+ def v(self, value):
175
+ raise AttributeError("Cannot set the value of the constraint.")
ams/opt/exprcalc.py ADDED
@@ -0,0 +1,127 @@
1
+ """
2
+ Module for optimization ExpressionCalc.
3
+ """
4
+ import logging
5
+
6
+ from typing import Optional
7
+ import re
8
+
9
+ import numpy as np
10
+
11
+ import cvxpy as cp
12
+
13
+ from ams.utils import pretty_long_message
14
+ from ams.shared import _prefix, _max_length
15
+
16
+ from ams.opt import OptzBase, ensure_symbols, ensure_mats_and_parsed
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class ExpressionCalc(OptzBase):
22
+ """
23
+ Class for calculating expressions.
24
+
25
+ Note that `ExpressionCalc` is not a CVXPY expression, and it should not be used
26
+ in the optimization model.
27
+ It is used to calculate expression values **after** successful optimization.
28
+ """
29
+
30
+ def __init__(self,
31
+ name: Optional[str] = None,
32
+ info: Optional[str] = None,
33
+ unit: Optional[str] = None,
34
+ e_str: Optional[str] = None,
35
+ model: Optional[str] = None,
36
+ src: Optional[str] = None,
37
+ ):
38
+ OptzBase.__init__(self, name=name, info=info, unit=unit, model=model)
39
+ self.optz = None
40
+ self.e_str = e_str
41
+ self.code = None
42
+ self.src = src
43
+
44
+ @ensure_symbols
45
+ def parse(self):
46
+ """
47
+ Parse the Expression.
48
+ """
49
+ # parse the expression str
50
+ sub_map = self.om.rtn.syms.sub_map
51
+ code_expr = self.e_str
52
+ for pattern, replacement in sub_map.items():
53
+ try:
54
+ code_expr = re.sub(pattern, replacement, code_expr)
55
+ except Exception as e:
56
+ raise Exception(f"Error in parsing expr <{self.name}>.\n{e}")
57
+ # store the parsed expression str code
58
+ self.code = code_expr
59
+ msg = f" - ExpressionCalc <{self.name}>: {self.e_str}"
60
+ logger.debug(pretty_long_message(msg, _prefix, max_length=_max_length))
61
+ return True
62
+
63
+ @ensure_mats_and_parsed
64
+ def evaluate(self):
65
+ """
66
+ Evaluate the expression.
67
+ """
68
+ msg = f" - Expression <{self.name}>: {self.code}"
69
+ logger.debug(pretty_long_message(msg, _prefix, max_length=_max_length))
70
+ try:
71
+ local_vars = {'self': self, 'np': np, 'cp': cp}
72
+ self.optz = self._evaluate_expression(self.code, local_vars=local_vars)
73
+ except Exception as e:
74
+ raise Exception(f"Error in evaluating ExpressionCalc <{self.name}>.\n{e}")
75
+ return True
76
+
77
+ def _evaluate_expression(self, code, local_vars=None):
78
+ """
79
+ Helper method to evaluate the expression code.
80
+
81
+ Parameters
82
+ ----------
83
+ code : str
84
+ The code string representing the expression.
85
+
86
+ Returns
87
+ -------
88
+ cp.Expression
89
+ The evaluated cvxpy expression.
90
+ """
91
+ return eval(code, {}, local_vars)
92
+
93
+ @property
94
+ def v(self):
95
+ """
96
+ Return the CVXPY expression value.
97
+ """
98
+ if self.optz is None:
99
+ return None
100
+ else:
101
+ return self.optz.value
102
+
103
+ @property
104
+ def e(self):
105
+ """
106
+ Return the calculated expression value.
107
+ """
108
+ if self.code is None:
109
+ logger.info(f"ExpressionCalc <{self.name}> is not parsed yet.")
110
+ return None
111
+
112
+ val_map = self.om.rtn.syms.val_map
113
+ code = self.code
114
+ for pattern, replacement in val_map.items():
115
+ try:
116
+ code = re.sub(pattern, replacement, code)
117
+ except TypeError as e:
118
+ raise TypeError(e)
119
+
120
+ try:
121
+ logger.debug(pretty_long_message(f"Value code: {code}",
122
+ _prefix, max_length=_max_length))
123
+ local_vars = {'self': self, 'np': np, 'cp': cp, 'val_map': val_map}
124
+ return self._evaluate_expression(code, local_vars)
125
+ except Exception as e:
126
+ logger.error(f"Error in calculating expr <{self.name}>.\n{e}")
127
+ return None
ams/opt/expression.py ADDED
@@ -0,0 +1,188 @@
1
+ """
2
+ Module for optimization Expression.
3
+ """
4
+ import logging
5
+
6
+ from typing import Optional
7
+ import re
8
+
9
+ import numpy as np
10
+
11
+ import cvxpy as cp
12
+
13
+ from ams.utils import pretty_long_message
14
+ from ams.shared import _prefix, _max_length
15
+
16
+ from ams.opt import OptzBase, ensure_symbols, ensure_mats_and_parsed
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class Expression(OptzBase):
22
+ """
23
+ Base class for expressions used in a routine.
24
+
25
+ Parameters
26
+ ----------
27
+ name : str, optional
28
+ Expression name. One should typically assigning the name directly because
29
+ it will be automatically assigned by the model. The value of ``name``
30
+ will be the symbol name to be used in expressions.
31
+ tex_name : str, optional
32
+ LaTeX-formatted variable symbol. Defaults to the value of ``name``.
33
+ info : str, optional
34
+ Descriptive information
35
+ unit : str, optional
36
+ Unit
37
+ e_str : str, optional
38
+ Expression string
39
+ model : str, optional
40
+ Name of the owner model or group.
41
+ src : str, optional
42
+ Source expression name
43
+ vtype : type, optional
44
+ Value type
45
+ horizon : ams.routines.RParam, optional
46
+ Horizon
47
+ """
48
+
49
+ def __init__(self,
50
+ name: Optional[str] = None,
51
+ tex_name: Optional[str] = None,
52
+ info: Optional[str] = None,
53
+ unit: Optional[str] = None,
54
+ e_str: Optional[str] = None,
55
+ model: Optional[str] = None,
56
+ src: Optional[str] = None,
57
+ vtype: Optional[str] = float,
58
+ horizon: Optional[str] = None,
59
+ ):
60
+ OptzBase.__init__(self, name=name, info=info, unit=unit, model=model)
61
+ self.tex_name = tex_name
62
+ self.e_str = e_str
63
+ self.optz = None
64
+ self.code = None
65
+ self.src = src
66
+ self.horizon = horizon
67
+
68
+ @ensure_symbols
69
+ def parse(self):
70
+ """
71
+ Parse the expression.
72
+
73
+ Returns
74
+ -------
75
+ bool
76
+ Returns True if the parsing is successful, False otherwise.
77
+ """
78
+ sub_map = self.om.rtn.syms.sub_map
79
+ code_expr = self.e_str
80
+ for pattern, replacement in sub_map.items():
81
+ try:
82
+ code_expr = re.sub(pattern, replacement, code_expr)
83
+ except Exception as e:
84
+ raise Exception(f"Error in parsing expr <{self.name}>.\n{e}")
85
+ self.code = code_expr
86
+ msg = f" - Expression <{self.name}>: {self.e_str}"
87
+ logger.debug(pretty_long_message(msg, _prefix, max_length=_max_length))
88
+ return True
89
+
90
+ @ensure_mats_and_parsed
91
+ def evaluate(self):
92
+ """
93
+ Evaluate the expression.
94
+
95
+ Returns
96
+ -------
97
+ bool
98
+ Returns True if the evaluation is successful, False otherwise.
99
+ """
100
+ msg = f" - Expression <{self.name}>: {self.code}"
101
+ logger.debug(pretty_long_message(msg, _prefix, max_length=_max_length))
102
+ try:
103
+ local_vars = {'self': self, 'np': np, 'cp': cp, 'sub_map': self.om.rtn.syms.val_map}
104
+ self.optz = self._evaluate_expression(self.code, local_vars=local_vars)
105
+ except Exception as e:
106
+ raise Exception(f"Error in evaluating Expression <{self.name}>.\n{e}")
107
+ return True
108
+
109
+ def _evaluate_expression(self, code, local_vars=None):
110
+ """
111
+ Helper method to evaluate the expression code.
112
+
113
+ Parameters
114
+ ----------
115
+ code : str
116
+ The code string representing the expression.
117
+
118
+ Returns
119
+ -------
120
+ cp.Expression
121
+ The evaluated cvxpy expression.
122
+ """
123
+ return eval(code, {}, local_vars)
124
+
125
+ @property
126
+ def v(self):
127
+ """
128
+ Return the CVXPY expression value.
129
+ """
130
+ if self.optz is None:
131
+ return None
132
+ else:
133
+ return self.optz.value
134
+
135
+ @v.setter
136
+ def v(self, value):
137
+ """
138
+ Set the value.
139
+ """
140
+ raise NotImplementedError('Cannot set value to an Expression.')
141
+
142
+ @property
143
+ def shape(self):
144
+ """
145
+ Return the shape.
146
+ """
147
+ try:
148
+ return self.om.__dict__[self.name].shape
149
+ except KeyError:
150
+ logger.warning('Shape info is not ready before initialization.')
151
+ return None
152
+
153
+ @property
154
+ def size(self):
155
+ """
156
+ Return the size.
157
+ """
158
+ if self.rtn.initialized:
159
+ return self.om.__dict__[self.name].size
160
+ else:
161
+ logger.warning(f'Routine <{self.rtn.class_name}> is not initialized yet.')
162
+ return None
163
+
164
+ @property
165
+ def e(self):
166
+ """
167
+ Return the calculated expression value.
168
+ """
169
+ if self.code is None:
170
+ logger.info(f"Expression <{self.name}> is not parsed yet.")
171
+ return None
172
+
173
+ val_map = self.om.rtn.syms.val_map
174
+ code = self.code
175
+ for pattern, replacement in val_map.items():
176
+ try:
177
+ code = re.sub(pattern, replacement, code)
178
+ except TypeError as e:
179
+ raise TypeError(e)
180
+
181
+ try:
182
+ logger.debug(pretty_long_message(f"Value code: {code}",
183
+ _prefix, max_length=_max_length))
184
+ local_vars = {'self': self, 'np': np, 'cp': cp, 'val_map': val_map}
185
+ return self._evaluate_expression(code, local_vars)
186
+ except Exception as e:
187
+ logger.error(f"Error in calculating expr <{self.name}>.\n{e}")
188
+ return None
ams/opt/objective.py ADDED
@@ -0,0 +1,174 @@
1
+ """
2
+ Module for optimization Objective.
3
+ """
4
+ import logging
5
+
6
+ from typing import Optional
7
+ import re
8
+
9
+ import numpy as np
10
+
11
+ import cvxpy as cp
12
+
13
+ from ams.utils import pretty_long_message
14
+ from ams.shared import _prefix, _max_length
15
+
16
+ from ams.opt import OptzBase, ensure_symbols, ensure_mats_and_parsed
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class Objective(OptzBase):
22
+ """
23
+ Base class for objective functions.
24
+
25
+ This class serves as a template for defining objective functions. Each
26
+ instance of this class represents a single objective function that can
27
+ be minimized or maximized depending on the sense ('min' or 'max').
28
+
29
+ Parameters
30
+ ----------
31
+ name : str, optional
32
+ A user-defined name for the objective function.
33
+ e_str : str, optional
34
+ A mathematical expression representing the objective function.
35
+ info : str, optional
36
+ Additional informational text about the objective function.
37
+ sense : str, optional
38
+ The sense of the objective function, default to 'min'.
39
+ `min` for minimization and `max` for maximization.
40
+
41
+ Attributes
42
+ ----------
43
+ v : NoneType
44
+ The value of the objective function. It needs to be set through
45
+ computation.
46
+ rtn : ams.routines.Routine
47
+ The owner routine instance.
48
+ code : str
49
+ The code string for the objective function.
50
+ """
51
+
52
+ def __init__(self,
53
+ name: Optional[str] = None,
54
+ e_str: Optional[str] = None,
55
+ info: Optional[str] = None,
56
+ unit: Optional[str] = None,
57
+ sense: Optional[str] = 'min'):
58
+ OptzBase.__init__(self, name=name, info=info, unit=unit)
59
+ self.e_str = e_str
60
+ self.sense = sense
61
+ self.code = None
62
+
63
+ @property
64
+ def e(self):
65
+ """
66
+ Return the calculated objective value.
67
+
68
+ Note that `v` should be used primarily as it is obtained
69
+ from the solver directly.
70
+
71
+ `e` is for debugging purpose. For a successfully solved problem,
72
+ `e` should equal to `v`. However, when a problem is infeasible
73
+ or unbounded, `e` can be used to check the objective value.
74
+ """
75
+ if self.code is None:
76
+ logger.info(f"Objective <{self.name}> is not parsed yet.")
77
+ return None
78
+
79
+ val_map = self.om.rtn.syms.val_map
80
+ code = self.code
81
+ for pattern, replacement in val_map.items():
82
+ try:
83
+ code = re.sub(pattern, replacement, code)
84
+ except TypeError as e:
85
+ logger.error(f"Error in parsing value for obj <{self.name}>.")
86
+ raise e
87
+
88
+ try:
89
+ logger.debug(pretty_long_message(f"Value code: {code}",
90
+ _prefix, max_length=_max_length))
91
+ local_vars = {'self': self, 'np': np, 'cp': cp, 'val_map': val_map}
92
+ return self._evaluate_expression(code, local_vars)
93
+ except Exception as e:
94
+ logger.error(f"Error in calculating obj <{self.name}>.\n{e}")
95
+ return None
96
+
97
+ @property
98
+ def v(self):
99
+ """
100
+ Return the CVXPY objective value.
101
+ """
102
+ if self.optz is None:
103
+ return None
104
+ else:
105
+ return self.optz.value
106
+
107
+ @v.setter
108
+ def v(self, value):
109
+ raise AttributeError("Cannot set the value of the objective function.")
110
+
111
+ @ensure_symbols
112
+ def parse(self):
113
+ """
114
+ Parse the objective function.
115
+
116
+ Returns
117
+ -------
118
+ bool
119
+ Returns True if the parsing is successful, False otherwise.
120
+ """
121
+ # parse the expression str
122
+ sub_map = self.om.rtn.syms.sub_map
123
+ code_obj = self.e_str
124
+ for pattern, replacement, in sub_map.items():
125
+ try:
126
+ code_obj = re.sub(pattern, replacement, code_obj)
127
+ except Exception as e:
128
+ raise Exception(f"Error in parsing obj <{self.name}>.\n{e}")
129
+ # store the parsed expression str code
130
+ self.code = code_obj
131
+ if self.sense not in ['min', 'max']:
132
+ raise ValueError(f'Objective sense {self.sense} is not supported.')
133
+ sense = 'cp.Minimize' if self.sense == 'min' else 'cp.Maximize'
134
+ self.code = f"{sense}({code_obj})"
135
+ msg = f" - Objective <{self.name}>: {self.code}"
136
+ logger.debug(pretty_long_message(msg, _prefix, max_length=_max_length))
137
+ return True
138
+
139
+ @ensure_mats_and_parsed
140
+ def evaluate(self):
141
+ """
142
+ Evaluate the objective function.
143
+
144
+ Returns
145
+ -------
146
+ bool
147
+ Returns True if the evaluation is successful, False otherwise.
148
+ """
149
+ logger.debug(f" - Objective <{self.name}>: {self.e_str}")
150
+ try:
151
+ local_vars = {'self': self, 'cp': cp}
152
+ self.optz = self._evaluate_expression(self.code, local_vars=local_vars)
153
+ except Exception as e:
154
+ raise Exception(f"Error in evaluating Objective <{self.name}>.\n{e}")
155
+ return True
156
+
157
+ def _evaluate_expression(self, code, local_vars=None):
158
+ """
159
+ Helper method to evaluate the expression code.
160
+
161
+ Parameters
162
+ ----------
163
+ code : str
164
+ The code string representing the expression.
165
+
166
+ Returns
167
+ -------
168
+ cp.Expression
169
+ The evaluated cvxpy expression.
170
+ """
171
+ return eval(code, {}, local_vars)
172
+
173
+ def __repr__(self):
174
+ return f"{self.class_name}: {self.name} [{self.sense.upper()}]"