Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into Separatus
Browse files Browse the repository at this point in the history
# Conflicts:
#	GHEtool/test/test_examples.py
  • Loading branch information
wouterpeere committed Nov 1, 2024
2 parents 0c1d22c + fe2aba4 commit df05737
Show file tree
Hide file tree
Showing 41 changed files with 957 additions and 89 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand Down
24 changes: 15 additions & 9 deletions GHEtool/Borefield.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -1706,25 +1707,30 @@ 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)

# 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)
Expand Down
5 changes: 3 additions & 2 deletions GHEtool/Examples/active_passive_cooling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
130 changes: 130 additions & 0 deletions GHEtool/Examples/combined_active_and_passive_cooling.py
Original file line number Diff line number Diff line change
@@ -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()
14 changes: 10 additions & 4 deletions GHEtool/Methods/optimise_load_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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

Expand Down
11 changes: 6 additions & 5 deletions GHEtool/VariableClasses/Efficiency/EER.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Loading

0 comments on commit df05737

Please sign in to comment.