lib/openstudio-standards/standards/Standards.Model.rb in openstudio-standards-0.4.0 vs lib/openstudio-standards/standards/Standards.Model.rb in openstudio-standards-0.5.0.rc1

- old
+ new

@@ -1,6 +1,7 @@ require 'csv' +require 'date' class Standard attr_accessor :space_multiplier_map attr_accessor :standards_data @@ -16,44 +17,41 @@ # Creates a Performance Rating Method (aka Appendix G aka LEED) baseline building model # Method used for 90.1-2016 and onward # # @note Per 90.1, the Performance Rating Method "does NOT offer an alternative compliance path for minimum standard compliance." # This means you can't use this method for code compliance to get a permit. - # @param user_model [OpenStudio::model::Model] User specified OpenStudio model + # @param model [OpenStudio::Model::Model] User specified OpenStudio model # @param climate_zone [String] the climate zone # @param hvac_building_type [String] the building type for baseline HVAC system determination (90.1-2016 and onward) # @param wwr_building_type [String] the building type for baseline WWR determination (90.1-2016 and onward) # @param swh_building_type [String] the building type for baseline SWH determination (90.1-2016 and onward) # @param output_dir [String] the directory where the PRM generations will be performed - # @param run_all_orients [Boolean] indicate weather a baseline model should be created for all 4 orientations: same as user model, +90 deg, +180 deg, +270 deg # @param debug [Boolean] If true, will report out more detailed debugging output - # @return [Bool] returns true if successful, false if not - - # Method used for 90.1-2016 and onward + # @return [Boolean] returns true if successful, false if not def model_create_prm_stable_baseline_building(model, climate_zone, hvac_building_type, wwr_building_type, swh_building_type, output_dir = Dir.pwd, unmet_load_hours_check = true, debug = false) - model_create_prm_any_baseline_building(model, '', climate_zone, hvac_building_type, wwr_building_type, swh_building_type, true, false, output_dir, true, unmet_load_hours_check, debug) + model_create_prm_any_baseline_building(model, '', climate_zone, hvac_building_type, wwr_building_type, swh_building_type, true, true, false, output_dir, true, unmet_load_hours_check, debug) end # Creates a Performance Rating Method (aka Appendix G aka LEED) baseline building model # Method used for 90.1-2013 and prior - # @param user_model [OpenStudio::model::Model] User specified OpenStudio model + # @param model [OpenStudio::Model::Model] User specified OpenStudio model # @param building_type [String] the building type # @param climate_zone [String] the climate zone # @param custom [String] the custom logic that will be applied during baseline creation. Valid choices are 'Xcel Energy CO EDA' or '90.1-2007 with addenda dn'. # If nothing is specified, no custom logic will be applied; the process will follow the template logic explicitly. # @param sizing_run_dir [String] the directory where the sizing runs will be performed - # @param debug [Boolean] If true, will report out more detailed debugging output + # @param debug [Boolean] if true, will report out more detailed debugging output def model_create_prm_baseline_building(model, building_type, climate_zone, custom = nil, sizing_run_dir = Dir.pwd, debug = false) - model_create_prm_any_baseline_building(model, building_type, climate_zone, 'All others', 'All others', 'All others', false, custom, sizing_run_dir, false, false, debug) + model_create_prm_any_baseline_building(model, building_type, climate_zone, 'All others', 'All others', 'All others', false, false, custom, sizing_run_dir, false, false, debug) end - # Creates a Performance Rating Method (aka Appendix G aka LEED) baseline building model - # based on the inputs currently in the model. + # Creates a Performance Rating Method (aka 90.1-Appendix G) baseline building model + # based on the inputs currently in the user model. # # @note Per 90.1, the Performance Rating Method "does NOT offer an alternative compliance path for minimum standard compliance." # This means you can't use this method for code compliance to get a permit. - # @param user_model [OpenStudio::model::Model] User specified OpenStudio model + # @param user_model [OpenStudio::Model::Model] User specified OpenStudio model # @param building_type [String] the building type # @param climate_zone [String] the climate zone # @param hvac_building_type [String] the building type for baseline HVAC system determination (90.1-2016 and onward) # @param wwr_building_type [String] the building type for baseline WWR determination (90.1-2016 and onward) # @param swh_building_type [String] the building type for baseline SWH determination (90.1-2016 and onward) @@ -61,31 +59,73 @@ # @param custom [String] the custom logic that will be applied during baseline creation. Valid choices are 'Xcel Energy CO EDA' or '90.1-2007 with addenda dn'. # If nothing is specified, no custom logic will be applied; the process will follow the template logic explicitly. # @param sizing_run_dir [String] the directory where the sizing runs will be performed # @param run_all_orients [Boolean] indicate weather a baseline model should be created for all 4 orientations: same as user model, +90 deg, +180 deg, +270 deg # @param debug [Boolean] If true, will report out more detailed debugging output - # @return [Bool] returns true if successful, false if not - def model_create_prm_any_baseline_building(user_model, building_type, climate_zone, hvac_building_type = 'All others', wwr_building_type = 'All others', swh_building_type = 'All others', model_deep_copy = false, custom = nil, sizing_run_dir = Dir.pwd, run_all_orients = false, unmet_load_hours_check = true, debug = false) + # @return [Boolean] returns true if successful, false if not + def model_create_prm_any_baseline_building(user_model, building_type, climate_zone, hvac_building_type = 'All others', wwr_building_type = 'All others', swh_building_type = 'All others', model_deep_copy = false, create_proposed_model = false, custom = nil, sizing_run_dir = Dir.pwd, run_all_orients = false, unmet_load_hours_check = true, debug = false) + if create_proposed_model + # Perform a user model design day run only to make sure + # that the user model is valid, i.e. can run without major + # errors + if !model_run_sizing_run(user_model, "#{sizing_run_dir}/USER-SR") + OpenStudio.logFree(OpenStudio::Warn, 'prm.log', + "The user model is not a valid OpenStudio model. Baseline and proposed model(s) won't be created.") + prm_raise(false, + sizing_run_dir, + "The user model is not a valid OpenStudio model. Baseline and proposed model(s) won't be created.") + end + + # Check if proposed HVAC system is autosized + if model_is_hvac_autosized(user_model) + OpenStudio.logFree(OpenStudio::Warn, 'prm.log', + "The user model's HVAC system is partly autosized.") + end + + # Generate proposed model from the user-provided model + proposed_model = model_create_prm_proposed_building(user_model) + end + # Check proposed model unmet load hours if unmet_load_hours_check - # Run proposed model; need annual simulation to get unmet load hours - if model_run_simulation_and_log_errors(user_model, run_dir = "#{sizing_run_dir}/PROP") - umlh = model_get_unmet_load_hours(user_model) + # Run user model; need annual simulation to get unmet load hours + if model_run_simulation_and_log_errors(proposed_model, run_dir = "#{sizing_run_dir}/PROP") + umlh = model_get_unmet_load_hours(proposed_model) if umlh > 300 - OpenStudio.logFree(OpenStudio::Error, 'prm.log', "Proposed model unmet load hours exceed 300. Baseline model(s) won't be created.") - raise "Proposed model unmet load hours exceed 300. Baseline model(s) won't be created." + OpenStudio.logFree(OpenStudio::Warn, 'prm.log', + "Proposed model unmet load hours (#{umlh}) exceed 300. Baseline model(s) won't be created.") + prm_raise(false, + sizing_run_dir, + "Proposed model unmet load hours exceed 300. Baseline model(s) won't be created.") end else - OpenStudio.logFree(OpenStudio::Error, 'prm.log', 'Simulation failed. Check the model to make sure no severe errors.') - raise 'Simulation on proposed model failed. Baseline generation is stopped.' + OpenStudio.logFree(OpenStudio::Error, 'prm.log', + 'Simulation failed. Check the model to make sure no severe errors.') + prm_raise(false, + sizing_run_dir, + 'Simulation on proposed model failed. Baseline generation is stopped.') end end + if create_proposed_model + # Make the run directory if it doesn't exist + unless Dir.exist?(sizing_run_dir) + FileUtils.mkdir_p(sizing_run_dir) + end + # Save proposed model + proposed_model.save(OpenStudio::Path.new("#{sizing_run_dir}/proposed_final.osm"), true) + forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new + idf = forward_translator.translateModel(proposed_model) + idf_path = OpenStudio::Path.new("#{sizing_run_dir}/proposed_final.idf") + idf.save(idf_path, true) + end # User data process # bldg_type_hvac_zone_hash could be an empty hash if all zones in the models are unconditioned + # TODO - move this portion to the top of the function bldg_type_hvac_zone_hash = {} - handle_user_input_data(user_model, climate_zone, hvac_building_type, wwr_building_type, swh_building_type, bldg_type_hvac_zone_hash) + handle_user_input_data(user_model, climate_zone, sizing_run_dir, hvac_building_type, wwr_building_type, swh_building_type, bldg_type_hvac_zone_hash) + # Define different orientation from original orientation # for each individual baseline models # Need to run proposed model sizing simulation if no sql data is available degs_from_org = run_all_orientations(run_all_orients, user_model) ? [0, 90, 180, 270] : [0] @@ -173,22 +213,31 @@ set_ventilation = false set_infiltration = false # For PRM, it only applies lights for now. space_type_apply_internal_loads(space_type, set_people, set_lights, set_electric_equipment, set_gas_equipment, set_ventilation, set_infiltration) end + # Modify the lighting schedule to handle lighting occupancy sensors # Modify the upper limit value of fractional schedule to avoid the fatal error caused by schedule value higher than 1 space_type_light_sch_change(model) + # Modify electric equipment computer room schedule + model.getSpaces.sort.each do |space| + space_add_prm_computer_room_equipment_schedule(space) + end + model_apply_baseline_exterior_lighting(model) # Modify the elevator motor peak power model_add_prm_elevators(model) # Calculate infiltration as per 90.1 PRM rules - model_baseline_apply_infiltration_standard(model, climate_zone) + model_apply_standard_infiltration(model) + # Apply user outdoor air specs as per 90.1 PRM rules exceptions + model_apply_userdata_outdoor_air(model) + # If any of the lights are missing schedules, assign an always-off schedule to those lights. # This is assumed to be the user's intent in the proposed model. model.getLightss.sort.each do |lights| if lights.schedule.empty? lights.setSchedule(model.alwaysOffDiscreteSchedule) @@ -254,15 +303,11 @@ end end end # Compute and marke DCV related information before deleting proposed model HVAC systems - model_mark_zone_dcv_existence(model) - model_add_dcv_user_exception_properties(model) - model_add_dcv_requirement_properties(model) - model_add_apxg_dcv_properties(model) - model_raise_user_model_dcv_errors(model) + model_evaluate_dcv_requirements(model) # Remove all HVAC from model, excluding service water heating model_remove_prm_hvac(model) # Remove all EMS objects from the model @@ -440,11 +485,11 @@ # @todo: turn off self shading # Set Solar Distribution to MinimalShadowing... problem is when you also have detached shading such as surrounding buildings etc # It won't be taken into account, while it should: only self shading from the building itself should be turned off but to my knowledge there isn't a way to do this in E+ - model_status = degs > 0 ? "final_#{degs}" : 'final' + model_status = degs > 0 ? "baseline_final_#{degs}" : 'baseline_final' model.save(OpenStudio::Path.new("#{sizing_run_dir}/#{model_status}.osm"), true) # Translate to IDF and save for debugging forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new idf = forward_translator.translateModel(model) @@ -519,16 +564,142 @@ end return true end + # Creates a Performance Rating Method (aka 90.1-Appendix G) proposed building model + # based on the inputs currently in the user model. + # + # @param user_model [OpenStudio::model::Model] User specified OpenStudio model + # @return [OpenStudio::model::Model] returns the proposed building model corresponding to a user model + def model_create_prm_proposed_building(user_model) + # Create copy of the user model + proposed_model = BTAP::FileIO.deep_copy(user_model) + + # Get user building level data + building_name = proposed_model.building.get.name.get + user_buildings = @standards_data.key?('userdata_building') ? @standards_data['userdata_building'] : nil + + # If needed, modify user model infiltration + if user_buildings + user_building_index = user_buildings.index { |user_building| building_name.include? user_building['name'] } + # TODO: Move the user data processing section + infiltration_modeled_from_field_verification_results = 'false' + if user_building_index && user_buildings[user_building_index]['infiltration_modeled_from_field_verification_results'] + infiltration_modeled_from_field_verification_results = user_buildings[user_building_index]['infiltration_modeled_from_field_verification_results'].to_s.downcase + end + + # Calculate total infiltration flow rate per envelope area + building_envelope_area_m2 = model_building_envelope_area(proposed_model) + curr_tot_infil_m3_per_s_per_envelope_area = model_current_building_envelope_infiltration_at_75pa(proposed_model, building_envelope_area_m2) + curr_tot_infil_cfm_per_envelope_area = OpenStudio.convert(curr_tot_infil_m3_per_s_per_envelope_area, 'm^3/s*m^2', 'cfm/ft^2').get + + # Warn users if the infiltration modeling in the user/proposed model is not based on field verification + # If not modeled based on field verification, it should be modeled as 0.6 cfm/ft2 + unless infiltration_modeled_from_field_verification_results.casecmp('true') + if curr_tot_infil_cfm_per_envelope_area < 0.6 + OpenStudio.logFree(OpenStudio::Info, 'prm.log', "The user model's I_75Pa is estimated to be #{curr_tot_infil_cfm_per_envelope_area} m3/s per m2 of total building envelope") + end + end + + # Modify model to follow the PRM infiltration modeling method + model_apply_standard_infiltration(proposed_model, curr_tot_infil_cfm_per_envelope_area) + end + + # If needed, remove all non-adiabatic pipes of SWH loops + proposed_model.getPlantLoops.sort.each do |plant_loop| + # Skip non service water heating loops + next unless plant_loop_swh_loop?(plant_loop) + + plant_loop_adiabatic_pipes_only(plant_loop) + end + + # TODO: Once data refactoring has been completed lookup values from the database; + # For now, hard-code LPD for selected spaces. Current Standards Space Type + # of OS:SpaceType is the PRM interior lighting space type. These values are + # from Table 9.6.1 as required by Section G3.1.6.e. + proposed_lpd_residential_spaces = { + 'dormitory - living quarters' => 0.5, # "primary_space_type": "Dormitory—Living Quarters", + 'apartment - hardwired' => 0.6, # "primary_space_type": "Dwelling Unit" + 'guest room' => 0.41 # "primary_space_type": "Guest Room", + } + + # Make proposed model space related adjustments + proposed_model.getSpaces.each do |space| + # If needed, modify computer equipment schedule + # Section G3.1.3.16 + space_add_prm_computer_room_equipment_schedule(space) + + # If needed, modify lighting power denstities in residential spaces/zones + # Section G3.1.6.e + standard_space_type = prm_get_optional_handler(space, @sizing_run_dir, 'spaceType', 'standardsSpaceType').downcase + user_spaces = @standards_data.key?('userdata_space') ? @standards_data['userdata_space'] : nil + if ['dormitory - living quarters', 'apartment - hardwired', 'guest room'].include?(standard_space_type) + user_spaces.each do |user_data| + if user_data['name'].to_s == space.name.to_s && user_data['has_residential_exception'].to_s.downcase != 'yes' + # Get LPDs + lpd_w_per_m2 = space.lightingPowerPerFloorArea + ref_space_lpd_per_ft2 = proposed_lpd_residential_spaces[standard_space_type] + ref_space_lpd_per_m2 = OpenStudio.convert(ref_space_lpd_per_ft2, 'W/ft^2', 'W/m^2').get + # Set new LPD + space.setLightingPowerPerFloorArea([lpd_w_per_m2, ref_space_lpd_per_m2].max) + end + end + end + end + + return proposed_model + end + + # Determine whether or not the HVAC system in a model is autosized + # + # As it is not realistic expectation to have all autosizable + # fields hard input, the method relies on autosizable field + # of prime movers (fans, pumps) and heating/cooling devices + # in the models (boilers, chillers, coils) + # + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [Boolean] returns true if the HVAC system is likely autosized, false otherwise + def model_is_hvac_autosized(model) + is_hvac_autosized = false + model.modelObjects.each do |obj| + obj_type = obj.iddObjectType.valueName.to_s.downcase + + # Check if the object needs to be checked for autosizing + obj_to_be_checked_for_autosizing = false + if obj_type.include?('chiller') || obj_type.include?('boiler') || obj_type.include?('coil') || obj_type.include?('fan') || obj_type.include?('pump') || obj_type.include?('waterheater') + if !obj_type.include?('controller') + obj_to_be_checked_for_autosizing = true + end + end + + # Check for autosizing + if obj_to_be_checked_for_autosizing + casted_obj = model_cast_model_object(obj) + + next if casted_obj.nil? + + casted_obj.methods.each do |method| + if method.to_s.include?('is') && method.to_s.include?('Autosized') + if casted_obj.public_send(method) == true + is_hvac_autosized = true + OpenStudio.logFree(OpenStudio::Info, 'prm.log', "The #{method.to_s.sub('is', '').sub('Autosized', '').sub(':', '')} field of the #{obj_type} named #{casted_obj.name} is autosized. It should be hard sized.") + end + end + end + end + end + + return is_hvac_autosized + end + # Determine if there needs to be a sizing run after constructions are added # so that EnergyPlus can calculate the VLTs of layer-by-layer glazing constructions. # These VLT values are needed for the daylighting controls logic for some templates. # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if required, false if not + # @return [Boolean] returns true if required, false if not def model_create_prm_baseline_building_requires_vlt_sizing_run(model) return false # Not required for most templates end # Determine if there is a need for a proposed model sizing run. @@ -594,11 +765,11 @@ end # Add design day schedule objects for space loads, # not used for 2013 and earlier # @author Xuechen (Jerry) Lei, PNNL - # @param model [OpenStudio::model::Model] OpenStudio model object + # @param model [OpenStudio::Model::Model] OpenStudio model object # def model_apply_prm_baseline_sizing_schedule(model) return true end @@ -668,12 +839,18 @@ # with keys area_ft2, type, fuel, and zones (an array of zones) def model_prm_baseline_system_groups(model, custom, bldg_type_hvac_zone_hash = nil) # Define the minimum area for the # exception that allows a different # system type in part of the building. - exception_min_area_m2 = model_prm_baseline_system_group_minimum_area(model, custom) - exception_min_area_ft2 = OpenStudio.convert(exception_min_area_m2, 'm^2', 'ft^2').get + if custom == 'Xcel Energy CO EDA' + # Customization - Xcel EDA Program Manual 2014 + # 3.2.1 Mechanical System Selection ii + exception_min_area_ft2 = 5000 + OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "Customization; per Xcel EDA Program Manual 2014 3.2.1 Mechanical System Selection ii, minimum area for non-predominant conditions reduced to #{exception_min_area_ft2} ft2.") + else + exception_min_area_ft2 = 20_000 + end # Get occupancy type, fuel type, and area information for all zones, # excluding unconditioned zones. # Occupancy types are: # Residential @@ -863,20 +1040,26 @@ # If there is any district heating or district cooling in the proposed building, the heating and cooling # fuels in the entire baseline building are changed for the purposes of HVAC system assignment all_htg_fuels = [] all_clg_fuels = [] + + # error if HVACComponent heating fuels method is not available + if model.version < OpenStudio::VersionString.new('3.6.0') + OpenStudio.logFree(OpenStudio::Error, 'openstudio.Standards.Model', 'Required HVACComponent methods .heatingFuelTypes and .coolingFuelTypes are not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.') + end + model.getThermalZones.sort.each do |zone| - all_htg_fuels += zone.heating_fuels - all_clg_fuels += zone.cooling_fuels + all_htg_fuels += zone.heatingFuelTypes.map(&:valueName) + all_clg_fuels += zone.coolingFuelTypes.map(&:valueName) end purchased_heating = false purchased_cooling = false # Purchased heating - if all_htg_fuels.include?('DistrictHeating') + if all_htg_fuels.include?('DistrictHeating') || all_htg_fuels.include?('DistrictHeatingWater') || all_htg_fuels.include?('DistrictHeatingSteam') purchased_heating = true end # Purchased cooling if all_clg_fuels.include?('DistrictCooling') @@ -921,17 +1104,23 @@ return final_groups end # Before deleting proposed HVAC components, determine for each zone if it has district heating - # @return [Hash] of boolean with zone name as key + # @return [Hash] Hash of boolean with zone name as key def model_get_district_heating_zones(model) has_district_hash = {} model.getThermalZones.sort.each do |zone| has_district_hash['building'] = false - htg_fuels = zone.heating_fuels - if htg_fuels.include?('DistrictHeating') + + # error if HVACComponent heating fuels method is not available + if model.version < OpenStudio::VersionString.new('3.6.0') + OpenStudio.logFree(OpenStudio::Error, 'openstudio.Standards.Model', 'Required HVACComponent method .heatingFuelTypes is not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.') + end + + htg_fuels = zone.heatingFuelTypes.map(&:valueName) + if htg_fuels.include?('DistrictHeating') || htg_fuels.include?('DistrictHeatingWater') || htg_fuels.include?('DistrictHeatingSteam') has_district_hash[zone.name] = true has_district_hash['building'] = true else has_district_hash[zone.name] = false end @@ -939,18 +1128,25 @@ return has_district_hash end # Get list of heat types across a list of zones # @param zones [array of objects] array of zone objects - # @return [string] concatenated string showing different fuel types in a group of zones + # @return [String concatenated string showing different fuel types in a group of zones def get_group_heat_types(model, zones) heat_list = '' has_district_heat = false has_fuel_heat = false has_elec_heat = false + + # error if HVACComponent heating fuels method is not available + if model.version < OpenStudio::VersionString.new('3.6.0') + OpenStudio.logFree(OpenStudio::Error, 'openstudio.Standards.Model', 'Required HVACComponent method .heatingFuelTypes is not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.') + end + zones.each do |zone| - if zone.heating_fuels.include?('DistrictHeating') + htg_fuels = zone.heatingFuelTypes.map(&:valueName) + if htg_fuels.include?('DistrictHeating') || htg_fuels.include?('DistrictHeatingWater') || htg_fuels.include?('DistrictHeatingSteam') has_district_heat = true end other_heat = thermal_zone_fossil_or_electric_type(zone, '') if other_heat == 'fossil' has_fuel_heat = true @@ -971,20 +1167,20 @@ end # Store fan operation schedule for each zone before deleting HVAC objects # @author Doug Maddox, PNNL # @param model [object] - # @return [hash] of zoneName:fan_schedule_8760 + # @return [Hash] of zoneName:fan_schedule_8760 def get_fan_schedule_for_each_zone(model) fan_sch_names = {} # Start with air loops model.getAirLoopHVACs.sort.each do |air_loop_hvac| fan_schedule_8760 = [] # Check for availability managers # Assume only AvailabilityManagerScheduled will control fan schedule - # TODO: also check AvailabilityManagerScheduledOn + # @todo also check AvailabilityManagerScheduledOn avail_mgrs = air_loop_hvac.availabilityManagers # if avail_mgrs.is_initialized if !avail_mgrs.nil? avail_mgrs.each do |avail_mgr| # avail_mgr = avail_mgr.get @@ -1097,11 +1293,11 @@ # Convert from schedule object to array of hourly values for entire year # Array will include extra 24 values for leap year # Array will also include extra 24 values at end for holiday day type # @author: Doug Maddox, PNNL - # @TODO: consider moving this to Standards.Schedule.rb + # @todo consider moving this to Standards.Schedule.rb # @param: model [Object] # @param: fan_schedule [Object] # @return: [Array<String>] annual hourly values from schedule def get_8760_values_from_schedule(model, fan_schedule) sch_object_type = fan_schedule.iddObjectType.valueName.to_s @@ -1116,54 +1312,47 @@ # First convert to ScheduleRuleset sch_translator = ScheduleTranslator.new(model, fan_schedule) fan_schedule_ruleset = sch_translator.convert_schedule_compact_to_schedule_ruleset fan_8760 = get_8760_values_from_schedule_ruleset(model, fan_schedule_ruleset) when 'OS_Schedule_Year' - # TODO: add function for ScheduleYear + # @todo add function for ScheduleYear # fan_8760 = get_8760_values_from_schedule_year(model, fan_schedule) OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', 'Automated baseline measure does not support use of Schedule Year') end return fan_8760 end - # Determines the area of the building above which point - # the non-dominant area type gets it's own HVAC system type. - # - # @param model [OpenStudio::Model::Model] OpenStudio model object - # @param custom [String] custom fuel type - # @return [Double] the minimum area (m^2) - def model_prm_baseline_system_group_minimum_area(model, custom) - exception_min_area_ft2 = 20_000 - exception_min_area_m2 = OpenStudio.convert(exception_min_area_ft2, 'ft^2', 'm^2').get - return exception_min_area_m2 - end - # Determine the baseline system type given the inputs. Logic is different for different standards. # # 90.1-2007, 90.1-2010, 90.1-2013 # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A' - # @param sys_group [hash] Hash defining a group of zones that have the same Appendix G system type + # @param sys_group [Hash] Hash defining a group of zones that have the same Appendix G system type # @param custom [String] custom fuel type # @return [String] The system type. Possibilities are PTHP, PTAC, PSZ_AC, PSZ_HP, PVAV_Reheat, PVAV_PFP_Boxes, # VAV_Reheat, VAV_PFP_Boxes, Gas_Furnace, Electric_Furnace # @todo add 90.1-2013 systems 11-13 def model_prm_baseline_system_type(model, climate_zone, sys_group, custom, hvac_building_type = nil, district_heat_zones = nil) area_type = sys_group['occ'] fuel_type = sys_group['fuel'] area_ft2 = sys_group['area_ft2'] num_stories = sys_group['stories'] - # [type, central_heating_fuel, zone_heating_fuel, cooling_fuel] + # [type, central_heating_fuel, zone_heating_fuel, cooling_fuel] system_type = [nil, nil, nil, nil] # Get the row from TableG3.1.1A sys_num = model_prm_baseline_system_number(model, climate_zone, area_type, fuel_type, area_ft2, num_stories, custom) # Modify the fuel type if called for by the standard - fuel_type = model_prm_baseline_system_change_fuel_type(model, fuel_type, climate_zone, custom) + if custom == 'Xcel Energy CO EDA' + # fuel type remains unchanged + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', 'Custom; per Xcel EDA Program Manual 2014 Table 3.2.2 Baseline HVAC System Types, the 90.1-2010 rules for heating fuel type (based on proposed model) rules apply.') + else + fuel_type = model_prm_baseline_system_change_fuel_type(model, fuel_type, climate_zone) + end # Define the lookup by row and by fuel type sys_lookup = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) } # fossil, fossil and electric, purchased heat, purchased heat and cooling @@ -1259,47 +1448,16 @@ # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param fuel_type [String] Valid choices are electric, fossil, fossilandelectric, # purchasedheat, purchasedcooling, purchasedheatandcooling # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A' - # @param custom [String] custom fuel type # @return [String] the revised fuel type - def model_prm_baseline_system_change_fuel_type(model, fuel_type, climate_zone, custom = nil) - return fuel_type # Don't change fuel type for most templates + def model_prm_baseline_system_change_fuel_type(model, fuel_type, climate_zone) + # Don't change fuel type for most templates + return fuel_type end - # Determine whether heating type is fuel or electric - # @param hvac_building_type [String] Key for lookup of baseline system type - # @param climate_zone [String] full name of climate zone - # @return [String] fuel or electric - def find_prm_heat_type(hvac_building_type, climate_zone) - climate_code = get_climate_zone_code(climate_zone) - heat_type_props = model_find_object(standards_data['prm_heat_type'], - 'template' => template, - 'hvac_building_type' => hvac_building_type, - 'climate_zone' => climate_code) - if !heat_type_props - # try again with wild card for climate - heat_type_props = model_find_object(standards_data['prm_heat_type'], - 'template' => template, - 'hvac_building_type' => hvac_building_type, - 'climate_zone' => 'any') - end - if !heat_type_props - # try again with wild card for building type - heat_type_props = model_find_object(standards_data['prm_heat_type'], - 'template' => template, - 'hvac_building_type' => 'all others', - 'climate_zone' => climate_code) - end - if !heat_type_props - OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Could not find baseline heat type for: #{template}-#{hvac_building_type}-#{climate_zone}.") - else - return heat_type_props['heat_type'] - end - end - # Get ASHRAE ID code for climate zone # @param climate_zone [String] full name of climate zone # @return [String] ASHRAE ID code for climate zone def get_climate_zone_code(climate_zone) cz_codes = [] @@ -1339,15 +1497,15 @@ # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param system_type [String] The system type. Valid choices are PTHP, PTAC, PSZ_AC, PSZ_HP, PVAV_Reheat, # PVAV_PFP_Boxes, VAV_Reheat, VAV_PFP_Boxes, Gas_Furnace, Electric_Furnace, # which are also returned by the method OpenStudio::Model::Model.prm_baseline_system_type. - # @param main_heat_fuel [String] main heating fuel. Valid choices are Electricity, NaturalGas, DistrictHeating - # @param zone_heat_fuel [String] zone heating/reheat fuel. Valid choices are Electricity, NaturalGas, DistrictHeating + # @param main_heat_fuel [String] main heating fuel. Valid choices are Electricity, NaturalGas, DistrictHeating, DistrictHeatingWater, DistrictHeatingSteam + # @param zone_heat_fuel [String] zone heating/reheat fuel. Valid choices are Electricity, NaturalGas, DistrictHeating, DistrictHeatingWater, DistrictHeatingSteam # @param cool_fuel [String] cooling fuel. Valid choices are Electricity, DistrictCooling # @param zones [Array<OpenStudio::Model::ThermalZone>] an array of zones - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not # @todo Add 90.1-2013 systems 11-13 def model_add_prm_baseline_system(model, system_type, main_heat_fuel, zone_heat_fuel, cool_fuel, zones, zone_fan_scheds) case system_type when 'PTAC' # System 1 unless zones.empty? @@ -1379,11 +1537,11 @@ when 'PSZ_AC' # System 3 unless zones.empty? heating_type = 'Gas' # if district heating hot_water_loop = nil - if main_heat_fuel == 'DistrictHeating' + if main_heat_fuel.include?('DistrictHeating') heating_type = 'Water' hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized model.getPlantLoopByName('Hot Water Loop').get else model_add_hw_loop(model, main_heat_fuel) @@ -1479,16 +1637,18 @@ system_name = "#{story_name} PVAV_Reheat (Sys5)" # If and only if there are primary zones to attach to the loop # counter example: floor with only one elevator machine room that get classified as sec_zones unless pri_zones.empty? - model_add_pvav(model, - pri_zones, - system_name: system_name, - hot_water_loop: hot_water_loop, - chilled_water_loop: chilled_water_loop, - electric_reheat: electric_reheat) + air_loop = model_add_pvav(model, + pri_zones, + system_name: system_name, + hot_water_loop: hot_water_loop, + chilled_water_loop: chilled_water_loop, + electric_reheat: electric_reheat) + model_system_outdoor_air_sizing_vrp_method(air_loop) + air_loop_hvac_apply_vav_damper_action(air_loop) model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name) end # Add a PSZ_AC for each secondary zone unless sec_zones.empty? @@ -1616,19 +1776,21 @@ unless pri_zones.empty? # if the loop configuration is primary / secondary loop if chilled_water_loop.additionalProperties.hasFeature('secondary_loop_name') chilled_water_loop = model.getPlantLoopByName(chilled_water_loop.additionalProperties.getFeatureAsString('secondary_loop_name').get).get end - model_add_vav_reheat(model, - pri_zones, - system_name: system_name, - reheat_type: reheat_type, - hot_water_loop: hot_water_loop, - chilled_water_loop: chilled_water_loop, - fan_efficiency: 0.62, - fan_motor_efficiency: 0.9, - fan_pressure_rise: 4.0) + air_loop = model_add_vav_reheat(model, + pri_zones, + system_name: system_name, + reheat_type: reheat_type, + hot_water_loop: hot_water_loop, + chilled_water_loop: chilled_water_loop, + fan_efficiency: 0.62, + fan_motor_efficiency: 0.9, + fan_pressure_rise: 4.0) + model_system_outdoor_air_sizing_vrp_method(air_loop) + air_loop_hvac_apply_vav_damper_action(air_loop) model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name) end # Add a PSZ_AC for each secondary zone unless sec_zones.empty? @@ -1706,11 +1868,11 @@ when 'Gas_Furnace' # System 9 unless zones.empty? # If district heating hot_water_loop = nil - if main_heat_fuel == 'DistrictHeating' + if main_heat_fuel.include?('DistrictHeating') hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized model.getPlantLoopByName('Hot Water Loop').get else model_add_hw_loop(model, main_heat_fuel) end @@ -1735,11 +1897,11 @@ end when 'SZ_CV' # System 12 (gas or district heat) or System 13 (electric resistance heat) unless zones.empty? hot_water_loop = nil - if zone_heat_fuel == 'DistrictHeating' || zone_heat_fuel == 'NaturalGas' + if zone_heat_fuel.include?('DistrictHeating') || zone_heat_fuel == 'NaturalGas' heating_type = 'Water' hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized model.getPlantLoopByName('Hot Water Loop').get else model_add_hw_loop(model, main_heat_fuel) @@ -2142,12 +2304,12 @@ return value_avg end # For a multizone system, create the fan schedule based on zone occupancy/fan schedules # @author Doug Maddox, PNNL - # @param model - # @param zone_fan_scheds [Hash] of hash of zoneName:8760FanSchedPerZone + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @param zone_op_hrs [Hash] hash of zoneName zone_op_hrs # @param pri_zones [Array<String>] names of zones served by the multizone system # @param system_name [String] name of air loop def model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name) # Not applicable if not stable baseline return @@ -2202,11 +2364,11 @@ # Assign each space in the model to a building story based on common z (height) values. # If no story object is found for a particular height, create a new one and assign it to the space. # Does not assign a story to plenum spaces. # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_assign_spaces_to_stories(model) # Make hash of spaces and minz values sorted_spaces = {} model.getSpaces.sort.each do |space| # Skip plenum spaces @@ -2242,11 +2404,11 @@ # Applies the multi-zone VAV outdoor air sizing requirements to all applicable air loops in the model. # @note This must be performed before the sizing run because it impacts component sizes, which in turn impact efficiencies. # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_apply_multizone_vav_outdoor_air_sizing(model) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying multizone vav OA sizing.') # Multi-zone VAV outdoor air sizing model.getAirLoopHVACs.sort.each { |obj| air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(obj) } @@ -2256,14 +2418,14 @@ # Applies the HVAC parts of the template to all objects in the model using the the template specified in the model. # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A' - # @param apply_controls [Bool] toggle whether to apply air loop and plant loop controls + # @param apply_controls [Boolean] toggle whether to apply air loop and plant loop controls # @param sql_db_vars_map [Hash] hash map - # @param necb_ref_hp [Bool] for compatability with NECB ruleset only. - # @return [Bool] returns true if successful, false if not + # @param necb_ref_hp [Boolean] for compatability with NECB ruleset only. + # @return [Boolean] returns true if successful, false if not def model_apply_hvac_efficiency_standard(model, climate_zone, apply_controls: true, sql_db_vars_map: nil, necb_ref_hp: false) sql_db_vars_map = {} if sql_db_vars_map.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Started applying HVAC efficiency standards for #{template} template.") @@ -2343,11 +2505,11 @@ end # Applies daylighting controls to each space in the model per the standard. # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_add_daylighting_controls(model) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started adding daylighting controls.') # Add daylighting controls to each space model.getSpaces.sort.each do |space| @@ -2357,21 +2519,22 @@ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding daylighting controls.') return true end # For backward compatibility, infiltration standard not used for 2013 and earlier - # @return [Bool] true if successful, false if not - def model_baseline_apply_infiltration_standard(model, climate_zone) + # + # @return [Boolean] true if successful, false if not + def model_apply_standard_infiltration(model, specific_space_infiltration_rate_75_pa = nil) return true end # Apply the air leakage requirements to the model, as described in PNNL section 5.2.1.6. # This method creates customized infiltration objects for each space # and removes the SpaceType-level infiltration objects. # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not # @todo This infiltration method is not used by the Reference buildings, fix this inconsistency. def model_apply_infiltration_standard(model) # Set the infiltration rate at each space model.getSpaces.sort.each do |space| space_apply_infiltration_rate(space) @@ -2783,11 +2946,11 @@ # Create ScheduleTypeLimits # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param standard_sch_type_limit [String] the name of a standard schedule type limit with predefined limits - # options are Temperature, Humidity Ratio, Fractional, OnOff, and Activity + # options are Dimensionless, Temperature, Humidity Ratio, Fractional, OnOff, and Activity # @param name [String] the name of the schedule type limits # @param lower_limit_value [double] the lower limit value for the schedule type # @param upper_limit_value [double] the upper limit value for the schedule type # @param numeric_type [String] the numeric type, options are Continuous or Discrete # @param unit_type [String] the unit type, options are defined in EnergyPlus I/O reference @@ -2821,10 +2984,18 @@ schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Temperature') end else case standard_sch_type_limit.downcase + when 'dimensionless' + schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) + schedule_type_limits.setName('Dimensionless') + schedule_type_limits.setLowerLimitValue(0.0) + schedule_type_limits.setUpperLimitValue(1000.0) + schedule_type_limits.setNumericType('Continuous') + schedule_type_limits.setUnitType('Dimensionless') + when 'temperature' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Temperature') schedule_type_limits.setLowerLimitValue(0.0) schedule_type_limits.setUpperLimitValue(100.0) @@ -3235,11 +3406,11 @@ frame.setFrameWidth(frame_with_m) frame.setFrameConductance(frame_conductance_si) skylights_frame_added = 0 model.getSubSurfaces.each do |sub_surface| next unless sub_surface.outsideBoundaryCondition == 'Outdoors' && sub_surface.subSurfaceType == 'Skylight' - + if model.version < OpenStudio::VersionString.new('3.1.0') # window frame setting before https://github.com/NREL/OpenStudio/issues/2895 was fixed sub_surface.setString(8, frame.name.get.to_s) skylights_frame_added += 1 else @@ -3364,11 +3535,11 @@ # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A' # @param building_type [String] the building type # @param spc_type [String] the space type - # @param is_residential [Bool] true if the building is residential + # @param is_residential [Boolean] true if the building is residential # @return [OpenStudio::Model::OptionalDefaultConstructionSet] an optional default construction set def model_add_construction_set(model, climate_zone, building_type, spc_type, is_residential) construction_set = OpenStudio::Model::OptionalDefaultConstructionSet.new # Find the climate zone set that this climate zone falls into @@ -3968,12 +4139,12 @@ # This is used by other methods to get the climate zone and building type from a model. # It has logic to break office into small, # medium or large based on building area that can be turned off # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @param remap_office [bool] re-map small office or leave it alone - # @return [hash] key for climate zone, building type, and standards template. All values are strings. + # @param remap_office [Boolean] re-map small office or leave it alone + # @return [Hash] key for climate zone, building type, and standards template. All values are strings. def model_get_building_properties(model, remap_office = true) # get climate zone from model climate_zone = model_standards_climate_zone(model) # get building type from model @@ -4251,11 +4422,11 @@ # Clone the existing constructions and set their intended surface type and standards construction type per the PRM. # For some standards, this will involve making modifications. For others, it will not. # # 90.1-2007, 90.1-2010, 90.1-2013 # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_apply_prm_construction_types(model) types_to_modify = [] # Possible boundary conditions are # Adiabatic @@ -4337,11 +4508,11 @@ # Apply the standard construction to each surface in the model, based on the construction type currently assigned. # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A' - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_apply_standard_constructions(model, climate_zone, wwr_building_type: nil, wwr_info: {}) types_to_modify = [] # Possible boundary conditions are # Adiabatic @@ -4479,11 +4650,11 @@ # WWR reduction will be done by moving vertices inward toward centroid. # This causes the least impact on the daylighting area calculations and controls placement. # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A' - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not # @todo add proper support for 90.1-2013 with all those building type specific values # @todo support 90.1-2004 requirement that windows be modeled as horizontal bands. # Currently just using existing window geometry, and shrinking as necessary if WWR is above limit. # @todo support semiheated spaces as a separate WWR category # @todo add window frame area to calculation of WWR @@ -4582,11 +4753,11 @@ if model_create_prm_baseline_building_requires_proposed_model_sizing_run(model) # For PRM 90.1-2019 and onward, determine space category # based on sizing run results cat = space_conditioning_category(space) else - # TODO: This should really use the heating/cooling loads from the proposed building. + # @todo This should really use the heating/cooling loads from the proposed building. # However, in an attempt to avoid another sizing run just for this purpose, # conditioned status is based on heating/cooling setpoints. # If heated-only, will be assumed Semiheated. # The full-bore method is on the next line in case needed. # cat = thermal_zone_conditioning_category(space, template, climate_zone) @@ -4733,25 +4904,24 @@ # If a vertical rectangle, raise sill height to avoid # impacting daylighting areas, otherwise # reduce toward centroid. # # daylighting control isn't modeled - surface_wwr = surface_get_wwr_of_a_surface(surface) - red = model_get_wwr_reduction_ratio(mult, - surface_wwr: surface_wwr, - surface_dr: surface_get_door_ratio_of_a_surface(surface), - wwr_building_type: bat, - wwr_target: wwr_lim / 100, # divide by 100 to revise it to decimals - total_wall_m2: total_wall_area, - total_wall_with_fene_m2: total_wall_with_fene_area, - total_fene_m2: total_fene_area, - total_plenum_wall_m2: total_plenum_wall_area) + red = surface_get_wwr_reduction_ratio(mult, + surface, + wwr_building_type: bat, + wwr_target: wwr_lim / 100, # divide by 100 to revise it to decimals + total_wall_m2: total_wall_area, + total_wall_with_fene_m2: total_wall_with_fene_area, + total_fene_m2: total_fene_area, + total_plenum_wall_m2: total_plenum_wall_area) if red < 0.0 # surface with fenestration to its maximum but adjusted by door areas when need to add windows in surfaces no fenestration # turn negative to positive to get the correct adjustment factor. red = -red + surface_wwr = surface_get_wwr(surface) residual_fene += (0.9 - red * surface_wwr) * surface.grossArea end surface_adjust_fenestration_in_a_surface(surface, red, model) end @@ -4766,11 +4936,11 @@ end # Reduces the SRR to the values specified by the PRM. SRR reduction will be done by shrinking vertices toward the centroid. # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not # @todo support semiheated spaces as a separate SRR category # @todo add skylight frame area to calculation of SRR def model_apply_prm_baseline_skylight_to_roof_ratio(model) # Loop through all spaces in the model, and # per the PNNL PRM Reference Manual, find the areas @@ -4911,11 +5081,11 @@ end # Apply baseline values to exterior lights objects # Only implemented for stable baseline # - # @param model [OpenStudio::model::Model] OpenStudio model object + # @param model [OpenStudio::Model::Model] OpenStudio model object def model_apply_baseline_exterior_lighting(model) return false end # Function to add baseline elevators based on user data @@ -4927,11 +5097,11 @@ # Remove all HVAC that will be replaced during the performance rating method baseline generation. # This does not include plant loops that serve WaterUse:Equipment or Fan:ZoneExhaust # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_remove_prm_hvac(model) # Plant loops model.getPlantLoops.sort.each do |loop| # Don't remove service water heating loops next if plant_loop_swh_loop?(loop) @@ -4983,11 +5153,11 @@ end # Remove EMS objects that may be orphaned from removing HVAC # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_remove_prm_ems_objects(model) model.getEnergyManagementSystemActuators.each(&:remove) model.getEnergyManagementSystemConstructionIndexVariables.each(&:remove) model.getEnergyManagementSystemCurveOrTableIndexVariables.each(&:remove) model.getEnergyManagementSystemGlobalVariables.each(&:remove) @@ -5004,11 +5174,11 @@ end # Remove external shading devices. Site shading will not be impacted. # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_remove_external_shading_devices(model) shading_surfaces_removed = 0 model.getShadingSurfaceGroups.sort.each do |shade_group| # Skip Site shading next if shade_group.shadingSurfaceType == 'Site' @@ -5024,11 +5194,11 @@ end # Changes the sizing parameters to the PRM specifications. # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_apply_prm_sizing_parameters(model) clg = 1.15 htg = 1.25 sizing_params = model.getSizingParameters @@ -5301,11 +5471,11 @@ # This method ensures that all spaces with spacetypes defined contain at least a standardSpaceType appropriate for the template. # So, if any space with a space type defined does not have a Stnadard spacetype, or is undefined, an error will stop # with information that the spacetype needs to be defined. # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_validate_standards_spacetypes_in_model(model) error_string = '' # populate search hash model.getSpaces.sort.each do |space| unless space.spaceType.empty? @@ -5454,11 +5624,11 @@ end # create space_type_hash with info such as effective_num_spaces, num_units, num_meds, num_meals # # @param model [OpenStudio::Model::Model] OpenStudio model object - # @param trust_effective_num_spaces [Bool] defaults to false - set to true if modeled every space as a real rpp, vs. space as collection of rooms + # @param trust_effective_num_spaces [Boolean] defaults to false - set to true if modeled every space as a real rpp, vs. space as collection of rooms # @return [Hash] hash of space types with misc information # @todo - add code when determining number of units to makeuse of trust_effective_num_spaces arg def model_create_space_type_hash(model, trust_effective_num_spaces = false) # assumed class size to deduct teachers from occupant count for classrooms typical_class_size = 20.0 @@ -5565,11 +5735,11 @@ # This method only reduces subsurface sizes at most. # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param ratio [Double] ratio # @param surface_type [String] surface type - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def apply_limit_to_subsurface_ratio(model, ratio, surface_type = 'Wall') fdwr = get_outdoor_subsurface_ratio(model, surface_type) if fdwr <= ratio OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Building FDWR of #{fdwr} is already lower than limit of #{ratio.round}%.") return true @@ -5600,11 +5770,11 @@ # 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] the model + # @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 model_standards_climate_zone(model) climate_zone = '' model.getClimateZones.climateZones.each do |cz| @@ -5628,13 +5798,13 @@ # Sets the climate zone object in the model using # the correct institution based on the climate zone specified # in the format used by the openstudio-standards lookups. # Clears out any climate zones previously added to the model. # - # @param model [OpenStudio::Model::Model] the model + # @param model [OpenStudio::Model::Model] OpenStudio model object # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A' - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_set_climate_zone(model, climate_zone) # Remove previous climate zones from the model model.getClimateZones.clear # Split the string into the correct institution and value if climate_zone.include? 'ASHRAE 169-2006-' @@ -5649,11 +5819,11 @@ end # This method return the building ratio of subsurface_area / surface_type_area # where surface_type can be "Wall" or "RoofCeiling" # - # @param model [OpenStudio::Model::Model] the model + # @param model [OpenStudio::Model::Model] OpenStudio model object # @param surface_type [String] surface type # @return [Double] surface ratio def get_outdoor_subsurface_ratio(model, surface_type = 'Wall') surface_area = 0.0 sub_surface_area = 0 @@ -5678,11 +5848,11 @@ end # Loads a osm as a starting point. # # @param osm_file [String] path to the .osm file, relative to the /data folder - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def load_initial_osm(osm_file) # Load the geometry .osm unless File.exist?(osm_file) raise("The initial osm path: #{osm_file} does not exist.") end @@ -5695,12 +5865,12 @@ return model end # validate that model contains objects # - # @param model [OpenStudio::Model::Model] the model - # @return [Bool] returns true if valid, false if not + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [Boolean] returns true if valid, false if not def validate_initial_model(model) is_valid = true if model.getBuildingStorys.empty? OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Please assign Spaces to BuildingStorys the geometry model.') is_valid = false @@ -5745,11 +5915,11 @@ # Determines how ventilation for the standard is specified. # When 'Sum', all min OA flow rates are added up. Commonly used by 90.1. # When 'Maximum', only the biggest OA flow rate. Used by T24. # - # @param model [OpenStudio::Model::Model] the model + # @param model [OpenStudio::Model::Model] OpenStudio model object # @return [String] the ventilation method, either Sum or Maximum def model_ventilation_method(model) building_data = model_get_building_properties(model) building_type = building_data['building_type'] if building_type != 'Laboratory' # Laboratory has multiple criteria on ventilation, pick the greatest @@ -5762,12 +5932,12 @@ end # Removes all of the unused ResourceObjects # (Curves, ScheduleDay, Material, etc.) from the model. # - # @param model [OpenStudio::Model::Model] the model - # @return [Bool] returns true if successful, false if not + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [Boolean] returns true if successful, false if not def model_remove_unused_resource_objects(model) start_size = model.objects.size model.getResourceObjects.sort.each do |obj| if obj.directUseCount.zero? OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "#{obj.name} is unused; it will be removed.") @@ -5787,14 +5957,14 @@ # In future this could create different hours of operation for residential vs. non-residential, by building type, story, or space type. # However this measure is a stop gap to convert old generic schedules to parametric schedules. # Future new schedules should be designed as paramtric from the start and would not need to run through this inference process # # @author David Goldwasser - # @param model [OpenStudio::Model::Model] the model + # @param model [OpenStudio::Model::Model] OpenStudio model object # @param fraction_of_daily_occ_range [Double] fraction above/below daily min range required to start and end hours of operation - # @param invert_res [Bool] if true will reverse hours of operation for residential space types - # @param gen_occ_profile [Bool] if true creates a merged occupancy schedule for diagnostic purposes. This schedule is added to the model but no specifically returned by this method + # @param invert_res [Boolean] if true will reverse hours of operation for residential space types + # @param gen_occ_profile [Boolean] if true creates a merged occupancy schedule for diagnostic purposes. This schedule is added to the model but no specifically returned by this method # @return [ScheduleRuleset] schedule that is assigned to the building as default hours of operation def model_infer_hours_of_operation_building(model, fraction_of_daily_occ_range: 0.25, invert_res: true, gen_occ_profile: false) # create an array of non-residential and residential spaces res_spaces = [] non_res_spaces = [] @@ -5941,14 +6111,14 @@ # inputs. Inputs include one or more load profile formulas. Data is stored in model attributes for downstream # application. This should impact all ScheduleRuleset objects in the model. Plant and Air loop hoours of operations # should be traced back to a space or spaces. # # @author David Goldwasser - # @param model [OpenStudio::Model::Model] the model + # @param model [OpenStudio::Model::Model] OpenStudio model object # @param step_ramp_logic [String] type of step logic to use - # @param infer_hoo_for_non_assigned_objects [Bool] attempt to get hoo for objects like swh with and exterior lighting - # @param gather_data_only [Bool] false (stops method before changes made if true) + # @param infer_hoo_for_non_assigned_objects [Boolean] attempt to get hoo for objects like swh with and exterior lighting + # @param gather_data_only [Boolean] false (stops method before changes made if true) # @param hoo_var_method [String] accepts hours and fractional. Any other value value will result in hoo variables not being applied # @return [Hash] schedule is key, value is hash of number of objects def model_setup_parametric_schedules(model, step_ramp_logic: nil, infer_hoo_for_non_assigned_objects: true, gather_data_only: false, hoo_var_method: 'hours') parametric_inputs = {} default_sch_type = OpenStudio::Model::DefaultScheduleType.new('HoursofOperationSchedule') @@ -6104,14 +6274,14 @@ # for summer and winter design days. # # @note This measure will replace any prior chagnes made to ScheduleRule objects with new ScheduleRule values from # profile formulas # @author David Goldwasser - # @param model [OpenStudio::Model::Model] the model + # @param model [OpenStudio::Model::Model] OpenStudio model object # @param ramp_frequency [Double] ramp frequency in minutes. If nil method will match simulation timestep - # @param infer_hoo_for_non_assigned_objects [Bool] # attempt to get hoo for objects like swh with and exterior lighting - # @param error_on_out_of_order [Bool] true will error if applying formula creates out of order values + # @param infer_hoo_for_non_assigned_objects [Boolean] # attempt to get hoo for objects like swh with and exterior lighting + # @param error_on_out_of_order [Boolean] true will error if applying formula creates out of order values # @return [Array] of modified ScheduleRuleset objects def model_apply_parametric_schedules(model, ramp_frequency: nil, infer_hoo_for_non_assigned_objects: true, error_on_out_of_order: true) # get ramp frequency (fractional hour) from timestep if ramp_frequency.nil? steps_per_hour = if model.getSimulationControl.timestep.is_initialized @@ -6146,14 +6316,18 @@ return parametric_schedules end private + def model_apply_userdata_outdoor_air(model) + return true + end + # This function checks whether it is required to adjust the window to wall ratio based on the model WWR and wwr limit. - # @param wwr_limit [Float] return wwr_limit + # @param wwr_limit [Double] window to wall ratio limit # @param wwr_list [Array] list of wwr of zone conditioning category in a building area type category - residential, nonresidential and semiheated - # @return require_adjustment [Boolean] True, require adjustment, false not require adjustment. + # @return [Boolean] True, require adjustment, false not require adjustment. def model_does_require_wwr_adjustment?(wwr_limit, wwr_list) require_adjustment = false wwr_list.each do |wwr| require_adjustment = true unless wwr > wwr_limit end @@ -6162,35 +6336,35 @@ # The function is used for codes that requires to adjusted wwr based on building categories for all other types # # @param bat [String] building area type category # @param wwr_list [Array] list of wwr of zone conditioning category in a building area type category - residential, nonresidential and semiheated - # @return wwr_limit [Float] return adjusted wwr_limit + # @return [Double] return adjusted wwr_limit def model_get_bat_wwr_target(bat, wwr_list) return 40.0 end # Readjusted the WWR for surfaces previously has no windows to meet the # overall WWR requirement. # This function shall only be called if the maximum WWR value for surfaces with fenestration is lower than 90% due to # accommodating the total door surface areas # - # @param residual_ratio: [Float] the ratio of residual surfaces among the total wall surface area with no fenestrations + # @param residual_ratio [Double] the ratio of residual surfaces among the total wall surface area with no fenestrations # @param space [OpenStudio::Model:Space] a space # @param model [OpenStudio::Model::Model] openstudio model - # @return [Bool] return true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_readjust_surface_wwr(residual_ratio, space, model) return true end # Helper method to fill in hourly values # - # @param model [OpenStudio::Model::Model] the model + # @param model [OpenStudio::Model::Model] OpenStudio model object # @param day_sch [OpenStudio::Model::ScheduleDay] schedule day object # @param sch_type [String] Constant or Hourly # @param values [Array<Double>] - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_add_vals_to_sch(model, day_sch, sch_type, values) if sch_type == 'Constant' day_sch.addValue(OpenStudio::Time.new(0, 24, 0, 0), values[0]) elsif sch_type == 'Hourly' (0..23).each do |i| @@ -6203,13 +6377,13 @@ end end # Modify the existing service water heating loops to match the baseline required heating type. # - # @param model [OpenStudio::Model::Model] the model + # @param model [OpenStudio::Model::Model] OpenStudio model object # @param building_type [String] the building type - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not # @author Julien Marrec def model_apply_baseline_swh_loops(model, building_type) model.getPlantLoops.sort.each do |plant_loop| # Skip non service water heating loops next unless plant_loop_swh_loop?(plant_loop) @@ -6291,12 +6465,12 @@ # This method goes through certain types of EnergyManagementSystem variables and replaces UIDs with object names. # This should be done by the forward translator, and this code should be removed after this bug is fixed: # https://github.com/NREL/OpenStudio/issues/2598 # - # @param model [OpenStudio::Model::Model] the model - # @return [Bool] returns true if successful, false if not + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [Boolean] returns true if successful, false if not # @todo remove this method after OpenStudio issue #2598 is fixed. def model_temp_fix_ems_references(model) # Internal Variables model.getEnergyManagementSystemInternalVariables.sort.each do |var| # Get the reference field value @@ -6434,11 +6608,11 @@ # pass array of space types or spaces # # @author David Goldwasser # @param space_space_types [Array] array of spaces or space types # @param parametric_inputs [Hash] - # @param gather_data_only [Bool] + # @param gather_data_only [Boolean] # @return [Hash] def gather_inputs_parametric_space_space_type_schedules(space_space_types, parametric_inputs, gather_data_only) space_space_types.each do |space_type| # get hours of operation for space type once next if space_type.class == 'OpenStudio::Model::SpaceTypes' && space_type.floorArea == 0 @@ -6493,11 +6667,11 @@ # # @author David Goldwasser # @param load_inst [OpenStudio::Model::SpaceLoadInstance] # @param parametric_inputs [Hash] # @param hours_of_operation [Hash] - # @param gather_data_only [Bool] + # @param gather_data_only [Boolean] # @return [Hash] def gather_inputs_parametric_load_inst_schedules(load_inst, parametric_inputs, hours_of_operation, gather_data_only) if load_inst.class.to_s == 'OpenStudio::Model::People' opt_sch = load_inst.numberofPeopleSchedule elsif load_inst.class.to_s == 'OpenStudio::Model::DesignSpecificationOutdoorAir' @@ -6519,13 +6693,13 @@ # @author David Goldwasser # @param sch [OpenStudio::Model::Schedule] # @param load_inst [OpenStudio::Model::SpaceLoadInstance] # @param parametric_inputs [Hash] # @param hours_of_operation [Hash] - # @param ramp [Bool] + # @param ramp [Boolean] # @param min_ramp_dur_hr [Double] - # @param gather_data_only [Bool] + # @param gather_data_only [Boolean] # @param hoo_var_method [String] accepts hours and fractional. Any other value value will result in hoo variables not being applied # @return [Hash] def gather_inputs_parametric_schedules(sch, load_inst, parametric_inputs, hours_of_operation, ramp: true, min_ramp_dur_hr: 2.0, gather_data_only: false, hoo_var_method: 'hours') if parametric_inputs.key?(sch) if hours_of_operation != parametric_inputs[sch][:hoo_inputs] # don't warn if the hours of operation between old and new schedule are equivalent @@ -6841,12 +7015,12 @@ return parametric_inputs end # Retrieves the lowest story in a model # - # @param model [OpenStudio::model::Model] OpenStudio model object - # @return [OpenStudio::model::BuildingStory] Lowest story included in the model + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [OpenStudio::Model::BuildingStory] Lowest story included in the model def find_lowest_story(model) min_z_story = 1E+10 lowest_story = nil model.getSpaces.sort.each do |space| story = space.buildingStory.get @@ -6860,11 +7034,11 @@ return lowest_story end # Utility function that returns the min and max value in a design day schedule. # - # TODO: move this to Standards.Schedule.rb + # @todo move this to Standards.Schedule.rb # @param schedule [OpenStudio::Model::Schedule] can be ScheduleCompact, ScheduleRuleset, ScheduleConstant # @param type [String] 'heating' for winter design day, 'cooling' for summer design day # @return [Hash] Hash has two keys, min and max. if failed, return 999.9 for min and max. def search_min_max_value_from_design_day_schedule(schedule, type = 'winter') if schedule.is_initialized @@ -6886,12 +7060,12 @@ return { 'min' => 999.9, 'max' => 999.9 } end # Identifies non mechanically cooled ("nmc") systems, if applicable # - # @param model [OpenStudio::model::Model] OpenStudio model object - # @return zone_nmc_sys_type [Hash] Zone to nmc system type mapping + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [Hash] Zone to nmc system type mapping def model_identify_non_mechanically_cooled_systems(model) return true end # Indicate if fan power breakdown (supply, return, and relief) @@ -6906,10 +7080,11 @@ # The method calculates the window to wall ratio (assuming all spaces are conditioned) # and select the range based on the calculated window to wall ratio # @param model [OpenStudio::Model::Model] OpenStudio model object # @param wwr_parameter [Hash] parameters to choose min and max percent of surfaces, # could be different set in different standard + # @return [Hash] Hash of minimum_percent_of_surface and maximum_percent_of_surface def model_get_percent_of_surface_range(model, wwr_parameter = {}) return { 'minimum_percent_of_surface' => nil, 'maximum_percent_of_surface' => nil } end # Default SAT reset type @@ -6920,119 +7095,87 @@ return 'warmest_zone' end # Calculate the window to wall ratio reduction factor # - # @param multiplier [Float] multiplier of the wwr - # @param surface_wwr [Float] the surface window to wall ratio - # @param surface_dr [Float] the surface door to wall ratio - # @param wwr_building_type[String] building type for wwr - # @param wwr_target [Float] target window to wall ratio - # @param total_wall_m2 [Float] total wall area of the category in m2. - # @param total_wall_with_fene_m2 [Float] total wall area of the category with fenestrations in m2. - # @param total_fene_m2 [Float] total fenestration area - # @param total_plenum_wall_m2 [Float] total sqaure meter of a plenum - # @return [Float] reduction factor - def model_get_wwr_reduction_ratio(multiplier, - surface_wwr: 0.0, - surface_dr: 0.0, - wwr_building_type: 'All others', - wwr_target: 0.0, - total_wall_m2: 0.0, - total_wall_with_fene_m2: 0.0, - total_fene_m2: 0.0, - total_plenum_wall_m2: 0.0) + # @param multiplier [Double] multiplier of the wwr + # @param surface [OpenStudio::Model:Surface] OpenStudio Surface object + # @param wwr_target [Double] target window to wall ratio + # @param total_wall_m2 [Double] total wall area of the category in m2. + # @param total_wall_with_fene_m2 [Double] total wall area of the category with fenestrations in m2. + # @param total_fene_m2 [Double] total fenestration area + # @param total_plenum_wall_m2 [Double] total sqaure meter of a plenum + # @return [Double] reduction factor + def surface_get_wwr_reduction_ratio(multiplier, + surface, + wwr_building_type: 'All others', + wwr_target: 0.0, + total_wall_m2: 0.0, + total_wall_with_fene_m2: 0.0, + total_fene_m2: 0.0, + total_plenum_wall_m2: 0.0) return 1.0 - multiplier end # A template method that handles the loading of user input data from multiple sources # include data source from: # 1. user data csv files # 2. data from measure and OpenStudio interface # @param [OpenStudio:model:Model] model # @param [String] climate_zone + # @param [String] sizing_run_dir # @param [String] default_hvac_building_type # @param [String] default_wwr_building_type # @param [String] default_swh_building_type # @param [Hash] bldg_type_hvac_zone_hash A hash maps building type for hvac to a list of thermal zones - # @return True - def handle_user_input_data(model, climate_zone, default_hvac_building_type, default_wwr_building_type, default_swh_building_type, bldg_type_hvac_zone_hash) + # @return [Boolean] returns true + def handle_user_input_data(model, climate_zone, sizing_run_dir, default_hvac_building_type, default_wwr_building_type, default_swh_building_type, bldg_type_hvac_zone_hash) return true end # Template method for adding a setpoint manager for a coil control logic to a heating coil. # ASHRAE 90.1-2019 Appendix G. # # @param model [OpenStudio::Model::Model] OpenStudio model - # @param thermalZones Array([OpenStudio::Model::ThermalZone]) thermal zone array - # @param coil Heating Coils - # @return [Boolean] true + # @param thermal_zones [Array<OpenStudio::Model::ThermalZone>] thermal zone array + # @param coil [OpenStudio::Model::StraightComponent] heating coil + # @return [Boolean] returns true if successful, false if not def model_set_central_preheat_coil_spm(model, thermal_zones, coil) return true end - # Template method for adding zone additional property "zone DCV implemented in user model" + # Template method for evaluate DCV requirements in the user model # - # @author Xuechen (Jerry) Lei, PNNL # @param model [OpenStudio::Model::Model] OpenStudio model - def model_mark_zone_dcv_existence(model) + # @return [Boolean] returns true if successful, false if not + def model_evaluate_dcv_requirements(model) return true end # Check whether the baseline model generation needs to run all four orientations # The default shall be true # - # @param [Boolean] run_all_orients: user inputs to indicate whether it is required to run all orientations - # @param [OpenStudio::Model::Model] OpenStudio model + # @param run_all_orients [Boolean] user inputs to indicate whether it is required to run all orientations + # @param user_model [OpenStudio::Model::Model] OpenStudio model + # @return [Boolean] return True if all orientation need to be run, False if not def run_all_orientations(run_all_orients, user_model) return run_all_orients end - # Template method for reading user data and adding to zone additional properties - # - # @author Xuechen (Jerry) Lei, PNNL - # @param model [OpenStudio::Model::Model] OpenStudio model - def model_add_dcv_user_exception_properties(model) - return true - end - - # Template method for raising user model DCV warning and errors - # - # @author Xuechen (Jerry) Lei, PNNL - # @param model [OpenStudio::Model::Model] OpenStudio model - def model_raise_user_model_dcv_errors(model) - return true - end - - # Template method for adding zone additional property "airloop dcv required by 901" and "zone dcv required by 901" - # - # @author Xuechen (Jerry) Lei, PNNL - # @param model [OpenStudio::Model::Model] OpenStudio model - def model_add_dcv_requirement_properties(model) - return true - end - - # Template method for checking if zones in the baseline model should have DCV based on 90.1 2019 G3.1.2.5. - # Zone additional property 'apxg no need to have DCV' added - # - # @author Xuechen (Jerry) Lei, PNNL - # @param model [OpenStudio::Model::Model] OpenStudio model - def model_add_apxg_dcv_properties(model) - return true - end - # Template method for setting DCV in baseline HVAC system if required # # @author Xuechen (Jerry) Lei, PNNL # @param model [OpenStudio::Model::Model] OpenStudio model + # @return [Boolean] returns true if successful, false if not def model_set_baseline_demand_control_ventilation(model, climate_zone) return true end # Identify the return air type associated with each thermal zone # # @param model [OpenStudio::Model::Model] OpenStudio model object + # @return [Boolean] returns true if successful, false if not def model_identify_return_air_type(model) # air-loop based system model.getThermalZones.each do |zone| # Conditioning category won't include indirectly conditioned thermal zones cond_cat = thermal_zone_conditioning_category(zone, model_standards_climate_zone(model)) @@ -7098,14 +7241,20 @@ # Catch all if return_air_type.nil? return_air_type = 'ducted_return_or_direct_to_unit' end + # error if zone design air flow rate is not available + if zone.model.version < OpenStudio::VersionString.new('3.6.0') + OpenStudio.logFree(OpenStudio::Error, 'openstudio.Standards.Model', 'Required ThermalZone method .autosizedDesignAirFlowRate is not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.') + end + zone.additionalProperties.setFeature('return_air_type', return_air_type) zone.additionalProperties.setFeature('plenum', return_plenum) unless return_plenum.nil? - zone.additionalProperties.setFeature('proposed_model_zone_design_air_flow', zone.designAirFlowRate.to_f) + zone.additionalProperties.setFeature('proposed_model_zone_design_air_flow', zone.autosizedDesignAirFlowRate.to_f) end + return true end # Determine the baseline return air type associated with each zone # # @param model [OpenStudio::Model::model] OpenStudio model object @@ -7171,12 +7320,13 @@ end # Add reporting tolerances. Default values are based on the suggestions from the PRM-RM. # # @param model [OpenStudio::Model::Model] OpenStudio Model - # @param heating_tolerance_deg_f [Float] Tolerance for time heating setpoint not met in degree F - # @param cooling_tolerance_deg_f [Float] Tolerance for time cooling setpoint not met in degree F + # @param heating_tolerance_deg_f [Double] Tolerance for time heating setpoint not met in degree F + # @param cooling_tolerance_deg_f [Double] Tolerance for time cooling setpoint not met in degree F + # @return [Boolean] returns true if successful, false if not def model_add_reporting_tolerances(model, heating_tolerance_deg_f: 1.0, cooling_tolerance_deg_f: 1.0) reporting_tolerances = model.getOutputControlReportingTolerances heating_tolerance_deg_c = OpenStudio.convert(heating_tolerance_deg_f, 'R', 'K').get cooling_tolerance_deg_c = OpenStudio.convert(cooling_tolerance_deg_f, 'R', 'K').get reporting_tolerances.setToleranceforTimeHeatingSetpointNotMet(heating_tolerance_deg_c) @@ -7185,30 +7335,30 @@ return true end # Apply the standard construction to each surface in the model, based on the construction type currently assigned. # - # @return [Bool] true if successful, false if not # @param model [OpenStudio::Model::Model] OpenStudio model object # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A' - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_apply_constructions(model, climate_zone, wwr_building_type, wwr_info) model_apply_standard_constructions(model, climate_zone, wwr_building_type: nil, wwr_info: {}) return true end # Generate baseline log to a specific file directory # @param file_directory [String] file directory + # @return [Boolean] returns true if successful, false if not def generate_baseline_log(file_directory) return true end # Update ground temperature profile based on the weather file specified in the model # # @param model [OpenStudio::Model::Model] OpenStudio model object # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A' - # @return [Bool] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not def model_update_ground_temperature_profile(model, climate_zone) return true end end