Class: OpenStudio::Model::PlantLoop

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

Overview

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

Instance Method Summary collapse

Instance Method Details

#apply_performance_rating_method_baseline_pump_power(template) ⇒ Object

Todo: I think it makes more sense to sense the motor efficiency right there… But actually it’s completely irrelevant… you could set at 0.9 and just calculate the pressurise rise to have your 19 W/GPM or whatever



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

def apply_performance_rating_method_baseline_pump_power(template)
  
  # Determine the pumping power per

  # flow based on loop type.

  pri_w_per_gpm = nil
  sec_w_per_gpm = nil

  sizing_plant = self.sizingPlant
  loop_type = sizing_plant.loopType    
  
  case loop_type
  when 'Heating'
  
    has_district_heating = false
    self.supplyComponents.each do |sc|
      if sc.to_DistrictHeating.is_initialized
        has_district_heating = true
      end
    end

    if has_district_heating # District HW

      pri_w_per_gpm = 14.0
    else # HW 

      pri_w_per_gpm = 19.0
    end

  when 'Cooling'

    has_district_cooling = false
    self.supplyComponents.each do |sc|
      if sc.to_DistrictCooling.is_initialized
        has_district_cooling = true
      end
    end

    has_secondary_pump = false
    self.demandComponents.each do |sc|
      if sc.to_PumpConstantSpeed.is_initialized || sc.to_PumpVariableSpeed.is_initialized
        has_secondary_pump = true
      end
    end  

    if has_district_cooling # District CHW

      pri_w_per_gpm = 16.0
    elsif has_secondary_pump # Primary/secondary CHW

      pri_w_per_gpm = 9.0
      sec_w_per_gpm = 13.0
    else # Primary only CHW

      pri_w_per_gpm = 22.0
    end

  when 'Condenser'
  
    # TODO prm condenser loop pump power

    pri_w_per_gpm = 19.0

  end

  # Modify all the primary pumps

  self.supplyComponents.each do |sc|
    if sc.to_PumpConstantSpeed.is_initialized
      pump = sc.to_PumpConstantSpeed.get
      pump.set_performance_rating_method_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
    elsif sc.to_PumpVariableSpeed.is_initialized
      pump = sc.to_PumpVariableSpeed.get
      pump.set_performance_rating_method_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
    end
  end
  
  # Modify all the secondary pumps

  self.demandComponents.each do |sc|
    if sc.to_PumpConstantSpeed.is_initialized
      pump = sc.to_PumpConstantSpeed.get
      pump.set_performance_rating_method_pressure_rise_and_motor_efficiency(sec_w_per_gpm, template)
    elsif sc.to_PumpVariableSpeed.is_initialized
      pump = sc.to_PumpVariableSpeed.get
      pump.set_performance_rating_method_pressure_rise_and_motor_efficiency(sec_w_per_gpm, template)
    end
  end

  return true

end

#apply_performance_rating_method_baseline_pumping_type(template) ⇒ Object



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
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
# File 'lib/openstudio-standards/standards/Standards.PlantLoop.rb', line 568

def apply_performance_rating_method_baseline_pumping_type(template)
  
  sizing_plant = self.sizingPlant
  loop_type = sizing_plant.loopType    
  
  case loop_type
  when 'Heating'
    
    # Hot water systems

  
    # Determine the minimum area to determine

    # pumping type.

    minimum_area_ft2 = nil
    case template
    when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
      minimum_area_ft2 = 120000
    end
  
    # Determine the area served

    area_served_m2 = self.total_floor_area_served
    area_served_ft2 = OpenStudio.convert(area_served_m2,'m^2','ft^2').get
  
    # Determine the pump type 

    control_type = 'Riding Curve'
    if area_served_ft2 > minimum_area_ft2
      control_type = 'VSD No Reset'
    end
    
    # Modify all the primary pumps

    self.supplyComponents.each do |sc|
      if sc.to_PumpVariableSpeed.is_initialized
        pump = sc.to_PumpVariableSpeed.get
        pump.set_control_type(control_type)
      end
    end
 
     # Report out the pumping type

    unless control_type.nil?
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, pump type is #{control_type}.")
    end

  when 'Cooling'

    # Chilled water systems

  
    # Determine the pumping type.

    # For some templates, this is

    # based on area.  For others, it is built

    # on cooling capacity.

    pri_control_type = nil
    sec_control_type = nil
    case template
    when '90.1-2004'

      minimum_area_ft2 = 120000
      
      # Determine the area served

      area_served_m2 = self.total_floor_area_served
      area_served_ft2 = OpenStudio.convert(area_served_m2,'m^2','ft^2').get
    
      # Determine the primary pump type 

      pri_control_type = 'Riding Curve'
      
      # Determine the secondary pump type

      sec_control_type = 'Riding Curve'
      if area_served_ft2 > minimum_area_ft2
        sec_control_type = 'VSD No Reset'
      end
    
    when '90.1-2007', '90.1-2010', '90.1-2013'
      
      minimum_cap_tons = 300
      
      # Determine the capacity

      cap_w = self.total_cooling_capacity
      cap_tons = OpenStudio.convert(cap_w,'m^2','ft^2').get
    
      # Determine the primary pump type 

      pri_control_type = 'Riding Curve'
      
      # Determine the secondary pump type

      sec_control_type = 'Riding Curve'
      if cap_tons > minimum_cap_tons
        sec_control_type = 'VSD No Reset'
      end
 
    end
    
    # Report out the pumping type

    unless pri_control_type.nil?
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, primary pump type is #{pri_control_type}.")
    end

    unless sec_control_type.nil?
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, secondary pump type is #{sec_control_type}.")
    end      
    
    # Modify all the primary pumps

    self.supplyComponents.each do |sc|
      if sc.to_PumpVariableSpeed.is_initialized
        pump = sc.to_PumpVariableSpeed.get
        pump.set_control_type(pri_control_type)
      end
    end    
  
    # Modify all the secondary pumps

    self.demandComponents.each do |sc|
      if sc.to_PumpVariableSpeed.is_initialized
        pump = sc.to_PumpVariableSpeed.get
        pump.set_control_type(sec_control_type)
      end
    end      

  when 'Condenser'
  
    # Condenser water systems

  
    # All condenser water loops are constant flow

    control_type = 'Riding Curve'

    # Report out the pumping type

    unless control_type.nil?
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, pump type is #{control_type}.")
    end
    
    # Modify all primary pumps

    self.supplyComponents.each do |sc|
      if sc.to_PumpVariableSpeed.is_initialized
        pump = sc.to_PumpVariableSpeed.get
        pump.set_control_type(control_type)
      end
    end

  end
  
  return true

end

#apply_performance_rating_method_baseline_temperatures(template) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/openstudio-standards/standards/Standards.PlantLoop.rb', line 136

def apply_performance_rating_method_baseline_temperatures(template)

  sizing_plant = self.sizingPlant
  loop_type = sizing_plant.loopType
  case loop_type
  when 'Heating'

    # Loop properties

    # G3.1.3.3 - HW Supply at 180°F, return at 130°F

    hw_temp_f = 180
    hw_delta_t_r = 50
    min_temp_f = 50
    
    hw_temp_c = OpenStudio.convert(hw_temp_f,'F','C').get
    hw_delta_t_k = OpenStudio.convert(hw_delta_t_r,'R','K').get
    min_temp_c = OpenStudio.convert(min_temp_f,'F','C').get

    sizing_plant.setDesignLoopExitTemperature(hw_temp_c)
    sizing_plant.setLoopDesignTemperatureDifference(hw_delta_t_k)
    self.setMinimumLoopTemperature(min_temp_c)

    # ASHRAE Appendix G - G3.1.3.4 (for ASHRAE 90.1-2004, 2007 and 2010)

    # HW reset: 180°F at 20°F and below, 150°F at 50°F and above

    self.enable_supply_water_temperature_reset

    # Boiler properties

    self.supplyComponents.each do |sc|
      if sc.to_BoilerHotWater.is_initialized
        boiler = sc.to_BoilerHotWater.get
        boiler.setDesignWaterOutletTemperature(hw_temp_c)
      end
    end   

  when 'Cooling'
  
    # Loop properties

    # G3.1.3.8 - LWT 44 / EWT 56

    chw_temp_f = 44
    chw_delta_t_r = 12
    min_temp_f = 34
    max_temp_f = 200
    # For water-cooled chillers this is the water temperature entering the condenser (e.g., leaving the cooling tower).

    ref_cond_wtr_temp_f = 85

    chw_temp_c = OpenStudio.convert(chw_temp_f,'F','C').get
    chw_delta_t_k = OpenStudio.convert(chw_delta_t_r,'R','K').get
    min_temp_c = OpenStudio.convert(min_temp_f,'F','C').get
    max_temp_c = OpenStudio.convert(max_temp_f,'F','C').get
    ref_cond_wtr_temp_c = OpenStudio.convert(ref_cond_wtr_temp_f,'F','C').get

    sizing_plant.setDesignLoopExitTemperature(chw_temp_c)
    sizing_plant.setLoopDesignTemperatureDifference(chw_delta_t_k)
    self.setMinimumLoopTemperature(min_temp_c)
    self.setMaximumLoopTemperature(max_temp_c)

    # ASHRAE Appendix G - G3.1.3.9 (for ASHRAE 90.1-2004, 2007 and 2010)

    # ChW reset: 44°F at 80°F and above, 54°F at 60°F and below

    self.enable_supply_water_temperature_reset
    
    # Chiller properties

    self.supplyComponents.each do |sc|
      if sc.to_ChillerElectricEIR.is_initialized
        chiller = sc.to_ChillerElectricEIR.get
        chiller.setReferenceLeavingChilledWaterTemperature(chw_temp_c)
        chiller.setReferenceEnteringCondenserFluidTemperature(ref_cond_wtr_temp_c)
      end
    end
    
  when 'Condenser'
  
    # G3.1.3.11 - LCnWT 85°F or 10°F approaching design wet bulb temperature, whichever is lower

    # Design Temperature rise of 10°F => Range: 10°F

    lcnwt_f = 85   # See notes and proposed alternative below, if we want to actually check the design days...

    range_t_r = 10
    lcnwt_c = OpenStudio.convert(lcnwt_f,'F','C').get
    range_t_k = OpenStudio.convert(range_t_r,'R','K').get

    # Typical design of min temp is really around 40°F (that's what basin heaters, when used, are sized for usually)

    min_temp_f = 34
    max_temp_f = 200
    min_temp_c = OpenStudio.convert(min_temp_f,'F','C').get
    max_temp_c = OpenStudio.convert(max_temp_f,'F','C').get

    sizing_plant.setDesignLoopExitTemperature(lcnwt_c)
    sizing_plant.setLoopDesignTemperatureDifference(range_t_k)
    self.setMinimumLoopTemperature(min_temp_c)
    self.setMaximumLoopTemperature(max_temp_c)

    # G3.1.3.11 - Tower shall be controlled to maintain a 70°F LCnWT where weather permits

    # Use a SetpointManager:FollowOutdoorAirTemperature

    float_down_to_f = 70
    float_down_to_c = OpenStudio.convert(float_down_to_f,'F','C').get

    # Todo: Problem is what to set for Offset Temperature Difference (=approach):

    # * if unreasonably low approach, fan runs full blast and energy consumption is penalized

    # * if too high, you don't get as much energy savings...

    # "LCnWT 85°F or 10°F approaching design wet bulb temperature, whichever is lower" ==> approach is maximum 10, could be less depending on design conditions

    # In most cases in the US a tower will be sized on CTI conditions, 78°F WB, so usually 7°F approach.

    # Could also check the design days, but begs the question of finding the right one to begin with if you have several...

    # You'll need to deal with potentially different 'Humidity Indicating Type'

    #

    # See https://unmethours.com/question/12530/appendix-g-condenser-water-temperature-reset-in-energyplus/

    # See http://www.comnet.org/mgp/content/cooling-towers?purpose=0#footnote1_do6jpuh


    # This is an example of how jmarrec would implement checking the design days

=begin
summer_dday_wbs = []
model.getDesignDays.each do |dd|
model.getDesignDays.each do |dd|
  if dd.dayType == 'SummerDesignDay' && dd.humidityIndicatingType == 'Wetbulb'
    summer_dday_wbs << dd.humidityIndicatingConditionsAtMaximumDryBulb
  end
end
end

# Then take worst case condition (max), or the average?
design_inlet_wb_c = summer_dday_wbs.max
design_inlet_wb_f = OpenStudio.convert(design_inlet_wb_c,'C','F').get
lcnwt_f = 85
lcnwt_10f_approach = design_inlet_wb_f+10
lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85



=end


    design_inlet_wb_f = 78
    design_approach_r = 7
    design_inlet_wb_c = OpenStudio.convert(design_inlet_wb_f,'F','C').get
    design_approach_k = OpenStudio.convert(design_approach_r,'R','K').get

    cw_t_stpt_manager = OpenStudio::Model::SetpointManagerFollowOutdoorAirTemperature.new(self.model)
    cw_t_stpt_manager.setReferenceTemperatureType('OutdoorAirWetBulb')
    cw_t_stpt_manager.setMaximumSetpointTemperature(lcnwt_c)
    cw_t_stpt_manager.setMinimumSetpointTemperature(float_down_to_c)
    cw_t_stpt_manager.setOffsetTemperatureDifference(design_approach_k)
    cw_t_stpt_manager.addToNode(self.supplyOutletNode)

    # Cooling Tower properties

    self.supplyComponents.each do |sc|
      if sc.to_CoolingTowerSingleSpeed.is_initialized
        ct = sc.to_CoolingTowerSingleSpeed.get
        ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
        ct.setDesignApproachTemperature(design_approach_k)
        ct.setDesignRangeTemperature(range_t_k)
      elsif sc.to_CoolingTowerTwoSpeed.is_initialized
        ct = sc.to_CoolingTowerTwoSpeed.get
        ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
        ct.setDesignApproachTemperature(design_approach_k)
        ct.setDesignRangeTemperature(range_t_k)
      elsif sc.to_CoolingTowerVariableSpeed.is_initialized
        ct = sc.to_CoolingTowerVariableSpeed.get
        ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
        ct.setDesignApproachTemperature(design_approach_k)
        ct.setDesignRangeTemperature(range_t_k)
      elsif sc.to_CoolingTowerPerformanceYorkCalc.is_initialized
        ct = sc.to_CoolingTowerPerformanceYorkCalc.get
        ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
        ct.setDesignApproachTemperature(design_approach_k)
        ct.setDesignRangeTemperature(range_t_k)
      elsif sc.to_CoolingTowerPerformanceCoolTools.is_initialized
        ct = sc.to_CoolingTowerPerformanceCoolTools.get
        ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
        ct.setDesignApproachTemperature(design_approach_k)
        ct.setDesignRangeTemperature(range_t_k)
      end

    end

  end

  return true

end

#apply_performance_rating_method_number_of_boilers(template) ⇒ Object



707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
# File 'lib/openstudio-standards/standards/Standards.PlantLoop.rb', line 707

def apply_performance_rating_method_number_of_boilers(template)
  
  # Skip non-heating plants

  return true unless self.sizingPlant.loopType == 'Heating'

  # Determine the minimum area to determine

  # number of boilers.

  minimum_area_ft2 = nil
  case template
  when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
    minimum_area_ft2 = 15000
  end

  # Determine the area served

  area_served_m2 = self.total_floor_area_served
  area_served_ft2 = OpenStudio.convert(area_served_m2,'m^2','ft^2').get

  # Do nothing if only one boiler is required

  return true if area_served_ft2 < minimum_area_ft2

  # Get all existing boilers

  boilers = []
  self.supplyComponents.each do |sc|
    if sc.to_BoilerHotWater.is_initialized
      boilers << sc.to_BoilerHotWater.get
    end
  end
  
  # Ensure there is only 1 boiler to start

  first_boiler = nil
  if boilers.size == 0
    return true
  elsif boilers.size > 1
    OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}, found #{boilers.size}, cannot split up per performance rating method baseline requirements.")
  else
    first_boiler = boilers[0]
  end
  
  # Clone the existing boiler and create

  # a new branch for it

  second_boiler = first_boiler.clone(self.model)
  if second_boiler.to_BoilerHotWater.is_initialized
    second_boiler = second_boiler.to_BoilerHotWater.get
  else
    OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}, could not clone boiler #{first_boiler.name}, cannot apply the performance rating method number of boilers.")
    return false
  end
  self.addSupplyBranchForComponent(second_boiler)
  final_boilers = [first_boiler, second_boiler]
  OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}, added a second boiler.")


  # Set the sizing factor for all boilers evenly and Rename the boilers

  sizing_factor = (1.0/final_boilers.size).round(2)
  final_boilers.each_with_index do |boiler, i|
    boiler.setSizingFactor(sizing_factor)
    boiler.setName("#{first_boiler.name} #{i+1} of #{final_boilers.size}")
  end
  
  # Set the equipment to stage sequentially

  self.setLoadDistributionScheme('SequentialLoad')

  return true

end

#apply_performance_rating_method_number_of_chillers(template) ⇒ Object



773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
# File 'lib/openstudio-standards/standards/Standards.PlantLoop.rb', line 773

def apply_performance_rating_method_number_of_chillers(template)
  
  # Skip non-cooling plants

  return true unless self.sizingPlant.loopType == 'Cooling'

  # Determine the number and type of chillers

  num_chillers = nil
  chiller_cooling_type = nil
  chiller_compressor_type = nil
  case template
  when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
    
    # Determine the capacity of the loop

    cap_w = self.total_cooling_capacity
    cap_tons = OpenStudio.convert(cap_w,'W','ton').get
  
    if cap_tons <= 300
      num_chillers = 1
      chiller_cooling_type = 'WaterCooled'
      chiller_compressor_type = 'Rotary Screw'
    elsif cap_tons > 300 && cap_tons < 600
      num_chillers = 2
      chiller_cooling_type = 'WaterCooled'
      chiller_compressor_type = 'Rotary Screw'
    else
      # Max capacity of a single chiller

      max_cap_ton = 800.0
      num_chillers = (cap_tons/max_cap_ton).floor + 1
      # Must be at least 2 chillers

      num_chillers +=1 if num_chillers == 1
      chiller_cooling_type = 'WaterCooled'
      chiller_compressor_type = 'Centrifugal'        
    end

  end
  
  # Get all existing chillers

  chillers = []
  self.supplyComponents.each do |sc|
    if sc.to_ChillerElectricEIR.is_initialized
      chillers << sc.to_ChillerElectricEIR.get
    end
  end

  # Ensure there is only 1 chiller to start

  first_chiller = nil
  if chillers.size == 0
    return true
  elsif chillers.size > 1
    OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}, found #{chillers.size} chillers, cannot split up per performance rating method baseline requirements.")
  else
    first_chiller = chillers[0]
  end
  
  # Determine the per-chiller capacity

  # and sizing factor

  per_chiller_sizing_factor = (1.0/num_chillers).round(2)
  # This is unused

  per_chiller_cap_tons = cap_tons / num_chillers

  # Set the sizing factor and the chiller type: could do it on the first chiller before cloning it, but renaming warrants looping on chillers anyways

  
  # Add any new chillers

  final_chillers = [first_chiller]
  (num_chillers-1).times do
    #new_chiller = OpenStudio::Model::ChillerElectricEIR.new(self.model)

    # TODO renable the cloning of the chillers after curves are shared resources

    # Should be good to go since 1.10.2 (?)

    new_chiller = first_chiller.clone(self.model)
    if new_chiller.to_ChillerElectricEIR.is_initialized
      new_chiller = new_chiller.to_ChillerElectricEIR.get
    else
      OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}, could not clone chiller #{first_chiller.name}, cannot apply the performance rating method number of chillers.")
      return false
    end
    self.addSupplyBranchForComponent(new_chiller)
    final_chillers << new_chiller
  end

  # Set the sizing factor and the chiller types

  final_chillers.each_with_index do |final_chiller, i|
    final_chiller.setName("#{template} #{chiller_cooling_type} #{chiller_compressor_type} Chiller #{i+1} of #{final_chillers.size}")
    final_chiller.setSizingFactor(per_chiller_sizing_factor)
    final_chiller.setCondenserType(chiller_cooling_type)
  end
  OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}, there are #{final_chillers.size} #{chiller_cooling_type} #{chiller_compressor_type} chillers.")
  
  # Set the equipment to stage sequentially

  self.setLoadDistributionScheme('SequentialLoad')

  return true

end

#apply_standard_controls(template, climate_zone) ⇒ Bool

Apply all standard required controls to the plantloop

Returns:

  • (Bool)

    returns true if successful, false if not



9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/openstudio-standards/standards/Standards.PlantLoop.rb', line 9

def apply_standard_controls(template, climate_zone)
  
  # Variable flow system

  if self.is_variable_flow_required(template)
    self.enable_variable_flow(template)
  end
  
  # Supply water temperature reset

  if self.is_supply_water_temperature_reset_required(template)
    self.enable_supply_water_temperature_reset
  end

end

#applySizingValuesObject

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



13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.PlantLoop.rb', line 13

def applySizingValues

  maximum_loop_flow_rate = self.autosizedMaximumLoopFlowRate
  if maximum_loop_flow_rate.is_initialized
    self.setMaximumLoopFlowRate(maximum_loop_flow_rate.get) 
  end

  plant_loop_volume = self.autosizedPlantLoopVolume
  if plant_loop_volume.is_initialized
    self.setPlantLoopVolume(plant_loop_volume.get) 
  end
  
end

#autosizeObject

Sets all auto-sizeable fields to autosize



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

def autosize
  OpenStudio::logFree(OpenStudio::Warn, "openstudio.sizing.PlantLoop", ".autosize not yet implemented for #{self.iddObject.type.valueDescription}.")
end

#autosizedMaximumLoopFlowRateObject

returns the autosized maximum loop flow rate as an optional double



28
29
30
31
32
# File 'lib/openstudio-standards/hvac_sizing/HVACSizing.PlantLoop.rb', line 28

def autosizedMaximumLoopFlowRate

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

#autosizedPlantLoopVolumeObject

returns the autosized plant loop volume as an optional double



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

def autosizedPlantLoopVolume

  return self.model.getAutosizedValue(self, 'Plant Loop Volume', 'm3')
  
end

#enable_supply_water_temperature_resetObject



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

def enable_supply_water_temperature_reset

  # Get the current setpoint manager on the outlet node

  # and determine if already has temperature reset

  spms = self.supplyOutletNode.setpointManagers
  spms.each do |spm|
    if spm.to_SetpointManagerOutdoorAirReset
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset is already enabled.")
      return false
    end
  end

  # Get the design water temperature

  sizing_plant = self.sizingPlant
  design_temp_c = sizing_plant.loopDesignExitTemperature
  design_temp_f = OpenStudio.convert(design_temp_c,'C','F').get
  loop_type = self.loopType
  
  # Apply the reset, depending on the type of loop.

  case loop_type
  when 'Heating'
 
    # Hot water as-designed when cold outside

    hwt_at_lo_oat_f = design_temp_f
    hwt_at_lo_oat_c = OpenStudio.convert(hwt_at_lo_oat_f, 'F', 'C').get
    # 30F decrease when it's hot outside,

    # and therefore less heating capacity is likely required.

    decrease_f = 30.0
    hwt_at_hi_oat_f = hwt_at_lo_oat_f - decrease_f 
    hwt_at_hi_oat_c = OpenStudio.convert(hwt_at_hi_oat_f, 'C', 'F').get

    # Define the high and low outdoor air temperatures

    lo_oat_f = 20
    lo_oat_c = OpenStudio.convert(lo_oat_f, 'F', 'C').get
    hi_oat_f = 50
    hi_oat_c = OpenStudio.convert(hi_oat_f, 'F', 'C').get
    
    # Create a setpoint manager

    hwt_oa_reset = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(self.model)
    hwt_oa_reset.setName("#{self.name} HW Temp Reset")
    hwt_oa_reset.setControlVariable('Temperature')
    hwt_oa_reset.setSetpointatOutdoorLowTemperature(hwt_at_lo_oat_c)
    hwt_oa_reset.setOutdoorLowTemperature(lo_oat_c)
    hwt_oa_reset.setSetpointatOutdoorHighTemperature(hwt_at_hi_oat_c)
    hwt_oa_reset.setOutdoorHighTemperature(hi_oat_c)
    hwt_oa_reset.addToNode(self.supplyOutletNode)
  
    OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: hot water temperature reset from #{hwt_at_lo_oat_f.round}F to #{hwt_at_hi_oat_f}F between outdoor air temps of #{lo_oat_f.round}F and #{hi_oat_f}F.")
  
  when 'Cooling'
    
    # Chilled water as-designed when hot outside

    chwt_at_hi_oat_f = design_temp_f
    chwt_at_hi_oat_c = OpenStudio.convert(chwt_at_hi_oat_f, 'F', 'C').get
    # 10F increase when it's cold outside,

    # and therefore less cooling capacity is likely required.

    increase_f = 10.0
    chwt_at_lo_oat_f = chwt_at_hi_oat_f + increase_f
    chwt_at_lo_oat_c = OpenStudio.convert(chwt_at_lo_oat_f, 'F', 'C').get
    
    # Define the high and low outdoor air temperatures

    lo_oat_f = 50
    lo_oat_c = OpenStudio.convert(lo_oat_f, 'F', 'C').get
    hi_oat_f = 70
    hi_oat_c = OpenStudio.convert(hi_oat_f, 'F', 'C').get
    
    # Create a setpoint manager

    chwt_oa_reset = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(self.model)
    chwt_oa_reset.setName("#{self.name} CHW Temp Reset")
    chwt_oa_reset.setControlVariable('Temperature')
    chwt_oa_reset.setSetpointatOutdoorLowTemperature(chwt_at_lo_oat_c)
    chwt_oa_reset.setOutdoorLowTemperature(lo_oat_c)
    chwt_oa_reset.setSetpointatOutdoorHighTemperature(chwt_at_hi_oat_c)
    chwt_oa_reset.setOutdoorHighTemperature(hi_oat_c)
    chwt_oa_reset.addToNode(self.supplyOutletNode)

    OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: chilled water temperature reset from #{chwt_at_hi_oat_f}F to #{chwt_at_lo_oat_f.round}F between outdoor air temps of #{hi_oat_f}F and #{lo_oat_f.round}F.")      
    
  else
    
    OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}: cannot enable supply water temperature reset for a #{loop_type} loop.")
    return false
  
  end

  return true

end

#enable_variable_flow(template) ⇒ Object



23
24
25
# File 'lib/openstudio-standards/standards/Standards.PlantLoop.rb', line 23

def enable_variable_flow(template)

end

#find_maximum_loop_flow_rateDouble

find maximum_loop_flow_rate

Returns:

  • (Double)

    maximum_loop_flow_rate m^3/s



923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
# File 'lib/openstudio-standards/standards/Standards.PlantLoop.rb', line 923

def find_maximum_loop_flow_rate()

  # Get the maximum_loop_flow_rate

  maximum_loop_flow_rate = nil
  if self.maximumLoopFlowRate.is_initialized
    maximum_loop_flow_rate = self.maximumLoopFlowRate.get
  elsif self.autosizedMaximumLoopFlowRate.is_initialized
    maximum_loop_flow_rate = self.autosizedMaximumLoopFlowRate.get
  else
    OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{self.name} maximum loop flow rate is not available.")
  end

  return maximum_loop_flow_rate

end

#is_supply_water_temperature_reset_required(template) ⇒ Object



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

def is_supply_water_temperature_reset_required(template)

  reset_required = false

  case template
  when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
    
    # Not required before 90.1-2004

    return reset_required
    
  when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
    
    # Not required for variable flow systems

    if is_variable_flow_system
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset not required for variable flow systems per 6.5.4.3 Exception b.")
      return reset_required
    end
    
    # Determine the capacity of the system

    heating_capacity_w = self.total_heating_capacity
    cooling_capacity_w = self.total_cooling_capacity
    
    heating_capacity_btu_per_hr = OpenStudio.convert(heating_capacity_w,'W','Btu/hr').get
    cooling_capacity_btu_per_hr = OpenStudio.convert(cooling_capacity_w,'W','Btu/hr').get
   
    # Compare against capacity minimum requirement

    min_cap_btu_per_hr = 300000
    if heating_capacity_btu_per_hr > min_cap_btu_per_hr 
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset is required because heating capacity of #{heating_capacity_btu_per_hr.round} Btu/hr exceeds the minimum threshold of #{min_cap_btu_per_hr.round} Btu/hr.")
      reset_required = true
    elsif cooling_capacity_btu_per_hr > min_cap_btu_per_hr
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset is required because cooling capacity of #{cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum threshold of #{min_cap_btu_per_hr.round} Btu/hr.")
      reset_required = true
    else
      OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset is not required because capacity is less than minimum of #{min_cap_btu_per_hr.round} Btu/hr.")
    end
  
  end

  return reset_required

end

#is_variable_flow_systemObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/openstudio-standards/standards/Standards.PlantLoop.rb', line 27

def is_variable_flow_system
  
  variable_flow = false
  
  # Modify all the primary pumps

  self.supplyComponents.each do |sc|
    if sc.to_PumpVariableSpeed.is_initialized
      variable_flow = true
    end
  end
  
  # Modify all the secondary pumps

  self.demandComponents.each do |sc|
    if sc.to_PumpVariableSpeed.is_initialized
      variable_flow = true
    end
  end
  
  return variable_flow

end

#total_cooling_capacityDouble

Get the total cooling capacity for the plant loop

Returns:

  • (Double)

    total cooling capacity units = Watts (W)



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

def total_cooling_capacity

  # Sum the cooling capacity for all cooling components

  # on the plant loop.

  total_cooling_capacity_w = 0
  self.supplyComponents.each do |sc|
    # ChillerElectricEIR

    if sc.to_ChillerElectricEIR.is_initialized
      chiller = sc.to_ChillerElectricEIR.get
      if chiller.referenceCapacity.is_initialized
        total_cooling_capacity_w += chiller.referenceCapacity.get
      elsif chiller.autosizedReferenceCapacity.is_initialized
        total_cooling_capacity_w += chiller.autosizedReferenceCapacity.get
      else
        OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} capacity of #{chiller.name} is not available, total cooling capacity of plant loop will be incorrect when applying standard.")
      end
    end
  end

  total_cooling_capacity_tons = OpenStudio.convert(total_cooling_capacity_w,'W','ton').get
  OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, cooling capacity is #{total_cooling_capacity_tons.round} tons of refrigeration.")    
  
  return total_cooling_capacity_w

end

#total_floor_area_servedObject



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/openstudio-standards/standards/Standards.PlantLoop.rb', line 503

def total_floor_area_served
  
  sizing_plant = self.sizingPlant
  loop_type = sizing_plant.loopType
  
  # Get all the coils served by this loop

  coils = []
  case loop_type
  when 'Heating'  
    self.demandComponents.each do |dc|
      if dc.to_CoilHeatingWater.is_initialized
        coils << dc.to_CoilHeatingWater.get
      end
    end
  when 'Cooling'
    self.demandComponents.each do |dc|
      if dc.to_CoilCoolingWater.is_initialized
        coils << dc.to_CoilCoolingWater.get
      end
    end
  else
    return 0.0
  end
  
  # The coil can either be on an airloop (as a main heating coil)

  # in an HVAC Component (like a unitary system on an airloop),

  # or in a Zone HVAC Component (like a fan coil).

  zones_served = []
  coils.each do |coil|
  
    if coil.airLoopHVAC.is_initialized
      air_loop = coil.airLoopHVAC.get
      zones_served += air_loop.thermalZones
    elsif coil.containingHVACComponent.is_initialized
      containing_comp = coil.containingHVACComponent.get
      if containing_comp.airLoopHVAC.is_initialized
        air_loop = containing_comp.airLoopHVAC.get
        zones_served += air_loop.thermalZones
      end
    elsif coil.containingZoneHVACComponent.is_initialized
      zone_hvac = coil.containingZoneHVACComponent.get
      if zone_hvac.thermalZone.is_initialized
        zones_served << zone_hvac.thermalZone.get
      end
    end
  
  end    
  
  # Add up the area of all zones served.

  # Make sure to only add unique zones in

  # case the same zone is served by multiple

  # coils served by the same loop.  For example,

  # a HW and Reheat 

  area_served_m2 = 0.0
  zones_served.uniq.each do |zone|
    area_served_m2 += zone.floorArea
  end
  area_served_ft2 = OpenStudio.convert(area_served_m2,'m^2','ft^2').get

  OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, serves #{area_served_ft2.round} ft^2.")

  return area_served_m2

end

#total_heating_capacityDouble

Get the total heating capacity for the plant loop

Returns:

  • (Double)

    total heating capacity units = Watts (W)



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

def total_heating_capacity

  # Sum the heating capacity for all heating components

  # on the plant loop.

  total_heating_capacity_w = 0
  self.supplyComponents.each do |sc|
    # BoilerHotWater

    if sc.to_BoilerHotWater.is_initialized
      boiler = sc.to_BoilerHotWater.get
      if boiler.nominalCapacity.is_initialized
        total_heating_capacity_w += boiler.nominalCapacity.get
      elsif boiler.autosizedNominalCapacity.is_initialized
        total_heating_capacity_w += boiler.autosizedNominalCapacity.get
      else
        OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} capacity of #{boiler.name} is not available, total cooling capacity of plant loop will be incorrect when applying standard.")
      end
    end
  end

  total_heating_capacity_kbtu_per_hr = OpenStudio.convert(total_heating_capacity_w,'W','tons').get
  OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, heating capacity is #{total_heating_capacity_kbtu_per_hr.round} kBtu/hr.")    
  
  return total_heating_capacity_w

end

#total_rated_w_per_gpmDouble

Determines the total rated watts per GPM of the loop

Returns:

  • (Double)

    rated power consumption per flow @units Watts per GPM (W*s/m^3)



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

def total_rated_w_per_gpm()
  sizing_plant = self.sizingPlant
  loop_type = sizing_plant.loopType

  # Supply W/GPM

  supply_w_per_gpm = 0
  demand_w_per_gpm = 0

  self.supplyComponents.each do |component|
    if component.to_PumpConstantSpeed.is_initialized
      pump = component.to_PumpConstantSpeed.get
      pump_rated_w_per_gpm = pump.rated_w_per_gpm
      OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Pump", "'#{loop_type}' Loop #{self.name} - Primary (Supply) Constant Speed Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
      supply_w_per_gpm += pump_rated_w_per_gpm
    elsif component.to_PumpVariableSpeed.is_initialized
      pump = component.to_PumpVariableSpeed.get
      pump_rated_w_per_gpm = pump.rated_w_per_gpm
      OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Pump", "'#{loop_type}' Loop #{self.name} - Primary (Supply) VSD Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
      supply_w_per_gpm += pump_rated_w_per_gpm
    end
  end

  # Determine if primary only or primary-secondary

  # IF there's a pump on the demand side it's primary-secondary

  demandPumps = self.demandComponents('OS_Pump_VariableSpeed'.to_IddObjectType) + self.demandComponents('OS_Pump_ConstantSpeed'.to_IddObjectType)
  demandPumps.each do |component|
    if component.to_PumpConstantSpeed.is_initialized
      pump = component.to_PumpConstantSpeed.get
      pump_rated_w_per_gpm = pump.rated_w_per_gpm
      OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Pump", "'#{loop_type}' Loop #{self.name} - Secondary (Demand) Constant Speed Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
      demand_w_per_gpm += pump_rated_w_per_gpm
    elsif component.to_PumpVariableSpeed.is_initialized
      pump = component.to_PumpVariableSpeed.get
      pump_rated_w_per_gpm = pump.rated_w_per_gpm
      OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Pump", "'#{loop_type}' Loop #{self.name} - Secondary (Demand) VSD Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
      demand_w_per_gpm += pump_rated_w_per_gpm
    end
  end


  total_rated_w_per_gpm = supply_w_per_gpm + demand_w_per_gpm

  OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Loop", "'#{loop_type}' Loop #{self.name} - Total #{total_rated_w_per_gpm} W/GPM - Supply #{supply_w_per_gpm} W/GPM - Demand #{demand_w_per_gpm} W/GPM")

  return total_rated_w_per_gpm

end