@@ -17,8 +17,6 @@ def optimise_load_profile_power(
1717 """
1818 This function optimises the load for maximum power in extraction and injection based on the given borefield and
1919 the given hourly building load. It does so based on a load-duration curve.
20- The temperatures of the borefield are calculated on a monthly basis, even though we have hourly data,
21- for an hourly calculation of the temperatures would take a very long time.
2220
2321 Parameters
2422 ----------
@@ -115,6 +113,7 @@ def optimise_load_profile_power(
115113 else :
116114 peak_dhw_load = max (0.1 , peak_dhw_load - 1 * max (1 , 10 * (
117115 borefield .Tf_min - min (borefield .results .peak_extraction ))))
116+ heat_ok = False
118117 else :
119118 if (dhw_preferential and peak_heat_load != init_peak_heating ) or (
120119 not dhw_preferential and 0.1 >= peak_dhw_load ) or dhw_preferential is None :
@@ -132,6 +131,7 @@ def optimise_load_profile_power(
132131 if np .max (borefield .results .peak_injection ) > borefield .Tf_max :
133132 peak_cool_load = max (0.1 , peak_cool_load - 1 * max (1 , 10 * (
134133 - borefield .Tf_max + np .max (borefield .results .peak_injection ))))
134+ cool_ok = False
135135 else :
136136 peak_cool_load = min (init_peak_cooling , peak_cool_load * 1.01 )
137137 if peak_cool_load == init_peak_cooling :
@@ -344,3 +344,183 @@ def f(hourly_load, monthly_peak) -> np.ndarray:
344344 building_load_copy .hourly_cooling_load_simulation_period - borefield_load .hourly_cooling_load_simulation_period ))
345345
346346 return borefield_load , external_load
347+
348+
349+ def optimise_load_profile_balance (
350+ borefield ,
351+ building_load : Union [HourlyBuildingLoad , HourlyBuildingLoadMultiYear ],
352+ temperature_threshold : float = 0.05 ,
353+ use_hourly_resolution : bool = True ,
354+ max_peak_heating : float = None ,
355+ max_peak_cooling : float = None ,
356+ dhw_preferential : bool = None ,
357+ imbalance_factor : float = 0.01 ,
358+ ) -> tuple [HourlyBuildingLoad , HourlyBuildingLoad ]:
359+ """
360+ This function optimises the load for maximum power in extraction and injection based on the given borefield and
361+ the given hourly building load, by maintaining a zero imbalance. It does so based on a load-duration curve.
362+
363+ Parameters
364+ ----------
365+ borefield : Borefield
366+ Borefield object
367+ building_load : HourlyBuildingLoad | HourlyBuildingLoadMultiYear
368+ Load data used for the optimisation.
369+ temperature_threshold : float
370+ The maximum allowed temperature difference between the maximum and minimum fluid temperatures and their
371+ respective limits. The lower this threshold, the longer the convergence will take.
372+ use_hourly_resolution : bool
373+ If use_hourly_resolution is used, the hourly data will be used for this optimisation. This can take some
374+ more time than using the monthly resolution, but it will give more accurate results.
375+ max_peak_heating : float
376+ The maximum peak power for the heating (building side) [kW]
377+ max_peak_cooling : float
378+ The maximum peak power for the cooling (building side) [kW]
379+ dhw_preferential : bool
380+ True if heating should first be reduced only after which the dhw share is reduced.
381+ If it is None, then the dhw profile is not optimised and kept constant.
382+ imbalance_factor : float
383+ Maximum allowed imbalance w.r.t. to the maximum of either the heat injection or extraction.
384+ It should be given in a range of 0-1. At 1, it converges to the solution for optimise for power.
385+
386+ Returns
387+ -------
388+ tuple [HourlyBuildingLoad, HourlyBuildingLoad]
389+ borefield load, external load
390+
391+ Raises
392+ ------
393+ ValueError
394+ ValueError if no correct load data is given or the threshold is negative
395+ """
396+ # copy borefield
397+ borefield = copy .deepcopy (borefield )
398+
399+ # check if hourly load is given
400+ if not isinstance (building_load , (HourlyBuildingLoad , HourlyBuildingLoadMultiYear )):
401+ raise ValueError ("The building load should be of the class HourlyBuildingLoad or HourlyBuildingLoadMultiYear!" )
402+
403+ # check if threshold is positive
404+ if temperature_threshold < 0 :
405+ raise ValueError (f"The temperature threshold is { temperature_threshold } , but it cannot be below 0!" )
406+
407+ if imbalance_factor > 1 or imbalance_factor < 0 :
408+ raise ValueError (f"The imbalance factor is { imbalance_factor } , but it should be between 0-1!" )
409+
410+ # since the depth does not change, the Rb* value is constant
411+ borefield .Rb = borefield .borehole .get_Rb (borefield .H , borefield .D , borefield .r_b ,
412+ borefield .ground_data .k_s (borefield .depth , borefield .D ))
413+
414+ # set load
415+ borefield .load = copy .deepcopy (building_load )
416+
417+ # set initial peak loads
418+ init_peak_heating : float = borefield .load .max_peak_heating
419+ init_peak_dhw : float = borefield .load .max_peak_dhw
420+ init_peak_cooling : float = borefield .load .max_peak_cooling
421+
422+ # correct for max peak powers
423+ if max_peak_heating is not None :
424+ init_peak_heating = min (init_peak_heating , max_peak_heating )
425+ if max_peak_cooling is not None :
426+ init_peak_cooling = min (init_peak_cooling , max_peak_cooling )
427+
428+ # peak loads for iteration
429+ peak_heat_load : float = init_peak_heating
430+ peak_dhw_load : float = init_peak_dhw
431+ peak_cool_load : float = init_peak_cooling
432+
433+ # set iteration criteria
434+ cool_ok , heat_ok = False , False
435+ while not cool_ok or not heat_ok :
436+ # limit the primary geothermal extraction and injection load to peak_heat_load and peak_cool_load
437+ borefield .load .set_hourly_cooling_load (
438+ np .minimum (peak_cool_load , building_load .hourly_cooling_load
439+ if isinstance (borefield .load , HourlyBuildingLoad ) else building_load .hourly_cooling_load_simulation_period ))
440+ borefield .load .set_hourly_heating_load (
441+ np .minimum (peak_heat_load , building_load .hourly_heating_load
442+ if isinstance (borefield .load , HourlyBuildingLoad ) else building_load .hourly_heating_load_simulation_period ))
443+ borefield .load .set_hourly_dhw_load (
444+ np .minimum (peak_dhw_load , building_load .hourly_dhw_load
445+ if isinstance (borefield .load , HourlyBuildingLoad ) else building_load .hourly_dhw_load_simulation_period ))
446+
447+ # calculate temperature profile, just for the results
448+ borefield .calculate_temperatures (length = borefield .H , hourly = use_hourly_resolution )
449+
450+ # calculate relative imbalance
451+ imbalance = borefield .load .imbalance / np .maximum (borefield .load .yearly_average_injection_load ,
452+ borefield .load .yearly_average_extraction_load )
453+
454+ # deviation from minimum temperature
455+ if abs (min (borefield .results .peak_extraction ) - borefield .Tf_min ) > temperature_threshold or \
456+ (abs (imbalance ) > imbalance_factor and imbalance < 0 ):
457+ # check if it goes below the threshold
458+ if min (borefield .results .peak_extraction ) < borefield .Tf_min :
459+ if (dhw_preferential and peak_heat_load > 0.1 ) \
460+ or (not dhw_preferential and peak_dhw_load <= 0.1 ) \
461+ or dhw_preferential is None :
462+ # first reduce the peak load in heating before touching the dhw load
463+ # if dhw_preferential is None, it is not optimised and kept constant
464+ peak_heat_load = max (0.1 , peak_heat_load - 1 * max (1 , 10 * (
465+ borefield .Tf_min - min (borefield .results .peak_extraction ))))
466+ else :
467+ peak_dhw_load = max (0.1 , peak_dhw_load - 1 * max (1 , 10 * (
468+ borefield .Tf_min - min (borefield .results .peak_extraction ))))
469+ heat_ok = False
470+ else :
471+ if abs (imbalance ) > imbalance_factor and imbalance < 0 :
472+ # remove imbalance
473+ if (dhw_preferential and peak_heat_load > 0.1 ) \
474+ or (not dhw_preferential and peak_dhw_load <= 0.1 ) \
475+ or dhw_preferential is None :
476+ # first reduce the peak load in heating before touching the dhw load
477+ # if dhw_preferential is None, it is not optimised and kept constant
478+ peak_heat_load = peak_heat_load * 0.99
479+ else :
480+ peak_dhw_load = peak_dhw_load * 0.99
481+ elif abs (imbalance ) > imbalance_factor and imbalance > 0 :
482+ if (dhw_preferential and peak_heat_load != init_peak_heating ) or (
483+ not dhw_preferential and 0.1 >= peak_dhw_load ) or dhw_preferential is None :
484+ peak_heat_load = min (init_peak_heating , peak_heat_load * 1.01 )
485+ else :
486+ peak_dhw_load = min (init_peak_dhw , peak_dhw_load * 1.01 )
487+ if (peak_heat_load == init_peak_heating and peak_dhw_load == init_peak_dhw ) or cool_ok :
488+ heat_ok = True
489+ else :
490+ # imbalance small enough
491+ heat_ok = True
492+ else :
493+ heat_ok = True
494+
495+ # deviation from maximum temperature
496+ if abs (np .max (borefield .results .peak_injection ) - borefield .Tf_max ) > temperature_threshold or \
497+ (abs (imbalance ) > imbalance_factor and imbalance > 0 ):
498+ # check if it goes above the threshold
499+ if np .max (borefield .results .peak_injection ) > borefield .Tf_max :
500+ peak_cool_load = max (0.1 , peak_cool_load - 1 * max (1 , 10 * (
501+ - borefield .Tf_max + np .max (borefield .results .peak_injection ))))
502+ cool_ok = False
503+ else :
504+ if abs (imbalance ) > imbalance_factor and imbalance > 0 :
505+ # remove imbalance
506+ peak_cool_load = peak_cool_load * 0.99
507+ elif abs (imbalance ) > imbalance_factor and imbalance < 0 :
508+ peak_cool_load = min (init_peak_cooling , peak_cool_load * 1.01 )
509+ if peak_cool_load == init_peak_cooling or heat_ok :
510+ cool_ok = True
511+ else :
512+ # imbalance is small enough
513+ cool_ok = True
514+ else :
515+ cool_ok = True
516+
517+ # calculate external load
518+ external_load = HourlyBuildingLoad (simulation_period = building_load .simulation_period )
519+ external_load .set_hourly_heating_load (
520+ np .maximum (0 , building_load .hourly_heating_load - borefield .load .hourly_heating_load ))
521+ external_load .set_hourly_cooling_load (
522+ np .maximum (0 , building_load .hourly_cooling_load - borefield .load .hourly_cooling_load ))
523+ external_load .set_hourly_dhw_load (
524+ np .maximum (0 , building_load .hourly_dhw_load - borefield .load .hourly_dhw_load ))
525+
526+ return borefield .load , external_load
0 commit comments