import pandas as pd
import logging
import numpy as np
from pandaprosumer.controller.base import BasicProsumerController
logging.basicConfig(level=logging.WARNING)
"""
Module containing the HeatPumpController class.
"""
[docs]
class BoosterHeatPumpController(BasicProsumerController):
"""
Controller for heat pumps.
"""
def name_class(self):
return "booster_heat_pump_controller"
def __init__(self, prosumer, heat_pump_object, order, level, in_service=True, index=None, **kwargs):
"""
Initializes the HeatPumpController.
:param prosumer: The prosumer object
:param heat_pump_object: The heat pump object
:param order: The order of the controller
:param level: The level of the controller
:param in_service: The in-service status of the controller
:param index: The index of the controller
:param kwargs: Additional keyword arguments
"""
super().__init__(prosumer, heat_pump_object, order=order, level=level, in_service=in_service, index=index, **kwargs)
@property
def _t_amb_k(self):
return self._get_input("t_amb_k")
@property
def _t_source_k(self):
return self._get_input("t_source_k")
@property
def _t_sink_k(self):
return self._get_input("t_sink_k")
@property
def _mode(self):
return self._get_input("mode")
@property
def _q_received_kw(self):
return self._get_input("q_received_kw")
@property
def _p_received_kw(self):
return self._get_input("p_received_kw")
[docs]
def q_to_receive_kw(self, prosumer):
"""
Calculates the heat to receive in kW.
:param prosumer: The prosumer object
:return: Heat to receive in kW
"""
self.applied = False
q_to_receive_kw = 0.
for responder in self._get_generic_mapped_responders(prosumer):
q_to_receive_kw += responder.q_to_receive_kw(prosumer)
return q_to_receive_kw
[docs]
def p_to_receive_kw(self, prosumer):
"""
Calculates the power to receive in kW.
:param prosumer: The prosumer object
:return: Power to receive in kW
"""
self.applied = False
p_to_receive_kw = 0.
for responder in self._get_generic_mapped_responders(prosumer):
p_to_receive_kw += responder.p_to_receive_kw(prosumer)
return p_to_receive_kw
[docs]
def q_requested_kw(self, prosumer):
"""
Calculates the heat to deliver in kW.
:param prosumer: The prosumer object
:return: Heat to deliver in kW
"""
q_to_deliver_kw = 0.
for responder in self._get_generic_mapped_responders(prosumer):
q_to_deliver_kw += responder.q_to_receive_kw(prosumer)
return q_to_deliver_kw
[docs]
def control_step(self, prosumer):
"""
Executes the control step for the controller.
:param prosumer: The prosumer object
"""
if not (self.in_service and getattr(prosumer, self.obj.element_name).iloc[self.obj.element_index[0]].in_service):
self.applied = True
return
super().control_step(prosumer)
demand_kw = self.q_requested_kw(prosumer)
p_el_kw = self._p_received_kw
q_kw = self._q_received_kw
t_amb_k = self._t_amb_k
t_source_k = self._t_source_k
t_sink_k = self._t_sink_k
bhp_type = self._get_element_param(prosumer, "bhp_type")
mode = self._mode
q_max_kw = self._get_element_param(prosumer, "q_max_kw")
t_source_k = t_source_k - 273.0 # in celsius
t_amb_k = t_amb_k - 273.0 # in celsius
if bhp_type == "air-water":
t_min_source_k = -25.0
t_max_source_k = 45.0
cop_coeff = [5.06, -0.05, 0.00006]
if np.isnan(t_sink_k):
t_sink_floor_heating_k = 30.0 - 0.5 * t_amb_k
t_sink_radiator_heating_k = 40.0 - 1.0 * t_amb_k
else:
t_sink_floor_heating_k = t_sink_k - 273.0
t_sink_radiator_heating_k = t_sink_k - 273.0
if pd.isna(q_max_kw):
q_max_kw = 5.8 + 0.21 * t_source_k
elif bhp_type == "water-water1":
t_min_source_k = -10.0
t_max_source_k = 25.0
cop_coeff = [5.06, -0.05, 0.00006]
if np.isnan(t_sink_k):
t_sink_floor_heating_k = 30.0 - 0.5 * t_amb_k
t_sink_radiator_heating_k = 40.0 - 1.0 * t_amb_k
else:
t_sink_floor_heating_k = t_sink_k - 273.0
t_sink_radiator_heating_k = t_sink_k - 273.0
if pd.isna(q_max_kw):
q_max_kw = 5.8 + 0.21 * t_source_k
elif bhp_type == "water-water2":
t_min_source_k = 10.0
t_max_source_k = 80.0
cop_coeff = [23.69, -0.986, 0.012]
if np.isnan(t_sink_k):
t_sink_floor_heating_k = 30.0 - 0.5 * t_amb_k
t_sink_radiator_heating_k = 40.0 - 1.0 * t_amb_k
else:
t_sink_floor_heating_k = t_sink_k - 273.0
t_sink_radiator_heating_k = t_sink_k - 273.0
if pd.isna(q_max_kw):
q_max_kw = 25.308 + 0.963 * t_source_k
elif bhp_type == "water-water3":
t_min_source_k = 10.0
t_max_source_k = 80.0
cop_coeff = [19.37, -0.757, 0.009]
if np.isnan(t_sink_k):
t_sink_floor_heating_k = 30.0 - 0.5 * t_amb_k
t_sink_radiator_heating_k = 40.0 - 1.0 * t_amb_k
else:
t_sink_floor_heating_k = t_sink_k - 273.0
t_sink_radiator_heating_k = t_sink_k - 273.0
if pd.isna(q_max_kw):
q_max_kw = 29.005 + 1.035 * t_source_k
else:
raise ValueError(f"Unknown heat pump type: {bhp_type}")
if mode in [1, 2, 3]:
if mode == 1: # Mode 1: total heat is source heat plus heat generated (boosting)
if bhp_type == 'water-water1' or bhp_type == 'air-water':
if t_source_k > t_max_source_k or t_source_k < t_min_source_k:
cop_floor = 0
cop_radiator = 0
pel_floor_kw = 0
pel_radiator_kw = 0
q_floor_kw = 0
q_radiator_kw = 0
q_remain_kw = demand_kw
logging.warning(f"Heat pump is not operating due to the heat source "
f"temperature being out of range: {float(t_source_k)} celsius")
else:
q_remain_kw, q_floor_kw, q_radiator_kw, cop_floor, cop_radiator = (
self.first_mode_calc(q_kw, demand_kw, p_el_kw, q_max_kw,
t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff))
pel_floor_kw = p_el_kw
pel_radiator_kw = p_el_kw
if bhp_type == 'water-water2':
if t_source_k > t_max_source_k:
cop_floor = 0
cop_radiator = 0
pel_floor_kw = 0
pel_radiator_kw = 0
q_floor_kw = 0
q_radiator_kw = 0
q_remain_kw = demand_kw
logging.warning(f"Heat pump is not operating due to the heat source "
f"temperature being out of range: {float(t_source_k)} celsius")
else:
q_remain_kw, q_floor_kw, q_radiator_kw, cop_floor, cop_radiator = (
self.first_mode_calc(q_kw, demand_kw, p_el_kw, q_max_kw,
t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff))
pel_floor_kw = p_el_kw
pel_radiator_kw = p_el_kw
if bhp_type == 'water-water3':
if t_source_k > t_max_source_k:
cop_floor = 0
cop_radiator = 0
pel_floor_kw = 0
pel_radiator_kw = 0
q_floor_kw = 0
q_radiator_kw = 0
q_remain_kw = demand_kw
logging.warning(f"Heat pump is not operating due to the heat source "
f"temperature being out of range: {float(t_source_k)} celsius")
else:
q_remain_kw, q_floor_kw, q_radiator_kw, cop_floor, cop_radiator = (
self.first_mode_calc(q_kw, demand_kw, p_el_kw, q_max_kw,
t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff))
pel_floor_kw = p_el_kw
pel_radiator_kw = p_el_kw
if mode == 2:
if bhp_type == 'water-water1' or bhp_type == 'air-water':# Mode 2: total heat is heat generated
if t_source_k > t_max_source_k or t_source_k < t_min_source_k:
cop_floor = 0
cop_radiator = 0
pel_floor_kw = 0
pel_radiator_kw = 0
q_floor_kw = 0
q_radiator_kw = 0
q_remain_kw = demand_kw
logging.warning(f"Heat pump is not operating due to the heat source "
f"temperature being out of range: {float(t_source_k)} celsius")
else:
q_remain_kw, q_floor_kw, q_radiator_kw, cop_floor, cop_radiator = (
self.second_mode_calc(demand_kw, p_el_kw, q_max_kw,
t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff))
pel_floor_kw = p_el_kw
pel_radiator_kw = p_el_kw
if bhp_type == 'water-water2':
if t_source_k > t_max_source_k:
cop_floor = 0
cop_radiator = 0
pel_floor_kw = 0
pel_radiator_kw = 0
q_floor_kw = 0
q_radiator_kw = 0
q_remain_kw = demand_kw
logging.warning(f"Heat pump is not operating due to the heat source "
f"temperature being out of range: {float(t_source_k)} celsius")
else:
q_remain_kw, q_floor_kw, q_radiator_kw, cop_floor, cop_radiator = (
self.second_mode_calc(demand_kw, p_el_kw, q_max_kw,
t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff))
pel_floor_kw = p_el_kw
pel_radiator_kw = p_el_kw
if bhp_type == 'water-water3':
if t_source_k > t_max_source_k:
cop_floor = 0
cop_radiator = 0
pel_floor_kw = 0
pel_radiator_kw = 0
q_floor_kw = 0
q_radiator_kw = 0
q_remain_kw = demand_kw
logging.warning(f"Heat pump is not operating due to the heat source "
f"temperature being out of range: {float(t_source_k)} celsius")
else:
q_remain_kw, q_floor_kw, q_radiator_kw, cop_floor, cop_radiator = (
self.second_mode_calc(demand_kw, p_el_kw, q_max_kw,
t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff))
pel_floor_kw = p_el_kw
pel_radiator_kw = p_el_kw
if mode == 3:
if bhp_type == 'water-water1' or bhp_type == 'air-water':# Mode 3: produced heat is a result of infinite electrical source, produced heat is either q_max_kw or demand_kw
if t_source_k > t_max_source_k or t_source_k < t_min_source_k:
cop_floor = 0
cop_radiator = 0
pel_floor_kw = 0
pel_radiator_kw = 0
q_floor_kw = 0
q_radiator_kw = 0
q_remain_kw = demand_kw
logging.warning(f"Heat pump is not operating due to the heat source "
f"temperature being out of range: {float(t_source_k)} celsius")
else:
q_remain_kw, cop_floor, cop_radiator, pel_floor_kw, pel_radiator_kw, q_floor_kw, q_radiator_kw \
= self.third_mode_calc(demand_kw, q_max_kw, t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff)
if bhp_type == 'water-water2':
if t_source_k > t_max_source_k:
cop_floor = 0
cop_radiator = 0
pel_floor_kw = 0
pel_radiator_kw = 0
q_floor_kw = 0
q_radiator_kw = 0
q_remain_kw = demand_kw
logging.warning(f"Heat pump is not operating due to the heat source "
f"temperature being out of range: {float(t_source_k)} celsius")
else:
q_remain_kw, cop_floor, cop_radiator, pel_floor_kw, pel_radiator_kw, q_floor_kw, q_radiator_kw \
= self.third_mode_calc(demand_kw, q_max_kw, t_sink_floor_heating_k,
t_sink_radiator_heating_k, t_source_k, cop_coeff)
if bhp_type == 'water-water3':
if t_source_k > t_max_source_k:
cop_floor = 0
cop_radiator = 0
pel_floor_kw = 0
pel_radiator_kw = 0
q_floor_kw = 0
q_radiator_kw = 0
q_remain_kw = demand_kw
logging.warning(f"Heat pump is not operating due to the heat source "
f"temperature being out of range: {float(t_source_k)} celsius")
else:
q_remain_kw, cop_floor, cop_radiator, pel_floor_kw, pel_radiator_kw, q_floor_kw, q_radiator_kw \
= self.third_mode_calc(demand_kw, q_max_kw, t_sink_floor_heating_k,
t_sink_radiator_heating_k, t_source_k, cop_coeff)
else:
raise ValueError(f"Unknown mode: {mode}")
result = np.array([pd.Series(cop_floor),
pd.Series(cop_radiator),
pd.Series(pel_floor_kw),
pd.Series(pel_radiator_kw),
pd.Series(q_remain_kw),
pd.Series(q_floor_kw),
pd.Series(q_radiator_kw)
])
self.last_result = {
"cop_floor": cop_floor,
"cop_radiator": cop_radiator,
"pel_floor_kw": pel_floor_kw,
"pel_radiator_kw": pel_radiator_kw,
"q_remain_kw": q_remain_kw,
"q_floor_kw": q_floor_kw,
"q_radiator_kw": q_radiator_kw
}
self.finalize(prosumer, result.T)
self.applied = True
[docs]
def first_mode_calc(self, q_kw, demand_kw, p_el_kw, q_max_kw,
t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff):
"""
Calculation of quantities for the mode 1 of the heat pump. Booster heat pump in mode 1
takes the inputted heat and electricity from external source and boosts heat
for the floor and radiator heating. Mode 1 also calculates the COP for the floor and radiator heating
that is dependent on the temperature of the heat source and heat sink.
:param q_kw: Inputted heat in kW
:param demand_kw: Demand of heat in kW
:param p_el_kw: Electrical power in kW
:param q_max_kw: Maximum heat in kW
:param t_sink_floor_heating_k: Temperature of the floor heating sink in K
:param t_sink_radiator_heating_k: Temperature of the radiator heating sink in K
:param t_source_k: Temperature of the heat source in K
:param cop_coeff: Coefficients for the COP calculation
:returns:
- q_remain_kw - Remaining heat in kW
- q_floor_kw - Heat for the floor heating in kW
- q_radiator_kw - Heat for the radiator heating in kW
- cop_floor - COP for the floor heating
- cop_radiator - COP for the radiator heating
"""
if demand_kw > q_max_kw:
q_remain_kw = demand_kw - q_max_kw
else:
q_remain_kw = 0
cop_floor = cop_coeff[0] + cop_coeff[1] * (t_sink_floor_heating_k - t_source_k) + cop_coeff[2] * (
t_sink_floor_heating_k - t_source_k) ** 2
cop_radiator = cop_coeff[0] + cop_coeff[1] * (t_sink_radiator_heating_k - t_source_k) + cop_coeff[2] * (
t_sink_radiator_heating_k - t_source_k) ** 2
q_generated_floor = p_el_kw * cop_floor + q_kw
q_generated_radiator = p_el_kw * cop_radiator + q_kw
if q_generated_floor < q_max_kw and q_generated_floor < demand_kw:
q_floor_kw = q_generated_floor
else:
q_floor_kw = min(q_max_kw, demand_kw)
if q_generated_radiator < q_max_kw and q_generated_radiator < demand_kw:
q_radiator_kw = q_generated_radiator
else:
q_radiator_kw = min(q_max_kw, demand_kw)
return q_remain_kw, q_floor_kw, q_radiator_kw, cop_floor, cop_radiator
[docs]
def second_mode_calc(self, demand_kw, p_el_kw, q_max_kw,
t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff):
"""
Calculation of quantities for the mode 2 of the heat pump. Booster heat pump in mode 2
takes the inputted electricity from external source and produces heat
for the floor and radiator heating. Mode 2 also calculates the COP for the floor and radiator heating
that is dependent on the temperature of the heat source and heat sink.
:param demand_kw: Demand of heat in kW
:param p_el_kw: Electrical power in kW
:param q_max_kw: Maximum heat in kW
:param t_sink_floor_heating_k: Temperature of the floor heating sink in K
:param t_sink_radiator_heating_k: Temperature of the radiator heating sink in K
:param t_source_k: Temperature of the heat source in K
:param cop_coeff: Coefficients for the COP calculation
:returns:
- q_remain_kw - Remaining heat in kW
- q_floor_kw - Heat for the floor heating in kW
- q_radiator_kw - Heat for the radiator heating in kW
- cop_floor - COP for the floor heating
- cop_radiator - COP for the radiator heating
"""
if demand_kw > q_max_kw:
q_remain_kw = demand_kw - q_max_kw
else:
q_remain_kw = 0
cop_floor = cop_coeff[0] + cop_coeff[1] * (t_sink_floor_heating_k - t_source_k) + cop_coeff[2] * (
t_sink_floor_heating_k - t_source_k) ** 2
cop_radiator = cop_coeff[0] + cop_coeff[1] * (t_sink_radiator_heating_k - t_source_k) + cop_coeff[2] * (
t_sink_radiator_heating_k - t_source_k) ** 2
q_generated_floor = p_el_kw * cop_floor
q_generated_radiator = p_el_kw * cop_radiator
if q_generated_floor < q_max_kw and q_generated_floor < demand_kw:
q_floor_kw = q_generated_floor
else:
q_floor_kw = min(q_max_kw, demand_kw)
if q_generated_radiator < q_max_kw and q_generated_radiator < demand_kw:
q_radiator_kw = q_generated_radiator
else:
q_radiator_kw = min(q_max_kw, demand_kw)
return q_remain_kw, q_floor_kw, q_radiator_kw, cop_floor, cop_radiator
[docs]
def third_mode_calc(self, demand_kw, q_max_kw, t_sink_floor_heating_k, t_sink_radiator_heating_k, t_source_k, cop_coeff):
"""
Calculation of quantities for the mode 3 of the heat pump. Mode 3
calculates the inputted electricity from infinite source (grid etc.) based on the heat demand and produces heat
for the floor and radiator heating. Mode 3 also calculates the COP for the floor and radiator heating
that is dependent on the temperature of the heat source and heat sink.
:param demand_kw: Demand of heat in kW
:param q_max_kw: Maximum heat in kW
:param t_sink_floor_heating_k: Temperature of the floor heating sink in K
:param t_sink_radiator_heating_k: Temperature of the radiator heating sink in K
:param t_source_k: Temperature of the heat source in K
:param cop_coeff: Coefficients for the COP calculation
:returns:
- q_remain_kw - Remaining heat in kW
- cop_floor - COP for the floor heating
- cop_radiator - COP for the radiator heating
- pel_floor_kw - Electrical power needed for the floor heating in kW
- pel_radiator_kw - Electrical power needed for the radiator heating in kW
- q_floor_kw - Heat for the floor heating in kW
- q_radiator_kw - Heat for the radiator heating in kW
"""
cop_floor = cop_coeff[0] + cop_coeff[1] * (t_sink_floor_heating_k - t_source_k) + cop_coeff[2] * (
t_sink_floor_heating_k - t_source_k) ** 2
cop_radiator = cop_coeff[0] + cop_coeff[1] * (t_sink_radiator_heating_k - t_source_k) + cop_coeff[2] * (
t_sink_floor_heating_k - t_source_k) ** 2
if demand_kw > q_max_kw:
q_remain_kw = demand_kw - q_max_kw
pel_floor_kw = q_max_kw / cop_floor
pel_radiator_kw = q_max_kw / cop_radiator
q_floor_kw = q_max_kw
q_radiator_kw = q_max_kw
return q_remain_kw, cop_floor, cop_radiator, pel_floor_kw, pel_radiator_kw, q_floor_kw, q_radiator_kw
else:
q_remain_kw = 0
pel_floor_kw = demand_kw / cop_floor
pel_radiator_kw = demand_kw / cop_radiator
q_floor_kw = demand_kw
q_radiator_kw = demand_kw
return q_remain_kw, cop_floor, cop_radiator, pel_floor_kw, pel_radiator_kw, q_floor_kw, q_radiator_kw