lib/openstudio-standards/standards/Standards.Model.rb in openstudio-standards-0.1.15 vs lib/openstudio-standards/standards/Standards.Model.rb in openstudio-standards-0.2.0.rc1

- old
+ new

@@ -1,348 +1,268 @@ +class Standard + attr_accessor :space_multiplier_map -# Loads the openstudio standards dataset. -# -# @return [Hash] a hash of standards data -def load_openstudio_standards_json - standards_files = [] - standards_files << 'OpenStudio_Standards_boilers.json' - standards_files << 'OpenStudio_Standards_chillers.json' - standards_files << 'OpenStudio_Standards_climate_zone_sets.json' - standards_files << 'OpenStudio_Standards_climate_zones.json' - standards_files << 'OpenStudio_Standards_construction_properties.json' - standards_files << 'OpenStudio_Standards_construction_sets.json' - standards_files << 'OpenStudio_Standards_constructions.json' - standards_files << 'OpenStudio_Standards_curve_bicubics.json' - standards_files << 'OpenStudio_Standards_curve_biquadratics.json' - standards_files << 'OpenStudio_Standards_curve_cubics.json' - standards_files << 'OpenStudio_Standards_curve_quadratics.json' - standards_files << 'OpenStudio_Standards_ground_temperatures.json' - standards_files << 'OpenStudio_Standards_heat_pumps_heating.json' - standards_files << 'OpenStudio_Standards_heat_pumps.json' - standards_files << 'OpenStudio_Standards_materials.json' - standards_files << 'OpenStudio_Standards_motors.json' - standards_files << 'OpenStudio_Standards_prototype_inputs.json' - standards_files << 'OpenStudio_Standards_schedules.json' - standards_files << 'OpenStudio_Standards_space_types.json' - standards_files << 'OpenStudio_Standards_templates.json' - standards_files << 'OpenStudio_Standards_unitary_acs.json' - standards_files << 'OpenStudio_Standards_heat_rejection.json' - standards_files << 'OpenStudio_Standards_exterior_lighting.json' - standards_files << 'OpenStudio_Standards_parking.json' - standards_files << 'OpenStudio_Standards_entryways.json' - # standards_files << 'OpenStudio_Standards_unitary_hps.json' - - # Combine the data from the JSON files into a single hash - top_dir = File.expand_path('../../..', File.dirname(__FILE__)) - standards_data_dir = "#{top_dir}/data/standards" - standards_data = {} - standards_files.sort.each do |standards_file| - temp = "" - begin - temp = load_resource_relative("../../../data/standards/#{standards_file}", 'r:UTF-8') - rescue NoMethodError - File.open("#{standards_data_dir}/#{standards_file}", 'r:UTF-8') do |f| - temp = f.read - end - end - file_hash = JSON.load(temp) - standards_data = standards_data.merge(file_hash) + def define_space_multiplier + return @space_multiplier_map end - # Check that standards data was loaded - if standards_data.keys.size.zero? - OpenStudio.logFree(OpenStudio::Error, 'OpenStudio Standards JSON data was not loaded correctly.') - end + # @!group Model - return standards_data -end - -# open the class to add methods to apply HVAC efficiency standards -class OpenStudio::Model::Model - # Load the helper libraries for getting the autosized - # values for each type of model object. - require_relative 'Standards.AirTerminalSingleDuctParallelPIUReheat' - require_relative 'Standards.BuildingStory' - require_relative 'Standards.Fan' - require_relative 'Standards.FanConstantVolume' - require_relative 'Standards.FanVariableVolume' - require_relative 'Standards.FanOnOff' - require_relative 'Standards.FanZoneExhaust' - require_relative 'Standards.ChillerElectricEIR' - require_relative 'Standards.CoilDX' - require_relative 'Standards.CoilCoolingDXTwoSpeed' - require_relative 'Standards.CoilCoolingDXSingleSpeed' - require_relative 'Standards.CoilHeatingDXSingleSpeed' - require_relative 'Standards.BoilerHotWater' - require_relative 'Standards.AirLoopHVAC' - require_relative 'Standards.WaterHeaterMixed' - require_relative 'Standards.Space' - require_relative 'Standards.Construction' - require_relative 'Standards.ThermalZone' - require_relative 'Standards.Surface' - require_relative 'Standards.SubSurface' - require_relative 'Standards.ScheduleRuleset' - require_relative 'Standards.ScheduleConstant' - require_relative 'Standards.ScheduleCompact' - require_relative 'Standards.SpaceType' - require_relative 'Standards.PlanarSurface' - require_relative 'Standards.PlantLoop' - require_relative 'Standards.Pump' - require_relative 'Standards.PumpConstantSpeed' - require_relative 'Standards.PumpVariableSpeed' - require_relative 'Standards.AirTerminalSingleDuctVAVReheat' - require_relative 'Standards.CoolingTower' - require_relative 'Standards.CoolingTowerSingleSpeed' - require_relative 'Standards.CoolingTowerTwoSpeed' - require_relative 'Standards.CoolingTowerVariableSpeed' - require_relative 'Standards.ZoneHVACComponent' - require_relative 'Standards.HeatExchangerSensLat' - require_relative 'Standards.HeaderedPumpsConstantSpeed' - require_relative 'Standards.HeaderedPumpsVariableSpeed' - # Creates a Performance Rating Method (aka Appendix G aka LEED) baseline building model # based on the inputs currently in the model. # the current model with this 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 building_type [String] the building type - # @param template [String] the template. Valid choices are 90.1-2004, 90.1-2007, 90.1-2010, 90.1-2013. # @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 # @return [Bool] returns true if successful, false if not - def create_prm_baseline_building(building_type, template, climate_zone, custom = nil, sizing_run_dir = Dir.pwd, debug = false) - lookup_building_type = get_lookup_name(building_type) + def model_create_prm_baseline_building(model, building_type, climate_zone, custom = nil, sizing_run_dir = Dir.pwd, debug = false) + model.getBuilding.setName("#{template}-#{building_type}-#{climate_zone} PRM baseline created: #{Time.new}") - getBuilding.setName("#{template}-#{building_type}-#{climate_zone} PRM baseline created: #{Time.new}") - # Remove external shading devices OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Removing External Shading Devices ***') - remove_external_shading_devices + model_remove_external_shading_devices(model) # Reduce the WWR and SRR, if necessary OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Adjusting Window and Skylight Ratios ***') - apply_prm_baseline_window_to_wall_ratio(template, climate_zone) - apply_prm_baseline_skylight_to_roof_ratio(template) + model_apply_prm_baseline_window_to_wall_ratio(model, climate_zone) + model_apply_prm_baseline_skylight_to_roof_ratio(model) # Assign building stories to spaces in the building # where stories are not yet assigned. - assign_spaces_to_stories + model_assign_spaces_to_stories(model) # Modify the internal loads in each space type, # keeping user-defined schedules. - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Changing Lighting Loads ***') - getSpaceTypes.sort.each do |space_type| + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Changing Lighting Loads ***') + model.getSpaceTypes.sort.each do |space_type| set_people = false set_lights = true set_electric_equipment = false set_gas_equipment = false set_ventilation = false set_infiltration = false - space_type.apply_internal_loads(template, set_people, set_lights, set_electric_equipment, set_gas_equipment, set_ventilation, set_infiltration) + space_type_apply_internal_loads(space_type, set_people, set_lights, set_electric_equipment, set_gas_equipment, set_ventilation, set_infiltration) end # 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. - getLightss.each do |lights| + model.getLightss.sort.each do |lights| if lights.schedule.empty? - lights.setSchedule(alwaysOffDiscreteSchedule) + lights.setSchedule(model.alwaysOffDiscreteSchedule) end end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Adding Daylighting Controls ***') # Run a sizing run to calculate VLT for layer-by-layer windows. - # Only necessary for 90.1-2010 daylighting control determination. - if template == '90.1-2010' - if runSizingRun("#{sizing_run_dir}/SRVLT") == false + if model_create_prm_baseline_building_requires_vlt_sizing_run(model) + if model_run_sizing_run(model, "#{sizing_run_dir}/SRVLT") == false return false end end # Add daylighting controls to each space - getSpaces.sort.each do |space| - added = space.add_daylighting_controls(template, false, false) + model.getSpaces.sort.each do |space| + added = space_add_daylighting_controls(space, false, false) end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Applying Baseline Constructions ***') # Modify some of the construction types as necessary - apply_prm_construction_types(template) + model_apply_prm_construction_types(model) # Set the construction properties of all the surfaces in the model - apply_standard_constructions(template, climate_zone) + model_apply_standard_constructions(model, climate_zone) # Get the groups of zones that define the # baseline HVAC systems for later use. # This must be done before removing the HVAC systems # because it requires knowledge of proposed HVAC fuels. OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Grouping Zones by Fuel Type and Occupancy Type ***') - sys_groups = prm_baseline_system_groups(template, custom) + sys_groups = model_prm_baseline_system_groups(model, custom) # Remove all HVAC from model, # excluding service water heating - remove_prm_hvac + model_remove_prm_hvac(model) # Modify the service water heating loops # per the baseline rules OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Cleaning up Service Water Heating Loops ***') - apply_baseline_swh_loops(template, building_type) + model_apply_baseline_swh_loops(model, building_type) # Determine the baseline HVAC system type for each of # the groups of zones and add that system type. OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Adding Baseline HVAC Systems ***') sys_groups.each do |sys_group| # Determine the primary baseline system type - system_type = prm_baseline_system_type(template, - climate_zone, - sys_group['occ'], - sys_group['fuel'], - sys_group['area_ft2'], - sys_group['stories'], - custom) + system_type = model_prm_baseline_system_type(model, + climate_zone, + sys_group['occ'], + sys_group['fuel'], + sys_group['area_ft2'], + sys_group['stories'], + custom) sys_group['zones'].sort.each_slice(5) do |zone_list| zone_names = [] zone_list.each do |zone| zone_names << zone.name.get.to_s end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "--- #{zone_names.join(', ')}") end # Add the system type for these zones - add_prm_baseline_system(template, - system_type[0], - system_type[1], - system_type[2], - system_type[3], - sys_group['zones']) + model_add_prm_baseline_system(model, + system_type[0], + system_type[1], + system_type[2], + system_type[3], + sys_group['zones']) end # Set the zone sizing SAT for each zone in the model - getThermalZones.each(&:apply_prm_baseline_supply_temperatures) + model.getThermalZones.each do |zone| + thermal_zone_apply_prm_baseline_supply_temperatures(zone) + end # Set the system sizing properties based on the zone sizing information - getAirLoopHVACs.each(&:apply_prm_sizing_temperatures) + model.getAirLoopHVACs.each do |air_loop| + air_loop_hvac_apply_prm_sizing_temperatures(air_loop) + end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Applying Baseline HVAC System Controls ***') # SAT reset, economizers - getAirLoopHVACs.sort.each do |air_loop| - air_loop.apply_prm_baseline_controls(template, climate_zone) + model.getAirLoopHVACs.sort.each do |air_loop| + air_loop_hvac_apply_prm_baseline_controls(air_loop, climate_zone) end # Apply the minimum damper positions, assuming no DDC control of VAV terminals - getAirLoopHVACs.sort.each do |air_loop| - air_loop.apply_minimum_vav_damper_positions(template, false) + model.getAirLoopHVACs.sort.each do |air_loop| + air_loop_hvac_apply_minimum_vav_damper_positions(air_loop, false) end # Apply the baseline system temperatures - getPlantLoops.sort.each do |plant_loop| + model.getPlantLoops.sort.each do |plant_loop| # Skip the SWH loops - next if plant_loop.swh_loop? - plant_loop.apply_prm_baseline_temperatures(template) + next if plant_loop_swh_loop?(plant_loop) + plant_loop_apply_prm_baseline_temperatures(plant_loop) end # Set the heating and cooling sizing parameters - apply_prm_sizing_parameters + model_apply_prm_sizing_parameters(model) # Run sizing run with the HVAC equipment - if runSizingRun("#{sizing_run_dir}/SR1") == false + if model_run_sizing_run(model, "#{sizing_run_dir}/SR1") == false return false end # If there are any multizone systems, reset damper positions # to achieve a 60% ventilation effectiveness minimum for the system # following the ventilation rate procedure from 62.1 - apply_multizone_vav_outdoor_air_sizing(template) + model_apply_multizone_vav_outdoor_air_sizing(model) # Set the baseline fan power for all airloops - getAirLoopHVACs.sort.each do |air_loop| - air_loop.apply_prm_baseline_fan_power(template) + model.getAirLoopHVACs.sort.each do |air_loop| + air_loop_hvac_apply_prm_baseline_fan_power(air_loop) end # Set the baseline fan power for all zone HVAC - getZoneHVACComponents.sort.each do |zone_hvac| - zone_hvac.apply_prm_baseline_fan_power(template) + model.getZoneHVACComponents.sort.each do |zone_hvac| + zone_hvac_component_apply_prm_baseline_fan_power(zone_hvac) end # Set the baseline number of boilers and chillers - getPlantLoops.sort.each do |plant_loop| + model.getPlantLoops.sort.each do |plant_loop| # Skip the SWH loops - next if plant_loop.swh_loop? - plant_loop.apply_prm_number_of_boilers(template) - plant_loop.apply_prm_number_of_chillers(template) + next if plant_loop_swh_loop?(plant_loop) + plant_loop_apply_prm_number_of_boilers(plant_loop) + plant_loop_apply_prm_number_of_chillers(plant_loop) end # Set the baseline number of cooling towers # Must be done after all chillers are added - getPlantLoops.sort.each do |plant_loop| + model.getPlantLoops.sort.each do |plant_loop| # Skip the SWH loops - next if plant_loop.swh_loop? - plant_loop.apply_prm_number_of_cooling_towers(template) + next if plant_loop_swh_loop?(plant_loop) + plant_loop_apply_prm_number_of_cooling_towers(plant_loop) end # Run sizing run with the new chillers, boilers, and # cooling towers to determine capacities - if runSizingRun("#{sizing_run_dir}/SR2") == false + if model_run_sizing_run(model, "#{sizing_run_dir}/SR2") == false return false end # Set the pumping control strategy and power # Must be done after sizing components - getPlantLoops.sort.each do |plant_loop| + model.getPlantLoops.sort.each do |plant_loop| # Skip the SWH loops - next if plant_loop.swh_loop? - plant_loop.apply_prm_baseline_pump_power(template) - plant_loop.apply_prm_baseline_pumping_type(template) + next if plant_loop_swh_loop?(plant_loop) + plant_loop_apply_prm_baseline_pump_power(plant_loop) + plant_loop_apply_prm_baseline_pumping_type(plant_loop) end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Applying Prescriptive HVAC Controls and Equipment Efficiencies ***') # Apply the HVAC efficiency standard - apply_hvac_efficiency_standard(template, climate_zone) + model_apply_hvac_efficiency_standard(model, climate_zone) + # Fix EMS references. + # Temporary workaround for OS issue #2598 + model_temp_fix_ems_references(model) + # Delete all the unused curves - getCurves.sort.each do |curve| - if curve.parent.empty? - # OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.Model', "#{curve.name} is unused; it will be removed.") - curve.remove + model.getCurves.sort.each do |curve| + if curve.directUseCount == 0 + OpenStudio::logFree(OpenStudio::Debug, 'openstudio.standards.Model', "#{curve.name} is unused; it will be removed.") + model.removeObject(curve.handle) end end # 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 = 'final' - save(OpenStudio::Path.new("#{sizing_run_dir}/#{model_status}.osm"), true) + 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(self) + idf = forward_translator.translateModel(model) idf_path = OpenStudio::Path.new("#{sizing_run_dir}/#{model_status}.idf") idf.save(idf_path, true) return true 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. + def model_create_prm_baseline_building_requires_vlt_sizing_run(model) + return false # Not required for most templates + end + # Determine the residential and nonresidential floor areas # based on the space type properties for each space. # For spaces with no space type, assume nonresidential. # # @return [Hash] keys are 'residential' and 'nonresidential', units are m^2 - def residential_and_nonresidential_floor_areas(template) + def model_residential_and_nonresidential_floor_areas(model) res_area_m2 = 0 nonres_area_m2 = 0 - getSpaces.each do |space| - if space.residential?(template) + model.getSpaces.sort.each do |space| + if thermal_zone_residential?(space) res_area_m2 += space.floorArea else nonres_area_m2 += space.floorArea end end @@ -356,11 +276,11 @@ # floor multiplier and increase the number of stories accordingly. # Stories do not have to be contiguous. # # @param zones [Array<OpenStudio::Model::ThermalZone>] an array of zones # @return [Integer] the number of stories spanned - def num_stories_spanned(zones) + def model_num_stories_spanned(model, zones) # Get the story object for all zones stories = [] zones.each do |zone| zone.spaces.each do |space| story = space.buildingStory @@ -373,34 +293,34 @@ stories = stories.uniq # Tally up stories including multipliers num_stories = 0 stories.each do |story| - num_stories += story.floor_multiplier + num_stories += building_story_floor_multiplier(story) end return num_stories end # Categorize zones by occupancy type and fuel type, # where the types depend on the standard. # # @return [Array<Hash>] an array of hashes, one for each zone, # with the keys 'zone', 'type' (occ type), 'fuel', and 'area' - def zones_with_occ_and_fuel_type(template, custom) + def model_zones_with_occ_and_fuel_type(model, custom) zones = [] - getThermalZones.sort.each do |zone| + model.getThermalZones.sort.each do |zone| # Skip plenums - if zone.plenum? + if thermal_zone_plenum?(zone) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Zone #{zone.name} is a plenum. It will not be assigned a baseline system.") next end # Skip unconditioned zones - heated = zone.heated? - cooled = zone.cooled? + heated = thermal_zone_heated?(zone) + cooled = thermal_zone_cooled?(zone) if !heated && !cooled OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Zone #{zone.name} is unconditioned. It will not be assigned a baseline system.") next end @@ -411,47 +331,32 @@ # Floor area zn_hash['area'] = zone.floorArea # Occupancy type - zn_hash['occ'] = zone.occupancy_type(template) + zn_hash['occ'] = thermal_zone_occupancy_type(zone) # Fuel type - zn_hash['fuel'] = zone.fossil_or_electric_type(custom) + zn_hash['fuel'] = thermal_zone_fossil_or_electric_type(zone, custom) zones << zn_hash end return zones end # Determine the dominant and exceptional areas of the # building based on fuel types and occupancy types. # - # @param template [String] the template. Valid choices are 90.1-2004, 90.1-2007, 90.1-2010, 90.1-2013. # @return [Array<Hash>] an array of hashes of area information, # with keys area_ft2, type, fuel, and zones (an array of zones) - def prm_baseline_system_groups(template, custom) - + def model_prm_baseline_system_groups(model, custom) # Define the minimum area for the # exception that allows a different # system type in part of the building. - # This is common across different versions - # of 90.1 - exception_min_area_ft2 = nil - case template - when '90.1-2004', '90.1-2007', '90.1-2010' - exception_min_area_ft2 = 20_000 - when '90.1-2013' - exception_min_area_ft2 = 20_000 - # Customization - Xcel EDA Program Manual 2014 - # 3.2.1 Mechanical System Selection ii - if custom == 'Xcel Energy CO EDA' - 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.") - end - end + 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 # Get occupancy type, fuel type, and area information for all zones, # excluding unconditioned zones. # Occupancy types are: # Residential @@ -462,15 +367,15 @@ # Fuel types are: # fossil # electric # (and for Xcel Energy CO EDA) # fossilandelectric - zones = zones_with_occ_and_fuel_type(template, custom) + zones = model_zones_with_occ_and_fuel_type(model, custom) # Ensure that there is at least one conditioned zone if zones.size.zero? - OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "The building does not appear to have any conditioned zones. Make sure zones have thermostat with appropriate heating and cooling setpoint schedules.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', 'The building does not appear to have any conditioned zones. Make sure zones have thermostat with appropriate heating and cooling setpoint schedules.') return [] end # Group the zones by occupancy type type_to_area = Hash.new { 0.0 } @@ -488,33 +393,32 @@ dom_occ_group = zones_grouped_by_occ[dom_occ] # Check the non-dominant occupancy type groups to see if they # are big enough to trigger the occupancy exception. # If they are, leave the group standing alone. - # If they are not, add the zones in that group + # If they are not, add the zones in that group # back to the dominant occupancy type group. occ_groups = [] zones_grouped_by_occ.each do |occ_type, zns| # Skip the dominant occupancy type next if occ_type == dom_occ - + # Add up the floor area of the group area_m2 = 0 zns.each do |zn| area_m2 += zn['area'] end area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get - + # If the non-dominant group is big enough, preserve that group. if area_ft2 > exception_min_area_ft2 occ_groups << [occ_type, zns] OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The portion of the building with an occupancy type of #{occ_type} is bigger than the minimum exception area of #{exception_min_area_ft2.round} ft2. It will be assigned a separate HVAC system type.") - # Otherwise, add the zones back to the dominant group. + # Otherwise, add the zones back to the dominant group. else dom_occ_group += zns end - end # Add the dominant occupancy group to the list occ_groups << [dom_occ, dom_occ_group] # Inside of each remaining occupancy group, @@ -536,12 +440,12 @@ # Determine the dominant fuel type # from the subset of the dominant area type zones fuel_to_area = Hash.new { 0.0 } zones_grouped_by_fuel = dom_occ_zns.group_by { |z| z['fuel'] } - zones_grouped_by_fuel.each do |fuel, zns| - zns.each do |zn| + zones_grouped_by_fuel.each do |fuel, zns_by_fuel| + zns_by_fuel.each do |zn| fuel_to_area[fuel] += zn['area'] end end sorted_by_area = fuel_to_area.sort_by { |k, v| v }.reverse @@ -551,11 +455,11 @@ # go to the next biggest if dom_fuel == 'unconditioned' if sorted_by_area.size > 1 dom_fuel = sorted_by_area[1][0] else - OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "The fuel type was not able to be determined for any zones in this model. Run with debug messages enabled to see possible reasons.") + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', 'The fuel type was not able to be determined for any zones in this model. Run with debug messages enabled to see possible reasons.') return [] end end # Get the dominant fuel type group @@ -569,70 +473,67 @@ dom_fuel_group['zones'] += nondom_occ_zns # Check the non-dominant occupancy type groups to see if they # are big enough to trigger the occupancy exception. # If they are, leave the group standing alone. - # If they are not, add the zones in that group + # If they are not, add the zones in that group # back to the dominant occupancy type group. - zones_grouped_by_fuel.each do |fuel_type, zns| + zones_grouped_by_fuel.each do |fuel_type, zns_by_fuel| # Skip the dominant occupancy type next if fuel_type == dom_fuel # Add up the floor area of the group area_m2 = 0 - zns.each do |zn| + zns_by_fuel.each do |zn| area_m2 += zn['area'] end area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get # If the non-dominant group is big enough, preserve that group. if area_ft2 > exception_min_area_ft2 group = {} group['occ'] = occ_type group['fuel'] = fuel_type - group['zones'] = zns + group['zones'] = zns_by_fuel occ_and_fuel_groups << group OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The portion of the building with an occupancy type of #{occ_type} and fuel type of #{fuel_type} is bigger than the minimum exception area of #{exception_min_area_ft2.round} ft2. It will be assigned a separate HVAC system type.") - # Otherwise, add the zones back to the dominant group. + # Otherwise, add the zones back to the dominant group. else - dom_fuel_group['zones'] += zns + dom_fuel_group['zones'] += zns_by_fuel end - end # Add the dominant occupancy group to the list - occ_and_fuel_groups << dom_fuel_group - + occ_and_fuel_groups << dom_fuel_group end # Moved heated-only zones into their own groups. # Per the PNNL PRM RM, this must be done AFTER # the dominant occ and fuel types are determined # so that heated-only zone areas are part of # the determination. final_groups = [] occ_and_fuel_groups.each do |gp| - # Skip unconditioned groups next if gp['fuel'] == 'unconditioned' heated_only_zones = [] heated_cooled_zones = [] gp['zones'].each do |zn| - if zn['zone'].heated? && !zn['zone'].cooled? + if thermal_zone_heated?(zn['zone']) && !thermal_zone_cooled?(zn['zone']) heated_only_zones << zn else heated_cooled_zones << zn end end gp['zones'] = heated_cooled_zones # Add the group (less unheated zones) to the final list final_groups << gp - + # If there are any heated-only zones, create # a new group for them. - if heated_only_zones.size > 0 + unless heated_only_zones.empty? htd_only_group = {} htd_only_group['occ'] = 'heatedonly' htd_only_group['fuel'] = gp['fuel'] htd_only_group['zones'] = heated_only_zones final_groups << htd_only_group @@ -651,22 +552,21 @@ area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get gp['area_ft2'] = area_ft2 gp['zones'] = gp_zns end - # TODO Remove the secondary zones before + # TODO: Remove the secondary zones before # determining the area used to pick the HVAC # system, per PNNL PRM RM - - + # 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 = [] - getThermalZones.each do |zone| + model.getThermalZones.sort.each do |zone| all_htg_fuels += zone.heating_fuels all_clg_fuels += zone.cooling_fuels end purchased_heating = false @@ -684,19 +584,19 @@ # Categorize district_fuel = nil if purchased_heating && purchased_cooling district_fuel = 'purchasedheatandcooling' - OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The proposed model included purchased heating and cooling. All baseline building system selection will be based on this information.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', 'The proposed model included purchased heating and cooling. All baseline building system selection will be based on this information.') elsif purchased_heating && !purchased_cooling district_fuel = 'purchasedheat' - OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The proposed model included purchased heating. All baseline building system selection will be based on this information.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', 'The proposed model included purchased heating. All baseline building system selection will be based on this information.') elsif !purchased_heating && purchased_cooling district_fuel = 'purchasedcooling' - OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The proposed model included purchased cooling. All baseline building system selection will be based on this information.") - end - + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', 'The proposed model included purchased cooling. All baseline building system selection will be based on this information.') + end + # Change the fuel in all final groups # if district systems were found. if district_fuel final_groups.each do |gp| gp['fuel'] = district_fuel @@ -705,11 +605,11 @@ # Determine the number of stories spanned # by each group and report out info. final_groups.each do |group| # Determine the number of stories this group spans - num_stories = num_stories_spanned(group['zones']) + num_stories = model_num_stories_spanned(model, group['zones']) group['stories'] = num_stories # Report out the final grouping OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Final system type group: occ = #{group['occ']}, fuel = #{group['fuel']}, area = #{group['area_ft2'].round} ft2, num stories = #{group['stories']}, zones:") group['zones'].sort.each_slice(5) do |zone_list| zone_names = [] @@ -721,14 +621,22 @@ end return final_groups end + # Determines the area of the building above which point + # the non-dominant area type gets it's own HVAC system 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. # - # @param template [String] Valid choices are 90.1-2004, # 90.1-2007, 90.1-2010, 90.1-2013 # @param area_type [String] Valid choices are residential, # nonresidential, and heatedonly # @param fuel_type [String] Valid choices are # electric, fossil, fossilandelectric, @@ -737,131 +645,20 @@ # @param num_stories [Integer] Number of stories # @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 prm_baseline_system_type(template, climate_zone, area_type, fuel_type, area_ft2, num_stories, custom) + def model_prm_baseline_system_type(model, climate_zone, area_type, fuel_type, area_ft2, num_stories, custom) # [type, central_heating_fuel, zone_heating_fuel, cooling_fuel] system_type = [nil, nil, nil, nil] - # Customization - Xcel EDA Program Manual 2014 - # Table 3.2.2 Baseline HVAC System Types - if custom == 'Xcel Energy CO EDA' - template = '90.1-2010' - 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 lookup for HVAC system types shall be used.') - end - if custom == "90.1-2007 with addenda dn" - template = '90.1-2010' - OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', 'Custom; per Addenda dn of 90.1-2007, System 10 and 11 (same as system 9 and 10 in 90.1-2010) will be used for heated only space.') - end - # Get the row from TableG3.1.1A - sys_num = nil - case template - when '90.1-2004', '90.1-2007' - # Set the limit differently for - # different codes - limit_ft2 = 25_000 - limit_ft2 = 75_000 if template == '90.1-2004' + sys_num = model_prm_baseline_system_number(model, climate_zone, area_type, fuel_type, area_ft2, num_stories, custom) - # Warn about heated only - if area_type == 'heatedonly' - OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Per Table G3.1.10.d, '(In the proposed building) Where no cooling system exists or no cooling system has been specified, the cooling system shall be identical to the system modeled in the baseline building design.' This requires that you go back and add a cooling system to the proposed model. This code cannot do that for you; you must do it manually.") - end + # 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) - case area_type - when 'residential' - sys_num = '1_or_2' - when 'nonresidential', 'heatedonly' - # nonresidential and 3 floors or less and <25,000 ft2 - if num_stories <= 3 && area_ft2 < limit_ft2 - sys_num = '3_or_4' - # nonresidential and 4 or 5 floors or 5 floors or less and 25,000 ft2 to 150,000 ft2 - elsif ((num_stories == 4 || num_stories == 5) && area_ft2 < limit_ft2) || (num_stories <= 5 && (area_ft2 >= limit_ft2 && area_ft2 <= 150_000)) - sys_num = '5_or_6' - # nonresidential and more than 5 floors or >150,000 ft2 - elsif num_stories >= 5 || area_ft2 > 150_000 - sys_num = '7_or_8' - end - end - - when '90.1-2010' - - limit_ft2 = 25_000 - - # Customization for Xcel EDA. - # No special retail category - # for regular 90.1-2010. - unless custom == 'Xcel Energy CO EDA' - if area_type == 'retail' - area_type = 'nonresidential' - end - end - - case area_type - when 'residential' - sys_num = '1_or_2' - when 'nonresidential' - # nonresidential and 3 floors or less and <25,000 ft2 - if num_stories <= 3 && area_ft2 < limit_ft2 - sys_num = '3_or_4' - # nonresidential and 4 or 5 floors or 5 floors or less and 25,000 ft2 to 150,000 ft2 - elsif ((num_stories == 4 || num_stories == 5) && area_ft2 < limit_ft2) || (num_stories <= 5 && (area_ft2 >= limit_ft2 && area_ft2 <= 150_000)) - sys_num = '5_or_6' - # nonresidential and more than 5 floors or >150,000 ft2 - elsif num_stories >= 5 || area_ft2 > 150_000 - sys_num = '7_or_8' - end - when 'heatedonly' - sys_num = '9_or_10' - when 'retail' - # Should only be hit by Xcel EDA - sys_num = '3_or_4' - end - - when '90.1-2013' - - limit_ft2 = 25_000 - - case area_type - when 'residential' - sys_num = '1_or_2' - when 'nonresidential' - # nonresidential and 3 floors or less and <25,000 ft2 - if num_stories <= 3 && area_ft2 < limit_ft2 - sys_num = '3_or_4' - # nonresidential and 4 or 5 floors or 5 floors or less and 25,000 ft2 to 150,000 ft2 - elsif ((num_stories == 4 || num_stories == 5) && area_ft2 < limit_ft2) || (num_stories <= 5 && (area_ft2 >= limit_ft2 && area_ft2 <= 150_000)) - sys_num = '5_or_6' - # nonresidential and more than 5 floors or >150,000 ft2 - elsif num_stories >= 5 || area_ft2 > 150_000 - sys_num = '7_or_8' - end - when 'heatedonly' - sys_num = '9_or_10' - when 'retail' - sys_num = '3_or_4' - end - - end - - # For 90.1-2013 the fuel type is determined based on climate zone. - # Don't change the fuel if it purchased heating or cooling. - if template == '90.1-2013' - if fuel_type == 'electric' || fuel_type == 'fossil' - case climate_zone - when 'ASHRAE 169-2006-1A', - 'ASHRAE 169-2006-2A', - 'ASHRAE 169-2006-3A' - fuel_type = 'electric' - else - fuel_type = 'fossil' - end - OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Heating fuel is #{fuel_type} for 90.1-2013, climate zone #{climate_zone}. This is independent of the heating fuel type in the proposed building, per G3.1.1-3. This is different than previous versions of 90.1.") - end - 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 sys_lookup['1_or_2']['fossil'] = ['PTAC', 'NaturalGas', nil, 'Electricity'] @@ -910,76 +707,105 @@ end return system_type end + # Determines which system number is used + # for the baseline system. Default is 90.1-2004 approach. + # @return [String] the system number: 1_or_2, 3_or_4, + # 5_or_6, 7_or_8, 9_or_10 + def model_prm_baseline_system_number(model, climate_zone, area_type, fuel_type, area_ft2, num_stories, custom) + sys_num = nil + # Set the area limit + limit_ft2 = 75_000 + + # Warn about heated only + if area_type == 'heatedonly' + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Per Table G3.1.10.d, '(In the proposed building) Where no cooling system exists or no cooling system has been specified, the cooling system shall be identical to the system modeled in the baseline building design.' This requires that you go back and add a cooling system to the proposed model. This code cannot do that for you; you must do it manually.") + end + + case area_type + when 'residential' + sys_num = '1_or_2' + when 'nonresidential', 'heatedonly' + # nonresidential and 3 floors or less and <25,000 ft2 + if num_stories <= 3 && area_ft2 < limit_ft2 + sys_num = '3_or_4' + # nonresidential and 4 or 5 floors or 5 floors or less and 25,000 ft2 to 150,000 ft2 + elsif ((num_stories == 4 || num_stories == 5) && area_ft2 < limit_ft2) || (num_stories <= 5 && (area_ft2 >= limit_ft2 && area_ft2 <= 150_000)) + sys_num = '5_or_6' + # nonresidential and more than 5 floors or >150,000 ft2 + elsif num_stories >= 5 || area_ft2 > 150_000 + sys_num = '7_or_8' + end + end + + return sys_num + end + + # Change the fuel type based on climate zone, depending on the standard. + # Defaults to no change. + # @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 + end + # Add the specified baseline system type to the # specified zons based on the specified template. # For some multi-zone system types, the standards require # identifying zones whose loads or schedules # are outliers and putting these systems on separate # single-zone systems. This method does that. # - # @param template [String] Valid choices are 90.1-2004, - # 90.1-2007, 90.1-2010, 90.1-2013 - # @param area_type [String] Valid choices are residential, - # nonresidential, and heatedonly - # @param heating_fuel_type [String] Valid choices are - # electric and fossil - # @param area_ft2 [Double] Area in ft^2 - # @param num_stories [Integer] Number of stories # @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 main_heat_fuel [String] zone heating/reheat fuel. Valid choices are + # @param zone_heat_fuel [String] zone heating/reheat fuel. Valid choices are # Electricity, NaturalGas, DistrictHeating - # @param main_heat_fuel [String] cooling fuel. Valid choices are + # @param cool_fuel [String] cooling fuel. Valid choices are # Electricity, DistrictCooling # @todo add 90.1-2013 systems 11-13 - def add_prm_baseline_system(template, system_type, main_heat_fuel, zone_heat_fuel, cool_fuel, zones) - case template - when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' - - case system_type + def model_add_prm_baseline_system(model, system_type, main_heat_fuel, zone_heat_fuel, cool_fuel, zones) + case system_type when 'PTAC' # System 1 unless zones.empty? # Retrieve the existing hot water loop # or add a new one if necessary. hot_water_loop = nil - hot_water_loop = if getPlantLoopByName('Hot Water Loop').is_initialized - getPlantLoopByName('Hot Water Loop').get + hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized + model.getPlantLoopByName('Hot Water Loop').get else - add_hw_loop(main_heat_fuel) + model_add_hw_loop(model, main_heat_fuel) end # Add a hot water PTAC to each zone - add_ptac(template, - nil, - hot_water_loop, - zones, - 'ConstantVolume', - 'Water', - 'Single Speed DX AC') + model_add_ptac(model, + nil, + hot_water_loop, + zones, + 'ConstantVolume', + 'Water', + 'Single Speed DX AC') end when 'PTHP' # System 2 unless zones.empty? # Add an air-source packaged terminal # heat pump with electric supplemental heat # to each zone. - add_pthp(template, - nil, - zones, - 'ConstantVolume') + model_add_pthp(model, + nil, + zones, + 'ConstantVolume') end when 'PSZ_AC' # System 3 @@ -988,485 +814,484 @@ heating_type = 'Gas' # If district heating hot_water_loop = nil if main_heat_fuel == 'DistrictHeating' heating_type = 'Water' - hot_water_loop = if getPlantLoopByName('Hot Water Loop').is_initialized - getPlantLoopByName('Hot Water Loop').get + hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized + model.getPlantLoopByName('Hot Water Loop').get else - add_hw_loop(main_heat_fuel) + model_add_hw_loop(model, main_heat_fuel) end end cooling_type = 'Single Speed DX AC' # If district cooling chilled_water_loop = nil if cool_fuel == 'DistrictCooling' cooling_type = 'Water' - chilled_water_loop = if getPlantLoopByName('Chilled Water Loop').is_initialized - getPlantLoopByName('Chilled Water Loop').get + chilled_water_loop = if model.getPlantLoopByName('Chilled Water Loop').is_initialized + model.getPlantLoopByName('Chilled Water Loop').get else - add_chw_loop(template, - 'const_pri', - chiller_cooling_type = nil, - chiller_condenser_type = nil, - chiller_compressor_type = nil, - cool_fuel, - condenser_water_loop = nil, - building_type = nil) + model_add_chw_loop(model, + 'const_pri', + chiller_cooling_type = nil, + chiller_condenser_type = nil, + chiller_compressor_type = nil, + cool_fuel, + condenser_water_loop = nil, + building_type = nil) end end # Add a gas-fired PSZ-AC to each zone # hvac_op_sch=nil means always on # oa_damper_sch to nil means always open - add_psz_ac(template, - sys_name = nil, - hot_water_loop, - chilled_water_loop, - zones, - hvac_op_sch = nil, - oa_damper_sch = nil, - fan_location = 'DrawThrough', - fan_type = 'ConstantVolume', - heating_type, - supplemental_heating_type = 'Gas', # Should we really add supplemental heating here? - cooling_type, - building_type = nil) + model_add_psz_ac(model, + sys_name = nil, + hot_water_loop, + chilled_water_loop, + zones, + hvac_op_sch = nil, + oa_damper_sch = nil, + fan_location = 'DrawThrough', + fan_type = 'ConstantVolume', + heating_type, + supplemental_heating_type = 'Gas', # Should we really add supplemental heating here? + cooling_type, + building_type = nil) end when 'PSZ_HP' # System 4 unless zones.empty? # Add an air-source packaged single zone # heat pump with electric supplemental heat # to each zone. - add_psz_ac(template, - 'PSZ-HP', - nil, - nil, - zones, - nil, - nil, - 'DrawThrough', - 'ConstantVolume', - 'Single Speed Heat Pump', - 'Electric', - 'Single Speed Heat Pump', - building_type = nil) + model_add_psz_ac(model, + 'PSZ-HP', + nil, + nil, + zones, + nil, + nil, + 'DrawThrough', + 'ConstantVolume', + 'Single Speed Heat Pump', + 'Electric', + 'Single Speed Heat Pump', + building_type = nil) end when 'PVAV_Reheat' # System 5 # Retrieve the existing hot water loop # or add a new one if necessary. hot_water_loop = nil - hot_water_loop = if getPlantLoopByName('Hot Water Loop').is_initialized - getPlantLoopByName('Hot Water Loop').get + hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized + model.getPlantLoopByName('Hot Water Loop').get else - add_hw_loop(main_heat_fuel) + model_add_hw_loop(model, main_heat_fuel) end # If district cooling chilled_water_loop = nil if cool_fuel == 'DistrictCooling' - chilled_water_loop = if getPlantLoopByName('Chilled Water Loop').is_initialized - getPlantLoopByName('Chilled Water Loop').get + chilled_water_loop = if model.getPlantLoopByName('Chilled Water Loop').is_initialized + model.getPlantLoopByName('Chilled Water Loop').get else - add_chw_loop(template, - 'const_pri', - chiller_cooling_type = nil, - chiller_condenser_type = nil, - chiller_compressor_type = nil, - cool_fuel, - condenser_water_loop = nil, - building_type = nil) + model_add_chw_loop(model, + 'const_pri', + chiller_cooling_type = nil, + chiller_condenser_type = nil, + chiller_compressor_type = nil, + cool_fuel, + condenser_water_loop = nil, + building_type = nil) end end # If electric zone heat electric_reheat = false if zone_heat_fuel == 'Electricity' electric_reheat = true end # Group zones by story - story_zone_lists = group_zones_by_story(zones) + story_zone_lists = model_group_zones_by_story(model, zones) # For the array of zones on each story, # separate the primary zones from the secondary zones. # Add the baseline system type to the primary zones # and add the suplemental system type to the secondary zones. story_zone_lists.each do |story_group| # Differentiate primary and secondary zones - pri_sec_zone_lists = differentiate_primary_secondary_thermal_zones(story_group) + pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group) pri_zones = pri_sec_zone_lists['primary'] sec_zones = pri_sec_zone_lists['secondary'] # Add a PVAV with Reheat for the primary zones stories = [] story_group[0].spaces.each do |space| - stories << [space.buildingStory.get.name.get, space.buildingStory.get.minimum_z_value] + stories << [space.buildingStory.get.name.get, building_story_minimum_z_value(space.buildingStory.get)] end - story_name = stories.sort_by{ |nm, z| z }[0][0] + story_name = stories.sort_by { |nm, z| z }[0][0] sys_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? - add_pvav(template, - sys_name, - pri_zones, - nil, - nil, - electric_reheat, - hot_water_loop, - chilled_water_loop, - nil, - nil) + model_add_pvav(model, + sys_name, + pri_zones, + nil, + nil, + electric_reheat, + hot_water_loop, + chilled_water_loop, + nil, + nil) end # Add a PSZ_AC for each secondary zone unless sec_zones.empty? - add_prm_baseline_system(template, 'PSZ_AC', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones) + model_add_prm_baseline_system(model, 'PSZ_AC', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones) end end when 'PVAV_PFP_Boxes' # System 6 # If district cooling chilled_water_loop = nil if cool_fuel == 'DistrictCooling' - chilled_water_loop = if getPlantLoopByName('Chilled Water Loop').is_initialized - getPlantLoopByName('Chilled Water Loop').get + chilled_water_loop = if model.getPlantLoopByName('Chilled Water Loop').is_initialized + model.getPlantLoopByName('Chilled Water Loop').get else - add_chw_loop(template, - 'const_pri', - chiller_cooling_type = nil, - chiller_condenser_type = nil, - chiller_compressor_type = nil, - cool_fuel, - condenser_water_loop = nil, - building_type = nil) + model_add_chw_loop(model, + 'const_pri', + chiller_cooling_type = nil, + chiller_condenser_type = nil, + chiller_compressor_type = nil, + cool_fuel, + condenser_water_loop = nil, + building_type = nil) end end # Group zones by story - story_zone_lists = group_zones_by_story(zones) + story_zone_lists = model_group_zones_by_story(model, zones) # For the array of zones on each story, # separate the primary zones from the secondary zones. # Add the baseline system type to the primary zones # and add the suplemental system type to the secondary zones. story_zone_lists.each do |story_group| # Differentiate primary and secondary zones - pri_sec_zone_lists = differentiate_primary_secondary_thermal_zones(story_group) + pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group) pri_zones = pri_sec_zone_lists['primary'] sec_zones = pri_sec_zone_lists['secondary'] # Add an VAV for the primary zones stories = [] story_group[0].spaces.each do |space| - stories << [space.buildingStory.get.name.get, space.buildingStory.get.minimum_z_value] + stories << [space.buildingStory.get.name.get, building_story_minimum_z_value(space.buildingStory.get)] end - story_name = stories.sort_by{ |nm, z| z }[0][0] + story_name = stories.sort_by { |nm, z| z }[0][0] sys_name = "#{story_name} PVAV_PFP_Boxes (Sys6)" # If and only if there are primary zones to attach to the loop unless pri_zones.empty? - add_pvav_pfp_boxes(template, - sys_name, - pri_zones, - nil, - nil, - 0.62, - 0.9, - OpenStudio.convert(4.0, 'inH_{2}O', 'Pa').get, - chilled_water_loop, - nil) + model_add_pvav_pfp_boxes(model, + sys_name, + pri_zones, + nil, + nil, + 0.62, + 0.9, + OpenStudio.convert(4.0, 'inH_{2}O', 'Pa').get, + chilled_water_loop, + nil) end # Add a PSZ_HP for each secondary zone unless sec_zones.empty? - add_prm_baseline_system(template, 'PSZ_HP', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones) + model_add_prm_baseline_system(model, 'PSZ_HP', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones) end end when 'VAV_Reheat' # System 7 # Retrieve the existing hot water loop # or add a new one if necessary. hot_water_loop = nil - hot_water_loop = if getPlantLoopByName('Hot Water Loop').is_initialized - getPlantLoopByName('Hot Water Loop').get + hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized + model.getPlantLoopByName('Hot Water Loop').get else - add_hw_loop(main_heat_fuel) + model_add_hw_loop(model, main_heat_fuel) end # Retrieve the existing chilled water loop # or add a new one if necessary. chilled_water_loop = nil - if getPlantLoopByName('Chilled Water Loop').is_initialized - chilled_water_loop = getPlantLoopByName('Chilled Water Loop').get + if model.getPlantLoopByName('Chilled Water Loop').is_initialized + chilled_water_loop = model.getPlantLoopByName('Chilled Water Loop').get else if cool_fuel == 'DistrictCooling' - chilled_water_loop = add_chw_loop(template, - 'const_pri', - chiller_cooling_type = nil, - chiller_condenser_type = nil, - chiller_compressor_type = nil, - cool_fuel, - condenser_water_loop = nil, - building_type = nil) + chilled_water_loop = model_add_chw_loop(model, + 'const_pri', + chiller_cooling_type = nil, + chiller_condenser_type = nil, + chiller_compressor_type = nil, + cool_fuel, + condenser_water_loop = nil, + building_type = nil) else - fan_type = 'TwoSpeed Fan' - if template == '90.1-2013' - fan_type = 'Variable Speed Fan' - end - condenser_water_loop = add_cw_loop(template, - 'Open Cooling Tower', - 'Propeller or Axial', - fan_type, - 1, - 1, - nil) - chilled_water_loop = add_chw_loop(template, - 'const_pri_var_sec', - 'WaterCooled', - chiller_condenser_type = nil, - 'Rotary Screw', - cooling_fuel = nil, - condenser_water_loop, - building_type = nil) + fan_type = model_cw_loop_cooling_tower_fan_type(model) + condenser_water_loop = model_add_cw_loop(model, + 'Open Cooling Tower', + 'Propeller or Axial', + fan_type, + 1, + 1, + nil) + chilled_water_loop = model_add_chw_loop(model, + 'const_pri_var_sec', + 'WaterCooled', + chiller_condenser_type = nil, + 'Rotary Screw', + cooling_fuel = nil, + condenser_water_loop, + building_type = nil) end end # If electric zone heat reheat_type = 'Water' if zone_heat_fuel == 'Electricity' reheat_type = 'Electricity' end # Group zones by story - story_zone_lists = group_zones_by_story(zones) + story_zone_lists = model_group_zones_by_story(model, zones) # For the array of zones on each story, # separate the primary zones from the secondary zones. # Add the baseline system type to the primary zones # and add the suplemental system type to the secondary zones. story_zone_lists.each do |story_group| - # The group_zones_by_story NO LONGER returns empty lists when a given floor doesn't have any of the zones + # The model_group_zones_by_story(model) NO LONGER returns empty lists when a given floor doesn't have any of the zones # So NO need to filter it out otherwise you get an error undefined method `spaces' for nil:NilClass # next if zones.empty? # Differentiate primary and secondary zones - pri_sec_zone_lists = differentiate_primary_secondary_thermal_zones(story_group) + pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group) pri_zones = pri_sec_zone_lists['primary'] sec_zones = pri_sec_zone_lists['secondary'] # Add a VAV for the primary zones stories = [] story_group[0].spaces.each do |space| - stories << [space.buildingStory.get.name.get, space.buildingStory.get.minimum_z_value] + stories << [space.buildingStory.get.name.get, building_story_minimum_z_value(space.buildingStory.get)] end - story_name = stories.sort_by{ |nm, z| z }[0][0] + story_name = stories.sort_by { |nm, z| z }[0][0] sys_name = "#{story_name} VAV_Reheat (Sys7)" # 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? - add_vav_reheat(template, - sys_name, - hot_water_loop, - chilled_water_loop, - pri_zones, - nil, - nil, - 0.62, - 0.9, - OpenStudio.convert(4.0, 'inH_{2}O', 'Pa').get, - nil, - reheat_type, - nil) + model_add_vav_reheat(model, + sys_name, + hot_water_loop, + chilled_water_loop, + pri_zones, + nil, + nil, + 0.62, + 0.9, + OpenStudio.convert(4.0, 'inH_{2}O', 'Pa').get, + nil, + reheat_type, + nil) end # Add a PSZ_AC for each secondary zone unless sec_zones.empty? - add_prm_baseline_system(template, 'PSZ_AC', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones) + model_add_prm_baseline_system(model, 'PSZ_AC', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones) end end when 'VAV_PFP_Boxes' # System 8 # Retrieve the existing chilled water loop # or add a new one if necessary. chilled_water_loop = nil - if getPlantLoopByName('Chilled Water Loop').is_initialized - chilled_water_loop = getPlantLoopByName('Chilled Water Loop').get + if model.getPlantLoopByName('Chilled Water Loop').is_initialized + chilled_water_loop = model.getPlantLoopByName('Chilled Water Loop').get else if cool_fuel == 'DistrictCooling' - chilled_water_loop = add_chw_loop(template, - 'const_pri', - chiller_cooling_type = nil, - chiller_condenser_type = nil, - chiller_compressor_type = nil, - cool_fuel, - condenser_water_loop = nil, - building_type = nil) + chilled_water_loop = model_add_chw_loop(model, + 'const_pri', + chiller_cooling_type = nil, + chiller_condenser_type = nil, + chiller_compressor_type = nil, + cool_fuel, + condenser_water_loop = nil, + building_type = nil) else - fan_type = 'TwoSpeed Fan' - if template == '90.1-2013' - fan_type = 'Variable Speed Fan' - end - condenser_water_loop = add_cw_loop(template, - 'Open Cooling Tower', - 'Propeller or Axial', - fan_type, - 1, - 1, - nil) - chilled_water_loop = add_chw_loop(template, - 'const_pri_var_sec', - 'WaterCooled', - chiller_condenser_type = nil, - 'Rotary Screw', - cool_fueling = nil, - condenser_water_loop, - building_type = nil) + fan_type = model_cw_loop_cooling_tower_fan_type(model) + condenser_water_loop = model_add_cw_loop(model, + 'Open Cooling Tower', + 'Propeller or Axial', + fan_type, + 1, + 1, + nil) + chilled_water_loop = model_add_chw_loop(model, + 'const_pri_var_sec', + 'WaterCooled', + chiller_condenser_type = nil, + 'Rotary Screw', + cool_fueling = nil, + condenser_water_loop, + building_type = nil) end end # Group zones by story - story_zone_lists = group_zones_by_story(zones) + story_zone_lists = model_group_zones_by_story(model, zones) # For the array of zones on each story, # separate the primary zones from the secondary zones. # Add the baseline system type to the primary zones # and add the suplemental system type to the secondary zones. story_zone_lists.each do |story_group| # Differentiate primary and secondary zones - pri_sec_zone_lists = differentiate_primary_secondary_thermal_zones(story_group) + pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group) pri_zones = pri_sec_zone_lists['primary'] sec_zones = pri_sec_zone_lists['secondary'] # Add an VAV for the primary zones stories = [] story_group[0].spaces.each do |space| - stories << [space.buildingStory.get.name.get, space.buildingStory.get.minimum_z_value] + stories << [space.buildingStory.get.name.get, building_story_minimum_z_value(space.buildingStory.get)] end - story_name = stories.sort_by{ |nm, z| z }[0][0] + story_name = stories.sort_by { |nm, z| z }[0][0] sys_name = "#{story_name} VAV_PFP_Boxes (Sys8)" # If and only if there are primary zones to attach to the loop unless pri_zones.empty? - add_vav_pfp_boxes(template, - sys_name, - chilled_water_loop, - pri_zones, - nil, - nil, - 0.62, - 0.9, - OpenStudio.convert(4.0, 'inH_{2}O', 'Pa').get) + model_add_vav_pfp_boxes(model, + sys_name, + chilled_water_loop, + pri_zones, + nil, + nil, + 0.62, + 0.9, + OpenStudio.convert(4.0, 'inH_{2}O', 'Pa').get) end # Add a PSZ_HP for each secondary zone unless sec_zones.empty? - add_prm_baseline_system(template, 'PSZ_HP', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones) + model_add_prm_baseline_system(model, 'PSZ_HP', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones) end end when 'Gas_Furnace' # System 9 unless zones.empty? # If district heating hot_water_loop = nil if main_heat_fuel == 'DistrictHeating' - hot_water_loop = if getPlantLoopByName('Hot Water Loop').is_initialized - getPlantLoopByName('Hot Water Loop').get + hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized + model.getPlantLoopByName('Hot Water Loop').get else - add_hw_loop(main_heat_fuel) + model_add_hw_loop(model, main_heat_fuel) end end # Add a System 9 - Gas Unit Heater to each zone - add_unitheater(template, - nil, - zones, - nil, - 'ConstantVolume', - OpenStudio.convert(0.2, 'inH_{2}O', 'Pa').get, - main_heat_fuel, - hot_water_loop, - nil) + model_add_unitheater(model, + nil, + zones, + nil, + 'ConstantVolume', + OpenStudio.convert(0.2, 'inH_{2}O', 'Pa').get, + main_heat_fuel, + hot_water_loop, + nil) end when 'Electric_Furnace' # System 10 unless zones.empty? # Add a System 10 - Electric Unit Heater to each zone - add_unitheater(template, - nil, - zones, - nil, - 'ConstantVolume', - OpenStudio.convert(0.2, 'inH_{2}O', 'Pa').get, - main_heat_fuel, - nil, - nil) + model_add_unitheater(model, + nil, + zones, + nil, + 'ConstantVolume', + OpenStudio.convert(0.2, 'inH_{2}O', 'Pa').get, + main_heat_fuel, + nil, + nil) end else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "System type #{system_type} is not a valid choice, nothing will be added to the model.") + return false - end - end + return true end + # Determines the fan type used by VAV_Reheat and VAV_PFP_Boxes systems. + # Defaults to two speed fan. + # @return [String] the fan type: TwoSpeed Fan, Variable Speed Fan + def model_baseline_system_vav_fan_type(model) + fan_type = 'TwoSpeed Fan' + return fan_type + end + # Looks through the model and creates an hash of what the baseline # system type should be for each zone. # # @return [Hash] keys are zones, values are system type strings # PTHP, PTAC, PSZ_AC, PSZ_HP, PVAV_Reheat, PVAV_PFP_Boxes, # VAV_Reheat, VAV_PFP_Boxes, Gas_Furnace, Electric_Furnace - def get_baseline_system_type_by_zone(template, climate_zone, custom = nil) + def model_get_baseline_system_type_by_zone(model, climate_zone, custom = nil) zone_to_sys_type = {} # Get the groups of zones that define the # baseline HVAC systems for later use. # This must be done before removing the HVAC systems # because it requires knowledge of proposed HVAC fuels. - sys_groups = prm_baseline_system_groups(template, custom) + sys_groups = model_prm_baseline_system_groups(model, custom) # Assign building stories to spaces in the building # where stories are not yet assigned. - assign_spaces_to_stories + model_assign_spaces_to_stories(model) # Determine the baseline HVAC system type for each of # the groups of zones and add that system type. sys_groups.each do |sys_group| # Determine the primary baseline system type - pri_system_type = prm_baseline_system_type(template, - climate_zone, - sys_group['occ'], - sys_group['fuel'], - sys_group['area_ft2'], - sys_group['stories'], - custom)[0] + pri_system_type = model_prm_baseline_system_type(model, + climate_zone, + sys_group['occ'], + sys_group['fuel'], + sys_group['area_ft2'], + sys_group['stories'], + custom)[0] # Record the zone-by-zone system type assignments - case template - when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' - - case pri_system_type + case pri_system_type when 'PTAC', 'PTHP', 'PSZ_AC', 'PSZ_HP', 'Gas_Furnace', 'Electric_Furnace' sys_group['zones'].each do |zone| zone_to_sys_type[zone] = pri_system_type end @@ -1481,39 +1306,36 @@ when 'PVAV_PFP_Boxes', 'VAV_PFP_Boxes' sec_system_type = 'PSZ_HP' end # Group zones by story - story_zone_lists = group_zones_by_story(sys_group['zones']) + story_zone_lists = model_group_zones_by_story(model, sys_group['zones']) # For the array of zones on each story, # separate the primary zones from the secondary zones. # Add the baseline system type to the primary zones # and add the suplemental system type to the secondary zones. story_zone_lists.each do |zones| # Differentiate primary and secondary zones - pri_sec_zone_lists = differentiate_primary_secondary_thermal_zones(zones) + pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, zones) # Record the primary zone system types pri_sec_zone_lists['primary'].each do |zone| zone_to_sys_type[zone] = pri_system_type end # Record the secondary zone system types pri_sec_zone_lists['secondary'].each do |zone| zone_to_sys_type[zone] = sec_system_type end end - - end - end end return zone_to_sys_type end # @param array_of_zones [Array] an array of Hashes for each zone, # with the keys 'zone', - def eliminate_outlier_zones(array_of_zones, key_to_inspect, tolerance, field_name, units) + def model_eliminate_outlier_zones(model, array_of_zones, key_to_inspect, tolerance, field_name, units) # Sort the zones by the desired key array_of_zones = array_of_zones.sort_by { |hsh| hsh[key_to_inspect] } # Calculate the area-weighted average total = 0.0 @@ -1560,11 +1382,11 @@ if biggest_delta > tolerance zn_name = array_of_zones[biggest_delta_i]['zone'].name.get.to_s OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "For zone #{zn_name}, the #{field_name} of #{worst.round(1)} #{units} is more than #{tolerance} #{units} outside the area-weighted average of #{avg.round(1)} #{units}; it will be placed on its own secondary system.") array_of_zones.delete_at(biggest_delta_i) # Call method recursively if something was eliminated - array_of_zones = eliminate_outlier_zones(array_of_zones, key_to_inspect, tolerance, field_name, units) + array_of_zones = model_eliminate_outlier_zones(model, array_of_zones, key_to_inspect, tolerance, field_name, units) else # OpenStudio::logFree(OpenStudio::Debug, 'openstudio.standards.Model', "#{worst.round(1)} - #{avg.round(1)} = #{biggest_delta.round(1)} #{units} < tolerance of #{tolerance} #{units}, stopping elimination process.") OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "#{worst} - #{avg} = #{biggest_delta} #{units} < tolerance of #{tolerance} #{units}, stopping elimination process.") end @@ -1582,11 +1404,11 @@ # area-weighted average of all other zones # on the system by more than 10 Btu/hr*ft^2. # # @return [Hash] A hash of two arrays of ThermalZones, # where the keys are 'primary' and 'secondary' - def differentiate_primary_secondary_thermal_zones(zones) + def model_differentiate_primary_secondary_thermal_zones(model, zones) OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', 'Determining which zones are served by the primary vs. secondary HVAC system.') # Determine the operational hours (proxy is annual # full load lighting hours) for all zones zone_data_1 = [] @@ -1618,18 +1440,18 @@ # Skip lights with no schedule next if lights_sch.empty? lights_sch = lights_sch.get if lights_sch.to_ScheduleRuleset.is_initialized lights_sch = lights_sch.to_ScheduleRuleset.get - full_load_hrs = lights_sch.annual_equivalent_full_load_hrs + full_load_hrs = schedule_ruleset_annual_equivalent_full_load_hrs(lights_sch) if full_load_hrs > 0 ann_op_hrs = full_load_hrs break # Stop after the first schedule with more than 0 hrs end elsif lights_sch.to_ScheduleConstant.is_initialized lights_sch = lights_sch.to_ScheduleConstant.get - full_load_hrs = lights_sch.annual_equivalent_full_load_hrs + full_load_hrs = schedule_constant_annual_equivalent_full_load_hrs(lights_sch) if full_load_hrs > 0 ann_op_hrs = full_load_hrs break # Stop after the first schedule with more than 0 hrs end end @@ -1643,11 +1465,11 @@ end # Filter out any zones that operate differently by more # than 40hrs/wk. This will be determined by a difference of more # than (40 hrs/wk * 52 wks/yr) = 2080 annual full load hrs. - zones_same_hrs = eliminate_outlier_zones(zone_data_1, 'wk_op_hrs', 40, 'weekly operating hrs', 'hrs') + zones_same_hrs = model_eliminate_outlier_zones(model, zone_data_1, 'wk_op_hrs', 40, 'weekly operating hrs', 'hrs') # Get the internal loads for # all remaining zones. zone_data_2 = [] zones_same_hrs.each do |zn_data| @@ -1657,20 +1479,20 @@ # Get the area area_m2 = zone.floorArea * zone.multiplier area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get data['area_ft2'] = area_ft2 # Get the internal loads - int_load_w = zone.design_internal_load + int_load_w = thermal_zone_design_internal_load(zone) # Normalize per-area int_load_w_per_m2 = int_load_w / area_m2 int_load_btu_per_ft2 = OpenStudio.convert(int_load_w_per_m2, 'W/m^2', 'Btu/hr*ft^2').get data['int_load_btu_per_ft2'] = int_load_btu_per_ft2 zone_data_2 << data end # Filter out any zones that are +/- 10 Btu/hr*ft^2 from the average - pri_zn_data = eliminate_outlier_zones(zone_data_2, 'int_load_btu_per_ft2', 10, 'internal load', 'Btu/hr*ft^2') + pri_zn_data = model_eliminate_outlier_zones(model, zone_data_2, 'int_load_btu_per_ft2', 10, 'internal load', 'Btu/hr*ft^2') # Get just the primary zones themselves pri_zones = [] pri_zone_names = [] pri_zn_data.each do |zn_data| @@ -1702,14 +1524,14 @@ # Group an array of zones into multiple arrays, one # for each story in the building. Zones with spaces on multiple stories # will be assigned to only one of the stories. # Removes empty array (when the story doesn't contain any of the zones) # @return [Array<Array<OpenStudio::Model::ThermalZone>>] array of arrays of zones - def group_zones_by_story(zones) + def model_group_zones_by_story(model, zones) story_zone_lists = [] zones_already_assigned = [] - getBuildingStorys.sort.each do |story| + model.getBuildingStorys.sort.each do |story| # Get all the spaces on this story spaces = story.spaces # Get all the thermal zones that serve these spaces all_zones_on_story = [] @@ -1747,16 +1569,16 @@ # 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. # # @return [Bool] returns true if successful, false if not. - def assign_spaces_to_stories + def model_assign_spaces_to_stories(model) # Make hash of spaces and minz values sorted_spaces = {} - getSpaces.each do |space| + model.getSpaces.sort.each do |space| # Skip plenum spaces - next if space.plenum? + next if space_plenum?(space) # loop through space surfaces to find min z value z_points = [] space.surfaces.each do |surface| surface.vertices.each do |vertex| @@ -1766,18 +1588,18 @@ minz = z_points.min + space.zOrigin sorted_spaces[space] = minz end # Pre-sort spaces - sorted_spaces = sorted_spaces.sort { |a, b| a[1] <=> b[1] } + sorted_spaces = sorted_spaces.sort_by { |a| a[1] } # Take the sorted list and assign/make stories sorted_spaces.each do |space| space_obj = space[0] space_minz = space[1] if space_obj.buildingStory.empty? - story = get_story_for_nominal_z_coordinate(space_minz) + story = model_get_story_for_nominal_z_coordinate(model, space_minz) space_obj.setBuildingStory(story) OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Standards.Model', "Space #{space[0].name} was not assigned to a story by the user. It has been assigned to #{story.name}.") end end @@ -1788,37 +1610,36 @@ # Performance Rating Method (aka Appendix G aka LEED) and adds it to the model. # This method creates and adds the constructions and their materials as well. # # @param category [String] the construction set category desired. # Valid choices are Nonresidential, Residential, and Semiheated - # @param template [String] the template. Valid choices are 90.1-2004, 90.1-2007, 90.1-2010, 90.1-2013. # @return [OpenStudio::Model::DefaultConstructionSet] returns a default # construction set populated with the specified constructions. - def add_prm_construction_set(template, category) + def model_add_prm_construction_set(model, category) construction_set = OpenStudio::Model::OptionalDefaultConstructionSet.new # Find the climate zone set that this climate zone falls into - climate_zone_set = find_climate_zone_set(clim, template) + climate_zone_set = model_find_climate_zone_set(model, clim) unless climate_zone_set return construction_set end # Get the object data - data = find_object($os_standards['construction_sets'], 'template' => template, 'climate_zone_set' => climate_zone_set, 'building_type' => building_type, 'space_type' => spc_type, 'is_residential' => is_residential) + data = model_find_object(standards_data['construction_sets'], 'template' => template, 'climate_zone_set' => climate_zone_set, 'building_type' => building_type, 'space_type' => spc_type, 'is_residential' => is_residential) unless data - data = find_object($os_standards['construction_sets'], 'template' => template, 'climate_zone_set' => climate_zone_set, 'building_type' => building_type, 'space_type' => spc_type) + data = model_find_object(standards_data['construction_sets'], 'template' => template, 'climate_zone_set' => climate_zone_set, 'building_type' => building_type, 'space_type' => spc_type) unless data return construction_set end end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Adding construction set: #{template}-#{clim}-#{building_type}-#{spc_type}-is_residential#{is_residential}") - name = make_name(template, clim, building_type, spc_type) + name = model_make_name(model, clim, building_type, spc_type) # Create a new construction set and name it - construction_set = OpenStudio::Model::DefaultConstructionSet.new(self) + construction_set = OpenStudio::Model::DefaultConstructionSet.new(model) construction_set.setName(name) # Specify the types of constructions # Exterior surfaces constructions exterior_floor_standards_construction_type = 'SteelFramed' @@ -1835,135 +1656,135 @@ exterior_door_standards_construction_type = 'IEAD' exterior_overhead_door_standards_construction_type = 'IEAD' exterior_skylight_standards_construction_type = 'IEAD' # Exterior surfaces constructions - exterior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(self) + exterior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model) construction_set.setDefaultExteriorSurfaceConstructions(exterior_surfaces) - exterior_surfaces.setFloorConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorFloor', - exterior_floor_standards_construction_type, - category)) - - exterior_surfaces.setWallConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorWall', - exterior_wall_standards_construction_type, - category)) - - exterior_surfaces.setRoofCeilingConstruction(find_and_add_construction(template, + exterior_surfaces.setFloorConstruction(model_find_and_add_construction(model, climate_zone_set, - 'ExteriorRoof', - exterior_roof_standards_construction_type, + 'ExteriorFloor', + exterior_floor_standards_construction_type, category)) + exterior_surfaces.setWallConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorWall', + exterior_wall_standards_construction_type, + category)) + + exterior_surfaces.setRoofCeilingConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorRoof', + exterior_roof_standards_construction_type, + category)) + # Interior surfaces constructions - interior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(self) + interior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model) construction_set.setDefaultInteriorSurfaceConstructions(interior_surfaces) construction_name = interior_floors unless construction_name.nil? - interior_surfaces.setFloorConstruction(add_construction(construction_name)) + interior_surfaces.setFloorConstruction(model_add_construction(model, construction_name)) end construction_name = interior_walls unless construction_name.nil? - interior_surfaces.setWallConstruction(add_construction(construction_name)) + interior_surfaces.setWallConstruction(model_add_construction(model, construction_name)) end construction_name = interior_ceilings unless construction_name.nil? - interior_surfaces.setRoofCeilingConstruction(add_construction(construction_name)) + interior_surfaces.setRoofCeilingConstruction(model_add_construction(model, construction_name)) end # Ground contact surfaces constructions - ground_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(self) + ground_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model) construction_set.setDefaultGroundContactSurfaceConstructions(ground_surfaces) - ground_surfaces.setFloorConstruction(find_and_add_construction(template, - climate_zone_set, - 'GroundContactFloor', - ground_contact_floor_standards_construction_type, - category)) + ground_surfaces.setFloorConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'GroundContactFloor', + ground_contact_floor_standards_construction_type, + category)) - ground_surfaces.setWallConstruction(find_and_add_construction(template, - climate_zone_set, - 'GroundContactWall', - ground_contact_wall_standards_construction_type, - category)) + ground_surfaces.setWallConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'GroundContactWall', + ground_contact_wall_standards_construction_type, + category)) # Exterior sub surfaces constructions - exterior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(self) + exterior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(model) construction_set.setDefaultExteriorSubSurfaceConstructions(exterior_subsurfaces) if exterior_fixed_window_standards_construction_type && exterior_fixed_window_building_category - exterior_subsurfaces.setFixedWindowConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorWindow', - exterior_fixed_window_standards_construction_type, - category)) + exterior_subsurfaces.setFixedWindowConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorWindow', + exterior_fixed_window_standards_construction_type, + category)) end if exterior_operable_window_standards_construction_type && exterior_operable_window_building_category - exterior_subsurfaces.setOperableWindowConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorWindow', - exterior_operable_window_standards_construction_type, - category)) + exterior_subsurfaces.setOperableWindowConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorWindow', + exterior_operable_window_standards_construction_type, + category)) end if exterior_door_standards_construction_type && exterior_door_building_category - exterior_subsurfaces.setDoorConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorDoor', - exterior_door_standards_construction_type, - category)) + exterior_subsurfaces.setDoorConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorDoor', + exterior_door_standards_construction_type, + category)) end construction_name = exterior_glass_doors unless construction_name.nil? - exterior_subsurfaces.setGlassDoorConstruction(add_construction(construction_name)) + exterior_subsurfaces.setGlassDoorConstruction(model_add_construction(model, construction_name)) end if exterior_overhead_door_standards_construction_type && exterior_overhead_door_building_category - exterior_subsurfaces.setOverheadDoorConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorDoor', - exterior_overhead_door_standards_construction_type, - category)) + exterior_subsurfaces.setOverheadDoorConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorDoor', + exterior_overhead_door_standards_construction_type, + category)) end if exterior_skylight_standards_construction_type && exterior_skylight_building_category - exterior_subsurfaces.setSkylightConstruction(find_and_add_construction(template, - climate_zone_set, - 'Skylight', - exterior_skylight_standards_construction_type, - category)) + exterior_subsurfaces.setSkylightConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'Skylight', + exterior_skylight_standards_construction_type, + category)) end if construction_name == tubular_daylight_domes - exterior_subsurfaces.setTubularDaylightDomeConstruction(add_construction(construction_name)) + exterior_subsurfaces.setTubularDaylightDomeConstruction(model_add_construction(model, construction_name)) end if construction_name == tubular_daylight_diffusers - exterior_subsurfaces.setTubularDaylightDiffuserConstruction(add_construction(construction_name)) + exterior_subsurfaces.setTubularDaylightDiffuserConstruction(model_add_construction(model, construction_name)) end # Interior sub surfaces constructions - interior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(self) + interior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(model) construction_set.setDefaultInteriorSubSurfaceConstructions(interior_subsurfaces) if construction_name == interior_fixed_windows - interior_subsurfaces.setFixedWindowConstruction(add_construction(construction_name)) + interior_subsurfaces.setFixedWindowConstruction(model_add_construction(model, construction_name)) end if construction_name == interior_operable_windows - interior_subsurfaces.setOperableWindowConstruction(add_construction(construction_name)) + interior_subsurfaces.setOperableWindowConstruction(model_add_construction(model, construction_name)) end if construction_name == interior_doors - interior_subsurfaces.setDoorConstruction(add_construction(construction_name)) + interior_subsurfaces.setDoorConstruction(model_add_construction(model, construction_name)) end # Other constructions if construction_name == interior_partitions - construction_set.setInteriorPartitionConstruction(add_construction(construction_name)) + construction_set.setInteriorPartitionConstruction(model_add_construction(model, construction_name)) end if construction_name == space_shading - construction_set.setSpaceShadingConstruction(add_construction(construction_name)) + construction_set.setSpaceShadingConstruction(model_add_construction(model, construction_name)) end if construction_name == building_shading - construction_set.setBuildingShadingConstruction(add_construction(construction_name)) + construction_set.setBuildingShadingConstruction(model_add_construction(model, construction_name)) end if construction_name == site_shading - construction_set.setSiteShadingConstruction(add_construction(construction_name)) + construction_set.setSiteShadingConstruction(model_add_construction(model, construction_name)) end # componentize the construction set # construction_set_component = construction_set.createComponent @@ -1976,109 +1797,112 @@ # 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. - def apply_multizone_vav_outdoor_air_sizing(template) + 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 - getAirLoopHVACs.sort.each { |obj| obj.apply_multizone_vav_outdoor_air_sizing(template) } + model.getAirLoopHVACs.sort.each { |obj| air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(obj) } OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying multizone vav OA sizing.') end # Applies the HVAC parts of the template to all objects in the model # using the the template specified in the model. - def apply_hvac_efficiency_standard(template, climate_zone) + def model_apply_hvac_efficiency_standard(model, climate_zone) sql_db_vars_map = {} OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying HVAC efficiency standards.') # Air Loop Controls - getAirLoopHVACs.sort.each { |obj| obj.apply_standard_controls(template, climate_zone) } + model.getAirLoopHVACs.sort.each { |obj| air_loop_hvac_apply_standard_controls(obj, climate_zone) } + # Plant Loop Controls + # TODO refactor: enable this code (missing before refactor) + # getPlantLoops.sort.each { |obj| plant_loop_apply_standard_controls(obj, template, climate_zone) } + ##### Apply equipment efficiencies # Fans - getFanVariableVolumes.sort.each { |obj| obj.apply_standard_minimum_motor_efficiency(template, obj.brake_horsepower) } - getFanConstantVolumes.sort.each { |obj| obj.apply_standard_minimum_motor_efficiency(template, obj.brake_horsepower) } - getFanOnOffs.sort.each { |obj| obj.apply_standard_minimum_motor_efficiency(template, obj.brake_horsepower) } - getFanZoneExhausts.sort.each { |obj| obj.apply_standard_minimum_motor_efficiency(template, obj.brake_horsepower) } + model.getFanVariableVolumes.sort.each { |obj| fan_apply_standard_minimum_motor_efficiency(obj, fan_brake_horsepower(obj)) } + model.getFanConstantVolumes.sort.each { |obj| fan_apply_standard_minimum_motor_efficiency(obj, fan_brake_horsepower(obj)) } + model.getFanOnOffs.sort.each { |obj| fan_apply_standard_minimum_motor_efficiency(obj, fan_brake_horsepower(obj)) } + model.getFanZoneExhausts.sort.each { |obj| fan_apply_standard_minimum_motor_efficiency(obj, fan_brake_horsepower(obj)) } # Pumps - getPumpConstantSpeeds.sort.each { |obj| obj.apply_standard_minimum_motor_efficiency(template) } - getPumpVariableSpeeds.sort.each { |obj| obj.apply_standard_minimum_motor_efficiency(template) } - getHeaderedPumpsConstantSpeeds.sort.each { |obj| obj.apply_standard_minimum_motor_efficiency(template) } - getHeaderedPumpsVariableSpeeds.sort.each { |obj| obj.apply_standard_minimum_motor_efficiency(template) } + model.getPumpConstantSpeeds.sort.each { |obj| pump_apply_standard_minimum_motor_efficiency(obj) } + model.getPumpVariableSpeeds.sort.each { |obj| pump_apply_standard_minimum_motor_efficiency(obj) } + model.getHeaderedPumpsConstantSpeeds.sort.each { |obj| pump_apply_standard_minimum_motor_efficiency(obj) } + model.getHeaderedPumpsVariableSpeeds.sort.each { |obj| pump_apply_standard_minimum_motor_efficiency(obj) } # Unitary HPs # set DX HP coils before DX clg coils because when DX HP coils need to first # pull the capacities of their paried DX clg coils, and this does not work # correctly if the DX clg coil efficiencies have been set because they are renamed. - getCoilHeatingDXSingleSpeeds.sort.each { |obj| sql_db_vars_map = obj.apply_efficiency_and_curves(template, sql_db_vars_map) } + model.getCoilHeatingDXSingleSpeeds.sort.each { |obj| sql_db_vars_map = coil_heating_dx_single_speed_apply_efficiency_and_curves(obj, sql_db_vars_map) } # Unitary ACs - getCoilCoolingDXTwoSpeeds.sort.each { |obj| sql_db_vars_map = obj.apply_efficiency_and_curves(template, sql_db_vars_map) } - getCoilCoolingDXSingleSpeeds.sort.each { |obj| sql_db_vars_map = obj.apply_efficiency_and_curves(template, sql_db_vars_map) } + model.getCoilCoolingDXTwoSpeeds.sort.each { |obj| sql_db_vars_map = coil_cooling_dx_two_speed_apply_efficiency_and_curves(obj, sql_db_vars_map) } + model.getCoilCoolingDXSingleSpeeds.sort.each { |obj| sql_db_vars_map = coil_cooling_dx_single_speed_apply_efficiency_and_curves(obj, sql_db_vars_map) } # Chillers - clg_tower_objs = getCoolingTowerSingleSpeeds - getChillerElectricEIRs.sort.each { |obj| obj.apply_efficiency_and_curves(template, clg_tower_objs) } + clg_tower_objs = model.getCoolingTowerSingleSpeeds + model.getChillerElectricEIRs.sort.each { |obj| chiller_electric_eir_apply_efficiency_and_curves(obj, clg_tower_objs) } # Boilers - getBoilerHotWaters.sort.each { |obj| obj.apply_efficiency_and_curves(template) } + model.getBoilerHotWaters.sort.each { |obj| boiler_hot_water_apply_efficiency_and_curves(obj) } # Water Heaters - getWaterHeaterMixeds.sort.each { |obj| obj.apply_efficiency(template) } + model.getWaterHeaterMixeds.sort.each { |obj| water_heater_mixed_apply_efficiency(obj) } # Cooling Towers - getCoolingTowerSingleSpeeds.sort.each { |obj| obj.apply_efficiency_and_curves(template) } - getCoolingTowerTwoSpeeds.sort.each { |obj| obj.apply_efficiency_and_curves(template) } - getCoolingTowerVariableSpeeds.sort.each { |obj| obj.apply_efficiency_and_curves(template) } + model.getCoolingTowerSingleSpeeds.sort.each { |obj| cooling_tower_single_speed_apply_efficiency_and_curves(obj) } + model.getCoolingTowerTwoSpeeds.sort.each { |obj| cooling_tower_two_speed_apply_efficiency_and_curves(obj) } + model.getCoolingTowerVariableSpeeds.sort.each { |obj| cooling_tower_variable_speed_apply_efficiency_and_curves(obj) } # ERVs - getHeatExchangerAirToAirSensibleAndLatents.each { |obj| obj.apply_efficiency(template) } + model.getHeatExchangerAirToAirSensibleAndLatents.each { |obj| heat_exchanger_air_to_air_sensible_and_latent_apply_efficiency(obj) } OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying HVAC efficiency standards.') end # Applies daylighting controls to each space in the model # per the standard. - def add_daylighting_controls(template) + def model_add_daylighting_controls(model) OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started adding daylighting controls.') # Add daylighting controls to each space - getSpaces.sort.each do |space| - added = space.add_daylighting_controls(template, false, false) + model.getSpaces.sort.each do |space| + added = space_add_daylighting_controls(space, false, false) end OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding daylighting controls.') end # Apply the air leakage requirements to the model, - # as described in PNNL section 5.2.1.6. + # 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. # # base infiltration rates off of. # @return [Bool] true if successful, false if not # @todo This infiltration method is not used by the Reference # buildings, fix this inconsistency. - def apply_infiltration_standard(template) + def model_apply_infiltration_standard(model) # Set the infiltration rate at each space - getSpaces.sort.each do |space| - space.apply_infiltration_rate(template) + model.getSpaces.sort.each do |space| + space_apply_infiltration_rate(space) end - case template - when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004' - # "For 'DOE Ref Pre-1980' and 'DOE Ref 1980-2004', infiltration rates are not defined using this method, no changes have been made to the model. - else - # Remove infiltration rates set at the space type. Kind of redundant for NECB 2011 - getSpaceTypes.each do |space_type| - space_type.spaceInfiltrationDesignFlowRates.each(&:remove) - end + # Remove infiltration rates set at the space type + model.getSpaceTypes.sort.each do |space_type| + space_type.spaceInfiltrationDesignFlowRates.each(&:remove) end + + return true end # Method to search through a hash for the objects that meets the # desired search criteria, as passed via a hash. # Returns an Array (empty if nothing found) of matching objects. @@ -2088,16 +1912,16 @@ # @param capacity [Double] capacity of the object in question. If capacity is supplied, # the objects will only be returned if the specified capacity is between # the minimum_capacity and maximum_capacity values. # @return [Array] returns an array of hashes, one hash per object. Array is empty if no results. # @example Find all the schedule rules that match the name - # rules = self.find_objects($os_standards['schedules'], {'name'=>schedule_name}) + # rules = model_find_objects(self, standards_data['schedules'], {'name'=>schedule_name}) # if rules.size == 0 # OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Cannot find data for schedule: #{schedule_name}, will not be created.") # return false #TODO change to return empty optional schedule:ruleset? # end - def find_objects(hash_of_objects, search_criteria, capacity = nil) + def model_find_objects(hash_of_objects, search_criteria, capacity = nil) # matching_objects = hash_of_objects.clone # #new # puts "searching" # puts search_criteria # raise ("hash of objects is nil or empty. #{hash_of_objects}") if hash_of_objects.nil? || hash_of_objects.empty? || matching_objects[0].nil? @@ -2129,18 +1953,23 @@ # old desired_object = nil search_criteria_matching_objects = [] matching_objects = [] + if hash_of_objects.is_a?(Hash) and hash_of_objects.key?('table') + hash_of_objects = hash_of_objects['table'] + end + # Compare each of the objects against the search criteria hash_of_objects.each do |object| meets_all_search_criteria = true search_criteria.each do |key, value| # Don't check non-existent search criteria next unless object.key?(key) # Stop as soon as one of the search criteria is not met - if object[key] != value + # 'Any' is a special key that matches anything + unless object[key] == value || object[key] == 'Any' meets_all_search_criteria = false break end end # Skip objects that don't meet all search criteria @@ -2171,11 +2000,11 @@ end # If no object was found, round the capacity down a little # to avoid issues where the number fell between the limits # in the json file. if matching_objects.size.zero? - capacity = capacity * 0.99 + capacity *= 0.99 search_criteria_matching_objects.each do |object| # Skip objects that don't have fields for minimum_capacity and maximum_capacity next if !object.key?('minimum_capacity') || !object.key?('maximum_capacity') # Skip objects that don't have values specified for minimum_capacity and maximum_capacity next if object['minimum_capacity'].nil? || object['maximum_capacity'].nil? @@ -2222,13 +2051,16 @@ # 'template' => template, # 'number_of_poles' => 4.0, # 'type' => 'Enclosed', # } # motor_properties = self.model.find_object(motors, search_criteria, 2.5) - def find_object(hash_of_objects, search_criteria, capacity = nil, date = nil) - # new_matching_objects = self.find_objects(hash_of_objects, search_criteria, capacity) + def model_find_object(hash_of_objects, search_criteria, capacity = nil, date = nil) + # new_matching_objects = model_find_objects(self, hash_of_objects, search_criteria, capacity) + if hash_of_objects.is_a?(Hash) and hash_of_objects.key?('table') + hash_of_objects = hash_of_objects['table'] + end desired_object = nil search_criteria_matching_objects = [] matching_objects = [] # Compare each of the objects against the search criteria @@ -2236,11 +2068,12 @@ meets_all_search_criteria = true search_criteria.each do |key, value| # Don't check non-existent search criteria next unless object.key?(key) # Stop as soon as one of the search criteria is not met - if object[key] != value + # 'Any' is a special key that matches anything + unless object[key] == value || object[key] == 'Any' meets_all_search_criteria = false break end end # Skip objects that don't meet all search criteria @@ -2271,11 +2104,11 @@ end # If no object was found, round the capacity down a little # to avoid issues where the number fell between the limits # in the json file. if matching_objects.size.zero? - capacity = capacity * 0.99 + capacity *= 0.99 search_criteria_matching_objects.each do |object| # Skip objects that don't have fields for minimum_capacity and maximum_capacity next if !object.key?('minimum_capacity') || !object.key?('maximum_capacity') # Skip objects that don't have values specified for minimum_capacity and maximum_capacity next if object['minimum_capacity'].nil? || object['maximum_capacity'].nil? @@ -2319,34 +2152,33 @@ return desired_object end # Create constant ScheduleRuleset # - # @param [double] constant value - # @param [string] name + # @param value [double] the value to use, 24-7, 365 + # @param name [string] the name of the schedule # @return schedule - def add_constant_schedule_ruleset(value,name = nil) - schedule = OpenStudio::Model::ScheduleRuleset.new(self) - if not name.nil? + def model_add_constant_schedule_ruleset(model, value, name = nil) + schedule = OpenStudio::Model::ScheduleRuleset.new(model) + unless name.nil? schedule.setName(name) schedule.defaultDaySchedule.setName("#{name} Default") end schedule.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), value) - return schedule end # Create a schedule from the openstudio standards dataset and # add it to the model. # # @param schedule_name [String} name of the schedule # @return [ScheduleRuleset] the resulting schedule ruleset # @todo make return an OptionalScheduleRuleset - def add_schedule(schedule_name) + def model_add_schedule(model, schedule_name) return nil if schedule_name.nil? || schedule_name == '' # First check model and return schedule if it already exists - getSchedules.each do |schedule| + model.getSchedules.sort.each do |schedule| if schedule.name.get.to_s == schedule_name OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Already added schedule: #{schedule_name}") return schedule end end @@ -2354,18 +2186,20 @@ require 'date' # OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.Model', "Adding schedule: #{schedule_name}") # Find all the schedule rules that match the name - rules = find_objects($os_standards['schedules'], 'name' => schedule_name) + rules = model_find_objects(standards_data['schedules'], 'name' => schedule_name) if rules.size.zero? - OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Cannot find data for schedule: #{schedule_name}, will not be created.") - return false # TODO: change to return empty optional schedule:ruleset? + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Cannot find data for schedule: #{schedule_name}, will not be created.") + sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(model) + sch_ruleset.setName("NOT ACTUALLY #{schedule_name}") + return sch_ruleset end # Make a schedule ruleset - sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(self) + sch_ruleset = OpenStudio::Model::ScheduleRuleset.new( model ) sch_ruleset.setName(schedule_name.to_s) # Loop through the rules, making one for each row in the spreadsheet rules.each do |rule| day_types = rule['day_types'] @@ -2373,34 +2207,33 @@ end_date = DateTime.parse(rule['end_date']) sch_type = rule['type'] values = rule['values'] # Day Type choices: Wkdy, Wknd, Mon, Tue, Wed, Thu, Fri, Sat, Sun, WntrDsn, SmrDsn, Hol - # Default if day_types.include?('Default') day_sch = sch_ruleset.defaultDaySchedule day_sch.setName("#{schedule_name} Default") - add_vals_to_sch(day_sch, sch_type, values) + model_add_vals_to_sch(model, day_sch, sch_type, values) end # Winter Design Day if day_types.include?('WntrDsn') - day_sch = OpenStudio::Model::ScheduleDay.new(self) + day_sch = OpenStudio::Model::ScheduleDay.new(model) sch_ruleset.setWinterDesignDaySchedule(day_sch) day_sch = sch_ruleset.winterDesignDaySchedule day_sch.setName("#{schedule_name} Winter Design Day") - add_vals_to_sch(day_sch, sch_type, values) + model_add_vals_to_sch(model, day_sch, sch_type, values) end # Summer Design Day if day_types.include?('SmrDsn') - day_sch = OpenStudio::Model::ScheduleDay.new(self) + day_sch = OpenStudio::Model::ScheduleDay.new(model) sch_ruleset.setSummerDesignDaySchedule(day_sch) day_sch = sch_ruleset.summerDesignDaySchedule day_sch.setName("#{schedule_name} Summer Design Day") - add_vals_to_sch(day_sch, sch_type, values) + model_add_vals_to_sch(model, day_sch, sch_type, values) end # Other days (weekdays, weekends, etc) if day_types.include?('Wknd') || day_types.include?('Wkdy') || @@ -2414,11 +2247,11 @@ # Make the Rule sch_rule = OpenStudio::Model::ScheduleRule.new(sch_ruleset) day_sch = sch_rule.daySchedule day_sch.setName("#{schedule_name} #{day_types} Day") - add_vals_to_sch(day_sch, sch_type, values) + model_add_vals_to_sch(model, day_sch, sch_type, values) # Set the dates when the rule applies sch_rule.setStartDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_date.month.to_i), start_date.day.to_i)) sch_rule.setEndDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_date.month.to_i), end_date.day.to_i)) @@ -2442,42 +2275,40 @@ sch_rule.setApplyWednesday(true) if day_types.include?('Wed') sch_rule.setApplyThursday(true) if day_types.include?('Thu') sch_rule.setApplyFriday(true) if day_types.include?('Fri') sch_rule.setApplySaturday(true) if day_types.include?('Sat') sch_rule.setApplySunday(true) if day_types.include?('Sun') - end end # Next rule - return sch_ruleset end # Create a material from the openstudio standards dataset. # @todo make return an OptionalMaterial - def add_material(material_name) + def model_add_material(model, material_name) # First check model and return material if it already exists - getMaterials.each do |material| + model.getMaterials.sort.each do |material| if material.name.get.to_s == material_name OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Already added material: #{material_name}") return material end end # OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.Model', "Adding material: #{material_name}") # Get the object data - data = find_object($os_standards['materials'], 'name' => material_name) + data = model_find_object(standards_data['materials'], 'name' => material_name) unless data OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Cannot find data for material: #{material_name}, will not be created.") return false # TODO: change to return empty optional material end material = nil material_type = data['material_type'] if material_type == 'StandardOpaqueMaterial' - material = OpenStudio::Model::StandardOpaqueMaterial.new(self) + material = OpenStudio::Model::StandardOpaqueMaterial.new(model) material.setName(material_name) material.setRoughness(data['roughness'].to_s) material.setThickness(OpenStudio.convert(data['thickness'].to_f, 'in', 'm').get) material.setConductivity(OpenStudio.convert(data['conductivity'].to_f, 'Btu*in/hr*ft^2*R', 'W/m*K').get) @@ -2486,11 +2317,11 @@ material.setThermalAbsorptance(data['thermal_absorptance'].to_f) material.setSolarAbsorptance(data['solar_absorptance'].to_f) material.setVisibleAbsorptance(data['visible_absorptance'].to_f) elsif material_type == 'MasslessOpaqueMaterial' - material = OpenStudio::Model::MasslessOpaqueMaterial.new(self) + material = OpenStudio::Model::MasslessOpaqueMaterial.new(model) material.setName(material_name) material.setThermalResistance(OpenStudio.convert(data['resistance'].to_f, 'hr*ft^2*R/Btu', 'm^2*K/W').get) material.setConductivity(OpenStudio.convert(data['conductivity'].to_f, 'Btu*in/hr*ft^2*R', 'W/m*K').get) material.setDensity(OpenStudio.convert(data['density'].to_f, 'lb/ft^3', 'kg/m^3').get) @@ -2498,32 +2329,32 @@ material.setThermalAbsorptance(data['thermal_absorptance'].to_f) material.setSolarAbsorptance(data['solar_absorptance'].to_f) material.setVisibleAbsorptance(data['visible_absorptance'].to_f) elsif material_type == 'AirGap' - material = OpenStudio::Model::AirGap.new(self) + material = OpenStudio::Model::AirGap.new(model) material.setName(material_name) material.setThermalResistance(OpenStudio.convert(data['resistance'].to_f, 'hr*ft^2*R/Btu*in', 'm*K/W').get) elsif material_type == 'Gas' - material = OpenStudio::Model::Gas.new(self) + material = OpenStudio::Model::Gas.new(model) material.setName(material_name) material.setThickness(OpenStudio.convert(data['thickness'].to_f, 'in', 'm').get) material.setGasType(data['gas_type'].to_s) elsif material_type == 'SimpleGlazing' - material = OpenStudio::Model::SimpleGlazing.new(self) + material = OpenStudio::Model::SimpleGlazing.new(model) material.setName(material_name) material.setUFactor(OpenStudio.convert(data['u_factor'].to_f, 'Btu/hr*ft^2*R', 'W/m^2*K').get) material.setSolarHeatGainCoefficient(data['solar_heat_gain_coefficient'].to_f) material.setVisibleTransmittance(data['visible_transmittance'].to_f) elsif material_type == 'StandardGlazing' - material = OpenStudio::Model::StandardGlazing.new(self) + material = OpenStudio::Model::StandardGlazing.new(model) material.setName(material_name) material.setOpticalDataType(data['optical_data_type'].to_s) material.setThickness(OpenStudio.convert(data['thickness'].to_f, 'in', 'm').get) material.setSolarTransmittanceatNormalIncidence(data['solar_transmittance_at_normal_incidence'].to_f) @@ -2552,51 +2383,47 @@ end # Create a construction from the openstudio standards dataset. # If construction_props are specified, modifies the insulation layer accordingly. # @todo make return an OptionalConstruction - def add_construction(construction_name, construction_props = nil) + def model_add_construction(model, construction_name, construction_props = nil) # First check model and return construction if it already exists - getConstructions.each do |construction| + model.getConstructions.sort.each do |construction| if construction.name.get.to_s == construction_name OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Already added construction: #{construction_name}") return construction end end OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Adding construction: #{construction_name}") # Get the object data - data = find_object($os_standards['constructions'], 'name' => construction_name) + data = model_find_object(standards_data['constructions'], 'name' => construction_name) unless data OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Cannot find data for construction: #{construction_name}, will not be created.") return OpenStudio::Model::OptionalConstruction.new end # Make a new construction and set the standards details - construction = OpenStudio::Model::Construction.new(self) + construction = OpenStudio::Model::Construction.new(model) construction.setName(construction_name) standards_info = construction.standardsInformation intended_surface_type = data['intended_surface_type'] - unless intended_surface_type - intended_surface_type = '' - end + intended_surface_type ||= '' standards_info.setIntendedSurfaceType(intended_surface_type) standards_construction_type = data['standards_construction_type'] - unless standards_construction_type - standards_construction_type = '' - end + standards_construction_type ||= '' standards_info.setStandardsConstructionType(standards_construction_type) # TODO: could put construction rendering color in the spreadsheet # Add the material layers to the construction layers = OpenStudio::Model::MaterialVector.new data['materials'].each do |material_name| - material = add_material(material_name) + material = model_add_material(model, material_name) if material layers << material end end construction.setLayers(layers) @@ -2606,386 +2433,422 @@ if construction_props # Determine the target U-value, C-factor, and F-factor target_u_value_ip = construction_props['assembly_maximum_u_value'] target_f_factor_ip = construction_props['assembly_maximum_f_factor'] target_c_factor_ip = construction_props['assembly_maximum_c_factor'] + target_shgc = construction_props['assembly_maximum_solar_heat_gain_coefficient'] + u_includes_int_film = construction_props['u_value_includes_interior_film_coefficient'] + u_includes_ext_film = construction_props['u_value_includes_exterior_film_coefficient'] OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "#{data['intended_surface_type']} u_val #{target_u_value_ip} f_fac #{target_f_factor_ip} c_fac #{target_c_factor_ip}") - if target_u_value_ip && !(data['intended_surface_type'] == 'ExteriorWindow' || data['intended_surface_type'] == 'Skylight') + if target_u_value_ip - # Set the U-Value - construction.set_u_value(target_u_value_ip.to_f, data['insulation_layer'], data['intended_surface_type'], true) + # Handle Opaque and Fenestration Constructions differently + if construction.isFenestration && construction_simple_glazing?(construction) + # Set the U-Value and SHGC + construction_set_glazing_u_value(construction, target_u_value_ip.to_f, data['intended_surface_type'], u_includes_int_film, u_includes_ext_film) + construction_set_glazing_shgc(construction, target_shgc.to_f) + else # if !data['intended_surface_type'] == 'ExteriorWindow' && !data['intended_surface_type'] == 'Skylight' + # Set the U-Value + construction_set_u_value(construction, target_u_value_ip.to_f, data['insulation_layer'], data['intended_surface_type'], u_includes_int_film, u_includes_ext_film) + # else + # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Not modifying U-value for #{data['intended_surface_type']} u_val #{target_u_value_ip} f_fac #{target_f_factor_ip} c_fac #{target_c_factor_ip}") + end elsif target_f_factor_ip && data['intended_surface_type'] == 'GroundContactFloor' # Set the F-Factor (only applies to slabs on grade) # TODO figure out what the prototype buildings did about ground heat transfer - # construction.set_slab_f_factor(target_f_factor_ip.to_f, data['insulation_layer']) - construction.set_u_value(0.0, data['insulation_layer'], data['intended_surface_type'], true) + # construction_set_slab_f_factor(construction, target_f_factor_ip.to_f, data['insulation_layer']) + construction_set_u_value(construction, 0.0, data['insulation_layer'], data['intended_surface_type'], u_includes_int_film, u_includes_ext_film) elsif target_c_factor_ip && data['intended_surface_type'] == 'GroundContactWall' # Set the C-Factor (only applies to underground walls) # TODO figure out what the prototype buildings did about ground heat transfer - # construction.set_underground_wall_c_factor(target_c_factor_ip.to_f, data['insulation_layer']) - construction.set_u_value(0.0, data['insulation_layer'], data['intended_surface_type'], true) + # construction_set_underground_wall_c_factor(construction, target_c_factor_ip.to_f, data['insulation_layer']) + construction_set_u_value(construction, 0.0, data['insulation_layer'], data['intended_surface_type'], u_includes_int_film, u_includes_ext_film) end end + # # Check if the construction with the modified name was already in the model. + # # If it was, delete this new construction and return the copy already in the model. + # m = construction.name.get.to_s.match(/\s(\d+)/) + # if m + # revised_cons_name = construction.name.get.to_s.gsub(/\s\d+/,'') + # model.getConstructions.sort.each do |exist_construction| + # if exist_construction.name.get.to_s == revised_cons_name + # OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Already added construction: #{construction_name}") + # # Remove the recently added construction + # lyrs = construction.layers + # # Erase the layers in the construction + # construction.setLayers([]) + # # Delete unused materials + # lyrs.uniq.each do |lyr| + # if lyr.directUseCount.zero? + # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Removing Material: #{lyr.name}") + # lyr.remove + # end + # end + # construction.remove # Remove the construction + # return exist_construction + # end + # end + # end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Adding construction #{construction.name}.") return construction end # Helper method to find a particular construction and add it to the model # after modifying the insulation value if necessary. - def find_and_add_construction(template, climate_zone_set, intended_surface_type, standards_construction_type, building_category) + def model_find_and_add_construction(model, climate_zone_set, intended_surface_type, standards_construction_type, building_category) # Get the construction properties, # which specifies properties by construction category by climate zone set. # AKA the info in Tables 5.5-1-5.5-8 - props = find_object($os_standards['construction_properties'], 'template' => template, - 'climate_zone_set' => climate_zone_set, - 'intended_surface_type' => intended_surface_type, - 'standards_construction_type' => standards_construction_type, - 'building_category' => building_category) + props = model_find_object(standards_data['construction_properties'], 'template' => template, + 'climate_zone_set' => climate_zone_set, + 'intended_surface_type' => intended_surface_type, + 'standards_construction_type' => standards_construction_type, + 'building_category' => building_category) if !props OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Could not find construction properties for: #{template}-#{climate_zone_set}-#{intended_surface_type}-#{standards_construction_type}-#{building_category}.") # Return an empty construction - construction = OpenStudio::Model::Construction.new(self) + construction = OpenStudio::Model::Construction.new(model) construction.setName('Could not find construction properties set to Adiabatic ') - almost_adiabatic = OpenStudio::Model::MasslessOpaqueMaterial.new(self, 'Smooth', 500) + almost_adiabatic = OpenStudio::Model::MasslessOpaqueMaterial.new(model, 'Smooth', 500) construction.insertLayer(0, almost_adiabatic) return construction else - OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Construction properties for: #{template}-#{climate_zone_set}-#{intended_surface_type}-#{standards_construction_type}-#{building_category} = #{props}.") + # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Construction properties for: #{template}-#{climate_zone_set}-#{intended_surface_type}-#{standards_construction_type}-#{building_category} = #{props}.") end # Make sure that a construction is specified if props['construction'].nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "No typical construction is specified for construction properties of: #{template}-#{climate_zone_set}-#{intended_surface_type}-#{standards_construction_type}-#{building_category}. Make sure it is entered in the spreadsheet.") # Return an empty construction - construction = OpenStudio::Model::Construction.new(self) + construction = OpenStudio::Model::Construction.new(model) construction.setName('No typical construction was specified') return construction end # Add the construction, modifying properties as necessary - construction = add_construction(props['construction'], props) + construction = model_add_construction(model, props['construction'], props) return construction end # Create a construction set from the openstudio standards dataset. # Returns an Optional DefaultConstructionSet - def add_construction_set(template, clim, building_type, spc_type, is_residential) + def model_add_construction_set(model, clim, building_type, spc_type, is_residential) construction_set = OpenStudio::Model::OptionalDefaultConstructionSet.new # Find the climate zone set that this climate zone falls into - climate_zone_set = find_climate_zone_set(clim, template) + climate_zone_set = model_find_climate_zone_set(model, clim) unless climate_zone_set return construction_set end # Get the object data - data = find_object($os_standards['construction_sets'], 'template' => template, 'climate_zone_set' => climate_zone_set, 'building_type' => building_type, 'space_type' => spc_type, 'is_residential' => is_residential) + data = model_find_object(standards_data['construction_sets'], 'template' => template, 'climate_zone_set' => climate_zone_set, 'building_type' => building_type, 'space_type' => spc_type, 'is_residential' => is_residential) unless data - data = find_object($os_standards['construction_sets'], 'template' => template, 'climate_zone_set' => climate_zone_set, 'building_type' => building_type, 'space_type' => spc_type) - + data = model_find_object(standards_data['construction_sets'], 'template' => template, 'climate_zone_set' => climate_zone_set, 'building_type' => building_type, 'space_type' => spc_type) unless data - - # for debugging (maria) - # puts "data = #{data}" - + # if nothing matches say that we could not find it. + OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Construction set for template =#{template}, climate zone set =#{climate_zone_set}, building type = #{building_type}, space type = #{spc_type}, is residential = #{is_residential} was not found in standards_data['construction_sets']") return construction_set end - end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Adding construction set: #{template}-#{clim}-#{building_type}-#{spc_type}-is_residential#{is_residential}") - name = make_name(template, clim, building_type, spc_type) + name = model_make_name(model, clim, building_type, spc_type) # Create a new construction set and name it - construction_set = OpenStudio::Model::DefaultConstructionSet.new(self) + construction_set = OpenStudio::Model::DefaultConstructionSet.new(model) construction_set.setName(name) # Exterior surfaces constructions - exterior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(self) + exterior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model) construction_set.setDefaultExteriorSurfaceConstructions(exterior_surfaces) if data['exterior_floor_standards_construction_type'] && data['exterior_floor_building_category'] - exterior_surfaces.setFloorConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorFloor', - data['exterior_floor_standards_construction_type'], - data['exterior_floor_building_category'])) + exterior_surfaces.setFloorConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorFloor', + data['exterior_floor_standards_construction_type'], + data['exterior_floor_building_category'])) end if data['exterior_wall_standards_construction_type'] && data['exterior_wall_building_category'] - exterior_surfaces.setWallConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorWall', - data['exterior_wall_standards_construction_type'], - data['exterior_wall_building_category'])) + exterior_surfaces.setWallConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorWall', + data['exterior_wall_standards_construction_type'], + data['exterior_wall_building_category'])) end if data['exterior_roof_standards_construction_type'] && data['exterior_roof_building_category'] - exterior_surfaces.setRoofCeilingConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorRoof', - data['exterior_roof_standards_construction_type'], - data['exterior_roof_building_category'])) + exterior_surfaces.setRoofCeilingConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorRoof', + data['exterior_roof_standards_construction_type'], + data['exterior_roof_building_category'])) end # Interior surfaces constructions - interior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(self) + interior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model) construction_set.setDefaultInteriorSurfaceConstructions(interior_surfaces) construction_name = data['interior_floors'] unless construction_name.nil? - interior_surfaces.setFloorConstruction(add_construction(construction_name)) + interior_surfaces.setFloorConstruction(model_add_construction(model, construction_name)) end construction_name = data['interior_walls'] unless construction_name.nil? - interior_surfaces.setWallConstruction(add_construction(construction_name)) + interior_surfaces.setWallConstruction(model_add_construction(model, construction_name)) end construction_name = data['interior_ceilings'] unless construction_name.nil? - interior_surfaces.setRoofCeilingConstruction(add_construction(construction_name)) + interior_surfaces.setRoofCeilingConstruction(model_add_construction(model, construction_name)) end # Ground contact surfaces constructions - ground_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(self) + ground_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model) construction_set.setDefaultGroundContactSurfaceConstructions(ground_surfaces) if data['ground_contact_floor_standards_construction_type'] && data['ground_contact_floor_building_category'] - ground_surfaces.setFloorConstruction(find_and_add_construction(template, - climate_zone_set, - 'GroundContactFloor', - data['ground_contact_floor_standards_construction_type'], - data['ground_contact_floor_building_category'])) + ground_surfaces.setFloorConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'GroundContactFloor', + data['ground_contact_floor_standards_construction_type'], + data['ground_contact_floor_building_category'])) end if data['ground_contact_wall_standards_construction_type'] && data['ground_contact_wall_building_category'] - ground_surfaces.setWallConstruction(find_and_add_construction(template, - climate_zone_set, - 'GroundContactWall', - data['ground_contact_wall_standards_construction_type'], - data['ground_contact_wall_building_category'])) + ground_surfaces.setWallConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'GroundContactWall', + data['ground_contact_wall_standards_construction_type'], + data['ground_contact_wall_building_category'])) end if data['ground_contact_ceiling_standards_construction_type'] && data['ground_contact_ceiling_building_category'] - ground_surfaces.setRoofCeilingConstruction(find_and_add_construction(template, - climate_zone_set, - 'GroundContactRoof', - data['ground_contact_ceiling_standards_construction_type'], - data['ground_contact_ceiling_building_category'])) + ground_surfaces.setRoofCeilingConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'GroundContactRoof', + data['ground_contact_ceiling_standards_construction_type'], + data['ground_contact_ceiling_building_category'])) end # Exterior sub surfaces constructions - exterior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(self) + exterior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(model) construction_set.setDefaultExteriorSubSurfaceConstructions(exterior_subsurfaces) if data['exterior_fixed_window_standards_construction_type'] && data['exterior_fixed_window_building_category'] - exterior_subsurfaces.setFixedWindowConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorWindow', - data['exterior_fixed_window_standards_construction_type'], - data['exterior_fixed_window_building_category'])) + exterior_subsurfaces.setFixedWindowConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorWindow', + data['exterior_fixed_window_standards_construction_type'], + data['exterior_fixed_window_building_category'])) end if data['exterior_operable_window_standards_construction_type'] && data['exterior_operable_window_building_category'] - exterior_subsurfaces.setOperableWindowConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorWindow', - data['exterior_operable_window_standards_construction_type'], - data['exterior_operable_window_building_category'])) + exterior_subsurfaces.setOperableWindowConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorWindow', + data['exterior_operable_window_standards_construction_type'], + data['exterior_operable_window_building_category'])) end if data['exterior_door_standards_construction_type'] && data['exterior_door_building_category'] - exterior_subsurfaces.setDoorConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorDoor', - data['exterior_door_standards_construction_type'], - data['exterior_door_building_category'])) + exterior_subsurfaces.setDoorConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorDoor', + data['exterior_door_standards_construction_type'], + data['exterior_door_building_category'])) end construction_name = data['exterior_glass_doors'] unless construction_name.nil? - exterior_subsurfaces.setGlassDoorConstruction(add_construction(construction_name)) + exterior_subsurfaces.setGlassDoorConstruction(model_add_construction(model, construction_name)) end if data['exterior_overhead_door_standards_construction_type'] && data['exterior_overhead_door_building_category'] - exterior_subsurfaces.setOverheadDoorConstruction(find_and_add_construction(template, - climate_zone_set, - 'ExteriorDoor', - data['exterior_overhead_door_standards_construction_type'], - data['exterior_overhead_door_building_category'])) + exterior_subsurfaces.setOverheadDoorConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'ExteriorDoor', + data['exterior_overhead_door_standards_construction_type'], + data['exterior_overhead_door_building_category'])) end if data['exterior_skylight_standards_construction_type'] && data['exterior_skylight_building_category'] - exterior_subsurfaces.setSkylightConstruction(find_and_add_construction(template, - climate_zone_set, - 'Skylight', - data['exterior_skylight_standards_construction_type'], - data['exterior_skylight_building_category'])) + exterior_subsurfaces.setSkylightConstruction(model_find_and_add_construction(model, + climate_zone_set, + 'Skylight', + data['exterior_skylight_standards_construction_type'], + data['exterior_skylight_building_category'])) end if (construction_name = data['tubular_daylight_domes']) - exterior_subsurfaces.setTubularDaylightDomeConstruction(add_construction(construction_name)) + exterior_subsurfaces.setTubularDaylightDomeConstruction(model_add_construction(model, construction_name)) end if (construction_name = data['tubular_daylight_diffusers']) - exterior_subsurfaces.setTubularDaylightDiffuserConstruction(add_construction(construction_name)) + exterior_subsurfaces.setTubularDaylightDiffuserConstruction(model_add_construction(model, construction_name)) end # Interior sub surfaces constructions - interior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(self) + interior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(model) construction_set.setDefaultInteriorSubSurfaceConstructions(interior_subsurfaces) if (construction_name = data['interior_fixed_windows']) - interior_subsurfaces.setFixedWindowConstruction(add_construction(construction_name)) + interior_subsurfaces.setFixedWindowConstruction(model_add_construction(model, construction_name)) end if (construction_name = data['interior_operable_windows']) - interior_subsurfaces.setOperableWindowConstruction(add_construction(construction_name)) + interior_subsurfaces.setOperableWindowConstruction(model_add_construction(model, construction_name)) end if (construction_name = data['interior_doors']) - interior_subsurfaces.setDoorConstruction(add_construction(construction_name)) + interior_subsurfaces.setDoorConstruction(model_add_construction(model, construction_name)) end # Other constructions if (construction_name = data['interior_partitions']) - construction_set.setInteriorPartitionConstruction(add_construction(construction_name)) + construction_set.setInteriorPartitionConstruction(model_add_construction(model, construction_name)) end if (construction_name = data['space_shading']) - construction_set.setSpaceShadingConstruction(add_construction(construction_name)) + construction_set.setSpaceShadingConstruction(model_add_construction(model, construction_name)) end if (construction_name = data['building_shading']) - construction_set.setBuildingShadingConstruction(add_construction(construction_name)) + construction_set.setBuildingShadingConstruction(model_add_construction(model, construction_name)) end if (construction_name = data['site_shading']) - construction_set.setSiteShadingConstruction(add_construction(construction_name)) + construction_set.setSiteShadingConstruction(model_add_construction(model, construction_name)) end # componentize the construction set # construction_set_component = construction_set.createComponent # Return the construction set return OpenStudio::Model::OptionalDefaultConstructionSet.new(construction_set) end - def add_curve(curve_name) - # OpenStudio::logFree(OpenStudio::Info, "openstudio.prototype.addCurve", "Adding curve '#{curve_name}' to the model.") - - success = false - - curve_biquadratics = $os_standards['curve_biquadratics'] - curve_quadratics = $os_standards['curve_quadratics'] - curve_bicubics = $os_standards['curve_bicubics'] - curve_cubics = $os_standards['curve_cubics'] - - # Make biquadratic curves - curve_data = find_object(curve_biquadratics, 'name' => curve_name) - if curve_data - curve = OpenStudio::Model::CurveBiquadratic.new(self) - curve.setName(curve_data['name']) - curve.setCoefficient1Constant(curve_data['coeff_1']) - curve.setCoefficient2x(curve_data['coeff_2']) - curve.setCoefficient3xPOW2(curve_data['coeff_3']) - curve.setCoefficient4y(curve_data['coeff_4']) - curve.setCoefficient5yPOW2(curve_data['coeff_5']) - curve.setCoefficient6xTIMESY(curve_data['coeff_6']) - curve.setMinimumValueofx(curve_data['min_x']) - curve.setMaximumValueofx(curve_data['max_x']) - curve.setMinimumValueofy(curve_data['min_y']) - curve.setMaximumValueofy(curve_data['max_y']) - if curve_data['min_out'] - curve.setMinimumCurveOutput(curve_data['min_out']) + # Adds a curve from the OpenStudio-Standards dataset to the model + # based on the curve name. + def model_add_curve(model, curve_name) + # First check model and return curve if it already exists + existing_curves = [] + existing_curves += model.getCurveLinears + existing_curves += model.getCurveCubics + existing_curves += model.getCurveQuadratics + existing_curves += model.getCurveBicubics + existing_curves += model.getCurveBiquadratics + existing_curves.sort.each do |curve| + if curve.name.get.to_s == curve_name + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Already added curve: #{curve_name}") + return curve end - if curve_data['max_out'] - curve.setMaximumCurveOutput(curve_data['max_out']) - end - success = true - return curve end - # Make quadratic curves - curve_data = find_object(curve_quadratics, 'name' => curve_name) - if curve_data - curve = OpenStudio::Model::CurveQuadratic.new(self) - curve.setName(curve_data['name']) - curve.setCoefficient1Constant(curve_data['coeff_1']) - curve.setCoefficient2x(curve_data['coeff_2']) - curve.setCoefficient3xPOW2(curve_data['coeff_3']) - curve.setMinimumValueofx(curve_data['min_x']) - curve.setMaximumValueofx(curve_data['max_x']) - if curve_data['min_out'] - curve.setMinimumCurveOutput(curve_data['min_out']) - end - if curve_data['max_out'] - curve.setMaximumCurveOutput(curve_data['max_out']) - end - success = true - return curve - end + # OpenStudio::logFree(OpenStudio::Info, "openstudio.prototype.addCurve", "Adding curve '#{curve_name}' to the model.") - # Make cubic curves - curve_data = find_object(curve_cubics, 'name' => curve_name) - if curve_data - curve = OpenStudio::Model::CurveCubic.new(self) - curve.setName(curve_data['name']) - curve.setCoefficient1Constant(curve_data['coeff_1']) - curve.setCoefficient2x(curve_data['coeff_2']) - curve.setCoefficient3xPOW2(curve_data['coeff_3']) - curve.setCoefficient4xPOW3(curve_data['coeff_4']) - curve.setMinimumValueofx(curve_data['min_x']) - curve.setMaximumValueofx(curve_data['max_x']) - if curve_data['min_out'] - curve.setMinimumCurveOutput(curve_data['min_out']) - end - if curve_data['max_out'] - curve.setMaximumCurveOutput(curve_data['max_out']) - end - success = true - return curve + # Find curve data + data = model_find_object(standards_data['curves'], 'name' => curve_name) + if data.nil? + OpenStudio::logFree(OpenStudio::Warn, "openstudio.Model.Model", "Could not find a curve called '#{curve_name}' in the standards.") + return nil end - # Make bicubic curves - curve_data = find_object(curve_bicubics, 'name' => curve_name) - if curve_data - curve = OpenStudio::Model::CurveBicubic.new(self) - curve.setName(curve_data['name']) - curve.setCoefficient1Constant(curve_data['coeff_1']) - curve.setCoefficient2x(curve_data['coeff_2']) - curve.setCoefficient3xPOW2(curve_data['coeff_3']) - curve.setCoefficient4y(curve_data['coeff_4']) - curve.setCoefficient5yPOW2(curve_data['coeff_5']) - curve.setCoefficient6xTIMESY(curve_data['coeff_6']) - curve.setCoefficient7xPOW3(curve_data['coeff_7']) - curve.setCoefficient8yPOW3(curve_data['coeff_8']) - curve.setCoefficient9xPOW2TIMESY(curve_data['coeff_9']) - curve.setCoefficient10xTIMESYPOW2(curve_data['coeff_10']) - curve.setMinimumValueofx(curve_data['min_x']) - curve.setMaximumValueofx(curve_data['max_x']) - curve.setMinimumValueofy(curve_data['min_y']) - curve.setMaximumValueofy(curve_data['max_y']) - if curve_data['min_out'] - curve.setMinimumCurveOutput(curve_data['min_out']) - end - if curve_data['max_out'] - curve.setMaximumCurveOutput(curve_data['max_out']) - end - success = true + # Make the correct type of curve + case data['form'] + when 'Linear' + curve = OpenStudio::Model::CurveLinear.new(model) + curve.setName(data['name']) + curve.setCoefficient1Constant(data['coeff_1']) + curve.setCoefficient2x(data['coeff_2']) + curve.setMinimumValueofx(data['minimum_independent_variable_1']) if data['minimum_independent_variable_1'] + curve.setMaximumValueofx(data['maximum_independent_variable_1']) if data['maximum_independent_variable_1'] + curve.setMinimumCurveOutput(data['minimum_dependent_variable_output']) if data['minimum_dependent_variable_output'] + curve.setMaximumCurveOutput(data['maximum_dependent_variable_output']) if data['maximum_dependent_variable_output'] return curve - end - - # Return false if the curve was not created - if success == false - # OpenStudio::logFree(OpenStudio::Warn, "openstudio.prototype.addCurve", "Could not find a curve called '#{curve_name}' in the standards.") + when 'Cubic' + curve = OpenStudio::Model::CurveCubic.new(model) + curve.setName(data['name']) + curve.setCoefficient1Constant(data['coeff_1']) + curve.setCoefficient2x(data['coeff_2']) + curve.setCoefficient3xPOW2(data['coeff_3']) + curve.setCoefficient4xPOW3(data['coeff_4']) + curve.setMinimumValueofx(data['minimum_independent_variable_1']) if data['minimum_independent_variable_1'] + curve.setMaximumValueofx(data['maximum_independent_variable_1']) if data['maximum_independent_variable_1'] + curve.setMinimumCurveOutput(data['minimum_dependent_variable_output']) if data['minimum_dependent_variable_output'] + curve.setMaximumCurveOutput(data['maximum_dependent_variable_output']) if data['maximum_dependent_variable_output'] + return curve + when 'Quadratic' + curve = OpenStudio::Model::CurveQuadratic.new(model) + curve.setName(data['name']) + curve.setCoefficient1Constant(data['coeff_1']) + curve.setCoefficient2x(data['coeff_2']) + curve.setCoefficient3xPOW2(data['coeff_3']) + curve.setMinimumValueofx(data['minimum_independent_variable_1']) if data['minimum_independent_variable_1'] + curve.setMaximumValueofx(data['maximum_independent_variable_1']) if data['maximum_independent_variable_1'] + curve.setMinimumCurveOutput(data['minimum_dependent_variable_output']) if data['minimum_dependent_variable_output'] + curve.setMaximumCurveOutput(data['maximum_dependent_variable_output']) if data['maximum_dependent_variable_output'] + return curve + when 'BiCubic' + curve = OpenStudio::Model::CurveBicubic.new(model) + curve.setName(data['name']) + curve.setCoefficient1Constant(data['coeff_1']) + curve.setCoefficient2x(data['coeff_2']) + curve.setCoefficient3xPOW2(data['coeff_3']) + curve.setCoefficient4y(data['coeff_4']) + curve.setCoefficient5yPOW2(data['coeff_5']) + curve.setCoefficient6xTIMESY(data['coeff_6']) + curve.setCoefficient7xPOW3(data['coeff_7']) + curve.setCoefficient8yPOW3(data['coeff_8']) + curve.setCoefficient9xPOW2TIMESY(data['coeff_9']) + curve.setCoefficient10xTIMESYPOW2(data['coeff_10']) + curve.setMinimumValueofx(data['minimum_independent_variable_1']) if data['minimum_independent_variable_1'] + curve.setMaximumValueofx(data['maximum_independent_variable_1']) if data['maximum_independent_variable_1'] + curve.setMinimumValueofy(data['minimum_independent_variable_2']) if data['minimum_independent_variable_2'] + curve.setMaximumValueofy(data['maximum_independent_variable_2']) if data['maximum_independent_variable_2'] + curve.setMinimumCurveOutput(data['minimum_dependent_variable_output']) if data['minimum_dependent_variable_output'] + curve.setMaximumCurveOutput(data['maximum_dependent_variable_output']) if data['maximum_dependent_variable_output'] + return curve + when 'BiQuadratic' + curve = OpenStudio::Model::CurveBiquadratic.new(model) + curve.setName(data['name']) + curve.setCoefficient1Constant(data['coeff_1']) + curve.setCoefficient2x(data['coeff_2']) + curve.setCoefficient3xPOW2(data['coeff_3']) + curve.setCoefficient4y(data['coeff_4']) + curve.setCoefficient5yPOW2(data['coeff_5']) + curve.setCoefficient6xTIMESY(data['coeff_6']) + curve.setMinimumValueofx(data['minimum_independent_variable_1']) if data['minimum_independent_variable_1'] + curve.setMaximumValueofx(data['maximum_independent_variable_1']) if data['maximum_independent_variable_1'] + curve.setMinimumValueofy(data['minimum_independent_variable_2']) if data['minimum_independent_variable_2'] + curve.setMaximumValueofy(data['maximum_independent_variable_2']) if data['maximum_independent_variable_2'] + curve.setMinimumCurveOutput(data['minimum_dependent_variable_output']) if data['minimum_dependent_variable_output'] + curve.setMaximumCurveOutput(data['maximum_dependent_variable_output']) if data['maximum_dependent_variable_output'] + return curve + when 'BiLinear' + curve = OpenStudio::Model::CurveBiquadratic.new(model) + curve.setName(data['name']) + curve.setCoefficient1Constant(data['coeff_1']) + curve.setCoefficient2x(data['coeff_2']) + curve.setCoefficient4y(data['coeff_3']) + curve.setMinimumValueofx(data['minimum_independent_variable_1']) if data['minimum_independent_variable_1'] + curve.setMaximumValueofx(data['maximum_independent_variable_1']) if data['maximum_independent_variable_1'] + curve.setMinimumValueofy(data['minimum_independent_variable_2']) if data['minimum_independent_variable_2'] + curve.setMaximumValueofy(data['maximum_independent_variable_2']) if data['maximum_independent_variable_2'] + curve.setMinimumCurveOutput(data['minimum_dependent_variable_output']) if data['minimum_dependent_variable_output'] + curve.setMaximumCurveOutput(data['maximum_dependent_variable_output']) if data['maximum_dependent_variable_output'] + return curve + else + OpenStudio::logFree(OpenStudio::Error, "openstudio.Model.Model", "#{curve_name}' has an invalid form: #{data['form']}', cannot create this curve.") return nil end end # Get the full path to the weather file that is specified in the model. # # @return [OpenStudio::OptionalPath] - def get_full_weather_file_path + def model_get_full_weather_file_path(model) full_epw_path = OpenStudio::OptionalPath.new - if weatherFile.is_initialized - epw_path = weatherFile.get.path + 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 @@ -3009,22 +2872,21 @@ # Method to gather prototype simulation results for a specific climate zone, building type, and template # # @param climate_zone [String] string for the ASHRAE climate zone. # @param building_type [String] string for prototype building type. - # @param template [String] string for prototype template to target. # @return [Hash] Returns a hash with data presented in various bins. Returns nil if no search results - def process_results_for_datapoint(climate_zone, building_type, template) + def model_process_results_for_datapoint(model, climate_zone, building_type) # Combine the data from the JSON files into a single hash top_dir = File.expand_path('../../..', File.dirname(__FILE__)) standards_data_dir = "#{top_dir}/data/standards" # Load the legacy idf results JSON file into a ruby hash temp = '' begin - temp = load_resource_relative("../../../data/standards/legacy_idf_results.json", 'r:UTF-8') - rescue NoMethodError + temp = load_resource_relative('../../../data/standards/legacy_idf_results.json', 'r:UTF-8') + rescue NoMethodError temp = File.read("#{standards_data_dir}/legacy_idf_results.json") end legacy_idf_results = JSON.parse(temp) # List of all fuel types @@ -3088,13 +2950,13 @@ # Keep track of floor area for prototype buildings. # This is used to calculate EUI's to compare against non prototype buildings # Areas taken from scorecard Excel Files # - # @param [Sting] building type + # @param building_type [String] the building type # @return [Double] floor area (m^2) of prototype building for building type passed in. Returns nil if unexpected building type - def find_prototype_floor_area(building_type) + def model_find_prototype_floor_area(model, building_type) if building_type == 'FullServiceRestaurant' # 5502 ft^2 result = 511 elsif building_type == 'Hospital' # 241,410 ft^2 (including basement) result = 22_422 elsif building_type == 'LargeHotel' # 122,132 ft^2 @@ -3136,50 +2998,50 @@ return result end # this is used by other methods to get the clinzte aone 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 [bool] re-map small office or leave it alone + # @param remap_office [bool] re-map small office or leave it alone # @return [hash] key for climate zone and building type, both values are strings - def get_building_climate_zone_and_building_type(remap_office = true) + def model_get_building_climate_zone_and_building_type(model, remap_office = true) # get climate zone from model # get ashrae climate zone from model climate_zone = '' - getClimateZones.climateZones.each do |cz| + model.getClimateZones.climateZones.each do |cz| if cz.institution == 'ASHRAE' - if cz.value == '7'||cz.value == '8' - climate_zone = "ASHRAE 169-2006-#{cz.value}A" - else - climate_zone = "ASHRAE 169-2006-#{cz.value}" - end + climate_zone = if cz.value == '7' || cz.value == '8' + "ASHRAE 169-2006-#{cz.value}A" + else + "ASHRAE 169-2006-#{cz.value}" + end next end end # get building type from model building_type = '' - if getBuilding.standardsBuildingType.is_initialized - building_type = getBuilding.standardsBuildingType.get + if model.getBuilding.standardsBuildingType.is_initialized + building_type = model.getBuilding.standardsBuildingType.get end # map office building type to small medium or large if building_type == 'Office' && remap_office - open_studio_area = getBuilding.floorArea - building_type = self.remap_office(open_studio_area) + open_studio_area = model.getBuilding.floorArea + building_type = model_remap_office(model, open_studio_area) end results = {} results['climate_zone'] = climate_zone results['building_type'] = building_type return results end # remap office to one of the protptye buildings - # @param [Double] floor area + # @param floor_area [Double] floor area (m^2) # @return [String] SmallOffice, MediumOffice, LargeOffice - def remap_office(floor_area) + def model_remap_office(model, floor_area) # prototype small office approx 500 m^2 # prototype medium office approx 5000 m^2 # prototype large office approx 50,000 m^2 # map office building type to small medium or large building_type = if floor_area < 2750 @@ -3193,22 +3055,21 @@ # user needs to pass in template as string. The building type and climate zone will come from the model. # If the building type or ASHRAE climate zone is not set in the model this will return nil # If the lookup doesn't find matching simulation results this wil return nil # - # @param [String] target prototype template for eui lookup # @return [Double] EUI (MJ/m^2) for target template for given OSM. Returns nil if can't calculate EUI - def find_target_eui(template) - building_data = get_building_climate_zone_and_building_type + def model_find_target_eui(model) + building_data = model_get_building_climate_zone_and_building_type(model) climate_zone = building_data['climate_zone'] building_type = building_data['building_type'] # look up results - target_consumption = process_results_for_datapoint(climate_zone, building_type, template) + target_consumption = model_process_results_for_datapoint(model, climate_zone, building_type) # lookup target floor area for prototype buildings - target_floor_area = find_prototype_floor_area(building_type) + target_floor_area = model_find_prototype_floor_area(model, building_type) if target_consumption['total_legacy_energy_val'] > 0 if target_floor_area > 0 result = target_consumption['total_legacy_energy_val'] / target_floor_area else @@ -3225,22 +3086,21 @@ # user needs to pass in template as string. The building type and climate zone will come from the model. # If the building type or ASHRAE climate zone is not set in the model this will return nil # If the lookup doesn't find matching simulation results this wil return nil # - # @param [String] target prototype template for eui lookup # @return [Hash] EUI (MJ/m^2) This will return a hash of end uses. key is end use, value is eui - def find_target_eui_by_end_use(template) - building_data = get_building_climate_zone_and_building_type + def model_find_target_eui_by_end_use(model) + building_data = model_get_building_climate_zone_and_building_type(model) climate_zone = building_data['climate_zone'] building_type = building_data['building_type'] # look up results - target_consumption = process_results_for_datapoint(climate_zone, building_type, template) + target_consumption = model_process_results_for_datapoint(model, climate_zone, building_type) # lookup target floor area for prototype buildings - target_floor_area = find_prototype_floor_area(building_type) + target_floor_area = model_find_prototype_floor_area(model, building_type) if target_consumption['total_legacy_energy_val'] > 0 if target_floor_area > 0 result = {} target_consumption['total_energy_by_end_use'].each do |end_use, consumption| @@ -3296,15 +3156,15 @@ # Skylight # TubularDaylightDome # TubularDaylightDiffuser # return [Array<OpenStudio::Model::ConstructionBase>] # an array of all constructions. - def find_constructions(boundary_condition, type) + def model_find_constructions(model, boundary_condition, type) constructions = [] # From default construction sets - getDefaultConstructionSets.each do |const_set| + model.getDefaultConstructionSets.sort.each do |const_set| ext_surfs = const_set.defaultExteriorSurfaceConstructions int_surfs = const_set.defaultInteriorSurfaceConstructions gnd_surfs = const_set.defaultGroundContactSurfaceConstructions ext_subsurfs = const_set.defaultExteriorSubSurfaceConstructions int_subsurfs = const_set.defaultInteriorSubSurfaceConstructions @@ -3326,57 +3186,57 @@ ext_subsurfs = ext_subsurfs.get int_subsurfs = int_subsurfs.get case type # Exterior Surfaces - when 'ExteriorWall', 'AtticWall' - constructions << ext_surfs.wallConstruction - when 'ExteriorFloor' - constructions << ext_surfs.floorConstruction - when 'ExteriorRoof', 'AtticRoof' - constructions << ext_surfs.roofCeilingConstruction + when 'ExteriorWall', 'AtticWall' + constructions << ext_surfs.wallConstruction + when 'ExteriorFloor' + constructions << ext_surfs.floorConstruction + when 'ExteriorRoof', 'AtticRoof' + constructions << ext_surfs.roofCeilingConstruction # Interior Surfaces - when 'InteriorWall', 'DemisingWall', 'InteriorPartition' - constructions << int_surfs.wallConstruction - when 'InteriorFloor', 'AtticFloor', 'DemisingFloor' - constructions << int_surfs.floorConstruction - when 'InteriorCeiling', 'DemisingRoof' - constructions << int_surfs.roofCeilingConstruction + when 'InteriorWall', 'DemisingWall', 'InteriorPartition' + constructions << int_surfs.wallConstruction + when 'InteriorFloor', 'AtticFloor', 'DemisingFloor' + constructions << int_surfs.floorConstruction + when 'InteriorCeiling', 'DemisingRoof' + constructions << int_surfs.roofCeilingConstruction # Ground Contact Surfaces - when 'GroundContactWall' - constructions << gnd_surfs.wallConstruction - when 'GroundContactFloor' - constructions << gnd_surfs.floorConstruction - when 'GroundContactRoof' - constructions << gnd_surfs.roofCeilingConstruction + when 'GroundContactWall' + constructions << gnd_surfs.wallConstruction + when 'GroundContactFloor' + constructions << gnd_surfs.floorConstruction + when 'GroundContactRoof' + constructions << gnd_surfs.roofCeilingConstruction # Exterior SubSurfaces - when 'ExteriorWindow' - constructions << ext_subsurfs.fixedWindowConstruction - constructions << ext_subsurfs.operableWindowConstruction - when 'ExteriorDoor' - constructions << ext_subsurfs.doorConstruction - when 'GlassDoor' - constructions << ext_subsurfs.glassDoorConstruction - when 'OverheadDoor' - constructions << ext_subsurfs.overheadDoorConstruction - when 'Skylight' - constructions << ext_subsurfs.skylightConstruction - when 'TubularDaylightDome' - constructions << ext_subsurfs.tubularDaylightDomeConstruction - when 'TubularDaylightDiffuser' - constructions << ext_subsurfs.tubularDaylightDiffuserConstruction + when 'ExteriorWindow' + constructions << ext_subsurfs.fixedWindowConstruction + constructions << ext_subsurfs.operableWindowConstruction + when 'ExteriorDoor' + constructions << ext_subsurfs.doorConstruction + when 'GlassDoor' + constructions << ext_subsurfs.glassDoorConstruction + when 'OverheadDoor' + constructions << ext_subsurfs.overheadDoorConstruction + when 'Skylight' + constructions << ext_subsurfs.skylightConstruction + when 'TubularDaylightDome' + constructions << ext_subsurfs.tubularDaylightDomeConstruction + when 'TubularDaylightDiffuser' + constructions << ext_subsurfs.tubularDaylightDiffuserConstruction # Interior SubSurfaces - when 'InteriorWindow' - constructions << int_subsurfs.fixedWindowConstruction - constructions << int_subsurfs.operableWindowConstruction - when 'InteriorDoor' - constructions << int_subsurfs.doorConstruction + when 'InteriorWindow' + constructions << int_subsurfs.fixedWindowConstruction + constructions << int_subsurfs.operableWindowConstruction + when 'InteriorDoor' + constructions << int_subsurfs.doorConstruction end end # Hard-assigned surfaces - getSurfaces.each do |surf| + model.getSurfaces.sort.each do |surf| next unless surf.outsideBoundaryCondition == boundary_condition surf_type = surf.surfaceType if surf_type == 'Floor' || surf_type == 'Wall' next unless type.include?(surf_type) elsif surf_type == 'RoofCeiling' @@ -3384,11 +3244,11 @@ end constructions << surf.construction end # Hard-assigned subsurfaces - getSubSurfaces.each do |surf| + model.getSubSurfaces.sort.each do |surf| next unless surf.outsideBoundaryCondition == boundary_condition surf_type = surf.subSurfaceType if surf_type == 'FixedWindow' || surf_type == 'OperableWindow' next unless type == 'ExteriorWindow' elsif surf_type == 'Door' @@ -3419,14 +3279,13 @@ # constructions. 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. # - # @param template [String] valid choices are 90.1-2004, # 90.1-2007, 90.1-2010, 90.1-2013 # @return [Bool] returns true if successful, false if not - def apply_prm_construction_types(template) + def model_apply_prm_construction_types(model) types_to_modify = [] # Possible boundary conditions are # Adiabatic # Surface @@ -3483,44 +3342,35 @@ # Glass with Curb # Plastic with Curb # Without Curb # Create an array of types + types_to_modify << ['Outdoors', 'ExteriorWall', 'SteelFramed'] + types_to_modify << ['Outdoors', 'ExteriorRoof', 'IEAD'] + types_to_modify << ['Outdoors', 'ExteriorFloor', 'SteelFramed'] + types_to_modify << ['Ground', 'GroundContactFloor', 'Unheated'] + types_to_modify << ['Ground', 'GroundContactWall', 'Mass'] - case template - when 'NECB 2011' - BTAP::Compliance::NECB2011.set_all_construction_sets_to_necb!(self, runner = nil) - return true - else - case template - when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' - types_to_modify << ['Outdoors', 'ExteriorWall', 'SteelFramed'] - types_to_modify << ['Outdoors', 'ExteriorRoof', 'IEAD'] - types_to_modify << ['Outdoors', 'ExteriorFloor', 'SteelFramed'] - types_to_modify << ['Ground', 'GroundContactFloor', 'Unheated'] - types_to_modify << ['Ground', 'GroundContactWall', 'Mass'] - end - # Modify all constructions of each type - types_to_modify.each do |boundary_cond, surf_type, const_type| - constructions = find_constructions(boundary_cond, surf_type) + # Modify all constructions of each type + types_to_modify.each do |boundary_cond, surf_type, const_type| + constructions = model_find_constructions(model, boundary_cond, surf_type) - constructions.sort.each do |const| - standards_info = const.standardsInformation - standards_info.setIntendedSurfaceType(surf_type) - standards_info.setStandardsConstructionType(const_type) - end + constructions.sort.each do |const| + standards_info = const.standardsInformation + standards_info.setIntendedSurfaceType(surf_type) + standards_info.setStandardsConstructionType(const_type) end - return true end - return false + + 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 - def apply_standard_constructions(template, climate_zone) + def model_apply_standard_constructions(model, climate_zone) types_to_modify = [] # Possible boundary conditions are # Adiabatic # Surface @@ -3539,48 +3389,44 @@ # Skylight # TubularDaylightDome # TubularDaylightDiffuser # Create an array of surface types - # each standard applies to. - case template - when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' - types_to_modify << ['Outdoors', 'Floor'] - types_to_modify << ['Outdoors', 'Wall'] - types_to_modify << ['Outdoors', 'RoofCeiling'] - types_to_modify << ['Outdoors', 'FixedWindow'] - types_to_modify << ['Outdoors', 'OperableWindow'] - types_to_modify << ['Outdoors', 'Door'] - types_to_modify << ['Outdoors', 'GlassDoor'] - types_to_modify << ['Outdoors', 'OverheadDoor'] - types_to_modify << ['Outdoors', 'Skylight'] - types_to_modify << ['Ground', 'Floor'] - types_to_modify << ['Ground', 'Wall'] - end + types_to_modify << ['Outdoors', 'Floor'] + types_to_modify << ['Outdoors', 'Wall'] + types_to_modify << ['Outdoors', 'RoofCeiling'] + types_to_modify << ['Outdoors', 'FixedWindow'] + types_to_modify << ['Outdoors', 'OperableWindow'] + types_to_modify << ['Outdoors', 'Door'] + types_to_modify << ['Outdoors', 'GlassDoor'] + types_to_modify << ['Outdoors', 'OverheadDoor'] + types_to_modify << ['Outdoors', 'Skylight'] + types_to_modify << ['Ground', 'Floor'] + types_to_modify << ['Ground', 'Wall'] # Find just those surfaces surfaces_to_modify = [] types_to_modify.each do |boundary_condition, surface_type| # Surfaces - getSurfaces.each do |surf| + model.getSurfaces.sort.each do |surf| next unless surf.outsideBoundaryCondition == boundary_condition next unless surf.surfaceType == surface_type surfaces_to_modify << surf end # SubSurfaces - getSubSurfaces.each do |surf| + model.getSubSurfaces.sort.each do |surf| next unless surf.outsideBoundaryCondition == boundary_condition next unless surf.subSurfaceType == surface_type surfaces_to_modify << surf end end # Modify these surfaces prev_created_consts = {} surfaces_to_modify.sort.each do |surf| - prev_created_consts = surf.apply_standard_construction(template, climate_zone, prev_created_consts) + prev_created_consts = planar_surface_apply_standard_construction(surf, climate_zone, prev_created_consts) end # List the unique array of constructions if prev_created_consts.size.zero? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', 'None of the constructions in your proposed model have both Intended Surface Type and Standards Construction Type') @@ -3593,19 +3439,18 @@ return true end # Returns standards data for selected construction # - # @param [string] target template for lookup - # @param [string] intended_surface_type template for lookup - # @param [string] standards_construction_type template for lookup - # @param [string] building_category template for lookup + # @param intended_surface_type [string] the surface type + # @param standards_construction_type [string] the type of construction + # @param building_category [string] the type of building # @return [hash] hash of construction properties - def get_construction_properties(template, intended_surface_type, standards_construction_type, building_category = 'Nonresidential') + def model_get_construction_properties(model, intended_surface_type, standards_construction_type, building_category = 'Nonresidential') # get climate_zone_set - climate_zone = get_building_climate_zone_and_building_type['climate_zone'] - climate_zone_set = find_climate_zone_set(climate_zone, template) + climate_zone = model_get_building_climate_zone_and_building_type(model)['climate_zone'] + climate_zone_set = model_find_climate_zone_set(model, climate_zone) # populate search hash search_criteria = { 'template' => template, 'climate_zone_set' => climate_zone_set, @@ -3613,11 +3458,11 @@ 'standards_construction_type' => standards_construction_type, 'building_category' => building_category } # switch to use this but update test in standards and measures to load this outside of the method - construction_properties = find_object($os_standards['construction_properties'], search_criteria) + construction_properties = model_find_object(standards_data['construction_properties'], search_criteria) return construction_properties end # Reduces the WWR to the values specified by the PRM. WWR reduction @@ -3629,11 +3474,11 @@ # @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 - def apply_prm_baseline_window_to_wall_ratio(template, climate_zone) + def model_apply_prm_baseline_window_to_wall_ratio(model, climate_zone) # Loop through all spaces in the model, and # per the PNNL PRM Reference Manual, find the areas # of each space conditioning category (res, nonres, semi-heated) # separately. Include space multipliers. nr_wall_m2 = 0.001 # Avoids divide by zero errors later @@ -3644,11 +3489,11 @@ sh_wind_m2 = 0 total_wall_m2 = 0.001 total_subsurface_m2 = 0.0 # Store the space conditioning category for later use space_cats = {} - getSpaces.each do |space| + model.getSpaces.sort.each do |space| # Loop through all surfaces in this space wall_area_m2 = 0 wind_area_m2 = 0 space.surfaces.sort.each do |surface| # Skip non-outdoor surfaces @@ -3657,72 +3502,63 @@ next unless surface.surfaceType.casecmp('wall').zero? # This wall's gross area (including window area) wall_area_m2 += surface.grossArea * space.multiplier # Subsurfaces in this surface surface.subSurfaces.sort.each do |ss| - if 'NECB 2011' == template - wind_area_m2 += ss.netArea * space.multiplier - elsif ss.subSurfaceType == 'FixedWindow' || ss.subSurfaceType == 'OperableWindow' - wind_area_m2 += ss.netArea * space.multiplier - else - next - end + next unless ss.subSurfaceType == 'FixedWindow' || ss.subSurfaceType == 'OperableWindow' + wind_area_m2 += ss.netArea * space.multiplier end end # Determine the space category # 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 = space.conditioning_category(template, climate_zone) - cooled = space.cooled? - heated = space.heated? + # cat = thermal_zone_conditioning_category(space, template, climate_zone) + cooled = space_cooled?(space) + heated = space_heated?(space) cat = 'Unconditioned' # Unconditioned if !heated && !cooled cat = 'Unconditioned' - # Heated-Only + # Heated-Only elsif heated && !cooled cat = 'Semiheated' - # Heated and Cooled + # Heated and Cooled else - res = space.residential?(template) + res = space_residential?(space) cat = if res 'ResConditioned' else 'NonResConditioned' end end space_cats[space] = cat # Add to the correct category case cat - when 'Unconditioned' - next # Skip unconditioned spaces - when 'NonResConditioned' - nr_wall_m2 += wall_area_m2 - nr_wind_m2 += wind_area_m2 - when 'ResConditioned' - res_wall_m2 += wall_area_m2 - res_wind_m2 += wind_area_m2 - when 'Semiheated' - sh_wall_m2 += wall_area_m2 - sh_wind_m2 += wind_area_m2 + when 'Unconditioned' + next # Skip unconditioned spaces + when 'NonResConditioned' + nr_wall_m2 += wall_area_m2 + nr_wind_m2 += wind_area_m2 + when 'ResConditioned' + res_wall_m2 += wall_area_m2 + res_wind_m2 += wind_area_m2 + when 'Semiheated' + sh_wall_m2 += wall_area_m2 + sh_wind_m2 += wind_area_m2 end - # keep track of totals for NECB - total_wall_m2 += wall_area_m2 - total_subsurface_m2 += wind_area_m2 # this contains doors as well. end # Calculate the WWR of each category wwr_nr = ((nr_wind_m2 / nr_wall_m2) * 100.0).round(1) wwr_res = ((res_wind_m2 / res_wall_m2) * 100).round(1) wwr_sh = ((sh_wind_m2 / sh_wall_m2) * 100).round(1) - fdwr = ((total_subsurface_m2 / total_wall_m2) * 100).round(1) # used by NECB 2011 # Convert to IP and report nr_wind_ft2 = OpenStudio.convert(nr_wind_m2, 'm^2', 'ft^2').get nr_wall_ft2 = OpenStudio.convert(nr_wall_m2, 'm^2', 'ft^2').get @@ -3738,58 +3574,30 @@ # WWR limit wwr_lim = 40.0 # Check against WWR limit - red_nr = wwr_nr > wwr_lim ? true : false - red_res = wwr_res > wwr_lim ? true : false - red_sh = wwr_sh > wwr_lim ? true : false + red_nr = wwr_nr > wwr_lim + red_res = wwr_res > wwr_lim + red_sh = wwr_sh > wwr_lim - case template - when 'NECB 2011' - # NECB FDWR limit - hdd = BTAP::Environment::WeatherFile.new(weatherFile.get.path.get).hdd18 - fdwr_lim = (BTAP::Compliance::NECB2011.max_fwdr(hdd) * 100.0).round(1) + # Stop here unless windows need reducing + return true unless red_nr || red_res || red_sh - # Stop here unless windows / doors need reducing - return true unless fdwr > fdwr_lim - OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{wwr_lim.round}%.") - # Determine the factors by which to reduce the window / door area - mult = fdwr_lim / fdwr - # Reduce the window area if any of the categories necessary - getSpaces.each do |space| - # Loop through all surfaces in this space - space.surfaces.sort.each do |surface| - # Skip non-outdoor surfaces - next unless surface.outsideBoundaryCondition == 'Outdoors' - # Skip non-walls - next unless surface.surfaceType == 'Wall' - # Subsurfaces in this surface - surface.subSurfaces.sort.each do |ss| - # Reduce the size of the window - red = 1.0 - mult - ss.reduce_area_by_percent_by_raising_sill(red) - end - end - end - else # all other template types - # Stop here unless windows need reducing - return true unless red_nr || red_res || red_sh + # Determine the factors by which to reduce the window area + mult_nr_red = wwr_lim / wwr_nr + mult_res_red = wwr_lim / wwr_res + mult_sh_red = wwr_lim / wwr_sh - # Determine the factors by which to reduce the window area - mult_nr_red = wwr_lim / wwr_nr - mult_res_red = wwr_lim / wwr_res - mult_sh_red = wwr_lim / wwr_sh + # Reduce the window area if any of the categories necessary + model.getSpaces.sort.each do |space| + # Determine the space category + # from the previously stored values + cat = space_cats[space] - # Reduce the window area if any of the categories necessary - getSpaces.each do |space| - # Determine the space category - # from the previously stored values - cat = space_cats[space] - - # Get the correct multiplier - case cat + # Get the correct multiplier + case cat when 'Unconditioned' next # Skip unconditioned spaces when 'NonResConditioned' next unless red_nr mult = mult_nr_red @@ -3797,46 +3605,44 @@ next unless red_res mult = mult_res_red when 'Semiheated' next unless red_sh mult = mult_sh_red - end + end - # Loop through all surfaces in this space - space.surfaces.sort.each do |surface| - # Skip non-outdoor surfaces - next unless surface.outsideBoundaryCondition == 'Outdoors' - # Skip non-walls - next unless surface.surfaceType.casecmp('wall').zero? - # Subsurfaces in this surface - surface.subSurfaces.sort.each do |ss| - next unless ss.subSurfaceType == 'FixedWindow' || ss.subSurfaceType == 'OperableWindow' - # Reduce the size of the window - # If a vertical rectangle, raise sill height to avoid - # impacting daylighting areas, otherwise - # reduce toward centroid. - red = 1.0 - mult - if ss.vertical_rectangle? - ss.reduce_area_by_percent_by_raising_sill(red) - else - ss.reduce_area_by_percent_by_shrinking_toward_centroid(red) - end + # Loop through all surfaces in this space + space.surfaces.sort.each do |surface| + # Skip non-outdoor surfaces + next unless surface.outsideBoundaryCondition == 'Outdoors' + # Skip non-walls + next unless surface.surfaceType.casecmp('wall').zero? + # Subsurfaces in this surface + surface.subSurfaces.sort.each do |ss| + next unless ss.subSurfaceType == 'FixedWindow' || ss.subSurfaceType == 'OperableWindow' + # Reduce the size of the window + # If a vertical rectangle, raise sill height to avoid + # impacting daylighting areas, otherwise + # reduce toward centroid. + red = 1.0 - mult + if sub_surface_vertical_rectangle?(ss) + sub_surface_reduce_area_by_percent_by_raising_sill(ss, red) + else + sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red) end end end - end return true end # Reduces the SRR to the values specified by the PRM. SRR reduction # will be done by shrinking vertices toward the centroid. # # @todo support semiheated spaces as a separate SRR category # @todo add skylight frame area to calculation of SRR - def apply_prm_baseline_skylight_to_roof_ratio(template) + 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 # of each space conditioning category (res, nonres, semi-heated) # separately. Include space multipliers. nr_wall_m2 = 0.001 # Avoids divide by zero errors later @@ -3845,11 +3651,11 @@ res_sky_m2 = 0 sh_wall_m2 = 0.001 sh_sky_m2 = 0 total_roof_m2 = 0.001 total_subsurface_m2 = 0 - getSpaces.each do |space| + model.getSpaces.sort.each do |space| # Loop through all surfaces in this space wall_area_m2 = 0 sky_area_m2 = 0 space.surfaces.sort.each do |surface| # Skip non-outdoor surfaces @@ -3858,35 +3664,35 @@ next unless surface.surfaceType == 'RoofCeiling' # This wall's gross area (including skylight area) wall_area_m2 += surface.grossArea * space.multiplier # Subsurfaces in this surface surface.subSurfaces.sort.each do |ss| - next unless 'NECB 2011' == template || (ss.subSurfaceType == 'Skylight') + next unless ss.subSurfaceType == 'Skylight' sky_area_m2 += ss.netArea * space.multiplier end end # Determine the space category cat = 'NonRes' - if space.residential?(template) + if space_residential?(space) cat = 'Res' end # if space.is_semiheated # cat = 'Semiheated' # end # Add to the correct category case cat - when 'NonRes' - nr_wall_m2 += wall_area_m2 - nr_sky_m2 += sky_area_m2 - when 'Res' - res_wall_m2 += wall_area_m2 - res_sky_m2 += sky_area_m2 - when 'Semiheated' - sh_wall_m2 += wall_area_m2 - sh_sky_m2 += sky_area_m2 + when 'NonRes' + nr_wall_m2 += wall_area_m2 + nr_sky_m2 += sky_area_m2 + when 'Res' + res_wall_m2 += wall_area_m2 + res_sky_m2 += sky_area_m2 + when 'Semiheated' + sh_wall_m2 += wall_area_m2 + sh_sky_m2 += sky_area_m2 end total_roof_m2 += wall_area_m2 total_subsurface_m2 += sky_area_m2 end @@ -3896,140 +3702,115 @@ srr_sh = ((sh_sky_m2 / sh_wall_m2) * 100).round(1) srr = ((total_subsurface_m2 / total_roof_m2) * 100.0).round(1) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The skylight to roof ratios (SRRs) are: NonRes: #{srr_nr.round}%, Res: #{srr_res.round}%.") # SRR limit - srr_lim = nil - case template - when '90.1-2004', '90.1-2007', '90.1-2010', 'NECB 2011' - srr_lim = 5.0 - when '90.1-2013' - srr_lim = 3.0 - end + srr_lim = model_prm_skylight_to_roof_ratio_limit(model) # Check against SRR limit - red_nr = srr_nr > srr_lim ? true : false - red_res = srr_res > srr_lim ? true : false - red_sh = srr_sh > srr_lim ? true : false + red_nr = srr_nr > srr_lim + red_res = srr_res > srr_lim + red_sh = srr_sh > srr_lim - case template - when 'NECB 2011' - # Stop here unless windows need reducing - return true unless srr > srr_lim - OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{srr_lim.round}%.") - # Determine the factors by which to reduce the window / door area - mult = srr_lim / srr + # Stop here unless skylights need reducing + return true unless red_nr || red_res || red_sh - # Reduce the subsurface areas - getSpaces.each do |space| - # Loop through all surfaces in this space - space.surfaces.sort.each do |surface| - # Skip non-outdoor surfaces - next unless surface.outsideBoundaryCondition == 'Outdoors' - # Skip non-walls - next unless surface.surfaceType == 'RoofCeiling' - # Subsurfaces in this surface - surface.subSurfaces.sort.each do |ss| - # Reduce the size of the subsurface - red = 1.0 - mult - ss.reduce_area_by_percent_by_shrinking_toward_centroid(red) - end - end - end + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all skylights equally down to the limit of #{srr_lim.round}%.") - else + # Determine the factors by which to reduce the skylight area + mult_nr_red = srr_lim / srr_nr + mult_res_red = srr_lim / srr_res + # mult_sh_red = srr_lim / srr_sh - # Stop here unless skylights need reducing - return true unless red_nr || red_res || red_sh + # Reduce the skylight area if any of the categories necessary + model.getSpaces.sort.each do |space| + # Determine the space category + cat = 'NonRes' + if space_residential?(space) + cat = 'Res' + end + # if space.is_semiheated + # cat = 'Semiheated' + # end - OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all skylights equally down to the limit of #{srr_lim.round}%.") - - # Determine the factors by which to reduce the skylight area - mult_nr_red = srr_lim / srr_nr - mult_res_red = srr_lim / srr_res - # mult_sh_red = srr_lim / srr_sh - - # Reduce the skylight area if any of the categories necessary - getSpaces.each do |space| - # Determine the space category - cat = 'NonRes' - if space.residential?(template) - cat = 'Res' - end - # if space.is_semiheated - # cat = 'Semiheated' - # end - - # Skip spaces whose skylights don't need to be reduced - case cat + # Skip spaces whose skylights don't need to be reduced + case cat when 'NonRes' next unless red_nr mult = mult_nr_red when 'Res' next unless red_res mult = mult_res_red when 'Semiheated' next unless red_sh - # mult = mult_sh_red - end + # mult = mult_sh_red + end - # Loop through all surfaces in this space - space.surfaces.sort.each do |surface| - # Skip non-outdoor surfaces - next unless surface.outsideBoundaryCondition == 'Outdoors' - # Skip non-walls - next unless surface.surfaceType == 'RoofCeiling' - # Subsurfaces in this surface - surface.subSurfaces.sort.each do |ss| - next unless ss.subSurfaceType == 'Skylight' - # Reduce the size of the skylight - red = 1.0 - mult - ss.reduce_area_by_percent_by_shrinking_toward_centroid(red) + # Loop through all surfaces in this space + space.surfaces.sort.each do |surface| + # Skip non-outdoor surfaces + next unless surface.outsideBoundaryCondition == 'Outdoors' + # Skip non-walls + next unless surface.surfaceType == 'RoofCeiling' + # Subsurfaces in this surface + surface.subSurfaces.sort.each do |ss| + next unless ss.subSurfaceType == 'Skylight' + # Reduce the size of the skylight + red = 1.0 - mult + sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red) end - end end - end # template case + end + return true end + # Determines the skylight to roof ratio limit for a given standard + # @return [Double] the skylight to roof ratio, as a percent: 5.0 = 5% + # 5% by default. + def model_prm_skylight_to_roof_ratio_limit(model) + srr_lim = 5.0 + return srr_lim + end + # 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 # # @return [Bool] true if successful, false if not - def remove_prm_hvac + def model_remove_prm_hvac(model) # Plant loops - getPlantLoops.each do |loop| + model.getPlantLoops.sort.each do |loop| # Don't remove service water heating loops - next if loop.swh_loop? + next if plant_loop_swh_loop?(loop) loop.remove end # Air loops - getAirLoopHVACs.each(&:remove) + model.getAirLoopHVACs.each(&:remove) # Zone equipment - getThermalZones.each do |zone| + model.getThermalZones.sort.each do |zone| zone.equipment.each do |zone_equipment| next if zone_equipment.to_FanZoneExhaust.is_initialized zone_equipment.remove end end # Outdoor VRF units (not in zone, not in loops) - getAirConditionerVariableRefrigerantFlows.each(&:remove) + model.getAirConditionerVariableRefrigerantFlows.each(&:remove) return true end # Remove external shading devices. # Site shading will not be impacted. # @return [Bool] returns true if successful, false if not. - def remove_external_shading_devices + def model_remove_external_shading_devices(model) shading_surfaces_removed = 0 - getShadingSurfaceGroups.each do |shade_group| + model.getShadingSurfaceGroups.sort.each do |shade_group| # Skip Site shading next if shade_group.shadingSurfaceType == 'Site' # Space shading surfaces should be removed shading_surfaces_removed += shade_group.shadingSurfaces.size shade_group.remove @@ -4039,43 +3820,42 @@ return true end # Changes the sizing parameters to the PRM specifications. - def apply_prm_sizing_parameters - + def model_apply_prm_sizing_parameters(model) clg = 1.15 htg = 1.25 - sizing_params = getSizingParameters + sizing_params = model.getSizingParameters sizing_params.setHeatingSizingFactor(htg) sizing_params.setCoolingSizingFactor(clg) OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Set sizing factors to #{htg} for heating and #{clg} for cooling.") end - + # Helper method to get the story object that # cooresponds to a specific minimum z value. # Makes a new story if none found at this height. # # # @param minz [Double] the z value (height) of the # desired story, in meters. # @param tolerance [Double] tolerance for comparison, in m. # Default is 0.3 m ~1ft # @return [OpenStudio::Model::BuildingStory] the story - def get_story_for_nominal_z_coordinate(minz, tolerance = 0.3) - getBuildingStorys.each do |story| - z = story.minimum_z_value + def model_get_story_for_nominal_z_coordinate(model, minz, tolerance = 0.3) + model.getBuildingStorys.sort.each do |story| + z = building_story_minimum_z_value(story) if (minz - z).abs < tolerance OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "The story with a min z value of #{minz.round(2)} is #{story.name}.") return story end end - story = OpenStudio::Model::BuildingStory.new(self) + story = OpenStudio::Model::BuildingStory.new(model) story.setNominalZCoordinate(minz) OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "No story with a min z value of #{minz.round(2)} m +/- #{tolerance} m was found, so a new story called #{story.name} was created.") return story end @@ -4087,15 +3867,15 @@ # Will return an array of hashes. Many may have one array entry. # all values other than block size are gallons. # # @return [Array] array of hashes. Each array entry based on different capacity # specific to building type. Array will be empty for some building types. - def find_ashrae_hot_water_demand + def model_find_ashrae_hot_water_demand(model) # TODO: - for types not in table use standards area normalized swh values # get building type - building_data = get_building_climate_zone_and_building_type + building_data = model_get_building_climate_zone_and_building_type(model) building_type = building_data['building_type'] result = [] if building_type == 'FullServiceRestaurant' result << { units: 'meal', block: nil, max_hourly: 1.5, max_daily: 11.0, avg_day_unit: 2.4 } @@ -4139,33 +3919,33 @@ # Returns average daily hot water consumption for residential buildings # gal/day from ICC IECC 2015 Residential Standard Reference Design # from Table R405.5.2(1) # # @return [Double] gal/day - def find_icc_iecc_2015_hot_water_demand(units_per_bldg, bedrooms_per_unit) + def model_find_icc_iecc_2015_hot_water_demand(model, units_per_bldg, bedrooms_per_unit) swh_gal_per_day = units_per_bldg * (30.0 + (10.0 * bedrooms_per_unit)) return swh_gal_per_day end # Returns average daily internal loads for residential buildings # from Table R405.5.2(1) # # @return [Hash] mech_vent_cfm, infiltration_ach, igain_btu_per_day, internal_mass_lbs - def find_icc_iecc_2015_internal_loads(units_per_bldg, bedrooms_per_unit) + def model_find_icc_iecc_2015_internal_loads(model, units_per_bldg, bedrooms_per_unit) # get total and conditioned floor area - total_floor_area = getBuilding.floorArea - if getBuilding.conditionedFloorArea.is_initialized - conditioned_floor_area = getBuilding.conditionedFloorArea.get + total_floor_area = model.getBuilding.floorArea + if model.getBuilding.conditionedFloorArea.is_initialized + conditioned_floor_area = model.getBuilding.conditionedFloorArea.get else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', 'Cannot find conditioned floor area, will use total floor area.') conditioned_floor_area = total_floor_area end # get climate zone value climate_zone_value = '' - getClimateZones.climateZones.each do |cz| + model.getClimateZones.climateZones.each do |cz| if cz.institution == 'ASHRAE' climate_zone_value = cz.value next end end @@ -4183,11 +3963,11 @@ return internal_loads end # Helper method to make a shortened version of a name # that will be readable in a GUI. - def make_name(template, clim, building_type, spc_type) + def model_make_name(model, clim, building_type, spc_type) clim = clim.gsub('ClimateZone ', 'CZ') if clim == 'CZ1-8' clim = '' end @@ -4248,62 +4028,54 @@ return result end # Helper method to find out which climate zone set contains a specific climate zone. # Returns climate zone set name as String if success, nil if not found. - def find_climate_zone_set(clim, template) + def model_find_climate_zone_set(model, clim) result = nil - possible_climate_zones = [] - $os_standards['climate_zone_sets'].each do |climate_zone_set| + possible_climate_zone_sets = [] + standards_data['climate_zone_sets'].each do |climate_zone_set| if climate_zone_set['climate_zones'].include?(clim) - possible_climate_zones << climate_zone_set['name'] + possible_climate_zone_sets << climate_zone_set['name'] end end # Check the results - if possible_climate_zones.size.zero? + if possible_climate_zone_sets.size.zero? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Cannot find a climate zone set containing #{clim}") - elsif possible_climate_zones.size > 2 + elsif possible_climate_zone_sets.size > 2 OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Found more than 2 climate zone sets containing #{clim}; will return last matching cliimate zone set.") end - # For Pre-1980 and 1980-2004, use the most specific climate zone set. - # For example, 2A and 2 both contain 2A, so use 2A. - # For 2004-2013, use least specific climate zone set. - # For example, 2A and 2 both contain 2A, so use 2. - case template - when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004' - result = possible_climate_zones.sort.last - when '90.1-2007', '90.1-2010', '90.1-2013', 'NECB 2011' - result = possible_climate_zones.sort.first - when '90.1-2004' - result = if possible_climate_zones.include? 'ClimateZone 3' - possible_climate_zones.sort.last - else - possible_climate_zones.sort.first - end - when 'ICC IECC 2015', 'OEESC 2014' - result = possible_climate_zones.sort.first - end + # Get the climate zone from the possible set + climate_zone_set = model_get_climate_zone_set_from_list(model, possible_climate_zone_sets) # Check that a climate zone set was found - if result.nil? + if climate_zone_set.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Cannot find a climate zone set when #{template}") end - return result + return climate_zone_set end + # Determine which climate zone to use. + # Defaults to the least specific climate zone set. + # For example, 2A and 2 both contain 2A, so use 2. + def model_get_climate_zone_set_from_list(model, possible_climate_zone_sets) + climate_zone_set = possible_climate_zone_sets.sort.first + return climate_zone_set + end + # 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 NECB spacetype, or is undefined, an error will stop + # 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. - def validate_standards_spacetypes_in_model(template) + def model_validate_standards_spacetypes_in_model(model) error_string = '' # populate search hash - getSpaces.each do |space| + model.getSpaces.sort.each do |space| unless space.spaceType.empty? if space.spaceType.get.standardsSpaceType.empty? || space.spaceType.get.standardsBuildingType.empty? error_string << "Space: #{space.name} has SpaceType of #{space.spaceType.get.name} but the standardSpaceType or standardBuildingType is undefined. Please use an appropriate standardSpaceType for #{template}\n" next else @@ -4311,19 +4083,19 @@ 'template' => template, 'building_type' => space.spaceType.get.standardsBuildingType.get, 'space_type' => space.spaceType.get.standardsSpaceType.get } # lookup space type properties - space_type_properties = @model.find_object($os_standards['space_types'], search_criteria) + space_type_properties = model_find_object(standards_data['space_types'], search_criteria) if space_type_properties.nil? error_string << "Could not find spacetype of criteria : #{search_criteria}. Please ensure you have a valid standardSpaceType and stantdardBuildingType defined.\n" space_type_properties = {} end end end end - if '' == error_string + if error_string == '' return true else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', error_string) return false end @@ -4331,32 +4103,29 @@ # Create sorted hash of stories with data need to determine effective number of stories above and below grade # the key should be the story object, which would allow other measures the ability to for example loop through spaces of the bottom story # # @return [hash] hash of space types with data in value necessary to determine effective number of stories above and below grade - def create_story_hash - + def model_create_story_hash(model) story_hash = {} # loop through stories - self.getBuildingStorys.each do |story| - + model.getBuildingStorys.sort.each do |story| # skip of story doesn't have any spaces - next if story.spaces.size == 0 + next if story.spaces.empty? story_min_z = nil story_zone_multipliers = [] story_spaces_part_of_floor_area = [] story_spaces_not_part_of_floor_area = [] story_ext_wall_area = 0.0 story_ground_wall_area = 0.0 # loop through space surfaces to find min z value story.spaces.each do |space| - # skip of space doesn't have any geometry - next if space.surfaces.size == 0 + next if space.surfaces.empty? # get space multiplier story_zone_multipliers << space.multiplier # space part of floor area check @@ -4375,105 +4144,94 @@ surface.vertices.each do |vertex| z_points << vertex.z end # update count of ground wall areas - next if not surface.surfaceType == "Wall" - next if not surface.outsideBoundaryCondition == "Ground" # todo - make more flexible for slab/basement modeling + next if surface.surfaceType != 'Wall' + next if surface.outsideBoundaryCondition != 'Ground' # TODO: - make more flexible for slab/basement model.modeling story_ground_wall_area += surface.grossArea - end # skip if surface had no vertices - next if z_points.size == 0 + next if z_points.empty? # update story min_z space_min_z = z_points.min + space.zOrigin - if story_min_z.nil? or story_min_z > space_min_z + if story_min_z.nil? || (story_min_z > space_min_z) story_min_z = space_min_z end - end # update story hash story_hash[story] = {} story_hash[story][:min_z] = story_min_z story_hash[story][:multipliers] = story_zone_multipliers story_hash[story][:part_of_floor_area] = story_spaces_part_of_floor_area story_hash[story][:not_part_of_floor_area] = story_spaces_not_part_of_floor_area story_hash[story][:ext_wall_area] = story_ext_wall_area story_hash[story][:ground_wall_area] = story_ground_wall_area - end # sort hash by min_z low to high - story_hash = story_hash.sort_by{|k,v| v[:min_z]} + story_hash = story_hash.sort_by { |k, v| v[:min_z] } # reassemble into hash after sorting hash = {} story_hash.each do |story, props| hash[story] = props end return hash - end # populate this method # Determine the effective number of stories above and below grade # # @return hash with effective_num_stories_below_grade and effective_num_stories_above_grade - def effective_num_stories - + def model_effective_num_stories(model) below_grade = 0 above_grade = 0 - # call create_story_hash - story_hash = self.create_story_hash + # call model_create_story_hash(model) + story_hash = model_create_story_hash(model) - story_hash.each do |story,hash| - + story_hash.each do |story, hash| # skip if no spaces in story are included in the building area - next if hash[:part_of_floor_area].size == 0 + next if hash[:part_of_floor_area].empty? # only count as below grade if ground wall area is greater than ext wall area and story below is also below grade - if above_grade == 0 and hash[:ground_wall_area] > hash[:ext_wall_area] + if above_grade.zero? && (hash[:ground_wall_area] > hash[:ext_wall_area]) below_grade += 1 * hash[:multipliers].min else above_grade += 1 * hash[:multipliers].min end - end # populate hash effective_num_stories = {} effective_num_stories[:below_grade] = below_grade effective_num_stories[:above_grade] = above_grade effective_num_stories[:story_hash] = story_hash return effective_num_stories - end # create space_type_hash with info such as effective_num_spaces, num_units, num_meds, num_meals # - # @param template [String] # @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 # @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 create_space_type_hash(template,trust_effective_num_spaces = false) - + 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 space_type_hash = {} - self.getSpaceTypes.each do |space_type| - + model.getSpaceTypes.sort.each do |space_type| # get standards info stds_bldg_type = space_type.standardsBuildingType stds_space_type = space_type.standardsSpaceType - if stds_bldg_type.is_initialized and stds_space_type.is_initialized and space_type.spaces.size > 0 + if stds_bldg_type.is_initialized && stds_space_type.is_initialized && !space_type.spaces.empty? stds_bldg_type = stds_bldg_type.get stds_space_type = stds_space_type.get effective_num_spaces = 0 floor_area = 0.0 num_people = 0.0 @@ -4484,44 +4242,43 @@ num_meals = nil # determine num_elevators in another method # determine num_parking_spots in another method # loop through spaces to get mis values - space_type.spaces.each do |space| - next if not space.partofTotalFloorArea + space_type.spaces.sort.each do |space| + next unless space.partofTotalFloorArea effective_num_spaces += space.multiplier floor_area += space.floorArea * space.multiplier num_people += space.numberOfPeople * space.multiplier - end # determine number of units - if stds_bldg_type == "SmallHotel" && stds_space_type.include?("GuestRoom") # doesn't always == GuestRoom so use include? - avg_unit_size = OpenStudio::convert(354.2,"ft^2","m^2").get # calculated from prototype + if stds_bldg_type == 'SmallHotel' && stds_space_type.include?('GuestRoom') # doesn't always == GuestRoom so use include? + avg_unit_size = OpenStudio.convert(354.2, 'ft^2', 'm^2').get # calculated from prototype num_units = floor_area / avg_unit_size - elsif stds_bldg_type == "LargeHotel" && stds_space_type.include?("GuestRoom") - avg_unit_size = OpenStudio::convert(279.7,"ft^2","m^2").get # calculated from prototype + elsif stds_bldg_type == 'LargeHotel' && stds_space_type.include?('GuestRoom') + avg_unit_size = OpenStudio.convert(279.7, 'ft^2', 'm^2').get # calculated from prototype num_units = floor_area / avg_unit_size - elsif stds_bldg_type == "MidriseApartment" && stds_space_type.include?("Apartment") - avg_unit_size = OpenStudio::convert(949.9,"ft^2","m^2").get # calculated from prototype + elsif stds_bldg_type == 'MidriseApartment' && stds_space_type.include?('Apartment') + avg_unit_size = OpenStudio.convert(949.9, 'ft^2', 'm^2').get # calculated from prototype num_units = floor_area / avg_unit_size - elsif stds_bldg_type == "HighriseApartment" && stds_space_type.include?("Apartment") - avg_unit_size = OpenStudio::convert(949.9,"ft^2","m^2").get # calculated from prototype + elsif stds_bldg_type == 'HighriseApartment' && stds_space_type.include?('Apartment') + avg_unit_size = OpenStudio.convert(949.9, 'ft^2', 'm^2').get # calculated from prototype num_units = floor_area / avg_unit_size - elsif stds_bldg_type == "StripMall" - avg_unit_size = OpenStudio::convert(22500.0/10.0,"ft^2","m^2").get # calculated from prototype + elsif stds_bldg_type == 'StripMall' + avg_unit_size = OpenStudio.convert(22_500.0 / 10.0, 'ft^2', 'm^2').get # calculated from prototype num_units = floor_area / avg_unit_size end # determine number of beds - if stds_bldg_type == "Hospital" && ["PatRoom","ICU_PatRm","ICU_Open"].include?(stds_space_type) + if stds_bldg_type == 'Hospital' && ['PatRoom', 'ICU_PatRm', 'ICU_Open'].include?(stds_space_type) num_beds = num_people end # determine number of students - if ["PrimarySchool","SecondarySchool"].include?(stds_bldg_type) && stds_space_type == "Classroom" - num_students += num_people * ((typical_class_size - 1.0)/typical_class_size) + if ['PrimarySchool', 'SecondarySchool'].include?(stds_bldg_type) && stds_space_type == 'Classroom' + num_students += num_people * ((typical_class_size - 1.0) / typical_class_size) end space_type_hash[space_type] = {} space_type_hash[space_type][:stds_bldg_type] = stds_bldg_type space_type_hash[space_type][:stds_space_type] = stds_space_type @@ -4530,22 +4287,30 @@ space_type_hash[space_type][:num_people] = num_people space_type_hash[space_type][:num_students] = num_students space_type_hash[space_type][:num_units] = num_units space_type_hash[space_type][:num_beds] = num_beds + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "For #{space_type.name}, floor area = #{OpenStudio.convert(floor_area, 'm^2', 'ft^2').get.round} ft^2.") unless floor_area == 0.0 + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "For #{space_type.name}, number of spaces = #{effective_num_spaces}.") unless effective_num_spaces == 0.0 + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "For #{space_type.name}, number of units = #{num_units}.") unless num_units == 0.0 + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "For #{space_type.name}, number of people = #{num_people.round}.") unless num_people == 0.0 + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "For #{space_type.name}, number of students = #{num_students}.") unless num_students == 0.0 + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "For #{space_type.name}, number of beds = #{num_beds}.") unless num_beds == 0.0 + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "For #{space_type.name}, number of meals = #{num_meals}.") unless num_meals.nil? + else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Cannot identify standards buidling type and space type for #{space_type.name}, it won't be added to space_type_hash.") end end - return space_type_hash + return space_type_hash.sort.to_h end private # Helper method to fill in hourly values - def add_vals_to_sch(day_sch, sch_type, values) + 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| next if values[i] == values[i + 1] @@ -4558,91 +4323,159 @@ # Modify the existing service water heating loops # to match the baseline required heating type. # @return [Bool] return true if successful, false if not # @author Julien Marrec - def apply_baseline_swh_loops(template, building_type) - - getPlantLoops.each do |plant_loop| + 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? + next unless plant_loop_swh_loop?(plant_loop) # Rename the loop to avoid accidentally hooking # up the HVAC systems to this loop later. - plant_loop.setName("Service Water Heating Loop") + plant_loop.setName('Service Water Heating Loop') - htg_fuels, combination_system, storage_capacity, total_heating_capacity = plant_loop.swh_system_type + htg_fuels, combination_system, storage_capacity, total_heating_capacity = plant_loop_swh_system_type(plant_loop) # htg_fuels.size == 0 shoudln't happen electric = true if htg_fuels.include?('NaturalGas') || - htg_fuels.include?('PropaneGas') || - htg_fuels.include?('FuelOil#1') || - htg_fuels.include?('FuelOil#2') || - htg_fuels.include?('Coal') || - htg_fuels.include?('Diesel') || - htg_fuels.include?('Gasoline') + htg_fuels.include?('PropaneGas') || + htg_fuels.include?('FuelOil#1') || + htg_fuels.include?('FuelOil#2') || + htg_fuels.include?('Coal') || + htg_fuels.include?('Diesel') || + htg_fuels.include?('Gasoline') electric = false end - # Per Table G3.1 11.e, if the baseline system was a combination of + # Per Table G3.1 11.e, if the baseline system was a combination of # heating and service water heating, delete all heating equipment # and recreate a WaterHeater:Mixed. if combination_system plant_loop.supplyComponents.each do |component| - # Get the object type obj_type = component.iddObjectType.valueName.to_s next if ['OS_Node', 'OS_Pump_ConstantSpeed', 'OS_Pump_VariableSpeed', 'OS_Connector_Splitter', 'OS_Connector_Mixer', 'OS_Pipe_Adiabatic'].include?(obj_type) component.remove - end - water_heater = OpenStudio::Model::WaterHeaterMixed.new(self) - water_heater.setName("Baseline Water Heater") + water_heater = OpenStudio::Model::WaterHeaterMixed.new(model) + water_heater.setName('Baseline Water Heater') water_heater.setHeaterMaximumCapacity(total_heating_capacity) water_heater.setTankVolume(storage_capacity) plant_loop.addSupplyBranchForComponent(water_heater) if electric # G3.1.11.b: If electric, WaterHeater:Mixed with electric resistance - water_heater.setHeaterFuelType("Electricity") + water_heater.setHeaterFuelType('Electricity') water_heater.setHeaterThermalEfficiency(1.0) else # TODO: for now, just get the first fuel that isn't Electricity # A better way would be to count the capacities associated # with each fuel type and use the preponderant one - fuels = htg_fuels - ["Electricity"] + fuels = htg_fuels - ['Electricity'] fossil_fuel_type = fuels[0] water_heater.setHeaterFuelType(fossil_fuel_type) water_heater.setHeaterThermalEfficiency(0.8) end - # If it's not a combination heating and service water heating system - # just change the fuel type of all water heaters on the system - # to electric resistance if it's electric + # If it's not a combination heating and service water heating system + # just change the fuel type of all water heaters on the system + # to electric resistance if it's electric else if electric plant_loop.supplyComponents.each do |component| next unless component.to_WaterHeaterMixed.is_initialized water_heater = component.to_WaterHeaterMixed.get # G3.1.11.b: If electric, WaterHeater:Mixed with electric resistance - water_heater.setHeaterFuelType("Electricity") + water_heater.setHeaterFuelType('Electricity') water_heater.setHeaterThermalEfficiency(1.0) end end end - end # Set the water heater fuel types if it's 90.1-2013 - getWaterHeaterMixeds.each do |water_heater| - water_heater.apply_prm_baseline_fuel_type(template, building_type) + model.getWaterHeaterMixeds.sort.each do |water_heater| + water_heater_mixed_apply_prm_baseline_fuel_type(water_heater, building_type) end - + return true - end + # 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 + # + # @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 + ref = var.internalDataIndexKeyName + # Convert to UUID + uid = OpenStudio.toUUID(ref) + # Get the model object with this UID + obj = model.getModelObject(uid) + # If it exists, replace the UID with the object name + if obj.is_initialized + var.setInternalDataIndexKeyName(obj.get.name.get) + end + end + + return true + end + + # Loads a osm as a starting point. + # + # @return [Bool] 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 + osm_model_path = OpenStudio::Path.new(osm_file.to_s) + # Upgrade version if required. + version_translator = OpenStudio::OSVersion::VersionTranslator.new + model = version_translator.loadModel(osm_model_path).get + if model.getBuildingStorys.empty? + OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please assign Spaces to BuildingStorys in the geometry model: #{osm_model_path}.") + end + if model.getThermalZones.empty? + OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please assign Spaces to ThermalZones in the geometry model: #{osm_model_path}.") + end + if model.getBuilding.standardsNumberOfStories.empty? + OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please define Building.standardsNumberOfStories in the geometry model #{osm_model_path}.") + end + if model.getBuilding.standardsNumberOfAboveGroundStories.empty? + OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please define Building.standardsNumberOfAboveStories in the geometry model#{osm_model_path}.") + end + + if @space_type_map.nil? || @space_type_map.empty? + @space_type_map = get_space_type_maps_from_model(model) + if @space_type_map.nil? || @space_type_map.empty? + OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please assign SpaceTypes in the geometry model: #{osm_model_path} or in standards database #{@space_type_map}.") + else + @space_type_map = @space_type_map.sort.to_h + OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Loaded space type map from osm file: #{osm_model_path}") + end + end + + # ensure that model is intersected correctly. + model.getSpaces.each { |space1| model.getSpaces.each { |space2| space1.intersectSurfaces(space2) } } + # Get multipliers from TZ in model. Need this for HVAC contruction. + @space_multiplier_map = {} + model.getSpaces.sort.each do |space| + @space_multiplier_map[space.name.get] = space.multiplier if space.multiplier > 1 + end + OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding geometry') + unless @space_multiplier_map.empty? + OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Found mulitpliers for space #{@space_multiplier_map}") + end + return model + end end