Class: OpenStudio::Model::ThermalZone

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

Overview

open the class to add methods to return sizing values

Instance Method Summary collapse

Instance Method Details

#add_unconditioned_thermostatObject



1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 1235

def add_unconditioned_thermostat
  # Heated to 0F (below heated? threshold)
  htg_t_f = 0
  htg_t_c = OpenStudio.convert(htg_t_f, 'F', 'C').get
  htg_stpt_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  htg_stpt_sch.setName('Unconditioned Minimal Heating')
  htg_stpt_sch.defaultDaySchedule.setName('Unconditioned Minimal Heating Default')
  htg_stpt_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), htg_t_c)

  # Cooled to 120F (above cooled? threshold)
  clg_t_f = 120
  clg_t_c = OpenStudio.convert(clg_t_f, 'F', 'C').get
  clg_stpt_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  clg_stpt_sch.setName('Unconditioned Minimal Heating')
  clg_stpt_sch.defaultDaySchedule.setName('Unconditioned Minimal Heating Default')
  clg_stpt_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), clg_t_c)

  # Thermostat
  thermostat = OpenStudio::Model::ThermostatSetpointDualSetpoint.new(model)
  thermostat.setName("#{name} Unconditioned Thermostat")
  thermostat.setHeatingSetpointTemperatureSchedule(htg_stpt_sch)
  thermostat.setCoolingSetpointTemperatureSchedule(clg_stpt_sch)

  return true
end

#apply_prm_baseline_supply_temperaturesBool

Set the design delta-T for zone heating and cooling sizing supply air temperatures. This value determines zone air flows, which will be summed during system design airflow calculation.

Returns:

  • (Bool)

    true if successful, false if not



1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 1211

def apply_prm_baseline_supply_temperatures
  # Skip spaces that aren't heated or cooled
  return true unless heated? || cooled?

  # Heating
  htg_sat_c = prm_baseline_heating_design_supply_temperature
  htg_success = sizingZone.setZoneHeatingDesignSupplyAirTemperature(htg_sat_c)

  # Cooling
  clg_sat_c = prm_baseline_cooling_design_supply_temperature
  clg_success = sizingZone.setZoneCoolingDesignSupplyAirTemperature(clg_sat_c)

  htg_sat_f = OpenStudio.convert(htg_sat_c, 'C', 'F').get
  clg_sat_f = OpenStudio.convert(clg_sat_c, 'C', 'F').get
  OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.ThermalZone', "For #{name}, Htg SAT = #{htg_sat_f.round(1)}F, Clg SAT = #{clg_sat_f.round(1)}F.")

  result = false
  if htg_success && clg_success
    result = true
  end

  return result
end

#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/Siz.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/Siz.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/Siz.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/Siz.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/Siz.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/Siz.ThermalZone.rb', line 41

def autosizedMinimumOutdoorAirFlowRate

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

#conditioning_category(template, climate_zone) ⇒ String

TODO:

add logic to detect indirectly-conditioned spaces

Determines whether the zone is conditioned per 90.1, which is based on heating and cooling loads.

Parameters:

  • climate_zone (String)

    climate zone

Returns:

  • (String)

    NonResConditioned, ResConditioned, Semiheated, Unconditioned



1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 1010

def conditioning_category(template, climate_zone)
  # Get the heating load
  htg_load_btu_per_ft2 = 0.0
  htg_load_w_per_m2 = heatingDesignLoad
  if htg_load_w_per_m2.is_initialized
    htg_load_btu_per_ft2 = OpenStudio.convert(htg_load_w_per_m2.get, 'W/m^2', 'Btu/hr*ft^2').get
  end

  # Get the cooling load
  clg_load_btu_per_ft2 = 0.0
  clg_load_w_per_m2 = coolingDesignLoad
  if clg_load_w_per_m2.is_initialized
    clg_load_btu_per_ft2 = OpenStudio.convert(clg_load_w_per_m2.get, 'W/m^2', 'Btu/hr*ft^2').get
  end

  # Determine the heating limit based on climate zone
  # From Table 3.1 Heated Space Criteria
  htg_lim_btu_per_ft2 = 0.0
  case climate_zone
  when 'ASHRAE 169-2006-1A',
      'ASHRAE 169-2006-1B',
      'ASHRAE 169-2006-2A',
      'ASHRAE 169-2006-2B'
    htg_lim_btu_per_ft2 = 5
  when 'ASHRAE 169-2006-3A',
      'ASHRAE 169-2006-3B',
      'ASHRAE 169-2006-3C'
    htg_lim_btu_per_ft2 = 10
  when 'ASHRAE 169-2006-4A',
      'ASHRAE 169-2006-4B',
      'ASHRAE 169-2006-4C',
      'ASHRAE 169-2006-5A',
      'ASHRAE 169-2006-5B',
      'ASHRAE 169-2006-5C',
    htg_lim_btu_per_ft2 = 15
  when 'ASHRAE 169-2006-6A',
      'ASHRAE 169-2006-6B',
      'ASHRAE 169-2006-7A',
      'ASHRAE 169-2006-7B',
    htg_lim_btu_per_ft2 = 20
  when
      'ASHRAE 169-2006-8A',
      'ASHRAE 169-2006-8B'
    htg_lim_btu_per_ft2 = 25
  end

  # Cooling limit is climate-independent
  clg_lim_btu_per_ft2 = 5

  # Semiheated limit is climate-independent
  semihtd_lim_btu_per_ft2 = 3.4

  # Determine if residential
  res = false
  if residential?(template)
    res = true
  end

  cond_cat = 'Unconditioned'
  if htg_load_btu_per_ft2 > htg_lim_btu_per_ft2
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.ThermalZone', "Zone #{name} is conditioned because heating load of #{htg_load_btu_per_ft2.round} Btu/hr*ft^2 exceeds minimum of #{htg_lim_btu_per_ft2.round} Btu/hr*ft^2.")
    cond_cat = if res
                 'ResConditioned'
               else
                 'NonResConditioned'
               end
  elsif clg_load_btu_per_ft2 > clg_lim_btu_per_ft2
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.ThermalZone', "Zone #{name} is conditioned because cooling load of #{clg_load_btu_per_ft2.round} Btu/hr*ft^2 exceeds minimum of #{clg_lim_btu_per_ft2.round} Btu/hr*ft^2.")
    cond_cat = if res
                 'ResConditioned'
               else
                 'NonResConditioned'
               end
  elsif htg_load_btu_per_ft2 > semihtd_lim_btu_per_ft2
    cond_cat = 'Semiheated'
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.ThermalZone', "Zone #{name} is semiheated because heating load of #{htg_load_btu_per_ft2.round} Btu/hr*ft^2 exceeds minimum of #{semihtd_lim_btu_per_ft2.round} Btu/hr*ft^2.")
  end

  return cond_cat
end

#convert_oa_req_to_per_areaBool

Convert total minimum OA requirement to a per-area value.

Returns:

  • (Bool)

    true if successful, false if not



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

def convert_oa_req_to_per_area

  # For each space in the zone, convert
  # all design OA to per-area
  spaces.each do |space|
    dsn_oa = space.designSpecificationOutdoorAir
    next if dsn_oa.empty?
    dsn_oa = dsn_oa.get

    # Get the space properties
    floor_area = space.floorArea
    number_of_people = space.numberOfPeople
    volume = space.volume

    # Sum up the total OA from all sources
    oa_for_people = number_of_people * dsn_oa.outdoorAirFlowperPerson
    oa_for_floor_area = floor_area * dsn_oa.outdoorAirFlowperFloorArea
    oa_rate = dsn_oa.outdoorAirFlowRate
    oa_for_volume = volume * dsn_oa.outdoorAirFlowAirChangesperHour
    tot_oa = oa_for_people + oa_for_floor_area + oa_rate + oa_for_volume

    # Convert total to per-area
    tot_oa_per_area = tot_oa / floor_area

    # Set the per-area requirement
    dsn_oa.setOutdoorAirFlowperFloorArea(tot_oa_per_area)
    # Zero-out the per-person, ACH, and flow requirements
    dsn_oa.setOutdoorAirFlowperPerson(0.0)
    dsn_oa.setOutdoorAirFlowAirChangesperHour(0.0)
    dsn_oa.setOutdoorAirFlowRate(0.0)

  end

  return true
end

#cooled?Bool

Determines cooling status. If the zone has a thermostat with a minimum cooling setpoint below 33C (91F), counts as cooled. Plenums are also assumed to be cooled.

Returns:

  • (Bool)

    true if cooled, false if not

Author:

  • Andrew Parker, Julien Marrec



852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 852

def cooled?
  temp_f = 91
  temp_c = OpenStudio.convert(temp_f, 'F', 'C').get

  cld = false

  # Consider plenum zones cooled
  area_plenum = 0
  area_non_plenum = 0
  spaces.each do |space|
    if space.plenum?
      area_plenum += space.floorArea
    else
      area_non_plenum += space.floorArea
    end
  end

  # Majority
  if area_plenum > area_non_plenum
    cld = true
    return cld
  end

  # Check if the zone has radiant cooling,
  # and if it does, get cooling setpoint schedule
  # directly from the radiant system to check.
  equipment.each do |equip|
    clg_sch = nil
    if equip.to_ZoneHVACLowTempRadiantConstFlow.is_initialized
      equip = equip.to_ZoneHVACLowTempRadiantConstFlow.get
      clg_coil = equip.heatingCoil
      if clg_coil.to_CoilCoolingLowTempRadiantConstFlow.is_initialized
        clg_coil = clg_coil.to_CoilCoolingLowTempRadiantConstFlow.get
        if clg_coil.coolingLowControlTemperatureSchedule.is_initialized
          clg_sch = clg_coil.coolingLowControlTemperatureSchedule.get
        end
      end
    elsif equip.to_ZoneHVACLowTempRadiantVarFlow.is_initialized
      equip = equip.to_ZoneHVACLowTempRadiantVarFlow.get
      clg_coil = equip.heatingCoil
      if clg_coil.to_CoilCoolingLowTempRadiantVarFlow.is_initialized
        clg_coil = clg_coil.to_CoilCoolingLowTempRadiantVarFlow.get
        if clg_coil.coolingControlTemperatureSchedule.is_initialized
          clg_sch = clg_coil.coolingControlTemperatureSchedule.get
        end
      end    
    end
    # Move on if no cooling schedule was found
    next if clg_sch.nil?
    # Get the setpoint from the schedule
    if clg_sch.to_ScheduleRuleset.is_initialized
      clg_sch = clg_sch.to_ScheduleRuleset.get
      min_c = clg_sch.annual_min_max_value['min']
      if min_c < temp_c
        cld = true
      end
    elsif clg_sch.to_ScheduleConstant.is_initialized
      clg_sch = clg_sch.to_ScheduleConstant.get
      min_c = clg_sch.annual_min_max_value['min']
      if min_c < temp_c
        cld = true
      end
    elsif clg_sch.to_ScheduleCompact.is_initialized
      clg_sch = clg_sch.to_ScheduleCompact.get
      min_c = clg_sch.annual_min_max_value['min']
      if min_c < temp_c
        cld = true
      end
    else
      OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.ThermalZone', "Zone #{name} used an unknown schedule type for the cooling setpoint; assuming cooled.")
      cld = true
    end
  end

  # Unheated if no thermostat present
  if thermostat.empty?
    return cld
  end

  # Check the cooling setpoint
  tstat = thermostat.get
  if tstat.to_ThermostatSetpointDualSetpoint
    tstat = tstat.to_ThermostatSetpointDualSetpoint.get
    clg_sch = tstat.getCoolingSchedule
    if clg_sch.is_initialized
      clg_sch = clg_sch.get
      if clg_sch.to_ScheduleRuleset.is_initialized
        clg_sch = clg_sch.to_ScheduleRuleset.get
        min_c = clg_sch.annual_min_max_value['min']
        if min_c < temp_c
          cld = true
        end
      elsif clg_sch.to_ScheduleConstant.is_initialized
        clg_sch = clg_sch.to_ScheduleConstant.get
        min_c = clg_sch.annual_min_max_value['min']
        if min_c < temp_c
          cld = true
        end
      elsif clg_sch.to_ScheduleCompact.is_initialized
        clg_sch = clg_sch.to_ScheduleCompact.get
        min_c = clg_sch.annual_min_max_value['min']
        if min_c < temp_c
          cld = true
        end
      else
        OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.ThermalZone', "Zone #{name} used an unknown schedule type for the cooling setpoint; assuming cooled.")
        cld = true
      end
    end
  elsif tstat.to_ZoneControlThermostatStagedDualSetpoint
    tstat = tstat.to_ZoneControlThermostatStagedDualSetpoint.get
    clg_sch = tstat.coolingTemperatureSetpointSchedule
    if clg_sch.is_initialized
      clg_sch = clg_sch.get
      if clg_sch.to_ScheduleRuleset.is_initialized
        clg_sch = clg_sch.to_ScheduleRuleset.get
        min_c = clg_sch.annual_min_max_value['min']
        if min_c < temp_c
          cld = true
        end
      end
    end
  end

  return cld
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.



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/openstudio-standards/hvac_sizing/Siz.ThermalZone.rb', line 284

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)

  OpenStudio::logFree(OpenStudio::Debug, 'openstudio.model.Model', "For #{name}, cooling fuels = #{fuels.uniq.sort.join(', ')}.")

  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
181
182
183
# File 'lib/openstudio-standards/hvac_sizing/Siz.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'
            AND RowName='#{name}'
            AND Units='W'"

    val = sql.execAndReturnFirstDouble(query)
    
    if val.is_initialized
      floor_area_no_multiplier_m2 = self.floorArea
      floor_area_m2 = floor_area_no_multiplier_m2 * self.multiplier
      w_per_m2 = val.get/floor_area_m2
      result = OpenStudio::OptionalDouble.new(w_per_m2)
    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

#demand_control_ventilation_required?(template, climate_zone) ⇒ Bool

TODO:

Add exception logic for 90.1-2013 for cells, sickrooms, labs, barbers, salons, and bowling alleys

Determine if demand control ventilation (DCV) is required for this zone based on area and occupant density. Does not account for System requirements like ERV, economizer, etc. Those are accounted for in the AirLoopHVAC method of the same name.

Returns:

  • (Bool)

    Returns true if required, false if not.



1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 1352

def demand_control_ventilation_required?(template, climate_zone)
  dcv_required = false

  # Not required by the old vintages
  if template == 'DOE Ref Pre-1980' || template == 'DOE Ref 1980-2004' || template == 'NECB 2011'
    return dcv_required
  end

  # Area and occupant density limits
  min_area_ft2 = 0
  min_occ_per_1000_ft2 = 0
  case template
  when '90.1-2004'
    min_area_ft2 = 0
    min_occ_per_1000_ft2 = 100
  when '90.1-2007', '90.1-2010'
    min_area_ft2 = 500
    min_occ_per_1000_ft2 = 40
  when '90.1-2013'
    min_area_ft2 = 500
    min_occ_per_1000_ft2 = 25
  end

  # Get the area served and the number of occupants
  area_served_m2 = 0
  num_people = 0
  spaces.each do |space|
    area_served_m2 += space.floorArea
    num_people += space.numberOfPeople
  end

  # Check the minimum area
  area_served_ft2 = OpenStudio.convert(area_served_m2, 'm^2', 'ft^2').get
  if area_served_ft2 < min_area_ft2
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ThermalZone', "For #{name}: DCV is not required since the area is #{area_served_ft2.round} ft2, but the minimum size is #{min_area_ft2.round} ft2.")
    return dcv_required
  end

  # Check the minimum occupancy density
  occ_per_ft2 = num_people / area_served_ft2
  occ_per_1000_ft2 = occ_per_ft2 * 1000

  if occ_per_1000_ft2 < min_occ_per_1000_ft2
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ThermalZone', "For #{name}: DCV is not required since the occupant density is #{occ_per_1000_ft2.round} people/1000 ft2, but the minimum occupant density is #{min_occ_per_1000_ft2.round} people/1000 ft2.")
    return dcv_required
  end

  # If here, DCV is required
  dcv_required = true

  return dcv_required
end

#design_internal_loadDouble

Determine the design internal load (W) for this zone without space multipliers. This include People, Lights, Electric Equipment, and Gas Equipment in all spaces in this zone. It assumes 100% of the wattage is converted to heat, and that the design peak schedule value is 1 (100%).

Returns:

  • (Double)

    the design internal load, in W



1270
1271
1272
1273
1274
1275
1276
1277
1278
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 1270

def design_internal_load
  load_w = 0.0

  spaces.each do |space|
    load_w += space.design_internal_load
  end

  return load_w
end

#floor_area_with_zone_multipliersDouble

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)



550
551
552
553
554
555
556
557
558
559
560
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 550

def floor_area_with_zone_multipliers
  area_m2 = 0
  zone_mult = multiplier
  spaces.each do |space|
    # If space is not part of floor area, we don't add it
    next unless space.partofTotalFloorArea
    area_m2 += space.floorArea
  end

  return area_m2 * zone_mult
end

#fossil_hybrid_or_purchased_heat?Boolean

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

Returns:

  • (Boolean)


392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 392

def 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 = 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(', ')}; fossil_hybrid_or_purchased_heat? = #{is_fossil}.")

  return is_fossil
end

#fossil_or_electric_type(custom) ⇒ String

Determine if the thermal zone’s fuel type category. Options are: fossil, electric, unconditioned If a customization is passed, additional categories may be returned. If ‘Xcel Energy CO EDA’, the type fossilandelectric is added. DistrictHeating is considered a fossil fuel since it is typically created by natural gas boilers.

Returns:

  • (String)

    the fuel type category



429
430
431
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
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 429

def fossil_or_electric_type(custom)
  fossil = false
  electric = false

  # Fossil heating
  htg_fuels = 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')
    fossil = true
  end

  # Electric heating
  if htg_fuels.include?('Electricity')
    electric = true
  end

  # Cooling fuels, for determining
  # unconditioned zones
  clg_fuels = cooling_fuels

  # Categorize
  fuel_type = nil
  if fossil
    # If uses any fossil, counts as fossil even if electric is present too
    fuel_type = 'fossil'
  elsif electric
    fuel_type = 'electric'
  elsif htg_fuels.size.zero? && clg_fuels.size.zero?
    fuel_type = 'unconditioned'
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Standards.Model', "For #{name}, could not determine fuel type, assuming fossil.  Heating fuels = #{htg_fuels.join(', ')}; cooling fuels = #{clg_fuels.join(', ')}.")
    fuel_type = 'fossil'
  end

  # Customization for Xcel.
  # Likely useful for other utility
  # programs where fuel switching is important.
  # This is primarily for systems where Gas is
  # used at the central AHU and electric is
  # used at the terminals/zones.  Examples
  # include zone VRF/PTHP with gas-heated DOAS,
  # and gas VAV with electric reheat
  case custom
  when 'Xcel Energy CO EDA'
    if fossil && electric
      fuel_type = 'fossilandelectric'
    end
  end

  # OpenStudio::logFree(OpenStudio::Info, "openstudio.Standards.Model", "For #{self.name}, fuel type = #{fuel_type}.")

  return fuel_type
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



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
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 144

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
  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
          else
            occ_schedules_num_occ[num_ppl_sch] += num_ppl
          end
          max_occ_on_thermal_zone += num_ppl
        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
        else
          occ_schedules_num_occ[num_ppl_sch] += num_ppl
        end
        max_occ_on_thermal_zone += num_ppl
      end
    end
  end

  # For each day of the year, determine
  # time_value_pairs = []
  year = model.getYearDescription
  yearly_data = []
  yearly_times = OpenStudio::DateTimeVector.new
  yearly_values = []
  (1..365).each do |i|
    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, j|
      next if value == daily_values[j + 1]
      simple_daily_times << daily_times[j]
      simple_daily_os_times << daily_os_times[j]
      simple_daily_values << daily_values[j]
      simple_daily_occs << daily_occs[j]
    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 = "#{name} Occ Sch"
  sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(model)
  sch_ruleset.setName(sch_name.to_s)

  # 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(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(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 |weekday|
    end_of_prev_rule = yearly_data[0]['date']
    yearly_data.each_with_index do |daily_data, k|
      # Skip unless it is the day of week
      # currently under inspection
      day = daily_data['day_of_week']
      next unless day == weekday
      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
      unless yearly_data[k + 7].nil?
        next_day_times = yearly_data[k + 7]['times']
        next_day_values = yearly_data[k + 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} #{weekday} Rule")
      day_sch = sch_rule.daySchedule
      day_sch.setName("#{sch_name} #{weekday}")
      daily_os_times.each_with_index do |time, l|
        value = values[l]
        next if value == values[l + 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 weekday == 'Monday'
      sch_rule.setApplyTuesday(true) if weekday == 'Tuesday'
      sch_rule.setApplyWednesday(true) if weekday == 'Wednesday'
      sch_rule.setApplyThursday(true) if weekday == 'Thursday'
      sch_rule.setApplyFriday(true) if weekday == 'Friday'
      sch_rule.setApplySaturday(true) if weekday == 'Saturday'
      sch_rule.setApplySunday(true) if weekday == '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

#heated?Bool

Determines heating status. If the zone has a thermostat with a maximum heating setpoint above 5C (41F), counts as heated. Plenums are also assumed to be heated.

Returns:

  • (Bool)

    true if heated, false if not

Author:

  • Andrew Parker, Julien Marrec



711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 711

def heated?
  temp_f = 41
  temp_c = OpenStudio.convert(temp_f, 'F', 'C').get

  htd = false

  # Consider plenum zones heated
  area_plenum = 0
  area_non_plenum = 0
  spaces.each do |space|
    if space.plenum?
      area_plenum += space.floorArea
    else
      area_non_plenum += space.floorArea
    end
  end

  # Majority
  if area_plenum > area_non_plenum
    htd = true
    return htd
  end

  # Check if the zone has radiant heating,
  # and if it does, get heating setpoint schedule
  # directly from the radiant system to check.
  equipment.each do |equip|
    htg_sch = nil
    if equip.to_ZoneHVACHighTemperatureRadiant.is_initialized
      equip = equip.to_ZoneHVACHighTemperatureRadiant.get
      if equip.heatingSetpointTemperatureSchedule.is_initialized
        htg_sch = equip.heatingSetpointTemperatureSchedule.get
      end 
    elsif equip.to_ZoneHVACLowTemperatureRadiantElectric.is_initialized
      equip = equip.to_ZoneHVACLowTemperatureRadiantElectric.get
      htg_sch = equip.heatingSetpointTemperatureSchedule.get
    elsif equip.to_ZoneHVACLowTempRadiantConstFlow.is_initialized
      equip = equip.to_ZoneHVACLowTempRadiantConstFlow.get
      htg_coil = equip.heatingCoil
      if htg_coil.to_CoilHeatingLowTempRadiantConstFlow.is_initialized
        htg_coil = htg_coil.to_CoilHeatingLowTempRadiantConstFlow.get
        if htg_coil.heatingHighControlTemperatureSchedule.is_initialized
          htg_sch = htg_coil.heatingHighControlTemperatureSchedule.get
        end
      end
    elsif equip.to_ZoneHVACLowTempRadiantVarFlow.is_initialized
      equip = equip.to_ZoneHVACLowTempRadiantVarFlow.get
      htg_coil = equip.heatingCoil
      if htg_coil.to_CoilHeatingLowTempRadiantVarFlow.is_initialized
        htg_coil = htg_coil.to_CoilHeatingLowTempRadiantVarFlow.get
        if htg_coil.heatingControlTemperatureSchedule.is_initialized
          htg_sch = htg_coil.heatingControlTemperatureSchedule.get
        end
      end    
    end
    # Move on if no heating schedule was found
    next if htg_sch.nil?
    # Get the setpoint from the schedule
    if htg_sch.to_ScheduleRuleset.is_initialized
      htg_sch = htg_sch.to_ScheduleRuleset.get
      max_c = htg_sch.annual_min_max_value['max']
      if max_c > temp_c
        htd = true
      end
    elsif htg_sch.to_ScheduleConstant.is_initialized
      htg_sch = htg_sch.to_ScheduleConstant.get
      max_c = htg_sch.annual_min_max_value['max']
      if max_c > temp_c
        htd = true
      end
    elsif htg_sch.to_ScheduleCompact.is_initialized
      htg_sch = htg_sch.to_ScheduleCompact.get
      max_c = htg_sch.annual_min_max_value['max']
      if max_c > temp_c
        htd = true
      end
    else
      OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.ThermalZone', "Zone #{name} used an unknown schedule type for the heating setpoint; assuming heated.")
      htd = true
    end
  end

  # Unheated if no thermostat present
  if thermostat.empty?
    return htd
  end

  # Check the heating setpoint
  tstat = thermostat.get
  if tstat.to_ThermostatSetpointDualSetpoint
    tstat = tstat.to_ThermostatSetpointDualSetpoint.get
    htg_sch = tstat.getHeatingSchedule
    if htg_sch.is_initialized
      htg_sch = htg_sch.get
      if htg_sch.to_ScheduleRuleset.is_initialized
        htg_sch = htg_sch.to_ScheduleRuleset.get
        max_c = htg_sch.annual_min_max_value['max']
        if max_c > temp_c
          htd = true
        end
      elsif htg_sch.to_ScheduleConstant.is_initialized
        htg_sch = htg_sch.to_ScheduleConstant.get
        max_c = htg_sch.annual_min_max_value['max']
        if max_c > temp_c
          htd = true
        end
      elsif htg_sch.to_ScheduleCompact.is_initialized
        htg_sch = htg_sch.to_ScheduleCompact.get
        max_c = htg_sch.annual_min_max_value['max']
        if max_c > temp_c
          htd = true
        end
      else
        OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.ThermalZone', "Zone #{name} used an unknown schedule type for the heating setpoint; assuming heated.")
        htd = true
      end
    end
  elsif tstat.to_ZoneControlThermostatStagedDualSetpoint
    tstat = tstat.to_ZoneControlThermostatStagedDualSetpoint.get
    htg_sch = tstat.heatingTemperatureSetpointSchedule
    if htg_sch.is_initialized
      htg_sch = htg_sch.get
      if htg_sch.to_ScheduleRuleset.is_initialized
        htg_sch = htg_sch.to_ScheduleRuleset.get
        max_c = htg_sch.annual_min_max_value['max']
        if max_c > temp_c
          htd = true
        end
      end
    end
  end

  return htd
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.



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

def heating_fuels

  fuels = []
  
  # Special logic for models imported from Sefaira.
  # In this case, the fuels are listed as a comment
  # above the Zone object.
  if !self.comment == ''
    m = self.comment.match /! *(.*)/
    if m
      all_fuels = m[1].split(',')
      all_fuels.each do |fuel|
        fuels += fuel.strip
      end
    end
    if fuels.size > 0
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.model.Model', "For #{self.name}, fuel type #{fuels.join(', ')} pulled from Zone comment.")
      fuels.uniq.sort
    end
  end
  
  # 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)

  OpenStudio::logFree(OpenStudio::Debug, 'openstudio.model.Model', "For #{name}, heating fuels = #{fuels.uniq.sort.join(', ')}.")

  return fuels.uniq.sort
  
end

#heatingDesignLoadObject

returns the calculated heating design load as an optional double



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

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'
            AND RowName='#{name}'
            AND Units='W'"

    val = sql.execAndReturnFirstDouble(query)
    
    if val.is_initialized
      floor_area_no_multiplier_m2 = self.floorArea
      floor_area_m2 = floor_area_no_multiplier_m2 * self.multiplier
      w_per_m2 = val.get/floor_area_m2
      result = OpenStudio::OptionalDouble.new(w_per_m2)
    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



570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 570

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
  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 = heating_fuels
  clg_fuels = cooling_fuels
  is_fossil = 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)
        sys_type = if cooling_fuels.size.zero?
                     'Gas_Furnace'
                   # PSZ_AC
                   else
                     '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)
        sys_type = if cooling_fuels.size.zero?
                     'Electric_Furnace'
                   # PSZ_HP
                   else
                     '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 #{name}, the baseline system type could not be inferred.")
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.ThermalZone', "***#{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

#majority_space_typeBoost::Optional<OpenStudio::Model::SpaceType>

Returns the space type that represents a majority of the floor area.

Returns:



1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 1284

def majority_space_type
  space_type_to_area = Hash.new(0.0)

  spaces.each do |space|
    if space.spaceType.is_initialized
      space_type = space.spaceType.get
      space_type_to_area[space_type] += space.floorArea
    end
  end

  # If no space types, return empty optional SpaceType
  if space_type_to_area.size.zero?
    return OpenStudio::Model::OptionalSpaceType.new
  end

  # Sort by area
  biggest_space_type = space_type_to_area.sort_by { |st, area| area }.reverse[0][0]

  return OpenStudio::Model::OptionalSpaceType.new(biggest_space_type)
end

#mixed_heating_fuel?Boolean

Determine if the thermal zone is Fossil/Purchased Heat/Electric Hybrid

return [Bool] true if mixed Fossil/Electric Hybrid, and Purchased Heat zone

Returns:

  • (Boolean)


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

def mixed_heating_fuel?
  is_mixed = 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 = heating_fuels

  # Includes fossil
  fossil = false
  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')

    fossil = true
  end

  # Electric and fossil and district
  if htg_fuels.include?('Electricity') && htg_fuels.include?('DistrictHeating') && fossil
    is_mixed = true
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.Model', "For #{name}, heating mixed electricity, fossil, and district.")
  end

  # Electric and fossil
  if htg_fuels.include?('Electricity') && fossil
    is_mixed = true
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.Model', "For #{name}, heating mixed electricity and fossil.")
  end

  # Electric and district
  if htg_fuels.include?('Electricity') && htg_fuels.include?('DistrictHeating')
    is_mixed = true
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.Model', "For #{name}, heating mixed electricity and district.")
  end

  # Fossil and district
  if fossil && htg_fuels.include?('DistrictHeating')
    is_mixed = true
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.Model', "For #{name}, heating mixed fossil and district.")
  end

  return is_mixed
end

#occupancy_type(template) ⇒ String

TODO:

Add public assembly building types

Determine if the thermal zone’s occupancy type category. Options are: residential, nonresidential 90.1-2013 adds additional Options: publicassembly, retail

Returns:

  • (String)

    the occupancy type category



1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 1313

def occupancy_type(template)
  occ_type = if residential?(template)
               'residential'
             else
               'nonresidential'
             end

  # Based on the space type that
  # represents a majority of the zone.
  if template == '90.1-2013'
    space_type = majority_space_type
    if space_type.is_initialized
      space_type = space_type.get
      bldg_type = space_type.standardsBuildingType
      if bldg_type.is_initialized
        bldg_type = bldg_type.get
        case bldg_type
        when 'Retail', 'StripMall', 'SuperMarket'
          occ_type = 'retail'
          # when 'SomeBuildingType' # TODO add publicassembly building types
          # occ_type = 'publicassembly'
        end
      end
    end
  end

  # OpenStudio::logFree(OpenStudio::Info, "openstudio.Standards.ThermalZone", "For #{self.name}, occupancy type = #{occ_type}.")

  return occ_type
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)



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

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 = floor_area * 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 #{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)



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 76

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
  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

#plenum?Bool

Determine if the thermal zone is a plenum based on whether a majority of the spaces in the zone are plenums or not.

Returns:

  • (Bool)

    true if majority plenum, false if not



983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 983

def plenum?
  plenum_status = false

  area_plenum = 0
  area_non_plenum = 0
  spaces.each do |space|
    if space.plenum?
      area_plenum += space.floorArea
    else
      area_non_plenum += space.floorArea
    end
  end

  # Majority
  if area_plenum > area_non_plenum
    plenum_status = true
  end

  return plenum_status
end

#prm_baseline_cooling_design_supply_temperatureDouble

TODO:

Exception: 17F delta-T for labs

Calculate the cooling supply temperature based on the specified delta-T. Delta-T is calculated based on the highest value found in the cooling setpoint schedule.

Returns:

  • (Double)

    the design heating supply temperature, in C



1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 1154

def prm_baseline_cooling_design_supply_temperature
  setpoint_c = nil

  # Setpoint schedule
  tstat = thermostatSetpointDualSetpoint
  if tstat.is_initialized
    tstat = tstat.get
    setpoint_sch = tstat.coolingSetpointTemperatureSchedule
    if setpoint_sch.is_initialized
      setpoint_sch = setpoint_sch.get
      if setpoint_sch.to_ScheduleRuleset.is_initialized
        setpoint_sch = setpoint_sch.to_ScheduleRuleset.get
        setpoint_c = setpoint_sch.annual_min_max_value['min']
      elsif setpoint_sch.to_ScheduleConstant.is_initialized
        setpoint_sch = setpoint_sch.to_ScheduleConstant.get
        setpoint_c = setpoint_sch.annual_min_max_value['min']
      elsif setpoint_sch.to_ScheduleCompact.is_initialized
        setpoint_sch = setpoint_sch.to_ScheduleCompact.get
        setpoint_c = setpoint_sch.annual_min_max_value['min']
      end
    end
  end

  # If the cooling setpoint could not be determined
  # return the current design cooling temperature
  if setpoint_c.nil?
    setpoint_c = sizingZone.zoneCoolingDesignSupplyAirTemperature
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Standards.ThermalZone', "For #{name}: could not determine min cooling setpoint.  Design cooling SAT will be #{OpenStudio.convert(setpoint_c, 'C', 'F').get.round} F from proposed model.")
    return setpoint_c
  end

  # If the cooling setpoint was set very high so that
  # cooling equipment never comes on
  # return the current design cooling temperature
  if setpoint_c > OpenStudio.convert(91, 'F', 'C').get
    setpoint_f = OpenStudio.convert(setpoint_c, 'C', 'F').get
    new_setpoint_c = sizingZone.zoneCoolingDesignSupplyAirTemperature
    new_setpoint_f = OpenStudio.convert(new_setpoint_c, 'C', 'F').get
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Standards.ThermalZone', "For #{name}: max cooling setpoint in proposed model was #{setpoint_f.round} F.  20 F SAT delta-T from this point is unreasonable. Design cooling SAT will be #{new_setpoint_f.round} F from proposed model.")
    return new_setpoint_c
  end

  # Subtract 20F delta-T
  delta_t_r = 20
  delta_t_k = OpenStudio.convert(delta_t_r, 'R', 'K').get

  sat_c = setpoint_c - delta_t_k # Subtract for cooling

  return sat_c
end

#prm_baseline_heating_design_supply_temperatureDouble

TODO:

Exception: 17F delta-T for labs

Calculate the heating supply temperature based on the specified delta-T. Delta-T is calculated based on the highest value found in the heating setpoint schedule.

Returns:

  • (Double)

    the design heating supply temperature, in C



1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 1097

def prm_baseline_heating_design_supply_temperature
  setpoint_c = nil

  # Setpoint schedule
  tstat = thermostatSetpointDualSetpoint
  if tstat.is_initialized
    tstat = tstat.get
    setpoint_sch = tstat.heatingSetpointTemperatureSchedule
    if setpoint_sch.is_initialized
      setpoint_sch = setpoint_sch.get
      if setpoint_sch.to_ScheduleRuleset.is_initialized
        setpoint_sch = setpoint_sch.to_ScheduleRuleset.get
        setpoint_c = setpoint_sch.annual_min_max_value['max']
      elsif setpoint_sch.to_ScheduleConstant.is_initialized
        setpoint_sch = setpoint_sch.to_ScheduleConstant.get
        setpoint_c = setpoint_sch.annual_min_max_value['max']
      elsif setpoint_sch.to_ScheduleCompact.is_initialized
        setpoint_sch = setpoint_sch.to_ScheduleCompact.get
        setpoint_c = setpoint_sch.annual_min_max_value['max']
      end
    end
  end

  # If the heating setpoint could not be determined
  # return the current design heating temperature
  if setpoint_c.nil?
    setpoint_c = sizingZone.zoneHeatingDesignSupplyAirTemperature
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Standards.ThermalZone', "For #{name}: could not determine max heating setpoint.  Design heating SAT will be #{OpenStudio.convert(setpoint_c, 'C', 'F').get.round} F from proposed model.")
    return setpoint_c
  end

  # If the heating setpoint was set very low so that
  # heating equipment never comes on
  # return the current design heating temperature
  if setpoint_c < OpenStudio.convert(41, 'F', 'C').get
    setpoint_f = OpenStudio.convert(setpoint_c, 'C', 'F').get
    new_setpoint_c = sizingZone.zoneHeatingDesignSupplyAirTemperature
    new_setpoint_f = OpenStudio.convert(new_setpoint_c, 'C', 'F').get
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Standards.ThermalZone', "For #{name}: max heating setpoint in proposed model was #{setpoint_f.round} F.  20 F SAT delta-T from this point is unreasonable. Design heating SAT will be #{new_setpoint_f.round} F from proposed model.")
    return new_setpoint_c
  end

  # Add 20F delta-T
  delta_t_r = 20
  delta_t_k = OpenStudio.convert(delta_t_r, 'R', 'K').get

  sat_c = setpoint_c + delta_t_k # Add for heating

  return sat_c
end

#residential?(template) ⇒ Boolean

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

Returns:

  • (Boolean)


359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/openstudio-standards/standards/Standards.ThermalZone.rb', line 359

def residential?(template)
  # Determine the respective areas
  res_area_m2 = 0
  nonres_area_m2 = 0
  spaces.each do |space|
    # Ignore space if not part of total area
    next unless space.partofTotalFloorArea
    if space.residential?(template)
      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