lib/openstudio-standards/standards/Standards.Model.rb in openstudio-standards-0.2.2 vs lib/openstudio-standards/standards/Standards.Model.rb in openstudio-standards-0.2.3
- old
+ new
@@ -1,5 +1,9 @@
+require 'csv'
+
+
+
class Standard
attr_accessor :space_multiplier_map
def define_space_multiplier
return @space_multiplier_map
@@ -9,17 +13,16 @@
# 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.
+ # @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 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.
+ # 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 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}")
@@ -31,16 +34,14 @@
# Reduce the WWR and SRR, if necessary
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Adjusting Window and Skylight Ratios ***')
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 building stories to spaces in the building where stories are not yet assigned.
model_assign_spaces_to_stories(model)
- # Modify the internal loads in each space type,
- # keeping user-defined schedules.
+ # Modify the internal loads in each space type, keeping user-defined schedules.
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
@@ -48,13 +49,12 @@
set_ventilation = false
set_infiltration = false
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.
+ # If any of the lights are missing schedules, assign an always-off schedule to those lights.
+ # This is assumed to be the user's intent in the proposed model.
model.getLightss.sort.each do |lights|
if lights.schedule.empty?
lights.setSchedule(model.alwaysOffDiscreteSchedule)
end
end
@@ -79,28 +79,23 @@
model_apply_prm_construction_types(model)
# Set the construction properties of all the surfaces in the model
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.
+ # 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 = model_prm_baseline_system_groups(model, custom)
- # Remove all HVAC from model,
- # excluding service water heating
+ # Remove all HVAC from model, excluding service water heating
model_remove_prm_hvac(model)
- # Modify the service water heating loops
- # per the baseline rules
+ # Modify the service water heating loops per the baseline rules
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Cleaning up Service Water Heating Loops ***')
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.
+ # 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 = model_prm_baseline_system_type(model,
climate_zone,
@@ -162,12 +157,11 @@
# Run sizing run with the HVAC equipment
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
+ # 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
model_apply_multizone_vav_outdoor_air_sizing(model)
# Set the baseline fan power for all airloops
model.getAirLoopHVACs.sort.each do |air_loop|
@@ -193,12 +187,11 @@
# Skip the SWH loops
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
+ # Run sizing run with the new chillers, boilers, and cooling towers to determine capacities
if model_run_sizing_run(model, "#{sizing_run_dir}/SR2") == false
return false
end
# Set the pumping control strategy and power
@@ -217,17 +210,12 @@
# Fix EMS references.
# Temporary workaround for OS issue #2598
model_temp_fix_ems_references(model)
- # Delete all the unused curves
- 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
+ # Delete all the unused resource objects
+ model_remove_unused_resource_objects(model)
# 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+
@@ -241,20 +229,18 @@
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.
+ # 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.
+ # 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 model_residential_and_nonresidential_floor_areas(model)
res_area_m2 = 0
@@ -268,14 +254,13 @@
end
return { 'residential' => res_area_m2, 'nonresidential' => nonres_area_m2 }
end
- # Determine the number of stories spanned by the
- # supplied zones. If all zones on one of the stories have
- # an indentical multiplier, assume that the multiplier is a
- # floor multiplier and increase the number of stories accordingly.
+ # Determine the number of stories spanned by the supplied zones.
+ # If all zones on one of the stories have an indentical multiplier,
+ # assume that the multiplier is a 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 model_num_stories_spanned(model, zones)
@@ -299,15 +284,13 @@
end
return num_stories
end
- # Categorize zones by occupancy type and fuel type,
- # where the types depend on the standard.
+ # 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'
+ # @return [Array<Hash>] an array of hashes, one for each zone, with the keys 'zone', 'type' (occ type), 'fuel', and 'area'
def model_zones_with_occ_and_fuel_type(model, custom)
zones = []
model.getThermalZones.sort.each do |zone|
# Skip plenums
@@ -333,10 +316,13 @@
zn_hash['area'] = zone.floorArea
# Occupancy type
zn_hash['occ'] = thermal_zone_occupancy_type(zone)
+ # Building type
+ zn_hash['bldg_type'] = thermal_zone_building_type(zone)
+
# Fuel type
zn_hash['fuel'] = thermal_zone_fossil_or_electric_type(zone, custom)
zones << zn_hash
end
@@ -390,15 +376,13 @@
dom_occ = type_to_area.sort_by { |k, v| v }.reverse[0][0]
# Get the dominant occupancy type group
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.
+ # 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
- # back to the dominant occupancy type 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
@@ -419,14 +403,12 @@
end
end
# Add the dominant occupancy group to the list
occ_groups << [dom_occ, dom_occ_group]
- # Inside of each remaining occupancy group,
- # determine the dominant fuel type. This determination
- # should only include zones that are part of the
- # dominant area type inside of this group.
+ # Inside of each remaining occupancy group, determine the dominant fuel type.
+ # This determination should only include zones that are part of the dominant area type inside of this group.
occ_and_fuel_groups = []
occ_groups.each do |occ_type, zns|
# Separate the zones that are part of the dominant occ type
dom_occ_zns = []
nondom_occ_zns = []
@@ -436,12 +418,11 @@
else
nondom_occ_zns << zn
end
end
- # Determine the dominant fuel type
- # from the subset of the dominant area type zones
+ # 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_by_fuel|
zns_by_fuel.each do |zn|
fuel_to_area[fuel] += zn['area']
@@ -449,12 +430,11 @@
end
sorted_by_area = fuel_to_area.sort_by { |k, v| v }.reverse
dom_fuel = sorted_by_area[0][0]
- # Don't allow unconditioned to be the dominant fuel,
- # go to the next biggest
+ # Don't allow unconditioned to be the dominant fuel, 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.')
@@ -466,19 +446,16 @@
dom_fuel_group = {}
dom_fuel_group['occ'] = occ_type
dom_fuel_group['fuel'] = dom_fuel
dom_fuel_group['zones'] = zones_grouped_by_fuel[dom_fuel]
- # The zones that aren't part of the dominant occ type
- # are automatically added to the dominant fuel group
+ # The zones that aren't part of the dominant occ type are automatically added to the dominant fuel group
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.
+ # 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
- # back to the dominant occupancy type 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_by_fuel|
# Skip the dominant occupancy type
next if fuel_type == dom_fuel
# Add up the floor area of the group
@@ -504,14 +481,12 @@
# Add the dominant occupancy group to the list
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.
+ # 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'
@@ -527,23 +502,21 @@
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 there are any heated-only zones, create a new group for them.
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
end
end
- # Calculate the area for each of the final groups
- # and replace the zone hashes with the zone objects
+ # Calculate the area for each of the final groups and replace the zone hashes with the zone objects
final_groups.each do |gp|
area_m2 = 0.0
gp_zns = []
gp['zones'].each do |zn|
area_m2 += zn['area']
@@ -553,17 +526,14 @@
gp['area_ft2'] = area_ft2
gp['zones'] = gp_zns
end
# TODO: Remove the secondary zones before
- # determining the area used to pick the HVAC
- # system, per PNNL PRM RM
+ # 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
+ # 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 = []
model.getThermalZones.sort.each do |zone|
all_htg_fuels += zone.heating_fuels
all_clg_fuels += zone.cooling_fuels
@@ -593,20 +563,18 @@
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
- # Change the fuel in all final groups
- # if district systems were found.
+ # Change the fuel in all final groups if district systems were found.
if district_fuel
final_groups.each do |gp|
gp['fuel'] = district_fuel
end
end
- # Determine the number of stories spanned
- # by each group and report out info.
+ # 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 = model_num_stories_spanned(model, group['zones'])
group['stories'] = num_stories
# Report out the final grouping
@@ -630,25 +598,21 @@
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.
+ # Determine the baseline system type given the inputs. Logic is different for different standards.
#
# 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,
- # purchasedheat, purchasedcooling, purchasedheatandcooling
+ # @param area_type [String] Valid choices are residential, nonresidential, and heatedonly
+ # @param fuel_type [String] Valid choices are electric, fossil, fossilandelectric,
+ # purchasedheat, purchasedcooling, purchasedheatandcooling
# @param area_ft2 [Double] Area in ft^2
# @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
+ # @return [String] The system type. Possibilities are PTHP, PTAC, PSZ_AC, PSZ_HP, PVAV_Reheat, PVAV_PFP_Boxes,
+ # VAV_Reheat, VAV_PFP_Boxes, Gas_Furnace, Electric_Furnace
+ # TODO: add 90.1-2013 systems 11-13
def model_prm_baseline_system_type(model, climate_zone, area_type, fuel_type, area_ft2, num_stories, custom)
# [type, central_heating_fuel, zone_heating_fuel, cooling_fuel]
system_type = [nil, nil, nil, nil]
# Get the row from TableG3.1.1A
@@ -707,14 +671,12 @@
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
+ # 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
@@ -740,81 +702,60 @@
end
return sys_num
end
- # Change the fuel type based on climate zone, depending on the standard.
- # Defaults to no change.
+ # 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.
+ # Add the specified baseline system type to the specified zones 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 system_type [String] The system type. Valid choices are
- # PTHP, PTAC, PSZ_AC, PSZ_HP, PVAV_Reheat, PVAV_PFP_Boxes,
- # VAV_Reheat, VAV_PFP_Boxes, Gas_Furnace, Electric_Furnace,
- # which are also returned by the method
- # OpenStudio::Model::Model.prm_baseline_system_type.
- # @param main_heat_fuel [String] main heating fuel. Valid choices are
- # Electricity, NaturalGas, DistrictHeating
- # @param zone_heat_fuel [String] zone heating/reheat fuel. Valid choices are
- # Electricity, NaturalGas, DistrictHeating
- # @param cool_fuel [String] cooling fuel. Valid choices are
- # Electricity, DistrictCooling
- # @todo add 90.1-2013 systems 11-13
+ # @param system_type [String] The system type. Valid choices are PTHP, PTAC, PSZ_AC, PSZ_HP, PVAV_Reheat,
+ # PVAV_PFP_Boxes, VAV_Reheat, VAV_PFP_Boxes, Gas_Furnace, Electric_Furnace,
+ # which are also returned by the method OpenStudio::Model::Model.prm_baseline_system_type.
+ # @param main_heat_fuel [String] main heating fuel. Valid choices are Electricity, NaturalGas, DistrictHeating
+ # @param zone_heat_fuel [String] zone heating/reheat fuel. Valid choices are Electricity, NaturalGas, DistrictHeating
+ # @param cool_fuel [String] cooling fuel. Valid choices are Electricity, DistrictCooling
+ # TODO: Add 90.1-2013 systems 11-13
def model_add_prm_baseline_system(model, system_type, main_heat_fuel, zone_heat_fuel, cool_fuel, zones)
case system_type
when 'PTAC' # System 1
-
unless zones.empty?
-
- # Retrieve the existing hot water loop
- # or add a new one if necessary.
+ # Retrieve the existing hot water loop or add a new one if necessary.
hot_water_loop = nil
hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized
model.getPlantLoopByName('Hot Water Loop').get
else
model_add_hw_loop(model, main_heat_fuel)
end
# Add a hot water PTAC to each zone
model_add_ptac(model,
- nil,
- hot_water_loop,
zones,
- 'ConstantVolume',
- 'Water',
- 'Single Speed DX AC')
+ cooling_type: "Single Speed DX AC",
+ heating_type: "Water",
+ hot_water_loop: hot_water_loop,
+ fan_type: "ConstantVolume")
end
when 'PTHP' # System 2
-
unless zones.empty?
-
- # Add an air-source packaged terminal
- # heat pump with electric supplemental heat
- # to each zone.
+ # add an air-source packaged terminal heat pump with electric supplemental heat to each zone.
model_add_pthp(model,
- nil,
zones,
- 'ConstantVolume')
-
+ fan_type: 'ConstantVolume')
end
when 'PSZ_AC' # System 3
-
unless zones.empty?
-
heating_type = 'Gas'
- # If district heating
+ # if district heating
hot_water_loop = nil
if main_heat_fuel == 'DistrictHeating'
heating_type = 'Water'
hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized
model.getPlantLoopByName('Hot Water Loop').get
@@ -830,67 +771,42 @@
cooling_type = 'Water'
chilled_water_loop = if model.getPlantLoopByName('Chilled Water Loop').is_initialized
model.getPlantLoopByName('Chilled Water Loop').get
else
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)
-
+ cooling_fuel: cool_fuel,
+ chw_pumping_type: 'const_pri')
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 a PSZ-AC to each zone
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)
-
+ cooling_type: cooling_type,
+ chilled_water_loop: chilled_water_loop,
+ heating_type: heating_type,
+ supplemental_heating_type: "Gas",
+ hot_water_loop: hot_water_loop,
+ fan_location: 'DrawThrough',
+ fan_type: 'ConstantVolume')
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 an air-source packaged single zone heat pump with electric supplemental heat to each zone.
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)
-
+ system_name: 'PSZ-HP',
+ cooling_type: 'Single Speed Heat Pump',
+ heating_type: 'Single Speed Heat Pump',
+ supplemental_heating_type: 'Electric',
+ fan_location: 'DrawThrough',
+ fan_type: 'ConstantVolume')
end
when 'PVAV_Reheat' # System 5
-
- # Retrieve the existing hot water loop
- # or add a new one if necessary.
+ # Retrieve the existing hot water loop or add a new one if necessary.
hot_water_loop = nil
hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized
model.getPlantLoopByName('Hot Water Loop').get
else
model_add_hw_loop(model, main_heat_fuel)
@@ -901,17 +817,12 @@
if cool_fuel == 'DistrictCooling'
chilled_water_loop = if model.getPlantLoopByName('Chilled Water Loop').is_initialized
model.getPlantLoopByName('Chilled Water Loop').get
else
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)
+ cooling_fuel: cool_fuel,
+ chw_pumping_type: 'const_pri')
end
end
# If electric zone heat
electric_reheat = false
@@ -936,50 +847,39 @@
stories = []
story_group[0].spaces.each do |space|
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]
- sys_name = "#{story_name} PVAV_Reheat (Sys5)"
+ system_name = "#{story_name} PVAV_Reheat (Sys5)"
# If and only if there are primary zones to attach to the loop
# counter example: floor with only one elevator machine room that get classified as sec_zones
unless pri_zones.empty?
-
model_add_pvav(model,
- sys_name,
pri_zones,
- nil,
- nil,
- electric_reheat,
- hot_water_loop,
- chilled_water_loop,
- nil,
- nil)
+ system_name: system_name,
+ hot_water_loop: hot_water_loop,
+ chilled_water_loop: chilled_water_loop,
+ electric_reheat: electric_reheat)
end
# Add a PSZ_AC for each secondary zone
unless sec_zones.empty?
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 model.getPlantLoopByName('Chilled Water Loop').is_initialized
model.getPlantLoopByName('Chilled Water Loop').get
else
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)
+ cooling_fuel: cool_fuel,
+ chw_pumping_type: 'const_pri')
end
end
# Group zones by story
story_zone_lists = model_group_zones_by_story(model, zones)
@@ -998,73 +898,58 @@
stories = []
story_group[0].spaces.each do |space|
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]
- sys_name = "#{story_name} PVAV_PFP_Boxes (Sys6)"
+ system_name = "#{story_name} PVAV_PFP_Boxes (Sys6)"
# If and only if there are primary zones to attach to the loop
unless pri_zones.empty?
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)
+ system_name: system_name,
+ chilled_water_loop: chilled_water_loop,
+ fan_efficiency: 0.62,
+ fan_motor_efficiency: 0.9,
+ fan_pressure_rise: 4.0)
end
# Add a PSZ_HP for each secondary zone
unless sec_zones.empty?
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.
+ # Retrieve the existing hot water loop or add a new one if necessary.
hot_water_loop = nil
hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized
model.getPlantLoopByName('Hot Water Loop').get
else
model_add_hw_loop(model, main_heat_fuel)
end
- # Retrieve the existing chilled water loop
- # or add a new one if necessary.
+ # Retrieve the existing chilled water loop or add a new one if necessary.
chilled_water_loop = nil
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 = 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)
+ cooling_fuel: cool_fuel,
+ chw_pumping_type: 'const_pri')
else
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)
+ cooling_tower_type: 'Open Cooling Tower',
+ cooling_tower_fan_type: 'Propeller or Axial',
+ cooling_tower_capacity_control: fan_type,
+ number_of_cells_per_tower: 1,
+ number_cooling_towers: 1)
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)
+ chw_pumping_type: 'const_pri_var_sec',
+ chiller_cooling_type: 'WaterCooled',
+ chiller_compressor_type: 'Rotary Screw',
+ condenser_water_loop: condenser_water_loop)
end
end
# If electric zone heat
reheat_type = 'Water'
@@ -1073,14 +958,12 @@
end
# Group zones by story
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.
+ # 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 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?
@@ -1093,70 +976,55 @@
stories = []
story_group[0].spaces.each do |space|
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]
- sys_name = "#{story_name} VAV_Reheat (Sys7)"
+ system_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?
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)
+ system_name: system_name,
+ reheat_type: reheat_type,
+ hot_water_loop: hot_water_loop,
+ chilled_water_loop: chilled_water_loop,
+ fan_efficiency: 0.62,
+ fan_motor_efficiency: 0.9,
+ fan_pressure_rise: 4.0)
end
# Add a PSZ_AC for each secondary zone
unless sec_zones.empty?
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.
+ # Retrieve the existing chilled water loop or add a new one if necessary.
chilled_water_loop = nil
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 = 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)
+ cooling_fuel: cool_fuel,
+ chw_pumping_type: 'const_pri')
else
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)
+ cooling_tower_type: 'Open Cooling Tower',
+ cooling_tower_fan_type: 'Propeller or Axial',
+ cooling_tower_capacity_control: fan_type,
+ number_of_cells_per_tower: 1,
+ number_cooling_towers: 1)
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)
+ chw_pumping_type: 'const_pri_var_sec',
+ chiller_cooling_type: 'WaterCooled',
+ chiller_compressor_type: 'Rotary Screw',
+ condenser_water_loop: condenser_water_loop)
end
end
# Group zones by story
story_zone_lists = model_group_zones_by_story(model, zones)
@@ -1175,78 +1043,60 @@
stories = []
story_group[0].spaces.each do |space|
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]
- sys_name = "#{story_name} VAV_PFP_Boxes (Sys8)"
+ system_name = "#{story_name} VAV_PFP_Boxes (Sys8)"
# If and only if there are primary zones to attach to the loop
unless pri_zones.empty?
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)
+ system_name: system_name,
+ chilled_water_loop: chilled_water_loop,
+ fan_efficiency:0.62,
+ fan_motor_efficiency: 0.9,
+ fan_pressure_rise: 4.0)
end
# Add a PSZ_HP for each secondary zone
unless sec_zones.empty?
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 model.getPlantLoopByName('Hot Water Loop').is_initialized
model.getPlantLoopByName('Hot Water Loop').get
else
model_add_hw_loop(model, main_heat_fuel)
end
end
-
# Add a System 9 - Gas Unit Heater to each zone
model_add_unitheater(model,
- nil,
zones,
- nil,
- 'ConstantVolume',
- OpenStudio.convert(0.2, 'inH_{2}O', 'Pa').get,
- main_heat_fuel,
- hot_water_loop,
- nil)
-
+ fan_control_type: 'ConstantVolume',
+ fan_pressure_rise: 0.2,
+ heating_type: main_heat_fuel,
+ hot_water_loop: hot_water_loop)
end
when 'Electric_Furnace' # System 10
-
unless zones.empty?
-
# Add a System 10 - Electric Unit Heater to each zone
model_add_unitheater(model,
- nil,
zones,
- nil,
- 'ConstantVolume',
- OpenStudio.convert(0.2, 'inH_{2}O', 'Pa').get,
- main_heat_fuel,
- nil,
- nil)
-
+ fan_control_type: 'ConstantVolume',
+ fan_pressure_rise: 0.2,
+ heating_type: main_heat_fuel)
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
return true
end
# Determines the fan type used by VAV_Reheat and VAV_PFP_Boxes systems.
@@ -1255,12 +1105,11 @@
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.
+ # 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 model_get_baseline_system_type_by_zone(model, climate_zone, custom = nil)
@@ -1329,12 +1178,11 @@
end
return zone_to_sys_type
end
- # @param array_of_zones [Array] an array of Hashes for each zone,
- # with the keys 'zone',
+ # @param array_of_zones [Array] an array of Hashes for each zone, with the keys 'zone',
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
@@ -1356,12 +1204,11 @@
OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Values for #{field_name}, tol = #{tolerance} #{units}, area ft2:")
OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "vals #{all_vals.join(', ')}")
OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "areas #{all_areas.join(', ')}")
OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "names #{all_zn_names.join(', ')}")
- # Calculate the biggest delta
- # and the index of the biggest delta
+ # Calculate the biggest delta and the index of the biggest delta
biggest_delta_i = nil
biggest_delta = 0.0
worst = nil
array_of_zones.each_with_index do |zn, i|
val = zn[key_to_inspect]
@@ -1373,37 +1220,30 @@
end
end
# puts " #{worst} - #{avg.round} = #{biggest_delta.round} biggest delta"
- # Compare the biggest delta
- # against the difference and
- # eliminate that zone if higher
- # than the limit.
+ # Compare the biggest delta against the difference and eliminate that zone if higher than the limit.
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 = 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.")
+ 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} #{worst.round(2)} #{units} - average #{field_name} #{avg.round(2)} #{units} = #{biggest_delta.round(2)} #{units} < tolerance of #{tolerance} #{units}, stopping elimination process.")
end
return array_of_zones
end
- # Determine which of the zones
- # should be served by the primary HVAC system.
- # First, eliminate zones that differ by more
- # than 40 full load hours per week. In this case,
- # lighting schedule is used as the proxy for operation
- # instead of occupancy to avoid accidentally removing
- # transition spaces. Second, eliminate zones whose
- # design internal loads differ from the
- # area-weighted average of all other zones
+ # Determine which of the zones should be served by the primary HVAC system.
+ # First, eliminate zones that differ by more# than 40 full load hours per week.
+ # In this case, lighting schedule is used as the proxy for operation instead
+ # of occupancy to avoid accidentally removing transition spaces.
+ # Second, eliminate zones whose design internal loads differ from the 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 model_differentiate_primary_secondary_thermal_zones(model, zones)
@@ -1462,13 +1302,12 @@
end
zone_data_1 << data
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.
+ # 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 = 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 = []
@@ -1479,11 +1318,11 @@
# 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 = thermal_zone_design_internal_load(zone)
+ int_load_w = thermal_zone_design_internal_load(zone) * zone.multiplier
# 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
@@ -1519,13 +1358,12 @@
end
return { 'primary' => pri_zones, 'secondary' => sec_zones }
end
- # 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.
+ # 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 model_group_zones_by_story(model, zones)
story_zone_lists = []
zones_already_assigned = []
@@ -1562,15 +1400,13 @@
end
return story_zone_lists
end
- # Assign each space in the model to a building story
- # based on common z (height) values. If no story
- # object is found for a particular height, create a new one
- # and assign it to the space. Does not assign a story
- # to plenum spaces.
+ # Assign each space in the model to a building story based on common z (height) values.
+ # If no story object is found for a particular height, create a new one and assign it to the space.
+ # Does not assign a story to plenum spaces.
#
# @return [Bool] returns true if successful, false if not.
def model_assign_spaces_to_stories(model)
# Make hash of spaces and minz values
sorted_spaces = {}
@@ -1604,226 +1440,41 @@
end
return true
end
- # Creates a construction set with the construction types specified in the
- # 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
- # @return [OpenStudio::Model::DefaultConstructionSet] returns a default
- # construction set populated with the specified constructions.
- 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 = model_find_climate_zone_set(model, clim)
- unless climate_zone_set
- return construction_set
- end
-
- # Get the object data
- 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 = 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 = model_make_name(model, clim, building_type, spc_type)
-
- # Create a new construction set and name it
- 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'
- exterior_wall_standards_construction_type = 'SteelFramed'
- exterior_roof_standards_construction_type = 'IEAD'
-
- # Ground contact surfaces constructions
- ground_contact_floor_standards_construction_type = 'Unheated'
- ground_contact_wall_standards_construction_type = 'Mass'
-
- # Exterior sub surfaces constructions
- exterior_fixed_window_standards_construction_type = 'IEAD'
- exterior_operable_window_standards_construction_type = 'IEAD'
- 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(model)
- construction_set.setDefaultExteriorSurfaceConstructions(exterior_surfaces)
- exterior_surfaces.setFloorConstruction(model_find_and_add_construction(model,
- climate_zone_set,
- '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(model)
- construction_set.setDefaultInteriorSurfaceConstructions(interior_surfaces)
- construction_name = interior_floors
- unless construction_name.nil?
- interior_surfaces.setFloorConstruction(model_add_construction(model, construction_name))
- end
- construction_name = interior_walls
- unless construction_name.nil?
- interior_surfaces.setWallConstruction(model_add_construction(model, construction_name))
- end
- construction_name = interior_ceilings
- unless construction_name.nil?
- interior_surfaces.setRoofCeilingConstruction(model_add_construction(model, construction_name))
- end
-
- # Ground contact surfaces constructions
- ground_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model)
- construction_set.setDefaultGroundContactSurfaceConstructions(ground_surfaces)
- ground_surfaces.setFloorConstruction(model_find_and_add_construction(model,
- climate_zone_set,
- 'GroundContactFloor',
- ground_contact_floor_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(model)
- construction_set.setDefaultExteriorSubSurfaceConstructions(exterior_subsurfaces)
- if exterior_fixed_window_standards_construction_type && exterior_fixed_window_building_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(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(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(model_add_construction(model, construction_name))
- end
- if exterior_overhead_door_standards_construction_type && exterior_overhead_door_building_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(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(model_add_construction(model, construction_name))
- end
- if construction_name == tubular_daylight_diffusers
- exterior_subsurfaces.setTubularDaylightDiffuserConstruction(model_add_construction(model, construction_name))
- end
-
- # Interior sub surfaces constructions
- interior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(model)
- construction_set.setDefaultInteriorSubSurfaceConstructions(interior_subsurfaces)
- if construction_name == interior_fixed_windows
- interior_subsurfaces.setFixedWindowConstruction(model_add_construction(model, construction_name))
- end
- if construction_name == interior_operable_windows
- interior_subsurfaces.setOperableWindowConstruction(model_add_construction(model, construction_name))
- end
- if construction_name == interior_doors
- interior_subsurfaces.setDoorConstruction(model_add_construction(model, construction_name))
- end
-
- # Other constructions
- if construction_name == interior_partitions
- construction_set.setInteriorPartitionConstruction(model_add_construction(model, construction_name))
- end
- if construction_name == space_shading
- construction_set.setSpaceShadingConstruction(model_add_construction(model, construction_name))
- end
- if construction_name == building_shading
- construction_set.setBuildingShadingConstruction(model_add_construction(model, construction_name))
- end
- if construction_name == site_shading
- 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)
-
- # Create a constuction set that is all
- end
-
# 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.
+ # @note This must be performed before the sizing run because it impacts component sizes, which in turn impact efficiencies.
def model_apply_multizone_vav_outdoor_air_sizing(model)
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying multizone vav OA sizing.')
# Multi-zone VAV outdoor air sizing
- model.getAirLoopHVACs.sort.each { |obj| air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(obj) }
+ 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 model_apply_hvac_efficiency_standard(model, climate_zone)
+ # Applies the HVAC parts of the template to all objects in the model using the the template specified in the model.
+ def model_apply_hvac_efficiency_standard(model, climate_zone, apply_controls: true)
sql_db_vars_map = {}
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying HVAC efficiency standards.')
# Air Loop Controls
- model.getAirLoopHVACs.sort.each { |obj| air_loop_hvac_apply_standard_controls(obj, climate_zone) }
+ if apply_controls.nil? || apply_controls == true
+ model.getAirLoopHVACs.sort.each { |obj| air_loop_hvac_apply_standard_controls(obj, climate_zone) }
+ end
# Plant Loop Controls
# TODO refactor: enable this code (missing before refactor)
# getPlantLoops.sort.each { |obj| plant_loop_apply_standard_controls(obj, template, climate_zone) }
+ # Zone HVAC Controls
+ model.getZoneHVACComponents.sort.each { |obj| zone_hvac_component_apply_standard_controls(obj) }
+
##### Apply equipment efficiencies
# Fans
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)) }
@@ -1865,12 +1516,11 @@
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.
+ # Applies daylighting controls to each space in the model per the standard.
def model_add_daylighting_controls(model)
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started adding daylighting controls.')
# Add daylighting controls to each space
model.getSpaces.sort.each do |space|
@@ -1901,19 +1551,17 @@
end
return true
end
- # Method to search through a hash for the objects that meets the
- # desired search criteria, as passed via a hash.
+ # 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.
#
# @param hash_of_objects [Hash] hash of objects to search through
# @param search_criteria [Hash] hash of search criteria
# @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.
+ # 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 = 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.")
@@ -2032,16 +1680,13 @@
# raise ("Hell")
# end
return matching_objects
end
- # Method to search through a hash for an object that meets the
- # desired search criteria, as passed via a hash. If capacity is supplied,
- # the object will only be returned if the specified capacity is between
- # the minimum_capacity and maximum_capacity values.
+ # Method to search through a hash for an object that meets the desired search criteria, as passed via a hash.
+ # If capacity is supplied, the object will only be returned if the specified capacity is between the minimum_capacity and maximum_capacity values.
#
- #
# @param hash_of_objects [Hash] hash of objects to search through
# @param search_criteria [Hash] hash of search criteria
# @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.
@@ -2154,21 +1799,126 @@
# Create constant ScheduleRuleset
#
# @param value [double] the value to use, 24-7, 365
# @param name [string] the name of the schedule
+ # @param sch_type_limit [string] the name of a schedule type limit
+ # options are Temperature, Humidity Ratio, Fractional, OnOff, and Activity
# @return schedule
- def model_add_constant_schedule_ruleset(model, value, name = nil)
+ def model_add_constant_schedule_ruleset(model,
+ value,
+ name = nil,
+ sch_type_limit: 'Temperature')
+ # check to see if schedule exists with same name and constant value and return if true
+ unless name.nil?
+ existing_sch = model.getScheduleRulesetByName(name)
+ if existing_sch.is_initialized
+ existing_sch = existing_sch.get
+ existing_day_sch_vals = existing_sch.defaultDaySchedule.values
+ if existing_day_sch_vals.size == 1 && existing_day_sch_vals[0] == value
+ return existing_sch
+ end
+ end
+ end
+
schedule = OpenStudio::Model::ScheduleRuleset.new(model)
unless name.nil?
schedule.setName(name)
schedule.defaultDaySchedule.setName("#{name} Default")
end
+
+ if !sch_type_limit.nil?
+ sch_type_limits_obj = model_add_schedule_type_limits(model, standard_sch_type_limit: sch_type_limit)
+ schedule.setScheduleTypeLimits(sch_type_limits_obj)
+ end
+
schedule.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), value)
return schedule
end
+ # Create ScheduleTypeLimits
+ #
+ # @param standard_sch_type_limit [string] the name of a standard schedule type limit with predefined limits
+ # options are Temperature, Humidity Ratio, Fractional, OnOff, and Activity
+ # @param name[string] the name of the schedule type limits
+ # @param lower_limit_value [double] the lower limit value for the schedule type
+ # @param upper_limit_value [double] the upper limit value for the schedule type
+ # @param numeric_type [string] the numeric type, options are Continuous or Discrete
+ # @param unit_type [string] the unit type, options are defined in EnergyPlus I/O reference
+ # @return [<OpenStudio::Model::ScheduleTypeLimits>]
+ def model_add_schedule_type_limits(model,
+ standard_sch_type_limit: nil,
+ name: nil,
+ lower_limit_value: nil,
+ upper_limit_value: nil,
+ numeric_type: nil,
+ unit_type: nil)
+
+ if standard_sch_type_limit.nil?
+ if lower_limit_value.nil? || upper_limit_value.nil? || numeric_type.nil? || unit_type.nil?
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "If calling model_add_schedule_type_limits without a standard_sch_type_limit, you must specify all properties of ScheduleTypeLimits.")
+ return false
+ end
+ schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
+ schedule_type_limits.setName(name) if !name.nil?
+ schedule_type_limits.setLowerLimitValue(lower_limit_value)
+ schedule_type_limits.setUpperLimitValue(upper_limit_value)
+ schedule_type_limits.setNumericType(numeric_type)
+ schedule_type_limits.setUnitType(unit_type)
+ else
+ schedule_type_limits = model.getScheduleTypeLimitsByName(standard_sch_type_limit)
+ if !schedule_type_limits.empty?
+ schedule_type_limits = schedule_type_limits.get
+ else
+ case standard_sch_type_limit.downcase
+ when 'temperature'
+ schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
+ schedule_type_limits.setName("Temperature")
+ schedule_type_limits.setLowerLimitValue(0.0)
+ schedule_type_limits.setUpperLimitValue(100.0)
+ schedule_type_limits.setNumericType("Continuous")
+ schedule_type_limits.setUnitType("Temperature")
+
+ when 'humidity ratio'
+ schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
+ schedule_type_limits.setName("Humidity Ratio")
+ schedule_type_limits.setLowerLimitValue(0.0)
+ schedule_type_limits.setUpperLimitValue(0.3)
+ schedule_type_limits.setNumericType("Continuous")
+ schedule_type_limits.setUnitType("Dimensionless")
+
+ when 'fraction', 'fractional'
+ schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
+ schedule_type_limits.setName("Fraction")
+ schedule_type_limits.setLowerLimitValue(0.0)
+ schedule_type_limits.setUpperLimitValue(1.0)
+ schedule_type_limits.setNumericType("Continuous")
+ schedule_type_limits.setUnitType("Dimensionless")
+
+ when 'onoff'
+ schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
+ schedule_type_limits.setName("OnOff")
+ schedule_type_limits.setLowerLimitValue(0)
+ schedule_type_limits.setUpperLimitValue(1)
+ schedule_type_limits.setNumericType("Discrete")
+ schedule_type_limits.setUnitType("Availability")
+
+ when 'activity'
+ schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
+ schedule_type_limits.setName("Activity")
+ schedule_type_limits.setLowerLimitValue(70.0)
+ schedule_type_limits.setUpperLimitValue(1000.0)
+ schedule_type_limits.setNumericType("Continuous")
+ schedule_type_limits.setUnitType("ActivityLevel")
+ else
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Invalid standard_sch_type_limit for method model_add_schedule_type_limits.")
+ end
+ end
+ end
+ return schedule_type_limits
+ 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
@@ -2189,17 +1939,15 @@
# Find all the schedule rules that match the name
rules = model_find_objects(standards_data['schedules'], 'name' => schedule_name)
if rules.size.zero?
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
+ return false
end
# Make a schedule ruleset
- sch_ruleset = OpenStudio::Model::ScheduleRuleset.new( model )
+ 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,11 +2121,11 @@
else
material.setSolarDiffusing(false)
end
else
- puts "Unknown material type #{material_type}"
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Unknown material type #{material_type}, cannot add material called #{material_name}.")
exit
end
return material
end
@@ -2469,10 +2217,47 @@
# 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
+ # If the construction has a skylight framing material specified,
+ # get the skylight frame material properties and add frame to
+ # all skylights in the model.
+ if data['skylight_framing']
+ # Get the skylight framing material
+ framing_name = data['skylight_framing']
+ frame_data = model_find_object(standards_data['materials'], 'name' => framing_name)
+ if frame_data
+ frame_width_in = frame_data['frame_width'].to_f
+ frame_with_m = OpenStudio.convert(frame_width_in, 'in', 'm').get
+ frame_resistance_ip = frame_data['resistance'].to_f
+ frame_resistance_si = OpenStudio.convert(frame_resistance_ip, 'hr*ft^2*R/Btu', 'm^2*K/W').get
+ frame_conductance_si = 1.0/frame_resistance_si
+ frame = OpenStudio::Model::WindowPropertyFrameAndDivider.new(model)
+ frame.setName("Skylight frame R-#{frame_resistance_ip.round(2)} #{frame_width_in.round(1)} in. wide")
+ frame.setFrameWidth(frame_with_m)
+ frame.setFrameConductance(frame_conductance_si)
+ skylights_frame_added = 0
+ model.getSubSurfaces.each do |sub_surface|
+ next unless sub_surface.outsideBoundaryCondition == 'Outdoors' && sub_surface.subSurfaceType == 'Skylight'
+ # todo enable proper window frame setting after https://github.com/NREL/OpenStudio/issues/2895 is fixed
+ sub_surface.setString(8, frame.name.get.to_s)
+ skylights_frame_added += 1
+ # if sub_surface.allowWindowPropertyFrameAndDivider
+ # sub_surface.setWindowPropertyFrameAndDivider(frame)
+ # skylights_frame_added += 1
+ # else
+ # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "For #{sub_surface.name}: cannot add a frame to this skylight.")
+ # end
+ end
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Adding #{frame.name} to #{skylights_frame_added} skylights.") if skylights_frame_added > 0
+ else
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Cannot find skylight framing data for: #{framing_name}, will not be created.")
+ return false # TODO: change to return empty optional material
+ end
+ 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
@@ -2500,22 +2285,22 @@
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.
+ # Helper method to find a particular construction and add it to the model after modifying the insulation value if necessary.
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 = 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)
+ 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(model)
@@ -2552,16 +2337,16 @@
unless climate_zone_set
return construction_set
end
# Get the object data
-
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
+ # Search again without the is_residential criteria in the case that this field is not specified for a standard
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
- # if nothing matches say that we could not find it.
+ # 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
@@ -2574,38 +2359,61 @@
construction_set.setName(name)
# Exterior surfaces constructions
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(model_find_and_add_construction(model,
- climate_zone_set,
- 'ExteriorFloor',
- data['exterior_floor_standards_construction_type'],
- data['exterior_floor_building_category']))
+ # Special condition for attics, where the insulation is actually on the floor but the soffit is uninsulated
+ if spc_type == 'Attic'
+ exterior_surfaces.setFloorConstruction(model_add_construction(model, 'Typical Attic Soffit'))
+ else
+ if 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
end
if 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(model_find_and_add_construction(model,
- climate_zone_set,
- 'ExteriorRoof',
- data['exterior_roof_standards_construction_type'],
- data['exterior_roof_building_category']))
+ # Special condition for attics, where the insulation is actually on the floor and the roof itself is uninsulated
+ if spc_type == 'Attic'
+ if data['exterior_roof_standards_construction_type'] && data['exterior_roof_building_category']
+ exterior_surfaces.setRoofCeilingConstruction(model_add_construction(model, 'Typical Uninsulated Wood Joist Attic Roof'))
+ end
+ else
+ if 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
end
-
# Interior surfaces constructions
interior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model)
construction_set.setDefaultInteriorSurfaceConstructions(interior_surfaces)
construction_name = data['interior_floors']
- unless construction_name.nil?
- interior_surfaces.setFloorConstruction(model_add_construction(model, construction_name))
+ # Special condition for attics, where the insulation is actually on the floor and the roof itself is uninsulated
+ if spc_type == 'Attic'
+ if data['exterior_roof_standards_construction_type'] && data['exterior_roof_building_category']
+ interior_surfaces.setFloorConstruction(model_find_and_add_construction(model,
+ climate_zone_set,
+ 'ExteriorRoof',
+ data['exterior_roof_standards_construction_type'],
+ data['exterior_roof_building_category']))
+
+ end
+ else
+ unless construction_name.nil?
+ interior_surfaces.setFloorConstruction(model_add_construction(model, construction_name))
+ end
end
construction_name = data['interior_walls']
unless construction_name.nil?
interior_surfaces.setWallConstruction(model_add_construction(model, construction_name))
end
@@ -2662,13 +2470,16 @@
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(model_add_construction(model, construction_name))
+ if data['exterior_glass_door_standards_construction_type'] && data['exterior_glass_door_building_category']
+ exterior_subsurfaces.setGlassDoorConstruction(model_find_and_add_construction(model,
+ climate_zone_set,
+ 'GlassDoor',
+ data['exterior_glass_door_standards_construction_type'],
+ data['exterior_glass_door_building_category']))
end
if 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',
@@ -2749,95 +2560,95 @@
return nil
end
# 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
- 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
+ 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
+ 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.
#
@@ -2868,51 +2679,82 @@
end
return full_epw_path
end
+ # Find the legacy simulation results from a CSV of previously created results.
+ #
+ # @return [Hash] a hash of results for each fuel, where the keys are in the form 'End Use|Fuel Type',
+ # e.g. Heating|Electricity, Exterior Equipment|Water. All end use/fuel type combos are present, with
+ # values of 0.0 if none of this end use/fuel type combo was used by the simulation. Returns nil
+ # if the legacy results couldn't be found.
+ def model_legacy_results_by_end_use_and_fuel_type(model, climate_zone, building_type)
+ # Load the legacy idf results CSV file into a ruby hash
+ top_dir = File.expand_path('../../..', File.dirname(__FILE__))
+ standards_data_dir = "#{top_dir}/data/standards"
+ temp = ''
+ # Run differently depending on whether running from embedded filesystem in OpenStudio CLI or not
+ if __dir__[0] == ':' # Running from OpenStudio CLI
+ # load file from embedded files
+ temp = load_resource_relative('../../../data/standards/legacy_idf_results.csv', 'r:UTF-8')
+ else
+ # loaded gem from system path
+ temp = File.read("#{standards_data_dir}/legacy_idf_results.csv")
+ end
+ legacy_idf_csv = CSV.new(temp, :headers => true, :converters => :all)
+ legacy_idf_results = legacy_idf_csv.to_a.map {|row| row.to_hash }
+
+ # Get the results for this building
+ search_criteria = {
+ 'Building Type' => building_type,
+ 'Template' => template,
+ 'Climate Zone' => climate_zone
+ }
+ energy_values = model_find_object(legacy_idf_results, search_criteria)
+ if energy_values.nil?
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Could not find legacy simulation results for #{search_criteria}")
+ return {}
+ end
+
+ return energy_values
+ end
+
# 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.
# @return [Hash] Returns a hash with data presented in various bins. Returns nil if no search results
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"
+ # Hash to store the legacy results by fuel and by end use
+ legacy_results_hash = {}
+ legacy_results_hash['total_legacy_energy_val'] = 0
+ legacy_results_hash['total_legacy_water_val'] = 0
+ legacy_results_hash['total_energy_by_fuel'] = {}
+ legacy_results_hash['total_energy_by_end_use'] = {}
- # 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 = File.read("#{standards_data_dir}/legacy_idf_results.json")
+ # Get the lecay simulation results
+ legacy_values = model_legacy_results_by_end_use_and_fuel_type(model, climate_zone, building_type)
+ if legacy_values.nil?
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Could not find legacy idf results for #{search_criteria}")
+ return legacy_results_hash
end
- legacy_idf_results = JSON.parse(temp)
# List of all fuel types
fuel_types = ['Electricity', 'Natural Gas', 'Additional Fuel', 'District Cooling', 'District Heating', 'Water']
# List of all end uses
- end_uses = ['Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps', 'Heat Rejection', 'Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators']
+ end_uses = ['Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps', 'Heat Rejection','Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators']
- # Get legacy idf results
- legacy_results_hash = {}
- legacy_results_hash['total_legacy_energy_val'] = 0
- legacy_results_hash['total_legacy_water_val'] = 0
- legacy_results_hash['total_energy_by_fuel'] = {}
- legacy_results_hash['total_energy_by_end_use'] = {}
+ # Sum the legacy results up by fuel and by end use
fuel_types.each do |fuel_type|
end_uses.each do |end_use|
next if end_use == 'Exterior Equipment'
+ legacy_val = legacy_values["#{end_use}|#{fuel_type}"]
- # Get the legacy results number
- legacy_val = legacy_idf_results.dig(building_type, template, climate_zone, fuel_type, end_use)
-
# Combine the exterior lighting and exterior equipment
if end_use == 'Exterior Lighting'
- legacy_exterior_equipment = legacy_idf_results.dig(building_type, template, climate_zone, fuel_type, 'Exterior Equipment')
+ legacy_exterior_equipment = legacy_values["Exterior Equipment|#{fuel_type}"]
unless legacy_exterior_equipment.nil?
legacy_val += legacy_exterior_equipment
end
end
@@ -2996,29 +2838,17 @@
end
return result
end
- # this is used by other methods to get the clinzte aone and building type from a model.
+ # this is used by other methods to get the climate zone and building type from a model.
# it has logic to break office into small, medium or large based on building area that can be turned off
# @param 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 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 = ''
- model.getClimateZones.climateZones.each do |cz|
- if cz.institution == 'ASHRAE'
- climate_zone = if cz.value == '7' || cz.value == '8'
- "ASHRAE 169-2006-#{cz.value}A"
- else
- "ASHRAE 169-2006-#{cz.value}"
- end
- elsif cz.institution == 'CEC'
- climate_zone = "CEC T24-CEC#{cz.value}"
- end
- end
+ climate_zone = model_standards_climate_zone(model)
# get building type from model
building_type = ''
if model.getBuilding.standardsBuildingType.is_initialized
building_type = model.getBuilding.standardsBuildingType.get
@@ -3117,14 +2947,12 @@
end
return result
end
- # Get a unique list of constructions with given
- # boundary condition and a given type of surface.
- # Pulls from both default construction sets and
- # hard-assigned constructions.
+ # Get a unique list of constructions with given boundary condition and a given type of surface.
+ # Pulls from both default construction sets and hard-assigned constructions.
#
# @param boundary_condition [String] the desired boundary condition
# valid choices are:
# Adiabatic
# Surface
@@ -3274,15 +3102,13 @@
all_constructions = all_constructions.sort
return all_constructions
end
- # Go through the default construction sets and hard-assigned
- # 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.
+ # Go through the default construction sets and hard-assigned 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.
#
# 90.1-2007, 90.1-2010, 90.1-2013
# @return [Bool] returns true if successful, false if not
def model_apply_prm_construction_types(model)
types_to_modify = []
@@ -3363,12 +3189,11 @@
end
return true
end
- # Apply the standard construction to each surface in the
- # model, based on the construction type currently assigned.
+ # 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 model_apply_standard_constructions(model, climate_zone)
types_to_modify = []
@@ -3464,19 +3289,17 @@
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
- # will be done by moving vertices inward toward centroid. This causes the least impact
- # on the daylighting area calculations and controls placement.
+ # Reduces the WWR to the values specified by the PRM.
+ # WWR reduction will be done by moving vertices inward toward centroid.
+ # This causes the least impact on the daylighting area calculations and controls placement.
#
- # @todo add proper support for 90.1-2013 with all those building
- # type specific values
- # @todo support 90.1-2004 requirement that windows be modeled as
- # horizontal bands. Currently just using existing window geometry,
- # and shrinking as necessary if WWR is above limit.
+ # @todo add proper support for 90.1-2013 with all those building type specific values
+ # @todo support 90.1-2004 requirement that windows be modeled as horizontal bands.
+ # Currently just using existing window geometry, and shrinking as necessary if WWR is above limit.
# @todo support semiheated spaces as a separate WWR category
# @todo add window frame area to calculation of WWR
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
@@ -3509,15 +3332,14 @@
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.
+ # TODO: This should really use the heating/cooling loads from the proposed building.
+ # However, in an attempt to avoid another sizing run just for this purpose,
+ # conditioned status is based on heating/cooling setpoints.
+ # If heated-only, will be assumed Semiheated.
# The full-bore method is on the next line in case needed.
# cat = thermal_zone_conditioning_category(space, template, climate_zone)
cooled = space_cooled?(space)
heated = space_heated?(space)
cat = 'Unconditioned'
@@ -3634,12 +3456,11 @@
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.
+ # 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 model_apply_prm_baseline_skylight_to_roof_ratio(model)
# Loop through all spaces in the model, and
@@ -3771,14 +3592,12 @@
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
+ # 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 model_remove_prm_hvac(model)
# Plant loops
model.getPlantLoops.sort.each do |loop|
@@ -3802,12 +3621,11 @@
model.getAirConditionerVariableRefrigerantFlows.each(&:remove)
return true
end
- # Remove external shading devices.
- # Site shading will not be impacted.
+ # Remove external shading devices. Site shading will not be impacted.
# @return [Bool] returns true if successful, false if not.
def model_remove_external_shading_devices(model)
shading_surfaces_removed = 0
model.getShadingSurfaceGroups.sort.each do |shade_group|
# Skip Site shading
@@ -3832,19 +3650,16 @@
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.
+ # Helper method to get the story object that corresponds 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
+ # @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 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)
@@ -3860,11 +3675,11 @@
return story
end
# Returns average daily hot water consumption by building type
- # recommendations from 2011 ASHRAE Handobook - HVAC Applications Table 7 section 60.14
+ # recommendations from 2011 ASHRAE Handbook - HVAC Applications Table 7 section 60.14
# Not all building types are included in lookup
# some recommendations have multiple values based on number of units.
# Will return an array of hashes. Many may have one array entry.
# all values other than block size are gallons.
#
@@ -3926,12 +3741,11 @@
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)
+ # 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 model_find_icc_iecc_2015_internal_loads(model, units_per_bldg, bedrooms_per_unit)
# get total and conditioned floor area
total_floor_area = model.getBuilding.floorArea
@@ -3941,17 +3755,11 @@
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 = ''
- model.getClimateZones.climateZones.each do |cz|
- if cz.institution == 'ASHRAE'
- climate_zone_value = cz.value
- next
- end
- end
+ climate_zone = model_standards_climate_zone(model)
internal_loads = {}
internal_loads['mech_vent_cfm'] = units_per_bldg * (0.01 * conditioned_floor_area + 7.5 * (bedrooms_per_unit + 1.0))
internal_loads['infiltration_ach'] = if ['1A', '1B', '2A', '2B'].include? climate_zone_value
5.0
@@ -3962,12 +3770,11 @@
internal_loads['internal_mass_lbs'] = total_floor_area * 8.0
return internal_loads
end
- # Helper method to make a shortened version of a name
- # that will be readable in a GUI.
+ # Helper method to make a shortened version of a name that will be readable in a GUI.
def model_make_name(model, clim, building_type, spc_type)
clim = clim.gsub('ClimateZone ', 'CZ')
if clim == 'CZ1-8'
clim = ''
end
@@ -4065,13 +3872,12 @@
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 Stnadard spacetype, or is undefined, an error will stop
+ # This method ensures that all spaces with spacetypes defined contain at least a standardSpaceType appropriate for the template.
+ # So, if any space with a space type defined does not have a Stnadard spacetype, or is undefined, an error will stop
# with information that the spacetype needs to be defined.
def model_validate_standards_spacetypes_in_model(model)
error_string = ''
# populate search hash
model.getSpaces.sort.each do |space|
@@ -4304,10 +4110,242 @@
end
return space_type_hash.sort.to_h
end
+
+ # This method will apply the a FDWR to a model. It will remove any existing windows and doors and use the
+ # Default contruction to set to apply the window construction. Sill height is in meters
+ def apply_max_fdwr(model, runner, sillHeight_si, wwr)
+ empty_const_warning = false
+ model.getSpaces.sort.each do |space|
+ space.surfaces.sort.each do |surface|
+ zone = surface.space.get.thermalZone
+ zone_multiplier = nil
+ next if zone.empty?
+ if surface.outsideBoundaryCondition == 'Outdoors' and surface.surfaceType == "Wall"
+ surface.subSurfaces.each {|ss| ss.remove}
+ new_window = surface.setWindowToWallRatio(wwr, sillHeight_si, true)
+ raise "#{surface.name.get} did not get set to #{wwr}. The size of the surface is #{surface.grossArea}" unless surface.windowToWallRatio.round(3) == wwr.round(3)
+ if new_window.empty?
+ runner.registerWarning("The requested window to wall ratio for surface '#{surface.name}' was too large. Fenestration was not altered for this surface.")
+ else
+ windows_added = true
+ # warn user if resulting window doesn't have a construction, as it will result in failed simulation. In the future may use logic from starting windows to apply construction to new window.
+ if new_window.get.construction.empty? && (empty_const_warning == false)
+ runner.registerWarning('one or more resulting windows do not have constructions. This script is intended to be used with models using construction sets versus hard assigned constructions.')
+ empty_const_warning = true
+ end
+ end
+ end
+ end
+ end
+ end
+
+ # This method will apply the a SRR to a model. It will remove any existing skylights and use the
+ # Default contruction to set to apply the skylight construction. A default skylight square area of 0.25^2 is used.
+ def apply_max_srr(model, runner, srr, skylight_area = 0.25 * 0.25)
+ spaces = []
+ surface_type = "RoofCeiling"
+ model.getSpaces.sort.each do |space|
+ space.surfaces.sort.each do |surface|
+ if surface.outsideBoundaryCondition == 'Outdoors' and surface.surfaceType == surface_type
+ spaces << space
+ break
+ end
+ end
+ end
+ pattern = OpenStudio::Model.generateSkylightPattern(spaces, spaces[0].directionofRelativeNorth, srr, Math.sqrt(skylight_area), Math.sqrt(skylight_area)) # ratio, x value, y value
+ # applying skylight pattern
+ skylights = OpenStudio::Model.applySkylightPattern(pattern, spaces, OpenStudio::Model::OptionalConstructionBase.new)
+ spacenames = spaces.map {|space| space.name.get}
+ runner.registerInfo("Adding #{skylights.size} skylights to #{spacenames}")
+ end
+
+ # This method will limit the subsurface of a given surface_type ("Wall" or "RoofCeiling") to the ratio for the building.
+ # This method only reduces subsurface sizes at most.
+ def apply_limit_to_subsurface_ratio(model, ratio, surface_type = "Wall")
+ fdwr = get_outdoor_subsurface_ratio(model, surface_type)
+ if fdwr <= ratio
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Building FDWR of #{fdwr} is already lower than limit of #{ratio.round}%.")
+ return true
+ end
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by shrinking to centroid) to reduce window area down to the limit of #{ratio.round}%.")
+ # Determine the factors by which to reduce the window / door area
+ mult = ratio / fdwr
+ # Reduce the window area if any of the categories necessary
+ model.getSpaces.sort.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 == surface_type
+ # Subsurfaces in this surface
+ surface.subSurfaces.sort.each do |ss|
+ # Reduce the size of the window
+ red = 1.0 - mult
+ sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red)
+ end
+ end
+ end
+ return true
+ end
+
+ # Converts the climate zone in the model into the format used
+ # by the openstudio-standards lookup tables. For example:
+ # institution: ASHRAE, value: 6A becomes: ASHRAE 169-2006-6A.
+ # institution: CEC, value: 3 becomes: CEC T24-CEC3.
+ #
+ # @param model [OpenStudio::Model::Model] the model
+ # @return [String] the string representation of the climate zone,
+ # empty string if no climate zone is present in the model.
+ def model_standards_climate_zone(model)
+ climate_zone = ''
+ model.getClimateZones.climateZones.each do |cz|
+ if cz.institution == 'ASHRAE'
+ next if cz.value == '' # Skip blank ASHRAE climate zones put in by OpenStudio Application
+ climate_zone = if cz.value == '7' || cz.value == '8'
+ "ASHRAE 169-2006-#{cz.value}A"
+ else
+ "ASHRAE 169-2006-#{cz.value}"
+ end
+ elsif cz.institution == 'CEC'
+ next if cz.value == '' # Skip blank ASHRAE climate zones put in by OpenStudio Application
+ climate_zone = "CEC T24-CEC#{cz.value}"
+ end
+ end
+ return climate_zone
+ end
+
+ # Sets the climate zone object in the model using
+ # the correct institution based on the climate zone specified
+ # in the format used by the openstudio-standards lookups.
+ # Clears out any climate zones previously added to the model.
+ #
+ # @param model [OpenStudio::Model::Model] the model
+ # @param climate_zone [String] the climate zone in openstudio-standards format.
+ # For example: ASHRAE 169-2006-2A, CEC T24-CEC3
+ # @return [Boolean] returns true if successful, false if not
+ def model_set_climate_zone(model, climate_zone)
+ # Remove previous climate zones from the model
+ model.getClimateZones.clear
+ # Split the string into the correct institution and value
+ if climate_zone.include? 'ASHRAE 169-2006-'
+ model.getClimateZones.setClimateZone('ASHRAE', climate_zone.gsub('ASHRAE 169-2006-', ''))
+ elsif climate_zone.include? 'CEC T24-CEC'
+ model.getClimateZones.setClimateZone('CEC', climate_zone.gsub('CEC T24-CEC', ''))
+
+ end
+ return true
+ end
+
+
+ # This method return the building ratio of subsurface_area / surface_type_area where surface_type can be "Wall" or "RoofCeiling"
+ def get_outdoor_subsurface_ratio(model, surface_type = "Wall")
+ surface_area = 0.0
+ sub_surface_area = 0
+ all_surfaces = []
+ all_sub_surfaces = []
+ model.getSpaces.sort.each do |space|
+ zone = space.thermalZone
+ zone_multiplier = nil
+ next if zone.empty?
+ zone_multiplier = zone.get.multiplier
+ space.surfaces.sort.each do |surface|
+ if surface.outsideBoundaryCondition == 'Outdoors' and surface.surfaceType == surface_type
+ surface_area += surface.grossArea * zone_multiplier
+ surface.subSurfaces.sort.each do |sub_surface|
+ sub_surface_area += sub_surface.grossArea * sub_surface.multiplier * zone_multiplier
+ end
+ end
+ end
+ end
+ return fdwr = (sub_surface_area / surface_area)
+ 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
+ validate_initial_model(model)
+ return model
+ end
+
+ def validate_initial_model(model)
+ if model.getBuildingStorys.empty?
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please assign Spaces to BuildingStorys the geometry model.")
+ end
+ if model.getThermalZones.empty?
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please assign Spaces to ThermalZones the geometry model.")
+ end
+ if model.getBuilding.standardsNumberOfStories.empty?
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please define Building.standardsNumberOfStories the geometry model.")
+ end
+ if model.getBuilding.standardsNumberOfAboveGroundStories.empty?
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Please define Building.standardsNumberOfAboveStories in the geometry model.")
+ 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 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 model")
+ 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
+ end
+
+
+ # Determines how ventilation for the standard is specified.
+ # When 'Sum', all min OA flow rates are added up. Commonly used by 90.1.
+ # When 'Maximum', only the biggest OA flow rate. Used by T24.
+ #
+ # @param model [OpenStudio::Model::Model] the model
+ # @return [String] the ventilation method, either Sum or Maximum
+ def model_ventilation_method(model)
+ ventilation_method = 'Sum'
+ return ventilation_method
+ end
+
+ # Removes all of the unused ResourceObjects
+ # (Curves, ScheduleDay, Material, etc.) from the model.
+ #
+ # @return [Bool] returns true if successful, false if not
+ def model_remove_unused_resource_objects(model)
+ model.getResourceObjects.sort.each do |obj|
+ if obj.directUseCount.zero?
+ OpenStudio::logFree(OpenStudio::Debug, 'openstudio.standards.Model', "#{obj.name} is unused; it will be removed.")
+ model.removeObject(obj.handle)
+ end
+ end
+
+ return true
+ end
+
+
private
# Helper method to fill in hourly values
def model_add_vals_to_sch(model, day_sch, sch_type, values)
if sch_type == 'Constant'
@@ -4320,21 +4358,19 @@
else
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Schedule type: #{sch_type} is not recognized. Valid choices are 'Constant' and 'Hourly'.")
end
end
- # Modify the existing service water heating loops
- # to match the baseline required heating type.
+ # 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 model_apply_baseline_swh_loops(model, building_type)
model.getPlantLoops.sort.each do |plant_loop|
# Skip non service water heating loops
next unless plant_loop_swh_loop?(plant_loop)
- # Rename the loop to avoid accidentally hooking
- # up the HVAC systems to this loop later.
+ # Rename the loop to avoid accidentally hooking up the HVAC systems to this loop later.
plant_loop.setName('Service Water Heating Loop')
htg_fuels, combination_system, storage_capacity, total_heating_capacity = plant_loop_swh_system_type(plant_loop)
# htg_fuels.size == 0 shoudln't happen
@@ -4349,13 +4385,12 @@
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
- # heating and service water heating, delete all heating equipment
- # and recreate a WaterHeater:Mixed.
+ # 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)
@@ -4404,17 +4439,15 @@
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:
+ # 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.
+ # 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
@@ -4480,20 +4513,9 @@
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