Skip to content

Commit

Permalink
Merge pull request #1693 from NREL/hvac_zone_mapping_create_typical
Browse files Browse the repository at this point in the history
Hvac zone mapping added for create_typical_building_from_model method
  • Loading branch information
mdahlhausen authored Apr 2, 2024
2 parents bc7c4c3 + 2b25c6d commit 2bd5c78
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 67 deletions.
162 changes: 97 additions & 65 deletions lib/openstudio-standards/create_typical/create_typical.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ module CreateTypical
# @param enable_dst [Boolean] Enable daylight savings
# @param unmet_hours_tolerance_r [Double] Thermostat setpoint tolerance for unmet hours in degrees Rankine
# @param remove_objects [Boolean] Clean model of non-geometry objects. Only removes the same objects types as those added to the model.
# @param user_hvac_mapping [Hash] Hash defining a mapping of system types to zones.
# Structure is:
# ['systems'][N]['system_type'] = 'MY_CBECS_HVAC_TYPE' as defined in lib/openstudio-standards/hvac/cbecs_hvac.rb
# ['systems'][N]['thermal_zones'] = ['Zone 1', 'Zone 2', ...]
# @return [Boolean] returns true if successful, false if not
def self.create_typical_building_from_model(model,
template,
Expand Down Expand Up @@ -79,7 +83,11 @@ def self.create_typical_building_from_model(model,
hoo_var_method: 'hours',
enable_dst: true,
unmet_hours_tolerance_r: 1.0,
remove_objects: true)
remove_objects: true,
user_hvac_mapping: nil,
sizing_run_directory: nil)
# sizing run directory
sizing_run_directory = Dir.pwd if sizing_run_directory.nil?

# report initial condition of model
initial_object_size = model.getModelObjects.size
Expand Down Expand Up @@ -531,7 +539,7 @@ def self.create_typical_building_from_model(model,

# add daylight controls, need to perform a sizing run for 2010
if template == '90.1-2010' || template == 'ComStock 90.1-2010'
if standard.model_run_sizing_run(model, "#{Dir.pwd}/create_typical_building_from_model_SR0") == false
if standard.model_run_sizing_run(model, "#{sizing_run_directory}/create_typical_building_from_model_SR0") == false
return false
end
end
Expand Down Expand Up @@ -609,82 +617,84 @@ def self.create_typical_building_from_model(model,
standard.model_remove_prm_hvac(model)
end

case hvac_system_type
when 'Inferred'
# If user does not map HVAC types to zones with a JSON file, run conventional approach to HVAC assignment
if user_hvac_mapping.nil?
case hvac_system_type
when 'Inferred'

# Get the hvac delivery type enum
hvac_delivery = case hvac_delivery_type
when 'Forced Air'
'air'
when 'Hydronic'
'hydronic'
end
# Get the hvac delivery type enum
hvac_delivery = case hvac_delivery_type
when 'Forced Air'
'air'
when 'Hydronic'
'hydronic'
end

# Group the zones by occupancy type. Only split out non-dominant groups if their total area exceeds the limit.
min_area_m2 = OpenStudio.convert(20_000, 'ft^2', 'm^2').get
sys_groups = OpenstudioStandards::Geometry.model_group_thermal_zones_by_occupancy_type(model, min_area_m2: min_area_m2)

# For each group, infer the HVAC system type.
sys_groups.each do |sys_group|
# Infer the primary system type
sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel = standard.model_typical_hvac_system_type(model,
climate_zone,
sys_group['type'],
hvac_delivery,
heating_fuel,
cooling_fuel,
OpenStudio.convert(sys_group['area_ft2'], 'ft^2', 'm^2').get,
sys_group['stories'])

# Infer the secondary system type for multizone systems
sec_sys_type = case sys_type
when 'PVAV Reheat', 'VAV Reheat'
'PSZ-AC'
when 'PVAV PFP Boxes', 'VAV PFP Boxes'
'PSZ-HP'
else
sys_type # same as primary system type
end
# For each group, infer the HVAC system type.
sys_groups.each do |sys_group|
# Infer the primary system type
sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel = standard.model_typical_hvac_system_type(model,
climate_zone,
sys_group['type'],
hvac_delivery,
heating_fuel,
cooling_fuel,
OpenStudio.convert(sys_group['area_ft2'], 'ft^2', 'm^2').get,
sys_group['stories'])

# Infer the secondary system type for multizone systems
sec_sys_type = case sys_type
when 'PVAV Reheat', 'VAV Reheat'
'PSZ-AC'
when 'PVAV PFP Boxes', 'VAV PFP Boxes'
'PSZ-HP'
else
sys_type # same as primary system type
end

# group zones
story_zone_lists = OpenstudioStandards::Geometry.model_group_thermal_zones_by_building_story(model, sys_group['zones'])

# On each story, add the primary system to the primary zones
# and add the secondary system to any zones that are different.
story_zone_lists.each do |story_group|
# Differentiate primary and secondary zones, based on
# operating hours and internal loads (same as 90.1 PRM)
pri_sec_zone_lists = standard.model_differentiate_primary_secondary_thermal_zones(model, story_group)
system_zones = pri_sec_zone_lists['primary']

# if the primary system type is PTAC, filter to cooled zones to prevent sizing error if no cooling
if sys_type == 'PTAC'
heated_and_cooled_zones = system_zones.select { |zone| standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
cooled_only_zones = system_zones.select { |zone| !standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
system_zones = heated_and_cooled_zones + cooled_only_zones
end

# Add the primary system to the primary zones
unless standard.model_add_hvac_system(model, sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel, system_zones)
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.CreateTypical', "HVAC system type '#{sys_type}' not recognized. Check input system type argument against Model.hvac.rb for valid hvac system type names.")
return false
end
# On each story, add the primary system to the primary zones
# and add the secondary system to any zones that are different.
story_zone_lists.each do |story_group|
# Differentiate primary and secondary zones, based on
# operating hours and internal loads (same as 90.1 PRM)
pri_sec_zone_lists = standard.model_differentiate_primary_secondary_thermal_zones(model, story_group)
system_zones = pri_sec_zone_lists['primary']

# Add the secondary system to the secondary zones (if any)
if !pri_sec_zone_lists['secondary'].empty?
system_zones = pri_sec_zone_lists['secondary']
if (sec_sys_type == 'PTAC') || (sec_sys_type == 'PSZ-AC')
# if the primary system type is PTAC, filter to cooled zones to prevent sizing error if no cooling
if sys_type == 'PTAC'
heated_and_cooled_zones = system_zones.select { |zone| standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
cooled_only_zones = system_zones.select { |zone| !standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
system_zones = heated_and_cooled_zones + cooled_only_zones
end
unless standard.model_add_hvac_system(model, sec_sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel, system_zones)

# Add the primary system to the primary zones
unless standard.model_add_hvac_system(model, sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel, system_zones)
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.CreateTypical', "HVAC system type '#{sys_type}' not recognized. Check input system type argument against Model.hvac.rb for valid hvac system type names.")
return false
end

# Add the secondary system to the secondary zones (if any)
if !pri_sec_zone_lists['secondary'].empty?
system_zones = pri_sec_zone_lists['secondary']
if (sec_sys_type == 'PTAC') || (sec_sys_type == 'PSZ-AC')
heated_and_cooled_zones = system_zones.select { |zone| standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
cooled_only_zones = system_zones.select { |zone| !standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
system_zones = heated_and_cooled_zones + cooled_only_zones
end
unless standard.model_add_hvac_system(model, sec_sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel, system_zones)
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.CreateTypical', "HVAC system type '#{sys_type}' not recognized. Check input system type argument against Model.hvac.rb for valid hvac system type names.")
return false
end
end
end
end
end

else
# HVAC system_type specified
Expand All @@ -695,16 +705,38 @@ def self.create_typical_building_from_model(model,
# group zones
story_zone_groups = OpenstudioStandards::Geometry.model_group_thermal_zones_by_building_story(model, sys_group['zones'])

# Add the user specified HVAC system for each story.
# Single-zone systems will get one per zone.
story_zone_groups.each do |zones|
unless OpenstudioStandards::HVAC.add_cbecs_hvac_system(model, standard, hvac_system_type, zones)
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.CreateTypical', "HVAC system type '#{hvac_system_type}' not recognized. Check input system type argument against cbecs_hvac.rb in the HVAC module for valid HVAC system type names.")
return false
# Add the user specified HVAC system for each story.
# Single-zone systems will get one per zone.
story_zone_groups.each do |zones|
unless OpenstudioStandards::HVAC.add_cbecs_hvac_system(model, standard, hvac_system_type, zones)
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.CreateTypical', "HVAC system type '#{hvac_system_type}' not recognized. Check input system type argument against cbecs_hvac.rb in the HVAC module for valid HVAC system type names.")
return false
end
end
end

end

else
# If user specified a mapping of HVAC systems to zones
user_hvac_mapping['systems'].each do |system_hash|
hvac_system_type = system_hash['system_type']
zone_names = system_hash['thermal_zones']

# Get OS:ThermalZone objects
zones = zone_names.map do |zone_name|
model.getThermalZoneByName(zone_name).get
end

puts "Adding #{hvac_system_type} to #{zone_names.join(', ')}"

unless OpenstudioStandards::HVAC.add_cbecs_hvac_system(model, standard, hvac_system_type, zones)
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.CreateTypical', "HVAC system type '#{hvac_system_type}' not recognized. Check input system type argument against cbecs_hvac.rb in the HVAC module for valid HVAC system type names.")
return false
end
end
end

end

# hours of operation
Expand Down Expand Up @@ -760,7 +792,7 @@ def self.create_typical_building_from_model(model,
standard.model_apply_prm_sizing_parameters(model)

# Perform a sizing run
if standard.model_run_sizing_run(model, "#{Dir.pwd}/create_typical_building_from_model_SR1") == false
if standard.model_run_sizing_run(model, "#{sizing_run_directory}/create_typical_building_from_model_SR1") == false
return false
end

Expand Down
20 changes: 20 additions & 0 deletions test/modules/create_typical/data/hvac_zone_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"systems": [
{
"system_type": "PSZ-AC with gas coil",
"thermal_zones": [
"TZ-Core_ZN"
]
},
{
"system_type": "PTAC with electric coil",
"thermal_zones": [
"TZ-Perimeter_ZN_1",
"TZ-Perimeter_ZN_2",
"TZ-Perimeter_ZN_3",
"TZ-Perimeter_ZN_4"
]
}

]
}
44 changes: 42 additions & 2 deletions test/modules/create_typical/test_create_typical.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ def test_create_typical_building_from_model
model = std.safe_load_model("#{File.dirname(__FILE__)}/../../../data/geometry/ASHRAEPrimarySchool.osm")
OpenstudioStandards::Weather.model_set_building_location(model, climate_zone: climate_zone)

# set output directory
output_dir = "#{__dir__}/output"
FileUtils.mkdir output_dir unless Dir.exist? output_dir

# apply create typical
starting_size = model.getModelObjects.size
result = @create.create_typical_building_from_model(model, template, climate_zone: climate_zone)
result = @create.create_typical_building_from_model(model, template,
climate_zone: climate_zone,
sizing_run_directory: output_dir)
ending_size = model.getModelObjects.size
assert(result)
assert(starting_size < ending_size)
Expand All @@ -31,4 +37,38 @@ def test_create_space_types_and_constructions
assert(model.getSpaceTypes.size > 0)
assert(model.getDefaultConstructionSets.size > 0)
end
end

def test_create_typical_building_from_model_with_hvac_mapping
# load model and set up weather file
template = '90.1-2013'
climate_zone = 'ASHRAE 169-2013-4A'
std = Standard.build(template)
model = std.safe_load_model("#{File.dirname(__FILE__)}/../../../data/geometry/ASHRAESmallOffice.osm")
OpenstudioStandards::Weather.model_set_building_location(model, climate_zone: climate_zone)

# Read in HVAC to zone mapping with 4 zones served by PTAC and 1 by a PSZ AC
hvac_zone_json_path = File.join(File.dirname(__FILE__),'data','hvac_zone_mapping.json')
hvac_zone_json = File.read(hvac_zone_json_path)
hvac_mapping_hash = JSON.parse(hvac_zone_json)

# set output directory
output_dir = "#{__dir__}/output"
FileUtils.mkdir output_dir unless Dir.exist? output_dir

# apply create typical with zone mapping
starting_size = model.getModelObjects.size
result = @create.create_typical_building_from_model(model, template,
climate_zone: climate_zone,
user_hvac_mapping: hvac_mapping_hash,
sizing_run_directory: output_dir)
ending_size = model.getModelObjects.size
ptacs = model.getZoneHVACPackagedTerminalAirConditioners
psz_ac = model.getAirLoopHVACUnitarySystems

# Check that JSON specs were applied
assert(result)
assert(starting_size < ending_size)
assert(ptacs.length==4)
assert(psz_ac.length==1)
end
end

0 comments on commit 2bd5c78

Please sign in to comment.