Source code for buildingmodel.models.thermal_losses

import datetime
import pandas as pd
import numpy as np
import polars as pl

from ..logger import duration_logging
from ..utils import heating_season, add_parameter_from_building, CP_AIR, RHO_AIR
from ..creation.boundary import EXTERIOR_WALL, INTERIOR_WALL, ROOF, FLOOR


[docs] @duration_logging def unified_degree_hours(buildings, boundaries, climate, parameters): """ Calculates the unified degree hours for each boundary and building. Unified degree hours are obtained by taking the sum of the difference between the heating set point and the exterior temperature for each hour of the heating season during which the exterior temperature is below the heating set point. For walls, roofs and buildings, the exterior temperature is the air temperature. For floors, it is the ground temperature. Args: buildings (GeoDataframe): a GeoDataframe containing the building geometries boundaries (GeoDataframe): a GeoDataframe containing the boundary geometries climate (Dataframe): a Dataframe containing climate data heating_season_start (datetime.time): start of the heating season heating_season_end (datetime.time): end of the heating season Returns: """ c_hs = parameters.conventional_heating_set_point a_hs = parameters.actual_heating_set_point buildings = buildings.with_columns( conventional_heating_set_point=pl.lit(c_hs), actual_heating_set_point=pl.lit(a_hs), ) boundaries = boundaries.with_columns( conventional_heating_set_point=pl.lit(c_hs), actual_heating_set_point=pl.lit(a_hs), ) # we select the temperatures during the heating season heating_season_index = heating_season( climate, parameters.heating_season_start, parameters.heating_season_end ) air_temperature = climate.loc[heating_season_index, "air_temperature"] ground_temperature = climate.loc[heating_season_index, "ground_temperature"] # Calculation is done for unique values of the heating set points and then applied to the corresponding boundaries # and buildings for mode in ["conventional", "actual"]: set_point_variable = f"{mode}_heating_set_point" udh_variable = f"{mode}_unified_degree_hours" unique_set_points = boundaries[set_point_variable].unique() udh_boundaries = np.zeros(boundaries.shape[0]) udh_buildings = np.zeros(buildings.shape[0]) for set_point in unique_set_points: air_unified_degree_hour = ( set_point - air_temperature[air_temperature < set_point] ).sum() mask = (boundaries["type"].is_in([EXTERIOR_WALL, INTERIOR_WALL, ROOF])) & ( boundaries[set_point_variable] == set_point ) udh_boundaries[mask] = air_unified_degree_hour mask = buildings[set_point_variable] == set_point udh_buildings[mask] = air_unified_degree_hour ground_unified_degree_hour = ( set_point - ground_temperature[ground_temperature < set_point] ).sum() mask = (boundaries["type"].is_in([FLOOR])) & ( boundaries[set_point_variable] == set_point ) udh_boundaries[mask] = ground_unified_degree_hour boundaries = boundaries.with_columns(**{udh_variable: udh_boundaries}) buildings = buildings.with_columns(**{udh_variable: udh_buildings}) buildings = buildings.with_columns( heating_season_duration=pl.lit(heating_season_index.shape[0]) ) return buildings, boundaries
[docs] @duration_logging def maximal_temperature_difference(buildings, boundaries, climate): """ Calculates the maximal temperature difference between the interior of a building and the exterior air during the heating period Args: boundaries: climate: Returns: """ min_air_temperature = climate["air_temperature"].min() min_ground_temperature = climate["ground_temperature"].min() boundaries = boundaries.with_columns( min_temperature=pl.when( pl.col("type").is_in([EXTERIOR_WALL, INTERIOR_WALL, ROOF]) ) .then(pl.lit(min_air_temperature)) .otherwise(pl.lit(min_ground_temperature)) ).with_columns( maximal_temperature_difference=( pl.col("actual_heating_set_point") - pl.col("min_temperature") ).clip(lower_bound=0.0) ) buildings = buildings.with_columns( maximal_temperature_difference=( pl.col("actual_heating_set_point") - min_air_temperature ).clip(lower_bound=0.0) ) return buildings, boundaries
[docs] @duration_logging def adjacency_factor(boundaries): """ Calculates a factor to apply to a boundary to model the share of losses due to adjacency. Its value is 1. for exterior walls, roofs and floors (no loss prevented), 0.2 for interior walls when the other use is residential, 0.8 for commercial use and 1. for other uses. Args: boundaries: Returns: """ exterior_bnds = boundaries["type"].is_in([EXTERIOR_WALL, ROOF, FLOOR]) interior_bnds = boundaries["type"].is_in([INTERIOR_WALL]) boundaries = boundaries.with_columns( adjacency_factor=pl.when(exterior_bnds) .then(pl.lit(1.0)) .when( interior_bnds & (boundaries["adjacency_usage"].cast(pl.String) == "residential") ) .then(pl.lit(0.2)) .when( interior_bnds & (boundaries["adjacency_usage"].cast(pl.String) == "commercial") ) .then(pl.lit(0.5)) .otherwise(pl.lit(1.0)) ) return boundaries
[docs] @duration_logging def boundary_losses(boundaries): """Calculates the annual boundary losses by multiplying the U values of the boundaries with their area and unified degree hours. Calculates the peak boundary losses by multiplying the U values of the boundaries with their area and maximal temperature difference. Args: boundaries (GeoDataframe): a GeoDataframe containing the boundary geometries and parameters Returns: """ boundaries = boundaries.with_columns( loss_factor=pl.col("u_value") * pl.col("opaque_area") + pl.col("window_u_value") * pl.col("window_area") ) boundaries = boundaries.with_columns( annual_thermal_losses=pl.col("loss_factor") * pl.col("adjacency_factor") * pl.col("actual_unified_degree_hours") / 1000.0, conventional_thermal_losses=pl.col("loss_factor") * pl.col("adjacency_factor") * pl.col("conventional_unified_degree_hours") / 1000.0, peak_thermal_losses=pl.col("loss_factor") * pl.col("adjacency_factor") * pl.col("maximal_temperature_difference"), ) return boundaries
[docs] @duration_logging def ventilation_losses(buildings): """Calculates the ventilation losses for each building by multiplying the unified degree hours by the air change rate, the volume and the heat capacity of the air Args: buildings: Returns: """ buildings = buildings.with_columns( volume=pl.col("height") * pl.col("floor_area") / pl.col("floor_count") ) buildings = buildings.with_columns( annual_ventilation_losses=pl.col("volume") * pl.col("air_change_rate") * pl.col("actual_unified_degree_hours") * CP_AIR * RHO_AIR / (3600.0 * 1000.0), conventional_ventilation_losses=pl.col("volume") * pl.col("air_change_rate") * pl.col("conventional_unified_degree_hours") * CP_AIR * RHO_AIR / (3600.0 * 1000.0), peak_ventilation_losses=pl.col("volume") * pl.col("air_change_rate") * pl.col("maximal_temperature_difference") * CP_AIR * RHO_AIR / 3600.0, ) return buildings
[docs] def run_models(buildings, boundaries, climate, parameters): """ run the models to calculate the thermal losses of a building (ventilation and boundaries) Args: buildings (GeoDataframe): a GeoDataframe containing the building geometries and parameters boundaries (GeoDataframe): a GeoDataframe containing the boundary geometries and parameters climate (Dataframe): a Dataframe containing climate data parameters Returns: """ buildings, boundaries = unified_degree_hours( buildings, boundaries, climate, parameters ) buildings, boundaries = maximal_temperature_difference( buildings, boundaries, climate ) boundaries = adjacency_factor(boundaries) boundaries = boundary_losses(boundaries) buildings = ventilation_losses(buildings) return buildings, boundaries