fram-core 0.0.0__py3-none-any.whl → 0.1.0__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.
- fram_core-0.1.0.dist-info/METADATA +42 -0
- fram_core-0.1.0.dist-info/RECORD +100 -0
- {fram_core-0.0.0.dist-info → fram_core-0.1.0.dist-info}/WHEEL +1 -2
- fram_core-0.1.0.dist-info/licenses/LICENSE.md +8 -0
- framcore/Base.py +161 -0
- framcore/Model.py +90 -0
- framcore/__init__.py +10 -0
- framcore/aggregators/Aggregator.py +172 -0
- framcore/aggregators/HydroAggregator.py +849 -0
- framcore/aggregators/NodeAggregator.py +530 -0
- framcore/aggregators/WindSolarAggregator.py +315 -0
- framcore/aggregators/__init__.py +13 -0
- framcore/aggregators/_utils.py +184 -0
- framcore/attributes/Arrow.py +307 -0
- framcore/attributes/ElasticDemand.py +90 -0
- framcore/attributes/ReservoirCurve.py +23 -0
- framcore/attributes/SoftBound.py +16 -0
- framcore/attributes/StartUpCost.py +65 -0
- framcore/attributes/Storage.py +158 -0
- framcore/attributes/TargetBound.py +16 -0
- framcore/attributes/__init__.py +63 -0
- framcore/attributes/hydro/HydroBypass.py +49 -0
- framcore/attributes/hydro/HydroGenerator.py +100 -0
- framcore/attributes/hydro/HydroPump.py +178 -0
- framcore/attributes/hydro/HydroReservoir.py +27 -0
- framcore/attributes/hydro/__init__.py +13 -0
- framcore/attributes/level_profile_attributes.py +911 -0
- framcore/components/Component.py +136 -0
- framcore/components/Demand.py +144 -0
- framcore/components/Flow.py +189 -0
- framcore/components/HydroModule.py +371 -0
- framcore/components/Node.py +99 -0
- framcore/components/Thermal.py +208 -0
- framcore/components/Transmission.py +198 -0
- framcore/components/_PowerPlant.py +81 -0
- framcore/components/__init__.py +22 -0
- framcore/components/wind_solar.py +82 -0
- framcore/curves/Curve.py +44 -0
- framcore/curves/LoadedCurve.py +146 -0
- framcore/curves/__init__.py +9 -0
- framcore/events/__init__.py +21 -0
- framcore/events/events.py +51 -0
- framcore/expressions/Expr.py +591 -0
- framcore/expressions/__init__.py +30 -0
- framcore/expressions/_get_constant_from_expr.py +477 -0
- framcore/expressions/_utils.py +73 -0
- framcore/expressions/queries.py +416 -0
- framcore/expressions/units.py +227 -0
- framcore/fingerprints/__init__.py +11 -0
- framcore/fingerprints/fingerprint.py +292 -0
- framcore/juliamodels/JuliaModel.py +171 -0
- framcore/juliamodels/__init__.py +7 -0
- framcore/loaders/__init__.py +10 -0
- framcore/loaders/loaders.py +405 -0
- framcore/metadata/Div.py +73 -0
- framcore/metadata/ExprMeta.py +56 -0
- framcore/metadata/LevelExprMeta.py +32 -0
- framcore/metadata/Member.py +55 -0
- framcore/metadata/Meta.py +44 -0
- framcore/metadata/__init__.py +15 -0
- framcore/populators/Populator.py +108 -0
- framcore/populators/__init__.py +7 -0
- framcore/querydbs/CacheDB.py +50 -0
- framcore/querydbs/ModelDB.py +34 -0
- framcore/querydbs/QueryDB.py +45 -0
- framcore/querydbs/__init__.py +11 -0
- framcore/solvers/Solver.py +63 -0
- framcore/solvers/SolverConfig.py +272 -0
- framcore/solvers/__init__.py +9 -0
- framcore/timeindexes/AverageYearRange.py +27 -0
- framcore/timeindexes/ConstantTimeIndex.py +22 -0
- framcore/timeindexes/DailyIndex.py +33 -0
- framcore/timeindexes/FixedFrequencyTimeIndex.py +814 -0
- framcore/timeindexes/HourlyIndex.py +33 -0
- framcore/timeindexes/IsoCalendarDay.py +33 -0
- framcore/timeindexes/ListTimeIndex.py +277 -0
- framcore/timeindexes/ModelYear.py +23 -0
- framcore/timeindexes/ModelYears.py +27 -0
- framcore/timeindexes/OneYearProfileTimeIndex.py +29 -0
- framcore/timeindexes/ProfileTimeIndex.py +43 -0
- framcore/timeindexes/SinglePeriodTimeIndex.py +37 -0
- framcore/timeindexes/TimeIndex.py +103 -0
- framcore/timeindexes/WeeklyIndex.py +33 -0
- framcore/timeindexes/__init__.py +36 -0
- framcore/timeindexes/_time_vector_operations.py +689 -0
- framcore/timevectors/ConstantTimeVector.py +131 -0
- framcore/timevectors/LinearTransformTimeVector.py +131 -0
- framcore/timevectors/ListTimeVector.py +127 -0
- framcore/timevectors/LoadedTimeVector.py +97 -0
- framcore/timevectors/ReferencePeriod.py +51 -0
- framcore/timevectors/TimeVector.py +108 -0
- framcore/timevectors/__init__.py +17 -0
- framcore/utils/__init__.py +35 -0
- framcore/utils/get_regional_volumes.py +387 -0
- framcore/utils/get_supported_components.py +60 -0
- framcore/utils/global_energy_equivalent.py +63 -0
- framcore/utils/isolate_subnodes.py +172 -0
- framcore/utils/loaders.py +97 -0
- framcore/utils/node_flow_utils.py +236 -0
- framcore/utils/storage_subsystems.py +106 -0
- fram_core-0.0.0.dist-info/METADATA +0 -5
- fram_core-0.0.0.dist-info/RECORD +0 -4
- fram_core-0.0.0.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,911 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from numpy.typing import NDArray
|
|
8
|
+
|
|
9
|
+
from framcore import Base
|
|
10
|
+
from framcore.expressions import (
|
|
11
|
+
Expr,
|
|
12
|
+
ensure_expr,
|
|
13
|
+
get_leaf_profiles,
|
|
14
|
+
get_level_value,
|
|
15
|
+
get_profile_exprs_from_leaf_levels,
|
|
16
|
+
get_profile_vector,
|
|
17
|
+
get_timeindexes_from_expr,
|
|
18
|
+
get_units_from_expr,
|
|
19
|
+
)
|
|
20
|
+
from framcore.expressions._get_constant_from_expr import _get_constant_from_expr
|
|
21
|
+
from framcore.querydbs import QueryDB
|
|
22
|
+
from framcore.timeindexes import FixedFrequencyTimeIndex, SinglePeriodTimeIndex, TimeIndex
|
|
23
|
+
from framcore.timevectors import ConstantTimeVector, ReferencePeriod, TimeVector
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from framcore import Model
|
|
27
|
+
from framcore.loaders import Loader
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# TODO: Name all abstract classes Abstract[clsname]
|
|
31
|
+
class LevelProfile(Base, ABC):
|
|
32
|
+
"""
|
|
33
|
+
Attributes representing timeseries data for Components. Mostly as Level * Profile, where both Level and Profile are Expr (expressions).
|
|
34
|
+
|
|
35
|
+
Level and Profile represent two distinct dimensions of time. This is because we want to simulate future system states with historical weather patterns.
|
|
36
|
+
Therefore, Level represents the system state at a given time (data_dim), while Profile represents the scenario dimension (scen_dim).
|
|
37
|
+
A Level would for example represent the installed capacity of solar plants towards 2030,
|
|
38
|
+
while the Profile would represent the historical variation between 1991-2020.
|
|
39
|
+
|
|
40
|
+
Level and Profile can have two main formats: A maximum Level with a Profile that varies between 0-1,
|
|
41
|
+
and an average Level with a Profile with a mean of 1 (the latter can have a ReferencePeriod).
|
|
42
|
+
The max format is, for example, used for capacities, while the mean format can be used for prices and flows.
|
|
43
|
+
The system needs to be able to convert between the two formats. This is especially important for aggregations
|
|
44
|
+
(for example weighted averages) where all the TimeVectors need to be on the same format for a correct result.
|
|
45
|
+
One simple example of conversion is pairing a max Level of 100 MW with a mean_one Profile [0, 1, 2].
|
|
46
|
+
Asking for this on the max format will return the series 100*[0, 0.5, 1] MW, while on the avg format it will return 50*[0, 1, 2] MW.
|
|
47
|
+
|
|
48
|
+
Queries to LevelProfile need to provide a database, the desired target TimeIndex for both dimensions, the target unit and the desired format.
|
|
49
|
+
At the moment we support these queries for LevelProfile:
|
|
50
|
+
- self.get_data_value(db, scen_dim, data_dim, unit, is_max_level)
|
|
51
|
+
- self.get_scenario_vector(db, scen_dim, data_dim, unit, is_float32)
|
|
52
|
+
|
|
53
|
+
In addition, we have the possibility to shift, scale, and change the intercept of the LevelProfiles.
|
|
54
|
+
Then we get the full representation: Scale * (Level + Level_shift) * Profile + Intercept.
|
|
55
|
+
- Level_shift adds a constant value to Level, has the same Profile as Level.
|
|
56
|
+
- Scale multiplies (Level + Level_shift) by a constant value.
|
|
57
|
+
- Intercept adds a constant value to LevelProfile, ignoring Level and Profile. **This is the only way of supporting a timeseries that crosses zero
|
|
58
|
+
in our system. This functionality is under development and has not been properly tested.**
|
|
59
|
+
|
|
60
|
+
LevelProfiles also have additional properties that describes their behaviour. These can be used for initialization, validation,
|
|
61
|
+
and to simplify queries. The properties are:
|
|
62
|
+
- is_stock: True if attribute is a stock variable. Level Expr should also have is_stock=True. See Expr for details.
|
|
63
|
+
- is_flow: True if attribute is a flow variable. Level Expr should also have is_flow=True. See Expr for details.
|
|
64
|
+
- is_not_negative: True if attribute is not allowed to have negative values. Level Expr should also have only non-negative values.
|
|
65
|
+
- is_max_and_zero_one: Preferred format of Level and Profile. Used for initialization and queries.
|
|
66
|
+
- is_ingoing: True if attribute is ingoing, False if outgoing, None if neither.
|
|
67
|
+
- is_cost: True if attribute is objective function cost coefficient. Else None.
|
|
68
|
+
- is_unitless: True if attribute is known to be unitless. False if known to have a unit that is not None. Else None.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
# must be overwritten by subclass when otherwise
|
|
73
|
+
# don't change the defaults
|
|
74
|
+
_IS_ABSTRACT: bool = True
|
|
75
|
+
_IS_STOCK: bool = False
|
|
76
|
+
_IS_FLOW: bool = False
|
|
77
|
+
_IS_NOT_NEGATIVE: bool = True
|
|
78
|
+
_IS_MAX_AND_ZERO_ONE: bool = False
|
|
79
|
+
|
|
80
|
+
# must be set by subclass when applicable
|
|
81
|
+
_IS_INGOING: bool | None = None
|
|
82
|
+
_IS_COST: bool | None = None
|
|
83
|
+
_IS_UNITLESS: bool | None = None
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
level: Expr | TimeVector | str | None = None,
|
|
88
|
+
profile: Expr | TimeVector | str | None = None,
|
|
89
|
+
value: float | int | None = None, # To support Price(value=20, unit="EUR/MWh")
|
|
90
|
+
unit: str | None = None,
|
|
91
|
+
level_shift: Expr | None = None,
|
|
92
|
+
intercept: Expr | None = None,
|
|
93
|
+
scale: Expr | None = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Initialize LevelProfile.
|
|
97
|
+
|
|
98
|
+
See the LevelProfile class docstring for details. A complete LevelProfile is represented as:
|
|
99
|
+
Scale * (Level + Level_shift) * Profile + Intercept. Normally only Level and Profile are used.
|
|
100
|
+
|
|
101
|
+
Either give level and profile, or value and unit.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
level (Expr | TimeVector | str | None, optional): Level Expr. Defaults to None.
|
|
105
|
+
profile (Expr | TimeVector | str | None, optional): Profile Expr. Defaults to None.
|
|
106
|
+
value (float | int | None, optional): A constant value to initialize Level. Defaults to None.
|
|
107
|
+
unit (str | None, optional): Unit of the constant value to initialize Level. Defaults to None.
|
|
108
|
+
level_shift (Expr | None, optional): Level_shift Expr. Defaults to None.
|
|
109
|
+
intercept (Expr | None, optional): Intercept Expr. Defaults to None.
|
|
110
|
+
scale (Expr | None, optional): Scale Expr. Defaults to None.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
self._assert_invariants()
|
|
114
|
+
|
|
115
|
+
self._check_type(value, (float, int, type(None)))
|
|
116
|
+
self._check_type(unit, (str, type(None)))
|
|
117
|
+
self._check_type(level, (Expr, TimeVector, str, type(None)))
|
|
118
|
+
self._check_type(profile, (Expr, TimeVector, str, type(None)))
|
|
119
|
+
self._check_type(level_shift, (Expr, type(None)))
|
|
120
|
+
self._check_type(intercept, (Expr, type(None)))
|
|
121
|
+
self._check_type(scale, (Expr, type(None)))
|
|
122
|
+
level = self._ensure_level_expr(level, value, unit)
|
|
123
|
+
profile = self._ensure_profile_expr(profile)
|
|
124
|
+
self._ensure_compatible_level_profile_combo(level, profile)
|
|
125
|
+
self._ensure_compatible_level_profile_combo(level_shift, profile)
|
|
126
|
+
self._level: Expr | None = level
|
|
127
|
+
self._profile: Expr | None = profile
|
|
128
|
+
self._level_shift: Expr | None = level_shift
|
|
129
|
+
self._intercept: Expr | None = intercept
|
|
130
|
+
self._scale: Expr | None = scale
|
|
131
|
+
# TODO: Validate that profiles are equal in level and level_shift.
|
|
132
|
+
# TODO: Validate that level_shift, scale and intercept only consist of Exprs with ConstantTimeVectors
|
|
133
|
+
# TODO: Validate that level_shift, level_scale and intercept have correct Expr properties
|
|
134
|
+
|
|
135
|
+
def _assert_invariants(self) -> None:
|
|
136
|
+
abstract = self._IS_ABSTRACT
|
|
137
|
+
max_level_profile = self._IS_MAX_AND_ZERO_ONE
|
|
138
|
+
stock = self._IS_STOCK
|
|
139
|
+
flow = self._IS_FLOW
|
|
140
|
+
unitless = self._IS_UNITLESS
|
|
141
|
+
ingoing = self._IS_INGOING
|
|
142
|
+
cost = self._IS_COST
|
|
143
|
+
not_negative = self._IS_NOT_NEGATIVE
|
|
144
|
+
|
|
145
|
+
assert not abstract, "Abstract types should only be used for type hints and checks."
|
|
146
|
+
assert isinstance(max_level_profile, bool)
|
|
147
|
+
assert isinstance(stock, bool)
|
|
148
|
+
assert isinstance(flow, bool)
|
|
149
|
+
assert isinstance(not_negative, bool)
|
|
150
|
+
assert isinstance(ingoing, bool | type(None))
|
|
151
|
+
assert isinstance(unitless, bool | type(None))
|
|
152
|
+
assert isinstance(cost, bool | type(None))
|
|
153
|
+
assert not (flow and stock)
|
|
154
|
+
if flow or stock:
|
|
155
|
+
assert not unitless, "flow and stock must have unit that is not None."
|
|
156
|
+
assert not_negative, "flow and stock cannot have negative values."
|
|
157
|
+
if ingoing is True:
|
|
158
|
+
assert cost is None, "cost must be None when ingoing is True."
|
|
159
|
+
if cost is True:
|
|
160
|
+
assert ingoing is None, "ingoing must be None when cost is True."
|
|
161
|
+
|
|
162
|
+
parent = super()
|
|
163
|
+
if isinstance(parent, LevelProfile) and not parent._IS_ABSTRACT: # noqa: SLF001
|
|
164
|
+
self._assert_same_behaviour(parent)
|
|
165
|
+
|
|
166
|
+
def add_loaders(self, loaders: set[Loader]) -> None:
|
|
167
|
+
"""Add all loaders stored in expressions to loaders."""
|
|
168
|
+
from framcore.utils import add_loaders_if
|
|
169
|
+
|
|
170
|
+
add_loaders_if(loaders, self.get_level())
|
|
171
|
+
add_loaders_if(loaders, self.get_profile())
|
|
172
|
+
|
|
173
|
+
def clear(self) -> None:
|
|
174
|
+
"""
|
|
175
|
+
Set all internal fields to None.
|
|
176
|
+
|
|
177
|
+
You may want to use this to get exogenous flow to use capacities instead of volume.
|
|
178
|
+
"""
|
|
179
|
+
self._level = None
|
|
180
|
+
self._profile = None
|
|
181
|
+
self._level_shift = None
|
|
182
|
+
self._intercept = None
|
|
183
|
+
self._scale = None
|
|
184
|
+
|
|
185
|
+
def is_stock(self) -> bool:
|
|
186
|
+
"""
|
|
187
|
+
Return True if attribute is a stock variable.
|
|
188
|
+
|
|
189
|
+
Return False if attribute is not a stock variable.
|
|
190
|
+
"""
|
|
191
|
+
return self._IS_STOCK
|
|
192
|
+
|
|
193
|
+
def is_flow(self) -> bool:
|
|
194
|
+
"""
|
|
195
|
+
Return True if attribute is a flow variable.
|
|
196
|
+
|
|
197
|
+
Return False if attribute is not a flow variable.
|
|
198
|
+
"""
|
|
199
|
+
return self._IS_FLOW
|
|
200
|
+
|
|
201
|
+
def is_not_negative(self) -> bool:
|
|
202
|
+
"""
|
|
203
|
+
Return True if attribute is not allowed to have negative values.
|
|
204
|
+
|
|
205
|
+
Return False if attribute can have both positive and negative values.
|
|
206
|
+
"""
|
|
207
|
+
return self._IS_NOT_NEGATIVE
|
|
208
|
+
|
|
209
|
+
def is_max_and_zero_one(self) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
When True level should be max (not average) and corresponding profile should be zero_one (not mean_one).
|
|
212
|
+
|
|
213
|
+
When False level should be average (not max) and corresponding profile should be mean_one (not zero_one).
|
|
214
|
+
"""
|
|
215
|
+
return self._IS_MAX_AND_ZERO_ONE
|
|
216
|
+
|
|
217
|
+
def is_ingoing(self) -> bool | None:
|
|
218
|
+
"""
|
|
219
|
+
Return True if attribute is ingoing.
|
|
220
|
+
|
|
221
|
+
Return True if attribute is outgoing.
|
|
222
|
+
|
|
223
|
+
Return None if not applicable.
|
|
224
|
+
"""
|
|
225
|
+
return self._IS_INGOING
|
|
226
|
+
|
|
227
|
+
def is_cost(self) -> bool | None:
|
|
228
|
+
"""
|
|
229
|
+
Return True if attribute is objective function cost coefficient.
|
|
230
|
+
|
|
231
|
+
Return False if attribute is objective function revenue coefficient.
|
|
232
|
+
|
|
233
|
+
Return None if not applicable.
|
|
234
|
+
"""
|
|
235
|
+
return self._IS_COST
|
|
236
|
+
|
|
237
|
+
def is_unitless(self) -> bool | None:
|
|
238
|
+
"""
|
|
239
|
+
Return True if attribute is known to be unitless.
|
|
240
|
+
|
|
241
|
+
Return False if attribute is known to have a unit that is not None.
|
|
242
|
+
|
|
243
|
+
Return None if not applicable.
|
|
244
|
+
"""
|
|
245
|
+
return self._IS_UNITLESS
|
|
246
|
+
|
|
247
|
+
def has_level(self) -> bool:
|
|
248
|
+
"""Return True if get_level will return value not None."""
|
|
249
|
+
return (self._level is not None) or (self._level_shift is not None)
|
|
250
|
+
|
|
251
|
+
def has_profile(self) -> bool:
|
|
252
|
+
"""Return True if get_profile will return value not None."""
|
|
253
|
+
return self._profile is not None
|
|
254
|
+
|
|
255
|
+
def has_intercept(self) -> bool:
|
|
256
|
+
"""Return True if get_intercept will return value not None."""
|
|
257
|
+
return self._intercept is not None
|
|
258
|
+
|
|
259
|
+
def copy_from(self, other: LevelProfile) -> None:
|
|
260
|
+
"""Copy fields from other."""
|
|
261
|
+
self._check_type(other, LevelProfile)
|
|
262
|
+
self._assert_same_behaviour(other)
|
|
263
|
+
self._level = other._level
|
|
264
|
+
self._profile = other._profile
|
|
265
|
+
self._level_shift = other._level_shift
|
|
266
|
+
self._intercept = other._intercept
|
|
267
|
+
self._scale = other._scale
|
|
268
|
+
|
|
269
|
+
def get_level(self) -> Expr | None:
|
|
270
|
+
"""Get level part of (level * profile + intercept)."""
|
|
271
|
+
level = self._level
|
|
272
|
+
|
|
273
|
+
if level is None:
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
if level.is_leaf():
|
|
277
|
+
level = Expr(
|
|
278
|
+
src=level.get_src(),
|
|
279
|
+
operations=level.get_operations(expect_ops=False, copy_list=True),
|
|
280
|
+
is_stock=level.is_stock(),
|
|
281
|
+
is_flow=level.is_flow(),
|
|
282
|
+
is_level=True,
|
|
283
|
+
is_profile=False,
|
|
284
|
+
profile=self._profile,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if self._level_shift is not None:
|
|
288
|
+
level += self._level_shift
|
|
289
|
+
|
|
290
|
+
if self._scale is not None:
|
|
291
|
+
level *= self._scale
|
|
292
|
+
|
|
293
|
+
return level
|
|
294
|
+
|
|
295
|
+
def set_level(self, level: Expr | TimeVector | str | None) -> None:
|
|
296
|
+
"""Set level part of (scale * (level + level_shift) * profile + intercept)."""
|
|
297
|
+
self._check_type(level, (Expr, TimeVector, str, type(None)))
|
|
298
|
+
level = self._ensure_level_expr(level)
|
|
299
|
+
self._ensure_compatible_level_profile_combo(level, self._profile)
|
|
300
|
+
self._level = level
|
|
301
|
+
|
|
302
|
+
def get_profile(self) -> Expr | None:
|
|
303
|
+
"""Get profile part of (level * profile + intercept)."""
|
|
304
|
+
return self._profile
|
|
305
|
+
|
|
306
|
+
def set_profile(self, profile: Expr | TimeVector | str | None) -> None:
|
|
307
|
+
"""Set profile part of (scale * (level + level_shift) * profile + intercept)."""
|
|
308
|
+
self._check_type(profile, (Expr, TimeVector, str, type(None)))
|
|
309
|
+
profile = self._ensure_profile_expr(profile)
|
|
310
|
+
self._ensure_compatible_level_profile_combo(self._level, profile)
|
|
311
|
+
self._profile = profile
|
|
312
|
+
|
|
313
|
+
def get_intercept(self) -> Expr | None:
|
|
314
|
+
"""Get intercept part of (level * profile + intercept)."""
|
|
315
|
+
intercept = self._intercept
|
|
316
|
+
if self._scale is not None:
|
|
317
|
+
intercept *= self._scale
|
|
318
|
+
return intercept
|
|
319
|
+
|
|
320
|
+
def set_intercept(self, value: Expr | None) -> None:
|
|
321
|
+
"""Set intercept part of (level * profile + intercept)."""
|
|
322
|
+
self._check_type(value, (Expr, type(None)))
|
|
323
|
+
if value is not None:
|
|
324
|
+
self._check_level_expr(value)
|
|
325
|
+
self._intercept = value
|
|
326
|
+
|
|
327
|
+
def get_level_unit_set(
|
|
328
|
+
self,
|
|
329
|
+
db: QueryDB | Model,
|
|
330
|
+
) -> set[TimeIndex]:
|
|
331
|
+
"""
|
|
332
|
+
Return set with all units behind level expression.
|
|
333
|
+
|
|
334
|
+
Useful for discovering valid unit input to get_level_value.
|
|
335
|
+
"""
|
|
336
|
+
if not self.has_level():
|
|
337
|
+
return set()
|
|
338
|
+
return get_units_from_expr(db, self.get_level())
|
|
339
|
+
|
|
340
|
+
def get_profile_timeindex_set(
|
|
341
|
+
self,
|
|
342
|
+
db: QueryDB | Model,
|
|
343
|
+
) -> set[TimeIndex]:
|
|
344
|
+
"""
|
|
345
|
+
Return set with all TimeIndex behind profile expression.
|
|
346
|
+
|
|
347
|
+
Can be used to run optimized queries, i.e. not asking for
|
|
348
|
+
finer time resolutions than necessary.
|
|
349
|
+
"""
|
|
350
|
+
if not self.has_profile():
|
|
351
|
+
return set()
|
|
352
|
+
return get_timeindexes_from_expr(db, self.get_profile())
|
|
353
|
+
|
|
354
|
+
def get_scenario_vector(
|
|
355
|
+
self,
|
|
356
|
+
db: QueryDB | Model,
|
|
357
|
+
scenario_horizon: FixedFrequencyTimeIndex,
|
|
358
|
+
level_period: SinglePeriodTimeIndex,
|
|
359
|
+
unit: str | None,
|
|
360
|
+
is_float32: bool = True,
|
|
361
|
+
) -> NDArray:
|
|
362
|
+
"""
|
|
363
|
+
Evaluate LevelProfile over the periods in scenario dimension, and at the level period of the data dimension.
|
|
364
|
+
|
|
365
|
+
Underlying profiles are evalutated over the scenario dimension,
|
|
366
|
+
and levels are evalutated to scalars over level_period in the data dimension.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
db (QueryDB | Model): The database or model instance used to fetch the required data.
|
|
370
|
+
scenario_horizon (FixedFrequencyTimeIndex): TimeIndex of the scenario dimension to evaluate profiles.
|
|
371
|
+
level_period (SinglePeriodTimeIndex): TimeIndex of the data dimension to evaluate levels.
|
|
372
|
+
unit (str | None): The unit to convert the resulting values into (e.g., MW, GWh). If None,
|
|
373
|
+
the expression should be unitless.
|
|
374
|
+
is_float32 (bool, optional): Whether to return the vector as a NumPy array with `float32`
|
|
375
|
+
precision. Defaults to True.
|
|
376
|
+
|
|
377
|
+
"""
|
|
378
|
+
return self._get_scenario_vector(db, scenario_horizon, level_period, unit, is_float32)
|
|
379
|
+
|
|
380
|
+
def get_data_value(
|
|
381
|
+
self,
|
|
382
|
+
db: QueryDB | Model,
|
|
383
|
+
scenario_horizon: FixedFrequencyTimeIndex,
|
|
384
|
+
level_period: SinglePeriodTimeIndex,
|
|
385
|
+
unit: str | None,
|
|
386
|
+
is_max_level: bool | None = None,
|
|
387
|
+
) -> float:
|
|
388
|
+
"""
|
|
389
|
+
Evaluate LevelProfile to a scalar at the level period of the data dimension, and as an average over the scenario horizon.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
db (QueryDB | Model): The database or model instance used to fetch the required data.
|
|
393
|
+
scenario_horizon (FixedFrequencyTimeIndex): TimeIndex of the scenario dimension to evaluate profiles.
|
|
394
|
+
level_period (SinglePeriodTimeIndex): TimeIndex of the data dimension to evaluate levels.
|
|
395
|
+
unit (str | None): The unit to convert the resulting values into (e.g., MW, GWh). If None,
|
|
396
|
+
the expression should be unitless.
|
|
397
|
+
is_max_level (bool | None, optional): Whether to evaluate the expression as a maximum level (with a zero_one profile)
|
|
398
|
+
or as an average level (with a mean_one profile). If None, the default format of the attribute is used.
|
|
399
|
+
|
|
400
|
+
"""
|
|
401
|
+
return self._get_data_value(db, scenario_horizon, level_period, unit, is_max_level)
|
|
402
|
+
|
|
403
|
+
def shift_intercept(self, value: float, unit: str | None) -> None:
|
|
404
|
+
"""Modify the intercept part of (level * profile + intercept) of an attribute by adding a constant value."""
|
|
405
|
+
expr = ensure_expr(
|
|
406
|
+
ConstantTimeVector(self._ensure_float(value), unit=unit, is_max_level=False),
|
|
407
|
+
is_level=True,
|
|
408
|
+
is_profile=False,
|
|
409
|
+
is_stock=self._IS_STOCK,
|
|
410
|
+
is_flow=self._IS_FLOW,
|
|
411
|
+
profile=None,
|
|
412
|
+
)
|
|
413
|
+
if self._intercept is None:
|
|
414
|
+
self._intercept = expr
|
|
415
|
+
else:
|
|
416
|
+
self._intercept += expr
|
|
417
|
+
|
|
418
|
+
def shift_level(
|
|
419
|
+
self,
|
|
420
|
+
value: float | int,
|
|
421
|
+
unit: str | None = None,
|
|
422
|
+
reference_period: ReferencePeriod | None = None,
|
|
423
|
+
is_max_level: bool | None = None,
|
|
424
|
+
use_profile: bool = True, # TODO: Remove. Should always use profile. If has profile validate that it is equal to the profile of Level.
|
|
425
|
+
) -> None:
|
|
426
|
+
"""Modify the level_shift part of (scale * (level + level_shift) * profile + intercept) of an attribute by adding a constant value."""
|
|
427
|
+
# TODO: Not allowed to shift if there is intercept?
|
|
428
|
+
self._check_type(value, (float, int))
|
|
429
|
+
self._check_type(unit, (str, type(None)))
|
|
430
|
+
self._check_type(reference_period, (ReferencePeriod, type(None)))
|
|
431
|
+
self._check_type(is_max_level, (bool, type(None)))
|
|
432
|
+
self._check_type(use_profile, bool)
|
|
433
|
+
|
|
434
|
+
if is_max_level is None:
|
|
435
|
+
is_max_level = self._IS_MAX_AND_ZERO_ONE
|
|
436
|
+
|
|
437
|
+
expr = ensure_expr(
|
|
438
|
+
ConstantTimeVector(
|
|
439
|
+
self._ensure_float(value),
|
|
440
|
+
unit=unit,
|
|
441
|
+
is_max_level=is_max_level,
|
|
442
|
+
reference_period=reference_period,
|
|
443
|
+
),
|
|
444
|
+
is_level=True,
|
|
445
|
+
is_profile=False,
|
|
446
|
+
is_stock=self._IS_STOCK,
|
|
447
|
+
is_flow=self._IS_FLOW,
|
|
448
|
+
profile=self._profile if use_profile else None,
|
|
449
|
+
)
|
|
450
|
+
if self._level_shift is None:
|
|
451
|
+
self._level_shift = expr
|
|
452
|
+
else:
|
|
453
|
+
self._level_shift += expr
|
|
454
|
+
|
|
455
|
+
def scale(self, value: float | int) -> None:
|
|
456
|
+
"""Modify the scale part of (scale * (level + level_shift) * profile + intercept) of an attribute by multiplying with a constant value."""
|
|
457
|
+
# TODO: Not allowed to scale if there is intercept?
|
|
458
|
+
expr = ensure_expr(
|
|
459
|
+
ConstantTimeVector(self._ensure_float(value), unit=None, is_max_level=False),
|
|
460
|
+
is_level=True,
|
|
461
|
+
is_profile=False,
|
|
462
|
+
profile=None,
|
|
463
|
+
)
|
|
464
|
+
if self._scale is None:
|
|
465
|
+
self._scale = expr
|
|
466
|
+
else:
|
|
467
|
+
self._scale *= expr
|
|
468
|
+
|
|
469
|
+
def _ensure_level_expr(
|
|
470
|
+
self,
|
|
471
|
+
level: Expr | str | TimeVector | None,
|
|
472
|
+
value: float | int | None = None,
|
|
473
|
+
unit: str | None = None,
|
|
474
|
+
reference_period: ReferencePeriod | None = None,
|
|
475
|
+
) -> Expr | None:
|
|
476
|
+
if value is not None:
|
|
477
|
+
level = ConstantTimeVector(
|
|
478
|
+
scalar=float(value),
|
|
479
|
+
unit=unit,
|
|
480
|
+
is_max_level=self._IS_MAX_AND_ZERO_ONE,
|
|
481
|
+
is_zero_one_profile=None,
|
|
482
|
+
reference_period=reference_period,
|
|
483
|
+
)
|
|
484
|
+
if level is None:
|
|
485
|
+
return None
|
|
486
|
+
|
|
487
|
+
if isinstance(level, Expr):
|
|
488
|
+
self._check_level_expr(level)
|
|
489
|
+
return level
|
|
490
|
+
|
|
491
|
+
return Expr(
|
|
492
|
+
src=level,
|
|
493
|
+
is_flow=self._IS_FLOW,
|
|
494
|
+
is_stock=self._IS_STOCK,
|
|
495
|
+
is_level=True,
|
|
496
|
+
is_profile=False,
|
|
497
|
+
profile=None,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
def _ensure_compatible_level_profile_combo(self, level: Expr | None, profile: Expr | None) -> None:
|
|
501
|
+
"""Check that all profiles in leaf levels (in level) also exist in profile."""
|
|
502
|
+
if level is None or profile is None:
|
|
503
|
+
return
|
|
504
|
+
|
|
505
|
+
leaf_level_profiles = get_profile_exprs_from_leaf_levels(level)
|
|
506
|
+
leaf_profile_profiles = get_leaf_profiles(profile)
|
|
507
|
+
|
|
508
|
+
for p in leaf_level_profiles:
|
|
509
|
+
if p not in leaf_profile_profiles:
|
|
510
|
+
message = (
|
|
511
|
+
f"Incompatible level/profile combination because all profiles in leaf levels (in level) does not exist in profile. "
|
|
512
|
+
f"Profile expression {p} found in level {level} but not in profile."
|
|
513
|
+
)
|
|
514
|
+
raise ValueError(message)
|
|
515
|
+
|
|
516
|
+
def _check_level_expr(self, expr: Expr) -> None:
|
|
517
|
+
msg = f"{self} requires {expr} to be "
|
|
518
|
+
if expr.is_stock() != self._IS_STOCK:
|
|
519
|
+
raise ValueError(msg + f"is_stock={self._IS_STOCK}")
|
|
520
|
+
if expr.is_flow() != self._IS_FLOW:
|
|
521
|
+
raise ValueError(msg + f"is_flow={self._IS_STOCK}")
|
|
522
|
+
if expr.is_level() is False:
|
|
523
|
+
raise ValueError(msg + "is_level=True")
|
|
524
|
+
if expr.is_profile() is True:
|
|
525
|
+
raise ValueError(msg + "is_profile=False")
|
|
526
|
+
|
|
527
|
+
def _check_profile_expr(self, expr: Expr) -> None:
|
|
528
|
+
msg = f"{self} requires {expr} to be "
|
|
529
|
+
if expr.is_stock() is True:
|
|
530
|
+
raise ValueError(msg + "is_stock=False")
|
|
531
|
+
if expr.is_flow() is True:
|
|
532
|
+
raise ValueError(msg + "is_flow=False")
|
|
533
|
+
if expr.is_level() is True:
|
|
534
|
+
raise ValueError(msg + "is_level=False")
|
|
535
|
+
if expr.is_profile() is False:
|
|
536
|
+
raise ValueError(msg + "is_profile=True")
|
|
537
|
+
|
|
538
|
+
def _ensure_profile_expr(
|
|
539
|
+
self,
|
|
540
|
+
value: Expr | str | TimeVector | None,
|
|
541
|
+
) -> Expr | None:
|
|
542
|
+
if value is None:
|
|
543
|
+
return None
|
|
544
|
+
|
|
545
|
+
if isinstance(value, Expr):
|
|
546
|
+
self._check_profile_expr(value)
|
|
547
|
+
return value
|
|
548
|
+
|
|
549
|
+
return Expr(
|
|
550
|
+
src=value,
|
|
551
|
+
is_flow=False,
|
|
552
|
+
is_stock=False,
|
|
553
|
+
is_level=False,
|
|
554
|
+
is_profile=True,
|
|
555
|
+
profile=None,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
def _get_data_value(
|
|
559
|
+
self,
|
|
560
|
+
db: QueryDB,
|
|
561
|
+
scenario_horizon: FixedFrequencyTimeIndex,
|
|
562
|
+
level_period: SinglePeriodTimeIndex,
|
|
563
|
+
unit: str | None,
|
|
564
|
+
is_max_level: bool | None,
|
|
565
|
+
) -> float:
|
|
566
|
+
# NB! don't type check db, as this is done in get_level_value and get_profile_vector
|
|
567
|
+
self._check_type(scenario_horizon, FixedFrequencyTimeIndex)
|
|
568
|
+
self._check_type(level_period, SinglePeriodTimeIndex)
|
|
569
|
+
self._check_type(unit, (str, type(None)))
|
|
570
|
+
self._check_type(is_max_level, (bool, type(None)))
|
|
571
|
+
|
|
572
|
+
level_expr = self.get_level()
|
|
573
|
+
|
|
574
|
+
if is_max_level is None:
|
|
575
|
+
is_max_level = self._IS_MAX_AND_ZERO_ONE
|
|
576
|
+
|
|
577
|
+
self._check_type(level_expr, (Expr, type(None)))
|
|
578
|
+
if not isinstance(level_expr, Expr):
|
|
579
|
+
raise ValueError("Attribute level Expr is None. Have you called Solver.solve yet?")
|
|
580
|
+
|
|
581
|
+
level_value = get_level_value(
|
|
582
|
+
expr=level_expr,
|
|
583
|
+
db=db,
|
|
584
|
+
scen_dim=scenario_horizon,
|
|
585
|
+
data_dim=level_period,
|
|
586
|
+
unit=unit,
|
|
587
|
+
is_max=is_max_level,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
intercept = None
|
|
591
|
+
if self._intercept is not None:
|
|
592
|
+
intercept = _get_constant_from_expr(
|
|
593
|
+
self._intercept,
|
|
594
|
+
db,
|
|
595
|
+
unit=unit,
|
|
596
|
+
data_dim=level_period,
|
|
597
|
+
scen_dim=scenario_horizon,
|
|
598
|
+
is_max=is_max_level,
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
if intercept is None:
|
|
602
|
+
return level_value
|
|
603
|
+
|
|
604
|
+
return level_value + intercept
|
|
605
|
+
|
|
606
|
+
def _get_scenario_vector(
|
|
607
|
+
self,
|
|
608
|
+
db: QueryDB | Model,
|
|
609
|
+
scenario_horizon: FixedFrequencyTimeIndex,
|
|
610
|
+
level_period: SinglePeriodTimeIndex,
|
|
611
|
+
unit: str | None,
|
|
612
|
+
is_float32: bool = True,
|
|
613
|
+
) -> NDArray:
|
|
614
|
+
"""Return vector with values along the given scenario horizon using level over level_period."""
|
|
615
|
+
# NB! don't type check db, as this is done in get_level_value and get_profile_vector
|
|
616
|
+
self._check_type(scenario_horizon, FixedFrequencyTimeIndex)
|
|
617
|
+
self._check_type(level_period, SinglePeriodTimeIndex)
|
|
618
|
+
self._check_type(unit, (str, type(None)))
|
|
619
|
+
self._check_type(is_float32, bool)
|
|
620
|
+
|
|
621
|
+
level_expr = self.get_level()
|
|
622
|
+
|
|
623
|
+
self._check_type(level_expr, (Expr, type(None)))
|
|
624
|
+
if not isinstance(level_expr, Expr):
|
|
625
|
+
raise ValueError("Attribute level Expr is None. Have you called Solver.solve yet?")
|
|
626
|
+
|
|
627
|
+
level_value = get_level_value(
|
|
628
|
+
expr=level_expr,
|
|
629
|
+
db=db,
|
|
630
|
+
scen_dim=scenario_horizon,
|
|
631
|
+
data_dim=level_period,
|
|
632
|
+
unit=unit,
|
|
633
|
+
is_max=self._IS_MAX_AND_ZERO_ONE,
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
profile_expr = self.get_profile()
|
|
637
|
+
|
|
638
|
+
if profile_expr is None:
|
|
639
|
+
profile_vector = np.ones(
|
|
640
|
+
scenario_horizon.get_num_periods(),
|
|
641
|
+
dtype=np.float32 if is_float32 else np.float64,
|
|
642
|
+
)
|
|
643
|
+
else:
|
|
644
|
+
profile_vector = get_profile_vector(
|
|
645
|
+
expr=profile_expr,
|
|
646
|
+
db=db,
|
|
647
|
+
scen_dim=scenario_horizon,
|
|
648
|
+
data_dim=level_period,
|
|
649
|
+
is_zero_one=self._IS_MAX_AND_ZERO_ONE,
|
|
650
|
+
is_float32=is_float32,
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
intercept = None
|
|
654
|
+
if self._intercept is not None:
|
|
655
|
+
intercept = _get_constant_from_expr(
|
|
656
|
+
self._intercept,
|
|
657
|
+
db,
|
|
658
|
+
unit=unit,
|
|
659
|
+
data_dim=level_period,
|
|
660
|
+
scen_dim=scenario_horizon,
|
|
661
|
+
is_max=self._IS_MAX_AND_ZERO_ONE,
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
if intercept is None:
|
|
665
|
+
return level_value * profile_vector
|
|
666
|
+
|
|
667
|
+
return level_value * profile_vector + intercept
|
|
668
|
+
|
|
669
|
+
def _has_same_behaviour(self, other: LevelProfile) -> bool:
|
|
670
|
+
return all(
|
|
671
|
+
(
|
|
672
|
+
self._IS_FLOW == other._IS_FLOW,
|
|
673
|
+
self._IS_STOCK == other._IS_STOCK,
|
|
674
|
+
self._IS_NOT_NEGATIVE == other._IS_NOT_NEGATIVE,
|
|
675
|
+
self._IS_MAX_AND_ZERO_ONE == other._IS_MAX_AND_ZERO_ONE,
|
|
676
|
+
self._IS_INGOING == other._IS_INGOING,
|
|
677
|
+
self._IS_COST == other._IS_COST,
|
|
678
|
+
self._IS_UNITLESS == other._IS_UNITLESS,
|
|
679
|
+
),
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
def _assert_same_behaviour(self, other: LevelProfile) -> None:
|
|
683
|
+
if not self._has_same_behaviour(other):
|
|
684
|
+
message = f"Not same behaviour for {self} and {other}"
|
|
685
|
+
raise ValueError(message)
|
|
686
|
+
|
|
687
|
+
def __eq__(self, other) -> bool: # noqa: ANN001
|
|
688
|
+
"""Return True if other is equal to self."""
|
|
689
|
+
if not isinstance(other, LevelProfile):
|
|
690
|
+
return False
|
|
691
|
+
if not self._has_same_behaviour(other):
|
|
692
|
+
return False
|
|
693
|
+
return all(
|
|
694
|
+
(
|
|
695
|
+
self._level == other._level,
|
|
696
|
+
self._profile == other._profile,
|
|
697
|
+
self._level_shift == other._level_shift,
|
|
698
|
+
self._intercept == other._intercept,
|
|
699
|
+
self._scale == other._scale,
|
|
700
|
+
),
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
def __hash__(self) -> int:
|
|
704
|
+
"""Compute hash of self."""
|
|
705
|
+
return hash(
|
|
706
|
+
(
|
|
707
|
+
type(self).__name__,
|
|
708
|
+
self._level,
|
|
709
|
+
self._profile,
|
|
710
|
+
self._level_shift,
|
|
711
|
+
self._intercept,
|
|
712
|
+
self._scale,
|
|
713
|
+
),
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
# Abstract subclasses intended type hints and checks
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
class FlowVolume(LevelProfile):
|
|
721
|
+
"""
|
|
722
|
+
Abstract class representing a flow volume attribute, indicating that the attribute is a flow variable.
|
|
723
|
+
|
|
724
|
+
Subclass of LevelProfile. See LevelProfile for details.
|
|
725
|
+
"""
|
|
726
|
+
|
|
727
|
+
_IS_FLOW = True
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
class Coefficient(LevelProfile):
|
|
731
|
+
"""
|
|
732
|
+
Abstract class representing a coefficient attribute, used as a base class for various coefficient types.
|
|
733
|
+
|
|
734
|
+
Subclass of LevelProfile. See LevelProfile for details.
|
|
735
|
+
"""
|
|
736
|
+
|
|
737
|
+
pass
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
class ArrowCoefficient(Coefficient):
|
|
741
|
+
"""
|
|
742
|
+
Abstract class representing an arrow coefficient attribute, used for efficiency, loss, and conversion coefficients.
|
|
743
|
+
|
|
744
|
+
Subclass of Coefficient < LevelProfile. See LevelProfile for details.
|
|
745
|
+
"""
|
|
746
|
+
|
|
747
|
+
pass
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
class ShadowPrice(Coefficient):
|
|
751
|
+
"""
|
|
752
|
+
Abstract class representing a shadow price attribute, indicating that the attribute has a unit and might be negative.
|
|
753
|
+
|
|
754
|
+
Subclass of Coefficient < LevelProfile. See LevelProfile for details.
|
|
755
|
+
"""
|
|
756
|
+
|
|
757
|
+
_IS_UNITLESS = False
|
|
758
|
+
_IS_NOT_NEGATIVE = False
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
class ObjectiveCoefficient(Coefficient):
|
|
762
|
+
"""
|
|
763
|
+
Abstract class representing an objective coefficient attribute, indicating cost or revenue coefficients in the objective function.
|
|
764
|
+
|
|
765
|
+
Subclass of Coefficient < LevelProfile. See LevelProfile for details.
|
|
766
|
+
"""
|
|
767
|
+
|
|
768
|
+
_IS_UNITLESS = False
|
|
769
|
+
_IS_NOT_NEGATIVE = False
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
# Concrete subclasses intended for final use
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
class Price(ShadowPrice):
|
|
776
|
+
"""
|
|
777
|
+
Concrete class representing a price attribute, indicating the price of a commodity at a specific node.
|
|
778
|
+
|
|
779
|
+
Subclass of ShadowPrice < Coefficient < LevelProfile. See LevelProfile for details.
|
|
780
|
+
"""
|
|
781
|
+
|
|
782
|
+
_IS_ABSTRACT = False
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
class WaterValue(ShadowPrice):
|
|
786
|
+
"""
|
|
787
|
+
Concrete class representing a water value attribute, indicating the value of water in the system.
|
|
788
|
+
|
|
789
|
+
Subclass of ShadowPrice < Coefficient < LevelProfile. See LevelProfile for details.
|
|
790
|
+
"""
|
|
791
|
+
|
|
792
|
+
_IS_ABSTRACT = False
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
class Cost(ObjectiveCoefficient):
|
|
796
|
+
"""
|
|
797
|
+
Concrete class representing a cost attribute, indicating cost coefficients in the objective function.
|
|
798
|
+
|
|
799
|
+
Subclass of ObjectiveCoefficient < Coefficient < LevelProfile. See LevelProfile for details.
|
|
800
|
+
"""
|
|
801
|
+
|
|
802
|
+
_IS_ABSTRACT = False
|
|
803
|
+
_IS_COST = True
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
class ReservePrice(ObjectiveCoefficient):
|
|
807
|
+
"""
|
|
808
|
+
Concrete class representing a reserve price attribute, indicating revenue coefficients in the objective function.
|
|
809
|
+
|
|
810
|
+
Subclass of ObjectiveCoefficient < Coefficient < LevelProfile. See LevelProfile for details.
|
|
811
|
+
"""
|
|
812
|
+
|
|
813
|
+
_IS_ABSTRACT = False
|
|
814
|
+
_IS_COST = False
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
class Elasticity(Coefficient): # TODO: How do this work?
|
|
818
|
+
"""
|
|
819
|
+
Concrete class representing an elasticity coefficient attribute, indicating a unitless coefficient.
|
|
820
|
+
|
|
821
|
+
Subclass of Coefficient < LevelProfile. See LevelProfile for details.
|
|
822
|
+
"""
|
|
823
|
+
|
|
824
|
+
_IS_ABSTRACT = False
|
|
825
|
+
_IS_UNITLESS = True
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
class Proportion(Coefficient):
|
|
829
|
+
"""
|
|
830
|
+
Concrete class representing a proportion coefficient attribute, indicating a unitless coefficient between 0 and 1.
|
|
831
|
+
|
|
832
|
+
Subclass of Coefficient < LevelProfile. See LevelProfile for details.
|
|
833
|
+
"""
|
|
834
|
+
|
|
835
|
+
_IS_ABSTRACT = False
|
|
836
|
+
_IS_UNITLESS = True
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
class Hours(Coefficient): # TODO: How do this work?
|
|
840
|
+
"""
|
|
841
|
+
Concrete class representing an hours coefficient attribute, indicating a time-related coefficient.
|
|
842
|
+
|
|
843
|
+
Subclass of Coefficient < LevelProfile. See LevelProfile for details.
|
|
844
|
+
"""
|
|
845
|
+
|
|
846
|
+
_IS_ABSTRACT = False
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
class Efficiency(ArrowCoefficient):
|
|
850
|
+
"""
|
|
851
|
+
Concrete class representing an efficiency coefficient attribute, indicating a unitless coefficient.
|
|
852
|
+
|
|
853
|
+
Subclass of ArrowCoefficient < Coefficient < LevelProfile. See LevelProfile for details.
|
|
854
|
+
"""
|
|
855
|
+
|
|
856
|
+
_IS_ABSTRACT = False
|
|
857
|
+
_IS_UNITLESS = True
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
class Loss(ArrowCoefficient): # TODO: Make a loss for storage that is percentage per time
|
|
861
|
+
"""
|
|
862
|
+
Concrete class representing a loss coefficient attribute, indicating a unitless coefficient.
|
|
863
|
+
|
|
864
|
+
Subclass of ArrowCoefficient < Coefficient < LevelProfile. See LevelProfile for details.
|
|
865
|
+
"""
|
|
866
|
+
|
|
867
|
+
_IS_ABSTRACT = False
|
|
868
|
+
_IS_UNITLESS = True
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
class Conversion(ArrowCoefficient):
|
|
872
|
+
"""
|
|
873
|
+
Concrete class representing a conversion coefficient attribute, used for conversion factors in the model.
|
|
874
|
+
|
|
875
|
+
Subclass of ArrowCoefficient < Coefficient < LevelProfile. See LevelProfile for details.
|
|
876
|
+
"""
|
|
877
|
+
|
|
878
|
+
_IS_ABSTRACT = False
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
class AvgFlowVolume(FlowVolume):
|
|
882
|
+
"""
|
|
883
|
+
Concrete class representing an average flow volume attribute, indicating a flow variable with average values.
|
|
884
|
+
|
|
885
|
+
Subclass of FlowVolume < LevelProfile. See LevelProfile for details.
|
|
886
|
+
"""
|
|
887
|
+
|
|
888
|
+
_IS_ABSTRACT = False
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
class MaxFlowVolume(FlowVolume):
|
|
892
|
+
"""
|
|
893
|
+
Concrete class representing a maximum flow volume attribute, indicating a flow variable with maximum values.
|
|
894
|
+
|
|
895
|
+
Subclass of FlowVolume < LevelProfile. See LevelProfile for details.
|
|
896
|
+
"""
|
|
897
|
+
|
|
898
|
+
_IS_ABSTRACT = False
|
|
899
|
+
_IS_MAX_AND_ZERO_ONE = True
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
class StockVolume(LevelProfile):
|
|
903
|
+
"""
|
|
904
|
+
Concrete class representing a stock volume attribute, indicating a stock variable with maximum values.
|
|
905
|
+
|
|
906
|
+
Subclass of LevelProfile. See LevelProfile for details.
|
|
907
|
+
"""
|
|
908
|
+
|
|
909
|
+
_IS_ABSTRACT = False
|
|
910
|
+
_IS_STOCK = True
|
|
911
|
+
_IS_MAX_AND_ZERO_ONE = True
|