Skip to content

Commit

Permalink
Merge branch 'clusters' into BAC
Browse files Browse the repository at this point in the history
  • Loading branch information
wouterpeere committed Sep 16, 2024
2 parents 0bbfba9 + fd35ca2 commit 37def9f
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- 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 Down
10 changes: 5 additions & 5 deletions GHEtool/Borefield.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
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
from GHEtool.VariableClasses.LoadData import *
from GHEtool.VariableClasses.LoadData import _LoadData, _LoadDataBuilding
from GHEtool.VariableClasses.PipeData import _PipeData
Expand Down Expand Up @@ -497,13 +497,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 +525,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
75 changes: 75 additions & 0 deletions GHEtool/VariableClasses/LoadData/Cluster.py
Original file line number Diff line number Diff line change
@@ -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:
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)
1 change: 1 addition & 0 deletions GHEtool/VariableClasses/LoadData/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .GeothermalLoad import *
from .BuildingLoad import *
from .Baseclasses import _LoadData, _LoadDataBuilding
from .Cluster import Cluster
32 changes: 31 additions & 1 deletion GHEtool/test/unit-tests/test_loads/test_hourly_load_building.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down Expand Up @@ -645,3 +645,33 @@ def test_time_array():
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)
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -362,3 +362,21 @@ 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])

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)

cluster.simulation_period = 21
assert cluster.list_of_buildings[0].simulation_period == 21
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,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).
Expand Down Expand Up @@ -387,8 +390,16 @@ 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

2024.

Peere, W. (2024). Are Rules of Thumb Misleading? The Complexity of Borefield Sizing and the Importance of Design
Expand Down
Loading

0 comments on commit 37def9f

Please sign in to comment.