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 diff --git a/model/simulationtests/zone_hvac_evaporative_cooler.py b/model/simulationtests/zone_hvac_evaporative_cooler.py new file mode 100644 index 00000000..afd0ff52 --- /dev/null +++ b/model/simulationtests/zone_hvac_evaporative_cooler.py @@ -0,0 +1,122 @@ +from itertools import product + +import openstudio +from lib.baseline_model import BaselineModel + +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/simulationtests/zone_hvac_evaporative_cooler.rb b/model/simulationtests/zone_hvac_evaporative_cooler.rb new file mode 100644 index 00000000..898332ac --- /dev/null +++ b/model/simulationtests/zone_hvac_evaporative_cooler.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'openstudio' +require_relative 'lib/baseline_model' + +model = BaselineModel.new + +# make a 8 stories, 100m X 50m, 8 zone 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' => 24, + 'cooling_setpoint' => 28 }) + +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) + 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 + 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); + 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 + 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") + supplyAirFan.setName("#{z.nameString} Supply Fan") + zoneHVACEvaporativeCoolerUnit.setName("#{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.setSupplyAirFan(zoneHVACEvaporativeCoolerUnit.supplyAirFan) + 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("#{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 +zones = model.getThermalZones.sort_by { |z| z.name.to_s } + +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'] +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, + 'osm_name' => 'in.osm' }) diff --git a/model_tests.rb b/model_tests.rb index e9275dd3..9623f739 100644 --- a/model_tests.rb +++ b/model_tests.rb @@ -2147,6 +2147,19 @@ 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 + + # 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') + # end + def test_zone_hvac_equipment_list_rb result = sim_test('zone_hvac_equipment_list.rb') end