diff --git a/CHANGELOG.md b/CHANGELOG.md index 0acbbc93..acb16801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Efficiency classes for COP, EER, SCOP, SEER (issue #285). - Added building laod classes (issue #288). - Added __eq__ method for Result and Efficiency classes (issue #288). +- Added _time_array to building loads (issue #291). +- Added EERCombined for combined active and passive cooling efficiency (issue #291, #296). +- Cluster Class (issue #298). ## Changed @@ -39,6 +42,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). issue #283). - Removed a couple of log messages (issue #288). - Optimise load profile works with a variable COP/EER (issue #288). +- Rename cylindrical_correction.py (issue #298). ## Fixed @@ -47,6 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Problem in CI/CD and testing for python <3.12 (issue #274). - Fix compatibility with numpy 2.0 (issue #274). - Fix problem with start month and zero peak loads (issue #288). +- Problem with EERCombined (issue #300). ## [2.2.2] - 2024-05-16 diff --git a/GHEtool/Borefield.py b/GHEtool/Borefield.py index acc0c0b0..a3a0b9cd 100644 --- a/GHEtool/Borefield.py +++ b/GHEtool/Borefield.py @@ -17,7 +17,8 @@ from scipy.signal import convolve from GHEtool.VariableClasses import FluidData, Borehole, GroundConstantTemperature, ResultsMonthly, ResultsHourly -from GHEtool.VariableClasses import CustomGFunction, load_custom_gfunction, GFunction, CalculationSetup +from GHEtool.VariableClasses import CustomGFunction, load_custom_gfunction, GFunction, CalculationSetup, Cluster, \ + EERCombined from GHEtool.VariableClasses.LoadData import * from GHEtool.VariableClasses.LoadData import _LoadData, _LoadDataBuilding from GHEtool.VariableClasses.PipeData import _PipeData @@ -497,13 +498,13 @@ def set_investment_cost(self, investment_cost: list = None) -> None: investment_cost = Borefield.DEFAULT_INVESTMENT self.cost_investment: list = investment_cost - def set_load(self, load: _LoadData) -> None: + def set_load(self, load: Union[_LoadData, Cluster]) -> None: """ This function sets the _load attribute. Parameters ---------- - load : _LoadData + load : _LoadData or Cluster Load data object Returns @@ -525,13 +526,13 @@ def load(self) -> HourlyGeothermalLoad | MonthlyGeothermalLoadAbsolute | \ return self._borefield_load @load.setter - def load(self, load: _LoadData) -> None: + def load(self, load: Union[_LoadData, Cluster]) -> None: """ This function sets the _load attribute. Parameters ---------- - load : _LoadData + load : _LoadData or Cluster Load data object Returns @@ -1706,12 +1707,16 @@ def calculate_temperatures(H, hourly=hourly): def calculate_difference(results_old: Union[ResultsMonthly, ResultsHourly], result_new: Union[ResultsMonthly, ResultsHourly]) -> float: return max( - np.max((result_new.peak_injection - results_old.peak_injection) / self.load.max_peak_injection), - np.max((result_new.peak_extraction - results_old.peak_extraction) / self.load.max_peak_extraction)) + np.max(result_new.peak_injection - results_old.peak_injection), + np.max(result_new.peak_extraction - results_old.peak_extraction)) if isinstance(self.load, _LoadDataBuilding): # when building load is given, the load should be updated after each temperature calculation. - self.load.reset_results(self.Tf_min, self.Tf_max) + # check if active_passive, because then, threshold should be taken + if isinstance(self.load.eer, EERCombined) and self.load.eer.threshold_temperature is not None: + self.load.reset_results(self.Tf_min, self.load.eer.threshold_temperature) + else: + self.load.reset_results(self.Tf_min, self.Tf_max) results_old = calculate_temperatures(H, hourly=hourly) self.load.set_results(results_old) results = calculate_temperatures(H, hourly=hourly) @@ -1719,12 +1724,13 @@ def calculate_difference(results_old: Union[ResultsMonthly, ResultsHourly], # safety i = 0 while calculate_difference(results_old, - results) > 0.01 and i < self._calculation_setup.max_nb_of_iterations: + results) > self._calculation_setup.atol and i < self._calculation_setup.max_nb_of_iterations: results_old = results self.load.set_results(results) results = calculate_temperatures(H, hourly=hourly) i += 1 self.results = results + self.load.set_results(results) return self.results = calculate_temperatures(H, hourly=hourly) diff --git a/GHEtool/Examples/active_passive_cooling.py b/GHEtool/Examples/active_passive_cooling.py index 603b6ff7..bdd56763 100644 --- a/GHEtool/Examples/active_passive_cooling.py +++ b/GHEtool/Examples/active_passive_cooling.py @@ -27,7 +27,7 @@ def active_passive_cooling(location='Active_passive_example.csv'): # variable COP and EER data COP = [0.122, 4.365] # ax+b - EER = [-3.916, 17.901] # ax+b + EER = [-0.3916, 17.901] # ax+b threshold_active_cooling = 16 # set simulation period @@ -81,7 +81,7 @@ def update_load_EER(temp_profile: np.ndarray, Geothermal cooling load : np.ndarray """ EER_array = temp_profile * EER[0] + EER[1] - passive: np.ndarray = temp_profile < threshold_active_cooling + passive: np.ndarray = temp_profile <= threshold_active_cooling active = np.invert(passive) return active * load_profile * (1 + 1 / EER_array) + passive * load_profile @@ -177,6 +177,7 @@ def calculate_costs(borefield: Borefield, heating_building: np.ndarray, heating_ heating_ground = heating_building.copy() borefield.set_max_avg_fluid_temperature(25) + borefield.gfunction_calculation_object.store_previous_values = False while abs(depths[0] - depths[1]) > 0.1: # set loads load = HourlyGeothermalLoadMultiYear() diff --git a/GHEtool/Examples/combined_active_and_passive_cooling.py b/GHEtool/Examples/combined_active_and_passive_cooling.py new file mode 100644 index 00000000..5239e8e5 --- /dev/null +++ b/GHEtool/Examples/combined_active_and_passive_cooling.py @@ -0,0 +1,130 @@ +""" +This file contains an example code of how the EERCombined works. For an auditorium building, two cases for the +active/passive combination will be considered: + 1) Default active cooling in certain months + 2) Active cooling above a certain temperature threshold + +It is shown that a temperature threshold will lead to 90% less active cooling over the simulation period. Due to the +imbalance, active cooling will take up a smaller percentage of cooling load over time. +""" + +import numpy as np +import matplotlib.pyplot as plt + +from GHEtool import * + +# set general parameters +ground_data = GroundFluxTemperature(3, 10) +fluid_data = FluidData(0.2, 0.568, 998, 4180, 1e-3) +pipe_data = DoubleUTube(1, 0.015, 0.02, 0.4, 0.05) + +load = HourlyBuildingLoad(efficiency_heating=5) # use SCOP of 5 for heating +load.load_hourly_profile(FOLDER.joinpath("test\methods\hourly_data\\auditorium.csv"), header=True, + separator=";", col_cooling=0, col_heating=1) + +eer_active = EER(np.array([15.943, 6.153]), np.array([5, 30])) # based on the data of the WRE092 chiller of Galletti + + +def default_cooling_in_summer(): + borefield = Borefield() + borefield.create_rectangular_borefield(3, 3, 6, 6, 110, 0.7, 0.075) + borefield.set_ground_parameters(ground_data) + borefield.set_fluid_parameters(fluid_data) + borefield.set_pipe_parameters(pipe_data) + borefield.set_max_avg_fluid_temperature(25) + borefield.set_min_avg_fluid_temperature(3) + + # create combined active and passive EER + eer = EERCombined(20, eer_active, months_active_cooling=[6, 7, 8]) + + # set variables + load.eer = eer + borefield.load = load + + borefield.print_temperature_profile(plot_hourly=True) + + # get active cooling data + active_cooling_array = borefield.load.eer.get_time_series_active_cooling(borefield.results.peak_injection, + load.month_indices) + active_cooling_energy = load.hourly_cooling_load_simulation_period * active_cooling_array + passive_cooling_energy = load.hourly_cooling_load_simulation_period * np.invert(active_cooling_array) + print(f'{np.sum(active_cooling_energy) / load.simulation_period:.0f}kWh of active cooling on average per year. ' + f'This is {np.sum(active_cooling_energy) / np.sum(load.hourly_cooling_load_simulation_period) * 100:.2f}% ' + f'of the building cooling load.') + print( + f'The peak power for active and passive cooling is: {np.max(active_cooling_energy):.2f}kW and {np.max(passive_cooling_energy):.2f}kW respectively.') + + # create graphs + fig = plt.figure() + ax = fig.add_subplot(111) + time_array = load.time_L4 / 12 / 3600 / 730 + ax.plot(time_array, active_cooling_array) + fig.suptitle('Active cooling on') + ax.set_xlabel('Time [years]') + ax.set_xticks(range(0, load.simulation_period + 1, 2)) + + fig = plt.figure() + ax = fig.add_subplot(111) + ax.plot(np.sum( + np.reshape(load.hourly_cooling_load_simulation_period * active_cooling_array, (load.simulation_period, 8760)), + axis=1)) + fig.suptitle('Active cooling energy') + ax.set_xlabel('Time [years]') + ax.set_ylabel('Energy per year [kWh]') + ax.set_xticks(range(0, load.simulation_period + 1, 2)) + plt.show() + return np.sum(active_cooling_energy) + + +def active_above_threshold(): + borefield = Borefield() + borefield.create_rectangular_borefield(3, 3, 6, 6, 110, 0.7, 0.075) + borefield.set_ground_parameters(ground_data) + borefield.set_fluid_parameters(fluid_data) + borefield.set_pipe_parameters(pipe_data) + borefield.set_max_avg_fluid_temperature(25) + borefield.set_min_avg_fluid_temperature(3) + + eer = EERCombined(20, eer_active, threshold_temperature=17) + + load.eer = eer + borefield.load = load + + borefield.print_temperature_profile(plot_hourly=True) + + # get active cooling data + active_cooling_array = borefield.load.eer.get_time_series_active_cooling(borefield.results.peak_injection, + load.month_indices) + active_cooling_energy = load.hourly_cooling_load_simulation_period * active_cooling_array + passive_cooling_energy = load.hourly_cooling_load_simulation_period * np.invert(active_cooling_array) + print(f'{np.sum(active_cooling_energy) / load.simulation_period:.0f}kWh of active cooling on average per year. ' + f'This is {np.sum(active_cooling_energy) / np.sum(load.hourly_cooling_load_simulation_period) * 100:.2f}% ' + f'of the building cooling load.') + print( + f'The peak power for active and passive cooling is: {np.max(active_cooling_energy):.2f}kW and {np.max(passive_cooling_energy):.2f}kW respectively.') + + # create graphs + fig = plt.figure() + ax = fig.add_subplot(111) + time_array = load.time_L4 / 12 / 3600 / 730 + ax.plot(time_array, active_cooling_array) + fig.suptitle('Active cooling on') + ax.set_xlabel('Time [years]') + ax.set_xticks(range(0, load.simulation_period + 1, 2)) + + fig = plt.figure() + ax = fig.add_subplot(111) + ax.plot(np.sum( + np.reshape(load.hourly_cooling_load_simulation_period * active_cooling_array, (load.simulation_period, 8760)), + axis=1)) + fig.suptitle('Active cooling energy') + ax.set_xlabel('Time [years]') + ax.set_ylabel('Energy per year [kWh]') + ax.set_xticks(range(0, load.simulation_period + 1, 2)) + plt.show() + return np.sum(active_cooling_energy) + + +if __name__ == "__main__": + default_cooling_in_summer() + active_above_threshold() diff --git a/GHEtool/Methods/optimise_load_profile.py b/GHEtool/Methods/optimise_load_profile.py index cfa06810..4fa1ea82 100644 --- a/GHEtool/Methods/optimise_load_profile.py +++ b/GHEtool/Methods/optimise_load_profile.py @@ -89,9 +89,11 @@ def optimise_load_profile_power( while not cool_ok or not heat_ok: # limit the primary geothermal extraction and injection load to peak_heat_load and peak_cool_load borefield.load.set_hourly_cooling_load( - np.minimum(peak_cool_load, building_load.hourly_cooling_load)) + np.minimum(peak_cool_load, building_load.hourly_cooling_load + if isinstance(borefield.load, HourlyBuildingLoad) else building_load.hourly_cooling_load_simulation_period)) borefield.load.set_hourly_heating_load( - np.minimum(peak_heat_load, building_load.hourly_heating_load)) + np.minimum(peak_heat_load, building_load.hourly_heating_load + if isinstance(borefield.load, HourlyBuildingLoad) else building_load.hourly_heating_load_simulation_period)) # calculate temperature profile, just for the results borefield.calculate_temperatures(depth=depth, hourly=use_hourly_resolution) @@ -197,7 +199,9 @@ def optimise_load_profile_energy( building_load = HourlyBuildingLoadMultiYear(building_load.hourly_heating_load_simulation_period, building_load.hourly_cooling_load_simulation_period, building_load._cop, - building_load._eer) + building_load._eer, + building_load.hourly_dhw_load_simulation_period, + building_load._cop_dhw) # set max peak values init_peak_heating = building_load.hourly_heating_load_simulation_period.copy() @@ -237,7 +241,9 @@ def optimise_load_profile_energy( peak_heating=building_load.monthly_peak_heating_simulation_period, peak_cooling=building_load.monthly_peak_cooling_simulation_period, efficiency_heating=building_load._cop, - efficiency_cooling=building_load._eer) + efficiency_cooling=building_load._eer, + dhw=building_load.monthly_baseload_dhw_simulation_period, + efficiency_dhw=building_load._cop_dhw) borefield.load = monthly_load diff --git a/GHEtool/VariableClasses/cylindrical_correction.py b/GHEtool/VariableClasses/Cylindrical_correction.py similarity index 100% rename from GHEtool/VariableClasses/cylindrical_correction.py rename to GHEtool/VariableClasses/Cylindrical_correction.py diff --git a/GHEtool/VariableClasses/Efficiency/EER.py b/GHEtool/VariableClasses/Efficiency/EER.py index dbfb82ba..c702d17c 100644 --- a/GHEtool/VariableClasses/Efficiency/EER.py +++ b/GHEtool/VariableClasses/Efficiency/EER.py @@ -50,7 +50,8 @@ def __init__(self, def get_EER(self, primary_temperature: Union[float, np.ndarray], secondary_temperature: Union[float, np.ndarray] = None, - power: Union[float, np.ndarray] = None) -> np.ndarray: + power: Union[float, np.ndarray] = None, + **kwargs) -> np.ndarray: """ This function calculates the EER. This function uses a linear interpolation and sets the out-of-bound values to the nearest value in the dataset. This function does hence not extrapolate. @@ -79,8 +80,8 @@ def get_EER(self, def get_SEER(self, power: np.ndarray, primary_temperature: np.ndarray, - secondary_temperature: np.ndarray = None - ) -> float: + secondary_temperature: np.ndarray = None, + **kwargs) -> float: """ This function calculates and returns the SEER. @@ -108,9 +109,9 @@ def get_SEER(self, secondary_temperature is None or len(secondary_temperature) == len(power)): raise ValueError('The hourly arrays should have equal length!') - cop_array = self.get_EER(primary_temperature, secondary_temperature, power) + eer_array = self.get_EER(primary_temperature, secondary_temperature, power) # SEER = sum(Q)/sum(W) - w_array = np.array(power) / cop_array + w_array = np.array(power) / eer_array return np.sum(power) / np.sum(w_array) diff --git a/GHEtool/VariableClasses/Efficiency/EERCombined.py b/GHEtool/VariableClasses/Efficiency/EERCombined.py new file mode 100644 index 00000000..362687f8 --- /dev/null +++ b/GHEtool/VariableClasses/Efficiency/EERCombined.py @@ -0,0 +1,199 @@ +import numpy as np + +from typing import Union + +from GHEtool.VariableClasses.Efficiency.EER import EER +from GHEtool.VariableClasses.Efficiency.SEER import SEER + + +class EERCombined: + """ + Class for EER efficiency of combined active and passive cooling, with dependencies on main average inlet temperature, + main average outlet temperature (optional) and part-load (optional) conditions. + """ + + def __init__(self, + efficiency_passive_cooling: Union[EER, SEER, float], + efficiency_active_cooling: Union[EER, SEER, float], + threshold_temperature: float = None, + months_active_cooling: Union[np.ndarray, list] = None): + """ + + Parameters + ---------- + efficiency_passive_cooling : EER, SEER or float + The efficiency class for the passive cooling. Floats will be converted to SEER. + efficiency_active_cooling : EER, SEER or float + The efficiency class for the active cooling. Floats will be converted to SEER. + threshold_temperature : float + Temperature threshold above which active cooling is chosen. + months_active_cooling : np.ndarray, list + Months at which by default there is active cooling (jan:1, feb:2 etc.). + + Raises + ------ + ValueError + If neither a threshold_temperature nor months_active_cooling is provided. + """ + + self.efficiency_passive_cooling = efficiency_passive_cooling + self.efficiency_active_cooling = efficiency_active_cooling + self.threshold_temperature = threshold_temperature + self.months_active_cooling = np.array(months_active_cooling) if months_active_cooling is not None else None + + if isinstance(efficiency_active_cooling, (float, int)): + self.efficiency_active_cooling = SEER(efficiency_active_cooling) + + if isinstance(efficiency_passive_cooling, (float, int)): + self.efficiency_passive_cooling = SEER(efficiency_passive_cooling) + + if threshold_temperature is None and months_active_cooling is None: + raise ValueError('Please set either a threshold temperature or the months for active cooling.') + + def get_time_series_active_cooling(self, primary_temperature: np.ndarray, + month_indices: np.ndarray = None) -> np.ndarray: + """ + This function calculates the time series when there is active cooling based on the primary fluid temperature + and the month indices. + + Parameters + ---------- + primary_temperature : np.ndarray + Array with the primary fluid temperatures + month_indices : np.ndarray + Array with all the monthly indices, after correction for the start month. Should be the same length as + the primary_temperature + + Returns + ------- + Time series active cooling : np.ndarray + Array with boolean values to indicate when there is active cooling + """ + + active_cooling_bool = np.full(primary_temperature.shape, False) + + if self.threshold_temperature is not None: + active_cooling_bool = primary_temperature > self.threshold_temperature + + if self.months_active_cooling is not None: + if month_indices is None: + raise ValueError('Please provide a month value, for otherwise the system cannot decide if it is ' + 'active or passive cooling.') + active_cooling_bool = np.add(active_cooling_bool, np.isin(month_indices, self.months_active_cooling)) + + return active_cooling_bool + + def get_EER(self, + primary_temperature: Union[float, np.ndarray], + secondary_temperature: Union[float, np.ndarray] = None, + power: Union[float, np.ndarray] = None, + month_indices: Union[float, np.ndarray] = None) -> Union[np.ndarray, float]: + """ + This function calculates the EER. This function uses a linear interpolation and sets the out-of-bound values + to the nearest value in the dataset. This function does hence not extrapolate. + + Parameters + ---------- + primary_temperature : np.ndarray or float + Value(s) for the average primary temperature of the heat pump for the EER calculation. + secondary_temperature : np.ndarray or float + Value(s) for the average secondary temperature of the heat pump for the EER calculation. + power : np.ndarray or float + Value(s) for the part load data of the heat pump for the EER calculation. + month_indices : np.ndarray or float + Array with all the monthly indices, after correction for the start month. Should be the same length as the + other input parameters + + Raises + ------ + ValueError + When secondary_temperature is in the dataset, and it is not provided. Same for power. + + Returns + ------- + EER + np.ndarray + """ + + if isinstance(primary_temperature, (float, int)) and ( + isinstance(month_indices, (float, int)) or month_indices is None): + # check temperature threshold + active_cooling_bool = False + if self.threshold_temperature is not None and primary_temperature > self.threshold_temperature: + active_cooling_bool = True + + if not active_cooling_bool and self.months_active_cooling is not None: + if month_indices is None: + raise ValueError('Please provide a month value, for otherwise the system cannot decide if it is ' + 'active or passive cooling.') + active_cooling_bool = month_indices in self.months_active_cooling + + if active_cooling_bool: + return self.efficiency_active_cooling.get_EER(primary_temperature, secondary_temperature, power) + return self.efficiency_passive_cooling.get_EER(primary_temperature, secondary_temperature, power) + + # now for monthly loads + active_cooling_eer = self.efficiency_active_cooling.get_EER(primary_temperature, secondary_temperature, power) + passive_cooling_eer = self.efficiency_passive_cooling.get_EER(primary_temperature, secondary_temperature, power) + + if month_indices is not None and isinstance(primary_temperature, (float, int)): + primary_temperature = np.full(month_indices.shape, primary_temperature) + + active_cooling_bool = self.get_time_series_active_cooling(primary_temperature, month_indices) + + # select correct data + return active_cooling_eer * active_cooling_bool + passive_cooling_eer * np.invert(active_cooling_bool) + + def get_SEER(self, + power: np.ndarray, + primary_temperature: np.ndarray, + secondary_temperature: np.ndarray = None, + month_indices: Union[float, np.ndarray] = None) -> float: + """ + This function calculates and returns the SEER. + + Parameters + ---------- + power : np.ndarray + Array with the hourly secondary power of the heat pump [kW] + primary_temperature : np.ndarray + Values for the average primary temperature of the heat pump for the EER calculation. + secondary_temperature : np.ndarray + Values for the average secondary temperature of the heat pump for the EER calculation. + month_indices : np.ndarray or float + Array with all the monthly indices, after correction for the start month. Should be the same length as the + other input parameters + Raises + ------ + ValueError + When the length of all the arrays are not equal + + Returns + ------- + SEER + float + """ + + if len(primary_temperature) != len(power) or ( + secondary_temperature is not None and len(secondary_temperature) != len(power)) \ + or (month_indices is not None and len(month_indices) != len(power)): + raise ValueError('The hourly arrays should have equal length!') + + eer_array = self.get_EER(primary_temperature, secondary_temperature, power, month_indices) + + # SEER = sum(Q)/sum(W) + w_array = np.array(power) / eer_array + + return np.sum(power) / np.sum(w_array) + + def __eq__(self, other) -> bool: + if not isinstance(other, self.__class__): + return False + + if self.efficiency_passive_cooling != other.efficiency_passive_cooling or \ + self.efficiency_active_cooling != other.efficiency_active_cooling or \ + self.threshold_temperature != other.threshold_temperature or \ + np.all(self.months_active_cooling != other.months_active_cooling): + return False + + return True diff --git a/GHEtool/VariableClasses/Efficiency/_Efficiency.py b/GHEtool/VariableClasses/Efficiency/_Efficiency.py index 60b0e7fb..ba081244 100644 --- a/GHEtool/VariableClasses/Efficiency/_Efficiency.py +++ b/GHEtool/VariableClasses/Efficiency/_Efficiency.py @@ -69,7 +69,11 @@ def __init__(self, self._nearestp = None self._has_secondary: bool = secondary self._has_part_load: bool = part_load - + self._data_: np.ndarray = data + self._coordinates_: np.ndarray = coordinates + self._reference_nominal_power: float = reference_nominal_power + self._nominal_power: float = nominal_power + self._range_primary: np.ndarray = np.array([]) self._range_secondary: np.ndarray = np.array([]) self._range_part_load: np.ndarray = np.array([]) diff --git a/GHEtool/VariableClasses/Efficiency/__init__.py b/GHEtool/VariableClasses/Efficiency/__init__.py index b1d115ca..2c34753b 100644 --- a/GHEtool/VariableClasses/Efficiency/__init__.py +++ b/GHEtool/VariableClasses/Efficiency/__init__.py @@ -2,3 +2,4 @@ from .EER import EER from .SCOP import SCOP from .SEER import SEER +from .EERCombined import EERCombined diff --git a/GHEtool/VariableClasses/GFunction.py b/GHEtool/VariableClasses/GFunction.py index 6d56020a..a83daa8b 100644 --- a/GHEtool/VariableClasses/GFunction.py +++ b/GHEtool/VariableClasses/GFunction.py @@ -9,7 +9,7 @@ from .CustomGFunction import _time_values -from GHEtool.VariableClasses.cylindrical_correction import update_pygfunction +from GHEtool.VariableClasses.Cylindrical_correction import update_pygfunction # add cylindrical correction to pygfunction update_pygfunction() @@ -91,7 +91,7 @@ class GFunction: def __init__(self): self._store_previous_values: bool = GFunction.DEFAULT_STORE_PREVIOUS_VALUES - self._store_previous_values_backup: bool= GFunction.DEFAULT_STORE_PREVIOUS_VALUES + self._store_previous_values_backup: bool = GFunction.DEFAULT_STORE_PREVIOUS_VALUES self.options: dict = {'method': 'equivalent'} self.alpha: float = 0. self.borefield: list[gt.boreholes.Borehole] = [] @@ -106,7 +106,6 @@ def __init__(self): self.fifo_list: FIFO = FIFO(8) - @property def store_previous_values(self) -> bool: """ @@ -191,7 +190,7 @@ def gvalues(time_values: np.ndarray, borefield: List[gt.boreholes.Borehole], alp # calculate the g-values for uniform borehole wall temperature gfunc_calculated = gt.gfunction.gFunction(borefield, alpha, time_values, options=self.options).gFunc - + # store the calculated g-values self.set_new_calculated_data(time_values, depth, gfunc_calculated, borefield, alpha) @@ -218,7 +217,8 @@ def gvalues(time_values: np.ndarray, borefield: List[gt.boreholes.Borehole], alp return gfunc_interpolated # calculate the g-values for uniform borehole wall temperature - gfunc_calculated = gt.gfunction.gFunction(borefield, alpha, time_values, options=self.options, method=self.options['method']).gFunc + gfunc_calculated = gt.gfunction.gFunction(borefield, alpha, time_values, options=self.options, + method=self.options['method']).gFunc if np.any(gfunc_calculated < 0): warnings.warn('There are negative g-values. This can be caused by a large borehole radius.') if self.use_cyl_correction_when_negative: @@ -230,7 +230,8 @@ def gvalues(time_values: np.ndarray, borefield: List[gt.boreholes.Borehole], alp backup = self.options.get('Cylindrical_correction') self.options["cylindrical_correction"] = True - gfunc_calculated = gt.gfunction.gFunction(borefield, alpha, time_values, options=self.options, method=self.options['method']).gFunc + gfunc_calculated = gt.gfunction.gFunction(borefield, alpha, time_values, options=self.options, + method=self.options['method']).gFunc self.options["cylindrical_correction"] = backup # store the calculated g-values diff --git a/GHEtool/VariableClasses/LoadData/Baseclasses/_HourlyDataBuilding.py b/GHEtool/VariableClasses/LoadData/Baseclasses/_HourlyDataBuilding.py index af2fb199..b96623d7 100644 --- a/GHEtool/VariableClasses/LoadData/Baseclasses/_HourlyDataBuilding.py +++ b/GHEtool/VariableClasses/LoadData/Baseclasses/_HourlyDataBuilding.py @@ -17,9 +17,10 @@ class _HourlyDataBuilding(_LoadDataBuilding, _HourlyData, ABC): def __init__(self, efficiency_heating: Union[int, float, COP, SCOP], - efficiency_cooling: Union[int, float, EER, SEER], + efficiency_cooling: Union[int, float, EER, SEER, EERCombined], dhw: Union[float, np.ndarray] = None, - efficiency_dhw: Union[int, float, COP, SCOP] = 4): + efficiency_dhw: Union[int, float, COP, SCOP] = 4, + multiyear: bool = False): """ Parameters @@ -32,8 +33,10 @@ def __init__(self, Yearly value of array with energy demand for domestic hot water (DHW) [kWh] efficiency_dhw : int, float, COP, SCOP, Efficiency in DHW + multiyear : bool + True if multiyear data """ - _LoadDataBuilding.__init__(self, efficiency_heating, efficiency_cooling, dhw, efficiency_dhw) + _LoadDataBuilding.__init__(self, efficiency_heating, efficiency_cooling, dhw, efficiency_dhw, multiyear) _HourlyData.__init__(self) # initiate variables @@ -195,7 +198,7 @@ def _get_hourly_eer(self, power: np.ndarray = None) -> Union[float, np.ndarray]: Array of EER values """ if isinstance(self.cop, SCOP) and isinstance(self.eer, SEER) and isinstance(self.cop_dhw, SCOP): - return self.eer.get_EER(0, power=np.nan_to_num(power)) + return self.eer.get_EER(0, power=np.nan_to_num(power), month_indices=self.month_indices) if isinstance(self.results, ResultsMonthly): raise TypeError('You cannot get an hourly EER values based on monthly temperature results.') if isinstance(self.results, tuple): @@ -203,7 +206,7 @@ def _get_hourly_eer(self, power: np.ndarray = None) -> Union[float, np.ndarray]: else: temperature = self.results.Tf - return self.eer.get_EER(temperature, power=np.nan_to_num(power)) + return self.eer.get_EER(temperature, power=np.nan_to_num(power), month_indices=self.month_indices) @property def hourly_injection_load_simulation_period(self) -> np.ndarray: @@ -536,8 +539,7 @@ def max_peak_injection(self) -> float: ------- max peak injection : float """ - if isinstance(self._results, ResultsMonthly) and \ - (isinstance(self.cop, COP) or isinstance(self.eer, EER) or isinstance(self.cop_dhw, COP)): + if isinstance(self._results, ResultsMonthly): return np.max(self.monthly_peak_injection_simulation_period) return np.max(self.hourly_injection_load_simulation_period) @@ -550,8 +552,7 @@ def max_peak_extraction(self) -> float: ------- max peak extraction : float """ - if isinstance(self._results, ResultsMonthly) and \ - (isinstance(self.cop, COP) or isinstance(self.eer, EER) or isinstance(self.cop_dhw, COP)): + if isinstance(self._results, ResultsMonthly): return np.max(self.monthly_peak_extraction_simulation_period) return np.max(self.hourly_extraction_load_simulation_period) @@ -565,8 +566,18 @@ def imbalance(self) -> float: ------- imbalance : float """ - if isinstance(self._results, ResultsMonthly) and \ - (isinstance(self.cop, COP) or isinstance(self.eer, EER) or isinstance(self.cop_dhw, COP)): + if isinstance(self._results, ResultsMonthly): return super(_HourlyData, self).imbalance return np.sum( self.hourly_injection_load_simulation_period - self.hourly_extraction_load_simulation_period) / self.simulation_period + + @property + def month_indices(self) -> np.ndarray: + """ + This property returns the array of all monthly indices for the simulation period. + + Returns + ------- + time array : np.ndarray + """ + return np.tile(np.repeat(np.arange(1, 13), self.UPM), self.simulation_period) diff --git a/GHEtool/VariableClasses/LoadData/Baseclasses/_LoadData.py b/GHEtool/VariableClasses/LoadData/Baseclasses/_LoadData.py index 7b60c454..9cf3daa2 100644 --- a/GHEtool/VariableClasses/LoadData/Baseclasses/_LoadData.py +++ b/GHEtool/VariableClasses/LoadData/Baseclasses/_LoadData.py @@ -661,3 +661,18 @@ def set_results(self, results) -> None: # pragma: no cover def reset_results(self, min_temperature: float, max_temperature: float) -> None: # pragma: no cover pass + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + if not np.all( + self.monthly_peak_injection_simulation_period == other.monthly_peak_injection_simulation_period) or \ + not np.all( + self.monthly_peak_extraction_simulation_period == other.monthly_peak_extraction_simulation_period) or \ + not np.all( + self.monthly_baseload_extraction_simulation_period == other.monthly_baseload_extraction_simulation_period) or \ + not np.all( + self.monthly_baseload_injection_simulation_period == other.monthly_baseload_injection_simulation_period): + return False + + return True diff --git a/GHEtool/VariableClasses/LoadData/Baseclasses/_LoadDataBuilding.py b/GHEtool/VariableClasses/LoadData/Baseclasses/_LoadDataBuilding.py index 010f9a1f..198a7207 100644 --- a/GHEtool/VariableClasses/LoadData/Baseclasses/_LoadDataBuilding.py +++ b/GHEtool/VariableClasses/LoadData/Baseclasses/_LoadDataBuilding.py @@ -17,9 +17,10 @@ class _LoadDataBuilding(_LoadData, ABC): def __init__(self, efficiency_heating: Union[int, float, COP, SCOP], - efficiency_cooling: Union[int, float, EER, SEER], + efficiency_cooling: Union[int, float, EER, SEER, EERCombined], dhw: Union[float, np.ndarray] = None, - efficiency_dhw: Union[int, float, COP, SCOP] = 4): + efficiency_dhw: Union[int, float, COP, SCOP] = 4, + multiyear: bool = False): """ Parameters @@ -32,8 +33,11 @@ def __init__(self, Yearly value of array with energy demand for domestic hot water (DHW) [kWh] efficiency_dhw : int, float, COP, SCOP, Efficiency in DHW + multiyear : bool + True if multiyear data """ super().__init__() + self._multiyear = multiyear # initiate variables self._baseload_heating: np.ndarray = np.zeros(12) @@ -62,8 +66,7 @@ def monthly_baseload_heating_simulation_period(self) -> np.ndarray: This function returns the monthly heating baseload in kWh/month for the whole simulation period. Returns - ------- - baseload heating : np.ndarray + ------- baseload heating : np.ndarray Baseload heating for the whole simulation period """ @@ -244,7 +247,7 @@ def eer(self): return self._eer @eer.setter - def eer(self, efficiency_cooling: Union[int, float, EER, SEER]) -> None: + def eer(self, efficiency_cooling: Union[int, float, EER, SEER, EERCombined]) -> None: """ This function defines the efficiency in cooling. Integer and float values will be automatically converted to an SEER object. @@ -457,7 +460,7 @@ def _get_monthly_eer(self, peak: bool, power: np.ndarray = None) -> Union[float, else: temperature = self.results.monthly_injection - return self.eer.get_EER(temperature, power=np.nan_to_num(power)) + return self.eer.get_EER(temperature, power=np.nan_to_num(power), month_indices=self.month_indices) @staticmethod def conversion_factor_secondary_to_primary_heating(cop_value: Union[int, float, np.ndarray]) -> Union[ @@ -1010,3 +1013,14 @@ def simulation_period(self) -> int: simulation period : int """ return int(len(self.monthly_baseload_cooling_simulation_period) / 12) + + @property + def month_indices(self) -> np.ndarray: + """ + This property returns the array of all month indices for the simulation period. + + Returns + ------- + time array : np.ndarray + """ + return np.tile(np.arange(1, 13), self.simulation_period) diff --git a/GHEtool/VariableClasses/LoadData/BuildingLoad/HourlyBuildingLoad.py b/GHEtool/VariableClasses/LoadData/BuildingLoad/HourlyBuildingLoad.py index 6aec4066..15166695 100644 --- a/GHEtool/VariableClasses/LoadData/BuildingLoad/HourlyBuildingLoad.py +++ b/GHEtool/VariableClasses/LoadData/BuildingLoad/HourlyBuildingLoad.py @@ -24,7 +24,7 @@ def __init__(self, heating_load: ArrayLike = None, cooling_load: ArrayLike = None, simulation_period: int = 20, efficiency_heating: Union[int, float, COP, SCOP] = 5, - efficiency_cooling: Union[int, float, EER, SEER] = 20, + efficiency_cooling: Union[int, float, EER, SEER, EERCombined] = 20, dhw: Union[float, np.ndarray] = None, efficiency_dhw: Union[int, float, COP, SCOP] = 4): """ @@ -234,7 +234,7 @@ def correct_for_start_month(self, array: np.ndarray) -> np.ndarray: """ This function corrects the load for the correct start month. If the simulation starts in september, the start month is 9 and hence the array should start - at index 9. + at index of the first hour of month 9. Parameters ---------- @@ -249,6 +249,17 @@ def correct_for_start_month(self, array: np.ndarray) -> np.ndarray: return array return np.concatenate((array[self._start_hour:], array[: self._start_hour])) + @property + def month_indices(self) -> np.ndarray: + """ + This property returns the array of all monthly indices for the simulation period. + + Returns + ------- + time array : np.ndarray + """ + return np.tile(self.correct_for_start_month(np.repeat(np.arange(1, 13), self.UPM)), self.simulation_period) + def plot_load_duration(self, legend: bool = False) -> Tuple[plt.Figure, plt.Axes]: """ This function makes a load-duration curve from the hourly values. diff --git a/GHEtool/VariableClasses/LoadData/BuildingLoad/HourlyBuildingLoadMultiYear.py b/GHEtool/VariableClasses/LoadData/BuildingLoad/HourlyBuildingLoadMultiYear.py index 649b5a44..01ac2479 100644 --- a/GHEtool/VariableClasses/LoadData/BuildingLoad/HourlyBuildingLoadMultiYear.py +++ b/GHEtool/VariableClasses/LoadData/BuildingLoad/HourlyBuildingLoadMultiYear.py @@ -16,7 +16,7 @@ class HourlyBuildingLoadMultiYear(_HourlyDataBuilding): def __init__(self, heating_load: ArrayLike = None, cooling_load: ArrayLike = None, efficiency_heating: Union[int, float, COP, SCOP] = 5, - efficiency_cooling: Union[int, float, EER, SEER] = 20, + efficiency_cooling: Union[int, float, EER, SEER, EERCombined] = 20, dhw: Union[float, np.ndarray] = None, efficiency_dhw: Union[int, float, COP, SCOP] = 4): """ @@ -37,9 +37,8 @@ def __init__(self, heating_load: ArrayLike = None, Efficiency in DHW """ - _HourlyDataBuilding.__init__(self, efficiency_heating, efficiency_cooling, dhw, efficiency_dhw) + _HourlyDataBuilding.__init__(self, efficiency_heating, efficiency_cooling, dhw, efficiency_dhw, True) self._multiyear = True - self._hourly = True # set variables heating_load = np.zeros(8760) if heating_load is None and cooling_load is None else heating_load diff --git a/GHEtool/VariableClasses/LoadData/BuildingLoad/MonthlyBuildingLoadAbsolute.py b/GHEtool/VariableClasses/LoadData/BuildingLoad/MonthlyBuildingLoadAbsolute.py index d9483072..988d8c92 100644 --- a/GHEtool/VariableClasses/LoadData/BuildingLoad/MonthlyBuildingLoadAbsolute.py +++ b/GHEtool/VariableClasses/LoadData/BuildingLoad/MonthlyBuildingLoadAbsolute.py @@ -24,7 +24,7 @@ def __init__( peak_cooling: ArrayLike = None, simulation_period: int = 20, efficiency_heating: Union[int, float, COP, SCOP] = 5, - efficiency_cooling: Union[int, float, EER, SEER] = 20, + efficiency_cooling: Union[int, float, EER, SEER, EERCombined] = 20, dhw: Union[float, np.ndarray] = None, efficiency_dhw: Union[int, float, COP, SCOP] = 4 ): @@ -396,6 +396,17 @@ def correct_for_start_month(self, array: np.ndarray) -> np.ndarray: return array return np.concatenate((array[self.start_month - 1:], array[: self.start_month - 1])) + @property + def month_indices(self) -> np.ndarray: + """ + This property returns the array of all monthly indices for the simulation period. + + Returns + ------- + time array : np.ndarray + """ + return np.tile(self.correct_for_start_month(np.arange(1, 13)), self.simulation_period) + def set_results(self, results: ResultsMonthly) -> None: """ This function sets the temperature results. diff --git a/GHEtool/VariableClasses/LoadData/BuildingLoad/MonthlyBuildingLoadMultiYear.py b/GHEtool/VariableClasses/LoadData/BuildingLoad/MonthlyBuildingLoadMultiYear.py index 348bdd64..6e16cb21 100644 --- a/GHEtool/VariableClasses/LoadData/BuildingLoad/MonthlyBuildingLoadMultiYear.py +++ b/GHEtool/VariableClasses/LoadData/BuildingLoad/MonthlyBuildingLoadMultiYear.py @@ -23,7 +23,7 @@ def __init__( peak_heating: ArrayLike = None, peak_cooling: ArrayLike = None, efficiency_heating: Union[int, float, COP, SCOP] = 5, - efficiency_cooling: Union[int, float, EER, SEER] = 20, + efficiency_cooling: Union[int, float, EER, SEER, EERCombined] = 20, dhw: Union[float, np.ndarray] = None, efficiency_dhw: Union[int, float, COP, SCOP] = 4 ): @@ -49,7 +49,7 @@ def __init__( Efficiency in DHW """ - _LoadDataBuilding.__init__(self, efficiency_heating, efficiency_cooling, dhw, efficiency_dhw) + _LoadDataBuilding.__init__(self, efficiency_heating, efficiency_cooling, dhw, efficiency_dhw, True) self._multiyear = True # initiate variables diff --git a/GHEtool/VariableClasses/LoadData/Cluster.py b/GHEtool/VariableClasses/LoadData/Cluster.py new file mode 100644 index 00000000..843805c1 --- /dev/null +++ b/GHEtool/VariableClasses/LoadData/Cluster.py @@ -0,0 +1,75 @@ +import numpy as np + +from GHEtool.VariableClasses.LoadData.Baseclasses._LoadData import _LoadData + + +class Cluster: + + def __init__(self, buildings: list[_LoadData] = []): + """ + Parameters + ---------- + buildings : list of _LoadData + + Returns + ------- + None + """ + self.__dict__['list_of_buildings'] = buildings # Avoid recursion with __setattr__ + + def add_building(self, building: _LoadData): + """ + This function adds a building to the cluster. + + Parameters + ---------- + building : _LoadData + + Returns + ------- + None + """ + self.list_of_buildings.append(building) + + def __getattr__(self, attr_name): + """ + Intercepts any method call that doesn't exist in Cluster and forwards it to all buildings. + + Parameters + ---------- + attr_name : str + The name of the method to be applied to each building. + + Returns + ------- + function + A function that applies the method to all buildings and sums the result. + """ + + if callable(getattr(self.list_of_buildings[0], attr_name, None)): + def method_applier(*args, **kwargs): + results = [] + for building in self.list_of_buildings: + method = getattr(building, attr_name) + result = method(*args, **kwargs) + if result is not None: # pragma: no cover + results.append(result) + return np.sum(results, axis=0) + + return method_applier + else: + return np.sum([getattr(building, attr_name) for building in self.list_of_buildings], axis=0) + + def __setattr__(self, attr_name, value): + """ + Sets an attribute on all buildings in the cluster. + + Parameters + ---------- + attr_name : str + The name of the attribute to be set. + value : + The value to set for the attribute. + """ + for building in self.list_of_buildings: + setattr(building, attr_name, value) diff --git a/GHEtool/VariableClasses/LoadData/__init__.py b/GHEtool/VariableClasses/LoadData/__init__.py index e6ca97ac..3f7aa610 100644 --- a/GHEtool/VariableClasses/LoadData/__init__.py +++ b/GHEtool/VariableClasses/LoadData/__init__.py @@ -1,3 +1,4 @@ from .GeothermalLoad import * from .BuildingLoad import * from .Baseclasses import _LoadData, _LoadDataBuilding +from .Cluster import Cluster diff --git a/GHEtool/test/general_tests/test_GHEtool.py b/GHEtool/test/general_tests/test_GHEtool.py index 863077d5..f1c6f166 100644 --- a/GHEtool/test/general_tests/test_GHEtool.py +++ b/GHEtool/test/general_tests/test_GHEtool.py @@ -7,6 +7,8 @@ import pandas as pd import pygfunction as gt import pytest +import pickle + from pytest import raises from GHEtool import GroundConstantTemperature, GroundFluxTemperature, FluidData, Borefield, CalculationSetup, FOLDER, \ @@ -300,3 +302,30 @@ def test_size_with_different_peak_lengths(borefield): borefield.load.peak_injection_duration = 8 borefield.load.peak_extraction_duration = 6 assert np.isclose(99.33058400216774, borefield.size(L3_sizing=True)) + + +def test_convergence_eer_combined(): + borefield1: Borefield = pickle.load(open(FOLDER.joinpath("test/general_tests/test_optimise.pkl"), 'rb')) + borefield1.set_max_avg_fluid_temperature(16) + borefield1.calculate_temperatures(hourly=True) + results_16 = copy.deepcopy(borefield1.results) + borefield1.set_max_avg_fluid_temperature(25) + borefield1.calculate_temperatures(hourly=True) + results_25 = copy.deepcopy(borefield1.results) + assert np.allclose(results_16.peak_injection, results_25.peak_injection) + + +def test_optimise_load_eer_combined(): + borefield1: Borefield = pickle.load(open(FOLDER.joinpath("test/general_tests/test_optimise.pkl"), 'rb')) + borefield1.set_max_avg_fluid_temperature(16) + borefield1.calculate_temperatures(hourly=True) + results_16 = copy.deepcopy(borefield1.results) + borefield1.set_max_avg_fluid_temperature(25) + _, sec_load = borefield1.optimise_load_profile_power(borefield1.load) + borefield1.calculate_temperatures(hourly=True) + results_25 = copy.deepcopy(borefield1.results) + assert np.allclose(results_16.peak_injection, results_25.peak_injection) + _, sec_load = borefield1.optimise_load_profile_energy(borefield1.load) + borefield1.calculate_temperatures(hourly=True) + results_25 = copy.deepcopy(borefield1.results) + assert np.allclose(results_16.peak_injection, results_25.peak_injection) diff --git a/GHEtool/test/general_tests/test_optimise.pkl b/GHEtool/test/general_tests/test_optimise.pkl new file mode 100644 index 00000000..48d4e551 Binary files /dev/null and b/GHEtool/test/general_tests/test_optimise.pkl differ diff --git a/GHEtool/test/methods/method_data.py b/GHEtool/test/methods/method_data.py index 7c8aa041..d52ff5ce 100644 --- a/GHEtool/test/methods/method_data.py +++ b/GHEtool/test/methods/method_data.py @@ -560,3 +560,28 @@ borefield.load = hourly_load_building list_of_test_objects.add(SizingObject(borefield, L2_output=310.725, L3_output=310.725, L4_output=308.269, quadrant=4, name='BS2023 Swimming pool')) + +eer_combined = EERCombined(20, 5, 10) +borefield = Borefield() +borefield.create_rectangular_borefield(3, 6, 6, 6, 146, 4) +borefield.set_min_avg_fluid_temperature(3) +borefield.set_max_avg_fluid_temperature(16) +borefield.load.peak_duration = 6 +load = HourlyBuildingLoad(efficiency_heating=4, efficiency_cooling=eer_combined) +# column order is inverted +load.load_hourly_profile(FOLDER.joinpath("test\methods\hourly_data\hourly_profile.csv"), col_heating=1, col_cooling=0) +load.simulation_period = 40 +borefield.load = load + +borefield.set_ground_parameters(GroundTemperatureGradient(1.9, 10, gradient=2)) +borefield.set_fluid_parameters(FluidData(0.1, 0.475, 1033, 3930, 0.001)) +borefield.set_pipe_parameters(SingleUTube(1.5, 0.016, 0.02, 0.42, 0.04)) +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, load, 146, 45.978137699335, 11.029080983424729, + 52.82586122830533, 28.00877268650213, 605.9817888622596, + 512.6954920945816, + name='Optimise load profile (eer combined) (power)', power=True, + hourly=False)) +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, load, 146, 38.165390819460434, 4.08828744800462, + 75.9124206410309, 86.06503815919983, 631.1307699700886, 535.936136, + name='Optimise load profile (eer combined) (energy)', power=False, + hourly=False)) diff --git a/GHEtool/test/test_examples.py b/GHEtool/test/test_examples.py index dc90d55d..24697711 100644 --- a/GHEtool/test/test_examples.py +++ b/GHEtool/test/test_examples.py @@ -55,17 +55,23 @@ def test_sizing_with_building_load(monkeypatch): size_with_variable_ground_temperature, \ size_with_part_load_data assert np.allclose(size_with_scop(), (96.5589765783911, 4.072466974615784)) - assert np.allclose(size_with_variable_ground_temperature(), (95.64066844079264, 4.17665670561309)) - assert np.allclose(size_with_part_load_data(), (98.1273127062551, 4.685121612513776)) + assert np.allclose(size_with_variable_ground_temperature(), (95.63725412552411, 4.134065429053346)) + assert np.allclose(size_with_part_load_data(), (98.12508584747407, 4.625118577257844)) def test_sizing_with_building_load_hourly(monkeypatch): monkeypatch.setattr(plt, 'show', lambda: None) from GHEtool.Examples.sizing_with_building_load_hourly import L3_sizing, L4_sizing - assert np.allclose(L3_sizing(), (127.05154931011464, 6.131588043404349)) - assert np.allclose(L4_sizing(), (153.26361812264668, 6.237959315069309)) + assert np.allclose(L3_sizing(), (128.48637749558208, 6.288025315353533)) + assert np.allclose(L4_sizing(), (154.34184329022935, 6.385782647864632)) +def test_combined_active_and_passive_cooling(monkeypatch): + monkeypatch.setattr(plt, 'show', lambda: None) + from GHEtool.Examples.combined_active_and_passive_cooling import active_above_threshold, default_cooling_in_summer + assert np.isclose(active_above_threshold(), 7483.480000000002) + assert np.isclose(default_cooling_in_summer(), 67683.27999999997) + def test_separatus(monkeypatch): monkeypatch.setattr(plt, 'show', lambda: None) from GHEtool.Examples.separatus import design_with_single_U, design_with_double_U, design_with_separatus diff --git a/GHEtool/test/unit-tests/test_efficiency.py b/GHEtool/test/unit-tests/test_efficiency.py index 54b7a039..eb665d55 100644 --- a/GHEtool/test/unit-tests/test_efficiency.py +++ b/GHEtool/test/unit-tests/test_efficiency.py @@ -305,3 +305,60 @@ def test_interpolation(): part_load=True, secondary=True) assert np.array_equal(cop._range_part_load, np.array([1, 2, 3])) assert np.array_equal(cop._data, np.array([[[1, 1.5, 2], [1, 1.5, 2]], [[2, 3, 3], [2, 3, 3]]])) + + +def test_EERCombined(): + with pytest.raises(ValueError): + EERCombined(20, 5) + + # with threshold + eer = EERCombined(20, 5, 10) + assert eer.get_EER(1, 0, 0, 0) == 20 + assert eer.get_EER(10, 0, 0, 0) == 20 + assert eer.get_EER(20, 0, 0, 0) == 5 + assert np.allclose(eer.get_EER(np.array([1, 10, 20])), np.array([20, 20, 5])) + assert np.allclose(eer.get_time_series_active_cooling(np.array([1, 10, 20]), month_indices=np.array([5, 6, 7])), + np.array([False, False, True])) + # with month array + eer = EERCombined(20, 5, months_active_cooling=np.array([7, 8, 9])) + with pytest.raises(ValueError): + eer.get_EER(1) + with pytest.raises(ValueError): + eer.get_EER(np.array([1, 10, 20])) + assert eer.get_EER(1, 0, 0, month_indices=1) == 20 + assert eer.get_EER(15, 0, 0, month_indices=1) == 20 + assert eer.get_EER(20, 0, 0, month_indices=7) == 5 + assert np.allclose(eer.get_EER(np.array([1, 15, 20]), month_indices=np.array([5, 6, 7])), np.array([20, 20, 5])) + assert np.allclose(eer.get_time_series_active_cooling(np.array([1, 10, 20]), month_indices=np.array([5, 6, 7])), + np.array([False, False, True])) + # with threshold and month array + eer = EERCombined(20, 5, 10, months_active_cooling=np.array([7, 8, 9])) + with pytest.raises(ValueError): + eer.get_EER(1) + with pytest.raises(ValueError): + eer.get_EER(np.array([1, 10, 20])) + assert eer.get_EER(1, 0, 0, month_indices=1) == 20 + assert eer.get_EER(15, 0, 0, month_indices=1) == 5 + assert eer.get_EER(20, 0, 0, month_indices=7) == 5 + assert np.allclose(eer.get_EER(np.array([1, 15, 20]), month_indices=np.array([5, 6, 7])), np.array([20, 5, 5])) + assert np.allclose(eer.get_EER(1, month_indices=np.array([5, 6, 7])), np.array([20, 20, 5])) + + assert np.allclose(eer.get_time_series_active_cooling(np.array([1, 15, 20]), month_indices=np.array([5, 6, 7])), + np.array([False, True, True])) + + with pytest.raises(ValueError): + eer.get_SEER(np.array([10, 10, 10]), np.array([1, 15, 20]), month_indices=np.array([6, 7])) + assert np.isclose(eer.get_SEER(np.array([10, 10, 10]), np.array([1, 15, 20]), month_indices=np.array([5, 6, 7])), + 30 / 4.5) + + +def test_eq_eer_combined(): + eer_combined = EERCombined(20, 5, 10) + eer_combined2 = EERCombined(20, 50, 10) + eer_combined3 = EERCombined(20, 50, 10) + + seer = SEER(20) + + assert eer_combined != seer + assert eer_combined2 != eer_combined + assert eer_combined2 == eer_combined3 diff --git a/GHEtool/test/unit-tests/test_loads/test_hourly_load_building.py b/GHEtool/test/unit-tests/test_loads/test_hourly_load_building.py index 3a3cfa8b..0ebbd149 100644 --- a/GHEtool/test/unit-tests/test_loads/test_hourly_load_building.py +++ b/GHEtool/test/unit-tests/test_loads/test_hourly_load_building.py @@ -4,7 +4,7 @@ import numpy as np from GHEtool import FOLDER -from GHEtool.VariableClasses import HourlyBuildingLoad +from GHEtool.VariableClasses import HourlyBuildingLoad, Cluster from GHEtool.VariableClasses.Result import ResultsMonthly, ResultsHourly from GHEtool.VariableClasses.Efficiency import * @@ -561,6 +561,7 @@ def test_hourly_injection_load_simulation_period_monthly_data(): assert np.allclose(load.monthly_peak_injection_simulation_period, load.resample_to_monthly(np.tile(np.concatenate((np.full(4380, 7.5), np.full(4380, 11))), 10))[0]) + assert np.isclose(load.max_peak_injection, 11) load.eer = eer_pl assert np.allclose(load.monthly_baseload_injection_simulation_period, load.resample_to_monthly( @@ -595,6 +596,9 @@ def test_hourly_extraction_load_simulation_period_monthly_data(): np.tile(np.concatenate((np.full(4380, 2.5), np.full(4380, 9))), 10))[0]) assert np.allclose(load._monthly_baseload_extraction_dhw_simulation_period, np.zeros(120)) assert np.allclose(load._monthly_peak_extraction_dhw_simulation_period, np.zeros(120)) + assert np.isclose(load.max_peak_extraction, 9) + assert np.isclose(load.imbalance, -50370.0) + load.cop = cop_pl assert np.allclose(load.monthly_baseload_extraction_simulation_period, load.resample_to_monthly( @@ -632,3 +636,42 @@ def test_hourly_extraction_load_simulation_period_monthly_data(): np.tile(np.concatenate((np.full(4380, 3.75), np.full(4380, 9.5))), 10))[0]) assert np.allclose(load._monthly_baseload_extraction_heating_simulation_period, np.zeros(120)) assert np.allclose(load._monthly_peak_extraction_heating_simulation_period, np.zeros(120)) + + +def test_time_array(): + load = HourlyBuildingLoad(np.zeros(8760), np.zeros(8760), 10, scop, seer) + assert np.allclose(load.month_indices, np.tile(np.repeat(np.arange(1, 13), load.UPM), 10)) + + load.start_month = 2 + assert np.allclose(load.month_indices, np.tile(np.concatenate(( + np.repeat(np.arange(1, 13), load.UPM)[730:], np.repeat(np.arange(1, 13), load.UPM)[:730])), 10)) + + +def test_cluster(): + load1 = HourlyBuildingLoad(np.linspace(1, 2000, 8760), np.linspace(1, 8760 - 1, 8760) * 2, 10, cop_basic, eer_basic) + load2 = HourlyBuildingLoad(np.linspace(1, 2000, 8760), np.linspace(1, 8760 - 1, 8760) * 2, 10, cop_basic, eer_basic) + load = HourlyBuildingLoad(np.linspace(1, 2000, 8760) * 2, np.linspace(1, 8760 - 1, 8760) * 2 * 2, 10, cop_basic, + eer_basic) + + cluster = Cluster([load1, load2]) + + assert np.allclose(load.monthly_baseload_extraction, + cluster.monthly_baseload_extraction) + assert np.allclose(load.monthly_baseload_injection, + cluster.monthly_baseload_injection) + assert np.allclose(load.monthly_peak_extraction, + cluster.monthly_peak_extraction) + assert np.allclose(load.monthly_peak_injection, + cluster.monthly_peak_injection) + assert np.allclose(load.hourly_extraction_load, cluster.hourly_extraction_load) + assert np.allclose(load.hourly_injection_load, cluster.hourly_injection_load) + + load.reset_results(0, 10) + cluster.reset_results(0, 10) + assert np.allclose(load.hourly_extraction_load, cluster.hourly_extraction_load) + assert np.allclose(load.hourly_injection_load, cluster.hourly_injection_load) + + load.set_results(results_hourly_test) + cluster.set_results(results_hourly_test) + assert np.allclose(load.hourly_extraction_load, cluster.hourly_extraction_load) + assert np.allclose(load.hourly_injection_load, cluster.hourly_injection_load) diff --git a/GHEtool/test/unit-tests/test_loads/test_hourly_load_building_multi_year.py b/GHEtool/test/unit-tests/test_loads/test_hourly_load_building_multi_year.py index 038b81fb..34ce7c6d 100644 --- a/GHEtool/test/unit-tests/test_loads/test_hourly_load_building_multi_year.py +++ b/GHEtool/test/unit-tests/test_loads/test_hourly_load_building_multi_year.py @@ -96,6 +96,7 @@ def test_yearly_loads_multiyear(): def test_dhw(): + HourlyBuildingLoadMultiYear(np.zeros(8760 * 10), np.ones(8760 * 10), 5, 20, np.ones(8760 * 10), 3) load = HourlyBuildingLoadMultiYear(np.zeros(8760 * 10), np.linspace(1, 8760 * 10 - 1, 8760 * 10) * 2, 6, 5) assert load.dhw == 0 @@ -145,3 +146,8 @@ def test_dhw(): load.exclude_DHW_from_peak = True # idem since we started with an hourly data resolution assert np.allclose(load.monthly_peak_extraction_simulation_period, np.zeros(120)) + + +def test_time_array(): + load = HourlyBuildingLoadMultiYear(np.zeros(8760 * 10), np.linspace(1, 8760 * 10 - 1, 8760 * 10) * 2, 6, 5) + assert np.allclose(load.month_indices, np.tile(np.repeat(np.arange(1, 13), load.UPM), 10)) diff --git a/GHEtool/test/unit-tests/test_loads/test_monthly_load_building.py b/GHEtool/test/unit-tests/test_loads/test_monthly_load_building.py index 7a79b1b9..17f8c15a 100644 --- a/GHEtool/test/unit-tests/test_loads/test_monthly_load_building.py +++ b/GHEtool/test/unit-tests/test_loads/test_monthly_load_building.py @@ -626,3 +626,10 @@ def test_electricity_consumption(): assert np.allclose(load.yearly_electricity_consumption_heating, np.tile(300000 / 5, 10)) assert np.allclose(load.yearly_electricity_consumption_dhw, np.tile(10000 / 4, 10)) assert np.allclose(load.yearly_electricity_consumption, np.tile(10000 / 4 + 300000 / 5 + 150000 / 20, 10)) + + +def test_time_array(): + load = MonthlyBuildingLoadAbsolute(*load_case(1), 10) + assert np.allclose(load.month_indices, np.tile(np.arange(1, 13), 10)) + load.start_month = 2 + assert np.allclose(load.month_indices, np.tile(np.concatenate((np.arange(2, 13), [1])), 10)) diff --git a/GHEtool/test/unit-tests/test_loads/test_monthly_load_building_multi_year.py b/GHEtool/test/unit-tests/test_loads/test_monthly_load_building_multi_year.py index b8441b97..083442a8 100644 --- a/GHEtool/test/unit-tests/test_loads/test_monthly_load_building_multi_year.py +++ b/GHEtool/test/unit-tests/test_loads/test_monthly_load_building_multi_year.py @@ -160,6 +160,13 @@ def test_set_results(): def test_dhw(): + MonthlyBuildingLoadMultiYear( + baseload_heating=np.tile(baseload_heating, 10), + baseload_cooling=np.tile(baseload_cooling, 10), + peak_heating=np.tile(peak_heating, 10), + peak_cooling=np.tile(peak_cooling, 10), + dhw=np.ones(120)) + load = MonthlyBuildingLoadMultiYear( baseload_heating=np.tile(baseload_heating, 10), baseload_cooling=np.tile(baseload_cooling, 10), @@ -217,3 +224,12 @@ def test_dhw(): assert load.max_peak_dhw == 12 load.exclude_DHW_from_peak = True assert np.allclose(load.monthly_peak_extraction_simulation_period, np.zeros(120)) + + +def test_time_array(): + load = MonthlyBuildingLoadMultiYear( + baseload_heating=np.tile(baseload_heating, 10), + baseload_cooling=np.tile(baseload_cooling, 10), + peak_heating=np.tile(peak_heating, 10), + peak_cooling=np.tile(peak_cooling, 10)) + assert np.allclose(load.month_indices, np.tile(np.arange(1, 13), 10)) diff --git a/GHEtool/test/unit-tests/test_loads/test_monthly_load_geothermal.py b/GHEtool/test/unit-tests/test_loads/test_monthly_load_geothermal.py index 91a60077..2552a5ce 100644 --- a/GHEtool/test/unit-tests/test_loads/test_monthly_load_geothermal.py +++ b/GHEtool/test/unit-tests/test_loads/test_monthly_load_geothermal.py @@ -2,7 +2,7 @@ import numpy as np -from GHEtool.VariableClasses import MonthlyGeothermalLoadAbsolute, HourlyGeothermalLoad, MonthlyGeothermalLoadMultiYear +from GHEtool.VariableClasses import MonthlyGeothermalLoadAbsolute, HourlyGeothermalLoad, Cluster from GHEtool.Validation.cases import load_case @@ -343,7 +343,7 @@ def test_different_start_month(): assert np.allclose(load.monthly_baseload_injection_simulation_period, np.tile(result, 20)) assert np.allclose(load.monthly_peak_extraction_simulation_period, np.tile(result, 20)) assert np.allclose(load.monthly_peak_injection_simulation_period, np.tile(result, 20)) - + load.peak_injection = np.zeros(12) load.peak_extraction = np.zeros(12) assert np.allclose(load.monthly_peak_extraction_simulation_period, np.tile(result, 20) / 730) @@ -362,3 +362,30 @@ def test_yearly_loads(): def test_depreciation_warning(): with pytest.raises(DeprecationWarning): MonthlyGeothermalLoadAbsolute(baseload_heating=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + + +def test_cluster(): + load1 = MonthlyGeothermalLoadAbsolute(*load_case(1)) + load2 = MonthlyGeothermalLoadAbsolute(*load_case(2)) + cluster = Cluster([load1, load2]) + cluster2 = Cluster() + cluster2.add_building(load1) + cluster2.add_building(load2) + assert np.allclose(load1.monthly_baseload_extraction + load2.monthly_baseload_extraction, + cluster.monthly_baseload_extraction) + assert np.allclose(load1.monthly_baseload_injection + load2.monthly_baseload_injection, + cluster.monthly_baseload_injection) + assert np.allclose(load1.monthly_peak_extraction + load2.monthly_peak_extraction, + cluster.monthly_peak_extraction) + assert np.allclose(load1.monthly_peak_injection + load2.monthly_peak_injection, + cluster.monthly_peak_injection) + assert np.allclose(load1.monthly_baseload_extraction + load2.monthly_baseload_extraction, + cluster2.monthly_baseload_extraction) + assert np.allclose(load1.monthly_baseload_injection + load2.monthly_baseload_injection, + cluster2.monthly_baseload_injection) + assert np.allclose(load1.monthly_peak_extraction + load2.monthly_peak_extraction, + cluster2.monthly_peak_extraction) + assert np.allclose(load1.monthly_peak_injection + load2.monthly_peak_injection, + cluster2.monthly_peak_injection) + cluster.simulation_period = 21 + assert cluster.list_of_buildings[0].simulation_period == 21 diff --git a/GHEtool/test/unit-tests/test_loads/test_monthly_load_geothermal_multi_year.py b/GHEtool/test/unit-tests/test_loads/test_monthly_load_geothermal_multi_year.py index 50f4e3f9..e4726440 100644 --- a/GHEtool/test/unit-tests/test_loads/test_monthly_load_geothermal_multi_year.py +++ b/GHEtool/test/unit-tests/test_loads/test_monthly_load_geothermal_multi_year.py @@ -1,7 +1,7 @@ import pytest import numpy as np -from GHEtool import MonthlyGeothermalLoadMultiYear +from GHEtool import MonthlyGeothermalLoadMultiYear, MonthlyBuildingLoadMultiYear # Initialize test data baseload_extraction = np.array([1000] * 12) # 1000 kWh/month for each month @@ -145,3 +145,35 @@ def test_yearly_loads(): def test_depreciation_warning(): with pytest.raises(DeprecationWarning): MonthlyGeothermalLoadMultiYear(baseload_heating=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + + +def test_eq(): + load = MonthlyGeothermalLoadMultiYear( + baseload_extraction=baseload_extraction, + baseload_injection=baseload_injection, + peak_extraction=peak_extraction, + peak_injection=peak_injection + ) + load2 = MonthlyGeothermalLoadMultiYear( + baseload_extraction=baseload_extraction, + baseload_injection=baseload_injection, + peak_extraction=peak_extraction * 2, + peak_injection=peak_injection + ) + load3 = MonthlyGeothermalLoadMultiYear( + baseload_extraction=baseload_extraction, + baseload_injection=baseload_injection, + peak_extraction=peak_extraction, + peak_injection=peak_injection + ) + load4 = MonthlyBuildingLoadMultiYear( + baseload_heating=baseload_extraction, + baseload_cooling=baseload_injection, + peak_heating=peak_extraction, + peak_cooling=peak_injection + ) + + assert load == load3 + assert load != load2 + assert load2 != load3 + assert load2 != load4 diff --git a/GHEtool/test/unit-tests/test_main_class.py b/GHEtool/test/unit-tests/test_main_class.py index a407350e..bb3d2275 100644 --- a/GHEtool/test/unit-tests/test_main_class.py +++ b/GHEtool/test/unit-tests/test_main_class.py @@ -7,10 +7,11 @@ import pytest from GHEtool import GroundConstantTemperature, GroundFluxTemperature, FluidData, DoubleUTube, Borefield, \ - CalculationSetup, FOLDER, MultipleUTube + CalculationSetup, FOLDER, MultipleUTube, EERCombined from GHEtool.logger import ghe_logger from GHEtool.Validation.cases import load_case -from GHEtool.VariableClasses.LoadData import MonthlyGeothermalLoadAbsolute, HourlyGeothermalLoad, HourlyBuildingLoad +from GHEtool.VariableClasses.LoadData import MonthlyGeothermalLoadAbsolute, HourlyGeothermalLoad, HourlyBuildingLoad, \ + HourlyBuildingLoadMultiYear from GHEtool.VariableClasses.BaseClass import UnsolvableDueToTemperatureGradient data = GroundConstantTemperature(3, 10) @@ -520,6 +521,26 @@ def test_size_L4(): assert borefield.calculate_quadrant() == 4 +def test_calculate_temperatures_eer_combined(): + eer_combined = EERCombined(20, 5, 17) + borefield = Borefield() + borefield.set_ground_parameters(ground_data_constant) + load = HourlyBuildingLoad(efficiency_cooling=eer_combined) + + borefield.borefield = copy.deepcopy(borefield_gt) + load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) + borefield.load = load + borefield.calculate_temperatures(hourly=True) + + active_cooling_array = borefield.load.eer.get_time_series_active_cooling(borefield.results.peak_injection, + borefield.load.month_indices) + assert np.allclose(borefield.load.hourly_cooling_load_simulation_period * active_cooling_array * + (1 + 1 / 5) + borefield.load.hourly_cooling_load_simulation_period * np.invert( + active_cooling_array) * + (1 + 1 / 20), + borefield.load.hourly_injection_load_simulation_period) + + def test_investment_cost(): borefield = Borefield() borefield.borefield = copy.deepcopy(borefield_gt) @@ -859,6 +880,23 @@ def test_optimise_load_profile_power(monkeypatch): assert len(borefield.results.peak_extraction) == 0 +def test_optimise_load_profile_power_multiyear(monkeypatch): + # multiyear should also have a multiyear as output + borefield = Borefield() + monkeypatch.setattr(plt, "show", lambda: None) + borefield.set_ground_parameters(ground_data_constant) + borefield.borefield = copy.deepcopy(borefield_gt) + load = HourlyBuildingLoad(efficiency_heating=10 ** 6, efficiency_cooling=10 * 66) + load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) + load_my = HourlyBuildingLoadMultiYear(load.hourly_heating_load_simulation_period, + load.hourly_cooling_load_simulation_period) + secundary_borefield_load, external_load = borefield.optimise_load_profile_power(load_my, 150) + assert borefield.load.simulation_period == 20 + assert secundary_borefield_load.simulation_period == 20 + assert external_load.simulation_period == 20 + assert len(borefield.results.peak_extraction) == 0 + + def test_optimise_load_profile_energy(monkeypatch): borefield = Borefield() monkeypatch.setattr(plt, "show", lambda: None) @@ -1068,3 +1106,24 @@ def test_deep_sizing(case, result): def test_depreciation_warning(): with pytest.raises(DeprecationWarning): Borefield(baseload_heating=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + + +def test_optimise_load_borefield(): + load = HourlyBuildingLoad() + load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) + load.simulation_period = 10 + borefield = Borefield(load=load) + borefield.set_min_avg_fluid_temperature(2) + borefield.set_max_avg_fluid_temperature(17) + borefield.borefield = gt.boreholes.rectangle_field(20, 4, 6, 6, 150, 1, 0.07) + borefield.Rb = 0.1699 + ground_data = GroundFluxTemperature(2, 9.6, flux=0.07) + borefield.ground_data = ground_data + borefield_load, external_load = borefield.optimise_load_profile_energy(load) + assert np.isclose(borefield_load.imbalance, -228386.82055766508) + borefield.load = borefield_load + borefield.calculate_temperatures(hourly=False) + assert np.isclose(np.max(borefield.results.peak_injection), 17.044473901670603) + assert np.isclose(np.min(borefield.results.peak_extraction), 1.9471241454443655) + assert np.isclose(borefield.load.max_peak_cooling, 329.9393053) + assert np.isclose(np.sum(borefield.load.hourly_heating_load), 593385.1066074175) diff --git a/README.md b/README.md index 71b639e5..a3adceff 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ These efficiencies can be used in the Building load classes (cf. infra). The dif * _SEER_: Constant seasonal performance for cooling * _COP_: Instant efficiency for heating, with inlet temperature, outlet temperature and part load dependency * _EER_: Instant efficiency for cooling, with inlet temperature, outlet temperature and part load dependency +* _EERCombined_: EER for combined active and passive cooling #### Load data @@ -209,6 +210,9 @@ Depending on your answer on these three questions, you can opt for one of eight * _MonthlyBuildingLoadMultiYear_: You can set the monthly heating and cooling load for multiple years (i.e. for the whole simulation period). +On the other hand, you can also choose a Cluster load where you can add multiple loads together. Be careful however when +mixing hourly and monthly loads! + All building load classes also have the option to add a yearly domestic hot water (DHW) demand and require you to define an efficiency for heating, cooling (and optionally DHW) (cf. supra). @@ -390,6 +394,13 @@ KU Leuven, Belgium. ### Applications/Mentions of GHEtool +Meertens, L. (2024). Reducing Capital Cost for Geothermal Heat Pump Systems Through Dynamic Borefield Sizing. _IEA HPT +Magazine 42_(2), https://doi.org/10.23697/9r3w-jm57. + +Blanke, T., Born, H., Döring, B. et al. Model for dimensioning borehole heat exchanger applied to +mixed-integer-linear-problem (MILP) energy system optimization. _Geotherm Energy_ 12, 30 ( +2024). https://doi.org/10.1186/s40517-024-00301-w. + Dion G., Pasquier, P., Perraudin, D. (2024). Sizing equation based on the outlet fluid temperature of closed-loop ground heat exchangers. In _Proceedings of International Ground Source Heat Pump Association_. Montréal (Canada), 28-30 May diff --git a/codecov.yml b/codecov.yml index 9320c59e..40a129e8 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,4 +2,4 @@ ignore: - "GHEtool/gui/mac_settings.py" - "GHEtool/gui/start_gui.py" - - "GHEtool/VariableClasses/cylindrical_correction.py" \ No newline at end of file + - "GHEtool/VariableClasses/Cylindrical_correction.py" \ No newline at end of file diff --git a/docs/sources/articles.md b/docs/sources/articles.md index 43f50612..42a3bdd8 100644 --- a/docs/sources/articles.md +++ b/docs/sources/articles.md @@ -1,52 +1,92 @@ # GHEtool in literature -Below is a general list of all the articles which use or mention GHEtool in literature. Please let us know if we missed a contribution. + +Below is a general list of all the articles which use or mention GHEtool in literature. Please let us know if we missed +a contribution. Note that for some of the publications, the code used in that publication, is available in this ReadTheDocs. -In this way, we want to contribute to the transparency in the academic world by sharing computer code so it is easier to replicate and verify published results. +In this way, we want to contribute to the transparency in the academic world by sharing computer code so it is easier to +replicate and verify published results. The articles for which this is the case, are: ```{toctree} articles/HPT_magazine.rst ``` -> Meertens, L., Peere, W., and Helsen, L. (2024). Influence of short-term dynamic effects on geothermal borefield size. In _Proceedings of International Ground Source Heat Pump Association Conference 2024_. Montréal (Canada), 28-30 May 2024. https://doi.org/10.22488/okstate.24.000004 -> Dion G., Pasquier, P., Perraudin, D. (2024). Sizing equation based on the outlet fluid temperature of closed-loop ground heat exchangers. In Proceedings of International Ground Source Heat Pump Association. Montréal (Canada), 28-30 May 2024. +> Meertens, L. (2024). Reducing Capital Cost for Geothermal Heat Pump Systems Through Dynamic Borefield Sizing. _IEA HPT +Magazine 42_(2), https://doi.org/10.23697/9r3w-jm57. + +> Blanke, T., Born, H., Döring, B. et al. Model for dimensioning borehole heat exchanger applied to +> mixed-integer-linear-problem (MILP) energy system optimization. _Geotherm Energy_ 12, 30 ( +> 2024). https://doi.org/10.1186/s40517-024-00301-w. + +> Meertens, L., Peere, W., and Helsen, L. (2024). Influence of short-term dynamic effects on geothermal borefield size. +> In _Proceedings of International Ground Source Heat Pump Association Conference 2024_. Montr�al (Canada), 28-30 May +2024. https://doi.org/10.22488/okstate.24.000004 + +> Dion G., Pasquier, P., Perraudin, D. (2024). Sizing equation based on the outlet fluid temperature of closed-loop +> ground heat exchangers. In Proceedings of International Ground Source Heat Pump Association. Montr�al (Canada), 28-30 +> May 2024. -> Peere, W. (2024). Are Rules of Thumb Misleading? The Complexity of Borefield Sizing and the Importance of Design Software. _IEA HPT Magazine 42_(1), https://doi.org/10.23697/7nec-0g78. +> Peere, W. (2024). Are Rules of Thumb Misleading? The Complexity of Borefield Sizing and the Importance of Design +> Software. _IEA HPT Magazine 42_(1), https://doi.org/10.23697/7nec-0g78. -> Meertens, L. (2024). Invloed van dynamische korte-termijneffecten op de dimensionering van geothermische boorvelden. Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. +> Meertens, L. (2024). Invloed van dynamische korte-termijneffecten op de dimensionering van geothermische boorvelden. +> Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. -> Coninx, M., De Nies, J., Hermans, L., Peere, W., Boydens, W., Helsen, L. (2024). Cost-efficient cooling of buildings by means of geothermal borefields with active and passive cooling. _Applied Energy_, 355, Art. No. 122261, https://doi.org/10.1016/j.apenergy.2023.122261. +> Coninx, M., De Nies, J., Hermans, L., Peere, W., Boydens, W., Helsen, L. (2024). Cost-efficient cooling of buildings +> by means of geothermal borefields with active and passive cooling. _Applied Energy_, 355, Art. No. +> 122261, https://doi.org/10.1016/j.apenergy.2023.122261. -> Peere, W., Hermans, L., Boydens, W., and Helsen, L. (2023). Evaluation of the oversizing and computational speed of different open-source borefield sizing methods. In _Proceedings of International Building Simulation Conference 2023_. Shanghai (Belgium), 4-6 September 2023. +> Peere, W., Hermans, L., Boydens, W., and Helsen, L. (2023). Evaluation of the oversizing and computational speed of +> different open-source borefield sizing methods. In _Proceedings of International Building Simulation Conference 2023_. +> Shanghai (Belgium), 4-6 September 2023. -> Weynjes, J. (2023). Methode voor het dimensioneren van een geothermisch systeem met regeneratie binnen verschillende ESCO-structuren. Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. +> Weynjes, J. (2023). Methode voor het dimensioneren van een geothermisch systeem met regeneratie binnen verschillende +> ESCO-structuren. Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. -> Hermans, L., Haesen, R., Uytterhoeven, A., Peere, W., Boydens, W., Helsen, L. (2023). Pre-design of collective residential solar districts with seasonal thermal energy storage: Importance of level of detail. _Applied thermal engineering_ 226, Art.No. 120203, 10.1016/j.applthermaleng.2023.120203. +> Hermans, L., Haesen, R., Uytterhoeven, A., Peere, W., Boydens, W., Helsen, L. (2023). Pre-design of collective +> residential solar districts with seasonal thermal energy storage: Importance of level of detail. _Applied thermal +engineering_ 226, Art.No. 120203, 10.1016/j.applthermaleng.2023.120203. -> Cimmino, M., Cook., J. C. (2022). pygfunction 2.2 : New Features and Improvements in Accuracy and Computational Efficiency. In _Proceedings of IGSHPA Research Track 2022_. Las Vegas (USA), 6-8 December 2022. https://doi.org/10.22488/okstate.22.000015 +> Cimmino, M., Cook., J. C. (2022). pygfunction 2.2 : New Features and Improvements in Accuracy and Computational +> Efficiency. In _Proceedings of IGSHPA Research Track 2022_. Las Vegas (USA), 6-8 December +2022. https://doi.org/10.22488/okstate.22.000015 -> Verleyen, L., Peere, W., Michiels, E., Boydens, W., Helsen, L. (2022). The beauty of reason and insight: a story about 30 years old borefield equations. _IEA HPT Magazine 40_(3), 36-39, https://doi.org/10.23697/6q4n-3223 +> Verleyen, L., Peere, W., Michiels, E., Boydens, W., Helsen, L. (2022). The beauty of reason and insight: a story about +> 30 years old borefield equations. _IEA HPT Magazine 40_(3), 36-39, https://doi.org/10.23697/6q4n-3223 -> Peere, W., Boydens, W., Helsen, L. (2022). GHEtool: een open-sourcetool voor boorvelddimensionering. Presented at the 15e warmtepompsymposium: van uitdaging naar aanpak, Quadrivium, Heverlee, België. +> Peere, W., Boydens, W., Helsen, L. (2022). GHEtool: een open-sourcetool voor boorvelddimensionering. Presented at the +> 15e warmtepompsymposium: van uitdaging naar aanpak, Quadrivium, Heverlee, België. -> Peere, W., Coninx, M., De Nies, J., Hermans, L., Boydens, W., Helsen, L. (2022). Cost-efficient Cooling of Buildings by means of Borefields with Active and Passive Cooling. Presented at the 15e warmtepompsymposium: van uitdaging naar aanpak, Quadrivium, Heverlee, België. +> Peere, W., Coninx, M., De Nies, J., Hermans, L., Boydens, W., Helsen, L. (2022). Cost-efficient Cooling of Buildings +> by means of Borefields with Active and Passive Cooling. Presented at the 15e warmtepompsymposium: van uitdaging naar +> aanpak, Quadrivium, Heverlee, België. -> Peere, W. (2022). Technologieën voor de energietransitie. Presented at the Energietransitie in meergezinswoningen en kantoorgebouwen: uitdagingen!, VUB Brussel Bruxelles - U Residence. +> Peere, W. (2022). Technologieën voor de energietransitie. Presented at the Energietransitie in meergezinswoningen en +> kantoorgebouwen: uitdagingen!, VUB Brussel Bruxelles - U Residence. -> Peere, W., Blanke, T.(2022). GHEtool: An open-source tool for borefield sizing in Python. _Journal of Open Source Software, 7_(76), 4406, https://doi.org/10.21105/joss.04406 +> Peere, W., Blanke, T.(2022). GHEtool: An open-source tool for borefield sizing in Python. _Journal of Open Source +Software, 7_(76), 4406, https://doi.org/10.21105/joss.04406 -> Sharifi., M. (2022). Early-Stage Integrated Design Methods for Hybrid GEOTABS Buildings. PhD thesis, Department of Architecture and Urban Planning, Faculty of Engineering and Architecture, Ghent University. +> Sharifi., M. (2022). Early-Stage Integrated Design Methods for Hybrid GEOTABS Buildings. PhD thesis, Department of +> Architecture and Urban Planning, Faculty of Engineering and Architecture, Ghent University. -> Coninx M., De Nies J. (2022). Cost-efficient Cooling of Buildings by means of Borefields with Active and Passive Cooling. Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. +> Coninx M., De Nies J. (2022). Cost-efficient Cooling of Buildings by means of Borefields with Active and Passive +> Cooling. Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. -> Michiels, E. (2022). Dimensionering van meerdere gekoppelde boorvelden op basis van het type vraagprofiel en de verbinding met de gebruikers. Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. +> Michiels, E. (2022). Dimensionering van meerdere gekoppelde boorvelden op basis van het type vraagprofiel en de +> verbinding met de gebruikers. Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. -> Vanpoucke, B. (2022). Optimale dimensionering van boorvelden door een variabel massadebiet. Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. +> Vanpoucke, B. (2022). Optimale dimensionering van boorvelden door een variabel massadebiet. Master thesis, Department +> of Mechanical Engineering, KU Leuven, Belgium. -> Haesen, R., Hermans, L. (2021). Design and Assessment of Low-carbon Residential District Concepts with (Collective) Seasonal Thermal Energy Storage. Master thesis, Departement of Mechanical Engineering, KU Leuven, Belgium. +> Haesen, R., Hermans, L. (2021). Design and Assessment of Low-carbon Residential District Concepts with (Collective) +> Seasonal Thermal Energy Storage. Master thesis, Departement of Mechanical Engineering, KU Leuven, Belgium. -> Peere, W., Picard, D., Cupeiro Figueroa, I., Boydens, W., and Helsen, L. (2021). Validated combined first and last year borefield sizing methodology. In _Proceedings of International Building Simulation Conference 2021_. Brugge (Belgium), 1-3 September 2021. https://doi.org/10.26868/25222708.2021.30180 +> Peere, W., Picard, D., Cupeiro Figueroa, I., Boydens, W., and Helsen, L. (2021). Validated combined first and last +> year borefield sizing methodology. In _Proceedings of International Building Simulation Conference 2021_. Brugge ( +> Belgium), 1-3 September 2021. https://doi.org/10.26868/25222708.2021.30180 -> Peere, W. (2020). Methode voor economische optimalisatie van geothermische verwarmings- en koelsystemen. Master thesis, Department of Mechanical Engineering, KU Leuven, Belgium. +> Peere, W. (2020). Methode voor economische optimalisatie van geothermische verwarmings- en koelsystemen. Master +> thesis, Department of Mechanical Engineering, KU Leuven, Belgium. diff --git a/docs/sources/code/Examples/combined_active_and_passive_cooling.rst b/docs/sources/code/Examples/combined_active_and_passive_cooling.rst new file mode 100644 index 00000000..58eade80 --- /dev/null +++ b/docs/sources/code/Examples/combined_active_and_passive_cooling.rst @@ -0,0 +1,7 @@ +*********************************************************** +Combined active and passive cooling +*********************************************************** + +.. literalinclude:: ../../../../GHEtool/Examples/combined_active_and_passive_cooling.py + :language: python + :linenos: \ No newline at end of file diff --git a/docs/sources/code/Modules/VariableClasses/Efficiency.rst b/docs/sources/code/Modules/VariableClasses/Efficiency.rst index 7b8aa3bb..bd1d3e6f 100644 --- a/docs/sources/code/Modules/VariableClasses/Efficiency.rst +++ b/docs/sources/code/Modules/VariableClasses/Efficiency.rst @@ -15,6 +15,10 @@ Efficiency data :members: :show-inheritance: +.. automodule:: GHEtool.VariableClasses.Efficiency.EERCombined + :members: + :show-inheritance: + .. automodule:: GHEtool.VariableClasses.Efficiency.SCOP :members: :show-inheritance: diff --git a/docs/sources/code/Modules/VariableClasses/Load.rst b/docs/sources/code/Modules/VariableClasses/Load.rst index cb0b6d47..c65d87ac 100644 --- a/docs/sources/code/Modules/VariableClasses/Load.rst +++ b/docs/sources/code/Modules/VariableClasses/Load.rst @@ -67,3 +67,7 @@ All of the load classes are based children of the abstract load classes. .. automodule:: GHEtool.VariableClasses.LoadData.BuildingLoad.HourlyBuildingLoadMultiYear :members: :show-inheritance: + +.. automodule:: GHEtool.VariableClasses.LoadData.Cluster.Cluster + :members: + :show-inheritance: diff --git a/docs/sources/code/examples.md b/docs/sources/code/examples.md index b8f787ff..1b3bfd5f 100644 --- a/docs/sources/code/examples.md +++ b/docs/sources/code/examples.md @@ -4,6 +4,7 @@ :maxdepth: 2 Examples/active_passive_cooling.rst +Examples/combined_active_and_passive_cooling.rst Examples/custom_borefield_configuration.rst Examples/effect_of_borehole_configuration.rst Examples/import_data.rst diff --git a/docs/sources/code/getting_started.md b/docs/sources/code/getting_started.md index 89679a9d..6733adcc 100644 --- a/docs/sources/code/getting_started.md +++ b/docs/sources/code/getting_started.md @@ -119,6 +119,7 @@ These efficiencies can be used in the Building load classes (cf. infra). The dif * _SEER_: Constant seasonal performance for cooling * _COP_: Instant efficiency for heating, with inlet temperature, outlet temperature and part load dependency * _EER_: Instant efficiency for cooling, with inlet temperature, outlet temperature and part load dependency +* _EERCombined_: EER for combined active and passive cooling ### Load data @@ -150,9 +151,11 @@ Depending on your answer on these three questions, you can opt for one of eight * _MonthlyBuildingLoadMultiYear_: You can set the monthly heating and cooling load for multiple years (i.e. for the whole simulation period). +On the other hand, you can also choose a Cluster load where you can add multiple loads together. Be careful however +when mixing hourly and monthly loads! + All building load classes also have the option to add a yearly domestic hot water (DHW) demand and require you to define -an -efficiency for heating, cooling (and optionally DHW) (cf. supra). +an efficiency for heating, cooling (and optionally DHW) (cf. supra). Please note that it is possible to add your own load types by inheriting the attributes from the abstract _LoadData, _HourlyLoad, _LoadDataBuilding and _HourlyLoadBuilding classes. diff --git a/setup.cfg b/setup.cfg index 7efaccfe..a6475b02 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = GHEtool -version = 2.3.0.dev2 +version = 2.3.0.dev6 author = Wouter Peere author_email = wouter@ghetool.eu description = Python package for borefield sizing