Skip to content

Commit

Permalink
GSYE-745: Fixed VHP solver and energy parameters market cycle operati…
Browse files Browse the repository at this point in the history
…on. Corrected unit tests.
  • Loading branch information
spyrostz committed Jul 25, 2024
1 parent a2885cd commit 277f626
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 210 deletions.
21 changes: 12 additions & 9 deletions src/gsy_e/models/strategy/energy_parameters/heat_pump.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from abc import ABC, abstractmethod
from typing import Optional, Dict, Union, List

from gsy_framework.constants_limits import ConstSettings, GlobalConfig
from gsy_framework.enums import HeatPumpSourceType
from gsy_framework.read_user_profile import InputProfileTypes
from pendulum import DateTime

from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.models.strategy.state import HeatPumpState
from gsy_e.models.strategy.strategy_profile import profile_factory
from gsy_e.models.strategy.energy_parameters.heat_pump_tank import (
TankParameters,
AllTanksEnergyParameters,
)
from gsy_framework.constants_limits import ConstSettings, GlobalConfig
from gsy_framework.enums import HeatPumpSourceType
from gsy_framework.read_user_profile import InputProfileTypes
from pendulum import DateTime
from gsy_e.models.strategy.state import HeatPumpState
from gsy_e.models.strategy.strategy_profile import profile_factory

# pylint: disable=pointless-string-statement
"""
Expand Down Expand Up @@ -158,7 +159,9 @@ def serialize(self):
"maximum_power_rating_kW": self._maximum_power_rating_kW,
"consumption_kWh": self._consumption_kWh.input_profile,
"consumption_profile_uuid": self._consumption_kWh.input_profile_uuid,
"consumption_kWh_measurement_uuid": self._measurement_consumption_kWh.input_profile_uuid,
"consumption_kWh_measurement_uuid": (
self._measurement_consumption_kWh.input_profile_uuid
),
"external_temp_C": self._ext_temp_C.input_profile,
"external_temp_profile_uuid": self._ext_temp_C.input_profile_uuid,
"external_temp_measurement_uuid": self._measurement_ext_temp_C.input_profile_uuid,
Expand All @@ -170,7 +173,7 @@ def event_traded_energy(self, time_slot: DateTime, energy_kWh: float):
self._decrement_posted_energy(time_slot, energy_kWh)

traded_heat_energy = self._calc_Q_from_energy_kWh(time_slot, energy_kWh)
self._tanks.increase_tanks_temp(traded_heat_energy, time_slot)
self._tanks.increase_tanks_temp_from_heat_energy(traded_heat_energy, time_slot)

self._calculate_and_set_unmatched_demand(time_slot)

Expand Down Expand Up @@ -200,7 +203,7 @@ def _populate_state(self, time_slot: DateTime):
produced_heat_energy = self._calc_Q_from_energy_kWh(
time_slot, self._consumption_kWh.profile[time_slot]
)
self._tanks.set_temp_decrease(produced_heat_energy, time_slot)
self._tanks.decrease_tanks_temp_from_heat_energy(produced_heat_energy, time_slot)
super()._populate_state(time_slot)
self.state.set_energy_consumption_kWh(
time_slot, self._consumption_kWh.get_value(time_slot)
Expand Down
93 changes: 67 additions & 26 deletions src/gsy_e/models/strategy/energy_parameters/heat_pump_tank.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.models.strategy.state import HeatPumpTankState
from gsy_e.models.strategy.energy_parameters.heatpump_constants import (
WATER_SPECIFIC_HEAT_CAPACITY,
WATER_DENSITY,
SPECIFIC_HEAT_CONST_WATER,
)
from gsy_e.models.strategy.energy_parameters.virtual_heatpump_solver import (
TankSolverParameters,
VirtualHeatpumpSolverParameters,
Expand All @@ -17,20 +22,19 @@
logger = logging.getLogger(__name__)


WATER_SPECIFIC_HEAT_CAPACITY = 4182 # [J/kg°C]
SPECIFIC_HEAT_CONST_WATER = 0.00116 # [kWh / (K * kg)]
WATER_DENSITY = 1 # [kg / l]


@dataclass
class TankParameters:
"""Nameplate parameters of a water tank."""

min_temp_C: float = ConstSettings.HeatPumpSettings.MIN_TEMP_C
max_temp_C: float = ConstSettings.HeatPumpSettings.MAX_TEMP_C
initial_temp_C: float = ConstSettings.HeatPumpSettings.INIT_TEMP_C
tank_volume_L: float = ConstSettings.HeatPumpSettings.TANK_VOL_L


class TankEnergyParameters:
"""Manage the operation of heating and extracting temperature from a single tank."""

def __init__(self, tank_parameters: TankParameters, slot_length):
self._parameters = tank_parameters
self._state = HeatPumpTankState(
Expand All @@ -40,34 +44,31 @@ def __init__(self, tank_parameters: TankParameters, slot_length):
max_storage_temp_C=tank_parameters.max_temp_C,
)

def max_desired_temp_diff(self, time_slot):
return (
self._parameters.max_temp_C
- self._state.get_storage_temp_C(time_slot)
+ self._state.get_temp_decrease_K(time_slot)
)

@property
def _Q_specific(self):
return SPECIFIC_HEAT_CONST_WATER * self._parameters.tank_volume_L * WATER_DENSITY

def serialize(self):
"""Serializable dict with the parameters of the water tank."""
return {
"max_temp_C": self._parameters.max_temp_C,
"min_temp_C": self._parameters.min_temp_C,
"tank_volume_l": self._parameters.tank_volume_L,
}

def increase_tank_temp(self, heat_energy: float, time_slot: DateTime):
def increase_tank_temp_from_heat_energy(self, heat_energy: float, time_slot: DateTime):
"""Increase the temperature of the water tank with the provided heat energy."""
temp_increase_K = self._Q_kWh_to_temp_diff(heat_energy)
self._state.update_temp_increase_K(time_slot, temp_increase_K)

def decrease_tank_temp(self, heat_energy: float, time_slot: DateTime):
def decrease_tank_temp_from_heat_energy(self, heat_energy: float, time_slot: DateTime):
"""Decrease the temperature of the water tank with the provided heat energy."""
temp_decrease_K = self._Q_kWh_to_temp_diff(heat_energy)
self._state.set_temp_decrease_K(time_slot, temp_decrease_K)

def update_tank_temp_vhp(self, temp_diff: float, time_slot: DateTime):
return temp_diff + self._state.get_temp_decrease_K(time_slot)
def increase_tank_temp_from_temp_delta(self, temp_diff: float, time_slot: DateTime):
"""Increase the tank temperature from temperature delta."""
self._state.update_temp_increase_K(time_slot, temp_diff)

def decrease_tank_temp_vhp(self, heat_energy: float, time_slot: DateTime):
"""
Expand All @@ -91,6 +92,7 @@ def decrease_tank_temp_vhp(self, heat_energy: float, time_slot: DateTime):
return False

def get_max_energy_consumption(self, cop: float, time_slot: DateTime):
"""Calculate max energy consumption that a heatpump with provided COP can consume."""
max_desired_temp_diff = (
self._parameters.max_temp_C
- self._state.get_storage_temp_C(time_slot)
Expand All @@ -99,23 +101,29 @@ def get_max_energy_consumption(self, cop: float, time_slot: DateTime):
return max_desired_temp_diff * self._Q_specific / cop

def get_min_energy_consumption(self, cop: float, time_slot: DateTime):
"""Calculate min energy consumption that a heatpump with provided COP can consume."""
return self._state.get_temp_decrease_K(time_slot) * self._Q_specific / cop

def current_tank_temperature(self, time_slot: DateTime) -> float:
"""Get current tank temperature for timeslot."""
return self._state.get_storage_temp_C(time_slot)

def get_unmatched_demand_kWh(self, time_slot: DateTime) -> float:
"""Get unmatched demand for timeslot."""
temp_balance = self._state.get_temp_increase_K(
time_slot
) - self._state.get_temp_decrease_K(time_slot)
if temp_balance < FLOATING_POINT_TOLERANCE:
return self._temp_diff_to_Q_kWh(abs(temp_balance))
else:
return 0.0
return 0.0

def create_tank_parameters_for_maintaining_tank_temp(
self, time_slot: DateTime
) -> TankSolverParameters:
"""
Return tank solver parameters, that can be used by the solver to maintain the current tank
temp.
"""
current_storage_temp_C = self._state.get_storage_temp_C(time_slot)
target_storage_temp_C = current_storage_temp_C
if not self._parameters.min_temp_C < target_storage_temp_C < self._parameters.max_temp_C:
Expand All @@ -138,6 +146,10 @@ def create_tank_parameters_for_maintaining_tank_temp(
def create_tank_parameters_for_maxing_tank_temp(
self, time_slot: DateTime
) -> TankSolverParameters:
"""
Return tank solver parameters, that can be used by the solver to increase the current tank
temp to its maximum.
"""
current_storage_temp_C = self._state.get_storage_temp_C(time_slot)
target_storage_temp_C = self._parameters.max_temp_C
return TankSolverParameters(
Expand All @@ -147,6 +159,10 @@ def create_tank_parameters_for_maxing_tank_temp(
)

def create_tank_parameters_without_target_tank_temp(self, time_slot: DateTime):
"""
Create tank parameters without specifying tank temp, in order to be used as an output of
the solver.
"""
current_storage_temp_C = self._state.get_storage_temp_C(time_slot)
return TankSolverParameters(
tank_volume_L=self._parameters.tank_volume_L,
Expand All @@ -161,21 +177,25 @@ def _Q_kWh_to_temp_diff(self, energy_kWh: float) -> float:


class AllTanksEnergyParameters:
"""Manage the operation of heating and extracting temperature from multiple tanks."""

def __init__(self, tank_parameters: List[TankParameters]):
self._tanks_energy_parameters = [
TankEnergyParameters(tank, GlobalConfig.slot_length) for tank in tank_parameters
]

def increase_tanks_temp(self, heat_energy: float, time_slot: DateTime):
def increase_tanks_temp_from_heat_energy(self, heat_energy: float, time_slot: DateTime):
"""Increase the temperature of the water tanks with the provided heat energy."""
# Split heat energy equally across tanks
heat_energy_per_tank = heat_energy / len(self._tanks_energy_parameters)
for tank in self._tanks_energy_parameters:
tank.increase_tank_temp(heat_energy_per_tank, time_slot)
tank.increase_tank_temp_from_heat_energy(heat_energy_per_tank, time_slot)

def set_temp_decrease(self, heat_energy: float, time_slot: DateTime):
def decrease_tanks_temp_from_heat_energy(self, heat_energy: float, time_slot: DateTime):
"""Decrease the temperature of the water tanks with the provided heat energy."""
heat_energy_per_tank = heat_energy / len(self._tanks_energy_parameters)
for tank in self._tanks_energy_parameters:
tank.decrease_tank_temp(heat_energy_per_tank, time_slot)
tank.decrease_tank_temp_from_heat_energy(heat_energy_per_tank, time_slot)

def set_temp_decrease_vhp(
self, heat_energy: float, time_slot: DateTime
Expand All @@ -193,14 +213,20 @@ def set_temp_decrease_vhp(
# temp to target tank temp ignores the energy that is already consumed from the heatpump
# and the corresponding temp increase.
if any(unmatched_heat_demand):
return self.create_tank_parameters_for_maintaining_tank_temperature(time_slot)
return self.create_tank_solver_for_maintaining_tank_temperature(time_slot)
return []

def update_tanks_temperature(self, time_slot: DateTime):
"""
Update the current temperature of all tanks, based on temp increase/decrease of the market
slot.
"""
for tank in self._tanks_energy_parameters:
# pylint: disable=protected-access
tank._state.update_storage_temp(time_slot)

def get_max_energy_consumption(self, cop: float, time_slot: DateTime):
"""Get max energy consumption from all water tanks."""
max_energy_consumption_kWh = sum(
tank.get_max_energy_consumption(cop, time_slot)
for tank in self._tanks_energy_parameters
Expand All @@ -209,6 +235,7 @@ def get_max_energy_consumption(self, cop: float, time_slot: DateTime):
return max_energy_consumption_kWh

def get_min_energy_consumption(self, cop: float, time_slot: DateTime):
"""Get min energy consumption from all water tanks."""
min_energy_consumption_kWh = sum(
tank.get_min_energy_consumption(cop, time_slot)
for tank in self._tanks_energy_parameters
Expand All @@ -217,25 +244,32 @@ def get_min_energy_consumption(self, cop: float, time_slot: DateTime):
return min_energy_consumption_kWh

def get_average_tank_temperature(self, time_slot: DateTime):
"""Get average tank temperature of all water tanks."""
return mean(
tank.current_tank_temperature(time_slot) for tank in self._tanks_energy_parameters
)

def get_unmatched_demand_kWh(self, time_slot: DateTime):
"""Get unmatched demand of all water tanks."""
return sum(
tank.get_unmatched_demand_kWh(time_slot) for tank in self._tanks_energy_parameters
)

def serialize(self) -> Union[Dict, List]:
"""Serializable dict with the parameters of all water tanks."""
if len(self._tanks_energy_parameters) == 1:
# Return a dict for the case of one tank, in order to not break other services that
# support one single tank.
return self._tanks_energy_parameters[0].serialize()
return [tank.serialize() for tank in self._tanks_energy_parameters]

def create_tank_parameters_for_maintaining_tank_temperature(
def create_tank_solver_for_maintaining_tank_temperature(
self, time_slot: DateTime
) -> List[TankSolverParameters]:
"""
Return tank solver parameters for all water tanks, that can be used by the solver to
maintain all current tanks' temp.
"""
return [
tank.create_tank_parameters_for_maintaining_tank_temp(time_slot)
for tank in self._tanks_energy_parameters
Expand All @@ -244,14 +278,21 @@ def create_tank_parameters_for_maintaining_tank_temperature(
def create_tank_parameters_for_maxing_tank_temperature(
self, time_slot: DateTime
) -> List[TankSolverParameters]:
"""
Return tank solver parameters for all water tanks, that can be used by the solver to
increase all tanks' temp to its maximum.
"""
return [
tank.create_tank_parameters_for_maxing_tank_temp(time_slot)
for tank in self._tanks_energy_parameters
]

def update_tanks_temperature_with_energy(
def increase_tanks_temperature_with_energy_vhp(
self, heatpump_parameters: VirtualHeatpumpSolverParameters, time_slot: DateTime
) -> HeatpumpStorageEnergySolver:
"""
Increase tank temperature from energy, provided as part of the heatpump parameters.
"""
tank_parameters = [
tank.create_tank_parameters_without_target_tank_temp(time_slot)
for tank in self._tanks_energy_parameters
Expand All @@ -263,7 +304,7 @@ def update_tanks_temperature_with_energy(
logger.debug(solver)

for tank_index, tank_output in enumerate(solver.tank_parameters):
self._tanks_energy_parameters[tank_index].update_tank_temp_vhp(
self._tanks_energy_parameters[tank_index].increase_tank_temp_from_temp_delta(
tank_output.target_storage_temp_C - tank_output.current_storage_temp_C, time_slot
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
WATER_SPECIFIC_HEAT_CAPACITY = 4182 # [J/kg°C]
SPECIFIC_HEAT_CONST_WATER = 0.00116 # [kWh / (K * kg)]
WATER_DENSITY = 1 # [kg / l]

GROUND_WATER_TEMPERATURE_C = 12
Loading

0 comments on commit 277f626

Please sign in to comment.