"""
Building.py
A. Molar-Cruz @ TUM ENS
"""
import os
from random import randint
import numpy as np
import scipy
import UrbanHeatPro.Functions as UrbanHeatPro
from .HotWaterDemand import HotWaterDemand
from .HotWaterDemand_D import HotWaterDemand_D
from .SpaceHeatingDemand import SpaceHeatingDemand
# import ipdb
[docs]
class Building:
# --------------------------------------------------------------------------------
def __init__(self, b, building_stock_stats,
# Simulation
dt_vectors, resolution, number_of_typ_days, weights,
Tamb, I,
_space_heating, _hot_water, _energy_only,
# Space heating demand
Tb0_str, dTset, dT_per_hour, eta, thermal_inertia,
_active_population, _workday_weekend, sh_prob,
_solar_gains, _internal_gains,
_night_set_back, schedule_nsb, T_nsb, power_reduction,
# Hot water demand
Tw, dhw_prob, hw_tank_limit, hw_flow,
day_vector, seasonal_vector, min_vector,
# Results
result_dir, plot, save, debug):
"""
Initialize the Building object with the given parameters.
Args:
b: Building dataframe
building_stock_stats: Building stock statistics
dt_vectors: Vector of time steps as datetime objects
resolution: Temporal resolution in min
number_of_typ_days: Number of typical days
weights: Weights of typical days
Tamb: Ambient temperature vector in degC
I: Solar radiation vector in W/m2 [I_Gh, I_Dh, I_ex, hs]
_space_heating: Calculate space heating demand?
_hot_water: Calculate hot water demand?
_energy_only: Calculate only aggregated demand?
Tb0_str: Initial building temperature as string: 'ambient' or 'Tset'
dTset: Delta temperature (for Tset_min, Tset_max)
dT_per_hour: Maximum dT allowed in building per hour [degC]
eta: Heating process efficiency
thermal_inertia: Thermal inertia of the heating system
_active_population: Consider active population for occupancy vector
_workday_weekend: Consider dif between workdays and weekends
sh_prob: Probability vector of using space heating
_solar_gains: Consider solar gains?
_internal_gains: Consider internal gains?
_night_set_back: Share of buildings with nsb
schedule_nsb: [start, end] of nsb in h
T_nsb: Night set-back temperature in degC
power_reduction: Percentage of power reduced (as decimal)
Tw: Hot water temperature in [degC]
dhw_prob: Probabilities for dhw-loads
hw_tank_limit: Hot water tank limit as perc (decimal)
hw_flow: Flow to refill hot water tank in L/min
day_vector: Vector of days in simulation time frame
seasonal_vector: Sinusoidal function for seasonal variations of DHW consumption
min_vector: Vector of simulation time steps in minutes
result_dir: Directory where results are stored
plot: Whether to plot the results or not
save: Whether to save the results or not
debug: Whether to print debug information or not
"""
# General data
# --------------------------------------------------
# Building dataframe (from SynCity)
self.building = b # Building dataframe
# Simulation
self.dt_vector = dt_vectors[0] # Vector of time steps as datetime objects
self.dt_vector_excel = dt_vectors[1] # Vector of time steps as excel date
self.resolution = resolution # Temporal resolution in min
self.number_of_typ_days = number_of_typ_days # Number of typical days
self.weights = weights # Weights of typical days
# Flags
self._space_heating = _space_heating # Calculate space heating demand?
self._hot_water = _hot_water # Calculate hot water demand?
self._energy_only = _energy_only # Calculate only aggregated demand?
# External factors
self.Tamb = Tamb # Ambient temperature vector in degC
self.I = I # Solar radiation vector in W/m2 [I_Gh, I_Dh, I_ex, hs]
self.eta = eta # Heating process efficiency
self.thermal_inertia = thermal_inertia # Thermal inertia of the heating system
# Result directory
self.result_dir = result_dir + '/Buildings/' # Directory where results are stored
# Space heating demand
# --------------------------------------------------
# Building stock statistics
## Residential
self.FOOTPRINT = building_stock_stats[0][0] # Footprint area of building typologies (TABULA)
self.ARATIO = building_stock_stats[0][1] # Area ratio [Roof, Wall, Window]
self.WRATIO_ORIENTATION = building_stock_stats[0][2] # Window ratio [East, South, West, North]
self.FLOORS = building_stock_stats[0][3] # Number of floors
self.U_RES = building_stock_stats[0][4] # U-values for residential buildings
self.V_RES = building_stock_stats[0][5] # Air flow rate (ventilation losses) for residential
self.C_RES = building_stock_stats[0][6] # Thermal cap for residential buildings
self.CURRENT_REF_RES = building_stock_stats[0][7][0] # Current percentage of residential refurbished buildings
self.MAX_REF_RES = building_stock_stats[0][7][1] # Max percentage of residential refurbished buildings
self.SINGLE_DWELLING = building_stock_stats[0][8] # Percentage of single dwellings for SFH and TH
self.AVG_DWELLING_SIZE = building_stock_stats[0][9] # Average dwelling size in m2
self.HOUSEHOLD_SIZE = building_stock_stats[0][10] # Household size for dwelling size categories
self.STOCK_RES = building_stock_stats[0][17] # Building stock statistics for residential
## Non-residential
self.U_NRES = building_stock_stats[0][11] # U-values for non-residential buildings
self.V_NRES = building_stock_stats[0][12] # Air flow rate (ventilation losses) for non-residential
self.C_NRES = building_stock_stats[0][13] # Thermal cap for non-residential buildings
self.CURRENT_REF_NRES = building_stock_stats[0][14][0] # Current percentage of residential refurbished
# buildings
self.MAX_REF_NRES = building_stock_stats[0][14][1] # Max percentage of residential refurbished buildings
self.STOCK_NRES = building_stock_stats[0][18] # Building stock statistics for non-residential
## Both
self.TSET = building_stock_stats[0][15] # Target temperatures per building use
self.SCHEDULE = building_stock_stats[0][16] # Active hours per building use
# Building thermal properties
self.Tb0_str = Tb0_str # Initial building temperature as string: 'ambient' or 'Tset'
self.dTset = dTset # Delta temperature (for Tset_min, Tset_max)
self.dT_per_hour = dT_per_hour # Maximum dT allowed in building per hour [degC]
# Activity and occupancy in building
self._active_population = _active_population # Consider active population for occupancy vector
self._workday_weekend = _workday_weekend # Consider dif between workdays and weekends
self.sh_prob = sh_prob # Probability vector of using space heating
# Heat gains
self._solar_gains = _solar_gains
self._internal_gains = _internal_gains
# DSM
self._night_set_back = _night_set_back # Share of buildings with nsb
self.schedule_nsb = schedule_nsb # [start, end] of nsb in h
self.T_nsb = T_nsb # Night set-back temperature in degC
self.power_reduction = power_reduction # Percentage of power reduced (as decimal)
# Hot water demand
# --------------------------------------------------
# Domestic hot water consumption
self.Tw = Tw # Hot water temperature in [degC]
self.dhw_prob = dhw_prob # Probabilities for dhw-loads
self.hw_tank_limit = hw_tank_limit # Hot water tank limit as perc (decimal)
self.hw_flow = hw_flow # Flow to refill hot water tank in L/min
# Seasonality
self.day_vector = day_vector # Vector of days in simulation time frame
self.seasonal_vector = seasonal_vector # Sinusoidal function for seasonal variations of DHW consumption
self.min_vector = min_vector # Vector of simulation time steps in minutes
# Reporting
# --------------------------------------------------
self.plot = plot
self.save = save
self.debug = debug
# Heating energy demand
# --------------------------------------------------------------------------------
[docs]
def calculate_space_heating_demand(self):
"""
Calculates building space heating demand as timeseries [W] and aggregated value [Wh]
"""
# Number of time steps
self.nts = len(self.dt_vector)
# Activity and Occupancy vector
if self._active_population:
self.calculate_building_activity_occupancy_vector()
# Calculate window areas
if self._solar_gains:
self.calculate_building_window_areas()
else:
self.window_areas = None
# Initial building temperature (Tb[0])
if self.Tb0_str == 'Tamb':
self.Tb0 = self.Tamb[0]
elif self.Tb0_str == 'Tset':
self.Tb0 = self.building.Tset
# Space heating demand
# --------------------------------------------------
self.my_space_heating_demand = SpaceHeatingDemand(self.dt_vector, self.resolution, self.building.heated_area,
self.Tamb, self.I, self.Tb0, self.dT_per_hour, self.eta,
self.thermal_inertia,
self.building.U, self.building.V, self.building.C,
self.building.Tset, self.dTset,
self.activity_vector, self.occupancy_vector, self.sh_prob,
self._solar_gains, self._internal_gains,
self._night_set_back, self.schedule_nsb, self.T_nsb,
self.power_reduction,
self.window_areas, [self.building.lat, self.building.lon],
self.debug)
self.my_space_heating_demand.calculate()
# Power [W]
self.space_heating_power = self.my_space_heating_demand.sh_power
self.solar_gains = self.my_space_heating_demand.solar_gains
self.internal_gains = self.my_space_heating_demand.internal_gains
# Building temperature [degC]
self.Tb = self.my_space_heating_demand.Tb
# Energy [Wh] -> ANNUAL
if self.number_of_typ_days < 365:
self.space_heating_energy = self.calculate_annual_demand(self.space_heating_power)
self.solar_gains_energy = self.calculate_annual_demand(self.solar_gains)
self.internal_gains_energy = self.calculate_annual_demand(self.internal_gains)
else:
self.space_heating_energy = (self.space_heating_power.sum() * self.resolution * 1 / 60)
self.solar_gains_energy = (self.solar_gains.sum() * self.resolution * 1 / 60)
self.internal_gains_energy = (self.internal_gains.sum() * self.resolution * 1 / 60)
# Energy per (conditioned) area [kWh/m2]
self.space_heating_energy_per_area = (self.space_heating_energy / self.building.heated_area)
self.solar_gains_per_area = (self.solar_gains_energy / self.building.heated_area)
self.internal_gains_per_area = (self.internal_gains_energy / self.building.heated_area)
result = [self.space_heating_energy, self.solar_gains_energy, self.internal_gains_energy,
self.space_heating_energy_per_area, self.solar_gains_per_area, self.internal_gains_per_area]
result = np.resize(result, (1, len(result)))[0]
return result
#
[docs]
def calculate_hot_water_demand(self, save_debug=False):
"""
Calculates building hot water demand as timeseries [W] and [m3] and aggregated value [Wh].
Only for residential buildings.
Args:
save_debug (boolean): Is debug file saved?
Returns:
self.hot_water_m3
self.hot_water_power
self.dhw_energy
"""
# Number of time steps
self.nts = len(self.dt_vector)
if self.building.use == 3: # residential
if not self._space_heating:
# self.activity_vector, self.occupancy_vector
self.calculate_building_activity_occupancy_vector()
# Hot water demand
# --------------------------------------------------
# Energy only: simplified model
if self._energy_only:
self.my_hot_water_demand = HotWaterDemand_D(self.resolution,
self.day_vector,
self.Tw, self.building.hw_demand_day)
self.my_hot_water_demand.calculate()
self.dhw_m3 = self.my_hot_water_demand.dhw_m3
self.dhw_energy = self.my_hot_water_demand.dhw_energy
#
# scale up to year
if self.number_of_typ_days < 365:
self.dhw_m3 = self.dhw_m3 / self.number_of_typ_days * 365.
self.dhw_energy = self.dhw_energy / self.number_of_typ_days * 365.
# Timeseries: probabilistic model
else:
# define size and state of hot water tank
self.set_hot_water_tank_initial_state()
# Hot water demand
# --------------------------------------------------
self.my_hot_water_demand = HotWaterDemand(self.dt_vector, self.resolution,
self.day_vector, self.seasonal_vector, self.activity_vector,
self.Tw, self.building.hw_demand_day, self.dhw_prob,
self.building.hw_tank_cap, self.hw_tank_limit,
self.hw_tank_volume_t0, self.hw_flow,
self.result_dir, self.building.use,
int(self.building.year_class), int(self.building.size_class),
self.building.bid,
self.debug, save_debug)
self.my_hot_water_demand.calculate()
if save_debug:
self.dhw_debug = self.my_hot_water_demand.dhw_debug
# Power [W]
self.hot_water_power = self.my_hot_water_demand.dhw_power
# Flow rate [m3]
self.hot_water_m3 = self.my_hot_water_demand.dhw_m3
self.hot_water_tank_m3 = self.my_hot_water_demand.dhw_tank_m3
# Energy [Wh]
self.dhw_m3 = self.hot_water_m3.sum()
self.dhw_energy = (self.hot_water_power.sum() * self.resolution * 1 / 60)
#
# scale up to year
if self.number_of_typ_days < 365:
self.dhw_energy = self.calculate_annual_demand(self.hot_water_power)
self.dhw_m3 = self.calculate_annual_demand(self.hot_water_m3)
result = np.array([self.dhw_energy, self.dhw_m3])
else: # Non-residential
# No hot water demand
self.hot_water_power = np.zeros([self.nts])
self.hot_water_m3 = np.zeros([self.nts])
self.hot_water_tank_m3 = np.zeros([self.nts])
self.dhw_energy = 0.
self.dhw_m3 = 0.
return np.array([self.dhw_energy, self.dhw_m3])
#
[docs]
def calculate_total_heat_demand(self):
"""
Agrregate the space heating and/or hot water demand time series.
The total time series is delayed depending on the distance to the heat plant.
"""
# initialize matrices
self.total_power = np.zeros([self.nts]) # Total heat demand in W
self.total_energy = 0. # Aggregated total heat demand in Wh
# Space heating demand
if self._space_heating:
if not self._energy_only:
self.total_power += self.space_heating_power
self.total_energy += self.space_heating_energy
# Hot water demand
if self._hot_water:
if not self._energy_only:
self.total_power += self.hot_water_power
self.total_energy += self.dhw_energy
# Delayed time series
if not self._energy_only:
if self.building.dist_to_heat_source > 0.:
self.calculate_delayed_timeseries()
return np.array([self.total_energy, self.total_energy / self.building.heated_area])
#
[docs]
def calculate_delayed_timeseries(self, flow_vel=1.):
"""
Delays vector of heat demand depending on the distance of the building
centroid to the (geothermal) heat plant. A flow velocity of 1 m/s in the district heating network is
considered.
"""
self.delayed_min_vector = np.zeros([self.nts]) # Vector of delayed time steps in min
self.total_power_delayed = np.zeros([self.nts]) # Delayed total heat demand in W
# calculate flow velocity in minutes
flow_vel_min = flow_vel * 60. # m/min
# calculate delay
# Delay is two times the distance between the building and the power plant (round trip: flow
# from the power plant to the building and back)
self.delay = 2. * self.building.dist_to_heat_source / flow_vel_min # min
# add delay to datetime minute vector
for iii, dt in enumerate(self.min_vector):
self.delayed_min_vector[iii] = self.min_vector[iii] - self.delay
# interpolate to calculate delayed time series in base datetime vector
f = scipy.interpolate.interp1d(self.delayed_min_vector, self.total_power, fill_value="extrapolate")
self.total_power_delayed = f(self.min_vector)
# calculate delayed energy demand in Wh
self.total_energy_delayed = (self.total_power_delayed.sum() * self.resolution * 1 / 60)
# Building parametrization
# --------------------------------------------------------------------------------
[docs]
def parametrize_building(self):
"""
Calculates missing building properties necessary for the heat demand calculation.
"""
# MIN PARAMETERS
self.bid = self.building.bid
self.footprint_area = self.building.footprint_area
self.use = int(self.building.use)
self.free_walls = self.building.free_walls
self.lat = self.building.lat
self.lon = self.building.lon
# RESIDENTIAL
# -----------
if self.building.use == 3:
#
# Construction year class and size class
try:
self.year_class = int(self.building.year_class)
self.size_class = int(self.building.size_class)
except:
# categorize building according TABULA typology -> self.year_class, self.size_class
self.categorize_building_residential()
#
# Number of floors
try:
self.floors = int(self.building.floors)
except:
self.floors = False
#
# Area correction factor
try:
self.area_corr_factor = self.building.area_corr_factor
except:
self.area_corr_factor = False
#
# Heated area
try:
self.heated_area = self.building.heated_area
except:
self.heated_area = False
# calculate floors and reference areas -> self.area_corr_factor, self.floors, self.storey_area,
# self.heated_area
self.calculate_areas_residential()
#
# Number of dwellings
try:
self.dwellings = int(self.building.dwellings)
except:
self.dwellings = False
# calculate number of dwellings per building -> self.dwellings, self.dwelling_size
self.calculate_number_of_dwellings()
#
# Number of occupants
try:
self.occupants_in_building = int(self.building.occupants)
except:
# calculate number of occupants per building -> self.occupants
self.calculate_number_of_occupants_residential()
#
# Refurbishment level per element
try:
self.ref_level_roof = int(self.building.ref_level_roof)
self.ref_level_wall = int(self.building.ref_level_wall)
self.ref_level_floor = int(self.building.ref_level_floor)
self.ref_level_window = int(self.building.ref_level_window)
self.ref_level = [self.ref_level_roof, self.ref_level_wall, self.ref_level_floor, self.ref_level_window]
except:
# compute refurbishment level -> self.ref_level
self.compute_current_refurbishment_level_residential()
#
# Envelope areas
self.calculate_building_envelope_areas_residential()
#
# Thermal properties per unit area
self.get_building_thermal_properties_per_unit_area_residential()
#
# Domestic hot water demand and tank capacity -> self.hw_demand_day, self.hw_tank_cap
self.calculate_daily_hot_water_demand()
self.parametrize_hot_water_tank()
# NON-RESIDENTIAL
# ---------------
elif self.building.use == 0 or self.building.use == 1 or self.building.use == 2:
#
# Construction year class and size class
try:
self.year_class = int(self.building.year_class)
self.size_class = int(self.building.size_class)
except:
# categorize building -> self.year_class, self.size_class
self.categorize_building_non_residential()
#
# Number of floors
try:
self.floors = int(self.building.floors)
except:
self.floors = False
# calculate floors and reference areas -> self.area_corr_factor, self.floors, self.storey_area, self.heated_area
self.calculate_areas_non_residential()
#
# Number of occupants
try:
self.occupants_in_building = int(self.building.occupants)
except:
# calculate number of occupants per building -> self.occupants
self.calculate_number_of_occupants_non_residential()
self.dwellings = -1
self.dwelling_size = -1.
#
# Refurbishment level per element
try:
self.ref_level_roof = int(self.building.ref_level_roof)
self.ref_level_wall = int(self.building.ref_level_wall)
self.ref_level_floor = int(self.building.ref_level_floor)
self.ref_level_window = int(self.building.ref_level_window)
self.ref_level = [self.ref_level_roof, self.ref_level_wall, self.ref_level_floor, self.ref_level_window]
except:
# compute refurbishment level -> self.ref_level
self.compute_current_refurbishment_level_non_residential()
#
# Envelope areas
self.calculate_building_envelope_areas_non_residential()
#
# Thermal properties per unit area
self.get_building_thermal_properties_per_unit_area_non_residential()
#
# Hot water demand
self.hw_demand_day = -1.
self.hw_tank_cap = -1.
else:
raise ValueError('Building use does not exist')
# RES + NRES
# ----------
#
# Distance to heat source
try:
self.dist_to_heat_source = self.building.dist_to_heat_source
except:
self.dist_to_heat_source = -1.
#
# Equivalent thermal properties -> self.U, self.V, self.C
self.calculate_building_thermal_properties()
#
# Set temperature -> self.Tset
try:
self.Tset = self.building.Tset
except:
self.calculate_building_Tset()
#
# Active hours -> self.active_hours
self.calculate_building_active_hours()
# ==============================================================
# add parameters to building dataframe
self.building.is_copy = False
self.building.loc['dist_to_heat_source'] = self.dist_to_heat_source
self.building.loc['year_class'] = self.year_class
self.building.loc['size_class'] = self.size_class
self.building.loc['floors'] = self.floors
self.building.loc['storey_area'] = self.storey_area
self.building.loc['area_corr_factor'] = self.area_corr_factor
self.building.loc['heated_area'] = self.heated_area
self.building.loc['window_area'] = self.env_areas[3]
self.building.loc['dwellings'] = self.dwellings
self.building.loc['dwelling_size'] = self.dwelling_size
self.building.loc['occupants'] = self.occupants_in_building
self.building.loc['ref_level_roof'] = self.ref_level[0]
self.building.loc['ref_level_wall'] = self.ref_level[1]
self.building.loc['ref_level_floor'] = self.ref_level[2]
self.building.loc['ref_level_window'] = self.ref_level[3]
self.building.loc['U'] = self.U
self.building.loc['V'] = self.V
self.building.loc['C'] = self.C
self.building.loc['Tau'] = self.Tau
self.building.loc['Tset'] = self.Tset
self.building.loc['act_start'] = self.active_hours[0]
self.building.loc['act_end'] = self.active_hours[1]
self.building.loc['hw_demand_day'] = self.hw_demand_day
self.building.loc['hw_tank_cap'] = self.hw_tank_cap
return self.building
#
[docs]
def update_building_refurbishment_level(self, ref_matrix_res, ref_matrix_nres):
"""
Updates building refurbishment level based on desired refurbishment level
from scenario and max percentage of refurbished buildings in region
(MAX_REF_RES and MAX_REF_NRES).
Refurbishment levels according to TABULA typology:
- 1 National minimum requirement
- 2 Improved standard
- 3 Ambitious standard
"""
# Parameters
self.use = int(self.building.use)
self.size_class = int(self.building.size_class)
self.year_class = int(self.building.year_class)
self.free_walls = self.building.free_walls
self.floors = self.building.floors
self.heated_area = self.building.heated_area
# RESIDENTIAL
# -----------
if self.use == 3:
flag = self.compute_scenario_refurbishment_level_residential(ref_matrix_res)
if flag:
#
# Envelope areas
self.calculate_building_envelope_areas_residential()
#
# Thermal properties per unit area
self.get_building_thermal_properties_per_unit_area_residential()
# NON-RESIDENTIAL
# ---------------
elif self.use == 0 or self.use == 1 or self.use == 2:
flag = self.compute_scenario_refurbishment_level_non_residential(ref_matrix_nres)
if flag:
#
# Envelope areas
self.calculate_building_envelope_areas_non_residential()
#
# Thermal properties per unit area
self.get_building_thermal_properties_per_unit_area_non_residential()
else:
raise ValueError('Building use does not exist')
if flag:
# Equivalent thermal properties -> self.U, self.V, self.C
self.calculate_building_thermal_properties()
# update values in building df
self.building.loc['ref_level_roof'] = self.ref_level[0]
self.building.loc['ref_level_wall'] = self.ref_level[1]
self.building.loc['ref_level_floor'] = self.ref_level[2]
self.building.loc['ref_level_window'] = self.ref_level[3]
self.building.loc['U'] = self.U
self.building.loc['V'] = self.V
self.building.loc['C'] = self.C
self.building.loc['Tau'] = self.Tau
return self.building
## Building parameters: Space heating demand
#
[docs]
def categorize_building_residential(self):
"""
Probabilistic categorization building according to TABULA typologies.
Construction year class and building type are calculated by comparing the residential
building gross floor area (footprint_area) with the FOOTPRINT of typical buildings (from TABULA).
Values are adapted to fit building stock statistics.
"""
# get size of FOOTPRINT matrix
rows = len(self.FOOTPRINT) # construction year class
cols = len(self.FOOTPRINT[0]) # building type
# initialize distance vectors
distance = np.zeros(rows * cols)
distance_inv = np.zeros(rows * cols) # 1/d
row_col = [[] for _ in range(rows * cols)]
t_distance_inv = 0 # total inv distance, sum(1/d)
kkk = 0 # typology counter
#
for iii in range(0, rows):
for jjj in range(0, cols):
# calculate distance between building footprint_area and FOOTPRINT (TABULA) matrix
distance[kkk] = abs(
self.FOOTPRINT[iii][jjj] - self.footprint_area) + 0.001 # "+ 0.001" to avoid having x/0
# calculate inverse and weight by building stock statistics
distance_inv[kkk] = (1.0 / distance[kkk]) * self.STOCK_RES[iii, jjj]
row_col[kkk] = [iii, jjj]
t_distance_inv += distance_inv[kkk]
kkk += 1
# normalize inverse distance vector
norm_distance = distance_inv / t_distance_inv
# calculate cumulative density function
cdf = np.cumsum(norm_distance)
# Probabilistic categorization
# test categorization X times or until the typology exists
value = 0
counter = 0
while value == 0 and counter < 5:
rnd = np.random.uniform(0, 1, 1)
index = np.argmax(rnd < cdf)
### Construction year class as int
self.year_class = UrbanHeatPro.year_class_to_tuple(self.use, row_col[index][0])[0]
### Building size class as int
if self.free_walls == 2:
self.size_class = 1
else:
self.size_class = UrbanHeatPro.size_class_to_tuple(self.use, row_col[index][1])[0]
### test if typology exists
value = self.FOOTPRINT[self.year_class][self.size_class]
counter += 1
#
[docs]
def categorize_building_non_residential(self):
"""
Probabilistic categorization of non-residential buildings according to the
building statistics and the following construction year classes:
+-----+-------------------------+
| int | construction year class |
+=====+=========================+
| 0 | < 1918 |
+-----+-------------------------+
| 1 | 1919 - 1976 |
+-----+-------------------------+
| 2 | 1977 - 1983 |
+-----+-------------------------+
| 3 | 1984 - 1994 |
+-----+-------------------------+
| 4 | > 1995 |
+-----+-------------------------+
>> Source for building stock missing
"""
# calculate cumulative density function
cdf = np.cumsum(self.STOCK_NRES) / np.sum(self.STOCK_NRES)
# Probabilistic categorization
rnd = np.random.uniform(0, 1, 1)
index = np.argmax(rnd < cdf)
self.year_class = UrbanHeatPro.year_class_to_tuple(self.use, index)[0]
# set size class to Nan
self.size_class = -1
#
[docs]
def compute_current_refurbishment_level_residential(self):
"""
Computes the refurbishment level for the different building elements
[roof, wall, floor, window] according to the current refurbishment statistics.
Refurbishment levels according to TABULA typology:
- 1 National minimum requirement
- 2 Improved standard
- 3 Ambitious standard
"""
# check current share of refurbished buildings for building size and year class
perc_refurbished = self.CURRENT_REF_RES[self.size_class][self.year_class]
# assign refurbishment level for every building element
# if random number is smaller than percentage, then refurbishment level of
# element is 2, otherwise is 1. No building is considered as level 3.
self.ref_level = np.ones(4, dtype=int)
for element in range(4): # [roof, wall, floor, window]
rand_num = np.random.uniform(0, 1, 1)
if rand_num <= perc_refurbished[element]:
self.ref_level[element] = 2
#
[docs]
def compute_current_refurbishment_level_non_residential(self):
"""
Computes the refurbishment level for the different building components
[roof, wall, floor, window] according to the refurbishment statistics.
Refurbishment levels according to TABULA typology:
- 1 National minimum requirement
- 2 Improved standard
- 3 Ambitious standard
>> Statistics on refurbishment in non-residential buildings missing
"""
# check current share of refurbished buildings for building size and year class
perc_refurbished = self.CURRENT_REF_NRES[self.year_class]
# assign refurbishment level for every building element
# if random number is smaller than percentage, then refurbishment level of
# element is 2, otherwise is 1. No building is considered as level 3.
self.ref_level = np.ones(4, dtype=int)
for element in range(4): # [roof, wall, floor, window]
rand_num = np.random.uniform(0, 1, 1)
if rand_num <= perc_refurbished[element]:
self.ref_level[element] = 2
#
[docs]
def compute_scenario_refurbishment_level_residential(self, ref_matrix_res):
"""
Computes the refurbishment level for the different building elements
[roof, wall, floor, window] according to the scenario refurbishment level
per typology and the maximum share of refurbished buildings (MAX_REF_RES).
Refurbishment levels according to TABULA typology:
- 1 National minimum requirement
- 2 Improved standard
- 3 Ambitious standard
"""
# check max share of refurbished buildings per building size class and year class
perc_max_refurbished = self.MAX_REF_RES[int(self.building.size_class)][int(self.building.year_class)]
# check current refurbishment level
self.ref_level = [self.building.ref_level_roof,
self.building.ref_level_wall,
self.building.ref_level_floor,
self.building.ref_level_window]
# check (desired) set refurbishment level
ref_level_set = ref_matrix_res[int(self.building.size_class)][int(self.building.year_class)]
flag = False
# if current level is equal or greater than desired level, then no changes
# otherwise, the refurbishment level is likely to be improved
for element in range(4): # [roof, wall, floor, window]
if self.ref_level[element] < ref_level_set[element]:
# improve element refurbishment to desired level if random number
# is smaller than max refurbishment percentage
rand_num = np.random.uniform(0, 1, 1)
if rand_num <= perc_max_refurbished[element]:
self.ref_level[element] = ref_level_set[element]
flag = True
return flag
#
[docs]
def compute_scenario_refurbishment_level_non_residential(self, ref_matrix_nres):
"""
Computes the refurbishment level for the different building elements
[roof, wall, floor, window] according to the scenario refurbishment level
per typology and the maximum share of refurbished buildings (MAX_REF_NRES).
Refurbishment levels according to TABULA typology:
- 1 National minimum requirement
- 2 Improved standard
- 3 Ambitious standard
"""
# check max share of refurbished buildings per year class
perc_max_refurbished = self.MAX_REF_NRES[int(self.building.year_class)]
# check current refurbishment level
self.ref_level = [self.building.ref_level_roof,
self.building.ref_level_wall,
self.building.ref_level_floor,
self.building.ref_level_window]
# check (desired) set refurbishment level
ref_level_set = ref_matrix_nres[int(self.building.year_class)]
flag = False
# if current level is equal or greater than desired level, then no changes
# otherwise, the refurbishment level is likely to be improved
for element in range(4): # [roof, wall, floor, window]
if self.ref_level[element] < ref_level_set[element]:
# improve element refurbishment to desired level if random number
# is smaller than max refurbishment percentage
rand_num = np.random.uniform(0, 1, 1)
if rand_num <= perc_max_refurbished[element]:
self.ref_level[element] = ref_level_set[element]
# update thermal properties flag
flag = True
return flag
#
[docs]
def calculate_areas_residential(self):
"""
Calculate storey area and heated/conditioned area based on definitions
from VDI 3807.
"""
# Area correction factor (VDI 3807-2 Section 7.1.1 Table 1)
if not self.area_corr_factor:
if not self.heated_area:
self.area_corr_factor = 0.84
else:
self.area_corr_factor = self.heated_area / self.footprint_area
# Floors
if not self.floors:
self.calculate_number_of_floors_residential()
# Storey area (VDI 3807-1 Section 5.4.2)
self.storey_area = self.footprint_area * self.floors
# Heated area (VDI 3807-1 Section 5.4.2) or reference area in TABULA
if not self.heated_area:
self.heated_area = self.footprint_area * self.area_corr_factor * self.floors
#
[docs]
def calculate_areas_non_residential(self):
"""
Calculate storey area and heated/conditioned area based on definitions
from VDI 3807.
"""
# Area correction factor (VDI 3807-2 Section 7.1.1 Table 1)
self.area_corr_factor = np.random.randint(75, 85) / 100.
# Floors
if not self.floors:
self.calculate_number_of_floors_non_residential()
# Storey area (VDI 3807-1 Section 5.4.2)
self.storey_area = self.footprint_area * self.floors
# Heated area (VDI 3807-1 Section 5.4.2)
self.heated_area = self.footprint_area * self.area_corr_factor * self.floors
#
[docs]
def calculate_number_of_floors_residential(self):
"""
Calculates number of floors based on the TABULA typology.
The number of floors calculated from TABULA are referenced to the conditioned
or heated area but the number of floors are calculated using the storey area.
"""
# get number of floors
self.floors = int(np.ceil(self.FLOORS[self.year_class][self.size_class] / (1 + (1 - self.area_corr_factor))))
# set minimum to 1
if self.floors == 0:
self.floors = 1
#
[docs]
def calculate_number_of_floors_non_residential(self, left=1, mode=2, right=3):
"""
Calculates number of floors as random sample number from the triangular
distribution with lower limit left, peak at mode and upper limit right.
>> Non-residential buildings are assumed to have two floors as mode and
a maximum of three floors
Source missing
"""
# calculate number of floors
self.floors = int(np.random.triangular(left, mode, right))
#
[docs]
def calculate_number_of_dwellings(self):
"""
Calculates number of dwellings based on the building living area and mean dwelling size.
It is assumed that SFH and TH have only 1 or 2 dwellings which is determined using the
single-dwelling buildings statistics. For MFH and AB, the number of dweelings is calculated
based on the average dwelling size.
"""
if not self.dwellings:
# For SFH and TH
# compare random number to single-dwelling buildings statistics
if self.size_class == 0 or self.size_class == 1: # SFH or TH
perc_single_dwelling = self.SINGLE_DWELLING[self.year_class][self.size_class]
rand_num = np.random.uniform(0, 1, 1)
if rand_num <= perc_single_dwelling:
self.dwellings = 1
else:
self.dwellings = 2
# For MFH and AB
# divide total living area by dwelling size
else:
# calculate random dwelling size from normal dist with mean AVG_DWELLING_SIZE
# and sigma AVG_DWELLING_SIZE/10
self.dwelling_size = 0
while self.dwelling_size <= 25: # min dwelling size [m2]
self.dwelling_size = np.random.normal(self.AVG_DWELLING_SIZE, self.AVG_DWELLING_SIZE / 10, 1)
# calculate number of dwellings
self.dwellings = int(np.ceil(self.heated_area / self.dwelling_size))
# Dwelling size
self.dwelling_size = self.heated_area / self.dwellings
#
[docs]
def determine_dwelling_size_category(self):
"""
Determine dwelling size category based on statistics
https://ergebnisse.zensus2011.de/#StaticContent:091840148148,GWZ_4_3_2,m,table
"""
# determine size category
if self.dwelling_size > 0 and self.dwelling_size <= 40:
dwelling_size_cat = 0
elif self.dwelling_size > 40 and self.dwelling_size <= 60:
dwelling_size_cat = 1
elif self.dwelling_size > 60 and self.dwelling_size <= 80:
dwelling_size_cat = 2
elif self.dwelling_size > 80 and self.dwelling_size <= 100:
dwelling_size_cat = 3
elif self.dwelling_size > 100 and self.dwelling_size <= 120:
dwelling_size_cat = 4
elif self.dwelling_size > 120 and self.dwelling_size <= 140:
dwelling_size_cat = 5
elif self.dwelling_size > 140 and self.dwelling_size <= 160:
dwelling_size_cat = 6
elif self.dwelling_size > 160 and self.dwelling_size <= 180:
dwelling_size_cat = 7
elif self.dwelling_size > 180 and self.dwelling_size <= 200:
dwelling_size_cat = 8
else:
dwelling_size_cat = 9
return dwelling_size_cat
#
[docs]
def calculate_number_of_occupants_residential(self):
"""
Calculates number of occupants based on household size and number of dwellings
statistics.
"""
# calculate dwelling size category
dwelling_size_cat = self.determine_dwelling_size_category()
# CFD for household size
### x-values
household_size = range(1, 7)
### CFD
cfd = np.cumsum(self.HOUSEHOLD_SIZE[dwelling_size_cat])
# calculate number of occupants
self.occupants_in_building = 0
for dwelling in range(self.dwellings):
# get random_number and obtain x-value from cfd
rand_num = np.random.uniform(0, 1, 1)
index = np.argmax(rand_num <= cfd)
# get household size (number of occupants)
occupants_in_dwelling = household_size[index]
# add occupants to building
self.occupants_in_building += occupants_in_dwelling
#
[docs]
def calculate_number_of_occupants_non_residential(self, capacity=0.1):
"""
Calculates random number of occupants in the building based on the
recommended area per person for different building types from
https://www.engineeringtoolbox.com/number-persons-buildings-d_118.html.
"""
if self.use == 0: # commercial
# retail, supermarket, department stores
area_per_person = np.random.randint(3, 10) # in m2
elif self.use == 1: # industrial
# light manufacturing, heavy manufacturing
area_per_person = np.random.randint(10, 30) # in m2
elif self.use == 2: # public
# municipal buildings, library, museum
area_per_person = np.random.randint(3, 10) # in m2
# calculate number of occupants
# buildings are assumed to be occupied at a perc of total capacity
self.occupants_in_building = int(self.heated_area / area_per_person * capacity)
return self.occupants_in_building
#
[docs]
def get_building_thermal_properties_per_unit_area_residential(self):
"""
Gets building thermal properties from TABULA Web Tool data based on the
building typology [year_class, btype]
Sets the attributes:
- list: u: [u_roof, u_wall, u_floor, u_window] in W/(K m2)
- list: v: [v_usage, v_infiltration] in 1/h
- list: c: [c_roof, c_wall, c_floor] in J/(K m2)
"""
# Transmission losses (U-values) [W/(K m2)]
### ROOF
if self.ref_level[0] == 1:
u_roof = self.U_RES[0][0][self.year_class][self.size_class]
elif self.ref_level[0] == 2:
u_roof = self.U_RES[0][1][self.year_class][self.size_class]
elif self.ref_level[0] == 3:
u_roof = self.U_RES[0][2][self.year_class][self.size_class]
### WALL
if self.ref_level[1] == 1:
u_wall = self.U_RES[1][0][self.year_class][self.size_class]
elif self.ref_level[1] == 2:
u_wall = self.U_RES[1][1][self.year_class][self.size_class]
elif self.ref_level[1] == 3:
u_wall = self.U_RES[1][2][self.year_class][self.size_class]
### FLOOR
if self.ref_level[2] == 1:
u_floor = self.U_RES[2][0][self.year_class][self.size_class]
elif self.ref_level[2] == 2:
u_floor = self.U_RES[2][1][self.year_class][self.size_class]
elif self.ref_level[2] == 3:
u_floor = self.U_RES[2][2][self.year_class][self.size_class]
### WINDOW
if self.ref_level[3] == 1:
u_window = self.U_RES[3][0][self.year_class][self.size_class]
elif self.ref_level[3] == 2:
u_window = self.U_RES[3][1][self.year_class][self.size_class]
elif self.ref_level[3] == 3:
u_window = self.U_RES[3][2][self.year_class][self.size_class]
# Ventilation losses (air exchange rate) [1/h]
overall_ref_level = np.ceil(np.mean(self.ref_level))
if overall_ref_level == 1:
v_usage = self.V_RES[0][0][self.year_class][self.size_class]
v_inf = self.V_RES[1][0][self.year_class][self.size_class]
elif overall_ref_level == 2:
v_usage = self.V_RES[0][1][self.year_class][self.size_class]
v_inf = self.V_RES[1][1][self.year_class][self.size_class]
elif overall_ref_level == 3:
v_usage = self.V_RES[0][2][self.year_class][self.size_class]
v_inf = self.V_RES[1][2][self.year_class][self.size_class]
# Thermal capacitance [J/(K m2)]
# Impact of refurbishment is not considered
c_roof = self.C_RES[0][self.year_class][self.size_class]
c_wall = self.C_RES[1][self.year_class][self.size_class]
c_floor = self.C_RES[2][self.year_class][self.size_class]
self.u = [u_roof, u_wall, u_floor, u_window]
self.v = [v_usage, v_inf]
self.c = [c_roof, c_wall, c_floor]
#
[docs]
def get_building_thermal_properties_per_unit_area_non_residential(self):
"""
Gets building thermal properties based on:
>> source missing
Sets the following attributes:
- u list: [u_roof, u_wall, u_floor, u_window] in W/(K m2)
- v list: [v_usage, v_infiltration] in 1/h
- c list: [c_roof, c_wall, c_floor] in J/(K m2)
"""
# Transmission losses (U-values) [W/(K m2)]
### ROOF
if self.ref_level[0] == 1:
u_roof = self.U_NRES[0][0][self.year_class]
elif self.ref_level[0] == 2:
u_roof = self.U_NRES[0][1][self.year_class]
elif self.ref_level[0] == 3:
u_roof = self.U_NRES[0][2][self.year_class]
### WALL
if self.ref_level[1] == 1:
u_wall = self.U_NRES[1][0][self.year_class]
elif self.ref_level[1] == 2:
u_wall = self.U_NRES[1][1][self.year_class]
elif self.ref_level[1] == 3:
u_wall = self.U_NRES[1][2][self.year_class]
### FLOOR
if self.ref_level[2] == 1:
u_floor = self.U_NRES[2][0][self.year_class]
elif self.ref_level[2] == 2:
u_floor = self.U_NRES[2][1][self.year_class]
elif self.ref_level[2] == 3:
u_floor = self.U_NRES[2][2][self.year_class]
### WINDOW
if self.ref_level[3] == 1:
u_window = self.U_NRES[3][0][self.year_class]
elif self.ref_level[3] == 2:
u_window = self.U_NRES[3][1][self.year_class]
elif self.ref_level[3] == 3:
u_window = self.U_NRES[3][2][self.year_class]
# Ventilation losses (air exchange rate) [1/h]
# Impact of refurbishment is not considered
v_usage = self.V_NRES[0][self.year_class]
v_inf = self.V_NRES[1][self.year_class]
# Thermal capacitance [J/(K m2)]
# Impact of refurbishment is not considered
c_roof = self.C_NRES[0][self.year_class]
c_wall = self.C_NRES[1][self.year_class]
c_floor = self.C_NRES[2][self.year_class]
self.u = [u_roof, u_wall, u_floor, u_window]
self.v = [v_usage, v_inf]
self.c = [c_roof, c_wall, c_floor]
#
[docs]
def calculate_building_envelope_areas_residential(self):
"""
Calculates building envelope areas (wall, roof and window).
Residential: areas are calculated according to building typologies in TABULA.
Only the heated area is considered.
"""
# Floor-to-floor height
# from http://www.ctbuh.org/HighRiseInfo/TallestDatabase/Criteria/HeightCalculator/tabid/1007/language/en-GB/Default.aspx
# self.f2f_height = (randint(30, 32) / 10.0) # m
# from TABULA
self.f2f_height = 2.5 # m
self.env_areas = np.zeros(4)
# Building areas
### Floor
self.env_areas[2] = self.heated_area / self.floors
### Roof
self.env_areas[0] = self.ARATIO[0][self.year_class][self.size_class] * self.env_areas[2]
### Wall
self.env_areas[1] = self.ARATIO[1][self.year_class][self.size_class] * self.env_areas[2] * self.free_walls
### Window
self.env_areas[3] = self.ARATIO[2][self.year_class][self.size_class] * self.env_areas[2]
#
[docs]
def calculate_building_envelope_areas_non_residential(self):
"""
Calculates building envelope areas (wall, roof and window).
Non-residential: number of floors and window-to-wall ratio are
derived from statistics and used to calculate the building areas.
Only the heated area is considered.
"""
self.env_areas = np.zeros(4)
# Floor-to-floor height
# from http://www.ctbuh.org/HighRiseInfo/TallestDatabase/Criteria/HeightCalculator/tabid/1007/language/en-GB/Default.aspx
self.f2f_height = (randint(30, 39) / 10.0) # m
# Building areas
### Floor
self.env_areas[2] = self.heated_area / self.floors
### Roof
self.env_areas[0] = self.env_areas[2]
### Wall
width = np.sqrt(self.env_areas[2]) # [m] building is assumed to be a cube
self.env_areas[1] = self.free_walls * self.floors * self.f2f_height * width
### Window
# >> Add Window-to-Wall ratio for the different building types
# >> Source missing
self.env_areas[3] = (randint(1, 4) / 10.0) * self.env_areas[1]
#
[docs]
def calculate_building_window_areas(self, ):
"""
Calculate window areas in each direction to calculate solar gains.
"""
# Window areas oriented to [east, south, west, north]
self.window_areas = np.zeros(4)
# RESIDENTIAL
if self.building.use == 3:
for j in range(4):
self.window_areas[j] = self.building.window_area * self.WRATIO_ORIENTATION[
5 * j + int(self.building.size_class)]
# NON-RESIDENTIAL
else:
for j in range(4):
self.window_areas[j] = self.building.window_area * self.WRATIO_ORIENTATION[5 * j + 4]
#
[docs]
def calculate_building_thermal_properties(self):
"""
Calculates equivalent U-value, thermal capacitance (C) and time constant (Tau)
for the building. These properties are used in the first order thermal model.
"""
# multiply u and c by envelope areas
# consider adjustment factor for border situation acording to TABULA
b_tr = 0.5 # adjustment factor for surface bordering on soil
self.U_roof = self.u[0] * self.env_areas[0]
self.U_wall = self.u[1] * self.env_areas[1]
self.U_floor = self.u[2] * self.env_areas[2] * b_tr
self.U_window = self.u[3] * self.env_areas[3]
self.C_roof = self.c[0] * self.env_areas[0]
self.C_wall = self.c[1] * self.env_areas[1]
self.C_floor = self.c[2] * self.env_areas[2]
# calculate ventilation losses, V [W/K]
# air_specific_heat_capacity * air_density = 0.34 Wh/(m3 K)
# air flow rate related to usage is considered as half the value in TABULA (0.4)
self.V = (self.v[0] + self.v[1]) * self.env_areas[2] * self.f2f_height * 0.34
# Equivalent building thermal properties
### Transmission losses (U-value), U [W/K]
self.U = self.U_roof + self.U_wall + self.U_floor + self.U_window
# self.adjust_building_thermal_properties(ref_level, size_class, U)
### Thermal capacitance, C [J/K]
self.C = self.C_floor + self.C_wall + self.C_roof
# Time constant, Tau [s]
if self.U != 0:
self.Tau = self.C / (self.U + self.V)
else:
self.Tau = np.nan
#
[docs]
def adjust_building_thermal_properties(self):
"""
Empirical adjustment of U-values to match TABULA results
"""
if np.mean(self.ref_level) >= 1. and np.mean(self.ref_level) < 2.:
if self.size_class == 0: # SFH
self.U = self.U * 1.
elif self.size_class == 1: # TH
self.U = self.U * 1.2
elif self.size_class == 2: # MFH
self.U = self.U * 1.18
elif self.size_class == 3: # AB
self.U = self.U * 1.18
elif np.mean(self.ref_level) >= 2. and np.mean(self.ref_level) < 3.:
if self.size_class == 0: # SFH
self.U = self.U * 1.35
elif self.size_class == 1: # TH
self.U = self.U * 1.31
elif self.size_class == 2: # MFH
self.U = self.U * 1.53
elif self.size_class == 3: # AB
self.U = self.U * 1.5
elif np.mean(self.ref_level) >= 3.:
if self.size_class == 0: # SFH
self.U = self.U * 1.6
elif self.size_class == 1: # TH
self.U = self.U * 1.65
elif self.size_class == 2: # MFH
self.U = self.U * 1.7
elif self.size_class == 3: # AB
self.U = self.U * 1.55
#
[docs]
def calculate_building_Tset(self):
"""
Derives a target temperature by choosing a random temperature from Tset_mean
+/- dT. Values differ for different building types.
From http://tc76.org/spc100/docs/IBP%2018599/18599-10.pdf
"""
if self.use == 0: # commercial
Tset_mean = self.TSET[0][0]
dT = self.TSET[1][0]
elif self.use == 1: # industrial
Tset_mean = self.TSET[0][1]
dT = self.TSET[1][1]
elif self.use == 2: # public
Tset_mean = self.TSET[0][2]
dT = self.TSET[1][2]
elif self.use == 3: # residential
Tset_mean = self.TSET[0][3]
dT = self.TSET[1][3]
self.Tset = float(randint(Tset_mean - dT, Tset_mean + dT))
#
[docs]
def calculate_building_active_hours(self):
"""
Assigns random start and end hours for building active hours.
Values differ for different building types.
Sets the following attributes:
self.active_hours list: [(start0, end0), (start1, end1)] in h
"""
### Residential
if self.use == 3:
self.active_hours = [0, 23]
### Non-residential
else:
if self.use == 0: # commercial
start_mean = self.SCHEDULE[0][0][0]
end_mean = self.SCHEDULE[0][0][1]
dt = self.SCHEDULE[1][0]
elif self.use == 1: # industrial
start_mean = self.SCHEDULE[0][1][0]
end_mean = self.SCHEDULE[0][1][1]
dt = self.SCHEDULE[1][1]
elif self.use == 2: # public
start_mean = self.SCHEDULE[0][2][0]
end_mean = self.SCHEDULE[0][2][1]
dt = self.SCHEDULE[1][2]
start = randint(start_mean - dt, start_mean + dt)
end = randint(end_mean - dt, end_mean + dt)
self.active_hours = [start, end]
## Building parameters: Hot water demand
#
[docs]
def calculate_daily_hot_water_demand(self):
"""
Returns the daily hot water demand by getting a random value
from the cdf function based on the statistics from VDI 3807-3
(specific dhw demand in m3/m2 of living area)
"""
# calculate specific dhw demand
rand_num = np.random.uniform(0, 1, 1)
specific_dhw = self.dhw_prob[0](rand_num)[0]
# calculate building dhw demand
self.hw_demand_day = specific_dhw * self.heated_area
#
[docs]
def parametrize_hot_water_tank(self, X=1.5):
"""
Calculates size and initial state of hot water tank.
Size is X times the calculated daily demand.
"""
# set tank capacity as function of daily hot water demand limit
self.hw_tank_cap = X * self.hw_demand_day
#
[docs]
def set_hot_water_tank_initial_state(self):
"""
"""
# define initial state of hot water tank as random percentage of capacity
self.hw_tank_volume_t0 = np.random.uniform(0, 1, 1)[0] * self.building.hw_tank_cap
# Building occupancy
# --------------------------------------------------------------------------------
[docs]
def calculate_building_activity_occupancy_vector(self):
"""
Calculate vector of activity in building, i.e. percentage of occupied dwellings (for space heating)
Active_hours (scheduled), building occupancy and weekends are considered
"""
# initialize matrices
self.activity_vector = np.ones([self.nts]) * 1. # Building activity vector
self.occupancy_vector = np.ones([self.nts]) * 1. # Number of occupants in building per timestep
# calculate occupant schedule
if self.building.use == 3: # residential
self.calculate_occupants_schedule()
# calculate activity vector
for iii in range(0, self.nts):
hour = self.dt_vector[iii].hour
wd = self.dt_vector[iii].weekday() # 0 Monday ... 6 Sunday
if self.building.use == 0: # commercial
if self._workday_weekend:
if wd == 6: # no activities on Sunday
self.activity_vector[iii] = 0.
else:
if hour < self.building.act_start or hour > self.building.act_end:
self.activity_vector[iii] = 0.
# calculate occupancy vector
self.occupancy_vector[iii] = self.activity_vector[iii] * self.building.occupants
elif self.building.use == 1 or self.building.use == 2: # industrial/public
if self._workday_weekend:
if wd >= 5: # no activities on Saturday/Sunday
self.activity_vector[iii] = 0.
else:
if hour < self.building.act_start or hour > self.building.act_end:
self.activity_vector[iii] = 0.
# calculate occupancy vector
self.occupancy_vector[iii] = self.activity_vector[iii] * self.building.occupants
elif self.building.use == 3: # residential
# calculate building occupancy considering active population if the flag active_pop is True
if self._active_population:
# initialize counters
not_in_building = 0
if self._workday_weekend:
if wd < 5: # weekends are considered as active
# occupant loop
for occupant in self.occupant_vector:
# check if occupant is in the building
if hour > occupant[1][0][1] and hour < occupant[1][1][0]: # not at home
not_in_building += 1
else:
# occupant loop
for occupant in self.occupant_vector:
# check if occupant is in the building
if hour > occupant[1][0][1] and hour < occupant[1][1][0]: # not at home
not_in_building += 1
# check occupancy share
self.occupancy_vector[iii] = self.building.occupants - not_in_building
self.activity_vector[iii] = (self.building.occupants - not_in_building) / self.building.occupants
else:
# calculate occupancy vector
self.occupancy_vector[iii] = self.activity_vector[iii] * self.building.occupants
#
[docs]
def calculate_occupants_schedule(self):
"""
A schedule is assigned to every occupant based on studying/working schedule.
Sets the following attributes:
occupant_vector: list = [dwelling, [occupant, [schedule]]] for occupant and dwelling in building
"""
# calculate occupancy based on percentage of active population
perc_working_pop = 0.583 # percentage of population that works or studies
# from https://ergebnisse.zensus2011.de/#StaticContent:091840148148,BEG_1_6_1,m,table
# occupant vector
O = int(self.building.occupants)
self.occupant_vector = [[[], []] for occupant in range(O)]
for iii in range(O):
# add occupant to vector
self.occupant_vector[iii][0] = iii + 1
# compare random number with percentage of working/student population
# to determine if occupants are at home
rand_num = np.random.uniform(0, 1, 1)
if rand_num > perc_working_pop: # occupant leaves the building to work/study
# calculate working/studying start/end time from "public" building usage
start_mean = self.SCHEDULE[0][2][0]
end_mean = self.SCHEDULE[0][2][1]
dt = self.SCHEDULE[1][2]
start = randint(start_mean - dt, start_mean + dt)
end = randint(end_mean - dt, end_mean + dt)
# define active_hours per occupant
self.occupant_vector[iii][1] = [[0, start], [end, 23]]
else:
self.occupant_vector[iii][1] = [[0, 23], [0, 0]]
# Results
# --------------------------------------------------------------------------------
[docs]
def calculate_annual_demand(self, data):
"""
Calculate the annual energy demand by weighting the heating demand of typical days
"""
# Divide data into days
## initialize variables
days_in_sim = self.number_of_typ_days
data_in_days = np.zeros((days_in_sim, (24 * 60) // self.resolution), dtype=float)
energy = np.zeros(days_in_sim, dtype=float)
row_start = np.zeros(days_in_sim, dtype=int)
row_end = np.zeros(days_in_sim, dtype=int)
## arrange data
for day in range(days_in_sim):
if day == 0:
row_start[day] = 0
row_end[day] = 24 * 60 / self.resolution
else:
row_start[day] = row_end[day - 1]
row_end[day] = 24 * 60 / self.resolution + row_start[day]
data_in_days[day, :] = np.reshape(data[row_start[day]:row_end[day]], ((24 * 60) // self.resolution,))
# add energy per day and multiply by weight of typical day
energy[day] = data_in_days[day, :].sum() * self.resolution * 1 / 60 * self.weights[day]
# calculate annual demand
annual_demand = energy.sum()
return annual_demand
#
[docs]
def plot_timeseries(self,
space_heating=True, Tb=False,
hot_water=True,
total=True,
xticks=('month', 3)):
"""
Plots heat demand timeseries
"""
if not os.path.exists(self.result_dir):
os.makedirs(self.result_dir)
if space_heating:
fig_name = '{}/SpaceHeatingDemand_{}_{}_{}_{}.png'.format(self.result_dir,
self.building.use, self.building.year_class,
self.building.size_class, self.building.bid)
UrbanHeatPro.plot_timeseries(self.dt_vector,
[self.space_heating_power], ['Space heating demand'], fig_name,
ynumticks='auto', ylabel='Power [kW]', ylim0=True, yfactor=1e3,
xticks=xticks)
if Tb:
fig_name = '{}/BuildingTemperature_{}_{}_{}_{}.png'.format(self.result_dir,
self.building.use, self.building.year_class,
self.building.size_class, self.building.bid)
UrbanHeatPro.plot_timeseries(self.dt_vector,
[self.Tamb, self.Tb], ['T_amb', 'T_b'], fig_name,
ynumticks='auto', ylabel='Temperature [degC]', ylim0=False, yfactor=1,
xticks=xticks)
if hot_water:
fig_name = '{}/HotWaterDemand_{}_{}_{}_{}.png'.format(self.result_dir,
self.building.use, self.building.year_class,
self.building.size_class, self.building.bid)
UrbanHeatPro.plot_timeseries(self.dt_vector,
[self.hot_water_power], ['Hot water demand'], fig_name,
ynumticks='auto', ylabel='Power [kW]', ylim0=True, yfactor=1e3,
xticks=xticks)
if total:
fig_name = '{}/TotalHeatDemand_{}_{}_{}_{}.png'.format(self.result_dir,
self.building.use, self.building.year_class,
self.building.size_class, self.building.bid)
UrbanHeatPro.plot_stacked_timeseries(self.dt_vector,
[self.hot_water_power, self.space_heating_power],
['Hot water', 'Space heating'], fig_name,
ynumticks='auto', ylabel='Power [kW]', ylim0=True, yfactor=1e3,
xticks=xticks)
#
[docs]
def save_csv(self):
"""
Saves key building parameters and heat demand (space heating, hot water and
total) as timeseries.
"""
if not os.path.exists(self.result_dir):
os.makedirs(self.result_dir)
#
try:
self.hot_water_power *= 1.
except:
self.hot_water_power = np.zeros([self.nts])
self.hot_water_tank_m3 = np.zeros([self.nts])
try:
self.total_power_delayed *= 1.
except:
self.total_power_delayed = np.zeros([self.nts])
array_to_save = np.array([self.dt_vector_excel, self.activity_vector,
self.Tamb,
self.Tb,
self.occupancy_vector,
self.space_heating_power, self.internal_gains, self.solar_gains,
self.hot_water_power, self.hot_water_tank_m3,
self.total_power,
self.total_power_delayed]).transpose()
filename = '{}/HeatDemand_{}_{}_{}_{}.csv'.format(self.result_dir,
int(self.building.use), int(self.building.year_class),
int(self.building.size_class), int(self.building.bid))
# Column names
with open(filename, 'w') as text_file:
text_file.write(
'datenum;active?;Tamb_degC;Tb_degC;Occupants;SpaceHeatingD_W;int_gains_W;sol_gains_W;HotWaterD_W'
';HotWaterTank_m3;TotalD_W;DelayedTotalD_W\n')
# Time series
with open(filename, 'a') as f:
np.savetxt(f, array_to_save, delimiter=';', fmt='%.2f')
#
[docs]
def save_load_duration_curve(self):
"""
Save sorted demand
"""
filename = '{}/LoadDurationCurve_{}_{}_{}_{}.csv'.format(self.result_dir,
self.use, self.year_class, self.size_class, self.bid)
# sort demand to save
array_to_save = np.sort(self.total_power_delayed)[::-1].transpose()
with open(filename, 'a') as f:
np.savetxt(f, array_to_save, delimiter=';', fmt='%.4f')
#
[docs]
def save_dhw_debug_csv(self):
"""
Saves debug values for dhw demand.
"""
array_to_save = np.array(self.dhw_debug)
filename = '{}/DHWDemand_{}_{}_{}_{}.csv'.format(self.result_dir,
self.use, self.year_class, self.size_class, self.bid)
# Building parameters
with open(filename, 'w') as text_file:
text_file.write(
'timstep;hour;day;day_in_year;daily_dhw;daily_limit;activity;shower?;bath?;medium?;small?;fr_shower'
';fr_bath;fr_medium;fr_small;d_shower;d_bath;d_medium;d_small;c_shower;c_bath;c_medium;c_small'
';daily_agg;daily_convergence\n')
# Time series
with open(filename, 'a') as f:
np.savetxt(f, array_to_save, delimiter=';', fmt='%.4f')