# coding=utf-8
from __future__ import division
import codecs
import os
import platform
import re
from .analysisperiod import AnalysisPeriod
from .designday import ASHRAEClearSky
from .designday import ASHRAETau
from .designday import DesignDay
from .designday import DryBulbCondition
from .designday import HumidityCondition
from .designday import WindCondition
from .ddy import DDY
from .dt import Date
from .location import Location
try:
from itertools import izip as zip # python 2
except ImportError:
xrange = range # python 3
[docs]
class STAT(object):
"""Import data from a local .stat file.
Args:
file_path: Address to a local .stat file.
Properties:
* location
* ashrae_climate_zone
* koppen_climate_zone
* extreme_cold_week
* extreme_hot_week
* typical_winter_week
* typical_spring_week
* typical_summer_week
* typical_autumn_week
* other_typical_weeks
* annual_heating_design_day_996
* annual_heating_design_day_990
* annual_cooling_design_day_004
* annual_cooling_design_day_010
* monthly_cooling_design_days_100
* monthly_cooling_design_days_050
* monthly_cooling_design_days_020
* monthly_cooling_design_days_004
* monthly_db_temp_050
* monthly_wb_temp_050
* monthly_db_temp_range_050
* monthly_wb_temp_range_050
* monthly_found
* standard_pressure_at_elev
* monthly_wind_conditions
* monthly_ws_avg
* monthly_wind_dirs
* monthly_clear_sky_conditions
* monthly_tau_beam
* monthly_tau_diffuse
* file_path
"""
# categories used for parsing text
_months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
_wind_dirs = (0, 45, 90, 135, 180, 225, 270, 315)
_wind_dir_names = ('North', 'NorthEast', 'East', 'SouthEast', 'South',
'SouthWest', 'West', 'NorthWest')
# compiled strings for identifying data in the file
_coord_pattern1 = re.compile(r"{([NSEW])(\s*\d*).(\s*\d*)")
_coord_pattern2 = re.compile(r"{([NSEW])(\s*\d*) (\s*\d*)")
_elev_pattern1 = re.compile(r"Elevation\s*[-]*\s*(\d*)m\s*(\S*)")
_elev_pattern2 = re.compile(r"Elevation\s*[-]*\s*(\d*)\s*m\s*(\S*)")
_timez_pattern = re.compile(r"{GMT\s*(\S*)\s*Hours}")
_press_pattern = re.compile(r"Elevation\s*[-]*\s*(\d*)")
_ashraecz_pattern = re.compile(r'Climate type\s"(\S*)"\s\(A')
_koppencz_pattern = re.compile(r'Climate type\s"(\S*)"\s\(K')
_hotweek_pattern = re.compile(r"Extreme Hot Week Period selected:"
r"\s*(\w{3})\s*(\d{1,2}):\s*(\w{3})\s*(\d{1,2}),")
_coldweek_pattern = re.compile(r"Extreme Cold Week Period selected:"
r"\s*(\w{3})\s*(\d{1,2}):\s*(\w{3})\s*(\d{1,2}),")
_typweek_pattern = re.compile(r"(\S*)\s*Typical Week Period selected:"
r"\s*(\w{3})\s*(\d{1,2}):\s*(\w{3})\s*(\d{1,2}),")
_heat_pattern = re.compile(r"Heating\s(\d.*)")
_cool_pattern = re.compile(r"Cooling\s(\d.*)")
_tau_beam_pattern = re.compile(r"taub \(beam\)(.*)")
_tau_diffuse_pattern = re.compile(r"taud \(diffuse\)(.*)")
_db_50_pattern = re.compile(r"Drybulb 5.0%(.*)")
_wb_50_pattern = re.compile(r"Coincident Wetbulb 5.0%(.*)")
_db_100_pattern = re.compile(r"Drybulb 10.%(.*)")
_wb_100_pattern = re.compile(r"Coincident Wetbulb 10.%(.*)")
_db_20_pattern = re.compile(r"Drybulb 2.0%(.*)")
_wb_20_pattern = re.compile(r"Coincident Wetbulb 2.0%(.*)")
_db_04_pattern = re.compile(r"Drybulb 0.4%(.*)")
_wb_04_pattern = re.compile(r"Coincident Wetbulb 0.4%(.*)")
_db_range_50_pattern = re.compile(r"Drybulb range - DB 5%(.*)")
_wb_range_50_pattern = re.compile(r"Wetbulb range - DB 5%(.*)")
_winds_pattern = re.compile(r"Monthly Statistics for Wind Speed[\s\S]*Daily Avg(.*)")
_windd_patterns = tuple(re.compile(
r"Monthly Wind Direction %[\s\S]*" + dir + r"\s(.*)") for dir in _wind_dir_names)
__slots__ = (
'_file_path', '_winter_des_day_dict', '_summer_des_day_dict',
'_monthly_wind_dirs', '_location',
'_ashrae_climate_zone', '_koppen_climate_zone',
'_extreme_cold_week', '_extreme_hot_week', '_typical_weeks', '_monthly_db_50',
'_monthly_wb_50', '_monthly_db_range_50', '_monthly_wb_range_50',
'_monthly_db_100', '_monthly_wb_100', '_monthly_db_20', '_monthly_wb_20',
'_monthly_db_04', '_monthly_wb_04', '_monthly_wind',
'_stand_press_at_elev', '_monthly_tau_beam', '_monthly_tau_diffuse',
'_header', '_body'
)
def __init__(self, file_path):
"""Initialize the class.
"""
if file_path is not None:
if not os.path.isfile(file_path):
raise ValueError(
'Cannot find an stat file at {}'.format(file_path))
if not file_path.lower().endswith('stat'):
raise TypeError('{} is not an .stat file.'.format(file_path))
self._file_path = os.path.normpath(file_path)
# defaults empty state for certain parameters
self._winter_des_day_dict = {}
self._summer_des_day_dict = {}
self._monthly_wind_dirs = []
# import the data from the file
if file_path is not None:
self._import_data()
[docs]
@classmethod
def from_dict(cls, data):
""" Create Stat from a dictionary.
Args:
data: A python dictionary in the following format
.. code-block:: python
{
"location": {}, # ladybug location schema
"ashrae_climate_zone": ""5A, # str
"koppen_climate_zone": "Dfa", # str
"extreme_cold_week": {}, # ladybug analysis period schema
"extreme_hot_week": {}, # ladybug analysis period schema
"typical_weeks": {}, # dict of ladybug analysis period schemas
"heating_dict": {}, # dict containing heating design conditions
"cooling_dict": {}, # dict containing cooling design conditions
"monthly_db_50": [], # list of 12 float values for each month
"monthly_wb_50": [], # list of 12 float values for each month
"monthly_db_range_50": [], # list of 12 float values for each month
"monthly_wb_range_50": [], # list of 12 float values for each month
"monthly_db_100": [], # list of 12 float values for each month
"monthly_wb_100": [], # list of 12 float values for each month
"monthly_db_20": [], # list of 12 float values for each month
"monthly_wb_20": [], # list of 12 float values for each month
"monthly_db_04": [], # list of 12 float values for each month
"monthly_wb_04": [], # list of 12 float values for each month
"monthly_wind": [], # list of 12 float values for each month
"monthly_wind_dirs": [], # matrix with 12 cols for months of the year
#and 8 rows for the cardinal directions.
"standard_pressure_at_elev": 101325, # float value for pressure in Pa
"monthly_tau_beam":[], # list of 12 float values for each month
"monthly_tau_diffuse": [] # list of 12 float values for each month
}
"""
# Initialize the class with all data missing
stat_ob = cls(None)
# Check required and optional keys
option_keys_none = ('ashrae_climate_zone', 'koppen_climate_zone',
'extreme_cold_week', 'extreme_hot_week',
'standard_pressure_at_elev')
option_keys_list = ('monthly_db_50', 'monthly_wb_50',
'monthly_db_range_50', 'monthly_wb_range_50',
'monthly_db_100', 'monthly_wb_100', 'monthly_db_20',
'monthly_wb_20', 'monthly_db_04', 'monthly_wb_04',
'monthly_wind', 'monthly_wind_dirs',
'monthly_tau_beam', 'monthly_tau_diffuse')
option_keys_dict = ('typical_weeks', 'heating_dict', 'cooling_dict')
assert 'location' in data, 'Required key "location" is missing!'
for key in option_keys_none:
if key not in data:
data[key] = None
for key in option_keys_list:
if key not in data:
data[key] = []
for key in option_keys_dict:
if key not in data:
data[key] = {}
# assign the properties of the dictionary to the stat object.
stat_ob._location = Location.from_dict(data['location'])
stat_ob._ashrae_climate_zone = data['ashrae_climate_zone']
stat_ob._koppen_climate_zone = data['koppen_climate_zone']
stat_ob._extreme_cold_week = AnalysisPeriod.from_dict(data['extreme_cold_week'])\
if data['extreme_cold_week'] else None
stat_ob._extreme_hot_week = AnalysisPeriod.from_dict(data['extreme_hot_week'])\
if data['extreme_hot_week'] else None
stat_ob._typical_weeks = {}
for key, val in data['typical_weeks'].items():
if isinstance(val, list):
stat_ob._typical_weeks[key] = [AnalysisPeriod.from_dict(v) for v in val]
else:
stat_ob._typical_weeks[key] = AnalysisPeriod.from_dict(val)
stat_ob._winter_des_day_dict = data['heating_dict']
stat_ob._summer_des_day_dict = data['cooling_dict']
stat_ob._monthly_db_50 = data['monthly_db_50']
stat_ob._monthly_wb_50 = data['monthly_wb_50']
stat_ob._monthly_db_range_50 = data['monthly_db_range_50']
stat_ob._monthly_wb_range_50 = data['monthly_wb_range_50']
stat_ob._monthly_db_100 = data['monthly_db_100']
stat_ob._monthly_wb_100 = data['monthly_wb_100']
stat_ob._monthly_db_20 = data['monthly_db_20']
stat_ob._monthly_wb_20 = data['monthly_wb_20']
stat_ob._monthly_db_04 = data['monthly_db_04']
stat_ob._monthly_wb_04 = data['monthly_wb_04']
stat_ob._monthly_wind = data['monthly_wind']
stat_ob._monthly_wind_dirs = data['monthly_wind_dirs']
stat_ob._stand_press_at_elev = data['standard_pressure_at_elev']
stat_ob._monthly_tau_beam = data['monthly_tau_beam']
stat_ob._monthly_tau_diffuse = data['monthly_tau_diffuse']
return stat_ob
@property
def file_path(self):
"""Get the path to the stat file."""
return self._file_path
def _import_data(self):
"""Import data from a stat file.
"""
# set default state to ironpython for very old ironpython (2.7.0)
iron_python = True
try:
iron_python = True if platform.python_implementation() == 'IronPython' \
else False
except ValueError as e:
# older versions of IronPython fail to parse version correctly
# failed to parse IronPython sys.version: '2.7.5 (IronPython 2.7.5 (2.7.5.0)
# on .NET 4.0.30319.42000 (64-bit))'
if 'IronPython' in str(e):
iron_python = True
if iron_python:
statwin = codecs.open(self.file_path, 'r')
else:
statwin = codecs.open(self.file_path, 'r', encoding='utf-8', errors='ignore')
try:
line = statwin.readline()
# import header with location
self._header = [line] + [statwin.readline() for i in xrange(9)]
self._body = statwin.read()
except Exception as e:
import traceback
raise Exception('{}\n{}'.format(e, traceback.format_exc()))
else:
# import location data
loc_name = self._header[2].strip().replace('Location -- ', '')
if ' - ' in loc_name:
city = ' '.join(loc_name.split(' - ')[:-1])
else: # for US stat files it is full name separated by spaces
city = ' '.join(loc_name.split()[:-2])
country = loc_name.split(' ')[-1]
source = self._header[6].strip().replace('Data Source -- ', '')
station_id = self._header[8].strip().replace('WMO Station ', '')
if iron_python: # IronPython
matches = self._coord_pattern1.findall(self._header[3])
else: # CPython
matches = self._coord_pattern2.findall(self._header[3])
lat_sign = -1 if matches[0][0] == 'S' else 1
latitude = lat_sign * (float(matches[0][1]) + (float(matches[0][2]) / 60))
lon_sign = -1 if matches[1][0] == 'W' else 1
longitude = lon_sign * (float(matches[1][1]) + (float(matches[1][2]) / 60))
time_zone = self._regex_check(self._timez_pattern, self._header[3])
elev_matches = self._elev_pattern1.findall(self._header[4])
if len(elev_matches) == 0:
elev_matches = self._elev_pattern2.findall(self._header[4])
elev_sign = -1 if elev_matches[0][-1].lower() == 'below' else 1
elevation = elev_sign * float(elev_matches[0][0])
self._location = Location()
self._location.city = city
self._location.country = country
self._location.source = source
self._location.station_id = station_id
self._location.latitude = latitude
self._location.longitude = longitude
self._location.time_zone = time_zone
self._location.elevation = elevation
# pull out individual properties
self._stand_press_at_elev = self._regex_check(
self._press_pattern, self._header[5])
self._ashrae_climate_zone = self._regex_check(
self._ashraecz_pattern, self._body)
self._koppen_climate_zone = self._regex_check(
self._koppencz_pattern, self._body)
# pull out extreme and seasonal weeks.
self._extreme_hot_week = self._regex_week_parse(self._hotweek_pattern)
self._extreme_cold_week = self._regex_week_parse(self._coldweek_pattern)
self._typical_weeks = self._regex_typical_week_parse()
# pull out annual design days
winter_vals = self._regex_parse(self._heat_pattern)
for key, val in zip(DesignDay.HEATING_KEYS, winter_vals):
self._winter_des_day_dict[key] = val
summer_vals = self._regex_parse(self._cool_pattern)
for key, val in zip(DesignDay.COOLING_KEYS, summer_vals):
self._summer_des_day_dict[key] = val
# Pull out relevant monthly information
self._monthly_tau_beam = self._regex_parse(self._tau_beam_pattern)
self._monthly_tau_diffuse = self._regex_parse(self._tau_diffuse_pattern)
self._monthly_db_50 = self._regex_parse(self._db_50_pattern)
self._monthly_wb_50 = self._regex_parse(self._wb_50_pattern)
self._monthly_db_100 = self._regex_parse(self._db_100_pattern)
self._monthly_wb_100 = self._regex_parse(self._wb_100_pattern)
self._monthly_db_20 = self._regex_parse(self._db_20_pattern)
self._monthly_wb_20 = self._regex_parse(self._wb_20_pattern)
self._monthly_db_04 = self._regex_parse(self._db_04_pattern)
self._monthly_wb_04 = self._regex_parse(self._wb_04_pattern)
self._monthly_db_range_50 = self._regex_parse(self._db_range_50_pattern)
self._monthly_wb_range_50 = self._regex_parse(self._wb_range_50_pattern)
self._monthly_wind = self._regex_parse(self._winds_pattern)
for direction in self._windd_patterns:
dirs = self._regex_parse(direction)
if dirs != []:
self._monthly_wind_dirs.append(dirs)
if self._monthly_wind_dirs == []:
self._monthly_wind_dirs = [[0] * 12 for i in xrange(8)]
finally:
statwin.close()
def _regex_check(self, regex_pattern, search_space):
matches = regex_pattern.findall(search_space)
if len(matches) > 0:
try:
return float(matches[0])
except ValueError:
return matches[0]
else:
return None
def _regex_week(self, match):
if len(match) == 4:
try:
st_mon = int(self._months.index(match[0])) + 1
end_mon = int(self._months.index(match[2])) + 1
st_day = int(match[1])
end_day = int(match[3])
except ValueError:
return None
return AnalysisPeriod(st_mon, st_day, 0, end_mon, end_day, 23)
else:
return None
def _regex_week_parse(self, regex_pattern):
matches = regex_pattern.findall(self._body)
if len(matches) > 0:
return self._regex_week(matches[0])
else:
return None
def _regex_typical_week_parse(self):
typ_weeks = {'other': []}
matches = self._typweek_pattern.findall(self._body)
for match in matches:
a_per = self._regex_week(match[1:])
if 'winter' in match[0]:
typ_weeks['winter'] = a_per
elif 'spring' in match[0]:
typ_weeks['spring'] = a_per
elif 'summer' in match[0]:
typ_weeks['summer'] = a_per
elif 'autumn' in match[0]:
typ_weeks['autumn'] = a_per
else:
typ_weeks['other'].append(a_per)
return typ_weeks
def _regex_parse(self, regex_pattern):
matches = regex_pattern.findall(self._body)
if len(matches) > 0:
raw_txt = matches[0].strip().split('\t')
try:
return [float(i) if i not in ('N', ' N_A') else None for i in raw_txt]
except ValueError:
return [str(i) for i in raw_txt]
else:
return []
@property
def monthly_found(self):
if self._monthly_db_range_50 != [] and self._monthly_wb_range_50 != [] \
and self._monthly_wind != [] \
and self._stand_press_at_elev is not None:
return True
else:
return False
@property
def location(self):
"""Return ladybug location object."""
return self._location
@property
def ashrae_climate_zone(self):
"""Return a text string indicating the ASHRAE climate zone.
Numbers in the zone denote average temperature (0 = Hottest; 8 = Coldest)
Letters in the zone denote wetness (A = Humid; B = Dry; C = Marine)
"""
return self._ashrae_climate_zone
@property
def koppen_climate_zone(self):
"""Return a text string indicating the Koppen climate zone.
The Koppen climate classification is the most widely used climate
classification system and combines average annual and monthly
temperatures, precipitation, and the seasonality of precipitation.
"""
return self._koppen_climate_zone
@property
def extreme_cold_week(self):
"""AnalysisPeriod for the coldest week within the corresponding EPW."""
return self._extreme_cold_week
@property
def extreme_hot_week(self):
"""AnalysisPeriod for the hottest week within the corresponding EPW."""
return self._extreme_hot_week
@property
def typical_winter_week(self):
"""AnalysisPeriod for a typical winter week within the corresponding EPW."""
try:
return self._typical_weeks['winter']
except KeyError:
return None
@property
def typical_spring_week(self):
"""AnalysisPeriod for a typical spring week within the corresponding EPW."""
try:
return self._typical_weeks['spring']
except KeyError:
return None
@property
def typical_summer_week(self):
"""AnalysisPeriod for a typical summer week within the corresponding EPW."""
try:
return self._typical_weeks['summer']
except KeyError:
return None
@property
def typical_autumn_week(self):
"""AnalysisPeriod for a typical autumn week within the corresponding EPW."""
try:
return self._typical_weeks['autumn']
except KeyError:
return None
@property
def other_typical_weeks(self):
"""List of AnalysisPeriods for typical weeks outside of the seasonal weeks."""
return self._typical_weeks['other']
@property
def annual_heating_design_day_996(self):
"""A design day object representing the annual 99.6% heating design day."""
if bool(self._winter_des_day_dict):
return DesignDay.from_ashrae_dict_heating(
self._winter_des_day_dict, self.location, False,
self._stand_press_at_elev)
else:
return None
@property
def annual_heating_design_day_990(self):
"""A design day object representing the annual 99.0% heating design day."""
if bool(self._winter_des_day_dict):
return DesignDay.from_ashrae_dict_heating(
self._winter_des_day_dict, self.location, True,
self._stand_press_at_elev)
else:
return None
@property
def annual_cooling_design_day_004(self):
"""A design day object representing the annual 0.4% cooling design day."""
if bool(self._summer_des_day_dict):
tau = None
month_num = int(self._summer_des_day_dict['Month'])
if self._monthly_tau_beam != [] and self._monthly_tau_diffuse != [] \
and self._monthly_tau_beam[month_num - 1] is not None and \
self._monthly_tau_diffuse[month_num - 1] is not None:
tau = (self._monthly_tau_beam[month_num - 1],
self._monthly_tau_diffuse[month_num - 1])
return DesignDay.from_ashrae_dict_cooling(
self._summer_des_day_dict, self.location, False,
self._stand_press_at_elev, tau)
else:
return None
@property
def annual_cooling_design_day_010(self):
"""A design day object representing the annual 1.0% cooling design day."""
if bool(self._summer_des_day_dict):
tau = None
month_num = int(self._summer_des_day_dict['Month'])
if self._monthly_tau_beam != [] and self._monthly_tau_diffuse != [] \
and self._monthly_tau_beam[month_num - 1] is not None and \
self._monthly_tau_diffuse[month_num - 1] is not None:
tau = (self._monthly_tau_beam[month_num - 1],
self._monthly_tau_diffuse[month_num - 1])
return DesignDay.from_ashrae_dict_cooling(
self._summer_des_day_dict, self.location, True,
self._stand_press_at_elev, tau)
else:
return None
@property
def monthly_cooling_design_days_050(self):
"""A list of 12 objects representing monthly 5.0% cooling design days."""
if not self.monthly_found or self._monthly_db_50 == [] \
or self._monthly_wb_50 == []:
return []
else:
db_conds = [DryBulbCondition(x, y) for x, y in zip(
self._monthly_db_50, self._monthly_db_range_50)]
hu_conds = [HumidityCondition(
'Wetbulb', x, self._stand_press_at_elev) for x in self._monthly_wb_50]
ws_conds = self.monthly_wind_conditions
sky_conds = self.monthly_clear_sky_conditions
return [DesignDay(
'Cooling Design Day {} 5% Condns DB=>MCWB'.format(self._months[i]),
'SummerDesignDay', self._location,
db_conds[i], hu_conds[i], ws_conds[i], sky_conds[i])
for i in xrange(12)]
@property
def monthly_cooling_design_days_100(self):
"""A list of 12 objects representing monthly 10.0% cooling design days."""
if not self.monthly_found or self._monthly_db_100 == [] \
or self._monthly_wb_100 == []:
return []
else:
db_conds = [DryBulbCondition(x, y) for x, y in zip(
self._monthly_db_100, self._monthly_db_range_50)]
hu_conds = [HumidityCondition(
'Wetbulb', x, self._stand_press_at_elev) for x in self._monthly_wb_100]
ws_conds = self.monthly_wind_conditions
sky_conds = self.monthly_clear_sky_conditions
return [DesignDay(
'Cooling Design Day {} 10% Condns DB=>MCWB'.format(self._months[i]),
'SummerDesignDay', self._location,
db_conds[i], hu_conds[i], ws_conds[i], sky_conds[i])
for i in xrange(12)]
@property
def monthly_cooling_design_days_020(self):
"""A list of 12 objects representing monthly 2.0% cooling design days."""
if not self.monthly_found or self._monthly_db_20 == [] \
or self._monthly_wb_20 == []:
return []
else:
db_conds = [DryBulbCondition(x, y) for x, y in zip(
self._monthly_db_20, self._monthly_db_range_50)]
hu_conds = [HumidityCondition(
'Wetbulb', x, self._stand_press_at_elev) for x in self._monthly_wb_20]
ws_conds = self.monthly_wind_conditions
sky_conds = self.monthly_clear_sky_conditions
return [DesignDay(
'Cooling Design Day {} 2% Condns DB=>MCWB'.format(self._months[i]),
'SummerDesignDay', self._location,
db_conds[i], hu_conds[i], ws_conds[i], sky_conds[i])
for i in xrange(12)]
@property
def monthly_cooling_design_days_004(self):
"""A list of 12 objects representing monthly 0.4% cooling design days."""
if not self.monthly_found or self._monthly_db_04 == [] \
or self._monthly_wb_04 == []:
return []
else:
db_conds = [DryBulbCondition(x, y) for x, y in zip(
self._monthly_db_04, self._monthly_db_range_50)]
hu_conds = [HumidityCondition(
'Wetbulb', x, self._stand_press_at_elev) for x in self._monthly_wb_04]
ws_conds = self.monthly_wind_conditions
sky_conds = self.monthly_clear_sky_conditions
return [DesignDay(
'Cooling Design Day {} 0.4% Condns DB=>MCWB'.format(self._months[i]),
'SummerDesignDay', self._location,
db_conds[i], hu_conds[i], ws_conds[i], sky_conds[i])
for i in xrange(12)]
@property
def monthly_db_temp_050(self):
"""A list of 12 float values for monthly 5.0% dry bulb temperature."""
return self._monthly_db_50
@property
def monthly_wb_temp_050(self):
"""A list of 12 float values for monthly 5.0% wet bulb temperature."""
return self._monthly_wb_50
@property
def monthly_db_temp_range_050(self):
"""A list of 12 values for monthly ranges of dry bulb temperatures at 5.0%."""
return self._monthly_db_range_50
@property
def monthly_wb_temp_range_050(self):
"""A list of 12 values for monthly ranges of wet bulb temperatures at 5.0%."""
return self._monthly_wb_range_50
@property
def standard_pressure_at_elev(self):
"""The standard pressure on pascals at the elevation of the location."""
return self._stand_press_at_elev
@property
def monthly_wind_conditions(self):
"""A list of 12 monthly wind conditions that are used on the design days."""
return [WindCondition(x, y) for x, y in zip(
self._monthly_wind, self.monthly_wind_dirs)]
@property
def monthly_ws_avg(self):
"""A list of 12 float values for monthly average wind speeds."""
return self._monthly_wind
@property
def monthly_wind_dirs(self):
"""A list of prevailing wind directions for each month."""
mwd = zip(*self._monthly_wind_dirs)
return [self._wind_dirs[mon.index(max(mon))] for mon in mwd]
@property
def monthly_clear_sky_conditions(self):
"""A list of 12 monthly clear sky conditions that are used on the design days."""
if self._monthly_tau_diffuse is [] or self._monthly_tau_beam is []:
return [ASHRAEClearSky(Date(i, 21)) for i in xrange(1, 13)]
md = zip(list(xrange(1, 13)), self._monthly_tau_beam, self._monthly_tau_diffuse)
return [ASHRAETau(Date(i, 21), x, y) if x is not None
else ASHRAEClearSky(Date(i, 21)) for i, x, y in md]
@property
def monthly_tau_beam(self):
"""A list of 12 float values for monthly beam optical depth.
These values can be used to generate ASHRAE Revised Clear Skies, which
are intended to determine peak solar load and sizing parmeters for
HVAC systems.
"""
return self._monthly_tau_beam
@property
def monthly_tau_diffuse(self):
"""Return a list of 12 float values for monthly diffuse optical depth.
These values can be used to generate ASHRAE Revised Clear Skies, which
are intended to determine peak solar load and sizing parmeters for
HVAC systems.
"""
return self._monthly_tau_diffuse
[docs]
def to_ddy(self, file_path, percentile=0.4):
"""Produce a DDY file with a heating + cooling design day from this STAT.
If design days following the input percentile are not found in the STAT
data, a ValueError will be raised.
Args:
file_path: Full file path for output ddy file.
percentile: A number for the percentile difference from the most
extreme conditions for the design days. Choose from 0.4 and
1.0. (Default: 0.4).
"""
# get the design day objects
if percentile == 0.4:
des_days = \
[self.annual_heating_design_day_996, self.annual_cooling_design_day_004]
elif percentile == 1:
des_days = \
[self.annual_heating_design_day_990, self.annual_cooling_design_day_010]
else:
raise ValueError('STAT files do not contain design days for '
'{}% percentile.'.format(percentile))
if None in des_days:
raise ValueError('The STAT file do not contain design days for '
'{}% percentile.'.format(percentile))
# write the DDY
if not file_path.lower().endswith('.ddy'):
file_path += '.ddy'
ddy = DDY(self.location, des_days)
ddy.write(file_path)
return file_path
[docs]
def to_ddy_monthly_cooling(
self, file_path, annual_percentile=0.4, monthly_percentile=5):
"""Produce a DDY file with 1 heating and 12 cooling design days.
The heating design day represents a cold and completely dark day whereas
the cooling design days represent the warmest conditions in each month.
If design days following the input percentile are not found in the STAT
data, a ValueError will be raised.
Args:
file_path: Full file path for output ddy file.
annual_percentile: A number for the percentile difference from the most
extreme conditions for the design days. Choose from 0.4 and
1.0. (Default: 0.4).
monthly_percentile: A number between for the percentile difference from the
most extreme conditions within each month to be used for the cooling
design days. Choose from 10, 5, 2 or 0.04. (Default: 5).
"""
# get the heating design day object
if annual_percentile == 0.4:
heating = self.annual_heating_design_day_996
elif annual_percentile == 1:
heating = self.annual_heating_design_day_990
else:
raise ValueError('STAT files do not contain heating design days for '
'{}% percentile.'.format(annual_percentile))
# get the cooling design day objects
if monthly_percentile == 5:
cooling = self.monthly_cooling_design_days_050
elif monthly_percentile == 10:
cooling = self.monthly_cooling_design_days_100
elif monthly_percentile == 2:
cooling = self.monthly_cooling_design_days_020
elif monthly_percentile == 0.4:
cooling = self.monthly_cooling_design_days_004
else:
raise ValueError('STAT files do not contain monthly cooling design days for '
'{}% percentile.'.format(monthly_percentile))
ann_eq = round(monthly_percentile / 12, 1)
ann_eq = int(ann_eq) if int(ann_eq) == ann_eq else ann_eq
for cd in cooling:
cd.name = '{} ({}% Condns DB=>MWB)'.format(cd.name, ann_eq)
# write the DDY
des_days = [heating] + cooling
if None in des_days:
msg = 'heating design days for {}'.format(annual_percentile) if heating \
is None else 'cooling design days for {}'.format(monthly_percentile)
raise ValueError('The STAT file do not contain {}% percentile.'.format(msg))
if not file_path.lower().endswith('.ddy'):
file_path += '.ddy'
ddy = DDY(self.location, des_days)
ddy.write(file_path)
return file_path
[docs]
def to_dict(self):
"""Convert the stat object to a dictionary."""
def dictify_dict(base_dict):
new_dict = {}
for key, val in base_dict.items():
if isinstance(val, list):
new_dict[key] = [v.to_dict() for v in val]
else:
new_dict[key] = val.to_dict()
return new_dict
return {
'location': self.location.to_dict(),
'ashrae_climate_zone': self.ashrae_climate_zone,
'koppen_climate_zone': self.koppen_climate_zone,
'extreme_cold_week': self.extreme_cold_week.to_dict()
if self.extreme_cold_week else None,
'extreme_hot_week': self.extreme_hot_week.to_dict()
if self.extreme_cold_week else None,
'typical_weeks': dictify_dict(self._typical_weeks),
'heating_dict': self._winter_des_day_dict,
'cooling_dict': self._summer_des_day_dict,
"monthly_db_50": self._monthly_db_50,
"monthly_wb_50": self._monthly_wb_50,
"monthly_db_range_50": self._monthly_db_range_50,
"monthly_wb_range_50": self._monthly_wb_range_50,
"monthly_db_100": self._monthly_db_100,
"monthly_wb_100": self._monthly_wb_100,
"monthly_db_20": self._monthly_db_20,
"monthly_wb_20": self._monthly_wb_20,
"monthly_db_04": self._monthly_db_04,
"monthly_wb_04": self._monthly_wb_04,
"monthly_wind": self._monthly_wind,
"monthly_wind_dirs": self._monthly_wind_dirs,
"standard_pressure_at_elev": self.standard_pressure_at_elev,
"monthly_tau_beam": self.monthly_tau_beam,
"monthly_tau_diffuse": self.monthly_tau_diffuse,
"type": 'STAT'
}
[docs]
def ToString(self):
"""Overwrite .NET ToString."""
return self.__repr__()
def __repr__(self):
"""stat file representation."""
return "STAT [%s]" % self.location.city