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/routines/routine.py
ADDED
@@ -0,0 +1,1033 @@
|
|
1
|
+
"""
|
2
|
+
Module for routine data.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import os
|
7
|
+
from typing import Optional, Union, Type, Iterable, Dict
|
8
|
+
from collections import OrderedDict
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
|
12
|
+
from andes.core import Config
|
13
|
+
from andes.utils.misc import elapsed
|
14
|
+
|
15
|
+
from ams.core.param import RParam
|
16
|
+
from ams.core.symprocessor import SymProcessor
|
17
|
+
from ams.core.documenter import RDocumenter
|
18
|
+
from ams.core.service import RBaseService, ValueService
|
19
|
+
from ams.opt import OModel
|
20
|
+
from ams.opt import Param, Var, Constraint, Objective, ExpressionCalc, Expression
|
21
|
+
|
22
|
+
from ams.shared import pd
|
23
|
+
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
|
26
|
+
|
27
|
+
class RoutineBase:
|
28
|
+
"""
|
29
|
+
Class to hold descriptive routine models and data mapping.
|
30
|
+
|
31
|
+
Attributes
|
32
|
+
----------
|
33
|
+
system : Optional[Type]
|
34
|
+
The system object associated with the routine.
|
35
|
+
config : Config
|
36
|
+
Configuration object for the routine.
|
37
|
+
info : Optional[str]
|
38
|
+
Information about the routine.
|
39
|
+
tex_names : OrderedDict
|
40
|
+
LaTeX names for the routine parameters.
|
41
|
+
syms : SymProcessor
|
42
|
+
Symbolic processor for the routine.
|
43
|
+
_syms : bool
|
44
|
+
Flag indicating whether symbols have been generated.
|
45
|
+
rparams : OrderedDict
|
46
|
+
Registry for RParam objects.
|
47
|
+
services : OrderedDict
|
48
|
+
Registry for service objects.
|
49
|
+
params : OrderedDict
|
50
|
+
Registry for Param objects.
|
51
|
+
vars : OrderedDict
|
52
|
+
Registry for Var objects.
|
53
|
+
constrs : OrderedDict
|
54
|
+
Registry for Constraint objects.
|
55
|
+
exprcs : OrderedDict
|
56
|
+
Registry for ExpressionCalc objects.
|
57
|
+
exprs : OrderedDict
|
58
|
+
Registry for Expression objects.
|
59
|
+
obj : Optional[Objective]
|
60
|
+
Objective of the routine.
|
61
|
+
initialized : bool
|
62
|
+
Flag indicating whether the routine has been initialized.
|
63
|
+
type : str
|
64
|
+
Type of the routine.
|
65
|
+
docum : RDocumenter
|
66
|
+
Documentation generator for the routine.
|
67
|
+
map1 : OrderedDict
|
68
|
+
Mapping from ANDES.
|
69
|
+
map2 : OrderedDict
|
70
|
+
Mapping to ANDES.
|
71
|
+
om : OModel
|
72
|
+
Optimization model for the routine.
|
73
|
+
exec_time : float
|
74
|
+
Execution time of the routine.
|
75
|
+
exit_code : int
|
76
|
+
Exit code of the routine.
|
77
|
+
converged : bool
|
78
|
+
Flag indicating whether the routine has converged.
|
79
|
+
converted : bool
|
80
|
+
Flag indicating whether AC conversion has been performed.
|
81
|
+
"""
|
82
|
+
|
83
|
+
def __init__(self, system=None, config=None):
|
84
|
+
"""
|
85
|
+
Initialize the routine.
|
86
|
+
|
87
|
+
Parameters
|
88
|
+
----------
|
89
|
+
system : Optional[Type]
|
90
|
+
The system object associated with the routine.
|
91
|
+
config : Optional[dict]
|
92
|
+
Configuration dictionary for the routine.
|
93
|
+
"""
|
94
|
+
self.system = system
|
95
|
+
self.config = Config(self.class_name)
|
96
|
+
self.info = None
|
97
|
+
self.tex_names = OrderedDict(
|
98
|
+
(
|
99
|
+
("sys_f", "f_{sys}"),
|
100
|
+
("sys_mva", "S_{b,sys}"),
|
101
|
+
)
|
102
|
+
)
|
103
|
+
self.syms = SymProcessor(self) # symbolic processor
|
104
|
+
self._syms = False # symbol generation flag
|
105
|
+
|
106
|
+
self.rparams = OrderedDict() # RParam registry
|
107
|
+
self.services = OrderedDict() # Service registry
|
108
|
+
self.params = OrderedDict() # Param registry
|
109
|
+
self.vars = OrderedDict() # Var registry
|
110
|
+
self.constrs = OrderedDict() # Constraint registry
|
111
|
+
self.exprcs = OrderedDict() # ExpressionCalc registry
|
112
|
+
self.exprs = OrderedDict() # Expression registry
|
113
|
+
self.obj = None # Objective
|
114
|
+
self.initialized = False # initialization flag
|
115
|
+
self.type = "UndefinedType" # routine type
|
116
|
+
self.docum = RDocumenter(self) # documentation generator
|
117
|
+
|
118
|
+
# --- sync mapping ---
|
119
|
+
self.map1 = OrderedDict() # from ANDES
|
120
|
+
self.map2 = OrderedDict() # to ANDES
|
121
|
+
|
122
|
+
# --- optimization modeling ---
|
123
|
+
self.om = OModel(routine=self) # optimization model
|
124
|
+
|
125
|
+
if config is not None:
|
126
|
+
self.config.load(config)
|
127
|
+
|
128
|
+
# NOTE: the difference between exit_code and converged is that
|
129
|
+
# exit_code is the solver exit code, while converged is the
|
130
|
+
# convergence flag of the routine.
|
131
|
+
self.exec_time = 0.0 # running time
|
132
|
+
self.exit_code = 0 # exit code
|
133
|
+
self.converged = False # convergence flag
|
134
|
+
self.converted = False # AC conversion flag
|
135
|
+
|
136
|
+
@property
|
137
|
+
def class_name(self):
|
138
|
+
return self.__class__.__name__
|
139
|
+
|
140
|
+
def get(self, src: str, idx, attr: str = 'v',
|
141
|
+
horizon: Optional[Union[int, str, Iterable]] = None):
|
142
|
+
"""
|
143
|
+
Get the value of a variable or parameter.
|
144
|
+
|
145
|
+
Parameters
|
146
|
+
----------
|
147
|
+
src: str
|
148
|
+
Name of the variable or parameter.
|
149
|
+
idx: int, str, or list
|
150
|
+
Index of the variable or parameter.
|
151
|
+
attr: str
|
152
|
+
Attribute name.
|
153
|
+
horizon: list, optional
|
154
|
+
Horizon index.
|
155
|
+
"""
|
156
|
+
if src not in self.__dict__.keys():
|
157
|
+
raise ValueError(f"<{src}> does not exist in <<{self.class_name}>.")
|
158
|
+
item = self.__dict__[src]
|
159
|
+
|
160
|
+
if not hasattr(item, attr):
|
161
|
+
raise ValueError(f"{attr} does not exist in {self.class_name}.{src}.")
|
162
|
+
|
163
|
+
idx_all = item.get_all_idxes()
|
164
|
+
|
165
|
+
if idx_all is None:
|
166
|
+
raise ValueError(f"<{self.class_name}> item <{src}> has no idx.")
|
167
|
+
|
168
|
+
is_format = False # whether the idx is formatted as a list
|
169
|
+
idx_u = None
|
170
|
+
if isinstance(idx, (str, int)):
|
171
|
+
idx_u = [idx]
|
172
|
+
is_format = True
|
173
|
+
elif isinstance(idx, (np.ndarray, pd.Series)):
|
174
|
+
idx_u = idx.tolist()
|
175
|
+
elif isinstance(idx, list):
|
176
|
+
idx_u = idx.copy()
|
177
|
+
|
178
|
+
loc = [idx_all.index(idxe) if idxe in idx_all else None for idxe in idx_u]
|
179
|
+
if None in loc:
|
180
|
+
idx_none = [idxe for idxe in idx_u if idxe not in idx_all]
|
181
|
+
msg = f"Var <{self.class_name}.{src}> does not contain value with idx={idx_none}"
|
182
|
+
raise ValueError(msg)
|
183
|
+
out = getattr(item, attr)[loc]
|
184
|
+
|
185
|
+
if horizon is not None:
|
186
|
+
if item.horizon is None:
|
187
|
+
raise ValueError(f"horizon is not defined for {self.class_name}.{src}.")
|
188
|
+
horizon_all = item.horizon.get_all_idxes()
|
189
|
+
if not isinstance(horizon, list):
|
190
|
+
raise TypeError(f"horizon must be a list, not {type(horizon)}.")
|
191
|
+
loc_h = [
|
192
|
+
horizon_all.index(idxe) if idxe in horizon_all else None
|
193
|
+
for idxe in horizon
|
194
|
+
]
|
195
|
+
if None in loc_h:
|
196
|
+
idx_none = [idxe for idxe in horizon if idxe not in horizon_all]
|
197
|
+
msg = f"Var <{self.class_name}.{src}> does not contain horizon with idx={idx_none}"
|
198
|
+
raise ValueError(msg)
|
199
|
+
out = out[:, loc_h]
|
200
|
+
if out.shape[1] == 1:
|
201
|
+
out = out[:, 0]
|
202
|
+
|
203
|
+
return out[0] if is_format else out
|
204
|
+
|
205
|
+
def set(self, src: str, idx, attr: str = "v", value=0.0):
|
206
|
+
"""
|
207
|
+
Set the value of an attribute of a routine parameter.
|
208
|
+
|
209
|
+
Performs ``self.<src>.<attr>[idx] = value``. This method will not modify
|
210
|
+
the input values from the case file that have not been converted to the
|
211
|
+
system base. As a result, changes applied by this method will not affect
|
212
|
+
the dumped case file.
|
213
|
+
|
214
|
+
To alter parameters and reflect it in the case file, use :meth:`alter`
|
215
|
+
instead.
|
216
|
+
|
217
|
+
Parameters
|
218
|
+
----------
|
219
|
+
src : str
|
220
|
+
Name of the model property
|
221
|
+
idx : str, int, float, array-like
|
222
|
+
Indices of the devices
|
223
|
+
attr : str, optional, default='v'
|
224
|
+
The internal attribute of the property to get.
|
225
|
+
``v`` for values, ``a`` for address, and ``e`` for equation value.
|
226
|
+
value : array-like
|
227
|
+
New values to be set
|
228
|
+
|
229
|
+
Returns
|
230
|
+
-------
|
231
|
+
bool
|
232
|
+
True when successful.
|
233
|
+
"""
|
234
|
+
if self.__dict__[src].owner is not None:
|
235
|
+
# TODO: fit to `_v` type param in the future
|
236
|
+
owner = self.__dict__[src].owner
|
237
|
+
src0 = self.__dict__[src].src
|
238
|
+
try:
|
239
|
+
res = owner.set(src=src0, idx=idx, attr=attr, value=value)
|
240
|
+
return res
|
241
|
+
except KeyError as e:
|
242
|
+
msg = f"Failed to set <{src0}> in <{owner.class_name}>. "
|
243
|
+
msg += f"Original error: {e}"
|
244
|
+
raise KeyError(msg)
|
245
|
+
else:
|
246
|
+
# FIXME: add idx for non-grouped variables
|
247
|
+
raise TypeError(f"Variable {self.name} has no owner.")
|
248
|
+
|
249
|
+
def doc(self, max_width=78, export="plain"):
|
250
|
+
"""
|
251
|
+
Retrieve routine documentation as a string.
|
252
|
+
"""
|
253
|
+
return self.docum.get(max_width=max_width, export=export)
|
254
|
+
|
255
|
+
def _get_off_constrs(self):
|
256
|
+
"""
|
257
|
+
Chcek if constraints are turned off.
|
258
|
+
"""
|
259
|
+
disabled = []
|
260
|
+
for cname, c in self.constrs.items():
|
261
|
+
if c.is_disabled:
|
262
|
+
disabled.append(cname)
|
263
|
+
if len(disabled) > 0:
|
264
|
+
msg = "Disabled constraints: "
|
265
|
+
d_str = [f'{constr}' for constr in disabled]
|
266
|
+
msg += ", ".join(d_str)
|
267
|
+
logger.warning(msg)
|
268
|
+
return disabled
|
269
|
+
|
270
|
+
def _data_check(self, info=True):
|
271
|
+
"""
|
272
|
+
Check if data is valid for a routine.
|
273
|
+
|
274
|
+
Parameters
|
275
|
+
----------
|
276
|
+
info: bool
|
277
|
+
Whether to print warning messages.
|
278
|
+
"""
|
279
|
+
logger.debug(f"Entering data check for <{self.class_name}>")
|
280
|
+
no_input = []
|
281
|
+
owner_list = []
|
282
|
+
for rname, rparam in self.rparams.items():
|
283
|
+
if rparam.owner is not None:
|
284
|
+
# NOTE: skip checking Shunt.g
|
285
|
+
if (rparam.owner.class_name == 'Shunt') and (rparam.src == 'g'):
|
286
|
+
pass
|
287
|
+
elif rparam.owner.n == 0:
|
288
|
+
no_input.append(rname)
|
289
|
+
owner_list.append(rparam.owner.class_name)
|
290
|
+
# TODO: add more data config check?
|
291
|
+
if rparam.config.pos:
|
292
|
+
if not np.all(rparam.v > 0):
|
293
|
+
logger.warning(f"RParam <{rname}> should have all positive values.")
|
294
|
+
if len(no_input) > 0:
|
295
|
+
if info:
|
296
|
+
msg = f"Following models are missing in input: {set(owner_list)}"
|
297
|
+
logger.error(msg)
|
298
|
+
return False
|
299
|
+
# TODO: add data validation for RParam, typical range, etc.
|
300
|
+
logger.debug(" -> Data check passed")
|
301
|
+
return True
|
302
|
+
|
303
|
+
def init(self, **kwargs):
|
304
|
+
"""
|
305
|
+
Initialize the routine.
|
306
|
+
|
307
|
+
Other parameters
|
308
|
+
----------------
|
309
|
+
force: bool
|
310
|
+
Whether to force initialization regardless of the current initialization status.
|
311
|
+
force_mats: bool
|
312
|
+
Whether to force build the system matrices, goes to `self.system.mats.build()`.
|
313
|
+
force_constr: bool
|
314
|
+
Whether to turn on all constraints.
|
315
|
+
force_om: bool
|
316
|
+
Whether to force initialize the optimization model.
|
317
|
+
"""
|
318
|
+
force = kwargs.pop('force', False)
|
319
|
+
force_mats = kwargs.pop('force_mats', False)
|
320
|
+
force_constr = kwargs.pop('force_constr', False)
|
321
|
+
force_om = kwargs.pop('force_om', False)
|
322
|
+
|
323
|
+
skip_all = not (force and force_mats) and self.initialized and self.om.initialized
|
324
|
+
|
325
|
+
if skip_all:
|
326
|
+
logger.debug(f"{self.class_name} has already been initialized.")
|
327
|
+
return True
|
328
|
+
|
329
|
+
t0, _ = elapsed()
|
330
|
+
# --- data check ---
|
331
|
+
self._data_check()
|
332
|
+
|
333
|
+
# --- turn on all constrs ---
|
334
|
+
if force_constr:
|
335
|
+
for constr in self.constrs.values():
|
336
|
+
constr.is_disabled = False
|
337
|
+
|
338
|
+
# --- matrix build ---
|
339
|
+
self.system.mats.build(force=force_mats)
|
340
|
+
|
341
|
+
# --- constraint check ---
|
342
|
+
_ = self._get_off_constrs()
|
343
|
+
|
344
|
+
if not self.om.initialized:
|
345
|
+
self.om.init(force=force_om)
|
346
|
+
_, s_init = elapsed(t0)
|
347
|
+
|
348
|
+
msg = f"<{self.class_name}> "
|
349
|
+
if self.om.initialized:
|
350
|
+
msg += f"initialized in {s_init}."
|
351
|
+
self.initialized = True
|
352
|
+
else:
|
353
|
+
msg += "initialization failed!"
|
354
|
+
self.initialized = False
|
355
|
+
logger.info(msg)
|
356
|
+
return self.initialized
|
357
|
+
|
358
|
+
def solve(self, **kwargs):
|
359
|
+
"""
|
360
|
+
Solve the routine optimization model.
|
361
|
+
"""
|
362
|
+
raise NotImplementedError
|
363
|
+
|
364
|
+
def unpack(self, **kwargs):
|
365
|
+
"""
|
366
|
+
Unpack the results.
|
367
|
+
"""
|
368
|
+
raise NotImplementedError
|
369
|
+
|
370
|
+
def _post_solve(self):
|
371
|
+
"""
|
372
|
+
Post-solve calculation.
|
373
|
+
"""
|
374
|
+
raise NotImplementedError
|
375
|
+
|
376
|
+
def run(self, **kwargs):
|
377
|
+
"""
|
378
|
+
Run the routine.
|
379
|
+
args and kwargs go to `self.solve()`.
|
380
|
+
|
381
|
+
Force initialization (`force_init=True`) will do the following:
|
382
|
+
- Rebuild the system matrices
|
383
|
+
- Enable all constraints
|
384
|
+
- Reinitialize the optimization model
|
385
|
+
|
386
|
+
Parameters
|
387
|
+
----------
|
388
|
+
force_init: bool
|
389
|
+
Whether to force re-initialize the routine.
|
390
|
+
force_mats: bool
|
391
|
+
Whether to force build the system matrices.
|
392
|
+
force_constr: bool
|
393
|
+
Whether to turn on all constraints.
|
394
|
+
force_om: bool
|
395
|
+
Whether to force initialize the OModel.
|
396
|
+
"""
|
397
|
+
# --- setup check ---
|
398
|
+
force_init = kwargs.pop('force_init', False)
|
399
|
+
force_mats = kwargs.pop('force_mats', False)
|
400
|
+
force_constr = kwargs.pop('force_constr', False)
|
401
|
+
force_om = kwargs.pop('force_om', False)
|
402
|
+
self.init(force=force_init, force_mats=force_mats,
|
403
|
+
force_constr=force_constr, force_om=force_om)
|
404
|
+
|
405
|
+
# --- solve optimization ---
|
406
|
+
t0, _ = elapsed()
|
407
|
+
_ = self.solve(**kwargs)
|
408
|
+
status = self.om.prob.status
|
409
|
+
self.exit_code = self.syms.status[status]
|
410
|
+
self.converged = self.exit_code == 0
|
411
|
+
_, s = elapsed(t0)
|
412
|
+
self.exec_time = float(s.split(" ")[0])
|
413
|
+
sstats = self.om.prob.solver_stats # solver stats
|
414
|
+
if sstats.num_iters is None:
|
415
|
+
n_iter = -1
|
416
|
+
else:
|
417
|
+
n_iter = int(sstats.num_iters)
|
418
|
+
n_iter_str = f"{n_iter} iterations " if n_iter > 1 else f"{n_iter} iteration "
|
419
|
+
if self.exit_code == 0:
|
420
|
+
msg = f"<{self.class_name}> solved as {status} in {s}, converged in "
|
421
|
+
msg += n_iter_str + f"with {sstats.solver_name}."
|
422
|
+
logger.warning(msg)
|
423
|
+
self.unpack(**kwargs)
|
424
|
+
self._post_solve()
|
425
|
+
self.system.report()
|
426
|
+
return True
|
427
|
+
else:
|
428
|
+
msg = f"{self.class_name} failed as {status} in "
|
429
|
+
msg += n_iter_str + f"with {sstats.solver_name}!"
|
430
|
+
logger.warning(msg)
|
431
|
+
return False
|
432
|
+
|
433
|
+
def export_csv(self, path=None):
|
434
|
+
"""
|
435
|
+
Export scheduling results to a csv file.
|
436
|
+
For multi-period routines, the column "Time" is the time index of
|
437
|
+
``timeslot.v``, which usually comes from ``EDTSlot`` or ``UCTSlot``.
|
438
|
+
The rest columns are the variables registered in ``vars``.
|
439
|
+
|
440
|
+
For single-period routines, the column "Time" have a pseduo value of "T1".
|
441
|
+
|
442
|
+
Parameters
|
443
|
+
----------
|
444
|
+
path : str
|
445
|
+
path of the csv file to save
|
446
|
+
|
447
|
+
Returns
|
448
|
+
-------
|
449
|
+
export_path
|
450
|
+
The path of the exported csv file
|
451
|
+
"""
|
452
|
+
if not self.converged:
|
453
|
+
logger.warning("Routine did not converge, aborting export.")
|
454
|
+
return None
|
455
|
+
|
456
|
+
if not path:
|
457
|
+
if self.system.files.fullname is None:
|
458
|
+
logger.info("Input file name not detacted. Using `Untitled`.")
|
459
|
+
file_name = f'Untitled_{self.class_name}'
|
460
|
+
else:
|
461
|
+
file_name = os.path.splitext(self.system.files.fullname)[0]
|
462
|
+
file_name += f'_{self.class_name}'
|
463
|
+
path = os.path.join(os.getcwd(), file_name + '.csv')
|
464
|
+
|
465
|
+
data_dict = initialize_data_dict(self)
|
466
|
+
|
467
|
+
collect_data(self, data_dict, self.vars, 'v')
|
468
|
+
collect_data(self, data_dict, self.exprs, 'v')
|
469
|
+
collect_data(self, data_dict, self.exprcs, 'v')
|
470
|
+
|
471
|
+
if 'T1' in data_dict['Time']:
|
472
|
+
data_dict = OrderedDict([(k, [v]) for k, v in data_dict.items()])
|
473
|
+
|
474
|
+
pd.DataFrame(data_dict).to_csv(path, index=False)
|
475
|
+
|
476
|
+
return file_name + '.csv'
|
477
|
+
|
478
|
+
def summary(self, **kwargs):
|
479
|
+
"""
|
480
|
+
Summary interface
|
481
|
+
"""
|
482
|
+
raise NotImplementedError
|
483
|
+
|
484
|
+
def __repr__(self):
|
485
|
+
return f"{self.class_name} at {hex(id(self))}"
|
486
|
+
|
487
|
+
def _ppc2ams(self):
|
488
|
+
"""
|
489
|
+
Convert PYPOWER results to AMS.
|
490
|
+
"""
|
491
|
+
raise NotImplementedError
|
492
|
+
|
493
|
+
def dc2ac(self, **kwargs):
|
494
|
+
"""
|
495
|
+
Convert the DC-based results with ACOPF.
|
496
|
+
"""
|
497
|
+
raise NotImplementedError
|
498
|
+
|
499
|
+
def _check_attribute(self, key, value):
|
500
|
+
"""
|
501
|
+
Check the attribute pair for valid names while instantiating the class.
|
502
|
+
|
503
|
+
This function assigns `owner` to the model itself, assigns the name and tex_name.
|
504
|
+
"""
|
505
|
+
if key in self.__dict__:
|
506
|
+
existing_keys = []
|
507
|
+
for rtn_type in ["constrs", "vars", "rparams", "services"]:
|
508
|
+
if rtn_type in self.__dict__:
|
509
|
+
existing_keys += list(self.__dict__[rtn_type].keys())
|
510
|
+
if key in existing_keys:
|
511
|
+
msg = f"Attribute <{key}> already exists in <{self.class_name}>."
|
512
|
+
logger.warning(msg)
|
513
|
+
|
514
|
+
# register owner routine instance of following attributes
|
515
|
+
if isinstance(value, (RBaseService)):
|
516
|
+
value.rtn = self
|
517
|
+
|
518
|
+
def __setattr__(self, key, value):
|
519
|
+
"""
|
520
|
+
Overload the setattr function to register attributes.
|
521
|
+
|
522
|
+
Parameters
|
523
|
+
----------
|
524
|
+
key: str
|
525
|
+
name of the attribute
|
526
|
+
value:
|
527
|
+
value of the attribute
|
528
|
+
"""
|
529
|
+
|
530
|
+
# NOTE: value.id is not in use yet
|
531
|
+
if isinstance(value, Var):
|
532
|
+
value.id = len(self.vars)
|
533
|
+
self._check_attribute(key, value)
|
534
|
+
self._register_attribute(key, value)
|
535
|
+
|
536
|
+
super(RoutineBase, self).__setattr__(key, value)
|
537
|
+
|
538
|
+
def _register_attribute(self, key, value):
|
539
|
+
"""
|
540
|
+
Register a pair of attributes to the routine instance.
|
541
|
+
|
542
|
+
Called within ``__setattr__``, this is where the magic happens.
|
543
|
+
Subclass attributes are automatically registered based on the variable type.
|
544
|
+
"""
|
545
|
+
if isinstance(value, (Param, Var, Constraint, Objective, ExpressionCalc, Expression)):
|
546
|
+
value.om = self.om
|
547
|
+
value.rtn = self
|
548
|
+
if isinstance(value, Param):
|
549
|
+
self.params[key] = value
|
550
|
+
self.om.params[key] = None # cp.Parameter
|
551
|
+
if isinstance(value, Var):
|
552
|
+
self.vars[key] = value
|
553
|
+
self.om.vars[key] = None # cp.Variable
|
554
|
+
elif isinstance(value, Constraint):
|
555
|
+
self.constrs[key] = value
|
556
|
+
self.om.constrs[key] = None # cp.Constraint
|
557
|
+
elif isinstance(value, Expression):
|
558
|
+
self.exprs[key] = value
|
559
|
+
self.om.exprs[key] = None # cp.Expression
|
560
|
+
elif isinstance(value, ExpressionCalc):
|
561
|
+
self.exprcs[key] = value
|
562
|
+
elif isinstance(value, RParam):
|
563
|
+
self.rparams[key] = value
|
564
|
+
elif isinstance(value, RBaseService):
|
565
|
+
self.services[key] = value
|
566
|
+
|
567
|
+
def update(self, params=None, build_mats=False):
|
568
|
+
"""
|
569
|
+
Update the values of Parameters in the optimization model.
|
570
|
+
|
571
|
+
This method is particularly important when some `RParams` are
|
572
|
+
linked with system matrices.
|
573
|
+
In such cases, setting `build_mats=True` is necessary to rebuild
|
574
|
+
these matrices for the changes to take effect.
|
575
|
+
This is common in scenarios involving topology changes, connection statuses,
|
576
|
+
or load value modifications.
|
577
|
+
If unsure, it is advisable to use `build_mats=True` as a precautionary measure.
|
578
|
+
|
579
|
+
Parameters
|
580
|
+
----------
|
581
|
+
params: Parameter, str, or list
|
582
|
+
Parameter, Parameter name, or a list of parameter names to be updated.
|
583
|
+
If None, all parameters will be updated.
|
584
|
+
build_mats: bool
|
585
|
+
True to rebuild the system matrices. Set to False to speed up the process
|
586
|
+
if no system matrices are changed.
|
587
|
+
"""
|
588
|
+
t0, _ = elapsed()
|
589
|
+
re_finalize = False
|
590
|
+
# sanitize input
|
591
|
+
sparams = []
|
592
|
+
if params is None:
|
593
|
+
sparams = [val for val in self.params.values()]
|
594
|
+
build_mats = True
|
595
|
+
elif isinstance(params, Param):
|
596
|
+
sparams = [params]
|
597
|
+
elif isinstance(params, str):
|
598
|
+
sparams = [self.params[params]]
|
599
|
+
elif isinstance(params, list):
|
600
|
+
sparams = [self.params[param] for param in params if isinstance(param, str)]
|
601
|
+
for param in sparams:
|
602
|
+
param.update()
|
603
|
+
|
604
|
+
for param in sparams:
|
605
|
+
if param.optz is None: # means no_parse=True
|
606
|
+
re_finalize = True
|
607
|
+
break
|
608
|
+
|
609
|
+
self.system.mats.build(force=build_mats)
|
610
|
+
|
611
|
+
if re_finalize:
|
612
|
+
logger.warning(f"<{self.class_name}> reinit OModel due to non-parametric change.")
|
613
|
+
self.om.evaluate(force=True)
|
614
|
+
self.om.finalize(force=True)
|
615
|
+
|
616
|
+
results = self.om.update(params=sparams)
|
617
|
+
t0, s0 = elapsed(t0)
|
618
|
+
logger.debug(f"Update params in {s0}.")
|
619
|
+
return results
|
620
|
+
|
621
|
+
def __delattr__(self, name):
|
622
|
+
"""
|
623
|
+
Overload the delattr function to unregister attributes.
|
624
|
+
|
625
|
+
Parameters
|
626
|
+
----------
|
627
|
+
name: str
|
628
|
+
name of the attribute
|
629
|
+
"""
|
630
|
+
self._unregister_attribute(name)
|
631
|
+
if name == "obj":
|
632
|
+
self.obj = None
|
633
|
+
else:
|
634
|
+
super().__delattr__(name) # Call the superclass implementation
|
635
|
+
|
636
|
+
def _unregister_attribute(self, name):
|
637
|
+
"""
|
638
|
+
Unregister a pair of attributes from the routine instance.
|
639
|
+
|
640
|
+
Called within ``__delattr__``, this is where the magic happens.
|
641
|
+
Subclass attributes are automatically unregistered based on the variable type.
|
642
|
+
"""
|
643
|
+
if name in self.vars:
|
644
|
+
del self.vars[name]
|
645
|
+
if name in self.om.vars:
|
646
|
+
del self.om.vars[name]
|
647
|
+
elif name in self.rparams:
|
648
|
+
del self.rparams[name]
|
649
|
+
elif name in self.constrs:
|
650
|
+
del self.constrs[name]
|
651
|
+
if name in self.om.constrs:
|
652
|
+
del self.om.constrs[name]
|
653
|
+
elif name in self.services:
|
654
|
+
del self.services[name]
|
655
|
+
|
656
|
+
def enable(self, name):
|
657
|
+
"""
|
658
|
+
Enable a constraint by name.
|
659
|
+
|
660
|
+
Parameters
|
661
|
+
----------
|
662
|
+
name: str or list
|
663
|
+
name of the constraint to be enabled
|
664
|
+
"""
|
665
|
+
if isinstance(name, list):
|
666
|
+
constr_act = []
|
667
|
+
for n in name:
|
668
|
+
if n not in self.constrs:
|
669
|
+
logger.warning(f"Constraint <{n}> not found.")
|
670
|
+
continue
|
671
|
+
if not self.constrs[n].is_disabled:
|
672
|
+
logger.warning(f"Constraint <{n}> has already been enabled.")
|
673
|
+
continue
|
674
|
+
self.constrs[n].is_disabled = False
|
675
|
+
self.om.finalized = False
|
676
|
+
constr_act.append(n)
|
677
|
+
if len(constr_act) > 0:
|
678
|
+
msg = ", ".join(constr_act)
|
679
|
+
logger.warning(f"Turn on constraints: {msg}")
|
680
|
+
return True
|
681
|
+
|
682
|
+
if name in self.constrs:
|
683
|
+
if not self.constrs[name].is_disabled:
|
684
|
+
logger.warning(f"Constraint <{name}> has already been enabled.")
|
685
|
+
else:
|
686
|
+
self.constrs[name].is_disabled = False
|
687
|
+
self.om.finalized = False
|
688
|
+
logger.warning(f"Turn on constraint <{name}>.")
|
689
|
+
return True
|
690
|
+
|
691
|
+
def disable(self, name):
|
692
|
+
"""
|
693
|
+
Disable a constraint by name.
|
694
|
+
|
695
|
+
Parameters
|
696
|
+
----------
|
697
|
+
name: str or list
|
698
|
+
name of the constraint to be disabled
|
699
|
+
"""
|
700
|
+
if isinstance(name, list):
|
701
|
+
constr_act = []
|
702
|
+
for n in name:
|
703
|
+
if n not in self.constrs:
|
704
|
+
logger.warning(f"Constraint <{n}> not found.")
|
705
|
+
elif self.constrs[n].is_disabled:
|
706
|
+
logger.warning(f"Constraint <{n}> has already been disabled.")
|
707
|
+
else:
|
708
|
+
self.constrs[n].is_disabled = True
|
709
|
+
self.om.finalized = False
|
710
|
+
constr_act.append(n)
|
711
|
+
if len(constr_act) > 0:
|
712
|
+
msg = ", ".join(constr_act)
|
713
|
+
logger.warning(f"Turn off constraints: {msg}")
|
714
|
+
return True
|
715
|
+
|
716
|
+
if name in self.constrs:
|
717
|
+
if self.constrs[name].is_disabled:
|
718
|
+
logger.warning(f"Constraint <{name}> has already been disabled.")
|
719
|
+
else:
|
720
|
+
self.constrs[name].is_disabled = True
|
721
|
+
self.om.finalized = False
|
722
|
+
logger.warning(f"Turn off constraint <{name}>.")
|
723
|
+
return True
|
724
|
+
|
725
|
+
logger.warning(f"Constraint <{name}> not found.")
|
726
|
+
|
727
|
+
def _post_add_check(self):
|
728
|
+
"""
|
729
|
+
Post-addition check.
|
730
|
+
"""
|
731
|
+
# --- reset routine status ---
|
732
|
+
self.initialized = False
|
733
|
+
self.exec_time = 0.0
|
734
|
+
self.exit_code = 0
|
735
|
+
# --- reset symprocessor status ---
|
736
|
+
self._syms = False
|
737
|
+
# --- reset optimization model status ---
|
738
|
+
self.om.parsed = False
|
739
|
+
self.om.evaluated = False
|
740
|
+
self.om.finalized = False
|
741
|
+
# --- reset OModel parser status ---
|
742
|
+
self.om.parsed = False
|
743
|
+
|
744
|
+
def addRParam(self,
|
745
|
+
name: str,
|
746
|
+
tex_name: Optional[str] = None,
|
747
|
+
info: Optional[str] = None,
|
748
|
+
src: Optional[str] = None,
|
749
|
+
unit: Optional[str] = None,
|
750
|
+
model: Optional[str] = None,
|
751
|
+
v: Optional[np.ndarray] = None,
|
752
|
+
indexer: Optional[str] = None,
|
753
|
+
imodel: Optional[str] = None,):
|
754
|
+
"""
|
755
|
+
Add `RParam` to the routine.
|
756
|
+
|
757
|
+
Parameters
|
758
|
+
----------
|
759
|
+
name : str
|
760
|
+
Name of this parameter. If not provided, `name` will be set
|
761
|
+
to the attribute name.
|
762
|
+
tex_name : str, optional
|
763
|
+
LaTeX-formatted parameter name. If not provided, `tex_name`
|
764
|
+
will be assigned the same as `name`.
|
765
|
+
info : str, optional
|
766
|
+
A description of this parameter
|
767
|
+
src : str, optional
|
768
|
+
Source name of the parameter.
|
769
|
+
unit : str, optional
|
770
|
+
Unit of the parameter.
|
771
|
+
model : str, optional
|
772
|
+
Name of the owner model or group.
|
773
|
+
v : np.ndarray, optional
|
774
|
+
External value of the parameter.
|
775
|
+
indexer : str, optional
|
776
|
+
Indexer of the parameter.
|
777
|
+
imodel : str, optional
|
778
|
+
Name of the owner model or group of the indexer.
|
779
|
+
"""
|
780
|
+
item = RParam(name=name, tex_name=tex_name, info=info, src=src, unit=unit,
|
781
|
+
model=model, v=v, indexer=indexer, imodel=imodel)
|
782
|
+
|
783
|
+
# add the parameter as an routine attribute
|
784
|
+
setattr(self, name, item)
|
785
|
+
|
786
|
+
# NOTE: manually register the owner of the parameter
|
787
|
+
# This is skipped in ``addVars`` because of ``Var.__setattr__``
|
788
|
+
item.rtn = self
|
789
|
+
|
790
|
+
# check variable owner validity if given
|
791
|
+
if model is not None:
|
792
|
+
if item.model in self.system.groups.keys():
|
793
|
+
item.is_group = True
|
794
|
+
item.owner = self.system.groups[item.model]
|
795
|
+
elif item.model in self.system.models.keys():
|
796
|
+
item.owner = self.system.models[item.model]
|
797
|
+
else:
|
798
|
+
msg = f'Model indicator \'{item.model}\' of <{item.rtn.class_name}.{name}>'
|
799
|
+
msg += ' is not a model or group. Likely a modeling error.'
|
800
|
+
logger.warning(msg)
|
801
|
+
|
802
|
+
self._post_add_check()
|
803
|
+
return item
|
804
|
+
|
805
|
+
def addService(self,
|
806
|
+
name: str,
|
807
|
+
value: np.ndarray,
|
808
|
+
tex_name: str = None,
|
809
|
+
unit: str = None,
|
810
|
+
info: str = None,
|
811
|
+
vtype: Type = None,):
|
812
|
+
"""
|
813
|
+
Add `ValueService` to the routine.
|
814
|
+
|
815
|
+
Parameters
|
816
|
+
----------
|
817
|
+
name : str
|
818
|
+
Instance name.
|
819
|
+
value : np.ndarray
|
820
|
+
Value.
|
821
|
+
tex_name : str, optional
|
822
|
+
TeX name.
|
823
|
+
unit : str, optional
|
824
|
+
Unit.
|
825
|
+
info : str, optional
|
826
|
+
Description.
|
827
|
+
vtype : Type, optional
|
828
|
+
Variable type.
|
829
|
+
"""
|
830
|
+
item = ValueService(name=name, tex_name=tex_name,
|
831
|
+
unit=unit, info=info,
|
832
|
+
vtype=vtype, value=value)
|
833
|
+
# add the service as an routine attribute
|
834
|
+
setattr(self, name, item)
|
835
|
+
|
836
|
+
self._post_add_check()
|
837
|
+
|
838
|
+
return item
|
839
|
+
|
840
|
+
def addConstrs(self,
|
841
|
+
name: str,
|
842
|
+
e_str: str,
|
843
|
+
info: Optional[str] = None,
|
844
|
+
is_eq: Optional[str] = False,):
|
845
|
+
"""
|
846
|
+
Add `Constraint` to the routine. to the routine.
|
847
|
+
|
848
|
+
Parameters
|
849
|
+
----------
|
850
|
+
name : str
|
851
|
+
Constraint name. One should typically assigning the name directly because
|
852
|
+
it will be automatically assigned by the model. The value of ``name``
|
853
|
+
will be the symbol name to be used in expressions.
|
854
|
+
e_str : str
|
855
|
+
Constraint expression string.
|
856
|
+
info : str, optional
|
857
|
+
Descriptive information
|
858
|
+
is_eq : str, optional
|
859
|
+
Flag indicating if the constraint is an equality constraint. False indicates
|
860
|
+
an inequality constraint in the form of `<= 0`.
|
861
|
+
"""
|
862
|
+
item = Constraint(name=name, e_str=e_str, info=info, is_eq=is_eq)
|
863
|
+
# add the constraint as an routine attribute
|
864
|
+
setattr(self, name, item)
|
865
|
+
|
866
|
+
self._post_add_check()
|
867
|
+
|
868
|
+
return item
|
869
|
+
|
870
|
+
def addVars(self,
|
871
|
+
name: str,
|
872
|
+
model: Optional[str] = None,
|
873
|
+
shape: Optional[Union[int, tuple]] = None,
|
874
|
+
tex_name: Optional[str] = None,
|
875
|
+
info: Optional[str] = None,
|
876
|
+
src: Optional[str] = None,
|
877
|
+
unit: Optional[str] = None,
|
878
|
+
horizon: Optional[RParam] = None,
|
879
|
+
nonneg: Optional[bool] = False,
|
880
|
+
nonpos: Optional[bool] = False,
|
881
|
+
cplx: Optional[bool] = False,
|
882
|
+
imag: Optional[bool] = False,
|
883
|
+
symmetric: Optional[bool] = False,
|
884
|
+
diag: Optional[bool] = False,
|
885
|
+
psd: Optional[bool] = False,
|
886
|
+
nsd: Optional[bool] = False,
|
887
|
+
hermitian: Optional[bool] = False,
|
888
|
+
boolean: Optional[bool] = False,
|
889
|
+
integer: Optional[bool] = False,
|
890
|
+
pos: Optional[bool] = False,
|
891
|
+
neg: Optional[bool] = False,):
|
892
|
+
"""
|
893
|
+
Add a variable to the routine.
|
894
|
+
|
895
|
+
Parameters
|
896
|
+
----------
|
897
|
+
name : str, optional
|
898
|
+
Variable name. One should typically assigning the name directly because
|
899
|
+
it will be automatically assigned by the model. The value of ``name``
|
900
|
+
will be the symbol name to be used in expressions.
|
901
|
+
model : str, optional
|
902
|
+
Name of the owner model or group.
|
903
|
+
shape : int or tuple, optional
|
904
|
+
Shape of the variable. If is None, the shape of `model` will be used.
|
905
|
+
info : str, optional
|
906
|
+
Descriptive information
|
907
|
+
unit : str, optional
|
908
|
+
Unit
|
909
|
+
tex_name : str
|
910
|
+
LaTeX-formatted variable symbol. If is None, the value of `name` will be
|
911
|
+
used.
|
912
|
+
src : str, optional
|
913
|
+
Source variable name. If is None, the value of `name` will be used.
|
914
|
+
lb : str, optional
|
915
|
+
Lower bound
|
916
|
+
ub : str, optional
|
917
|
+
Upper bound
|
918
|
+
horizon : ams.routines.RParam, optional
|
919
|
+
Horizon idx.
|
920
|
+
nonneg : bool, optional
|
921
|
+
Non-negative variable
|
922
|
+
nonpos : bool, optional
|
923
|
+
Non-positive variable
|
924
|
+
cplx : bool, optional
|
925
|
+
Complex variable
|
926
|
+
imag : bool, optional
|
927
|
+
Imaginary variable
|
928
|
+
symmetric : bool, optional
|
929
|
+
Symmetric variable
|
930
|
+
diag : bool, optional
|
931
|
+
Diagonal variable
|
932
|
+
psd : bool, optional
|
933
|
+
Positive semi-definite variable
|
934
|
+
nsd : bool, optional
|
935
|
+
Negative semi-definite variable
|
936
|
+
hermitian : bool, optional
|
937
|
+
Hermitian variable
|
938
|
+
bool : bool, optional
|
939
|
+
Boolean variable
|
940
|
+
integer : bool, optional
|
941
|
+
Integer variable
|
942
|
+
pos : bool, optional
|
943
|
+
Positive variable
|
944
|
+
neg : bool, optional
|
945
|
+
Negative variable
|
946
|
+
"""
|
947
|
+
if model is None and shape is None:
|
948
|
+
raise ValueError("Either model or shape must be specified.")
|
949
|
+
item = Var(name=name, tex_name=tex_name,
|
950
|
+
info=info, src=src, unit=unit,
|
951
|
+
model=model, shape=shape, horizon=horizon,
|
952
|
+
nonneg=nonneg, nonpos=nonpos,
|
953
|
+
cplx=cplx, imag=imag,
|
954
|
+
symmetric=symmetric, diag=diag,
|
955
|
+
psd=psd, nsd=nsd, hermitian=hermitian,
|
956
|
+
boolean=boolean, integer=integer,
|
957
|
+
pos=pos, neg=neg, )
|
958
|
+
|
959
|
+
# add the variable as an routine attribute
|
960
|
+
setattr(self, name, item)
|
961
|
+
|
962
|
+
# check variable owner validity if given
|
963
|
+
if model is not None:
|
964
|
+
if item.model in self.system.groups.keys():
|
965
|
+
item.is_group = True
|
966
|
+
item.owner = self.system.groups[item.model]
|
967
|
+
elif item.model in self.system.models.keys():
|
968
|
+
item.owner = self.system.models[item.model]
|
969
|
+
else:
|
970
|
+
msg = (
|
971
|
+
f"Model indicator '{item.model}' of <{item.rtn.class_name}.{name}>"
|
972
|
+
)
|
973
|
+
msg += " is not a model or group. Likely a modeling error."
|
974
|
+
logger.warning(msg)
|
975
|
+
|
976
|
+
self._post_add_check()
|
977
|
+
|
978
|
+
return item
|
979
|
+
|
980
|
+
def _initial_guess(self):
|
981
|
+
"""
|
982
|
+
Generate initial guess for the optimization model.
|
983
|
+
"""
|
984
|
+
raise NotImplementedError
|
985
|
+
|
986
|
+
|
987
|
+
def initialize_data_dict(rtn: RoutineBase):
|
988
|
+
"""
|
989
|
+
Initialize the data dictionary for export.
|
990
|
+
|
991
|
+
Parameters
|
992
|
+
----------
|
993
|
+
rtn : ams.routines.routine.RoutineBase
|
994
|
+
The routine to collect data from
|
995
|
+
|
996
|
+
Returns
|
997
|
+
-------
|
998
|
+
OrderedDict
|
999
|
+
The initialized data dictionary.
|
1000
|
+
"""
|
1001
|
+
if hasattr(rtn, 'timeslot'):
|
1002
|
+
timeslot = rtn.timeslot.v.copy()
|
1003
|
+
return OrderedDict([('Time', timeslot)])
|
1004
|
+
else:
|
1005
|
+
return OrderedDict([('Time', 'T1')])
|
1006
|
+
|
1007
|
+
|
1008
|
+
def collect_data(rtn: RoutineBase, data_dict: Dict, items: Dict, attr: str):
|
1009
|
+
"""
|
1010
|
+
Collect data for export.
|
1011
|
+
|
1012
|
+
Parameters
|
1013
|
+
----------
|
1014
|
+
rtn : ams.routines.routine.RoutineBase
|
1015
|
+
The routine to collect data from.
|
1016
|
+
data_dict : OrderedDict
|
1017
|
+
The data dictionary to populate.
|
1018
|
+
items : dict
|
1019
|
+
Dictionary of items to collect data from.
|
1020
|
+
attr : str
|
1021
|
+
Attribute to collect data for.
|
1022
|
+
"""
|
1023
|
+
for key, item in items.items():
|
1024
|
+
if item.owner is None:
|
1025
|
+
continue
|
1026
|
+
idx_v = item.get_all_idxes()
|
1027
|
+
try:
|
1028
|
+
data_v = rtn.get(src=key, attr=attr, idx=idx_v,
|
1029
|
+
horizon=rtn.timeslot.v if hasattr(rtn, 'timeslot') else None).round(6)
|
1030
|
+
except Exception as e:
|
1031
|
+
logger.debug(f"Error with collecting data for '{key}': {e}")
|
1032
|
+
data_v = [np.nan] * len(idx_v)
|
1033
|
+
data_dict.update(OrderedDict(zip([f'{key} {dev}' for dev in idx_v], data_v)))
|