Source code for uwg.uwg

"""Urban Weather Generator (UWG) Version 4.2

Original Author: B. Bueno[1]
Edited by A. Nakano & Lingfu Zhang
Modified by Joseph Yang (joeyang@mit.edu) - May, 2016
Translated to Python by Saeran Vasanthakumar - February, 2018

Note:
    [1] Bueno, Bruno; Norford, Leslie; Hidalgo, Julia; Pigeon, Gregoire (2012a).
    The urban weather generator, Journal of Building Performance Simulation. 6:4,269-281.
    doi: 10.1080/19401493.2012.718797
"""

from __future__ import division, print_function
from functools import reduce

try:
    range = xrange
except NameError:
    pass

try:
    str = basestring
except NameError:
    pass

import sys
import os
import math
import copy

try:
    import cPickle as pickle
except ImportError:
    import pickle

from .simparam import SimParam
from .weather import Weather
from .material import Material
from .element import Element
from .param import Param
from .UCMDef import UCMDef
from .forcing import Forcing
from .UBLDef import UBLDef
from .RSMDef import RSMDef
from .solarcalcs import SolarCalcs
from .psychrometrics import psychrometrics
from .urbflux import urbflux
from .schdef import SchDef
from .BEMDef import BEMDef
from . import utilities
from .utilities import int_in_range, float_in_range, int_positive, float_positive
from .utilities import REF_BLDTYPE, REF_BUILTERA, REF_ZONETYPE, REF_BLDTYPE_SET, \
    REF_BUILTERA_SET, REF_ZONETYPE_SET


[docs] class UWG(object): """Morph a rural EPW file to urban conditions based on defined urban parameters. Args: epw_path: Text string for full path of the rural .epw file that will be morphed. If set to None, other input parameters can be assigned but the UWG model cannot be generated from the inputs, which is useful in cases where a UWG model needs to be serialized but the file path structure is not known. (Default: None). new_epw_dir: Optional text string for the destination directory into which the morphed .epw file is written. If None the morphed file will be written into the same directory as the rural .epw file. (Default: None). new_epw_name: Optional text string for the destination file name of the morphed .epw file. If None the morphed file will append '_UWG' to the original file name. (Default: None). Properties: * epw_path -- Full path of the rural .epw file that will be morphed. * new_epw_path -- Full path of the file name of the morphed .epw file. * refBEM -- Reference BEMDef matrix defined by built type, era, and zone. * refSchedule -- Reference SchDef matrix defined by built type, era, and zone. * month -- Number (1-12) representing simulation start month. * day -- Number (1-31) representing simulation start day. * nday -- Number of days to simulate. * dtsim -- Simlation time step in seconds. * dtweather -- Number for weather data time-step in seconds. * autosize -- Boolean to set HVAC autosize. * sensocc -- Sensible heat from occupant [W]. * latfocc -- Latent heat fraction from occupant. * radfocc -- Radiant heat fraction from occupant. * radfequip -- Radiant heat fraction from equipment. * radflight -- Radiant heat fraction from electric light. * h_ubl1 -- Daytime urban boundary layer height in meters. * h_ubl2 -- Nighttime urban boundary layer height in meters. * h_ref -- Inversion height in meters. * h_temp -- Temperature height in meters. * h_wind -- Wind height in meters. * c_circ -- Wind scaling coefficient. * c_exch -- Exchange velocity coefficient. * maxday -- Maximum heat flux threshold for daytime conditions [W/m2]. * maxnight -- Maximum heat flux threshold for nighttime conditions [W/m2]. * windmin -- Minimum wind speed in m/s. * h_obs -- Rural average obstacle height in meters. * bldheight -- Urban building height in meters. * h_mix -- Fraction of HVAC waste heat released to street canyon. * blddensity -- Building footprint density as fraction of urban area. * vertohor -- Vertical-to-horizontal urban area ratio. * charlength -- Urban characteristic length in meters. * albroad -- Urban road albedo. * droad -- Thickness of urban road pavement thickness in meters. * sensanth -- Street level anthropogenic sensible heat [W/m2]. * zone -- Index representing an ASHRAE climate zone. * grasscover -- Fraction of urban ground covered in grass only. * treecover -- Fraction of urban ground covered in trees. * vegstart -- Month in which vegetation starts to evapotranspire. * vegend -- Month in which vegetation stops evapotranspiration. * albveg -- Vegetation albedo. * rurvegcover -- Fraction of rural ground covered by vegetation. * latgrss -- Fraction of latent heat absorbed by urban grass. * lattree -- Fraction latent heat absorbed by urban trees. * schtraffic -- Schedule of fractional anthropogenic heat load. * kroad -- Road pavement conductivity [W/m-K]. * croad -- Road pavement volumetric heat capacity [J/m^3K]. * bld -- Matrix of numbers representing fraction of urban building stock. * albroof -- Average building roof albedo. * vegroof -- Fraction of roof covered in grass/shrubs. * glzr -- Building glazing ratio. * albwall -- Building albedo. * shgc -- Building glazing Solar Heat Gain Coefficient (SHGC). * flr_h -- Building floor height in meters. * ref_bem_vector -- List of custom BEMDef objects to override the refBEM. * ref_sch_vector -- List of custom SchDef objects to override the refSchedule. """ # Definitions for constants / other parameters MINTHICKNESS = 0.01 # Minimum layer thickness (to prevent crashing) (m) MAXTHICKNESS = 0.05 # Maximum layer thickness (m) # http://web.mit.edu/parmstr/Public/NRCan/nrcc29118.pdf (Figly & Snodgrass) SOILTCOND = 1 # http://www.europment.org/library/2013/venice/bypaper/MFHEEF/MFHEEF-21.pdf # (average taken from Table 1) SOILVOLHEAT = 2e6 # Soil material used for soil-depth padding SOIL = Material(SOILTCOND, SOILVOLHEAT, name='soil') # Physical constants G = 9.81 # gravity (m s-2) CP = 1004. # heat capacity for air (J/kg K) VK = 0.40 # von karman constant (dimensionless) R = 287. # gas constant dry air (J/kg K) RV = 461.5 # gas constant water vapor (J/kg K) LV = 2.26e6 # latent heat of evaporation (J/kg) SIGMA = 5.67e-08 # Stefan Boltzmann constant (W m-2 K-4) WATERDENS = 1000. # water density (kg m-3) LVTT = 2.5008e6 # TT = 273.16 # ESTT = 611.14 # CL = 4.218e3 # CPV = 1846.1 # B = 9.4 # Coefficients derived by Louis (1979) CM = 7.4 # # (Pr/Sc)^(2/3) for Colburn analogy in water evaporation COLBURN = math.pow((0.713 / 0.621), (2 / 3.)) # Site-specific parameters WGMAX = 0.005 # maximum film water depth on horizontal surfaces (m) # UWG object constants PARAMETER_LIST = ('month', 'day', 'nday', 'dtsim', 'dtweather', 'autosize', 'sensocc', 'latfocc', 'radfocc', 'radfequip', 'radflight', 'h_ubl1', 'h_ubl2', 'h_ref', 'h_temp', 'h_wind', 'c_circ', 'c_exch', 'maxday', 'maxnight', 'windmin', 'h_obs', 'bldheight', 'h_mix', 'blddensity', 'vertohor', 'charlength', 'albroad', 'droad', 'sensanth', 'zone', 'grasscover', 'treecover', 'vegstart', 'vegend', 'albveg', 'rurvegcover', 'latgrss', 'lattree', 'schtraffic', 'kroad', 'croad', 'bld', 'shgc', 'albroof', 'glzr', 'vegroof', 'albwall', 'flr_h') OPTIONAL_PARAMETER_SET = {'shgc', 'albroof', 'glzr', 'vegroof', 'albwall', 'flr_h'} DEFAULT_BLD = (('largeoffice', 'pst80', 0.4), ('midriseapartment', 'pst80', 0.6)) DEFAULT_SCHTRAFFIC = ( (0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.7, 0.9, 0.9, 0.6, 0.6, 0.6, 0.6, 0.6, 0.7, 0.8, 0.9, 0.9, 0.8, 0.8, 0.7, 0.3, 0.2, 0.2), # Weekday (0.2, 0.2, 0.2, 0.2, 0.2, 0.3, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.7, 0.7, 0.7, 0.7, 0.5, 0.4, 0.3, 0.2, 0.2), # Saturday (0.2, 0.2, 0.2, 0.2, 0.2, 0.3, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.3, 0.3, 0.2, 0.2)) # Sunday # Constant file paths CURRENT_PATH = os.path.abspath(os.path.dirname(__file__)) Z_MESO_PATH = os.path.join(CURRENT_PATH, 'refdata', 'z_meso.txt') REFDOE_PATH = os.path.join(CURRENT_PATH, 'refdata', 'readDOE.pkl') def __init__(self, epw_path=None, new_epw_dir=None, new_epw_name=None): self.epw_path = epw_path self._new_epw_dir, self._new_epw_name = new_epw_dir, new_epw_name self._new_epw_path = None # set defaults for reference data self._refBEM = None self._refSchedule = None self._ref_bem_vector = None self._ref_sch_vector = None # Parameters for UWG computation self.epw_precision = 1 # Set default for optional parameters as None, if user doesn't modify # use DOE reference equivalents. self._shgc = None self._flr_h = None self._albroof = None self._albwall = None self._glzr = None self._vegroof = None # Empty latanth not used. self.latanth = None
[docs] @classmethod def from_param_file(cls, param_path, epw_path=None, new_epw_dir=None, new_epw_name=None): """Create a UWG object from the .uwg parameter file. Note: this method of initializing the UWG object doesn't permit adding custom reference data. Args: param_path: Optional text string for full path of the the .uwg parameter file path. epw_path: Text string for full path of the rural .epw file that will be morphed. If set to None, other input parameters can be assigned but the UWG model cannot be generated from the inputs, which is useful in cases where a UWG model needs to be serialized but the file path structure is not known. (Default: None). new_epw_dir: Optional text string destination directory for the morphed .epw file. If None the morphed file will be written into the same directory as the rural .epw file. (Default: None). new_epw_name: Optional destination file name for the morphed .epw file. If None the morphed file will append '_UWG' to the original file name. (Default: None). """ assert os.path.exists(param_path), 'Parameter file "{}" does not ' \ 'exist.'.format(param_path) assert param_path.endswith('.uwg'), 'Parameter file must be a ".uwg" ' \ 'filetype. Got: {}.'.format(param_path) model = UWG(epw_path, new_epw_dir, new_epw_name) model._read_input(param_path) return model
[docs] @classmethod def from_param_args(cls, bldheight, blddensity, vertohor, grasscover, treecover, zone, month=1, day=1, nday=31, dtsim=300, dtweather=3600, bld=DEFAULT_BLD, autosize=False, h_mix=1, sensocc=100, latfocc=0.3, radfocc=0.2, radfequip=0.5, radflight=0.7, charlength=1000, albroad=0.1, droad=0.5, kroad=1, croad=1600000, rurvegcover=0.9, vegstart=4, vegend=10, albveg=0.25, latgrss=0.4, lattree=0.6, sensanth=20, schtraffic=DEFAULT_SCHTRAFFIC, h_ubl1=1000, h_ubl2=80, h_ref=150, h_temp=2, h_wind=10, c_circ=1.2, c_exch=1, maxday=150, maxnight=20, windmin=1, h_obs=0.1, epw_path=None, new_epw_dir=None, new_epw_name=None, ref_bem_vector=None, ref_sch_vector=None): """Create an UWG object based on default method arguments. The default parameters are set from example parameters defined by Bueno et al. (2012a)[1] for Singapore. The original file can be accessed here: https://github.com/ladybug-tools/uwg/blob/master/resources/initialize_singapore.uwg. Note: [1] Bueno, Bruno; Norford, Leslie; Hidalgo, Julia; Pigeon, Gregoire (2012a). The urban weather generator, Journal of Building Performance Simulation. 6:4, 269-281. doi: 10.1080/19401493.2012.718797 """ model = UWG(epw_path, new_epw_dir, new_epw_name) model.bldheight = bldheight model.blddensity = blddensity model.vertohor = vertohor model.zone = zone model.month = month model.day = day model.nday = nday model.dtsim = dtsim model.dtweather = dtweather model.autosize = autosize model.h_mix = h_mix model.sensocc = sensocc model.latfocc = latfocc model.radfocc = radfocc model.radfequip = radfequip model.radflight = radflight model.bld = bld model.charlength = charlength model.albroad = albroad model.droad = droad model.sensanth = sensanth model.kroad = kroad model.croad = croad model.treecover = treecover model.grasscover = grasscover model.vegstart = vegstart model.vegend = vegend model.albveg = albveg model.rurvegcover = rurvegcover model.latgrss = latgrss model.lattree = lattree model.schtraffic = schtraffic model.h_ubl1 = h_ubl1 model.h_ubl2 = h_ubl2 model.h_ref = h_ref model.h_temp = h_temp model.h_wind = h_wind model.c_circ = c_circ model.c_exch = c_exch model.maxday = maxday model.maxnight = maxnight model.windmin = windmin model.h_obs = h_obs # check and add reference data refcheck = int(ref_sch_vector is not None) + \ int(ref_bem_vector is not None) assert refcheck != 1, 'The ref_sch_vector and ref_bem_vector must both be ' \ 'defined in order to modify the UWG reference data. Only {} is ' \ 'defined.'.format( 'ref_sch_vector' if ref_bem_vector is None else 'ref_bem_vector') if refcheck == 2: model.ref_bem_vector, model.ref_sch_vector = \ model._check_reference_data(ref_bem_vector, ref_sch_vector) return model
[docs] @classmethod def from_dict(cls, data, epw_path=None, new_epw_dir=None, new_epw_name=None): """Create an UWG object from a dictionary. Args: data: An UWG dictionary following the format below. Note that this example has been truncated for the sake of brevity. For the full list of required properties in the UWG, see the initialization docstrings. epw_path: Text string for full path of the rural .epw file that will be morphed. If set to None, other input parameters can be assigned but the UWG model cannot be generated from the inputs, which is useful in cases where a UWG model needs to be serialized but the file path structure is not known. (Default: None). new_epw_dir: Optional text string for the destination directory into which the morphed .epw file is written. If None the morphed file will be written into the same directory as the rural .epw file. (Default: None). new_epw_name: Optional text string for the destination file name of the morphed .epw file. If None the morphed file will append '_UWG' to the original file name. (Default: None). .. code-block:: python { "type": "UWG", "bldheight": 10, "blddensity": 0.5, "vertohor": 0.8, ... "h_obs": 0.1, "flr_h": 3.5, "shgc": None, # Optional vector of SchDef dictionary. "ref_sch_vector": [sch.to_dict()] # Optional vector of BEMDef dictionary. "ref_bem_vector": [bem.to_dict()] } """ assert data['type'] == 'UWG', \ 'Expected UWG dictionary. Got {}.'.format(data['type']) model = UWG(epw_path, new_epw_dir, new_epw_name) # set UWG parameters for attr in cls.PARAMETER_LIST: setattr(model, attr, data[attr]) # check and add reference data check_sch = 'ref_sch_vector' in data and data['ref_sch_vector'] is not None check_bem = 'ref_bem_vector' in data and data['ref_bem_vector'] is not None refcheck = int(check_sch) + int(check_bem) assert refcheck != 1, 'The ref_sch_vector and ref_bem_vector ' \ 'properties must both be defined in order to modify the UWG reference ' \ 'data. Only {} is defined.'.format( 'ref_sch_vector' if 'ref_sch_vector' in data else 'ref_bem_vector') if refcheck == 2: ref_sch_vector = [SchDef.from_dict(schdict) for schdict in data['ref_sch_vector']] ref_bem_vector = [BEMDef.from_dict(bemdict) for bemdict in data['ref_bem_vector']] if refcheck == 2: model.ref_bem_vector, model.ref_sch_vector = \ model._check_reference_data(ref_bem_vector, ref_sch_vector) return model
@property def epw_path(self): """Get full path to rural .epw file to be morphed.""" return self._epw_path @epw_path.setter def epw_path(self, value): if value is not None: assert os.path.exists( value), 'File: "{}" does not exist.'.format(value) self._epw_path = value @property def new_epw_path(self): """Get text string for new epw filepath.""" if self._new_epw_path is None: if self._new_epw_dir is None or self._new_epw_name is None: epw_dir, epw_name = os.path.split(self.epw_path) if self._new_epw_dir is None: self._new_epw_dir = epw_dir if self._new_epw_name is None: nepw_name = epw_name if nepw_name.lower().endswith('.epw'): nepw_name = nepw_name[:-4] self._new_epw_name = nepw_name + '_UWG.epw' self._new_epw_path = os.path.join( self._new_epw_dir, self._new_epw_name) return self._new_epw_path @property def refBEM(self): """Get matrix of DOE reference BEMDefs defined by built type, era, and zone.""" if self._refBEM is None: self._refBEM, self._refSchedule = UWG.load_refDOE() return self._refBEM @property def refSchedule(self): """Get matrix of DOE reference SchDefs defined by built type, era, and zone.""" if self._refSchedule is None: self._refBEM, self._refSchedule = UWG.load_refDOE() return self._refSchedule @property def month(self): """Get or set number (1-12) as simulation start month.""" return self._month @month.setter def month(self, value): self._month = int_in_range(value, 1, 12, 'month') @property def day(self): """Get or set number (1-31) as simulation start day.""" return self._day @day.setter def day(self, value): self._day = int_in_range(value, 1, 31, 'day') @property def nday(self): """Get or set number of days to simulate.""" return self._nday @nday.setter def nday(self, value): self._nday = int_positive(value, 'nday') @property def dtsim(self): """Get or set simulation timestep in seconds.""" return self._dtsim @dtsim.setter def dtsim(self, value): self._dtsim = int_positive(value, 'dtsim') @property def dtweather(self): """Get or set weather data timestep in seconds.""" return self._dtweather @dtweather.setter def dtweather(self, value): self._dtweather = float_positive(value, 'dtweather') @property def autosize(self): """Get or set boolean to autosize HVAC system.""" return self._autosize @autosize.setter def autosize(self, value): assert isinstance(value, (bool, int, float)), 'Input autosize must be a ' \ 'boolean value or a number that can be cast as or boolean value. ' \ 'Got: {}.'.format(value) self._autosize = bool(value) @property def sensocc(self): """Get or set sensible heat from occupant [W].""" return self._sensocc @sensocc.setter def sensocc(self, value): self._sensocc = float_positive(value, 'sensocc') @property def latfocc(self): """Get or set latent heat fraction from occupant.""" return self._latfocc @latfocc.setter def latfocc(self, value): self._latfocc = float_in_range(value, 0, 1, 'latfocc') @property def radfocc(self): """Get or set radiant heat fraction from occupant.""" return self._radfocc @radfocc.setter def radfocc(self, value): self._radfocc = float_in_range(value, 0, 1, 'radfocc') @property def radfequip(self): """Get or set radiant heat fraction from equipment.""" return self._radfequip @radfequip.setter def radfequip(self, value): self._radfequip = float_in_range(value, 0, 1, 'radfequip') @property def radflight(self): """Get or set radiant heat fraction from electric light.""" return self._radflight @radflight.setter def radflight(self, value): self._radflight = float_in_range(value, 0, 1, 'radflight') @property def h_ubl1(self): """Get or set daytime urban boundary layer height in meters.""" return self._h_ubl1 @h_ubl1.setter def h_ubl1(self, value): self._h_ubl1 = float_positive(value, 'h_ubl1') @property def h_ubl2(self): """Get or set nighttime urban boundary layer height in meters.""" return self._h_ubl2 @h_ubl2.setter def h_ubl2(self, value): self._h_ubl2 = float_positive(value, 'h_ubl2') @property def h_ref(self): """Get or set inversion height in meters.""" return self._h_ref @h_ref.setter def h_ref(self, value): self._h_ref = float_positive(value, 'h_ref') @property def h_temp(self): """Get or set temperature height in meters.""" return self._h_temp @h_temp.setter def h_temp(self, value): self._h_temp = float_positive(value, 'h_temp') @property def h_wind(self): """Get or set wind height in meters.""" return self._h_wind @h_wind.setter def h_wind(self, value): self._h_wind = float_positive(value, 'h_wind') @property def c_circ(self): """Get or set wind scaling coefficient.""" return self._c_circ @c_circ.setter def c_circ(self, value): self._c_circ = float_positive(value, 'c_circ') @property def c_exch(self): """Get or set exchange velocity coefficient.""" return self._c_exch @c_exch.setter def c_exch(self, value): self._c_exch = float_positive(value, 'c_exch') @property def maxday(self): """Get or set maximum heat flux threshold for daytime conditions [W/m].""" return self._maxday @maxday.setter def maxday(self, value): self._maxday = float_positive(value, 'maxday') @property def maxnight(self): """Get or set maximum heat flux threshold for nighttime conditions [W/m2].""" return self._maxnight @maxnight.setter def maxnight(self, value): self._maxnight = float_positive(value, 'maxnight') @property def windmin(self): """Get or set minimum wind speed in m/s.""" return self._windmin @windmin.setter def windmin(self, value): self._windmin = float_positive(value, 'windmin') @property def h_obs(self): """Get or set rural average obstacle height in meters.""" return self._h_obs @h_obs.setter def h_obs(self, value): self._h_obs = float_positive(value, 'h_obs') @property def bldheight(self): """Get or set average urban building height in meters.""" return self._bldheight @bldheight.setter def bldheight(self, value): self._bldheight = float_positive(value, 'bldheight') @property def h_mix(self): """Get or set fraction of building HVAC waste heat released to street canyon. It is assumed the rest of building HVAC waste heat is released from the roof. """ return self._h_mix @h_mix.setter def h_mix(self, value): self._h_mix = float_in_range(value, 0, 1, 'h_mix') @property def vertohor(self): """Get or set vertical-to-horizontal urban area ratio. The vertical-to-horizontal urban area ratio is calculated by dividing the urban facade area by total urban area. """ return self._vertohor @vertohor.setter def vertohor(self, value): self._vertohor = float_positive(value, 'vertohor') @property def charlength(self): """Get or set the urban characteristic length in meters. The characteristic length is the dimension of a square that encompasses the whole neighborhood. """ return self._charlength @charlength.setter def charlength(self, value): self._charlength = float_positive(value, 'charlength') @property def albroad(self): """Get or set urban road albedo.""" return self._albroad @albroad.setter def albroad(self, value): self._albroad = float_in_range(value, 0, 1, 'albroad') @property def droad(self): """Get or set thickness of urban road pavement thickness in meters.""" return self._droad @droad.setter def droad(self, value): self._droad = float_positive(value, 'droad') @property def sensanth(self): """Get or set street level anthropogenic sensible heat [W/m2]. Street level anthropogenic heat is non-building heat like heat emitted from cars, pedestrians, and street cooking. """ return self._sensanth @sensanth.setter def sensanth(self, value): self._sensanth = float_positive(value, 'sensanth') @property def bld(self): """Get or set list of building types, eras, and fractions of urban building stock. This property consists of a list of tuples, each containing a string for the the built era, and a number between 0 and 1, inclusive, defining built stock fraction, i.e ('LargeOffice', 'New', 0.4). The fractions should sum to one. 768 predefined models are built referencing 16 building types for 3 built eras and 16 climate zones according to models from the Department of Energy (DOE). Choose from the following text identifiers to reference a DOE building type: * 'fullservicerestaurant' * 'hospital' * 'largehotel' * 'largeoffice' * 'medoffice' * 'midriseapartment' * 'outpatient' * 'primaryschool' * 'quickservicerestaurant' * 'secondaryschool' * 'smallhotel' * 'smalloffice' * 'standaloneretail' * 'stripmall' * 'supermarket' * 'warehouse' Choose from the following built eras: * 'pre80' * 'pst80' * 'new' Custom building types can also be referenced in this property. For example, a built stock consisting of 40% post-1980's large office, 30% new midrise apartment, and 30% of a pre-1980s custom building type (defined by the user) is referenced as follows: .. code-block:: python bld = [('largeoffice', 'pst80', 0.4), ('midriseapartment', 'new', 0.3), ('custombuilding', 'pre80', 0.3)] """ return self._bld @bld.setter def bld(self, value): assert isinstance(value, (list, tuple)), 'The bld property must be a list. ' \ 'Got {}.'.format(value) # check values and fraction sum. total_frac = 0.0 for bld_row in value: assert len(bld_row) == 3, 'Each bld tuple must contain three ' \ 'items defining building type, era and fraction. Got ' \ '{} values.'.format(len(bld_row)) bldtype, builtera, frac = \ bld_row[0], bld_row[1], bld_row[2] assert isinstance(bldtype, str), 'The first item in the ' \ 'bld tuple must be text defining the reference building ' \ 'type. Got: {}.'.format(bldtype) assert isinstance(builtera, str) and builtera.lower() in REF_BUILTERA_SET, \ 'The second item in the bld tuple must be text defining the built ' \ 'era as one of {}. Got: {}.'.format( REF_BUILTERA, builtera.lower()) assert 0.0 <= frac <= 1.0, 'The third item in the bld tuple ' \ 'must be a value between 0 and 1, inclusive, defining the ' \ 'fraction of total built stock. Got: {}.'.format(frac) total_frac += frac assert abs(total_frac - 1.0) < 1e-2, 'The sum of reference building ' \ 'fractions defined in bld must equal one. Got: {}.'.format( total_frac) self._bld = value @property def lattree(self): """Get or set fraction of latent heat absorbed by urban trees.""" return self._lattree @lattree.setter def lattree(self, value): self._lattree = float_in_range(value, 0, 1, 'lattree') @property def latgrss(self): """Get or set fraction of latent heat absorbed by urban grass.""" return self._latgrss @latgrss.setter def latgrss(self, value): self._latgrss = float_in_range(value, 0, 1, 'latgrss') @property def zone(self): """Get or set text representing an ASHRAE climate zone. This value is used to specify climate zone-specific construction, and HVAC parameters for the DOE reference building types. This will not effect the simulation if only custom reference buildings are used. Choose from the following: * '1A' - (i.e Miami) * '1B' - (i.e Kuwait) * '2A' - (i.e Houston) * '2B' - (i.e Phoenix) * '3A' - (i.e Atlanta) * '3B-CA' - (i.e Los Angeles) * '3B' - (i.e Las Vegas) * '3C' - (i.e San Francisco) * '4A' - (i.e Baltimore) * '4B' - (i.e Albuquerque) * '4C' - (i.e Seattle) * '5A' - (i.e Chicago) * '5B' - (i.e Boulder) * '5C' - (i.e Bremerton) * '6A' - (i.e Minneapolis) * '6B' - (i.e Helena) * '7' - (i.e Duluth) * '8' - (i.e Fairbanks) """ return self._zone @zone.setter def zone(self, value): assert isinstance(value, str), \ 'zone must be a string. Got: {}.'.format(value) value = value.upper() assert value in REF_ZONETYPE_SET, 'zone must be one of {}. Got: {}.'.format( REF_ZONETYPE, value) self._zone = value @property def vegstart(self): """Get or set value from 1 to 12 for month at which vegetation starts to evapotranspire. This month corresponds to when the leaves of vegetation are assumed to be out. """ return self._vegstart @vegstart.setter def vegstart(self, value): self._vegstart = int_in_range(value, 1, 12, 'vegstart') @property def vegend(self): """Get or set value from 1 to 12 for month at which vegetation stops evapotranspiration. This month corresponds to when the leaves of vegetation are assumed to fall. """ return self._vegend @vegend.setter def vegend(self, value): self._vegend = int_in_range(value, 1, 12, 'vegend') @property def blddensity(self): """Get or set building footprint density as fraction of urban area. The sum of blddensity, grasscover and treecover must be less than or equal to 1. """ return self._blddensity @blddensity.setter def blddensity(self, value): try: assert self.vegcover + value <= 1, 'The sum of the blddensity, treecover '\ ' and grasscover ratios must be less than one. Got: {}, {} and {}, ' \ 'respectively.'.format(value, self.treecover, self.grasscover) except AttributeError: pass # attributes haven't been set self._blddensity = float_in_range(value, 0, 1, 'blddensity') @property def treecover(self): """Get or set fraction of urban area covered in trees. The sum of blddensity, grasscover and treecover must be less than or equal to 1. """ return self._treecover @treecover.setter def treecover(self, value): try: assert self.grasscover + self.blddensity + value <= 1, 'The sum of the ' \ 'blddensity, treecover and grasscover ratios must be less than one. ' \ 'Got: {}, {} and {}, respectively.'.format( self.blddensity, value, self.grasscover) except AttributeError: pass # attributes haven't been set self._treecover = float_in_range(value, 0, 1, 'treecover') @property def grasscover(self): """Get or set fraction of urban area covered exclusively in grass. This value does not including grass under trees. The sum of blddensity, grasscover and treecover must be less than or equal to 1. """ return self._grasscover @grasscover.setter def grasscover(self, value): try: assert self.treecover + self.blddensity + value <= 1, 'The sum of the ' \ 'blddensity, treecover and grasscover ratios must be less than one. ' \ 'Got: {}, {} and {}, respectively.'.format( self.blddensity, self.treecover, value) except AttributeError: pass # attributes haven't been set self._grasscover = float_in_range(value, 0, 1, 'grasscover') @property def vegcover(self): """Get fraction of urban ground covered by trees and grass.""" return self.treecover + self.grasscover @property def albveg(self): """Get or set vegetation albedo.""" return self._albveg @albveg.setter def albveg(self, value): self._albveg = float_in_range(value, 0, 1, 'albveg') @property def rurvegcover(self): """Get or set fraction of rural ground covered by vegetation.""" return self._rurvegcover @rurvegcover.setter def rurvegcover(self, value): self._rurvegcover = float_in_range(value, 0, 1, 'rurvegcover') @property def kroad(self): """Get or set road pavement conductivity [W/mK].""" return self._kroad @kroad.setter def kroad(self, value): self._kroad = float_positive(value, 'kroad') @property def croad(self): """Get or set road pavement volumetric heat capacity [J/m^3K].""" return self._croad @croad.setter def croad(self, value): self._croad = float_positive(value, 'croad') @property def schtraffic(self): """Get or set matrix for schedule of fractional anthropogenic heat load. This property consists of a 3 x 24 matrix. Each row corresponding to a schedule for a weekday, Saturday, and Sunday, and each column corresponds to an hour in the day, for example: .. code-block:: python # Weekday schedule wkday = [0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.7, 0.9, 0.9, 0.6, 0.6, 0.6, 0.6, 0.6, 0.7, 0.8, 0.9, 0.9, 0.8, 0.8, 0.7, 0.3, 0.2, 0.2] # Saturday schedule satday = [0.2, 0.2, 0.2, 0.2, 0.2, 0.3, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.7, 0.7, 0.7, 0.7, 0.5, 0.4, 0.3, 0.2, 0.2] # Sunday schedule sunday = [0.2, 0.2, 0.2, 0.2, 0.2, 0.3, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.3, 0.3, 0.2, 0.2] schtraffic = [wkday, satday, sunday] """ return self._schtraffic @schtraffic.setter def schtraffic(self, value): value = SchDef.check_week_validity(value, 'schtraffic') self._schtraffic = [[0 for c in range(24)] for r in range(3)] # Check column number and add value for i in range(3): for j in range(24): self._schtraffic[i][j] = float_in_range(value[i][j]) @property def shgc(self): """Get or set average building glazing Solar Heat Gain Coefficient. If value is None, a unique shgc is set for each building from the refBEM. (Default: None). """ return self._shgc @shgc.setter def shgc(self, value): if value is None: self._shgc = value else: self._shgc = float_in_range(value, 0, 1, 'shgc') @property def albroof(self): """Get or set average building roof albedo. If value is None, a unique albroof is set for each building from the refBEM. (Default: None). """ return self._albroof @albroof.setter def albroof(self, value): if value is None: self._albroof = value else: self._albroof = float_in_range(value, 0, 1, 'albroof') @property def glzr(self): """Get or set average building glazing ratio. If value is None, a unique glzr is set for each building from the refBEM. (Default: None). """ return self._glzr @glzr.setter def glzr(self, value): if value is None: self._glzr = value else: self._glzr = float_in_range(value, 0, 1, 'glzr') @property def vegroof(self): """Get or set fraction of roofs covered in grass/shrubs. If value is None, a unique vegroof is set for each building from the refBEM. (Default: None). """ return self._vegroof @vegroof.setter def vegroof(self, value): if value is None: self._vegroof = value else: self._vegroof = float_in_range(value, 0, 1, 'vegroof') @property def albwall(self): """Get or set average building albedo. If value is None, a unique albwall is set for each building from the refBEM. (Default: None). """ return self._albwall @albwall.setter def albwall(self, value): if value is None: self._albwall = value else: self._albwall = float_in_range(value, 0, 1, 'albwall') @property def flr_h(self): """Get or set average building floor height in meters. If value is None, a unique flr_h is set for each building from the refBEM. (Default: None). """ return self._flr_h @flr_h.setter def flr_h(self, value): if value is None: self._flr_h = value else: self._flr_h = float_positive(value, 'flr_h') @property def ref_bem_vector(self): """Get list of custom BEMDef objects to add to refBEM. If value is None, all BEMDef objects are referenced from the DOE typologies defined by default in the refBEM matrix. (Default: None). """ return self._ref_bem_vector @ref_bem_vector.setter def ref_bem_vector(self, value): if self._ref_bem_vector is None: self._ref_bem_vector = value else: raise Exception('Cannot reset ref_bem_vector value.') @property def ref_sch_vector(self): """Get list of custom SchDef objects to add to refSchedule. If value is None, all SchDef objects are referenced from the DOE typologies defined by default in the refSch matrix. (Default: None). """ return self._ref_sch_vector @ref_sch_vector.setter def ref_sch_vector(self, value): if self._ref_sch_vector is None: self._ref_sch_vector = value else: raise Exception('Cannot reset ref_sch_vector value.')
[docs] def to_dict(self, include_refDOE=False): """UWG dictionary representation. Args: add_refDOE: Optional boolean to include custom reference BEMDef and SchDef objects from the ref_bem_vector and ref_sch_vector attributes. (Default: False). """ base = {'type': 'UWG'} # Add UWG parameters for attr in self.PARAMETER_LIST: base[attr] = getattr(self, attr) # Add reference data if include_refDOE and self.ref_bem_vector and self.ref_sch_vector: base['ref_sch_vector'] = [s.to_dict() for s in self.ref_sch_vector] base['ref_bem_vector'] = [b.to_dict() for b in self.ref_bem_vector] return base
[docs] def generate(self): """Generate all UWG objects after input parameters are set.""" if not self.epw_path: raise Exception('Cannot generate the UWG while epw_path is None.') if self.ref_bem_vector: self._customize_reference_data() self._read_epw() self._compute_BEM() self._compute_input() self._hvac_autosize()
[docs] def simulate(self): """Simulate UWG object and produce urban canyon weather timeseries data. This function will set the following attributes in the UWG object: * N - Total number of hours in simulation * ph - Number of simulation time steps per hour * dayType - Number representing day type: Sunday, Saturday or Weekday * ceil_time_step - sim timestep fitted to weather file timestep * solar - SolarCalcs object for current timestep solar calculation * WeatherData - N x 1 output vector of forc object instance * UCMData - N x 1 output vector of UCM object instance * UBLData - N x 1 output vector of UBL object instance * RSMData - N x 1 output vector of RSM object instace * USMData - N x 1 output vector of USM object instance """ # total number of hours in simulation self.N = int(self.simTime.days * 24) n = 0 # weather time step counter self.ph = self.simTime.dt / 3600. # dt (simulation time step) in hours # Data dump variables self.WeatherData = [None for x in range(self.N)] self.UCMData = [None for x in range(self.N)] self.UBLData = [None for x in range(self.N)] self.RSMData = [None for x in range(self.N)] self.USMData = [None for x in range(self.N)] print('Simulating new temperature and humidity values for ' '{} days from {}/{}.'.format(self.nday, self.month, self.day)) # iterate through every simulation time-step (i.e 5 min) defined by UWG day_step = int((1 / self.ph) * 24) for it in range(1, self.simTime.nt, 1): if it % day_step == 0: print('Simulating Day {}'.format(int(it / day_step))) # Update water temperature (estimated) if self.nSoil < 3: # for BUBBLE/CAPITOUL/Singapore only self.forc.deepTemp = sum( self.forcIP.temp) / float(len(self.forcIP.temp)) self.forc.waterTemp = \ sum(self.forcIP.temp) / float(len(self.forcIP.temp)) - 10.0 else: # soil temperature by depth, by month self.forc.deepTemp = self.Tsoil[self._soilindex1][self.simTime.month - 1] self.forc.waterTemp = self.Tsoil[2][self.simTime.month - 1] # There's probably a better way to update the weather... self.simTime.update_date() # simulation time increment raised to weather time step self.ceil_time_step = int(math.ceil(it * self.ph)) - 1 # minus one to be consistent with forcIP list index # Updating forcing instance # horizontal Infrared Radiation Intensity (W m-2) self.forc.infra = self.forcIP.infra[self.ceil_time_step] # wind speed (m s-1) self.forc.wind = max(self.forcIP.wind[self.ceil_time_step], self.geoParam.windMin) # wind direction self.forc.uDir = self.forcIP.uDir[self.ceil_time_step] # specific humidty (kg kg-1) self.forc.hum = self.forcIP.hum[self.ceil_time_step] # Pressure (Pa) self.forc.pres = self.forcIP.pres[self.ceil_time_step] # air temp (C) self.forc.temp = self.forcIP.temp[self.ceil_time_step] self.forc.rHum = self.forcIP.rHum[self.ceil_time_step] # RH (%) # Precip (mm h-1) self.forc.prec = self.forcIP.prec[self.ceil_time_step] # horizontal solar diffuse radiation (W m-2) self.forc.dif = self.forcIP.dif[self.ceil_time_step] # normal solar direct radiation (W m-2) self.forc.dir = self.forcIP.dir[self.ceil_time_step] # Canyon humidity (absolute) same as rural self.UCM.canHum = copy.copy(self.forc.hum) # Update solar flux self.solar = SolarCalcs( self.UCM, self.BEM, self.simTime, self.RSM, self.forc, self.geoParam, self.rural) self.rural, self.UCM, self.BEM = self.solar.solarcalcs() # Update building & traffic schedule # Assign day type (1 = weekday, 2 = sat, 3 = sun/other) if utilities.is_near_zero(self.simTime.julian % 7, 1e-10): self.dayType = 3 # Sunday elif utilities.is_near_zero(self.simTime.julian % 7 - 6., 1e-10): self.dayType = 2 # Saturday else: self.dayType = 1 # Weekday # Update anthropogenic heat load for each hour (building & UCM) self.UCM.sensAnthrop = \ self.sensanth * \ (self.schtraffic[self.dayType - 1][self.simTime.hourDay]) # Update the energy components for building types defined in initialize.UWG for i in range(len(self.BEM)): di = self.dayType - 1 hi = self.simTime.hourDay # Set temperature # add from temperature schedule for cooling self.BEM[i].building.cool_setpoint_day = \ self.Sch[i].cool[di][hi] + 273.15 self.BEM[i].building.cool_setpoint_night = \ self.BEM[i].building.cool_setpoint_day # add from temperature schedule for heating self.BEM[i].building.heat_setpoint_day = \ self.Sch[i].heat[di][hi] + 273.15 self.BEM[i].building.heat_setpoint_night = \ self.BEM[i].building.heat_setpoint_day # Internal Heat Load Schedule (W/m^2 of floor area for Q) # Qelec x elec fraction for day self.BEM[i].elec = self.Sch[i].q_elec * \ self.Sch[i].elec[di][hi] # Qlight x light fraction for day self.BEM[i].light = self.Sch[i].q_light * \ self.Sch[i].light[di][hi] # Number of occupants x occ fraction for day self.BEM[i].Nocc = self.Sch[i].n_occ * self.Sch[i].occ[di][hi] # Sensible Q occ * fraction occ sensible Q * number of occ self.BEM[i].Qocc = self.sensocc * \ (1 - self.latfocc) * self.BEM[i].Nocc # SWH and ventilation schedule # L/hr/m2 x SWH fraction for day self.BEM[i].swh = self.Sch[i].v_swh * self.Sch[i].swh[di][hi] # m^3/s/m^2 x Vent fraction for day self.BEM[i].building.vent = self.Sch[i].vent # Gas Equip Schedule, per m^2 of floor self.BEM[i].gas = self.Sch[i].q_gas * self.Sch[i].gas[di][hi] # This is quite messy, should update # Update internal heat and corresponding fractional loads intHeat = self.BEM[i].light + \ self.BEM[i].elec + self.BEM[i].Qocc # W/m2 from light, electricity, occupants self.BEM[i].building.int_heat_day = intHeat self.BEM[i].building.int_heat_night = intHeat # fraction of radiant heat from light/equipment of whole internal heat self.BEM[i].building.int_heat_f_rad = \ (self.radflight * self.BEM[i].light + self.radfequip * self.BEM[i].elec) / intHeat # fraction of latent heat (from occupants) of whole internal heat self.BEM[i].building.int_heat_flat = \ self.latfocc * self.sensocc * self.BEM[i].Nocc / intHeat # Update envelope temperature layers self.BEM[i].T_wallex = self.BEM[i].wall.layerTemp[0] self.BEM[i].T_wallin = self.BEM[i].wall.layerTemp[-1] self.BEM[i].T_roofex = self.BEM[i].roof.layerTemp[0] self.BEM[i].T_roofin = self.BEM[i].roof.layerTemp[-1] # Update rural heat fluxes & update vertical diffusion model (VDM) self.rural.infra = self.forc.infra - self.rural.emissivity * self.SIGMA * \ self.rural.layerTemp[0] ** 4. # Infrared radiation from rural road self.rural.SurfFlux(self.forc, self.geoParam, self.simTime, self.forc.hum, self.forc.temp, self.forc.wind, 2., 0.) self.RSM.vdm(self.forc, self.rural, self.geoParam, self.simTime) # Calculate urban heat fluxes, update UCM & UBL self.UCM, self.UBL, self.BEM = urbflux( self.UCM, self.UBL, self.BEM, self.forc, self.geoParam, self.simTime, self.RSM) self.UCM.UCModel(self.BEM, self.UBL.ublTemp, self.forc, self.geoParam) self.UBL.ublmodel( self.UCM, self.RSM, self.rural, self.forc, self.geoParam, self.simTime) # Experimental code to run diffusion model in the urban area # N.B Commented out in python UWG because computed wind speed in urban # VDM: y = =0.84 * ln((2 - x / 20) / 0.51) results in negative log for # building heights >= 40m. # Uroad = copy.copy(self.UCM.road) # Uroad.sens = copy.copy(self.UCM.sensHeat) # Uforc = copy.copy(self.forc) # Uforc.wind = copy.copy(self.UCM.canWind) # Uforc.temp = copy.copy(self.UCM.canTemp) # self.USM.VDM(Uforc,Uroad,self.geoParam,self.simTime) istimestep = utilities.is_near_zero( self.simTime.secDay % self.simTime.timePrint, 1e-10) if istimestep and n < self.N: self.WeatherData[n] = copy.copy(self.forc) _Tdb, _w, self.UCM.canRHum, _h, self.UCM.Tdp, _v = psychrometrics( self.UCM.canTemp, self.UCM.canHum, self.forc.pres) self.UBLData[n] = copy.copy(self.UBL) self.UCMData[n] = copy.copy(self.UCM) self.RSMData[n] = copy.copy(self.RSM) n += 1
[docs] def write_epw(self): """Write new EPW file to new_epw_path property.""" epw_prec = self.epw_precision # precision of epw file input for iJ in range(len(self.UCMData)): # [iJ+self.simTime.timeInitial-8] - increments along weather timestep in epw # [6 to 21] - column data of epw # dry bulb temperature [C] self.epwinput[iJ + self.simTime.timeInitial - 8][6] = \ '{0:.{1}f}'.format(self.UCMData[iJ].canTemp - 273.15, epw_prec) # dew point temperature [?C] self.epwinput[iJ + self.simTime.timeInitial - 8][7] = \ '{0:.{1}f}'.format(self.UCMData[iJ].Tdp, epw_prec) # relative humidity [%] self.epwinput[iJ + self.simTime.timeInitial - 8][8] = \ '{0:.{1}f}'.format(self.UCMData[iJ].canRHum, epw_prec) # wind speed [m/s] self.epwinput[iJ + self.simTime.timeInitial - 8][21] = \ '{0:.{1}f}'.format(self.WeatherData[iJ].wind, epw_prec) # Writing new EPW file epw_new_id = open(self.new_epw_path, 'w') for i in range(8): new_epw_line = \ '{}\n'.format( reduce(lambda x, y: x + ',' + y, self._header[i])) epw_new_id.write(new_epw_line) for i in range(len(self.epwinput)): printme = '' for ei in range(len(self.epwinput[i])): printme += "{}".format(self.epwinput[i][ei]) + ',' printme = printme + "{}".format(self.epwinput[i][ei]) new_epw_line = '{0}\n'.format(printme) epw_new_id.write(new_epw_line) epw_new_id.close() print('New climate file is generated at {}.'.format( self.new_epw_path))
def _read_input(self, param_path): """Read the parameter input file (.uwg file) and set as UWG attributes.""" # Open .UWG file and feed csv data to initializeDataFile param_data = utilities.read_csv(param_path) # The initialize.UWG is read with a dictionary so that users changing # line endings or line numbers doesn't make reading input incorrect count = 0 self._init_param_dict = {} while count < len(param_data): row = param_data[count] row = [c.replace(' ', '').lower() for c in row] # optional parameters might be empty so handle separately is_optional_parameter = \ row[0] in self.OPTIONAL_PARAMETER_SET if len( row) > 0 else False try: if row == [] or '#' in row[0]: count += 1 elif row[0] == 'schtraffic': # SchTraffic: 3 x 24 matrix trafficrows = param_data[count + 1:count + 4] self._init_param_dict[row[0]] = \ [utilities.str2fl(r[:24]) for r in trafficrows] count += 4 elif row[0] == 'bld': # bld: list of (bldtype, builtera, frac) count += 1 bld = [] nextrow = [c.replace(' ', '').lower() for c in param_data[count]] while len(nextrow) > 0 and nextrow[0] in REF_BLDTYPE_SET: bldtype, builtera, frac = nextrow[0], nextrow[1], nextrow[2] bld.append((bldtype, builtera, float(frac))) count += 1 nextrow = [c.replace(' ', '').lower() for c in param_data[count]] self._init_param_dict[row[0]] = bld elif row[0] == 'zone': self._init_param_dict[row[0]] = row[1] count += 1 elif is_optional_parameter: self._init_param_dict[row[0]] = None if row[1] == '' else float( row[1]) count += 1 else: self._init_param_dict[row[0]] = float(row[1]) count += 1 except (ValueError, IndexError) as e: print(e) print('Error while reading parameter at row {}. Got: {}.'.format( count, row)) # Set UWG parameters for attr in self.PARAMETER_LIST: assert attr in self._init_param_dict, 'The {} attribute is not defined in ' \ 'the {} parameter file.'.format( attr, os.path.split(param_path)[-1]) setattr(self, attr, self._init_param_dict[attr]) def _read_epw(self): """Read EPW file and sets corresponding UWG weather and site properties. This function will set the following attributes in the UWG object: * epwinput - EPW weather data as list * lat - latitude from EPW header * lon - longitude from EPW header * gmt - Greenwich Mean Time from EPW header * nSoil - Number of soil depths from EPW header * Tsoil - nSoil x 12 matrix for soil temperture from EPW header[1] * depth_soil - nSoil x 1 matrix for soil depth from EPW header[1] * new_epw_path - file path to modified EPW file Note: [1] EPW CSV Format (In/Out). Retrieved August 26, 2020, from https://bigladdersoftware.com/epx/docs/8-2/auxiliary-programs/epw-csv-format-inout.html """ # Open epw file and feed csv data to climate_data climate_data = utilities.read_csv(self.epw_path) # Read header lines (1 to 8) from EPW and ensure TMY2 format. self._header = climate_data[0:8] # Read weather data from EPW for each time step in weather file. (lines 8 - end) self.epwinput = climate_data[8:] # Read Lat, Long (line 1 of EPW) self.lat = float(self._header[0][6]) self.lon = float(self._header[0][7]) self.gmt = float(self._header[0][8]) # Read in soil temperature data (assumes this is always there) soilData = self._header[3] # Number of ground temperature depths self.nSoil = int(soilData[1]) # nSoil x 12 matrix for soil temperture (K) self.Tsoil = [[0 for c in range(12)] for r in range(self.nSoil)] # nSoil x 1 matrix for soil depth (m) self.depth_soil = [[0] for r in range(self.nSoil)] # Read monthly data for each layer of soil from EPW file for i in range(self.nSoil): # get soil depth for each nSoil self.depth_soil[i][0] = float(soilData[2 + (i * 16)]) # monthly data for j in range(12): # 12 months of soil T for specific depth self.Tsoil[i][j] = float(soilData[6 + (i * 16) + j]) + 273.15 def _compute_BEM(self): """Define BEMDef objects for each archetype defined in the bld matrix. This function will set the following attributes in the UWG object: * r_glaze_total - Area-weighted average of glazing ratio from urban building stock [Km/W]. * SHGC_total - Area-weighted average of SHGC from urban building stock. * alb_wall_total - Area-weighted average of wall albedo from urban building stock. * BEM - list of BEMDef objects extracted from readDOE * Sch - list of Schedule objects extracted from readDOE """ # Define building energy models k = 0 self.r_glaze_total = 0. self.SHGC_total = 0. self.alb_wall_total = 0. h_floor = self.flr_h or 3.05 # average floor height total_urban_bld_area = math.pow(self.charlength, 2) * self.blddensity * \ self.bldheight / h_floor # total building floor area self.BEM = [] # list of BEMDef objects self.Sch = [] # list of Schedule objects # Modify zone to be used as python index bld_z = '1A' if self.zone == '1B' else '5B' if self.zone == '5C' else self.zone zone_idx = REF_ZONETYPE.index(bld_z) # Build unique key based on bldtype and builtera strings bld_dict = {bldg[0] + bldg[1]: (bldg[0], REF_BUILTERA.index(bldg[1]), bldg[2]) for bldg in self.bld} for i in range(len(self.refBEM)): # ~16 building types (more w/ custom refs) for j in range(3): # ~ 3 built eras if not self.refBEM[i][j][0]: # when add custom types some matrix elements are None continue ref_key = \ self.refBEM[i][j][0].bldtype + \ self.refBEM[i][j][0].builtera if ref_key in bld_dict: # Add to BEM list bldtype, builtera_idx, frac = bld_dict[ref_key] self.BEM.append(self.refBEM[i][builtera_idx][zone_idx]) self.BEM[k].frac = frac self.BEM[k].fl_area = frac * total_urban_bld_area # Overwrite with optional parameters if provided if self.glzr: self.BEM[k].building.glazing_ratio = self.glzr if self.albroof: self.BEM[k].roof.albedo = self.albroof if self.vegroof: self.BEM[k].roof.vegcoverage = self.vegroof if self.shgc: self.BEM[k].building.shgc = self.shgc if self.albwall: self.BEM[k].wall.albedo = self.albwall if self.flr_h: self.BEM[k].building.floor_height = self.flr_h # Keep track of total urban r_glaze, SHGC, and alb_wall for UCM model self.r_glaze_total += \ self.BEM[k].frac * self.BEM[k].building.glazing_ratio self.SHGC_total += self.BEM[k].frac * \ self.BEM[k].building.shgc self.alb_wall_total += self.BEM[k].frac * \ self.BEM[k].wall.albedo # Add to schedule list self.Sch.append( self.refSchedule[i][builtera_idx][zone_idx]) k += 1 def _compute_input(self): """Create input objects from user-defined parameters. This function will set the following input objects as attributes in the UWG object: * simTime - SimParam object for time parameters * weather - Weather object for weather data * forcIP - Forcing object to estimate deep ground/water temperature * forc - empty Forcing object to estimate deep ground/water temperature * RSM - RSMDef object for rural site and vertical diffusion model * USM - RSMDef object for urban site and vertical diffusion model * geoParam - Param object for geographic parameters * UBL - UBL object for urban boundary layer model * UCM - UCMDef object for urban canopy model * road - Element object for urban road * rural - Element object for rural road """ self.simTime = SimParam(self.dtsim, self.dtweather, self.month, self.day, self.nday) # simulation time parameters # weather file data for simulation time period self.weather = Weather( self.epw_path, self.simTime.timeInitial, self.simTime.timeFinal) self.forcIP = Forcing(self.weather.staTemp, self.weather) # init Forcing obj self.forc = Forcing() # empty Forcing obj # Initialize geographic Param and Urban Boundary Layer Objects nightStart = 18. # arbitrary values for begin/end hour for night setpoint nightEnd = 8. maxdx = 250. # max dx (m) self.geoParam = Param( self.h_ubl1, self.h_ubl2, self.h_ref, self.h_temp, self.h_wind, self.c_circ, self.maxday, self.maxnight, self.lattree, self.latgrss, self.albveg, self.vegstart, self.vegend, nightStart, nightEnd, self.windmin, self.WGMAX, self.c_exch, maxdx, self.G, self.CP, self.VK, self.R, self.RV, self.LV, math.pi, self.SIGMA, self.WATERDENS, self.LVTT, self.TT, self.ESTT, self.CL, self.CPV, self.B, self.CM, self.COLBURN) self.UBL = UBLDef( 'C', self.charlength, self.weather.staTemp[0], maxdx, self.geoParam.dayBLHeight, self.geoParam.nightBLHeight) # Defining road emis = 0.93 asphalt = Material(self.kroad, self.croad, 'asphalt') road_T_init = 293. road_horizontal = 1 # fraction of vegetation (tree & grass) coverage on unbuilt surface road_veg_coverage = self.vegcover / (1 - self.blddensity) road_grass_coverage = self.grasscover / (1 - self.blddensity) road_tree_coverage = self.treecover / (1 - self.blddensity) # define road layers road_layer_num = int(math.ceil(self.droad / 0.05)) # 0.5/0.05 ~ 10 x 1 matrix of 0.05 thickness thickness_vector = [0.05 for r in range(road_layer_num)] material_vector = [asphalt for r in range(road_layer_num)] self.road = Element( self.albroad, emis, thickness_vector, material_vector, road_veg_coverage, road_T_init, road_horizontal, name='urban_road') self.road.treecoverage = road_tree_coverage self.road.grasscoverage = road_grass_coverage self.rural = Element( self.albroad, emis, thickness_vector, material_vector, self.rurvegcover, road_T_init, road_horizontal, name='rural_road') # Reference site class (also include VDM) self.RSM = RSMDef( self.lat, self.lon, self.gmt, self.h_obs, self.weather.staTemp[0], self.weather.staPres[0], self.geoParam, self.Z_MESO_PATH) self.USM = RSMDef( self.lat, self.lon, self.gmt, self.bldheight / 10., self.weather.staTemp[0], self.weather.staPres[0], self.geoParam, self.Z_MESO_PATH) T_init = self.weather.staTemp[0] H_init = self.weather.staHum[0] wind_init = self.weather.staUmod[0] self.UCM = UCMDef( self.bldheight, self.blddensity, self.vertohor, self.treecover, self.sensanth, self.latanth, T_init, H_init, wind_init, self.geoParam, self.r_glaze_total, self.SHGC_total, self.alb_wall_total, self.road) self.UCM.h_mix = self.h_mix # Define Road Element & buffer to match ground temperature depth roadMat, newthickness = UWG._procmat( self.road, self.MAXTHICKNESS, self.MINTHICKNESS) for i in range(self.nSoil): # if soil depth is greater then the thickness of the road # we add new slices of soil at max thickness until road is greater or equal is_soildepth_equal = utilities.is_near_zero( self.depth_soil[i][0] - sum(newthickness), 1e-15) if is_soildepth_equal or (self.depth_soil[i][0] > sum(newthickness)): while self.depth_soil[i][0] > sum(newthickness): newthickness.append(self.MAXTHICKNESS) roadMat.append(self.SOIL) self._soilindex1 = i break self.road = Element( self.road.albedo, self.road.emissivity, newthickness, roadMat, self.road.vegcoverage, self.road.layerTemp[0], self.road.horizontal, self.road.name) # Define Rural Element ruralMat, newthickness = self._procmat( self.rural, self.MAXTHICKNESS, self.MINTHICKNESS) for i in range(self.nSoil): # if soil depth is greater then the thickness of the road # we add new slices of soil at max thickness until road is greater or equal is_soildepth_equal = utilities.is_near_zero( self.depth_soil[i][0] - sum(newthickness), 1e-15) if is_soildepth_equal or (self.depth_soil[i][0] > sum(newthickness)): while self.depth_soil[i][0] > sum(newthickness): newthickness.append(self.MAXTHICKNESS) ruralMat.append(self.SOIL) self._soilindex2 = i break self.rural = Element( self.rural.albedo, self.rural.emissivity, newthickness, ruralMat, self.rural.vegcoverage, self.rural.layerTemp[0], self.rural.horizontal, self.rural.name) def _hvac_autosize(self): """HVAC autosizing (unlimited cooling & heating) in BEM objects.""" for i in range(len(self.BEM)): if self.autosize: self.BEM[i].building.coolcap = 9999. self.BEM[i].building.heat_cap = 9999. def _check_reference_data(self, ref_bem_vector, ref_sch_vector): """Check validity of reference BEMDef and SchDef lists. Args: ref_bem_vector: List of custom BEMDef objects to override or add to the refBEM matrix according to the BEMDef bldtype and builtera values. ref_sch_vector: List of custom SchDef objects to override or add to the refSchedule matrix according to the SchDef bldtype and builtera values. Returns: Tuple consisting of validated ref_bem_vector and ref_sch_vector. """ # check for null vector if ref_bem_vector is None: return None, None assert len(ref_sch_vector) == len(ref_bem_vector), 'The ' \ 'ref_sch_vector and ref_bem_vector properties must be lists of equal ' \ 'length. Got lengths {} and {}, respectively.'.format( len(ref_sch_vector), len(ref_bem_vector)) assert all(isinstance(v, SchDef) for v in ref_sch_vector), 'All items in the ' \ 'ref_sch_vector must be a SchDef object.' assert all(isinstance(v, BEMDef) for v in ref_bem_vector), 'All items in the ' \ 'ref_bem_vector must be a BEMDef object.' for ref_bem, ref_sch in zip(ref_bem_vector, ref_sch_vector): assert ref_bem.bldtype == ref_sch.bldtype, 'The bldtype for ' \ 'corresponding items in ref_bem_vector and ref_sch_vector must be the ' \ 'same. Got {} and {}.'.format(ref_bem.bldtype, ref_sch.bldtype) assert ref_bem.builtera == ref_sch.builtera, 'The builtera for ' \ 'corresponding items in ref_bem_vector and ref_sch_vector must be the ' \ 'same. Got {} and {}.'.format( ref_bem.builtera, ref_sch.builtera) return ref_bem_vector, ref_sch_vector def _customize_reference_data(self): """Customize refBEM and refSchedule data by extending or overriding DOE reference data. The custom BEMDef and SchDef objects must contain bldtype and builtera identifiers referencing a nonzero fraction of urban area in the UWG bld property to be used in the UWG model. Also note that this method should be used before calling the generate method, in order to ensure the reference data gets transferred over to the UWG object BEM and Schedule properties. Args: ref_bem_vector: List of custom BEMDef objects to override or add to the refBEM matrix according to the BEMDef bldtype and builtera values. ref_sch_vector: List of custom SchDef objects to override or add to the refSchedule matrix according to the SchDef bldtype and builtera values. """ bld_z = '1A' if self.zone == '1B' else '5B' if self.zone == '5C' else self.zone zi = REF_ZONETYPE.index(bld_z) # Insert or extend refSchedule matrix for sch in self.ref_sch_vector: ei = REF_BUILTERA.index(sch.builtera) try: ti = REF_BLDTYPE.index(sch.bldtype) # will fail if custom type print('Overwrite DOE reference schedule "{} {}" ' 'with custom schedule.'.format(sch.builtera, sch.bldtype)) except ValueError: # Add new rows based on type index in object ti = len(self.refSchedule) self.refSchedule.append([[None for c in range(16)] for r in range(3)]) print('Add custom schedule for "{} {}".'.format( sch.builtera, sch.bldtype)) self.refSchedule[ti][ei][zi] = sch # Insert or extend refBEM matrix for bem in self.ref_bem_vector: ei = REF_BUILTERA.index(bem.builtera) try: ti = REF_BLDTYPE.index(bem.bldtype) # will fail if custom type print('Overwrite DOE reference BEM "{} {}" ' 'with custom schedule.'.format(bem.builtera, bem.bldtype)) except ValueError: # Add new rows based on type index in object ti = len(self.refBEM) self.refBEM.append([[None for c in range(16)] for r in range(3)]) print('Add custom bem for "{} {}".'.format( bem.builtera, bem.bldtype)) self.refBEM[ti][ei][zi] = bem
[docs] @ staticmethod def load_refDOE(refDOE_path=REFDOE_PATH): """Static method to deserialize DOE reference data. Args: readDOE_path: Text string for full path to the refDOE pickle. (Default: the filepath specified in the UWG.REFDOE_PATH constant). Returns: Two 16 x 3 x 16 matrices of reference BEMDef and SchDef objects, respectively. """ assert os.path.exists(refDOE_path), \ 'File: {} does not exist.'.format(refDOE_path) # open pickle file in binary form with open(refDOE_path, 'rb') as refDOE_file: refBEM = pickle.load(refDOE_file) refSchedule = pickle.load(refDOE_file) return refBEM, refSchedule
@ staticmethod def _procmat(materials, max_thickness, min_thickness): """Processes material layer slices in Element objects based on layer number and depth. This function will divide a material with single layer thickness or that is too thick into subdivisions. """ newmat = [] newthickness = [] k = materials.layerThermalCond Vhc = materials.layerVolHeat if len(materials.layer_thickness_lst) > 1: for j in range(len(materials.layer_thickness_lst)): # Break up each layer that's more than max thickness (0.05m) if materials.layer_thickness_lst[j] > max_thickness: nlayers = math.ceil( (materials.layer_thickness_lst[j] / float(max_thickness))) for i in range(int(nlayers)): newmat.append( Material(k[j], Vhc[j], name=materials.name)) newthickness.append( materials.layer_thickness_lst[j] / float(nlayers)) # Material that's less then min_thickness is not added. elif materials.layer_thickness_lst[j] < min_thickness: print('WARNING: Material layer too thin (less then 2 cm) to process.' 'Material {} is {:.2f} cm.'.format( materials.name, min_thickness * 100)) else: newmat.append(Material(k[j], Vhc[j], name=materials.name)) newthickness.append(materials.layer_thickness_lst[j]) else: # Divide single layer into two (UWG assumes at least 2 layers) if materials.layer_thickness_lst[0] > max_thickness: nlayers = math.ceil( (materials.layer_thickness_lst[0] / float(max_thickness))) for i in range(int(nlayers)): newmat.append(Material(k[0], Vhc[0], name=materials.name)) newthickness.append( materials.layer_thickness_lst[0] / float(nlayers)) # Material should be at least 1cm thick, so if we're here, # should give warning and stop. Only warning given for now. elif materials.layer_thickness_lst[0] < min_thickness * 2: newthickness = [min_thickness / 2., min_thickness / 2.] newmat = [Material(k[0], Vhc[0], name=materials.name), Material(k[0], Vhc[0], name=materials.name)] print('WARNING: Material layer less then 2 cm is found.' 'Material {} is {:.2f} cm. May cause error.'.format( materials.name, min_thickness * 100)) else: newthickness = [materials.layer_thickness_lst[0] / 2., materials.layer_thickness_lst[0] / 2.] newmat = [Material(k[0], Vhc[0], name=materials.name), Material(k[0], Vhc[0], name=materials.name)] return newmat, newthickness
[docs] def ToString(self): """Overwrite .NET ToString method.""" return self.__repr__()
def __repr__(self): def _split_string(s): return s[0] + ':\n ' + s[1].replace(',', '\n ') def _tabbed(s): return _split_string(s.__repr__().split(':')) def _list_2_tabbed(b): return reduce(lambda a, b: a + '\n' + b, [_tabbed(_b) for _b in b]) simtime_str = _tabbed(self.simTime) + '\n' \ if hasattr(self, 'simTime') else 'No simTime attr.\n' weather_str = _tabbed(self.weather) + '\n' \ if hasattr(self, 'weather') else 'No weather attr.\n' param_str = _tabbed(self.geoParam) + '\n' \ if hasattr(self, 'geoParam') else 'No geoParam attr.\n' ubl_str = _tabbed(self.UBL) + '\n' \ if hasattr(self, 'UBL') else 'No UBL attr.\n' rsm_str = 'Rural ' + _tabbed(self.RSM) + '\n' \ if hasattr(self, 'RSM') else 'No Rural RSM attr.\n' usm_str = 'Urban ' + _tabbed(self.USM) + '\n' \ if hasattr(self, 'USM') else 'No Urban RSM attr.\n' ucm_str = _tabbed(self.UCM) + '\n' \ if hasattr(self, 'UCM') else 'No UCM attr.\n' bem_str = reduce(lambda a, b: a.__repr__() + '\n' + b.__repr__(), self.BEM) \ if hasattr(self, 'BEM') else 'No BEM attr.' return 'UWG for {}:\n\n{}{}{}{}{}{}{}{}'.format( self.epw_path, simtime_str, weather_str, param_str, ubl_str, rsm_str, usm_str, ucm_str, bem_str)