Class: OpenStudio::Model::ThermalZone

Inherits:
Object
  • Object
show all
Defined in:
lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb,
lib/openstudio-standards/standards/Standards.ThermalZone.rb

Overview

Reopen the OpenStudio class to add methods to apply standards to this object

Instance Method Summary collapse

Instance Method Details

#applySizingValuesObject

Takes the values calculated by the EnergyPlus sizing routines and puts them into this object model in place of the autosized fields. Must have previously completed a run with sql output for this to work.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 13

def applySizingValues

  # In OpenStudio, the design OA flow rates are calculated by the

  # Controller:OutdoorAir object associated with this system.

  # Therefore, this property will be retrieved from that object's sizing values

  air_loop = self.airLoopHVAC
  if air_loop.airLoopHVACOutdoorAirSystem.is_initialized
    controller_oa = air_loop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir
    # get the max oa flow rate from the controller:outdoor air sizing

    maximum_outdoor_air_flow_rate = controller_oa.autosizedMaximumOutdoorAirFlowRate
    if maximum_outdoor_air_flow_rate.is_initialized
      self.setDesignOutdoorAirFlowRate(maximum_outdoor_air_flow_rate.get)
      # Set the OA flow method to "ZoneSum" to avoid severe errors

      # in the fully hard-sized model.

      self.setSystemOutdoorAirMethod("ZoneSum")
    end
  end
  
end

#autosizeObject

Sets all auto-sizeable fields to autosize



6
7
8
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 6

def autosize
  self.autosizeDesignOutdoorAirFlowRate
end

#autosizedCoolingDesignAirFlowRateObject

returns the autosized cooling design air flow rate as an optional double



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
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 48

def autosizedCoolingDesignAirFlowRate

  result = OpenStudio::OptionalDouble.new

  name = self.name.get.upcase

  sql = self.model.sqlFile
  
  if sql.is_initialized
    sql = sql.get
  
    # In E+ 8.4, (OS 1.9.3 onward) the table name changed

    table_name = nil
    if self.model.version < OpenStudio::VersionString.new('1.9.3')
      table_name = 'Zone Cooling'
    else
      table_name = 'Zone Sensible Cooling'
    end  
  
    query = "SELECT Value 
            FROM tabulardatawithstrings
            WHERE ReportName='HVACSizingSummary' 
            AND ReportForString='Entire Facility' 
            AND TableName='#{table_name}'
            AND ColumnName='User Design Air Flow'
            AND RowName='#{name}'
            AND Units='m3/s'"

    val = sql.execAndReturnFirstDouble(query)
    
    if val.is_initialized
      result = OpenStudio::OptionalDouble.new(val.get)
    else
      #OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Model", "Data not found for query: #{query}")

    end

  else
    OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Model has no sql file containing results, cannot lookup data.')
  end

  return result

end

#autosizedHeatingDesignAirFlowRateObject

returns the autosized heating design air flow rate as an optional double



93
94
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
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 93

def autosizedHeatingDesignAirFlowRate

  result = OpenStudio::OptionalDouble.new

  name = self.name.get.upcase

  sql = self.model.sqlFile
  
  if sql.is_initialized
    sql = sql.get
  
    # In E+ 8.4, (OS 1.9.3 onward) the table name changed

    table_name = nil
    if self.model.version < OpenStudio::VersionString.new('1.9.3')
      table_name = 'Zone Heating'
    else
      table_name = 'Zone Sensible Heating'
    end    
  
    query = "SELECT Value 
            FROM tabulardatawithstrings
            WHERE ReportName='HVACSizingSummary' 
            AND ReportForString='Entire Facility' 
            AND TableName='#{table_name}'
            AND ColumnName='User Design Air Flow'
            AND RowName='#{name}'
            AND Units='m3/s'"

    val = sql.execAndReturnFirstDouble(query)
    
    if val.is_initialized
      result = OpenStudio::OptionalDouble.new(val.get)
    else
      #OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Model", "Data not found for query: #{query}")

    end

  else
    OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Model has no sql file containing results, cannot lookup data.')
  end

  return result

end

#autosizedMaximumOutdoorAirFlowRateObject

returns the autosized maximum outdoor air flow rate as an optional double



34
35
36
37
38
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 34

def autosizedMaximumOutdoorAirFlowRate

  return self.model.getAutosizedValue(self, 'Maximum Outdoor Air Flow Rate', 'm3/s')
  
end

#autosizedMinimumOutdoorAirFlowRateObject

returns the autosized minimum outdoor air flow rate as an optional double



41
42
43
44
45
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 41

def autosizedMinimumOutdoorAirFlowRate

  return self.model.getAutosizedValue(self, 'Minimum Outdoor Air Flow Rate', 'm3/s')
  
end

#cooling_fuelsObject

Determine the zone cooling fuels, including any fuels used by zone equipment, reheat terminals, the air loops serving the zone, and any plant loops serving those air loops.

return [Array<String>] An array. Possible values are Electricity, NaturalGas, PropaneGas, FuelOil#1, FuelOil#2, Coal, Diesel, Gasoline, DistrictCooling, DistrictHeating, and SolarEnergy.



259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 259

def cooling_fuels

  fuels = []
  
  # Check the zone hvac cooling fuels

  fuels += self.model.zone_equipment_cooling_fuels(self)

  # Check the zone airloop cooling fuels

  fuels += self.model.zone_airloop_cooling_fuels(self)

  return fuels.uniq.sort
  
end

#coolingDesignLoadObject

returns the calculated cooling design load as an optional double



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
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 138

def coolingDesignLoad

  result = OpenStudio::OptionalDouble.new

  name = self.name.get.upcase

  sql = self.model.sqlFile
  
  if sql.is_initialized
    sql = sql.get
  
    # In E+ 8.4, (OS 1.9.3 onward) the table name changed

    table_name = nil
    if self.model.version < OpenStudio::VersionString.new('1.9.3')
      table_name = 'Zone Cooling'
    else
      table_name = 'Zone Sensible Cooling'
    end    
  
    query = "SELECT Value 
            FROM tabulardatawithstrings
            WHERE ReportName='HVACSizingSummary' 
            AND ReportForString='Entire Facility' 
            AND TableName='#{table_name}'
            AND ColumnName='User Design Load per Area'
            AND RowName='#{name}'
            AND Units='W/m2'"

    val = sql.execAndReturnFirstDouble(query)
    
    if val.is_initialized
      result = OpenStudio::OptionalDouble.new(val.get)
    else
      #OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Model", "Data not found for query: #{query}")

    end

  else
    OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Model has no sql file containing results, cannot lookup data.')
  end

  return result  

end

#get_net_areaDouble

Determine the net area of the zone Loops on each space, and checks if part of total floor area or not If not part of total floor area, it is not added to the zone floor area Will multiply it by the ZONE MULTIPLIER as well!

Returns:

  • (Double)

    the zone net floor area in m^2 (with multiplier taken into account) @units square meters (m^2)



411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 411

def get_net_area
  area_m2 = 0
  zone_mult = self.multiplier
  self.spaces.each do |space|
    # If space is not part of floor area, we don't add it

    next if !space.partofTotalFloorArea
    area_m2 += space.floorArea
  end
  
  return area_m2 * zone_mult
  
end

#get_occupancy_schedule(occupied_percentage_threshold = 0.05) ⇒ ScheduleRuleset

TODO:

Speed up this method. Bottleneck is ScheduleRule.getDaySchedules

This method creates a schedule where the value is zero when the overall occupancy for 1 zone is below the specified threshold, and one when the overall occupancy is greater than or equal to the threshold. This method is designed to use the total number of people in the zone.

Parameters:

  • occupied_percentage_threshold (Double) (defaults to: 0.05)

    the minimum fraction (0 to 1) that counts as occupied

Returns:

  • (ScheduleRuleset)

    a ScheduleRuleset where 0 = unoccupied, 1 = occupied



112
113
114
115
116
117
118
119
120
121
122
123
124
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
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 112

def get_occupancy_schedule(occupied_percentage_threshold = 0.05)

  # Get all the occupancy schedules in every space in the zone

  # Include people added via the SpaceType

  # in addition to people hard-assigned to the Space itself.

  occ_schedules_num_occ = {}
  max_occ_on_thermal_zone = 0
  
  # Get the people objects

  self.spaces.each do |space|
    # From the space type

    if space.spaceType.is_initialized
      space.spaceType.get.people.each do |people|
        num_ppl_sch = people.numberofPeopleSchedule
        if num_ppl_sch.is_initialized
          num_ppl_sch = num_ppl_sch.get
          num_ppl_sch = num_ppl_sch.to_ScheduleRuleset
          next if num_ppl_sch.empty? # Skip non-ruleset schedules

          num_ppl_sch = num_ppl_sch.get
          num_ppl = people.getNumberOfPeople(space.floorArea)
          if occ_schedules_num_occ[num_ppl_sch].nil?
            occ_schedules_num_occ[num_ppl_sch] = num_ppl
            max_occ_on_thermal_zone += num_ppl     
          else
            occ_schedules_num_occ[num_ppl_sch] += num_ppl
            max_occ_on_thermal_zone += num_ppl
          end
        end
      end
    end
    # From the space

    space.people.each do |people|
      num_ppl_sch = people.numberofPeopleSchedule
      if num_ppl_sch.is_initialized
        num_ppl_sch = num_ppl_sch.get
        num_ppl_sch = num_ppl_sch.to_ScheduleRuleset
        next if num_ppl_sch.empty? # Skip non-ruleset schedules

        num_ppl_sch = num_ppl_sch.get
        num_ppl = people.getNumberOfPeople(space.floorArea)
        if occ_schedules_num_occ[num_ppl_sch].nil?
          occ_schedules_num_occ[num_ppl_sch] = num_ppl
          max_occ_on_thermal_zone += num_ppl     
        else
          occ_schedules_num_occ[num_ppl_sch] += num_ppl
          max_occ_on_thermal_zone += num_ppl
        end
      end
    end
  end
  
  # For each day of the year, determine

  #time_value_pairs = []

  year = self.model.getYearDescription
  yearly_data = []
  yearly_times = OpenStudio::DateTimeVector.new
  yearly_values = []
  for i in 1..365

    times_on_this_day = []
    os_date = year.makeDate(i)
    day_of_week = os_date.dayOfWeek.valueName
  
    # Get the unique time indices and corresponding day schedules

    occ_schedules_day_schs = {}
    day_sch_num_occ = {}
    occ_schedules_num_occ.each do |occ_sch, num_occ|
      
      # Get the day schedules for this day

      # (there should only be one)

      day_schs = occ_sch.getDaySchedules(os_date, os_date)
      day_schs[0].times.each do |time|
        times_on_this_day << time.toString
      end
      day_sch_num_occ[day_schs[0]] = num_occ
      
    end

    # Determine the total fraction for the airloop at each time

    daily_times = []
    daily_os_times = []
    daily_values = []
    daily_occs = []
    times_on_this_day.uniq.sort.each do |time|

      os_time = OpenStudio::Time.new(time)
      os_date_time = OpenStudio::DateTime.new(os_date, os_time)
      # Total number of people at each time

      tot_occ_at_time = 0
      day_sch_num_occ.each do |day_sch, num_occ|
        occ_frac = day_sch.getValue(os_time)
        tot_occ_at_time += occ_frac * num_occ
      end
    
      # Total fraction for the airloop at each time

      thermal_zone_occ_frac = tot_occ_at_time / max_occ_on_thermal_zone
      occ_status = 0 # unoccupied

      if thermal_zone_occ_frac >= occupied_percentage_threshold
        occ_status = 1
      end

      # Add this data to the daily arrays

      daily_times << time
      daily_os_times << os_time
      daily_values << occ_status
      daily_occs << thermal_zone_occ_frac.round(2)

    end

   
    # Simplify the daily times to eliminate intermediate

    # points with the same value as the following point.

    simple_daily_times = []
    simple_daily_os_times = []
    simple_daily_values = []
    simple_daily_occs = []
    daily_values.each_with_index do |value, i| 
      next if value == daily_values[i+1]
      simple_daily_times << daily_times[i]
      simple_daily_os_times << daily_os_times[i]
      simple_daily_values << daily_values[i]
      simple_daily_occs << daily_occs[i]
    end
     
    # Store the daily values

    yearly_data << {'date'=>os_date,'day_of_week'=>day_of_week,'times'=>simple_daily_times,'values'=>simple_daily_values,'daily_os_times'=>simple_daily_os_times, 'daily_occs'=>simple_daily_occs}

  end
  
  # Create a TimeSeries from the data

  #time_series = OpenStudio::TimeSeries.new(times, values, 'unitless')


  # Make a schedule ruleset

  sch_name = "#{self.name} Occ Sch"
  sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(self.model)
  sch_ruleset.setName("#{sch_name}")  

  # Default - All Occupied

  day_sch = sch_ruleset.defaultDaySchedule
  day_sch.setName("#{sch_name} Default")
  day_sch.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1)

  # Winter Design Day - All Occupied

  day_sch = OpenStudio::Model::ScheduleDay.new(self.model)
  sch_ruleset.setWinterDesignDaySchedule(day_sch)
  day_sch = sch_ruleset.winterDesignDaySchedule
  day_sch.setName("#{sch_name} Winter Design Day")
  day_sch.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1)     

  # Summer Design Day - All Occupied

  day_sch = OpenStudio::Model::ScheduleDay.new(self.model)
  sch_ruleset.setSummerDesignDaySchedule(day_sch)
  day_sch = sch_ruleset.summerDesignDaySchedule
  day_sch.setName("#{sch_name} Summer Design Day")
  day_sch.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1)
  
  # Create ruleset schedules, attempting to create

  # the minimum number of unique rules.

  ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'].each do |day_of_week|
    end_of_prev_rule = yearly_data[0]['date']
    yearly_data.each_with_index do |daily_data, i|
      # Skip unless it is the day of week

      # currently under inspection

      day = daily_data['day_of_week']
      next unless day == day_of_week
      date = daily_data['date']
      times = daily_data['times']
      values = daily_data['values']
      daily_occs = daily_data['daily_occs']
      
      # If the next (Monday, Tuesday, etc.)

      # is the same as today, keep going.

      # If the next is different, or if

      # we've reached the end of the year,

      # create a new rule

      if !yearly_data[i+7].nil?
        next_day_times = yearly_data[i+7]['times']
        next_day_values = yearly_data[i+7]['values']
        next if times == next_day_times && values == next_day_values
      end
      
      daily_os_times = daily_data['daily_os_times']
      daily_occs = daily_data['daily_occs']
      
      # If here, we need to make a rule to cover from the previous

      # rule to today

 
      sch_rule = OpenStudio::Model::ScheduleRule.new(sch_ruleset)
      sch_rule.setName("#{sch_name} #{day_of_week} Rule")
      day_sch = sch_rule.daySchedule
      day_sch.setName("#{sch_name} #{day_of_week}")
      daily_os_times.each_with_index do |time, i|
        value = values[i]
        next if value == values[i+1] # Don't add breaks if same value

        day_sch.addValue(time, value)
      end
      
      # Set the dates when the rule applies

      sch_rule.setStartDate(end_of_prev_rule)
      sch_rule.setEndDate(date)

      # Individual Days

      sch_rule.setApplyMonday(true) if day_of_week == 'Monday'
      sch_rule.setApplyTuesday(true) if day_of_week == 'Tuesday'
      sch_rule.setApplyWednesday(true) if day_of_week == 'Wednesday'
      sch_rule.setApplyThursday(true) if day_of_week == 'Thursday'
      sch_rule.setApplyFriday(true) if day_of_week == 'Friday'
      sch_rule.setApplySaturday(true) if day_of_week == 'Saturday'
      sch_rule.setApplySunday(true) if day_of_week == 'Sunday'
    
      # Reset the previous rule end date

      end_of_prev_rule = date + OpenStudio::Time.new(0, 24, 0, 0)
    
    end
    
  end

  return sch_ruleset
    
end

#heating_fuelsObject

Determine the zone heating fuels, including any fuels used by zone equipment, reheat terminals, the air loops serving the zone, and any plant loops serving those air loops.

return [Array<String>] An array. Possible values are Electricity, NaturalGas, PropaneGas, FuelOil#1, FuelOil#2, Coal, Diesel, Gasoline, DistrictCooling, DistrictHeating, and SolarEnergy.



236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 236

def heating_fuels

  fuels = []
  
  # Check the zone hvac heating fuels

  fuels += self.model.zone_equipment_heating_fuels(self)

  # Check the zone airloop heating fuels

  fuels += self.model.zone_airloop_heating_fuels(self)

  return fuels.uniq.sort
  
end

#heatingDesignLoadObject

returns the calculated heating design load as an optional double



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
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.ThermalZone.rb', line 183

def heatingDesignLoad

  result = OpenStudio::OptionalDouble.new

  name = self.name.get.upcase

  sql = self.model.sqlFile
  
  if sql.is_initialized
    sql = sql.get
    
    # In E+ 8.4, (OS 1.9.3 onward) the table name changed

    table_name = nil
    if self.model.version < OpenStudio::VersionString.new('1.9.3')
      table_name = 'Zone Heating'
    else
      table_name = 'Zone Sensible Heating'
    end
    
    query = "SELECT Value 
            FROM tabulardatawithstrings
            WHERE ReportName='HVACSizingSummary' 
            AND ReportForString='Entire Facility' 
            AND TableName='#{table_name}'
            AND ColumnName='User Design Load per Area'
            AND RowName='#{name}'
            AND Units='W/m2'"

    val = sql.execAndReturnFirstDouble(query)
    
    if val.is_initialized
      result = OpenStudio::OptionalDouble.new(val.get)
    else
      #OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Model", "Data not found for query: #{query}")

    end

  else
    OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Model has no sql file containing results, cannot lookup data.')
  end

  return result  

end

#infer_system_typeString

Infers the baseline system type based on the equipment serving the zone and their heating/cooling fuels. Only does a high-level inference; does not look for the presence/absence of required controls, etc.

PTHP, PTAC, PSZ_AC, PSZ_HP, PVAV_Reheat, PVAV_PFP_Boxes, VAV_Reheat, VAV_PFP_Boxes, Gas_Furnace, Electric_Furnace

Returns:

  • (String)

    Possible system types are



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 432

def infer_system_type

  # Determine the characteristics

  # of the equipment serving the zone

  has_air_loop = false
  air_loop_num_zones = 0
  air_loop_is_vav = false
  air_loop_has_chw = false
  has_ptac = false
  has_pthp = false
  has_unitheater = false
  self.equipment.each do |equip|
    # Skip HVAC components

    next unless equip.to_HVACComponent.is_initialized
    equip = equip.to_HVACComponent.get
    if equip.airLoopHVAC.is_initialized
      has_air_loop = true
      air_loop = equip.airLoopHVAC.get
      air_loop_num_zones = air_loop.thermalZones.size
      air_loop.supplyComponents.each do |sc|
        if sc.to_FanVariableVolume.is_initialized
          air_loop_is_vav = true
        elsif sc.to_CoilCoolingWater.is_initialized
          air_loop_has_chw = true
        end
      end
    elsif equip.to_ZoneHVACPackagedTerminalAirConditioner.is_initialized
      has_ptac = true
    elsif equip.to_ZoneHVACPackagedTerminalHeatPump.is_initialized
      has_pthp = true
    elsif equip.to_ZoneHVACUnitHeater.is_initialized
      has_unitheater = true
    end
  end

  # Get the zone heating and cooling fuels

  htg_fuels = self.heating_fuels
  clg_fuels = self.cooling_fuels
  is_fossil = self.is_fossil_hybrid_or_purchased_heat

  # Infer the HVAC type

  sys_type = 'Unknown'

  # Single zone

  if air_loop_num_zones < 2
    # Gas

    if is_fossil
      # Air Loop

      if has_air_loop
        # Gas_Furnace (as air loop)

        if cooling_fuels.size == 0
          sys_type = 'Gas_Furnace'
        # PSZ_AC 

        else
          sys_type = 'PSZ_AC'
        end
      # Zone Equipment

      else
        # Gas_Furnace (as unit heater)

        if has_unitheater
          sys_type = 'Gas_Furnace'
        end
        # PTAC

        if has_ptac
          sys_type = 'PTAC'
        end
      end 
    # Electric

    else
      # Air Loop

      if has_air_loop
        # Electric_Furnace (as air loop)

        if cooling_fuels.size == 0
          sys_type = 'Electric_Furnace'
        # PSZ_HP

        else
          sys_type = 'PSZ_HP'
        end
      # Zone Equipment

      else
        # Electric_Furnace (as unit heater)

        if has_unitheater
          sys_type = 'Electric_Furnace'
        end
        # PTHP

        if has_pthp
          sys_type = 'PTHP'
        end
      end
    end
  # Multi-zone

  else    
    # Gas

    if is_fossil
      # VAV_Reheat

      if air_loop_has_chw && air_loop_is_vav
        sys_type = 'VAV_Reheat'
      end
      # PVAV_Reheat

      if !air_loop_has_chw && air_loop_is_vav
        sys_type = 'PVAV_Reheat'
      end
    # Electric

    else
      # VAV_PFP_Boxes

      if air_loop_has_chw && air_loop_is_vav
        sys_type = 'VAV_PFP_Boxes'
      end
      # PVAV_PFP_Boxes

      if !air_loop_has_chw && air_loop_is_vav
        sys_type = 'PVAV_PFP_Boxes'
      end
    end
  end

  # Report out the characteristics for debugging if

  # the system type cannot be inferred.

  if sys_type == 'Unknown'
    OpenStudio::logFree(OpenStudio::Warn, "openstudio.Standards.ThermalZone", "For #{self.name}, the baseline system type could not be inferred.")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "***#{self.name}***")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "system type = #{sys_type}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "has_air_loop = #{has_air_loop}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "air_loop_num_zones = #{air_loop_num_zones}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "air_loop_is_vav = #{air_loop_is_vav}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "air_loop_has_chw = #{air_loop_has_chw}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "has_ptac = #{has_ptac}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "has_pthp = #{has_pthp}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "has_unitheater = #{has_unitheater}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "htg_fuels = #{htg_fuels}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "clg_fuels = #{clg_fuels}")
    OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.ThermalZone", "is_fossil = #{is_fossil}")
  end

  return sys_type

end

#is_fossil_hybrid_or_purchased_heatObject

Determine if the thermal zone is a Fossil Fuel, Fossil/Electric Hybrid, and Purchased Heat zone. If not, it is an Electric or Other Zone. This is as-defined by 90.1 Appendix G.

return [Bool] true if Fossil Fuel, Fossil/Electric Hybrid, and Purchased Heat zone, false if Electric or Other. To-do: It’s not doing it properly right now. If you have a zone with a VRF + a DOAS (via an ATU SingleDUct Uncontrolled) it’ll pick up both natural gas and electricity and classify it as fossil fuel, when I would definitely classify it as electricity



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 375

def is_fossil_hybrid_or_purchased_heat

  is_fossil = false

  # Get an array of the heating fuels

  # used by the zone.  Possible values are

  # Electricity, NaturalGas, PropaneGas, FuelOil#1, FuelOil#2,

  # Coal, Diesel, Gasoline, DistrictHeating, 

  # and SolarEnergy.

  htg_fuels = self.heating_fuels
  
  if htg_fuels.include?('NaturalGas') ||
     htg_fuels.include?('PropaneGas') ||
     htg_fuels.include?('FuelOil#1') ||
     htg_fuels.include?('FuelOil#2') ||
     htg_fuels.include?('Coal') ||
     htg_fuels.include?('Diesel') ||
     htg_fuels.include?('Gasoline') ||
     htg_fuels.include?('DistrictHeating')
     
    is_fossil = true
  end

  #OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Model", "For #{self.name}, heating fuels = #{htg_fuels.join(', ')}; is_fossil_hybrid_or_purchased_heat = #{is_fossil}.")


  return is_fossil

end

#is_residential(standard) ⇒ Object

Determine if the thermal zone is residential based on the space type properties for the spaces in the zone. If there are both residential and nonresidential spaces in the zone, the result will be whichever type has more floor area. In the event that they are equal, it will be assumed nonresidential.

return [Bool] true if residential, false if nonresidential



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 340

def is_residential(standard)

  # Determine the respective areas

  res_area_m2 = 0
  nonres_area_m2 = 0
  self.spaces.each do |space|
    # Ignore space if not part of total area

    next if !space.partofTotalFloorArea
    if space.is_residential(standard)
      res_area_m2 += space.floorArea
    else
      nonres_area_m2 += space.floorArea
    end
  end
    
  # Determine which is larger

  is_res = false
  if res_area_m2 > nonres_area_m2
    is_res = true
  end 

  return is_res

end

#outdoor_airflow_rateDouble

Calculates the zone outdoor airflow requirement (Voz) based on the inputs in the DesignSpecification:OutdoorAir obects in all spaces in the zone.

Returns:

  • (Double)

    the zone outdoor air flow rate @units cubic meters per second (m^3/s)



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 11

def outdoor_airflow_rate

  tot_oa_flow_rate = 0.0

  spaces = self.spaces.sort

  sum_floor_area = 0.0
  sum_number_of_people = 0.0
  sum_volume = 0.0

  # Variables for merging outdoor air

  any_max_oa_method = false
  sum_oa_for_people = 0.0
  sum_oa_for_floor_area = 0.0
  sum_oa_rate = 0.0
  sum_oa_for_volume = 0.0

  # Find common variables for the new space

  spaces.each do |space|

    floor_area = space.floorArea
    sum_floor_area += floor_area

    number_of_people = space.numberOfPeople
    sum_number_of_people += number_of_people

    volume = space.volume
    sum_volume += volume

    dsn_oa = space.designSpecificationOutdoorAir
    next if dsn_oa.empty?
    dsn_oa = dsn_oa.get
    
    # compute outdoor air rates in case we need them

    oa_for_people = number_of_people * dsn_oa.outdoorAirFlowperPerson
    oa_for_floor_area = floorArea * dsn_oa.outdoorAirFlowperFloorArea
    oa_rate = dsn_oa.outdoorAirFlowRate
    oa_for_volume = volume * dsn_oa.outdoorAirFlowAirChangesperHour

    # First check if this space uses the Maximum method and other spaces do not

    if dsn_oa.outdoorAirMethod == 'Maximum'
      sum_oa_rate += [oa_for_people, oa_for_floor_area, oa_rate, oa_for_volume].max
    elsif dsn_oa.outdoorAirMethod == 'Sum'
      sum_oa_for_people += oa_for_people
      sum_oa_for_floor_area += oa_for_floor_area
      sum_oa_rate += oa_rate
      sum_oa_for_volume += oa_for_volume
    end

  end

  tot_oa_flow_rate += sum_oa_for_people
  tot_oa_flow_rate += sum_oa_for_floor_area
  tot_oa_flow_rate += sum_oa_rate
  tot_oa_flow_rate += sum_oa_for_volume
  
  # Convert to cfm

  tot_oa_flow_rate_cfm = OpenStudio.convert(tot_oa_flow_rate,'m^3/s','cfm').get
  
  OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Model", "For #{self.name}, design min OA = #{tot_oa_flow_rate_cfm.round} cfm.")
  
  return tot_oa_flow_rate

end

#outdoor_airflow_rate_per_areaDouble

Calculates the zone outdoor airflow requirement and divides by the zone area.

Returns:

  • (Double)

    the zone outdoor air flow rate per area @units cubic meters per second (m^3/s)



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 81

def outdoor_airflow_rate_per_area()

  tot_oa_flow_rate_per_area = 0.0

  # Find total area of the zone

  sum_floor_area = 0.0
  self.spaces.sort.each do |space|
    sum_floor_area += space.floorArea
  end

  # Get the OA flow rate

  tot_oa_flow_rate = outdoor_airflow_rate
  
  # Calculate the per-area value

  tot_oa_flow_rate_per_area = tot_oa_flow_rate / sum_floor_area

  # OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Model", "For #{self.name}, OA per area = #{tot_oa_flow_rate_per_area.round(8)} m^3/s*m^2.")


  return tot_oa_flow_rate_per_area

end