Class: NECB2015

Inherits:
NECB2011 show all
Defined in:
lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb,
lib/openstudio-standards/standards/necb/NECB2015/lighting.rb,
lib/openstudio-standards/standards/necb/NECB2015/hvac_systems.rb,
lib/openstudio-standards/standards/necb/NECB2015/qaqc/necb_2015_qaqc.rb

Overview

This class holds methods that apply NECB2011 rules.

Technical References:

Direct Known Subclasses

NECB2017

Instance Method Summary collapse

Constructor Details

#initializeNECB2015

Returns a new instance of NECB2015.


8
9
10
11
12
13
# File 'lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb', line 8

def initialize
  super()
  @template = self.class.name
  @standards_data = self.load_standards_database_new()
  self.corrupt_standards_database()
end

Instance Method Details

#apply_lighting_schedule(space_type, space_type_properties, default_sch_set) ⇒ Object


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
# File 'lib/openstudio-standards/standards/necb/NECB2015/lighting.rb', line 6

def apply_lighting_schedule(space_type, space_type_properties,default_sch_set)
  require 'date'
  lighting_per_area = space_type_properties['lighting_per_area'].to_f
  lights_rel_absence_occ = space_type_properties['rel_absence_occ'].to_f
  lights_personal_control = space_type_properties['personal_control'].to_f
  lights_occ_sense = space_type_properties['occ_sense'].to_f
  occupancy_schedule =space_type_properties['occupancy_schedule'].to_s
  orig_lighting_sch = space_type_properties['lighting_schedule'].to_s

  schedule_table = @standards_data['schedules']

  #checks which rules to apply based on LPD
  if lighting_per_area <= 0.799256505 #8.6 W/m2
    #do not apply occupancy sensor control
    orig_lighting_sch = space_type_properties['lighting_schedule']
    unless orig_lighting_sch.nil?
      default_sch_set.setLightingSchedule(model_add_schedule(space_type.model, orig_lighting_sch))
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set lighting schedule to #{orig_lighting_sch}.")
    end

  else # LPD > 8.6 W/m2

    #apply occupancy sensor control
    #get occupancy schedule's day rules
    rules = model_find_objects(schedule_table, {'name' => occupancy_schedule}) # returns all schedules with schedule name entered
    #check if it exists
    if rules.size.zero? #does not exist -apply default lighting sched
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Cannot find data for schedule: #{occupancy_schedule}. Cannot apply occupancy sensor control for lighting for space: #{space_type.name} ")
      orig_lighting_sch = space_type_properties['lighting_schedule']
      unless orig_lighting_sch.nil?
        default_sch_set.setLightingSchedule(model_add_schedule(space_type.model, orig_lighting_sch))
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set lighting schedule to #{orig_lighting_sch}.")
      end
    else #exists

      #check if schedule exists already . # First check model and return schedule if it already exists
      space_type.model.getSchedules.sort.each do |exisiting_light_ruleset|
        if exisiting_light_ruleset.name.get.to_s == "#{occupancy_schedule}-#{orig_lighting_sch}-#{lights_rel_absence_occ}-#{lights_personal_control}-#{lights_occ_sense}-Light Ruleset"
          OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Already added schedule: #{exisiting_light_ruleset.name.get.to_s}")
          #set the lighting schedule
          unless exisiting_light_ruleset.nil?
            default_sch_set.setLightingSchedule(exisiting_light_ruleset)
            OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name.to_s} set lighting schedule to #{exisiting_light_ruleset}.")
            return true
          end
        end
      end

      #Create new lighting schedule
      lighting_sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(space_type.model)
      lighting_sch_ruleset.setName("#{occupancy_schedule}-#{orig_lighting_sch}-#{lights_rel_absence_occ}-#{lights_personal_control}-#{lights_occ_sense}-Light Ruleset")
      #loop through the number of day types (each occupancy schedule day)
      rules.each do|rule|
        #get day type, hourly values from the occupancy schedule day
        day_types = rule['day_types'] #Default Wkdy, Wknd, Mon, Tue, Wed, Thu, Fri, Sat, Sun, WntrDsn, SmrDsn
        occupancy_value = rule['values']
        sch_type = rule['type'] #should be 'Hourly'
        start_date = DateTime.parse(rule['start_date'])
        end_date = DateTime.parse(rule['end_date'])
        #create new array to hold occ_control values for each day-type/schedule day
        hourly_occ_control = Array.new
        #loop through hourly values to check if occupancy sensor control should apply and store the new lighting day hourly value
        hourly_index = 0
        for hourly_value in occupancy_value do
          #default light schedule hourly value
          lighting_sched_value = 999
          #get the hourly value from the .json schedule
          #get lighting schedule
          orig_lighting_rules = model_find_objects(schedule_table, {'name' => orig_lighting_sch}) # returns all schedules with schedule name
          if orig_lighting_rules.size.zero?
            OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Cannot find data for schedule: #{orig_lighting_sch}.")
          end
          if hourly_value<lights_rel_absence_occ
            #occupancy sensor control applies for this hour
            occ_control = 1-(lights_rel_absence_occ*lights_occ_sense)-lights_personal_control
            #go through lighitng schedule and adjust the value for the hour
            orig_lighting_rules.each do |orig_lighting_rule|
              if day_types == orig_lighting_rule['day_types'] #if light day schedule type matches occupancy day type
                orig_hourly_values =orig_lighting_rule['values']
                lighting_sched_value = (orig_hourly_values[hourly_index])*occ_control

              end
            end

          else
            #occupancy sensor control does not apply for this hour. Use default schedule value from .json file
            #go through each lighting schedule day  to find the one with the matching day type
            #assuming occupancy schedule day matches lighting schedule day
            orig_lighting_rules.each do |orig_lighting_rule|
              if day_types == orig_lighting_rule['day_types'] #if light day schedule type matches occupancy day type
                orig_hourly_values =orig_lighting_rule['values']
                occ_control =1
                lighting_sched_value = orig_hourly_values[hourly_index] #set the current hourly_index's original lighting schedule day value to lighting_sched_value

              end

            end
          end # if hourly_value<lights_rel_absence_occ
          #store the lighting_sched_value factor for this hour to the array
          hourly_occ_control << lighting_sched_value
          #update index
          hourly_index = hourly_index + 1
        end #for hourly_value in occupancy_value do

        #for each schedule day, create a new day rule with the new hourly schedule values for lighting
        if day_types.include?('Default')
          day_sch = lighting_sch_ruleset.defaultDaySchedule
          day_sch.setName("#{occupancy_schedule}-#{orig_lighting_sch}-#{lights_rel_absence_occ}-#{lights_personal_control}-#{lights_occ_sense}-Light Default")
          model_add_vals_to_sch(space_type.model, day_sch, sch_type, hourly_occ_control)
        end
        if day_types.include?('Wknd') ||
            day_types.include?('Wkdy') ||
            day_types.include?('Sat') ||
            day_types.include?('Sun') ||
            day_types.include?('Mon') ||
            day_types.include?('Tue') ||
            day_types.include?('Wed') ||
            day_types.include?('Thu') ||
            day_types.include?('Fri')
          # Make the Rule
          sch_rule = OpenStudio::Model::ScheduleRule.new(lighting_sch_ruleset)
          day_sch = sch_rule.daySchedule
          day_sch.setName("#{occupancy_schedule}-#{orig_lighting_sch}-#{lights_rel_absence_occ}-#{lights_personal_control}-#{lights_occ_sense}-#{day_types}-Light Day")
          model_add_vals_to_sch(space_type.model, day_sch, sch_type, hourly_occ_control)
          # Set the dates when the rule applies
          sch_rule.setStartDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_date.month.to_i), start_date.day.to_i))
          sch_rule.setEndDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_date.month.to_i), end_date.day.to_i))
          # Set the days when the rule applies
          # Weekends
          if day_types.include?('Wknd')
            sch_rule.setApplySaturday(true)
            sch_rule.setApplySunday(true)
          end
          # Weekdays
          if day_types.include?('Wkdy')
            sch_rule.setApplyMonday(true)
            sch_rule.setApplyTuesday(true)
            sch_rule.setApplyWednesday(true)
            sch_rule.setApplyThursday(true)
            sch_rule.setApplyFriday(true)
          end
          # Individual Days
          sch_rule.setApplyMonday(true) if day_types.include?('Mon')
          sch_rule.setApplyTuesday(true) if day_types.include?('Tue')
          sch_rule.setApplyWednesday(true) if day_types.include?('Wed')
          sch_rule.setApplyThursday(true) if day_types.include?('Thu')
          sch_rule.setApplyFriday(true) if day_types.include?('Fri')
          sch_rule.setApplySaturday(true) if day_types.include?('Sat')
          sch_rule.setApplySunday(true) if day_types.include?('Sun')
        end
        if day_types.include?('WntrDsn')
          day_sch = OpenStudio::Model::ScheduleDay.new(space_type.model)
          lighting_sch_ruleset.setWinterDesignDaySchedule(day_sch)
          day_sch = lighting_sch_ruleset.winterDesignDaySchedule
          day_sch.setName("#{occupancy_schedule}-#{orig_lighting_sch}-#{lights_rel_absence_occ}-#{lights_personal_control}-#{lights_occ_sense}-Light Winter Design")
          model_add_vals_to_sch(space_type.model, day_sch, sch_type, hourly_occ_control)
        end
        if day_types.include?('SmrDsn')
          day_sch = OpenStudio::Model::ScheduleDay.new(space_type.model)
          lighting_sch_ruleset.setSummerDesignDaySchedule(day_sch)
          day_sch = lighting_sch_ruleset.summerDesignDaySchedule
          day_sch.setName("#{occupancy_schedule}-#{orig_lighting_sch}-#{lights_rel_absence_occ}-#{lights_personal_control}-#{lights_occ_sense}-Light Summer Design")
          model_add_vals_to_sch(space_type.model, day_sch, sch_type, hourly_occ_control)
        end
      end #rules.each do|rule|
      #set the lighting schedule
      unless lighting_sch_ruleset.nil?
        default_sch_set.setLightingSchedule(lighting_sch_ruleset)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name.to_s} set lighting schedule to #{lighting_sch_ruleset}.")
      end
    end #if rules.size.zero? #does not exist
  end #if lighting_per_area <= 0.7999256505 #8.6 W/m2
end

#apply_loop_pump_power(model:, sizing_run_dir:) ⇒ Object


54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb', line 54

def apply_loop_pump_power(model:, sizing_run_dir:)
  # NECB2015 Custom code
  # Do another sizing run to take into account adjustments to equipment efficiency etc. on capacities. This was done primarily
  # because the cooling tower loop capacity is affected by the chiller COP.  If the chiller COP is not properly set then
  # the cooling tower loop capacity can be significantly off which will affect the NECB 2015 maximum loop pump capacity.  Found
  # all sizing was off somewhat if the additional sizing run was not done.
  if model_run_sizing_run(model, "#{sizing_run_dir}/SR2") == false
    raise("sizing run 2 failed!")
  end
  # Apply maxmimum loop pump power normalized by peak demand by served spaces as per NECB2015 5.2.6.3.(1)
  apply_maximum_loop_pump_power(model)
  #model = BTAP::FileIO::remove_duplicate_materials_and_constructions(model)
  return model
end

#apply_maximum_loop_pump_power(model) ⇒ Object

Searches through any hydronic loops and applies the maxmimum total pump power by modifying the pump design power consumption. This is as per NECB2015 5.2.6.3.(1)


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

def apply_maximum_loop_pump_power(model)
  plant_loops = model.getPlantLoops
  return model if plant_loops.nil?
  plant_loops.each do |plantloop|
    next if plant_loop_swh_loop?(plantloop) == true
    pumps = []
    max_powertoload = 0
    total_pump_power = 0
    # This cycles through the plant loop supply side components to determine if there is a heat pump present or a pump
    # If a heat pump is present the pump power to total demand ratio is set to what NECB 2015 table 5.2.6.3. say it should be.
    # If a pump is present, this is a handy time to grab it for modification later.  Also, it adds the pump power consumption
    # to a total which will be used to determine how much to modify the pump power consumption later.
    max_total_loop_pump_power_table = @standards_data['max_total_loop_pump_power']
    plantloop.supplyComponents.each do |supplycomp|
      case supplycomp.iddObjectType.valueName.to_s
        when 'OS_CentralHeatPumpSystem', 'OS_Coil_Heating_WaterToAirHeatPump_EquationFit','OS_Coil_Heating_WaterToAirHeatPump_VariableSpeedEquationFit','OS_Coil_Heating_WaterToAirHeatPump_VariableSpeedEquationFit_SpeedData','OS_HeatPump_WaterToWater_EquationFit_Cooling','OS_HeatPump_WaterToWater_EquationFit_Heating'
          max_powertoload = model_find_object(max_total_loop_pump_power_table, {'hydronic_system_type' => 'WSHP'})['total_normalized_pump_power_wperkw']
        when 'OS_Pump_VariableSpeed'
          pumps << supplycomp.to_PumpVariableSpeed.get
          total_pump_power += model.getAutosizedValue(supplycomp, 'Design Power Consumption', 'W').to_f
        when 'OS_Pump_ConstantSpeed'
          pumps << supplycomp.to_PumpConstantSpeed.get
          total_pump_power += model.getAutosizedValue(supplycomp, 'Design Power Consumption', 'W').to_f
        when 'OS_HeaderedPumps_ConstantSpeed'
          OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "A pump used in the plant loop named #{plantloop.name.to_s} is headered.  This may result in an error and cause a failure.")
          pumps << supplycomp.to_HeaderedPumpsConstantSpeed.get
          total_pump_power += model.getAutosizedValue(supplycomp, 'Design Power Consumption', 'W').to_f
        when 'OS_HeaderedPumps_VariableSpeed'
          OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "A pump used in the plant loop named #{plantloop.name.to_s} is headered.  This may result in an error and cause a failure.")
          pumps << supplycomp.to_HeaderedPumpsVariableSpeed.get
          total_pump_power += model.getAutosizedValue(supplycomp, 'Design Power Consumption', 'W').to_f
      end
    end
    # If no pumps were found then there is nothing to set so go to the next plant loop
    next if pumps.length == 0
    # If a heat pump was found then the pump power to total demand ratio should have been set to what NECB 2015 table 5.2.6.3 says.
    # If the pump power to total demand ratio was not set then no heat pump was present so set according to if the plant loop is
    # used for heating, cooling, or heat rejection (condeser as OpenStudio calls it).
    unless max_powertoload > 0
      case plantloop.sizingPlant.loopType
        when 'Heating'
          max_powertoload = model_find_object(max_total_loop_pump_power_table, {'hydronic_system_type' => 'Heating'})['total_normalized_pump_power_wperkw']
        when 'Cooling'
          max_powertoload = model_find_object(max_total_loop_pump_power_table, {'hydronic_system_type' => 'Cooling'})['total_normalized_pump_power_wperkw']
        when 'Condenser'
          max_powertoload = model_find_object(max_total_loop_pump_power_table, {'hydronic_system_type' => 'Heat_rejection'})['total_normalized_pump_power_wperkw']
      end
    end
    # If nothing was found then do nothing (though by this point if nothing was found then an error should have been thrown).
    next if max_powertoload == 0
    # Get the capacity of the loop (using the more general method of calculating via maxflow*temp diff*density*heat capacity)
    # This is more general than the other method in Standards.PlantLoop.rb which only looks at heat and cooling.  Also,
    # that method looks for spceific equipment and would be thrown if other equipment was present.  However my method
    # only works for water for now.
    plantloop_capacity = plant_loop_capacity_W_by_maxflow_and_deltaT_forwater (plantloop)
    # Sizing factor is pump power (W)/ zone demand (in kW, as approximated using plant loop capacity).
    necb_pump_power_cap = plantloop_capacity*max_powertoload/1000
    pump_power_adjustment = necb_pump_power_cap/total_pump_power
    # Multiply the factor EnergyPlus uses to calculate the pump power by the sizing factor to make pump power in line with NECB 2015.
    pumps.each do |pump|
      case pump.designPowerSizingMethod
        when 'PowerPerFlowPerPressure'
          pump.setDesignShaftPowerPerUnitFlowRatePerUnitHead(pump.designShaftPowerPerUnitFlowRatePerUnitHead.to_f*pump_power_adjustment)
        when 'PowerPerFlow'
          pump.setDesignElectricPowerPerUnitFlowRate(pump.designElectricPowerPerUnitFlowRate.to_f*pump_power_adjustment)
      end
    end
  end
  return model
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


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

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.013 * 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

#load_qaqc_database_newObject


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
# File 'lib/openstudio-standards/standards/necb/NECB2015/qaqc/necb_2015_qaqc.rb', line 4

def load_qaqc_database_new()
  super()
  # replace 2011 to 2015 for all references in the tables.
  # puts JSON.pretty_generate( @standards_data['tables'] )
  @qaqc_data['tables'].each do |table|
    if table.has_key?('refs')
      # check if the reference is an array
      if table['refs'].is_a?(Array)
        table['refs'].each {|item|
          # Supply air system - necb_design_supply_temp_compliance
          item.gsub!('NECB2011-8.4.4.19', 'NECB2015-8.4.4.18')
          # Zone sizing compliance - Re: heating_sizing_factor and cooling_sizing_factor
          item.gsub!('NECB2011-8.4.4.9', 'NECB2015-8.4.4.8')
          item.gsub!('NECB2011', 'NECB2015')
        }
        # if the reference is a hash (e.g. see space.json compliance), then
        # replace the 2011 in the value with 2015
      elsif table['refs'].is_a?(Hash)
        table['refs'].keys.each  {|key|
          table['refs'][key].gsub!('NECB2011', 'NECB2015') unless table['refs'][key].nil?
        }
      end
    end
  end

  # Overwrite the data present from 2011 with the data read from the JSON files
  files = Dir.glob("#{File.dirname(__FILE__)}/qaqc_data/*.json").select {|e| File.file? e}
  puts "\n\n#{files}\n\n"
  files.each do |file|
    puts "loading standards data from #{file}"
    data = JSON.parse(File.read(file))
    if not data["tables"].nil? and data["tables"].first["data_type"] =="table"
      @qaqc_data["tables"] << data["tables"].first
    else
      @qaqc_data[data.keys.first] = data[data.keys.first]
    end
  end

  #needed for compatibility of standards database format
  @qaqc_data['tables'].each do |table|
    @qaqc_data[table['name']] = table
  end
  return @qaqc_data
end

#load_standards_database_newObject


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
# File 'lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb', line 15

def load_standards_database_new()
  #load NECB2011 data.
  super()

  if __dir__[0] == ':' # Running from OpenStudio CLI
    embedded_files_relative('data/', /.*\.json/).each do |file|
      data = JSON.parse(EmbeddedScripting.getFileAsString(file))
      if !data['tables'].nil?
        @standards_data['tables'] = [*@standards_data['tables'], *data['tables']].to_h
      elsif !data['constants'].nil?
        @standards_data['constants'] = [*@standards_data['constants'], *data['constants']].to_h
      elsif !data['constants'].nil?
        @standards_data['formulas'] = [*@standards_data['formulas'], *data['formulas']].to_h
      end
    end
  else
    files = Dir.glob("#{File.dirname(__FILE__)}/data/*.json").select {|e| File.file? e}
    files.each do |file|
      data = JSON.parse(File.read(file))
      if !data['tables'].nil?
        @standards_data['tables'] = [*@standards_data['tables'], *data['tables']].to_h
      elsif !data['constants'].nil?
        @standards_data['constants'] = [*@standards_data['constants'], *data['constants']].to_h
      elsif !data['formulas'].nil?
        @standards_data['formulas'] = [*@standards_data['formulas'], *data['formulas']].to_h
      end
    end
  end
  # Write database to file.
  # File.open(File.join(File.dirname(__FILE__), '..', 'NECB2017.json'), 'w') {|f| f.write(JSON.pretty_generate(@standards_data))}

  return @standards_data
end

#necb_envelope_compliance(qaqc) ⇒ Object


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
# File 'lib/openstudio-standards/standards/necb/NECB2015/qaqc/necb_2015_qaqc.rb', line 50

def necb_envelope_compliance(qaqc)
  puts "\nUsing necb_envelope_compliance in NECB2015 Class\n"
  # Envelope
  necb_section_name = "NECB2015-Section 3.2.1.4"
  # store hdd in short form
  hdd = qaqc[:geography][:hdd]
  # calculate fdwr based on hdd.
  # [fdwr] *maximum* allowable total vertical fenestration and door area to
  # gross wall area ratio
  fdwr = 0
  # if hdd < 4000 [NECB 2011]
  if hdd <= 4000
    fdwr = 0.40
    # elsif hdd >= 4000 and hdd <=7000 [NECB 2011]
  elsif hdd > 4000 and hdd <7000
    fdwr = (2000-0.2 * hdd)/3000
    # elsif hdd >7000   [NECB 2011]
  elsif hdd >=7000
    fdwr = 0.20
  end
  #hardset srr to 0.05
  srr = 0.05

  # perform test. result must be equal to.
  necb_section_test(
      qaqc,
      (fdwr * 100), # fdwr is the maximum value possible
      '>=', # NECB 2011 [No Change]
      qaqc[:envelope][:fdwr].round(3),
      necb_section_name,
      "[ENVELOPE]fenestration_to_door_and_window_percentage",
      1 #padmassun added tollerance
  )

  # The total skylight area shall be less than 5% of gross roof area as determined
  # in article 3.1.1.6
  necb_section_test(
      qaqc,
      (srr * 100),
      '>=', # NECB 2011 [No Change]
      qaqc[:envelope][:srr].round(3),
      necb_section_name,
      "[ENVELOPE]skylight_to_roof_percentage",
      1 #padmassun added tollerance
  )
end

#necb_qaqc(qaqc, model) ⇒ Object


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
# File 'lib/openstudio-standards/standards/necb/NECB2015/qaqc/necb_2015_qaqc.rb', line 150

def necb_qaqc(qaqc, model)
  puts "\n\nin necb_qaqc 2015 now\n\n"
  #Now perform basic QA/QC on items for NECB2015
  qaqc[:information] = []
  qaqc[:warnings] =[]
  qaqc[:errors] = []
  qaqc[:unique_errors]=[]

  necb_space_compliance(qaqc)

  necb_envelope_compliance(qaqc) # [DONE]

  necb_infiltration_compliance(qaqc, model) # [DONE-NC]

  necb_exterior_opaque_compliance(qaqc) # [DONE-NC]

  necb_exterior_fenestration_compliance(qaqc) # [DONE-NC]

  necb_exterior_ground_surfaces_compliance(qaqc) # [DONE-NC]

  necb_zone_sizing_compliance(qaqc) # [DONE] made changes to NECB section numbers

  necb_design_supply_temp_compliance(qaqc) # [DONE] made changes to NECB section numbers

  # Cannot implement 5.2.2.8.(4) and 5.2.2.8.(5) due to OpenStudio's limitation.
  necb_economizer_compliance(qaqc) # [DONE-NC]

  #NECB code regarding MURBS (ยง5.2.10.4) has not been implemented in both NECB 2011 and 2015
  necb_hrv_compliance(qaqc, model) # [DONE-NC]

  necb_vav_fan_power_compliance(qaqc) # [DONE-NC]

  sanity_check(qaqc)

  necb_plantloop_sanity(qaqc)

  qaqc[:information] = qaqc[:information].sort
  qaqc[:warnings] = qaqc[:warnings].sort
  qaqc[:errors] = qaqc[:errors].sort
  qaqc[:unique_errors]= qaqc[:unique_errors].sort
  return qaqc
end

#necb_space_compliance(qaqc) ⇒ Object


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
# File 'lib/openstudio-standards/standards/necb/NECB2015/qaqc/necb_2015_qaqc.rb', line 97

def necb_space_compliance(qaqc)
  #    #Padmassun's Code Start
  #csv_file_name ="#{File.dirname(__FILE__)}/necb_2011_spacetype_info.csv"
  qaqc[:spaces].each do |space|
    building_type =""
    space_type =""
    if space[:space_type_name].include? 'Space Function '
      space_type = (space[:space_type_name].to_s.rpartition('Space Function '))[2].strip
      building_type = 'Space Function'
    elsif space[:space_type_name].include? ' WholeBuilding'
      space_type = (space[:space_type_name].to_s.rpartition(' WholeBuilding'))[0].strip
      building_type = 'WholeBuilding'
    end

    ["occupancy_per_area_people_per_m2", "occupancy_schedule", "electric_equipment_per_area_w_per_m2"].each {|compliance_var|
      #qaqc_table = get_qaqc_table("space_compliance", {"template" => 'NECB2015', "building_type" => building_type, "space_type" => space_type}).first
      #qaqc_table = @qaqc_data['space_compliance']

      search_criteria = {"template" => 'NECB2015', "building_type" => building_type, "space_type" => space_type}
      table_to_search = @standards_data[@qaqc_data['space_compliance']]
      qaqc_table = model_find_objects(table_to_search, search_criteria)
      qaqc_table = qaqc_table.first
      puts"{\"building_type\" => #{building_type}, \"space_type\" => #{space_type}}"
      puts "#{qaqc_table}\n\n"

      necb_section_name = get_qaqc_table("space_compliance")['refs'][compliance_var]
      tolerance = get_qaqc_table("space_compliance")['tolerance'][compliance_var]
      # puts "\ncompliance_var:#{compliance_var}\n\tnecb_section_name:#{necb_section_name}\n\texp Value:#{qaqc_table[compliance_var]}\n"
      if compliance_var =="occupancy_per_area_people_per_m2"
        result_value = space[:occ_per_m2]
      elsif compliance_var =="occupancy_schedule"
        result_value = space[:occupancy_schedule]
      elsif compliance_var =="electric_equipment_per_area_w_per_m2"
        result_value = space[:electric_w_per_m2]
      end

      test_text = "[SPACE][#{space[:name]}]-[TYPE:][#{space_type}]-#{compliance_var}"
      next if result_value.nil?
      necb_section_test(
          qaqc,
          result_value,
          '==',
          qaqc_table[compliance_var],
          necb_section_name,
          test_text,
          tolerance
      )
    }

  end
  #Padmassun's Code End
end

#set_lighting_per_area(space_type, definition, lighting_per_area) ⇒ Object


2
3
4
5
# File 'lib/openstudio-standards/standards/necb/NECB2015/lighting.rb', line 2

def set_lighting_per_area(space_type, definition, lighting_per_area)
  definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area.to_f, 'W/ft^2', 'W/m^2').get)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set LPD to #{lighting_per_area} W/ft^2.")
end

#set_occ_sensor_spacetypes(model, space_type_map) ⇒ Object

occupancy sensor control applied using lighting schedule, see apply_lighting_schedule method


50
51
52
# File 'lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb', line 50

def set_occ_sensor_spacetypes(model, space_type_map)
  return true
end