Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue229 new optimise load profile functionality #230

Merged
merged 5 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
30 changes: 23 additions & 7 deletions GHEtool/Examples/optimise_load_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +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.
borefield.optimise_load_profile(building_load=load, depth=150, print_results=True)
# 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 resulting external peak cooling profile
print(borefield._external_load.max_peak_cooling)
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)
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
Expand Down
4 changes: 2 additions & 2 deletions GHEtool/VariableClasses/LoadData/_LoadData.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
"""
Expand Down
284 changes: 243 additions & 41 deletions GHEtool/main_class.py

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions GHEtool/test/methods/TestMethodClass.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
40 changes: 32 additions & 8 deletions GHEtool/test/methods/method_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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))
7 changes: 5 additions & 2 deletions GHEtool/test/methods/test_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(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)
Expand Down
85 changes: 73 additions & 12 deletions GHEtool/test/unit-tests/test_main_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,40 +775,83 @@
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)

Check warning on line 779 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L778-L779

Added lines #L778 - L779 were not covered by tests


def test_optimise_load_profile(monkeypatch):
def test_optimise_load_profile_power(monkeypatch):

Check warning on line 782 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L782

Added line #L782 was not covered by tests
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(load, 150)
borefield.optimise_load_profile_power(load, 150)

Check warning on line 790 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L790

Added line #L790 was not covered by tests
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

Check warning on line 809 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L797-L809

Added lines #L797 - L809 were not covered by tests


def test_optimise_borefield_small_power(monkeypatch):

Check warning on line 812 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L812

Added line #L812 was not covered by tests
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(load, 150, print_results=True)
borefield.optimise_load_profile_power(load, 150)

Check warning on line 820 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L820

Added line #L820 was not covered by tests
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)
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

Check warning on line 839 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L827-L839

Added lines #L827 - L839 were not covered by tests


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)

Check warning on line 851 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L842-L851

Added lines #L842 - L851 were not covered by tests


def test_optimise_borefield_wrong_threshold_energy(monkeypatch):

Check warning on line 854 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L854

Added line #L854 was not covered by tests
borefield = Borefield()
monkeypatch.setattr(plt, "show", lambda: None)
borefield.set_ground_parameters(ground_data_constant)
Expand All @@ -817,7 +860,7 @@
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)

Check warning on line 863 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L863

Added line #L863 was not covered by tests


def test_calculate_quadrants_without_data():
Expand All @@ -829,10 +872,16 @@
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())

Check warning on line 878 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L875-L878

Added lines #L875 - L878 were not covered by tests


def test_optimise_load_profile_energy_without_data():

Check warning on line 881 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L881

Added line #L881 was not covered by tests
borefield = Borefield()
with pytest.raises(ValueError):
borefield.optimise_load_profile(MonthlyGeothermalLoadAbsolute())
borefield.optimise_load_profile_energy(MonthlyGeothermalLoadAbsolute())

Check warning on line 884 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L884

Added line #L884 was not covered by tests


def test_load_load():
Expand All @@ -853,16 +902,28 @@
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)

Check warning on line 914 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L905-L914

Added lines #L905 - L914 were not covered by tests


def test_optimise_load_profile_energy_without_hourly_data():

Check warning on line 917 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L917

Added line #L917 was not covered by tests
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)

Check warning on line 921 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L921

Added line #L921 was not covered by tests
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)

Check warning on line 926 in GHEtool/test/unit-tests/test_main_class.py

View check run for this annotation

Codecov / codecov/patch

GHEtool/test/unit-tests/test_main_class.py#L926

Added line #L926 was not covered by tests


@pytest.mark.parametrize(
Expand Down
Loading