From be726030f0c026c2ab031cb83b7f0a1888e3a30d Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Tue, 7 Jan 2025 11:47:56 -0700 Subject: [PATCH 01/11] Stub new zonehvac evap cooler test file. --- .../zone_hvac_evaporative_cooler.rb | 59 +++++++++++++++++++ model_tests.rb | 12 ++++ 2 files changed, 71 insertions(+) create mode 100644 model/simulationtests/zone_hvac_evaporative_cooler.rb diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.rb b/model/simulationtests/zone_hvac_evaporative_cooler.rb new file mode 100644 index 00000000..d4a6b64e --- /dev/null +++ b/model/simulationtests/zone_hvac_evaporative_cooler.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'openstudio' +require_relative 'lib/baseline_model' + +model = BaselineModel.new + +# make a 1 story, 100m X 50m, 1 zone building +model.add_geometry({ 'length' => 100, + 'width' => 50, + 'num_floors' => 1, + 'floor_to_floor_height' => 4, + 'plenum_height' => 0, + 'perimeter_zone_depth' => 0 }) + +# add windows at a 40% window-to-wall ratio +model.add_windows({ 'wwr' => 0.4, + 'offset' => 1, + 'application_type' => 'Above Floor' }) + +# add ASHRAE System type 03, PSZ-AC +model.add_hvac({ 'ashrae_sys_num' => '03' }) + +# assign constructions from a local library to the walls/windows/etc. in the model +model.set_constructions + +# set whole building space type; simplified 90.1-2004 Large Office Whole Building +model.set_space_type + +# add design days to the model (Chicago) +model.add_design_days + +# add thermostats +model.add_thermostats({ 'heating_setpoint' => 24, + 'cooling_setpoint' => 28 }) + + +# In order to produce more consistent results between different runs, +# we sort the zones by names +# (There's only one here, but just in case this would be copy pasted somewhere +# else...) +zones = model.getThermalZones.sort_by { |z| z.name.to_s } +z = zones[0] +air_system = z.airLoopHVAC.get + +oa_node = air_system.airLoopHVACOutdoorAirSystem.get.outboardOANode.get + +# Add ZoneHVACEvaporativeCoolerUnit +zoneHVACEvaporativeCoolerUnit = OpenStudio::Model::ZoneHVACEvaporativeCoolerUnit.new(model) +direct_evap = zoneHVACEvaporativeCoolerUnit.firstEvaporativeCooler +direct_evap.addToNode(oa_node) +indirect_evap = OpenStudio::Model::EvaporativeCoolerIndirectResearchSpecial.new(model) +indirect_evap.addToNode(oa_node) +zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(indirect_evap) +zoneHVACEvaporativeCoolerUnit.addToThermalZone(z) + +# save the OpenStudio model (.osm) +model.save_openstudio_osm({ 'osm_save_directory' => Dir.pwd, + 'osm_name' => 'in.osm' }) diff --git a/model_tests.rb b/model_tests.rb index e9275dd3..27ec0bb3 100644 --- a/model_tests.rb +++ b/model_tests.rb @@ -2147,6 +2147,18 @@ def test_zone_hvac_cooling_panel_osm result = sim_test('zone_hvac_cooling_panel.osm') end + def test_zone_hvac_evaporative_cooler_rb + result = sim_test('zone_hvac_evaporative_cooler.rb') + end + + # def test_zone_hvac_evaporative_cooler_py + # result = sim_test('zone_hvac_evaporative_cooler.py') + # end + + # def test_zone_hvac_evaporative_cooler_osm + # result = sim_test('zone_hvac_evaporative_cooler.osm') + # end + def test_zone_hvac_equipment_list_rb result = sim_test('zone_hvac_equipment_list.rb') end From fa5ae45f2df507328ca2ee582c0462302cb23fae Mon Sep 17 00:00:00 2001 From: "github-rubocop-actions[bot]" Date: Tue, 7 Jan 2025 18:53:15 +0000 Subject: [PATCH 02/11] Commit rubocop --auto-correct (Ruby ruby 2.7.8p225 (2023-03-30 revision 1f4d455848) [x86_64-linux], Rubocop 0.81.0) --- model/simulationtests/zone_hvac_evaporative_cooler.rb | 1 - model_tests.rb | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.rb b/model/simulationtests/zone_hvac_evaporative_cooler.rb index d4a6b64e..7d97e306 100644 --- a/model/simulationtests/zone_hvac_evaporative_cooler.rb +++ b/model/simulationtests/zone_hvac_evaporative_cooler.rb @@ -34,7 +34,6 @@ model.add_thermostats({ 'heating_setpoint' => 24, 'cooling_setpoint' => 28 }) - # In order to produce more consistent results between different runs, # we sort the zones by names # (There's only one here, but just in case this would be copy pasted somewhere diff --git a/model_tests.rb b/model_tests.rb index 27ec0bb3..f8cdfb67 100644 --- a/model_tests.rb +++ b/model_tests.rb @@ -2152,11 +2152,11 @@ def test_zone_hvac_evaporative_cooler_rb end # def test_zone_hvac_evaporative_cooler_py - # result = sim_test('zone_hvac_evaporative_cooler.py') + # result = sim_test('zone_hvac_evaporative_cooler.py') # end # def test_zone_hvac_evaporative_cooler_osm - # result = sim_test('zone_hvac_evaporative_cooler.osm') + # result = sim_test('zone_hvac_evaporative_cooler.osm') # end def test_zone_hvac_equipment_list_rb From 75f4ccc77f41ac50b11994b846067c182329af1c Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Thu, 9 Jan 2025 13:30:15 +0100 Subject: [PATCH 03/11] Adjust test and rename nodes for clarity --- .../simulationtests/zone_hvac_evaporative_cooler.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.rb b/model/simulationtests/zone_hvac_evaporative_cooler.rb index 7d97e306..59f820dd 100644 --- a/model/simulationtests/zone_hvac_evaporative_cooler.rb +++ b/model/simulationtests/zone_hvac_evaporative_cooler.rb @@ -18,9 +18,6 @@ 'offset' => 1, 'application_type' => 'Above Floor' }) -# add ASHRAE System type 03, PSZ-AC -model.add_hvac({ 'ashrae_sys_num' => '03' }) - # assign constructions from a local library to the walls/windows/etc. in the model model.set_constructions @@ -40,19 +37,19 @@ # else...) zones = model.getThermalZones.sort_by { |z| z.name.to_s } z = zones[0] -air_system = z.airLoopHVAC.get - -oa_node = air_system.airLoopHVACOutdoorAirSystem.get.outboardOANode.get # Add ZoneHVACEvaporativeCoolerUnit zoneHVACEvaporativeCoolerUnit = OpenStudio::Model::ZoneHVACEvaporativeCoolerUnit.new(model) direct_evap = zoneHVACEvaporativeCoolerUnit.firstEvaporativeCooler -direct_evap.addToNode(oa_node) indirect_evap = OpenStudio::Model::EvaporativeCoolerIndirectResearchSpecial.new(model) -indirect_evap.addToNode(oa_node) zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(indirect_evap) zoneHVACEvaporativeCoolerUnit.addToThermalZone(z) +z.zoneAirNode.setName("#{z.nameString} Zone Air Node") +# z.returnAirModelObjects.modelObjects[0].setName("#{z.nameString} Zone Return Air Node") +z.inletPortList.modelObjects[0].setName("#{z.nameString} Zone Inlet Node") +z.exhaustPortList.modelObjects[0].setName("#{z.nameString} Zone Exhaust Air Node") + # save the OpenStudio model (.osm) model.save_openstudio_osm({ 'osm_save_directory' => Dir.pwd, 'osm_name' => 'in.osm' }) From 96801a12e4d659e7d878b5016bdc64871c6773e9 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Thu, 16 Jan 2025 12:00:17 +0100 Subject: [PATCH 04/11] Add missing TODO + exercice entire API for demonstration purposes --- .../zone_hvac_evaporative_cooler.rb | 19 +++++++++++++++++++ model_tests.rb | 6 ++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.rb b/model/simulationtests/zone_hvac_evaporative_cooler.rb index 59f820dd..17061b32 100644 --- a/model/simulationtests/zone_hvac_evaporative_cooler.rb +++ b/model/simulationtests/zone_hvac_evaporative_cooler.rb @@ -40,11 +40,30 @@ # Add ZoneHVACEvaporativeCoolerUnit zoneHVACEvaporativeCoolerUnit = OpenStudio::Model::ZoneHVACEvaporativeCoolerUnit.new(model) +# There is an alternative constructor +# ZoneHVACEvaporativeCoolerUnit(const Model& model, Schedule& availabilitySchedule, HVACComponent& supplyAirFan, HVACComponent& firstEvaporativeCooler); direct_evap = zoneHVACEvaporativeCoolerUnit.firstEvaporativeCooler + +# Redoing what the default constructor does for demonstration purposes +# zoneHVACEvaporativeCoolerUnit.setDesignSupplyAirFlowRate(1.0) +zoneHVACEvaporativeCoolerUnit.autosizeDesignSupplyAirFlowRate +zoneHVACEvaporativeCoolerUnit.setAvailabilitySchedule(model.alwaysOnDiscreteSchedule) +zoneHVACEvaporativeCoolerUnit.setSupplyAirFan(zoneHVACEvaporativeCoolerUnit.supplyAirFan) +zoneHVACEvaporativeCoolerUnit.setFanPlacement("BlowThrough") +zoneHVACEvaporativeCoolerUnit.setCoolerUnitControlMethod("ZoneTemperatureDeadbandOnOffCycling") +zoneHVACEvaporativeCoolerUnit.setThrottlingRangeTemperatureDifference(1.0) +zoneHVACEvaporativeCoolerUnit.setCoolingLoadControlThresholdHeatTransferRate(100.0) +zoneHVACEvaporativeCoolerUnit.setShutOffRelativeHumidity(100.0) + +# An optional Second EvporativeCooler indirect_evap = OpenStudio::Model::EvaporativeCoolerIndirectResearchSpecial.new(model) zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(indirect_evap) +# zoneHVACEvaporativeCoolerUnit.resetSecondEvaporativeCooler + +# Add it to a ThermalZone zoneHVACEvaporativeCoolerUnit.addToThermalZone(z) +# Rename nodes for clarity z.zoneAirNode.setName("#{z.nameString} Zone Air Node") # z.returnAirModelObjects.modelObjects[0].setName("#{z.nameString} Zone Return Air Node") z.inletPortList.modelObjects[0].setName("#{z.nameString} Zone Inlet Node") diff --git a/model_tests.rb b/model_tests.rb index f8cdfb67..71ed2466 100644 --- a/model_tests.rb +++ b/model_tests.rb @@ -2151,12 +2151,14 @@ def test_zone_hvac_evaporative_cooler_rb result = sim_test('zone_hvac_evaporative_cooler.rb') end + # TODO: pending being happy with the ruby test # def test_zone_hvac_evaporative_cooler_py - # result = sim_test('zone_hvac_evaporative_cooler.py') + # result = sim_test('zone_hvac_evaporative_cooler.py') # end + # TODO: To be added in the next official release after: 3.9.0 # def test_zone_hvac_evaporative_cooler_osm - # result = sim_test('zone_hvac_evaporative_cooler.osm') + # result = sim_test('zone_hvac_evaporative_cooler.osm') # end def test_zone_hvac_equipment_list_rb From 9eaada9f50aa3722d476d990cb6659d9ca7244a1 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Thu, 16 Jan 2025 12:44:59 +0100 Subject: [PATCH 05/11] Add ZoneHVACEvaporativeCoollerUnit to autosize_hvac --- model/simulationtests/autosize_hvac.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/model/simulationtests/autosize_hvac.rb b/model/simulationtests/autosize_hvac.rb index febde2fb..964846a4 100644 --- a/model/simulationtests/autosize_hvac.rb +++ b/model/simulationtests/autosize_hvac.rb @@ -1215,9 +1215,14 @@ def make_tes_coil(model) term.addToThermalZone(zn) vrf.addTerminal(term) + when 44 + zoneHVACEvaporativeCoolerUnit = OpenStudio::Model::ZoneHVACEvaporativeCoolerUnit.new(model) + zoneHVACEvaporativeCoolerUnit.autosizeDesignSupplyAirFlowRate + zoneHVACEvaporativeCoolerUnit.addToThermalZone(zn) + when 26, 27, 28, 29, 30, 31, 32, 33, 38, 40, 43 # Previously used for the unitary systems, dehum, etc - when 0, 44 + when 0 # This wasn't assigned yet and is for grabs puts "Nothing added to #{zn.name}, index #{zone_index}" else From f16dda88b4c842b090984d82249a490c8eb6332c Mon Sep 17 00:00:00 2001 From: "github-rubocop-actions[bot]" Date: Thu, 16 Jan 2025 11:46:27 +0000 Subject: [PATCH 06/11] Commit rubocop --auto-correct (Ruby ruby 2.7.8p225 (2023-03-30 revision 1f4d455848) [x86_64-linux], Rubocop 0.81.0) --- model/simulationtests/zone_hvac_evaporative_cooler.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.rb b/model/simulationtests/zone_hvac_evaporative_cooler.rb index 17061b32..d510259f 100644 --- a/model/simulationtests/zone_hvac_evaporative_cooler.rb +++ b/model/simulationtests/zone_hvac_evaporative_cooler.rb @@ -49,8 +49,8 @@ zoneHVACEvaporativeCoolerUnit.autosizeDesignSupplyAirFlowRate zoneHVACEvaporativeCoolerUnit.setAvailabilitySchedule(model.alwaysOnDiscreteSchedule) zoneHVACEvaporativeCoolerUnit.setSupplyAirFan(zoneHVACEvaporativeCoolerUnit.supplyAirFan) -zoneHVACEvaporativeCoolerUnit.setFanPlacement("BlowThrough") -zoneHVACEvaporativeCoolerUnit.setCoolerUnitControlMethod("ZoneTemperatureDeadbandOnOffCycling") +zoneHVACEvaporativeCoolerUnit.setFanPlacement('BlowThrough') +zoneHVACEvaporativeCoolerUnit.setCoolerUnitControlMethod('ZoneTemperatureDeadbandOnOffCycling') zoneHVACEvaporativeCoolerUnit.setThrottlingRangeTemperatureDifference(1.0) zoneHVACEvaporativeCoolerUnit.setCoolingLoadControlThresholdHeatTransferRate(100.0) zoneHVACEvaporativeCoolerUnit.setShutOffRelativeHumidity(100.0) From 516030dc35063314e093e33e448254b5c9aa7ead Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Thu, 16 Jan 2025 16:03:23 +0100 Subject: [PATCH 07/11] Make the test use both configuration: Direct first or Indirect first. --- .../zone_hvac_evaporative_cooler.rb | 104 ++++++++++++------ 1 file changed, 68 insertions(+), 36 deletions(-) diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.rb b/model/simulationtests/zone_hvac_evaporative_cooler.rb index d510259f..405d99f3 100644 --- a/model/simulationtests/zone_hvac_evaporative_cooler.rb +++ b/model/simulationtests/zone_hvac_evaporative_cooler.rb @@ -5,10 +5,10 @@ model = BaselineModel.new -# make a 1 story, 100m X 50m, 1 zone building +# make a 2 stories, 100m X 50m, 2 zone building model.add_geometry({ 'length' => 100, 'width' => 50, - 'num_floors' => 1, + 'num_floors' => 2, 'floor_to_floor_height' => 4, 'plenum_height' => 0, 'perimeter_zone_depth' => 0 }) @@ -31,43 +31,75 @@ model.add_thermostats({ 'heating_setpoint' => 24, 'cooling_setpoint' => 28 }) +def make_zone_hvac_cooler(z, direct_is_first: false) + model = z.model + if direct_is_first + # There is a default ctor + zoneHVACEvaporativeCoolerUnit = OpenStudio::Model::ZoneHVACEvaporativeCoolerUnit.new(model) + direct_evap = zoneHVACEvaporativeCoolerUnit.firstEvaporativeCooler + supplyAirFan = zoneHVACEvaporativeCoolerUnit.supplyAirFan + + fan_system = OpenStudio::Model::FanSystemModel.new(model) + zoneHVACEvaporativeCoolerUnit.setSupplyAirFan(fan_system) + supplyAirFan.remove + supplyAirFan = fan_system + # An optional Second EvaporativeCooler + indirect_evap = OpenStudio::Model::EvaporativeCoolerIndirectResearchSpecial.new(model) + zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(indirect_evap) + else + # And an explicit Ctor: + # ZoneHVACEvaporativeCoolerUnit(const Model& model, Schedule& availabilitySchedule, HVACComponent& supplyAirFan, HVACComponent& firstEvaporativeCooler); + supplyAirFan = OpenStudio::Model::FanSystemModel.new(model) + + indirect_evap = OpenStudio::Model::EvaporativeCoolerIndirectResearchSpecial.new(model) + zoneHVACEvaporativeCoolerUnit = OpenStudio::Model::ZoneHVACEvaporativeCoolerUnit.new( + model, model.alwaysOnDiscreteSchedule, supplyAirFan, indirect_evap + ) + # An optional Second EvaporativeCooler + direct_evap = OpenStudio::Model::EvaporativeCoolerDirectResearchSpecial.new(model, model.alwaysOnDiscreteSchedule) + zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(direct_evap) + end + indirect_evap.setName("#{z.nameString} Indirect Evaporative Cooler") + direct_evap.setName("#{z.nameString} Direct Evaporative Cooler") + supplyAirFan.setName("#{z.nameString} Supply Fan") + zoneHVACEvaporativeCoolerUnit.setName("#{z.nameString} Zone Evap Unit") + # zoneHVACEvaporativeCoolerUnit.resetSecondEvaporativeCooler + # + # Redoing what the default constructor does for demonstration purposes + # zoneHVACEvaporativeCoolerUnit.setDesignSupplyAirFlowRate(1.0) + zoneHVACEvaporativeCoolerUnit.autosizeDesignSupplyAirFlowRate + zoneHVACEvaporativeCoolerUnit.setAvailabilitySchedule(model.alwaysOnDiscreteSchedule) + zoneHVACEvaporativeCoolerUnit.setSupplyAirFan(zoneHVACEvaporativeCoolerUnit.supplyAirFan) + zoneHVACEvaporativeCoolerUnit.setFanPlacement('BlowThrough') + zoneHVACEvaporativeCoolerUnit.setCoolerUnitControlMethod('ZoneCoolingLoadVariableSpeedFan') + zoneHVACEvaporativeCoolerUnit.setThrottlingRangeTemperatureDifference(1.1) + zoneHVACEvaporativeCoolerUnit.setCoolingLoadControlThresholdHeatTransferRate(100.0) + zoneHVACEvaporativeCoolerUnit.setShutOffRelativeHumidity(100.0) + + # Add it to a ThermalZone + zoneHVACEvaporativeCoolerUnit.addToThermalZone(z) + + # Rename nodes for clarity + z.zoneAirNode.setName("#{z.nameString} Zone Air Node") + # z.returnAirModelObjects.modelObjects[0].setName("#{z.nameString} Zone Return Air Node") + z.inletPortList.modelObjects[0].setName("#{z.nameString} Zone Air Inlet Node") + z.exhaustPortList.modelObjects[0].setName("#{z.nameString} Zone Air Exhaust Node") +end + + # In order to produce more consistent results between different runs, # we sort the zones by names -# (There's only one here, but just in case this would be copy pasted somewhere -# else...) zones = model.getThermalZones.sort_by { |z| z.name.to_s } -z = zones[0] - -# Add ZoneHVACEvaporativeCoolerUnit -zoneHVACEvaporativeCoolerUnit = OpenStudio::Model::ZoneHVACEvaporativeCoolerUnit.new(model) -# There is an alternative constructor -# ZoneHVACEvaporativeCoolerUnit(const Model& model, Schedule& availabilitySchedule, HVACComponent& supplyAirFan, HVACComponent& firstEvaporativeCooler); -direct_evap = zoneHVACEvaporativeCoolerUnit.firstEvaporativeCooler - -# Redoing what the default constructor does for demonstration purposes -# zoneHVACEvaporativeCoolerUnit.setDesignSupplyAirFlowRate(1.0) -zoneHVACEvaporativeCoolerUnit.autosizeDesignSupplyAirFlowRate -zoneHVACEvaporativeCoolerUnit.setAvailabilitySchedule(model.alwaysOnDiscreteSchedule) -zoneHVACEvaporativeCoolerUnit.setSupplyAirFan(zoneHVACEvaporativeCoolerUnit.supplyAirFan) -zoneHVACEvaporativeCoolerUnit.setFanPlacement('BlowThrough') -zoneHVACEvaporativeCoolerUnit.setCoolerUnitControlMethod('ZoneTemperatureDeadbandOnOffCycling') -zoneHVACEvaporativeCoolerUnit.setThrottlingRangeTemperatureDifference(1.0) -zoneHVACEvaporativeCoolerUnit.setCoolingLoadControlThresholdHeatTransferRate(100.0) -zoneHVACEvaporativeCoolerUnit.setShutOffRelativeHumidity(100.0) - -# An optional Second EvporativeCooler -indirect_evap = OpenStudio::Model::EvaporativeCoolerIndirectResearchSpecial.new(model) -zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(indirect_evap) -# zoneHVACEvaporativeCoolerUnit.resetSecondEvaporativeCooler - -# Add it to a ThermalZone -zoneHVACEvaporativeCoolerUnit.addToThermalZone(z) - -# Rename nodes for clarity -z.zoneAirNode.setName("#{z.nameString} Zone Air Node") -# z.returnAirModelObjects.modelObjects[0].setName("#{z.nameString} Zone Return Air Node") -z.inletPortList.modelObjects[0].setName("#{z.nameString} Zone Inlet Node") -z.exhaustPortList.modelObjects[0].setName("#{z.nameString} Zone Exhaust Air Node") + +z1 = zones[0] +z1.setName("Direct First Zone") +make_zone_hvac_cooler(z1, direct_is_first: true) + +# Mimic SMStore8 from StripMallZoneEvapCoolerAutosized.idf +# https://github.com/NREL/EnergyPlus/blob/31e3c33467c5873371bf48b12a7318215971c315/testfiles/StripMallZoneEvapCoolerAutosized.idf#L4767-L4784 +z2 = zones[1] +z2.setName("Indirect First Zone") +make_zone_hvac_cooler(z2, direct_is_first: false) # save the OpenStudio model (.osm) model.save_openstudio_osm({ 'osm_save_directory' => Dir.pwd, From 9670dce645fee00db4acf08212c9ed69910af782 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Thu, 16 Jan 2025 16:15:28 +0100 Subject: [PATCH 08/11] The nodes are HARD to manage: test all combinations of Fan Placement x Direct/Indirect x secondary (8 combos) --- .../zone_hvac_evaporative_cooler.rb | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.rb b/model/simulationtests/zone_hvac_evaporative_cooler.rb index 405d99f3..63d459e4 100644 --- a/model/simulationtests/zone_hvac_evaporative_cooler.rb +++ b/model/simulationtests/zone_hvac_evaporative_cooler.rb @@ -5,10 +5,10 @@ model = BaselineModel.new -# make a 2 stories, 100m X 50m, 2 zone building +# make a 8 stories, 100m X 50m, 8 zone building model.add_geometry({ 'length' => 100, 'width' => 50, - 'num_floors' => 2, + 'num_floors' => 8, 'floor_to_floor_height' => 4, 'plenum_height' => 0, 'perimeter_zone_depth' => 0 }) @@ -31,8 +31,10 @@ model.add_thermostats({ 'heating_setpoint' => 24, 'cooling_setpoint' => 28 }) -def make_zone_hvac_cooler(z, direct_is_first: false) +def make_zone_hvac_cooler(z, direct_is_first: false, add_secondary: true, fan_placement: 'BlowThrough') model = z.model + indirect_evap = nil + direct_evap = nil if direct_is_first # There is a default ctor zoneHVACEvaporativeCoolerUnit = OpenStudio::Model::ZoneHVACEvaporativeCoolerUnit.new(model) @@ -44,8 +46,10 @@ def make_zone_hvac_cooler(z, direct_is_first: false) supplyAirFan.remove supplyAirFan = fan_system # An optional Second EvaporativeCooler - indirect_evap = OpenStudio::Model::EvaporativeCoolerIndirectResearchSpecial.new(model) - zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(indirect_evap) + if add_secondary + indirect_evap = OpenStudio::Model::EvaporativeCoolerIndirectResearchSpecial.new(model) + zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(indirect_evap) + end else # And an explicit Ctor: # ZoneHVACEvaporativeCoolerUnit(const Model& model, Schedule& availabilitySchedule, HVACComponent& supplyAirFan, HVACComponent& firstEvaporativeCooler); @@ -56,13 +60,15 @@ def make_zone_hvac_cooler(z, direct_is_first: false) model, model.alwaysOnDiscreteSchedule, supplyAirFan, indirect_evap ) # An optional Second EvaporativeCooler - direct_evap = OpenStudio::Model::EvaporativeCoolerDirectResearchSpecial.new(model, model.alwaysOnDiscreteSchedule) - zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(direct_evap) + if add_secondary + direct_evap = OpenStudio::Model::EvaporativeCoolerDirectResearchSpecial.new(model, model.alwaysOnDiscreteSchedule) + zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(direct_evap) + end end - indirect_evap.setName("#{z.nameString} Indirect Evaporative Cooler") - direct_evap.setName("#{z.nameString} Direct Evaporative Cooler") + indirect_evap.setName("#{z.nameString} Indirect Evaporative Cooler") unless indirect_evap.nil? + direct_evap.setName("#{z.nameString} Direct Evaporative Cooler") unless direct_evap.nil? supplyAirFan.setName("#{z.nameString} Supply Fan") - zoneHVACEvaporativeCoolerUnit.setName("#{z.nameString} Zone Evap Unit") + zoneHVACEvaporativeCoolerUnit.setName("#{z.nameString} Evap Unit") # zoneHVACEvaporativeCoolerUnit.resetSecondEvaporativeCooler # # Redoing what the default constructor does for demonstration purposes @@ -70,7 +76,7 @@ def make_zone_hvac_cooler(z, direct_is_first: false) zoneHVACEvaporativeCoolerUnit.autosizeDesignSupplyAirFlowRate zoneHVACEvaporativeCoolerUnit.setAvailabilitySchedule(model.alwaysOnDiscreteSchedule) zoneHVACEvaporativeCoolerUnit.setSupplyAirFan(zoneHVACEvaporativeCoolerUnit.supplyAirFan) - zoneHVACEvaporativeCoolerUnit.setFanPlacement('BlowThrough') + zoneHVACEvaporativeCoolerUnit.setFanPlacement(fan_placement) zoneHVACEvaporativeCoolerUnit.setCoolerUnitControlMethod('ZoneCoolingLoadVariableSpeedFan') zoneHVACEvaporativeCoolerUnit.setThrottlingRangeTemperatureDifference(1.1) zoneHVACEvaporativeCoolerUnit.setCoolingLoadControlThresholdHeatTransferRate(100.0) @@ -91,15 +97,29 @@ def make_zone_hvac_cooler(z, direct_is_first: false) # we sort the zones by names zones = model.getThermalZones.sort_by { |z| z.name.to_s } -z1 = zones[0] -z1.setName("Direct First Zone") -make_zone_hvac_cooler(z1, direct_is_first: true) +configs = [ + {zone_name: "DirectFirst", direct_is_first: true, add_secondary: true}, + + # Mimic SMStore8 from StripMallZoneEvapCoolerAutosized.idf + # https://github.com/NREL/EnergyPlus/blob/31e3c33467c5873371bf48b12a7318215971c315/testfiles/StripMallZoneEvapCoolerAutosized.idf#L4767-L4784 + {zone_name: "IndirectFirst", direct_is_first: false, add_secondary: true}, + + {zone_name: "DirectOnly", direct_is_first: true, add_secondary: false}, -# Mimic SMStore8 from StripMallZoneEvapCoolerAutosized.idf -# https://github.com/NREL/EnergyPlus/blob/31e3c33467c5873371bf48b12a7318215971c315/testfiles/StripMallZoneEvapCoolerAutosized.idf#L4767-L4784 -z2 = zones[1] -z2.setName("Indirect First Zone") -make_zone_hvac_cooler(z2, direct_is_first: false) + {zone_name: "IndirectOnly", direct_is_first: false, add_secondary: false}, +] +fan_placements = ["BlowThrough", "DrawThrough"] +raise "Mismatch" unless configs.size * fan_placements.size == zones.size + +configs.product(fan_placements).zip(zones).each do |(config, fan_placement), z| + z.setName("#{config[:zone_name]} #{fan_placement} Zn") + make_zone_hvac_cooler( + z, + direct_is_first: config[:direct_is_first], + add_secondary: config[:add_secondary], + fan_placement: fan_placement + ) +end # save the OpenStudio model (.osm) model.save_openstudio_osm({ 'osm_save_directory' => Dir.pwd, From f90ba33ba5489251be2cfd56c3a160be0b4f4870 Mon Sep 17 00:00:00 2001 From: "github-rubocop-actions[bot]" Date: Thu, 16 Jan 2025 15:26:02 +0000 Subject: [PATCH 09/11] Commit rubocop --auto-correct (Ruby ruby 2.7.8p225 (2023-03-30 revision 1f4d455848) [x86_64-linux], Rubocop 0.81.0) --- .../zone_hvac_evaporative_cooler.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.rb b/model/simulationtests/zone_hvac_evaporative_cooler.rb index 63d459e4..898332ac 100644 --- a/model/simulationtests/zone_hvac_evaporative_cooler.rb +++ b/model/simulationtests/zone_hvac_evaporative_cooler.rb @@ -65,8 +65,8 @@ def make_zone_hvac_cooler(z, direct_is_first: false, add_secondary: true, fan_pl zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(direct_evap) end end - indirect_evap.setName("#{z.nameString} Indirect Evaporative Cooler") unless indirect_evap.nil? - direct_evap.setName("#{z.nameString} Direct Evaporative Cooler") unless direct_evap.nil? + indirect_evap&.setName("#{z.nameString} Indirect Evaporative Cooler") + direct_evap&.setName("#{z.nameString} Direct Evaporative Cooler") supplyAirFan.setName("#{z.nameString} Supply Fan") zoneHVACEvaporativeCoolerUnit.setName("#{z.nameString} Evap Unit") # zoneHVACEvaporativeCoolerUnit.resetSecondEvaporativeCooler @@ -92,24 +92,23 @@ def make_zone_hvac_cooler(z, direct_is_first: false, add_secondary: true, fan_pl z.exhaustPortList.modelObjects[0].setName("#{z.nameString} Zone Air Exhaust Node") end - # In order to produce more consistent results between different runs, # we sort the zones by names zones = model.getThermalZones.sort_by { |z| z.name.to_s } configs = [ - {zone_name: "DirectFirst", direct_is_first: true, add_secondary: true}, + { zone_name: 'DirectFirst', direct_is_first: true, add_secondary: true }, # Mimic SMStore8 from StripMallZoneEvapCoolerAutosized.idf # https://github.com/NREL/EnergyPlus/blob/31e3c33467c5873371bf48b12a7318215971c315/testfiles/StripMallZoneEvapCoolerAutosized.idf#L4767-L4784 - {zone_name: "IndirectFirst", direct_is_first: false, add_secondary: true}, + { zone_name: 'IndirectFirst', direct_is_first: false, add_secondary: true }, - {zone_name: "DirectOnly", direct_is_first: true, add_secondary: false}, + { zone_name: 'DirectOnly', direct_is_first: true, add_secondary: false }, - {zone_name: "IndirectOnly", direct_is_first: false, add_secondary: false}, + { zone_name: 'IndirectOnly', direct_is_first: false, add_secondary: false } ] -fan_placements = ["BlowThrough", "DrawThrough"] -raise "Mismatch" unless configs.size * fan_placements.size == zones.size +fan_placements = ['BlowThrough', 'DrawThrough'] +raise 'Mismatch' unless configs.size * fan_placements.size == zones.size configs.product(fan_placements).zip(zones).each do |(config, fan_placement), z| z.setName("#{config[:zone_name]} #{fan_placement} Zn") From 9c626d455129676133d0bccd3da84c121604a491 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Thu, 16 Jan 2025 17:15:34 +0100 Subject: [PATCH 10/11] Make a python version of the test --- .../zone_hvac_evaporative_cooler.py | 118 ++++++++++++++++++ model_tests.rb | 7 +- 2 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 model/simulationtests/zone_hvac_evaporative_cooler.py diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.py b/model/simulationtests/zone_hvac_evaporative_cooler.py new file mode 100644 index 00000000..c992d63c --- /dev/null +++ b/model/simulationtests/zone_hvac_evaporative_cooler.py @@ -0,0 +1,118 @@ +import openstudio + +from lib.baseline_model import BaselineModel +from itertools import product + +model = BaselineModel() + +# make a 8 stories, 100m X 50m, 8 zones building +model.add_geometry(length=100, width=50, num_floors=8, floor_to_floor_height=4, plenum_height=0, perimeter_zone_depth=0) + +# add windows at a 40% window-to-wall ratio +model.add_windows(wwr=0.4, offset=1, application_type="Above Floor") + +# assign constructions from a local library to the walls/windows/etc. in the model +model.set_constructions() + +# set whole building space type; simplified 90.1-2004 Large Office Whole Building +model.set_space_type() + +# add design days to the model (Chicago) +model.add_design_days() + +# add thermostats +model.add_thermostats(heating_setpoint=19, cooling_setpoint=26) + + +def make_zone_hvac_cooler(z: openstudio.model.ThermalZone, direct_is_first: bool = False, add_secondary: bool = True, fan_placement: str = 'BlowThrough'): + model = z.model() + indirect_evap = None + direct_evap = None + if direct_is_first: + # There is a default ctor + zoneHVACEvaporativeCoolerUnit = openstudio.model.ZoneHVACEvaporativeCoolerUnit(model) + direct_evap = zoneHVACEvaporativeCoolerUnit.firstEvaporativeCooler() + supplyAirFan = zoneHVACEvaporativeCoolerUnit.supplyAirFan() + + fan_system = openstudio.model.FanSystemModel(model) + zoneHVACEvaporativeCoolerUnit.setSupplyAirFan(fan_system) + supplyAirFan.remove() + supplyAirFan = fan_system + # An optional Second EvaporativeCooler + if add_secondary: + indirect_evap = openstudio.model.EvaporativeCoolerIndirectResearchSpecial(model) + zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(indirect_evap) + else: + # And an explicit Ctor: + # ZoneHVACEvaporativeCoolerUnit(const Model& model, Schedule& availabilitySchedule, HVACComponent& supplyAirFan, HVACComponent& firstEvaporativeCooler); + supplyAirFan = openstudio.model.FanSystemModel(model) + + indirect_evap = openstudio.model.EvaporativeCoolerIndirectResearchSpecial(model) + zoneHVACEvaporativeCoolerUnit = openstudio.model.ZoneHVACEvaporativeCoolerUnit( + model, model.alwaysOnDiscreteSchedule(), supplyAirFan, indirect_evap + ) + # An optional Second EvaporativeCooler + if add_secondary: + direct_evap = openstudio.model.EvaporativeCoolerDirectResearchSpecial(model, + model.alwaysOnDiscreteSchedule()) + zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(direct_evap) + + if indirect_evap is not None: + indirect_evap.setName(f"{z.nameString()} Indirect Evaporative Cooler") + if direct_evap is not None: + direct_evap.setName(f"{z.nameString()} Direct Evaporative Cooler") + + supplyAirFan.setName(f"{z.nameString()} Supply Fan") + zoneHVACEvaporativeCoolerUnit.setName(f"{z.nameString()} Evap Unit") + # zoneHVACEvaporativeCoolerUnit.resetSecondEvaporativeCooler() + # + # Redoing what the default constructor does for demonstration purposes + # zoneHVACEvaporativeCoolerUnit.setDesignSupplyAirFlowRate(1.0) + zoneHVACEvaporativeCoolerUnit.autosizeDesignSupplyAirFlowRate() + zoneHVACEvaporativeCoolerUnit.setAvailabilitySchedule(model.alwaysOnDiscreteSchedule()) + zoneHVACEvaporativeCoolerUnit.setFanPlacement(fan_placement) + zoneHVACEvaporativeCoolerUnit.setCoolerUnitControlMethod('ZoneCoolingLoadVariableSpeedFan') + zoneHVACEvaporativeCoolerUnit.setThrottlingRangeTemperatureDifference(1.1) + zoneHVACEvaporativeCoolerUnit.setCoolingLoadControlThresholdHeatTransferRate(100.0) + zoneHVACEvaporativeCoolerUnit.setShutOffRelativeHumidity(100.0) + + # Add it to a ThermalZone + zoneHVACEvaporativeCoolerUnit.addToThermalZone(z) + + # Rename nodes for clarity + z.zoneAirNode().setName(f"{z.nameString()} Zone Air Node") + # z.returnAirModelObjects.modelObjects()[0].setName("{z.nameString()} Zone Return Air Node") + z.inletPortList().modelObjects()[0].setName(f"{z.nameString()} Zone Air Inlet Node") + z.exhaustPortList().modelObjects()[0].setName(f"{z.nameString()} Zone Air Exhaust Node") + + return zoneHVACEvaporativeCoolerUnit + +# In order to produce more consistent results between different runs, +# we sort the zones by names +zones = sorted(model.getThermalZones(), key=lambda z: z.nameString()) + +configs = [ + { 'zone_name': 'DirectFirst', 'direct_is_first': True, 'add_secondary': True }, + + # Mimic SMStore8 from StripMallZoneEvapCoolerAutosized.idf + # https://github.com/NREL/EnergyPlus/blob/31e3c33467c5873371bf48b12a7318215971c315/testfiles/StripMallZoneEvapCoolerAutosized.idf#L4767-L4784 + { 'zone_name': 'IndirectFirst', 'direct_is_first': False, 'add_secondary': True }, + + { 'zone_name': 'DirectOnly', 'direct_is_first': True, 'add_secondary': False }, + + { 'zone_name': 'IndirectOnly', 'direct_is_first': False, 'add_secondary': False } +] +fan_placements = ['BlowThrough', 'DrawThrough'] +assert len(configs) * len(fan_placements) == len(zones) + +for (config, fan_placement), z in zip(product(configs, fan_placements), zones): + z.setName(f"{config['zone_name']} {fan_placement} Zn") + make_zone_hvac_cooler( + z=z, + direct_is_first=config['direct_is_first'], + add_secondary=config['add_secondary'], + fan_placement=fan_placement + ) + +# save the OpenStudio model (.osm) +model.save_openstudio_osm(osm_save_directory=None, osm_name="in.osm") diff --git a/model_tests.rb b/model_tests.rb index 71ed2466..9623f739 100644 --- a/model_tests.rb +++ b/model_tests.rb @@ -2151,10 +2151,9 @@ def test_zone_hvac_evaporative_cooler_rb result = sim_test('zone_hvac_evaporative_cooler.rb') end - # TODO: pending being happy with the ruby test - # def test_zone_hvac_evaporative_cooler_py - # result = sim_test('zone_hvac_evaporative_cooler.py') - # end + def test_zone_hvac_evaporative_cooler_py + result = sim_test('zone_hvac_evaporative_cooler.py') + end # TODO: To be added in the next official release after: 3.9.0 # def test_zone_hvac_evaporative_cooler_osm From 0299fafe82ffb8bee292805659d7b5f6042befe0 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Thu, 16 Jan 2025 17:18:07 +0100 Subject: [PATCH 11/11] Lint (isort & black) the new python test --- .../zone_hvac_evaporative_cooler.py | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.py b/model/simulationtests/zone_hvac_evaporative_cooler.py index c992d63c..afd0ff52 100644 --- a/model/simulationtests/zone_hvac_evaporative_cooler.py +++ b/model/simulationtests/zone_hvac_evaporative_cooler.py @@ -1,7 +1,7 @@ -import openstudio +from itertools import product +import openstudio from lib.baseline_model import BaselineModel -from itertools import product model = BaselineModel() @@ -24,7 +24,12 @@ model.add_thermostats(heating_setpoint=19, cooling_setpoint=26) -def make_zone_hvac_cooler(z: openstudio.model.ThermalZone, direct_is_first: bool = False, add_secondary: bool = True, fan_placement: str = 'BlowThrough'): +def make_zone_hvac_cooler( + z: openstudio.model.ThermalZone, + direct_is_first: bool = False, + add_secondary: bool = True, + fan_placement: str = "BlowThrough", +): model = z.model() indirect_evap = None direct_evap = None @@ -49,12 +54,13 @@ def make_zone_hvac_cooler(z: openstudio.model.ThermalZone, direct_is_first: bool indirect_evap = openstudio.model.EvaporativeCoolerIndirectResearchSpecial(model) zoneHVACEvaporativeCoolerUnit = openstudio.model.ZoneHVACEvaporativeCoolerUnit( - model, model.alwaysOnDiscreteSchedule(), supplyAirFan, indirect_evap + model, model.alwaysOnDiscreteSchedule(), supplyAirFan, indirect_evap ) # An optional Second EvaporativeCooler if add_secondary: - direct_evap = openstudio.model.EvaporativeCoolerDirectResearchSpecial(model, - model.alwaysOnDiscreteSchedule()) + direct_evap = openstudio.model.EvaporativeCoolerDirectResearchSpecial( + model, model.alwaysOnDiscreteSchedule() + ) zoneHVACEvaporativeCoolerUnit.setSecondEvaporativeCooler(direct_evap) if indirect_evap is not None: @@ -71,7 +77,7 @@ def make_zone_hvac_cooler(z: openstudio.model.ThermalZone, direct_is_first: bool zoneHVACEvaporativeCoolerUnit.autosizeDesignSupplyAirFlowRate() zoneHVACEvaporativeCoolerUnit.setAvailabilitySchedule(model.alwaysOnDiscreteSchedule()) zoneHVACEvaporativeCoolerUnit.setFanPlacement(fan_placement) - zoneHVACEvaporativeCoolerUnit.setCoolerUnitControlMethod('ZoneCoolingLoadVariableSpeedFan') + zoneHVACEvaporativeCoolerUnit.setCoolerUnitControlMethod("ZoneCoolingLoadVariableSpeedFan") zoneHVACEvaporativeCoolerUnit.setThrottlingRangeTemperatureDifference(1.1) zoneHVACEvaporativeCoolerUnit.setCoolingLoadControlThresholdHeatTransferRate(100.0) zoneHVACEvaporativeCoolerUnit.setShutOffRelativeHumidity(100.0) @@ -87,31 +93,29 @@ def make_zone_hvac_cooler(z: openstudio.model.ThermalZone, direct_is_first: bool return zoneHVACEvaporativeCoolerUnit + # In order to produce more consistent results between different runs, # we sort the zones by names zones = sorted(model.getThermalZones(), key=lambda z: z.nameString()) configs = [ - { 'zone_name': 'DirectFirst', 'direct_is_first': True, 'add_secondary': True }, - - # Mimic SMStore8 from StripMallZoneEvapCoolerAutosized.idf - # https://github.com/NREL/EnergyPlus/blob/31e3c33467c5873371bf48b12a7318215971c315/testfiles/StripMallZoneEvapCoolerAutosized.idf#L4767-L4784 - { 'zone_name': 'IndirectFirst', 'direct_is_first': False, 'add_secondary': True }, - - { 'zone_name': 'DirectOnly', 'direct_is_first': True, 'add_secondary': False }, - - { 'zone_name': 'IndirectOnly', 'direct_is_first': False, 'add_secondary': False } + {"zone_name": "DirectFirst", "direct_is_first": True, "add_secondary": True}, + # Mimic SMStore8 from StripMallZoneEvapCoolerAutosized.idf + # https://github.com/NREL/EnergyPlus/blob/31e3c33467c5873371bf48b12a7318215971c315/testfiles/StripMallZoneEvapCoolerAutosized.idf#L4767-L4784 + {"zone_name": "IndirectFirst", "direct_is_first": False, "add_secondary": True}, + {"zone_name": "DirectOnly", "direct_is_first": True, "add_secondary": False}, + {"zone_name": "IndirectOnly", "direct_is_first": False, "add_secondary": False}, ] -fan_placements = ['BlowThrough', 'DrawThrough'] +fan_placements = ["BlowThrough", "DrawThrough"] assert len(configs) * len(fan_placements) == len(zones) for (config, fan_placement), z in zip(product(configs, fan_placements), zones): z.setName(f"{config['zone_name']} {fan_placement} Zn") make_zone_hvac_cooler( z=z, - direct_is_first=config['direct_is_first'], - add_secondary=config['add_secondary'], - fan_placement=fan_placement + direct_is_first=config["direct_is_first"], + add_secondary=config["add_secondary"], + fan_placement=fan_placement, ) # save the OpenStudio model (.osm)