Source code for project.thermal

# Copyright 2020-2021 Ecole Nationale des Ponts et Chaussées
#
# This file is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# Original author Lucas Vivier <vivier@centre-cired.fr>
import numpy as np
import pandas as pd

from numpy import log
from project.utils import reindex_mi, get_pandas
import os
from datetime import timedelta

"""LOGISTIC_COEFFICIENT = pd.read_csv('project/input/logistic_regression_coefficient_epc.csv', index_col=[0])
LOGISTIC_COEFFICIENT.columns = ['Intercept', 'Proxy_conso_square']
LOGISTIC_COEFFICIENT.index.names = ['Performance']"""
CONVERSION = 2.3
HDD = 55706
CERTIFICATE_3USES_BOUNDARIES = {
    'A': [0, 50],
    'B': [50, 90],
    'C': [90, 150],
    'D': [150, 230],
    'E': [230, 330],
    'F': [330, 450],
    'G': [450, 1000],
}

# kWhPE/m2.a
CERTIFICATE_5USES_BOUNDARIES_ENERGY = {
    'A': [0, 70],
    'B': [70, 110],
    'C': [110, 180],
    'D': [180, 250],
    'E': [250, 330],
    'F': [330, 420],
    'G': [420, 1000],
}
# kgCO2/m2.a
CERTIFICATE_5USES_BOUNDARIES_EMISSION = {
    'A': [0, 6],
    'B': [6, 11],
    'C': [11, 30],
    'D': [30, 50],
    'E': [50, 70],
    'F': [70, 100],
    'G': [100, 1000],
}


# Transmission heat losses
THERMAL_BRIDGING = {'Minimal': 0,
                    'Low': 0.05,
                    'Medium': 0.1,
                    'High': 0.15}
FACTOR_SOIL = 0.5

# Ventilation heat losses
HEAT_CAPACITY_AIR = 0.34 # Wh/m3.K

AIR_TIGHTNESS_INFILTRATION = {'Minimal': 0.05,
                              'Low': 0.1,
                              'Medium': 0.2,
                              'High': 0.5}

ROOM_HEIGHT = 2.5 # m
VENTILATION_TYPES = {'Ventilation naturelle': 0.4,
                     'VMC SF auto et VMC double flux': 0.3,
                     'VMC SF hydrogérable': 0.2}

# Heat transfer
FACTOR_NON_UNIFORM = 0.9
TEMP_INDOOR = 19 #°C
TEMP_OUTDOOR_PEAK = -6

# Solar heat load
FACTOR_SHADING = 0.6
FACTOR_FRAME = 0.3
FACTOR_NON_PERPENDICULAR = 0.9
# see Methode 3CL
ORIENTATION_FACTOR = {'South': 1.1, 'West': 0.57, 'Est': 0.57, 'North': 0.2}
ORIENTATION_FACTOR['Mean'] = (ORIENTATION_FACTOR['South'] + ORIENTATION_FACTOR['West'] + ORIENTATION_FACTOR['Est'] + ORIENTATION_FACTOR['North']) / 4

# Internal heat sources
INTERNAL_HEAT_SOURCES = 4.17 # W/m2

# Gain factor
INTERNAL_HEAT_CAPACITY = 45 # Wh/m2.K
A_0 = 0.8
TAU_0 = 30

FACTOR_TABULA_3CL = 0.9

# Climatic data
TEMP_BASE = 12 # °C
# HDD_EQ = (temp_indoor - TEMP_EXT_3CL) * 24 * DAYS_HEATING_SEASON
SOLAR_ENERGY_TRANSMITTANCE = 0.62 # data to check

TEMP_EXT_3CL = 7.1 # °C
DAYS_HEATING_SEASON_3CL = 209
SOLAR_RADIATION_3CL = 306.4 # kWh/m2.an

DHW_NEED = pd.Series([15.3, 19.8], index=pd.Index(['Single-family',	'Multi-family'], name='Housing type')) # kWh/m2.a
DHW_EFFICIENCY = {'Electricity-Direct electric': 0.95,
                 'Electricity-Wood stove': 0.95,
                  'Electricity-Heat pump air': 0.95,
                  'Electricity-Heat pump': 2.5,
                  'Electricity-Heat pump water': 2.5,
                  'Natural gas-Performance boiler': 0.6,
                  'Natural gas-Standard boiler': 0.6,
                  'Natural gas-Collective boiler': 0.6,
                  'Oil fuel-Performance boiler': 0.6,
                  'Oil fuel-Standard boiler': 0.6,
                  'Oil fuel-Collective boiler': 0.6,
                  'Wood fuel-Performance boiler': 0.6,
                  'Wood fuel-Standard boiler': 0.6,
                  'Heating-District heating': 0.6
                  }

# kWh/m2.a from EDF
AUXILIARY_CONSUMPTION = {'Electricity-Direct electric': 1.4,
                         'Electricity-Wood stove': 1.4,
                         'Electricity-Heat pump air': 2.17,
                         'Electricity-Heat pump': 2.17,
                         'Electricity-Heat pump water': 2.17,
                         'Natural gas-Performance boiler': 3.65,
                         'Natural gas-Standard boiler': 3.65,
                         'Natural gas-Collective boiler': 3.65,
                         'Oil fuel-Performance boiler': 3.83,
                         'Oil fuel-Standard boiler': 3.83,
                         'Oil fuel-Collective boiler': 3.83,
                         'Wood fuel-Performance boiler': 2.07,
                         'Wood fuel-Standard boiler': 2.07,
                         'Heating-District heating': 1.75
                         }
AUXILIARY_CONSUMPTION = 3

CARBON_CONTENT = {'Electricity': 0.079,
                  'Natural gas': 0.227,
                  'Oil fuel': 0.324,
                  'Wood fuel': 0.03,
                  'Heating': 0.227
                  }
CARBON_CONTENT = {'Electricity-Direct electric': 0.079,
                    'Electricity-Wood stove': 0.079,
                  'Electricity-Heat pump air': 0.079,
                  'Electricity-Heat pump': 0.079,
                  'Electricity-Heat pump water': 0.079,
                  'Natural gas-Performance boiler': 0.227,
                  'Natural gas-Standard boiler': 0.227,
                  'Natural gas-Collective boiler': 0.227,
                  'Oil fuel-Performance boiler': 0.324,
                  'Oil fuel-Standard boiler': 0.324,
                  'Oil fuel-Collective boiler': 0.324,
                  'Wood fuel-Performance boiler': 0.03,
                  'Wood fuel-Standard boiler': 0.03,
                  'Heating-District heating': 0.03
                  }
C_LIGHT = 0.9
P_LIGHT = 1.4 # W/m2
HOURS_LIGHT = 1500 # h
CONSUMPTION_LIGHT = (C_LIGHT * P_LIGHT * HOURS_LIGHT) / 1e3 # kWh/m2.a

HOURLY_PROFILE_POWER = pd.Series(
    [0.035, 0.039, 0.041, 0.042, 0.046, 0.05, 0.055, 0.058, 0.053, 0.049, 0.045, 0.041, 0.037, 0.034,
     0.03, 0.033, 0.037, 0.042, 0.046, 0.041, 0.037, 0.034, 0.033, 0.042], index=pd.TimedeltaIndex(range(0, 24), unit='h'))

HOURLY_PROFILE_FOSSIL = pd.Series(
    [0.04, 0.038, 0.039, 0.04, 0.041, 0.042, 0.044, 0.046, 0.048, 0.045, 0.043, 0.04, 0.037, 0.033, 0.034, 0.036, 0.039,
     0.043, 0.045, 0.046, 0.047, 0.046, 0.045, 0.043], index=pd.TimedeltaIndex(range(0, 24), unit='h'))

TEMP_SINK = 55

CLIMATE_DATA = {'year': os.path.join('project', 'input', 'climatic', 'climatic_data.csv'),
                'month': os.path.join('project', 'input', 'climatic', 'climatic_data_month.csv'),
                'day': os.path.join('project', 'input', 'climatic', 'climatic_data_daily.csv'),
                'hour': os.path.join('project', 'input', 'climatic', 'climatic_data_daily.csv'),
                'smooth_day': os.path.join('project', 'input', 'climatic', 'climatic_data_smooth_daily.csv')
                }


GAIN_UTILIZATION_FACTOR = False


[docs]def size_heating_system(u_wall, u_floor, u_roof, u_windows, ratio_surface, th_bridging='Medium', vent_types='Ventilation naturelle', infiltration='Medium', air_rate=None, unobserved=None, temp_indoor=None): """Seasonal method for space heating need. We apply a seasonal method according to EN ISO 13790 to estimate annual space heating demand by building type. The detailed calculation can be found in the TABULA project documentation (Loga, 2013). In a nutshell, the energy need for heating is the difference between the heat losses and the heat gain. The total heat losses result from heat transfer by transmission and ventilation during the heating season respectively proportional to the heat transfer coefficient $H_tr$ and $H_ve$. To not consider gain_utilization_factor create a difference of 5%. For consistency between results and Parameters ---------- u_wall: pd.Series Index should include Housing type {'Single-family', 'Multi-family'}. u_floor: pd.Series u_roof: pd.Series u_windows: pd.Series ratio_surface: pd.Series th_bridging: {'Minimal', 'Low', 'Medium', 'High'}, default None vent_types: {'Ventilation naturelle', 'VMC SF auto et VMC double flux', 'VMC SF hydrogérable'}, default None infiltration: {'Minimal', 'Low', 'Medium', 'High'}, default None air_rate: pd.Series, default None unobserved: {'Minimal', 'High'}, default None temp_indoor: optional, default temp_indoor Returns ------- Conventional heating need (kWh/m2.a) """ if temp_indoor is None: temp_indoor = TEMP_INDOOR if unobserved == 'Minimal': th_bridging = 'Minimal' vent_types = 'VMC SF hydrogérable' infiltration = 'Minimal' elif unobserved == 'High': th_bridging = 'High' vent_types = 'Ventilation naturelle' infiltration = 'High' surface_components = ratio_surface.copy() df = pd.concat([u_wall, u_floor, u_roof, u_windows], axis=1, keys=['Wall', 'Floor', 'Roof', 'Windows']) surface_components.loc[:, 'Floor'] *= FACTOR_SOIL surface_components = reindex_mi(surface_components, df.index) coefficient_transmission_transfer = (surface_components * df).sum(axis=1) coefficient_transmission_transfer += surface_components.sum(axis=1) * THERMAL_BRIDGING[th_bridging] if air_rate is None: air_rate = VENTILATION_TYPES[vent_types] + AIR_TIGHTNESS_INFILTRATION[infiltration] coefficient_ventilation_transfer = HEAT_CAPACITY_AIR * air_rate * ROOM_HEIGHT heat_transfer_coefficient = coefficient_ventilation_transfer + coefficient_transmission_transfer size = heat_transfer_coefficient * (temp_indoor - TEMP_OUTDOOR_PEAK) return size
[docs]def conventional_heating_need(u_wall, u_floor, u_roof, u_windows, ratio_surface, th_bridging='Medium', vent_types='Ventilation naturelle', infiltration='Medium', air_rate=None, unobserved=None, climate=None, smooth=False, freq='year', hourly_profile=None, temp_indoor=None, gain_utilization_factor=GAIN_UTILIZATION_FACTOR, ): """Seasonal method for space heating need. We apply a seasonal method according to EN ISO 13790 to estimate annual space heating demand by building type. The detailed calculation can be found in the TABULA project documentation (Loga, 2013). In a nutshell, the energy need for heating is the difference between the heat losses and the heat gain. The total heat losses result from heat transfer by transmission and ventilation during the heating season respectively proportional to the heat transfer coefficient $H_tr$ and $H_ve$. To not consider gain_utilization_factor create a difference of 5%. For consistency between results and Parameters ---------- u_wall: pd.Series Index should include Housing type {'Single-family', 'Multi-family'}. u_floor: pd.Series u_roof: pd.Series u_windows: pd.Series ratio_surface: pd.Series th_bridging: {'Minimal', 'Low', 'Medium', 'High'}, default None vent_types: {'Ventilation naturelle', 'VMC SF auto et VMC double flux', 'VMC SF hydrogérable'}, default None infiltration: {'Minimal', 'Low', 'Medium', 'High'}, default None air_rate: pd.Series, default None unobserved: {'Minimal', 'High'}, default None climate: int, default None Climatic year to use to calculate heating need. smooth: bool, default False Use smooth daily data to calculate heating need. freq hourly_profile: optional, pd.Series temp_indoor: optional, default temp_indoor gain_utilization_factor: bool, default False If False, for simplification we use gain_utilization_factor = 1. Returns ------- Conventional heating need (kWh/m2.a) """ temp_ext = TEMP_EXT_3CL days_heating_season = DAYS_HEATING_SEASON_3CL solar_radiation = SOLAR_RADIATION_3CL if temp_indoor is None: temp_indoor = TEMP_INDOOR if climate is not None: if freq == 'year': data = get_pandas(CLIMATE_DATA['year'], func=lambda x: pd.read_csv(x, index_col=[0], parse_dates=True)) temp_ext = float(data.loc[data.index.year == climate, 'TEMP_EXT']) days_heating_season = float(data.loc[data.index.year == climate, 'DAYS_HEATING_SEASON']) solar_radiation = float(data.loc[data.index.year == climate, 'SOLAR_RADIATION']) # print(temp_ext, days_heating_season, solar_radiation) else: path = CLIMATE_DATA[freq] if smooth: path = CLIMATE_DATA['smooth_day'] data = get_pandas(path, func=lambda x: pd.read_csv(x, index_col=[0], parse_dates=True)) temp_ext = data.loc[data.index.year == climate, 'TEMP_EXT'].rename(None) days_heating_season = data.loc[data.index.year == climate, 'DAYS_HEATING_SEASON'].rename(None) days_heating_season = days_heating_season.replace({True: 1, False: float('nan')}) solar_radiation = data.loc[data.index.year == climate, 'SOLAR_RADIATION'].rename(None) if unobserved == 'Minimal': th_bridging = 'Minimal' vent_types = 'VMC SF hydrogérable' infiltration = 'Minimal' elif unobserved == 'High': th_bridging = 'High' vent_types = 'Ventilation naturelle' infiltration = 'High' surface_components = ratio_surface.copy() df = pd.concat([u_wall, u_floor, u_roof, u_windows], axis=1, keys=['Wall', 'Floor', 'Roof', 'Windows']) surface_components.loc[:, 'Floor'] *= FACTOR_SOIL surface_components = reindex_mi(surface_components, df.index) coefficient_transmission_transfer = (surface_components * df).sum(axis=1) coefficient_transmission_transfer += surface_components.sum(axis=1) * THERMAL_BRIDGING[th_bridging] if air_rate is None: air_rate = VENTILATION_TYPES[vent_types] + AIR_TIGHTNESS_INFILTRATION[infiltration] coefficient_ventilation_transfer = HEAT_CAPACITY_AIR * air_rate * ROOM_HEIGHT heat_transfer_coefficient = coefficient_ventilation_transfer + coefficient_transmission_transfer solar_coefficient = FACTOR_SHADING * (1 - FACTOR_FRAME) * FACTOR_NON_PERPENDICULAR * SOLAR_ENERGY_TRANSMITTANCE * surface_components.loc[:, 'Windows'] coefficient = 24 / 1000 * FACTOR_NON_UNIFORM * days_heating_season coefficient_climatic = coefficient * (temp_indoor - temp_ext) internal_heat_sources = 24 / 1000 * INTERNAL_HEAT_SOURCES * days_heating_season if freq == 'year': heat_transfer = heat_transfer_coefficient * coefficient_climatic solar_load = solar_coefficient * solar_radiation heat_gains = solar_load + internal_heat_sources if gain_utilization_factor is True: time_constant = INTERNAL_HEAT_CAPACITY / (coefficient_transmission_transfer + coefficient_ventilation_transfer) a_h = A_0 + time_constant / TAU_0 heat_balance_ratio = (internal_heat_sources + solar_load) / heat_transfer gain_utilization_factor = (1 - heat_balance_ratio ** a_h) / (1 - heat_balance_ratio ** (a_h + 1)) else: gain_utilization_factor = 1 heat_need = (heat_transfer - heat_gains * gain_utilization_factor) * FACTOR_TABULA_3CL return heat_need else: heat_transfer = heat_transfer_coefficient.rename(None).to_frame().dot(coefficient_climatic.to_frame().T) solar_load = solar_coefficient.rename(None).to_frame().dot(solar_radiation.to_frame().T) heat_gains = solar_load + internal_heat_sources if gain_utilization_factor is True: time_constant = INTERNAL_HEAT_CAPACITY / (coefficient_transmission_transfer + coefficient_ventilation_transfer) a_h = A_0 + time_constant / TAU_0 # average over the month heat_balance_ratio = heat_gains.groupby(heat_gains.columns.month, axis=1).sum() / heat_transfer.groupby(heat_transfer.columns.month, axis=1).sum() gain_utilization_factor = (1 - (heat_balance_ratio.T ** a_h).T) / (1 - (heat_balance_ratio.T ** (a_h + 1)).T) temp = [] for i in gain_utilization_factor.columns.astype(str): if len(i) == 1: i = '0{}'.format(i) temp.append(i) gain_utilization_factor.columns = [np.datetime64('{}-{}'.format(climate, i), 'D') for i in temp] gain_utilization_factor = gain_utilization_factor.reindex(heat_gains.columns, axis=1, method='pad') else: gain_utilization_factor = 1 heat_need = ((heat_transfer - heat_gains * gain_utilization_factor) * FACTOR_TABULA_3CL).fillna(0) heat_need = heat_need.stack(heat_need.columns.names) if freq == 'hour': if hourly_profile is None: hourly_profile = HOURLY_PROFILE_POWER heat_need = heat_need.to_frame().dot(hourly_profile.to_frame().T) heat_need = heat_need.unstack(['time']) heat_need.columns = heat_need.columns.get_level_values(None) + heat_need.columns.get_level_values('time') else: heat_need = heat_need.unstack(['time']) return heat_need.sort_index(axis=1)
[docs]def conventional_heating_final(u_wall, u_floor, u_roof, u_windows, ratio_surface, efficiency, th_bridging='Medium', vent_types='Ventilation naturelle', infiltration='Medium', air_rate=None, unobserved=None, climate=None, freq='year', smooth=False, temp_indoor=None, gain_utilization_factor=GAIN_UTILIZATION_FACTOR, efficiency_hour=False, hourly_profile=None, temp_sink=None): """Monthly stead-state space heating final energy delivered. Heat-pump formula come from Stafell et al., 2012. Parameters ---------- u_wall: pd.Series u_floor: pd.Series u_roof: pd.Series u_windows: pd.Series ratio_surface: pd.Series efficiency: pd.Series th_bridging: {'Minimal', 'Low', 'Medium', 'High'} vent_types: {'Ventilation naturelle', 'VMC SF auto et VMC double flux', 'VMC SF hydrogérable'} infiltration: {'Minimal', 'Low', 'Medium', 'High'} air_rate: default None unobserved: {'Minimal', 'High'}, default None climate: int, default None Climatic year to use to calculate heating need. freq smooth: bool, default False Use smooth daily data to calculate heating need. temp_indoor gain_utilization_factor: bool, default False If False, for simplification we use gain_utilization_factor = 1. efficiency_hour Returns ------- """ heat_need = conventional_heating_need(u_wall, u_floor, u_roof, u_windows, ratio_surface, th_bridging=th_bridging, vent_types=vent_types, infiltration=infiltration, air_rate=air_rate, unobserved=unobserved, climate=climate, freq=freq, smooth=smooth, temp_indoor=temp_indoor, gain_utilization_factor=gain_utilization_factor, hourly_profile=hourly_profile) if (freq == 'hour') and (efficiency_hour is True): path = CLIMATE_DATA[freq] if smooth: path = CLIMATE_DATA['smooth_day'] data = get_pandas(path, func=lambda x: pd.read_csv(x, index_col=[0], parse_dates=True)) temp_ext = data.loc[data.index.year == climate, 'TEMP_EXT'].rename(None) if temp_sink is None: temp_sink = TEMP_SINK delta_temperature = temp_sink - temp_ext # TODO replace 0 by nan efficiency_hp = 6.81 - 0.121 * delta_temperature + 0.00063 * (delta_temperature ** 2) heat_pumps = ['Electricity-Heat pump air', 'Electricity-Heat pump water'] efficiency = pd.concat([efficiency] * efficiency_hp.shape[0], axis=1, keys=efficiency_hp.index, names=efficiency_hp.index.names) idx = efficiency[efficiency.index.get_level_values('Heating system').isin(heat_pumps)].index for i in idx: efficiency.loc[i, :] = efficiency_hp efficiency = efficiency.reindex(heat_need.columns, method='pad', axis=1) if isinstance(heat_need, pd.Series): return heat_need / efficiency if isinstance(heat_need, pd.DataFrame): if isinstance(efficiency, pd.Series): return (heat_need.T / efficiency).T elif isinstance(efficiency, pd.DataFrame): return heat_need / efficiency
[docs]def conventional_dhw_final(index): """Calculate dhw final energy consumption. Parameters ---------- index: pd.MultiIndex Returns ------- """ efficiency = pd.Series(index.get_level_values('Heating system')).astype('object').replace(DHW_EFFICIENCY).set_axis(index, axis=0) dhw_need = DHW_NEED.reindex(index.get_level_values('Housing type')).set_axis(index, axis=0) return dhw_need / efficiency
[docs]def conventional_energy_3uses(u_wall, u_floor, u_roof, u_windows, ratio_surface, efficiency, index, th_bridging='Medium', vent_types='Ventilation naturelle', infiltration='Medium', air_rate=None, unobserved=None, method='3uses' ): """Space heating conventional, and energy performance certificate. Method before july 2021. Parameters ---------- u_wall: pd.Series u_floor: pd.Series u_roof: pd.Series u_windows: pd.Series ratio_surface: pd.Series efficiency: pd.Series index: pd.MultiIndex or pd.Index Should include Housing type and Heating system. th_bridging: {'Minimal', 'Low', 'Medium', 'High'} vent_types: {'Ventilation naturelle', 'VMC SF auto et VMC double flux', 'VMC SF hydrogérable'} infiltration: {'Minimal', 'Low', 'Medium', 'High'} air_rate: default None unobserved: {'Minimal', 'High'}, default None Returns ------- """ heating_final = conventional_heating_final(u_wall, u_floor, u_roof, u_windows, ratio_surface, efficiency, th_bridging=th_bridging, vent_types=vent_types, infiltration=infiltration, air_rate=air_rate, unobserved=unobserved ) dhw_final = conventional_dhw_final(index) ac_final = 0 energy_final = heating_final + dhw_final + ac_final if 'Energy' not in index.names: energy_carrier = index.get_level_values('Heating system').str.split('-').str[0].rename('Energy') else: energy_carrier = index.get_level_values('Energy') energy_primary = final2primary(energy_final, energy_carrier) other_consumptions = None if method == '5uses': other_consumptions = (CONSUMPTION_LIGHT + AUXILIARY_CONSUMPTION) * CONVERSION performance = find_certificate(energy_primary, method=method, other_consumptions=other_consumptions) # performance = pd.concat((performance_3uses, performance_5uses), axis=1, keys=['3uses', '5uses']) return performance, energy_primary
[docs]def find_certificate(primary_consumption, other_consumptions=None, method='3uses'): """Returns energy performance certificate from A to G. Parameters ---------- primary_consumption: float or pd.Series or pd.DataFrame Space heating energy consumption (kWh PE / m2.year) Returns ------- float or pd.Series or pd.DataFrame Energy performance certificate. """ if method == '3uses': if isinstance(primary_consumption, pd.Series): certificate = pd.Series(dtype=str, index=primary_consumption.index) for key, item in CERTIFICATE_3USES_BOUNDARIES.items(): cond = (primary_consumption > item[0]) & (primary_consumption <= item[1]) certificate[cond] = key return certificate elif isinstance(primary_consumption, pd.DataFrame): certificate = pd.DataFrame(dtype=str, index=primary_consumption.index, columns=primary_consumption.columns) for key, item in CERTIFICATE_3USES_BOUNDARIES.items(): cond = (primary_consumption > item[0]) & (primary_consumption <= item[1]) certificate[cond] = key return certificate elif isinstance(primary_consumption, float): for key, item in CERTIFICATE_3USES_BOUNDARIES.items(): if (primary_consumption > item[0]) & (primary_consumption <= item[1]): return key elif method == '5uses': carbon_content = pd.Series(CARBON_CONTENT).rename_axis('Heating system') emission = primary_consumption * reindex_mi(carbon_content, primary_consumption.index) primary_consumption += other_consumptions emission += other_consumptions * reindex_mi(carbon_content, primary_consumption.index) if isinstance(primary_consumption, pd.Series): certificate_energy = pd.Series(dtype=str, index=primary_consumption.index) for key, item in CERTIFICATE_5USES_BOUNDARIES_ENERGY.items(): cond = (primary_consumption > item[0]) & (primary_consumption <= item[1]) certificate_energy[cond] = key certificate_emission = pd.Series(dtype=str, index=primary_consumption.index) for key, item in CERTIFICATE_5USES_BOUNDARIES_EMISSION.items(): cond = (emission > item[0]) & (emission <= item[1]) certificate_emission[cond] = key # maximum between energy and emission temp = pd.concat([certificate_energy, certificate_emission], axis=1, keys=['Energy', 'Emission']) certificate = temp.max(axis=1) return certificate elif isinstance(primary_consumption, pd.DataFrame): raise NotImplementedError else: raise NotImplementedError
[docs]def final2primary(heat_consumption, energy, conversion=CONVERSION): if isinstance(heat_consumption, pd.Series): primary_heat_consumption = heat_consumption.copy() primary_heat_consumption[energy == 'Electricity'] = primary_heat_consumption * conversion return primary_heat_consumption elif isinstance(heat_consumption, float): if energy == 'Electricity': return heat_consumption * conversion else: return heat_consumption if isinstance(heat_consumption, pd.DataFrame): # index if energy.index.equals(heat_consumption.index): primary_heat_consumption = heat_consumption.copy() primary_heat_consumption.loc[energy == 'Electricity', :] = primary_heat_consumption * conversion return primary_heat_consumption # columns elif energy.index.equals(heat_consumption.columns): primary_heat_consumption = heat_consumption.copy() primary_heat_consumption.loc[:, energy == 'Electricity'] = primary_heat_consumption * conversion return primary_heat_consumption else: raise 'Energy DataFrame do not match indexes and columns'
[docs]def primary_heating_consumption(u_wall, u_floor, u_roof, u_windows, efficiency, energy, ratio_surface, hdd, conversion=CONVERSION): """Convert final to primary heating consumption. Parameters ---------- u_wall u_floor u_roof u_windows hdd efficiency energy ratio_surface conversion Returns ------- """ # data = pd.concat([u_wall, u_floor, u_roof, u_windows], axis=1, keys=['Wall', 'Floor', 'Roof', 'Windows']) heat_consumption = stat_heating_consumption(u_wall, u_floor, u_roof, u_windows, efficiency, ratio_surface, hdd) return final2primary(heat_consumption, energy, conversion=conversion)
[docs]def heat_intensity(budget, method='v4'): if method == 'v3': return -0.191 * budget.apply(log) + 0.1105 elif method == 'v4': return 0.3564 * budget**(-0.244)
[docs]def stat_model_heating_consumption(df, a=0.921323, b=0.634717): """Statistical model based on ADEME DPE data. X = (Deper_mur + Deper_baie + Deper_plancher + Deper_plafond) / Efficiency * DDH Conso = X ** 1.746973 * exp(1.536192) """ return (df ** a) * np.exp(b)
[docs]def stat_model_3uses_consumption(df, a=0.846102, b=1.029880): """Statistical model based on ADEME DPE data. X = Primary space hating energy consumption (kWh EP / m2) Conso = X ** 0.846102 * exp(1.029880) """ return (df ** a) * np.exp(b)
[docs]def stat_heating_consumption(u_wall, u_floor, u_roof, u_windows, efficiency, ratio_surface, hdd): """Calculate space heating consumption in kWh/m2.year based on insulation performance and heating system efficiency. Function simulates the 3CL-method, and use parameters to estimate unobserved variables. Parameters ---------- u_wall: float or pd.Series u_floor: float or pd.Series u_roof: float or pd.Series u_windows: float or pd.Series hdd: float or pd.Series efficiency: float or pd.Series ratio_surface: pd.DataFrame Returns ------- float or pd.Series or pd.DataFrame Standard space heating consumption. """ data = pd.concat([u_wall, u_floor, u_roof, u_windows], axis=1, keys=['Wall', 'Floor', 'Roof', 'Windows']) partial_losses = (reindex_mi(ratio_surface, data.index) * data).sum(axis=1) if isinstance(partial_losses, (pd.Series, pd.DataFrame)): if partial_losses.index.equals(efficiency.index): indicator_losses = partial_losses / efficiency * hdd / 1000 consumption = stat_model_heating_consumption(indicator_losses).rename('Consumption') else: indicator_losses = (partial_losses * hdd / 1000).to_frame().dot( (1 / efficiency).to_frame().T) consumption = stat_model_heating_consumption(indicator_losses) else: indicator_losses = partial_losses / efficiency * hdd / 1000 consumption = stat_model_heating_consumption(indicator_losses).rename('Consumption') return consumption
[docs]def stat_certificate(df): """Returns energy performance certificate based on space heating energy consumption. Parameters ---------- df: float or pd.Series or pd.DataFrame Space heating energy consumption (kWh PE / m2.year) Returns ------- float or pd.Series or pd.DataFrame Energy performance certificate. """ primary_consumption = stat_model_3uses_consumption(df) if isinstance(primary_consumption, pd.Series): certificate = pd.Series(dtype=str, index=primary_consumption.index) for key, item in CERTIFICATE_3USES_BOUNDARIES.items(): cond = (primary_consumption > item[0]) & (primary_consumption <= item[1]) certificate[cond] = key return certificate elif isinstance(primary_consumption, pd.DataFrame): certificate = pd.DataFrame(dtype=str, index=primary_consumption.index, columns=primary_consumption.columns) for key, item in CERTIFICATE_3USES_BOUNDARIES.items(): cond = (primary_consumption > item[0]) & (primary_consumption <= item[1]) certificate[cond] = key return certificate elif isinstance(primary_consumption, float): for key, item in CERTIFICATE_3USES_BOUNDARIES.items(): if (primary_consumption > item[0]) & (primary_consumption <= item[1]): return key
[docs]def certificate_buildings(u_wall, u_floor, u_roof, u_windows, hdd, efficiency, energy, ratio_surface): """Returns energy performance certificate. Parameters ---------- u_wall u_floor u_roof u_windows hdd efficiency energy ratio_surface Returns ------- pd.Series Primary heating consump_tion for all buildings in the stock. pd.Series Certificates for all buildings in the stock. """ primary_heat_consumption = primary_heating_consumption(u_wall, u_floor, u_roof, u_windows, hdd, efficiency, energy, ratio_surface, conversion=CONVERSION) return primary_heat_consumption, stat_certificate(primary_heat_consumption)
if __name__ == '__main__': from project.model import get_inputs output = get_inputs(variables=['buildings']) buildings = output['buildings'] heating_need_year = buildings.heating_need(freq='year', climate=2006) energy = heating_need_year.index.get_level_values('Heating system').str.split('-').str[0] heating_need_year = heating_need_year.groupby(energy).sum() heating_need_month = buildings.heating_need(freq='month', climate=2006) heating_need_month = heating_need_month.sum(axis=1) energy = heating_need_month.index.get_level_values('Heating system').str.split('-').str[0] heating_need_month = heating_need_month.groupby(energy).sum() heating_need_day = buildings.heating_need(freq='day', climate=2006) heating_need_day = heating_need_day.sum(axis=1) energy = heating_need_day.index.get_level_values('Heating system').str.split('-').str[0] heating_need_day = heating_need_day.groupby(energy).sum() heating_need_hour = buildings.heating_need(freq='hour', climate=2006) heating_need_hour = heating_need_hour.sum(axis=1) energy = heating_need_hour.index.get_level_values('Heating system').str.split('-').str[0] heating_need_hour = heating_need_hour.groupby(energy).sum() print('break') print('break')