Source code for honeybee_energy.baseline.create

"""Module for creating baseline buildings conforming to standards."""
import os

from ladybug.futil import csv_to_matrix
from honeybee.boundarycondition import Outdoors
from honeybee.facetype import Wall, RoofCeiling

from ..material.glazing import EnergyWindowMaterialSimpleGlazSys
from ..construction.window import WindowConstruction
from ..lib.constructionsets import construction_set_by_identifier
from ..lib.programtypes import program_type_by_identifier
from ..hvac._template import _TemplateSystem
from ..hvac.heatcool._base import _HeatCoolBase
from ..hvac.allair.vav import VAV
from ..hvac.allair.pvav import PVAV
from ..hvac.allair.psz import PSZ
from ..hvac.allair.ptac import PTAC
from ..hvac.allair.furnace import ForcedAirFurnace
from ..hvac.doas.fcu import FCUwithDOAS
from ..shw import SHWSystem


[docs]def model_to_baseline(model, climate_zone, building_type='NonResidential', floor_area=None, story_count=None, lighting_by_building=False): """Convert a Model to be conformant with ASHRAE 90.1 appendix G. This includes running all other functions contained within this group to adjust the geometry, constructions, lighting, HVAC, SHW, and remove any clearly-defined energy conservation measures like daylight controls. Note that all schedules are essentially unchanged, meaning that additional post-processing of setpoints may be necessary to account for energy conservation strategies like expanded comfort ranges, ceiling fans, and personal thermal comfort devices. It may also be necessary to adjust electric equipment loads in cases where such equipment qualifies as an energy conservation strategy or hot water loads in cases where low-flow fixtures are implemented. Note that not all versions of ASHRAE 90.1 use this exact definition of a baseline model but version 2016 and onward conform to it. It is essentially an adjusted version of the 90.1-2004 methods. Args: model: A Honeybee Model that will be converted to conform to the ASHRAE 90.1 appendix G baseline. climate_zone: Text indicating the ASHRAE climate zone. This can be a single integer (in which case it is interpreted as A) or it can include the A, B, or C qualifier (eg. 3C). building_type: Text for the building type that the Model represents. This is used to determine the baseline window-to-wall ratio and HVAC system. If the type is not recognized or is "Unknown", it will be assumed that the building is a generic NonResidential. The following have specified meaning per the standard. * NonResidential * Residential * MidriseApartment * HighriseApartment * LargeOffice * MediumOffice * SmallOffice * Retail * StripMall * PrimarySchool * SecondarySchool * SmallHotel * LargeHotel * Hospital * Outpatient * Warehouse * SuperMarket * FullServiceRestaurant * QuickServiceRestaurant * Laboratory * Courthouse floor_area: A number for the floor area of the building that the model is a part of in m2. If None, the model floor area will be used. (Default: None). story_count: An integer for the number of stories of the building that the model is a part of. If None, the model stories will be used. (Default: None). lighting_by_building: A boolean to note whether the building_type should be used to assign the baseline lighting power density (True), which will use the same value for all Rooms in the model, or a space-by-space method should be used (False). To use the space-by-space method, the model should either be built with the programs that ship with Ladybug Tools in honeybee-energy-standards or the baseline_watts_per_area should be correctly assigned for all Rooms. (Default: False). """ model_geometry_to_baseline(model, building_type) model_constructions_to_baseline(model, climate_zone) if lighting_by_building: model_lighting_to_baseline_building(model, building_type) else: model_lighting_to_baseline(model) model_hvac_to_baseline(model, climate_zone, building_type, floor_area, story_count) model_shw_to_baseline(model, building_type) model_remove_ecms(model)
[docs]def model_geometry_to_baseline(model, building_type='NonResidential'): """Convert a Model's geometry to be conformant with ASHRAE 90.1 appendix G. This includes stripping out all attached shades (leaving detached shade as context), reducing the vertical glazing ratio to a level conformant to the building_type (or 40% if the building type is unknown and the model is above this value), and reducing the skylight ratio to 3% if it's above this value. Note that not all versions of ASHRAE 90.1 use this exact definition of baseline geometry but version 2016 and onward conform to it. It is essentially an adjusted version of the 90.1-2004 methods. Args: model: A Honeybee Model that will have its geometry adjusted to conform to the baseline. building_type: Text for the building type that the Model represents. This is used to set the maximum window ratio for the model. If the type is not recognized or is "Unknown", a maximum of 40% shall be used. The following have specified ratios per the standard. * LargeOffice * MediumOffice * SmallOffice * Retail * StripMall * PrimarySchool * SecondarySchool * SmallHotel * LargeHotel * Hospital * Outpatient * Warehouse * SuperMarket * FullServiceRestaurant * QuickServiceRestaurant """ # remove all non-context shade model.remove_assigned_shades() # remove all of the child shades or_shades = [shd for shd in model.orphaned_shades if shd.is_detached] model.remove_shades() for shd in or_shades: model.add_shade(shd) # determine the maximum glazing ratio using the building type ratio_file = os.path.join(os.path.dirname(__file__), 'data', 'fen_ratios.csv') ratio_data = csv_to_matrix(ratio_file) max_ratio = 0.4 for row in ratio_data: if row[0] == building_type: max_ratio = float(row[1]) break # compute the window and skylight ratios w_area = model.exterior_wall_area r_area = model.exterior_roof_area wa_area = model.exterior_wall_aperture_area ra_area = model.exterior_skylight_aperture_area wr = wa_area / w_area if w_area != 0 else 0 sr = ra_area / r_area if r_area != 0 else 0 # if the window or skylight ratio is greater than max permitted, set it to max if wr > max_ratio: # set all walls to have the maximum ratio adjust_factor = max_ratio / wr for room in model.rooms: for face in room.faces: if isinstance(face.boundary_condition, Outdoors) and \ isinstance(face.type, Wall): new_ratio = face.aperture_ratio * adjust_factor face.apertures_by_ratio(new_ratio, model.tolerance) if sr > 0.03: # reduce all skylights by the amount needed for 5% red_fract = 0.03 / sr # scale factor for all of the skylights for room in model.rooms: for face in room.faces: if isinstance(face.boundary_condition, Outdoors) and \ isinstance(face.type, RoofCeiling) and \ len(face._apertures) > 0: new_ratio = face.aperture_ratio * red_fract face.apertures_by_ratio(new_ratio)
[docs]def model_constructions_to_baseline(model, climate_zone): """Convert a Model's constructions to be conformant with ASHRAE 90.1 appendix G. This includes assigning a ConstructionSet that is compliant with Table G3.4 to all rooms in the model, accounting for the fenestration ratios in the process. Note that not all versions of ASHRAE 90.1 use this exact definition of baseline constructions but version 2016 and onward conform to it. It is essentially an adjusted version of the 90.1-2004 methods. Args: model: A Honeybee Model that will have its constructions adjusted to conform to the baseline. climate_zone: Text indicating the ASHRAE climate zone. This can be a single integer (in which case it is interpreted as A) or it can include the A, B, or C qualifier (eg. 3C). """ # compute the fenestration ratios across the model w_area = model.exterior_wall_area r_area = model.exterior_roof_area wr = model.exterior_wall_aperture_area / w_area if w_area != 0 else 0 sr = model.exterior_skylight_aperture_area / r_area if r_area != 0 else 0 # get the base ConstructionSet from the standards library clean_cz = str(climate_zone)[0] constr_set_id = '2004::ClimateZone{}::SteelFramed'.format(clean_cz) base_set = construction_set_by_identifier(constr_set_id) # parse the CSV file with exceptions to the base construction set ex_file = os.path.join(os.path.dirname(__file__), 'data', 'constructions.csv') ex_data = csv_to_matrix(ex_file) ex_cz = clean_cz if climate_zone != '3C' else climate_zone ex_ratio = '100' for ratio in (40, 30, 20, 10): if wr < ratio / 100 + 0.001: ex_ratio = str(ratio) for row in ex_data: if row[0] == ex_cz and row[1] == ex_ratio: vert_except = [float(val) for val in row[2:]] break # change the constructions for fixed and operable windows si_ip_u = 5.678263337 fixed_id = 'U {} SHGC {} Fixed Glz'.format(vert_except[0], vert_except[2]) fixed_mat = EnergyWindowMaterialSimpleGlazSys( fixed_id, vert_except[0] * si_ip_u, vert_except[2]) fixed_constr = WindowConstruction(fixed_id.replace('Glz', 'Window'), [fixed_mat]) oper_id = 'U {} SHGC {} Operable Glz'.format(vert_except[1], vert_except[2]) oper_mat = EnergyWindowMaterialSimpleGlazSys( oper_id, vert_except[1] * si_ip_u, vert_except[2]) oper_constr = WindowConstruction(oper_id.replace('Glz', 'Window'), [oper_mat]) base_set.aperture_set.window_construction = fixed_constr base_set.aperture_set.operable_construction = oper_constr # change the construction for skylights if the ratio is greater than 2% if sr > 0.021: for row in ex_data: if row[0] == ex_cz and row[1] == 'sky_5': sky_except = [float(row[2]), float(row[4])] break sky_id = 'U {} SHGC {} Skylight Glz'.format(sky_except[0], sky_except[1]) sky_mat = EnergyWindowMaterialSimpleGlazSys( sky_id, sky_except[0] * si_ip_u, sky_except[1]) sky_constr = WindowConstruction(sky_id.replace('Glz', 'Window'), [sky_mat]) base_set.aperture_set.skylight_construction = sky_constr # remove child constructions ans assign the construction set to all rooms model.properties.energy.remove_child_constructions() for room in model.rooms: room.properties.energy.construction_set = base_set
[docs]def model_lighting_to_baseline(model): """Convert a Model's lighting to be conformant with ASHRAE 90.1-2004 appendix G. This includes determining whether an ASHRAE 2004 equivalent exists for each program type in the model. If none is found, the baseline_watts_per_area on the room's program's lighting will be used, which will default to a typical office if none has been specified. Args: model: A Honeybee Model that will have its lighting power adjusted to conform to the baseline. """ # loop through the rooms and try to find equivalent programs in 2004 for room in model.rooms: if room.properties.energy.lighting is None or \ room.properties.energy.lighting.watts_per_area == 0: continue prog_name = room.properties.energy.program_type.identifier.split('::') prog_2004 = None if len(prog_name) >= 3: new_prog_name = '2004::{}::{}'.format(prog_name[1], prog_name[2]) try: prog_2004 = program_type_by_identifier(new_prog_name) except ValueError: # no equivalent program in ASHRAE 2004 pass if prog_2004 is not None: # if program was found, use it to assign the LPD if prog_2004.lighting is not None: dup_light = room.properties.energy.lighting.duplicate() dup_light.watts_per_area = prog_2004.lighting.watts_per_area elif room.properties.energy.program_type.lighting is not None: dup_light = room.properties.energy.program_type.lighting.duplicate() dup_light.watts_per_area = dup_light.baseline_watts_per_area else: dup_light = room.properties.energy.lighting.duplicate() dup_light.watts_per_area = dup_light.baseline_watts_per_area dup_light.identifier = '{}_Lighting'.format(room.identifier) room.properties.energy.lighting = dup_light
[docs]def model_lighting_to_baseline_building(model, building_type='NonResidential'): """Convert a Model's lighting to be conformant with ASHRAE 90.1-2004 appendix G. This includes looking up the building type's average lighting power density and assigning it to all Rooms in the model. Args: model: A Honeybee Model that will have its lighting power adjusted to conform to the baseline. building_type: Text for the building type that the Model represents. If the type is not recognized or is "Unknown", it will be assumed that the building is a generic NonResidential (aka. an office). The following have specified meaning per the standard. * NonResidential * Residential * MidriseApartment * HighriseApartment * LargeOffice * MediumOffice * SmallOffice * Retail * StripMall * PrimarySchool * SecondarySchool * SmallHotel * LargeHotel * Hospital * Outpatient * Warehouse * SuperMarket * FullServiceRestaurant * QuickServiceRestaurant * Laboratory * Courthouse """ # determine the lighting power density using the building type lpd_file = os.path.join(os.path.dirname(__file__), 'data', 'lpd_building.csv') lpd_data = csv_to_matrix(lpd_file) lpd = 1.0 for row in lpd_data: if row[0] == building_type: lpd = float(row[1]) break lpd = lpd * 10.7639 # convert to W/m2 from W/ft2 # assign the lighting power density to all rooms in the model for room in model.rooms: if room.properties.energy.lighting is None or \ room.properties.energy.lighting.watts_per_area == 0: continue dup_light = room.properties.energy.lighting.duplicate() dup_light.watts_per_area = lpd dup_light.identifier = '{}_Lighting'.format(room.identifier) room.properties.energy.lighting = dup_light
[docs]def model_hvac_to_baseline(model, climate_zone, building_type='NonResidential', floor_area=None, story_count=None): """Convert a Model's HVAC to be conformant with ASHRAE 90.1 appendix G. This includes the selection of the correct Appendix G template HVAC based on the inputs and the application of this HVAC to all conditioned spaces in the model. Note that not all versions of ASHRAE 90.1 use this exact definition of baseline HVAC but version 2016 and onward conform to it. It is essentially an adjusted version of the 90.1-2004 methods. Args: model: A Honeybee Model that will have its HVAC adjusted to conform to the baseline. climate_zone: Text indicating the ASHRAE climate zone. This can be a single integer (in which case it is interpreted as A) or it can include the A, B, or C qualifier (eg. 3C). building_type: Text for the building type that the Model represents. This is used to determine the baseline system. If the type is not recognized or is "Unknown", it will be assumed that the building is a generic NonResidential. The following have specified systems per the standard. * NonResidential * Residential * MidriseApartment * HighriseApartment * LargeOffice * MediumOffice * SmallOffice * Retail * StripMall * PrimarySchool * SecondarySchool * SmallHotel * LargeHotel * Hospital * Outpatient * Warehouse * SuperMarket * FullServiceRestaurant * QuickServiceRestaurant * Laboratory floor_area: A number for the floor area of the building that the model is a part of in m2. If None, the model floor area will be used. (Default: None). story_count: An integer for the number of stories of the building that the model is a part of. If None, the model stories will be used. (Default: None). """ # set the standard to be used and the climate zone std = 'ASHRAE_2004' if len(climate_zone) == 1: climate_zone = '{}A'.format(climate_zone) # determine whether the system uses fuel or electricity from the climate zone fuel = climate_zone not in ('0A', '0B', '1A', '1B', '2A', '2B', '3A') # determine whether the building type is residential or it's heated-only storage res_types = ('Residential', 'MidriseApartment', 'HighriseApartment' 'SmallHotel', 'LargeHotel') residential = building_type in res_types heat_only = False if building_type == 'Warehouse': for hvac in model.properties.energy.hvacs: if isinstance(hvac, _HeatCoolBase) and \ hvac.equipment_type in hvac.HEAT_ONLY_TYPES: heat_only = True break elif isinstance(hvac, ForcedAirFurnace): heat_only = True break # determine the HVAC template from the input criteria if residential: hvac_id = 'Baseline PT Residential HVAC' hvac_sys = PTAC(hvac_id, std, 'PTAC_BoilerBaseboard') \ if fuel else PTAC(hvac_id, std, 'PTHP') elif heat_only: hvac_id = 'Baseline Warm Air Furnace HVAC' hvac_sys = ForcedAirFurnace(hvac_id, std, 'Furnace') \ if fuel else ForcedAirFurnace(hvac_id, std, 'Furnace_Electric') else: # determine the floor area if it is not input if floor_area == 0 or floor_area is None: floor_area = model.floor_area floor_area = floor_area if model.units == 'Meters' else \ floor_area * model.conversion_factor_to_meters(model.units) # determine the number of stories if it is not input if story_count == 0 or story_count is None: story_count = len(model.stories) # determine the HVAC from the floor area and stories hvac_temp = 'Baseline {} HVAC' if building_type in ('Retail, StripMall') and story_count <= 2: hvac_id = hvac_temp.format('PSZ') hvac_sys = PSZ(hvac_id, std, 'PSZAC_Boiler') \ if fuel else PSZ(hvac_id, std, 'PSZHP') elif story_count > 5 or floor_area > 13935.5: # more than 150,000 ft2 hvac_id = hvac_temp.format('VAV') hvac_sys = VAV(hvac_id, std, 'VAV_Chiller_Boiler') \ if fuel else VAV(hvac_id, std, 'VAV_Chiller_PFP') elif story_count > 3 or floor_area > 2322.6: # more than 25,000 ft2 hvac_id = hvac_temp.format('PVAV') hvac_sys = PVAV(hvac_id, std, 'PVAV_Boiler') \ if fuel else PVAV(hvac_id, std, 'PVAV_PFP') elif building_type in ('Hospital', 'Laboratory'): hvac_id = hvac_temp.format('PVAV') hvac_sys = PVAV(hvac_id, std, 'PVAV_Boiler') \ if fuel else PVAV(hvac_id, std, 'PVAV_PFP') else: hvac_id = hvac_temp.format('PSZ') hvac_sys = PSZ(hvac_id, std, 'PSZAC_Boiler') \ if fuel else PSZ(hvac_id, std, 'PSZHP') if climate_zone not in ('0A', '0B', '1A', '1B', '2A', '3A', '4A'): hvac_sys.economizer_type = 'DifferentialDryBulb' # apply the HVAC template to all conditioned rooms in the model dhw_only, dch_only, dhw_dcw = [], [], [] for room in model.rooms: r_hvac = room.properties.energy.hvac if r_hvac is not None: if isinstance(r_hvac, _TemplateSystem): if not r_hvac.has_district_heating and not r_hvac.has_district_cooling: room.properties.energy.hvac = hvac_sys elif r_hvac.has_district_heating and r_hvac.has_district_cooling: dhw_dcw.append(room) elif r_hvac.has_district_heating: dhw_only.append(room) else: dch_only.append(room) else: room.properties.energy.hvac = hvac_sys # if there were any rooms with district heating or cooling, substitute the system if len(dhw_dcw) != 0: if isinstance(hvac_sys, (VAV, PVAV)): hvac_id = hvac_temp.format('VAV') + ' DHW DCW' new_hvac_sys = VAV(hvac_id, std, 'VAV_DCW_DHW') new_hvac_sys.economizer_type = hvac_sys.economizer_type elif isinstance(hvac_sys, PSZ): hvac_id = hvac_temp.format('PSZ') + ' DHW DCW' new_hvac_sys = PSZ(hvac_id, std, 'PSZAC_DCW_DHW') new_hvac_sys.economizer_type = hvac_sys.economizer_type elif isinstance(hvac_sys, PTAC): hvac_id = 'Baseline FCU Residential HVAC DHW DCW' new_hvac_sys = FCUwithDOAS(hvac_id, std, 'DOAS_FCU_DCW_DHW') else: new_hvac_sys = hvac_sys for room in dhw_dcw: room.properties.energy.hvac = new_hvac_sys if len(dch_only) != 0: if isinstance(hvac_sys, (VAV, PVAV)): hvac_id = hvac_temp.format('VAV') + ' DCW' new_hvac_sys = VAV(hvac_id, std, 'VAV_DCW_Boiler') \ if fuel else PVAV(hvac_id, std, 'VAV_DCW_PFP') new_hvac_sys.economizer_type = hvac_sys.economizer_type elif isinstance(hvac_sys, PSZ): hvac_id = hvac_temp.format('PSZ') + ' DCW' new_hvac_sys = PSZ(hvac_id, std, 'PSZAC_DCW_Boiler') new_hvac_sys.economizer_type = hvac_sys.economizer_type elif isinstance(hvac_sys, PTAC): hvac_id = 'Baseline FCU Residential HVAC DCW' new_hvac_sys = FCUwithDOAS(hvac_id, std, 'DOAS_FCU_DCW_Boiler') else: new_hvac_sys = hvac_sys for room in dch_only: room.properties.energy.hvac = new_hvac_sys if len(dhw_only) != 0: if isinstance(hvac_sys, VAV): hvac_id = hvac_temp.format('VAV') + ' DHW' new_hvac_sys = VAV(hvac_id, std, 'VAV_Chiller_DHW') new_hvac_sys.economizer_type = hvac_sys.economizer_type elif isinstance(hvac_sys, PVAV): hvac_id = hvac_temp.format('PVAV') + ' DHW' new_hvac_sys = PVAV(hvac_id, std, 'PVAV_DHW') new_hvac_sys.economizer_type = hvac_sys.economizer_type elif isinstance(hvac_sys, PSZ): hvac_id = hvac_temp.format('PSZ') + ' DHW' new_hvac_sys = PSZ(hvac_id, std, 'PSZAC_DHW') new_hvac_sys.economizer_type = hvac_sys.economizer_type elif isinstance(hvac_sys, PTAC): hvac_id = 'Baseline PT Residential HVAC DHW' hvac_sys = PTAC(hvac_id, std, 'PTAC_DHW') else: new_hvac_sys = hvac_sys for room in dhw_only: room.properties.energy.hvac = new_hvac_sys
[docs]def model_shw_to_baseline(model, building_type='NonResidential'): """Convert a Model's SHW systems to be conformant with ASHRAE 90.1-2004 appendix G. This includes looking up the building type's baseline heating method and assigning it to all Rooms with a SHW system in the model. Args: model: A Honeybee Model that will have its Service Hot Water (SHW) systems adjusted to conform to the baseline. building_type: Text for the building type that the Model represents. If the type is not recognized or is "Unknown", it will be assumed that the building is a generic NonResidential (aka. an office). The following have specified meaning per the standard. * NonResidential * Residential * MidriseApartment * HighriseApartment * LargeOffice * MediumOffice * SmallOffice * Retail * StripMall * PrimarySchool * SecondarySchool * SmallHotel * LargeHotel * Hospital * Outpatient * Warehouse * SuperMarket * FullServiceRestaurant * QuickServiceRestaurant * Laboratory * Courthouse """ # determine the service hot water system using the building type shw_file = os.path.join(os.path.dirname(__file__), 'data', 'shw.csv') shw_data = csv_to_matrix(shw_file) shw_t = 'Gas' for row in shw_data: if row[0] == building_type: shw_t = row[1] break shw_sys = SHWSystem('Baseline Gas SHW System', 'Gas_WaterHeater') if shw_t == 'Gas' \ else SHWSystem('Baseline Electric Resistance SHW System', 'Electric_WaterHeater') # assign the SHW system to all relevant rooms in the model for room in model.rooms: if room.properties.energy.shw is None: continue room.properties.energy.shw = shw_sys
[docs]def model_remove_ecms(model): """Remove energy conservation strategies (ECMs) not associated with baseline models. This includes removing the opening behavior of all operable windows, daylight controls, etc. Args: model: A Honeybee Model that will have its lighting power adjusted to conform to the baseline. """ # loop through the rooms and remove daylight controls for room in model.rooms: room.properties.energy.daylighting_control = None # loop through the rooms and remove operable windows for room in model.rooms: room.properties.energy.window_vent_control = None room.properties.energy.remove_ventilation_opening() for face in room.faces: for ap in face.apertures: ap.is_operable = False