fram-core 0.0.0__py3-none-any.whl → 0.1.0a1__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.0a1.dist-info/METADATA +41 -0
- fram_core-0.1.0a1.dist-info/RECORD +100 -0
- {fram_core-0.0.0.dist-info → fram_core-0.1.0a1.dist-info}/WHEEL +1 -2
- fram_core-0.1.0a1.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,330 @@
|
|
|
1
|
+
from framcore.attributes import Arrow, AvgFlowVolume, Conversion, FlowVolume, HydroBypass, HydroGenerator, HydroPump, HydroReservoir, WaterValue
|
|
2
|
+
from framcore.components import Component, Flow, Node
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HydroModule(Component):
|
|
6
|
+
"""HydroModule class representing a hydro module component."""
|
|
7
|
+
|
|
8
|
+
# We add this to module name to get corresponding node name
|
|
9
|
+
_NODE_NAME_POSTFIX = "_node"
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
release_to: str | None = None, # Must be reference to another HydroModule
|
|
14
|
+
release_capacity: FlowVolume | None = None,
|
|
15
|
+
generator: HydroGenerator | None = None, # attribute
|
|
16
|
+
pump: HydroPump | None = None,
|
|
17
|
+
inflow: AvgFlowVolume | None = None,
|
|
18
|
+
reservoir: HydroReservoir | None = None, # attribute
|
|
19
|
+
hydraulic_coupling: int = 0,
|
|
20
|
+
bypass: HydroBypass | None = None, # attribute
|
|
21
|
+
spill_to: str | None = None, # Must be reference to another HydroModule
|
|
22
|
+
commodity: str = "Hydro",
|
|
23
|
+
water_value: WaterValue | None = None,
|
|
24
|
+
release_volume: AvgFlowVolume | None = None,
|
|
25
|
+
spill_volume: AvgFlowVolume | None = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Initialize the HydroModule with its parameters."""
|
|
28
|
+
super().__init__()
|
|
29
|
+
self._check_type(release_to, (str, type(None)))
|
|
30
|
+
self._check_type(release_capacity, (FlowVolume, type(None)))
|
|
31
|
+
self._check_type(generator, (HydroGenerator, type(None)))
|
|
32
|
+
self._check_type(pump, (HydroPump, type(None)))
|
|
33
|
+
self._check_type(inflow, (AvgFlowVolume, type(None)))
|
|
34
|
+
self._check_type(reservoir, (HydroReservoir, type(None)))
|
|
35
|
+
self._check_type(hydraulic_coupling, int)
|
|
36
|
+
self._check_type(bypass, (HydroBypass, type(None)))
|
|
37
|
+
self._check_type(spill_to, (str, type(None)))
|
|
38
|
+
self._check_type(commodity, str)
|
|
39
|
+
self._check_type(water_value, (WaterValue, type(None)))
|
|
40
|
+
self._check_type(release_volume, (AvgFlowVolume, type(None)))
|
|
41
|
+
self._check_type(spill_volume, (AvgFlowVolume, type(None)))
|
|
42
|
+
|
|
43
|
+
self._release_to = release_to
|
|
44
|
+
self._release_capacity = release_capacity
|
|
45
|
+
self._generator = generator
|
|
46
|
+
self._pump = pump
|
|
47
|
+
self._inflow = inflow
|
|
48
|
+
self._reservoir = reservoir
|
|
49
|
+
self._hydraulic_coupling = hydraulic_coupling
|
|
50
|
+
self._bypass = bypass
|
|
51
|
+
self._spill_to = spill_to
|
|
52
|
+
self._commodity = commodity
|
|
53
|
+
|
|
54
|
+
if not water_value:
|
|
55
|
+
water_value = WaterValue()
|
|
56
|
+
|
|
57
|
+
if not release_volume:
|
|
58
|
+
release_volume = AvgFlowVolume()
|
|
59
|
+
|
|
60
|
+
if not spill_volume:
|
|
61
|
+
spill_volume = AvgFlowVolume()
|
|
62
|
+
|
|
63
|
+
self._water_value: WaterValue = water_value
|
|
64
|
+
self._release_volume: AvgFlowVolume = release_volume
|
|
65
|
+
self._spill_volume: AvgFlowVolume = spill_volume
|
|
66
|
+
|
|
67
|
+
def get_release_capacity(self) -> FlowVolume | None:
|
|
68
|
+
"""Get the capacity of the thermal unit."""
|
|
69
|
+
return self._release_capacity
|
|
70
|
+
|
|
71
|
+
def get_hydraulic_coupling(self) -> int:
|
|
72
|
+
"""Get the Modules hydraulic code."""
|
|
73
|
+
return self._hydraulic_coupling
|
|
74
|
+
|
|
75
|
+
def get_reservoir(self) -> HydroReservoir | None:
|
|
76
|
+
"""Get the reservoir of the hydro module."""
|
|
77
|
+
return self._reservoir
|
|
78
|
+
|
|
79
|
+
def set_reservoir(self, reservoir: HydroReservoir | None) -> None:
|
|
80
|
+
"""Set the reservoir of the hydro module."""
|
|
81
|
+
self._check_type(reservoir, (HydroReservoir, type(None)))
|
|
82
|
+
self._reservoir = reservoir
|
|
83
|
+
|
|
84
|
+
def get_pump(self) -> HydroPump | None:
|
|
85
|
+
"""Get the pump of the hydro module."""
|
|
86
|
+
return self._pump
|
|
87
|
+
|
|
88
|
+
def set_pump(self, pump: HydroPump | None) -> None:
|
|
89
|
+
"""Set the pump of the hydro module."""
|
|
90
|
+
self._check_type(pump, (HydroPump, type(None)))
|
|
91
|
+
self._pump = pump
|
|
92
|
+
|
|
93
|
+
def get_generator(self) -> HydroGenerator | None:
|
|
94
|
+
"""Get the generator of the hydro module."""
|
|
95
|
+
return self._generator
|
|
96
|
+
|
|
97
|
+
def set_generator(self, generator: HydroGenerator | None) -> None:
|
|
98
|
+
"""Set the generator of the hydro module."""
|
|
99
|
+
self._check_type(generator, (HydroGenerator, type(None)))
|
|
100
|
+
self._generator = generator
|
|
101
|
+
|
|
102
|
+
def get_bypass(self) -> HydroBypass | None:
|
|
103
|
+
"""Get the bypass of the hydro module."""
|
|
104
|
+
return self._bypass
|
|
105
|
+
|
|
106
|
+
def set_bypass(self, bypass: HydroBypass | None) -> None:
|
|
107
|
+
"""Set the bypass of the hydro module."""
|
|
108
|
+
self._check_type(bypass, (HydroBypass, type(None)))
|
|
109
|
+
self._bypass = bypass
|
|
110
|
+
|
|
111
|
+
def get_inflow(self) -> AvgFlowVolume | None:
|
|
112
|
+
"""Get the inflow of the hydro module."""
|
|
113
|
+
return self._inflow
|
|
114
|
+
|
|
115
|
+
def set_inflow(self, inflow: AvgFlowVolume | None) -> None:
|
|
116
|
+
"""Set the inflow of the hydro module."""
|
|
117
|
+
self._check_type(inflow, (AvgFlowVolume, type(None)))
|
|
118
|
+
self._inflow = inflow
|
|
119
|
+
|
|
120
|
+
def get_release_to(self) -> str | None:
|
|
121
|
+
"""Get the release_to module of the hydro module."""
|
|
122
|
+
return self._release_to
|
|
123
|
+
|
|
124
|
+
def set_release_to(self, release_to: str | None) -> None:
|
|
125
|
+
"""Set the release_to module of the hydro module."""
|
|
126
|
+
self._check_type(release_to, (str, type(None)))
|
|
127
|
+
self._release_to = release_to
|
|
128
|
+
|
|
129
|
+
def get_spill_to(self) -> str | None:
|
|
130
|
+
"""Get the spill_to module of the hydro module."""
|
|
131
|
+
return self._spill_to
|
|
132
|
+
|
|
133
|
+
def get_water_value(self) -> WaterValue:
|
|
134
|
+
"""Get water value at the hydro node."""
|
|
135
|
+
return self._water_value
|
|
136
|
+
|
|
137
|
+
def get_release_volume(self) -> FlowVolume:
|
|
138
|
+
"""Get the release_volume volume of the thermal unit."""
|
|
139
|
+
return self._release_volume
|
|
140
|
+
|
|
141
|
+
def get_spill_volume(self) -> FlowVolume:
|
|
142
|
+
"""Get the spill_volume volume of the thermal unit."""
|
|
143
|
+
return self._spill_volume
|
|
144
|
+
|
|
145
|
+
"""Implementation of Component interface"""
|
|
146
|
+
|
|
147
|
+
def _replace_node(self, old: str, new: str) -> None:
|
|
148
|
+
if self._pump and old == self._pump.get_power_node():
|
|
149
|
+
self._pump.set_power_node(new)
|
|
150
|
+
if self._generator and old == self._generator.get_power_node():
|
|
151
|
+
self._generator.set_power_node(new)
|
|
152
|
+
|
|
153
|
+
def _get_simpler_components(self, module_name: str) -> dict[str, Component]:
|
|
154
|
+
out: dict[str, Component] = {}
|
|
155
|
+
|
|
156
|
+
node_name = module_name + self._NODE_NAME_POSTFIX
|
|
157
|
+
|
|
158
|
+
out[node_name] = self._create_hydro_node()
|
|
159
|
+
out[module_name + "_release_flow"] = self._create_release_flow(node_name)
|
|
160
|
+
out[module_name + "_spill_flow"] = self._create_spill_flow(node_name)
|
|
161
|
+
|
|
162
|
+
if self._inflow is not None:
|
|
163
|
+
out[module_name + "_inflow_flow"] = self._create_inflow_flow(node_name)
|
|
164
|
+
|
|
165
|
+
if self._bypass is not None:
|
|
166
|
+
out[module_name + "_bypass_flow"] = self._create_bypass_flow(node_name)
|
|
167
|
+
|
|
168
|
+
if self._pump is not None:
|
|
169
|
+
out[module_name + "_pump_flow"] = self._create_pump_flow(node_name)
|
|
170
|
+
|
|
171
|
+
return out
|
|
172
|
+
|
|
173
|
+
def _create_hydro_node(self) -> Node:
|
|
174
|
+
return Node(
|
|
175
|
+
commodity=self._commodity,
|
|
176
|
+
price=self._water_value,
|
|
177
|
+
storage=self._reservoir,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def _create_release_flow(self, node_name: str) -> Flow:
|
|
181
|
+
# TODO: pq_curve, nominal_head, tailwater_elevation
|
|
182
|
+
flow = Flow(
|
|
183
|
+
main_node=node_name,
|
|
184
|
+
max_capacity=self._release_capacity,
|
|
185
|
+
volume=self._release_volume,
|
|
186
|
+
startupcost=None,
|
|
187
|
+
arrow_volumes=None,
|
|
188
|
+
is_exogenous=False,
|
|
189
|
+
)
|
|
190
|
+
arrow_volumes = flow.get_arrow_volumes()
|
|
191
|
+
|
|
192
|
+
outgoing_arrow = Arrow(
|
|
193
|
+
node=node_name,
|
|
194
|
+
is_ingoing=False,
|
|
195
|
+
conversion=Conversion(value=1),
|
|
196
|
+
)
|
|
197
|
+
flow.add_arrow(outgoing_arrow)
|
|
198
|
+
|
|
199
|
+
if self._release_to:
|
|
200
|
+
flow.add_arrow(
|
|
201
|
+
Arrow(
|
|
202
|
+
node=self._release_to + self._NODE_NAME_POSTFIX,
|
|
203
|
+
is_ingoing=True,
|
|
204
|
+
conversion=Conversion(value=1),
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if self._generator:
|
|
209
|
+
production_arrow = Arrow(
|
|
210
|
+
node=self._generator.get_power_node(),
|
|
211
|
+
is_ingoing=True,
|
|
212
|
+
conversion=self._generator.get_energy_eq(),
|
|
213
|
+
)
|
|
214
|
+
flow.add_arrow(production_arrow)
|
|
215
|
+
arrow_volumes[production_arrow] = self._generator.get_production()
|
|
216
|
+
|
|
217
|
+
if self._generator.get_voc() is not None:
|
|
218
|
+
flow.add_cost_term("VOC", self._generator.get_voc())
|
|
219
|
+
|
|
220
|
+
return flow
|
|
221
|
+
|
|
222
|
+
def _create_spill_flow(self, node_name: str) -> Flow:
|
|
223
|
+
flow = Flow(
|
|
224
|
+
main_node=node_name,
|
|
225
|
+
max_capacity=None,
|
|
226
|
+
volume=self._spill_volume,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
flow.add_arrow(
|
|
230
|
+
Arrow(
|
|
231
|
+
node=node_name,
|
|
232
|
+
is_ingoing=False,
|
|
233
|
+
conversion=Conversion(value=1),
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if self._spill_to is not None:
|
|
238
|
+
flow.add_arrow(
|
|
239
|
+
Arrow(
|
|
240
|
+
node=self._spill_to + self._NODE_NAME_POSTFIX,
|
|
241
|
+
is_ingoing=True,
|
|
242
|
+
conversion=Conversion(value=1),
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return flow
|
|
247
|
+
|
|
248
|
+
def _create_bypass_flow(self, node_name: str) -> Flow:
|
|
249
|
+
flow = Flow(
|
|
250
|
+
main_node=node_name,
|
|
251
|
+
max_capacity=self._bypass.get_capacity(),
|
|
252
|
+
volume=self._bypass.get_volume(),
|
|
253
|
+
is_exogenous=False,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
flow.add_arrow(
|
|
257
|
+
Arrow(
|
|
258
|
+
node=node_name,
|
|
259
|
+
is_ingoing=False,
|
|
260
|
+
conversion=Conversion(value=1),
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if self._bypass.get_to_module() is not None:
|
|
265
|
+
flow.add_arrow(
|
|
266
|
+
Arrow(
|
|
267
|
+
node=self._bypass.get_to_module() + self._NODE_NAME_POSTFIX,
|
|
268
|
+
is_ingoing=True,
|
|
269
|
+
conversion=Conversion(value=1),
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
return flow
|
|
274
|
+
|
|
275
|
+
def _create_inflow_flow(self, node_name: str) -> Flow:
|
|
276
|
+
flow = Flow(
|
|
277
|
+
main_node=node_name,
|
|
278
|
+
max_capacity=None,
|
|
279
|
+
volume=self._inflow,
|
|
280
|
+
is_exogenous=True,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
flow.add_arrow(
|
|
284
|
+
Arrow(
|
|
285
|
+
node=node_name,
|
|
286
|
+
is_ingoing=True,
|
|
287
|
+
conversion=Conversion(value=1),
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return flow
|
|
292
|
+
|
|
293
|
+
def _create_pump_flow(self, node_name: str) -> Flow:
|
|
294
|
+
# TODO: add rest of attributes
|
|
295
|
+
|
|
296
|
+
arrow_volumes: dict[Arrow, FlowVolume] = dict()
|
|
297
|
+
|
|
298
|
+
flow = Flow(
|
|
299
|
+
main_node=node_name,
|
|
300
|
+
max_capacity=self._pump.get_water_capacity(),
|
|
301
|
+
volume=self._pump.get_water_consumption(),
|
|
302
|
+
arrow_volumes=arrow_volumes,
|
|
303
|
+
is_exogenous=False,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
flow.add_arrow(
|
|
307
|
+
Arrow(
|
|
308
|
+
node=self._pump.get_to_module() + self._NODE_NAME_POSTFIX,
|
|
309
|
+
is_ingoing=True,
|
|
310
|
+
conversion=Conversion(value=1),
|
|
311
|
+
),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
flow.add_arrow(
|
|
315
|
+
Arrow(
|
|
316
|
+
node=self._pump.get_from_module() + self._NODE_NAME_POSTFIX,
|
|
317
|
+
is_ingoing=False,
|
|
318
|
+
conversion=Conversion(value=1),
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
pump_arrow = Arrow(
|
|
323
|
+
node=self._pump.get_power_node(),
|
|
324
|
+
is_ingoing=False,
|
|
325
|
+
conversion=self._pump.get_energy_eq(),
|
|
326
|
+
)
|
|
327
|
+
flow.add_arrow(pump_arrow)
|
|
328
|
+
arrow_volumes[pump_arrow] = self._pump.get_power_consumption()
|
|
329
|
+
|
|
330
|
+
return flow
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from framcore.attributes import Price, ShaddowPrice, Storage
|
|
6
|
+
from framcore.components import Component
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from framcore.loaders import Loader
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Node(Component):
|
|
13
|
+
"""Node class. Subclass of Component."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
commodity: str,
|
|
18
|
+
is_exogenous: bool = False, # TODO
|
|
19
|
+
price: ShaddowPrice | None = None,
|
|
20
|
+
storage: Storage | None = None,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Initialize the Node class."""
|
|
23
|
+
super().__init__()
|
|
24
|
+
self._check_type(commodity, str)
|
|
25
|
+
self._check_type(is_exogenous, bool)
|
|
26
|
+
self._check_type(price, (ShaddowPrice, type(None)))
|
|
27
|
+
self._check_type(storage, (Storage, type(None)))
|
|
28
|
+
|
|
29
|
+
self._commodity = commodity
|
|
30
|
+
self._is_exogenous = is_exogenous
|
|
31
|
+
|
|
32
|
+
self._storage = storage
|
|
33
|
+
|
|
34
|
+
if price is None:
|
|
35
|
+
price = Price()
|
|
36
|
+
|
|
37
|
+
self._price: Price = price
|
|
38
|
+
|
|
39
|
+
def set_exogenous(self) -> None:
|
|
40
|
+
"""Set internal is_exogenous flag to True."""
|
|
41
|
+
self._check_type(self._is_exogenous, bool)
|
|
42
|
+
self._is_exogenous = True
|
|
43
|
+
|
|
44
|
+
def set_endogenous(self) -> None:
|
|
45
|
+
"""Set internal is_exogenous flag to False."""
|
|
46
|
+
self._check_type(self._is_exogenous, bool)
|
|
47
|
+
self._is_exogenous = False
|
|
48
|
+
|
|
49
|
+
def is_exogenous(self) -> bool:
|
|
50
|
+
"""Return True if Node is exogenous (i.e. has fixed prices determined outside the model) else False."""
|
|
51
|
+
return self._is_exogenous
|
|
52
|
+
|
|
53
|
+
def get_price(self) -> ShaddowPrice:
|
|
54
|
+
"""Return price."""
|
|
55
|
+
return self._price
|
|
56
|
+
|
|
57
|
+
def get_storage(self) -> Storage | None:
|
|
58
|
+
"""Get Storage if any."""
|
|
59
|
+
return self._storage
|
|
60
|
+
|
|
61
|
+
def get_commodity(self) -> str:
|
|
62
|
+
"""Return commodity."""
|
|
63
|
+
return self._commodity
|
|
64
|
+
|
|
65
|
+
def add_loaders(self, loaders: set[Loader]) -> None:
|
|
66
|
+
"""Add loaders stored in attributes to loaders."""
|
|
67
|
+
from framcore.utils import add_loaders_if # noqa: PLC0415
|
|
68
|
+
|
|
69
|
+
add_loaders_if(loaders, self.get_price())
|
|
70
|
+
add_loaders_if(loaders, self.get_storage())
|
|
71
|
+
|
|
72
|
+
def _replace_node(self, old: str, new: str) -> None:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
def _get_simpler_components(self, _: str) -> dict[str, Component]:
|
|
76
|
+
return dict()
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from framcore.attributes import Arrow, AvgFlowVolume, Conversion, Cost, Efficiency, FlowVolume, StartUpCost
|
|
2
|
+
from framcore.components import Component, Flow
|
|
3
|
+
from framcore.components._PowerPlant import _PowerPlant
|
|
4
|
+
|
|
5
|
+
# TODO
|
|
6
|
+
# refactor to use _ensure_flow and _ensure_coeff by using check_type, and possibly other methods
|
|
7
|
+
# add exception to replace_nodes (see implementation in Demand)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Thermal(_PowerPlant):
|
|
11
|
+
"""
|
|
12
|
+
Represents a thermal power plant, subclassing PowerPlant.
|
|
13
|
+
|
|
14
|
+
This class models a thermal power plant with attributes inherited from PowerPlant.
|
|
15
|
+
Additionally, it includes specific attributes such as:
|
|
16
|
+
- fuel node
|
|
17
|
+
- efficiency
|
|
18
|
+
- emission node
|
|
19
|
+
- emission coefficient
|
|
20
|
+
- startup costs
|
|
21
|
+
|
|
22
|
+
This class is compatible with ThermalAggregator.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
power_node: str,
|
|
28
|
+
fuel_node: str,
|
|
29
|
+
efficiency: Efficiency,
|
|
30
|
+
emission_node: str | None = None,
|
|
31
|
+
emission_coefficient: Conversion | None = None,
|
|
32
|
+
startupcost: StartUpCost | None = None,
|
|
33
|
+
max_capacity: FlowVolume | None = None,
|
|
34
|
+
min_capacity: FlowVolume | None = None,
|
|
35
|
+
voc: Cost | None = None,
|
|
36
|
+
production: AvgFlowVolume | None = None,
|
|
37
|
+
fuel_demand: AvgFlowVolume | None = None,
|
|
38
|
+
emission_demand: AvgFlowVolume | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Initialize a Thermal power plant instance.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
power_node (str): The power node of the plant.
|
|
45
|
+
fuel_node (str): The fuel node of the plant.
|
|
46
|
+
efficiency (Efficiency): Efficiency of the plant.
|
|
47
|
+
emission_node (str | None, optional): Emission node.
|
|
48
|
+
emission_coefficient (Conversion | None, optional): Emission coefficient.
|
|
49
|
+
startupcost (StartUpCost | None, optional): Startup cost.
|
|
50
|
+
max_capacity (FlowVolume | None, optional): Maximum capacity.
|
|
51
|
+
min_capacity (FlowVolume | None, optional): Minimum capacity.
|
|
52
|
+
voc (Cost | None, optional): Variable operating cost.
|
|
53
|
+
production (AvgFlowVolume | None, optional): Production volume.
|
|
54
|
+
fuel_demand (AvgFlowVolume | None, optional): Fuel demand.
|
|
55
|
+
emission_demand (AvgFlowVolume | None, optional): Emission demand.
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
super().__init__(
|
|
59
|
+
power_node=power_node,
|
|
60
|
+
max_capacity=max_capacity,
|
|
61
|
+
min_capacity=min_capacity,
|
|
62
|
+
voc=voc,
|
|
63
|
+
production=production,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
self._check_type(fuel_node, str)
|
|
67
|
+
self._check_type(emission_node, (str, type(None)))
|
|
68
|
+
self._check_type(emission_coefficient, (Conversion, type(None)))
|
|
69
|
+
self._check_type(startupcost, (StartUpCost, type(None)))
|
|
70
|
+
self._check_type(production, (AvgFlowVolume, type(None)))
|
|
71
|
+
self._check_type(fuel_demand, (AvgFlowVolume, type(None)))
|
|
72
|
+
self._check_type(emission_demand, (AvgFlowVolume, type(None)))
|
|
73
|
+
|
|
74
|
+
self._fuel_node = fuel_node
|
|
75
|
+
self._efficiency = efficiency
|
|
76
|
+
self._emission_node = emission_node
|
|
77
|
+
self._emission_coefficient = emission_coefficient
|
|
78
|
+
self._startupcost = startupcost
|
|
79
|
+
|
|
80
|
+
if production is None:
|
|
81
|
+
production = AvgFlowVolume()
|
|
82
|
+
|
|
83
|
+
if fuel_demand is None:
|
|
84
|
+
fuel_demand = AvgFlowVolume()
|
|
85
|
+
|
|
86
|
+
if emission_demand is None and emission_node is not None:
|
|
87
|
+
emission_demand = AvgFlowVolume()
|
|
88
|
+
|
|
89
|
+
self._production = production
|
|
90
|
+
self._fuel_demand = fuel_demand
|
|
91
|
+
self._emission_demand = emission_demand
|
|
92
|
+
|
|
93
|
+
def get_fuel_node(self) -> str:
|
|
94
|
+
"""Get the fuel node of the thermal unit."""
|
|
95
|
+
return self._fuel_node
|
|
96
|
+
|
|
97
|
+
def set_fuel_node(self, fuel_node: str) -> None:
|
|
98
|
+
"""Set the fuel node of the thermal unit."""
|
|
99
|
+
self._check_type(fuel_node, str)
|
|
100
|
+
self._fuel_node = fuel_node
|
|
101
|
+
|
|
102
|
+
def get_emission_node(self) -> str | None:
|
|
103
|
+
"""Get the emission node of the thermal unit."""
|
|
104
|
+
return self._emission_node
|
|
105
|
+
|
|
106
|
+
def set_emission_node(self, emission_node: str | None) -> None:
|
|
107
|
+
"""Set the emission node of the thermal unit."""
|
|
108
|
+
self._check_type(emission_node, (str, type(None)))
|
|
109
|
+
self._emission_node = emission_node
|
|
110
|
+
|
|
111
|
+
def get_emission_coefficient(self) -> Conversion | None:
|
|
112
|
+
"""Get the emission coefficient of the thermal unit."""
|
|
113
|
+
return self._emission_coefficient
|
|
114
|
+
|
|
115
|
+
def set_emission_coefficient(self, emission_coefficient: Conversion | None) -> None:
|
|
116
|
+
"""Set the emission coefficient of the thermal unit."""
|
|
117
|
+
self._check_type(emission_coefficient, (Conversion, type(None)))
|
|
118
|
+
self._emission_coefficient = emission_coefficient
|
|
119
|
+
|
|
120
|
+
def get_fuel_demand(self) -> AvgFlowVolume:
|
|
121
|
+
"""Get the fuel demand of the thermal unit."""
|
|
122
|
+
return self._fuel_demand
|
|
123
|
+
|
|
124
|
+
def get_emission_demand(self) -> AvgFlowVolume | None:
|
|
125
|
+
"""Get the emission demand of the thermal unit."""
|
|
126
|
+
return self._emission_demand
|
|
127
|
+
|
|
128
|
+
def set_emission_demand(self, value: AvgFlowVolume | None) -> None:
|
|
129
|
+
"""Set the emission demand of the thermal unit."""
|
|
130
|
+
self._check_type(value, (AvgFlowVolume, type(None)))
|
|
131
|
+
self._emission_demand = value
|
|
132
|
+
|
|
133
|
+
def get_efficiency(self) -> Efficiency:
|
|
134
|
+
"""Get the efficiency of the thermal unit."""
|
|
135
|
+
return self._efficiency
|
|
136
|
+
|
|
137
|
+
def get_startupcost(self) -> StartUpCost | None:
|
|
138
|
+
"""Get the startup cost of the thermal unit."""
|
|
139
|
+
return self._startupcost
|
|
140
|
+
|
|
141
|
+
def set_startupcost(self, startupcost: StartUpCost | None) -> None:
|
|
142
|
+
"""Set the startup cost of the thermal unit."""
|
|
143
|
+
self._check_type(startupcost, (StartUpCost, type(None)))
|
|
144
|
+
self._startupcost = startupcost
|
|
145
|
+
|
|
146
|
+
"""Implementation of Component interface"""
|
|
147
|
+
|
|
148
|
+
def _get_simpler_components(self, base_name: str) -> dict[str, Component]:
|
|
149
|
+
return {base_name + "_Flow": self._create_flow()}
|
|
150
|
+
|
|
151
|
+
def _replace_node(self, old: str, new: str) -> None:
|
|
152
|
+
if self._power_node == old:
|
|
153
|
+
self._power_node = new
|
|
154
|
+
if self._fuel_node == old:
|
|
155
|
+
self._fuel_node = new
|
|
156
|
+
if (self._emission_node is not None) and (old == self._emission_node):
|
|
157
|
+
self._emission_node = new
|
|
158
|
+
|
|
159
|
+
def _create_flow(self) -> Flow:
|
|
160
|
+
arrow_volumes: dict[Arrow, AvgFlowVolume] = dict()
|
|
161
|
+
|
|
162
|
+
is_exogenous = self._max_capacity == self._min_capacity
|
|
163
|
+
|
|
164
|
+
flow = Flow(
|
|
165
|
+
main_node=self._power_node,
|
|
166
|
+
max_capacity=self._max_capacity,
|
|
167
|
+
min_capacity=self._min_capacity,
|
|
168
|
+
startupcost=self._startupcost,
|
|
169
|
+
volume=self._production,
|
|
170
|
+
arrow_volumes=arrow_volumes,
|
|
171
|
+
is_exogenous=is_exogenous,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
power_arrow = Arrow(
|
|
175
|
+
node=self._power_node,
|
|
176
|
+
is_ingoing=True,
|
|
177
|
+
conversion=Conversion(value=1),
|
|
178
|
+
)
|
|
179
|
+
flow.add_arrow(power_arrow)
|
|
180
|
+
|
|
181
|
+
fuel_arrow = Arrow(
|
|
182
|
+
node=self._fuel_node,
|
|
183
|
+
is_ingoing=False,
|
|
184
|
+
efficiency=self._efficiency,
|
|
185
|
+
)
|
|
186
|
+
flow.add_arrow(fuel_arrow)
|
|
187
|
+
arrow_volumes[fuel_arrow] = self._fuel_demand
|
|
188
|
+
|
|
189
|
+
if self._emission_node is not None:
|
|
190
|
+
if self._emission_demand is None:
|
|
191
|
+
self._emission_demand = AvgFlowVolume()
|
|
192
|
+
emission_arrow = Arrow(
|
|
193
|
+
node=self._emission_node,
|
|
194
|
+
is_ingoing=False,
|
|
195
|
+
conversion=self._emission_coefficient,
|
|
196
|
+
efficiency=self._efficiency,
|
|
197
|
+
)
|
|
198
|
+
flow.add_arrow(emission_arrow)
|
|
199
|
+
arrow_volumes[emission_arrow] = self._emission_demand
|
|
200
|
+
|
|
201
|
+
if self._voc:
|
|
202
|
+
flow.add_cost_term("VOC", self._voc)
|
|
203
|
+
|
|
204
|
+
return flow
|