From fe6548a1852bfd2fce9c2ad9945da3e68b995dae Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Fri, 16 Feb 2024 17:55:17 +0100 Subject: [PATCH 1/5] Frist try --- CHANGELOG.md | 4 +- GHEtool/Examples/optimise_load_profile.py | 3 +- GHEtool/main_class.py | 284 ++++++++++++++++++--- GHEtool/test/methods/test_methods.py | 2 +- GHEtool/test/unit-tests/test_main_class.py | 85 +++++- 5 files changed, 321 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9510c102..86950ac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Function to create box, U and L-shaped borefields (issue #224). - Multiple year validation for L3 and L4 sizing (issue #227). - Added MonthlyGeothermalLoadMultiYear (issue #227). +- Added optimise_load_profile_energy (issue #229). ## changed -/ + - Removed set_peak_length from Borefield class (issue #227). + - Definition of the optimise_load_profile_class (issue #229). ## fixed - Small typo's in functions (issue #224). diff --git a/GHEtool/Examples/optimise_load_profile.py b/GHEtool/Examples/optimise_load_profile.py index eb8abe1c..6fde4ad2 100644 --- a/GHEtool/Examples/optimise_load_profile.py +++ b/GHEtool/Examples/optimise_load_profile.py @@ -33,11 +33,10 @@ def optimise(): load.load_hourly_profile("hourly_profile.csv", header=True, separator=";") # optimise the load for a 10x10 field (see data above) and a fixed depth of 150m. - borefield.optimise_load_profile(building_load=load, depth=150, print_results=True) + borefield.optimise_load_profile_power(building_load=load, depth=150) # calculate temperatures borefield.calculate_temperatures(hourly=True) - # print resulting external peak cooling profile print(borefield._external_load.max_peak_cooling) diff --git a/GHEtool/main_class.py b/GHEtool/main_class.py index 5a67a0d2..c6b2075b 100644 --- a/GHEtool/main_class.py +++ b/GHEtool/main_class.py @@ -1756,13 +1756,69 @@ def Re(self) -> float: """ return self.borehole.Re - def optimise_load_profile( + def optimise_load_profile(self, + building_load: HourlyGeothermalLoad, + depth: float = None, + SCOP: float = 10**6, + SEER: float = 10**6, + temperature_threshold: float = 0.05, + print_results: bool = False): + + warnings.warn(DeprecationWarning('Optimise_load_profile is replaced by optimise_load_profile_power' + 'or optimise_load_profile_energy. This method will be depreciated in ' + 'v2.2.3.')) + + self.optimise_load_profile_power(building_load=building_load, + depth=depth, + SCOP=SCOP, + SEER=SEER, + temperature_threshold=temperature_threshold) + + if print_results: + warnings.warn(DeprecationWarning('print_results will be depreciated from v2.2.3 onwards for code clarity. ' + 'The user can replicate the same behaviour manually.')) + # print results + print( + "The peak load heating is: ", + f"{self._secundary_borefield_load.max_peak_heating:.0f}", + "kW, leading to", + f"{np.sum(self._secundary_borefield_load.hourly_heating_load):.0f}", + "kWh of heating.", + ) + print("This is", f"{self._percentage_heating:.0f}", "% of the total heating load.") + print( + "Another", + f"{np.sum(self._external_load.hourly_heating_load):.0f}", + "kWh of heating should come from another source, with a peak of", + f"{self._external_load.max_peak_heating:.0f}", + "kW.", + ) + print("------------------------------------------") + print( + "The peak load cooling is: ", + f"{self._secundary_borefield_load.max_peak_cooling:.0f}", + "kW, leading to", + f"{np.sum(self._secundary_borefield_load.hourly_cooling_load):.0f}", + "kWh of cooling.", + ) + print("This is", f"{self._percentage_cooling:.0f}", "% of the total cooling load.") + print( + "Another", + f"{np.sum(self._external_load.hourly_cooling_load):.0f}", + "kWh of cooling should come from another source, with a peak of", + f"{self._external_load.max_peak_cooling:.0f}", + "kW.", + ) + + # plot results + self.print_temperature_profile_fixed_depth(depth=depth) + + def optimise_load_profile_power( self, building_load: HourlyGeothermalLoad, depth: float = None, SCOP: float = 10**6, SEER: float = 10**6, - print_results: bool = False, temperature_threshold: float = 0.05, ) -> None: """ @@ -1782,8 +1838,6 @@ def optimise_load_profile( SCOP of the geothermal system (needed to convert hourly building load to geothermal load) SEER : float SEER of the geothermal system (needed to convert hourly building load to geothermal load) - print_results : bool - True when the results of this optimisation are to be printed in the terminal temperature_threshold : float The maximum allowed temperature difference between the maximum and minimum fluid temperatures and their respective limits. The lower this threshold, the longer the convergence will take. @@ -1889,42 +1943,189 @@ def optimise_load_profile( self.Rb = Rb_backup self.borehole.use_constant_Rb = use_constant_Rb_backup - if print_results: - # print results - print( - "The peak load heating is: ", - f"{self._secundary_borefield_load.max_peak_heating:.0f}", - "kW, leading to", - f"{np.sum(self._secundary_borefield_load.hourly_heating_load):.0f}", - "kWh of heating.", - ) - print("This is", f"{self._percentage_heating:.0f}", "% of the total heating load.") - print( - "Another", - f"{np.sum(self._external_load.hourly_heating_load):.0f}", - "kWh of heating should come from another source, with a peak of", - f"{self._external_load.max_peak_heating:.0f}", - "kW.", - ) - print("------------------------------------------") - print( - "The peak load cooling is: ", - f"{self._secundary_borefield_load.max_peak_cooling:.0f}", - "kW, leading to", - f"{np.sum(self._secundary_borefield_load.hourly_cooling_load):.0f}", - "kWh of cooling.", - ) - print("This is", f"{self._percentage_cooling:.0f}", "% of the total cooling load.") - print( - "Another", - f"{np.sum(self._external_load.hourly_cooling_load):.0f}", - "kWh of cooling should come from another source, with a peak of", - f"{self._external_load.max_peak_cooling:.0f}", - "kW.", - ) + def optimise_load_profile_energy( + self, + building_load: HourlyGeothermalLoad, + depth: float = None, + SCOP: float = 10**6, + SEER: float = 10**6, + temperature_threshold: float = 0.05, + ) -> None: + """ + This function optimises the load based on the given borefield and the given hourly load. + (When the load is not geothermal, the SCOP and SEER are used to convert it to a geothermal load.) + It does so based on a load-duration curve. The temperatures of the borefield are calculated on a monthly + basis, even though we have hourly data, for an hourly calculation of the temperatures + would take a very long time. + + Parameters + ---------- + building_load : _LoadData + Load data used for the optimisation + depth : float + Depth of the boreholes in the borefield [m] + SCOP : float + SCOP of the geothermal system (needed to convert hourly building load to geothermal load) + SEER : float + SEER of the geothermal system (needed to convert hourly building load to geothermal load) + temperature_threshold : float + The maximum allowed temperature difference between the maximum and minimum fluid temperatures and their + respective limits. The lower this threshold, the longer the convergence will take. + + Returns + ------- + None + + Raises + ------ + ValueError + ValueError if no hourly load is given or the threshold is negative + """ + + ## Explain variables + # load --> primary, geothermal load + # _building_load --> secundary, building load + # _secundary_borefield_load --> secundary, geothermal load + + # check if hourly load is given + if not building_load.hourly_resolution: + raise ValueError("No hourly load was given!") + + # check if threshold is positive + if temperature_threshold < 0: + raise ValueError(f"The temperature threshold is {temperature_threshold}, but it cannot be below 0!") + + # set depth + if depth is None: + depth = self.H + + # since the depth does not change, the Rb* value is constant + # set to use a constant Rb* value but save the initial parameters + Rb_backup = self.borehole.Rb + use_constant_Rb_backup = self.borehole.use_constant_Rb + self.Rb = self.borehole.get_Rb(depth, self.D, self.r_b, self.ground_data.k_s) + + # load hourly heating and cooling load and convert it to geothermal loads + primary_geothermal_load = HourlyGeothermalLoad(simulation_period=building_load.simulation_period) + primary_geothermal_load.set_hourly_cooling(building_load.hourly_cooling_load.copy() * (1 + 1 / SEER)) + primary_geothermal_load.set_hourly_heating(building_load.hourly_heating_load.copy() * (1 - 1 / SCOP)) + + # set relation qh-qm + nb_points = 100 + + power_heating_range = np.linspace(0.001, primary_geothermal_load.max_peak_heating, nb_points) + power_cooling_range = np.linspace(0.001, primary_geothermal_load.max_peak_cooling, nb_points) + + # relationship between the peak load and the corresponding monthly load + heating_peak_bl = np.zeros((nb_points, 12)) + cooling_peak_bl = np.zeros((nb_points, 12)) + + for idx in range(nb_points): + heating_peak_bl[idx] = primary_geothermal_load.resample_to_monthly(np.minimum(power_heating_range[idx], primary_geothermal_load.hourly_heating_load))[1] + cooling_peak_bl[idx] = primary_geothermal_load.resample_to_monthly(np.minimum(power_cooling_range[idx], primary_geothermal_load.hourly_cooling_load))[1] + + # create monthly multi-load + primary_monthly_load = \ + MonthlyGeothermalLoadMultiYear(baseload_heating=primary_geothermal_load.baseload_heating_simulation_period, + baseload_cooling=primary_geothermal_load.baseload_cooling_simulation_period, + peak_heating=primary_geothermal_load.peak_heating_simulation_period, + peak_cooling=primary_geothermal_load.peak_cooling_simulation_period) + + self.load = primary_monthly_load + + # store initial monthly peak loads + peak_heating = primary_geothermal_load.peak_heating + peak_cooling = primary_geothermal_load.peak_cooling + + for i in range(12 * self.load.simulation_period): + # set iteration criteria + cool_ok, heat_ok = False, False + + while not cool_ok or not heat_ok: + # calculate temperature profile, just for the results + self.calculate_temperatures(depth) + + # deviation from minimum temperature + if abs(self.results.peak_heating[i] - self.Tf_min) > temperature_threshold: + # check if it goes below the threshold + curr_heating_peak = self.load.peak_heating_simulation_period[i] + if self.results.peak_heating[i] < self.Tf_min: + curr_heating_peak = max(0.1, curr_heating_peak - 1 * max(1, 10 * (self.Tf_min - self.results.peak_heating[i]))) + else: + curr_heating_peak = min(peak_heating[i % 12], curr_heating_peak * 1.01) + if curr_heating_peak == peak_heating[i % 12]: + heat_ok = True + self.load._peak_heating[i], self.load._baseload_heating[i] =\ + curr_heating_peak, np.interp(curr_heating_peak, power_heating_range, heating_peak_bl[:, i % 12]) + else: + heat_ok = True + + # deviation from maximum temperature + if abs(self.results.peak_cooling[i] - self.Tf_max) > temperature_threshold: + # check if it goes above the threshold + curr_cooling_peak = self.load.peak_cooling_simulation_period[i] + if self.results.peak_cooling[i] > self.Tf_max: + curr_cooling_peak = max(0.1, curr_cooling_peak - 1 * max(1, 10 * ( + -self.Tf_max + np.max(self.results.peak_cooling)))) + else: + curr_cooling_peak = min(peak_cooling[i % 12], curr_cooling_peak * 1.01) + if curr_cooling_peak == peak_cooling[i % 12]: + cool_ok = True + self.load._peak_cooling[i], self.load._baseload_cooling[i] = \ + curr_cooling_peak, np.interp(curr_cooling_peak, power_cooling_range, cooling_peak_bl[:, i % 12]) + else: + cool_ok = True + + def f(hourly_load, monthly_peak) -> np.ndarray: + """ + This function creates a new hourly load where the values are limited by the monthly peaks. + + Parameters + ---------- + hourly_load : np.ndarray + An array with hourly values + monthly_peak : np.ndarray + An array with monthly values + + Returns + ------- + np.ndarray + New array with hourly values where each value is the minimum of the monthly and hourly array + """ + new_load = np.zeros_like(hourly_load) + UPM = np.cumsum(np.tile(building_load.UPM, building_load.simulation_period)) + month_idx = 0 + for idx, val in enumerate(hourly_load): + if idx == UPM[month_idx] and not month_idx == len(UPM) - 1: + month_idx += 1 + new_load[idx] = min(val, monthly_peak[month_idx]) + return new_load + + # calculate hourly load + temp = HourlyGeothermalLoadMultiYear() + temp.hourly_heating_load = f(primary_geothermal_load.hourly_heating_load_simulation_period, self.load.peak_heating_simulation_period) + temp.hourly_cooling_load = f(primary_geothermal_load.hourly_cooling_load_simulation_period, self.load.peak_cooling_simulation_period) + + # set correct hourly load + self.load = temp + + # calculate the corresponding geothermal load + self._secundary_borefield_load = HourlyGeothermalLoadMultiYear() + self._secundary_borefield_load.hourly_cooling_load = self.load.hourly_cooling_load_simulation_period / (1 + 1 / SEER) + self._secundary_borefield_load.hourly_heating_load = self.load.hourly_heating_load_simulation_period / (1 - 1 / SCOP) + + # set building load + self._building_load = building_load + + # calculate external load + self._external_load = HourlyGeothermalLoadMultiYear() + self._external_load.hourly_heating_load = np.maximum(0, building_load.hourly_heating_load_simulation_period - self._secundary_borefield_load.hourly_heating_load_simulation_period) + self._external_load.hourly_cooling_load = np.maximum(0, building_load.hourly_cooling_load_simulation_period - self._secundary_borefield_load.hourly_cooling_load_simulation_period) + + # restore the initial parameters + self.Rb = Rb_backup + self.borehole.use_constant_Rb = use_constant_Rb_backup - # plot results - self.print_temperature_profile_fixed_depth(depth=depth) @property def _percentage_heating(self) -> float: @@ -1936,7 +2137,8 @@ def _percentage_heating(self) -> float: float Percentage of heating load that can be done geothermally. """ - return np.sum(self._secundary_borefield_load.hourly_heating_load) / np.sum(self._building_load.hourly_heating_load) * 100 + return np.sum(self._secundary_borefield_load.hourly_heating_load_simulation_period) /\ + np.sum(self._building_load.hourly_heating_load_simulation_period) * 100 @property def _percentage_cooling(self) -> float: @@ -1948,7 +2150,7 @@ def _percentage_cooling(self) -> float: float Percentage of cooling load that can be done geothermally. """ - return np.sum(self._secundary_borefield_load.hourly_cooling_load) / np.sum(self._building_load.hourly_cooling_load) * 100 + return np.sum(self._secundary_borefield_load.hourly_cooling_load_simulation_period) /np.sum(self._building_load.hourly_cooling_load_simulation_period) * 100 def calculate_quadrant(self) -> int: """ diff --git a/GHEtool/test/methods/test_methods.py b/GHEtool/test/methods/test_methods.py index 6961c089..fa6b2a75 100644 --- a/GHEtool/test/methods/test_methods.py +++ b/GHEtool/test/methods/test_methods.py @@ -47,7 +47,7 @@ def test_L4(model: Borefield, result): def test_optimise(input, result): model: Borefield = input[0] load, depth, SCOP, SEER = input[1:] - model.optimise_load_profile(load, depth, SCOP, SEER) + model.optimise_load_profile_power(load, depth, SCOP, SEER) percentage_heating, percentage_cooling, peak_heating_geo, peak_cooling_geo, peak_heating_ext, peak_cooling_ext = \ result assert np.isclose(model._percentage_heating, percentage_heating) diff --git a/GHEtool/test/unit-tests/test_main_class.py b/GHEtool/test/unit-tests/test_main_class.py index bcd2980f..7f548f3c 100644 --- a/GHEtool/test/unit-tests/test_main_class.py +++ b/GHEtool/test/unit-tests/test_main_class.py @@ -775,10 +775,11 @@ def test_load_duration(monkeypatch): load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) borefield.load = load borefield.plot_load_duration(legend=True) - borefield.optimise_load_profile(load, 150) + borefield.optimise_load_profile_power(load, 150) + borefield.optimise_load_profile_energy(load, 150) -def test_optimise_load_profile(monkeypatch): +def test_optimise_load_profile_power(monkeypatch): borefield = Borefield() monkeypatch.setattr(plt, "show", lambda: None) borefield.set_ground_parameters(ground_data_constant) @@ -786,14 +787,29 @@ def test_optimise_load_profile(monkeypatch): load = HourlyGeothermalLoad() load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) load.simulation_period = 40 - borefield.optimise_load_profile(load, 150) + borefield.optimise_load_profile_power(load, 150) assert borefield.load.simulation_period == 40 assert borefield._building_load.simulation_period == 40 assert borefield._secundary_borefield_load.simulation_period == 40 assert borefield._external_load.simulation_period == 40 -def test_optimise_borefield_small(monkeypatch): +def test_optimise_load_profile_energy(monkeypatch): + borefield = Borefield() + monkeypatch.setattr(plt, "show", lambda: None) + borefield.set_ground_parameters(ground_data_constant) + borefield.borefield = copy.deepcopy(borefield_gt) + load = HourlyGeothermalLoad() + load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) + load.simulation_period = 40 + borefield.optimise_load_profile_energy(load, 150) + assert borefield.load.simulation_period == 40 + assert borefield._building_load.simulation_period == 40 + assert borefield._secundary_borefield_load.simulation_period == 40 + assert borefield._external_load.simulation_period == 40 + + +def test_optimise_borefield_small_power(monkeypatch): borefield = Borefield() monkeypatch.setattr(plt, "show", lambda: None) borefield.set_ground_parameters(ground_data_constant) @@ -801,14 +817,41 @@ def test_optimise_borefield_small(monkeypatch): load = HourlyGeothermalLoad() load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) load.simulation_period = 40 - borefield.optimise_load_profile(load, 150, print_results=True) + borefield.optimise_load_profile_power(load, 150, print_results=True) assert borefield.load.simulation_period == 40 assert borefield._building_load.simulation_period == 40 assert borefield._secundary_borefield_load.simulation_period == 40 assert borefield._external_load.simulation_period == 40 -def test_optimise_borefield_wrong_threshold(monkeypatch): +def test_optimise_borefield_small_energy(monkeypatch): + borefield = Borefield() + monkeypatch.setattr(plt, "show", lambda: None) + borefield.set_ground_parameters(ground_data_constant) + borefield.create_rectangular_borefield(5, 1, 6, 6, 100) + load = HourlyGeothermalLoad() + load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) + load.simulation_period = 40 + borefield.optimise_load_profile_energy(load, 150, print_results=True) + assert borefield.load.simulation_period == 40 + assert borefield._building_load.simulation_period == 40 + assert borefield._secundary_borefield_load.simulation_period == 40 + assert borefield._external_load.simulation_period == 40 + + +def test_optimise_borefield_wrong_threshold_power(monkeypatch): + borefield = Borefield() + monkeypatch.setattr(plt, "show", lambda: None) + borefield.set_ground_parameters(ground_data_constant) + borefield.create_rectangular_borefield(5, 1, 6, 6, 100) + load = HourlyGeothermalLoad() + load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) + load.simulation_period = 40 + with pytest.raises(ValueError): + borefield.optimise_load_profile_power(load, 150, temperature_threshold=-0.5) + + +def test_optimise_borefield_wrong_threshold_energy(monkeypatch): borefield = Borefield() monkeypatch.setattr(plt, "show", lambda: None) borefield.set_ground_parameters(ground_data_constant) @@ -817,7 +860,7 @@ def test_optimise_borefield_wrong_threshold(monkeypatch): load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) load.simulation_period = 40 with pytest.raises(ValueError): - borefield.optimise_load_profile(load, 150, print_results=True, temperature_threshold=-0.5) + borefield.optimise_load_profile_energy(load, 150, temperature_threshold=-0.5) def test_calculate_quadrants_without_data(): @@ -829,10 +872,16 @@ def test_calculate_quadrants_without_data(): borefield.calculate_quadrant() -def test_optimise_load_profile_without_data(): +def test_optimise_load_profile_power_without_data(): + borefield = Borefield() + with pytest.raises(ValueError): + borefield.optimise_load_profile_power(MonthlyGeothermalLoadAbsolute()) + + +def test_optimise_load_profile_energy_without_data(): borefield = Borefield() with pytest.raises(ValueError): - borefield.optimise_load_profile(MonthlyGeothermalLoadAbsolute()) + borefield.optimise_load_profile_energy(MonthlyGeothermalLoadAbsolute()) def test_load_load(): @@ -853,16 +902,28 @@ def test_calculate_temperature_profile(): borefield.calculate_temperatures(hourly=True) -def test_optimise_load_profile_without_hourly_data(): +def test_optimise_load_profile_power_without_hourly_data(): + borefield = Borefield() + borefield.load = MonthlyGeothermalLoadAbsolute(*load_case(1)) + with pytest.raises(ValueError): + borefield.optimise_load_profile_power(borefield.load) + borefield.load = HourlyGeothermalLoad() + borefield.set_ground_parameters(ground_data_constant) + borefield.load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) + borefield.create_rectangular_borefield(10, 10, 6, 6, 150) + borefield.optimise_load_profile_power(borefield.load) + + +def test_optimise_load_profile_energy_without_hourly_data(): borefield = Borefield() borefield.load = MonthlyGeothermalLoadAbsolute(*load_case(1)) with pytest.raises(ValueError): - borefield.optimise_load_profile(borefield.load) + borefield.optimise_load_profile_energy(borefield.load) borefield.load = HourlyGeothermalLoad() borefield.set_ground_parameters(ground_data_constant) borefield.load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) borefield.create_rectangular_borefield(10, 10, 6, 6, 150) - borefield.optimise_load_profile(borefield.load) + borefield.optimise_load_profile_energy(borefield.load) @pytest.mark.parametrize( From ee420fd26506d04087a5967d9ff83b5dd7db1f61 Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Fri, 16 Feb 2024 17:57:17 +0100 Subject: [PATCH 2/5] Bugfix test --- GHEtool/test/unit-tests/test_main_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GHEtool/test/unit-tests/test_main_class.py b/GHEtool/test/unit-tests/test_main_class.py index 7f548f3c..f2557849 100644 --- a/GHEtool/test/unit-tests/test_main_class.py +++ b/GHEtool/test/unit-tests/test_main_class.py @@ -817,7 +817,7 @@ def test_optimise_borefield_small_power(monkeypatch): load = HourlyGeothermalLoad() load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) load.simulation_period = 40 - borefield.optimise_load_profile_power(load, 150, print_results=True) + borefield.optimise_load_profile_power(load, 150) assert borefield.load.simulation_period == 40 assert borefield._building_load.simulation_period == 40 assert borefield._secundary_borefield_load.simulation_period == 40 @@ -832,7 +832,7 @@ def test_optimise_borefield_small_energy(monkeypatch): load = HourlyGeothermalLoad() load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) load.simulation_period = 40 - borefield.optimise_load_profile_energy(load, 150, print_results=True) + borefield.optimise_load_profile_energy(load, 150) assert borefield.load.simulation_period == 40 assert borefield._building_load.simulation_period == 40 assert borefield._secundary_borefield_load.simulation_period == 40 From e55c6fbf065a1ec9bdd4091d66b56963faae7c2b Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Fri, 16 Feb 2024 18:17:29 +0100 Subject: [PATCH 3/5] Fix example --- GHEtool/Examples/optimise_load_profile.py | 29 +++++++++++++++---- GHEtool/VariableClasses/LoadData/_LoadData.py | 4 +-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/GHEtool/Examples/optimise_load_profile.py b/GHEtool/Examples/optimise_load_profile.py index 6fde4ad2..49ebd7da 100644 --- a/GHEtool/Examples/optimise_load_profile.py +++ b/GHEtool/Examples/optimise_load_profile.py @@ -26,22 +26,39 @@ def optimise(): borefield.Rb = 0.12 # set borefield - borefield.create_rectangular_borefield(10, 10, 6, 6, 110, 1, 0.075) + borefield.create_rectangular_borefield(10, 10, 6, 6, 150, 1, 0.075) # load the hourly profile load = HourlyGeothermalLoad() load.load_hourly_profile("hourly_profile.csv", header=True, separator=";") # optimise the load for a 10x10 field (see data above) and a fixed depth of 150m. + # first for an optimisation based on the power borefield.optimise_load_profile_power(building_load=load, depth=150) - # calculate temperatures + print(f'Max heating power (primary): {borefield.load.max_peak_heating:,.0f}kW') + print(f'Max cooling power (primary): {borefield.load.max_peak_cooling:,.0f}kW') + + print(f'Total energy extracted from the borefield over simulation period: {np.sum(borefield.load.baseload_heating_simulation_period):,.0f}MWh') + print(f'Total energy injected in the borefield over simulation period): {np.sum(borefield.load.baseload_cooling_simulation_period):,.0f}MWh') + print('-----------------------------------------------------------------') + borefield.calculate_temperatures(hourly=True) + borefield.print_temperature_profile(plot_hourly=True) + + # first for an optimisation based on the energy + borefield.optimise_load_profile_energy(building_load=load, depth=150) + + print(f'Max heating power (primary): {borefield.load.max_peak_heating:,.0f}kW') + print(f'Max cooling power (primary): {borefield.load.max_peak_cooling:,.0f}kW') + + print( + f'Total energy extracted from the borefield over simulation period: {np.sum(borefield.load.baseload_heating_simulation_period):,.0f}MWh') + print( + f'Total energy injected in the borefield over simulation period): {np.sum(borefield.load.baseload_cooling_simulation_period):,.0f}MWh') + borefield.calculate_temperatures(hourly=True) - # print resulting external peak cooling profile - print(borefield._external_load.max_peak_cooling) + borefield.print_temperature_profile(plot_hourly=True) - # print resulting monthly load for an external heating source - print(np.sum(borefield._external_load.hourly_heating_load)) if __name__ == "__main__": # pragma: no cover diff --git a/GHEtool/VariableClasses/LoadData/_LoadData.py b/GHEtool/VariableClasses/LoadData/_LoadData.py index eae06317..85c2054c 100644 --- a/GHEtool/VariableClasses/LoadData/_LoadData.py +++ b/GHEtool/VariableClasses/LoadData/_LoadData.py @@ -592,7 +592,7 @@ def max_peak_cooling(self) -> float: ------- max peak cooling : float """ - return np.max(self.peak_cooling) + return np.max(self.peak_cooling_simulation_period) @property def max_peak_heating(self) -> float: @@ -603,7 +603,7 @@ def max_peak_heating(self) -> float: ------- max peak heating : float """ - return np.max(self.peak_heating) + return np.max(self.peak_heating_simulation_period) def add_dhw(self, dhw: float) -> None: """ From d3f2d6e3bf59b32f6c1f5c7637778cf3d583b5d7 Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Fri, 16 Feb 2024 18:55:19 +0100 Subject: [PATCH 4/5] Create method data --- GHEtool/test/methods/TestMethodClass.py | 5 ++-- GHEtool/test/methods/method_data.py | 40 ++++++++++++++++++++----- GHEtool/test/methods/test_methods.py | 7 +++-- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/GHEtool/test/methods/TestMethodClass.py b/GHEtool/test/methods/TestMethodClass.py index c3e1beaa..74a4ba7a 100644 --- a/GHEtool/test/methods/TestMethodClass.py +++ b/GHEtool/test/methods/TestMethodClass.py @@ -27,7 +27,7 @@ class OptimiseLoadProfileObject: def __init__(self, borefield: Borefield, load, depth: float, SCOP: float, SEER: float, percentage_heating: float, percentage_cooling: float, peak_heating_geo: float, peak_cooling_geo: float, peak_heating_ext: float, - peak_cooling_ext: float, name: str = ""): + peak_cooling_ext: float, name: str = "", power: bool= True): self.borefield = copy.deepcopy(borefield) self.load = copy.deepcopy(load) self.depth = depth @@ -40,6 +40,7 @@ def __init__(self, borefield: Borefield, load, depth: float, SCOP: float, SEER: self.peak_heating_ext = peak_heating_ext self.peak_cooling_ext = peak_cooling_ext self.name = name + self.power = power def test(self): # pragma: no cover self.borefield.optimise_load_profile(self.load, self.depth, self.SCOP, self.SEER) @@ -179,7 +180,7 @@ def optimise_load_profile_input(self) -> list: for _, i in enumerate(self.list_of_test_objects): if isinstance(i, SizingObject): continue - temp.append((copy.deepcopy(i.borefield), copy.deepcopy(i.load), i.depth, i.SCOP, i.SEER)) + temp.append((copy.deepcopy(i.borefield), copy.deepcopy(i.load), i.depth, i.SCOP, i.SEER, i.power)) return temp @property diff --git a/GHEtool/test/methods/method_data.py b/GHEtool/test/methods/method_data.py index c1ac002c..16d88073 100644 --- a/GHEtool/test/methods/method_data.py +++ b/GHEtool/test/methods/method_data.py @@ -338,18 +338,29 @@ hourly_load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv")) list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 150, 10**6, 10**6, 87.506, 97.012, - 305.842, 384.204, 230.193, 292.212, name='Optimise load profile 1')) + 305.842, 384.204, 230.193, 292.212, name='Optimise load profile 1 (power)', power=True)) list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 100, 10**6, 10**6, 70.054, 87.899, - 210.800, 247.186, 325.236, 429.231, name='Optimise load profile 2')) + 210.800, 247.186, 325.236, 429.231, name='Optimise load profile 2 (power)', power=True)) list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 50, 10**6, 10**6, 45.096, 63.799, - 118.898, 117.804, 417.138, 558.612, name='Optimise load profile 3')) + 118.898, 117.804, 417.138, 558.612, name='Optimise load profile 3 (power)', power=True)) + +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 150, 10**6, 10**6, 93.227, 99.557, + 536.035, 663.791, 233.455, 290.487, name='Optimise load profile 1 (energy)', power=False)) + +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 100, 10**6, 10**6, 77.527, 96.245, + 385.562, 414.703, 316.333, 422.325, name='Optimise load profile 2 (energy)', power=False)) + +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 50, 10**6, 10**6, 50.756, 75.372, + 181.332, 209.175, 402.103, 553.451, name='Optimise load profile 3 (energy)', power=False)) borefield.set_min_avg_fluid_temperature(-5) borefield.set_max_avg_fluid_temperature(25) list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 150, 10**6, 10**6, 100, 100, - 536.036, 676.417, 0, 0, name='Optimise load profile 100%')) + 536.036, 676.417, 0, 0, name='Optimise load profile 100% (power)', power=True)) +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 150, 10**6, 10**6, 100, 100, + 536.036, 676.417, 0, 0, name='Optimise load profile 100% (energy)', power=False)) borefield = Borefield() borefield.set_ground_parameters(data) @@ -360,16 +371,26 @@ hourly_load.load_hourly_profile(FOLDER.joinpath("Examples/hourly_profile.csv"), col_heating=1, col_cooling=0) list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 150, 10**6, 10**6, 99.976, 66.492, 643.137, 195.331, 33.278, 340.705, - name='Optimise load profile 1, reversed')) + name='Optimise load profile 1, reversed (power)', power=True)) +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 150, 10**6, 10**6, 99.999, 71.783, + 676.415, 342.769, 22.620, 344.786, + name='Optimise load profile 1, reversed (energy)', power=False)) borefield.set_max_avg_fluid_temperature(20) borefield.set_min_avg_fluid_temperature(4) list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 150, 10**6, 10**6, 97.012, 87.506, 384.204, 305.842, 292.211, 230.195, - name='Optimise load profile 2, reversed')) + name='Optimise load profile 2, reversed (power)', power=True)) list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 100, 10**6, 10**6, 87.899, 70.054, 247.186, 210.800, 429.23, 325.236, - name='Optimise load profile 3, reversed')) + name='Optimise load profile 3, reversed (power)', power=True)) +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 150, 10**6, 10**6, 99.576, 93.039, + 661.853, 536.037, 287.343, 235.187, + name='Optimise load profile 2, reversed (energy)', power=False)) + +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, hourly_load, 100, 10**6, 10**6, 96.304, 77.304, + 416.623, 384.529, 419.950, 318.392, + name='Optimise load profile 3, reversed (energy)', power=False)) borefield = Borefield() borefield.create_rectangular_borefield(3, 6, 6, 6, 146, 4) @@ -386,4 +407,7 @@ borefield.set_pipe_parameters(SingleUTube(1.5, 0.016, 0.02, 0.42, 0.04)) list_of_test_objects.add(OptimiseLoadProfileObject(borefield, load, 146, 4, 25, 81.6397, 88.1277, 22.2967, 39.52027, 55.28596, 58.400, - name='Optimise load profile (stuck in loop)')) + name='Optimise load profile (stuck in loop) (power)', power=True)) +list_of_test_objects.add(OptimiseLoadProfileObject(borefield, load, 146, 4, 25, 89.242, 98.362, + 56.147, 74.421, 55.020, 56.865, + name='Optimise load profile (stuck in loop) (energy)', power=False)) diff --git a/GHEtool/test/methods/test_methods.py b/GHEtool/test/methods/test_methods.py index fa6b2a75..7bef4a34 100644 --- a/GHEtool/test/methods/test_methods.py +++ b/GHEtool/test/methods/test_methods.py @@ -46,8 +46,11 @@ def test_L4(model: Borefield, result): ids=list_of_test_objects.names_optimise_load_profile) def test_optimise(input, result): model: Borefield = input[0] - load, depth, SCOP, SEER = input[1:] - model.optimise_load_profile_power(load, depth, SCOP, SEER) + load, depth, SCOP, SEER, power = input[1:] + if power: + model.optimise_load_profile_power(load, depth, SCOP, SEER) + else: + model.optimise_load_profile_energy(load, depth, SCOP, SEER) percentage_heating, percentage_cooling, peak_heating_geo, peak_cooling_geo, peak_heating_ext, peak_cooling_ext = \ result assert np.isclose(model._percentage_heating, percentage_heating) From d461f16ee2e1977100d520ed0ea9e98e7c103ffa Mon Sep 17 00:00:00 2001 From: wouterpeere Date: Fri, 16 Feb 2024 18:56:44 +0100 Subject: [PATCH 5/5] Change depreciation warning --- GHEtool/main_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GHEtool/main_class.py b/GHEtool/main_class.py index c6b2075b..f882c151 100644 --- a/GHEtool/main_class.py +++ b/GHEtool/main_class.py @@ -1766,7 +1766,7 @@ def optimise_load_profile(self, warnings.warn(DeprecationWarning('Optimise_load_profile is replaced by optimise_load_profile_power' 'or optimise_load_profile_energy. This method will be depreciated in ' - 'v2.2.3.')) + 'v2.3.1.')) self.optimise_load_profile_power(building_load=building_load, depth=depth, @@ -1775,7 +1775,7 @@ def optimise_load_profile(self, temperature_threshold=temperature_threshold) if print_results: - warnings.warn(DeprecationWarning('print_results will be depreciated from v2.2.3 onwards for code clarity. ' + warnings.warn(DeprecationWarning('print_results will be depreciated from v2.3.1 onwards for code clarity. ' 'The user can replicate the same behaviour manually.')) # print results print(