Source code for energy_demand.dwelling_stock.dw_stock

"""Virtual Dwelling Generator - Generates a virtual dwelling stock
"""
import numpy as np
from energy_demand.basic import lookup_tables

from energy_demand.technologies import diffusion_technologies


[docs]class Dwelling(object): """Dwelling or aggregated group of dwellings Arguments ---------- curr_yr : int Current year of simulation coordinates : float coordinates dwtype : int Dwelling type id. Description can be found in `daytype_lu` house_id : int Unique ID of dwelling or dwelling group age : int Age of dwelling in years (year the building was built) pop : float Dwelling population floorarea : float Floor area of dwelling hlc : float Heat loss coefficient hdd : float Heating degree days Note ----- - Depending on service or residential model, not all attributes are filled (then they are inistialised as None or zero) - For every dwelling, the scenario drivers are calculated for each enduse """ def __init__( self, curr_yr, coordinates, floorarea, enduses, driver_assumptions, population=None, age=None, dwtype=None, sector=None, gva=None ): """Constructor of Dwelling Class """ self.curr_yr = curr_yr self.enduses = enduses self.longitude = coordinates['longitude'] self.latitude = coordinates['latitude'] self.dwtype = dwtype self.age = age self.population = population self.floorarea = floorarea self.sector = sector self.gva = gva self.hlc = get_hlc(dwtype, age) # Calculate heat loss coefficient with age and dwelling type if possible self.calc_scenario_driver(driver_assumptions) # Generate attribute for each enduse containing calculated scenario driver value assert floorarea != 0
[docs] def calc_scenario_driver(self, driver_assumptions): """Sum scenario drivers per enduse and add as attribute Arguments --------- driver_assumptions : dict Scenario drivers for every enduse """ for enduse in self.enduses: scenario_driver_value = 1 #used to sum (not zero!) # If there is no scenario drivers for enduse, set to standard value 1 if enduse not in driver_assumptions: Dwelling.__setattr__(self, enduse, scenario_driver_value) else: scenario_drivers = driver_assumptions[enduse] # Iterate scenario driver and get attriute to multiply values try: for scenario_driver in scenario_drivers: # If scenario driver is set to zero, do not use this driver driver_value = getattr(self, scenario_driver) # Ignore zero driver values if driver_value == 0: pass else: scenario_driver_value *= driver_value except TypeError: #logging.info("Scenario driver `%s` calculation not possible", scenario_driver) pass Dwelling.add_new_attribute( self, enduse, scenario_driver_value) assert scenario_driver_value != 0
[docs] def add_new_attribute(self, name, value): """Add a new self asttribute to DwellingStock """ setattr(self, name, value)
[docs]class DwellingStock(object): """Class of the building stock in a region """ def __init__(self, dwellings, enduses): """Returns a new building stock object for every `region`. Arguments ---------- dwellings : list List containing all dwelling objects enduses : list Enduses """ self.dwellings = dwellings self.population = get_tot_pop(dwellings) # Calculate pop of dwelling stock # Calculate enduse specific scenario driver for enduse in enduses: enduse_scenario_driver = self.get_scenario_driver(enduse) DwellingStock.add_new_attribute( self, enduse, enduse_scenario_driver)
[docs] def get_scenario_driver(self, enduse): """Sum all scenario driver for an enduse Arguments ---------- enduse: string Enduse to calculate scenario drivers """ sum_driver = 0 for dwelling in self.dwellings: sum_driver += getattr(dwelling, enduse) return sum_driver
[docs] def add_new_attribute(self, name, value): """Add a new self asttribute to DwellingStock """ setattr(self, name, value)
[docs]def get_tot_pop(dwellings): """Get total population of all dwellings Return ------ tot_pop : float or bool If population is not provided, return `None`, otherwise summed population of all dwellings """ tot_pop = 0 for dwelling in dwellings: if dwelling.population is None: return None else: tot_pop += dwelling.population return tot_pop
[docs]def get_floorare_pp( floorarea, reg_pop_by, base_yr, sim_period, assump_diff_floorarea_pp ): """Calculate future floor area per person depending on assumptions on final change and base year data Arguments ---------- floorarea : dict Floor area base year for all regions reg_pop_by : dict Population of base year for all regions base_yr, : int base year sim_period: list Simulation period assump_diff_floorarea_pp : float Assumption of change in floor area up to end of simulation Returns ------- floor_area_pp : dict Contains all values for floor area per person for every year Note ---- - Linear change of floor area per person is assumed over time """ floor_area_pp = {} if reg_pop_by == 0: floor_area_pp[base_yr] = 0 else: # Floor area per person of base year floor_area_pp[base_yr] = floorarea / reg_pop_by for curr_yr in sim_period: if curr_yr == base_yr: pass else: # Floor area of current year = floor area of base year * change floor_area_pp[curr_yr] = floor_area_pp[base_yr] * (1 + assump_diff_floorarea_pp[curr_yr]) return floor_area_pp
[docs]def get_dwtype_floor_area( dwtype_floorarea_by, dwtype_floorarea_future, base_yr, sim_period ): """Calculates the floor area per dwelling type for every year Arguments ---------- dwtype_distr_by : dict Distribution of dwelling types base year dwtype_floorarea_future : dict Distribution of future dwelling types end year base_yr : list Simulation parameters sim_period : list Simulation period sim_period_yrs : list Nr of simlated years Returns ------- dwtype_floor_area : dict Contains the floor area change per dwelling type Note ----- - A linear change over time is assumed Example ------- out = {year: {'dwtype': 0.3}} """ dwtype_floor_area = {} dwtype_floor_area[base_yr] = dwtype_floorarea_by # Base year # Simulation years for curr_yr in sim_period: if curr_yr == base_yr: pass else: y_distr = {} for dwtype in dwtype_floorarea_by: val_by = dwtype_floorarea_by[dwtype] val_future = dwtype_floorarea_future[dwtype] yr_until_changed = dwtype_floorarea_future['yr_until_changed'] val_cy = diffusion_technologies.linear_diff( base_yr, curr_yr, val_by, val_future, yr_until_changed) y_distr[dwtype] = val_cy dwtype_floor_area[curr_yr] = y_distr return dwtype_floor_area
[docs]def get_dwtype_distr( dwtype_distr_by, dwtype_distr_fy, base_yr, sim_period ): """Calculates the annual distribution of dwelling types based on assumption of base and end year distribution Arguments ---------- dwtype_distr_by : dict Distribution of dwelling types base year dwtype_distr_fy : dict Distribution of dwelling types end year Returns ------- dwtype_distr : dict Contains all dwelling type distribution for every year Note ----- - A linear change over time is assumed Example ------- out = {year: {'dwtype': 0.3}} """ dwtype_distr = {} dwtype_distr[base_yr] = dwtype_distr_by # Base year for curr_yr in sim_period: if curr_yr == base_yr: pass else: y_distr = {} for dwtype in dwtype_distr_by: val_by = dwtype_distr_by[dwtype] val_future = dwtype_distr_fy[dwtype] yr_until_changed = dwtype_distr_fy['yr_until_changed'] val_cy = diffusion_technologies.linear_diff( base_yr, curr_yr, val_by, val_future, yr_until_changed) y_distr[dwtype] = val_cy dwtype_distr[curr_yr] = y_distr # Test if distribution is 100% for year in dwtype_distr: np.testing.assert_almost_equal( sum(dwtype_distr[year].values()), 1.0, decimal=5, err_msg='The distribution of dwelling types went wrong', verbose=True) return dwtype_distr
[docs]def ss_dw_stock( region, enduses, sectors, scenario_data, reg_coord, assumptions, curr_yr, base_yr, virtual_building_stock_criteria ): """Create dwelling stock for service sector Arguments ---------- regions : dict Regions data : dict Data container Returns ------- dwelling_stock : list List with objects Note ---- - Iterate years and change floor area depending on assumption on linear change up to ey """ dw_stock = [] for sector in sectors: pop_by = scenario_data['population'][base_yr][region] pop_cy = scenario_data['population'][curr_yr][region] # Floor area if virtual_building_stock_criteria: # If virtual building stock, change floor area proportionally to population lin_diff_factor = pop_cy / pop_by floorarea_sector_by = scenario_data['floor_area']['ss_floorarea'][base_yr][region][sector] floorarea_sector_cy = floorarea_sector_by * lin_diff_factor else: try: floorarea_sector_by = scenario_data['floor_area']['ss_floorarea'][base_yr][region][sector] except IndexError: floorarea_sector_by = scenario_data['floor_area']['ss_floorarea'][base_yr][region] try: floorarea_sector_cy = scenario_data['floor_area']['ss_floorarea'][curr_yr][region][sector] except IndexError: floorarea_sector_cy = scenario_data['floor_area']['ss_floorarea'][curr_yr][region] # GVA data try: gva_sector_lu = lookup_tables.economic_sectors_regional_MISTRAL() gva_nr = gva_sector_lu[sector]['match_int'] gva_dw_data = scenario_data['gva_industry'][curr_yr][region][gva_nr] except KeyError: # If not sector specific GVA, use overal GVA per head gva_dw_data = scenario_data['gva_per_head'][curr_yr][region] # Create dwelling objects dw_stock.append( Dwelling( curr_yr=curr_yr, coordinates=reg_coord[region], population=pop_cy, floorarea=floorarea_sector_cy, enduses=enduses, driver_assumptions=assumptions.scenario_drivers, sector=sector, gva=gva_dw_data)) dwelling_stock = DwellingStock(dw_stock, enduses) return dwelling_stock
[docs]def rs_dw_stock( region, assumptions, scenario_data, sim_yrs, dwelling_types, enduses, reg_coord, driver_assumptions, curr_yr, base_yr, virtual_building_stock_criteria ): """Creates a virtual building stock for every year and region Arguments ---------- region : dict Region name curr_yr : int Current year Returns ------- dwelling_stock : dict Building stock wei reg_dw_stock_by : Base year building stock reg_building_stock_yr : Building stock for every simulation year Notes ----- - The assumption about internal temperature change is used as for each dwelling the hdd are calculated based on wheater data and assumption on t_base - Doesn't take floor area as an input but calculates floor area based on floor area pp parameter. However, floor area could be read in by: 1.) Inserting `tot_floorarea_cy = data['rs_floorarea'][curr_yr]` 2.) Replacing 'dwtype_floor_area', 'dwtype_distr' and 'data_floorarea_pp' with more specific information from real building stock model """ # Get changes in absolute floor area per dwelling type over time dwtype_floor_area = get_dwtype_floor_area( assumptions.dwtype_floorarea_by, assumptions.dwtype_floorarea_fy, base_yr, sim_yrs) # Get distribution of dwelling types of all simulation years dwtype_distr = get_dwtype_distr( assumptions.dwtype_distr_by, assumptions.dwtype_distr_fy, base_yr, sim_yrs) # Get fraction of total floorarea for every dwelling type floorarea_p = get_floorarea_dwtype_p( dwelling_types, dwtype_floor_area, dwtype_distr) population_by = scenario_data['population'][base_yr][region] population_cy = scenario_data['population'][curr_yr][region] floorarea_by = scenario_data['floor_area']['rs_floorarea'][base_yr][region] if virtual_building_stock_criteria: # Get floor area per person for every simulation year data_floorarea_pp = get_floorare_pp( floorarea_by, scenario_data['population'][base_yr][region], base_yr, sim_yrs, assumptions.non_regional_vars['assump_diff_floorarea_pp']) # Calculate new necessary floor area per person of current year floorarea_pp_cy = data_floorarea_pp[curr_yr] # Calculate new floor area tot_floorarea_cy = floorarea_pp_cy * population_cy else: tot_floorarea_cy = scenario_data['floor_area']['rs_floorarea'][curr_yr][region] # Get floor area per person for every simulation year data_floorarea_pp = get_floorare_pp( tot_floorarea_cy, scenario_data['population'][base_yr][region], base_yr, sim_yrs, assumptions.non_regional_vars['assump_diff_floorarea_pp']) # Calculate new necessary floor area per person of current year floorarea_pp_cy = data_floorarea_pp[curr_yr] if population_by != 0: floorarea_pp_by = floorarea_by / population_by # [m2 / person] else: floorarea_pp_by = 0 new_floorarea_cy = tot_floorarea_cy - floorarea_by # Only calculate changing if curr_yr == base_yr: dw_stock_base = generate_dw_existing( driver_assumptions=driver_assumptions, enduses=enduses, reg_coord=reg_coord, region=region, curr_yr=curr_yr, dw_lu=dwelling_types, floorarea_p=floorarea_p[base_yr], floorarea_by=floorarea_by, dwtype_age_distr_by=assumptions.dwtype_age_distr[base_yr], floorarea_pp=floorarea_pp_by, gva_dw_data=scenario_data['gva_per_head'][curr_yr][region]) # Create regional base year building stock dwelling_stock = DwellingStock( dw_stock_base, enduses) else: """The number of people in the base year dwelling stock may change. If the floor area pp decreased with constant pop, the same number of people will be living in too large houses. It is not assumed that area is demolished. """ if virtual_building_stock_criteria: floor_area_cy = floorarea_pp_cy * population_by else: floor_area_cy = scenario_data['floor_area']['rs_floorarea'][curr_yr][region] if floor_area_cy > floorarea_by: demolished_area = 0 else: demolished_area = floorarea_by - floor_area_cy remaining_area = floorarea_by - demolished_area # Generate stock for existing area dw_stock_cy = generate_dw_existing( driver_assumptions=driver_assumptions, enduses=enduses, reg_coord=reg_coord, region=region, curr_yr=curr_yr, dw_lu=dwelling_types, floorarea_p=floorarea_p[curr_yr], floorarea_by=remaining_area, dwtype_age_distr_by=assumptions.dwtype_age_distr[base_yr], floorarea_pp=floorarea_pp_cy, gva_dw_data=scenario_data['gva_per_head'][curr_yr][region]) # Append buildings of new floor area to if new_floorarea_cy > 0: dw_stock_cy = generate_dw_new( driver_assumptions=driver_assumptions, reg_coord=reg_coord, enduses=enduses, dwtypes=dwelling_types, region=region, curr_yr=curr_yr, floorarea_p_by=floorarea_p[curr_yr], floorarea_pp_cy=floorarea_pp_cy, dw_stock_new_dw=dw_stock_cy, new_floorarea_cy=new_floorarea_cy, gva_dw_data=scenario_data['gva_per_head'][curr_yr][region]) else: pass # no new floor area is added # Generate region and save it in dictionary (Add old and new buildings to stock) dwelling_stock = DwellingStock( dw_stock_cy, enduses) return dwelling_stock
[docs]def get_floorarea_dwtype_p(dw_lookup, dw_floorarea, dwtype_distr): """Calculates the percentage of the total floor area belonging to each dwelling type. Depending on average floor area per dwelling type and the dwelling type distribution, the percentages are calculated for ever simulation year Arguments ---------- dw_lookup : dw_lookup Dwelling types dw_floorarea : dict Floor area per type and year dwtype_distr : dict Distribution of dwelling type over the simulation period Returns ------- dw_floorarea_p : dict Contains the percentage of the total floor area for each dwtype for every simulation year (must be 1.0 in tot) Notes ----- This calculation is necessary as the share of dwelling types may differ depending the year """ dw_floorarea_p = {} for curr_yr, type_distr_p in dwtype_distr.items(): area_dw_type = {} # Calculate share of dwelling area based on absolute size and distribution for dw_type in dw_lookup.values(): # Get absolut size of dw_type area_dw_type[dw_type] = type_distr_p[dw_type] * dw_floorarea[curr_yr][dw_type] # Convert absolute values into percentages tot_area = sum(area_dw_type.values()) for dw_type, dw_type_area in area_dw_type.items(): area_dw_type[dw_type] = dw_type_area / tot_area dw_floorarea_p[curr_yr] = area_dw_type return dw_floorarea_p
[docs]def generate_dw_existing( driver_assumptions, enduses, reg_coord, region, curr_yr, dw_lu, floorarea_p, floorarea_by, dwtype_age_distr_by, floorarea_pp, gva_dw_data ): """Generates dwellings according to age, floor area and distribution assumption Arguments ---------- assumptions : dict Assumptions enduses : list Enduses region : dict Region name curr_yr : int Base year dw_lu : dict Dwelling type look-up floorarea_p : dict Fraction of floor area per dwelling type floorarea_by : dict Floor area of base year dwtype_age_distr_by : dict Age distribution of dwelling floorarea_pp : dict Floor area per person tot_floorarea_cy : float Floor are in current year pop_by : dict Population in base year Return ------ dw_stock_by : list Dwelling stocks in a list """ dw_stock_by, control_pop, control_floorarea = [], 0, 0 for dwtype_name in dw_lu.values(): # Calculate floor area per dwelling type dwtype_floorarea = floorarea_p[dwtype_name] * floorarea_by # Distribute according to age for dwtype_age, distribution in dwtype_age_distr_by.items(): # Floor area of dwelling_class_age (distribute proportionally floor area) dwtype_age_class_floorarea = dwtype_floorarea * distribution # Floor area per person is divided by base area value to calc pop if floorarea_pp != 0: pop_dwtype_age_class = dwtype_age_class_floorarea / floorarea_pp else: pop_dwtype_age_class = 0 # create building object dw_stock_by.append( Dwelling( curr_yr=curr_yr, coordinates=reg_coord[region], floorarea=dwtype_age_class_floorarea, enduses=enduses, driver_assumptions=driver_assumptions, population=pop_dwtype_age_class, age=float(dwtype_age), dwtype=dwtype_name, gva=gva_dw_data)) control_floorarea += dwtype_age_class_floorarea control_pop += pop_dwtype_age_class return dw_stock_by
[docs]def generate_dw_new( driver_assumptions, reg_coord, enduses, dwtypes, region, curr_yr, floorarea_p_by, floorarea_pp_cy, dw_stock_new_dw, new_floorarea_cy, gva_dw_data ): """Generate dwelling objects for all new dwellings All new dwellings are appended to the existing building stock of the region Arguments ---------- data : dict Data container region : str Region curr_yr : int Current year floorarea_p_by : dict Fraction of floorarea in base year floorarea_pp_cy : dict Floor area per person in current year dw_stock_new_dw : dict New dwellings new_floorarea_cy : dict New floorarea in current year Returns ------- dw_stock_new_dw : list List with appended dwellings Notes ----- The floor area id divided proprtionally depending on dwelling type Then the population is distributed builindg is creatd """ control_pop, control_floorarea = 0, 0 for dwtype_name in dwtypes.values(): # Calculate new floor area per dewlling type dw_type_new_floorarea = floorarea_p_by[dwtype_name] * new_floorarea_cy # Calculate pop (Floor area is divided by floorarea_per_person) pop_dwtype_new_build_cy = dw_type_new_floorarea / floorarea_pp_cy # create building object dw_stock_new_dw.append( Dwelling( curr_yr=curr_yr, coordinates=reg_coord[region], floorarea=dw_type_new_floorarea, enduses=enduses, driver_assumptions=driver_assumptions, population=pop_dwtype_new_build_cy, age=curr_yr, dwtype=dwtype_name, gva=gva_dw_data)) control_floorarea += dw_type_new_floorarea control_pop += pop_dwtype_new_build_cy # Test if floor area and pop are the same #assert round(new_floorarea_cy, 3) == round(control_floorarea, 3) #assert round(new_floorarea_cy/floorarea_pp_cy, 2) == round(control_pop, 2) return dw_stock_new_dw
[docs]def get_hlc(dw_type, age): """Calculates the linearly derived heat loss coeeficients depending on age and dwelling type Arguments ---------- dw_type : int Dwelling type age : int Age of dwelling Returns ------- hls : Heat loss coefficient [W/m2 * K] Notes ----- Source: Linear trends derived from Table 3.17 ECUK Tables https://www.gov.uk/government/collections/energy-consumption-in-the-uk """ if dw_type is None or age is None: #logging.debug("The HLC could not be calculated of a dwelling age: {} dw_type: {}".format(dw_type, age)) return None else: # Dict with linear fits for all different dwelling types {dw_type: [slope, constant]} linear_fits_hlc = { 'detached': [-0.0223, 48.292], 'semi_detached': [-0.0223, 48.251], 'terraced': [-0.0223, 48.063], 'flat': [-0.0223, 47.02], 'bungalow': [-0.0223, 48.261]} # Get linearly fitted value hlc = linear_fits_hlc[dw_type][0] * age + linear_fits_hlc[dw_type][1] return hlc