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.
Files changed (103) hide show
  1. fram_core-0.1.0a2.dist-info/METADATA +42 -0
  2. fram_core-0.1.0a2.dist-info/RECORD +100 -0
  3. {fram_core-0.0.0.dist-info → fram_core-0.1.0a2.dist-info}/WHEEL +1 -2
  4. fram_core-0.1.0a2.dist-info/licenses/LICENSE.md +8 -0
  5. framcore/Base.py +142 -0
  6. framcore/Model.py +73 -0
  7. framcore/__init__.py +9 -0
  8. framcore/aggregators/Aggregator.py +153 -0
  9. framcore/aggregators/HydroAggregator.py +837 -0
  10. framcore/aggregators/NodeAggregator.py +495 -0
  11. framcore/aggregators/WindSolarAggregator.py +323 -0
  12. framcore/aggregators/__init__.py +13 -0
  13. framcore/aggregators/_utils.py +184 -0
  14. framcore/attributes/Arrow.py +305 -0
  15. framcore/attributes/ElasticDemand.py +90 -0
  16. framcore/attributes/ReservoirCurve.py +37 -0
  17. framcore/attributes/SoftBound.py +19 -0
  18. framcore/attributes/StartUpCost.py +54 -0
  19. framcore/attributes/Storage.py +146 -0
  20. framcore/attributes/TargetBound.py +18 -0
  21. framcore/attributes/__init__.py +65 -0
  22. framcore/attributes/hydro/HydroBypass.py +42 -0
  23. framcore/attributes/hydro/HydroGenerator.py +83 -0
  24. framcore/attributes/hydro/HydroPump.py +156 -0
  25. framcore/attributes/hydro/HydroReservoir.py +27 -0
  26. framcore/attributes/hydro/__init__.py +13 -0
  27. framcore/attributes/level_profile_attributes.py +714 -0
  28. framcore/components/Component.py +112 -0
  29. framcore/components/Demand.py +130 -0
  30. framcore/components/Flow.py +167 -0
  31. framcore/components/HydroModule.py +330 -0
  32. framcore/components/Node.py +76 -0
  33. framcore/components/Thermal.py +204 -0
  34. framcore/components/Transmission.py +183 -0
  35. framcore/components/_PowerPlant.py +81 -0
  36. framcore/components/__init__.py +22 -0
  37. framcore/components/wind_solar.py +67 -0
  38. framcore/curves/Curve.py +44 -0
  39. framcore/curves/LoadedCurve.py +155 -0
  40. framcore/curves/__init__.py +9 -0
  41. framcore/events/__init__.py +21 -0
  42. framcore/events/events.py +51 -0
  43. framcore/expressions/Expr.py +490 -0
  44. framcore/expressions/__init__.py +28 -0
  45. framcore/expressions/_get_constant_from_expr.py +483 -0
  46. framcore/expressions/_time_vector_operations.py +615 -0
  47. framcore/expressions/_utils.py +73 -0
  48. framcore/expressions/queries.py +423 -0
  49. framcore/expressions/units.py +207 -0
  50. framcore/fingerprints/__init__.py +11 -0
  51. framcore/fingerprints/fingerprint.py +293 -0
  52. framcore/juliamodels/JuliaModel.py +161 -0
  53. framcore/juliamodels/__init__.py +7 -0
  54. framcore/loaders/__init__.py +10 -0
  55. framcore/loaders/loaders.py +407 -0
  56. framcore/metadata/Div.py +73 -0
  57. framcore/metadata/ExprMeta.py +50 -0
  58. framcore/metadata/LevelExprMeta.py +17 -0
  59. framcore/metadata/Member.py +55 -0
  60. framcore/metadata/Meta.py +44 -0
  61. framcore/metadata/__init__.py +15 -0
  62. framcore/populators/Populator.py +108 -0
  63. framcore/populators/__init__.py +7 -0
  64. framcore/querydbs/CacheDB.py +50 -0
  65. framcore/querydbs/ModelDB.py +34 -0
  66. framcore/querydbs/QueryDB.py +45 -0
  67. framcore/querydbs/__init__.py +11 -0
  68. framcore/solvers/Solver.py +48 -0
  69. framcore/solvers/SolverConfig.py +272 -0
  70. framcore/solvers/__init__.py +9 -0
  71. framcore/timeindexes/AverageYearRange.py +20 -0
  72. framcore/timeindexes/ConstantTimeIndex.py +17 -0
  73. framcore/timeindexes/DailyIndex.py +21 -0
  74. framcore/timeindexes/FixedFrequencyTimeIndex.py +762 -0
  75. framcore/timeindexes/HourlyIndex.py +21 -0
  76. framcore/timeindexes/IsoCalendarDay.py +31 -0
  77. framcore/timeindexes/ListTimeIndex.py +197 -0
  78. framcore/timeindexes/ModelYear.py +17 -0
  79. framcore/timeindexes/ModelYears.py +18 -0
  80. framcore/timeindexes/OneYearProfileTimeIndex.py +21 -0
  81. framcore/timeindexes/ProfileTimeIndex.py +32 -0
  82. framcore/timeindexes/SinglePeriodTimeIndex.py +37 -0
  83. framcore/timeindexes/TimeIndex.py +90 -0
  84. framcore/timeindexes/WeeklyIndex.py +21 -0
  85. framcore/timeindexes/__init__.py +36 -0
  86. framcore/timevectors/ConstantTimeVector.py +135 -0
  87. framcore/timevectors/LinearTransformTimeVector.py +114 -0
  88. framcore/timevectors/ListTimeVector.py +123 -0
  89. framcore/timevectors/LoadedTimeVector.py +104 -0
  90. framcore/timevectors/ReferencePeriod.py +41 -0
  91. framcore/timevectors/TimeVector.py +94 -0
  92. framcore/timevectors/__init__.py +17 -0
  93. framcore/utils/__init__.py +36 -0
  94. framcore/utils/get_regional_volumes.py +369 -0
  95. framcore/utils/get_supported_components.py +60 -0
  96. framcore/utils/global_energy_equivalent.py +46 -0
  97. framcore/utils/isolate_subnodes.py +163 -0
  98. framcore/utils/loaders.py +97 -0
  99. framcore/utils/node_flow_utils.py +236 -0
  100. framcore/utils/storage_subsystems.py +107 -0
  101. fram_core-0.0.0.dist-info/METADATA +0 -5
  102. fram_core-0.0.0.dist-info/RECORD +0 -4
  103. 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