ltbams 0.9.9__py3-none-any.whl → 1.0.2__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 +206 -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 +231 -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.2.dist-info/METADATA +215 -0
- ltbams-1.0.2.dist-info/RECORD +188 -0
- {ltbams-0.9.9.dist-info → ltbams-1.0.2.dist-info}/WHEEL +1 -1
- ltbams-1.0.2.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.2.dist-info}/entry_points.txt +0 -0
ams/core/model.py
ADDED
@@ -0,0 +1,330 @@
|
|
1
|
+
"""
|
2
|
+
Module for Model class.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from collections import OrderedDict
|
7
|
+
from typing import Iterable
|
8
|
+
|
9
|
+
import numpy as np
|
10
|
+
from andes.core.common import Config
|
11
|
+
from andes.core.param import ExtParam
|
12
|
+
from andes.core.service import BaseService, BackRef
|
13
|
+
from andes.utils.func import list_flatten
|
14
|
+
|
15
|
+
from ams.core.documenter import Documenter
|
16
|
+
from ams.core.var import Algeb
|
17
|
+
|
18
|
+
from ams.utils.misc import deprec_get_idx
|
19
|
+
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
|
23
|
+
class Model:
|
24
|
+
"""
|
25
|
+
Base class for power system scheduling models.
|
26
|
+
|
27
|
+
This class is revised from ``andes.core.model.Model``.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self, system=None, config=None):
|
31
|
+
|
32
|
+
# --- Model ---
|
33
|
+
self.system = system
|
34
|
+
self.group = 'Undefined'
|
35
|
+
|
36
|
+
self.algebs = OrderedDict() # internal algebraic variables
|
37
|
+
self.vars_decl_order = OrderedDict() # variable in the order of declaration
|
38
|
+
|
39
|
+
self.params_ext = OrderedDict() # external parameters
|
40
|
+
|
41
|
+
self.services = OrderedDict() # service/temporary variables
|
42
|
+
self.services_ref = OrderedDict() # BackRefs
|
43
|
+
self.services_sum = OrderedDict() # VarSums
|
44
|
+
|
45
|
+
self.config = Config(name=self.class_name) # `config` that can be exported
|
46
|
+
if config is not None:
|
47
|
+
self.config.load(config)
|
48
|
+
|
49
|
+
# basic configs
|
50
|
+
self.config.add(OrderedDict((('allow_adjust', 1),
|
51
|
+
('adjust_lower', 0),
|
52
|
+
('adjust_upper', 1),
|
53
|
+
)))
|
54
|
+
self.docum = Documenter(self)
|
55
|
+
|
56
|
+
def _all_vars(self):
|
57
|
+
"""
|
58
|
+
An OrderedDict of States, ExtStates, Algebs, ExtAlgebs
|
59
|
+
"""
|
60
|
+
return OrderedDict(list(self.algebs.items()))
|
61
|
+
|
62
|
+
def _check_attribute(self, key, value):
|
63
|
+
"""
|
64
|
+
Check the attribute pair for valid names while instantiating the class.
|
65
|
+
|
66
|
+
This function assigns `owner` to the model itself, assigns the name and tex_name.
|
67
|
+
"""
|
68
|
+
if isinstance(value, (Algeb, BaseService)):
|
69
|
+
if not value.owner:
|
70
|
+
value.owner = self
|
71
|
+
if not value.name:
|
72
|
+
value.name = key
|
73
|
+
if not value.tex_name:
|
74
|
+
value.tex_name = key
|
75
|
+
if key in self.__dict__:
|
76
|
+
logger.warning(f"{self.class_name}: redefinition of member <{key}>. Likely a modeling error.")
|
77
|
+
|
78
|
+
def __setattr__(self, key, value):
|
79
|
+
"""
|
80
|
+
Overload the setattr function to register attributes.
|
81
|
+
|
82
|
+
Parameters
|
83
|
+
----------
|
84
|
+
key : str
|
85
|
+
name of the attribute
|
86
|
+
value : [Algeb]
|
87
|
+
value of the attribute
|
88
|
+
"""
|
89
|
+
self._check_attribute(key, value)
|
90
|
+
self._register_attribute(key, value)
|
91
|
+
|
92
|
+
super(Model, self).__setattr__(key, value)
|
93
|
+
|
94
|
+
def _register_attribute(self, key, value):
|
95
|
+
"""
|
96
|
+
Register a pair of attributes to the model instance.
|
97
|
+
|
98
|
+
Called within ``__setattr__``, this is where the magic happens.
|
99
|
+
Subclass attributes are automatically registered based on the variable type.
|
100
|
+
Block attributes will be exported and registered recursively.
|
101
|
+
"""
|
102
|
+
if isinstance(value, Algeb):
|
103
|
+
self.algebs[key] = value
|
104
|
+
elif isinstance(value, ExtParam):
|
105
|
+
self.params_ext[key] = value
|
106
|
+
elif isinstance(value, BackRef):
|
107
|
+
self.services_ref[key] = value
|
108
|
+
self.services[key] = value
|
109
|
+
|
110
|
+
@property
|
111
|
+
def class_name(self):
|
112
|
+
"""
|
113
|
+
Return the class name
|
114
|
+
"""
|
115
|
+
return self.__class__.__name__
|
116
|
+
|
117
|
+
def list2array(self):
|
118
|
+
"""
|
119
|
+
Convert all the value attributes ``v`` to NumPy arrays.
|
120
|
+
|
121
|
+
Value attribute arrays should remain in the same address afterwards.
|
122
|
+
Namely, all assignments to value array should be operated in place (e.g., with [:]).
|
123
|
+
"""
|
124
|
+
|
125
|
+
for instance in self.num_params.values():
|
126
|
+
instance.to_array()
|
127
|
+
|
128
|
+
def set_backref(self, name, from_idx, to_idx):
|
129
|
+
"""
|
130
|
+
Helper function for setting idx-es to ``BackRef``.
|
131
|
+
"""
|
132
|
+
|
133
|
+
if name not in self.services_ref:
|
134
|
+
return
|
135
|
+
|
136
|
+
uid = self.idx2uid(to_idx)
|
137
|
+
self.services_ref[name].v[uid].append(from_idx)
|
138
|
+
|
139
|
+
def get(self, src: str, idx, attr: str = 'v', allow_none=False, default=0.0):
|
140
|
+
"""
|
141
|
+
Get the value of an attribute of a model property.
|
142
|
+
|
143
|
+
The return value is ``self.<src>.<attr>[idx]``
|
144
|
+
|
145
|
+
Parameters
|
146
|
+
----------
|
147
|
+
src : str
|
148
|
+
Name of the model property
|
149
|
+
idx : str, int, float, array-like
|
150
|
+
Indices of the devices
|
151
|
+
attr : str, optional, default='v'
|
152
|
+
The attribute of the property to get.
|
153
|
+
``v`` for values, ``a`` for address, and ``e`` for equation value.
|
154
|
+
allow_none : bool
|
155
|
+
True to allow None values in the indexer
|
156
|
+
default : float
|
157
|
+
If `allow_none` is true, the default value to use for None indexer.
|
158
|
+
|
159
|
+
Returns
|
160
|
+
-------
|
161
|
+
array-like
|
162
|
+
``self.<src>.<attr>[idx]``
|
163
|
+
|
164
|
+
"""
|
165
|
+
uid = self.idx2uid(idx)
|
166
|
+
if isinstance(self.__dict__[src].__dict__[attr], list):
|
167
|
+
if isinstance(uid, Iterable):
|
168
|
+
if not allow_none and (uid is None or None in uid):
|
169
|
+
raise KeyError('None not allowed in uid/idx. Enable through '
|
170
|
+
'`allow_none` and provide a `default` if needed.')
|
171
|
+
return [self.__dict__[src].__dict__[attr][i] if i is not None else default
|
172
|
+
for i in uid]
|
173
|
+
# FIXME: this seems to be an unexpected case originted from ANDES
|
174
|
+
if isinstance(uid, Iterable):
|
175
|
+
if None in uid:
|
176
|
+
return [self.__dict__[src].__dict__[attr][i] if i is not None else default
|
177
|
+
for i in uid]
|
178
|
+
return self.__dict__[src].__dict__[attr][uid]
|
179
|
+
|
180
|
+
def set(self, src, idx, attr, value):
|
181
|
+
"""
|
182
|
+
Set the value of an attribute of a model property.
|
183
|
+
|
184
|
+
Performs ``self.<src>.<attr>[idx] = value``. This method will not modify
|
185
|
+
the input values from the case file that have not been converted to the
|
186
|
+
system base. As a result, changes applied by this method will not affect
|
187
|
+
the dumped case file.
|
188
|
+
|
189
|
+
To alter parameters and reflect it in the case file, use :meth:`alter`
|
190
|
+
instead.
|
191
|
+
|
192
|
+
Parameters
|
193
|
+
----------
|
194
|
+
src : str
|
195
|
+
Name of the model property
|
196
|
+
idx : str, int, float, array-like
|
197
|
+
Indices of the devices
|
198
|
+
attr : str, optional, default='v'
|
199
|
+
The internal attribute of the property to get.
|
200
|
+
``v`` for values, ``a`` for address, and ``e`` for equation value.
|
201
|
+
value : array-like
|
202
|
+
New values to be set
|
203
|
+
|
204
|
+
Returns
|
205
|
+
-------
|
206
|
+
bool
|
207
|
+
True when successful.
|
208
|
+
"""
|
209
|
+
uid = self.idx2uid(idx)
|
210
|
+
self.__dict__[src].__dict__[attr][uid] = value
|
211
|
+
return True
|
212
|
+
|
213
|
+
def alter(self, src, idx, value, attr='v'):
|
214
|
+
"""
|
215
|
+
Alter values of input parameters or constant service.
|
216
|
+
|
217
|
+
If the method operates on an input parameter, the new data should be in
|
218
|
+
the same base as that in the input file. This function will convert
|
219
|
+
``value`` to per unit in the system base whenever necessary.
|
220
|
+
|
221
|
+
The values for storing the input data, i.e., the parameter's ``vin``
|
222
|
+
field, will be overwritten. As a result, altered values will be
|
223
|
+
reflected in the dumped case file.
|
224
|
+
|
225
|
+
Parameters
|
226
|
+
----------
|
227
|
+
src : str
|
228
|
+
The parameter name to alter
|
229
|
+
idx : str, float, int
|
230
|
+
The device to alter
|
231
|
+
value : float
|
232
|
+
The desired value
|
233
|
+
attr : str
|
234
|
+
The attribute to alter, default is 'v'.
|
235
|
+
|
236
|
+
Notes
|
237
|
+
-----
|
238
|
+
New in version 0.9.14: Added the signature `attr` to alter specific attributes.
|
239
|
+
This feature is useful when you need to manipulate parameter values in the system
|
240
|
+
base and ensure that these changes are reflected in the dumped case file.
|
241
|
+
"""
|
242
|
+
|
243
|
+
instance = self.__dict__[src]
|
244
|
+
|
245
|
+
if hasattr(instance, 'vin') and (instance.vin is not None):
|
246
|
+
uid = self.idx2uid(idx)
|
247
|
+
if attr == 'vin':
|
248
|
+
self.set(src, idx, 'vin', value / instance.pu_coeff[uid])
|
249
|
+
self.set(src, idx, 'v', value=value)
|
250
|
+
else:
|
251
|
+
self.set(src, idx, 'vin', value)
|
252
|
+
self.set(src, idx, 'v', value * instance.pu_coeff[uid])
|
253
|
+
elif not hasattr(instance, 'vin') and attr == 'vin':
|
254
|
+
logger.warning(f"{self.class_name}.{src} has no `vin` attribute, changing `v`.")
|
255
|
+
self.set(src, idx, 'v', value)
|
256
|
+
else:
|
257
|
+
self.set(src, idx, attr=attr, value=value)
|
258
|
+
|
259
|
+
def idx2uid(self, idx):
|
260
|
+
"""
|
261
|
+
Convert idx to the 0-indexed unique index.
|
262
|
+
|
263
|
+
Parameters
|
264
|
+
----------
|
265
|
+
idx : array-like, numbers, or str
|
266
|
+
idx of devices
|
267
|
+
|
268
|
+
Returns
|
269
|
+
-------
|
270
|
+
list
|
271
|
+
A list containing the unique indices of the devices
|
272
|
+
"""
|
273
|
+
if idx is None:
|
274
|
+
logger.debug("idx2uid returned None for idx None")
|
275
|
+
return None
|
276
|
+
if isinstance(idx, (float, int, str, np.integer, np.floating)):
|
277
|
+
return self._one_idx2uid(idx)
|
278
|
+
elif isinstance(idx, Iterable):
|
279
|
+
if len(idx) > 0 and isinstance(idx[0], (list, np.ndarray)):
|
280
|
+
idx = list_flatten(idx)
|
281
|
+
return [self._one_idx2uid(i) if i is not None else None
|
282
|
+
for i in idx]
|
283
|
+
else:
|
284
|
+
raise NotImplementedError(f'Unknown idx type {type(idx)}')
|
285
|
+
|
286
|
+
def _one_idx2uid(self, idx):
|
287
|
+
"""
|
288
|
+
Helper function for checking if an idx exists and
|
289
|
+
converting it to uid.
|
290
|
+
"""
|
291
|
+
|
292
|
+
if idx not in self.uid:
|
293
|
+
raise KeyError("<%s>: device not exist with idx=%s." %
|
294
|
+
(self.class_name, idx))
|
295
|
+
|
296
|
+
return self.uid[idx]
|
297
|
+
|
298
|
+
def doc(self, max_width=78, export='plain'):
|
299
|
+
"""
|
300
|
+
Retrieve model documentation as a string.
|
301
|
+
"""
|
302
|
+
return self.docum.get(max_width=max_width, export=export)
|
303
|
+
|
304
|
+
@deprec_get_idx
|
305
|
+
def get_idx(self):
|
306
|
+
"""
|
307
|
+
Return the index of the model instance.
|
308
|
+
Equivalent to ``self.idx.v``, develoepd for consistency with group method
|
309
|
+
``get_idx``.
|
310
|
+
"""
|
311
|
+
return self.idx.v
|
312
|
+
|
313
|
+
def get_all_idxes(self):
|
314
|
+
"""
|
315
|
+
Return all the indexes of this model.
|
316
|
+
|
317
|
+
.. note::
|
318
|
+
New in version 1.0.0. Add to follow the group method ``get_all_idxes``.
|
319
|
+
|
320
|
+
Returns
|
321
|
+
-------
|
322
|
+
list
|
323
|
+
A list of indexes.
|
324
|
+
"""
|
325
|
+
return self.idx.v
|
326
|
+
|
327
|
+
def __repr__(self):
|
328
|
+
dev_text = 'device' if self.n == 1 else 'devices'
|
329
|
+
|
330
|
+
return f'{self.class_name} ({self.n} {dev_text}) at {hex(id(self))}'
|
ams/core/param.py
ADDED
@@ -0,0 +1,322 @@
|
|
1
|
+
"""
|
2
|
+
Base class for parameters.
|
3
|
+
"""
|
4
|
+
|
5
|
+
|
6
|
+
import logging
|
7
|
+
|
8
|
+
from typing import Optional, Iterable
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
from scipy.sparse import issparse
|
12
|
+
|
13
|
+
from ams.opt import Param
|
14
|
+
|
15
|
+
from ams.utils.misc import deprec_get_idx
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
class RParam(Param):
|
21
|
+
"""
|
22
|
+
Class for parameters used in a routine.
|
23
|
+
This class is developed to simplify the routine definition.
|
24
|
+
|
25
|
+
`RParm` is further used to define `Parameter` in the optimization model.
|
26
|
+
|
27
|
+
`no_parse` is used to skip parsing the `RParam` in optimization
|
28
|
+
model.
|
29
|
+
It means that the `RParam` will not be added to the optimization model.
|
30
|
+
This is useful when the RParam contains non-numeric values,
|
31
|
+
or it is not necessary to be added to the optimization model.
|
32
|
+
|
33
|
+
Parameters
|
34
|
+
----------
|
35
|
+
name : str, optional
|
36
|
+
Name of this parameter. If not provided, `name` will be set
|
37
|
+
to the attribute name.
|
38
|
+
tex_name : str, optional
|
39
|
+
LaTeX-formatted parameter name. If not provided, `tex_name`
|
40
|
+
will be assigned the same as `name`.
|
41
|
+
info : str, optional
|
42
|
+
A description of this parameter
|
43
|
+
src : str, optional
|
44
|
+
Source name of the parameter.
|
45
|
+
unit : str, optional
|
46
|
+
Unit of the parameter.
|
47
|
+
model : str, optional
|
48
|
+
Name of the owner model or group.
|
49
|
+
v : np.ndarray, optional
|
50
|
+
External value of the parameter.
|
51
|
+
indexer : str, optional
|
52
|
+
Indexer of the parameter.
|
53
|
+
imodel : str, optional
|
54
|
+
Name of the owner model or group of the indexer.
|
55
|
+
no_parse: bool, optional
|
56
|
+
True to skip parsing the parameter.
|
57
|
+
nonneg: bool, optional
|
58
|
+
True to set the parameter as non-negative.
|
59
|
+
nonpos: bool, optional
|
60
|
+
True to set the parameter as non-positive.
|
61
|
+
cplx: bool, optional
|
62
|
+
True to set the parameter as complex.
|
63
|
+
imag: bool, optional
|
64
|
+
True to set the parameter as imaginary.
|
65
|
+
symmetric: bool, optional
|
66
|
+
True to set the parameter as symmetric.
|
67
|
+
diag: bool, optional
|
68
|
+
True to set the parameter as diagonal.
|
69
|
+
hermitian: bool, optional
|
70
|
+
True to set the parameter as hermitian.
|
71
|
+
boolean: bool, optional
|
72
|
+
True to set the parameter as boolean.
|
73
|
+
integer: bool, optional
|
74
|
+
True to set the parameter as integer.
|
75
|
+
pos: bool, optional
|
76
|
+
True to set the parameter as positive.
|
77
|
+
neg: bool, optional
|
78
|
+
True to set the parameter as negative.
|
79
|
+
sparse: bool, optional
|
80
|
+
True to set the parameter as sparse.
|
81
|
+
|
82
|
+
Examples
|
83
|
+
--------
|
84
|
+
Example 1: Define a routine parameter from a source model or group.
|
85
|
+
|
86
|
+
In this example, we define the parameter `cru` from the source model
|
87
|
+
`SFRCost` with the parameter `cru`.
|
88
|
+
|
89
|
+
>>> self.cru = RParam(info='RegUp reserve coefficient',
|
90
|
+
>>> tex_name=r'c_{r,u}',
|
91
|
+
>>> unit=r'$/(p.u.)',
|
92
|
+
>>> name='cru',
|
93
|
+
>>> src='cru',
|
94
|
+
>>> model='SFRCost'
|
95
|
+
>>> )
|
96
|
+
|
97
|
+
Example 2: Define a routine parameter with a user-defined value.
|
98
|
+
|
99
|
+
In this example, we define the parameter with a user-defined value.
|
100
|
+
TODO: Add example
|
101
|
+
"""
|
102
|
+
|
103
|
+
def __init__(self,
|
104
|
+
name: Optional[str] = None,
|
105
|
+
tex_name: Optional[str] = None,
|
106
|
+
info: Optional[str] = None,
|
107
|
+
src: Optional[str] = None,
|
108
|
+
unit: Optional[str] = None,
|
109
|
+
model: Optional[str] = None,
|
110
|
+
v: Optional[np.ndarray] = None,
|
111
|
+
indexer: Optional[str] = None,
|
112
|
+
imodel: Optional[str] = None,
|
113
|
+
expand_dims: Optional[int] = None,
|
114
|
+
no_parse: Optional[bool] = False,
|
115
|
+
nonneg: Optional[bool] = False,
|
116
|
+
nonpos: Optional[bool] = False,
|
117
|
+
cplx: Optional[bool] = False,
|
118
|
+
imag: Optional[bool] = False,
|
119
|
+
symmetric: Optional[bool] = False,
|
120
|
+
diag: Optional[bool] = False,
|
121
|
+
hermitian: Optional[bool] = False,
|
122
|
+
boolean: Optional[bool] = False,
|
123
|
+
integer: Optional[bool] = False,
|
124
|
+
pos: Optional[bool] = False,
|
125
|
+
neg: Optional[bool] = False,
|
126
|
+
sparse: Optional[list] = None,
|
127
|
+
):
|
128
|
+
Param.__init__(self, nonneg=nonneg, nonpos=nonpos,
|
129
|
+
cplx=cplx, imag=imag, symmetric=symmetric,
|
130
|
+
diag=diag, hermitian=hermitian, boolean=boolean,
|
131
|
+
integer=integer, pos=pos, neg=neg, sparse=sparse)
|
132
|
+
self.name = name
|
133
|
+
self.tex_name = tex_name if (tex_name is not None) else name
|
134
|
+
self.info = info
|
135
|
+
self.src = name if (src is None) else src
|
136
|
+
self.unit = unit
|
137
|
+
self.is_group = False
|
138
|
+
self.model = model # name of a group or model
|
139
|
+
self.indexer = indexer # name of the indexer
|
140
|
+
self.imodel = imodel # name of a group or model of the indexer
|
141
|
+
self.expand_dims = expand_dims
|
142
|
+
self.no_parse = no_parse
|
143
|
+
self.owner = None # instance of the owner model or group
|
144
|
+
self.is_ext = False # indicate if the value is set externally
|
145
|
+
self._v = None # external value
|
146
|
+
if v is not None:
|
147
|
+
self._v = v
|
148
|
+
self.is_ext = True
|
149
|
+
|
150
|
+
# FIXME: might need a better organization
|
151
|
+
@property
|
152
|
+
def v(self):
|
153
|
+
"""
|
154
|
+
The value of the parameter.
|
155
|
+
|
156
|
+
Notes
|
157
|
+
-----
|
158
|
+
- This property is a wrapper for the ``get`` method of the owner class.
|
159
|
+
- The value will sort by the indexer if indexed, used for optmization modeling.
|
160
|
+
"""
|
161
|
+
out = None
|
162
|
+
if self.sparse and self.expand_dims is not None:
|
163
|
+
msg = 'Sparse matrix does not support expand_dims.'
|
164
|
+
raise NotImplementedError(msg)
|
165
|
+
if self.indexer is None:
|
166
|
+
if self.is_ext:
|
167
|
+
if issparse(self._v):
|
168
|
+
out = self._v.toarray()
|
169
|
+
else:
|
170
|
+
out = self._v
|
171
|
+
elif self.is_group:
|
172
|
+
out = self.owner.get(src=self.src, attr='v',
|
173
|
+
idx=self.owner.get_all_idxes())
|
174
|
+
else:
|
175
|
+
src_param = getattr(self.owner, self.src)
|
176
|
+
out = getattr(src_param, 'v')
|
177
|
+
else:
|
178
|
+
try:
|
179
|
+
imodel = getattr(self.rtn.system, self.imodel)
|
180
|
+
except AttributeError:
|
181
|
+
msg = f'Indexer source model <{self.imodel}> not found, '
|
182
|
+
msg += 'likely a modeling error.'
|
183
|
+
raise AttributeError(msg)
|
184
|
+
try:
|
185
|
+
sorted_idx = self.owner.find_idx(keys=self.indexer, values=imodel.get_all_idxes())
|
186
|
+
except AttributeError:
|
187
|
+
sorted_idx = self.owner.idx.v
|
188
|
+
except Exception as e:
|
189
|
+
raise e
|
190
|
+
model = getattr(self.rtn.system, self.model)
|
191
|
+
out = model.get(src=self.src, attr='v', idx=sorted_idx)
|
192
|
+
if self.expand_dims is not None:
|
193
|
+
out = np.expand_dims(out, axis=self.expand_dims)
|
194
|
+
return out
|
195
|
+
|
196
|
+
@property
|
197
|
+
def shape(self):
|
198
|
+
"""
|
199
|
+
Return the shape of the parameter.
|
200
|
+
"""
|
201
|
+
if self.is_ext:
|
202
|
+
return np.shape(self._v)
|
203
|
+
elif self.no_parse:
|
204
|
+
return None
|
205
|
+
else:
|
206
|
+
return np.shape(self.v)
|
207
|
+
|
208
|
+
@property
|
209
|
+
def dtype(self):
|
210
|
+
"""
|
211
|
+
Return the data type of the parameter value.
|
212
|
+
"""
|
213
|
+
if isinstance(self.v, (str, bytes)):
|
214
|
+
return str
|
215
|
+
elif isinstance(self.v, Iterable):
|
216
|
+
return type(self.v[0])
|
217
|
+
else:
|
218
|
+
return type(self.v)
|
219
|
+
|
220
|
+
@property
|
221
|
+
def n(self):
|
222
|
+
"""
|
223
|
+
Return the szie of the parameter.
|
224
|
+
"""
|
225
|
+
if self.is_ext:
|
226
|
+
return self._v.shape[0]
|
227
|
+
else:
|
228
|
+
return self.owner.n
|
229
|
+
|
230
|
+
@property
|
231
|
+
def class_name(self):
|
232
|
+
"""
|
233
|
+
Return the class name
|
234
|
+
"""
|
235
|
+
return self.__class__.__name__
|
236
|
+
|
237
|
+
def __repr__(self):
|
238
|
+
owner = self.owner.__class__.__name__ if self.owner is not None else self.rtn.__class__.__name__
|
239
|
+
postfix = '' if self.src is None else f'.{self.src}'
|
240
|
+
return f'{self.__class__.__name__}: {owner}' + postfix
|
241
|
+
|
242
|
+
@deprec_get_idx
|
243
|
+
def get_idx(self):
|
244
|
+
"""
|
245
|
+
Get the index of the parameter.
|
246
|
+
|
247
|
+
Returns
|
248
|
+
-------
|
249
|
+
idx : list
|
250
|
+
Index of the parameter.
|
251
|
+
|
252
|
+
Notes
|
253
|
+
-----
|
254
|
+
- The value will sort by the indexer if indexed.
|
255
|
+
"""
|
256
|
+
if self.indexer is None:
|
257
|
+
if self.is_group:
|
258
|
+
return self.owner.get_all_idxes()
|
259
|
+
elif self.owner is None:
|
260
|
+
logger.info(f'Param <{self.name}> has no owner.')
|
261
|
+
return None
|
262
|
+
elif hasattr(self.owner, 'idx'):
|
263
|
+
return self.owner.idx.v
|
264
|
+
else:
|
265
|
+
logger.info(f'Param <{self.name}> owner <{self.owner.class_name}> has no idx.')
|
266
|
+
return None
|
267
|
+
else:
|
268
|
+
try:
|
269
|
+
imodel = getattr(self.rtn.system, self.imodel)
|
270
|
+
except AttributeError:
|
271
|
+
msg = f'Indexer source model <{self.imodel}> not found, '
|
272
|
+
msg += 'likely a modeling error.'
|
273
|
+
raise AttributeError(msg)
|
274
|
+
try:
|
275
|
+
sorted_idx = self.owner.find_idx(keys=self.indexer, values=imodel.get_all_idxes())
|
276
|
+
except AttributeError:
|
277
|
+
msg = f'Indexer <{self.indexer}> not found in <{self.imodel}>, '
|
278
|
+
msg += 'likely a modeling error.'
|
279
|
+
raise AttributeError(msg)
|
280
|
+
return sorted_idx
|
281
|
+
|
282
|
+
def get_all_idxes(self):
|
283
|
+
"""
|
284
|
+
Get all the indexes of the parameter.
|
285
|
+
|
286
|
+
.. note::
|
287
|
+
New in version 1.0.0.
|
288
|
+
|
289
|
+
Returns
|
290
|
+
-------
|
291
|
+
idx : list
|
292
|
+
Index of the parameter.
|
293
|
+
|
294
|
+
Notes
|
295
|
+
-----
|
296
|
+
- The value will sort by the indexer if indexed.
|
297
|
+
"""
|
298
|
+
if self.indexer is None:
|
299
|
+
if self.is_group:
|
300
|
+
return self.owner.get_all_idxes()
|
301
|
+
elif self.owner is None:
|
302
|
+
logger.info(f'Param <{self.name}> has no owner.')
|
303
|
+
return None
|
304
|
+
elif hasattr(self.owner, 'idx'):
|
305
|
+
return self.owner.idx.v
|
306
|
+
else:
|
307
|
+
logger.info(f'Param <{self.name}> owner <{self.owner.class_name}> has no idx.')
|
308
|
+
return None
|
309
|
+
else:
|
310
|
+
try:
|
311
|
+
imodel = getattr(self.rtn.system, self.imodel)
|
312
|
+
except AttributeError:
|
313
|
+
msg = f'Indexer source model <{self.imodel}> not found, '
|
314
|
+
msg += 'likely a modeling error.'
|
315
|
+
raise AttributeError(msg)
|
316
|
+
try:
|
317
|
+
sorted_idx = self.owner.find_idx(keys=self.indexer, values=imodel.get_all_idxes())
|
318
|
+
except AttributeError:
|
319
|
+
msg = f'Indexer <{self.indexer}> not found in <{self.imodel}>, '
|
320
|
+
msg += 'likely a modeling error.'
|
321
|
+
raise AttributeError(msg)
|
322
|
+
return sorted_idx
|