diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b79faf4..1e280847 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Added
- GHEtool is available on conda-forge (issue #107).
+- Possibility to start in another month (issue #140).
- Equal functions for HourlyGeothermalLoad and MonthlyGeothermalLoadAbsolute (issue #189).
- Cylindrical borehole correction (issue #187).
- __add__ functionality for the load classes (issue #202).
diff --git a/GHEtool/Examples/start_in_different_month.py b/GHEtool/Examples/start_in_different_month.py
new file mode 100644
index 00000000..503f3db4
--- /dev/null
+++ b/GHEtool/Examples/start_in_different_month.py
@@ -0,0 +1,43 @@
+"""
+This example illustrates the importance of when a borefield is 'started' (i.e. when the first month of operation is).
+"""
+
+from GHEtool import *
+from GHEtool.Validation.cases import load_case
+
+import matplotlib.pyplot as plt
+
+
+def start_in_different_month():
+ # set data
+ ground_data = GroundTemperatureGradient(2.5, 10)
+ load = MonthlyGeothermalLoadAbsolute(*load_case(1))
+
+ # create borefield object
+ borefield = Borefield(load=load)
+ borefield.ground_data = ground_data
+ borefield.create_rectangular_borefield(10, 8, 6, 6, 100)
+
+ borefield.set_max_avg_fluid_temperature(17)
+ borefield.set_min_avg_fluid_temperature(3)
+ borefield.calculation_setup(max_nb_of_iterations=100)
+
+ depth_list = []
+
+ # iterate over all the start months
+ for month in range(1, 13, 1):
+ borefield.load.start_month = month
+ depth_list.append(borefield.size_L3())
+
+ plt.figure()
+ plt.bar(range(1, 13, 1), depth_list)
+ plt.ylabel('Required depth [m]')
+ plt.xlabel('First month of operation')
+ plt.xlim(0)
+ plt.ylim(0)
+ plt.title('Required depth as a function of the first month of operation')
+ plt.show()
+
+
+if __name__ == "__main__": # pragma: no cover
+ start_in_different_month()
diff --git a/GHEtool/VariableClasses/LoadData/GeothermalLoad/HourlyGeothermalLoad.py b/GHEtool/VariableClasses/LoadData/GeothermalLoad/HourlyGeothermalLoad.py
index bc9d241b..930f5c74 100644
--- a/GHEtool/VariableClasses/LoadData/GeothermalLoad/HourlyGeothermalLoad.py
+++ b/GHEtool/VariableClasses/LoadData/GeothermalLoad/HourlyGeothermalLoad.py
@@ -94,7 +94,7 @@ def hourly_heating_load(self) -> np.ndarray:
hourly heating : np.ndarray
Hourly heating values (incl. DHW) [kWh/h] for one year, so the length of the array is 8760
"""
- return self._hourly_heating_load + self.dhw / 8760
+ return self.correct_for_start_month(self._hourly_heating_load + self.dhw / 8760)
@hourly_heating_load.setter
def hourly_heating_load(self, load: Union[np.ndarray, list, tuple]) -> None:
@@ -152,7 +152,7 @@ def hourly_cooling_load(self) -> np.ndarray:
hourly cooling : np.ndarray
Hourly cooling values [kWh/h] for one year, so the length of the array is 8760
"""
- return self._hourly_cooling_load
+ return self.correct_for_start_month(self._hourly_cooling_load)
@hourly_cooling_load.setter
def hourly_cooling_load(self, load: Union[np.ndarray, list, tuple]) -> None:
@@ -387,3 +387,34 @@ def __add__(self, other):
return other.__add__(self)
except TypeError: # pragma: no cover
raise TypeError('Cannot perform addition. Please check if you use correct classes.') # pragma: no cover
+
+ @property
+ def _start_hour(self) -> int:
+ """
+ This function returns the hour at which the year starts based on the start month.
+
+ Returns
+ -------
+ int
+ Start hour of the year
+ """
+ return int(np.sum([self.UPM[i] for i in range(self.start_month - 1)]))
+
+ 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.
+
+ Parameters
+ ----------
+ array : np.ndarray
+ Load array
+
+ Returns
+ -------
+ load : np.ndarray
+ """
+ if self.start_month == 1:
+ return array
+ return np.concatenate((array[self._start_hour:], array[:self._start_hour]))
diff --git a/GHEtool/VariableClasses/LoadData/GeothermalLoad/MonthlyGeothermalLoadAbsolute.py b/GHEtool/VariableClasses/LoadData/GeothermalLoad/MonthlyGeothermalLoadAbsolute.py
index e6003583..ed89389f 100644
--- a/GHEtool/VariableClasses/LoadData/GeothermalLoad/MonthlyGeothermalLoadAbsolute.py
+++ b/GHEtool/VariableClasses/LoadData/GeothermalLoad/MonthlyGeothermalLoadAbsolute.py
@@ -95,7 +95,8 @@ def baseload_cooling(self) -> np.ndarray:
baseload cooling : np.ndarray
Baseload cooling values [kWh/month] for one year, so the length of the array is 12
"""
- return self._baseload_cooling
+ return self.correct_for_start_month(
+ self._baseload_cooling)
@baseload_cooling.setter
def baseload_cooling(self, load: Union[np.ndarray, list, tuple]) -> None:
@@ -157,7 +158,8 @@ def baseload_heating(self) -> np.ndarray:
baseload heating : np.ndarray
Baseload heating values (incl. DHW) [kWh/month] for one year, so the length of the array is 12
"""
- return self._baseload_heating + self.dhw / 8760 * self.UPM
+ return self.correct_for_start_month(
+ self._baseload_heating + self.dhw / 8760 * self.UPM)
@baseload_heating.setter
def baseload_heating(self, load: Union[np.ndarray, list, tuple]) -> None:
@@ -219,7 +221,8 @@ def peak_cooling(self) -> np.ndarray:
peak cooling : np.ndarray
Peak cooling values for one year, so the length of the array is 12
"""
- return np.maximum(self._peak_cooling, self.baseload_cooling_power)
+ return self.correct_for_start_month(
+ np.maximum(self._peak_cooling, self.baseload_cooling_power))
@peak_cooling.setter
def peak_cooling(self, load) -> None:
@@ -279,7 +282,8 @@ def peak_heating(self) -> np.ndarray:
peak heating : np.ndarray
Peak heating values for one year, so the length of the array is 12
"""
- return np.maximum(np.array(self._peak_heating) + self.dhw_power, self.baseload_heating_power)
+ return self.correct_for_start_month(
+ np.maximum(np.array(self._peak_heating) + self.dhw_power, self.baseload_heating_power))
@peak_heating.setter
def peak_heating(self, load: Union[np.ndarray, list, tuple]) -> None:
@@ -397,3 +401,22 @@ def __add__(self, other):
return result
raise TypeError('Cannot perform addition. Please check if you use correct classes.')
+
+ 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.
+
+ Parameters
+ ----------
+ array : np.ndarray
+ Load array
+
+ Returns
+ -------
+ load : np.ndarray
+ """
+ if self.start_month == 1:
+ return array
+ return np.concatenate((array[self.start_month-1:], array[:self.start_month-1]))
diff --git a/GHEtool/VariableClasses/LoadData/_LoadData.py b/GHEtool/VariableClasses/LoadData/_LoadData.py
index 145259ae..1335bdb1 100644
--- a/GHEtool/VariableClasses/LoadData/_LoadData.py
+++ b/GHEtool/VariableClasses/LoadData/_LoadData.py
@@ -35,6 +35,44 @@ def __init__(self, hourly_resolution: bool, simulation_period: int = DEFAULT_SIM
self.tm: int = _LoadData.AVG_UPM * 3600 # time in a month in seconds
self._all_months_equal: bool = True # true if it is assumed that all months are of the same length
self._dhw_yearly: float = 0.
+ self._start_month: float = 1
+
+ @property
+ def start_month(self) -> int:
+ """
+ This function returns the start month.
+
+ Returns
+ -------
+ float
+ Start month
+ """
+ return self._start_month
+
+ @start_month.setter
+ def start_month(self, month: int) -> None:
+ """
+ This function sets the start month.
+
+ Parameters
+ ----------
+ month : int
+ Start month (jan: 1, feb: 2 ...)
+
+ Returns
+ -------
+ None
+
+ Raises
+ ----------
+ ValueError
+ When the start month is smaller than 1, larger than 12 or non-integer
+ """
+
+ if not isinstance(month, int) or month < 1 or month > 12:
+ raise ValueError(f'The value for month is: {month} which is not an integer in [1,12].')
+
+ self._start_month = month
@property
def all_months_equal(self) -> bool:
@@ -626,3 +664,20 @@ def dhw_power(self) -> float:
dhw power : float
"""
return self._dhw_yearly / 8760
+
+ @abc.abstractmethod
+ 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.
+
+ Parameters
+ ----------
+ array : np.ndarray
+ Load array
+
+ Returns
+ -------
+ load : np.ndarray
+ """
diff --git a/GHEtool/test/methods/cases/test_BS2023.py b/GHEtool/test/methods/cases/test_BS2023.py
deleted file mode 100644
index 8b137891..00000000
--- a/GHEtool/test/methods/cases/test_BS2023.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/GHEtool/test/test_examples.py b/GHEtool/test/test_examples.py
index a66dfd4d..a51c3192 100644
--- a/GHEtool/test/test_examples.py
+++ b/GHEtool/test/test_examples.py
@@ -65,3 +65,9 @@ def test_optimise_load_profile(monkeypatch):
# optimise the load for a 10x10 field (see data above) and a fixed depth of 150m.
borefield.optimise_load_profile(load, depth=150, print_results=True)
+
+
+def test_start_in_different_month(monkeypatch):
+ monkeypatch.setattr(plt, 'show', lambda: None)
+ from GHEtool.Examples.start_in_different_month import start_in_different_month
+ start_in_different_month()
\ No newline at end of file
diff --git a/GHEtool/test/unit-tests/test_hourly_load_data.py b/GHEtool/test/unit-tests/test_hourly_load_data.py
index 16be9933..83e50adc 100644
--- a/GHEtool/test/unit-tests/test_hourly_load_data.py
+++ b/GHEtool/test/unit-tests/test_hourly_load_data.py
@@ -368,3 +368,46 @@ def test_add_multiyear():
assert False # pragma: no cover
except TypeError:
assert True
+
+
+def test_start_month_general():
+ load = HourlyGeothermalLoad()
+ assert load.start_month == 1
+ try:
+ load.start_month = 1.5
+ assert False # pragma: no cover
+ except ValueError:
+ assert True
+ try:
+ load.start_month = 0
+ assert False # pragma: no cover
+ except ValueError:
+ assert True
+ try:
+ load.start_month = 13
+ assert False # pragma: no cover
+ except ValueError:
+ assert True
+ load.start_month = 12
+ assert load.start_month == 12
+ assert load._start_hour == 11 * 730
+ load.start_month = 1
+ assert load.start_month == 1
+ assert load._start_hour == 0
+ load.start_month = 3
+ assert load.start_month == 3
+ assert load._start_hour == 730 * 2
+
+ load.all_months_equal = False
+ assert load._start_hour == 1416
+
+
+def test_different_start_month():
+ load = HourlyGeothermalLoad(np.arange(1, 8761, 1), np.arange(1, 8761, 1))
+ load.start_month = 3
+ assert load.start_month == 3
+ assert load.hourly_cooling_load[0] == 731 * 2 - 1
+ assert load.hourly_heating_load[0] == 731 * 2 - 1
+ load.all_months_equal = False
+ assert load.hourly_cooling_load[0] == 1417
+ assert load.hourly_heating_load[0] == 1417
diff --git a/GHEtool/test/unit-tests/test_monthly_load_data.py b/GHEtool/test/unit-tests/test_monthly_load_data.py
index 192e9322..e93fe68d 100644
--- a/GHEtool/test/unit-tests/test_monthly_load_data.py
+++ b/GHEtool/test/unit-tests/test_monthly_load_data.py
@@ -15,6 +15,30 @@ def test_checks():
assert load._check_input(np.ones(12))
+def test_start_month_general():
+ load = MonthlyGeothermalLoadAbsolute()
+ assert load.start_month == 1
+ try:
+ load.start_month = 1.5
+ assert False # pragma: no cover
+ except ValueError:
+ assert True
+ try:
+ load.start_month = 0
+ assert False # pragma: no cover
+ except ValueError:
+ assert True
+ try:
+ load.start_month = 13
+ assert False # pragma: no cover
+ except ValueError:
+ assert True
+ load.start_month = 12
+ assert load.start_month == 12
+ load.start_month = 1
+ assert load.start_month == 1
+
+
def test_imbalance():
load = MonthlyGeothermalLoadAbsolute(np.ones(12)*10, np.ones(12), np.ones(12), np.ones(12))
assert load.imbalance == -108
@@ -33,6 +57,7 @@ def test_baseload_heating():
assert np.array_equal(load.baseload_heating_power, load.peak_heating)
try:
load.set_baseload_heating(np.ones(11))
+ assert False # pragma: no cover
except ValueError:
assert True
@@ -49,6 +74,7 @@ def test_baseload_cooling():
try:
load.set_baseload_cooling(np.ones(11))
+ assert False # pragma: no cover
except ValueError:
assert True
@@ -64,6 +90,7 @@ def test_peak_heating():
assert np.array_equal(load.peak_heating, np.array([5., 5., 5., 5., 5., 6., 7., 8., 9., 10., 11., 12.]))
try:
load.set_peak_heating(np.ones(11))
+ assert False # pragma: no cover
except ValueError:
assert True
@@ -79,6 +106,7 @@ def test_peak_cooling():
assert np.array_equal(load.peak_cooling, np.array([5., 5., 5., 5., 5., 6., 7., 8., 9., 10., 11., 12.]))
try:
load.set_peak_cooling(np.ones(11))
+ assert False # pragma: no cover
except ValueError:
assert True
@@ -335,3 +363,16 @@ def test_add():
load_hourly.simulation_period = 20
with pytest.warns():
result = load_1 + load_hourly
+
+
+def test_different_start_month():
+ load = MonthlyGeothermalLoadAbsolute(baseload_heating=np.arange(1, 13, 1),
+ baseload_cooling=np.arange(1, 13, 1),
+ peak_cooling=np.arange(1, 13, 1),
+ peak_heating=np.arange(1, 13, 1))
+ load.start_month = 2
+ result = np.array([2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1])
+ assert np.array_equal(load.baseload_heating, result)
+ assert np.array_equal(load.baseload_cooling, result)
+ assert np.array_equal(load.peak_heating, result)
+ assert np.array_equal(load.peak_cooling, result)
diff --git a/README.md b/README.md
index 936ad9cf..7a9c084c 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
[](https://pepy.tech/project/ghetool)
[](https://ghetool.readthedocs.io/en/latest/)
## What is *GHEtool*?
-
+
GHEtool is a Python package that contains all the functionalities needed to deal with borefield design. GHEtool has been developed as a joint effort of KU Leuven (The SySi Team), boydens engineering (part of Sweco) and FH Aachen.
The core of this package is the automated sizing of borefield under different conditions. By making use of combination of just-in-time calculations of thermal ground responses (using [pygfunction](https://github.com/MassimoCimmino/pygfunction)) with
@@ -26,15 +26,15 @@ GHEtool Pro is the official and supported version of GHEtool which supports dril
With GHEtool Pro they can minimize the environmental and societal impact while maximizing the cost-effective utilization of geothermal projects.
Visit our website at [https://ghetool.eu](https://ghetool.eu) to learn more about the synergy between this open-source package and GHEtool Pro.
+
+
+
-
-