diff --git a/CHANGELOG.md b/CHANGELOG.md index 733a4e76e..82a98ea85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,7 +51,7 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp ## [Unreleased] - ????-??-?? -**Summary**: Internal architecture improvements to simplify FlowSystem-Element coupling and eliminate circular dependencies. +**Summary**: Renaming parameters in Linear Transformers for readability & Internal architecture improvements to simplify FlowSystem-Element coupling and eliminate circular dependencies. Old parameters till work but emmit warnings. If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOpt/flixOpt/releases/tag/v3.0.0) and [Migration Guide](https://flixopt.github.io/flixopt/latest/user-guide/migration-guide-v3/). @@ -71,9 +71,33 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp - **Two-phase modeling pattern within _do_modeling()**: Clarified the pattern where `_do_modeling()` creates nested submodels first (so their variables exist), then creates constraints that reference those variables - eliminates circular dependencies in Submodel architecture - **Improved cache invalidation**: Cache invalidation in `add_elements()` now happens once after all additions rather than per element - **Better logging**: Centralized element registration logging to show element type and full label +- **Parameter renaming in `linear_converters.py`**: Renamed parameters to use lowercase, descriptive names for better consistency and clarity: + - **Flow parameters** (deprecated uppercase abbreviations → descriptive names): + - `Boiler`: `Q_fu` → `fuel_flow`, `Q_th` → `thermal_flow` + - `Power2Heat`: `P_el` → `electrical_flow`, `Q_th` → `thermal_flow` + - `HeatPump`: `COP` → `cop`, `P_el` → `electrical_flow`, `Q_th` → `thermal_flow` + - `CoolingTower`: `P_el` → `electrical_flow`, `Q_th` → `thermal_flow` + - `CHP`: `Q_fu` → `fuel_flow`, `P_el` → `electrical_flow`, `Q_th` → `thermal_flow` + - `HeatPumpWithSource`: `COP` → `cop`, `P_el` → `electrical_flow`, `Q_ab` → `heat_source_flow`, `Q_th` → `thermal_flow` + - **Efficiency parameters** (abbreviated → descriptive names): + - `Boiler`: `eta` → `thermal_efficiency` + - `Power2Heat`: `eta` → `thermal_efficiency` + - `CHP`: `eta_th` → `thermal_efficiency`, `eta_el` → `electrical_efficiency` + - `HetaPump`: `COP` → `cop` + - `HetaPumpWithSource`: `COP` → `cop` + - **Storage Parameters**: + - `Storage`: `initial_charge_state="lastValueOfSim"` → `initial_charge_state="equals_last"` + +### 🗑️ Deprecated +- **Old parameter names in `linear_converters.py`**: The following parameter names are now deprecated and accessible as properties/kwargs that emit `DeprecationWarning`. They will be removed in v4.0.0: + - **Flow parameters**: `Q_fu`, `Q_th`, `P_el`, `Q_ab` (use `fuel_flow`, `thermal_flow`, `electrical_flow`, `heat_source_flow` instead) + - **Efficiency parameters**: `eta`, `eta_th`, `eta_el` (use `thermal_efficiency`, `electrical_efficiency` instead) + - **COP parameter**: `COP` (use lowercase `cop` instead) + - **Storage Parameter**: `Storage`: `initial_charge_state="lastValueOfSim"` (use `initial_charge_state="equals_last"`) ### 🐛 Fixed - Fixed inconsistent argument passing in `_fit_effect_coords()` - standardized all calls to use named arguments (`prefix=`, `effect_values=`, `suffix=`) instead of mix of positional and named arguments +- Fixed `check_bounds` function in `linear_converters.py` to normalize array inputs before comparisons, ensuring correct boundary checks with DataFrames, Series, and other array-like types ### 👷 Development - **Eliminated circular dependencies**: Implemented two-phase modeling pattern within `_do_modeling()` where nested submodels are created first (creating their variables), then constraints are created that can safely reference those submodel variables diff --git a/examples/00_Minmal/minimal_example.py b/examples/00_Minmal/minimal_example.py index 92e6801b2..9756396b3 100644 --- a/examples/00_Minmal/minimal_example.py +++ b/examples/00_Minmal/minimal_example.py @@ -18,9 +18,9 @@ fx.Effect('Costs', '€', 'Cost', is_standard=True, is_objective=True), fx.linear_converters.Boiler( 'Boiler', - eta=0.5, - Q_th=fx.Flow(label='Heat', bus='Heat', size=50), - Q_fu=fx.Flow(label='Gas', bus='Gas'), + thermal_efficiency=0.5, + thermal_flow=fx.Flow(label='Heat', bus='Heat', size=50), + fuel_flow=fx.Flow(label='Gas', bus='Gas'), ), fx.Sink( 'Sink', diff --git a/examples/01_Simple/simple_example.py b/examples/01_Simple/simple_example.py index fd5a3d9b7..d9737cf7b 100644 --- a/examples/01_Simple/simple_example.py +++ b/examples/01_Simple/simple_example.py @@ -46,19 +46,19 @@ # Boiler: Converts fuel (gas) into thermal energy (heat) boiler = fx.linear_converters.Boiler( label='Boiler', - eta=0.5, - Q_th=fx.Flow(label='Q_th', bus='Fernwärme', size=50, relative_minimum=0.1, relative_maximum=1), - Q_fu=fx.Flow(label='Q_fu', bus='Gas'), + thermal_efficiency=0.5, + thermal_flow=fx.Flow(label='Q_th', bus='Fernwärme', size=50, relative_minimum=0.1, relative_maximum=1), + fuel_flow=fx.Flow(label='Q_fu', bus='Gas'), ) # Combined Heat and Power (CHP): Generates both electricity and heat from fuel chp = fx.linear_converters.CHP( label='CHP', - eta_th=0.5, - eta_el=0.4, - P_el=fx.Flow('P_el', bus='Strom', size=60, relative_minimum=5 / 60), - Q_th=fx.Flow('Q_th', bus='Fernwärme'), - Q_fu=fx.Flow('Q_fu', bus='Gas'), + thermal_efficiency=0.5, + electrical_efficiency=0.4, + electrical_flow=fx.Flow('P_el', bus='Strom', size=60, relative_minimum=5 / 60), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme'), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ) # Storage: Energy storage system with charging and discharging capabilities diff --git a/examples/02_Complex/complex_example.py b/examples/02_Complex/complex_example.py index 3ff5b251c..0f7ca0b82 100644 --- a/examples/02_Complex/complex_example.py +++ b/examples/02_Complex/complex_example.py @@ -50,11 +50,11 @@ # A gas boiler that converts fuel into thermal output, with investment and on-off parameters Gaskessel = fx.linear_converters.Boiler( 'Kessel', - eta=0.5, # Efficiency ratio + thermal_efficiency=0.5, # Efficiency ratio on_off_parameters=fx.OnOffParameters( effects_per_running_hour={Costs.label: 0, CO2.label: 1000} ), # CO2 emissions per hour - Q_th=fx.Flow( + thermal_flow=fx.Flow( label='Q_th', # Thermal output bus='Fernwärme', # Linked bus size=fx.InvestParameters( @@ -79,19 +79,19 @@ switch_on_total_max=1000, # Max number of starts ), ), - Q_fu=fx.Flow(label='Q_fu', bus='Gas', size=200), + fuel_flow=fx.Flow(label='Q_fu', bus='Gas', size=200), ) # 2. Define CHP Unit # Combined Heat and Power unit that generates both electricity and heat from fuel bhkw = fx.linear_converters.CHP( 'BHKW2', - eta_th=0.5, - eta_el=0.4, + thermal_efficiency=0.5, + electrical_efficiency=0.4, on_off_parameters=fx.OnOffParameters(effects_per_switch_on=0.01), - P_el=fx.Flow('P_el', bus='Strom', size=60, relative_minimum=5 / 60), - Q_th=fx.Flow('Q_th', bus='Fernwärme', size=1e3), - Q_fu=fx.Flow('Q_fu', bus='Gas', size=1e3, previous_flow_rate=20), # The CHP was ON previously + electrical_flow=fx.Flow('P_el', bus='Strom', size=60, relative_minimum=5 / 60), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme', size=1e3), + fuel_flow=fx.Flow('Q_fu', bus='Gas', size=1e3, previous_flow_rate=20), # The CHP was ON previously ) # 3. Define CHP with Piecewise Conversion diff --git a/examples/03_Calculation_types/example_calculation_types.py b/examples/03_Calculation_types/example_calculation_types.py index e339c1c24..fa57e6f9a 100644 --- a/examples/03_Calculation_types/example_calculation_types.py +++ b/examples/03_Calculation_types/example_calculation_types.py @@ -71,9 +71,9 @@ # 1. Boiler a_gaskessel = fx.linear_converters.Boiler( 'Kessel', - eta=0.85, - Q_th=fx.Flow(label='Q_th', bus='Fernwärme'), - Q_fu=fx.Flow( + thermal_efficiency=0.85, + thermal_flow=fx.Flow(label='Q_th', bus='Fernwärme'), + fuel_flow=fx.Flow( label='Q_fu', bus='Gas', size=95, @@ -86,12 +86,12 @@ # 2. CHP a_kwk = fx.linear_converters.CHP( 'BHKW2', - eta_th=0.58, - eta_el=0.22, + thermal_efficiency=0.58, + electrical_efficiency=0.22, on_off_parameters=fx.OnOffParameters(effects_per_switch_on=24000), - P_el=fx.Flow('P_el', bus='Strom', size=200), - Q_th=fx.Flow('Q_th', bus='Fernwärme', size=200), - Q_fu=fx.Flow('Q_fu', bus='Kohle', size=288, relative_minimum=87 / 288, previous_flow_rate=100), + electrical_flow=fx.Flow('P_el', bus='Strom', size=200), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme', size=200), + fuel_flow=fx.Flow('Q_fu', bus='Kohle', size=288, relative_minimum=87 / 288, previous_flow_rate=100), ) # 3. Storage diff --git a/examples/04_Scenarios/scenario_example.py b/examples/04_Scenarios/scenario_example.py index bf4f24617..ca50876c7 100644 --- a/examples/04_Scenarios/scenario_example.py +++ b/examples/04_Scenarios/scenario_example.py @@ -114,8 +114,8 @@ # Modern condensing gas boiler with realistic efficiency boiler = fx.linear_converters.Boiler( label='Boiler', - eta=0.92, # Realistic efficiency for modern condensing gas boiler (92%) - Q_th=fx.Flow( + thermal_efficiency=0.92, # Realistic efficiency for modern condensing gas boiler (92%) + thermal_flow=fx.Flow( label='Q_th', bus='Fernwärme', size=50, @@ -123,18 +123,20 @@ relative_maximum=1, on_off_parameters=fx.OnOffParameters(), ), - Q_fu=fx.Flow(label='Q_fu', bus='Gas'), + fuel_flow=fx.Flow(label='Q_fu', bus='Gas'), ) # Combined Heat and Power (CHP): Generates both electricity and heat from fuel # Modern CHP unit with realistic efficiencies (total efficiency ~88%) chp = fx.linear_converters.CHP( label='CHP', - eta_th=0.48, # Realistic thermal efficiency (48%) - eta_el=0.40, # Realistic electrical efficiency (40%) - P_el=fx.Flow('P_el', bus='Strom', size=60, relative_minimum=5 / 60, on_off_parameters=fx.OnOffParameters()), - Q_th=fx.Flow('Q_th', bus='Fernwärme'), - Q_fu=fx.Flow('Q_fu', bus='Gas'), + thermal_efficiency=0.48, # Realistic thermal efficiency (48%) + electrical_efficiency=0.40, # Realistic electrical efficiency (40%) + electrical_flow=fx.Flow( + 'P_el', bus='Strom', size=60, relative_minimum=5 / 60, on_off_parameters=fx.OnOffParameters() + ), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme'), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ) # Storage: Thermal energy storage system with charging and discharging capabilities diff --git a/examples/05_Two-stage-optimization/two_stage_optimization.py b/examples/05_Two-stage-optimization/two_stage_optimization.py index 7354cb877..6c7b20276 100644 --- a/examples/05_Two-stage-optimization/two_stage_optimization.py +++ b/examples/05_Two-stage-optimization/two_stage_optimization.py @@ -45,9 +45,9 @@ fx.Effect('PE', 'kWh_PE', 'Primärenergie'), fx.linear_converters.Boiler( 'Kessel', - eta=0.85, - Q_th=fx.Flow(label='Q_th', bus='Fernwärme'), - Q_fu=fx.Flow( + thermal_efficiency=0.85, + thermal_flow=fx.Flow(label='Q_th', bus='Fernwärme'), + fuel_flow=fx.Flow( label='Q_fu', bus='Gas', size=fx.InvestParameters( @@ -60,14 +60,14 @@ ), fx.linear_converters.CHP( 'BHKW2', - eta_th=0.58, - eta_el=0.22, + thermal_efficiency=0.58, + electrical_efficiency=0.22, on_off_parameters=fx.OnOffParameters( effects_per_switch_on=1_000, consecutive_on_hours_min=10, consecutive_off_hours_min=10 ), - P_el=fx.Flow('P_el', bus='Strom'), - Q_th=fx.Flow('Q_th', bus='Fernwärme'), - Q_fu=fx.Flow( + electrical_flow=fx.Flow('P_el', bus='Strom'), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme'), + fuel_flow=fx.Flow( 'Q_fu', bus='Kohle', size=fx.InvestParameters( @@ -82,7 +82,7 @@ capacity_in_flow_hours=fx.InvestParameters( minimum_size=10, maximum_size=1000, effects_of_investment_per_size={'costs': 60} ), - initial_charge_state='lastValueOfSim', + initial_charge_state='equals_final', eta_charge=1, eta_discharge=1, relative_loss_per_hour=0.001, diff --git a/flixopt/components.py b/flixopt/components.py index 73e0c7972..38f980126 100644 --- a/flixopt/components.py +++ b/flixopt/components.py @@ -281,7 +281,7 @@ class Storage(Component): Scalar for fixed size or InvestParameters for optimization. relative_minimum_charge_state: Minimum charge state (0-1). Default: 0. relative_maximum_charge_state: Maximum charge state (0-1). Default: 1. - initial_charge_state: Charge at start. Numeric or 'lastValueOfSim'. Default: 0. + initial_charge_state: Charge at start. Numeric or 'equals_final'. Default: 0. minimal_final_charge_state: Minimum absolute charge required at end (optional). maximal_final_charge_state: Maximum absolute charge allowed at end (optional). relative_minimum_final_charge_state: Minimum relative charge at end. @@ -345,7 +345,7 @@ class Storage(Component): ), eta_charge=0.85, # Pumping efficiency eta_discharge=0.90, # Turbine efficiency - initial_charge_state='lastValueOfSim', # Ensuring no deficit compared to start + initial_charge_state='equals_final', # Ensuring no deficit compared to start relative_loss_per_hour=0.0001, # Minimal evaporation ) ``` @@ -394,7 +394,7 @@ def __init__( capacity_in_flow_hours: Numeric_PS | InvestParameters, relative_minimum_charge_state: Numeric_TPS = 0, relative_maximum_charge_state: Numeric_TPS = 1, - initial_charge_state: Numeric_PS | Literal['lastValueOfSim'] = 0, + initial_charge_state: Numeric_PS | Literal['equals_final'] = 0, minimal_final_charge_state: Numeric_PS | None = None, maximal_final_charge_state: Numeric_PS | None = None, relative_minimum_final_charge_state: Numeric_PS | None = None, @@ -414,6 +414,13 @@ def __init__( prevent_simultaneous_flows=[charging, discharging] if prevent_simultaneous_charge_and_discharge else None, meta_data=meta_data, ) + if isinstance(initial_charge_state, str) and initial_charge_state == 'lastValueOfSim': + warnings.warn( + f'{initial_charge_state=} is deprecated. Use "equals_final" instead.', + DeprecationWarning, + stacklevel=2, + ) + initial_charge_state = 'equals_final' self.charging = charging self.discharging = discharging @@ -491,12 +498,11 @@ def _plausibility_checks(self) -> None: super()._plausibility_checks() # Validate string values and set flag - initial_is_last = False + initial_equals_final = False if isinstance(self.initial_charge_state, str): - if self.initial_charge_state == 'lastValueOfSim': - initial_is_last = True - else: + if not self.initial_charge_state == 'equals_final': raise PlausibilityError(f'initial_charge_state has undefined value: {self.initial_charge_state}') + initial_equals_final = True # Use new InvestParameters methods to get capacity bounds if isinstance(self.capacity_in_flow_hours, InvestParameters): @@ -510,8 +516,8 @@ def _plausibility_checks(self) -> None: minimum_initial_capacity = maximum_capacity * self.relative_minimum_charge_state.isel(time=0) maximum_initial_capacity = minimum_capacity * self.relative_maximum_charge_state.isel(time=0) - # Only perform numeric comparisons if not using 'lastValueOfSim' - if not initial_is_last: + # Only perform numeric comparisons if not using 'equals_final' + if not initial_equals_final: if (self.initial_charge_state > maximum_initial_capacity).any(): raise PlausibilityError( f'{self.label_full}: {self.initial_charge_state=} ' diff --git a/flixopt/linear_converters.py b/flixopt/linear_converters.py index 8f02e4f70..055425eae 100644 --- a/flixopt/linear_converters.py +++ b/flixopt/linear_converters.py @@ -4,6 +4,7 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING import numpy as np @@ -30,13 +31,16 @@ class Boiler(LinearConverter): Args: label: The label of the Element. Used to identify it in the FlowSystem. - eta: Thermal efficiency factor (0-1 range). Defines the ratio of thermal + thermal_efficiency: Thermal efficiency factor (0-1 range). Defines the ratio of thermal output to fuel input energy content. - Q_fu: Fuel input-flow representing fuel consumption. - Q_th: Thermal output-flow representing heat generation. + fuel_flow: Fuel input-flow representing fuel consumption. + thermal_flow: Thermal output-flow representing heat generation. on_off_parameters: Parameters defining binary operation constraints and costs. meta_data: Used to store additional information. Not used internally but saved in results. Only use Python native types. + eta: *Deprecated*. Use `thermal_efficiency` instead. + Q_fu: *Deprecated*. Use `fuel_flow` instead. + Q_th: *Deprecated*. Use `thermal_flow` instead. Examples: Natural gas boiler: @@ -44,9 +48,9 @@ class Boiler(LinearConverter): ```python gas_boiler = Boiler( label='natural_gas_boiler', - eta=0.85, # 85% thermal efficiency - Q_fu=natural_gas_flow, - Q_th=hot_water_flow, + thermal_efficiency=0.85, # 85% thermal efficiency + fuel_flow=natural_gas_flow, + thermal_flow=hot_water_flow, ) ``` @@ -55,9 +59,9 @@ class Boiler(LinearConverter): ```python biomass_boiler = Boiler( label='wood_chip_boiler', - eta=seasonal_efficiency_profile, # Time-varying efficiency - Q_fu=biomass_flow, - Q_th=district_heat_flow, + thermal_efficiency=seasonal_efficiency_profile, # Time-varying efficiency + fuel_flow=biomass_flow, + thermal_flow=district_heat_flow, on_off_parameters=OnOffParameters( consecutive_on_hours_min=4, # Minimum 4-hour operation effects_per_switch_on={'startup_fuel': 50}, # Startup fuel penalty @@ -66,7 +70,7 @@ class Boiler(LinearConverter): ``` Note: - The conversion relationship is: Q_th = Q_fu × eta + The conversion relationship is: thermal_flow = fuel_flow × thermal_efficiency Efficiency should be between 0 and 1, where 1 represents perfect conversion (100% of fuel energy converted to useful thermal output). @@ -75,31 +79,100 @@ class Boiler(LinearConverter): def __init__( self, label: str, - eta: Numeric_TPS, - Q_fu: Flow, - Q_th: Flow, + thermal_efficiency: Numeric_TPS | None = None, + fuel_flow: Flow | None = None, + thermal_flow: Flow | None = None, on_off_parameters: OnOffParameters | None = None, meta_data: dict | None = None, + **kwargs, ): + # Handle deprecated parameters + fuel_flow = self._handle_deprecated_kwarg(kwargs, 'Q_fu', 'fuel_flow', fuel_flow) + thermal_flow = self._handle_deprecated_kwarg(kwargs, 'Q_th', 'thermal_flow', thermal_flow) + thermal_efficiency = self._handle_deprecated_kwarg(kwargs, 'eta', 'thermal_efficiency', thermal_efficiency) + self._validate_kwargs(kwargs) + + # Validate required parameters + if fuel_flow is None: + raise ValueError(f"'{label}': fuel_flow is required and cannot be None") + if thermal_flow is None: + raise ValueError(f"'{label}': thermal_flow is required and cannot be None") + if thermal_efficiency is None: + raise ValueError(f"'{label}': thermal_efficiency is required and cannot be None") + super().__init__( label, - inputs=[Q_fu], - outputs=[Q_th], - conversion_factors=[{Q_fu.label: eta, Q_th.label: 1}], + inputs=[fuel_flow], + outputs=[thermal_flow], on_off_parameters=on_off_parameters, meta_data=meta_data, ) - self.Q_fu = Q_fu - self.Q_th = Q_th + self.fuel_flow = fuel_flow + self.thermal_flow = thermal_flow + self.thermal_efficiency = thermal_efficiency # Uses setter + + @property + def thermal_efficiency(self): + return self.conversion_factors[0][self.fuel_flow.label] + + @thermal_efficiency.setter + def thermal_efficiency(self, value): + check_bounds(value, 'thermal_efficiency', self.label_full, 0, 1) + self.conversion_factors = [{self.fuel_flow.label: value, self.thermal_flow.label: 1}] @property - def eta(self): - return self.conversion_factors[0][self.Q_fu.label] + def eta(self) -> Numeric_TPS: + warnings.warn( + 'The "eta" property is deprecated. Use "thermal_efficiency" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.thermal_efficiency @eta.setter - def eta(self, value): - check_bounds(value, 'eta', self.label_full, 0, 1) - self.conversion_factors[0][self.Q_fu.label] = value + def eta(self, value: Numeric_TPS) -> None: + warnings.warn( + 'The "eta" property is deprecated. Use "thermal_efficiency" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.thermal_efficiency = value + + @property + def Q_fu(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "Q_fu" property is deprecated. Use "fuel_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.fuel_flow + + @Q_fu.setter + def Q_fu(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "Q_fu" property is deprecated. Use "fuel_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.fuel_flow = value + + @property + def Q_th(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.thermal_flow + + @Q_th.setter + def Q_th(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.thermal_flow = value @register_class_for_io @@ -114,14 +187,17 @@ class Power2Heat(LinearConverter): Args: label: The label of the Element. Used to identify it in the FlowSystem. - eta: Thermal efficiency factor (0-1 range). For resistance heating this is + thermal_efficiency: Thermal efficiency factor (0-1 range). For resistance heating this is typically close to 1.0 (nearly 100% efficiency), but may be lower for electrode boilers or systems with distribution losses. - P_el: Electrical input-flow representing electricity consumption. - Q_th: Thermal output-flow representing heat generation. + electrical_flow: Electrical input-flow representing electricity consumption. + thermal_flow: Thermal output-flow representing heat generation. on_off_parameters: Parameters defining binary operation constraints and costs. meta_data: Used to store additional information. Not used internally but saved in results. Only use Python native types. + eta: *Deprecated*. Use `thermal_efficiency` instead. + P_el: *Deprecated*. Use `electrical_flow` instead. + Q_th: *Deprecated*. Use `thermal_flow` instead. Examples: Electric resistance heater: @@ -129,9 +205,9 @@ class Power2Heat(LinearConverter): ```python electric_heater = Power2Heat( label='resistance_heater', - eta=0.98, # 98% efficiency (small losses) - P_el=electricity_flow, - Q_th=space_heating_flow, + thermal_efficiency=0.98, # 98% efficiency (small losses) + electrical_flow=electricity_flow, + thermal_flow=space_heating_flow, ) ``` @@ -140,9 +216,9 @@ class Power2Heat(LinearConverter): ```python electrode_boiler = Power2Heat( label='electrode_steam_boiler', - eta=0.95, # 95% efficiency including boiler losses - P_el=industrial_electricity, - Q_th=process_steam_flow, + thermal_efficiency=0.95, # 95% efficiency including boiler losses + electrical_flow=industrial_electricity, + thermal_flow=process_steam_flow, on_off_parameters=OnOffParameters( consecutive_on_hours_min=1, # Minimum 1-hour operation effects_per_switch_on={'startup_cost': 100}, @@ -151,9 +227,9 @@ class Power2Heat(LinearConverter): ``` Note: - The conversion relationship is: Q_th = P_el × eta + The conversion relationship is: thermal_flow = electrical_flow × thermal_efficiency - Unlike heat pumps, Power2Heat systems cannot exceed 100% efficiency (eta ≤ 1.0) + Unlike heat pumps, Power2Heat systems cannot exceed 100% efficiency (thermal_efficiency ≤ 1.0) as they only convert electrical energy without extracting additional energy from the environment. However, they provide fast response times and precise temperature control. @@ -162,32 +238,101 @@ class Power2Heat(LinearConverter): def __init__( self, label: str, - eta: Numeric_TPS, - P_el: Flow, - Q_th: Flow, + thermal_efficiency: Numeric_TPS | None = None, + electrical_flow: Flow | None = None, + thermal_flow: Flow | None = None, on_off_parameters: OnOffParameters | None = None, meta_data: dict | None = None, + **kwargs, ): + # Handle deprecated parameters + electrical_flow = self._handle_deprecated_kwarg(kwargs, 'P_el', 'electrical_flow', electrical_flow) + thermal_flow = self._handle_deprecated_kwarg(kwargs, 'Q_th', 'thermal_flow', thermal_flow) + thermal_efficiency = self._handle_deprecated_kwarg(kwargs, 'eta', 'thermal_efficiency', thermal_efficiency) + self._validate_kwargs(kwargs) + + # Validate required parameters + if electrical_flow is None: + raise ValueError(f"'{label}': electrical_flow is required and cannot be None") + if thermal_flow is None: + raise ValueError(f"'{label}': thermal_flow is required and cannot be None") + if thermal_efficiency is None: + raise ValueError(f"'{label}': thermal_efficiency is required and cannot be None") + super().__init__( label, - inputs=[P_el], - outputs=[Q_th], - conversion_factors=[{P_el.label: eta, Q_th.label: 1}], + inputs=[electrical_flow], + outputs=[thermal_flow], on_off_parameters=on_off_parameters, meta_data=meta_data, ) - self.P_el = P_el - self.Q_th = Q_th + self.electrical_flow = electrical_flow + self.thermal_flow = thermal_flow + self.thermal_efficiency = thermal_efficiency # Uses setter + + @property + def thermal_efficiency(self): + return self.conversion_factors[0][self.electrical_flow.label] + + @thermal_efficiency.setter + def thermal_efficiency(self, value): + check_bounds(value, 'thermal_efficiency', self.label_full, 0, 1) + self.conversion_factors = [{self.electrical_flow.label: value, self.thermal_flow.label: 1}] @property - def eta(self): - return self.conversion_factors[0][self.P_el.label] + def eta(self) -> Numeric_TPS: + warnings.warn( + 'The "eta" property is deprecated. Use "thermal_efficiency" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.thermal_efficiency @eta.setter - def eta(self, value): - check_bounds(value, 'eta', self.label_full, 0, 1) - self.conversion_factors[0][self.P_el.label] = value + def eta(self, value: Numeric_TPS) -> None: + warnings.warn( + 'The "eta" property is deprecated. Use "thermal_efficiency" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.thermal_efficiency = value + + @property + def P_el(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.electrical_flow + + @P_el.setter + def P_el(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.electrical_flow = value + + @property + def Q_th(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.thermal_flow + + @Q_th.setter + def Q_th(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.thermal_flow = value @register_class_for_io @@ -202,14 +347,17 @@ class HeatPump(LinearConverter): Args: label: The label of the Element. Used to identify it in the FlowSystem. - COP: Coefficient of Performance (typically 1-20 range). Defines the ratio of + cop: Coefficient of Performance (typically 1-20 range). Defines the ratio of thermal output to electrical input. COP > 1 indicates the heat pump extracts additional energy from the environment. - P_el: Electrical input-flow representing electricity consumption. - Q_th: Thermal output-flow representing heat generation. + electrical_flow: Electrical input-flow representing electricity consumption. + thermal_flow: Thermal output-flow representing heat generation. on_off_parameters: Parameters defining binary operation constraints and costs. meta_data: Used to store additional information. Not used internally but saved in results. Only use Python native types. + COP: *Deprecated*. Use `cop` instead. + P_el: *Deprecated*. Use `electrical_flow` instead. + Q_th: *Deprecated*. Use `thermal_flow` instead. Examples: Air-source heat pump with constant COP: @@ -217,9 +365,9 @@ class HeatPump(LinearConverter): ```python air_hp = HeatPump( label='air_source_heat_pump', - COP=3.5, # COP of 3.5 (350% efficiency) - P_el=electricity_flow, - Q_th=heating_flow, + cop=3.5, # COP of 3.5 (350% efficiency) + electrical_flow=electricity_flow, + thermal_flow=heating_flow, ) ``` @@ -228,9 +376,9 @@ class HeatPump(LinearConverter): ```python ground_hp = HeatPump( label='geothermal_heat_pump', - COP=temperature_dependent_cop, # Time-varying COP based on ground temp - P_el=electricity_flow, - Q_th=radiant_heating_flow, + cop=temperature_dependent_cop, # Time-varying COP based on ground temp + electrical_flow=electricity_flow, + thermal_flow=radiant_heating_flow, on_off_parameters=OnOffParameters( consecutive_on_hours_min=2, # Avoid frequent cycling effects_per_running_hour={'maintenance': 0.5}, @@ -239,7 +387,7 @@ class HeatPump(LinearConverter): ``` Note: - The conversion relationship is: Q_th = P_el × COP + The conversion relationship is: thermal_flow = electrical_flow × COP COP should be greater than 1 for realistic heat pump operation, with typical values ranging from 2-6 depending on technology and operating conditions. @@ -249,32 +397,101 @@ class HeatPump(LinearConverter): def __init__( self, label: str, - COP: Numeric_TPS, - P_el: Flow, - Q_th: Flow, + cop: Numeric_TPS | None = None, + electrical_flow: Flow | None = None, + thermal_flow: Flow | None = None, on_off_parameters: OnOffParameters | None = None, meta_data: dict | None = None, + **kwargs, ): + # Handle deprecated parameters + electrical_flow = self._handle_deprecated_kwarg(kwargs, 'P_el', 'electrical_flow', electrical_flow) + thermal_flow = self._handle_deprecated_kwarg(kwargs, 'Q_th', 'thermal_flow', thermal_flow) + cop = self._handle_deprecated_kwarg(kwargs, 'COP', 'cop', cop) + self._validate_kwargs(kwargs) + + # Validate required parameters + if electrical_flow is None: + raise ValueError(f"'{label}': electrical_flow is required and cannot be None") + if thermal_flow is None: + raise ValueError(f"'{label}': thermal_flow is required and cannot be None") + if cop is None: + raise ValueError(f"'{label}': cop is required and cannot be None") + super().__init__( label, - inputs=[P_el], - outputs=[Q_th], - conversion_factors=[{P_el.label: COP, Q_th.label: 1}], + inputs=[electrical_flow], + outputs=[thermal_flow], + conversion_factors=[], on_off_parameters=on_off_parameters, meta_data=meta_data, ) - self.P_el = P_el - self.Q_th = Q_th - self.COP = COP + self.electrical_flow = electrical_flow + self.thermal_flow = thermal_flow + self.cop = cop # Uses setter + + @property + def cop(self): + return self.conversion_factors[0][self.electrical_flow.label] + + @cop.setter + def cop(self, value): + check_bounds(value, 'cop', self.label_full, 1, 20) + self.conversion_factors = [{self.electrical_flow.label: value, self.thermal_flow.label: 1}] @property - def COP(self): # noqa: N802 - return self.conversion_factors[0][self.P_el.label] + def COP(self) -> Numeric_TPS: # noqa: N802 + warnings.warn( + 'The "COP" property is deprecated. Use "cop" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.cop @COP.setter - def COP(self, value): # noqa: N802 - check_bounds(value, 'COP', self.label_full, 1, 20) - self.conversion_factors[0][self.P_el.label] = value + def COP(self, value: Numeric_TPS) -> None: # noqa: N802 + warnings.warn( + 'The "COP" property is deprecated. Use "cop" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.cop = value + + @property + def P_el(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.electrical_flow + + @P_el.setter + def P_el(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.electrical_flow = value + + @property + def Q_th(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.thermal_flow + + @Q_th.setter + def Q_th(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.thermal_flow = value @register_class_for_io @@ -292,11 +509,13 @@ class CoolingTower(LinearConverter): specific_electricity_demand: Auxiliary electricity demand per unit of cooling power (dimensionless, typically 0.01-0.05 range). Represents the fraction of thermal power that must be supplied as electricity for fans and pumps. - P_el: Electrical input-flow representing electricity consumption for fans/pumps. - Q_th: Thermal input-flow representing waste heat to be rejected to environment. + electrical_flow: Electrical input-flow representing electricity consumption for fans/pumps. + thermal_flow: Thermal input-flow representing waste heat to be rejected to environment. on_off_parameters: Parameters defining binary operation constraints and costs. meta_data: Used to store additional information. Not used internally but saved in results. Only use Python native types. + P_el: *Deprecated*. Use `electrical_flow` instead. + Q_th: *Deprecated*. Use `thermal_flow` instead. Examples: Industrial cooling tower: @@ -305,8 +524,8 @@ class CoolingTower(LinearConverter): cooling_tower = CoolingTower( label='process_cooling_tower', specific_electricity_demand=0.025, # 2.5% auxiliary power - P_el=cooling_electricity, - Q_th=waste_heat_flow, + electrical_flow=cooling_electricity, + thermal_flow=waste_heat_flow, ) ``` @@ -316,8 +535,8 @@ class CoolingTower(LinearConverter): condenser_cooling = CoolingTower( label='power_plant_cooling', specific_electricity_demand=0.015, # 1.5% auxiliary power - P_el=auxiliary_electricity, - Q_th=condenser_waste_heat, + electrical_flow=auxiliary_electricity, + thermal_flow=condenser_waste_heat, on_off_parameters=OnOffParameters( consecutive_on_hours_min=4, # Minimum operation time effects_per_running_hour={'water_consumption': 2.5}, # m³/h @@ -326,7 +545,7 @@ class CoolingTower(LinearConverter): ``` Note: - The conversion relationship is: P_el = Q_th × specific_electricity_demand + The conversion relationship is: electrical_flow = thermal_flow × specific_electricity_demand The cooling tower consumes electrical power proportional to the thermal load. No thermal energy is produced - all thermal input is rejected to the environment. @@ -339,33 +558,79 @@ def __init__( self, label: str, specific_electricity_demand: Numeric_TPS, - P_el: Flow, - Q_th: Flow, + electrical_flow: Flow | None = None, + thermal_flow: Flow | None = None, on_off_parameters: OnOffParameters | None = None, meta_data: dict | None = None, + **kwargs, ): + # Handle deprecated parameters + electrical_flow = self._handle_deprecated_kwarg(kwargs, 'P_el', 'electrical_flow', electrical_flow) + thermal_flow = self._handle_deprecated_kwarg(kwargs, 'Q_th', 'thermal_flow', thermal_flow) + self._validate_kwargs(kwargs) + + # Validate required parameters + if electrical_flow is None: + raise ValueError(f"'{label}': electrical_flow is required and cannot be None") + if thermal_flow is None: + raise ValueError(f"'{label}': thermal_flow is required and cannot be None") + super().__init__( label, - inputs=[P_el, Q_th], + inputs=[electrical_flow, thermal_flow], outputs=[], - conversion_factors=[{P_el.label: -1, Q_th.label: specific_electricity_demand}], on_off_parameters=on_off_parameters, meta_data=meta_data, ) - self.P_el = P_el - self.Q_th = Q_th - - check_bounds(specific_electricity_demand, 'specific_electricity_demand', self.label_full, 0, 1) + self.electrical_flow = electrical_flow + self.thermal_flow = thermal_flow + self.specific_electricity_demand = specific_electricity_demand # Uses setter @property def specific_electricity_demand(self): - return self.conversion_factors[0][self.Q_th.label] + return self.conversion_factors[0][self.thermal_flow.label] @specific_electricity_demand.setter def specific_electricity_demand(self, value): check_bounds(value, 'specific_electricity_demand', self.label_full, 0, 1) - self.conversion_factors[0][self.Q_th.label] = value + self.conversion_factors = [{self.electrical_flow.label: -1, self.thermal_flow.label: value}] + + @property + def P_el(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.electrical_flow + + @P_el.setter + def P_el(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.electrical_flow = value + + @property + def Q_th(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.thermal_flow + + @Q_th.setter + def Q_th(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.thermal_flow = value @register_class_for_io @@ -380,16 +645,21 @@ class CHP(LinearConverter): Args: label: The label of the Element. Used to identify it in the FlowSystem. - eta_th: Thermal efficiency factor (0-1 range). Defines the fraction of fuel + thermal_efficiency: Thermal efficiency factor (0-1 range). Defines the fraction of fuel energy converted to useful thermal output. - eta_el: Electrical efficiency factor (0-1 range). Defines the fraction of fuel + electrical_efficiency: Electrical efficiency factor (0-1 range). Defines the fraction of fuel energy converted to electrical output. - Q_fu: Fuel input-flow representing fuel consumption. - P_el: Electrical output-flow representing electricity generation. - Q_th: Thermal output-flow representing heat generation. + fuel_flow: Fuel input-flow representing fuel consumption. + electrical_flow: Electrical output-flow representing electricity generation. + thermal_flow: Thermal output-flow representing heat generation. on_off_parameters: Parameters defining binary operation constraints and costs. meta_data: Used to store additional information. Not used internally but saved in results. Only use Python native types. + eta_th: *Deprecated*. Use `thermal_efficiency` instead. + eta_el: *Deprecated*. Use `electrical_efficiency` instead. + Q_fu: *Deprecated*. Use `fuel_flow` instead. + P_el: *Deprecated*. Use `electrical_flow` instead. + Q_th: *Deprecated*. Use `thermal_flow` instead. Examples: Natural gas CHP unit: @@ -397,11 +667,11 @@ class CHP(LinearConverter): ```python gas_chp = CHP( label='natural_gas_chp', - eta_th=0.45, # 45% thermal efficiency - eta_el=0.35, # 35% electrical efficiency (80% total) - Q_fu=natural_gas_flow, - P_el=electricity_flow, - Q_th=district_heat_flow, + thermal_efficiency=0.45, # 45% thermal efficiency + electrical_efficiency=0.35, # 35% electrical efficiency (80% total) + fuel_flow=natural_gas_flow, + electrical_flow=electricity_flow, + thermal_flow=district_heat_flow, ) ``` @@ -410,11 +680,11 @@ class CHP(LinearConverter): ```python industrial_chp = CHP( label='industrial_chp', - eta_th=0.40, - eta_el=0.38, - Q_fu=fuel_gas_flow, - P_el=plant_electricity, - Q_th=process_steam, + thermal_efficiency=0.40, + electrical_efficiency=0.38, + fuel_flow=fuel_gas_flow, + electrical_flow=plant_electricity, + thermal_flow=process_steam, on_off_parameters=OnOffParameters( consecutive_on_hours_min=8, # Minimum 8-hour operation effects_per_switch_on={'startup_cost': 5000}, @@ -425,10 +695,10 @@ class CHP(LinearConverter): Note: The conversion relationships are: - - Q_th = Q_fu × eta_th (thermal output) - - P_el = Q_fu × eta_el (electrical output) + - thermal_flow = fuel_flow × thermal_efficiency (thermal output) + - electrical_flow = fuel_flow × electrical_efficiency (electrical output) - Total efficiency (eta_th + eta_el) should be ≤ 1.0, with typical combined + Total efficiency (thermal_efficiency + electrical_efficiency) should be ≤ 1.0, with typical combined efficiencies of 80-90% for modern CHP units. This provides significant efficiency gains compared to separate heat and power generation. """ @@ -436,49 +706,167 @@ class CHP(LinearConverter): def __init__( self, label: str, - eta_th: Numeric_TPS, - eta_el: Numeric_TPS, - Q_fu: Flow, - P_el: Flow, - Q_th: Flow, + thermal_efficiency: Numeric_TPS | None = None, + electrical_efficiency: Numeric_TPS | None = None, + fuel_flow: Flow | None = None, + electrical_flow: Flow | None = None, + thermal_flow: Flow | None = None, on_off_parameters: OnOffParameters | None = None, meta_data: dict | None = None, + **kwargs, ): - heat = {Q_fu.label: eta_th, Q_th.label: 1} - electricity = {Q_fu.label: eta_el, P_el.label: 1} + # Handle deprecated parameters + fuel_flow = self._handle_deprecated_kwarg(kwargs, 'Q_fu', 'fuel_flow', fuel_flow) + electrical_flow = self._handle_deprecated_kwarg(kwargs, 'P_el', 'electrical_flow', electrical_flow) + thermal_flow = self._handle_deprecated_kwarg(kwargs, 'Q_th', 'thermal_flow', thermal_flow) + thermal_efficiency = self._handle_deprecated_kwarg(kwargs, 'eta_th', 'thermal_efficiency', thermal_efficiency) + electrical_efficiency = self._handle_deprecated_kwarg( + kwargs, 'eta_el', 'electrical_efficiency', electrical_efficiency + ) + self._validate_kwargs(kwargs) + + # Validate required parameters + if fuel_flow is None: + raise ValueError(f"'{label}': fuel_flow is required and cannot be None") + if electrical_flow is None: + raise ValueError(f"'{label}': electrical_flow is required and cannot be None") + if thermal_flow is None: + raise ValueError(f"'{label}': thermal_flow is required and cannot be None") + if thermal_efficiency is None: + raise ValueError(f"'{label}': thermal_efficiency is required and cannot be None") + if electrical_efficiency is None: + raise ValueError(f"'{label}': electrical_efficiency is required and cannot be None") super().__init__( label, - inputs=[Q_fu], - outputs=[Q_th, P_el], - conversion_factors=[heat, electricity], + inputs=[fuel_flow], + outputs=[thermal_flow, electrical_flow], + conversion_factors=[{}, {}], on_off_parameters=on_off_parameters, meta_data=meta_data, ) - self.Q_fu = Q_fu - self.P_el = P_el - self.Q_th = Q_th + self.fuel_flow = fuel_flow + self.electrical_flow = electrical_flow + self.thermal_flow = thermal_flow + self.thermal_efficiency = thermal_efficiency # Uses setter + self.electrical_efficiency = electrical_efficiency # Uses setter + + check_bounds( + electrical_efficiency + thermal_efficiency, + 'thermal_efficiency+electrical_efficiency', + self.label_full, + 0, + 1, + ) + + @property + def thermal_efficiency(self): + return self.conversion_factors[0][self.fuel_flow.label] + + @thermal_efficiency.setter + def thermal_efficiency(self, value): + check_bounds(value, 'thermal_efficiency', self.label_full, 0, 1) + self.conversion_factors[0] = {self.fuel_flow.label: value, self.thermal_flow.label: 1} + + @property + def electrical_efficiency(self): + return self.conversion_factors[1][self.fuel_flow.label] - check_bounds(eta_el + eta_th, 'eta_th+eta_el', self.label_full, 0, 1) + @electrical_efficiency.setter + def electrical_efficiency(self, value): + check_bounds(value, 'electrical_efficiency', self.label_full, 0, 1) + self.conversion_factors[1] = {self.fuel_flow.label: value, self.electrical_flow.label: 1} @property - def eta_th(self): - return self.conversion_factors[0][self.Q_fu.label] + def eta_th(self) -> Numeric_TPS: + warnings.warn( + 'The "eta_th" property is deprecated. Use "thermal_efficiency" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.thermal_efficiency @eta_th.setter - def eta_th(self, value): - check_bounds(value, 'eta_th', self.label_full, 0, 1) - self.conversion_factors[0][self.Q_fu.label] = value + def eta_th(self, value: Numeric_TPS) -> None: + warnings.warn( + 'The "eta_th" property is deprecated. Use "thermal_efficiency" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.thermal_efficiency = value @property - def eta_el(self): - return self.conversion_factors[1][self.Q_fu.label] + def eta_el(self) -> Numeric_TPS: + warnings.warn( + 'The "eta_el" property is deprecated. Use "electrical_efficiency" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.electrical_efficiency @eta_el.setter - def eta_el(self, value): - check_bounds(value, 'eta_el', self.label_full, 0, 1) - self.conversion_factors[1][self.Q_fu.label] = value + def eta_el(self, value: Numeric_TPS) -> None: + warnings.warn( + 'The "eta_el" property is deprecated. Use "electrical_efficiency" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.electrical_efficiency = value + + @property + def Q_fu(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "Q_fu" property is deprecated. Use "fuel_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.fuel_flow + + @Q_fu.setter + def Q_fu(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "Q_fu" property is deprecated. Use "fuel_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.fuel_flow = value + + @property + def P_el(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.electrical_flow + + @P_el.setter + def P_el(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.electrical_flow = value + + @property + def Q_th(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.thermal_flow + + @Q_th.setter + def Q_th(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.thermal_flow = value @register_class_for_io @@ -493,16 +881,20 @@ class HeatPumpWithSource(LinearConverter): Args: label: The label of the Element. Used to identify it in the FlowSystem. - COP: Coefficient of Performance (typically 1-20 range). Defines the ratio of + cop: Coefficient of Performance (typically 1-20 range). Defines the ratio of thermal output to electrical input. The heat source extraction is automatically - calculated as Q_ab = Q_th × (COP-1)/COP. - P_el: Electrical input-flow representing electricity consumption for compressor. - Q_ab: Heat source input-flow representing thermal energy extracted from environment + calculated as heat_source_flow = thermal_flow × (COP-1)/COP. + electrical_flow: Electrical input-flow representing electricity consumption for compressor. + heat_source_flow: Heat source input-flow representing thermal energy extracted from environment (ground, air, water source). - Q_th: Thermal output-flow representing useful heat delivered to the application. + thermal_flow: Thermal output-flow representing useful heat delivered to the application. on_off_parameters: Parameters defining binary operation constraints and costs. meta_data: Used to store additional information. Not used internally but saved in results. Only use Python native types. + COP: *Deprecated*. Use `cop` instead. + P_el: *Deprecated*. Use `electrical_flow` instead. + Q_ab: *Deprecated*. Use `heat_source_flow` instead. + Q_th: *Deprecated*. Use `thermal_flow` instead. Examples: Ground-source heat pump with explicit ground coupling: @@ -510,10 +902,10 @@ class HeatPumpWithSource(LinearConverter): ```python ground_source_hp = HeatPumpWithSource( label='geothermal_heat_pump', - COP=4.5, # High COP due to stable ground temperature - P_el=electricity_flow, - Q_ab=ground_heat_extraction, # Heat extracted from ground loop - Q_th=building_heating_flow, + cop=4.5, # High COP due to stable ground temperature + electrical_flow=electricity_flow, + heat_source_flow=ground_heat_extraction, # Heat extracted from ground loop + thermal_flow=building_heating_flow, ) ``` @@ -522,10 +914,10 @@ class HeatPumpWithSource(LinearConverter): ```python waste_heat_pump = HeatPumpWithSource( label='waste_heat_pump', - COP=temperature_dependent_cop, # Varies with temperature of heat source - P_el=electricity_consumption, - Q_ab=industrial_heat_extraction, # Heat extracted from a industrial process or waste water - Q_th=heat_supply, + cop=temperature_dependent_cop, # Varies with temperature of heat source + electrical_flow=electricity_consumption, + heat_source_flow=industrial_heat_extraction, # Heat extracted from a industrial process or waste water + thermal_flow=heat_supply, on_off_parameters=OnOffParameters( consecutive_on_hours_min=0.5, # 30-minute minimum runtime effects_per_switch_on={'costs': 1000}, @@ -535,9 +927,9 @@ class HeatPumpWithSource(LinearConverter): Note: The conversion relationships are: - - Q_th = P_el × COP (thermal output from electrical input) - - Q_ab = Q_th × (COP-1)/COP (heat source extraction) - - Energy balance: Q_th = P_el + Q_ab + - thermal_flow = electrical_flow × COP (thermal output from electrical input) + - heat_source_flow = thermal_flow × (COP-1)/COP (heat source extraction) + - Energy balance: thermal_flow = electrical_flow + heat_source_flow This formulation explicitly tracks the heat source, which is important for systems where the source capacity or temperature is limited, @@ -550,41 +942,128 @@ class HeatPumpWithSource(LinearConverter): def __init__( self, label: str, - COP: Numeric_TPS, - P_el: Flow, - Q_ab: Flow, - Q_th: Flow, + cop: Numeric_TPS | None = None, + electrical_flow: Flow | None = None, + heat_source_flow: Flow | None = None, + thermal_flow: Flow | None = None, on_off_parameters: OnOffParameters | None = None, meta_data: dict | None = None, + **kwargs, ): + # Handle deprecated parameters + electrical_flow = self._handle_deprecated_kwarg(kwargs, 'P_el', 'electrical_flow', electrical_flow) + heat_source_flow = self._handle_deprecated_kwarg(kwargs, 'Q_ab', 'heat_source_flow', heat_source_flow) + thermal_flow = self._handle_deprecated_kwarg(kwargs, 'Q_th', 'thermal_flow', thermal_flow) + cop = self._handle_deprecated_kwarg(kwargs, 'COP', 'cop', cop) + self._validate_kwargs(kwargs) + + # Validate required parameters + if electrical_flow is None: + raise ValueError(f"'{label}': electrical_flow is required and cannot be None") + if heat_source_flow is None: + raise ValueError(f"'{label}': heat_source_flow is required and cannot be None") + if thermal_flow is None: + raise ValueError(f"'{label}': thermal_flow is required and cannot be None") + if cop is None: + raise ValueError(f"'{label}': cop is required and cannot be None") + super().__init__( label, - inputs=[P_el, Q_ab], - outputs=[Q_th], - conversion_factors=[{P_el.label: COP, Q_th.label: 1}, {Q_ab.label: COP / (COP - 1), Q_th.label: 1}], + inputs=[electrical_flow, heat_source_flow], + outputs=[thermal_flow], on_off_parameters=on_off_parameters, meta_data=meta_data, ) - self.P_el = P_el - self.Q_ab = Q_ab - self.Q_th = Q_th + self.electrical_flow = electrical_flow + self.heat_source_flow = heat_source_flow + self.thermal_flow = thermal_flow + self.cop = cop # Uses setter - if np.any(np.asarray(self.COP) <= 1): - raise ValueError(f'{self.label_full}.COP must be strictly > 1 for HeatPumpWithSource.') + @property + def cop(self): + return self.conversion_factors[0][self.electrical_flow.label] + + @cop.setter + def cop(self, value): + check_bounds(value, 'cop', self.label_full, 1, 20) + if np.any(np.asarray(value) == 1): + raise ValueError(f'{self.label_full}.cop must be strictly !=1 for HeatPumpWithSource.') + self.conversion_factors = [ + {self.electrical_flow.label: value, self.thermal_flow.label: 1}, + {self.heat_source_flow.label: value / (value - 1), self.thermal_flow.label: 1}, + ] @property - def COP(self): # noqa: N802 - return self.conversion_factors[0][self.P_el.label] + def COP(self) -> Numeric_TPS: # noqa: N802 + warnings.warn( + 'The "COP" property is deprecated. Use "cop" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.cop @COP.setter - def COP(self, value): # noqa: N802 - check_bounds(value, 'COP', self.label_full, 1, 20) - if np.any(np.asarray(value) <= 1): - raise ValueError(f'{self.label_full}.COP must be strictly > 1 for HeatPumpWithSource.') - self.conversion_factors = [ - {self.P_el.label: value, self.Q_th.label: 1}, - {self.Q_ab.label: value / (value - 1), self.Q_th.label: 1}, - ] + def COP(self, value: Numeric_TPS) -> None: # noqa: N802 + warnings.warn( + 'The "COP" property is deprecated. Use "cop" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.cop = value + + @property + def P_el(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.electrical_flow + + @P_el.setter + def P_el(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "P_el" property is deprecated. Use "electrical_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.electrical_flow = value + + @property + def Q_ab(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "Q_ab" property is deprecated. Use "heat_source_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.heat_source_flow + + @Q_ab.setter + def Q_ab(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "Q_ab" property is deprecated. Use "heat_source_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.heat_source_flow = value + + @property + def Q_th(self) -> Flow: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + return self.thermal_flow + + @Q_th.setter + def Q_th(self, value: Flow) -> None: # noqa: N802 + warnings.warn( + 'The "Q_th" property is deprecated. Use "thermal_flow" instead.', + DeprecationWarning, + stacklevel=2, + ) + self.thermal_flow = value def check_bounds( @@ -604,21 +1083,12 @@ def check_bounds( lower_bound: The lower bound. upper_bound: The upper bound. """ - if isinstance(value, TimeSeriesData): - value = value.data - if isinstance(lower_bound, TimeSeriesData): - lower_bound = lower_bound.data - if isinstance(upper_bound, TimeSeriesData): - upper_bound = upper_bound.data - - # Convert to NumPy arrays to handle xr.DataArray, pd.Series, pd.DataFrame + # Convert to array for shape and statistics value_arr = np.asarray(value) - lower_arr = np.asarray(lower_bound) - upper_arr = np.asarray(upper_bound) - if not np.all(value_arr > lower_arr): + if not np.all(value_arr > lower_bound): logger.warning( - "'{}.{}' <= lower bound {}. {}.min={} shape={}", + "'{}.{}' <= lower bound {}. {}.min={}, shape={}", element_label, parameter_label, lower_bound, @@ -626,9 +1096,9 @@ def check_bounds( float(np.min(value_arr)), np.shape(value_arr), ) - if not np.all(value_arr < upper_arr): + if not np.all(value_arr < upper_bound): logger.warning( - "'{}.{}' >= upper bound {}. {}.max={} shape={}", + "'{}.{}' >= upper bound {}. {}.max={}, shape={}", element_label, parameter_label, upper_bound, diff --git a/tests/conftest.py b/tests/conftest.py index bd940b843..1873bab0e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -131,8 +131,8 @@ def simple(): """Simple boiler from simple_flow_system""" return fx.linear_converters.Boiler( 'Boiler', - eta=0.5, - Q_th=fx.Flow( + thermal_efficiency=0.5, + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=50, @@ -140,7 +140,7 @@ def simple(): relative_maximum=1, on_off_parameters=fx.OnOffParameters(), ), - Q_fu=fx.Flow('Q_fu', bus='Gas'), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ) @staticmethod @@ -148,9 +148,9 @@ def complex(): """Complex boiler with investment parameters from flow_system_complex""" return fx.linear_converters.Boiler( 'Kessel', - eta=0.5, + thermal_efficiency=0.5, on_off_parameters=fx.OnOffParameters(effects_per_running_hour={'costs': 0, 'CO2': 1000}), - Q_th=fx.Flow( + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', load_factor_max=1.0, @@ -175,7 +175,7 @@ def complex(): ), flow_hours_total_max=1e6, ), - Q_fu=fx.Flow('Q_fu', bus='Gas', size=200, relative_minimum=0, relative_maximum=1), + fuel_flow=fx.Flow('Q_fu', bus='Gas', size=200, relative_minimum=0, relative_maximum=1), ) class CHPs: @@ -184,13 +184,13 @@ def simple(): """Simple CHP from simple_flow_system""" return fx.linear_converters.CHP( 'CHP_unit', - eta_th=0.5, - eta_el=0.4, - P_el=fx.Flow( + thermal_efficiency=0.5, + electrical_efficiency=0.4, + electrical_flow=fx.Flow( 'P_el', bus='Strom', size=60, relative_minimum=5 / 60, on_off_parameters=fx.OnOffParameters() ), - Q_th=fx.Flow('Q_th', bus='Fernwärme'), - Q_fu=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme'), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ) @staticmethod @@ -198,12 +198,12 @@ def base(): """CHP from flow_system_base""" return fx.linear_converters.CHP( 'KWK', - eta_th=0.5, - eta_el=0.4, + thermal_efficiency=0.5, + electrical_efficiency=0.4, on_off_parameters=fx.OnOffParameters(effects_per_switch_on=0.01), - P_el=fx.Flow('P_el', bus='Strom', size=60, relative_minimum=5 / 60, previous_flow_rate=10), - Q_th=fx.Flow('Q_th', bus='Fernwärme', size=1e3), - Q_fu=fx.Flow('Q_fu', bus='Gas', size=1e3), + electrical_flow=fx.Flow('P_el', bus='Strom', size=60, relative_minimum=5 / 60, previous_flow_rate=10), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme', size=1e3), + fuel_flow=fx.Flow('Q_fu', bus='Gas', size=1e3), ) class LinearConverters: @@ -596,9 +596,9 @@ def flow_system_long(): flow_system.add_elements( fx.linear_converters.Boiler( 'Kessel', - eta=0.85, - Q_th=fx.Flow(label='Q_th', bus='Fernwärme'), - Q_fu=fx.Flow( + thermal_efficiency=0.85, + thermal_flow=fx.Flow(label='Q_th', bus='Fernwärme'), + fuel_flow=fx.Flow( label='Q_fu', bus='Gas', size=95, @@ -609,12 +609,12 @@ def flow_system_long(): ), fx.linear_converters.CHP( 'BHKW2', - eta_th=0.58, - eta_el=0.22, + thermal_efficiency=0.58, + electrical_efficiency=0.22, on_off_parameters=fx.OnOffParameters(effects_per_switch_on=24000), - P_el=fx.Flow('P_el', bus='Strom'), - Q_th=fx.Flow('Q_th', bus='Fernwärme'), - Q_fu=fx.Flow('Q_fu', bus='Kohle', size=288, relative_minimum=87 / 288), + electrical_flow=fx.Flow('P_el', bus='Strom'), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme'), + fuel_flow=fx.Flow('Q_fu', bus='Kohle', size=288, relative_minimum=87 / 288), ), fx.Storage( 'Speicher', diff --git a/tests/test_component.py b/tests/test_component.py index be1eecf3b..dbbd85c8f 100644 --- a/tests/test_component.py +++ b/tests/test_component.py @@ -416,7 +416,10 @@ def test_transmission_basic(self, basic_flow_system, highs_solver): flow_system.add_elements(fx.Bus('Wärme lokal')) boiler = fx.linear_converters.Boiler( - 'Boiler', eta=0.5, Q_th=fx.Flow('Q_th', bus='Wärme lokal'), Q_fu=fx.Flow('Q_fu', bus='Gas') + 'Boiler', + thermal_efficiency=0.5, + thermal_flow=fx.Flow('Q_th', bus='Wärme lokal'), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ) transmission = fx.Transmission( @@ -453,13 +456,16 @@ def test_transmission_balanced(self, basic_flow_system, highs_solver): boiler = fx.linear_converters.Boiler( 'Boiler_Standard', - eta=0.9, - Q_th=fx.Flow('Q_th', bus='Fernwärme', relative_maximum=np.array([0, 0, 0, 1, 1, 1, 1, 1, 1, 1])), - Q_fu=fx.Flow('Q_fu', bus='Gas'), + thermal_efficiency=0.9, + thermal_flow=fx.Flow('Q_th', bus='Fernwärme', relative_maximum=np.array([0, 0, 0, 1, 1, 1, 1, 1, 1, 1])), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ) boiler2 = fx.linear_converters.Boiler( - 'Boiler_backup', eta=0.4, Q_th=fx.Flow('Q_th', bus='Wärme lokal'), Q_fu=fx.Flow('Q_fu', bus='Gas') + 'Boiler_backup', + thermal_efficiency=0.4, + thermal_flow=fx.Flow('Q_th', bus='Wärme lokal'), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ) last2 = fx.Sink( @@ -527,13 +533,16 @@ def test_transmission_unbalanced(self, basic_flow_system, highs_solver): boiler = fx.linear_converters.Boiler( 'Boiler_Standard', - eta=0.9, - Q_th=fx.Flow('Q_th', bus='Fernwärme', relative_maximum=np.array([0, 0, 0, 1, 1, 1, 1, 1, 1, 1])), - Q_fu=fx.Flow('Q_fu', bus='Gas'), + thermal_efficiency=0.9, + thermal_flow=fx.Flow('Q_th', bus='Fernwärme', relative_maximum=np.array([0, 0, 0, 1, 1, 1, 1, 1, 1, 1])), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ) boiler2 = fx.linear_converters.Boiler( - 'Boiler_backup', eta=0.4, Q_th=fx.Flow('Q_th', bus='Wärme lokal'), Q_fu=fx.Flow('Q_fu', bus='Gas') + 'Boiler_backup', + thermal_efficiency=0.4, + thermal_flow=fx.Flow('Q_th', bus='Wärme lokal'), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ) last2 = fx.Sink( diff --git a/tests/test_effect.py b/tests/test_effect.py index cd3edc537..8293ec62f 100644 --- a/tests/test_effect.py +++ b/tests/test_effect.py @@ -247,13 +247,13 @@ def test_shares(self, basic_flow_system_linopy_coords, coords_config): effect3, fx.linear_converters.Boiler( 'Boiler', - eta=0.5, - Q_th=fx.Flow( + thermal_efficiency=0.5, + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=fx.InvestParameters(effects_of_investment_per_size=10, minimum_size=20, mandatory=True), ), - Q_fu=fx.Flow('Q_fu', bus='Gas'), + fuel_flow=fx.Flow('Q_fu', bus='Gas'), ), ) diff --git a/tests/test_flow_system_resample.py b/tests/test_flow_system_resample.py index d28872a0f..8946dd02f 100644 --- a/tests/test_flow_system_resample.py +++ b/tests/test_flow_system_resample.py @@ -52,9 +52,9 @@ def complex_fs(): # Piecewise converter converter = fx.linear_converters.Boiler( - 'boiler', eta=0.9, Q_fu=fx.Flow('gas', bus='elec'), Q_th=fx.Flow('heat', bus='heat') + 'boiler', thermal_efficiency=0.9, fuel_flow=fx.Flow('gas', bus='elec'), thermal_flow=fx.Flow('heat', bus='heat') ) - converter.Q_th.size = 100 + converter.thermal_flow.size = 100 fs.add_elements(converter) # Component with investment diff --git a/tests/test_functional.py b/tests/test_functional.py index a83bf112f..9b3c3e6d4 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -85,9 +85,9 @@ def flow_system_minimal(timesteps) -> fx.FlowSystem: flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow('Q_th', bus='Fernwärme'), + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme'), ) ) return flow_system @@ -141,9 +141,9 @@ def test_fixed_size(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=fx.InvestParameters(fixed_size=1000, effects_of_investment=10, effects_of_investment_per_size=1), @@ -162,14 +162,14 @@ def test_fixed_size(solver_fixture, time_steps_fixture): err_msg='The total costs does not have the right value', ) assert_allclose( - boiler.Q_th.submodel._investment.size.solution.item(), + boiler.thermal_flow.submodel.investment.size.solution.item(), 1000, rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__Investment_size" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel._investment.invested.solution.item(), + boiler.thermal_flow.submodel.investment.invested.solution.item(), 1, rtol=1e-5, atol=1e-10, @@ -182,9 +182,9 @@ def test_optimize_size(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=fx.InvestParameters(effects_of_investment=10, effects_of_investment_per_size=1), @@ -203,14 +203,14 @@ def test_optimize_size(solver_fixture, time_steps_fixture): err_msg='The total costs does not have the right value', ) assert_allclose( - boiler.Q_th.submodel._investment.size.solution.item(), + boiler.thermal_flow.submodel.investment.size.solution.item(), 20, rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__Investment_size" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel._investment.invested.solution.item(), + boiler.thermal_flow.submodel.investment.invested.solution.item(), 1, rtol=1e-5, atol=1e-10, @@ -223,9 +223,9 @@ def test_size_bounds(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=fx.InvestParameters(minimum_size=40, effects_of_investment=10, effects_of_investment_per_size=1), @@ -244,14 +244,14 @@ def test_size_bounds(solver_fixture, time_steps_fixture): err_msg='The total costs does not have the right value', ) assert_allclose( - boiler.Q_th.submodel._investment.size.solution.item(), + boiler.thermal_flow.submodel.investment.size.solution.item(), 40, rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__Investment_size" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel._investment.invested.solution.item(), + boiler.thermal_flow.submodel.investment.invested.solution.item(), 1, rtol=1e-5, atol=1e-10, @@ -264,9 +264,9 @@ def test_optional_invest(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=fx.InvestParameters( @@ -276,9 +276,9 @@ def test_optional_invest(solver_fixture, time_steps_fixture): ), fx.linear_converters.Boiler( 'Boiler_optional', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=fx.InvestParameters( @@ -300,14 +300,14 @@ def test_optional_invest(solver_fixture, time_steps_fixture): err_msg='The total costs does not have the right value', ) assert_allclose( - boiler.Q_th.submodel._investment.size.solution.item(), + boiler.thermal_flow.submodel.investment.size.solution.item(), 40, rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__Investment_size" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel._investment.invested.solution.item(), + boiler.thermal_flow.submodel.investment.invested.solution.item(), 1, rtol=1e-5, atol=1e-10, @@ -315,14 +315,14 @@ def test_optional_invest(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler_optional.Q_th.submodel._investment.size.solution.item(), + boiler_optional.thermal_flow.submodel.investment.size.solution.item(), 0, rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__Investment_size" does not have the right value', ) assert_allclose( - boiler_optional.Q_th.submodel._investment.invested.solution.item(), + boiler_optional.thermal_flow.submodel.investment.invested.solution.item(), 0, rtol=1e-5, atol=1e-10, @@ -336,9 +336,9 @@ def test_on(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow('Q_th', bus='Fernwärme', size=100, on_off_parameters=fx.OnOffParameters()), + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme', size=100, on_off_parameters=fx.OnOffParameters()), ) ) @@ -354,14 +354,14 @@ def test_on(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler.Q_th.submodel.on_off.on.solution.values, + boiler.thermal_flow.submodel.on_off.on.solution.values, [0, 1, 1, 0, 1], rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__on" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel.flow_rate.solution.values, + boiler.thermal_flow.submodel.flow_rate.solution.values, [0, 10, 20, 0, 10], rtol=1e-5, atol=1e-10, @@ -375,9 +375,9 @@ def test_off(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=100, @@ -398,21 +398,21 @@ def test_off(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler.Q_th.submodel.on_off.on.solution.values, + boiler.thermal_flow.submodel.on_off.on.solution.values, [0, 1, 1, 0, 1], rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__on" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel.on_off.off.solution.values, - 1 - boiler.Q_th.submodel.on_off.on.solution.values, + boiler.thermal_flow.submodel.on_off.off.solution.values, + 1 - boiler.thermal_flow.submodel.on_off.on.solution.values, rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__off" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel.flow_rate.solution.values, + boiler.thermal_flow.submodel.flow_rate.solution.values, [0, 10, 20, 0, 10], rtol=1e-5, atol=1e-10, @@ -426,9 +426,9 @@ def test_switch_on_off(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=100, @@ -449,28 +449,28 @@ def test_switch_on_off(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler.Q_th.submodel.on_off.on.solution.values, + boiler.thermal_flow.submodel.on_off.on.solution.values, [0, 1, 1, 0, 1], rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__on" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel.on_off.switch_on.solution.values, + boiler.thermal_flow.submodel.on_off.switch_on.solution.values, [0, 1, 0, 0, 1], rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__switch_on" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel.on_off.switch_off.solution.values, + boiler.thermal_flow.submodel.on_off.switch_off.solution.values, [0, 0, 0, 1, 0], rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__switch_on" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel.flow_rate.solution.values, + boiler.thermal_flow.submodel.flow_rate.solution.values, [0, 10, 20, 0, 10], rtol=1e-5, atol=1e-10, @@ -484,9 +484,9 @@ def test_on_total_max(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=100, @@ -495,9 +495,9 @@ def test_on_total_max(solver_fixture, time_steps_fixture): ), fx.linear_converters.Boiler( 'Boiler_backup', - 0.2, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow('Q_th', bus='Fernwärme', size=100), + thermal_efficiency=0.2, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme', size=100), ), ) @@ -513,14 +513,14 @@ def test_on_total_max(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler.Q_th.submodel.on_off.on.solution.values, + boiler.thermal_flow.submodel.on_off.on.solution.values, [0, 0, 1, 0, 0], rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__on" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel.flow_rate.solution.values, + boiler.thermal_flow.submodel.flow_rate.solution.values, [0, 0, 20, 0, 0], rtol=1e-5, atol=1e-10, @@ -534,9 +534,9 @@ def test_on_total_bounds(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=100, @@ -545,9 +545,9 @@ def test_on_total_bounds(solver_fixture, time_steps_fixture): ), fx.linear_converters.Boiler( 'Boiler_backup', - 0.2, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.2, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=100, @@ -572,14 +572,14 @@ def test_on_total_bounds(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler.Q_th.submodel.on_off.on.solution.values, + boiler.thermal_flow.submodel.on_off.on.solution.values, [0, 0, 1, 0, 1], rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__on" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel.flow_rate.solution.values, + boiler.thermal_flow.submodel.flow_rate.solution.values, [0, 0, 20, 0, 12 - 1e-5], rtol=1e-5, atol=1e-10, @@ -587,14 +587,14 @@ def test_on_total_bounds(solver_fixture, time_steps_fixture): ) assert_allclose( - sum(boiler_backup.Q_th.submodel.on_off.on.solution.values), + sum(boiler_backup.thermal_flow.submodel.on_off.on.solution.values), 3, rtol=1e-5, atol=1e-10, err_msg='"Boiler_backup__Q_th__on" does not have the right value', ) assert_allclose( - boiler_backup.Q_th.submodel.flow_rate.solution.values, + boiler_backup.thermal_flow.submodel.flow_rate.solution.values, [0, 10, 1.0e-05, 0, 1.0e-05], rtol=1e-5, atol=1e-10, @@ -608,9 +608,9 @@ def test_consecutive_on_off(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=100, @@ -619,9 +619,9 @@ def test_consecutive_on_off(solver_fixture, time_steps_fixture): ), fx.linear_converters.Boiler( 'Boiler_backup', - 0.2, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow('Q_th', bus='Fernwärme', size=100), + thermal_efficiency=0.2, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme', size=100), ), ) flow_system['Wärmelast'].inputs[0].fixed_relative_profile = np.array([5, 10, 20, 18, 12]) @@ -640,14 +640,14 @@ def test_consecutive_on_off(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler.Q_th.submodel.on_off.on.solution.values, + boiler.thermal_flow.submodel.on_off.on.solution.values, [1, 1, 0, 1, 1], rtol=1e-5, atol=1e-10, err_msg='"Boiler__Q_th__on" does not have the right value', ) assert_allclose( - boiler.Q_th.submodel.flow_rate.solution.values, + boiler.thermal_flow.submodel.flow_rate.solution.values, [5, 10, 0, 18, 12], rtol=1e-5, atol=1e-10, @@ -655,7 +655,7 @@ def test_consecutive_on_off(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler_backup.Q_th.submodel.flow_rate.solution.values, + boiler_backup.thermal_flow.submodel.flow_rate.solution.values, [0, 0, 20, 0, 0], rtol=1e-5, atol=1e-10, @@ -669,15 +669,15 @@ def test_consecutive_off(solver_fixture, time_steps_fixture): flow_system.add_elements( fx.linear_converters.Boiler( 'Boiler', - 0.5, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow('Q_th', bus='Fernwärme'), + thermal_efficiency=0.5, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow('Q_th', bus='Fernwärme'), ), fx.linear_converters.Boiler( 'Boiler_backup', - 0.2, - Q_fu=fx.Flow('Q_fu', bus='Gas'), - Q_th=fx.Flow( + thermal_efficiency=0.2, + fuel_flow=fx.Flow('Q_fu', bus='Gas'), + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', size=100, @@ -703,21 +703,21 @@ def test_consecutive_off(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler_backup.Q_th.submodel.on_off.on.solution.values, + boiler_backup.thermal_flow.submodel.on_off.on.solution.values, [0, 0, 1, 0, 0], rtol=1e-5, atol=1e-10, err_msg='"Boiler_backup__Q_th__on" does not have the right value', ) assert_allclose( - boiler_backup.Q_th.submodel.on_off.off.solution.values, + boiler_backup.thermal_flow.submodel.on_off.off.solution.values, [1, 1, 0, 1, 1], rtol=1e-5, atol=1e-10, err_msg='"Boiler_backup__Q_th__off" does not have the right value', ) assert_allclose( - boiler_backup.Q_th.submodel.flow_rate.solution.values, + boiler_backup.thermal_flow.submodel.flow_rate.solution.values, [0, 0, 1e-5, 0, 0], rtol=1e-5, atol=1e-10, @@ -725,7 +725,7 @@ def test_consecutive_off(solver_fixture, time_steps_fixture): ) assert_allclose( - boiler.Q_th.submodel.flow_rate.solution.values, + boiler.thermal_flow.submodel.flow_rate.solution.values, [5, 0, 20 - 1e-5, 18, 12], rtol=1e-5, atol=1e-10, diff --git a/tests/test_integration.py b/tests/test_integration.py index 6e5da63d6..88e4a21af 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -37,14 +37,14 @@ def test_model_components(self, simple_flow_system, highs_solver): # Boiler assertions assert_almost_equal_numeric( - comps['Boiler'].Q_th.submodel.flow_rate.solution.values, + comps['Boiler'].thermal_flow.submodel.flow_rate.solution.values, [0, 0, 0, 28.4864, 35, 0, 0, 0, 0], 'Q_th doesnt match expected value', ) # CHP unit assertions assert_almost_equal_numeric( - comps['CHP_unit'].Q_th.submodel.flow_rate.solution.values, + comps['CHP_unit'].thermal_flow.submodel.flow_rate.solution.values, [30.0, 26.66666667, 75.0, 75.0, 75.0, 20.0, 20.0, 20.0, 20.0], 'Q_th doesnt match expected value', ) @@ -220,7 +220,7 @@ def test_piecewise_conversion(self, flow_system_piecewise_conversion, highs_solv effects['CO2'].submodel.total.solution.item(), 1278.7939026086956, 'CO2 doesnt match expected value' ) assert_almost_equal_numeric( - comps['Kessel'].Q_th.submodel.flow_rate.solution.values, + comps['Kessel'].thermal_flow.submodel.flow_rate.solution.values, [0, 0, 0, 45, 0, 0, 0, 0, 0], 'Kessel doesnt match expected value', ) diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index 928eb88c6..f63405641 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -85,7 +85,7 @@ def test_system(): ), eta_charge=0.95, eta_discharge=0.95, - initial_charge_state='lastValueOfSim', + initial_charge_state='equals_final', ) # Create effects and objective @@ -139,9 +139,9 @@ def flow_system_complex_scenarios() -> fx.FlowSystem: boiler = fx.linear_converters.Boiler( 'Kessel', - eta=0.5, + thermal_efficiency=0.5, on_off_parameters=fx.OnOffParameters(effects_per_running_hour={'costs': 0, 'CO2': 1000}), - Q_th=fx.Flow( + thermal_flow=fx.Flow( 'Q_th', bus='Fernwärme', load_factor_max=1.0, @@ -166,7 +166,7 @@ def flow_system_complex_scenarios() -> fx.FlowSystem: ), flow_hours_total_max=1e6, ), - Q_fu=fx.Flow('Q_fu', bus='Gas', size=200, relative_minimum=0, relative_maximum=1), + fuel_flow=fx.Flow('Q_fu', bus='Gas', size=200, relative_minimum=0, relative_maximum=1), ) invest_speicher = fx.InvestParameters( diff --git a/tests/test_storage.py b/tests/test_storage.py index 8d0c495c2..6220ee08a 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -362,7 +362,7 @@ def test_storage_cyclic_initialization(self, basic_flow_system_linopy_coords, co charging=fx.Flow('Q_th_in', bus='Fernwärme', size=20), discharging=fx.Flow('Q_th_out', bus='Fernwärme', size=20), capacity_in_flow_hours=30, - initial_charge_state='lastValueOfSim', # Cyclic initialization + initial_charge_state='equals_final', # Cyclic initialization eta_charge=0.9, eta_discharge=0.9, relative_loss_per_hour=0.05,