fram-core 0.0.0__py3-none-any.whl → 0.1.0a2__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.0a2.dist-info/METADATA +42 -0
- fram_core-0.1.0a2.dist-info/RECORD +100 -0
- {fram_core-0.0.0.dist-info → fram_core-0.1.0a2.dist-info}/WHEEL +1 -2
- fram_core-0.1.0a2.dist-info/licenses/LICENSE.md +8 -0
- framcore/Base.py +142 -0
- framcore/Model.py +73 -0
- framcore/__init__.py +9 -0
- framcore/aggregators/Aggregator.py +153 -0
- framcore/aggregators/HydroAggregator.py +837 -0
- framcore/aggregators/NodeAggregator.py +495 -0
- framcore/aggregators/WindSolarAggregator.py +323 -0
- framcore/aggregators/__init__.py +13 -0
- framcore/aggregators/_utils.py +184 -0
- framcore/attributes/Arrow.py +305 -0
- framcore/attributes/ElasticDemand.py +90 -0
- framcore/attributes/ReservoirCurve.py +37 -0
- framcore/attributes/SoftBound.py +19 -0
- framcore/attributes/StartUpCost.py +54 -0
- framcore/attributes/Storage.py +146 -0
- framcore/attributes/TargetBound.py +18 -0
- framcore/attributes/__init__.py +65 -0
- framcore/attributes/hydro/HydroBypass.py +42 -0
- framcore/attributes/hydro/HydroGenerator.py +83 -0
- framcore/attributes/hydro/HydroPump.py +156 -0
- framcore/attributes/hydro/HydroReservoir.py +27 -0
- framcore/attributes/hydro/__init__.py +13 -0
- framcore/attributes/level_profile_attributes.py +714 -0
- framcore/components/Component.py +112 -0
- framcore/components/Demand.py +130 -0
- framcore/components/Flow.py +167 -0
- framcore/components/HydroModule.py +330 -0
- framcore/components/Node.py +76 -0
- framcore/components/Thermal.py +204 -0
- framcore/components/Transmission.py +183 -0
- framcore/components/_PowerPlant.py +81 -0
- framcore/components/__init__.py +22 -0
- framcore/components/wind_solar.py +67 -0
- framcore/curves/Curve.py +44 -0
- framcore/curves/LoadedCurve.py +155 -0
- framcore/curves/__init__.py +9 -0
- framcore/events/__init__.py +21 -0
- framcore/events/events.py +51 -0
- framcore/expressions/Expr.py +490 -0
- framcore/expressions/__init__.py +28 -0
- framcore/expressions/_get_constant_from_expr.py +483 -0
- framcore/expressions/_time_vector_operations.py +615 -0
- framcore/expressions/_utils.py +73 -0
- framcore/expressions/queries.py +423 -0
- framcore/expressions/units.py +207 -0
- framcore/fingerprints/__init__.py +11 -0
- framcore/fingerprints/fingerprint.py +293 -0
- framcore/juliamodels/JuliaModel.py +161 -0
- framcore/juliamodels/__init__.py +7 -0
- framcore/loaders/__init__.py +10 -0
- framcore/loaders/loaders.py +407 -0
- framcore/metadata/Div.py +73 -0
- framcore/metadata/ExprMeta.py +50 -0
- framcore/metadata/LevelExprMeta.py +17 -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 +48 -0
- framcore/solvers/SolverConfig.py +272 -0
- framcore/solvers/__init__.py +9 -0
- framcore/timeindexes/AverageYearRange.py +20 -0
- framcore/timeindexes/ConstantTimeIndex.py +17 -0
- framcore/timeindexes/DailyIndex.py +21 -0
- framcore/timeindexes/FixedFrequencyTimeIndex.py +762 -0
- framcore/timeindexes/HourlyIndex.py +21 -0
- framcore/timeindexes/IsoCalendarDay.py +31 -0
- framcore/timeindexes/ListTimeIndex.py +197 -0
- framcore/timeindexes/ModelYear.py +17 -0
- framcore/timeindexes/ModelYears.py +18 -0
- framcore/timeindexes/OneYearProfileTimeIndex.py +21 -0
- framcore/timeindexes/ProfileTimeIndex.py +32 -0
- framcore/timeindexes/SinglePeriodTimeIndex.py +37 -0
- framcore/timeindexes/TimeIndex.py +90 -0
- framcore/timeindexes/WeeklyIndex.py +21 -0
- framcore/timeindexes/__init__.py +36 -0
- framcore/timevectors/ConstantTimeVector.py +135 -0
- framcore/timevectors/LinearTransformTimeVector.py +114 -0
- framcore/timevectors/ListTimeVector.py +123 -0
- framcore/timevectors/LoadedTimeVector.py +104 -0
- framcore/timevectors/ReferencePeriod.py +41 -0
- framcore/timevectors/TimeVector.py +94 -0
- framcore/timevectors/__init__.py +17 -0
- framcore/utils/__init__.py +36 -0
- framcore/utils/get_regional_volumes.py +369 -0
- framcore/utils/get_supported_components.py +60 -0
- framcore/utils/global_energy_equivalent.py +46 -0
- framcore/utils/isolate_subnodes.py +163 -0
- framcore/utils/loaders.py +97 -0
- framcore/utils/node_flow_utils.py +236 -0
- framcore/utils/storage_subsystems.py +107 -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,305 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
|
|
8
|
+
from framcore import Base
|
|
9
|
+
from framcore.attributes import Conversion, Efficiency, Loss
|
|
10
|
+
from framcore.querydbs import QueryDB
|
|
11
|
+
from framcore.timeindexes import FixedFrequencyTimeIndex, SinglePeriodTimeIndex, TimeIndex
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from framcore import Model
|
|
15
|
+
from framcore.loaders import Loader
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Arrow(Base):
|
|
19
|
+
"""
|
|
20
|
+
Arrow class is used by Flows to represent contribution of its commodity to Nodes.
|
|
21
|
+
|
|
22
|
+
coefficient = conversion * (1 / efficiency) * (1 - loss)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
node: str,
|
|
28
|
+
is_ingoing: bool,
|
|
29
|
+
conversion: Conversion | None = None,
|
|
30
|
+
efficiency: Efficiency | None = None,
|
|
31
|
+
loss: Loss | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Initialize the Arrow class."""
|
|
34
|
+
self._check_type(node, str)
|
|
35
|
+
self._check_type(is_ingoing, bool)
|
|
36
|
+
self._check_type(conversion, (Conversion, type(None)))
|
|
37
|
+
self._check_type(efficiency, (Efficiency, type(None)))
|
|
38
|
+
self._check_type(loss, (Loss, type(None)))
|
|
39
|
+
self._node = node
|
|
40
|
+
self._is_ingoing = is_ingoing
|
|
41
|
+
self._conversion = conversion
|
|
42
|
+
self._efficiency = efficiency
|
|
43
|
+
self._loss = loss
|
|
44
|
+
|
|
45
|
+
def get_node(self) -> str:
|
|
46
|
+
"""Get the node the arrow is pointing to."""
|
|
47
|
+
return self._node
|
|
48
|
+
|
|
49
|
+
def set_node(self, node: str) -> None:
|
|
50
|
+
"""Set the node the arrow is pointing to."""
|
|
51
|
+
self._check_type(node, str)
|
|
52
|
+
self._node = node
|
|
53
|
+
|
|
54
|
+
def is_ingoing(self) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Return True if arrow is ingoing.
|
|
57
|
+
|
|
58
|
+
Ingoing means the flow variable supplies to node.
|
|
59
|
+
Outgoing means the flow variable takes out of node.
|
|
60
|
+
"""
|
|
61
|
+
return self._is_ingoing
|
|
62
|
+
|
|
63
|
+
def get_conversion(self) -> Conversion | None:
|
|
64
|
+
"""Get the conversion."""
|
|
65
|
+
return self._conversion
|
|
66
|
+
|
|
67
|
+
def set_conversion(self, value: Conversion | None) -> None:
|
|
68
|
+
"""Set the conversion."""
|
|
69
|
+
self._check_type(value, Conversion, type(None))
|
|
70
|
+
self._conversion = value
|
|
71
|
+
|
|
72
|
+
def get_efficiency(self) -> Efficiency | None:
|
|
73
|
+
"""Get the efficiency."""
|
|
74
|
+
return self._efficiency
|
|
75
|
+
|
|
76
|
+
def set_efficiency(self, value: Efficiency | None) -> None:
|
|
77
|
+
"""Set the efficiency."""
|
|
78
|
+
self._check_type(value, Efficiency, type(None))
|
|
79
|
+
self._efficiency = value
|
|
80
|
+
|
|
81
|
+
def get_loss(self) -> Loss | None:
|
|
82
|
+
"""Get the loss."""
|
|
83
|
+
return self._loss
|
|
84
|
+
|
|
85
|
+
def set_loss(self, value: Loss | None) -> None:
|
|
86
|
+
"""Set the loss."""
|
|
87
|
+
self._check_type(value, Loss, type(None))
|
|
88
|
+
self._loss = value
|
|
89
|
+
|
|
90
|
+
def has_profile(self) -> bool:
|
|
91
|
+
"""Return True if any of conversion, efficiency or loss has profile."""
|
|
92
|
+
if self._conversion is not None and self._conversion.has_profile():
|
|
93
|
+
return True
|
|
94
|
+
if self._efficiency is not None and self._efficiency.has_profile():
|
|
95
|
+
return True
|
|
96
|
+
return bool(self._loss is not None and self._loss.has_profile())
|
|
97
|
+
|
|
98
|
+
def get_conversion_unit_set(
|
|
99
|
+
self,
|
|
100
|
+
db: QueryDB | Model,
|
|
101
|
+
) -> set[str]:
|
|
102
|
+
"""Get set of units behind conversion level expr (if any)."""
|
|
103
|
+
if self._conversion is None:
|
|
104
|
+
return set()
|
|
105
|
+
return self._conversion.get_level_unit_set()
|
|
106
|
+
|
|
107
|
+
def get_profile_timeindex_set(
|
|
108
|
+
self,
|
|
109
|
+
db: QueryDB | Model,
|
|
110
|
+
) -> set[TimeIndex]:
|
|
111
|
+
"""
|
|
112
|
+
Get set of timeindexes behind profile.
|
|
113
|
+
|
|
114
|
+
Can be used to run optimized queries, i.e. not asking for
|
|
115
|
+
finer time resolutions than necessary.
|
|
116
|
+
"""
|
|
117
|
+
if self.has_profile() is None:
|
|
118
|
+
return set()
|
|
119
|
+
s = set()
|
|
120
|
+
if self._conversion is not None:
|
|
121
|
+
s.update(self._conversion.get_profile_timeindex_set(db))
|
|
122
|
+
if self._loss is not None:
|
|
123
|
+
s.update(self._loss.get_profile_timeindex_set(db))
|
|
124
|
+
if self._efficiency is not None:
|
|
125
|
+
s.update(self._efficiency.get_profile_timeindex_set(db))
|
|
126
|
+
return s
|
|
127
|
+
|
|
128
|
+
def get_scenario_vector(
|
|
129
|
+
self,
|
|
130
|
+
db: QueryDB | Model,
|
|
131
|
+
scenario_horizon: FixedFrequencyTimeIndex,
|
|
132
|
+
level_period: SinglePeriodTimeIndex,
|
|
133
|
+
unit: str | None,
|
|
134
|
+
is_float32: bool = True,
|
|
135
|
+
) -> NDArray:
|
|
136
|
+
"""Return vector with values along the given scenario horizon using level over level_period."""
|
|
137
|
+
conversion_vector = None
|
|
138
|
+
efficiency_vector = None
|
|
139
|
+
loss_vector = None
|
|
140
|
+
conversion_value = None
|
|
141
|
+
efficiency_value = None
|
|
142
|
+
loss_value = None
|
|
143
|
+
|
|
144
|
+
if self._conversion is not None:
|
|
145
|
+
if self._conversion.has_profile():
|
|
146
|
+
conversion_vector = self._conversion.get_scenario_vector(
|
|
147
|
+
db=db,
|
|
148
|
+
scenario_horizon=scenario_horizon,
|
|
149
|
+
level_period=level_period,
|
|
150
|
+
unit=unit,
|
|
151
|
+
is_float32=is_float32,
|
|
152
|
+
)
|
|
153
|
+
elif self._conversion.has_level():
|
|
154
|
+
conversion_value = self._conversion.get_data_value(
|
|
155
|
+
db=db,
|
|
156
|
+
scenario_horizon=scenario_horizon,
|
|
157
|
+
level_period=level_period,
|
|
158
|
+
unit=unit,
|
|
159
|
+
)
|
|
160
|
+
conversion_value = float(conversion_value)
|
|
161
|
+
|
|
162
|
+
if self._efficiency is not None:
|
|
163
|
+
if self._efficiency.has_profile():
|
|
164
|
+
efficiency_vector = self._efficiency.get_scenario_vector(
|
|
165
|
+
db=db,
|
|
166
|
+
scenario_horizon=scenario_horizon,
|
|
167
|
+
level_period=level_period,
|
|
168
|
+
unit=None,
|
|
169
|
+
is_float32=is_float32,
|
|
170
|
+
)
|
|
171
|
+
elif self._efficiency.has_level():
|
|
172
|
+
efficiency_value = self._efficiency.get_data_value(
|
|
173
|
+
db=db,
|
|
174
|
+
scenario_horizon=scenario_horizon,
|
|
175
|
+
level_period=level_period,
|
|
176
|
+
unit=None,
|
|
177
|
+
)
|
|
178
|
+
efficiency_value = float(efficiency_value)
|
|
179
|
+
|
|
180
|
+
if self._loss is not None:
|
|
181
|
+
if self._loss.has_profile():
|
|
182
|
+
loss_vector = self._loss.get_scenario_vector(
|
|
183
|
+
db=db,
|
|
184
|
+
scenario_horizon=scenario_horizon,
|
|
185
|
+
level_period=level_period,
|
|
186
|
+
unit=None,
|
|
187
|
+
is_float32=is_float32,
|
|
188
|
+
)
|
|
189
|
+
elif self._loss.has_level():
|
|
190
|
+
loss_value = self._loss.get_data_value(
|
|
191
|
+
db=db,
|
|
192
|
+
scenario_horizon=scenario_horizon,
|
|
193
|
+
level_period=level_period,
|
|
194
|
+
unit=None,
|
|
195
|
+
)
|
|
196
|
+
loss_value = float(loss_value)
|
|
197
|
+
|
|
198
|
+
if conversion_value is not None:
|
|
199
|
+
assert conversion_value >= 0, f"Arrow with invalid conversion ({conversion_value}): {self}"
|
|
200
|
+
out = conversion_value
|
|
201
|
+
else:
|
|
202
|
+
out = 1.0
|
|
203
|
+
|
|
204
|
+
if efficiency_value is not None:
|
|
205
|
+
assert efficiency_value > 0, f"Arrow with invalid efficiency ({efficiency_value}): {self}"
|
|
206
|
+
out = out / efficiency_value
|
|
207
|
+
|
|
208
|
+
if loss_value is not None:
|
|
209
|
+
assert loss_value >= 0 or loss_value < 1, f"Arrow with invalid loss ({loss_value}): {self}"
|
|
210
|
+
out = out - out * loss_value
|
|
211
|
+
|
|
212
|
+
if conversion_vector is not None:
|
|
213
|
+
np.multiply(conversion_vector, out, out=conversion_vector)
|
|
214
|
+
out = conversion_vector
|
|
215
|
+
|
|
216
|
+
if efficiency_vector is not None:
|
|
217
|
+
if isinstance(out, float):
|
|
218
|
+
np.divide(out, efficiency_vector, out=efficiency_vector)
|
|
219
|
+
out = efficiency_vector
|
|
220
|
+
else:
|
|
221
|
+
np.divide(out, efficiency_vector, out=out)
|
|
222
|
+
|
|
223
|
+
if loss_vector is not None:
|
|
224
|
+
if isinstance(out, float):
|
|
225
|
+
np.multiply(out, loss_vector, out=loss_vector)
|
|
226
|
+
np.subtract(out, loss_vector, out=loss_vector)
|
|
227
|
+
out = loss_vector
|
|
228
|
+
else:
|
|
229
|
+
np.multiply(out, loss_vector, out=loss_vector)
|
|
230
|
+
np.subtract(out, loss_vector, out=out)
|
|
231
|
+
|
|
232
|
+
if isinstance(out, float):
|
|
233
|
+
num_periods = scenario_horizon.get_num_periods()
|
|
234
|
+
vector = np.ones(num_periods, dtype=np.float32 if is_float32 else np.float64)
|
|
235
|
+
vector.fill(out)
|
|
236
|
+
return vector
|
|
237
|
+
|
|
238
|
+
return out
|
|
239
|
+
|
|
240
|
+
def get_data_value(
|
|
241
|
+
self,
|
|
242
|
+
db: QueryDB | Model,
|
|
243
|
+
scenario_horizon: FixedFrequencyTimeIndex,
|
|
244
|
+
level_period: SinglePeriodTimeIndex,
|
|
245
|
+
unit: str | None,
|
|
246
|
+
is_max_level: bool = False,
|
|
247
|
+
) -> float:
|
|
248
|
+
"""Return float for level_period."""
|
|
249
|
+
conversion_value = None
|
|
250
|
+
efficiency_value = None
|
|
251
|
+
loss_value = None
|
|
252
|
+
|
|
253
|
+
if self._conversion is not None and self._conversion.has_level():
|
|
254
|
+
conversion_value = self._conversion.get_data_value(
|
|
255
|
+
db=db,
|
|
256
|
+
scenario_horizon=scenario_horizon,
|
|
257
|
+
level_period=level_period,
|
|
258
|
+
unit=unit,
|
|
259
|
+
is_max_level=is_max_level,
|
|
260
|
+
)
|
|
261
|
+
conversion_value = float(conversion_value)
|
|
262
|
+
|
|
263
|
+
if self._efficiency is not None and self._efficiency.has_level():
|
|
264
|
+
efficiency_value = self._efficiency.get_data_value(
|
|
265
|
+
db=db,
|
|
266
|
+
scenario_horizon=scenario_horizon,
|
|
267
|
+
level_period=level_period,
|
|
268
|
+
unit=None,
|
|
269
|
+
is_max_level=is_max_level,
|
|
270
|
+
)
|
|
271
|
+
efficiency_value = float(efficiency_value)
|
|
272
|
+
|
|
273
|
+
if self._loss is not None and self._loss.has_level():
|
|
274
|
+
loss_value = self._loss.get_data_value(
|
|
275
|
+
db=db,
|
|
276
|
+
scenario_horizon=scenario_horizon,
|
|
277
|
+
level_period=level_period,
|
|
278
|
+
unit=None,
|
|
279
|
+
is_max_level=is_max_level,
|
|
280
|
+
)
|
|
281
|
+
loss_value = float(loss_value)
|
|
282
|
+
|
|
283
|
+
if conversion_value is not None:
|
|
284
|
+
assert conversion_value >= 0, f"Arrow with invalid conversion ({conversion_value}): {self}"
|
|
285
|
+
out = conversion_value
|
|
286
|
+
else:
|
|
287
|
+
out = 1.0
|
|
288
|
+
|
|
289
|
+
if efficiency_value is not None:
|
|
290
|
+
assert efficiency_value > 0, f"Arrow with invalid efficiency ({efficiency_value}): {self}"
|
|
291
|
+
out = out / efficiency_value
|
|
292
|
+
|
|
293
|
+
if loss_value is not None:
|
|
294
|
+
assert loss_value >= 0 or loss_value < 1, f"Arrow with invalid loss ({loss_value}): {self}"
|
|
295
|
+
out = out - out * loss_value
|
|
296
|
+
|
|
297
|
+
return out
|
|
298
|
+
|
|
299
|
+
def add_loaders(self, loaders: set[Loader]) -> None:
|
|
300
|
+
"""Add all loaders stored in attributes to loaders."""
|
|
301
|
+
from framcore.utils import add_loaders_if # noqa: PLC0415
|
|
302
|
+
|
|
303
|
+
add_loaders_if(loaders, self.get_conversion())
|
|
304
|
+
add_loaders_if(loaders, self.get_loss())
|
|
305
|
+
add_loaders_if(loaders, self.get_efficiency())
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""ElasticDemand attribute class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from framcore import Base
|
|
8
|
+
from framcore.attributes import Elasticity, Price
|
|
9
|
+
|
|
10
|
+
# TODO: Discuss price interval validation. Should we check in init? How?
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from framcore.loaders import Loader
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ElasticDemand(Base):
|
|
17
|
+
"""
|
|
18
|
+
ElasticDemand class representing the price elasticity of a demand Component.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
_price_elasticity: The price elasticity factor of the demand consumer.
|
|
22
|
+
_min_price: Lower limit for price elasticity.
|
|
23
|
+
_normal_price: Price for which the demand is inelastic. If it deviates from this price, the consumer will adjust
|
|
24
|
+
it's consumption according to the _price_elasticity factor.
|
|
25
|
+
_max_price: Upper limit for price elasticity / reservation price level.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
price_elasticity: Elasticity,
|
|
32
|
+
min_price: Price,
|
|
33
|
+
normal_price: Price,
|
|
34
|
+
max_price: Price,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Initialize the ElasticDemand class."""
|
|
37
|
+
self._check_type(price_elasticity, Elasticity)
|
|
38
|
+
self._check_type(min_price, Price)
|
|
39
|
+
self._check_type(normal_price, Price)
|
|
40
|
+
self._check_type(max_price, Price)
|
|
41
|
+
|
|
42
|
+
self._price_elasticity = price_elasticity
|
|
43
|
+
self._min_price = min_price
|
|
44
|
+
self._normal_price = normal_price
|
|
45
|
+
self._max_price = max_price
|
|
46
|
+
|
|
47
|
+
def get_price_elasticity(self) -> Elasticity:
|
|
48
|
+
"""Get the price elasticity."""
|
|
49
|
+
return self._price_elasticity
|
|
50
|
+
|
|
51
|
+
def set_price_elasticity(self, elasticity: Price) -> None:
|
|
52
|
+
"""Set the price elasticity."""
|
|
53
|
+
self._check_type(elasticity, Elasticity)
|
|
54
|
+
self._price_elasticity = elasticity
|
|
55
|
+
|
|
56
|
+
def get_min_price(self) -> Price:
|
|
57
|
+
"""Get the minimum price."""
|
|
58
|
+
return self._min_price
|
|
59
|
+
|
|
60
|
+
def set_min_price(self, min_price: Price) -> None:
|
|
61
|
+
"""Set the minimum price."""
|
|
62
|
+
self._check_type(min_price, Price)
|
|
63
|
+
self._min_price = min_price
|
|
64
|
+
|
|
65
|
+
def get_normal_price(self) -> Price:
|
|
66
|
+
"""Get the normal price."""
|
|
67
|
+
return self._normal_price
|
|
68
|
+
|
|
69
|
+
def set_normal_price(self, normal_price: Price) -> None:
|
|
70
|
+
"""Set the normal price."""
|
|
71
|
+
self._check_type(normal_price, Price)
|
|
72
|
+
self._normal_price = normal_price
|
|
73
|
+
|
|
74
|
+
def get_max_price(self) -> Price:
|
|
75
|
+
"""Get the maximum price."""
|
|
76
|
+
return self._max_price
|
|
77
|
+
|
|
78
|
+
def set_max_price(self, max_price: Price) -> None:
|
|
79
|
+
"""Set the maximum price."""
|
|
80
|
+
self._check_type(max_price, Price)
|
|
81
|
+
self._max_price = max_price
|
|
82
|
+
|
|
83
|
+
def add_loaders(self, loaders: set[Loader]) -> None:
|
|
84
|
+
"""Add all loaders stored in attributes to loaders."""
|
|
85
|
+
from framcore.utils import add_loaders_if # noqa: PLC0415
|
|
86
|
+
|
|
87
|
+
add_loaders_if(loaders, self._normal_price)
|
|
88
|
+
add_loaders_if(loaders, self._price_elasticity)
|
|
89
|
+
add_loaders_if(loaders, self._max_price)
|
|
90
|
+
add_loaders_if(loaders, self._min_price)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from framcore import Base
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from framcore.loaders import Loader
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReservoirCurve(Base):
|
|
12
|
+
"""
|
|
13
|
+
Represents a reservoir curve attribute.
|
|
14
|
+
|
|
15
|
+
Attributes
|
|
16
|
+
----------
|
|
17
|
+
_value : str | None
|
|
18
|
+
The value representing the reservoir curve.
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, value: str | None) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Initialize a ReservoirCurve instance.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
value : str | None
|
|
29
|
+
The value representing the reservoir curve.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
self._check_type(value, (str, type(None)))
|
|
33
|
+
self._value = value
|
|
34
|
+
|
|
35
|
+
def add_loaders(self, loaders: set[Loader]) -> None:
|
|
36
|
+
"""Add all loaders stored in attributes to loaders."""
|
|
37
|
+
return
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from framcore.loaders import Loader
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SoftBound:
|
|
10
|
+
"""
|
|
11
|
+
Represents a soft bound attribute.
|
|
12
|
+
|
|
13
|
+
This class can be extended to define soft bounds for various parameters.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def add_loaders(self, loaders: set[Loader]) -> None:
|
|
18
|
+
"""Add all loaders stored in attributes to loaders."""
|
|
19
|
+
return
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from framcore import Base
|
|
6
|
+
from framcore.attributes import Cost, Efficiency, Hours, Proportion
|
|
7
|
+
from framcore.fingerprints import Fingerprint
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from framcore.loaders import Loader
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StartUpCost(Base):
|
|
14
|
+
"""StartUpCost class representing the startup cost of a Component."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
startup_cost: Cost,
|
|
19
|
+
min_stable_load: Proportion,
|
|
20
|
+
start_hours: Hours,
|
|
21
|
+
part_load_efficiency: Efficiency,
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Initialize the StartUpCost class."""
|
|
24
|
+
self._check_type(startup_cost, Cost)
|
|
25
|
+
self._check_type(min_stable_load, Proportion)
|
|
26
|
+
self._check_type(start_hours, Hours)
|
|
27
|
+
self._check_type(part_load_efficiency, Efficiency)
|
|
28
|
+
|
|
29
|
+
self._startup_cost = startup_cost
|
|
30
|
+
self._min_stable_load = min_stable_load
|
|
31
|
+
self._start_hours = start_hours
|
|
32
|
+
self._part_load_efficiency = part_load_efficiency
|
|
33
|
+
|
|
34
|
+
def get_startupcost(self) -> Cost:
|
|
35
|
+
"""Get the startup cost."""
|
|
36
|
+
return self._startup_cost
|
|
37
|
+
|
|
38
|
+
def set_startupcost(self, startupcost: Cost) -> None:
|
|
39
|
+
"""Set the startup cost."""
|
|
40
|
+
self._check_type(startupcost, Cost)
|
|
41
|
+
self._startup_cost = startupcost
|
|
42
|
+
|
|
43
|
+
def get_fingerprint(self) -> Fingerprint:
|
|
44
|
+
"""Get the fingerprint of the startup cost."""
|
|
45
|
+
return self.get_fingerprint_default()
|
|
46
|
+
|
|
47
|
+
def add_loaders(self, loaders: set[Loader]) -> None:
|
|
48
|
+
"""Get all loaders stored in attributes."""
|
|
49
|
+
from framcore.utils import add_loaders_if # noqa: PLC0415
|
|
50
|
+
|
|
51
|
+
add_loaders_if(loaders, self.get_startupcost())
|
|
52
|
+
add_loaders_if(loaders, self._start_hours)
|
|
53
|
+
add_loaders_if(loaders, self._min_stable_load)
|
|
54
|
+
add_loaders_if(loaders, self._part_load_efficiency)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from framcore import Base
|
|
6
|
+
from framcore.attributes import Loss, ObjectiveCoefficient, ReservoirCurve, SoftBound, StockVolume, TargetBound
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from framcore.loaders import Loader
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Storage(Base):
|
|
13
|
+
"""
|
|
14
|
+
Represents all types of storage this system supports.
|
|
15
|
+
|
|
16
|
+
Subclasses are supposed to restrict which attributes that are
|
|
17
|
+
used, not add more.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
capacity: StockVolume,
|
|
23
|
+
volume: StockVolume | None = None,
|
|
24
|
+
loss: Loss | None = None,
|
|
25
|
+
reservoir_curve: ReservoirCurve | None = None,
|
|
26
|
+
max_soft_bound: SoftBound | None = None,
|
|
27
|
+
min_soft_bound: SoftBound | None = None,
|
|
28
|
+
target_bound: TargetBound | None = None,
|
|
29
|
+
initial_storage_percentage: float | None = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Create new storage."""
|
|
32
|
+
super().__init__()
|
|
33
|
+
|
|
34
|
+
self._check_type(capacity, StockVolume)
|
|
35
|
+
self._check_type(volume, (StockVolume, type(None)))
|
|
36
|
+
self._check_type(loss, (StockVolume, type(None)))
|
|
37
|
+
self._check_type(reservoir_curve, (ReservoirCurve, type(None)))
|
|
38
|
+
self._check_type(max_soft_bound, (SoftBound, type(None)))
|
|
39
|
+
self._check_type(min_soft_bound, (SoftBound, type(None)))
|
|
40
|
+
self._check_type(target_bound, (TargetBound, type(None)))
|
|
41
|
+
self._check_type(initial_storage_percentage, (float, type(None)))
|
|
42
|
+
|
|
43
|
+
if initial_storage_percentage is not None:
|
|
44
|
+
self._check_float(initial_storage_percentage, lower_bound=0.0, upper_bound=1.0)
|
|
45
|
+
|
|
46
|
+
self._capacity = capacity
|
|
47
|
+
|
|
48
|
+
self._loss = loss
|
|
49
|
+
self._reservoir_curve = reservoir_curve
|
|
50
|
+
self._max_soft_bound = max_soft_bound
|
|
51
|
+
self._min_soft_bound = min_soft_bound
|
|
52
|
+
self._target_bound = target_bound
|
|
53
|
+
self._initial_storage_percentage = initial_storage_percentage
|
|
54
|
+
|
|
55
|
+
self._cost_terms: dict[str, ObjectiveCoefficient] = dict()
|
|
56
|
+
|
|
57
|
+
if volume is None:
|
|
58
|
+
volume = StockVolume()
|
|
59
|
+
self._volume = volume
|
|
60
|
+
|
|
61
|
+
def get_capacity(self) -> StockVolume:
|
|
62
|
+
"""Get the capacity."""
|
|
63
|
+
return self._capacity
|
|
64
|
+
|
|
65
|
+
def get_volume(self) -> StockVolume:
|
|
66
|
+
"""Get the volume."""
|
|
67
|
+
return self._volume
|
|
68
|
+
|
|
69
|
+
def add_cost_term(self, key: str, cost_term: ObjectiveCoefficient) -> None:
|
|
70
|
+
"""Add a cost term."""
|
|
71
|
+
self._check_type(key, str)
|
|
72
|
+
self._check_type(cost_term, ObjectiveCoefficient)
|
|
73
|
+
self._cost_terms[key] = cost_term
|
|
74
|
+
|
|
75
|
+
def get_cost_terms(self) -> dict[str, ObjectiveCoefficient]:
|
|
76
|
+
"""Get the cost terms."""
|
|
77
|
+
return self._cost_terms
|
|
78
|
+
|
|
79
|
+
def get_loss(self) -> Loss | None:
|
|
80
|
+
"""Get the loss."""
|
|
81
|
+
return self._loss
|
|
82
|
+
|
|
83
|
+
def set_loss(self, value: Loss | None) -> None:
|
|
84
|
+
"""Set the loss."""
|
|
85
|
+
self._check_type(value, (Loss, type(None)))
|
|
86
|
+
self._loss = value
|
|
87
|
+
|
|
88
|
+
def get_reservoir_curve(self) -> ReservoirCurve | None:
|
|
89
|
+
"""Get the reservoir curve."""
|
|
90
|
+
return self._reservoir_curve
|
|
91
|
+
|
|
92
|
+
def set_reservoir_curve(self, value: ReservoirCurve | None) -> None:
|
|
93
|
+
"""Set the reservoir curve."""
|
|
94
|
+
self._check_type(value, (ReservoirCurve, type(None)))
|
|
95
|
+
self._reservoir_curve = value
|
|
96
|
+
|
|
97
|
+
def get_max_soft_bound(self) -> SoftBound | None:
|
|
98
|
+
"""Get the max soft bound."""
|
|
99
|
+
return self._max_soft_bound
|
|
100
|
+
|
|
101
|
+
def set_max_soft_bound(self, value: SoftBound | None) -> None:
|
|
102
|
+
"""Set the max soft bound."""
|
|
103
|
+
self._check_type(value, (SoftBound, type(None)))
|
|
104
|
+
self._max_soft_bound = value
|
|
105
|
+
|
|
106
|
+
def get_min_soft_bound(self) -> SoftBound | None:
|
|
107
|
+
"""Get the min soft bound."""
|
|
108
|
+
return self._min_soft_bound
|
|
109
|
+
|
|
110
|
+
def set_min_soft_bound(self, value: SoftBound | None) -> None:
|
|
111
|
+
"""Set the min soft bound."""
|
|
112
|
+
self._check_type(value, (SoftBound, type(None)))
|
|
113
|
+
self._min_soft_bound = value
|
|
114
|
+
|
|
115
|
+
def get_target_bound(self) -> TargetBound | None:
|
|
116
|
+
"""Get the target bound."""
|
|
117
|
+
return self._target_bound
|
|
118
|
+
|
|
119
|
+
def set_target_bound(self, value: TargetBound | None) -> None:
|
|
120
|
+
"""Set the target bound."""
|
|
121
|
+
self._check_type(value, (TargetBound, type(None)))
|
|
122
|
+
self._target_bound = value
|
|
123
|
+
|
|
124
|
+
def get_initial_storage_percentage(self) -> float | None:
|
|
125
|
+
"""Get the initial storage percentage (float in [0, 1])."""
|
|
126
|
+
return self._initial_storage_percentage
|
|
127
|
+
|
|
128
|
+
def set_initial_storage_percentage(self, value: float) -> None:
|
|
129
|
+
"""Set the initial storage percentage (float in [0, 1])."""
|
|
130
|
+
self._check_float(value, lower_bound=0.0, upper_bound=1.0)
|
|
131
|
+
self._initial_storage_percentage = value
|
|
132
|
+
|
|
133
|
+
def add_loaders(self, loaders: set[Loader]) -> None:
|
|
134
|
+
"""Add all loaders stored in attributes to loaders."""
|
|
135
|
+
from framcore.utils import add_loaders_if # noqa: PLC0415
|
|
136
|
+
|
|
137
|
+
add_loaders_if(loaders, self.get_capacity())
|
|
138
|
+
add_loaders_if(loaders, self.get_loss())
|
|
139
|
+
add_loaders_if(loaders, self.get_volume())
|
|
140
|
+
add_loaders_if(loaders, self.get_max_soft_bound())
|
|
141
|
+
add_loaders_if(loaders, self.get_min_soft_bound())
|
|
142
|
+
add_loaders_if(loaders, self.get_reservoir_curve())
|
|
143
|
+
add_loaders_if(loaders, self.get_target_bound())
|
|
144
|
+
|
|
145
|
+
for cost in self.get_cost_terms().values():
|
|
146
|
+
add_loaders_if(loaders, cost)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from framcore.loaders import Loader
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TargetBound:
|
|
10
|
+
"""
|
|
11
|
+
Represents a target bound attribute.
|
|
12
|
+
|
|
13
|
+
This class can be extended to define specific bounds for targets in the energy model.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def add_loaders(self, loaders: set[Loader]) -> None:
|
|
17
|
+
"""Add all loaders stored in attributes to loaders."""
|
|
18
|
+
return
|