class NECB2015

This class holds methods that apply NECB2011 rules. @ref [References::NECB2011]

Public Class Methods

new() click to toggle source
Calls superclass method NECB2011::new
# File lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb, line 7
def initialize
  super()
  @standards_data = load_standards_database_new
  corrupt_standards_database
end

Public Instance Methods

apply_lighting_schedule(space_type, space_type_properties, default_sch_set) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2015/lighting.rb, line 10
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.empty? # 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}")
          # 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} 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 = []
        # 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.empty?
            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
            # if hourly_value<lights_rel_absence_occ
          end
          # store the lighting_sched_value factor for this hour to the array
          hourly_occ_control << lighting_sched_value
          # update index
          hourly_index += 1
          # for hourly_value in occupancy_value do
        end

        # 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
        # rules.each do|rule|
      end
      # 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} set lighting schedule to #{lighting_sch_ruleset}.")
      end
      # if rules.empty? #does not exist
    end
    # if lighting_per_area <= 0.7999256505 #8.6 W/m2
  end
end
apply_loop_pump_power(model:, sizing_run_dir:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb, line 52
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) click to toggle source

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)

# File lib/openstudio-standards/standards/necb/NECB2015/hvac_systems.rb, line 126
def apply_maximum_loop_pump_power(model)
  plant_loops = model.getPlantLoops
  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'
          search_hash = { 'hydronic_system_type' => 'WSHP' }
          max_powertoload = model_find_object(max_total_loop_pump_power_table, search_hash)['total_normalized_pump_power_wperkw']
        when 'OS_GroundHeatExchanger_Vertical'
          max_powertoload = 21.0
        when 'OS_Pump_VariableSpeed'
          pump = supplycomp.to_PumpVariableSpeed.get
          pumps << pump
          total_pump_power += pump.autosizedRatedPowerConsumption.get
        when 'OS_Pump_ConstantSpeed'
          pump = supplycomp.to_PumpConstantSpeed.get
          pumps << pump
          total_pump_power += pump.autosizedRatedPowerConsumption.get
        when 'OS_HeaderedPumps_ConstantSpeed'
          OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "A pump used in the plant loop named #{plantloop.name} is headered.  This may result in an error and cause a failure.")
          pump = supplycomp.to_HeaderedPumpsConstantSpeed.get
          pumps << pump
          total_pump_power += pump.autosizedRatedPowerConsumption.get
        when 'OS_HeaderedPumps_VariableSpeed'
          OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "A pump used in the plant loop named #{plantloop.name} is headered.  This may result in an error and cause a failure.")
          pump = supplycomp.to_HeaderedPumpsVariableSpeed.get
          pumps << pump
          total_pump_power += pump.autosizedRatedPowerConsumption.get
      end
    end
    var_spd_pumps = pumps.select {|pump| pump.to_PumpVariableSpeed.is_initialized}
    # EnergyPlus doesn't currently properly account for variable speed pumps operation in the condenser loop.
    # This code is an approximation for a correction to the pump head when the loop has variable speed pumps for ground-source condenser loops.
    # These estimates were confirmed with OS runs using Montreal weather file for offices, schoold, and apartment bldgs. Office estimates are
    # then for other bldg types.
    if plantloop.name.to_s.upcase.include? "GLHX"
      max_powertoload = 21.0
      if !var_spd_pumps.empty?
        if model.getBuilding.standardsBuildingType.to_s.include? 'Office'
          max_powertoload = 21.0/4.0
        elsif model.getBuilding.standardsBuildingType.to_s.include? 'School'
          max_powertoload = 21.0/18.0
        elsif model.getBuilding.standardsBuildingType.to_s.include? 'Apartment'
           max_powertoload = 21.0/3.0
        else
          max_powertoload = 21.0/4.0
        end
      end
    end
    # If no pumps were found then there is nothing to set so go to the next plant loop
    next if pumps.empty?

    # 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'
        search_hash = { 'hydronic_system_type' => 'Heating' }
        max_powertoload = model_find_object(max_total_loop_pump_power_table, search_hash)['total_normalized_pump_power_wperkw']
      when 'Cooling'
        search_hash = { 'hydronic_system_type' => 'Cooling' }
        max_powertoload = model_find_object(max_total_loop_pump_power_table, search_hash)['total_normalized_pump_power_wperkw']
      when 'Condenser'
        search_hash = { 'hydronic_system_type' => 'Heat_rejection' }
        max_powertoload = model_find_object(max_total_loop_pump_power_table, search_hash)['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_delta_t_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
    # Update rated pump head to make pump power in line with NECB 2015.
    pumps.each do |pump|
      if pump.designPowerSizingMethod != "PowerPerFlowPerPressure" then OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', 'Design power sizing method for pump ',pump.name.to_s,' not set to PowerPerFlowPerPressure') end
      new_pump_head = pump_power_adjustment*pump.ratedPumpHead.to_f
      pump.setRatedPumpHead(new_pump_head)
    end
  end
  return model
end
chiller_electric_eir_apply_efficiency_and_curves(chiller_electric_eir, clg_tower_objs) click to toggle source

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

@return [Boolean] true if successful, false if not

# File lib/openstudio-standards/standards/necb/NECB2015/hvac_systems.rb, line 5
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)
  chiller_capacity = capacity_w
  if (chiller_electric_eir.name.to_s.include? 'Primary') || (chiller_electric_eir.name.to_s.include? 'Secondary')
    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
  end
  chiller_electric_eir.setReferenceCapacity(chiller_capacity)

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

  # Get chiller compressor type if needed
  chiller_types = ['reciprocating','scroll','rotary screw','centrifugal']
  chiller_name_has_type = chiller_types.any? {|type| chiller_electric_eir.name.to_s.downcase.include? type}
  unless chiller_name_has_type
    chlr_type_search_criteria = {}
    chlr_type_search_criteria['cooling_type'] = cooling_type
    chlr_types_table = @standards_data['chiller_types']
    chlr_type_props = model_find_object(chlr_types_table, chlr_type_search_criteria, capacity_tons)
    unless chlr_type_props
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find chiller types information")
      successfully_set_all_properties = false
      return successfully_set_all_properties
    end
    compressor_type = chlr_type_props['compressor_type']
    chiller_electric_eir.setName(chiller_electric_eir.name.to_s + ' ' + compressor_type)
  end
  # Get the chiller properties
  search_criteria['compressor_type'] = compressor_type
  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_new() click to toggle source
Calls superclass method NECB2011#load_qaqc_database_new
# File lib/openstudio-standards/standards/necb/NECB2015/qaqc/necb_2015_qaqc.rb, line 2
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.key?('refs')
      # check if the reference is an array
      if table['refs'].is_a?(Array)
        table['refs'].each do |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')
        end
        # 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 do |key|
          table['refs'][key].gsub!('NECB2011', 'NECB2015') unless table['refs'][key].nil?
        end
      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 !data['tables'].nil? && (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_new() click to toggle source
Calls superclass method NECB2011#load_standards_database_new
# File lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb, line 13
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) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2015/qaqc/necb_2015_qaqc.rb, line 47
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) && (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) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2015/qaqc/necb_2015_qaqc.rb, line 147
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 (Section 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) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2015/qaqc/necb_2015_qaqc.rb, line 94
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 do |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(table_name: 'space_compliance')['refs'][compliance_var]
      tolerance = get_qaqc_table(table_name: '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
  end
  # Padmassun's Code End
end
set_lighting_per_area(space_type:, definition:, lighting_per_area:, lights_scale:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2015/lighting.rb, line 2
def set_lighting_per_area(space_type:,
                          definition:,
                          lighting_per_area:,
                          lights_scale:)
  definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area.to_f * lights_scale, '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_lighting_per_area_led_lighting(space_type:, definition:, lighting_per_area_led_lighting:, lights_scale:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb, line 68
def set_lighting_per_area_led_lighting(space_type:, definition:, lighting_per_area_led_lighting:, lights_scale:)
  # puts "#{space_type.name.to_s} - 'space_height' - #{space_height.to_s}"
  # @todo Note that 'occ_sens_lpd_frac' in this function has been removed for NECB2015 and 2017.
  # ##### Since Atrium's LPD for LED lighting depends on atrium's height, the height of the atrium (if applicable) should be found.
  standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil # Sara
  if standards_space_type.include? 'Atrium' # @todo Note that since none of the archetypes has Atrium, this was tested for 'Dining'. #Atrium
    puts "#{standards_space_type} - has atrium" # space_type.name.to_s
    # puts space_height
    if get_max_space_height_for_space_type(space_type: space_type) < 12.0
      # @todo Regarding the below equations, identify which version of ASHRAE 90.1 was used in NECB2015.
      atrium_lpd_eq_smaller_12_intercept = 0
      atrium_lpd_eq_smaller_12_slope = 1.06
      atrium_lpd_eq_larger_12_intercept = 4.3
      atrium_lpd_eq_larger_12_slope = 1.06
      lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_smaller_12_intercept + atrium_lpd_eq_smaller_12_slope * space_height) * 0.092903 # W/ft2
    else # i.e. space_height >= 12.0
      lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_larger_12_intercept + atrium_lpd_eq_larger_12_slope * space_height) * 0.092903 # W/ft2
    end
    puts "#{standards_space_type} - has lighting_per_area_led_lighting_atrium - #{lighting_per_area_led_lighting_atrium}"
    lighting_per_area_led_lighting = lighting_per_area_led_lighting_atrium
  end
  lighting_per_area_led_lighting *= lights_scale

  definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area_led_lighting.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_led_lighting} W/ft^2.")
end
set_occ_sensor_spacetypes(model, space_type_map) click to toggle source

occupancy sensor control applied using lighting schedule, see apply_lighting_schedule method

# File lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb, line 48
def set_occ_sensor_spacetypes(model, space_type_map)
  return true
end