Class: FanAssistNightVentilation
- Inherits:
-
OpenStudio::Measure::ModelMeasure
- Object
- OpenStudio::Measure::ModelMeasure
- FanAssistNightVentilation
- Defined in:
- lib/measures/fan_assist_night_ventilation/measure.rb
Overview
start the measure
Instance Method Summary collapse
-
#arguments(model) ⇒ Object
define the arguments that the user will input.
-
#description ⇒ Object
human readable description.
- #inspect_airflow_surfaces(zone) ⇒ Object
-
#modeler_description ⇒ Object
human readable description of modeling approach.
-
#name ⇒ Object
human readable name.
-
#run(model, runner, user_arguments) ⇒ Object
define what happens when the measure is run.
Instance Method Details
#arguments(model) ⇒ Object
define the arguments that the user will input
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/measures/fan_assist_night_ventilation/measure.rb', line 33 def arguments(model) args = OpenStudio::Measure::OSArgumentVector.new # add argument for design_flow_rate design_flow_rate = OpenStudio::Measure::OSArgument.makeDoubleArgument('design_flow_rate', true) design_flow_rate.setDisplayName('Exhaust Flow Rate') design_flow_rate.setUnits('cfm') design_flow_rate.setDefaultValue(1000.0) args << design_flow_rate # add argument for design_flow_rate fan_pressure_rise = OpenStudio::Measure::OSArgument.makeDoubleArgument('fan_pressure_rise', true) fan_pressure_rise.setDisplayName('Fan Pressure Rise') fan_pressure_rise.setUnits('Pa') fan_pressure_rise.setDefaultValue(500.0) args << fan_pressure_rise # add argument for design_flow_rate efficiency = OpenStudio::Measure::OSArgument.makeDoubleArgument('efficiency', true) efficiency.setDisplayName('Fan Total Efficiency') efficiency.setDefaultValue(0.65) args << efficiency # populate raw choice argument for schedules schedule_handles = OpenStudio::StringVector.new schedule_display_names = OpenStudio::StringVector.new # putting raw schedules and names into hash schedule_args = model.getSchedules schedule_args_hash = {} schedule_args.each do |schedule_arg| schedule_args_hash[schedule_arg.name.to_s] = schedule_arg end # populate choice argument for ventilation_schedule ventilation_schedule_handles = OpenStudio::StringVector.new ventilation_schedule_display_names = OpenStudio::StringVector.new # looping through sorted hash of schedules to find air velocity schedules schedule_args_hash.sort.map do |key, value| next if value.scheduleTypeLimits.empty? if value.scheduleTypeLimits.get.unitType == 'Dimensionless' ventilation_schedule_handles << value.handle.to_s ventilation_schedule_display_names << key end end # make a choice argument for Air Velocity Schedule Name ventilation_schedule = OpenStudio::Measure::OSArgument.makeChoiceArgument('ventilation_schedule', ventilation_schedule_handles, ventilation_schedule_display_names, true) ventilation_schedule.setDisplayName('Choose a Ventilation Schedule.') args << ventilation_schedule # argument for minimum outdoor temperature min_outdoor_temp = OpenStudio::Measure::OSArgument.makeDoubleArgument('min_outdoor_temp', true) min_outdoor_temp.setDisplayName('Minimum Outdoor Temperature') min_outdoor_temp.setUnits('F') min_outdoor_temp.setDefaultValue(55.0) args << min_outdoor_temp return args end |
#description ⇒ Object
human readable description
19 20 21 |
# File 'lib/measures/fan_assist_night_ventilation/measure.rb', line 19 def description return "This measure is meant to roughly model the impact of fan assisted night ventilation. The user needs to have a ventilation schedule in the model, operable windows where natural ventilation is desired, and air walls or interior operable windows in walls and floors to define the path of air through the building. The user specified flow rate is proportionally split up based on the area of exterior operable windows. The size of interior air walls and windows doesn't matter." end |
#inspect_airflow_surfaces(zone) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/measures/fan_assist_night_ventilation/measure.rb', line 95 def inspect_airflow_surfaces(zone) array = [] # [adjacent_zone,surfaceType] zone.spaces.each do |space| space.surfaces.each do |surface| next if surface.adjacentSurface.is_initialized != true next if !surface.adjacentSurface.get.space.is_initialized next if !surface.adjacentSurface.get.space.get.thermalZone.is_initialized adjacent_zone = surface.adjacentSurface.get.space.get.thermalZone.get if surface.surfaceType == 'RoofCeiling' || surface.surfaceType == 'Wall' if surface.isAirWall array << [adjacent_zone, surface.surfaceType] else surface.subSurfaces.each do |sub_surface| next if sub_surface.adjacentSubSurface.is_initialized != true next if !sub_surface.adjacentSubSurface.get.surface.get.space.is_initialized next if !sub_surface.adjacentSubSurface.get.surface.get.space.get.thermalZone.is_initialized adjacent_zone = sub_surface.adjacentSubSurface.get.surface.get.space.get.thermalZone.get if sub_surface.isAirWall || sub_surface.subSurfaceType == 'OperableWindow' array << [adjacent_zone, surface.surfaceType] end end end end end end return array end |
#modeler_description ⇒ Object
human readable description of modeling approach
24 25 26 27 28 29 30 |
# File 'lib/measures/fan_assist_night_ventilation/measure.rb', line 24 def modeler_description return "It's up to the modeler to choose a flow rate that is approriate for the fenestration and interior openings within the building. Each zone with operable windows will get a zone ventilation object. The measure will first look for a celing opening to find a connection for zone a zone mixing object. If a ceiling isn't found, then it looks for a wall. Don't provide more than one ceiling paths or more than one wall path. The end result is zone ventilation object followed by a path of zone mixing objects. The fan consumption is modeled in the zone ventilation object, but no heat is brought in from the fan. There is no zone ventilation object at the end of the path of zones. In addition to schedule, the zone ventilation is controlled by a minimum outdoor temperature. The measure was developed for use in un-conditioned models. Has not been tested in conjunction with mechanical systems. To address an issue in OpenStudio zones with ZoneVentilation, this measure adds an exhaust fan added as well, but the CFM value for the exhaust fan is set to 0.0" end |
#name ⇒ Object
human readable name
14 15 16 |
# File 'lib/measures/fan_assist_night_ventilation/measure.rb', line 14 def name return 'Fan Assist Night Ventilation' end |
#run(model, runner, user_arguments) ⇒ Object
define what happens when the measure is run
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/measures/fan_assist_night_ventilation/measure.rb', line 125 def run(model, runner, user_arguments) super(model, runner, user_arguments) # use the built-in error checking if !runner.validateUserArguments(arguments(model), user_arguments) return false end # assign the user inputs to variables design_flow_rate = runner.getDoubleArgumentValue('design_flow_rate', user_arguments) design_flow_rate_si = OpenStudio.convert(design_flow_rate, 'cfm', 'm^3/s').get fan_pressure_rise = runner.getDoubleArgumentValue('fan_pressure_rise', user_arguments) efficiency = runner.getDoubleArgumentValue('efficiency', user_arguments) ventilation_schedule = runner.getOptionalWorkspaceObjectChoiceValue('ventilation_schedule', user_arguments, model) min_outdoor_temp = runner.getDoubleArgumentValue('min_outdoor_temp', user_arguments) # TODO: - validate this ventilation_schedule = ventilation_schedule.get.to_Schedule.get # report initial condition of model runner.registerInitialCondition("The building started with #{model.getZoneVentilationDesignFlowRates.size} zone ventilation design flow rate objects and #{model.getZoneMixings.size} zone mixing objects.") # setup hash to hold path objects and exhaust zones path_objects = {} exhaust_zones = {} # make hash of zones and the area of operable exterior windows operable_ext_window_hash = {} bldg_area_counter = 0.0 model.getThermalZones.sort.each do |zone| zone_area_counter = 0.0 zone.spaces.each do |space| space.surfaces.each do |surface| next if surface.surfaceType != 'Wall' surface.subSurfaces.each do |sub_surface| next if sub_surface.outsideBoundaryCondition != 'Outdoors' next if sub_surface.subSurfaceType != 'OperableWindow' zone_area_counter += sub_surface.netArea * sub_surface.multiplier end end end # store airflow paths for future use path_objects[zone] = inspect_airflow_surfaces(zone) # add to operable_ext_window_hash if non-zero area next if zone_area_counter == 0.0 bldg_area_counter += zone_area_counter operable_ext_window_hash[zone] = zone_area_counter end # setup has to store paths flow_paths = {} # return as NA if no exterior operable windows if operable_ext_window_hash.empty? runner.registerAsNotApplicable('No Exterior operable windows were found. The model will not be altered') return true end # Loop through zones in hash and make natural ventilation objects so the sum equals the user specified target operable_ext_window_hash.each do |zone, zone_opp_area| zone_ventilation = OpenStudio::Model::ZoneVentilationDesignFlowRate.new(model) zone_ventilation.setName("PathStart_#{zone.name}") zone_ventilation.addToThermalZone(zone) zone_ventilation.setVentilationType('Exhaust') # switched from Natural to use power. Need to set fan properties. Used exhaust so no heat from fan in stream fraction_flow = design_flow_rate_si * zone_opp_area / bldg_area_counter zone_ventilation.setDesignFlowRate(fraction_flow) zone_design_flow_rate_ip = OpenStudio.convert(zone_ventilation.designFlowRate, 'm^3/s', 'cfm').get # inputs used for fan power zone_ventilation.setFanPressureRise(fan_pressure_rise) zone_ventilation.setFanTotalEfficiency(efficiency) # set schedule from user arg zone_ventilation.setSchedule(ventilation_schedule) # set min outdoor air temperature min_outdoor_temp_si = OpenStudio.convert(min_outdoor_temp, 'F', 'C').get zone_ventilation.setMinimumOutdoorTemperature(min_outdoor_temp_si) # for some reason min indoor temp defaults to 18 vs. -100 zone_ventilation.setMinimumIndoorTemperature(-100.0) zone_ventilation.setDeltaTemperature(-100.0) # set coef values for constant flow zone_ventilation.setConstantTermCoefficient(1.0) zone_ventilation.setTemperatureTermCoefficient(0.0) zone_ventilation.setVelocityTermCoefficient(0.0) zone_ventilation.setVelocitySquaredTermCoefficient(0.0) zone_opp_area_ip = OpenStudio.convert(zone_opp_area, 'm^2', 'ft^2').get zone_opp_area_airflow_speed = zone_design_flow_rate_ip / (zone_opp_area_ip * 60.0) runner.registerInfo("Added natural ventilation to #{zone.name} of #{zone_design_flow_rate_ip.round(2)} (cfm).") runner.registerInfo("#{zone.name} has #{zone_opp_area_ip.round(2)} (ft^2) of operable windows, estimated airflow speed at operable window is #{zone_opp_area_airflow_speed.round(2)} (ft/sec).") # start trace of path adding air mixing objects found_path_end = false flow_paths[zone] = [] current_zone = zone zones_used_for_this_path = [current_zone] until found_path_end == true found_ceiling = false path_objects[current_zone].each do |object| next if zones_used_for_this_path.include? (object[0]) next if object[1].to_s != 'RoofCeiling' next if operable_ext_window_hash.include? (object[0]) if found_ceiling runner.registerWarning("Found more than one possible airflow path for #{current_zone.name}") else flow_paths[zone] << object[0] current_zone = object[0] zones_used_for_this_path << object[0] found_ceiling = true end end if !found_ceiling found_wall = false path_objects[current_zone].each do |object| next if zones_used_for_this_path.include? (object[0]) next if object[1].to_s != 'Wall' next if operable_ext_window_hash.include? (object[0]) if found_wall runner.registerWarning("Found more than one possible airflow path for #{current_zone.name}") else flow_paths[zone] << object[0] current_zone = object[0] zones_used_for_this_path << object[0] found_wall = true end end end if (found_ceiling == false) && (found_wall == false) found_path_end = true end end # add one way air mixing objects along path zones zone_path_string_array = [zone.name] vent_zone = zone source_zone = zone flow_paths[zone].each do |zone| zone_mixing = OpenStudio::Model::ZoneMixing.new(zone) zone_mixing.setName("PathStart_#{vent_zone.name}_#{source_zone.name}") zone_mixing.setSourceZone(source_zone) zone_mixing.setDesignFlowRate(fraction_flow) # set min outdoor temp schedule min_outdoor_sch = OpenStudio::Model::ScheduleConstant.new(model) min_outdoor_sch.setValue(min_outdoor_temp_si) zone_mixing.setMinimumOutdoorTemperatureSchedule(min_outdoor_sch) # set schedule from user arg zone_mixing.setSchedule(ventilation_schedule) # change source zone to what was just target zone zone_path_string_array << zone.name source_zone = zone end runner.registerInfo("Added Zone Mixing Path: #{zone_path_string_array.join(' > ')}") # add to exhaust zones if !flow_paths[zone].empty? if exhaust_zones.include? flow_paths[zone].last exhaust_zones[flow_paths[zone].last] += fraction_flow else exhaust_zones[flow_paths[zone].last] = fraction_flow end else # extra code if there is no path from entry zone if exhaust_zones.include? zone exhaust_zones[zone] += fraction_flow else exhaust_zones[zone] = fraction_flow runner.registerWarning("#{zone.name} doesn't have path to other zones. Exhaust assumed to be with the same zone as air enters.") end end end # report how much air exhausts to each exhaust zone # when I add an exhaust fan to the top floor I want it to use energy but I don't want to move any additional air. # The air is already being brought into the zone by the zone mixing objects exhaust_zones.each do |zone, flow_rate| ip_fraction_flow_rate = OpenStudio.convert(flow_rate, 'm^3/s', 'cfm').get runner.registerInfo("Zone Mixing flow rate into #{zone.name} is #{ip_fraction_flow_rate.round(2)} (cfm). Fan Consumption included with zone ventilation zones.") # check for exterior surface area if zone.exteriorSurfaceArea == 0 runner.registerWarning("Exhaust Zone #{zone.name} doesn't appear to have any exterior exposure. Review the paths to see that this is the expected result.") end # warn if zone multiplier are used non_default_multiplier = [] model.getThermalZones.each do |zone| if zone.multiplier > 1 non_default_multiplier << zone end end if !non_default_multiplier.empty? runner.registerWarning("This measure is not intended to be use when thermal zones have a non 1 multiplier. #{non_default_multiplier.size} zones in this model have multipliers greater than one. Results are likley invalid.") end # report final condition of model runner.registerFinalCondition("The building finished with #{model.getZoneVentilationDesignFlowRates.size} zone ventilation design flow rate objects and #{model.getZoneMixings.size} zone mixing objects.") # adding useful output variables for diagnostics OpenStudio::Model::OutputVariable.new('Zone Mixing Current Density Air Volume Flow Rate', model) OpenStudio::Model::OutputVariable.new('Zone Ventilation Current Density Volume Flow Rate', model) OpenStudio::Model::OutputVariable.new('Zone Ventilation Fan Electric Energy', model) OpenStudio::Model::OutputVariable.new('Zone Outdoor Air Drybulb Temperature', model) return true end end |