Class: NECB2011

Inherits:
Standard show all
Defined in:
lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb,
lib/openstudio-standards/standards/necb/NECB2011/autozone.rb,
lib/openstudio-standards/standards/necb/NECB2011/lighting.rb,
lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb,
lib/openstudio-standards/standards/necb/NECB2011/hvac_system_4.rb,
lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb,
lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb,
lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb,
lib/openstudio-standards/standards/necb/NECB2011/hvac_system_2_and_5.rb,
lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb,
lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb,
lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb,
lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb,
lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb,
lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb

Overview

This class holds methods that apply NECB2011 rules.

Technical References:

Direct Known Subclasses

BTAPPRE1980, NECB2015

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeNECB2011

Returns a new instance of NECB2011.


109
110
111
112
113
114
115
116
117
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 109

def initialize
  super()
  @template = self.class.name
  @standards_data = self.load_standards_database_new()
  self.corrupt_standards_database()
  #puts "loaded these tables..."
  #puts @standards_data.keys.size
  #raise("tables not all loaded in parent #{}") if @standards_data.keys.size < 24
end

Instance Attribute Details

#qaqc_dataObject

Returns the value of attribute qaqc_data


3
4
5
# File 'lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb', line 3

def qaqc_data
  @qaqc_data
end

#space_multiplier_mapObject

Returns the value of attribute space_multiplier_map


9
10
11
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 9

def space_multiplier_map
  @space_multiplier_map
end

#space_type_mapObject

Returns the value of attribute space_type_map


8
9
10
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 8

def space_type_map
  @space_type_map
end

#standards_dataObject

Returns the value of attribute standards_data


7
8
9
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 7

def standards_data
  @standards_data
end

#templateObject (readonly)

Returns the value of attribute template


6
7
8
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 6

def template
  @template
end

Instance Method Details

#add_all_spacetypes_to_model(model) ⇒ Object


3
4
5
6
7
8
9
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb', line 3

def add_all_spacetypes_to_model(model)
  # Get the space Type data from @standards data
  spacetype_data = nil
  if @standards_data['space_types'].is_a?(Hash) == true
    spacetype_data = @standards_data['space_types']['table']
  else
    spacetype_data = @standards_data['space_types']
  end
  spacetype_data.each do |spacedata|
    space_type = OpenStudio::Model::SpaceType.new(model)
    space_type.setStandardsSpaceType(spacedata["space_type"])
    space_type.setStandardsBuildingType(spacedata["building_type"])
    space_type.setName("#{spacedata['building_type']} #{spacedata['space_type']}")
    # Loads
    self.space_type_apply_internal_loads(space_type,
                                         true,
                                         true,
                                         true,
                                         true,
                                         true,
                                         true)

    # Schedules
    self.space_type_apply_internal_load_schedules(space_type,
                                                  true,
                                                  true,
                                                  true,
                                                  true,
                                                  true,
                                                  true,
                                                  true)

  end
end

#add_onespeed_DX_coil(model, always_on) ⇒ Object

Create a new DX cooling coil with NECB curve characteristics


1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 1737

def add_onespeed_DX_coil(model, always_on)

  #clg_cap_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model)
  # clg_cap_f_of_temp = model_add_curve("DXCOOL-NECB2011-REF-CAPFT")
  clg_cap_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model)
  clg_cap_f_of_temp.setCoefficient1Constant(0.867905)
  clg_cap_f_of_temp.setCoefficient2x(0.0142459)
  clg_cap_f_of_temp.setCoefficient3xPOW2(0.000554364)
  clg_cap_f_of_temp.setCoefficient4y(-0.00755748)
  clg_cap_f_of_temp.setCoefficient5yPOW2(3.3048e-05)
  clg_cap_f_of_temp.setCoefficient6xTIMESY(-0.000191808)
  clg_cap_f_of_temp.setMinimumValueofx(13.0)
  clg_cap_f_of_temp.setMaximumValueofx(24.0)
  clg_cap_f_of_temp.setMinimumValueofy(24.0)
  clg_cap_f_of_temp.setMaximumValueofy(46.0)

  #clg_cap_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model)
  clg_cap_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model)
  clg_cap_f_of_flow.setCoefficient1Constant(1.0)
  clg_cap_f_of_flow.setCoefficient2x(0.0)
  clg_cap_f_of_flow.setCoefficient3xPOW2(0.0)
  clg_cap_f_of_flow.setMinimumValueofx(0.0)
  clg_cap_f_of_flow.setMaximumValueofx(1.0)

  # clg_energy_input_ratio_f_of_temp = = model_add_curve(""DXCOOL-NECB2011-REF-COOLEIRFT")
  #clg_energy_input_ratio_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model)
  clg_energy_input_ratio_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model)
  clg_energy_input_ratio_f_of_temp.setCoefficient1Constant(0.116936)
  clg_energy_input_ratio_f_of_temp.setCoefficient2x(0.0284933)
  clg_energy_input_ratio_f_of_temp.setCoefficient3xPOW2(-0.000411156)
  clg_energy_input_ratio_f_of_temp.setCoefficient4y(0.0214108)
  clg_energy_input_ratio_f_of_temp.setCoefficient5yPOW2(0.000161028)
  clg_energy_input_ratio_f_of_temp.setCoefficient6xTIMESY(-0.000679104)
  clg_energy_input_ratio_f_of_temp.setMinimumValueofx(13.0)
  clg_energy_input_ratio_f_of_temp.setMaximumValueofx(24.0)
  clg_energy_input_ratio_f_of_temp.setMinimumValueofy(24.0)
  clg_energy_input_ratio_f_of_temp.setMaximumValueofy(46.0)

  #clg_energy_input_ratio_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model)
  # clg_energy_input_ratio_f_of_flow = = model_add_curve("DXCOOL-NECB2011-REF-CAPFFLOW")
  clg_energy_input_ratio_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model)
  clg_energy_input_ratio_f_of_flow.setCoefficient1Constant(1.0)
  clg_energy_input_ratio_f_of_flow.setCoefficient2x(0.0)
  clg_energy_input_ratio_f_of_flow.setCoefficient3xPOW2(0.0)
  clg_energy_input_ratio_f_of_flow.setMinimumValueofx(0.0)
  clg_energy_input_ratio_f_of_flow.setMaximumValueofx(1.0)

  # NECB curve modified to take into account how PLF is used in E+, and PLF ranges (> 0.7)
  # clg_part_load_ratio = model_add_curve("DXCOOL-NECB2011-REF-COOLPLFFPLR")
  clg_part_load_ratio = OpenStudio::Model::CurveCubic.new(model)
  clg_part_load_ratio.setCoefficient1Constant(0.0277)
  clg_part_load_ratio.setCoefficient2x(4.9151)
  clg_part_load_ratio.setCoefficient3xPOW2(-8.184)
  clg_part_load_ratio.setCoefficient4xPOW3(4.2702)
  clg_part_load_ratio.setMinimumValueofx(0.7)
  clg_part_load_ratio.setMaximumValueofx(1.0)


  return OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model,
                                                         always_on,
                                                         clg_cap_f_of_temp,
                                                         clg_cap_f_of_flow,
                                                         clg_energy_input_ratio_f_of_temp,
                                                         clg_energy_input_ratio_f_of_flow,
                                                         clg_part_load_ratio);
end

#add_ptac_dx_cooling(model, zone, zero_outdoor_air) ⇒ Object


1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 1825

def add_ptac_dx_cooling(model, zone, zero_outdoor_air)
  # Create a PTAC for each zone:
  # PTAC DX Cooling with electric heating coil; electric heating coil is always off

  # TO DO: need to apply this system to space types:
  # (1) data processing area: control room, data centre
  # when cooling capacity <= 20kW and
  # (2) residential/accommodation: murb, hotel/motel guest room
  # when building/space heated only (this as per NECB; apply to
  # all for initial work? CAN-QUEST limitation)

  # TO DO: PTAC characteristics: sizing, fan schedules, temperature setpoints, interaction with MAU
  always_on = model.alwaysOnDiscreteSchedule
  always_off = BTAP::Resources::Schedules::StandardSchedules::ON_OFF.always_off(model)
  htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_off)

  # Set up PTAC DX coil with NECB performance curve characteristics;
  clg_coil = self.add_onespeed_DX_coil(model, always_on)

  # Set up PTAC constant volume supply fan
  fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)
  fan.setPressureRise(640)

  ptac = OpenStudio::Model::ZoneHVACPackagedTerminalAirConditioner.new(model,
                                                                       always_on,
                                                                       fan,
                                                                       htg_coil,
                                                                       clg_coil)
  ptac.setName("#{zone.name} PTAC")
  if zero_outdoor_air
    ptac.setOutdoorAirFlowRateWhenNoCoolingorHeatingisNeeded (1.0e-5)
    ptac.setOutdoorAirFlowRateDuringCoolingOperation(1.0e-5)
    ptac.setOutdoorAirFlowRateDuringHeatingOperation(1.0e-5)
  end
  ptac.addToThermalZone(zone)
end

#add_sys1_unitary_ac_baseboard_heating(model:, zones:, mau_type:, mau_heating_coil_type:, baseboard_type:, hw_loop:, multi_speed: false) ⇒ Object


3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
136
137
138
139
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb', line 3

def add_sys1_unitary_ac_baseboard_heating(model:,
                                          zones:,
                                          mau_type:,
                                          mau_heating_coil_type:,
                                          baseboard_type:,
                                          hw_loop:,
                                          multi_speed: false)

  # Keep all data and assumptions for both systems on the top here for easy reference.
  system_data = Hash.new
  system_data[:name] = 'Sys_1_Make-up air unit'
  system_data[:PreheatDesignTemperature] = 7.0
  system_data[:PreheatDesignHumidityRatio] = 0.008
  system_data[:PrecoolDesignTemperature] = 13.0
  system_data[:PrecoolDesignHumidityRatio] = 0.008
  system_data[:SizingOption] = 'NonCoincident'
  system_data[:CoolingDesignAirFlowMethod] = 'DesignDay'
  system_data[:CoolingDesignAirFlowRate] = 0.0
  system_data[:HeatingDesignAirFlowMethod] = 'DesignDay'
  system_data[:HeatingDesignAirFlowRate] = 0.0
  system_data[:SystemOutdoorAirMethod] = 'ZoneSum'
  system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085
  system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:AllOutdoorAirinCooling] = true
  system_data[:AllOutdoorAirinHeating] = true
  system_data[:TypeofLoadtoSizeOn] = 'VentilationRequirement'
  system_data[:MinimumSystemAirFlowRatio] = 1.0
  #Zone data
  system_data[:system_supply_air_temperature] = 20.0
  system_data[:ZoneCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:ZoneHeatingDesignSupplyAirTemperature] = 43.0 #Examine to see if this is a code or assumption. 13.1 maybe need to check
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3


  # System Type 1: PTAC with no heating (unitary AC)
  # Zone baseboards, electric or hot water depending on argument baseboard_type
  # baseboard_type choices are "Hot Water" or "Electric"
  # PSZ to represent make-up air unit (if present)
  # This measure creates:
  # a PTAC  unit for each zone in the building; DX cooling coil
  # and heating coil that is always off
  # Baseboards ("Hot Water or "Electric") in zones connected to hot water loop
  # MAU is present if argument mau == true, not present if argument mau == false
  # MAU is PSZ; DX cooling
  # MAU heating coil: hot water coil or electric, depending on argument mau_heating_coil_type
  # mau_heating_coil_type choices are "Hot Water", "Electric"
  # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e.
  # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1"

  # Some system parameters are set after system is set up; by applying method 'apply_hvac_efficiency_standard'

  always_on = model.alwaysOnDiscreteSchedule
  always_off = BTAP::Resources::Schedules::StandardSchedules::ON_OFF.always_off(model)


  # Create MAU
  # TO DO: MAU sizing, characteristics (fan operation schedules, temperature setpoints, outdoor air, etc)

  if mau_type == true
    mau_air_loop = common_air_loop(model: model, system_data: system_data)
    mau_fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)
    #MAU Heating type selection.
    if mau_heating_coil_type == 'Electric' # electric coil
      mau_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
    end

    if mau_heating_coil_type == 'Hot Water'
      mau_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on)
      hw_loop.addDemandBranchForComponent(mau_htg_coil)
    end

    # Set up Single Speed DX coil with
    mau_clg_coil = self.add_onespeed_DX_coil(model, always_on)

    # Set up OA system
    oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model)
    oa_controller.autosizeMinimumOutdoorAirFlowRate
    oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller)

    # Add the components to the air loop
    # in order from closest to zone to furthest from zone
    supply_inlet_node = mau_air_loop.supplyInletNode
    mau_fan.addToNode(supply_inlet_node)
    mau_htg_coil.addToNode(supply_inlet_node)
    mau_clg_coil.addToNode(supply_inlet_node)
    oa_system.addToNode(supply_inlet_node)

    # Add a setpoint manager to control the supply air temperature
    sat_sch = OpenStudio::Model::ScheduleRuleset.new(model)
    sat_sch.setName('Makeup-Air Unit Supply Air Temp')
    sat_sch.defaultDaySchedule.setName('Makeup Air Unit Supply Air Temp Default')
    sat_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), system_data[:system_supply_air_temperature])
    setpoint_mgr = OpenStudio::Model::SetpointManagerScheduled.new(model, sat_sch)
    setpoint_mgr.addToNode(mau_air_loop.supplyOutletNode)
  end


  zones.each do |zone|
    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(system_data[:ZoneCoolingDesignSupplyAirTemperature])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(system_data[:ZoneHeatingDesignSupplyAirTemperature])
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])

    # Create a PTAC for each zone:
    # PTAC DX Cooling with electric heating coil; electric heating coil is always off
    # TO DO: need to apply this system to space types:
    # (1) data processing area: control room, data centre
    # when cooling capacity <= 20kW and
    # (2) residential/accommodation: murb, hotel/motel guest room
    # when building/space heated only (this as per NECB; apply to
    # all for initial work? CAN-QUEST limitation)

    # TO DO: PTAC characteristics: sizing, fan schedules, temperature setpoints, interaction with MAU

    # htg_coil_elec = OpenStudio::Model::CoilHeatingElectric.new(model,always_on)
    zero_outdoor_air = true  # flag to set outside air flow to 0.0
    add_ptac_dx_cooling(model, zone, zero_outdoor_air)

    # add zone baseboards
    add_zone_baseboards(baseboard_type: baseboard_type,
                        hw_loop: hw_loop,
                        model: model,
                        zone: zone)

    #  # Create a diffuser and attach the zone/diffuser pair to the MAU air loop, if applicable
    if mau_type == true
      diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
      mau_air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
    end # components for MAU
  end # of zone loop
  return true
end

#add_sys1_unitary_ac_baseboard_heating_multi_speed(model:, zones:, mau:, mau_heating_coil_type:, baseboard_type:, hw_loop:) ⇒ Object

sys1_unitary_ac_baseboard_heating


4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb', line 4

def add_sys1_unitary_ac_baseboard_heating_multi_speed(model:,
                                                      zones:,
                                                      mau:,
                                                      mau_heating_coil_type:,
                                                      baseboard_type:,
                                                      hw_loop:)
  raise("System 1 multi is not working right now. Please do not invoke!!!!")
  # System Type 1: PTAC with no heating (unitary AC)
  # Zone baseboards, electric or hot water depending on argument baseboard_type
  # baseboard_type choices are "Hot Water" or "Electric"
  # PSZ to represent make-up air unit (if present)
  # This measure creates:
  # a PTAC  unit for each zone in the building; DX cooling coil
  # and heating coil that is always off
  # Baseboards ("Hot Water or "Electric") in zones connected to hot water loop
  # MAU is present if argument mau == true, not present if argument mau == false
  # MAU is PSZ; DX cooling
  # MAU heating coil: hot water coil or electric, depending on argument mau_heating_coil_type
  # mau_heating_coil_type choices are "Hot Water", "Electric"
  # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e.
  # "NaturalGas","Electricity","PropaneGas","FuelOil#1","FuelOil#2","Coal","Diesel","Gasoline","OtherFuel1"

  # Some system parameters are set after system is set up; by applying method 'apply_hvac_efficiency_standard'

  always_on = model.alwaysOnDiscreteSchedule

  # define always off schedule for ptac heating coil
  always_off = BTAP::Resources::Schedules::StandardSchedules::ON_OFF.always_off(model)

  # TODO: Heating and cooling temperature set point schedules are set somewhere else
  # TODO: For now fetch the schedules and use them in setting up the heat pump system
  # TODO: Later on these schedules need to be passed on to this method
  htg_temp_sch = nil
  clg_temp_sch = nil
  zones.each do |izone|
    if izone.thermostat.is_initialized
      zone_thermostat = izone.thermostat.get
      if zone_thermostat.to_ThermostatSetpointDualSetpoint.is_initialized
        dual_thermostat = zone_thermostat.to_ThermostatSetpointDualSetpoint.get
        htg_temp_sch = dual_thermostat.heatingSetpointTemperatureSchedule.get
        clg_temp_sch = dual_thermostat.coolingSetpointTemperatureSchedule.get
        break
      end
    end
  end

  # Create MAU
  # TO DO: MAU sizing, characteristics (fan operation schedules, temperature setpoints, outdoor air, etc)

  if mau == true

    staged_thermostat = OpenStudio::Model::ZoneControlThermostatStagedDualSetpoint.new(model)
    staged_thermostat.setHeatingTemperatureSetpointSchedule(htg_temp_sch)
    staged_thermostat.setNumberofHeatingStages(4)
    staged_thermostat.setCoolingTemperatureSetpointBaseSchedule(clg_temp_sch)
    staged_thermostat.setNumberofCoolingStages(4)

    mau_air_loop = OpenStudio::Model::AirLoopHVAC.new(model)

    mau_air_loop.setName('Sys_1_Make-up air unit')

    # When an air_loop is constructed, its constructor creates a sizing:system object
    # the default sizing:system constructor makes a system:sizing object
    # appropriate for a multizone VAV system
    # this systems is a constant volume system with no VAV terminals,
    # and therfore needs different default settings
    air_loop_sizing = mau_air_loop.sizingSystem # TODO units
    air_loop_sizing.setTypeofLoadtoSizeOn('Sensible')
    air_loop_sizing.autosizeDesignOutdoorAirFlowRate
    if model.version < OpenStudio::VersionString.new('2.7.0')
      air_loop_sizing.setMinimumSystemAirFlowRatio(1.0)
    else
      air_loop_sizing.setCentralHeatingMaximumSystemAirFlowRatio(1.0)
    end
    air_loop_sizing.setPreheatDesignTemperature(7.0)
    air_loop_sizing.setPreheatDesignHumidityRatio(0.008)
    air_loop_sizing.setPrecoolDesignTemperature(12.8)
    air_loop_sizing.setPrecoolDesignHumidityRatio(0.008)
    air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(13.0)
    air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(43.0)
    air_loop_sizing.setSizingOption('NonCoincident')
    air_loop_sizing.setAllOutdoorAirinCooling(false)
    air_loop_sizing.setAllOutdoorAirinHeating(false)
    air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(0.0085)
    air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(0.0080)
    air_loop_sizing.setCoolingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setCoolingDesignAirFlowRate(0.0)
    air_loop_sizing.setHeatingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setHeatingDesignAirFlowRate(0.0)
    air_loop_sizing.setSystemOutdoorAirMethod('ZoneSum')

    mau_fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)

    # Multi-stage gas heating coil
    if mau_heating_coil_type == 'Electric' || mau_heating_coil_type == 'Hot Water'

      mau_htg_coil = OpenStudio::Model::CoilHeatingGasMultiStage.new(model)
      mau_htg_stage_1 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      mau_htg_stage_2 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      mau_htg_stage_3 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      mau_htg_stage_4 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)

      if mau_heating_coil_type == 'Electric'

        mau_supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)

      elsif mau_heating_coil_type == 'Hot Water'

        mau_supplemental_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on)
        hw_loop.addDemandBranchForComponent(mau_supplemental_htg_coil)

      end

      mau_htg_stage_1.setNominalCapacity(0.1)
      mau_htg_stage_2.setNominalCapacity(0.2)
      mau_htg_stage_3.setNominalCapacity(0.3)
      mau_htg_stage_4.setNominalCapacity(0.4)

    end

    # Add stages to heating coil
    mau_htg_coil.addStage(mau_htg_stage_1)
    mau_htg_coil.addStage(mau_htg_stage_2)
    mau_htg_coil.addStage(mau_htg_stage_3)
    mau_htg_coil.addStage(mau_htg_stage_4)

    # TODO: other fuel-fired heating coil types? (not available in OpenStudio/E+ - may need to play with efficiency to mimic other fuel types)

    # Set up DX cooling coil
    mau_clg_coil = OpenStudio::Model::CoilCoolingDXMultiSpeed.new(model)
    mau_clg_coil.setFuelType('Electricity')
    mau_clg_stage_1 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_stage_2 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_stage_3 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_stage_4 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_coil.addStage(mau_clg_stage_1)
    mau_clg_coil.addStage(mau_clg_stage_2)
    mau_clg_coil.addStage(mau_clg_stage_3)
    mau_clg_coil.addStage(mau_clg_stage_4)

    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.new(model, mau_fan, mau_htg_coil, mau_clg_coil, mau_supplemental_htg_coil)
    #              air_to_air_heatpump.setName("#{zone.name} ASHP")
    air_to_air_heatpump.setControllingZoneorThermostatLocation(zones[1])
    air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
    air_to_air_heatpump.setNumberofSpeedsforHeating(4)
    air_to_air_heatpump.setNumberofSpeedsforCooling(4)

    # oa_controller
    oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model)
    oa_controller.autosizeMinimumOutdoorAirFlowRate
    # oa_controller.setEconomizerControlType("DifferentialEnthalpy")

    # oa_system
    oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller)

    # Add the components to the air loop
    # in order from closest to zone to furthest from zone
    supply_inlet_node = mau_air_loop.supplyInletNode
    air_to_air_heatpump.addToNode(supply_inlet_node)
    oa_system.addToNode(supply_inlet_node)

  end # Create MAU


  zones.each do |zone|
    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(13.0)
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(43.0)
    sizing_zone.setZoneCoolingSizingFactor(1.1)
    sizing_zone.setZoneHeatingSizingFactor(1.3)

    # Set up PTAC heating coil; apply always off schedule

    # htg_coil_elec = OpenStudio::Model::CoilHeatingElectric.new(model,always_on)
    zero_outdoor_air = true  # flag to set outside air flow to zero
    add_ptac_dx_cooling(model, zone, zero_outdoor_air)

    # add zone baseboards
    add_zone_baseboards(baseboard_type: baseboard_type, hw_loop: hw_loop, model: model, zone: zone)

    #  # Create a diffuser and attach the zone/diffuser pair to the MAU air loop, if applicable
    if mau == true

      diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
      mau_air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)

    end # components for MAU
  end # of zone loop

  return true
end

#add_sys2_FPFC_sys5_TPFC(model:, zones:, chiller_type:, fan_coil_type:, mau_cooling_type:, hw_loop:) ⇒ Object


4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_system_2_and_5.rb', line 4

def add_sys2_FPFC_sys5_TPFC(model:,
                            zones:,
                            chiller_type:,
                            fan_coil_type:,
                            mau_cooling_type:,
                            hw_loop:)

  #System 2 AHU data
  system_data = Hash.new
  system_data[:name] = 'Sys_2_Make-up air unit'
  system_data[:PreheatDesignTemperature] = 7.0
  system_data[:PreheatDesignHumidityRatio] = 0.008
  system_data[:PrecoolDesignTemperature] = 13.0
  system_data[:PrecoolDesignHumidityRatio] = 0.008
  system_data[:SizingOption] = 'NonCoincident'
  system_data[:CoolingDesignAirFlowMethod] = 'DesignDay'
  system_data[:CoolingDesignAirFlowRate] = 0.0
  system_data[:HeatingDesignAirFlowMethod] = 'DesignDay'
  system_data[:HeatingDesignAirFlowRate] = 0.0
  system_data[:SystemOutdoorAirMethod] = 'ZoneSum'
  system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085
  system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080

  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 13.1
  system_data[:AllOutdoorAirinCooling] = false
  system_data[:AllOutdoorAirinHeating] = false
  system_data[:TypeofLoadtoSizeOn] = 'Sensible'
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 13.0
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.1
  system_data[:MinimumSystemAirFlowRatio] = 1.0

  #System 2 Zone data
  system_data[:ZoneCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:ZoneHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3



  # System Type 2: FPFC or System 5: TPFC
  # This measure creates:
  # -a four pipe or a two pipe fan coil unit for each zone in the building;
  # -a make up air-unit to provide ventilation to each zone;
  # -a heating loop, cooling loop and condenser loop to serve four pipe fan coil units
  # Arguments:
  #   boiler_fueltype: "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNO2","Coal","Diesel","Gasoline","OtherFuel1"
  #   chiller_type: "Scroll";"Centrifugal";"Rotary Screw";"Reciprocating"
  #   mua_cooling_type: make-up air unit cooling type "DX";"Hydronic"
  #   fan_coil_type options are "TPFC" or "FPFC"

  # TODO: Add arguments as needed when the sizing routine is finalized. For example we will need to know the
  # required size of the boilers to decide on how many units are needed based on NECB rules.

  always_on = model.alwaysOnDiscreteSchedule

  # schedule for two-pipe fan coil operation. 3 seasons for heating/cooling.
  tpfc_clg_availability_sch, tpfc_htg_availability_sch = create_heating_cooling_on_off_availability_schedule(model)

  # Create a chilled water loop
  chw_loop = OpenStudio::Model::PlantLoop.new(model)
  chiller1, chiller2 = self.setup_chw_loop_with_components(model, chw_loop, chiller_type)

  # Create a condenser Loop
  cw_loop = OpenStudio::Model::PlantLoop.new(model)
  ctower = self.setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2)

  # Set up make-up air unit for ventilation
  # TO DO: Need to investigate characteristics of make-up air unit for NECB reference
  # and define them here

  air_loop = mau_air_loop = common_air_loop(model: model, system_data: system_data)
  air_loop.setName(system_data[:name])



  fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)

  # Assume direct-fired gas heating coil for now; need to add logic
  # to set up hydronic or electric coil depending on proposed?

  htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)

  # Add DX or hydronic cooling coil
  if mau_cooling_type == 'DX'
    clg_coil = self.add_onespeed_DX_coil(model, tpfc_clg_availability_sch)
  elsif mau_cooling_type == 'Hydronic'
    clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, tpfc_clg_availability_sch)
    chw_loop.addDemandBranchForComponent(clg_coil)
  end

  # does MAU have an economizer?
  oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model)
  oa_controller.autosizeMinimumOutdoorAirFlowRate

  # oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model,oa_controller)
  oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller)

  # Add the components to the air loop
  # in order from closest to zone to furthest from zone
  supply_inlet_node = air_loop.supplyInletNode
  fan.addToNode(supply_inlet_node)
  htg_coil.addToNode(supply_inlet_node)
  clg_coil.addToNode(supply_inlet_node)
  oa_system.addToNode(supply_inlet_node)

  # Add a setpoint manager single zone reheat to control the
  # supply air temperature based on the needs of default zone (OpenStudio picks one)
  # TO DO: need to have method to pick appropriate control zone?

  setpoint_mgr_single_zone_reheat = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model)
  setpoint_mgr_single_zone_reheat.setMinimumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMin])
  setpoint_mgr_single_zone_reheat.setMaximumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMax])
  setpoint_mgr_single_zone_reheat.addToNode(air_loop.supplyOutletNode)

  # Set up zonal FC (ZoneHVAC,cooling coil, heating coil, fan) in each zone
  zones.each do |zone|
    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(system_data[:ZoneCoolingDesignSupplyAirTemperature])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(system_data[:ZoneHeatingDesignSupplyAirTemperature])
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])

    # fc supply fan
    fc_fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)

    if fan_coil_type == 'FPFC'
      # heating coil
      fc_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on)

      # cooling coil
      fc_clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, always_on)
    elsif fan_coil_type == 'TPFC'
      # heating coil
      fc_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, tpfc_htg_availability_sch)

      # cooling coil
      fc_clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, tpfc_clg_availability_sch)
    end

    # connect heating coil to hot water loop and cooling coil to chw loop.
    hw_loop.addDemandBranchForComponent(fc_htg_coil)
    chw_loop.addDemandBranchForComponent(fc_clg_coil)

    #add connections to FPFC.
    zone_fc = OpenStudio::Model::ZoneHVACFourPipeFanCoil.new(model, always_on, fc_fan, fc_clg_coil, fc_htg_coil)
    zone_fc.addToThermalZone(zone)

    # Create a diffuser and attach the zone/diffuser pair to the air loop (make-up air unit)
    diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
    air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
  end # zone loop
end

#add_sys3_and_8_zone_equip(air_loop, baseboard_type, hw_loop, model, zone) ⇒ Object


164
165
166
167
168
169
170
171
172
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb', line 164

def add_sys3_and_8_zone_equip(air_loop,
                              baseboard_type,
                              hw_loop, model,
                              zone)
  always_on = model.alwaysOnDiscreteSchedule
  diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
  air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
  add_zone_baseboards(baseboard_type: baseboard_type, hw_loop: hw_loop, model: model, zone: zone)
end

#add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed(model, zones, boiler_fueltype, heating_coil_type, baseboard_type, hw_loop) ⇒ Object


2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb', line 2

def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed(model,
                                                                                      zones,
                                                                                      boiler_fueltype,
                                                                                      heating_coil_type,
                                                                                      baseboard_type,
                                                                                      hw_loop)
  raise("System 3 multi is not working right now. Please do not invoke!!!!!")
  # System Type 3: PSZ-AC
  # This measure creates:
  # -a constant volume packaged single-zone A/C unit
  # for each zone in the building; DX cooling with
  # heating coil: fuel-fired or electric, depending on argument heating_coil_type
  # heating_coil_type choices are "Electric", "Gas", "DX"
  # zone baseboards: hot water or electric, depending on argument baseboard_type
  # baseboard_type choices are "Hot Water" or "Electric"
  # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e.
  # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1"

  always_on = model.alwaysOnDiscreteSchedule

  # TODO: Heating and cooling temperature set point schedules are set somewhere else
  # TODO: For now fetch the schedules and use them in setting up the heat pump system
  # TODO: Later on these schedules need to be passed on to this method
  htg_temp_sch = nil
  clg_temp_sch = nil
  zones.each do |izone|
    if izone.thermostat.is_initialized
      zone_thermostat = izone.thermostat.get
      if zone_thermostat.to_ThermostatSetpointDualSetpoint.is_initialized
        dual_thermostat = zone_thermostat.to_ThermostatSetpointDualSetpoint.get
        htg_temp_sch = dual_thermostat.heatingSetpointTemperatureSchedule.get
        clg_temp_sch = dual_thermostat.coolingSetpointTemperatureSchedule.get
        break
      end
    end
  end

  zones.each do |zone|
    air_loop = OpenStudio::Model::AirLoopHVAC.new(model)

    air_loop.setName("Sys_3_PSZ_#{zone.name}")

    # When an air_loop is constructed, its constructor creates a sizing:system object
    # the default sizing:system constructor makes a system:sizing object
    # appropriate for a multizone VAV system
    # this systems is a constant volume system with no VAV terminals,
    # and therfore needs different default settings
    air_loop_sizing = air_loop.sizingSystem # TODO units
    air_loop_sizing.setTypeofLoadtoSizeOn('Sensible')
    air_loop_sizing.autosizeDesignOutdoorAirFlowRate
    if model.version < OpenStudio::VersionString.new('2.7.0')
      air_loop_sizing.setMinimumSystemAirFlowRatio(1.0)
    else
      air_loop_sizing.setCentralHeatingMaximumSystemAirFlowRatio(1.0)
    end
    air_loop_sizing.setPreheatDesignTemperature(7.0)
    air_loop_sizing.setPreheatDesignHumidityRatio(0.008)
    air_loop_sizing.setPrecoolDesignTemperature(13.0)
    air_loop_sizing.setPrecoolDesignHumidityRatio(0.008)
    air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(13.0)
    air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(43.0)
    air_loop_sizing.setSizingOption('NonCoincident')
    air_loop_sizing.setAllOutdoorAirinCooling(false)
    air_loop_sizing.setAllOutdoorAirinHeating(false)
    air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(0.0085)
    air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(0.0080)
    air_loop_sizing.setCoolingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setCoolingDesignAirFlowRate(0.0)
    air_loop_sizing.setHeatingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setHeatingDesignAirFlowRate(0.0)
    air_loop_sizing.setSystemOutdoorAirMethod('ZoneSum')

    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(13.0)
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(43.0)
    sizing_zone.setZoneCoolingSizingFactor(1.1)
    sizing_zone.setZoneHeatingSizingFactor(1.3)

    fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)

    staged_thermostat = OpenStudio::Model::ZoneControlThermostatStagedDualSetpoint.new(model)
    staged_thermostat.setHeatingTemperatureSetpointSchedule(htg_temp_sch)
    staged_thermostat.setNumberofHeatingStages(4)
    staged_thermostat.setCoolingTemperatureSetpointBaseSchedule(clg_temp_sch)
    staged_thermostat.setNumberofCoolingStages(4)
    zone.setThermostat(staged_thermostat)

    # Multi-stage gas heating coil
    if heating_coil_type == 'Gas' || heating_coil_type == 'Electric'
      htg_coil = OpenStudio::Model::CoilHeatingGasMultiStage.new(model)
      htg_stage_1 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      htg_stage_2 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      htg_stage_3 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      htg_stage_4 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      if heating_coil_type == 'Gas'
        supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
      elsif heating_coil_type == 'Electric'
        supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
        htg_stage_1.setNominalCapacity(0.1)
        htg_stage_2.setNominalCapacity(0.2)
        htg_stage_3.setNominalCapacity(0.3)
        htg_stage_4.setNominalCapacity(0.4)
      end

      # Multi-Stage DX or Electric heating coil
    elsif heating_coil_type == 'DX'
      htg_coil = OpenStudio::Model::CoilHeatingDXMultiSpeed.new(model)
      htg_stage_1 = OpenStudio::Model::CoilHeatingDXMultiSpeedStageData.new(model)
      htg_stage_2 = OpenStudio::Model::CoilHeatingDXMultiSpeedStageData.new(model)
      htg_stage_3 = OpenStudio::Model::CoilHeatingDXMultiSpeedStageData.new(model)
      htg_stage_4 = OpenStudio::Model::CoilHeatingDXMultiSpeedStageData.new(model)
      supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
      sizing_zone.setZoneHeatingSizingFactor(1.3)
      sizing_zone.setZoneCoolingSizingFactor(1.0)
    else
      raise("#{heating_coil_type} is not a valid heating coil type.)")
    end

    # Add stages to heating coil
    htg_coil.addStage(htg_stage_1)
    htg_coil.addStage(htg_stage_2)
    htg_coil.addStage(htg_stage_3)
    htg_coil.addStage(htg_stage_4)

    # TODO: other fuel-fired heating coil types? (not available in OpenStudio/E+ - may need to play with efficiency to mimic other fuel types)

    # Set up DX cooling coil
    clg_coil = OpenStudio::Model::CoilCoolingDXMultiSpeed.new(model)
    clg_coil.setFuelType('Electricity')
    clg_stage_1 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_stage_2 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_stage_3 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_stage_4 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_coil.addStage(clg_stage_1)
    clg_coil.addStage(clg_stage_2)
    clg_coil.addStage(clg_stage_3)
    clg_coil.addStage(clg_stage_4)

    # oa_controller
    oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model)
    oa_controller.autosizeMinimumOutdoorAirFlowRate

    # oa_system
    oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller)

    # Add the components to the air loop
    # in order from closest to zone to furthest from zone
    supply_inlet_node = air_loop.supplyInletNode

    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.new(model, fan, htg_coil, clg_coil, supplemental_htg_coil)
    air_to_air_heatpump.setName("#{zone.name} ASHP")
    air_to_air_heatpump.setControllingZoneorThermostatLocation(zone)
    air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
    air_to_air_heatpump.addToNode(supply_inlet_node)
    air_to_air_heatpump.setNumberofSpeedsforHeating(4)
    air_to_air_heatpump.setNumberofSpeedsforCooling(4)

    oa_system.addToNode(supply_inlet_node)

    # Create a diffuser and attach the zone/diffuser pair to the air loop
    # diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model,always_on)
    diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
    air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)

    add_zone_baseboards(baseboard_type: baseboard_type, hw_loop: hw_loop, model: model, zone: zone)
  end # zone loop

  return true
end

#add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model:, zones:, heating_coil_type:, baseboard_type:, hw_loop:, new_auto_zoner: true) ⇒ Object

Some tests still require a simple way to set up a system without sizing.. so we are keeping the auto_zoner flag for this method.


5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb', line 5

def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model:,
                                                                                       zones:,
                                                                                       heating_coil_type:,
                                                                                       baseboard_type:,
                                                                                       hw_loop:,
                                                                                       new_auto_zoner: true)


  system_data = Hash.new
  system_data[:name] = 'Sys_3_PSZ'
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:AllOutdoorAirinCooling] = false
  system_data[:AllOutdoorAirinHeating] = false
  system_data[:TypeofLoadtoSizeOn] = 'Sensible'
  system_data[:MinimumSystemAirFlowRatio] = 1.0

  system_data[:PreheatDesignTemperature] = 7.0
  system_data[:PreheatDesignHumidityRatio] = 0.008
  system_data[:PrecoolDesignTemperature] = 13.0
  system_data[:PrecoolDesignHumidityRatio] = 0.008
  system_data[:SizingOption] = 'NonCoincident'
  system_data[:CoolingDesignAirFlowMethod] = 'DesignDay'
  system_data[:CoolingDesignAirFlowRate] = 0.0
  system_data[:HeatingDesignAirFlowMethod] = 'DesignDay'
  system_data[:HeatingDesignAirFlowRate] = 0.0
  system_data[:SystemOutdoorAirMethod] = 'ZoneSum'
  system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085
  system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080


  #System 3 Zone data
  system_data[:ZoneCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:ZoneHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.0
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 43.0
  system_data[:ZoneDXCoolingSizingFactor] = 1.0
  system_data[:ZoneDXHeatingSizingFactor] = 1.3
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3
  system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation] = -10.0


  if new_auto_zoner == true
    # Create system airloop

    # Add Air Loop
    air_loop = add_system_3_and_8_airloop(heating_coil_type,
                                          model,
                                          system_data,
                                          determine_control_zone(zones))
    # Add Zone equipment
    zones.each do |zone| # Zone sizing temperature
      sizing_zone = zone.sizingZone
      sizing_zone.setZoneCoolingDesignSupplyAirTemperature(system_data[:ZoneCoolingDesignSupplyAirTemperature])
      sizing_zone.setZoneHeatingDesignSupplyAirTemperature(system_data[:ZoneHeatingDesignSupplyAirTemperature])
      sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
      sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])
      add_sys3_and_8_zone_equip(air_loop,
                                baseboard_type,
                                hw_loop,
                                model,
                                zone)

    end
    return true
  else
    zones.each do |zone|
      air_loop = add_system_3_and_8_airloop(heating_coil_type, model, system_data, zone)
      add_sys3_and_8_zone_equip(air_loop,
                                baseboard_type,
                                hw_loop,
                                model,
                                zone)
    end
    return true
  end
end

#add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model:, zones:, heating_coil_type:, baseboard_type:, hw_loop:) ⇒ Object


2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_system_4.rb', line 2

def add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model:,
                                                                 zones:,
                                                                 heating_coil_type:,
                                                                 baseboard_type:,
                                                                 hw_loop:)
  system_data = Hash.new
  system_data[:name] = 'Sys_4_PSZ'
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:AllOutdoorAirinCooling] = false
  system_data[:AllOutdoorAirinHeating] = false
  system_data[:TypeofLoadtoSizeOn] = 'Sensible'
  system_data[:MinimumSystemAirFlowRatio] = 1.0

  system_data[:PreheatDesignTemperature] = 7.0
  system_data[:PreheatDesignHumidityRatio] = 0.008
  system_data[:PrecoolDesignTemperature] = 13.0
  system_data[:PrecoolDesignHumidityRatio] = 0.008
  system_data[:SizingOption] = 'NonCoincident'
  system_data[:CoolingDesignAirFlowMethod] = 'DesignDay'
  system_data[:CoolingDesignAirFlowRate] = 0.0
  system_data[:HeatingDesignAirFlowMethod] = 'DesignDay'
  system_data[:HeatingDesignAirFlowRate] = 0.0
  system_data[:SystemOutdoorAirMethod] = 'ZoneSum'
  system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085
  system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080

  #zone
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 43.0
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.0
  system_data[:ZoneCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:ZoneHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3


  # System Type 4: PSZ-AC
  # This measure creates:
  # -a constant volume packaged single-zone A/C unit
  # for each zone in the building; DX cooling with
  # heating coil: fuel-fired or electric, depending on argument heating_coil_type
  # heating_coil_type choices are "Electric", "Gas"
  # zone baseboards: hot water or electric, depending on argument baseboard_type
  # baseboard_type choices are "Hot Water" or "Electric"
  # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e.
  # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOil#2","Coal","Diesel","Gasoline","OtherFuel1"
  # NOTE: This is the same as system type 3 (single zone make-up air unit and single zone rooftop unit are both PSZ systems)
  # SHOULD WE COMBINE sys3 and sys4 into one script?
  #
  #control_zone = determine_control_zone(zones)
  # Todo change this when control zone method is working.
  control_zone = zones.first

  always_on = model.alwaysOnDiscreteSchedule

  # Create a PSZ for each zone
  # TO DO: need to apply this system to space types:
  # (1) automotive area: repair/parking garage, fire engine room, indoor truck bay
  # (2) supermarket/food service: food preparation with kitchen hood/vented appliance
  # (3) warehouse area (non-refrigerated spaces)


    air_loop = common_air_loop( model: model, system_data: system_data)
    air_loop.setName("#{system_data[:name]}_#{control_zone.name}")

    # Zone sizing temperature
    sizing_zone = control_zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(system_data[:ZoneCoolingDesignSupplyAirTemperature])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(system_data[:ZoneHeatingDesignSupplyAirTemperature])
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])

    fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)

    if heating_coil_type == 'Electric' # electric coil
      htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
    end

    if heating_coil_type == 'Gas'
      htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
    end

    # TO DO: other fuel-fired heating coil types? (not available in OpenStudio/E+ - may need to play with efficiency to mimic other fuel types)

    # Set up DX coil with NECB performance curve characteristics;

    clg_coil = self.add_onespeed_DX_coil(model, always_on)

    # oa_controller
    oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model)
    oa_controller.autosizeMinimumOutdoorAirFlowRate

    # oa_system
    oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller)

    # Add the components to the air loop
    # in order from closest to zone to furthest from zone
    supply_inlet_node = air_loop.supplyInletNode
    fan.addToNode(supply_inlet_node)
    htg_coil.addToNode(supply_inlet_node)
    clg_coil.addToNode(supply_inlet_node)
    oa_system.addToNode(supply_inlet_node)

    # Add a setpoint manager single zone reheat to control the
    # supply air temperature based on the needs of this zone
    setpoint_mgr_single_zone_reheat = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model)
    setpoint_mgr_single_zone_reheat.setControlZone(control_zone)
    setpoint_mgr_single_zone_reheat.setMinimumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMin])
    setpoint_mgr_single_zone_reheat.setMaximumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMax])
    setpoint_mgr_single_zone_reheat.addToNode(air_loop.supplyOutletNode)


    # Create sensible heat exchanger
    #              heat_exchanger = BTAP::Resources::HVAC::Plant::add_hrv(model)
    #              heat_exchanger.setSensibleEffectivenessat100HeatingAirFlow(0.5)
    #              heat_exchanger.setSensibleEffectivenessat75HeatingAirFlow(0.5)
    #              heat_exchanger.setSensibleEffectivenessat100CoolingAirFlow(0.5)
    #              heat_exchanger.setSensibleEffectivenessat75CoolingAirFlow(0.5)
    #              heat_exchanger.setLatentEffectivenessat100HeatingAirFlow(0.0)
    #              heat_exchanger.setLatentEffectivenessat75HeatingAirFlow(0.0)
    #              heat_exchanger.setLatentEffectivenessat100CoolingAirFlow(0.0)
    #              heat_exchanger.setLatentEffectivenessat75CoolingAirFlow(0.0)
    #              heat_exchanger.setSupplyAirOutletTemperatureControl(false)
    #
    #              Connect heat exchanger
    #              oa_node = oa_system.outboardOANode
    #              heat_exchanger.addToNode(oa_node.get)
  zones.each do |zone|
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(system_data[:ZoneCoolingDesignSupplyAirTemperature])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(system_data[:ZoneHeatingDesignSupplyAirTemperature])
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])
    # Create a diffuser and attach the zone/diffuser pair to the air loop
    # diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model,always_on)
    diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
    air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
    add_zone_baseboards(baseboard_type: baseboard_type,
                        hw_loop: hw_loop,
                        model: model,
                        zone: zone)
  end # zone loop

  return true
end

#add_sys6_multi_zone_built_up_system_with_baseboard_heating(model:, zones:, heating_coil_type:, baseboard_type:, chiller_type:, fan_type:, hw_loop:) ⇒ Object

end add_sys4_single_zone_make_up_air_unit_with_baseboard_heating


5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
136
137
138
139
140
141
142
143
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb', line 5

def add_sys6_multi_zone_built_up_system_with_baseboard_heating(model:,
                                                               zones:,
                                                               heating_coil_type:,
                                                               baseboard_type:,
                                                               chiller_type:,
                                                               fan_type:,
                                                               hw_loop:)
  # System Type 6: VAV w/ Reheat
  # This measure creates:
  # a single hot water loop with a natural gas or electric boiler or for the building
  # a single chilled water loop with water cooled chiller for the building
  # a single condenser water loop for heat rejection from the chiller
  # a VAV system w/ hot water or electric heating, chilled water cooling, and
  # hot water or electric reheat for each story of the building
  # Arguments:
  # "boiler_fueltype" choices match OS choices for boiler fuel type:
  # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOil#2","Coal","Diesel","Gasoline","OtherFuel1"
  # "heating_coil_type": "Electric" or "Hot Water"
  # "baseboard_type": "Electric" and "Hot Water"
  # "chiller_type": "Scroll";"Centrifugal";""Screw";"Reciprocating"
  # "fan_type": "AF_or_BI_rdg_fancurve";"AF_or_BI_inletvanes";"fc_inletvanes";"var_speed_drive"
  #
  system_data = Hash.new
  system_data[:name] = 'Sys_6_VAV with Reheat'
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 13.1
  system_data[:AllOutdoorAirinCooling] = false
  system_data[:AllOutdoorAirinHeating] = false
  system_data[:MinimumSystemAirFlowRatio] = 0.3

  
  #zone data
  system_data[:system_supply_air_temperature] = 13.0
  system_data[:ZoneCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:ZoneHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3
  system_data[:ZoneVAVMinFlowFactorPerFloorArea] = 0.002
  system_data[:ZoneVAVMaxReheatTemp] = 43.0
  system_data[:ZoneVAVDamperAction] = 'Normal'

  always_on = model.alwaysOnDiscreteSchedule

  # Chilled Water Plant

  chw_loop = OpenStudio::Model::PlantLoop.new(model)
  chiller1, chiller2 = self.setup_chw_loop_with_components(model, chw_loop, chiller_type)

  # Condenser System

  cw_loop = OpenStudio::Model::PlantLoop.new(model)
  ctower = self.setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2)

  # Make a Packaged VAV w/ PFP Boxes for each story of the building
  model.getBuildingStorys.sort.each do |story|
    unless (BTAP::Geometry::BuildingStoreys.get_zones_from_storey(story) & zones).empty?

      air_loop = common_air_loop( model: model, system_data: system_data)
      air_loop.setName('Sys_6_VAV with Reheat')

      supply_fan = OpenStudio::Model::FanVariableVolume.new(model, always_on)
      supply_fan.setName('Sys6 Supply Fan')
      return_fan = OpenStudio::Model::FanVariableVolume.new(model, always_on)
      return_fan.setName('Sys6 Return Fan')

      if heating_coil_type == 'Hot Water'
        htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on)
        hw_loop.addDemandBranchForComponent(htg_coil)
      end
      if heating_coil_type == 'Electric'
        htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
      end

      clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, always_on)
      chw_loop.addDemandBranchForComponent(clg_coil)

      oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model)
      oa_controller.autosizeMinimumOutdoorAirFlowRate

      oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller)

      # Add the components to the air loop
      # in order from closest to zone to furthest from zone
      supply_inlet_node = air_loop.supplyInletNode
      supply_outlet_node = air_loop.supplyOutletNode
      supply_fan.addToNode(supply_inlet_node)
      htg_coil.addToNode(supply_inlet_node)
      clg_coil.addToNode(supply_inlet_node)
      oa_system.addToNode(supply_inlet_node)
      returnAirNode = oa_system.returnAirModelObject.get.to_Node.get
      return_fan.addToNode(returnAirNode)

      # Add a setpoint manager to control the
      # supply air to a constant temperature
      sat_sch = OpenStudio::Model::ScheduleRuleset.new(model)
      sat_sch.setName('Supply Air Temp')
      sat_sch.defaultDaySchedule.setName('Supply Air Temp Default')
      sat_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), system_data[:system_supply_air_temperature])
      sat_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, sat_sch)
      sat_stpt_manager.addToNode(supply_outlet_node)

      # Make a VAV terminal with HW reheat for each zone on this story that is in intersection with the zones array.
      # and hook the reheat coil to the HW loop
      (BTAP::Geometry::BuildingStoreys.get_zones_from_storey(story) & zones).each do |zone|
        # Zone sizing parameters
        sizing_zone = zone.sizingZone
        sizing_zone.setZoneCoolingDesignSupplyAirTemperature(system_data[:ZoneCoolingDesignSupplyAirTemperature])
        sizing_zone.setZoneHeatingDesignSupplyAirTemperature(system_data[:ZoneHeatingDesignSupplyAirTemperature])
        sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
        sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])

        if heating_coil_type == 'Hot Water'
          reheat_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on)
          hw_loop.addDemandBranchForComponent(reheat_coil)
        elsif heating_coil_type == 'Electric'
          reheat_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
        end

        vav_terminal = OpenStudio::Model::AirTerminalSingleDuctVAVReheat.new(model, always_on, reheat_coil)
        air_loop.addBranchForZone(zone, vav_terminal.to_StraightComponent)
        # NECB2011 minimum zone airflow setting
        vav_terminal.setFixedMinimumAirFlowRate(system_data[:ZoneVAVMinFlowFactorPerFloorArea] * zone.floorArea )
        vav_terminal.setMaximumReheatAirTemperature(system_data[:ZoneVAVMaxReheatTemp])
        vav_terminal.setDamperHeatingAction(system_data[:ZoneVAVDamperAction])

        # Set zone baseboards
        add_zone_baseboards(model: model,
                            zone: zone,
                            baseboard_type: baseboard_type,
                            hw_loop: hw_loop)
      end
    end
  end # next story

  # for debugging
  # puts "end add_sys6_multi_zone_built_up_with_baseboard_heating"

  return true
end

#add_system_3_and_8_airloop(heating_coil_type, model, system_data, control_zone) ⇒ Object


85
86
87
88
89
90
91
92
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
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb', line 85

def add_system_3_and_8_airloop(heating_coil_type, model, system_data, control_zone)
  # System Type 3: PSZ-AC
  # This measure creates:
  # -a constant volume packaged single-zone A/C unit
  # for each zone in the building; DX cooling with
  # heating coil: fuel-fired or electric, depending on argument heating_coil_type
  # heating_coil_type choices are "Electric", "Gas", "DX"
  # zone baseboards: hot water or electric, depending on argument baseboard_type
  # baseboard_type choices are "Hot Water" or "Electric"
  # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e.
  # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1"


  always_on = model.alwaysOnDiscreteSchedule
  air_loop = common_air_loop(model: model, system_data: system_data)
  air_loop.setName("#{system_data[:name]} #{control_zone.name}")

  # Zone sizing temperature
  sizing_zone = control_zone.sizingZone
  sizing_zone.setZoneCoolingDesignSupplyAirTemperature(system_data[:ZoneCoolingDesignSupplyAirTemperature])
  sizing_zone.setZoneHeatingDesignSupplyAirTemperature(system_data[:ZoneHeatingDesignSupplyAirTemperature])
  sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
  sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])

  fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)

  case heating_coil_type
  when 'Electric' # electric coil
    htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
  when 'Gas'
    htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
  when 'DX'
    htg_coil = OpenStudio::Model::CoilHeatingDXSingleSpeed.new(model)
    supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
    htg_coil.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor])
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor])
  else
    raise("#{heating_coil_type} is not a valid heating coil type.)")
  end

  # TO DO: other fuel-fired heating coil types? (not available in OpenStudio/E+ - may need to play with efficiency to mimic other fuel types)

  # Set up DX coil with NECB performance curve characteristics;
  clg_coil = OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model)

  # oa_controller
  oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model)
  oa_controller.autosizeMinimumOutdoorAirFlowRate

  # oa_system
  oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller)

  # Add the components to the air loop
  # in order from closest to zone to furthest from zone
  supply_inlet_node = air_loop.supplyInletNode
  if heating_coil_type == 'DX'
    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, fan, htg_coil, clg_coil, supplemental_htg_coil)
    air_to_air_heatpump.setName("#{control_zone.name} ASHP")
    air_to_air_heatpump.setControllingZone(control_zone)
    air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
    air_to_air_heatpump.addToNode(supply_inlet_node)
  else
    fan.addToNode(supply_inlet_node)
    htg_coil.addToNode(supply_inlet_node)
    clg_coil.addToNode(supply_inlet_node)
  end
  oa_system.addToNode(supply_inlet_node)

  # Add a setpoint manager single zone reheat to control the
  # supply air temperature based on the needs of this zone
  setpoint_mgr_single_zone_reheat = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model)
  setpoint_mgr_single_zone_reheat.setControlZone(control_zone)
  setpoint_mgr_single_zone_reheat.setMinimumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMin])
  setpoint_mgr_single_zone_reheat.setMaximumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMax])
  setpoint_mgr_single_zone_reheat.addToNode(air_loop.supplyOutletNode)
  return air_loop
end

#add_zone_baseboards(baseboard_type:, hw_loop:, model:, zone:) ⇒ Object

Zonal systems


1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 1805

def add_zone_baseboards(baseboard_type:,
                        hw_loop:,
                        model:,
                        zone:)
  always_on = model.alwaysOnDiscreteSchedule
  if baseboard_type == 'Electric'
    zone_elec_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.new(model)
    zone_elec_baseboard.addToThermalZone(zone)
  end
  if baseboard_type == 'Hot Water'
    baseboard_coil = OpenStudio::Model::CoilHeatingWaterBaseboard.new(model)
    # Connect baseboard coil to hot water loop
    hw_loop.addDemandBranchForComponent(baseboard_coil)
    zone_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveWater.new(model, always_on, baseboard_coil)
    # add zone_baseboard to zone
    zone_baseboard.addToThermalZone(zone)
  end
end

#adjust_wildcard_spacetype_schedule(space, schedule) ⇒ Object

Set wildcard spactype schedule to NECB letter index.


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
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 711

def adjust_wildcard_spacetype_schedule(space, schedule)
  if space.spaceType.empty?
    OpenStudio.logFree(OpenStudio::Error, 'Error: No spacetype assigned for #{space.name.get}. This must be assigned. Aborting.')
  end
  # Get current spacetype name
  space_type_name = space.spaceType.get.standardsSpaceType.get.to_s
  # Determine new spacetype name.
  regex = /^(.*sch-)(\S)$/
  new_spacetype_name = "#{space_type_name.match(regex).captures.first}#{schedule}"
  new_spacetype = nil

  #if the new spacetype does not match the old space type. we gotta update the space with the new spacetype.
  if space_type_name != new_spacetype_name
    new_spacetype = space.model.getSpaceTypes.detect do |spacetype|
      (not spacetype.standardsBuildingType.empty?) and #need to do this to prevent an exception.
          spacetype.standardsBuildingType.get == space.spaceType.get.standardsBuildingType.get and
          (not spacetype.standardsSpaceType.empty?) and #need to do this to prevent an exception.
          spacetype.standardsSpaceType.get == new_spacetype_name
    end
    if new_spacetype.nil?
      # Space type is not in model. need to create from scratch.
      new_spacetype = OpenStudio::Model::SpaceType.new(space.model)
      new_spacetype.setStandardsBuildingType(space.spaceType.get.standardsBuildingType.get)
      new_spacetype.setStandardsSpaceType(new_spacetype_name)
      new_spacetype.setName("#{space.spaceType.get.standardsBuildingType.get} #{new_spacetype_name}")
      space_type_apply_internal_loads(new_spacetype, true, true, true, true, true, true)
      space_type_apply_internal_load_schedules(new_spacetype, true, true, true, true, true, true, true)
    end
    space.setSpaceType(new_spacetype)
    #sanity check.
    raise ("could not reassign space type schedule.") if schedule != space.spaceType.get.name.get.match(regex)[2]
  end
  return space
end

#air_loop_hvac_apply_economizer_integration(air_loop_hvac, climate_zone) ⇒ Bool

Note:

this method assumes you previously checked that an economizer is required at all via #economizer_required?

NECB always requires an integrated economizer (NoLockout); as per 5.2.2.8(3) this means that compressor allowed to turn on when economizer is open

Returns:

  • (Bool)

    returns true if successful, false if not


90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 90

def air_loop_hvac_apply_economizer_integration(air_loop_hvac, climate_zone)
  # Get the OA system and OA controller
  oa_sys = air_loop_hvac.airLoopHVACOutdoorAirSystem
  if oa_sys.is_initialized
    oa_sys = oa_sys.get
  else
    return false # No OA system
  end
  oa_control = oa_sys.getControllerOutdoorAir

  # Apply integrated economizer
  oa_control.setLockoutType('NoLockout')

  return true
end

#air_loop_hvac_apply_energy_recovery_ventilator(air_loop_hvac, climate = nil) ⇒ Bool

TODO:

Add exception logic for systems serving parking garage, warehouse, or multifamily

Add an ERV to this airloop. Will be a rotary-type HX

Returns:

  • (Bool)

    Returns true if required, false if not.


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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 287

def air_loop_hvac_apply_energy_recovery_ventilator(air_loop_hvac, climate = nil)
  # Get the oa system
  oa_system = nil
  if air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized
    oa_system = air_loop_hvac.airLoopHVACOutdoorAirSystem.get
  else
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV cannot be added because the system has no OA intake.")
    return false
  end

  # Create an ERV
  erv = OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.new(air_loop_hvac.model)
  erv.setName("#{air_loop_hvac.name} ERV")
  erv.setSensibleEffectivenessat100HeatingAirFlow(0.5)
  erv.setLatentEffectivenessat100HeatingAirFlow(0.5)
  erv.setSensibleEffectivenessat75HeatingAirFlow(0.5)
  erv.setLatentEffectivenessat75HeatingAirFlow(0.5)
  erv.setSensibleEffectivenessat100CoolingAirFlow(0.5)
  erv.setLatentEffectivenessat100CoolingAirFlow(0.5)
  erv.setSensibleEffectivenessat75CoolingAirFlow(0.5)
  erv.setLatentEffectivenessat75CoolingAirFlow(0.5)
  erv.setSupplyAirOutletTemperatureControl(true)
  erv.setHeatExchangerType('Rotary')
  erv.setFrostControlType('ExhaustOnly')
  erv.setEconomizerLockout(true)
  erv.setThresholdTemperature(-23.3) # -10F
  erv.setInitialDefrostTimeFraction(0.167)
  erv.setRateofDefrostTimeFractionIncrease(1.44)

  # Add the ERV to the OA system
  erv.addToNode(oa_system.outboardOANode.get)

  # Add a setpoint manager OA pretreat
  # to control the ERV
  spm_oa_pretreat = OpenStudio::Model::SetpointManagerOutdoorAirPretreat.new(air_loop_hvac.model)
  spm_oa_pretreat.setMinimumSetpointTemperature(-99.0)
  spm_oa_pretreat.setMaximumSetpointTemperature(99.0)
  spm_oa_pretreat.setMinimumSetpointHumidityRatio(0.00001)
  spm_oa_pretreat.setMaximumSetpointHumidityRatio(1.0)
  # Reference setpoint node and
  # Mixed air stream node are outlet
  # node of the OA system
  mixed_air_node = oa_system.mixedAirModelObject.get.to_Node.get
  spm_oa_pretreat.setReferenceSetpointNode(mixed_air_node)
  spm_oa_pretreat.setMixedAirStreamNode(mixed_air_node)
  # Outdoor air node is
  # the outboard OA node of teh OA system
  spm_oa_pretreat.setOutdoorAirStreamNode(oa_system.outboardOANode.get)
  # Return air node is the inlet
  # node of the OA system
  return_air_node = oa_system.returnAirModelObject.get.to_Node.get
  spm_oa_pretreat.setReturnAirStreamNode(return_air_node)
  # Attach to the outlet of the ERV
  erv_outlet = erv.primaryAirOutletModelObject.get.to_Node.get
  spm_oa_pretreat.addToNode(erv_outlet)

  # Apply the prototype Heat Exchanger power assumptions.
  heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_nominal_electric_power(erv)

  # Determine if the system is a DOAS based on
  # whether there is 100% OA in heating and cooling sizing.
  is_doas = false
  sizing_system = air_loop_hvac.sizingSystem
  if sizing_system.allOutdoorAirinCooling && sizing_system.allOutdoorAirinHeating
    is_doas = true
  end

  # Set the bypass control type
  # If DOAS system, BypassWhenWithinEconomizerLimits
  # to disable ERV during economizing.
  # Otherwise, BypassWhenOAFlowGreaterThanMinimum
  # to disable ERV during economizing and when OA
  # is also greater than minimum.
  bypass_ctrl_type = if is_doas
                       'BypassWhenWithinEconomizerLimits'
                     else
                       'BypassWhenOAFlowGreaterThanMinimum'
                     end
  oa_system.getControllerOutdoorAir.setHeatRecoveryBypassControlType(bypass_ctrl_type)

  return true
end

#air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(air_loop_hvac) ⇒ Object

NECB does not change damper positions

return [Bool] returns true if successful, false if not


14
15
16
17
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 14

def air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(air_loop_hvac)
  # Do not change anything.
  return true
end

#air_loop_hvac_apply_single_zone_controls(air_loop_hvac, climate_zone) ⇒ Bool

NECB has no single zone air loop control requirements

Returns:

  • (Bool)

    returns true if successful, false if not


436
437
438
439
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 436

def air_loop_hvac_apply_single_zone_controls(air_loop_hvac, climate_zone)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: No special economizer controls were modeled.")
  return true
end

#air_loop_hvac_apply_vav_damper_action(air_loop_hvac) ⇒ Bool

TODO:

see if this impacts the sizing run.

Set the VAV damper control to single maximum or dual maximum control depending on the standard.

Returns:

  • (Bool)

    Returns true if successful, false if not


388
389
390
391
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
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 388

def air_loop_hvac_apply_vav_damper_action(air_loop_hvac)
  damper_action = 'Single Maximum'

  # Interpret this as an EnergyPlus input
  damper_action_eplus = nil
  if damper_action == 'Single Maximum'
    damper_action_eplus = 'Normal'
  elsif damper_action == 'Dual Maximum'
    # EnergyPlus 8.7 changed the meaning of 'Reverse'.
    # For versions of OpenStudio using E+ 8.6 or lower
    damper_action_eplus = if air_loop_hvac.model.version < OpenStudio::VersionString.new('2.0.5')
                            'Reverse'
                            # For versions of OpenStudio using E+ 8.7 or higher
                          else
                            'ReverseWithLimits'
                          end
  end

  # Set the control for any VAV reheat terminals
  # on this airloop.
  control_type_set = false
  air_loop_hvac.demandComponents.each do |equip|
    if equip.to_AirTerminalSingleDuctVAVReheat.is_initialized
      term = equip.to_AirTerminalSingleDuctVAVReheat.get
      # Dual maximum only applies to terminals with HW reheat coils
      if damper_action == 'Dual Maximum'
        if term.reheatCoil.to_CoilHeatingWater.is_initialized
          term.setDamperHeatingAction(damper_action_eplus)
          control_type_set = true
        end
      else
        term.setDamperHeatingAction(damper_action_eplus)
        control_type_set = true
        term.setMaximumFlowFractionDuringReheat(0.5)
      end
    end
  end

  if control_type_set
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: VAV damper action was set to #{damper_action} control.")
  end

  return true
end

#air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac, climate_zone) ⇒ Bool

TODO:

Add exception logic for systems that serve multifamily, parking garage, warehouse

Determine if demand control ventilation (DCV) is required for this air loop.

Returns:

  • (Bool)

    Returns true if required, false if not.


377
378
379
380
381
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 377

def air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac, climate_zone)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{template} #{climate_zone}:  #{air_loop_hvac.name}: DCV is not required for any system.")
  dcv_required = false
  return dcv_required
end

#air_loop_hvac_economizer_required?(air_loop_hvac) ⇒ Bool

Determine whether or not this system is required to have an economizer.

Returns:

  • (Bool)

    returns true if an economizer is required, false if not


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
75
76
77
78
79
80
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 23

def air_loop_hvac_economizer_required?(air_loop_hvac)
  economizer_required = false

  # need a better way to determine if an economizer is needed.
  return economizer_required if air_loop_hvac.name.to_s.include? 'Outpatient F1'

  # A big number of btu per hr as the minimum requirement
  infinity_btu_per_hr = 999_999_999_999
  minimum_capacity_btu_per_hr = infinity_btu_per_hr

  # Determine if the airloop serves any computer rooms
  # / data centers, which changes the economizer.
  is_dc = false
  if air_loop_hvac_data_center_area_served(air_loop_hvac) > 0
    is_dc = true
  end

  # Determine the minimum capacity that requires an economizer
  minimum_capacity_btu_per_hr = 68_243 # NECB requires economizer for cooling cap > 20 kW

  # puts air_loop_hvac.name.to_s
  # Design Supply Air Flow Rate: This method below reads the value from the sql file.
  dsafr_m3_per_s = air_loop_hvac.model.getAutosizedValue(air_loop_hvac, 'Design Supply Air Flow Rate', 'm3/s')
  min_dsafr_l_per_s = 1500
  unless dsafr_m3_per_s.empty?
    dsafr_l_per_s = dsafr_m3_per_s.get() * 1000
    if dsafr_l_per_s > min_dsafr_l_per_s
      economizer_required = true
      puts "economizer_required = true for #{air_loop_hvac.name} because dsafr_l_per_s(#{dsafr_l_per_s}) > 1500"
      if is_dc
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the 'Design Supply Air Flow Rate' of #{dsafr_l_per_s} L/s exceeds the minimum air flow rate of #{min_dsafr_l_per_s} L/s for data centers.")
      else
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the 'Design Supply Air Flow Rate' of #{dsafr_l_per_s} L/s exceeds the minimum air flow rate of #{min_dsafr_l_per_s} L/s.")
      end
    end
  end
  # Check whether the system requires an economizer by comparing
  # the system capacity to the minimum capacity.
  total_cooling_capacity_w = air_loop_hvac_total_cooling_capacity(air_loop_hvac)
  total_cooling_capacity_btu_per_hr = OpenStudio.convert(total_cooling_capacity_w, 'W', 'Btu/hr').get
  if total_cooling_capacity_btu_per_hr >= minimum_capacity_btu_per_hr
    if is_dc
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr for data centers.")
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr.")
    end
    puts "economizer_required = true for #{air_loop_hvac.name} because total_cooling_capacity_btu_per_hr(#{total_cooling_capacity_btu_per_hr}) >= #{minimum_capacity_btu_per_hr}"
    economizer_required = true
  else
    if is_dc
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} does not require an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr is less than the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr for data centers.")
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} does not require an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr is less than the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr.")
    end
  end

  return economizer_required
end

#air_loop_hvac_energy_recovery_ventilator_required?(air_loop_hvac, climate_zone) ⇒ Bool

TODO:

Add exception logic for systems serving parking garage, warehouse, or multifamily

Check if ERV is required on this airloop.

Returns:

  • (Bool)

    Returns true if required, false if not.


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
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 111

def air_loop_hvac_energy_recovery_ventilator_required?(air_loop_hvac, climate_zone)
  # ERV Not Applicable for AHUs that serve
  # parking garage, warehouse, or multifamily
  # if space_types_served_names.include?('PNNL_Asset_Rating_Apartment_Space_Type') ||
  # space_types_served_names.include?('PNNL_Asset_Rating_LowRiseApartment_Space_Type') ||
  # space_types_served_names.include?('PNNL_Asset_Rating_ParkingGarage_Space_Type') ||
  # space_types_served_names.include?('PNNL_Asset_Rating_Warehouse_Space_Type')
  # OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV not applicable because it because it serves parking garage, warehouse, or multifamily.")
  # return false
  # end

  erv_required = nil
  # ERV not applicable for medical AHUs (AHU1 in Outpatient), per AIA 2001 - 7.31.D2.
  if air_loop_hvac.name.to_s.include? 'Outpatient F1'
    erv_required = false
    return erv_required
  end

  # ERV not applicable for medical AHUs, per AIA 2001 - 7.31.D2.
  if air_loop_hvac.name.to_s.include? 'VAV_ER'
    erv_required = false
    return erv_required
  elsif air_loop_hvac.name.to_s.include? 'VAV_OR'
    erv_required = false
    return erv_required
  end

  # ERV Not Applicable for AHUs that have DCV
  # or that have no OA intake.
  controller_oa = nil
  controller_mv = nil
  oa_system = nil
  if air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized
    oa_system = air_loop_hvac.airLoopHVACOutdoorAirSystem.get
    controller_oa = oa_system.getControllerOutdoorAir
    controller_mv = controller_oa.controllerMechanicalVentilation
    if controller_mv.demandControlledVentilation == true
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not applicable because DCV enabled.")
      return false
    end
  else
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not applicable because it has no OA intake.")
    return false
  end

  # Get the AHU design supply air flow rate
  dsn_flow_m3_per_s = nil
  if air_loop_hvac.designSupplyAirFlowRate.is_initialized
    dsn_flow_m3_per_s = air_loop_hvac.designSupplyAirFlowRate.get
  elsif air_loop_hvac.autosizedDesignSupplyAirFlowRate.is_initialized
    dsn_flow_m3_per_s = air_loop_hvac.autosizedDesignSupplyAirFlowRate.get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name} design supply air flow rate is not available, cannot apply efficiency standard.")
    return false
  end
  dsn_flow_cfm = OpenStudio.convert(dsn_flow_m3_per_s, 'm^3/s', 'cfm').get

  # Get the minimum OA flow rate
  min_oa_flow_m3_per_s = nil
  if controller_oa.minimumOutdoorAirFlowRate.is_initialized
    min_oa_flow_m3_per_s = controller_oa.minimumOutdoorAirFlowRate.get
  elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized
    min_oa_flow_m3_per_s = controller_oa.autosizedMinimumOutdoorAirFlowRate.get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{controller_oa.name}: minimum OA flow rate is not available, cannot apply efficiency standard.")
    return false
  end
  min_oa_flow_cfm = OpenStudio.convert(min_oa_flow_m3_per_s, 'm^3/s', 'cfm').get

  # Calculate the percent OA at design airflow
  pct_oa = min_oa_flow_m3_per_s / dsn_flow_m3_per_s

  # The NECB2011 requirement is that systems with an exhaust heat content > 150 kW require an HRV
  # The calculation for this is done below, to modify erv_required
  # erv_cfm set to nil here as placeholder, will lead to erv_required = false
  erv_cfm = nil

  # Determine if an ERV is required
  # erv_required = nil
  if erv_cfm.nil?
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not required based on #{(pct_oa * 100).round}% OA flow, design supply air flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}.")
    erv_required = false
  elsif dsn_flow_cfm < erv_cfm
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not required based on #{(pct_oa * 100).round}% OA flow, design supply air flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}. Does not exceed minimum flow requirement of #{erv_cfm}cfm.")
    erv_required = false
  else
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV required based on #{(pct_oa * 100).round}% OA flow, design supply air flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}. Exceeds minimum flow requirement of #{erv_cfm}cfm.")
    erv_required = true
  end

  # This code modifies erv_required for NECB2011
  # Calculation of exhaust heat content and check whether it is > 150 kW

  # get all zones in the model
  zones = air_loop_hvac.thermalZones

  # initialize counters
  sum_zone_oa = 0.0
  sum_zone_oa_times_heat_design_t = 0.0

  # zone loop
  zones.each do |zone|
    # get design heat temperature for each zone; this is equivalent to design exhaust temperature
    heat_design_t = 21.0
    zone_thermostat = zone.thermostat.get
    if zone_thermostat.to_ThermostatSetpointDualSetpoint.is_initialized
      dual_thermostat = zone_thermostat.to_ThermostatSetpointDualSetpoint.get
      if dual_thermostat.heatingSetpointTemperatureSchedule.is_initialized
        htg_temp_sch = dual_thermostat.heatingSetpointTemperatureSchedule.get
        htg_temp_sch_ruleset = htg_temp_sch.to_ScheduleRuleset.get
        winter_dd_sch = htg_temp_sch_ruleset.winterDesignDaySchedule
        heat_design_t = winter_dd_sch.values.max
      end
    end
    # initialize counter
    zone_oa = 0.0
    # outdoor defined at space level; get OA flow for all spaces within zone
    spaces = zone.spaces

    # space loop
    spaces.each do |space|
      unless space.designSpecificationOutdoorAir.empty? # if empty, don't do anything
        outdoor_air = space.designSpecificationOutdoorAir.get
        # in bTAP, outdoor air specified as outdoor air per
        oa_flow_per_floor_area = outdoor_air.outdoorAirFlowperFloorArea
        oa_flow = oa_flow_per_floor_area * space.floorArea * zone.multiplier # oa flow for the space
        zone_oa += oa_flow # add up oa flow for all spaces to get zone air flow
      end
    end # space loop
    sum_zone_oa += zone_oa # sum of all zone oa flows to get system oa flow
    sum_zone_oa_times_heat_design_t += (zone_oa * heat_design_t) # calculated to get oa flow weighted average of design exhaust temperature
  end # zone loop

  # Calculate average exhaust temperature (oa flow weighted average)
  avg_exhaust_temp = sum_zone_oa_times_heat_design_t / sum_zone_oa

  # for debugging/testing
  #      puts "average exhaust temp = #{avg_exhaust_temp}"
  #      puts "sum_zone_oa = #{sum_zone_oa}"

  # Get January winter design temperature
  # get model weather file name
  weather_file = BTAP::Environment::WeatherFile.new(air_loop_hvac.model.weatherFile.get.path.get)

  # get winter(heating) design temp stored in array
  # Note that the NECB2011 specifies using the 2.5% january design temperature
  # The outdoor temperature used here is the 0.4% heating design temperature of the coldest month, available in stat file
  outdoor_temp = weather_file.heating_design_info[1]

  #      for debugging/testing
  #      puts "outdoor design temp = #{outdoor_temp}"

  # Calculate exhaust heat content
  exhaust_heat_content = 0.00123 * sum_zone_oa * 1000.0 * (avg_exhaust_temp - outdoor_temp)

  # for debugging/testing
  #      puts "exhaust heat content = #{exhaust_heat_content}"

  # Modify erv_required based on exhaust heat content
  if exhaust_heat_content > 150.0
    erv_required = true
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV required based on exhaust heat content.")
  else
    erv_required = false
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV not required based on exhaust heat content.")
  end

  return erv_required
end

#air_loop_hvac_motorized_oa_damper_limits(air_loop_hvac, climate_zone) ⇒ Array<Double>

Determine the air flow and number of story limits for whether motorized OA damper is required. If both nil, never required

Returns:

  • (Array<Double>)

    [minimum_oa_flow_cfm, maximum_stories].


454
455
456
457
458
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 454

def air_loop_hvac_motorized_oa_damper_limits(air_loop_hvac, climate_zone)
  minimum_oa_flow_cfm = 0
  maximum_stories = 0
  return [minimum_oa_flow_cfm, maximum_stories]
end

#air_loop_hvac_static_pressure_reset_required?(air_loop_hvac, has_ddc) ⇒ Boolean

NECB doesn't require static pressure reset.

return [Bool] returns true if static pressure reset is required, false if not

Returns:

  • (Boolean)

444
445
446
447
448
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 444

def air_loop_hvac_static_pressure_reset_required?(air_loop_hvac, has_ddc)
  # static pressure reset not required
  sp_reset_required = false
  return sp_reset_required
end

#apply_auto_zoning(model:, sizing_run_dir: Dir.pwd) ⇒ Object

Top level method that merges spaces into zones where possible. This requires a sizing run. This follows the spirit of the EE4 modelling manual found here www.nrcan.gc.ca/energy/software-tools/7457 where the A zone includes those areas in the building that meet three criteria:

  • Served by the same HVAC system

  • Similar operation and function

  • Similar heating/cooling loads

Some expections are dwelling units wet zone and wild zones. These spaces will have special considerations when autozoning a building.


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 55

def apply_auto_zoning(model:, sizing_run_dir: Dir.pwd)
  raise('validation of model failed.') unless validate_initial_model(model)

  # Check to see if model is using another vintage of spacetypes. If so overwrite the @standards for the object with the
  # other spacetype data. This is required for correct system mapping.
  template = determine_spacetype_vintage(model)
  unless template == self.class.name
    # Frankenstein the standards data wrt spacetype data.
    @standards_data['space_types'] = Standard.build(template).standards_data['space_types']
  end


  # The first thing we need to do is get a sizing run to determine the heating loads of all the spaces. The default
  # btap geometry has a one to one relationship of zones to spaces.. So we simply create the thermal zones for all the spaces.
  # to do this we need to create thermals zone for each space.

  # Remove any Thermal zones assigned before
  model.getThermalZones.each(&:remove)
  # create new thermal zones one to one with spaces.
  model_create_thermal_zones(model)
  # do a sizing run.
  if model_run_sizing_run(model, "#{sizing_run_dir}/autozone") == false
    raise('autorun sizing run failed!')
  end

  # collect sizing information on each space.
  self.store_space_sizing_loads(model)
  # Remove any Thermal zones assigned again to start fresh.
  model.getThermalZones.each(&:remove)
  self.auto_zone_dwelling_units(model)
  self.auto_zone_wet_spaces(model)
  self.auto_zone_all_other_spaces(model)
  self.auto_zone_wild_spaces(model)
  #THis will color the spaces and zones.
  random = Random.new(1234)
  #Set ideal hvac in case we want to not implement the hvac yet and still run osm right after this function.
  #model.getThermalZones.each { |zone| zone.setUseIdealAirLoads(true) }
  model.getThermalZones.sort.each {|item| item.setRenderingColor(self.set_random_rendering_color(item, random))}
  model.getSpaceTypes.sort.each {|item| item.setRenderingColor(self.set_random_rendering_color(item, random))}
end

#apply_building_default_constructionset(model) ⇒ Object


499
500
501
502
503
504
# File 'lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb', line 499

def apply_building_default_constructionset(model)

  bldg_def_const_set = model_add_construction_set_from_osm(model: model)
  model.getBuilding.setDefaultConstructionSet(bldg_def_const_set)

end

#apply_default_constructionsets_to_spacetypes(climate_zone, model) ⇒ Object


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
# File 'lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb', line 506

def apply_default_constructionsets_to_spacetypes(climate_zone, model)
  model.getSpaceTypes.sort.each do |space_type|
    # Get the standards building type
    stds_building_type = nil
    if space_type.standardsBuildingType.is_initialized
      stds_building_type = space_type.standardsBuildingType.get
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards building type.")
    end

    # Get the standards space type
    stds_spc_type = nil
    if space_type.standardsSpaceType.is_initialized
      stds_spc_type = space_type.standardsSpaceType.get
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards space type.")
    end

    # If the standards space type is Attic,
    # the building type should be blank.
    if stds_spc_type == 'Attic'
      stds_building_type = ''
    end

    # Attempt to make a construction set for this space type
    # and assign it if it can be created.
    spc_type_const_set = model_add_construction_set_from_osm(model: model)
    if spc_type_const_set.is_initialized
      space_type.setDefaultConstructionSet(spc_type_const_set.get)
    end
  end
end

#apply_economizers(climate_zone, model) ⇒ Object


1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 1323

def apply_economizers(climate_zone, model)
  # NECB2011 prescribes ability to provide 100% OA (5.2.2.7-5.2.2.9)
  econ_max_100_pct_oa_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  econ_max_100_pct_oa_sch.setName('Economizer Max OA Fraction 100 pct')
  econ_max_100_pct_oa_sch.defaultDaySchedule.setName('Economizer Max OA Fraction 100 pct Default')
  econ_max_100_pct_oa_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0)

  # Check each airloop
  model.getAirLoopHVACs.sort.each do |air_loop|
    if air_loop_hvac_economizer_required?(air_loop) == true
      # If an economizer is required, determine the economizer type
      # in the prototype buildings, which depends on climate zone.
      economizer_type = nil

      # NECB 5.2.2.8 states that economizer can be controlled based on difference betweeen
      # return air temperature and outside air temperature OR return air enthalpy
      # and outside air enthalphy; latter chosen to be consistent with MNECB and CAN-QUEST implementation
      economizer_type = 'DifferentialEnthalpy'
      # Set the economizer type
      # Get the OA system and OA controller
      oa_sys = air_loop.airLoopHVACOutdoorAirSystem
      if oa_sys.is_initialized
        oa_sys = oa_sys.get
      else
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', "#{air_loop.name} is required to have an economizer, but it has no OA system.")
        next
      end
      oa_control = oa_sys.getControllerOutdoorAir
      oa_control.setEconomizerControlType(economizer_type)
    end
  end
end

#apply_envelope(model:, properties: { 'outdoors_wall_conductance' => nil, 'outdoors_floor_conductance' => nil, 'outdoors_roofceiling_conductance' => nil, 'ground_wall_conductance' => nil, 'ground_floor_conductance' => nil, 'ground_roofceiling_conductance' => nil, 'outdoors_door_conductance' => nil, 'outdoors_fixedwindow_conductance' => nil }) ⇒ Object


225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 225

def apply_envelope(model:,
                   properties: {
                       'outdoors_wall_conductance' => nil,
                       'outdoors_floor_conductance' => nil,
                       'outdoors_roofceiling_conductance' => nil,
                       'ground_wall_conductance' => nil,
                       'ground_floor_conductance' => nil,
                       'ground_roofceiling_conductance' => nil,
                       'outdoors_door_conductance' => nil,
                       'outdoors_fixedwindow_conductance' => nil
                   })
  raise('validation of model failed.') unless validate_initial_model(model)
  model_apply_infiltration_standard(model)
  model.getInsideSurfaceConvectionAlgorithm.setAlgorithm('TARP')
  model.getOutsideSurfaceConvectionAlgorithm.setAlgorithm('TARP')
  model_add_constructions(model)
  apply_standard_construction_properties(model: model, properties: properties)
  model_create_thermal_zones(model, @space_multiplier_map)
end

#apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0,, srr_set: -1.0)) ⇒ Object

Thermal zones need to be set to determine conditioned spaces when applying fdwr and srr limits.

# fdwr_set/srr_set settings:
# 0-1:  Remove all windows/skylights and add windows/skylights to match this fdwr/srr
# -1:  Remove all windows/skylights and add windows/skylights to match max fdwr/srr from NECB
# -2:  Do not apply any fdwr/srr changes, leave windows/skylights alone (also works for fdwr/srr > 1)
# -3:  Use old method which reduces existing window/skylight size (if necessary) to meet maximum NECB fdwr/srr
# limit
# <-3.1:  Remove all the windows/skylights
# > 1:  Do nothing

254
255
256
257
258
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 254

def apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0)
  apply_standard_window_to_wall_ratio(model: model, fdwr_set: fdwr_set)
  apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set)
  model_add_daylighting_controls(model) # to be removed after refactor.
end

#apply_limit_fdwr(model:, fdwr_lim:) ⇒ Object


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb', line 37

def apply_limit_fdwr(model:, fdwr_lim:)
  # Loop through all spaces in the model, and
  # per the PNNL PRM Reference Manual, find the areas
  # of each space conditioning category (res, nonres, semi-heated)
  # separately.  Include space multipliers.
  nr_wall_m2 = 0.001 # Avoids divide by zero errors later
  nr_wind_m2 = 0
  res_wall_m2 = 0.001
  res_wind_m2 = 0
  sh_wall_m2 = 0.001
  sh_wind_m2 = 0
  total_wall_m2 = 0.001
  total_subsurface_m2 = 0.0
  # Store the space conditioning category for later use
  space_cats = {}
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    wall_area_m2 = 0
    wind_area_m2 = 0
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType.casecmp('wall').zero?
      # This wall's gross area (including window area)
      wall_area_m2 += surface.grossArea * space.multiplier
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        wind_area_m2 += ss.netArea * space.multiplier
      end
    end

    # Determine the space category
    # zTODO This should really use the heating/cooling loads
    # from the proposed building.  However, in an attempt
    # to avoid another sizing run just for this purpose,
    # conditioned status is based on heating/cooling
    # setpoints.  If heated-only, will be assumed Semiheated.
    # The full-bore method is on the next line in case needed.
    # cat = thermal_zone_conditioning_category(space, template, climate_zone)
    cooled = space_cooled?(space)
    heated = space_heated?(space)
    cat = 'Unconditioned'
    # Unconditioned
    if !heated && !cooled
      cat = 'Unconditioned'
      # Heated-Only
    elsif heated && !cooled
      cat = 'Semiheated'
      # Heated and Cooled
    else
      res = thermal_zone_residential?(space.thermalZone.get)
      cat = if res
              'ResConditioned'
            else
              'NonResConditioned'
            end
    end
    space_cats[space] = cat
    # NECB2011 keep track of totals for NECB regardless of conditioned or not.
    total_wall_m2 += wall_area_m2
    total_subsurface_m2 += wind_area_m2 # this contains doors as well.

    # Add to the correct category
    case cat
    when 'Unconditioned'
      next # Skip unconditioned spaces
    when 'NonResConditioned'
      nr_wall_m2 += wall_area_m2
      nr_wind_m2 += wind_area_m2
    when 'ResConditioned'
      res_wall_m2 += wall_area_m2
      res_wind_m2 += wind_area_m2
    when 'Semiheated'
      sh_wall_m2 += wall_area_m2
      sh_wind_m2 += wind_area_m2
    end
  end

  # Calculate the WWR of each category
  wwr_nr = ((nr_wind_m2 / nr_wall_m2) * 100.0).round(1)
  wwr_res = ((res_wind_m2 / res_wall_m2) * 100).round(1)
  wwr_sh = ((sh_wind_m2 / sh_wall_m2) * 100).round(1)
  fdwr = ((total_subsurface_m2 / total_wall_m2) * 100).round(1) # used by NECB2011

  # Convert to IP and report
  nr_wind_ft2 = OpenStudio.convert(nr_wind_m2, 'm^2', 'ft^2').get
  nr_wall_ft2 = OpenStudio.convert(nr_wall_m2, 'm^2', 'ft^2').get

  res_wind_ft2 = OpenStudio.convert(res_wind_m2, 'm^2', 'ft^2').get
  res_wall_ft2 = OpenStudio.convert(res_wall_m2, 'm^2', 'ft^2').get

  sh_wind_ft2 = OpenStudio.convert(sh_wind_m2, 'm^2', 'ft^2').get
  sh_wall_ft2 = OpenStudio.convert(sh_wall_m2, 'm^2', 'ft^2').get

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR NonRes = #{wwr_nr.round}%; window = #{nr_wind_ft2.round} ft2, wall = #{nr_wall_ft2.round} ft2.")
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Res = #{wwr_res.round}%; window = #{res_wind_ft2.round} ft2, wall = #{res_wall_ft2.round} ft2.")
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Semiheated = #{wwr_sh.round}%; window = #{sh_wind_ft2.round} ft2, wall = #{sh_wall_ft2.round} ft2.")

  # WWR limit
  wwr_lim = 40.0

  # Check against WWR limit
  red_nr = wwr_nr > wwr_lim
  red_res = wwr_res > wwr_lim
  red_sh = wwr_sh > wwr_lim


  # puts "Current FDWR is #{fdwr}, must be less than #{fdwr_lim}."
  # puts "Current subsurf area is #{total_subsurface_m2} and gross surface area is #{total_wall_m2}"
  # Stop here unless windows / doors need reducing
  return true unless fdwr > fdwr_lim
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{wwr_lim.round}%.")
  # Determine the factors by which to reduce the window / door area
  mult = fdwr_lim / fdwr
  # Reduce the window area if any of the categories necessary
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType == 'Wall'
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        # Reduce the size of the window
        red = 1.0 - mult
        sub_surface_reduce_area_by_percent_by_raising_sill(ss, red)
      end
    end
  end
  return true
end

#apply_loads(model:) ⇒ Object


206
207
208
209
210
211
212
213
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 206

def apply_loads(model:)
  raise('validation of model failed.') unless validate_initial_model(model)
  raise('validation of spacetypes failed.') unless validate_and_upate_space_types(model)
  #this sets/stores the template version loads that the model uses.
  model.getBuilding.setStandardsTemplate(self.class.name)
  set_occ_sensor_spacetypes(model, @space_type_map)
  model_add_loads(model)
end

#apply_loop_pump_power(model:, sizing_run_dir:) ⇒ Object


272
273
274
275
276
277
278
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 272

def apply_loop_pump_power(model:, sizing_run_dir:)
  # Remove duplicate materials and constructions
  # Note For NECB2015 This is the 2nd time this method is bieng run.
  # First time it ran in the super() within model_apply_standard() method
  # model = return BTAP::FileIO::remove_duplicate_materials_and_constructions(model)
  return model
end

#apply_max_fdwr_nrcan(model:, fdwr_lim:) ⇒ Object

This method applies the maximum fenestration and door to wall ratio to a building as per NECB 2011 8.4.4.3 and 3.2.1.4 (or equivalent in other versions of the NECB). It first checks for al exterior walls adjacent to conditioned spaces. It distinguishes between plenums and other conditioned spaces. It uses both to calculate the maximum window area to be applied to the building but attempts to put these windows only on non-plenum conditioned spaces (if possible).


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
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
# File 'lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb', line 668

def apply_max_fdwr_nrcan(model:, fdwr_lim:)
  # First determine which vertical (between 89 and 91 degrees from horizontal) walls are adjacent to conditioned
  # spaces.
  exp_surf_info = find_exposed_conditioned_vertical_surfaces(model)
  # If there are none (or very few) then throw a warning.
  if exp_surf_info["total_exp_wall_area_m2"] < 0.1
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "This building has no exposed walls adjacent to heated spaces.")
    return false
  end

  construct_set = model.getBuilding.defaultConstructionSet.get
  fixed_window_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.fixedWindowConstruction.get


  # IF FDWR is greater than 1 then something is wrong raise an error.  If it is less than 0.001 assume all the windows
  # should go.
  if fdwr_lim > 1
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "This building requires a larger window area than there is wall area.")
    return false
  elsif fdwr_lim < 0.001
    exp_surf_info["exp_nonplenum_walls"].sort.each do |exp_surf|
      remove_All_Subsurfaces(surface: exp_surf)
    end
    return true
  end
  # Get the required window area.
  win_area = fdwr_lim * exp_surf_info["total_exp_wall_area_m2"]
  # Try to put the windows on non-plenum walls if possible.  So determine if you can fit the required window area
  # on the non-plenum wall area.
  if win_area <= exp_surf_info["exp_nonplenum_wall_area_m2"]
    # If you can fit the windows on the non-plenum wall area then recalculate the window ratio so that is is only for
    # the non-plenum walls.
    nonplenum_fdwr = win_area / exp_surf_info["exp_nonplenum_wall_area_m2"]
    exp_surf_info["exp_nonplenum_walls"].sort.each do |exp_surf|
      # Remove any subsurfaces, add the window, set the name to be whatever the surface name is plus the subsurface
      # type (which will be 'fixedwindow')
      remove_All_Subsurfaces(surface: exp_surf)
      set_Window_To_Wall_Ratio_set_name(surface: exp_surf, area_fraction: nonplenum_fdwr, construction: fixed_window_construct_set)
    end
  else
    # There was not enough non-plenum wall area so add the windows to both the plenum and non-plenum walls.  This is
    # done separately because the 'find_exposed_conditioned_vertical_surfaces' method returns the plenum and
    # non-plenum walls separately.
    exp_surf_info["exp_nonplenum_walls"].sort.each do |exp_surf|
      # Remove any subsurfaces, add the window, set the name to be whatever the surface name is plus the subsurface
      # type (which will be 'fixedwindow')
      remove_All_Subsurfaces(surface: exp_surf)
      set_Window_To_Wall_Ratio_set_name(surface: exp_surf, area_fraction: fdwr_lim, construction: fixed_window_construct_set)
    end
    exp_surf_info["exp_plenum_walls"].sort.each do |exp_surf|
      # Remove any subsurfaces, add the window, set the name to be whatever the surface name is plus the subsurface
      # type (which will be 'fixedwindow')
      remove_All_Subsurfaces(surface: exp_surf)
      set_Window_To_Wall_Ratio_set_name(surface: exp_surf, area_fraction: fdwr_lim, construction: fixed_window_construct_set)
    end
  end
  return true
end

#apply_max_srr_nrcan(model:, srr_lim:) ⇒ Object

This method is similar to the 'apply_max_fdwr' method above but applies the maximum skylight to roof area ratio to a building as per NECB 2011 8.4.4.3 and 3.2.1.4 (or equivalent in other versions of the NECB). It first checks for all exterior roofs adjacent to conditioned spaces. It distinguishes between plenums and other conditioned spaces. It uses only the non-plenum roof area to calculate the maximum skylight area to be applied to the building.


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
# File 'lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb', line 731

def apply_max_srr_nrcan(model:, srr_lim:)
  # First determine which roof surfaces are adjacent to heated spaces (both plenum and non-plenum).
  exp_surf_info = find_exposed_conditioned_roof_surfaces(model)
  # If the non-plenum roof area is very small raise a warning.  It may be perfectly fine but it is probably a good
  # idea to warn the user.
  if exp_surf_info["exp_nonplenum_roof_area_m2"] < 0.1
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "This building has no exposed ceilings adjacent to spaces that are not attics or plenums.  No skylights will be added.")
    return false
  end


  # If the SRR is greater than one something is seriously wrong so raise an error.  If it is less than 0.001 assume
  # all the skylights should go.
  if srr_lim > 1
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "This building requires a larger skylight area than there is roof area.")
    return false
  elsif srr_lim < 0.001
    exp_surf_info["exp_nonplenum_roofs"].sort.each do |exp_surf|
      remove_All_Subsurfaces(surface: exp_surf)
    end
    return true
  end

  construct_set = model.getBuilding.defaultConstructionSet.get
  skylight_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.skylightConstruction.get

  # Go through all of exposed roofs adjacent to heated, non-plenum spaces, remove any existing subsurfaces, and add
  # a skylight in the centroid of the surface, with the same shape of the surface, only scaled to be the area
  # determined by the SRR.  The name of the skylight will be the surface name with the subsurface type attached
  # ('skylight' in this case).  Note that this method will only work if the surface does not fold into itself (like an
  # L or a V).
  exp_surf_info["exp_nonplenum_roofs"].sort.each do |roof|
    # sub_surface_create_centered_subsurface_from_scaled_surface(roof, srr_lim, model)
    sub_surface_create_scaled_subsurfaces_from_surface(surface: roof, area_fraction: srr_lim, model: model, consturction: skylight_construct_set)
  end
  return true
end

#apply_standard_construction_properties(model:, runner: nil, properties: { 'outdoors_wall_conductance' => nil, 'outdoors_floor_conductance' => nil, 'outdoors_roofceiling_conductance' => nil, 'ground_wall_conductance' => nil, 'ground_floor_conductance' => nil, 'ground_roofceiling_conductance' => nil, 'outdoors_door_conductance' => nil, 'outdoors_fixedwindow_conductance' => nil }) ⇒ Bool

Go through the default construction sets and hard-assigned constructions. Clone the existing constructions and set their intended surface type and standards construction type per the PRM. For some standards, this will involve making modifications. For others, it will not.

90.1-2007, 90.1-2010, 90.1-2013

Returns:

  • (Bool)

    returns true if successful, false if not


323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb', line 323

def apply_standard_construction_properties(model:,
                                           runner: nil,
                                           properties: {
                                               'outdoors_wall_conductance' => nil,
                                               'outdoors_floor_conductance' => nil,
                                               'outdoors_roofceiling_conductance' => nil,
                                               'ground_wall_conductance' => nil,
                                               'ground_floor_conductance' => nil,
                                               'ground_roofceiling_conductance' => nil,
                                               'outdoors_door_conductance' => nil,
                                               'outdoors_fixedwindow_conductance' => nil
                                           })

  model.getDefaultConstructionSets.sort.each do |set|
    set_construction_set_to_necb!(model: model,
                                  default_surface_construction_set: set,
                                  runner: nil,
                                  properties: properties)
  end
  # sets all surfaces to use default constructions sets except adiabatic, where it does a hard assignment of the interior wall construction type.
  model.getPlanarSurfaces.sort.each(&:resetConstruction)
  # if the default construction set is defined..try to assign the interior wall to the adiabatic surfaces
  BTAP::Resources::Envelope.assign_interior_surface_construction_to_adiabatic_surfaces(model, nil)
end

#apply_standard_efficiencies(model:, sizing_run_dir:) ⇒ Object


260
261
262
263
264
265
266
267
268
269
270
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 260

def apply_standard_efficiencies(model:, sizing_run_dir:)
  raise('validation of model failed.') unless validate_initial_model(model)
  climate_zone = 'NECB HDD Method'
  raise("sizing run 1 failed! check #{sizing_run_dir}") if model_run_sizing_run(model, "#{sizing_run_dir}/plant_loops") == false
  # This is needed for NECB2011 as a workaround for sizing the reheat boxes
  model.getAirTerminalSingleDuctVAVReheats.each {|iobj| air_terminal_single_duct_vav_reheat_set_heating_cap(iobj)}
  # Apply the prototype HVAC assumptions
  model_apply_prototype_hvac_assumptions(model, nil, climate_zone)
  # Apply the HVAC efficiency standard
  model_apply_hvac_efficiency_standard(model, climate_zone)
end

#apply_standard_lights(set_lights, space_type, space_type_properties) ⇒ Object


2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
# File 'lib/openstudio-standards/standards/necb/NECB2011/lighting.rb', line 2

def apply_standard_lights(set_lights, space_type, space_type_properties)
  lights_have_info = false
  lighting_per_area = space_type_properties['lighting_per_area'].to_f
  lighting_per_person = space_type_properties['lighting_per_person'].to_f
  lights_frac_to_return_air = space_type_properties['lighting_fraction_to_return_air'].to_f
  lights_frac_radiant = space_type_properties['lighting_fraction_radiant'].to_f
  lights_frac_visible = space_type_properties['lighting_fraction_visible'].to_f
  lights_frac_replaceable = space_type_properties['lighting_fraction_replaceable'].to_f
  lights_have_info = true unless lighting_per_area.zero?
  lights_have_info = true unless lighting_per_person.zero?

  if set_lights && lights_have_info

    # Remove all but the first instance
    instances = space_type.lights.sort
    if instances.size.zero?
      definition = OpenStudio::Model::LightsDefinition.new(space_type.model)
      definition.setName("#{space_type.name} Lights Definition")
      instance = OpenStudio::Model::Lights.new(definition)
      instance.setName("#{space_type.name} Lights")
      instance.setSpaceType(space_type)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no lights, one has been created.")
      instances << instance
    elsif instances.size > 1
      instances.each_with_index do |inst, i|
        next if i.zero?
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
        inst.remove
      end
    end

    # Modify the definition of the instance
    space_type.lights.sort.each do |inst|
      definition = inst.lightsDefinition
      unless lighting_per_area.zero?
        set_lighting_per_area(space_type, definition, lighting_per_area)
      end
      unless lighting_per_person.zero?
        definition.setWattsperPerson(OpenStudio.convert(lighting_per_person.to_f, 'W/person', 'W/person').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set lighting to #{lighting_per_person} W/person.")
      end
      unless lights_frac_to_return_air.zero?
        definition.setReturnAirFraction(lights_frac_to_return_air)
      end
      unless lights_frac_radiant.zero?
        definition.setFractionRadiant(lights_frac_radiant)
      end
      unless lights_frac_visible.zero?
        definition.setFractionVisible(lights_frac_visible)
      end
      # unless lights_frac_replaceable.zero?
      #  definition.setFractionReplaceable(lights_frac_replaceable)
      # end
    end

    # If additional lights are specified, add those too
    additional_lighting_per_area = space_type_properties['additional_lighting_per_area'].to_f
    unless additional_lighting_per_area.zero?
      # Create the lighting definition
      additional_lights_def = OpenStudio::Model::LightsDefinition.new(space_type.model)
      additional_lights_def.setName("#{space_type.name} Additional Lights Definition")
      additional_lights_def.setWattsperSpaceFloorArea(OpenStudio.convert(additional_lighting_per_area.to_f, 'W/ft^2', 'W/m^2').get)
      additional_lights_def.setReturnAirFraction(lights_frac_to_return_air)
      additional_lights_def.setFractionRadiant(lights_frac_radiant)
      additional_lights_def.setFractionVisible(lights_frac_visible)

      # Create the lighting instance and hook it up to the space type
      additional_lights = OpenStudio::Model::Lights.new(additional_lights_def)
      additional_lights.setName("#{space_type.name} Additional Lights")
      additional_lights.setSpaceType(space_type)
    end

  end
end

#apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0)) ⇒ Object

Reduces the SRR to the values specified by the PRM. SRR reduction will be done by shrinking vertices toward the centroid.


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
# File 'lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb', line 174

def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0)

  # If srr_set is between 1.0 and 1.2 set it to the maximum allowed by the NECB.  If srr_set is between 0.0 and 1.0
  # apply whatever was passed.  If srr_set >= 1.2 then set the existing srr of the building to be the necb maximum
  # only if the the srr exceeds this maximum (otherwise leave it to be whatever was modeled).

  # srr_set settings:
  # 0-1:  Remove all skylights and add skylights to match this srr
  # -1:  Remove all skylights and add skylights to match max srr from NECB
  # -2:  Do not apply any srr changes, leave skylights alone (also works for srr > 1)
  # -3:  Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit
  # <-3.1:  Remove all the skylights
  # > 1:  Do nothing

  if srr_set.to_f > 1.0
    return
  elsif srr_set.to_f >= 0.0 && srr_set <= 1.0
    apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f)
    return
  elsif srr_set.to_f >= -1.1 && srr_set <= -0.9
    # Get the maximum NECB srr
    srr_lim = self.get_standards_constant('skylight_to_roof_ratio_max_value')
    apply_max_srr_nrcan(model: model, srr_lim: srr_lim.to_f)
    return
  elsif srr_set.to_f >= -2.1 && srr_set <= -1.9
    return
  elsif srr_set.to_f >= -3.1 && srr_set <= -2.9
    # Continue with the rest of this method, use old method which reduces existing skylight size (if necessary) to
    # meet maximum srr limit
  elsif srr_set < -3.1
    apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f)
    return
  else
    return
  end

  # SRR limit
  srr_lim = self.get_standards_constant('skylight_to_roof_ratio_max_value') * 100.0

  # Loop through all spaces in the model, and
  # per the PNNL PRM Reference Manual, find the areas
  # of each space conditioning category (res, nonres, semi-heated)
  # separately.  Include space multipliers.
  nr_wall_m2 = 0.001 # Avoids divide by zero errors later
  nr_sky_m2 = 0
  res_wall_m2 = 0.001
  res_sky_m2 = 0
  sh_wall_m2 = 0.001
  sh_sky_m2 = 0
  total_roof_m2 = 0.001
  total_subsurface_m2 = 0
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    wall_area_m2 = 0
    sky_area_m2 = 0
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType == 'RoofCeiling'
      # This wall's gross area (including skylight area)
      wall_area_m2 += surface.grossArea * space.multiplier
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        sky_area_m2 += ss.netArea * space.multiplier
      end
    end

    # Determine the space category
    cat = 'NonRes'
    if space_residential?(space)
      cat = 'Res'
    end
    # if space.is_semiheated
    # cat = 'Semiheated'
    # end

    # Add to the correct category
    case cat
    when 'NonRes'
      nr_wall_m2 += wall_area_m2
      nr_sky_m2 += sky_area_m2
    when 'Res'
      res_wall_m2 += wall_area_m2
      res_sky_m2 += sky_area_m2
    when 'Semiheated'
      sh_wall_m2 += wall_area_m2
      sh_sky_m2 += sky_area_m2
    end
    total_roof_m2 += wall_area_m2
    total_subsurface_m2 += sky_area_m2
  end

  # Calculate the SRR of each category
  srr_nr = ((nr_sky_m2 / nr_wall_m2) * 100).round(1)
  srr_res = ((res_sky_m2 / res_wall_m2) * 100).round(1)
  srr_sh = ((sh_sky_m2 / sh_wall_m2) * 100).round(1)
  srr = ((total_subsurface_m2 / total_roof_m2) * 100.0).round(1)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The skylight to roof ratios (SRRs) are: NonRes: #{srr_nr.round}%, Res: #{srr_res.round}%.")


  # Check against SRR limit
  red_nr = srr_nr > srr_lim
  red_res = srr_res > srr_lim
  red_sh = srr_sh > srr_lim

  # Stop here unless windows need reducing
  return true unless srr > srr_lim
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{srr_lim.round}%.")
  # Determine the factors by which to reduce the window / door area
  mult = srr_lim / srr

  # Reduce the subsurface areas
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType == 'RoofCeiling'
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        # Reduce the size of the subsurface
        red = 1.0 - mult
        sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red)
      end
    end
  end

  return true
end

#apply_standard_window_to_wall_ratio(model:, fdwr_set: -1.0)) ⇒ Object

Reduces the WWR to the values specified by the NECB NECB 3.2.1.4


4
5
6
7
8
9
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb', line 4

def apply_standard_window_to_wall_ratio(model:, fdwr_set: -1.0)
  # NECB FDWR limit
  hdd = self.get_necb_hdd18(model)

  # Get the maximum NECB fdwr
  # fdwr_set settings:
  # 0-1:  Remove all windows and add windows to match this fdwr
  # -1:  Remove all windows and add windows to match max fdwr from NECB
  # -2:  Do not apply any fdwr changes, leave windows alone (also works for fdwr > 1)
  # -3:  Use old method which reduces existing window size (if necessary) to meet maximum NECB fdwr limit
  # <-3.1:  Remove all the windows
  # > 1:  Do nothing

  if fdwr_set.to_f > 1.0
    return
  elsif fdwr_set.to_f >= 0.0 and fdwr_set <= 1.0
    apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_set.to_f)
    return
  elsif fdwr_set.to_f >= -1.1 and fdwr_set <= -0.9
    fdwr_lim = (max_fwdr(hdd)).round(3)
    apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_lim.to_f)
    return
  elsif fdwr_set.to_f >= -2.1 and fdwr_set <= -1.9
    return
  elsif fdwr_set.to_f >= -3.1 and fdwr_set <= -2.9
    fdwr_lim = (max_fwdr(hdd) * 100.0).round(1)
    return apply_limit_fdwr(model: model, fdwr_lim: fdwr_lim.to_f)
  elsif fdwr_set < -3.1
    apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_set.to_f)
    return
  end
end

#apply_systems(model:, primary_heating_fuel:, sizing_run_dir:) ⇒ Object

Organizes Zones and assigns them to appropriate systems according to NECB 2011-17 systems spacetype rules in Sec 8. requires requires fuel type to be assigned for each system aspect. Defaults to gas hydronic.


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
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 98

def apply_systems(model:, primary_heating_fuel:, sizing_run_dir:)
  raise('validation of model failed.') unless validate_initial_model(model)
  # Check to see if model is using another vintage of spacetypes. If so overwrite the @standards for the object with the
  # other spacetype data. This is required for correct system mapping.
  template = determine_spacetype_vintage(model)
  unless template == self.class.name
    # Frankenstein the standards data wrt spacetype data.
    @standards_data['space_types'] = Standard.build(template).standards_data['space_types']
  end

  # do a sizing run.
  if model_run_sizing_run(model, "#{sizing_run_dir}/autozone") == false
    raise('autorun sizing run failed!')
  end

  # collect sizing information on each space.
  self.store_space_sizing_loads(model)

  # Set the primary fuel set to default to to specific fuel type.
  if primary_heating_fuel == 'DefaultFuel'
    epw = BTAP::Environment::WeatherFile.new(model.weatherFile.get.path.get)
    primary_heating_fuel = @standards_data['regional_fuel_use'].detect {|fuel_sources| fuel_sources['state_province_regions'].include?(epw.state_province_region)}['fueltype_set']
  end
  # Get fuelset.
  system_fuel_defaults = @standards_data['fuel_type_sets'].detect {|fuel_type_set| fuel_type_set['name'] == primary_heating_fuel}
  raise("fuel_type_sets named #{primary_heating_fuel} not found in fuel_type_sets table.") if system_fuel_defaults.nil?


  # Assign fuel sources.
  boiler_fueltype = system_fuel_defaults['boiler_fueltype']
  baseboard_type = system_fuel_defaults['baseboard_type']
  mau_type = system_fuel_defaults['mau_type']
  mau_heating_coil_type = system_fuel_defaults['mau_heating_coil_type']
  mau_cooling_type = system_fuel_defaults['mau_cooling_type']
  chiller_type = system_fuel_defaults['chiller_type']
  heating_coil_type_sys3 = system_fuel_defaults['heating_coil_type_sys3']
  heating_coil_type_sys4 = system_fuel_defaults['heating_coil_type_sys4']
  heating_coil_type_sys6 = system_fuel_defaults['heating_coil_type_sys6']
  fan_type = system_fuel_defaults['fan_type']

  #remove idealair from zones if any.
  model.getZoneHVACIdealLoadsAirSystems.each(&:remove)
  @hw_loop = create_hw_loop_if_required(baseboard_type,
                                        boiler_fueltype,
                                        mau_heating_coil_type,
                                        model)
  #Rule that all dwelling units have their own zone and system.
  auto_system_dwelling_units(model: model,
                             baseboard_type: baseboard_type,
                             boiler_fueltype: boiler_fueltype,
                             chiller_type: chiller_type,
                             fan_type: fan_type,
                             heating_coil_type_sys3: heating_coil_type_sys3,
                             heating_coil_type_sys4: heating_coil_type_sys4,
                             hw_loop: @hw_loop,
                             heating_coil_type_sys6: heating_coil_type_sys6,
                             mau_cooling_type: mau_cooling_type,
                             mau_heating_coil_type: mau_heating_coil_type,
                             mau_type: mau_type
  )

  #Assign a single system 4 for all wet spaces.. and assign the control zone to the one with the largest load.
  auto_system_wet_spaces(baseboard_type: baseboard_type,
                         boiler_fueltype: boiler_fueltype,
                         heating_coil_type_sys4: heating_coil_type_sys4,
                         model: model)


  #Assign a single system 4 for all storage spaces.. and assign the control zone to the one with the largest load.
  auto_system_storage_spaces(baseboard_type: baseboard_type,
                             boiler_fueltype: boiler_fueltype,
                             heating_coil_type_sys4: heating_coil_type_sys4,
                             model: model)


  #Assign the wild spaces to a single system 4 system with a control zone with the largest load.
  auto_system_wild_spaces(baseboard_type: baseboard_type,
                          heating_coil_type_sys4: heating_coil_type_sys4,
                          model: model)
  # do the regular assignment for the rest and group where possible.
  auto_system_all_other_spaces(model: model,
                               baseboard_type: baseboard_type,
                               boiler_fueltype: boiler_fueltype,
                               chiller_type: chiller_type,
                               fan_type: fan_type,
                               heating_coil_type_sys3: heating_coil_type_sys3,
                               heating_coil_type_sys4: heating_coil_type_sys4,
                               hw_loop: @hw_loop,
                               heating_coil_type_sys6: heating_coil_type_sys6,
                               mau_cooling_type: mau_cooling_type,
                               mau_heating_coil_type: mau_heating_coil_type,
                               mau_type: mau_type
  )
  model_add_swh(model: model, swh_fueltype: system_fuel_defaults['swh_fueltype'])
  model_apply_sizing_parameters(model)
  # set a larger tolerance for unmet hours from default 0.2 to 1.0C
  model.getOutputControlReportingTolerances.setToleranceforTimeHeatingSetpointNotMet(1.0)
  model.getOutputControlReportingTolerances.setToleranceforTimeCoolingSetpointNotMet(1.0)
end

#apply_weather_data(model:, epw_file:) ⇒ Object


215
216
217
218
219
220
221
222
223
# File 'lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb', line 215

def apply_weather_data(model:, epw_file:)
  climate_zone = 'NECB HDD Method'
  # Fix EMS references. Temporary workaround for OS issue #2598
  model_temp_fix_ems_references(model)
  model.getThermostatSetpointDualSetpoints(&:remove)
  model.getYearDescription.setDayofWeekforStartDay('Sunday')
  model_add_design_days_and_weather_file(model, climate_zone, epw_file) # Standards
  model_add_ground_temperatures(model, nil, climate_zone)
end

#are_space_loads_similar?(space_1:, space_2:, surface_percent_difference_tolerance: 0.01, angular_percent_difference_tolerance: 0.001, heating_load_percent_difference_tolerance: 15.0) ⇒ Boolean

This method will determine if the loads on a space are similar. (Exposure, space type, space loads, and schedules, etc)

Returns:

  • (Boolean)

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
568
569
570
571
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 532

def are_space_loads_similar?(space_1:,
                             space_2:,
                             surface_percent_difference_tolerance: 0.01,
                             angular_percent_difference_tolerance: 0.001,
                             heating_load_percent_difference_tolerance: 15.0)
  # Do they have the same space type?
  return false unless space_1.multiplier == space_2.multiplier
  # Ensure that they both have defined spacetypes
  return false if space_1.spaceType.empty?
  return false if space_2.spaceType.empty?
  # ensure that they have the same spacetype.
  return false unless space_1.spaceType.get == space_2.spaceType.get
  # Perform surface comparision. If ranges are within percent_difference_tolerance.. they can be considered the same.
  space_1_floor_area = space_1.floorArea
  space_2_floor_area = space_2.floorArea
  space_1_surface_report = space_surface_report(space_1)
  space_2_surface_report = space_surface_report(space_2)
  #Spaces should have the same number of surface orientations.
  return false unless space_1_surface_report.size == space_2_surface_report.size
  #spaces should have similar loads
  return false unless self.percentage_difference(stored_space_heating_load(space_1), stored_space_heating_load(space_2)) <= heating_load_percent_difference_tolerance
  #Each surface should match
  space_1_surface_report.each do |space_1_surface|
    surface_match = space_2_surface_report.detect do |space_2_surface|
      space_1_surface[:surface_type] == space_2_surface[:surface_type] &&
          space_1_surface[:boundary_condition] == space_2_surface[:boundary_condition] &&
          self.percentage_difference(space_1_surface[:tilt], space_2_surface[:tilt]) <= angular_percent_difference_tolerance &&
          self.percentage_difference(space_1_surface[:azimuth], space_2_surface[:azimuth]) <= angular_percent_difference_tolerance &&
          self.percentage_difference(space_1_surface[:surface_area_to_floor_ratio],
                                     space_2_surface[:surface_area_to_floor_ratio]) <= surface_percent_difference_tolerance &&
          self.percentage_difference(space_1_surface[:glazed_subsurface_area_to_floor_ratio],
                                     space_2_surface[:glazed_subsurface_area_to_floor_ratio]) <= surface_percent_difference_tolerance &&
          self.percentage_difference(space_1_surface[:opaque_subsurface_area_to_floor_ratio],
                                     space_2_surface[:opaque_subsurface_area_to_floor_ratio]) <= surface_percent_difference_tolerance

    end
    return false if surface_match.nil?
  end
  return true
end

#are_zone_loads_similar?(zone_1:, zone_2:) ⇒ Boolean

This method will determine if the loads on a zone are similar. (Exposure, space type, space loads, and schedules, etc)

Returns:

  • (Boolean)

516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 516

def are_zone_loads_similar?(zone_1:, zone_2:)
  #make sure they have the same number of spaces.
  truthes = []
  return false if zone_1.spaces.size != zone_2.spaces.size
  zone_1.spaces.each do |space_1|
    zone_2.spaces.each do |space_2|
      if are_space_loads_similar?(space_1: space_1, space_2: space_2)
        truthes << true
      end
    end
  end
  #truthes sizes should be the same as the # of spaces if all spaces are similar.
  return truthes.size == zone_1.spaces.size
end

#assign_contruction_to_adiabatic_surfaces(model) ⇒ Object


556
557
558
559
560
561
562
563
564
565
566
567
568
569
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb', line 556

def assign_contruction_to_adiabatic_surfaces(model)
  cp02_carpet_pad = OpenStudio::Model::MasslessOpaqueMaterial.new(model)
  cp02_carpet_pad.setName('CP02 CARPET PAD')
  cp02_carpet_pad.setRoughness('VeryRough')
  cp02_carpet_pad.setThermalResistance(0.21648)
  cp02_carpet_pad.setThermalAbsorptance(0.9)
  cp02_carpet_pad.setSolarAbsorptance(0.7)
  cp02_carpet_pad.setVisibleAbsorptance(0.8)

  normalweight_concrete_floor = OpenStudio::Model::StandardOpaqueMaterial.new(model)
  normalweight_concrete_floor.setName('100mm Normalweight concrete floor')
  normalweight_concrete_floor.setRoughness('MediumSmooth')
  normalweight_concrete_floor.setThickness(0.1016)
  normalweight_concrete_floor.setConductivity(2.31)
  normalweight_concrete_floor.setDensity(2322)
  normalweight_concrete_floor.setSpecificHeat(832)

  nonres_floor_insulation = OpenStudio::Model::MasslessOpaqueMaterial.new(model)
  nonres_floor_insulation.setName('Nonres_Floor_Insulation')
  nonres_floor_insulation.setRoughness('MediumSmooth')
  nonres_floor_insulation.setThermalResistance(2.88291975297193)
  nonres_floor_insulation.setThermalAbsorptance(0.9)
  nonres_floor_insulation.setSolarAbsorptance(0.7)
  nonres_floor_insulation.setVisibleAbsorptance(0.7)

  floor_adiabatic_construction = OpenStudio::Model::Construction.new(model)
  floor_adiabatic_construction.setName('Floor Adiabatic construction')
  floor_layers = OpenStudio::Model::MaterialVector.new
  floor_layers << cp02_carpet_pad
  floor_layers << normalweight_concrete_floor
  floor_layers << nonres_floor_insulation
  floor_adiabatic_construction.setLayers(floor_layers)

  g01_13mm_gypsum_board = OpenStudio::Model::StandardOpaqueMaterial.new(model)
  g01_13mm_gypsum_board.setName('G01 13mm gypsum board')
  g01_13mm_gypsum_board.setRoughness('Smooth')
  g01_13mm_gypsum_board.setThickness(0.0127)
  g01_13mm_gypsum_board.setConductivity(0.1600)
  g01_13mm_gypsum_board.setDensity(800)
  g01_13mm_gypsum_board.setSpecificHeat(1090)
  g01_13mm_gypsum_board.setThermalAbsorptance(0.9)
  g01_13mm_gypsum_board.setSolarAbsorptance(0.7)
  g01_13mm_gypsum_board.setVisibleAbsorptance(0.5)

  wall_adiabatic_construction = OpenStudio::Model::Construction.new(model)
  wall_adiabatic_construction.setName('Wall Adiabatic construction')
  wall_layers = OpenStudio::Model::MaterialVector.new
  wall_layers << g01_13mm_gypsum_board
  wall_layers << g01_13mm_gypsum_board
  wall_adiabatic_construction.setLayers(wall_layers)

  m10_200mm_concrete_block_basement_wall = OpenStudio::Model::StandardOpaqueMaterial.new(model)
  m10_200mm_concrete_block_basement_wall.setName('M10 200mm concrete block basement wall')
  m10_200mm_concrete_block_basement_wall.setRoughness('MediumRough')
  m10_200mm_concrete_block_basement_wall.setThickness(0.2032)
  m10_200mm_concrete_block_basement_wall.setConductivity(1.326)
  m10_200mm_concrete_block_basement_wall.setDensity(1842)
  m10_200mm_concrete_block_basement_wall.setSpecificHeat(912)

  basement_wall_construction = OpenStudio::Model::Construction.new(model)
  basement_wall_construction.setName('Basement Wall construction')
  basement_wall_layers = OpenStudio::Model::MaterialVector.new
  basement_wall_layers << m10_200mm_concrete_block_basement_wall
  basement_wall_construction.setLayers(basement_wall_layers)

  basement_floor_construction = OpenStudio::Model::Construction.new(model)
  basement_floor_construction.setName('Basement Floor construction')
  basement_floor_layers = OpenStudio::Model::MaterialVector.new
  basement_floor_layers << m10_200mm_concrete_block_basement_wall
  basement_floor_layers << cp02_carpet_pad
  basement_floor_construction.setLayers(basement_floor_layers)

  model.getSurfaces.sort.each do |surface|
    if surface.outsideBoundaryCondition.to_s == 'Adiabatic'
      if surface.surfaceType.to_s == 'Wall'
        surface.setConstruction(wall_adiabatic_construction)
      else
        surface.setConstruction(floor_adiabatic_construction)
      end
    elsif surface.outsideBoundaryCondition.to_s == 'OtherSideCoefficients'
      # Ground
      if surface.surfaceType.to_s == 'Wall'
        surface.setOutsideBoundaryCondition('Ground')
        surface.setConstruction(basement_wall_construction)
      else
        surface.setOutsideBoundaryCondition('Ground')
        surface.setConstruction(basement_floor_construction)
      end
    end
  end
end

#auto_size_shw_capacity(model, u: 0.45, height_to_radius: 2) ⇒ Object

This calculates the volume and capacity of one mixed tank that is assumed to service all shw in the building u is the tank insulation in W/(m^2*K), height_to_radius is the ratio of tank radius to tank height and is dimensionless


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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb', line 183

def auto_size_shw_capacity(model, u: 0.45, height_to_radius: 2)
  peak_flow_rate = 0
  shw_space_types = []
  space_peak_flows = []
  water_use = 0
  weekly_peak_flow = {
      'Default|Wkdy' => Array.new(24, 0),
      'Sat' => Array.new(24, 0),
      'Sun|Hol' => Array.new(24, 0)
  }
  peak_day_sched = nil
  peak_hour_sched = 0
  peak_flow_sched = 0
  next_hour_day = nil
  next_hour_hour = 0
  next_hour_flow = 0
  total_peak_flow_rate = 0
  shw_spaces = []
  shw_sched_names = []
  # First go through all the spaces in the building and determine and determine their shw requirements
  space_types_table = @standards_data['space_types']
  model.getSpaces.sort.each do |space|
    space_peak_flow = 0
    data = nil
    space_type_name = space.spaceType.get.nameString
    tank_temperature = 60
    # find the specific space_type properties from standard.json
    space_types_table.each do |space_type|
      if space_type_name == (space_type['building_type'] + " " + space_type['space_type'])
        if space_type['necb_hvac_system_selection_type'] == "- undefined -"
          break
          # If there is no service hot water load.. Don't bother adding anything.
        elsif space_type['service_water_heating_peak_flow_per_area'].to_f == 0.0 && space_type['service_water_heating_peak_flow_rate'].to_f == 0.0 || space_type['service_water_heating_schedule'].nil?
          break
        else
          # If there is a service hot water load collect the space information
          data = space_type
          break
        end
      end
    end
    # If there is no service hot water load.. Don't bother adding anything.
    # Skip space types with no data
    next if data.nil?
    space_area = OpenStudio.convert(space.floorArea, 'm^2', 'ft^2').get # ft2
    # Calculate the peak shw flow rate for the space.  Peak flow from JSON file is in US Gal/hr/ft^2
    # space_peak_flow_ind is the peak flow rate for the space while space_peak_flow is the peak flow
    # rate for the space multiplied by the space (ultimately thermal zone) multiplier.  space_peak_flow is used for
    # much of the rest as it reflects how much water is used.  space_peak_flow_ind is recorded and used later on
    # when defining water use equipment.  When when water use equipment is assigned to spaces then the water use
    # by the equipment is multiplied by the space multiplier.  Note that there is a separate water use equipment
    # multiplier as well which is different than the space (ultimately thermal zone) multiplier.
    space_peak_flow_ind = data['service_water_heating_peak_flow_per_area'].to_f * space_area
    space_peak_flow = space_peak_flow_ind * space.multiplier
    #      space_peak_flows << space_peak_flow
    # Add the peak shw flow rate for the space to the total for the entire building
    total_peak_flow_rate += space_peak_flow
    # Get the tank temperature for the space.  This should always be 60 C but I added this part in case something changes in the future.
    if data['service_water_heating_target_temperature'].nil? || (data['service_water_heating_target_temperature'] <= 16)
      tank_temperature = 60
    else
      tank_temperature = data['service_water_heating_target_temperature']
    end
    # Get the shw schedule
    #      shw_sched_names << data['service_water_heating_schedule']
    # 'shw_peakflow_ind_SI' is the shw peak flow rate of the individual space (without the space multiplier)
    space_info = {
        'shw_spaces' => space,
        'shw_peakflow_SI' => OpenStudio.convert(space_peak_flow, 'gal/hr', 'm^3/s').get,
        'shw_peakflow_ind_SI' => OpenStudio.convert(space_peak_flow_ind, 'gal/hr', 'm^3/s').get,
        'shw_temp_SI' => tank_temperature,
        'shw_sched' => data['service_water_heating_schedule']
    }
    shw_spaces << space_info

    # The following gets the water use schedule for space and applies it to the peak flow rate for the space.  This
    # creates an array containing the hourly shw consumption for the space for each day type (Weekday/default, Saturday,
    # Sunday/Holiday).  The hourly shw consumption for each space is added to the array ultimately producing an array
    # containing the hourly shw demand for the entire building.  This is used to determine the peak shw demand
    # hour and rate for the building.  This is different than the overall peak shw demand for the building in that it
    # takes into account the shw schedule.  The peak shw demand hour and rate should always be less than the overall peak
    # shw demand.

    # Cycle through the hash accumulating the shw rates for each day type.
    weekly_peak_flow.sort.each do |day_peak_sched|
      day_sched = []
      # Create the search criteria and retrieve the schedule for the current space and current day type.
      search_criteria = {
          'template' => template,
          'name' => data['service_water_heating_schedule'],
          'day_types' => day_peak_sched[0]
      }
      schedules_table = @standards_data['schedules']

      day_sched = model_find_object(schedules_table, search_criteria)
      # Make sure the schedule is not empty and contains 24 hours.
      if day_sched.empty? || day_sched['values'].size != 24
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.model_add_swh', "The water use schedule called #{data['service_water_heating_schedule']} for #{space_type_name} is corrupted or could not be found.  Please check that the schedules.json file is available and that the schedule names are spelled correctly")
        return false
      end
      # For each hour of the current day type multiply the shw schedule fractional multiplier (representing the fraction of the total shw
      # rate used in that hour) times the overall peak shw rate for the current space.  Add the resulting values to the
      # array tracking hourly shw demand for the building.  Also, determine what the highest hourly demand is for the
      # building.
      day_peak_sched[1].sort.each_with_index do |hour_flow, hour_index|
        weekly_peak_flow[day_peak_sched[0]][hour_index] += day_sched['values'][hour_index] * space_peak_flow
        if weekly_peak_flow[day_peak_sched[0]][hour_index] > peak_flow_sched
          peak_flow_sched = weekly_peak_flow[day_peak_sched[0]][hour_index]
        end
      end
    end
  end
  if shw_spaces.empty?
    space_info = {
        'shw_spaces' => nil,
        'shw_peakflow_SI' => 0,
        'shw_peakflow_ind_SI' => 0,
        'shw_temp_SI' => 60,
        'shw_sched' => []
    }
    shw_spaces << space_info
    tank_param = {
        "tank_volume_SI" => 0,
        "tank_capacity_SI" => 0,
        "max_temp_SI" => 60,
        "loop_peak_flow_rate_SI" => 0,
        "parasitic_loss" => 0,
        "spaces_w_dhw" => shw_spaces
    }
    return tank_param
  end
  next_day_test = nil
  next_hour_test = 0
  # The following loop goes through each hour in the array tracking hourly shw demand to find which hours contain the
  # peak hourly shw demand (this is in case the peak hourly shw demand occurs more than once).  It then determines what
  # the hourly shw demand is for the following hour.  It is meant to determine, of the peak hourly shw times, which has
  # the highest shw demand the following hour.  This is used to determine shw capacity and volume.
  weekly_peak_flow.sort.each do |day_peak_sched|
    day_peak_sched[1].sort.each_with_index do |hour_flow, hour_index|
      if hour_flow == peak_flow_sched
        if hour_index == 23
          next_hour_test = 0
          case day_peak_sched[0]
          when 'Default|Wkdy'
            next_day_test = 'Sat'
          when 'Sat'
            next_day_test = 'Sun|Hol'
          when 'Sun|Hol'
            next_day_test = 'Default|Wkdy'
          end
        else
          next_hour_test = hour_index + 1
          next_day_test = day_peak_sched[0]
        end
        if next_hour_flow < weekly_peak_flow[next_day_test][next_hour_test]
          next_hour_flow = weekly_peak_flow[next_day_test][next_hour_test]
          next_hour_hour = next_hour_test
          next_hour_day = next_day_test
          peak_day_sched = day_peak_sched[0]
          peak_hour_sched = hour_index
        end
      end
    end
  end
  # The shw tank is sized so that it can fulfill the hour with the highest shw needs.  Since the flow is in US Gal/hr
  # No conversion is necessary.
  tank_volume = peak_flow_sched
  # Interperite the fractional shw schedules as being the fraction of the hour that the maximum shw rate is used and determine
  # what this fraction is for the entire building.
  peak_time_fraction = 1 - (peak_flow_sched / total_peak_flow_rate)
  # Assume the shw tank needs some minimum amount of time to recover (avoids requiring a ridiculously high capacity).
  # If the recovery time is to short then the tank needs to hold enough water to service the peak shw hour and the one
  # after.  Then give the tank the entire hour to heat up again.  Note again that since peak flows are per hour, and
  # we are only looking at an hour, no conversion is necessary.
  if peak_time_fraction <= 0.2
    tank_volume += (next_hour_flow)
    peak_time_fraction = 1
  end
  tank_volume_SI = OpenStudio.convert(tank_volume, 'gal', 'm^3').get
  # Determine the tank capacity as the heat output required to heat up the entire volume of the tank in time remaining
  # in the hour after the peak shw draw is stopped (assume water is provided to the building at 15C ).
  max_temp = -273
  shw_spaces.each do |shw_space|
    if shw_space['shw_temp_SI'] > max_temp
      max_temp = shw_space['shw_temp_SI']
    end
  end
  tank_capacity_SI = tank_volume_SI * 1000 * 4180 * (max_temp - 15) / (3600 * peak_time_fraction)
  tank_radius = (tank_volume_SI / (height_to_radius * Math::PI)) ** (1.0 / 3)
  tank_area = 2 * (1 + height_to_radius) * Math::PI * (tank_radius ** 2)
  room_temp = OpenStudio.convert(70, 'F', 'C').get
  parasitic_loss = u * tank_area * (max_temp - room_temp)
  tank_param = {
      "tank_volume_SI" => tank_volume_SI,
      "tank_capacity_SI" => tank_capacity_SI,
      "max_temp_SI" => max_temp,
      "loop_peak_flow_rate_SI" => OpenStudio.convert(total_peak_flow_rate, 'gal/hr', 'm^3/s').get,
      "parasitic_loss" => parasitic_loss,
      "spaces_w_dhw" => shw_spaces
  }
  return tank_param
end

#auto_size_shw_pump_head(model, default: true, pipe_dia_m: 0.01905, kin_visc_SI: 0.0000004736, density_SI: 983, pipe_rough_m: 0.0000015) ⇒ Object

Autosize the pump head by calculating the piping longest piping length and deriving required head from that. If default is set to true then it returns a default pump head of 179532 Pa which is based on the OpenStudio 2.4.1 defaults for a constant speed pump. The method first assumes that the tank and pump are located in the space closest to the center of the bottom of the building. It then assumes that water is delivered to the bottom center of every space that has a demand for shw. It calculates the x, y, and z components of the vector between the shw space and the spaces with demand for shw. The distance of the piping run is calculated by adding the x, y, and z components of the vector (rather than the magnitude of the vector). For the purposes of calculating pressure loss along the pipe bends, and other minor losses are accounted by doubling the calculated length of the pipe. the pipe diameter is defaulted to 0.01905m (3/4“) as recommended by Mike Lubun. The default kinematic viscosity of water is assumed to be that at 60 C (in m^2/s). The default density of water is assumed to be 983 kg/m^3 as per hypertextbook.com/facts/2007/AllenMa.shtml accessed 2018-07-27. The pipe is assumed to be made out of PVC and have a roughness height of 1.5*10^-6 m as per www.pipeflow.com/pipe-pressure-drop-calculations/pipe-roughness accessed on 2018-07-25. Chris Kirney 2018-07-27.


399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb', line 399

def auto_size_shw_pump_head(model, default: true, pipe_dia_m: 0.01905, kin_visc_SI: 0.0000004736, density_SI: 983, pipe_rough_m: 0.0000015)
  return 179532 if default
  mech_room, cond_spaces = find_mech_room(model)
  return 179532 if mech_room.nil? || cond_spaces.nil?
  space_coord_dists = []
  total_peak_flow = 0
  hl_Pas = []
  # Now go through each space with a shw load and determine the x, y, and z components of a vector from the centroid
  # of the floor of the space containing the shw_tank and the centroid of the floor of the given space
  cond_spaces.each do |cond_space|
    # Find the specific space_type properties from standard.json
    spaceType_name = cond_space['space'].spaceType.get.nameString
    sp_type = spaceType_name[15..-1]
    # Including regular expressions in the following match for cases where extra characters, which do not belong, are
    # added to either the space type in the model or the space type reference file.
    sp_type_info = @standards_data['tables']['space_types']['table'].detect do |data|
      ((Regexp.new(data['space_type'].to_s.upcase)).match(sp_type.upcase) || (Regexp.new(sp_type.upcase).match(data['space_type'].to_s.upcase)) || (data['space_type'].to_s.upcase == sp_type.upcase)) and
          data['building_type'].to_s == 'Space Function'
    end

    # If the space type could not be found let the use know and go on to the next space.
    if sp_type_info.nil?
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.model_add_swh', "The space type called #{sp_type} could not be found.  Please check that the schedules.json file is available and that the space types are spelled correctly")
      next
    end
    next if sp_type_info['service_water_heating_peak_flow_per_area'].to_f == 0.0 && sp_type_info['service_water_heating_peak_flow_rate'].to_f == 0.0 || sp_type_info['service_water_heating_schedule'].nil?
    space_area = OpenStudio.convert(cond_space['space'].floorArea, 'm^2', 'ft^2').get # ft2
    # Calculate the peak shw flow rate for the space
    space_peak_flow = (sp_type_info['service_water_heating_peak_flow_per_area'].to_f * space_area) * cond_space['space'].multiplier
    space_peak_flow_SI = OpenStudio.convert(space_peak_flow, 'gal/hr', 'm^3/s').get
    # Determine the total shw peak flow for the building.
    total_peak_flow += space_peak_flow_SI
    # I use centroid for the floor as the location of the source or point of use for the shw system.
    if space_peak_flow_SI > 0
      space_coord_dist = []
      cond_space['space_centroid'].each_with_index do |dist, coord|
        space_coord_dist << (dist - mech_room['space_centroid'][coord]).abs
      end
      space_coord_dists << space_coord_dist
    end
  end
  # The piping run length from the shw tank to a given space is assumed to be the sum of the coordinates of the vector
  # described above.  The longest piping run becomes the one used for sizing.  Note that I double the length of this
  # piping run below when calculating head loss.
  space_coord_dists.sort.each do |space_coord_dist|
    sizing_pipe_length = space_coord_dist[0] + space_coord_dist[1] + space_coord_dist[2]
    # The shw pump is sized by assuming that the sum of the peak shw volume flow rates for each space has to be fed
    # through the longest piping run.  So for the sizing calculations below, the flow rate is the sum of the peak volume
    # flow rates for the entire building.  The length of the piping run is twice the calculated longest piping run
    # described above.
    # Step 1:  Calculate the Reynold's number.  Note kinematic viscosity is set for water at 60 C and pipe diameter is
    #          set to 3/4".  These can be changed by passing different values to the method.  I got the kinematic
    #          viscosity from www.engineeringtoolbox.com/water-dynamic-kinematic-viscosity-d_596.html accessed 2018-07-05.
    #          I got the pipe roughness from www.pipeflow.com/pipe-pressure-drop-calculations/pipe-roughness accessed on
    #          2018-07-25.  I assume 3/4" pipe because that is what Mike Lubun says is used in most cases (unless it
    #          it is for process water but we assume that is not the case).
    # Determine the bulk velocity of the shw through the pipe.
    pipe_vel = 4 * total_peak_flow / (Math::PI * (pipe_dia_m ** 2))
    # Get the Reynolds number.
    re_pipe = (pipe_vel * pipe_dia_m) / kin_visc_SI
    # Step 2:  Figure out what the Darcy-Weisbach friction factor is.
    relative_rough = pipe_rough_m / pipe_dia_m
    f = friction_factor(re_pipe, relative_rough)
    # Step 3:  Calculate the major head loss
    #          Note that you may be thinking that I forgot to divide the last term by 2 in the equation below.  I didn't.
    #          I multiplied the piping length by 2 because I did not take pipe bends etc. into account and I calculate the
    #          maximum piping run in a really approximate way.  Thus I multiply the piping run by 2.  If you can think
    #          of something better please replace what I have.
    # hl is taken from https://neutrium.net/fluid_flow/pressure-loss-in-pipe accessed 2018-07-26 (I added the height
    # component).  Note that while I allow all of the other physical values to be set I assume that you are building on
    # earth hence g is hard coded to 9.81 m/s^2.
    hl_Pa = (f * (sizing_pipe_length / pipe_dia_m) * (pipe_vel ** 2) * density_SI) + density_SI * space_coord_dist[2] * 9.81
    if hl_Pa < 1
      hl_Pa = 1
    end
    hl_Pas << hl_Pa
  end
  # If no spaces with shw were found return the default pump head.
  if hl_Pas.empty?
    return 179532
  else
    return hl_Pas.max_by {|hl| (hl)}
  end
end

#auto_system_all_other_spaces(baseboard_type:, boiler_fueltype:, chiller_type:, fan_type:, heating_coil_type_sys3:, heating_coil_type_sys4:, heating_coil_type_sys6:, hw_loop:, mau_cooling_type:, mau_heating_coil_type:, mau_type:, model:) ⇒ Object

This method will deal with all non wet, non-wild, and non-dwelling units thermal zones.


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
978
979
980
981
982
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 943

def auto_system_all_other_spaces(baseboard_type:,
                                 boiler_fueltype:,
                                 chiller_type:,
                                 fan_type:,
                                 heating_coil_type_sys3:,
                                 heating_coil_type_sys4:,
                                 heating_coil_type_sys6:,
                                 hw_loop:,
                                 mau_cooling_type:,
                                 mau_heating_coil_type:,
                                 mau_type:,
                                 model:
)

  zones = []
  other_spaces = model.getSpaces.select do |space|
    (not is_a_necb_dwelling_unit?(space)) and
        (not is_an_necb_wildcard_space?(space)) and
        (not is_an_necb_storage_space?(space))
  end
  other_spaces.each do |space|
    zones << space.thermalZone.get
  end
  zones.uniq!

  #since dwelling units are all zoned 1:1 to space:zone we simply add the zone to the appropriate btap system.
  create_necb_system(baseboard_type: baseboard_type,
                     boiler_fueltype: boiler_fueltype,
                     chiller_type: chiller_type,
                     fan_type: fan_type,
                     heating_coil_type_sys3: heating_coil_type_sys3,
                     heating_coil_type_sys4: heating_coil_type_sys4,
                     heating_coil_type_sys6: heating_coil_type_sys6,
                     hw_loop: @hw_loop,
                     mau_cooling_type: mau_cooling_type,
                     mau_heating_coil_type: mau_heating_coil_type,
                     mau_type: mau_type,
                     model: model,
                     zones: zones)
end

#auto_system_dwelling_units(baseboard_type:, boiler_fueltype:, chiller_type:, fan_type:, heating_coil_type_sys3:, heating_coil_type_sys4:, heating_coil_type_sys6:, hw_loop:, mau_cooling_type:, mau_heating_coil_type:, mau_type:, model:) ⇒ Object

This methos will ensure that all dwelling units are assigned to a system 1 or 3. There is an option to have a shared AHU or not. Currently set to false. So by default all dwelling units will have their own AHU.


987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 987

def auto_system_dwelling_units(baseboard_type:,
                               boiler_fueltype:,
                               chiller_type:,
                               fan_type:,
                               heating_coil_type_sys3:,
                               heating_coil_type_sys4:,
                               heating_coil_type_sys6:,
                               hw_loop:,
                               mau_cooling_type:,
                               mau_heating_coil_type:,
                               mau_type:,
                               model:
)

  system_zones_hash = {}
  # Detemine if dwelling units have a shared AHU.  If user entered building stories > 4 then set to true.
  dwelling_shared_ahu = model.getBuilding.standardsNumberOfAboveGroundStories.get > 4
  # store dwelling zones into array
  zones = []
  model.getSpaces.select {|space| is_a_necb_dwelling_unit?(space)}.each do |space|
    zones << space.thermalZone.get
  end
  zones.uniq!

  #sort system 1 or 3 used for each dwelling unit as per T8.4.4.8.A NECB 2011-17
  zones.each do |zone|
    system_zones_hash[get_necb_thermal_zone_system_selection(zone)] = [] if system_zones_hash[get_necb_thermal_zone_system_selection(zone)].nil?
    system_zones_hash[get_necb_thermal_zone_system_selection(zone)] << zone
  end

  # go through each system and zones pairs to
  system_zones_hash.each_pair do |system, zones|
    case system
    when 1
      if dwelling_shared_ahu
        add_sys1_unitary_ac_baseboard_heating(model: model,
                                              zones: zones,
                                              mau_type: mau_type,
                                              mau_heating_coil_type: mau_heating_coil_type,
                                              baseboard_type: baseboard_type,
                                              hw_loop: @hw_loop)
      else
        #Create a separate air loop for each unit.
        zones.each do |zone|
          add_sys1_unitary_ac_baseboard_heating(model: model,
                                                zones: [zone],
                                                mau_type: mau_type,
                                                mau_heating_coil_type: mau_heating_coil_type,
                                                baseboard_type: baseboard_type,
                                                hw_loop: @hw_loop)

        end
      end

    when 3
      if dwelling_shared_ahu
        add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model: model,
                                                                                           zones: zones,
                                                                                           heating_coil_type: heating_coil_type_sys3,
                                                                                           baseboard_type: baseboard_type,
                                                                                           hw_loop: @hw_loop)
      else
        #Create a separate air loop for each unit.
        zones.each do |zone|
          add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model: model,
                                                                                             zones: [zone],
                                                                                             heating_coil_type: heating_coil_type_sys3,
                                                                                             baseboard_type: baseboard_type,
                                                                                             hw_loop: @hw_loop)

        end
      end
    end
  end
end

#auto_system_storage_spaces(baseboard_type:, boiler_fueltype:, heating_coil_type_sys4:, model:) ⇒ Object

All wet spaces will be on their own system 4 AHU.


1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 1087

def auto_system_storage_spaces(baseboard_type:,
                               boiler_fueltype:,
                               heating_coil_type_sys4:,
                               model:)
  #Determine what zones are wet zones.
  tz = []
  model.getSpaces.select {|space|
    is_an_necb_storage_space?(space)}.each do |space|
    tz << space.thermalZone.get
  end
  tz.uniq!
  #create a system 4 for the  zones.
  unless tz.empty?
    add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model,
                                                                 zones: tz,
                                                                 heating_coil_type: heating_coil_type_sys4,
                                                                 baseboard_type: baseboard_type,
                                                                 hw_loop: @hw_loop)
  end
end

#auto_system_wet_spaces(baseboard_type:, boiler_fueltype:, heating_coil_type_sys4:, model:) ⇒ Object

All wet spaces will be on their own system 4 AHU.


1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 1064

def auto_system_wet_spaces(baseboard_type:,
                           boiler_fueltype:,
                           heating_coil_type_sys4:,
                           model:)
  #Determine what zones are wet zones.
  wet_tz = []
  model.getSpaces.select {|space|
    is_an_necb_wet_space?(space)}.each do |space|
    wet_tz << space.thermalZone.get
  end
  wet_tz.uniq!
  #create a system 4 for the wet zones.
  unless wet_tz.empty?
    add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model,
                                                                 zones: wet_tz,
                                                                 heating_coil_type: heating_coil_type_sys4,
                                                                 baseboard_type: baseboard_type,
                                                                 hw_loop: @hw_loop)
  end
end

#auto_system_wild_spaces(baseboard_type:, heating_coil_type_sys4:, model:) ⇒ Object

All wild spaces will be on a single system 4 ahu with the largests heating load zone being the control zone.


1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 1110

def auto_system_wild_spaces(baseboard_type:,
                            heating_coil_type_sys4:,
                            model:
)

  zones = []
  model.getSpaces.select {|space|
    not is_an_necb_wet_space?(space) and is_an_necb_wildcard_space?(space)}.each do |space|
    zones << space.thermalZone.get
  end
  zones.uniq!
  unless zones.empty?
    #create a system 4 for the wild zones.
    add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model,
                                                                 zones: zones,
                                                                 heating_coil_type: heating_coil_type_sys4,
                                                                 baseboard_type: baseboard_type,
                                                                 hw_loop: @hw_loop)
  end
end

#auto_zone_all_other_spaces(model) ⇒ Object

This method will find all the spaces that are not wet, wild or dwelling units and zone them. It will try to determine if the spaces are similar based on exposure and load and blend those spaces into the same zone. It will not merge spaces from different floors, since this will impact Chris Kirneys costing algorithms.


336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 336

def auto_zone_all_other_spaces(model)
  other_tz_array = Array.new
  #iterate through all non wildcard spaces.
  model.getSpaces.select {|space| not is_a_necb_dwelling_unit?(space) and not is_an_necb_wildcard_space?(space)}.each do |space|
    #skip if already assigned to a thermal zone.
    next unless space.thermalZone.empty?
    #create new zone for this space based on the space name.
    zone = OpenStudio::Model::ThermalZone.new(model)
    tz_name = "ALL_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory().get.name}_SCH=#{ determine_dominant_schedule([space])}"
    zone.setName(tz_name)
    #sets space mulitplier unless it is nil or 1.
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end
    #Assign space to the new zone.
    space.setThermalZone(zone)

    # Add a thermostat
    space_type_name = space.spaceType.get.name.get
    thermostat_name = space_type_name + ' Thermostat'
    thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name)
    if thermostat.empty?
      # The thermostat name for the spacetype should exist.
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}")
    else
      thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get
      zone.setThermostatSetpointDualSetpoint(thermostat_clone)
      # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing.
      ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model)
      ideal_loads.addToThermalZone(zone)
    end
    # Go through other spaces and if you find something with similar loads on the same floor, add it to the zone.
    model.getSpaces.select {|space| not is_a_necb_dwelling_unit?(space) and not is_an_necb_wildcard_space?(space)}.each do |space_target|
      if space_target.thermalZone.empty?
        if are_space_loads_similar?(space_1: space, space_2: space_target) and space.buildingStory().get == space_target.buildingStory().get # added since chris needs zones to not span floors for costing.
          space_target.setThermalZone(zone)
        end
      end
    end
    other_tz_array << zone
  end
  return other_tz_array
end

#auto_zone_dwelling_units(model) ⇒ Object

Dwelling unit spaces need to have their own HVAC system. Thankfully NECB defines what spacetypes are considering dwelling units and have been defined as spaces that are openstudio-standards/standards/necb/NECB2011/data/necb_hvac_system_selection_type.json as spaces that are Residential/Accomodation and Sleeping area' this is determine by the is_a_necb_dwelling_unit? method. The thermostat is set by the space-type schedule. This will return an array of TZ.


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
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 254

def auto_zone_dwelling_units(model)
  dwelling_tz_array = []
  # ----Dwelling units----------- will always have their own system per unit, so they should have their own thermal zone.
  model.getSpaces.select {|space| is_a_necb_dwelling_unit?(space)}.each do |space|
    zone = OpenStudio::Model::ThermalZone.new(model)
    zone.setName("DU_BT=#{space.spaceType.get.standardsBuildingType.get}_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory().get.name}_SCH#{ determine_dominant_schedule([space])}")
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end
    space.setThermalZone(zone)

    # Add a thermostat based on the space type.
    space_type_name = space.spaceType.get.name.get
    thermostat_name = space_type_name + ' Thermostat'
    thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name)
    if thermostat.empty?
      # The thermostat name for the spacetype should exist.
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}")
    else
      thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get
      zone.setThermostatSetpointDualSetpoint(thermostat_clone)
      # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing.
      OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model).addToThermalZone(zone)
    end
    dwelling_tz_array << zone
  end
  return dwelling_tz_array
end

#auto_zone_wet_spaces(model) ⇒ Object

Something that the code is silent on are smelly humid areas that should not be on the same system as the rest of the

building.. These are the 'wet' spaces and have been defined as locker and washroom areas.. These will be put under

their own single system 4 system. These will be set to the dominant floor schedule.


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
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 286

def auto_zone_wet_spaces(model)
  wet_zone_array = Array.new
  model.getSpaces.select {|space| is_an_necb_wet_space?(space)}.each do |space|
    #if this space was already assigned to something skip it.
    next unless space.thermalZone.empty?
    # get space to dominant schedule
    dominant_schedule = determine_dominant_schedule(space.model.getSpaces)
    #create new TZ and set space to the zone.
    zone = OpenStudio::Model::ThermalZone.new(model)
    space.setThermalZone(zone)
    tz_name = "WET_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory().get.name}_SCH#{dominant_schedule}"
    zone.setName(tz_name)
    #Set multiplier from the original tz multiplier.
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end

    #this method will determine if the right schedule was used for this wet & wild space if not.. it will reset the space
    # to use the correct schedule version of the wet and wild space type.
    adjust_wildcard_spacetype_schedule(space, dominant_schedule)
    #Find spacetype thermostat and assign it to the zone.
    thermostat_name = space.spaceType.get.name.get + ' Thermostat'
    thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name)
    if thermostat.empty?
      # The thermostat name for the spacetype should exist.
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}-")
    else
      thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get
      zone.setThermostatSetpointDualSetpoint(thermostat_clone)
      # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing.
      ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model)
      ideal_loads.addToThermalZone(zone)
    end
    # Go through other spaces to see if there are similar spaces with similar loads on the same floor that can be grouped.
    model.getSpaces.select {|s| is_an_necb_wet_space?(s)}.each do |space_target|
      if space_target.thermalZone.empty?
        if are_space_loads_similar?(space_1: space, space_2: space_target) && space.buildingStory().get == space_target.buildingStory().get # added since chris needs zones to not span floors for costing.
          adjust_wildcard_spacetype_schedule(space_target, dominant_schedule)
          space_target.setThermalZone(zone)
        end
      end
    end
    wet_zone_array << zone
  end
  return wet_zone_array
end

#auto_zone_wild_spaces(model) ⇒ Object

This will take all the wildcard spaces and merge them to be supported by a system 4. The control zone will be the zone that has the largest heating load per area.


382
383
384
385
386
387
388
389
390
391
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
418
419
420
421
422
423
424
425
426
427
428
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
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/autozone.rb', line 382

def auto_zone_wild_spaces(model)
  other_tz_array = Array.new
  #iterate through wildcard spaces.
  model.getSpaces.select {|space| is_an_necb_wildcard_space?(space) and not is_an_necb_wet_space?(space)}.each do |space|
    #skip if already assigned to a thermal zone.
    next unless space.thermalZone.empty?
    #create new zone for this space based on the space name.
    zone = OpenStudio::Model::ThermalZone.new(model)
    tz_name = "WILD_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory().get.name}_SCH=#{determine_dominant_schedule(space.model.getSpaces)}"
    zone.setName(tz_name)
    #sets space mulitplier unless it is nil or 1.
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end
    #Assign space to the new zone.
    space.setThermalZone(zone)

    #lets keep the wild schedules to be the same as what dominate the floor.
    dominant_floor_schedule = determine_dominant_schedule(space.model.getSpaces)

    adjust_wildcard_spacetype_schedule(space, dominant_floor_schedule)

    # Add a thermostat
    space_type_name = space.spaceType.get.name.get
    thermostat_name = space_type_name + ' Thermostat'
    thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name)
    if thermostat.empty?
      # The thermostat name for the spacetype should exist.
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}")
    else
      thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get
      zone.setThermostatSetpointDualSetpoint(thermostat_clone)
      # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing.
      ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model)
      ideal_loads.addToThermalZone(zone)
    end
    # Go through other spaces and if you find something with similar loads on the same floor, add it to the zone.
    model.getSpaces.select {|space| is_an_necb_wildcard_space?(space) and not is_an_necb_wet_space?(space)}.each do |space_target|
      if space_target.thermalZone.empty?
        if are_space_loads_similar?(space_1: space, space_2: space_target) and
            space.buildingStory().get == space_target.buildingStory().get # added since chris needs zones to not span floors for costing.
          space_target.setThermalZone(zone)
        end
      end
    end
    other_tz_array << zone
  end
  return other_tz_array

  wild_zone_array = []
  #Get a list of all the wild spaces.
  model.getSpaces.select {|space| is_an_necb_wildcard_space?(space) and not is_an_necb_wet_space?(space)}.each do |space|
    #if this space was already assigned to something skip it.
    next unless space.thermalZone.empty?
    #find adjacent spaces to the current space.
    adj_spaces = space_get_adjacent_spaces_with_shared_wall_areas(space, true)
    adj_spaces = adj_spaces.map {|key, value| key}

    # find unassigned adjacent wild spaces that have not been assigned that have the same multiplier these will be
    # lumped together in the same zone.
    wild_adjacent_spaces = adj_spaces.select {|adj_space|
      is_an_necb_wildcard_space?(adj_space) and
          not is_an_necb_wet_space?(adj_space) and
          adj_space.thermalZone.empty? and
          space_multiplier_map[space.name.to_s] == space_multiplier_map[adj_space.name.to_s]
    }
    #put them all together.
    wild_adjacent_spaces << space

    # Get adjacent candidate foster zones. Must not be a wildcard space and must not be linked to another space incase
    # it is part of a mirrored space.
    other_adjacent_spaces = adj_spaces.select do |adj_space|
      is_an_necb_wildcard_space?(adj_space) == false and
          adj_space.thermalZone.get.spaces.size == 1 and
          space_multiplier_map[space.name.to_s] == space_multiplier_map[adj_space.name.to_s]
    end

    #If there are adjacent spaces that fit the above criteria.
    # We will need to set each space to the dominant floor schedule by setting the spaces spacetypes to that
    # schedule version and eventually set it to a system 4
    unless other_adjacent_spaces.empty?
      #assign the space(s) to the adjacent thermal zone.
      schedule_type = determine_dominant_schedule(space.buildingStory.get.spaces)
      zone = other_adjacent_spaces.first.thermalZone.get
      wild_adjacent_spaces.each do |space|
        adjust_wildcard_spacetype_schedule(space, schedule_type)
        space.setThermalZone(zone)
      end
    end

    #create new TZ and set space to the zone.
    zone = OpenStudio::Model::ThermalZone.new(model)
    space.setThermalZone(zone)
    zone.setName("Wild-ZN:BT=#{space.spaceType.get.standardsBuildingType.get}:ST=#{space.spaceType.get.standardsSpaceType.get}:FL=#{space.buildingStory().get.name}:")
    #Set multiplier from the original tz multiplier.
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end

    # Set space to dominant

    dominant_floor_schedule = determine_dominant_schedule(space.buildingStory().get.spaces)
    #this method will determine if the right schedule was used for this wet & wild space if not.. it will reset the space
    # to use the correct schedule version of the wet and wild space type.
    adjust_wildcard_spacetype_schedule(space, dominant_floor_schedule)
    #Find spacetype thermostat and assign it to the zone.
    thermostat_name = space.spaceType.get.name.get + ' Thermostat'
    thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name)
    if thermostat.empty?
      # The thermostat name for the spacetype should exist.
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}")
    else
      thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get
      zone.setThermostatSetpointDualSetpoint(thermostat_clone)
      # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing.
      ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model)
      ideal_loads.addToThermalZone(zone)
    end
    # Go through other spaces to see if there are similar spaces with similar loads on the same floor that can be grouped.
    model.getSpaces.select {|s| is_an_necb_wildcard_space?(s) and not is_an_necb_wet_space?(s)}.each do |space_target|
      if space_target.thermalZone.empty?
        if are_space_loads_similar?(space_1: space, space_2: space_target) &&
            space.buildingStory().get == space_target.buildingStory().get # added since chris needs zones to not span floors for costing.
          adjust_wildcard_spacetype_schedule(space_target, dominant_floor_schedule)
          space_target.setThermalZone(zone)
        end
      end
    end
    wild_zone_array << zone
  end
  return wild_zone_array
end

#boiler_hot_water_apply_efficiency_and_curves(boiler_hot_water) ⇒ Bool

Applies the standard efficiency ratings and typical performance curves to this object.

Parameters:

  • boiler_hot_water (OpenStudio::Model::BoilerHotWater)

    the object to modify

Returns:

  • (Bool)

    true if successful, false if not


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
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 464

def boiler_hot_water_apply_efficiency_and_curves(boiler_hot_water)
  successfully_set_all_properties = false

  # Define the criteria to find the boiler properties
  # in the hvac standards data set.
  search_criteria = boiler_hot_water_find_search_criteria(boiler_hot_water)
  fuel_type = search_criteria['fuel_type']
  fluid_type = search_criteria['fluid_type']

  # Get the capacity
  capacity_w = boiler_hot_water_find_capacity(boiler_hot_water)

  # Check if secondary and/or modulating boiler required
  if capacity_w / 1000.0 >= 352.0
    if boiler_hot_water.name.to_s.include?('Primary Boiler')
      boiler_capacity = capacity_w
      boiler_hot_water.setBoilerFlowMode('LeavingSetpointModulated')
      boiler_hot_water.setMinimumPartLoadRatio(0.25)
    elsif boiler_hot_water.name.to_s.include?('Secondary Boiler')
      boiler_capacity = 0.001
    end
  elsif ((capacity_w / 1000.0) >= 176.0) && ((capacity_w / 1000.0) < 352.0)
    boiler_capacity = capacity_w / 2
  elsif (capacity_w / 1000.0) <= 176.0
    if boiler_hot_water.name.to_s.include?('Primary Boiler')
      boiler_capacity = capacity_w
    elsif boiler_hot_water.name.to_s.include?('Secondary Boiler')
      boiler_capacity = 0.001
    end
  end
  boiler_hot_water.setNominalCapacity(boiler_capacity)

  # Convert capacity to Btu/hr
  capacity_btu_per_hr = OpenStudio.convert(boiler_capacity, 'W', 'Btu/hr').get
  capacity_kbtu_per_hr = OpenStudio.convert(boiler_capacity, 'W', 'kBtu/hr').get

  # Get the boiler properties
  boiler_table = @standards_data['boilers']
  blr_props = model_find_object(boiler_table, search_criteria, capacity_btu_per_hr)
  unless blr_props
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.BoilerHotWater', "For #{boiler_hot_water.name}, cannot find boiler properties, cannot apply efficiency standard.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # Make the EFFFPLR curve
  eff_fplr = model_add_curve(boiler_hot_water.model, blr_props['efffplr'])
  if eff_fplr
    boiler_hot_water.setNormalizedBoilerEfficiencyCurve(eff_fplr)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.BoilerHotWater', "For #{boiler_hot_water.name}, cannot find eff_fplr curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Get the minimum efficiency standards
  thermal_eff = nil

  # If specified as AFUE
  unless blr_props['minimum_annual_fuel_utilization_efficiency'].nil?
    min_afue = blr_props['minimum_annual_fuel_utilization_efficiency']
    thermal_eff = afue_to_thermal_eff(min_afue)
    new_comp_name = "#{boiler_hot_water.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_afue} AFUE"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.BoilerHotWater', "For #{template}: #{boiler_hot_water.name}: #{fuel_type} #{fluid_type} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; AFUE = #{min_afue}")
  end

  # If specified as thermal efficiency
  unless blr_props['minimum_thermal_efficiency'].nil?
    thermal_eff = blr_props['minimum_thermal_efficiency']
    new_comp_name = "#{boiler_hot_water.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{thermal_eff} Thermal Eff"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.BoilerHotWater', "For #{template}: #{boiler_hot_water.name}: #{fuel_type} #{fluid_type} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; Thermal Efficiency = #{thermal_eff}")
  end

  # If specified as combustion efficiency
  unless blr_props['minimum_combustion_efficiency'].nil?
    min_comb_eff = blr_props['minimum_combustion_efficiency']
    thermal_eff = combustion_eff_to_thermal_eff(min_comb_eff)
    new_comp_name = "#{boiler_hot_water.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_comb_eff} Combustion Eff"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.BoilerHotWater', "For #{template}: #{boiler_hot_water.name}: #{fuel_type} #{fluid_type} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; Combustion Efficiency = #{min_comb_eff}")
  end

  # Set the name
  boiler_hot_water.setName(new_comp_name)

  # Set the efficiency values
  unless thermal_eff.nil?
    boiler_hot_water.setNominalThermalEfficiency(thermal_eff)
  end

  return successfully_set_all_properties
end

#check_boolean_value(value, varname) ⇒ Object


1879
1880
1881
1882
1883
1884
# File 'lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb', line 1879

def check_boolean_value (value, varname)
  return true if value =~ (/^(true|t|yes|y|1)$/i)
  return false if value.empty? || value =~ (/^(false|f|no|n|0)$/i)

  raise ArgumentError.new "invalid value for #{varname}: #{value}"
end

#chiller_electric_eir_apply_efficiency_and_curves(chiller_electric_eir, clg_tower_objs) ⇒ Bool

Applies the standard efficiency ratings and typical performance curves to this object.

Returns:

  • (Bool)

    true if successful, false if not


558
559
560
561
562
563
564
565
566
567
568
569
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 558

def chiller_electric_eir_apply_efficiency_and_curves(chiller_electric_eir, clg_tower_objs)
  chillers = standards_data['chillers']

  # Define the criteria to find the chiller properties
  # in the hvac standards data set.
  search_criteria = chiller_electric_eir_find_search_criteria(chiller_electric_eir)
  cooling_type = search_criteria['cooling_type']
  condenser_type = search_criteria['condenser_type']
  compressor_type = search_criteria['compressor_type']

  # Get the chiller capacity
  capacity_w = chiller_electric_eir_find_capacity(chiller_electric_eir)

  # All chillers must be modulating down to 25% of their capacity
  chiller_electric_eir.setChillerFlowMode('LeavingSetpointModulated')
  chiller_electric_eir.setMinimumPartLoadRatio(0.25)
  chiller_electric_eir.setMinimumUnloadingRatio(0.25)
  if (capacity_w / 1000.0) < 2100.0
    if chiller_electric_eir.name.to_s.include? 'Primary Chiller'
      chiller_capacity = capacity_w
    elsif chiller_electric_eir.name.to_s.include? 'Secondary Chiller'
      chiller_capacity = 0.001
    end
  else
    chiller_capacity = capacity_w / 2.0
  end
  chiller_electric_eir.setReferenceCapacity(chiller_capacity)

  # Convert capacity to tons
  capacity_tons = OpenStudio.convert(chiller_capacity, 'W', 'ton').get

  # Get the chiller properties
  chlr_table = @standards_data['chillers']
  chlr_props = model_find_object(chlr_table, search_criteria, capacity_tons, Date.today)
  unless chlr_props
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find chiller properties, cannot apply standard efficiencies or curves.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # Make the CAPFT curve
  cool_cap_ft = model_add_curve(chiller_electric_eir.model, chlr_props['capft'])
  if cool_cap_ft
    chiller_electric_eir.setCoolingCapacityFunctionOfTemperature(cool_cap_ft)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find cool_cap_ft curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Make the EIRFT curve
  cool_eir_ft = model_add_curve(chiller_electric_eir.model, chlr_props['eirft'])
  if cool_eir_ft
    chiller_electric_eir.setElectricInputToCoolingOutputRatioFunctionOfTemperature(cool_eir_ft)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find cool_eir_ft curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Make the EIRFPLR curve
  # which may be either a CurveBicubic or a CurveQuadratic based on chiller type
  cool_plf_fplr = model_add_curve(chiller_electric_eir.model, chlr_props['eirfplr'])
  if cool_plf_fplr
    chiller_electric_eir.setElectricInputToCoolingOutputRatioFunctionOfPLR(cool_plf_fplr)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find cool_plf_fplr curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Set the efficiency value
  kw_per_ton = nil
  cop = nil
  if chlr_props['minimum_full_load_efficiency']
    kw_per_ton = chlr_props['minimum_full_load_efficiency']
    cop = kw_per_ton_to_cop(kw_per_ton)
    chiller_electric_eir.setReferenceCOP(cop)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find minimum full load efficiency, will not be set.")
    successfully_set_all_properties = false
  end

  # Set cooling tower properties now that the new COP of the chiller is set
  if chiller_electric_eir.name.to_s.include? 'Primary Chiller'
    # Single speed tower model assumes 25% extra for compressor power
    tower_cap = capacity_w * (1.0 + 1.0 / chiller_electric_eir.referenceCOP)
    if (tower_cap / 1000.0) < 1750
      clg_tower_objs[0].setNumberofCells(1)
    else
      clg_tower_objs[0].setNumberofCells((tower_cap / (1000 * 1750) + 0.5).round)
    end
    clg_tower_objs[0].setFanPoweratDesignAirFlowRate(0.015 * tower_cap)
  end

  # Append the name with size and kw/ton
  chiller_electric_eir.setName("#{chiller_electric_eir.name} #{capacity_tons.round}tons #{kw_per_ton.round(1)}kW/ton")
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.ChillerElectricEIR', "For #{template}: #{chiller_electric_eir.name}: #{cooling_type} #{condenser_type} #{compressor_type} Capacity = #{capacity_tons.round}tons; COP = #{cop.round(1)} (#{kw_per_ton.round(1)}kW/ton)")

  return successfully_set_all_properties
end

#coil_cooling_dx_multi_speed_apply_efficiency_and_curves(coil_cooling_dx_multi_speed, sql_db_vars_map) ⇒ Bool

Applies the standard efficiency ratings and typical performance curves to this object.

Returns:

  • (Bool)

    true if successful, false if not


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
845
846
847
848
849
850
851
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
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
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
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 799

def coil_cooling_dx_multi_speed_apply_efficiency_and_curves(coil_cooling_dx_multi_speed, sql_db_vars_map)
  successfully_set_all_properties = true

  # Define the criteria to find the chiller properties
  # in the hvac standards data set.
  search_criteria = {}
  cooling_type = coil_cooling_dx_multi_speed.condenserType
  search_criteria['cooling_type'] = cooling_type

  # TODO: Standards - add split system vs single package to model
  # For now, assume single package as default
  sub_category = 'Single Package'

  # Determine the heating type if unitary or zone hvac
  heat_pump = false
  heating_type = nil
  containing_comp = nil
  if coil_cooling_dx_multi_speed.airLoopHVAC.empty?
    if coil_cooling_dx_multi_speed.containingHVACComponent.is_initialized
      containing_comp = coil_cooling_dx_multi_speed.containingHVACComponent.get
      if containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.is_initialized
        htg_coil = containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.get.heatingCoil
        if htg_coil.to_CoilHeatingDXMultiSpeed.is_initialized
          heat_pump = true
          heating_type = 'Electric Resistance or None'
        elsif htg_coil.to_CoilHeatingGasMultiStage.is_initialized
          heating_type = 'All Other'
        end
      end # TODO: Add other unitary systems
    elsif coil_cooling_dx_multi_speed.containingZoneHVACComponent.is_initialized
      containing_comp = coil_cooling_dx_multi_speed.containingZoneHVACComponent.get
      if containing_comp.to_ZoneHVACPackagedTerminalAirConditioner.is_initialized
        sub_category = 'PTAC'
        htg_coil = containing_comp.to_ZoneHVACPackagedTerminalAirConditioner.get.heatingCoil
        if htg_coil.to_CoilHeatingElectric.is_initialized
          heating_type = 'Electric Resistance or None'
        elsif htg_coil.to_CoilHeatingWater.is_initialized || htg_coil.to_CoilHeatingGas.is_initialized || htg_col.to_CoilHeatingGasMultiStage
          heating_type = 'All Other'
        end
      end # TODO: Add other zone hvac systems
    end
  end

  # Add the heating type to the search criteria
  unless heating_type.nil?
    search_criteria['heating_type'] = heating_type
  end

  search_criteria['subcategory'] = sub_category

  # Get the coil capacity
  capacity_w = nil
  clg_stages = stages
  if clg_stages.last.grossRatedTotalCoolingCapacity.is_initialized
    capacity_w = clg_stages.last.grossRatedTotalCoolingCapacity.get
  elsif coil_cooling_dx_multi_speed.autosizedSpeed4GrossRatedTotalCoolingCapacity.is_initialized
    capacity_w = coil_cooling_dx_multi_speed.autosizedSpeed4GrossRatedTotalCoolingCapacity.get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name} capacity is not available, cannot apply efficiency standard.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # Volume flow rate
  flow_rate4 = nil
  if clg_stages.last.ratedAirFlowRate.is_initialized
    flow_rate4 = clg_stages.last.ratedAirFlowRate.get
  elsif coil_cooling_dx_multi_speed.autosizedSpeed4RatedAirFlowRate.is_initialized
    flow_rate4 = coil_cooling_dx_multi_speed.autosizedSpeed4RatedAirFlowRate.get
  end

  # Set number of stages
  stage_cap = []
  num_stages = (capacity_w / (66.0 * 1000.0) + 0.5).round
  num_stages = [num_stages, 4].min
  if num_stages == 1
    stage_cap[0] = capacity_w / 2.0
    stage_cap[1] = 2.0 * stage_cap[0]
    stage_cap[2] = stage_cap[1] + 0.1
    stage_cap[3] = stage_cap[2] + 0.1
  else
    stage_cap[0] = 66.0 * 1000.0
    stage_cap[1] = 2.0 * stage_cap[0]
    if num_stages == 2
      stage_cap[2] = stage_cap[1] + 0.1
      stage_cap[3] = stage_cap[2] + 0.1
    elsif num_stages == 3
      stage_cap[2] = 3.0 * stage_cap[0]
      stage_cap[3] = stage_cap[2] + 0.1
    elsif num_stages == 4
      stage_cap[2] = 3.0 * stage_cap[0]
      stage_cap[3] = 4.0 * stage_cap[0]
    end
  end
  # set capacities, flow rates, and sensible heat ratio for stages
  (0..3).each do |istage|
    clg_stages[istage].setGrossRatedTotalCoolingCapacity(stage_cap[istage])
    clg_stages[istage].setRatedAirFlowRate(flow_rate4 * stage_cap[istage] / capacity_w)
  end

  # Convert capacity to Btu/hr
  capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
  capacity_kbtu_per_hr = OpenStudio.convert(capacity_w, 'W', 'kBtu/hr').get

  # Lookup efficiencies depending on whether it is a unitary AC or a heat pump
  ac_props = nil
  heat_pump_table = @standards_data['heat_pumps']
  unitary_acs_table = @standards_data['unitary_acs']
  ac_props = if heat_pump == true
               model_find_object(heat_pump_table, search_criteria, capacity_btu_per_hr, Date.today)
             else
               model_find_object(unitary_acs_table, search_criteria, capacity_btu_per_hr, Date.today)
             end

  # Check to make sure properties were found
  if ac_props.nil?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find efficiency info, cannot apply efficiency standard.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # Make the COOL-CAP-FT curve
  cool_cap_ft = model_add_curve(model, ac_props['cool_cap_ft'], standards)
  if cool_cap_ft
    clg_stages.each do |stage|
      stage.setTotalCoolingCapacityFunctionofTemperatureCurve(cool_cap_ft)
    end
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_cap_ft curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Make the COOL-CAP-FFLOW curve
  cool_cap_fflow = model_add_curve(model, ac_props['cool_cap_fflow'], standards)
  if cool_cap_fflow
    clg_stages.each do |stage|
      stage.setTotalCoolingCapacityFunctionofFlowFractionCurve(cool_cap_fflow)
    end
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_cap_fflow curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Make the COOL-EIR-FT curve
  cool_eir_ft = model_add_curve(model, ac_props['cool_eir_ft'], standards)
  if cool_eir_ft
    clg_stages.each do |stage|
      stage.setEnergyInputRatioFunctionofTemperatureCurve(cool_eir_ft)
    end
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_eir_ft curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Make the COOL-EIR-FFLOW curve
  cool_eir_fflow = model_add_curve(model, ac_props['cool_eir_fflow'], standards)
  if cool_eir_fflow
    clg_stages.each do |stage|
      stage.setEnergyInputRatioFunctionofFlowFractionCurve(cool_eir_fflow)
    end
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_eir_fflow curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Make the COOL-PLF-FPLR curve
  cool_plf_fplr = model_add_curve(model, ac_props['cool_plf_fplr'], standards)
  if cool_plf_fplr
    clg_stages.each do |stage|
      stage.setPartLoadFractionCorrelationCurve(cool_plf_fplr)
    end
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name}, cannot find cool_plf_fplr curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Get the minimum efficiency standards
  cop = nil

  if coil_dx_subcategory(coil_cooling_dx_multi_speed) == 'PTAC'
    ptac_eer_coeff_1 = ac_props['ptac_eer_coefficient_1']
    ptac_eer_coeff_2 = ac_props['ptac_eer_coefficient_2']
    capacity_btu_per_hr = 7000 if capacity_btu_per_hr < 7000
    capacity_btu_per_hr = 15_000 if capacity_btu_per_hr > 15_000
    ptac_eer = ptac_eer_coeff_1 + (ptac_eer_coeff_2 * capacity_btu_per_hr)
    cop = eer_to_cop(ptac_eer)
    # self.setName("#{self.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{ptac_eer}EER")
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{ptac_eer}EER"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; EER = #{ptac_eer}")
  end

  # If specified as SEER
  unless ac_props['minimum_seasonal_energy_efficiency_ratio'].nil?
    min_seer = ac_props['minimum_seasonal_energy_efficiency_ratio']
    cop = seer_to_cop(min_seer)
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_seer}SEER"
    #      self.setName("#{self.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_seer}SEER")
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; SEER = #{min_seer}")
  end

  # If specified as EER
  unless ac_props['minimum_energy_efficiency_ratio'].nil?
    min_eer = ac_props['minimum_energy_efficiency_ratio']
    cop = eer_to_cop(min_eer)
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_eer}EER"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; EER = #{min_eer}")
  end

  # if specified as SEER (heat pump)
  unless ac_props['minimum_seasonal_efficiency'].nil?
    min_seer = ac_props['minimum_seasonal_efficiency']
    cop = seer_to_cop(min_seer)
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_seer}SEER"
    #      self.setName("#{self.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_seer}SEER")
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; SEER = #{min_seer}")
  end

  # If specified as EER (heat pump)
  unless ac_props['minimum_full_load_efficiency'].nil?
    min_eer = ac_props['minimum_full_load_efficiency']
    cop = eer_to_cop(min_eer)
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_eer}EER"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; EER = #{min_eer}")
  end

  sql_db_vars_map[new_comp_name] = name.to_s
  coil_cooling_dx_multi_speed.setName(new_comp_name)

  # Set the efficiency values

  unless cop.nil?
    clg_stages.each do |istage|
      istage.setGrossRatedCoolingCOP(cop)
    end
  end

  return sql_db_vars_map
end

#coil_heating_gas_apply_efficiency_and_curves(coil_heating_gas) ⇒ Bool

Applies the standard efficiency ratings and typical performance curves to this object.

Returns:

  • (Bool)

    true if successful, false if not


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
# File 'lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb', line 756

def coil_heating_gas_apply_efficiency_and_curves(coil_heating_gas)
  successfully_set_all_properties = true

  # Define the search criteria
  search_criteria = coil_heating_gas_find_search_criteria

  # Get the coil capacity
  <