lib/openstudio-standards/weather/information.rb in openstudio-standards-0.5.0 vs lib/openstudio-standards/weather/information.rb in openstudio-standards-0.6.0.rc1

- old
+ new

@@ -1,13 +1,122 @@ -# Methods to create Schedule objects module OpenstudioStandards + # The Weather module provides methods to set and get information for model weather files module Weather # @!group Information - # get the ASHRAE climate zone number + # A method to return an array of .epw files names mapped to each climate zone. # + # @param epw_file [String] optional epw_file name for NECB methods + # @return [Hash] a hash of climate zone weather file pairs + def self.climate_zone_weather_file_map(epw_file = '') + # Define the weather file for each climate zone + climate_zone_weather_file_map = { + 'ASHRAE 169-2006-0A' => 'VNM_SVN_Ho.Chi.Minh-Tan.Son.Nhat.Intl.AP.489000_TMYx.epw', + 'ASHRAE 169-2006-0B' => 'ARE_DU_Dubai.Intl.AP.411940_TMYx.epw', + 'ASHRAE 169-2006-1A' => 'USA_FL_Miami.Intl.AP.722020_TMY3.epw', + 'ASHRAE 169-2006-1B' => 'SAU_RI_Riyadh.AB.404380_TMYx.epw', + 'ASHRAE 169-2006-2A' => 'USA_TX_Houston-Bush.Intercontinental.AP.722430_TMY3.epw', + 'ASHRAE 169-2006-2B' => 'USA_AZ_Phoenix-Sky.Harbor.Intl.AP.722780_TMY3.epw', + 'ASHRAE 169-2006-3A' => 'USA_TN_Memphis.Intl.AP.723340_TMY3.epw', + 'ASHRAE 169-2006-3B' => 'USA_TX_El.Paso.Intl.AP.722700_TMY3.epw', + 'ASHRAE 169-2006-3C' => 'USA_CA_San.Francisco.Intl.AP.724940_TMY3.epw', + 'ASHRAE 169-2006-4A' => 'USA_MD_Baltimore-Washington.Intl.AP.724060_TMY3.epw', + 'ASHRAE 169-2006-4B' => 'USA_NM_Albuquerque.Intl.AP.723650_TMY3.epw', + 'ASHRAE 169-2006-4C' => 'USA_OR_Salem-McNary.Field.726940_TMY3.epw', + 'ASHRAE 169-2006-5A' => 'USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw', + 'ASHRAE 169-2006-5B' => 'USA_ID_Boise.Air.Terminal.726810_TMY3.epw', + 'ASHRAE 169-2006-5C' => 'CAN_BC_Vancouver.Intl.AP.718920_CWEC2020.epw', + 'ASHRAE 169-2006-6A' => 'USA_VT_Burlington.Intl.AP.726170_TMY3.epw', + 'ASHRAE 169-2006-6B' => 'USA_MT_Helena.Rgnl.AP.727720_TMY3.epw', + 'ASHRAE 169-2006-7A' => 'USA_MN_Duluth.Intl.AP.727450_TMY3.epw', + 'ASHRAE 169-2006-7B' => 'USA_MN_Duluth.Intl.AP.727450_TMY3.epw', + 'ASHRAE 169-2006-8A' => 'USA_AK_Fairbanks.Intl.AP.702610_TMY3.epw', + 'ASHRAE 169-2006-8B' => 'USA_AK_Fairbanks.Intl.AP.702610_TMY3.epw', + 'ASHRAE 169-2013-0A' => 'VNM_SVN_Ho.Chi.Minh-Tan.Son.Nhat.Intl.AP.489000_TMYx.epw', + 'ASHRAE 169-2013-0B' => 'ARE_DU_Dubai.Intl.AP.411940_TMYx.epw', + 'ASHRAE 169-2013-1A' => 'USA_HI_Honolulu.Intl.AP.911820_TMY3.epw', + 'ASHRAE 169-2013-1B' => 'IND_DL_New.Delhi-Safdarjung.AP.421820_TMYx.epw', + 'ASHRAE 169-2013-2A' => 'USA_FL_Tampa-MacDill.AFB.747880_TMY3.epw', + 'ASHRAE 169-2013-2B' => 'USA_AZ_Tucson-Davis-Monthan.AFB.722745_TMY3.epw', + 'ASHRAE 169-2013-3A' => 'USA_GA_Atlanta-Hartsfield.Jackson.Intl.AP.722190_TMY3.epw', + 'ASHRAE 169-2013-3B' => 'USA_TX_El.Paso.Intl.AP.722700_TMY3.epw', + 'ASHRAE 169-2013-3C' => 'USA_CA_San.Deigo-Brown.Field.Muni.AP.722904_TMY3.epw', + 'ASHRAE 169-2013-4A' => 'USA_NY_New.York-John.F.Kennedy.Intl.AP.744860_TMY3.epw', + 'ASHRAE 169-2013-4B' => 'USA_NM_Albuquerque.Intl.Sunport.723650_TMY3.epw', + 'ASHRAE 169-2013-4C' => 'USA_WA_Seattle-Tacoma.Intl.AP.727930_TMY3.epw', + 'ASHRAE 169-2013-5A' => 'USA_NY_Buffalo.Niagara.Intl.AP.725280_TMY3.epw', + 'ASHRAE 169-2013-5B' => 'USA_CO_Denver-Aurora-Buckley.AFB.724695_TMY3.epw', + 'ASHRAE 169-2013-5C' => 'USA_WA_Port.Angeles-William.R.Fairchild.Intl.AP.727885_TMY3.epw', + 'ASHRAE 169-2013-6A' => 'USA_MN_Rochester.Intl.AP.726440_TMY3.epw', + 'ASHRAE 169-2013-6B' => 'USA_MT_Great.Falls.Intl.AP.727750_TMY3.epw', + 'ASHRAE 169-2013-7A' => 'USA_MN_International.Falls.Intl.AP.727470_TMY3.epw', + 'ASHRAE 169-2013-7B' => 'USA_MN_International.Falls.Intl.AP.727470_TMY3.epw', + 'ASHRAE 169-2013-8A' => 'USA_AK_Fairbanks.Intl.AP.702610_TMY3.epw', + 'ASHRAE 169-2013-8B' => 'USA_AK_Fairbanks.Intl.AP.702610_TMY3.epw', + # For measure input + 'NECB HDD Method' => epw_file.to_s, + # For testing + 'NECB-CNEB-5' => epw_file.to_s, + 'NECB-CNEB-6' => epw_file.to_s, + 'NECB-CNEB-7a' => epw_file.to_s, + 'NECB-CNEB-7b' => epw_file.to_s, + 'NECB-CNEB-8' => epw_file.to_s, + # For DEER + 'CEC T24-CEC1' => 'ARCATA_725945_CZ2010.epw', + 'CEC T24-CEC2' => 'SANTA-ROSA_724957_CZ2010.epw', + 'CEC T24-CEC3' => 'OAKLAND_724930_CZ2010.epw', + 'CEC T24-CEC4' => 'SAN-JOSE-REID_724946_CZ2010.epw', + 'CEC T24-CEC5' => 'SANTA-MARIA_723940_CZ2010.epw', + 'CEC T24-CEC6' => 'TORRANCE_722955_CZ2010.epw', + 'CEC T24-CEC7' => 'SAN-DIEGO-LINDBERGH_722900_CZ2010.epw', + 'CEC T24-CEC8' => 'FULLERTON_722976_CZ2010.epw', + 'CEC T24-CEC9' => 'BURBANK-GLENDALE_722880_CZ2010.epw', + 'CEC T24-CEC10' => 'RIVERSIDE_722869_CZ2010.epw', + 'CEC T24-CEC11' => 'RED-BLUFF_725910_CZ2010.epw', + 'CEC T24-CEC12' => 'SACRAMENTO-EXECUTIVE_724830_CZ2010.epw', + 'CEC T24-CEC13' => 'FRESNO_723890_CZ2010.epw', + 'CEC T24-CEC14' => 'PALMDALE_723820_CZ2010.epw', + 'CEC T24-CEC15' => 'PALM-SPRINGS-INTL_722868_CZ2010.epw', + 'CEC T24-CEC16' => 'BLUE-CANYON_725845_CZ2010.epw' + } + return climate_zone_weather_file_map + end + + # Converts the climate zone in the model into the format used by the openstudio-standards lookup tables. + # For example, + # institution: ASHRAE, value: 6A becomes: ASHRAE 169-2013-6A. + # institution: CEC, value: 3 becomes: CEC T24-CEC3. + # # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [String] the string representation of the climate zone, + # empty string if no climate zone is present in the model. + def self.model_get_climate_zone(model) + climate_zone = '' + model.getClimateZones.climateZones.each do |cz| + if cz.institution == 'ASHRAE' + next if cz.value == '' # Skip blank ASHRAE climate zones put in by OpenStudio Application + + if cz.value == '7' || cz.value == '8' + climate_zone = "ASHRAE 169-2013-#{cz.value}A" + else + climate_zone = "ASHRAE 169-2013-#{cz.value}" + end + elsif cz.institution == 'CEC' + # Skip blank ASHRAE climate zones put in by OpenStudio Application + if cz.value == '' + next + end + + climate_zone = "CEC T24-CEC#{cz.value}" + end + end + return climate_zone + end + + # Get the ASHRAE climate zone number. + # + # @param model [OpenStudio::Model::Model] OpenStudio model object # @return [Integer] ASHRAE climate zone number, 0-8 def self.model_get_ashrae_climate_zone_number(model) # get ashrae climate zone from model ashrae_climate_zone = '' model.getClimateZones.climateZones.each do |climate_zone| @@ -15,21 +124,368 @@ ashrae_climate_zone = climate_zone.value end end if ashrae_climate_zone == '' - OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Weather', 'Please assign an ASHRAE Climate Zone to your model.') + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Weather.information', 'Please assign an ASHRAE Climate Zone to your model.') return false else cz_number = ashrae_climate_zone.split(//).first.to_i end # expected climate zone number should be 0 through 8 if ![0, 1, 2, 3, 4, 5, 6, 7, 8].include? cz_number - OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Weather', 'ASHRAE climate zone number is not within expected range of 1 to 8.') + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Weather.information', 'ASHRAE climate zone number is not within expected range of 1 to 8.') return false end return cz_number end + + # Get the full path to the weather file that is specified in the model. + # + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [OpenStudio::OptionalPath] path to weather file + def self.model_get_full_weather_file_path(model) + full_epw_path = OpenStudio::OptionalPath.new + + if model.weatherFile.is_initialized + epw_path = model.weatherFile.get.path + if epw_path.is_initialized + if File.exist?(epw_path.get.to_s) + full_epw_path = OpenStudio::OptionalPath.new(epw_path.get) + else + # If this is an always-run Measure, need to check a different path + alt_weath_path = File.expand_path(File.join(Dir.pwd, '../../resources')) + alt_epw_path = File.expand_path(File.join(alt_weath_path, epw_path.get.to_s)) + if File.exist?(alt_epw_path) + full_epw_path = OpenStudio::OptionalPath.new(OpenStudio::Path.new(alt_epw_path)) + else + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Model has been assigned a weather file, but the file is not in the specified location of '#{epw_path.get}'.") + end + end + else + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', 'Model has a weather file assigned, but the weather file path has been deleted.') + end + else + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', 'Model has not been assigned a weather file.') + end + + return full_epw_path + end + + # Get absolute path of a weather file included within openstudio-standards. + # + # @param weather_file_name [String] Name of a weather file included within openstudio-standards, including file extension .epw + # @return [String] Weather file path + def self.get_standards_weather_file_path(weather_file_name) + # Define where the weather files lives + weather_dir = nil + if __dir__[0] == ':' # Running from OpenStudio CLI + # load weather file from embedded files + epw_string = load_resource_relative("../../../data/weather/#{weather_file_name}") + ddy_string = load_resource_relative("../../../data/weather/#{weather_file_name.gsub('.epw', '.ddy')}") + stat_string = load_resource_relative("../../../data/weather/#{weather_file_name.gsub('.epw', '.stat')}") + + # extract to local weather dir + weather_dir = File.expand_path(File.join(Dir.pwd, 'extracted_files/weather/')) + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Weather.information', "Extracting weather files from OpenStudio CLI to #{weather_dir}") + FileUtils.mkdir_p(weather_dir) + + path_length = "#{weather_dir}/#{weather_file_name}".length + if path_length > 260 + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Weather.information', "Weather file path length #{path_length} is >260 characters and may cause issues in Windows environments.") + end + File.open("#{weather_dir}/#{weather_file_name}", 'wb').each do |f| + f << epw_string + f.flush + end + File.open("#{weather_dir}/#{weather_file_name.gsub('.epw', '.ddy')}", 'wb').each do |f| + f << ddy_string + f.flush + end + File.open("#{weather_dir}/#{weather_file_name.gsub('.epw', '.stat')}", 'wb').each do |f| + f << stat_string + f.flush + end + else + # loaded gem from system path + top_dir = File.expand_path('../../..', File.dirname(__FILE__)) + weather_dir = File.expand_path("#{top_dir}/data/weather") + end + + # Add Weather File + unless (Pathname.new weather_dir).absolute? + weather_dir = File.expand_path(File.join(File.dirname(__FILE__), weather_dir)) + end + + weather_file_path = File.join(weather_dir, weather_file_name) + + return weather_file_path + end + + # Get absolute path of a weather file included within openstudio-standards that is representative of the climate zone. + # + # @param climate_zone [String] full climate zone string, e.g. 'ASHRAE 169-2013-4A' + # @return [String] absolute file path + def self.climate_zone_representative_weather_file_path(climate_zone) + climate_zone_weather_file_map = OpenstudioStandards::Weather.climate_zone_weather_file_map + weather_file_name = climate_zone_weather_file_map[climate_zone] + if weather_file_name.nil? + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Weather.information', "Could not determine weather for climate zone: #{climate_zone}") + return false + end + + standards_weather_file_path = OpenstudioStandards::Weather.get_standards_weather_file_path(weather_file_name) + return standards_weather_file_path + end + + # Get a list of regular expressions matching the design day categories. + # + # For looking up design day objects by type + # @param category [String] The design day category, e.g. 'All Heating', 'Annual Cooling'. + # @return [Array<Regexp>] list of regular expressions matching design day names to import. + def self.ddy_regex_lookup(category) + ddy_regex_map = { + 'All Heating' => /Htg/, + 'Heating DB' => /Htg.* DB/, + 'Heating 99.6%' => /Htg.*99.6%/, + 'Heating 99%' => /Htg.*99%/, + 'Heating Wind' => /Htg Wind/, + 'All Cooling' => / (0?\.4|1|2|5)\.?0?%/, + 'Annual Cooling' => /Ann Clg/, + 'All Cooling DB' => /DB=>MC?WB/, + 'All Cooling WB' => /WB=>MC?DB/, + 'All Cooling DP' => /Clg.* DP/, + 'All Cooling Enth' => /Clg.* Enth/, + 'Annual Cooling DB' => /Clg.* DB/, + 'Annual Cooling WB' => /Clg.* WB/, + 'Annual Cooling DP' => /Clg.* DP/, + 'Annual Cooling Enth' => /Clg.* Enth/, + 'Cooling 0.4%' => /.4%/, + 'Cooling 2%' => /2%/, + 'Cooling 5%' => /5%/, + 'Annual Cooling 0.4%' => /Ann.*.4%/, + 'Annual Cooling 2%' => /Ann.*2%/, + 'Monthly Cooling' => /Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/, + 'Monthly 0.4%' => /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec).*0?.4/, + 'Monthly 2%' => /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec).*2%/, + 'Monthly 5%' => /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec).*5%/, + 'Monthly DB' => /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec).*DB=>MC?WB/, + 'Monthly WB' => /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec).*WB=>MC?DB/ + } + valid = ddy_regex_map.keys + unless valid.include?(category) + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Weather.information', "Could not find a matching ddy regular expression for entered category #{category}. Valid categories are #{valid}.") + return false + end + + return [ddy_regex_map[category]] + end + + # Returns the winter design outdoor air dry bulb temperatures in the model. + # + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [Array<Double>] an array of outdoor design dry bulb temperatures in degrees Celsius + def self.model_get_heating_design_outdoor_temperatures(model) + heating_design_outdoor_temps = [] + model.getDesignDays.each do |dd| + next unless dd.dayType == 'WinterDesignDay' + + heating_design_outdoor_temps << dd.maximumDryBulbTemperature + end + + return heating_design_outdoor_temps + end + + # Returns the ASHRAE climate zone based on degree days + # + # @param hdd18 [Double] Cooling Degree Days, 18C base + # @param cdd10 [Double] Cooling Degree Days, 10C base + # @return [String] full climate zone string, e.g. 'ASHRAE 169-2013-4A' + # @todo support Humid (A) / Dry (B) distinctions based on precipitation per Section A3 of ASHRAE 169 + def self.get_climate_zone_from_degree_days(hdd18, cdd10) + if cdd10 > 6000 + # Extremely Hot Humid (0A), Dry (0B) + return 'ASHRAE 169-2013-0A' + + elsif (cdd10 > 5000) && (cdd10 <= 6000) + # Very Hot Humid (1A), Dry (1B) + return 'ASHRAE 169-2013-1A' + + elsif (cdd10 > 3500) && (cdd10 <= 5000) + # Hot Humid (2A), Dry (2B) + return 'ASHRAE 169-2013-2A' + + elsif ((cdd10 > 2500) && (cdd10 < 3500)) && (hdd18 <= 2000) + # Warm Humid (3A), Dry (3B) + return 'ASHRAE 169-2013-3A' # and 'ASHRAE 169-2013-3B' + + elsif (cdd10 <= 2500) && (hdd18 <= 2000) + # Warm Marine (3C) + return 'ASHRAE 169-2013-3C' + + elsif ((cdd10 > 1500) && (cdd10 < 3500)) && ((hdd18 > 2000) && (hdd18 <= 3000)) + # Mixed Humid (4A), Dry (4B) + return 'ASHRAE 169-2013-4A' # and 'ASHRAE 169-2013-4B' + + elsif (cdd10 <= 1500) && ((hdd18 > 2000) && (hdd18 <= 3000)) + # Mixed Marine + return 'ASHRAE 169-2013-4C' + + elsif ((cdd10 > 1000) && (cdd10 <= 3500)) && ((hdd18 > 3000) && (hdd18 <= 4000)) + # Cool Humid (5A), Dry (5B) + return 'ASHRAE 169-2013-5A' # and 'ASHRAE 169-2013-5B' + + elsif (cdd10 <= 1000) && ((hdd18 > 3000) && (hdd18 <= 4000)) + # Cool Marine (5C) + return 'ASHRAE 169-2013-5C' + + elsif (hdd18 > 4000) && (hdd18 <= 5000) + # Cold Humid (6A), Dry (6B) + return 'ASHRAE 169-2013-6A' # and 'ASHRAE 169-2013-6B' + + elsif (hdd18 > 5000) && (hdd18 <= 7000) + # Very Cold (7) + return 'ASHRAE 169-2013-7A' + + elsif hdd18 > 7000 + # Subarctic/Arctic (8) + return 'ASHRAE 169-2013-8A' + + else + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Weather.information', "Could not determine climate zone from #{hdd18} heating degree days base 18°C and #{cdd10} cooling degree days base 10°C.") + return '' + end + end + + # Calculate average global irradiance for the design day + # Calculated from ASHRAE HOF 2017 Chp 14 Clear-Sky Solar Radiation + # + # @param design_day [OpenStudio::Model::DesignDay] OpenStudio DesignDay object + # @return [Double] average global irradiance over the full day (24 hours) (W/m^2) + def self.design_day_average_global_irradiance(design_day) + # get site longitude and time zone longitude + weather_file = design_day.model.weatherFile.get + site_longitude_degrees = weather_file.longitude + site_latitude_degrees = weather_file.latitude + site_latitude_radians = site_latitude_degrees * (Math::PI / 180.0) + time_zone_longitude_degrees = 15.0 * weather_file.timeZone + + # day of year + day_of_year = Date.new(y = 2009, m = design_day.month, d = design_day.dayOfMonth).yday + + # equation of time + gamma_degrees = 360 * (day_of_year - 1) / 365.0 + gamma_radians = gamma_degrees * (Math::PI / 180.0) + equation_of_time_minutes = 2.2918 * (0.0075 + (0.1868 * Math.cos(gamma_radians)) - (3.2077 * Math.sin(gamma_radians)) - (1.4615 * Math.cos(2 * gamma_radians)) - (4.089 * Math.sin(2 * gamma_radians))) + + # extraterrestrial normal irradiance, W/m^2 + extraterrestrial_normal_irradiance_degrees = 360 * (day_of_year - 3) / 365.0 + extraterrestrial_normal_irradiance_radians = extraterrestrial_normal_irradiance_degrees * (Math::PI / 180.0) + extraterrestrial_normal_irradiance = 1367.0 * (1.0 + 0.033 * Math.cos(extraterrestrial_normal_irradiance_radians)) + + # declination + day_angle_degrees = 360.0 * (day_of_year + 284) / 365.0 + day_angle_radians = day_angle_degrees * (Math::PI / 180.0) + declination_degrees = 23.45 * Math.sin(day_angle_radians) + declination_radians = declination_degrees * (Math::PI / 180.0) + + # air mass exponents from optical depth + tau_b = design_day.ashraeClearSkyOpticalDepthForBeamIrradiance + tau_d = design_day.ashraeClearSkyOpticalDepthForDiffuseIrradiance + ab = 1.454 - (0.406 * tau_b) - (0.268 * tau_d) + (0.021 * tau_b * tau_d) + ad = 0.507 + (0.205 * tau_b) - (0.080 * tau_d) - (0.190 * tau_b * tau_d) + + global_irradiance_array = [] + (0..23).to_a.each do |local_standard_time_hour| + # apparent solar time + apparent_solar_time = local_standard_time_hour + (equation_of_time_minutes / 60.0) + (site_longitude_degrees - time_zone_longitude_degrees) / 15.0 + + # hour angle + hour_angle_degrees = 15.0 * (apparent_solar_time - 12.0) + hour_angle_radians = hour_angle_degrees * (Math::PI / 180.0) + + # solar altitude + solar_altitude_radians = Math.asin(Math.cos(site_latitude_radians) * Math.cos(declination_radians) * Math.cos(hour_angle_radians) + Math.sin(site_latitude_radians) * Math.sin(declination_radians)) + solar_altitude_degrees = solar_altitude_radians * (180.0 / Math::PI) + + # equation 16 air mass + # equation 17 and 18 irradiance calculation + if solar_altitude_degrees > 0 + air_mass = 1 / (Math.sin(solar_altitude_radians) + 0.50572 * (6.07995 + solar_altitude_degrees)**-1.6364) + beam_normal_irradiance = extraterrestrial_normal_irradiance * Math.exp(-tau_b * air_mass**ab) + diffuse_horizontal_irradiance = extraterrestrial_normal_irradiance * Math.exp(-tau_d * air_mass**ad) + else + air_mass = nil + beam_normal_irradiance = 0.0 + diffuse_horizontal_irradiance = 0.0 + end + global_irradiance = beam_normal_irradiance + diffuse_horizontal_irradiance + global_irradiance_array << global_irradiance + + # puts "For local_standard_time_hour #{local_standard_time_hour}, apparent_solar_time #{apparent_solar_time}, hour_angle_degrees #{hour_angle_degrees}, solar_altitude_degrees #{solar_altitude_degrees}, air_mass #{air_mass}, beam_normal_irradiance #{beam_normal_irradiance}, diffuse_horizontal_irradiance #{diffuse_horizontal_irradiance}" + end + + average_daily_global_irradiance = global_irradiance_array.sum / 24.0 + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Weather.information', "For design day #{design_day.name}, day_of_year #{day_of_year}, time zone #{weather_file.timeZone}, site_longitude_degrees #{site_longitude_degrees}, time_zone_longitude_degrees #{time_zone_longitude_degrees}, site_latitude_degrees #{site_latitude_degrees}, equation_of_time_minutes #{equation_of_time_minutes}, declination_degrees #{declination_degrees}, extraterrestrial_normal_irradiance #{extraterrestrial_normal_irradiance}, average_daily_global_irradiance #{average_daily_global_irradiance} W/m^2.") + + return average_daily_global_irradiance + end + + # Calculate dehumidification degree days from an epw_file + # + # @param epw_file [OpenStudio::EpwFile] OpenStudio EpwFile object + # @param base_humidity_ratio [Double] base humidity ratio, default is 0.010 + # @return [Double] dehumdification degree days + def self.epw_file_get_dehumidification_degree_days(epw_file, base_humidity_ratio: 0.010) + db_temps_c = epw_file.getTimeSeries('Dry Bulb Temperature').get.values + db_temps_k = db_temps_c.map { |v| v + 273.15 } + rh_values = epw_file.getTimeSeries('Relative Humidity').get.values + atm_p_values = epw_file.getTimeSeries('Atmospheric Station Pressure').get.values + + # coefficients for the calculation of pws (Reference: ASHRAE Handbook - Fundamentals > CHAPTER 1. PSYCHROMETRICS) + c1 = -5.6745359E+03 + c2 = 6.3925247E+00 + c3 = -9.6778430E-03 + c4 = 6.2215701E-07 + c5 = 2.0747825E-09 + c6 = -9.4840240E-13 + c7 = 4.1635019E+00 + c8 = -5.8002206E+03 + c9 = 1.3914993E+00 + c10 = -4.8640239E-02 + c11 = 4.1764768E-05 + c12 = -1.4452093E-08 + c13 = 6.5459673E+00 + + # calculate saturation pressure of water vapor (Pa) + sp_values = [] + db_temps_k.each do |t| + if t <= 273.15 + sp = (c1 / t) + c2 + c3 * t + c4 * t**2 + c5 * t**3 + c6 * t**4 + c7 * Math.log(t, Math.exp(1)) + + else + sp = (c8 / t) + c9 + c10 * t + c11 * t**2 + c12 * t**3 + c13 * Math.log(t, Math.exp(1)) + end + sp_values << Math.exp(1)**sp + end + + # calculate partial pressure of water vapor (Pa) + pp_values = sp_values.zip(rh_values).map { |sp, rh| sp * rh / 100.0 } + + # calculate total pressure (Pa) + tp_values = pp_values.zip(atm_p_values).map { |pp, atm| pp + atm } + + # calculate humidity ratio + hr_values = pp_values.zip(tp_values).map { |pp, tp| (0.621945 * pp) / (tp - pp) } + + # calculate dehumidification degree days based on humidity ratio values above the base + hr_values_above_base = hr_values.map { |hr| hr > base_humidity_ratio ? hr : 0.0 } + dehumidification_degree_days = hr_values_above_base.sum / 24.0 + + return dehumidification_degree_days + end + + # @!endgroup Information end end