class NECB2011

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

Attributes

fuel_type_set[RW]
qaqc_data[RW]
space_multiplier_map[RW]
space_type_map[RW]
standards_data[RW]
tbd[R]
template[R]

Public Class Methods

new() click to toggle source
Calls superclass method Standard::new
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 134
def initialize
  super()
  @template = self.class.name
  @standards_data = load_standards_database_new
  corrupt_standards_database
  @tbd = nil
  # puts "loaded these tables..."
  # puts @standards_data.keys.size
  # raise("tables not all loaded in parent #{}") if @standards_data.keys.size < 24
end

Public Instance Methods

add_all_spacetypes_to_model(model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb, line 2
def add_all_spacetypes_to_model(model)
  # Get the space Type data from @standards data
  spacetype_data = nil
  if @standards_data['space_types'].is_a?(Hash) == true
    spacetype_data = @standards_data['space_types']['table']
  else
    spacetype_data = @standards_data['space_types']
  end
  spacetype_data.each do |spacedata|
    space_type = OpenStudio::Model::SpaceType.new(model)
    space_type.setStandardsSpaceType(spacedata['space_type'])
    space_type.setStandardsBuildingType(spacedata['building_type'])
    space_type.setName("#{spacedata['building_type']} #{spacedata['space_type']}")
    # Loads
    space_type_apply_internal_loads(space_type: space_type)

    # Schedules
    space_type_apply_internal_load_schedules(space_type,
                                             true,
                                             true,
                                             true,
                                             true,
                                             true,
                                             true,
                                             true)
  end
end
add_onespeed_DX_coil(model, always_on) click to toggle source

Create a new DX cooling coil with NECB curve characteristics

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1925
def add_onespeed_DX_coil(model, always_on)
  # clg_cap_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model)
  # clg_cap_f_of_temp = model_add_curve("DXCOOL-NECB2011-REF-CAPFT")
  clg_cap_f_of_temp = OpenStudio::Model::CurveBiquadratic.new(model)
  clg_cap_f_of_temp.setCoefficient1Constant(0.867905)
  clg_cap_f_of_temp.setCoefficient2x(0.0142459)
  clg_cap_f_of_temp.setCoefficient3xPOW2(0.000554364)
  clg_cap_f_of_temp.setCoefficient4y(-0.00755748)
  clg_cap_f_of_temp.setCoefficient5yPOW2(3.3048e-05)
  clg_cap_f_of_temp.setCoefficient6xTIMESY(-0.000191808)
  clg_cap_f_of_temp.setMinimumValueofx(13.0)
  clg_cap_f_of_temp.setMaximumValueofx(24.0)
  clg_cap_f_of_temp.setMinimumValueofy(24.0)
  clg_cap_f_of_temp.setMaximumValueofy(46.0)

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

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

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

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

  return OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model,
                                                         always_on,
                                                         clg_cap_f_of_temp,
                                                         clg_cap_f_of_flow,
                                                         clg_energy_input_ratio_f_of_temp,
                                                         clg_energy_input_ratio_f_of_flow,
                                                         clg_part_load_ratio)
end
add_onespeed_htg_DX_coil(model, sch) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1990
def add_onespeed_htg_DX_coil(model, sch)


  htg_cap_f_of_temp = OpenStudio::Model::CurveCubic.new(model)
  htg_cap_f_of_temp.setCoefficient1Constant(0.729009)
  htg_cap_f_of_temp.setCoefficient2x(0.0319275)
  htg_cap_f_of_temp.setCoefficient3xPOW2(0.000136404)
  htg_cap_f_of_temp.setCoefficient4xPOW3(-8.748e-06)
  htg_cap_f_of_temp.setMinimumValueofx(-20.0)
  htg_cap_f_of_temp.setMaximumValueofx(20.0)

  htg_cap_f_of_flow = OpenStudio::Model::CurveCubic.new(model)
  htg_cap_f_of_flow.setCoefficient1Constant(0.84)
  htg_cap_f_of_flow.setCoefficient2x(0.16)
  htg_cap_f_of_flow.setCoefficient3xPOW2(0.0)
  htg_cap_f_of_flow.setCoefficient4xPOW3(0.0)
  htg_cap_f_of_flow.setMinimumValueofx(0.5)
  htg_cap_f_of_flow.setMaximumValueofx(1.5)

  htg_energy_input_ratio_f_of_temp = OpenStudio::Model::CurveCubic.new(model)
  htg_energy_input_ratio_f_of_temp.setCoefficient1Constant(1.2183)
  htg_energy_input_ratio_f_of_temp.setCoefficient2x(-0.03612)
  htg_energy_input_ratio_f_of_temp.setCoefficient3xPOW2(0.00142)
  htg_energy_input_ratio_f_of_temp.setCoefficient4xPOW3(-2.68e-05)
  htg_energy_input_ratio_f_of_temp.setMinimumValueofx(-20.0)
  htg_energy_input_ratio_f_of_temp.setMaximumValueofx(20.0)

  htg_energy_input_ratio_f_of_flow = OpenStudio::Model::CurveQuadratic.new(model)
  htg_energy_input_ratio_f_of_flow.setCoefficient1Constant(1.3824)
  htg_energy_input_ratio_f_of_flow.setCoefficient2x(-0.4336)
  htg_energy_input_ratio_f_of_flow.setCoefficient3xPOW2(0.0512)
  htg_energy_input_ratio_f_of_flow.setMinimumValueofx(0.0)
  htg_energy_input_ratio_f_of_flow.setMaximumValueofx(1.0)

  htg_part_load_ratio = OpenStudio::Model::CurveCubic.new(model)
  htg_part_load_ratio.setCoefficient1Constant(0.3696)
  htg_part_load_ratio.setCoefficient2x(2.3362)
  htg_part_load_ratio.setCoefficient3xPOW2(-2.9577)
  htg_part_load_ratio.setCoefficient4xPOW3(1.2596)
  htg_part_load_ratio.setMinimumValueofx(0.7)
  htg_part_load_ratio.setMaximumValueofx(1.0)

  dx_htg_coil = OpenStudio::Model::CoilHeatingDXSingleSpeed.new(model,
                                                                sch,
                                                                htg_cap_f_of_temp,
                                                                htg_cap_f_of_flow,
                                                                htg_energy_input_ratio_f_of_temp,
                                                                htg_energy_input_ratio_f_of_flow,
                                                                htg_part_load_ratio)
  dx_htg_coil.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(-10)

  return dx_htg_coil
end
add_ptac_dx_cooling(model, zone, zero_outdoor_air) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2065
def add_ptac_dx_cooling(model, zone, zero_outdoor_air)
  # Create a PTAC for each zone:
  # PTAC DX Cooling with electric heating coil; electric heating coil is always off

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

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

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

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

  # This method will seem like an error in number of args..but this is due to swig voodoo.
  ptac = OpenStudio::Model::ZoneHVACPackagedTerminalAirConditioner.new(model,
                                                                       always_on,
                                                                       fan,
                                                                       htg_coil,
                                                                       clg_coil)
  ptac.setName("#{zone.name} PTAC")
  ptac.setSupplyAirFanOperatingModeSchedule(always_off)
  if zero_outdoor_air
    ptac.setOutdoorAirFlowRateWhenNoCoolingorHeatingisNeeded 1.0e-5
    ptac.setOutdoorAirFlowRateDuringCoolingOperation(1.0e-5)
    ptac.setOutdoorAirFlowRateDuringHeatingOperation(1.0e-5)
  end
  ptac.addToThermalZone(zone)
end
add_sys1_unitary_ac_baseboard_heating(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, mau_type:, mau_heating_coil_type:, baseboard_type:, hw_loop:, multispeed: false) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb, line 2
def add_sys1_unitary_ac_baseboard_heating(model:,
                                          necb_reference_hp:false,
                                          necb_reference_hp_supp_fuel:'DefaultFuel',
                                          zones:,
                                          mau_type:,
                                          mau_heating_coil_type:,
                                          baseboard_type:,
                                          hw_loop:,
                                          multispeed: false)
  if multispeed
    add_sys1_unitary_ac_baseboard_heating_multi_speed(model: model,
                                                      zones: zones,
                                                      mau_type: mau_type,
                                                      mau_heating_coil_type: mau_heating_coil_type,
                                                      baseboard_type: baseboard_type,
                                                      hw_loop: hw_loop)
  else
    add_sys1_unitary_ac_baseboard_heating_single_speed(model: model,
                                                       necb_reference_hp: necb_reference_hp,
                                                       necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                       zones: zones,
                                                       mau_type: mau_type,
                                                       mau_heating_coil_type: mau_heating_coil_type,
                                                       baseboard_type: baseboard_type,
                                                       hw_loop: hw_loop)
  end
end
add_sys1_unitary_ac_baseboard_heating_multi_speed(model:, zones:, mau_type:, mau_heating_coil_type:, baseboard_type:, hw_loop:) click to toggle source

At this point the only way to implement multi-stage cooling and heating in OS is through the use of object “AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed”. This component uses as an argument a control zone and then it responds to a call for heating or cooling for that control zone. This aspect of this component makes it incompatible with how a system_1 make up air unit works where a constant supply air temperature is delivered to the spaces. It is therefore not recommended to use this method and to use the single speed implementation of systems_1.

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb, line 8
def add_sys1_unitary_ac_baseboard_heating_multi_speed(model:,
                                                      zones:,
                                                      mau_type:,
                                                      mau_heating_coil_type:,
                                                      baseboard_type:,
                                                      hw_loop:)

  # Keep all data and assumptions for both systems on the top here for easy reference.
  system_data = {}
  system_data[:name] = 'Sys_1_Make-up air unit'
  system_data[:PreheatDesignTemperature] = 7.0
  system_data[:PreheatDesignHumidityRatio] = 0.008
  system_data[:PrecoolDesignTemperature] = 13.0
  system_data[:PrecoolDesignHumidityRatio] = 0.008
  system_data[:SizingOption] = 'NonCoincident'
  system_data[:CoolingDesignAirFlowMethod] = 'DesignDay'
  system_data[:CoolingDesignAirFlowRate] = 0.0
  system_data[:HeatingDesignAirFlowMethod] = 'DesignDay'
  system_data[:HeatingDesignAirFlowRate] = 0.0
  system_data[:SystemOutdoorAirMethod] = 'ZoneSum'
  system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085
  system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:AllOutdoorAirinCooling] = true
  system_data[:AllOutdoorAirinHeating] = true
  system_data[:TypeofLoadtoSizeOn] = 'VentilationRequirement'
  system_data[:MinimumSystemAirFlowRatio] = 1.0
  system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation] = -10.0
  # Zone data
  system_data[:system_supply_air_temperature] = 20.0
  system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0
  system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3

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

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

  always_on = model.alwaysOnDiscreteSchedule

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

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

  if mau_type == true

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

    # Setup heating and cooling coils
    mau_clg_coil = OpenStudio::Model::CoilCoolingDXMultiSpeed.new(model)
    mau_clg_coil.setFuelType('Electricity')
    mau_clg_stage_1 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_stage_2 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_coil.addStage(mau_clg_stage_1)
    mau_clg_coil.addStage(mau_clg_stage_2)
    mau_clg_coil.setApplyPartLoadFractiontoSpeedsGreaterthan1(false)
    mau_htg_coil = OpenStudio::Model::CoilHeatingGasMultiStage.new(model)
    mau_htg_stage_1 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
    mau_htg_coil.addStage(mau_htg_stage_1)
    mau_htg_stage_1.setNominalCapacity(0.001)
    if mau_heating_coil_type == 'Electric'
      mau_supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
    elsif mau_heating_coil_type == 'Hot Water'
      mau_supplemental_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on)
      hw_loop.addDemandBranchForComponent(mau_supplemental_htg_coil)
    else
      raise("#{mau_heating_coil_type} is not a valid heating coil type.)")
    end

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

    # This method will seem like an error in number of args..but this is due to swig voodoo.
    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.new(model,
                                                                                              mau_fan,
                                                                                              mau_htg_coil,
                                                                                              mau_clg_coil,
                                                                                              mau_supplemental_htg_coil)
    air_to_air_heatpump.setName("#{zones[0].name} ASHP")
    air_to_air_heatpump.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation])
    air_to_air_heatpump.setControllingZoneorThermostatLocation(zones[0])
    air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
    air_to_air_heatpump.setNumberofSpeedsforHeating(1)
    air_to_air_heatpump.setNumberofSpeedsforCooling(2)

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

    # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be
    # set explicitly)
    oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum')

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

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

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

  zones.each do |zone|
    # Zone sizing temperature difference
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod('TemperatureDifference')
    sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(11.0)
    sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod('TemperatureDifference')
    sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(21.0)
    sizing_zone.setZoneCoolingSizingFactor(1.1)
    sizing_zone.setZoneHeatingSizingFactor(1.3)

    # Set up PTAC heating coil; apply always off schedule

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

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

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

      diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
      mau_air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
      # components for MAU
    end
    # of zone loop
  end
  return true
end
add_sys1_unitary_ac_baseboard_heating_single_speed(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, mau_type:, mau_heating_coil_type:, baseboard_type:, hw_loop:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb, line 30
def add_sys1_unitary_ac_baseboard_heating_single_speed(model:,
                                                       necb_reference_hp:false,
                                                       necb_reference_hp_supp_fuel:'DefaultFuel',
                                                       zones:,
                                                       mau_type:,
                                                       mau_heating_coil_type:,
                                                       baseboard_type:,
                                                       hw_loop:)
  # Keep all data and assumptions for both systems on the top here for easy reference.
  system_data = {}
  system_data[:name] = 'Sys_1_Make-up air unit'
  system_data[:PreheatDesignTemperature] = 7.0
  system_data[:PreheatDesignHumidityRatio] = 0.008
  system_data[:PrecoolDesignTemperature] = 13.0
  system_data[:PrecoolDesignHumidityRatio] = 0.008
  system_data[:SizingOption] = 'NonCoincident'
  system_data[:CoolingDesignAirFlowMethod] = 'DesignDay'
  system_data[:CoolingDesignAirFlowRate] = 0.0
  system_data[:HeatingDesignAirFlowMethod] = 'DesignDay'
  system_data[:HeatingDesignAirFlowRate] = 0.0
  system_data[:SystemOutdoorAirMethod] = 'ZoneSum'
  system_data[:CentralCoolingDesignSupplyAirHumidityRatio] = 0.0085
  system_data[:CentralHeatingDesignSupplyAirHumidityRatio] = 0.0080
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:AllOutdoorAirinCooling] = true
  system_data[:AllOutdoorAirinHeating] = true
  if necb_reference_hp
    system_data[:TypeofLoadtoSizeOn] = 'Total'
  else
    system_data[:TypeofLoadtoSizeOn] = 'VentilationRequirement'
  end
  system_data[:MinimumSystemAirFlowRatio] = 1.0
  # Zone Sizing data
  system_data[:system_supply_air_temperature] = 20.0
  system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0
  system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0
  system_data[:ZoneDXCoolingSizingFactor] = 1.0
  system_data[:ZoneDXHeatingSizingFactor] = 1.3
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3

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

  # If reference_hp = true, NECB 8.4.4.13 Heat Pump System Type 1: CAV Packaged rooftop heat pump with
  # zone baseboard (electric or hot water depending on argument baseboard_type)

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

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

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



  if mau_type == true
    mau_air_loop = common_air_loop(model: model, system_data: system_data)

    #if reference_hp
      # AirLoopHVACUnitaryHeatPumpAirToAir needs FanOnOff in order for the fan to turn off during off hours
    #  mau_fan = OpenStudio::Model::FanOnOff.new(model, always_on)
    #else
      mau_fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)
    #end
    # MAU Heating type selection.
    raise("Flag 'necb_reference_hp' is true while 'mau_heating_coil_type' is not set to type DX") if (necb_reference_hp && (mau_heating_coil_type != 'DX'))
    if mau_heating_coil_type == 'Electric' # electric coil
      mau_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
    elsif  mau_heating_coil_type == 'Hot Water'
      mau_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on)
      hw_loop.addDemandBranchForComponent(mau_htg_coil)
    elsif mau_heating_coil_type == 'DX'
      mau_htg_coil = add_onespeed_htg_DX_coil(model, always_on)
      mau_htg_coil.setName('CoilHeatingDXSingleSpeed_ashp')
    end

    # Set up Single Speed DX coil with
    mau_clg_coil = add_onespeed_DX_coil(model, always_on)
    mau_clg_coil.setName('CoilCoolingDXSingleSpeed_dx')
    mau_clg_coil.setName('CoilCoolingDXSingleSpeed_ashp') if necb_reference_hp

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

    # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be
    # set explicitly)
    oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum')

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

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

    # Reference HP requires slight changes to default MAU heating
    #if reference_hp
      # Create supplemental heating coil based on default regional fuel type
      # epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get)
      #primary_heating_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set']
      #if primary_heating_fuel == 'NaturalGas'
      #  supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
      #elsif primary_heating_fuel == 'Electricity' or  primary_heating_fuel == 'FuelOilNo2'
      #  supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
      #else #hot water coils is an option in the future
      #  raise('Invalid fuel type selected for heat pump supplemental coil')
      #end
      #air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, mau_fan, mau_htg_coil, mau_clg_coil, supplemental_htg_coil)
      #air_to_air_heatpump.setName("#{control_zone.name} ASHP")
      #air_to_air_heatpump.setControllingZone(control_zone)
      #air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
      #air_to_air_heatpump.addToNode(supply_inlet_node)
    #else
      mau_fan.addToNode(supply_inlet_node)
      mau_htg_coil.addToNode(supply_inlet_node)
      mau_clg_coil.addToNode(supply_inlet_node)

    #end
    oa_system.addToNode(supply_inlet_node)

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

  zones.each do |zone|
    # Zone sizing temperature difference
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
    sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
    # Different sizing factors for reference HP capacity
    if necb_reference_hp
      sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor])
      sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor])
    else
      sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
      sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])
    end

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

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

    # htg_coil_elec = OpenStudio::Model::CoilHeatingElectric.new(model,always_on)
    zero_outdoor_air = true # flag to set outside air flow to 0.0
    # Reference HP system does not use PTAC
    unless necb_reference_hp
      add_ptac_dx_cooling(model, zone, zero_outdoor_air)
    end

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

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

    if necb_reference_hp
      # Create CAV RH (RH based on region's default fuel type or user input)
      if necb_reference_hp_supp_fuel == 'DefaultFuel'
        epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get)
        necb_reference_hp_supp_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set']
      end
      if necb_reference_hp_supp_fuel == 'NaturalGas'
        rh_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
      elsif necb_reference_hp_supp_fuel == 'Electricity' or  necb_reference_hp_supp_fuel == 'FuelOilNo2'
        rh_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
      else #hot water coils is an option in the future
        raise('Invalid fuel type selected for heat pump supplemental coil')
      end
      cav_rh_terminal = OpenStudio::Model::AirTerminalSingleDuctConstantVolumeReheat.new(model, always_on, rh_coil)
      mau_air_loop.addBranchForZone(zone, cav_rh_terminal.to_StraightComponent)
    elsif mau_type == true
      diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
      mau_air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
      # components for MAU
    end
    # of zone loop
  end
  if mau_type
    sys_name_pars = {}
    sys_name_pars['sys_hr'] = 'none'
    sys_name_pars['sys_clg'] = 'dx'
    sys_name_pars['sys_clg'] = 'ashp' if necb_reference_hp
    sys_name_pars['sys_htg'] = mau_heating_coil_type
    sys_name_pars['sys_htg'] = 'ashp' if necb_reference_hp
    sys_name_pars['sys_sf'] = 'cv'
    sys_name_pars['zone_htg'] = baseboard_type
    sys_oa = 'doas'
    if necb_reference_hp
      sys_name_pars['zone_clg'] = 'none'
      sys_oa = 'mixed'
    else
      sys_name_pars['zone_clg'] = 'ptac'
      sys_oa = 'doas'
    end
    sys_name_pars['sys_rf'] = 'none'
    assign_base_sys_name(mau_air_loop,
                         sys_abbr: 'sys_1',
                         sys_oa: sys_oa,
                         sys_name_pars: sys_name_pars)
  end

  return true
end
add_sys2_FPFC_sys5_TPFC(model:, zones:, chiller_type:, fan_coil_type:, mau_cooling_type:, hw_loop:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_2_and_5.rb, line 2
def add_sys2_FPFC_sys5_TPFC(model:,
                            zones:,
                            chiller_type:,
                            fan_coil_type:,
                            mau_cooling_type:,
                            hw_loop:)

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

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

  # System 2 Zone data
  system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0
  system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3

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

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

  always_on = model.alwaysOnDiscreteSchedule

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

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

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

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

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

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

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

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

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

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

  # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be
  # set explicitly)
  oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum')

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

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

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

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

  # Set up zonal FC (ZoneHVAC,cooling coil, heating coil, fan) in each zone
  zones.each do |zone|
    # Zone sizing temperature difference
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
    sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])

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

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

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

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

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

    # add connections to FPFC.
    # This method will seem like an error in number of args..but this is due to swig voodoo.
    zone_fc = OpenStudio::Model::ZoneHVACFourPipeFanCoil.new(model, always_on, fc_fan, fc_clg_coil, fc_htg_coil)
    zone_fc.addToThermalZone(zone)

    # Create a diffuser and attach the zone/diffuser pair to the air loop (make-up air unit)
    diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
    air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
    # zone loop
  end
  sys_abbr = 'sys_2'
  sys_abbr = 'sys_5' if fan_coil_type == 'TPFC'
  sys_name_pars = {}
  sys_name_pars['sys_hr'] = 'none'
  sys_name_pars['sys_clg'] = mau_cooling_type
  sys_name_pars['sys_htg'] = 'g'
  sys_name_pars['sys_sf'] = 'cv'
  sys_name_pars['zone_htg'] = fan_coil_type
  sys_name_pars['zone_clg'] = fan_coil_type
  sys_name_pars['sys_rf'] = 'none'
  assign_base_sys_name(mau_air_loop,
                       sys_abbr: sys_abbr,
                       sys_oa: 'doas',
                       sys_name_pars: sys_name_pars)
end
add_sys3_and_8_zone_equip(air_loop, baseboard_type, hw_loop, model, zone) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb, line 250
def add_sys3_and_8_zone_equip(air_loop,
                              baseboard_type,
                              hw_loop, model,
                              zone)
  always_on = model.alwaysOnDiscreteSchedule
  add_zone_baseboards(baseboard_type: baseboard_type, hw_loop: hw_loop, model: model, zone: zone)
  diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
  air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
end
add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, heating_coil_type:, baseboard_type:, hw_loop:, new_auto_zoner: true, multispeed: false) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb, line 2
def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model:,
                                                                          necb_reference_hp:false,
                                                                          necb_reference_hp_supp_fuel:'DefaultFuel',
                                                                          zones:,
                                                                          heating_coil_type:,
                                                                          baseboard_type:,
                                                                          hw_loop:,
                                                                          new_auto_zoner: true,
                                                                          multispeed: false)
  if multispeed
    add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed(model: model,
                                                                                      zones: zones,
                                                                                      heating_coil_type: heating_coil_type,
                                                                                      baseboard_type: baseboard_type,
                                                                                      hw_loop: hw_loop,
                                                                                      new_auto_zoner: new_auto_zoner)
  else
    add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model: model,
                                                                                       necb_reference_hp: necb_reference_hp,
                                                                                       necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                                                       zones: zones,
                                                                                       heating_coil_type: heating_coil_type,
                                                                                       baseboard_type: baseboard_type,
                                                                                       hw_loop: hw_loop,
                                                                                       new_auto_zoner: new_auto_zoner)

  end
end
add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed(model:, zones:, heating_coil_type:, baseboard_type:, hw_loop:, new_auto_zoner: true) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb, line 3
def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed(model:,
                                                                                      zones:,
                                                                                      heating_coil_type:,
                                                                                      baseboard_type:,
                                                                                      hw_loop:,
                                                                                      new_auto_zoner: true)
  system_data = {}
  system_data[:name] = 'Sys_3_PSZ'
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:AllOutdoorAirinCooling] = false
  system_data[:AllOutdoorAirinHeating] = false
  system_data[:TypeofLoadtoSizeOn] = 'Sensible'
  system_data[:MinimumSystemAirFlowRatio] = 1.0

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

  # System 3 Zone data
  system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0
  system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.0
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 43.0
  system_data[:ZoneDXCoolingSizingFactor] = 1.0
  system_data[:ZoneDXHeatingSizingFactor] = 1.3
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3
  system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation] = -10.0

  if new_auto_zoner == true
    # Create system airloop

    # Add Air Loop
    air_loop = add_system_3_and_8_airloop_multi_speed(heating_coil_type,
                                                      model,
                                                      system_data,
                                                      determine_control_zone(zones))
    # Add Zone equipment
    zones.each do |zone| # Zone sizing temperature difference
      sizing_zone = zone.sizingZone
      sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
      sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
      sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
      sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
      sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
      sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])
      add_sys3_and_8_zone_equip(air_loop,
                                baseboard_type,
                                hw_loop,
                                model,
                                zone)
    end
    return true
  else
    zones.each do |zone|
      air_loop = add_system_3_and_8_airloop_multi_speed(heating_coil_type, model, system_data, zone)
      add_sys3_and_8_zone_equip(air_loop,
                                baseboard_type,
                                hw_loop,
                                model,
                                zone)
    end
    return true
  end
end
add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, heating_coil_type:, baseboard_type:, hw_loop:, new_auto_zoner: true) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb, line 33
def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model:,
                                                                                       necb_reference_hp:false,
                                                                                       necb_reference_hp_supp_fuel:'DefaultFuel',
                                                                                       zones:,
                                                                                       heating_coil_type:,
                                                                                       baseboard_type:,
                                                                                       hw_loop:,
                                                                                       new_auto_zoner: true)
  system_data = {}
  system_data[:name] = 'Sys_3_PSZ'
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:AllOutdoorAirinCooling] = false
  system_data[:AllOutdoorAirinHeating] = false
  system_data[:TypeofLoadtoSizeOn] = 'Sensible'
  system_data[:MinimumSystemAirFlowRatio] = 1.0

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

  # System 3 Zone data
  system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0
  system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.0
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 43.0
  system_data[:ZoneDXCoolingSizingFactor] = 1.0
  system_data[:ZoneDXHeatingSizingFactor] = 1.3
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3
  system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation] = -10.0
  if new_auto_zoner == true
    # Create system airloop

    # Add Air Loop
    air_loop = add_system_3_and_8_airloop(heating_coil_type,
                                          model,
                                          system_data,
                                          determine_control_zone(zones),
                                          necb_reference_hp: necb_reference_hp,
                                          necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel)
    # Add Zone equipment
    zones.each do |zone| # Zone sizing temperature difference
      sizing_zone = zone.sizingZone
      sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
      sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
      sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
      sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
      if necb_reference_hp
        sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor])
        sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor])
      else
        sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
        sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])
      end
      add_sys3_and_8_zone_equip(air_loop,
                                baseboard_type,
                                hw_loop,
                                model,
                                zone)

    end
  else
    zones.each do |zone|
      air_loop = add_system_3_and_8_airloop(heating_coil_type, model, system_data, zone, necb_reference_hp: necb_reference_hp, necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel)
      add_sys3_and_8_zone_equip(air_loop,
                                baseboard_type,
                                hw_loop,
                                model,
                                zone)
    end
  end
  sys_name_pars = {}
  sys_name_pars['sys_hr'] = 'none'
  sys_name_pars['sys_clg'] = 'dx'
  sys_name_pars['sys_clg'] = 'ashp' if necb_reference_hp
  sys_name_pars['sys_htg'] = heating_coil_type
  sys_name_pars['sys_htg'] = 'ashp' if necb_reference_hp
  sys_name_pars['sys_sf'] = 'cv'
  sys_name_pars['zone_htg'] = baseboard_type
  sys_name_pars['zone_clg'] = 'none'
  sys_name_pars['sys_rf'] = 'none'
  assign_base_sys_name(air_loop,
                       sys_abbr: 'sys_3',
                       sys_oa: 'mixed',
                       sys_name_pars: sys_name_pars)
  return true
end
add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', zones:, heating_coil_type:, baseboard_type:, hw_loop:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_4.rb, line 2
def add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model:,
                                                                 necb_reference_hp:false,
                                                                 necb_reference_hp_supp_fuel:'DefaultFuel',
                                                                 zones:,
                                                                 heating_coil_type:,
                                                                 baseboard_type:,
                                                                 hw_loop:)
  system_data = {}
  system_data[:name] = 'Sys_4_PSZ'
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:AllOutdoorAirinCooling] = false
  system_data[:AllOutdoorAirinHeating] = false
  system_data[:TypeofLoadtoSizeOn] = 'Sensible'
  system_data[:MinimumSystemAirFlowRatio] = 1.0

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

  # zone
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMax] = 43.0
  system_data[:SetpointManagerSingleZoneReheatSupplyTempMin] = 13.0
  system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0
  system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0
  system_data[:ZoneHeatingDesignSupplyAirTemperature] = 43.0
  system_data[:ZoneDXCoolingSizingFactor] = 1.0
  system_data[:ZoneDXHeatingSizingFactor] = 1.3
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3

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

  always_on = model.alwaysOnDiscreteSchedule

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

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

  # Zone sizing temperature difference
  sizing_zone = control_zone.sizingZone
  sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
  sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
  sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
  sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
  if necb_reference_hp
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor])
  else
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])
  end

  if necb_reference_hp
    # AirLoopHVACUnitaryHeatPumpAirToAir needs FanOnOff in order for the fan to turn off during off hours
    fan = OpenStudio::Model::FanOnOff.new(model, always_on)
  else
    fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)
  end

  # Set up DX coil with NECB performance curve characteristics;
  clg_coil = add_onespeed_DX_coil(model, always_on)
  clg_coil.setName('CoilCoolingDXSingleSpeed_dx')
  clg_coil.setName('CoilCoolingDXSingleSpeed_ashp') if necb_reference_hp

  raise("Flag 'necb_reference_hp' is set to true while parameter 'heating_coil_type' is not set to DX") if (necb_reference_hp && (heating_coil_type != 'DX'))
  if heating_coil_type == 'Electric' # electric coil
    htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
  elsif heating_coil_type == 'Gas'
    htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
  elsif heating_coil_type == 'DX'
    htg_coil = add_onespeed_htg_DX_coil(model, always_on)
    htg_coil.setName('CoilHeatingDXSingleSpeed_ashp')
  end

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

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

  # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be
  # set explicitly)
  oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum')

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

  # Add the components to the air loop
  # in order from closest to zone to furthest from zone
  supply_inlet_node = air_loop.supplyInletNode
  if necb_reference_hp
    #create supplemental heating coil based on default regional fuel type
    if necb_reference_hp_supp_fuel == 'DefaultFuel'
      epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get)
      necb_reference_hp_supp_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set']
    end
    if necb_reference_hp_supp_fuel == 'NaturalGas'
      supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
    elsif necb_reference_hp_supp_fuel == 'Electricity' or  necb_reference_hp_supp_fuel == 'FuelOilNo2'
      supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
    else #hot water coils is an option in the future
      raise('Invalid fuel type selected for heat pump supplemental coil')
    end
    # This method will seem like an error in number of args..but this is due to swig voodoo.
    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, fan, htg_coil, clg_coil, supplemental_htg_coil)
    air_to_air_heatpump.setName("#{control_zone.name} ASHP")
    air_to_air_heatpump.setControllingZone(control_zone)
    air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
    air_to_air_heatpump.addToNode(supply_inlet_node)
  else
    fan.addToNode(supply_inlet_node)
    htg_coil.addToNode(supply_inlet_node)
    clg_coil.addToNode(supply_inlet_node)

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

  # Create sensible heat exchanger
  #              heat_exchanger = BTAP::Resources::HVAC::Plant::add_hrv(model)
  #              heat_exchanger.setSensibleEffectivenessat100HeatingAirFlow(0.5)
  #              heat_exchanger.setSensibleEffectivenessat75HeatingAirFlow(0.5)
  #              heat_exchanger.setSensibleEffectivenessat100CoolingAirFlow(0.5)
  #              heat_exchanger.setSensibleEffectivenessat75CoolingAirFlow(0.5)
  #              heat_exchanger.setLatentEffectivenessat100HeatingAirFlow(0.0)
  #              heat_exchanger.setLatentEffectivenessat75HeatingAirFlow(0.0)
  #              heat_exchanger.setLatentEffectivenessat100CoolingAirFlow(0.0)
  #              heat_exchanger.setLatentEffectivenessat75CoolingAirFlow(0.0)
  #              heat_exchanger.setSupplyAirOutletTemperatureControl(false)
  #
  #              Connect heat exchanger
  #              oa_node = oa_system.outboardOANode
  #              heat_exchanger.addToNode(oa_node.get)
  zones.each do |zone|
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
    sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
    sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
    if necb_reference_hp
      sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor])
      sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor])
    else
      sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
      sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])
    end
    # Create a diffuser and attach the zone/diffuser pair to the air loop
    # diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model,always_on)
    add_zone_baseboards(baseboard_type: baseboard_type,
                        hw_loop: hw_loop,
                        model: model,
                        zone: zone)
    diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
    air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
    # zone loop
  end
  sys_name_pars = {}
  sys_name_pars['sys_hr'] = 'none'
  sys_name_pars['sys_clg'] = 'dx'
  sys_name_pars['sys_clg'] = 'ashp' if necb_reference_hp
  sys_name_pars['sys_htg'] = heating_coil_type
  sys_name_pars['sys_htg'] = 'ashp' if necb_reference_hp
  sys_name_pars['sys_sf'] = 'cv'
  sys_name_pars['zone_htg'] = baseboard_type
  sys_name_pars['zone_clg'] = 'none'
  sys_name_pars['sys_rf'] = 'none'
  assign_base_sys_name(air_loop,
                       sys_abbr: 'sys_4',
                       sys_oa: 'mixed',
                       sys_name_pars: sys_name_pars)

  return true
end
add_sys6_multi_zone_built_up_system_with_baseboard_heating(model:, zones:, heating_coil_type:, baseboard_type:, chiller_type:, fan_type:, hw_loop:) click to toggle source

end add_sys4_single_zone_make_up_air_unit_with_baseboard_heating

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

  # zone data
  system_data[:system_supply_air_temperature] = 13.0
  system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0
  system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0
  system_data[:ZoneCoolingSizingFactor] = 1.1
  system_data[:ZoneHeatingSizingFactor] = 1.3
  system_data[:ZoneVAVMinFlowFactorPerFloorArea] = 0.002
  system_data[:ZoneVAVMaxReheatTemp] = 43.0
  system_data[:ZoneVAVDamperAction] = 'Normal'

  always_on = model.alwaysOnDiscreteSchedule

  # Chilled Water Plant

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

  # Condenser System

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

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

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

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

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

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

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

      # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be
      # set explicitly)
      oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum')

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

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

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

      # Make a VAV terminal with HW reheat for each zone on this story that is in intersection with the zones array.
      # and hook the reheat coil to the HW loop
      (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).each do |zone|
        # Zone sizing parameters
        sizing_zone = zone.sizingZone
        sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
        sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
        sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
        sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
        sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
        sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])

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

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

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

      end
      sys_name_pars = {}
      sys_name_pars['sys_hr'] = 'none'
      sys_name_pars['sys_htg'] = heating_coil_type
      sys_name_pars['sys_clg'] = 'Chilled Water'
      sys_name_pars['sys_sf'] = 'vv'
      sys_name_pars['zone_htg'] = baseboard_type
      sys_name_pars['zone_clg'] = 'none'
      sys_name_pars['sys_rf'] = 'vv'
      assign_base_sys_name(air_loop,
                           sys_abbr: 'sys_6',
                           sys_oa: 'mixed',
                           sys_name_pars: sys_name_pars)
    end
    # next story
  end
  # for debugging
  # puts "end add_sys6_multi_zone_built_up_with_baseboard_heating"
  return true
end
add_sys6_multi_zone_reference_hp_with_baseboard_heating(model:, zones:, heating_coil_type:, baseboard_type:, hw_loop:, necb_reference_hp_supp_fuel:'DefaultFuel') click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb, line 316
def add_sys6_multi_zone_reference_hp_with_baseboard_heating(model:,
                                                            zones:,
                                                            heating_coil_type:,
                                                            baseboard_type:,
                                                            hw_loop:,
                                                            necb_reference_hp_supp_fuel:'DefaultFuel')

  #system data
  system_data = {}
  system_data[:name] = 'Sys_6_VAV with Reheat'
  system_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_data[:CentralHeatingDesignSupplyAirTemperature] = 13.1
  system_data[:AllOutdoorAirinCooling] = false
  system_data[:AllOutdoorAirinHeating] = false

  # zone data
  system_data[:system_supply_air_temperature] = 13.0
  system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0
  system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0
  system_data[:ZoneDXCoolingSizingFactor] = 1.0
  system_data[:ZoneDXHeatingSizingFactor] = 1.3


  always_on = model.alwaysOnDiscreteSchedule

  model.getBuildingStorys.sort.each do |story|
    unless (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).empty?

      air_loop = common_air_loop(model: model, system_data: system_data)
      air_loop.setName('Sys_6_CAV')

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

      htg_coil = add_onespeed_htg_DX_coil(model, always_on)
      htg_coil.setName('CoilHeatingDXSingleSpeed_ashp')

      clg_coil = add_onespeed_DX_coil(model, always_on)
      clg_coil.setName('CoilCoolingDXSingleSpeed_ashp')

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

      # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be
      # set explicitly)
      oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum')
      oa_system = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, oa_controller)

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

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

      # Make CAV terminals for each zone on this story that is in intersection with the zones array.
      (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).each do |zone|
        # Zone sizing parameters
        sizing_zone = zone.sizingZone
        sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
        sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
        sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
        sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
        sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor])
        sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor])

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

        # Create CAV RH (RH based on region's default fuel type)
        if necb_reference_hp_supp_fuel == 'DefaultFuel'
          epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get)
          necb_reference_hp_supp_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set']
        end
        if necb_reference_hp_supp_fuel == 'NaturalGas'
          rh_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
        elsif necb_reference_hp_supp_fuel == 'Electricity' or  necb_reference_hp_supp_fuel == 'FuelOilNo2'
          rh_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
        else #hot water coils is an option in the future
          raise('Invalid fuel type selected for heat pump supplemental coil')
        end
        cav_rh_terminal = OpenStudio::Model::AirTerminalSingleDuctConstantVolumeReheat.new(model, always_on, rh_coil)
        air_loop.addBranchForZone(zone, cav_rh_terminal.to_StraightComponent)
      end
      sys_name_pars = {}
      sys_name_pars['sys_hr'] = 'none'
      sys_name_pars['sys_htg'] = 'ashp'
      sys_name_pars['sys_clg'] = 'ashp'
      sys_name_pars['sys_sf'] = 'cv'
      sys_name_pars['zone_htg'] = baseboard_type
      sys_name_pars['zone_clg'] = 'none'
      sys_name_pars['sys_rf'] = 'cv'
      assign_base_sys_name(air_loop,
                           sys_abbr: 'sys_6',
                           sys_oa: 'mixed',
                           sys_name_pars: sys_name_pars)

    end
  end



end
add_system_3_and_8_airloop(heating_coil_type, model, system_data, control_zone, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel') click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb, line 133
def add_system_3_and_8_airloop(heating_coil_type, model, system_data, control_zone, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel')
  # System Type 3: PSZ-AC
  # This measure creates:
  # -a constant volume packaged single-zone A/C unit
  # for each zone in the building; DX cooling with
  # heating coil: fuel-fired or electric, depending on argument heating_coil_type
  # heating_coil_type choices are "Electric", "Gas", "DX"
  # zone baseboards: hot water or electric, depending on argument baseboard_type
  # baseboard_type choices are "Hot Water" or "Electric"
  # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e.
  # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1"

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

  # Zone sizing temperature difference
  sizing_zone = control_zone.sizingZone
  sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
  sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
  sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
  sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
  if necb_reference_hp
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor])
  else
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])
  end

  if necb_reference_hp
    #AirLoopHVACUnitaryHeatPumpAirToAir needs FanOnOff in order for the fan to turn off during off hours
    fan = OpenStudio::Model::FanOnOff.new(model, always_on)
  else
    fan = OpenStudio::Model::FanConstantVolume.new(model, always_on)
  end

  # Set up DX coil
  if necb_reference_hp #NECB curve characteristics
    clg_coil = add_onespeed_DX_coil(model, always_on)
    clg_coil.setName('CoilCoolingDXSingleSpeed_ashp')
  else
    clg_coil = OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model) #sets default OS curve (but will be replaced with NECB curves later)
    clg_coil.setName('CoilCoolingDXSingleSpeed_dx')
  end

  raise("Flag 'necb_reference_hp' is set to true while parameter 'heating_coil_type' is not set to DX") if (necb_reference_hp && (heating_coil_type != 'DX'))
  case heating_coil_type
  when 'Electric' # electric coil
    htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
  when 'Gas'
    htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
  when 'DX'
    #create main DX heating coil
    htg_coil = add_onespeed_htg_DX_coil(model, always_on)
    htg_coil.setName('CoilHeatingDXSingleSpeed_ashp')
    sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneDXHeatingSizingFactor])
    sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneDXCoolingSizingFactor])
  else
    raise("#{heating_coil_type} is not a valid heating coil type.)")
  end

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



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

  # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be
  # set explicitly)
  oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum')

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

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

    #create supplemental heating coil based on default regional fuel type
    if necb_reference_hp_supp_fuel == 'DefaultFuel'
      epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get)
      necb_reference_hp_supp_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set']
    end
    if necb_reference_hp_supp_fuel == 'NaturalGas'
      supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
    elsif necb_reference_hp_supp_fuel == 'Electricity' or  necb_reference_hp_supp_fuel == 'FuelOilNo2'
      supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
    else #hot water coils is an option in the future
      raise('Invalid fuel type selected for heat pump supplemental coil')
    end
    # This method will seem like an error in number of args..but this is due to swig voodoo.
    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, fan, htg_coil, clg_coil, supplemental_htg_coil)
    air_to_air_heatpump.setName("#{control_zone.name} ASHP")
    air_to_air_heatpump.setControllingZone(control_zone)
    air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
    air_to_air_heatpump.addToNode(supply_inlet_node)
  else
    fan.addToNode(supply_inlet_node)
    htg_coil.addToNode(supply_inlet_node)
    clg_coil.addToNode(supply_inlet_node)
  end
  oa_system.addToNode(supply_inlet_node)

  # Add a setpoint manager single zone reheat to control the
  # supply air temperature based on the needs of this zone
  setpoint_mgr_single_zone_reheat = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model)
  setpoint_mgr_single_zone_reheat.setControlZone(control_zone)
  setpoint_mgr_single_zone_reheat.setMinimumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMin])
  setpoint_mgr_single_zone_reheat.setMaximumSupplyAirTemperature(system_data[:SetpointManagerSingleZoneReheatSupplyTempMax])
  setpoint_mgr_single_zone_reheat.addToNode(air_loop.supplyOutletNode)
  return air_loop
end
add_system_3_and_8_airloop_multi_speed(heating_coil_type, model, system_data, control_zone) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb, line 81
def add_system_3_and_8_airloop_multi_speed(heating_coil_type, model, system_data, control_zone)
  # System Type 3: PSZ-AC
  # This measure creates:
  # -a constant volume packaged single-zone A/C unit
  # for each zone in the building; DX cooling with
  # heating coil: fuel-fired or electric, depending on argument heating_coil_type
  # heating_coil_type choices are "Electric", "Gas", "DX"
  # zone baseboards: hot water or electric, depending on argument baseboard_type
  # baseboard_type choices are "Hot Water" or "Electric"
  # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e.
  # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1"

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

  # Zone sizing temperature difference
  sizing_zone = control_zone.sizingZone
  sizing_zone.setZoneCoolingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod])
  sizing_zone.setZoneCoolingDesignSupplyAirTemperatureDifference(system_data[:ZoneCoolingDesignSupplyAirTemperatureDifference])
  sizing_zone.setZoneHeatingDesignSupplyAirTemperatureInputMethod(system_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod])
  sizing_zone.setZoneHeatingDesignSupplyAirTemperatureDifference(system_data[:ZoneHeatingDesignSupplyAirTemperatureDifference])
  sizing_zone.setZoneCoolingSizingFactor(system_data[:ZoneCoolingSizingFactor])
  sizing_zone.setZoneHeatingSizingFactor(system_data[:ZoneHeatingSizingFactor])

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

  # Setup heating and cooling coils
  if (heating_coil_type == 'Gas') || (heating_coil_type == 'Electric')
    clg_coil = OpenStudio::Model::CoilCoolingDXMultiSpeed.new(model)
    clg_coil.setFuelType('Electricity')
    clg_stage_1 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_stage_2 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_coil.addStage(clg_stage_1)
    clg_coil.addStage(clg_stage_2)
    clg_coil.setApplyPartLoadFractiontoSpeedsGreaterthan1(false)
    htg_coil = OpenStudio::Model::CoilHeatingGasMultiStage.new(model)
    htg_stage_1 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
    htg_coil.addStage(htg_stage_1)
    if heating_coil_type == 'Gas'
      supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
      supplemental_htg_coil.setNominalCapacity(0.001)
    elsif heating_coil_type == 'Electric'
      supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
      htg_stage_1.setNominalCapacity(0.001)
    end
  # Single stage DX and Electric heating
  elsif heating_coil_type == 'DX'
    clg_coil = OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model)
    clg_coil.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation])
    htg_coil = OpenStudio::Model::CoilHeatingDXSingleSpeed.new(model)
    supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
  else
    raise("#{heating_coil_type} is not a valid heating coil type.)")
  end

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

  # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be
  # set explicitly)
  oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum')

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

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

  if (heating_coil_type == 'Gas') || (heating_coil_type == 'Electric')
    # This method will seem like an error in number of args..but this is due to swig voodoo.
    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.new(model, fan, htg_coil, clg_coil, supplemental_htg_coil)
    air_to_air_heatpump.setControllingZoneorThermostatLocation(control_zone)
    air_to_air_heatpump.setNumberofSpeedsforHeating(1)
    air_to_air_heatpump.setNumberofSpeedsforCooling(2)
    air_to_air_heatpump.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(system_data[:MinimumOutdoorDryBulbTemperatureforCompressorOperation])
  elsif heating_coil_type == 'DX'
    # This method will seem like an error in number of args..but this is due to swig voodoo.
    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, fan, htg_coil, clg_coil, supplemental_htg_coil)
    air_to_air_heatpump.setControllingZone(zone)
  end
  air_to_air_heatpump.setName("#{control_zone.name} ASHP")
  air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
  air_to_air_heatpump.addToNode(supply_inlet_node)

  oa_system.addToNode(supply_inlet_node)

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

  return air_loop
end
add_zone_baseboards(baseboard_type:, hw_loop:, model:, zone:) click to toggle source

Zonal systems

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2045
def add_zone_baseboards(baseboard_type:,
                        hw_loop:,
                        model:,
                        zone:)
  always_on = model.alwaysOnDiscreteSchedule
  if baseboard_type == 'Electric'
    zone_elec_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.new(model)
    zone_elec_baseboard.addToThermalZone(zone)
  end

  return unless baseboard_type == 'Hot Water'

  baseboard_coil = OpenStudio::Model::CoilHeatingWaterBaseboard.new(model)
  # Connect baseboard coil to hot water loop
  hw_loop.addDemandBranchForComponent(baseboard_coil)
  zone_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveWater.new(model, always_on, baseboard_coil)
  # add zone_baseboard to zone
  zone_baseboard.addToThermalZone(zone)
end
adjust_wildcard_spacetype_schedule(space:, schedule:, lights_type: 'NECB_Default', lights_scale: 1.0) click to toggle source

Set wildcard spactype schedule to NECB letter index.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 726
def adjust_wildcard_spacetype_schedule(space:, schedule:, lights_type: 'NECB_Default', lights_scale: 1.0)
  if space.spaceType.empty?
    OpenStudio.logFree(OpenStudio::Error, "Error: No spacetype assigned for #{space.name.get}. This must be assigned. Aborting.")
  end
  # Get current spacetype name
  space_type_name = space.spaceType.get.standardsSpaceType.get.to_s
  # Determine new spacetype name.
  regex = /^(.*sch-)(\S)$/
  new_spacetype_name = "#{space_type_name.match(regex).captures.first}#{schedule}"
  new_spacetype = nil

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

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

@note this method assumes you previously checked that an economizer is required at all

via #economizer_required?

@param (see economizer_required?) @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 90
def air_loop_hvac_apply_economizer_integration(air_loop_hvac, climate_zone)
  # Get the OA system and OA controller
  oa_sys = air_loop_hvac.airLoopHVACOutdoorAirSystem
  # No OA system
  return false if !oa_sys.is_initialized

  oa_sys = oa_sys.get
  oa_control = oa_sys.getControllerOutdoorAir

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

  return true
end
air_loop_hvac_apply_energy_recovery_ventilator(air_loop_hvac, climate = nil) click to toggle source

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

@param (see economizer_required?) @return [Boolean] Returns true if required, false if not. @todo Add exception logic for systems serving parking garage, warehouse, or multifamily

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

  # Create an ERV

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

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

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

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

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

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

  return true
end
air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(air_loop_hvac) click to toggle source

NECB does not change damper positions

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

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 13
def air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(air_loop_hvac)
  # Do not change anything.
  return true
end
air_loop_hvac_apply_single_zone_controls(air_loop_hvac, climate_zone) click to toggle source

NECB has no single zone air loop control requirements

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

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 475
def air_loop_hvac_apply_single_zone_controls(air_loop_hvac, climate_zone)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: No special economizer controls were modeled.")
  return true
end
air_loop_hvac_apply_vav_damper_action(air_loop_hvac) click to toggle source

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

@return [Boolean] Returns true if successful, false if not @todo see if this impacts the sizing run.

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 427
def air_loop_hvac_apply_vav_damper_action(air_loop_hvac)
  damper_action = 'Single Maximum'

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

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

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

  return true
end
air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac, climate_zone) click to toggle source

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

@param (see economizer_required?) @return [Boolean] Returns true if required, false if not. @todo Add exception logic for

systems that serve multifamily, parking garage, warehouse
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 416
def air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac, climate_zone)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{template} #{climate_zone}:  #{air_loop_hvac.name}: DCV is not required for any system.")
  dcv_required = false
  return dcv_required
end
air_loop_hvac_economizer_required?(air_loop_hvac) click to toggle source

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

@return [Boolean] returns true if an economizer is required, false if not

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 22
def air_loop_hvac_economizer_required?(air_loop_hvac)
  economizer_required = false

  # need a better way to determine if an economizer is needed.
  return economizer_required if ((air_loop_hvac.name.to_s.include? 'Outpatient F1' ) ||
                                 (air_loop_hvac.sizingSystem.typeofLoadtoSizeOn.to_s == "VentilationRequirement"))

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

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

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

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

  return economizer_required
end
air_loop_hvac_enable_unoccupied_fan_shutoff(air_loop_hvac, min_occ_pct = 0.05) click to toggle source

Shut off the system during unoccupied periods. During these times, systems will cycle on briefly if temperature drifts below setpoint. For systems with fan-powered terminals, the whole system (not just the terminal fans) will cycle on. Terminal-only night cycling is not used because the terminals cannot provide cooling, so terminal-only night cycling leads to excessive unmet cooling hours during unoccupied periods. If the system already has a schedule other than Always-On, no change will be made. If the system has an Always-On schedule assigned, a new schedule will be created. In this case, occupied is defined as the total percent occupancy for the loop for all zones served.

@param min_occ_pct [Double] the fractional value below which the system will be considered unoccupied. @return [Boolean] true if successful, false if not

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1064
def air_loop_hvac_enable_unoccupied_fan_shutoff(air_loop_hvac, min_occ_pct = 0.05)
  # Set the system to night cycle
  air_loop_hvac.setNightCycleControlType('CycleOnAny')

  # Check if already using a schedule other than always on
  avail_sch = air_loop_hvac.availabilitySchedule
  unless avail_sch == air_loop_hvac.model.alwaysOnDiscreteSchedule
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: Availability schedule is already set to #{avail_sch.name}.  Will assume this includes unoccupied shut down; no changes will be made.")
    return true
  end

  # Get the airloop occupancy schedule
  loop_occ_sch = air_loop_hvac_get_occupancy_schedule(air_loop_hvac, occupied_percentage_threshold: min_occ_pct)
  flh = OpenstudioStandards::Schedules.schedule_get_equivalent_full_load_hours(loop_occ_sch)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: Annual occupied hours = #{flh.round} hr/yr, assuming a #{min_occ_pct} occupancy threshold.  This schedule will be used as the HVAC operation schedule.")

  # Set HVAC availability schedule to follow occupancy
  air_loop_hvac.setAvailabilitySchedule(loop_occ_sch)
  air_loop_hvac.supplyComponents.each do |comp|
    if comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.is_initialized
      comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.get.setSupplyAirFanOperatingModeSchedule(loop_occ_sch)
    elsif comp.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.is_initialized
      comp.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.get.setAvailabilitySchedule(loop_occ_sch)
    end
  end

  return true
end
air_loop_hvac_energy_recovery_ventilator_required?(air_loop_hvac, climate_zone) click to toggle source

Check if ERV is required on this airloop.

@param (see economizer_required?) @return [Boolean] Returns true if required, false if not. @todo Add exception logic for systems serving parking garage, warehouse, or multifamily

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

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

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

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

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

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

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

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

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

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

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

  # initialize counters
  sum_zone_oa = 0.0
  sum_zone_oa_times_heat_design_t = 0.0

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

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

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

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

  # Get January winter design temperature
  # get model weather file name
  weather_file_path = air_loop_hvac.model.weatherFile.get.path.get.to_s
  stat_file_path = weather_file_path.gsub('.epw', '.stat')
  stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)

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

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

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

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

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

  return erv_required
end
air_loop_hvac_motorized_oa_damper_limits(air_loop_hvac, climate_zone) click to toggle source

Determine the air flow and number of story limits for whether motorized OA damper is required. @return [Array<Double>] [minimum_oa_flow_cfm, maximum_stories]. If both nil, never required

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 493
def air_loop_hvac_motorized_oa_damper_limits(air_loop_hvac, climate_zone)
  minimum_oa_flow_cfm = 0
  maximum_stories = 0
  return [minimum_oa_flow_cfm, maximum_stories]
end
air_loop_hvac_static_pressure_reset_required?(air_loop_hvac, has_ddc) click to toggle source

NECB doesn’t require static pressure reset.

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

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 483
def air_loop_hvac_static_pressure_reset_required?(air_loop_hvac, has_ddc)
  # static pressure reset not required
  sp_reset_required = false
  return sp_reset_required
end
air_terminal_single_duct_vav_reheat_set_heating_cap(air_terminal_single_duct_vav_reheat) click to toggle source

Sets the capacity of the reheat coil based on the minimum flow fraction, and the maximum flow rate.

@param air_terminal_single_duct_vav_reheat [OpenStudio::Model::AirTerminalSingleDuctVAVReheat] the air terminal object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2497
def air_terminal_single_duct_vav_reheat_set_heating_cap(air_terminal_single_duct_vav_reheat)
  flow_rate_fraction = 0.0
  if air_terminal_single_duct_vav_reheat.constantMinimumAirFlowFraction.is_initialized
    flow_rate_fraction = air_terminal_single_duct_vav_reheat.constantMinimumAirFlowFraction.get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirTerminalSingleDuctVAVReheat', \
    "Minimum flow fraction is not defined for terminal device #{air_terminal_single_duct_vav_reheat.name}")
    return false
  end
  cap = 1.2 * 1000.0 * flow_rate_fraction * air_terminal_single_duct_vav_reheat.autosizedMaximumAirFlowRate.to_f * (43.0 - 13.0)
  if air_terminal_single_duct_vav_reheat.reheatCoil.to_CoilHeatingElectric.is_initialized
    reheat_coil = air_terminal_single_duct_vav_reheat.reheatCoil.to_CoilHeatingElectric.get
    reheat_coil.setNominalCapacity(cap)
  elsif air_terminal_single_duct_vav_reheat.reheatCoil.to_CoilHeatingWater.is_initialized
    reheat_coil = air_terminal_single_duct_vav_reheat.reheatCoil.to_CoilHeatingWater.get
    reheat_coil.setPerformanceInputMethod('NominalCapacity')
    reheat_coil.setRatedCapacity(cap)
  end
  air_terminal_single_duct_vav_reheat.setMaximumReheatAirTemperature(43.0)
  return true
end
apply_auto_zoning(model:, sizing_run_dir: Dir.pwd, lights_type: 'NECB_Default', lights_scale: 1.0) click to toggle source

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

  • Served by the same HVAC system

  • Similar operation and function

  • Similar heating/cooling loads

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 54
def apply_auto_zoning(model:, sizing_run_dir: Dir.pwd, lights_type: 'NECB_Default', lights_scale: 1.0)
  raise('validation of model failed.') unless validate_initial_model(model)

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

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

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

  # collect sizing information on each space.
  store_space_sizing_loads(model)
  # Remove any Thermal zones assigned again to start fresh.
  model.getThermalZones.each(&:remove)
  auto_zone_dwelling_units(model)
  auto_zone_wet_spaces(model: model, lights_type: lights_type, lights_scale: lights_scale)
  auto_zone_all_other_spaces(model)
  auto_zone_wild_spaces(model: model, lights_type: lights_type, lights_scale: lights_scale)
  # This will color the spaces and zones.
  random = Random.new(1234)
  # Set ideal hvac in case we want to not implement the hvac yet and still run osm right after this function.
  # model.getThermalZones.each { |zone| zone.setUseIdealAirLoads(true) }
  model.getThermalZones.sort.each { |item| item.setRenderingColor(set_random_rendering_color(item, random)) }
  model.getSpaceTypes.sort.each { |item| item.setRenderingColor(set_random_rendering_color(item, random)) }
end
apply_building_default_constructionset(model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 486
def apply_building_default_constructionset(model)
  bldg_def_const_set = model_add_construction_set_from_osm(model: model)
  model.getBuilding.setDefaultConstructionSet(bldg_def_const_set)
end
apply_default_constructionsets_to_spacetypes(climate_zone, model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 491
def apply_default_constructionsets_to_spacetypes(climate_zone, model)
  model.getSpaceTypes.sort.each do |space_type|
    # Get the standards building type
    stds_building_type = nil
    if space_type.standardsBuildingType.is_initialized
      stds_building_type = space_type.standardsBuildingType.get
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards building type.")
    end

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

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

    # Attempt to make a construction set for this space type
    # and assign it if it can be created.
    spc_type_const_set = model_add_construction_set_from_osm(model: model)
    if spc_type_const_set.is_initialized
      space_type.setDefaultConstructionSet(spc_type_const_set.get)
    end
  end
end
apply_economizers(climate_zone, model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1551
def apply_economizers(climate_zone, model)
  # NECB2011 prescribes ability to provide 100% OA (5.2.2.7-5.2.2.9)
  econ_max_100_pct_oa_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  econ_max_100_pct_oa_sch.setName('Economizer Max OA Fraction 100 pct')
  econ_max_100_pct_oa_sch.defaultDaySchedule.setName('Economizer Max OA Fraction 100 pct Default')
  econ_max_100_pct_oa_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0)

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

      # NECB 5.2.2.8 states that economizer can be controlled based on difference betweeen
      # return air temperature and outside air temperature OR return air enthalpy
      # and outside air enthalphy; latter chosen to be consistent with MNECB and CAN-QUEST implementation
      economizer_type = 'DifferentialEnthalpy'
      # Set the economizer type
      # Get the OA system and OA controller
      oa_sys = air_loop.airLoopHVACOutdoorAirSystem
      if oa_sys.is_initialized
        oa_sys = oa_sys.get
      else
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', "#{air_loop.name} is required to have an economizer, but it has no OA system.")
        next
      end
      oa_control = oa_sys.getControllerOutdoorAir
      oa_control.setEconomizerControlType(economizer_type)
    end
  end
end
apply_envelope(model:, ext_wall_cond: nil, ext_floor_cond: nil, ext_roof_cond: nil, ground_wall_cond: nil, ground_floor_cond: nil, ground_roof_cond: nil, door_construction_cond: nil, fixed_window_cond: nil, glass_door_cond: nil, overhead_door_cond: nil, skylight_cond: nil, glass_door_solar_trans: nil, fixed_wind_solar_trans: nil, skylight_solar_trans: nil, infiltration_scale: nil, necb_hdd: true) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 681
def apply_envelope(model:,
                   ext_wall_cond: nil,
                   ext_floor_cond: nil,
                   ext_roof_cond: nil,
                   ground_wall_cond: nil,
                   ground_floor_cond: nil,
                   ground_roof_cond: nil,
                   door_construction_cond: nil,
                   fixed_window_cond: nil,
                   glass_door_cond: nil,
                   overhead_door_cond: nil,
                   skylight_cond: nil,
                   glass_door_solar_trans: nil,
                   fixed_wind_solar_trans: nil,
                   skylight_solar_trans: nil,
                   infiltration_scale: nil,
                   necb_hdd: true)
  raise('validation of model failed.') unless validate_initial_model(model)

  model_apply_infiltration_standard(model)
  ecm = ECMS.new
  ecm.scale_infiltration_loads(model: model, scale: infiltration_scale)
  model.getInsideSurfaceConvectionAlgorithm.setAlgorithm('TARP')
  model.getOutsideSurfaceConvectionAlgorithm.setAlgorithm('TARP')
  model_add_constructions(model)
  apply_standard_construction_properties(model: model,
                                         ext_wall_cond: ext_wall_cond,
                                         ext_floor_cond: ext_floor_cond,
                                         ext_roof_cond: ext_roof_cond,
                                         ground_wall_cond: ground_wall_cond,
                                         ground_floor_cond: ground_floor_cond,
                                         ground_roof_cond: ground_roof_cond,
                                         door_construction_cond: door_construction_cond,
                                         fixed_window_cond: fixed_window_cond,
                                         glass_door_cond: glass_door_cond,
                                         overhead_door_cond: overhead_door_cond,
                                         skylight_cond: skylight_cond,
                                         glass_door_solar_trans: glass_door_solar_trans,
                                         fixed_wind_solar_trans: fixed_wind_solar_trans,
                                         skylight_solar_trans: skylight_solar_trans,
                                         necb_hdd: necb_hdd)
  model_create_thermal_zones(model, @space_multiplier_map)
end
apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0, necb_hdd: true) click to toggle source

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

# fdwr_set/srr_set settings:
# 0-1:  Remove all windows/skylights and add windows/skylights to match this fdwr/srr
# -1:  Remove all windows/skylights and add windows/skylights to match max fdwr/srr from NECB
# -2:  Do not apply any fdwr/srr changes, leave windows/skylights alone (also works for fdwr/srr > 1)
# -3:  Use old method which reduces existing window/skylight size (if necessary) to meet maximum NECB fdwr/srr
# limit
# <-3.1:  Remove all the windows/skylights
# > 1:  Do nothing
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 952
def apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0, necb_hdd: true)
  fdwr_set = -1.0 if (fdwr_set == 'NECB_default') || fdwr_set.nil?
  srr_set = -1.0 if (srr_set == 'NECB_default') || srr_set.nil?
  fdwr_set = fdwr_set.to_f
  srr_set = srr_set.to_f
  apply_standard_window_to_wall_ratio(model: model, fdwr_set: fdwr_set, necb_hdd: necb_hdd)
  apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set)
  # model_add_daylighting_controls(model) # to be removed after refactor.
end
apply_kiva_foundation(model) click to toggle source

apply the Kiva foundation model to floors and walls with ground boundary condition created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 727
def apply_kiva_foundation(model)
  # define a Kiva model for the whole bldg that's used for the first floor in contact with ground in each zone
  bldg_kiva_model = OpenStudio::Model::FoundationKiva.new(model)
  bldg_kiva_model.setName("Bldg Kiva Foundation")
  bldg_kiva_model.setWallHeightAboveGrade(0.0)
  bldg_kiva_model.setWallDepthBelowSlab(0.0)
  model.getThermalZones.sort.each do |zone|
    zone_kiva_models = [bldg_kiva_model]
    zone_grd_flr_counter = 0
    zone.spaces.sort.each do |space|
      # store space floors and walls in contact with ground and exterior walls
      space_ground_floors = []
      space_ground_walls = []
      space_ext_walls = []
      space_ground_floors += space.surfaces.select {|surf| surf.surfaceType.downcase == 'floor' && surf.isGroundSurface }
      space_ground_walls += space.surfaces.select {|surf| surf.surfaceType.downcase == 'wall' && surf.isGroundSurface }
      space_ext_walls += space.surfaces.select {|surf| surf.surfaceType.downcase == 'wall' && surf.outsideBoundaryCondition.downcase == 'outdoors'}
      # loop through space floors in contact with ground and assing a Kiva model for each
      space_ground_floors.each do |gfloor|
        zone_grd_flr_counter += 1
        if zone_grd_flr_counter > 1
          # a new Kiva model is needed for each additional floor in contact with the ground in the zone
          kiva_model = OpenStudio::Model::FoundationKiva.new(model)
          kiva_model.setName("#{gfloor.name.to_s} Kiva Foundation")
          kiva_model.setWallHeightAboveGrade(0.0)
          kiva_model.setWallDepthBelowSlab(0.0)
          zone_kiva_models << kiva_model
        end
        # Kiva model only works with standard materials. Replace constructions massless materials with standard ones.
        replace_massless_material_with_std_material(model,gfloor)
        gfloor.setOutsideBoundaryCondition('Foundation')
        gfloor.setAdjacentFoundation(zone_kiva_models.last)
        # Set the exposed perimeter for space floors in contact with the ground.
        floor_exp_per = 0.0
        if !space_ground_walls.empty?
          floor_exp_per += get_surface_exp_per(gfloor,space_ground_walls)
        elsif !space_ext_walls.empty?
          floor_exp_per += get_surface_exp_per(gfloor,space_ext_walls)
        end
        gfloor.createSurfacePropertyExposedFoundationPerimeter('TotalExposedPerimeter',floor_exp_per)
        # specify a foundation boundary condition for space walls in contact with the ground and in
        # contact with the space floor in contact with ground 'gfloor'
        space_ground_walls.each do |gwall|
          if surfaces_are_in_contact?(gfloor,gwall)
            replace_massless_material_with_std_material(model,gwall)
            gwall.setOutsideBoundaryCondition('Foundation')
            gwall.setAdjacentFoundation(zone_kiva_models.last)
          end
        end
      end
    end
  end
  kiva_settings = model.getFoundationKivaSettings if !model.getFoundationKivas.empty?
end
apply_limit_fdwr(model:, fdwr_lim:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 25
def apply_limit_fdwr(model:, fdwr_lim:)
  # Loop through all spaces in the model, and
  # per the PNNL PRM Reference Manual, find the areas
  # of each space conditioning category (res, nonres, semi-heated)
  # separately.  Include space multipliers.
  nr_wall_m2 = 0.001 # Avoids divide by zero errors later
  nr_wind_m2 = 0
  res_wall_m2 = 0.001
  res_wind_m2 = 0
  sh_wall_m2 = 0.001
  sh_wind_m2 = 0
  total_wall_m2 = 0.001
  total_subsurface_m2 = 0.0
  # Store the space conditioning category for later use
  space_cats = {}
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    wall_area_m2 = 0
    wind_area_m2 = 0
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType.casecmp('wall').zero?

      # This wall's gross area (including window area)
      wall_area_m2 += surface.grossArea * space.multiplier
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        wind_area_m2 += ss.netArea * space.multiplier
      end
    end

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

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

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

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

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

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

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

  # WWR limit
  wwr_lim = 40.0

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

  # puts "Current FDWR is #{fdwr}, must be less than #{fdwr_lim}."
  # puts "Current subsurf area is #{total_subsurface_m2} and gross surface area is #{total_wall_m2}"
  # Stop here unless windows / doors need reducing
  return true unless fdwr > fdwr_lim

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{wwr_lim.round}%.")
  # Determine the factors by which to reduce the window / door area
  mult = fdwr_lim / fdwr
  # Reduce the window area if any of the categories necessary
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType == 'Wall'

      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        # Reduce the size of the window
        red = 1.0 - mult
        OpenstudioStandards::Geometry.sub_surface_reduce_area_by_percent_by_raising_sill(ss, red)
      end
    end
  end
  return true
end
apply_loads(model:, lights_type: 'NECB_Default', lights_scale: 1.0, validate: true, occupancy_loads_scale: nil, electrical_loads_scale: nil, oa_scale: nil) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 637
def apply_loads(model:,
                lights_type: 'NECB_Default',
                lights_scale: 1.0,
                validate: true,
                occupancy_loads_scale: nil,
                electrical_loads_scale: nil,
                oa_scale: nil)
  # Create ECM object.
  ecm = ECMS.new
  lights_scale = convert_arg_to_f(variable: lights_scale, default: 1.0)
  if validate
    raise('validation of model failed.') unless validate_initial_model(model)
    raise('validation of spacetypes failed.') unless validate_and_upate_space_types(model)
  end
  # this sets/stores the template version loads that the model uses.
  model.getBuilding.setStandardsTemplate(self.class.name)
  set_occ_sensor_spacetypes(model, @space_type_map)
  model_add_loads(model, lights_type, lights_scale)
  ecm.scale_occupancy_loads(model: model, scale: occupancy_loads_scale)
  ecm.scale_electrical_loads(model: model, scale: electrical_loads_scale)
  ecm.scale_oa_loads(model: model, scale: oa_scale)
end
apply_loop_pump_power(model:, sizing_run_dir:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1098
def apply_loop_pump_power(model:, sizing_run_dir:)
  # Remove duplicate materials and constructions
  # Note For NECB2015 This is the 2nd time this method is bieng run.
  # First time it ran in the super() within model_apply_standard() method
  # model = return BTAP::FileIO::remove_duplicate_materials_and_constructions(model)
  return model
end
apply_max_fdwr_nrcan(model:, fdwr_lim:) click to toggle source

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

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

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

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

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

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

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

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

  # Go through all of exposed roofs adjacent to heated, non-plenum spaces, remove any existing subsurfaces, and add
  # a skylight in the centroid of the surface, with the same shape of the surface, only scaled to be the area
  # determined by the SRR.  The name of the skylight will be the surface name with the subsurface type attached
  # ('skylight' in this case).  Note that this method will only work if the surface does not fold into itself (like an
  # L or a V).
  exp_surf_info['exp_nonplenum_roofs'].sort.each do |roof|
    # sub_surface_create_centered_subsurface_from_scaled_surface(roof, srr_lim, model)
    sub_surface_create_scaled_subsurfaces_from_surface(surface: roof, area_fraction: srr_lim, construction: skylight_construct_set)
  end
  return true
end
apply_standard_construction_properties(model:, runner: nil, ext_wall_cond: nil, ext_floor_cond: nil, ext_roof_cond: nil, ground_wall_cond: nil, ground_floor_cond: nil, ground_roof_cond: nil, fixed_window_cond: nil, fixed_wind_solar_trans: nil, fixed_wind_vis_trans: nil, operable_wind_solar_trans: nil, operable_window_cond: nil, operable_wind_vis_trans: nil, glass_door_cond: nil, glass_door_solar_trans: nil, glass_door_vis_trans: nil, door_construction_cond: nil, overhead_door_cond: nil, skylight_cond: nil, skylight_solar_trans: nil, skylight_vis_trans: nil, tubular_daylight_dome_cond: nil, tubular_daylight_dome_solar_trans: nil, tubular_daylight_dome_vis_trans: nil, tubular_daylight_diffuser_cond: nil, tubular_daylight_diffuser_solar_trans: nil, tubular_daylight_diffuser_vis_trans: nil, necb_hdd: true) click to toggle source

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

90.1-2007, 90.1-2010, 90.1-2013 @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 300
def apply_standard_construction_properties(model:,
                                           runner: nil,
                                           # ext surfaces
                                           ext_wall_cond: nil,
                                           ext_floor_cond: nil,
                                           ext_roof_cond: nil,
                                           # ground surfaces
                                           ground_wall_cond: nil,
                                           ground_floor_cond: nil,
                                           ground_roof_cond: nil,
                                           # fixed Windows
                                           fixed_window_cond: nil,
                                           fixed_wind_solar_trans: nil,
                                           fixed_wind_vis_trans: nil,
                                           # operable windows
                                           operable_wind_solar_trans: nil,
                                           operable_window_cond: nil,
                                           operable_wind_vis_trans: nil,
                                           # glass doors
                                           glass_door_cond: nil,
                                           glass_door_solar_trans: nil,
                                           glass_door_vis_trans: nil,
                                           # opaque doors
                                           door_construction_cond: nil,
                                           overhead_door_cond: nil,
                                           # skylights
                                           skylight_cond: nil,
                                           skylight_solar_trans: nil,
                                           skylight_vis_trans: nil,
                                           # tubular daylight dome
                                           tubular_daylight_dome_cond: nil,
                                           tubular_daylight_dome_solar_trans: nil,
                                           tubular_daylight_dome_vis_trans: nil,
                                           # tubular daylight diffuser
                                           tubular_daylight_diffuser_cond: nil,
                                           tubular_daylight_diffuser_solar_trans: nil,
                                           tubular_daylight_diffuser_vis_trans: nil,
                                           necb_hdd: true)

  model.getDefaultConstructionSets.sort.each do |default_surface_construction_set|
    BTAP.runner_register('Info', 'apply_standard_construction_properties', runner)
    if model.weatherFile.empty? || model.weatherFile.get.path.empty? || !File.exist?(model.weatherFile.get.path.get.to_s)

      BTAP.runner_register('Error', 'Weather file is not defined. Please ensure the weather file is defined and exists.', runner)
      return false
    end

    # hdd required in scope for eval function.
    hdd = get_necb_hdd18(model: model, necb_hdd: necb_hdd)
    # Lambdas are preferred over methods in methods for small utility methods.
    correct_cond = lambda do |conductivity, surface_type|
      return conductivity.nil? || conductivity.to_f <= 0.0 || conductivity == 'NECB_Default' ? eval(model_find_objects(@standards_data['surface_thermal_transmittance'], surface_type)[0]['formula']) : conductivity.to_f
    end

    # Converts trans and vis to nil if requesting default.. or casts the string to a float.
    correct_vis_trans = lambda do |value|
      return value.nil? || value.to_f <= 0.0 || value == 'NECB_Default' ? nil : value.to_f
    end

    BTAP::Resources::Envelope::ConstructionSets.customize_default_surface_construction_set!(model: model,
                                                                                            name: "#{default_surface_construction_set.name.get} at hdd = #{get_necb_hdd18(model: model, necb_hdd: necb_hdd)}",
                                                                                            default_surface_construction_set: default_surface_construction_set,
                                                                                            # ext surfaces
                                                                                            ext_wall_cond: correct_cond.call(ext_wall_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Wall'),
                                                                                            ext_floor_cond: correct_cond.call(ext_floor_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Floor'),
                                                                                            ext_roof_cond: correct_cond.call(ext_roof_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'RoofCeiling'),
                                                                                            # ground surfaces
                                                                                            ground_wall_cond: correct_cond.call(ground_wall_cond, 'boundary_condition' => 'Ground', 'surface' => 'Wall'),
                                                                                            ground_floor_cond: correct_cond.call(ground_floor_cond, 'boundary_condition' => 'Ground', 'surface' => 'Floor'),
                                                                                            ground_roof_cond: correct_cond.call(ground_roof_cond, 'boundary_condition' => 'Ground', 'surface' => 'RoofCeiling'),
                                                                                            # fixed Windows
                                                                                            fixed_window_cond: correct_cond.call(fixed_window_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'),
                                                                                            fixed_wind_solar_trans: correct_vis_trans.call(fixed_wind_solar_trans),
                                                                                            fixed_wind_vis_trans: correct_vis_trans.call(fixed_wind_vis_trans),
                                                                                            # operable windows
                                                                                            operable_wind_solar_trans: correct_vis_trans.call(operable_wind_solar_trans),
                                                                                            operable_window_cond: correct_cond.call(fixed_window_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'),
                                                                                            operable_wind_vis_trans: correct_vis_trans.call(operable_wind_vis_trans),
                                                                                            # glass doors
                                                                                            glass_door_cond: correct_cond.call(glass_door_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'),
                                                                                            glass_door_solar_trans: correct_vis_trans.call(glass_door_solar_trans),
                                                                                            glass_door_vis_trans: correct_vis_trans.call(glass_door_vis_trans),
                                                                                            # opaque doors
                                                                                            door_construction_cond: correct_cond.call(door_construction_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Door'),
                                                                                            overhead_door_cond: correct_cond.call(overhead_door_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Door'),
                                                                                            # skylights
                                                                                            skylight_cond: correct_cond.call(skylight_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'),
                                                                                            skylight_solar_trans: correct_vis_trans.call(skylight_solar_trans),
                                                                                            skylight_vis_trans: correct_vis_trans.call(skylight_vis_trans),
                                                                                            # tubular daylight dome
                                                                                            tubular_daylight_dome_cond: correct_cond.call(skylight_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'),
                                                                                            tubular_daylight_dome_solar_trans: correct_vis_trans.call(tubular_daylight_dome_solar_trans),
                                                                                            tubular_daylight_dome_vis_trans: correct_vis_trans.call(tubular_daylight_dome_vis_trans),
                                                                                            # tubular daylight diffuser
                                                                                            tubular_daylight_diffuser_cond: correct_cond.call(skylight_cond, 'boundary_condition' => 'Outdoors', 'surface' => 'Window'),
                                                                                            tubular_daylight_diffuser_solar_trans: correct_vis_trans.call(tubular_daylight_diffuser_solar_trans),
                                                                                            tubular_daylight_diffuser_vis_trans: correct_vis_trans.call(tubular_daylight_diffuser_vis_trans))
  end
  # sets all surfaces to use default constructions sets except adiabatic, where it does a hard assignment of the interior wall construction type.
  model.getPlanarSurfaces.sort.each(&:resetConstruction)
  # if the default construction set is defined..try to assign the interior wall to the adiabatic surfaces
  BTAP::Resources::Envelope.assign_interior_surface_construction_to_adiabatic_surfaces(model, nil)
  BTAP.runner_register('Info', ' apply_standard_construction_properties was sucessful.', runner)
end
apply_standard_efficiencies(model:, sizing_run_dir:, dcv_type: 'NECB_Default', necb_reference_hp: false) click to toggle source

@param necb_reference_hp [Boolean] if true, NECB reference model rules for heat pumps will be used.

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1029
def apply_standard_efficiencies(model:, sizing_run_dir:, dcv_type: 'NECB_Default', necb_reference_hp: false)
  raise('validation of model failed.') unless validate_initial_model(model)

  climate_zone = 'NECB HDD Method'
  raise("sizing run 1 failed! check #{sizing_run_dir}") if model_run_sizing_run(model, "#{sizing_run_dir}/plant_loops") == false

  # This is needed for NECB2011 as a workaround for sizing the reheat boxes.
  model.getAirTerminalSingleDuctVAVReheats.each { |iobj| air_terminal_single_duct_vav_reheat_set_heating_cap(iobj) }
  # Apply the prototype HVAC assumptions
  model_apply_prototype_hvac_assumptions(model, nil, climate_zone)
  # Apply the HVAC efficiency standard
  sql_db_vars_map = {}
  model_apply_hvac_efficiency_standard(model, climate_zone, sql_db_vars_map: sql_db_vars_map, necb_ref_hp: necb_reference_hp)

  model_enable_demand_controlled_ventilation(model, dcv_type)
  return sql_db_vars_map
end
apply_standard_lights(set_lights: true, space_type:, space_type_properties:, lights_type:, lights_scale:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/lighting.rb, line 2
def apply_standard_lights(set_lights: true,
                          space_type:,
                          space_type_properties:,
                          lights_type:,
                          lights_scale:)

  ##### Remove leading or trailing whitespace in case users add them in inputs
  if lights_scale.instance_of?(String)
    lights_scale = lights_scale.strip
  end

  if lights_type.nil? || lights_type == 'none'
    lights_type = 'NECB_Default'
  end
  if lights_scale.nil? || lights_scale == 'none' || lights_scale == 'NECB_Default'
    lights_scale = 1.0
  end

  ##### Convert a string to a float
  if lights_scale.instance_of?(String)
    lights_scale = lights_scale.to_f
  end

  lights_have_info = false
  lighting_per_area = space_type_properties['lighting_per_area'].to_f
  lighting_per_person = space_type_properties['lighting_per_person'].to_f
  lights_frac_to_return_air = space_type_properties['lighting_fraction_to_return_air'].to_f
  lights_frac_radiant = space_type_properties['lighting_fraction_radiant'].to_f
  lights_frac_visible = space_type_properties['lighting_fraction_visible'].to_f
  lights_frac_replaceable = space_type_properties['lighting_fraction_replaceable'].to_f
  lights_have_info = true if !lighting_per_area.zero? || !lighting_per_person.zero?

  ##### NOTE: Reference for LED lighting's return air, radiant, and visible fraction values is: page 142, NREL (2014), "Proven Energy-Saving Technologies for Commercial Properties", available at https://www.nrel.gov/docs/fy15osti/63807.pdf
  if lights_type == 'LED'
    led_lights_have_info = false
    led_spacetype_data = @standards_data['tables']['led_lighting_data']['table']
    standards_building_type = space_type.standardsBuildingType.is_initialized ? space_type.standardsBuildingType.get : nil
    standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil
    led_space_type_properties = led_spacetype_data.detect { |s| (s['building_type'] == standards_building_type) && (s['space_type'] == standards_space_type) }
    if led_space_type_properties.nil?
      raise("#{standards_building_type} for #{standards_space_type} was not found please verify the led lighting database names match the space type names.")
    end

    lighting_per_area_led_lighting = led_space_type_properties['lighting_per_area'].to_f
    lights_frac_to_return_air_led_lighting = led_space_type_properties['lighting_fraction_to_return_air'].to_f
    lights_frac_radiant_led_lighting = led_space_type_properties['lighting_fraction_radiant'].to_f
    lights_frac_visible_led_lighting = led_space_type_properties['lighting_fraction_visible'].to_f
    led_lights_have_info = true unless lighting_per_area_led_lighting.zero?

  end

  return unless set_lights && lights_have_info

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

      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
      inst.remove
    end
  end

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

  # If additional lights are specified, add those too
  additional_lighting_per_area = space_type_properties['additional_lighting_per_area'].to_f

  # If there are none then exit method
  return if additional_lighting_per_area.zero?

  # Create the lighting definition
  additional_lights_def = OpenStudio::Model::LightsDefinition.new(space_type.model)
  additional_lights_def.setName("#{space_type.name} Additional Lights Definition")
  additional_lights_def.setWattsperSpaceFloorArea(OpenStudio.convert(additional_lighting_per_area.to_f, 'W/ft^2', 'W/m^2').get)
  additional_lights_def.setReturnAirFraction(lights_frac_to_return_air)
  additional_lights_def.setFractionRadiant(lights_frac_radiant)
  additional_lights_def.setFractionVisible(lights_frac_visible)

  # Create the lighting instance and hook it up to the space type
  additional_lights = OpenStudio::Model::Lights.new(additional_lights_def)
  additional_lights.setName("#{space_type.name} Additional Lights")
  additional_lights.setSpaceType(space_type)
end
apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 164
def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0)
  # If srr_set is between 1.0 and 1.2 set it to the maximum allowed by the NECB.  If srr_set is between 0.0 and 1.0
  # apply whatever was passed.  If srr_set >= 1.2 then set the existing srr of the building to be the necb maximum
  # only if the the srr exceeds this maximum (otherwise leave it to be whatever was modeled).

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

  return if srr_set.to_f > 1.0
  return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f) if srr_set.to_f >= 0.0 && srr_set.to_f <= 1.0
  # Get the maximum NECB srr
  return apply_max_srr_nrcan(model: model, srr_lim: get_standards_constant('skylight_to_roof_ratio_max_value').to_f) if srr_set.to_f >= -1.1 && srr_set.to_f <= -0.9
  return if srr_set.to_f >= -2.1 && srr_set.to_f <= -1.9
  return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f) if srr_set.to_f < -3.1
  return unless srr_set.to_f >= -3.1 && srr_set.to_f <= -2.9

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

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

      # This wall's gross area (including skylight area)
      wall_area_m2 += surface.grossArea * space.multiplier
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        sky_area_m2 += ss.netArea * space.multiplier
      end
    end

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

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

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

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

  # Stop here unless windows need reducing
  return true unless srr > srr_lim

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

  # Reduce the subsurface areas
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType == 'RoofCeiling'

      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        # Reduce the size of the subsurface
        red = 1.0 - mult
        OpenstudioStandards::Geometry.sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red)
      end
    end
  end

  return true
end
apply_standard_window_to_wall_ratio(model:, fdwr_set: -1.0, necb_hdd: true) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 4
def apply_standard_window_to_wall_ratio(model:, fdwr_set: -1.0, necb_hdd: true)
  # NECB FDWR limit
  hdd = get_necb_hdd18(model: model, necb_hdd: necb_hdd)

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

  return if fdwr_set.to_f > 1.0
  return apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_set.to_f) if fdwr_set.to_f >= 0.0 && fdwr_set.to_f <= 1.0
  return apply_max_fdwr_nrcan(model: model, fdwr_lim: max_fwdr(hdd).round(3)) if fdwr_set.to_f >= -1.1 && fdwr_set.to_f <= -0.9
  return if fdwr_set.to_f >= -2.1 && fdwr_set.to_f <= -1.9
  return apply_limit_fdwr(model: model, fdwr_lim: (max_fwdr(hdd) * 100.0).round(1)) if fdwr_set.to_f >= -3.1 && fdwr_set.to_f <= -2.9
  return apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_set.to_f) if fdwr_set.to_f < -3.1
end
apply_systems(model:, primary_heating_fuel:, sizing_run_dir:, shw_scale:, baseline_system_zones_map_option:) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 96
def apply_systems(model:,
                  primary_heating_fuel:,
                  sizing_run_dir:,
                  shw_scale:,
                  baseline_system_zones_map_option:)
  raise('validation of model failed.') unless validate_initial_model(model)

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

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

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


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

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

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

  # Assign the wild spaces to a single system 4 system with a control zone with the largest load.
  auto_system_wild_spaces(baseboard_type: self.fuel_type_set.baseboard_type,
                          necb_reference_hp: self.fuel_type_set.necb_reference_hp,
                          necb_reference_hp_supp_fuel: self.fuel_type_set.necb_reference_hp_supp_fuel,
                          heating_coil_type_sys4: self.fuel_type_set.heating_coil_type_sys4,
                          model: model)
  # do the regular assignment for the rest and group where possible.
  auto_system_all_other_spaces(model: model,
                               necb_reference_hp: self.fuel_type_set.necb_reference_hp,
                               necb_reference_hp_supp_fuel: self.fuel_type_set.necb_reference_hp_supp_fuel,
                               baseboard_type: self.fuel_type_set.baseboard_type,
                               boiler_fueltype: self.fuel_type_set.boiler_fueltype,
                               chiller_type: self.fuel_type_set.chiller_type,
                               fan_type: self.fuel_type_set.fan_type,
                               heating_coil_type_sys3: self.fuel_type_set.heating_coil_type_sys3,
                               heating_coil_type_sys4: self.fuel_type_set.heating_coil_type_sys4,
                               hw_loop: @hw_loop,
                               heating_coil_type_sys6: self.fuel_type_set.heating_coil_type_sys6,
                               mau_cooling_type: self.fuel_type_set.mau_cooling_type,
                               mau_heating_coil_type: self.fuel_type_set.mau_heating_coil_type,
                               mau_type: self.fuel_type_set.mau_type
  )
  model_add_swh(model: model,
                swh_fueltype: self.fuel_type_set.swh_fueltype,
                shw_scale: shw_scale)
  model_apply_sizing_parameters(model)
  # set a larger tolerance for unmet hours from default 0.2 to 1.0C
  model.getOutputControlReportingTolerances.setToleranceforTimeHeatingSetpointNotMet(1.0)
  model.getOutputControlReportingTolerances.setToleranceforTimeCoolingSetpointNotMet(1.0)
end
apply_systems_and_efficiencies(model:, sizing_run_dir:, primary_heating_fuel:, dcv_type: 'NECB_Default', ecm_system_name: 'NECB_Default', ecm_system_zones_map_option: 'NECB_Default', erv_package: 'NECB_Default', boiler_eff: nil, furnace_eff: nil, unitary_cop: nil, shw_eff: nil, daylighting_type: 'NECB_Default', nv_type: nil, nv_opening_fraction: nil, nv_temp_out_min: nil, nv_delta_temp_in_out: nil, pv_ground_type:, pv_ground_total_area_pv_panels_m2:, pv_ground_tilt_angle:, pv_ground_azimuth_angle:, pv_ground_module_description:, chiller_type: 'NECB_Default', shw_scale:, airloop_economizer_type: nil, baseline_system_zones_map_option:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 518
def apply_systems_and_efficiencies(model:,
                                   sizing_run_dir:,
                                   primary_heating_fuel:,
                                   dcv_type: 'NECB_Default',
                                   ecm_system_name: 'NECB_Default',
                                   ecm_system_zones_map_option: 'NECB_Default',
                                   erv_package: 'NECB_Default',
                                   boiler_eff: nil,
                                   furnace_eff: nil,
                                   unitary_cop: nil,
                                   shw_eff: nil,
                                   daylighting_type: 'NECB_Default',
                                   nv_type: nil,
                                   nv_opening_fraction: nil,
                                   nv_temp_out_min: nil,
                                   nv_delta_temp_in_out: nil,
                                   pv_ground_type:,
                                   pv_ground_total_area_pv_panels_m2:,
                                   pv_ground_tilt_angle:,
                                   pv_ground_azimuth_angle:,
                                   pv_ground_module_description:,
                                   chiller_type: 'NECB_Default',
                                   shw_scale:,
                                   airloop_economizer_type: nil,
                                   baseline_system_zones_map_option:)

  # Create ECM object.
  ecm = ECMS.new

  # -------- Systems Layout-----------

  # Create Default Systems.
  apply_systems(model: model,
                primary_heating_fuel: primary_heating_fuel,
                sizing_run_dir: sizing_run_dir,
                shw_scale: shw_scale,
                baseline_system_zones_map_option: baseline_system_zones_map_option)

  # Apply new ECM system. Overwrite standard as required.
  ecm.apply_system_ecm(model: model,
                       ecm_system_name: ecm_system_name,
                       template_standard: self,
                       primary_heating_fuel: self.fuel_type_set.ecm_fueltype,
                       ecm_system_zones_map_option: ecm_system_zones_map_option)

  # -------- Performace, Efficiencies, Controls and Sensors ------------
  #
  # Set code standard equipment characteristics.
  sql_db_vars_map = apply_standard_efficiencies(model: model,
                                                sizing_run_dir: sizing_run_dir,
                                                necb_reference_hp: self.fuel_type_set.necb_reference_hp)
  # Apply System
  ecm.apply_system_efficiencies_ecm(model: model, ecm_system_name: ecm_system_name, template_standard: self)
  # Apply ECM ERV charecteristics as required. Part 2 of above ECM.
  ecm.apply_erv_ecm_efficiency(model: model, erv_package: erv_package)
  # Apply DCV as required
  model_enable_demand_controlled_ventilation(model, dcv_type)
  # Apply Boiler Efficiency
  ecm.modify_boiler_efficiency(model: model, boiler_eff: boiler_eff)
  # Apply Furnace Efficiency
  ecm.modify_furnace_efficiency(model: model, furnace_eff: furnace_eff)
  # Apply Unitary curves
  ecm.modify_unitary_cop(model: model, unitary_cop: unitary_cop, sizing_done: false, sql_db_vars_map: sql_db_vars_map)
  # Apply SHW Efficiency
  ecm.modify_shw_efficiency(model: model, shw_eff: shw_eff)
  # Apply daylight controls.
  model_add_daylighting_controls(model: model, daylighting_type: daylighting_type)
  # Apply Chiller efficiency
  ecm.modify_chiller_efficiency(model: model, chiller_type: chiller_type)
  # Apply airloop economizer
  ecm.add_airloop_economizer(model: model, airloop_economizer_type: airloop_economizer_type)
  # Perform a second sizing run if needed
  if (!unitary_cop.nil? && unitary_cop != 'NECB_Default') || !model.getPlantLoops.empty?
    if model_run_sizing_run(model, "#{sizing_run_dir}/SR2") == false
      raise('sizing run 2 failed!')
    end
  end
  # apply unitary cop
  ecm.modify_unitary_cop(model: model, unitary_cop: unitary_cop, sizing_done: true, sql_db_vars_map: sql_db_vars_map)
  # set capacities of district heating and cooling equipment for ground-source heat pump ecm
  district_heat = false
  if model.version < OpenStudio::VersionString.new('3.7.0')
    district_heat = !model.getDistrictHeatings.empty?
  else
    district_heat = !model.getDistrictHeatingWaters.empty?
  end
  ecm.set_ghx_loop_district_cap(model) if (district_heat && !model.getDistrictCoolings.empty?)

  # -------Pump sizing required by some vintages----------------
  # Apply Pump power as required.
  apply_loop_pump_power(model: model, sizing_run_dir: sizing_run_dir)

  # -------Natural ventilation----------------
  # Apply natural ventilation using simplified method.
  if nv_type == 'add_nv'
    ecm.apply_nv(model: model,
                 nv_type: nv_type,
                 nv_opening_fraction: nv_opening_fraction,
                 nv_temp_out_min: nv_temp_out_min,
                 nv_delta_temp_in_out: nv_delta_temp_in_out)
  end

  # -------Ground-mounted PV panels----------------
  # Apply ground-mounted PV panels as required.
  if pv_ground_type == 'add_pv_ground'
    ecm.apply_pv_ground(model: model,
                        pv_ground_type: pv_ground_type,
                        pv_ground_total_area_pv_panels_m2: pv_ground_total_area_pv_panels_m2,
                        pv_ground_tilt_angle: pv_ground_tilt_angle,
                        pv_ground_azimuth_angle: pv_ground_azimuth_angle,
                        pv_ground_module_description: pv_ground_module_description)
  end

  # Rename air loop and plant loop nodes to accommodate coming OpenStudio version
  rename_air_loop_nodes(model)
  rename_plant_loop_nodes(model)

end
apply_thermal_bridging(model: nil, tbd_option: 'none', tbd_interpolate: false, wall_U: nil, floor_U: nil, roof_U: nil) click to toggle source

(Optionally) uprates, then derates, envelope surface constructions due to MAJOR thermal bridges (e.g. roof parapets, corners, fenestration perimeters). See lib/openstudio-standards/btap/bridging.rb, which relies on the Thermal Bridging & Derating (TBD) gem.

@param model [OpenStudio::Model::Model] an OpenStudio model @param tbd_option [String] BTAP/TBD option @param tbd_interpolate [Boolean] true if TBD interpolates between costed Uo @param wall_U [Double] wall conductance in W/m2.K (nil by default) @param floor_U [Double] floor conductance in W/m2.K (nil by default) @param roof_U [Double] roof conductance in W/m2.K (nil by default)

@return [Boolean] true if successful, e.g. no errors, compliant if uprated

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 987
def apply_thermal_bridging(model: nil,
                           tbd_option: 'none',
                           tbd_interpolate: false,
                           wall_U: nil,
                           floor_U: nil,
                           roof_U: nil)
  return false unless model.is_a?(OpenStudio::Model::Model)
  return false unless tbd_option.respond_to?(:to_s)

  tbd_option = tbd_option.to_s
  # 4x options:
  #  - 'none' (TBD is ignored)
  #  - derate using 'bad' PSI factors (BTAP-costed)
  #  - derate using 'good' PSI factors (BTAP-costed)
  #  - 'uprate' (then derate), i.e. iterative process (BTAP-costed)
  ok = tbd_option == 'bad' || tbd_option == 'good' || tbd_option == 'uprate'
  return true  if tbd_option == 'none'
  return false unless ok

  argh = {} # BTAP/TBD arguments
  ok = tbd_interpolate == true || tbd_interpolate == false
  argh[:interpolate] = tbd_interpolate if ok
  argh[:interpolate] = false       unless ok

  argh[:walls ] = { uo: wall_U  }
  argh[:floors] = { uo: floor_U }
  argh[:roofs ] = { uo: roof_U  }

  if tbd_option == 'uprate'
    argh[:walls  ][:ut] = wall_U
    argh[:floors ][:ut] = floor_U
    argh[:roofs  ][:ut] = roof_U
  elsif tbd_option == 'good'
    argh[:quality] = :good
  end    # default == :bad

  @tbd = BTAP::Bridging.new(model, argh)

  true
end
apply_weather_data(model:, epw_file:, custom_weather_folder: nil) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 660
def apply_weather_data(model:, epw_file:, custom_weather_folder: nil)
  # Create full path to weather file
  weather_files = File.absolute_path(File.join(__FILE__, '..', '..', '..', '..', '..' , '..', "data/weather"))
  weather_file = File.join(weather_files, epw_file)
  # Check if the weather file exists.  If it does continue as normal, otherwise try to dowload it from the
  # canmet-energy/btap_weather repository
  unless File.exist?(weather_file)
    # Check if btap_batch transferred the weather file
    weather_transfer = check_datapoint_weather_folder(epw_file: epw_file, weather_folder: weather_files, custom_weather_folder: custom_weather_folder)
    # If btap_batch didn't transfer the weather file, download it.
    get_weather_file_from_repo(epw_file: epw_file) unless weather_transfer
  end

  # Fix EMS references. Temporary workaround for OS issue #2598
  model_temp_fix_ems_references(model)
  model.getThermostatSetpointDualSetpoints(&:remove)
  model.getYearDescription.setDayofWeekforStartDay('Sunday')
  weather_file_path = OpenstudioStandards::Weather.get_standards_weather_file_path(epw_file)
  OpenstudioStandards::Weather.model_set_building_location(model, weather_file_path: weather_file_path)
end
are_space_loads_similar?(space_1:, space_2:, surface_percent_difference_tolerance: 0.01, angular_percent_difference_tolerance: 0.001, heating_load_percent_difference_tolerance: 15.0) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 541
def are_space_loads_similar?(space_1:,
                             space_2:,
                             surface_percent_difference_tolerance: 0.01,
                             angular_percent_difference_tolerance: 0.001,
                             heating_load_percent_difference_tolerance: 15.0)
  # Do they have the same space type?
  return false unless space_1.multiplier == space_2.multiplier
  # Ensure that they both have defined spacetypes
  return false if space_1.spaceType.empty?
  return false if space_2.spaceType.empty?
  # ensure that they have the same spacetype.
  return false unless space_1.spaceType.get == space_2.spaceType.get

  # Perform surface comparision. If ranges are within percent_difference_tolerance.. they can be considered the same.
  space_1_floor_area = space_1.floorArea
  space_2_floor_area = space_2.floorArea
  space_1_surface_report = space_surface_report(space_1)
  space_2_surface_report = space_surface_report(space_2)
  # Spaces should have the same number of surface orientations.
  return false unless space_1_surface_report.size == space_2_surface_report.size
  # spaces should have similar loads
  return false unless percentage_difference(stored_space_heating_load(space_1), stored_space_heating_load(space_2)) <= heating_load_percent_difference_tolerance

  # Each surface should match
  space_1_surface_report.each do |space_1_surface|
    surface_match = space_2_surface_report.detect do |space_2_surface|
      space_1_surface[:surface_type] == space_2_surface[:surface_type] &&
        space_1_surface[:boundary_condition] == space_2_surface[:boundary_condition] &&
        percentage_difference(space_1_surface[:tilt], space_2_surface[:tilt]) <= angular_percent_difference_tolerance &&
        percentage_difference(space_1_surface[:azimuth], space_2_surface[:azimuth]) <= angular_percent_difference_tolerance &&
        percentage_difference(space_1_surface[:surface_area_to_floor_ratio],
                              space_2_surface[:surface_area_to_floor_ratio]) <= surface_percent_difference_tolerance &&
        percentage_difference(space_1_surface[:glazed_subsurface_area_to_floor_ratio],
                              space_2_surface[:glazed_subsurface_area_to_floor_ratio]) <= surface_percent_difference_tolerance &&
        percentage_difference(space_1_surface[:opaque_subsurface_area_to_floor_ratio],
                              space_2_surface[:opaque_subsurface_area_to_floor_ratio]) <= surface_percent_difference_tolerance
    end
    return false if surface_match.nil?
  end
  return true
end
are_zone_loads_similar?(zone_1:, zone_2:) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 524
def are_zone_loads_similar?(zone_1:, zone_2:)
  # make sure they have the same number of spaces.
  truthes = []
  return false if zone_1.spaces.size != zone_2.spaces.size

  zone_1.spaces.each do |space_1|
    zone_2.spaces.each do |space_2|
      if are_space_loads_similar?(space_1: space_1, space_2: space_2)
        truthes << true
      end
    end
  end
  # truthes sizes should be the same as the # of spaces if all spaces are similar.
  return truthes.size == zone_1.spaces.size
end
assign_base_sys_name(airloop, sys_abbr:, sys_oa:, sys_name_pars:) click to toggle source

Method to set the base system name based on the following syntax: |sys_abbr|sys_oa|sc>?|sh>?|ssf>?|zh>?|zc>?|srf>?| “sys_abbr” designates the NECB system type (“sys_1, sys_2, 
 sys_6”) “sys_oa”: “mixed” or “doas” “sys_name_pars” is a hash for the remaining system name parts for heat recovery, heating, cooling, supply fan, zone heating, zone cooling, and return fan

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2186
def assign_base_sys_name(airloop, sys_abbr:, sys_oa:, sys_name_pars:)
  sys_name = "#{sys_abbr}|#{sys_oa}|"
  sys_name_pars.each do |key, value|
    case key.downcase
    when 'sys_hr'
      case value.downcase
      when 'none'
        sys_name += 'shr>none'
      end

    when 'sys_htg'
      case value.downcase
      when 'none'
        sys_name += 'sh>none'
      when 'electric'
        sys_name += 'sh>c-e'
      when 'hot water'
        sys_name += 'sh>c-hw'
      when 'gas'
        sys_name += 'sh>c-g'
      when 'dx'
        sys_name += 'sh>ashp'
      when 'ccashp'
        sys_name += 'sh>ccashp'
      when 'ashp'
        sys_name += 'sh>ashp'
      end

    when 'sys_clg'
      case value.downcase
      when 'none'
        sys_name += 'sc>none'
      when 'chilled water'
        sys_name += 'sc>c-chw'
      when 'dx'
        if sys_name_pars['sys_htg'] == 'dx'
          sys_name += 'sc>ashp'
        else
          sys_name += 'sc>dx'
        end
      when 'ccashp'
        sys_name += 'sc>ccashp'
      when 'ashp'
        sys_name += 'sc>ashp'
      end

    when 'sys_sf'
      case value.downcase
      when 'none'
        sys_name += 'ssf>none'
      when 'cv'
        sys_name += 'ssf>cv'
      when 'vv'
        sys_name += 'ssf>vv'
      end

    when 'zone_htg'
      case value.downcase
      when 'none'
        sys_name += 'zh>none'
      when 'electric'
        sys_name += 'zh>b-e'
      when 'hot water'
        sys_name += 'zh>b-hw'
      when 'tpfc'
        sys_name += 'zh>fpfc'
      when 'fpfc'
        sys_name += 'zh>tpfc'
      when 'pthp'
        sys_name += 'zh>pthp'
      end

    when 'zone_clg'
      case value.downcase
      when 'none'
        sys_name += 'zc>none'
      when 'tpfc'
        sys_name += 'zc>tpfc'
      when 'fpfc'
        sys_name += 'zc>fpfc'
      when 'ptac'
        sys_name += 'zc>ptac'
      when 'pthp'
        sys_name += 'zc>pthp'
      end

    when 'sys_rf'
      case value.downcase
      when 'none'
        sys_name += 'srf>none'
      when 'cv'
        sys_name += 'srf>cv'
      when 'vv'
        sys_name += 'srf>vv'
      end
    end
    sys_name += '|'
  end

  airloop.setName(sys_name)
end
assign_contruction_to_adiabatic_surfaces(model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 539
def assign_contruction_to_adiabatic_surfaces(model)
  cp02_carpet_pad = OpenStudio::Model::MasslessOpaqueMaterial.new(model)
  cp02_carpet_pad.setName('CP02 CARPET PAD')
  cp02_carpet_pad.setRoughness('VeryRough')
  cp02_carpet_pad.setThermalResistance(0.21648)
  cp02_carpet_pad.setThermalAbsorptance(0.9)
  cp02_carpet_pad.setSolarAbsorptance(0.7)
  cp02_carpet_pad.setVisibleAbsorptance(0.8)

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

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

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

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

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

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

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

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

  model.getSurfaces.sort.each do |surface|
    if surface.outsideBoundaryCondition.to_s == 'Adiabatic'
      if surface.surfaceType.to_s == 'Wall'
        surface.setConstruction(wall_adiabatic_construction)
      else
        surface.setConstruction(floor_adiabatic_construction)
      end
    elsif surface.outsideBoundaryCondition.to_s == 'OtherSideCoefficients'
      # Ground
      if surface.surfaceType.to_s == 'Wall'
        surface.setOutsideBoundaryCondition('Ground')
        surface.setConstruction(basement_wall_construction)
      else
        surface.setOutsideBoundaryCondition('Ground')
        surface.setConstruction(basement_floor_construction)
      end
    end
  end
end
auto_size_shw_capacity(model:, u: 0.45, height_to_radius: 2, shw_scale: 'NECB_Default') click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 230
def auto_size_shw_capacity(model:, u: 0.45, height_to_radius: 2, shw_scale: 'NECB_Default')
  peak_flow_rate = 0
  shw_space_types = []
  space_peak_flows = []
  water_use = 0
  weekly_peak_flow = {
    'Default|Wkdy' => Array.new(24, 0),
    'Sat' => Array.new(24, 0),
    'Sun|Hol' => Array.new(24, 0)
  }
  peak_day_sched = nil
  peak_hour_sched = 0
  peak_flow_sched = 0
  next_hour_day = nil
  next_hour_hour = 0
  next_hour_flow = 0
  total_peak_flow_rate = 0
  shw_spaces = []
  shw_sched_names = []

  ##### Modify shw_scale if required
  if shw_scale.instance_of?(String)
    shw_scale = shw_scale.strip # remove leading or trailing whitespace in case users add them in shw_scale
  end
  if shw_scale == 'NECB_Default' or shw_scale.nil? or shw_scale == 'none' or shw_scale == false
    shw_scale = 1.0
  elsif shw_scale.instance_of?(String) # Convert a string to a float
    shw_scale = shw_scale.to_f
  end

  # First go through all the spaces in the building and determine and determine their shw requirements
  space_types_table = @standards_data['space_types']
  model.getSpaces.sort.each do |space|
    space_peak_flow = 0
    data = nil
    space_type_name = space.spaceType.get.standardsSpaceType.get.to_s
    tank_temperature = 60
    # find the specific space_type properties from standard.json
    space_types_table.each do |space_type|
      if (space_type['building_type'] + ' ' + space_type_name) == (space_type['building_type'] + ' ' + space_type['space_type'])
        break if space_type['necb_hvac_system_selection_type'] == '- undefined -'
        # If there is no service hot water load.. Don't bother adding anything.
        break if (space_type['service_water_heating_peak_flow_per_area'].to_f == 0.0 && space_type['service_water_heating_peak_flow_rate'].to_f == 0.0) || space_type['service_water_heating_schedule'].nil?

        # If there is a service hot water load collect the space information
        data = space_type
        break
      end
    end
    # If there is no service hot water load.. Don't bother adding anything.
    # Skip space types with no data
    next if data.nil?
    space_area = OpenStudio.convert(space.floorArea, 'm^2', 'ft^2').get # ft2
    # Calculate the peak shw flow rate for the space.  Peak flow from JSON file is in US Gal/hr/ft^2
    # space_peak_flow_ind is the peak flow rate for the space while space_peak_flow is the peak flow
    # rate for the space multiplied by the space (ultimately thermal zone) multiplier.  space_peak_flow is used for
    # much of the rest as it reflects how much water is used.  space_peak_flow_ind is recorded and used later on
    # when defining water use equipment.  When when water use equipment is assigned to spaces then the water use
    # by the equipment is multiplied by the space multiplier.  Note that there is a separate water use equipment
    # multiplier as well which is different than the space (ultimately thermal zone) multiplier.
    space_peak_flow_ind = data['service_water_heating_peak_flow_per_area'].to_f * space_area * shw_scale
    space_peak_flow = space_peak_flow_ind * space.multiplier
    #      space_peak_flows << space_peak_flow
    # Add the peak shw flow rate for the space to the total for the entire building
    total_peak_flow_rate += space_peak_flow
    # Get the tank temperature for the space.  This should always be 60 C but I added this part in case something changes in the future.
    if data['service_water_heating_target_temperature'].nil? || (data['service_water_heating_target_temperature'] <= 16)
      tank_temperature = 60
    else
      tank_temperature = data['service_water_heating_target_temperature']
    end
    # Get the shw schedule
    #      shw_sched_names << data['service_water_heating_schedule']
    # 'shw_peakflow_ind_SI' is the shw peak flow rate of the individual space (without the space multiplier)
    space_info = {
      'shw_spaces' => space,
      'shw_peakflow_SI' => OpenStudio.convert(space_peak_flow, 'gal/hr', 'm^3/s').get,
      'shw_peakflow_ind_SI' => OpenStudio.convert(space_peak_flow_ind, 'gal/hr', 'm^3/s').get,
      'shw_temp_SI' => tank_temperature,
      'shw_sched' => data['service_water_heating_schedule']
    }
    shw_spaces << space_info

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

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

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

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

# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 456
def auto_size_shw_pump_head(model, default: true, pipe_vel: 1.75, kin_visc_SI: 0.000004736, density_SI: 983, pipe_rough_m: 0.0000015)
  return 179532 if default

  mech_room, cond_spaces = find_mech_room(model)
  return 179532 if mech_room.nil? || cond_spaces.nil?

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

    # If the space type could not be found let the use know and go on to the next space.
    if sp_type_info.nil?
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.model_add_swh', "The space type called #{sp_type} could not be found.  Please check that the schedules.json file is available and that the space types are spelled correctly")
      next
    end
    next if sp_type_info['service_water_heating_peak_flow_per_area'].to_f == 0.0 && sp_type_info['service_water_heating_peak_flow_rate'].to_f == 0.0 || sp_type_info['service_water_heating_schedule'].nil?

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

  return hl_Pas.max_by { |hl| hl }
end
auto_system_all_other_spaces(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', boiler_fueltype:, chiller_type:, fan_type:, heating_coil_type_sys3:, heating_coil_type_sys4:, heating_coil_type_sys6:, hw_loop:, mau_cooling_type:, mau_heating_coil_type:, mau_type:, model:) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 982
def auto_system_all_other_spaces(baseboard_type:,
                                 necb_reference_hp:false,
                                 necb_reference_hp_supp_fuel:'DefaultFuel',
                                 boiler_fueltype:,
                                 chiller_type:,
                                 fan_type:,
                                 heating_coil_type_sys3:,
                                 heating_coil_type_sys4:,
                                 heating_coil_type_sys6:,
                                 hw_loop:,
                                 mau_cooling_type:,
                                 mau_heating_coil_type:,
                                 mau_type:,
                                 model:)

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

  # since dwelling units are all zoned 1:1 to space:zone we simply add the zone to the appropriate btap system.
  create_necb_system(baseboard_type: baseboard_type,
                     boiler_fueltype: boiler_fueltype,
                     chiller_type: chiller_type,
                     fan_type: fan_type,
                     heating_coil_type_sys3: heating_coil_type_sys3,
                     heating_coil_type_sys4: heating_coil_type_sys4,
                     heating_coil_type_sys6: heating_coil_type_sys6,
                     hw_loop: @hw_loop,
                     mau_cooling_type: mau_cooling_type,
                     mau_heating_coil_type: mau_heating_coil_type,
                     mau_type: mau_type,
                     model: model,
                     zones: zones,
                     necb_reference_hp: necb_reference_hp,
                     necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel)
end
auto_system_dwelling_units(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', boiler_fueltype:, chiller_type:, fan_type:, heating_coil_type_sys3:, heating_coil_type_sys4:, heating_coil_type_sys6:, hw_loop:, mau_cooling_type:, mau_heating_coil_type:, mau_type:, model:, baseline_system_zones_map_option:) click to toggle source

This method will ensure that all dwelling units are assigned to a system 1 or 3. There is an option to have a shared AHU or not.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1029
def auto_system_dwelling_units(baseboard_type:,
                               necb_reference_hp:false,
                               necb_reference_hp_supp_fuel:'DefaultFuel',
                               boiler_fueltype:,
                               chiller_type:,
                               fan_type:,
                               heating_coil_type_sys3:,
                               heating_coil_type_sys4:,
                               heating_coil_type_sys6:,
                               hw_loop:,
                               mau_cooling_type:,
                               mau_heating_coil_type:,
                               mau_type:,
                               model:,
                               baseline_system_zones_map_option:)
  system_zones_hash = {}
  # Determine if dwelling units have a shared AHU.  If user entered building stories > 4 then set to true.
  if baseline_system_zones_map_option == 'one_sys_per_dwelling_unit'
    dwelling_shared_ahu = false
  elsif baseline_system_zones_map_option == 'one_sys_per_bldg' || baseline_system_zones_map_option == 'NECB_Default' || baseline_system_zones_map_option == 'none' || baseline_system_zones_map_option == nil || necb_reference_hp
    dwelling_shared_ahu = true
  end
  # store dwelling zones into array
  zones = []
  model.getSpaces.select { |space| is_a_necb_dwelling_unit?(space) }.each do |space|
    zones << space.thermalZone.get
  end
  zones.uniq!

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

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

    when 3
      if dwelling_shared_ahu
        add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model,
                                                                              necb_reference_hp: necb_reference_hp,
                                                                              necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                                              zones: sys_zones,
                                                                              heating_coil_type: heating_coil_type_sys3,
                                                                              baseboard_type: baseboard_type,
                                                                              hw_loop: @hw_loop,
                                                                              multispeed: false)
      else
        # Create a separate air loop for each unit.
        sys_zones.each do |zone|
          add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model,
                                                                                necb_reference_hp: necb_reference_hp,
                                                                                necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                                                zones: [zone],
                                                                                heating_coil_type: heating_coil_type_sys3,
                                                                                baseboard_type: baseboard_type,
                                                                                hw_loop: @hw_loop,
                                                                                multispeed: false)
        end
      end
    end
  end
end
auto_system_storage_spaces(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', boiler_fueltype:, heating_coil_type_sys4:, model:) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1151
def auto_system_storage_spaces(baseboard_type:,
                               necb_reference_hp:false,
                               necb_reference_hp_supp_fuel:'DefaultFuel',
                               boiler_fueltype:,
                               heating_coil_type_sys4:,
                               model:)
  # Determine what zones are storage zones.
  tz = []
  storage_spaces = model.getSpaces.select { |space| is_an_necb_storage_space?(space) }
  storage_spaces.each { |space| tz << space.thermalZone.get }
  tz.uniq!

  return if tz.empty?

  # create a system 4 for the  zones.
  add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model,
                                                               necb_reference_hp: necb_reference_hp,
                                                               necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                               zones: tz,
                                                               heating_coil_type: heating_coil_type_sys4,
                                                               baseboard_type: baseboard_type,
                                                               hw_loop: @hw_loop)
  #      add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model,
  #                                                                            zones: tz,
  #                                                                            heating_coil_type: heating_coil_type_sys4,
  #                                                                            baseboard_type: baseboard_type,
  #                                                                            hw_loop: @hw_loop,
  #                                                                            multispeed: true)
end
auto_system_wet_spaces(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel', boiler_fueltype:, heating_coil_type_sys4:, model:) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1121
def auto_system_wet_spaces(baseboard_type:,
                           necb_reference_hp:false,
                           necb_reference_hp_supp_fuel:'DefaultFuel',
                           boiler_fueltype:,
                           heating_coil_type_sys4:,
                           model:)
  # Determine what zones are wet zones.
  wet_tz = []
  wet_spaces = model.getSpaces.select { |space| is_an_necb_wet_space?(space) }
  wet_spaces.each { |space| wet_tz << space.thermalZone.get }
  wet_tz.uniq!
  # create a system 4 for the wet zones.
  return if wet_tz.empty?

  add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model,
                                                               necb_reference_hp: necb_reference_hp,
                                                               necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                               zones: wet_tz,
                                                               heating_coil_type: heating_coil_type_sys4,
                                                               baseboard_type: baseboard_type,
                                                               hw_loop: @hw_loop)
  #      add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model,
  #                                                                            zones: wet_tz,
  #                                                                            heating_coil_type: heating_coil_type_sys4,
  #                                                                            baseboard_type: baseboard_type,
  #                                                                            hw_loop: @hw_loop,
  #                                                                            multispeed: false)
end
auto_system_wild_spaces(baseboard_type:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'Defaultfuel', heating_coil_type_sys4:, model:) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1182
def auto_system_wild_spaces(baseboard_type:,
                            necb_reference_hp:false,
                            necb_reference_hp_supp_fuel:'Defaultfuel',
                            heating_coil_type_sys4:,
                            model:)

  zones = []
  wild_spaces = model.getSpaces.select { |space| !is_an_necb_wet_space?(space) && is_an_necb_wildcard_space?(space) }
  wild_spaces.each { |space| zones << space.thermalZone.get }
  zones.uniq!

  return if zones.empty?

  # create a system 4 for the wild zones.
  add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model,
                                                               necb_reference_hp: necb_reference_hp,
                                                               necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                               zones: zones,
                                                               heating_coil_type: heating_coil_type_sys4,
                                                               baseboard_type: baseboard_type,
                                                               hw_loop: @hw_loop)
  #      add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model,
  #                                                                            zones: zones,
  #                                                                            heating_coil_type: heating_coil_type_sys4,
  #                                                                            baseboard_type: baseboard_type,
  #                                                                            hw_loop: @hw_loop,
  #                                                                            multispeed: true)
end
auto_zone_all_other_spaces(model) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 338
def auto_zone_all_other_spaces(model)
  other_tz_array = []
  # iterate through all non wildcard spaces.
  model.getSpaces.select { |space| !is_a_necb_dwelling_unit?(space) && !is_an_necb_wildcard_space?(space) }.each do |space|
    # skip if already assigned to a thermal zone.
    next unless space.thermalZone.empty?

    # create new zone for this space based on the space name.
    zone = OpenStudio::Model::ThermalZone.new(model)
    tz_name = "ALL_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory.get.name}_SCH=#{determine_dominant_schedule([space])}"
    zone.setName(tz_name)
    # sets space mulitplier unless it is nil or 1.
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end
    # Assign space to the new zone.
    space.setThermalZone(zone)

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

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

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

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

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

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

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 286
def auto_zone_wet_spaces(model:, lights_type: 'NECB_Default', lights_scale: 1.0)
  wet_zone_array = []
  model.getSpaces.select { |space| is_an_necb_wet_space?(space) }.each do |space|
    # if this space was already assigned to something skip it.
    next unless space.thermalZone.empty?

    # get space to dominant schedule
    dominant_schedule = determine_dominant_schedule(space.model.getSpaces)
    # create new TZ and set space to the zone.
    zone = OpenStudio::Model::ThermalZone.new(model)
    space.setThermalZone(zone)
    tz_name = "WET_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory.get.name}_SCH#{dominant_schedule}"
    zone.setName(tz_name)
    # Set multiplier from the original tz multiplier.
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end

    # this method will determine if the right schedule was used for this wet & wild space if not.. it will reset the space
    # to use the correct schedule version of the wet and wild space type.
    adjust_wildcard_spacetype_schedule(space: space, schedule: dominant_schedule, lights_type: lights_type, lights_scale: lights_scale)

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

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

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 385
def auto_zone_wild_spaces(model:, lights_type: 'NECB_Default', lights_scale: 1.0)
  other_tz_array = []
  # iterate through wildcard spaces.
  model.getSpaces.select { |space| is_an_necb_wildcard_space?(space) && !is_an_necb_wet_space?(space) }.each do |space|
    # skip if already assigned to a thermal zone.
    next unless space.thermalZone.empty?

    # create new zone for this space based on the space name.
    zone = OpenStudio::Model::ThermalZone.new(model)
    tz_name = "WILD_ST=#{space.spaceType.get.standardsSpaceType.get}_FL=#{space.buildingStory.get.name}_SCH=#{determine_dominant_schedule(space.model.getSpaces)}"
    zone.setName(tz_name)
    # sets space mulitplier unless it is nil or 1.
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end
    # Assign space to the new zone.
    space.setThermalZone(zone)

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

    adjust_wildcard_spacetype_schedule(space: space,
                                       schedule: dominant_floor_schedule,
                                       lights_type: lights_type,
                                       lights_scale: lights_scale)

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

  wild_zone_array = []
  # Get a list of all the wild spaces.
  model.getSpaces.select { |space| is_an_necb_wildcard_space?(space) && !is_an_necb_wet_space?(space) }.each do |space|
    # if this space was already assigned to something skip it.
    next unless space.thermalZone.empty?

    # find adjacent spaces to the current space.
    adj_spaces = OpenstudioStandards::Geometry.space_get_adjacent_spaces_with_shared_wall_areas(space, true)
    adj_spaces = adj_spaces.map { |key, value| key }

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

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

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

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

    # Set space to dominant

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

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

@param boiler_hot_water [OpenStudio::Model::BoilerHotWater] the object to modify @return [Boolean] true if successful, false if not

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 533
def boiler_hot_water_apply_efficiency_and_curves(boiler_hot_water)
  successfully_set_all_properties = false

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

  # Get the capacity
  capacity_w = boiler_hot_water_find_capacity(boiler_hot_water)

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

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

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

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

  # Get the minimum efficiency standards
  thermal_eff = nil

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

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

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

  # Set the name
  boiler_hot_water.setName(new_comp_name)

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

  return successfully_set_all_properties
end
boiler_hot_water_find_search_criteria(boiler_hot_water) click to toggle source

find search criteria

@param boiler_hot_water [OpenStudio::Model::BoilerHotWater] hot water boiler object @return [Hash] used for standards_lookup_table(model)

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 503
def boiler_hot_water_find_search_criteria(boiler_hot_water)
  # Define the criteria to find the boiler properties
  # in the hvac standards data set.
  search_criteria = {}
  search_criteria['template'] = template
  # Get fuel type
  fuel_type = nil
  case boiler_hot_water.fuelType
  when 'NaturalGas'
    fuel_type = 'Gas'
  when 'Electricity'
    fuel_type = 'Electric'
  when 'FuelOilNo1', 'FuelOilNo2'
    fuel_type = 'Oil'
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.BoilerHotWater', "For #{boiler_hot_water.name}, a fuel type of #{fuel_type} is not yet supported.  Assuming 'Gas.'")
    fuel_type = 'Gas'
  end

  search_criteria['fuel_type'] = fuel_type
  # Get the fluid type
  fluid_type = 'Hot Water'
  search_criteria['fluid_type'] = fluid_type
  return search_criteria
end
check_boolean_value(value, varname) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1904
def check_boolean_value(value, varname)
  return true if value =~ /^(true|t|yes|y|1)$/i
  return false if value.empty? || value =~ /^(false|f|no|n|0)$/i

  raise ArgumentError, "invalid value for #{varname}: #{value}"
end
check_datapoint_weather_folder(epw_file:, weather_folder:, custom_weather_folder: nil) click to toggle source

This method checks if a zip file containing weather data is stored in an external directory. If it is, then it checks if the name of the zip file (without extension) matches the name of the desired epw file (without extension). If it does then it copies the zip file to the openstudio-standards weather directory and expands the file. Presumably the zip file contains the .ddy, .epw, and .stat files needed by the rest of BTAP. If no appropriate weather zip files are present in the external directory then the method returns false. Arguments: epw_file (string): The name of the .epw file that BTAP will use. weather_folder (string): The path to the openstudio-standards weather folder. custom_weather_folder (string): The path to the external folder presumably containing the weather data zip file.

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2342
def check_datapoint_weather_folder(epw_file:, weather_folder:, custom_weather_folder: nil)
  # Check if the external weather directory exists and return false if there isn't one
  return false if custom_weather_folder.nil?
  # Check if there are any zip files in the external weather directory and return false if there isn't any.
  zip_search_term = File.join(custom_weather_folder.to_s, '*.zip')
  zip_files = Dir[zip_search_term]
  return false if zip_files.empty?
  # Look for a zip file in the external directory named after the .epw file.  If there isn't one return false.
  weather_loc = epw_file[0..-5]
  weather_zip = weather_loc + '.zip'
  zip_find = zip_files.select{ |check_file | File.basename(check_file).to_s.downcase == weather_zip.to_s.downcase }
  return false if zip_find.empty?
  # Copy the zip file we want from the external weather directory to the openstudio-standards weather directory and
  # extract the weather data in the file.
  puts "Copying: #{zip_find[0]} from the btap_cli weather folder to the openstudio-standards weather folder: #{weather_folder}"
  FileUtils.cp(zip_find[0], weather_folder)
  dest_zip = File.join(weather_folder, weather_zip)
  # Return true if everything goes well.
  return extract_weather_data(zipped_file: dest_zip, weather_dir: weather_folder)
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/NECB2011/hvac_systems.rb, line 635
def chiller_electric_eir_apply_efficiency_and_curves(chiller_electric_eir, clg_tower_objs)
  # 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 the chiller name includes 'Primary' or 'Secondary' then apply NECB rules
  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 type 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.015 * tower_cap)
  end

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

  return successfully_set_all_properties
end
clean_and_scale_model(model:, rotation_degrees: nil, scale_x: nil, scale_y: nil, scale_z: nil) click to toggle source

This method cleans the model of any existing HVAC systems and applies any desired ratation or scaling to the model.

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 478
def clean_and_scale_model(model:, rotation_degrees: nil, scale_x: nil, scale_y: nil, scale_z: nil)
  # clean model..
  BTAP::Resources::Envelope::remove_all_envelope_information(model)
  model = remove_all_hvac(model)
  model.getThermalZones.sort.each { |zone| zone.setUseIdealAirLoads(true) }
  model.getZoneHVACPackagedTerminalAirConditioners.each(&:remove)
  model.getCoilCoolingDXSingleSpeeds.each(&:remove)
  model.getZoneHVACBaseboardConvectiveWaters.each(&:remove)
  model.getAirLoopHVACZoneMixers.each(&:remove)
  model.getAirLoopHVACZoneSplitters.each(&:remove)
  model.getAirTerminalSingleDuctConstantVolumeNoReheats.each(&:remove)
  model.getWaterUseEquipmentDefinitions.each(&:remove)
  model.getWaterUseEquipments.each(&:remove)
  model.getWaterUseConnectionss.each(&:remove)
  model.getPumpConstantSpeeds.each(&:remove)
  model.getPumpVariableSpeeds.each(&:remove)
  model.getBoilerHotWaters.each(&:remove)
  model.getBoilerSteams.each(&:remove)
  model.getPlantLoops.each(&:remove)
  model.getSchedules.each(&:remove)
  model.getThermalZones.sort.each { |zone| zone.thermostat(&:remove) }
  model.getSpaces.sort.each { |space| space.designSpecificationOutdoorAir(&:remove) }
  model.getThermostatSetpointDualSetpoints.each(&:remove)

  scale_x = 1.0
  scale_y = 1.0
  scale_z = 1.0
  # Rotate to model if requested
  rotation_degrees = convert_arg_to_f(variable: rotation_degrees, default: 0.0)
  BTAP::Geometry.rotate_building(model: model, degrees: rotation_degrees) unless rotation_degrees == 0.0

  # Scale model if requested
  scale_x = convert_arg_to_f(variable: scale_x, default: 1.0)
  scale_y = convert_arg_to_f(variable: scale_y, default: 1.0)
  scale_z = convert_arg_to_f(variable: scale_z, default: 1.0)
  return unless scale_x != 1.0 || scale_y != 1.0 || scale_z != 1.0

  BTAP::Geometry.scale_model(model, scale_x, scale_y, scale_z)
end
coil_cooling_dx_multi_speed_apply_efficiency_and_curves(coil_cooling_dx_multi_speed, sql_db_vars_map) 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/NECB2011/hvac_systems.rb, line 892
def coil_cooling_dx_multi_speed_apply_efficiency_and_curves(coil_cooling_dx_multi_speed, sql_db_vars_map)
  successfully_set_all_properties = true
  model = coil_cooling_dx_multi_speed.model
  multi_speed_heat_pump = coil_cooling_dx_multi_speed.containingHVACComponent.get.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.get
  airloop = multi_speed_heat_pump.airLoopHVAC.get

  # Define the criteria to find the properties in the hvac standards data set
  search_criteria = coil_dx_find_search_criteria(coil_cooling_dx_multi_speed)
  capacity_w = coil_cooling_dx_multi_speed_find_capacity(coil_cooling_dx_multi_speed)

  # Find design outside air flow rate and flow fraction
  controller_oa = nil
  if airloop.airLoopHVACOutdoorAirSystem.is_initialized
    oa_system = airloop.airLoopHVACOutdoorAirSystem.get
    controller_oa = oa_system.getControllerOutdoorAir
  end
  min_oa_flow_rate = 0.0
  oaf = 0.0

  if controller_oa
    min_oa_flow_rate = nil
    if controller_oa.minimumOutdoorAirFlowRate.is_initialized
      min_oa_flow_rate = controller_oa.minimumOutdoorAirFlowRate.get
    elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized
      min_oa_flow_rate = controller_oa.autosizedMinimumOutdoorAirFlowRate.get
    end
    if min_oa_flow_rate then oaf = min_oa_flow_rate.to_f / airloop.autosizedDesignSupplyAirFlowRate.to_f end
  end

  # Find required capacity of each stage and total number of stages based on NECB rules
  # This implementation is limited to 4 stages only. The capacity of stages 1-3 is set to
  # 66 kW as stipulated by NECB. The capacity of the 4th stage is then allowed to exceed 66 kW
  # up to the design capacity.
  stage_cap = []
  num_stages = (capacity_w / (66.0 * 1000.0) + 0.5).round
  max_cap = 66.0 * 1000.0 * num_stages
  final_num_stages = num_stages
  case num_stages
  when 1
    stage_cap[0] = capacity_w / 2.0
    stage_cap[1] = 2.0 * stage_cap[0]
    final_num_stages = 2
  else
    stage_cap[0] = 66.0 * 1000.0
    stage_cap[1] = 2.0 * stage_cap[0]
    case num_stages
    when 2
    when 3
      stage_cap[2] = 3.0 * stage_cap[0]
    else
      final_num_stages = 4
      stage_cap[2] = 3.0 * stage_cap[0]
      stage_cap[3] = max_cap
    end
  end

  # Set final number of cooling stages and create missing stages if needed
  for istage in 2..final_num_stages - 1
    new_clg_stage = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    coil_cooling_dx_multi_speed.addStage(new_clg_stage)
  end
  multi_speed_heat_pump.setNumberofSpeedsforCooling(final_num_stages)

  # Set final capacities for each of the stages. The flow rate for each of the stages
  # is maintained above the outside air flow rate
  coil_cooling_dx_multi_speed.stages[0].setGrossRatedTotalCoolingCapacity(stage_cap[0])
  coil_cooling_dx_multi_speed.stages[1].setGrossRatedTotalCoolingCapacity(stage_cap[1])
  case coil_cooling_dx_multi_speed.stages.size
  when 2
    if oaf > 0.5 then multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate) end
  when 3
    coil_cooling_dx_multi_speed.stages[2].setGrossRatedTotalCoolingCapacity(stage_cap[2])
    if (oaf > 0.333) && (oaf <= 0.666)
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate)
    elsif oaf > 0.666
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate)
      multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate)
    end
  when 4
    coil_cooling_dx_multi_speed.stages[2].setGrossRatedTotalCoolingCapacity(stage_cap[2])
    coil_cooling_dx_multi_speed.stages[3].setGrossRatedTotalCoolingCapacity(stage_cap[3])
    if (oaf > 0.25) && (oaf <= 0.5)
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate)
    elsif (oaf > 0.5) && (oaf <= 0.75)
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate)
      multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate)
    elsif oaf > 0.75
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate)
      multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate)
      multi_speed_heat_pump.setSpeed3SupplyAirFlowRateDuringCoolingOperation(min_oa_flow_rate)
    end
  end

  capacity_btu_per_hr = OpenStudio.convert(stage_cap.last, 'W', 'Btu/hr').get
  capacity_kbtu_per_hr = OpenStudio.convert(stage_cap.last, 'W', 'kBtu/hr').get

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

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

  # get clg stages
  clg_stages = coil_cooling_dx_multi_speed.stages

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

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

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

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

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

  # Set the COP values
  cop, new_comp_name = coil_cooling_dx_multi_speed_standard_minimum_cop(coil_cooling_dx_multi_speed)
  unless cop.nil?
    clg_stages.sort.each do |curr_istage|
      curr_istage.setGrossRatedCoolingCOP(cop)
    end
  end
  sql_db_vars_map[new_comp_name] = coil_cooling_dx_multi_speed.name.to_s
  coil_cooling_dx_multi_speed.setName(new_comp_name)

  # It was found that the heat pump OS object doesn't respond to the call to turn on from the
  # system availability manager night cycle. This EMS script is then implemented to check the status
  # of the system availability manager night cycle and force the heat pump to turn on when needed. The
  # heat pump is still turned on when its availability schedule calls for it.
  create_ems_to_turn_on_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_for_night_cycle(multi_speed_heat_pump)

  return sql_db_vars_map
end
coil_dx_heating_type(coil_dx, necb_reference_hp = false) click to toggle source

NECB reference heat pump system heating type rules need to be flexible to account for

  1. DX htg/cooling + gas supplement htg

  2. Potential lack of AirLoopHVACUnitaryHeatPumpAirToAir or AirLoopHVACUnitarySystem

@param necb_reference_hp [Boolean] if true, NECB reference model rules for heat pumps will be used.

Calls superclass method CoilDX#coil_dx_heating_type
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2427
def coil_dx_heating_type(coil_dx, necb_reference_hp = false)
  supp_htg_type = nil

  # If not heat pump reference case use the standard implementation.
  if !necb_reference_hp
    return super(coil_dx)
  else
    if coil_dx.airLoopHVAC.empty?
      if coil_dx.containingHVACComponent.is_initialized
        containing_comp = coil_dx.containingHVACComponent.get
        if containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.is_initialized
          supp_htg_coil = containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.get.supplementalHeatingCoil
          if supp_htg_coil.to_CoilHeatingElectric.is_initialized
            supp_htg_type = 'Electric Resistance or None'
          elsif supp_htg_coil.to_CoilHeatingGas.is_initialized or supp_htg_coil.to_CoilHeatingWater.is_initialized
            supp_htg_type = 'All Other'
          else # None
            supp_htg_type = 'Electric Resistance or None'
          end
        else
          # For other virtual wrapper, use method in Standard.DXCoil
          # Or add future wrappers here
          return super
        end
      end

    elsif coil_dx.airLoopHVAC.is_initialized # Heat pumps without a wrapper (lone DX coils in the air loop)
      airloop = coil_dx.airLoopHVAC.get
      num_of_DX_Coils = 0
      num_of_supp_coils = 0
      supp_htg_type = ''
      # Go through and determine number of each type of coils in air loop to determine supp_htg_type
      airloop.supplyComponents.each do |supply_component|
        if supply_component.to_CoilHeatingDXSingleSpeed.is_initialized or supply_component.to_CoilHeatingDXMultiSpeed.is_initialized
          supply_component.to_CoilHeatingDXVariableSpeed.is_initialized
          num_of_DX_Coils = num_of_DX_Coils + 1
        elsif supply_component.to_CoilCoolingDXSingleSpeed.is_initialized or supply_component.to_CoilCoolingDXTwoSpeed.is_initialized or
          supply_component.to_CoilCoolingDXTwoSpeed.is_initialized or supply_component.to_CoilCoolingDXVariableSpeed.is_initialized or
          supply_component.to_CoilCoolingDXMultiSpeed.is_initialized or
          supply_component.to_CoilCoolingDXCurveFitPerformance.is_initialized or
          supply_component.to_CoilCoolingDXTwoStageWithHumidityControlMode.is_initialized
          num_of_DX_Coils = num_of_DX_Coils + 1
        elsif supply_component.to_CoilHeatingGas.is_initialized or supply_component.to_CoilHeatingGasMultiStage.is_initialized or
          supply_component.to_CoilHeatingWater.is_initialized
          num_of_supp_coils = num_of_supp_coils + 1
          supp_htg_type = 'All Other'
        elsif supply_component.to_CoilHeatingElectric.is_initialized
          num_of_supp_coils = num_of_supp_coils + 1
          supp_htg_type = 'Electric Resistance or None'
        end
      end

      #Two possible heat pump configuration
      if num_of_DX_Coils == 2 && num_of_supp_coils == 1 #Scenario 1: 1 DX htg + 1 DX clg + 1 Non-DX htg coil
        puts "scenario 1 supp_htg_type #{supp_htg_type}"
        return supp_htg_type # return supplmental heating type
      else #Scenario 2: num_of_DX_Coils < 2 or num_of_supp_coils = 0;
        puts "scenario 2 supp_htg_type #{supp_htg_type}"
        puts "num_of_DX_Coils #{num_of_DX_Coils}"
        puts "num_of_supp_coils #{num_of_supp_coils}"
        return supp_htg_type = 'Electric Resistance or None'
      end
    end
  end
end
coil_heating_dx_single_speed_find_capacity(coil_heating_dx_single_speed, necb_reference_hp = false) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2334
def coil_heating_dx_single_speed_find_capacity(coil_heating_dx_single_speed, necb_reference_hp = false)
  # Set Rated heating capacity = 50% cooling coil capacity at -8.3 C outdoor [8.4.4.13 (2)(c)]

  if necb_reference_hp #NECB reference heat pump rules apply
    # grab paired cooling coil
    if coil_heating_dx_single_speed.airLoopHVAC.empty?

      if coil_heating_dx_single_speed.containingHVACComponent.is_initialized

        containing_comp = coil_heating_dx_single_speed.containingHVACComponent.get
        if containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.is_initialized
          clg_coil = containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAir.get.coolingCoil
        elsif containing_comp.to_AirLoopHVACUnitarySystem.is_initialized
          unitary = containing_comp.to_AirLoopHVACUnitarySystem.get
          if unitary.coolingCoil.is_initialized
            clg_coil = unitary.coolingCoil.get
          end
        end
        # @todo Add other unitary systems
      elsif coil_heating_dx_single_speed.containingZoneHVACComponent.is_initialized
        containing_comp = coil_heating_dx_single_speed.containingZoneHVACComponent.get
        # PTHP
        if containing_comp.to_ZoneHVACPackagedTerminalHeatPump.is_initialized
          pthp = containing_comp.to_ZoneHVACPackagedTerminalHeatPump.get
          clg_coil = containing_comp.to_ZoneHVACPackagedTerminalHeatPump.get.coolingCoil
        end
      end
    elsif coil_heating_dx_single_speed.airLoopHVAC.is_initialized
      air_loop = coil_heating_dx_single_speed.airLoopHVAC.get
      # Check for the presence of any other type of cooling coil
      clg_types = ['OS:Coil:Cooling:DX:SingleSpeed',
                  'OS:Coil:Cooling:DX:TwoSpeed',
                  'OS:Coil:Cooling:DX:MultiSpeed']
      clg_types.each do |ct|
        coils = air_loop.supplyComponents(ct.to_IddObjectType)
        next if coils.empty?
        clg_coil = coils[0]
        puts "coils = air_loop.supplyComponents(ct.to_IddObjectType) #{}"
        break # Stop on first DX cooling coil found
      end
    end

    # Paired cooling coil parameters
    clg_coil = clg_coil.to_CoilCoolingDXSingleSpeed.get
    capacity_w = coil_cooling_dx_single_speed_find_capacity(clg_coil)
    indoor_wb = 19.4 #rated indoor wb
    outdoor_db = -8.3 # outdoor db

    # heating capacity = capacity factor (function of temp) from biquadratic curve
    # with curve limits on minimum y/outdoor db (no extrapolation)
    cooling_cap_f_temp_curve = clg_coil.totalCoolingCapacityFunctionOfTemperatureCurve
    cooling_cap_f_temp_factor_min_y = cooling_cap_f_temp_curve.evaluate(indoor_wb,outdoor_db)
    htg_cap_w_min_y = capacity_w*0.5*cooling_cap_f_temp_factor_min_y

    # heating capacity = capacity factor (function of temp) from biquadratic curve
    # without curve limits on minimum y/outdoor db (extrapolate)
    cooling_cap_f_temp_const = 0.867905
    cooling_cap_f_temp_x = 0.0142459
    cooling_cap_f_temp_x2 = 0.00055436
    cooling_cap_f_temp_y = -0.0075575
    cooling_cap_f_temp_y2 = 3.3e-05
    cooling_cap_f_temp_xy = -0.0001918
    cooling_cap_f_temp_factor_no_min_y = cooling_cap_f_temp_const + cooling_cap_f_temp_x*indoor_wb + cooling_cap_f_temp_x2*indoor_wb**2 +
    cooling_cap_f_temp_y*outdoor_db + cooling_cap_f_temp_y2*outdoor_db**2 + cooling_cap_f_temp_xy*indoor_wb*outdoor_db
    htg_cap_w_no_min_y = capacity_w*0.5*cooling_cap_f_temp_factor_no_min_y

    puts "capacity_w #{capacity_w}"
    puts "cooling_cap_f_temp_factor_no_min_y #{cooling_cap_f_temp_factor_no_min_y}"
    puts "cooling_cap_f_temp_factor_min_y #{cooling_cap_f_temp_factor_min_y}"
    puts "htg_cap_w_no_min_y #{htg_cap_w_no_min_y}"
    puts "htg_cap_w_min_y #{htg_cap_w_min_y}"

    # use actual factor from -8.3 to compute rated heating capacity unless it's < 0
    if cooling_cap_f_temp_factor_no_min_y>0
      htg_cap_w = htg_cap_w_no_min_y
    else
      htg_cap_w = htg_cap_w_min_y
    end

    # Hardsize rated capacity of heating coil
    coil_heating_dx_single_speed.setRatedTotalHeatingCapacity(htg_cap_w)

    return htg_cap_w
  else # Do not follow NECB reference HP rule; proceed as usual
    return super(coil_heating_dx_single_speed)
  end
end
coil_heating_gas_apply_efficiency_and_curves(coil_heating_gas) 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/NECB2011/hvac_systems.rb, line 851
def coil_heating_gas_apply_efficiency_and_curves(coil_heating_gas)
  successfully_set_all_properties = true

  # Define the search criteria
  search_criteria = coil_heating_gas_find_search_criteria

  # Get the coil capacity
  capacity_w = coil_heating_gas_find_capacity(coil_heating_gas)
  capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get

  # lookup properties
  coil_table = @standards_data['furnaces']
  coil_props = model_find_object(coil_table, search_criteria, [capacity_btu_per_hr, 0.001].max, Date.today)

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

  # Make the plf vs plr curve
  plffplr_curve = model_add_curve(coil_heating_gas.model, coil_props['efffplr'])
  if plffplr_curve
    coil_heating_gas.setPartLoadFractionCorrelationCurve(plffplr_curve)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGas', "For #{coil_heating_gas.name}, cannot find plffplr curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Thermal efficiency
  thermal_eff = coil_heating_gas_standard_minimum_thermal_efficiency(coil_heating_gas)

  # Set the efficiency values
  coil_heating_gas.setGasBurnerEfficiency(thermal_eff.to_f)

  return successfully_set_all_properties
end
coil_heating_gas_find_capacity(coil_heating_gas) click to toggle source

find furnace capacity

@return [Hash] used for standards_lookup_table(model)

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 769
def coil_heating_gas_find_capacity(coil_heating_gas)
  # Get the coil capacity
  capacity_w = nil
  if coil_heating_gas.nominalCapacity.is_initialized
    capacity_w = coil_heating_gas.nominalCapacity.get
  elsif coil_heating_gas.autosizedNominalCapacity.is_initialized
    capacity_w = coil_heating_gas.autosizedNominalCapacity.get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGas', "For #{coil_heating_gas.name} capacity is not available, cannot apply efficiency standard.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  return capacity_w
end
coil_heating_gas_find_search_criteria() click to toggle source

find search criteria

@return [Hash] used for standards_lookup_table(model)

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 756
def coil_heating_gas_find_search_criteria
  # Define the criteria to find the furnace properties
  # in the hvac standards data set.
  search_criteria = {}
  search_criteria['fluid_type'] = 'Air'
  search_criteria['fuel_type'] = 'Gas'

  return search_criteria
end
coil_heating_gas_multi_stage_apply_efficiency_and_curves(coil_heating_gas_multi_stage) 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/NECB2011/hvac_systems.rb, line 1140
def coil_heating_gas_multi_stage_apply_efficiency_and_curves(coil_heating_gas_multi_stage)
  successfully_set_all_properties = true
  model = coil_heating_gas_multi_stage.model

  # get multi speed heat pump and air loop
  multi_speed_heat_pump = nil
  multi_speed_heat_pumps = model.getAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeeds
  multi_speed_heat_pumps.sort.each do |iheat_pump|
    htg_coil = iheat_pump.heatingCoil
    if htg_coil.name.to_s.strip == coil_heating_gas_multi_stage.name.to_s.strip
      multi_speed_heat_pump = iheat_pump
      break
    end
  end
  airloop = multi_speed_heat_pump.airLoopHVAC.get

  # Define the criteria to find the properties in the hvac standards data set.
  search_criteria = coil_heating_gas_multi_stage_find_search_criteria(coil_heating_gas_multi_stage)
  fuel_type = search_criteria['fuel_type']
  capacity_w = coil_heating_gas_multi_stage_find_capacity(coil_heating_gas_multi_stage)

  # Find system design outside air flow rate and fraction
  controller_oa = nil
  if airloop.airLoopHVACOutdoorAirSystem.is_initialized
    oa_system = airloop.airLoopHVACOutdoorAirSystem.get
    controller_oa = oa_system.getControllerOutdoorAir
  end
  min_oa_flow_rate = 0.0
  oaf = 0.0
  if controller_oa
    min_oa_flow_rate = nil
    if controller_oa.minimumOutdoorAirFlowRate.is_initialized
      min_oa_flow_rate = controller_oa.minimumOutdoorAirFlowRate.get
    elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized
      min_oa_flow_rate = controller_oa.autosizedMinimumOutdoorAirFlowRate.get
    end
    if min_oa_flow_rate then oaf = min_oa_flow_rate.to_f / airloop.autosizedDesignSupplyAirFlowRate.to_f end
  end

  # Find capacities of each of the stages and the total number of stages required based on NECB rules.
  # This implementation is limited to 4 stages. The capacity of stages 1-3 is set to 66 kW as stipulated
  # by NECB. The capacity of the 4th stage can exceed 66 kW up to the design capacity.
  htg_stages = coil_heating_gas_multi_stage.stages
  num_stages = (capacity_w / (66.0 * 1000.0) + 0.5).round
  max_cap = 66.0 * 1000.0 * num_stages
  stage_cap = []
  final_num_stages = num_stages
  if capacity_w == 0.001
    final_num_stages = 1
    stage_cap[0] = capacity_w
  else
    case num_stages
    when 1
      stage_cap[0] = capacity_w / 2.0
      stage_cap[1] = 2.0 * stage_cap[0]
      final_num_stages = 2
    else
      stage_cap[0] = 66.0 * 1000.0
      stage_cap[1] = 2.0 * stage_cap[0]
      case num_stages
      when 2
      when 3
        stage_cap[2] = 3.0 * stage_cap[0]
      else
        final_num_stages = 4
        stage_cap[2] = 3.0 * stage_cap[0]
        stage_cap[3] = max_cap
      end
    end
  end

  # Set final number of stages and create missing stages if needed
  for istage in 1..final_num_stages - 1
    new_htg_stage = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
    coil_heating_gas_multi_stage.addStage(new_htg_stage)
  end
  multi_speed_heat_pump.setNumberofSpeedsforHeating(final_num_stages)

  # Set final capacities for each of the stages. The air flow rate for each of the stages
  # is maintained above the outside air flow rate
  coil_heating_gas_multi_stage.stages[0].setNominalCapacity(stage_cap[0])
  case coil_heating_gas_multi_stage.stages.size
  when 2
    coil_heating_gas_multi_stage.stages[1].setNominalCapacity(stage_cap[1])
    if oaf > 0.5 then multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate) end
  when 3
    coil_heating_gas_multi_stage.stages[1].setNominalCapacity(stage_cap[1])
    coil_heating_gas_multi_stage.stages[2].setNominalCapacity(stage_cap[2])
    if (oaf > 0.333) && (oaf <= 0.666)
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate)
    elsif oaf > 0.666
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate)
      multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate)
    end
  when 4
    coil_heating_gas_multi_stage.stages[1].setNominalCapacity(stage_cap[1])
    coil_heating_gas_multi_stage.stages[2].setNominalCapacity(stage_cap[2])
    coil_heating_gas_multi_stage.stages[3].setNominalCapacity(stage_cap[3])
    if (oaf > 0.25) && (oaf <= 0.5)
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate)
    elsif (oaf > 0.5) && (oaf <= 0.75)
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate)
      multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate)
    elsif oaf > 0.75
      multi_speed_heat_pump.setSpeed1SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate)
      multi_speed_heat_pump.setSpeed2SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate)
      multi_speed_heat_pump.setSpeed3SupplyAirFlowRateDuringHeatingOperation(min_oa_flow_rate)
    end
  end

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

  # Lookup efficiencies
  heater_props = nil
  heater_props = model_find_object(standards_data['furnaces'], search_criteria, capacity_btu_per_hr, Date.today)

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

  # Make the EFFPLR curve
  efffplr = model_add_curve(coil_heating_gas_multi_stage.model, heater_props['efffplr'])
  if efffplr
    coil_heating_gas_multi_stage.setPartLoadFractionCorrelationCurve(efffplr)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGasMultiStage', "For #{coil_heating_gas_multi_stage.name}, cannot find efffplr curve, will not be set.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # Get the minimum efficiency standards
  thermal_eff = nil

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

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

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

  # Set the name
  coil_heating_gas_multi_stage.setName(new_comp_name)

  # Get heating stages
  htg_stages = coil_heating_gas_multi_stage.stages

  # Set the efficiency values
  unless thermal_eff.nil?
    htg_stages.sort.each do |stage|
      stage.setGasBurnerEfficiency(thermal_eff)
    end
  end

  return successfully_set_all_properties
end
coil_heating_gas_standard_minimum_thermal_efficiency(coil_heating_gas, rename = false) click to toggle source

Finds lookup object in standards and return minimum thermal efficiency

@return [Double] minimum thermal efficiency

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 788
def coil_heating_gas_standard_minimum_thermal_efficiency(coil_heating_gas, rename = false)
  # Get the coil properties
  search_criteria = coil_heating_gas_find_search_criteria
  capacity_w = coil_heating_gas_find_capacity(coil_heating_gas)
  capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
  capacity_kbtu_per_hr = OpenStudio.convert(capacity_w, 'W', 'kBtu/hr').get

  # Get the minimum efficiency standards
  thermal_eff = nil

  # Get the coil properties
  coil_table = @standards_data['furnaces']
  coil_props = model_find_object(coil_table, search_criteria, [capacity_btu_per_hr, 0.001].max)

  unless coil_props
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGas', "For #{coil_heating_gas.name}, cannot find coil props, cannot apply efficiency standard.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # New name initial value
  new_comp_name = coil_heating_gas.name

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

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

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

  unless thermal_eff
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGas', "For #{CoilHeatingGas.name}, cannot find coil efficiency, cannot apply efficiency standard.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # Rename
  if rename
    coil_heating_gas.setName(new_comp_name)
  end

  return thermal_eff
end
common_air_loop(model:, system_data:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2104
def common_air_loop(model:, system_data:)
  mau_air_loop = OpenStudio::Model::AirLoopHVAC.new(model)
  mau_air_loop.setName(system_data[:name])
  air_loop_sizing = mau_air_loop.sizingSystem
  air_loop_sizing.autosizeDesignOutdoorAirFlowRate
  air_loop_sizing.setPreheatDesignTemperature(system_data[:PreheatDesignTemperature]) unless system_data[:PreheatDesignTemperature].nil?
  air_loop_sizing.setPreheatDesignHumidityRatio(system_data[:PreheatDesignHumidityRatio]) unless system_data[:PreheatDesignHumidityRatio].nil?
  air_loop_sizing.setPrecoolDesignTemperature(system_data[:PrecoolDesignTemperature]) unless system_data[:PrecoolDesignTemperature].nil?
  air_loop_sizing.setPrecoolDesignHumidityRatio(system_data[:PrecoolDesignHumidityRatio]) unless system_data[:PrecoolDesignHumidityRatio].nil?
  air_loop_sizing.setSizingOption(system_data[:SizingOption]) unless system_data[:SizingOption].nil?
  air_loop_sizing.setCoolingDesignAirFlowMethod(system_data[:CoolingDesignAirFlowMethod]) unless system_data[:CoolingDesignAirFlowMethod].nil?
  air_loop_sizing.setCoolingDesignAirFlowRate(system_data[:CoolingDesignAirFlowRate]) unless system_data[:CoolingDesignAirFlowRate].nil?
  air_loop_sizing.setHeatingDesignAirFlowMethod(system_data[:HeatingDesignAirFlowMethod]) unless system_data[:HeatingDesignAirFlowMethod].nil?
  air_loop_sizing.setHeatingDesignAirFlowRate(system_data[:HeatingDesignAirFlowRate]) unless system_data[:HeatingDesignAirFlowRate].nil?
  air_loop_sizing.setSystemOutdoorAirMethod(system_data[:SystemOutdoorAirMethod]) unless system_data[:SystemOutdoorAirMethod].nil?
  air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(system_data[:CentralCoolingDesignSupplyAirHumidityRatio]) unless system_data[:CentralCoolingDesignSupplyAirHumidityRatio].nil?
  air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(system_data[:CentralHeatingDesignSupplyAirHumidityRatio]) unless system_data[:CentralHeatingDesignSupplyAirHumidityRatio].nil?
  air_loop_sizing.setTypeofLoadtoSizeOn(system_data[:TypeofLoadtoSizeOn]) unless system_data[:TypeofLoadtoSizeOn].nil?
  air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(system_data[:CentralCoolingDesignSupplyAirTemperature]) unless system_data[:CentralCoolingDesignSupplyAirTemperature].nil?
  air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(system_data[:CentralHeatingDesignSupplyAirTemperature]) unless system_data[:CentralHeatingDesignSupplyAirTemperature].nil?
  air_loop_sizing.setAllOutdoorAirinCooling(system_data[:AllOutdoorAirinCooling]) unless system_data[:AllOutdoorAirinCooling].nil?
  air_loop_sizing.setAllOutdoorAirinHeating(system_data[:AllOutdoorAirinHeating]) unless system_data[:AllOutdoorAirinHeating].nil?
  if model.version < OpenStudio::VersionString.new('2.7.0')
    air_loop_sizing.setMinimumSystemAirFlowRatio(system_data[:MinimumSystemAirFlowRatio]) unless system_data[:MinimumSystemAirFlowRatio].nil?
  else
    air_loop_sizing.setCentralHeatingMaximumSystemAirFlowRatio(system_data[:MinimumSystemAirFlowRatio]) unless system_data[:MinimumSystemAirFlowRatio].nil?
  end
  return mau_air_loop
end
convert_arg_to_bool(variable:, default:) click to toggle source

This method converts arguments to bool. Anything other than a bool false or string ‘false’ is converted to a bool true. Bool false and case insesitive string false are turned into bool false.

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 28
def convert_arg_to_bool(variable:, default:)
  return true if variable.nil?
  if variable.is_a? String
    return true if variable.to_s.downcase == 'necb_default'
    return false if variable.to_s.downcase == 'false'
  end
  return false if variable == false
  return true
end
convert_arg_to_f(variable:, default:) click to toggle source

This is a helper method to convert arguments that may support ‘NECB_Default, and nils to convert to float’

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 17
def convert_arg_to_f(variable:, default:)
  return variable if variable.kind_of?(Numeric)
  return default if variable.nil? || (variable == 'NECB_Default')
  return unless variable.kind_of?(String)

  variable = variable.strip
  return variable.to_f
end
corrupt_standards_database() click to toggle source

2019-05-23 ckirney This is an ugly, disgusting, hack (hence the name) that I dreamed out so that we could quickly and easily finish the merge from the nrcan branch (using OS 2.6.0) to master (using OS 2.8.0). This must be revised and a more elegant solution found.

This method takes everything in the @standards_data hash and adds it to the main @standards_data hash. This was done because other contributors insist on using the ‘model_find_object’ method which is passed a hash and some search criteria. The ‘model_find_objects’ then looks through the hash to information matching the search criteria. NECB standards assumes that the ‘standards_lookup_table_first’ method is used. This does basically the some thing as ‘model_find_objects’ only it assumes that you are looking in the standards hash and you tell it which table in the standards hash to look for.

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1297
def corrupt_standards_database
  @standards_data['tables'].each do |table|
    @standards_data[table[0]] = table[1]['table']
  end
end
create_base_data(model) click to toggle source

Generates the base data hash mainly used to perform qaqc.

# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 183
def create_base_data(model)
  # construct command with local libs
  os_version = OpenStudio.openStudioLongVersion
  eplus_version = OpenStudio.energyPlusVersion
  puts "\n\n\nOS_version is [#{os_version.strip}]"
  puts "\n\n\nEP_version is [#{eplus_version.strip}]"

  # Ensure all surfaces are unique.
  surfaces = model.getSurfaces.sort

  # Sort surfaces by type

  interior_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, ['Surface', 'Adiabatic'])
  interior_floors = BTAP::Geometry::Surfaces.filter_by_surface_types(interior_surfaces, 'Floor')
  outdoor_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, 'Outdoors')
  outdoor_walls = BTAP::Geometry::Surfaces.filter_by_surface_types(outdoor_surfaces, 'Wall')
  outdoor_roofs = BTAP::Geometry::Surfaces.filter_by_surface_types(outdoor_surfaces, 'RoofCeiling')
  outdoor_floors = BTAP::Geometry::Surfaces.filter_by_surface_types(outdoor_surfaces, 'Floor')
  outdoor_subsurfaces = outdoor_surfaces.flat_map(&:subSurfaces)

  ground_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, ['Ground', 'Foundation'])
  ground_walls = BTAP::Geometry::Surfaces.filter_by_surface_types(ground_surfaces, 'Wall')
  ground_roofs = BTAP::Geometry::Surfaces.filter_by_surface_types(ground_surfaces, 'RoofCeiling')
  ground_floors = BTAP::Geometry::Surfaces.filter_by_surface_types(ground_surfaces, 'Floor')

  windows = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['FixedWindow', 'OperableWindow'])
  skylights = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['Skylight', 'TubularDaylightDiffuser', 'TubularDaylightDome'])
  doors = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['Door', 'GlassDoor'])
  overhead_doors = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['OverheadDoor'])

  # Peaks
  electric_peak = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters'" \
                                                                     " AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - Electricity' AND RowName='Electricity:Facility'" \
                                                                     " AND ColumnName='Electricity Maximum Value' AND Units='W'")
  natural_gas_peak = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters'" \
                                                                        " AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - Natural Gas' AND RowName='NaturalGas:Facility'" \
                                                                        " AND ColumnName='Natural Gas Maximum Value' AND Units='W'")

  get_sql_tables_to_json(model)

  # Create hash to store all the collected data.
  qaqc = {}
  qaqc[:sql_data] = get_sql_tables_to_json(model)
  error_warning = []
  # Store Building data.
  qaqc[:building] = {}
  qaqc[:building][:name] = model.building.get.name.get
  qaqc[:building][:conditioned_floor_area_m2] = nil
  if model.building.get.conditionedFloorArea.empty?
    error_warning << "model.building.get.conditionedFloorArea() is empty for #{model.building.get.name.get}"
  else
    qaqc[:building][:conditioned_floor_area_m2] = model.building.get.conditionedFloorArea.get
  end
  qaqc[:building][:exterior_area_m2] = model.building.get.exteriorSurfaceArea # m2
  qaqc[:building][:volume] = model.building.get.airVolume # m3
  qaqc[:building][:number_of_stories] = model.getBuildingStorys.size
  qaqc[:building][:standards_number_of_stories] = nil
  qaqc[:building][:standards_number_of_stories] = model.building.get.standardsNumberOfStories.get unless model.building.get.standardsNumberOfStories.empty?
  qaqc[:building][:standards_number_of_above_ground_stories] = nil
  qaqc[:building][:standards_number_of_above_ground_stories] = model.building.get.standardsNumberOfAboveGroundStories.get unless model.building.get.standardsNumberOfAboveGroundStories.empty?
  qaqc[:building][:standards_number_of_living_units] = nil
  qaqc[:building][:standards_number_of_living_units] = model.building.get.standardsNumberOfLivingUnits.get unless model.building.get.standardsNumberOfLivingUnits.empty?
  qaqc[:building][:nominal_floor_to_ceiling_height] = nil
  qaqc[:building][:nominal_floor_to_ceiling_height] = model.building.get.nominalFloortoCeilingHeight.get unless model.building.get.nominalFloortoCeilingHeight.empty?
  qaqc[:building][:nominal_floor_to_floor_height] = nil
  qaqc[:building][:nominal_floor_to_floor_height] = model.building.get.nominalFloortoFloorHeight.get unless model.building.get.nominalFloortoFloorHeight.empty?

  # Store Geography Data
  qaqc[:geography] = {}
  qaqc[:geography][:hdd_necb] = get_necb_hdd18(model: model, necb_hdd: true)
  qaqc[:geography][:hdd] = get_necb_hdd18(model: model, necb_hdd: false)
  weather_file_path = model.weatherFile.get.path.get.to_s
  stat_file_path = weather_file_path.gsub('.epw', '.stat')
  stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
  qaqc[:geography][:cdd] = stat_file.cdd18
  qaqc[:geography][:climate_zone] = NECB2011.new.get_climate_zone_name(qaqc[:geography][:hdd])
  qaqc[:geography][:city] = model.getWeatherFile.city
  qaqc[:geography][:state_province_region] = model.getWeatherFile.stateProvinceRegion
  qaqc[:geography][:country] = model.getWeatherFile.country
  qaqc[:geography][:latitude] = model.getWeatherFile.latitude
  qaqc[:geography][:longitude] = model.getWeatherFile.longitude

  # Spacetype Breakdown
  qaqc[:spacetype_area_breakdown] = {}
  model.getSpaceTypes.sort.each do |spaceType|
    next if spaceType.floorArea == 0

    # data for space type breakdown
    display = spaceType.name.get
    floor_area_si = 0
    # loop through spaces so I can skip if not included in floor area
    spaceType.spaces.sort.each do |space|
      next if !space.partofTotalFloorArea

      floor_area_si += space.floorArea * space.multiplier
    end
    qaqc[:spacetype_area_breakdown][spaceType.name.get.gsub(/\s+/, '_').downcase.to_sym] = floor_area_si
  end

  # Economics Section
  qaqc[:economics] = {}
  provinces_names_map = { 'QC' => 'Quebec', 'NL' => 'Newfoundland and Labrador', 'NS' => 'Nova Scotia', 'PE' => 'Prince Edward Island', 'ON' => 'Ontario', 'MB' => 'Manitoba', 'SK' => 'Saskatchewan', 'AB' => 'Alberta', 'BC' => 'British Columbia', 'YT' => 'Yukon', 'NT' => 'Northwest Territories', 'NB' => 'New Brunswick', 'NU' => 'Nunavut' }
  neb_prices_csv_file_name = "#{File.dirname(__FILE__)}/qaqc_resources/neb_end_use_prices.csv"
  puts neb_prices_csv_file_name
  building_type = 'Commercial'
  province = provinces_names_map[qaqc[:geography][:state_province_region]]
  neb_fuel_list = ['Electricity', 'Natural Gas', 'Oil']
  neb_eplus_fuel_map = { 'Electricity' => 'Electricity', 'Natural Gas' => 'Gas', 'Oil' => 'FuelOilNo2' }
  qaqc[:economics][:total_neb_cost] = 0.0
  qaqc[:economics][:total_neb_cost_per_m2] = 0.0
  neb_eplus_fuel_map.each do |neb_fuel, ep_fuel|
    search_info = {
      0 => building_type,
      1 => province,
      2 => neb_fuel
    }
    row = look_up_csv_data(neb_prices_csv_file_name, search_info)
    neb_fuel_cost = row['2020']
    fuel_consumption_gj = 0.0
    if neb_fuel == 'Electricity' || neb_fuel == 'Natural Gas'
      if model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters' AND ReportForString='Entire Facility' AND
      TableName='Annual and Peak Values - #{ep_fuel}' AND RowName='#{ep_fuel}:Facility' AND ColumnName='#{ep_fuel} Annual Value' AND Units='GJ'").is_initialized
        fuel_consumption_gj = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters' AND ReportForString='Entire Facility' AND
      TableName='Annual and Peak Values - #{ep_fuel}' AND RowName='#{ep_fuel}:Facility' AND ColumnName='#{ep_fuel} Annual Value' AND Units='GJ'").get
      end
    else
      if model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters' AND ReportForString='Entire Facility' AND
      TableName='Annual and Peak Values - Other' AND RowName='#{ep_fuel}:Facility' AND ColumnName='Annual Value' AND Units='GJ'").is_initialized
        fuel_consumption_gj = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters' AND ReportForString='Entire Facility' AND
      TableName='Annual and Peak Values - Other' AND RowName='#{ep_fuel}:Facility' AND ColumnName='Annual Value' AND Units='GJ'").get
      end
    end
    qaqc[:economics][:"#{neb_fuel}_neb_cost"] = fuel_consumption_gj * neb_fuel_cost.to_f
    qaqc[:economics][:"#{neb_fuel}_neb_cost_per_m2"] = qaqc[:economics][:"#{neb_fuel}_neb_cost"] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty?
    qaqc[:economics][:total_neb_cost] += qaqc[:economics][:"#{neb_fuel}_neb_cost"]
    qaqc[:economics][:total_neb_cost_per_m2] += qaqc[:economics][:"#{neb_fuel}_neb_cost_per_m2"] || 0.0
  end

  # Fuel cost based local utility rates
  costing_rownames = model.sqlFile.get.execAndReturnVectorOfString("SELECT RowName FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost'")
  #==> ["Electricity", "Natural Gas", "Additional", "Total"]
  costing_rownames = validate_optional(costing_rownames, model, 'N/A')
  if costing_rownames != 'N/A'
    costing_rownames.each do |rowname|
      case rowname
        when 'Electricity'
          qaqc[:economics][:electricity_cost] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost' AND RowName='#{rowname}'").get
          qaqc[:economics][:electricity_cost_per_m2] = qaqc[:economics][:electricity_cost] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty?
        when 'Natural Gas'
          qaqc[:economics][:natural_gas_cost] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost' AND RowName='#{rowname}'").get
          qaqc[:economics][:natural_gas_cost_per_m2] = qaqc[:economics][:natural_gas_cost] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty?

        when 'Additional'
          qaqc[:economics][:additional_cost] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost' AND RowName='#{rowname}'").get
          qaqc[:economics][:additional_cost_per_m2] = qaqc[:economics][:additional_cost] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty?

        when 'Total'
          qaqc[:economics][:total_cost] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost' AND RowName='#{rowname}'").get
          qaqc[:economics][:total_cost_per_m2] = qaqc[:economics][:total_cost] / qaqc[:building][:conditioned_floor_area_m2] unless model.building.get.conditionedFloorArea.empty?
      end
    end
  else
    error_warning << "costing is unavailable because the sql statement is nil RowName FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost'"
  end

  # Store end_use data
  end_uses = [
    'Heating',
    'Cooling',
    'Interior Lighting',
    'Exterior Lighting',
    'Interior Equipment',
    'Exterior Equipment',
    'Fans',
    'Pumps',
    'Heat Rejection',
    'Humidification',
    'Heat Recovery',
    'Water Systems',
    'Refrigeration',
    'Generators',
    'Total End Uses'
  ]

  fuels = [
    ['Electricity', 'GJ'],
    ['Natural Gas', 'GJ'],
    ['Additional Fuel', 'GJ'],
    ['District Cooling', 'GJ'],
    ['District Heating', 'GJ']
  ]

  qaqc[:end_uses] = {}
  qaqc[:end_uses_eui] = {}
  end_uses.each do |use_type|
    qaqc[:end_uses]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_gj"] = 0
    qaqc[:end_uses_eui]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_gj_per_m2"] = 0
    fuels.each do |fuel_type|
      value = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND RowName='#{use_type}' AND ColumnName='#{fuel_type[0]}' AND Units='#{fuel_type[1]}'")
      if value.empty? || (value.get == 0)
      else
        qaqc[:end_uses]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_gj"] += value.get
        unless qaqc[:building][:conditioned_floor_area_m2].nil?
          qaqc[:end_uses_eui]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_gj_per_m2"] += value.get / qaqc[:building][:conditioned_floor_area_m2]
        end
      end
    end
    value = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND RowName='#{use_type}' AND ColumnName='Water' AND Units='m3'")
    if value.empty? || (value.get == 0)
    else
      qaqc[:end_uses]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_water_m3"] = value.get
      unless qaqc[:building][:conditioned_floor_area_m2].nil?
        qaqc[:end_uses_eui]["#{use_type.gsub(/\s+/, '_').downcase.to_sym}_water_m3_per_m2"] = value.get / qaqc[:building][:conditioned_floor_area_m2]
      end
    end
  end

  # Store Peak Data
  qaqc[:meter_peaks] = {}
  qaqc[:meter_peaks][:electric_w] = electric_peak.empty? ? 'NA' : electric_peak.get
  qaqc[:meter_peaks][:natural_gas_w] = natural_gas_peak.empty? ? 'NA' : natural_gas_peak.get

  # Store unmet hour data
  qaqc[:unmet_hours] = {}
  qaqc[:unmet_hours][:cooling] = model.getFacility.hoursCoolingSetpointNotMet.get unless model.getFacility.hoursCoolingSetpointNotMet.empty?
  qaqc[:unmet_hours][:heating] = model.getFacility.hoursHeatingSetpointNotMet.get unless model.getFacility.hoursHeatingSetpointNotMet.empty?

  # puts "\n\n\n#{costing_rownames}\n\n\n"
  # Padmassun's Code -- Tarrif end

  # Padmassun's Code -- Service Hotwater Heating *start*
  qaqc[:service_water_heating] = {}
  qaqc[:service_water_heating][:total_nominal_occupancy] = -1
  # qaqc[:service_water_heating][:total_nominal_occupancy]=model.sqlFile().get().execAndReturnVectorOfDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='OutdoorAirSummary' AND ReportForString='Entire Facility' AND TableName='Average Outdoor Air During Occupied Hours' AND ColumnName='Nominal Number of Occupants'").get.inject(0, :+)
  qaqc[:service_water_heating][:total_nominal_occupancy] = get_total_nominal_capacity(model)

  qaqc[:service_water_heating][:electricity_per_year] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Electricity' AND RowName='Water Systems'")
  qaqc[:service_water_heating][:electricity_per_year] = validate_optional(qaqc[:service_water_heating][:electricity_per_year], model, -1)

  qaqc[:service_water_heating][:electricity_per_day] = qaqc[:service_water_heating][:electricity_per_year] / 365.5
  qaqc[:service_water_heating][:electricity_per_day_per_occupant] = qaqc[:service_water_heating][:electricity_per_day] / qaqc[:service_water_heating][:total_nominal_occupancy]

  qaqc[:service_water_heating][:natural_gas_per_year] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Natural Gas' AND RowName='Water Systems'")
  qaqc[:service_water_heating][:natural_gas_per_year] = validate_optional(qaqc[:service_water_heating][:natural_gas_per_year], model, -1)

  qaqc[:service_water_heating][:additional_fuel_per_year] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Additional Fuel' AND RowName='Water Systems'")
  qaqc[:service_water_heating][:additional_fuel_per_year] = validate_optional(qaqc[:service_water_heating][:additional_fuel_per_year], model, -1)

  qaqc[:service_water_heating][:water_m3_per_year] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Water' AND RowName='Water Systems'")
  qaqc[:service_water_heating][:water_m3_per_year] = validate_optional(qaqc[:service_water_heating][:water_m3_per_year], model, -1)

  qaqc[:service_water_heating][:water_m3_per_day] = qaqc[:service_water_heating][:water_m3_per_year] / 365.5
  qaqc[:service_water_heating][:water_m3_per_day_per_occupant] = qaqc[:service_water_heating][:water_m3_per_day] / qaqc[:service_water_heating][:total_nominal_occupancy]
  # puts qaqc[:service_water_heating][:total_nominal_occupancy]
  # Padmassun's Code -- Service Hotwater Heating *end*

  # Store Envelope data.
  qaqc[:envelope] = {}
  # Get Areas
  qaqc[:envelope][:outdoor_walls_area_m2] = outdoor_walls.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
  qaqc[:envelope][:outdoor_roofs_area_m2] = outdoor_roofs.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
  qaqc[:envelope][:outdoor_floors_area_m2] = outdoor_floors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
  qaqc[:envelope][:ground_walls_area_m2] = ground_walls.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
  qaqc[:envelope][:ground_roofs_area_m2] = ground_roofs.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
  qaqc[:envelope][:ground_floors_area_m2] = ground_floors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
  qaqc[:envelope][:interior_floors_area_m2] = interior_floors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }

  # Subsurface areas
  qaqc[:envelope][:windows_area_m2] = windows.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier }
  qaqc[:envelope][:skylights_area_m2] = skylights.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier }
  qaqc[:envelope][:doors_area_m2] = doors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier }
  qaqc[:envelope][:overhead_doors_area_m2] = overhead_doors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier }

  # Total Building Surface Area.
  qaqc[:envelope][:total_exterior_area_m2] = qaqc[:envelope][:outdoor_walls_area_m2] +
                                             qaqc[:envelope][:outdoor_roofs_area_m2] +
                                             qaqc[:envelope][:outdoor_floors_area_m2] +
                                             qaqc[:envelope][:ground_walls_area_m2] +
                                             qaqc[:envelope][:ground_roofs_area_m2] +
                                             qaqc[:envelope][:ground_floors_area_m2] +
                                             qaqc[:envelope][:windows_area_m2] +
                                             qaqc[:envelope][:skylights_area_m2] +
                                             qaqc[:envelope][:doors_area_m2] +
                                             qaqc[:envelope][:overhead_doors_area_m2]
  # Total Building Ground Surface Area.
  qaqc[:envelope][:total_ground_area_m2] = qaqc[:envelope][:ground_walls_area_m2] +
                                           qaqc[:envelope][:ground_roofs_area_m2] +
                                           qaqc[:envelope][:ground_floors_area_m2]
  # Total Building Outdoor Surface Area.
  qaqc[:envelope][:total_outdoor_area_m2] = qaqc[:envelope][:outdoor_walls_area_m2] +
                                            qaqc[:envelope][:outdoor_roofs_area_m2] +
                                            qaqc[:envelope][:outdoor_floors_area_m2] +
                                            qaqc[:envelope][:windows_area_m2] +
                                            qaqc[:envelope][:skylights_area_m2] +
                                            qaqc[:envelope][:doors_area_m2] +
                                            qaqc[:envelope][:overhead_doors_area_m2]

  # Average Conductances by surface Type
  qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(outdoor_walls).round(4) if !outdoor_walls.empty?
  qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(outdoor_roofs).round(4) if !outdoor_roofs.empty?
  qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(outdoor_floors).round(4) if !outdoor_floors.empty?
  qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(ground_walls).round(4) if !ground_walls.empty?
  qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(ground_roofs).round(4) if !ground_roofs.empty?
  qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(ground_floors).round(4) if !ground_floors.empty?
  qaqc[:envelope][:windows_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(windows).round(4) if !windows.empty?
  qaqc[:envelope][:skylights_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(skylights).round(4) if !skylights.empty?
  qaqc[:envelope][:doors_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(doors).round(4) if !doors.empty?
  qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k] = OpenstudioStandards::Constructions.surfaces_get_conductance(overhead_doors).round(4) if !overhead_doors.empty?

  # #Average Conductances for building whole weight factors
  !outdoor_walls.empty? ? o_wall_cond_weight = qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k] * qaqc[:envelope][:outdoor_walls_area_m2] : o_wall_cond_weight = 0
  !outdoor_roofs.empty? ? o_roof_cond_weight = qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k] * qaqc[:envelope][:outdoor_roofs_area_m2] : o_roof_cond_weight = 0
  !outdoor_floors.empty? ? o_floor_cond_weight = qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k] * qaqc[:envelope][:outdoor_floors_area_m2] : o_floor_cond_weight = 0
  !ground_walls.empty?   ? g_wall_cond_weight = qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k] * qaqc[:envelope][:ground_walls_area_m2] : g_wall_cond_weight = 0
  !ground_roofs.empty?   ? g_roof_cond_weight = qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k] * qaqc[:envelope][:ground_roofs_area_m2] : g_roof_cond_weight = 0
  !ground_floors.empty?  ? g_floor_cond_weight = qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k] * qaqc[:envelope][:ground_floors_area_m2] : g_floor_cond_weight = 0
  !windows.empty?        ? win_cond_weight = qaqc[:envelope][:windows_average_conductance_w_per_m2_k] * qaqc[:envelope][:windows_area_m2] : win_cond_weight = 0
  # doors.size > 0 ? sky_cond_weight = qaqc[:envelope][:skylights_average_conductance_w_per_m2_k] * qaqc[:envelope][:skylights_area_m2] : sky_cond_weight = 0
  if !doors.empty? && !qaqc[:envelope][:skylights_average_conductance_w_per_m2_k].nil? && !qaqc[:envelope][:skylights_area_m2].nil?
    sky_cond_weight = qaqc[:envelope][:skylights_average_conductance_w_per_m2_k] * qaqc[:envelope][:skylights_area_m2]
  else
    sky_cond_weight = 0
  end
  !overhead_doors.empty? ? door_cond_weight = qaqc[:envelope][:doors_average_conductance_w_per_m2_k] * qaqc[:envelope][:doors_area_m2] : door_cond_weight = 0
  !overhead_doors.empty? ? overhead_door_cond_weight = qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k] * qaqc[:envelope][:overhead_doors_area_m2] : overhead_door_cond_weight = 0

  # Building Average Conductance
  qaqc[:envelope][:building_outdoor_average_conductance_w_per_m2_k] = (
  o_floor_cond_weight +
      o_roof_cond_weight +
      o_wall_cond_weight +
      win_cond_weight +
      sky_cond_weight +
      door_cond_weight +
      overhead_door_cond_weight) / qaqc[:envelope][:total_outdoor_area_m2]

  # Building Average Ground Conductance
  qaqc[:envelope][:building_ground_average_conductance_w_per_m2_k] = (
  g_floor_cond_weight +
      g_roof_cond_weight +
      g_wall_cond_weight) / qaqc[:envelope][:total_ground_area_m2]

  # Building Average Conductance
  qaqc[:envelope][:building_average_conductance_w_per_m2_k] = (
  (qaqc[:envelope][:building_ground_average_conductance_w_per_m2_k] * qaqc[:envelope][:total_ground_area_m2]) +
      (qaqc[:envelope][:building_outdoor_average_conductance_w_per_m2_k] * qaqc[:envelope][:total_outdoor_area_m2])
) /
                                                              (qaqc[:envelope][:total_ground_area_m2] + qaqc[:envelope][:total_outdoor_area_m2])

  qaqc[:envelope][:fdwr] = (BTAP::Geometry.get_fwdr(model) * 100.0).round(1)
  qaqc[:envelope][:srr] = (BTAP::Geometry.get_srr(model) * 100.0).round(1)

  qaqc[:envelope][:constructions] = {}
  qaqc[:envelope][:constructions][:exterior_fenestration] = []
  constructions = []
  outdoor_subsurfaces.each { |surface| constructions << surface.construction.get }
  ext_const_base = Hash.new(0)
  constructions.each { |name| ext_const_base[name.to_Construction.get] += 1 }
  # iterate thought each construction and get store data
  ext_const_base.sort.each do |construction, count|
    construction_info = {}
    qaqc[:envelope][:constructions][:exterior_fenestration] << construction_info
    construction_info[:name] = construction.name.get
    construction_info[:net_area_m2] = construction.getNetArea.round(2)
    construction_info[:thermal_conductance_m2_w_per_k] = OpenstudioStandards::Constructions.construction_get_conductance(construction).round(3)
    construction_info[:solar_transmittance] = OpenstudioStandards::Constructions.construction_get_solar_transmittance(construction).round(3)
    construction_info[:visible_tranmittance] = OpenstudioStandards::Constructions.construction_get_visible_transmittance(construction).round(3)
  end

  # Exterior
  qaqc[:envelope][:constructions][:exterior_opaque] = []
  constructions = []
  outdoor_surfaces.each { |surface| constructions << surface.construction.get }
  ext_const_base = Hash.new(0)
  constructions.each { |name| ext_const_base[name.to_Construction.get] += 1 }
  # iterate thought each construction and get store data
  ext_const_base.sort.each do |construction, count|
    construction_info = {}
    qaqc[:envelope][:constructions][:exterior_opaque] << construction_info
    construction_info[:name] = construction.name.get
    construction_info[:net_area_m2] = construction.getNetArea.round(2)
    construction_info[:thermal_conductance_m2_w_per_k] = construction.thermalConductance.get.round(3) if construction.thermalConductance.is_initialized
    construction_info[:net_area_m2] = construction.to_Construction.get.getNetArea.round(2)
    construction_info[:solar_absorptance] = construction.to_Construction.get.layers[0].exteriorVisibleAbsorptance.get
  end

  # Ground
  qaqc[:envelope][:constructions][:ground] = []
  constructions = []
  ground_surfaces.each { |surface| constructions << surface.construction.get }
  ext_const_base = Hash.new(0)
  constructions.each { |name| ext_const_base[name.to_Construction.get] += 1 }
  # iterate thought each construction and get store data
  ext_const_base.sort.each do |construction, count|
    construction_info = {}
    qaqc[:envelope][:constructions][:ground] << construction_info
    construction_info[:name] = construction.name.get
    construction_info[:net_area_m2] = construction.getNetArea.round(2)
    construction_info[:thermal_conductance_m2_w_per_k] = construction.thermalConductance.get.round(3) if construction.thermalConductance.is_initialized
    construction_info[:net_area_m2] = construction.to_Construction.get.getNetArea.round(2)
    construction_info[:solar_absorptance] = construction.to_Construction.get.layers[0].exteriorVisibleAbsorptance.get
  end

  qaqc[:envelope][:average_thermal_conductance_m2_w_per_k] =
    # Store Space data.
    qaqc[:spaces] = []
  model.getSpaces.sort.each do |space|
    spaceinfo = {}
    qaqc[:spaces] << spaceinfo
    spaceinfo[:name] = space.name.get # name should be defined test
    spaceinfo[:multiplier] = space.multiplier
    spaceinfo[:volume] = space.volume # should be greater than zero
    spaceinfo[:exterior_wall_area] = space.exteriorWallArea # just for information.
    spaceinfo[:space_type_name] = space.spaceType.get.name.get unless space.spaceType.empty? # should have a space types name defined.
    spaceinfo[:thermal_zone] = space.thermalZone.get.name.get unless space.thermalZone.empty? # should be assigned a thermalzone name.
    # puts space.name.get
    # puts space.thermalZone.empty?
    spaceinfo[:breathing_zone_outdoor_airflow_vbz] = -1
    breathing_zone_outdoor_airflow_vbz = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='Standard62.1Summary' AND ReportForString='Entire Facility' AND TableName='Zone Ventilation Parameters' AND ColumnName='Breathing Zone Outdoor Airflow - Vbz' AND Units='m3/s' AND RowName='#{spaceinfo[:thermal_zone].to_s.upcase}' ")
    spaceinfo[:breathing_zone_outdoor_airflow_vbz] = breathing_zone_outdoor_airflow_vbz.get unless breathing_zone_outdoor_airflow_vbz.empty?
    spaceinfo[:infiltration_method] = 'N/A'
    spaceinfo[:infiltration_flow_per_m2] = -1.0
    if space.spaceInfiltrationDesignFlowRates[0].nil?
      error_warning << "space.spaceInfiltrationDesignFlowRates[0] is empty for #{spaceinfo[:name]}"
      error_warning << "space.spaceInfiltrationDesignFlowRates[0].designFlowRateCalculationMethod is empty for #{spaceinfo[:name]}"
      error_warning << "space.spaceInfiltrationDesignFlowRates[0].flowperExteriorSurfaceArea is empty for #{spaceinfo[:name]}"
    else
      spaceinfo[:infiltration_method] = space.spaceInfiltrationDesignFlowRates[0].designFlowRateCalculationMethod
      spaceinfo[:infiltration_flow_per_m2] = 'N/A'
      spaceinfo[:infiltration_flow_per_m2] = space.spaceInfiltrationDesignFlowRates[0].flowperExteriorSurfaceArea.get.round(5) unless space.spaceInfiltrationDesignFlowRates[0].flowperExteriorSurfaceArea.empty?
    end

    # the following should have values unless the spacetype is "undefined" other they should be set to the correct NECB values.
    if space.spaceType.empty?
      error_warning << "space.spaceType is empty for #{space.name.get}"
    else
      spaceinfo[:occupancy_schedule] = nil
      if space.spaceType.get.defaultScheduleSet.empty?
        error_warning << "space.spaceType.get.defaultScheduleSet is empty for #{space.name.get}"
      else
        if space.spaceType.get.defaultScheduleSet.get.numberofPeopleSchedule.empty?
          error_warning << "space.spaceType.get.defaultScheduleSet.get.numberofPeopleSchedule is empty for #{space.name.get}"
        else
          spaceinfo[:occupancy_schedule] = space.spaceType.get.defaultScheduleSet.get.numberofPeopleSchedule.get.name.get # should not empty.
        end
      end

      spaceinfo[:occ_per_m2] = space.spaceType.get.people[0].peopleDefinition.peopleperSpaceFloorArea.get.round(3) unless space.spaceType.get.people[0].nil? || space.spaceType.get.people[0].peopleDefinition.peopleperSpaceFloorArea.empty?
      if space.spaceType.get.lights[0].nil?
        error_warning << "space.spaceType.get.lights[0] is nil for Space:[#{space.name.get}] Space Type:[#{spaceinfo[:space_type_name]}]"
      else
        spaceinfo[:lighting_w_per_m2] = space.spaceType.get.lights[0].lightsDefinition.wattsperSpaceFloorArea # .get.round(3) unless space.spaceType.get.lights[0].nil?
        spaceinfo[:lighting_w_per_m2] = validate_optional(spaceinfo[:lighting_w_per_m2], model, -1.0)
        unless spaceinfo[:lighting_w_per_m2].nil?
          spaceinfo[:lighting_w_per_m2] = spaceinfo[:lighting_w_per_m2].round(3)
        end
      end
      # spaceinfo[:electric_w_per_m2] = space.spaceType.get.electricEquipment[0].electricEquipmentDefinition.wattsperSpaceFloorArea.get.round(3) unless space.spaceType.get.electricEquipment[0].nil?

      unless space.spaceType.get.electricEquipment[0].nil?
        unless space.spaceType.get.electricEquipment[0].electricEquipmentDefinition.wattsperSpaceFloorArea.empty?
          spaceinfo[:electric_w_per_m2] = space.spaceType.get.electricEquipment[0].electricEquipmentDefinition.wattsperSpaceFloorArea.get.round(3)
        end
      end
      spaceinfo[:shw_m3_per_s] = space.waterUseEquipment[0].waterUseEquipmentDefinition.peakFlowRate.round(3) unless space.waterUseEquipment[0].nil?
      spaceinfo[:waterUseEquipment] = []
      if !space.waterUseEquipment.empty?
        waterUseEquipment_info = {}
        spaceinfo[:waterUseEquipment] << waterUseEquipment_info
        waterUseEquipment_info[:peak_flow_rate] = space.waterUseEquipment[0].waterUseEquipmentDefinition.peakFlowRate
        waterUseEquipment_info[:peak_flow_rate_per_area] = waterUseEquipment_info[:peak_flow_rate] / space.floorArea
        area_per_occ = space.spaceType.get.people[0].nil? ? 0.0 : validate_optional(space.spaceType.get.people[0].spaceFloorAreaPerPerson, model, -1.0)
        #                             Watt per person =             m3/s/m3                * 1000W/kW * (specific heat * dT) * m2/person
        waterUseEquipment_info[:shw_watts_per_person] = waterUseEquipment_info[:peak_flow_rate_per_area] * 1000 * (4.19 * 44.4) * 1000 * area_per_occ
        # puts waterUseEquipment_info[:shw_watts_per_ponce the erson]
        # puts "\n\n\n"
      end
    end
  end

  # Store Thermal zone data
  qaqc[:thermal_zones] = []
  model.getThermalZones.sort.each do |zone|
    zoneinfo = {}
    qaqc[:thermal_zones] << zoneinfo
    zoneinfo[:name] = zone.name.get
    zoneinfo[:floor_area] = zone.floorArea
    zoneinfo[:multiplier] = zone.multiplier
    zoneinfo[:is_conditioned] = 'N/A'
    if zone.isConditioned.empty?
      error_warning << "zone.isConditioned is empty for #{zone.name.get}"
    else
      zoneinfo[:is_conditioned] = zone.isConditioned.get
    end

    zoneinfo[:is_ideal_air_loads] = zone.useIdealAirLoads
    zoneinfo[:heating_sizing_factor] = -1.0
    if zone.sizingZone.zoneHeatingSizingFactor.empty?
      error_warning << "zone.sizingZone.zoneHeatingSizingFactor is empty for #{zone.name.get}"
    else
      zoneinfo[:heating_sizing_factor] = zone.sizingZone.zoneHeatingSizingFactor.get
    end

    zoneinfo[:cooling_sizing_factor] = -1.0 # zone.sizingZone.zoneCoolingSizingFactor.get
    if zone.sizingZone.zoneCoolingSizingFactor.empty?
      error_warning << "zone.sizingZone.zoneCoolingSizingFactor is empty for #{zone.name.get}"
    else
      zoneinfo[:cooling_sizing_factor] = zone.sizingZone.zoneCoolingSizingFactor.get
    end

    zoneinfo[:zone_heating_design_supply_air_temperature] = zone.sizingZone.zoneHeatingDesignSupplyAirTemperature
    zoneinfo[:zone_cooling_design_supply_air_temperature] = zone.sizingZone.zoneCoolingDesignSupplyAirTemperature
    zoneinfo[:spaces] = []
    zone.spaces.sort.each do |space|
      spaceinfo = {}
      zoneinfo[:spaces] << spaceinfo
      spaceinfo[:name] = space.name.get
      spaceinfo[:type] = space.spaceType.get.name.get unless space.spaceType.empty?
    end
    zoneinfo[:equipment] = []
    zone.equipmentInHeatingOrder.each do |equipment|
      item = {}
      zoneinfo[:equipment] << item
      item[:name] = equipment.name.get
      if equipment.to_ZoneHVACComponent.is_initialized
        item[:type] = 'ZoneHVACComponent'
      elsif equipment.to_StraightComponent.is_initialized
        item[:type] = 'StraightComponent'
      end
    end
    # zone
  end
  # Store Air Loop Information
  qaqc[:air_loops] = []
  model.getAirLoopHVACs.sort.each do |air_loop|
    air_loop_info = {}
    air_loop_info[:name] = air_loop.name.get
    air_loop_info[:thermal_zones] = []
    air_loop_info[:total_floor_area_served] = 0.0
    air_loop_info[:total_breathing_zone_outdoor_airflow_vbz] = 0.0
    air_loop.thermalZones.sort.each do |zone|
      air_loop_info[:thermal_zones] << zone.name.get
      vbz = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='Standard62.1Summary' AND ReportForString='Entire Facility' AND TableName='Zone Ventilation Parameters' AND ColumnName='Breathing Zone Outdoor Airflow - Vbz' AND Units='m3/s' AND RowName='#{zone.name.get.to_s.upcase}' ")
      vbz = validate_optional(vbz, model, 0)
      air_loop_info[:total_breathing_zone_outdoor_airflow_vbz] += vbz
      air_loop_info[:total_floor_area_served] += zone.floorArea * zone.multiplier.to_f
    end
    air_loop_info[:area_outdoor_air_rate_m3_per_s_m2] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='Standard62.1Summary' AND ReportForString='Entire Facility' AND TableName='System Ventilation Parameters' AND ColumnName='Area Outdoor Air Rate - Ra' AND Units='m3/s-m2' AND RowName='#{air_loop_info[:name].to_s.upcase}' ")
    air_loop_info[:area_outdoor_air_rate_m3_per_s_m2] = validate_optional(air_loop_info[:area_outdoor_air_rate_m3_per_s_m2], model, -1.0)

    air_loop_info[:outdoor_air_L_per_s] = -1.0
    unless air_loop_info[:area_outdoor_air_rate_m3_per_s_m2] == -1.0
      air_loop_info[:outdoor_air_L_per_s] = air_loop_info[:area_outdoor_air_rate_m3_per_s_m2] * air_loop_info[:total_floor_area_served] * 1000
    end
    # Fan

    unless air_loop.supplyFan.empty?
      air_loop_info[:supply_fan] = {}
      if air_loop.supplyFan.get.to_FanConstantVolume.is_initialized
        air_loop_info[:supply_fan][:type] = 'CV'
        fan = air_loop.supplyFan.get.to_FanConstantVolume.get
      elsif air_loop.supplyFan.get.to_FanVariableVolume.is_initialized
        air_loop_info[:supply_fan][:type] = 'VV'
        fan = air_loop.supplyFan.get.to_FanVariableVolume.get
      end
      air_loop_info[:supply_fan][:name] = fan.name.get
      # puts "\n\n\n\n#{fan.name.get}\n\n\n\n"
      air_loop_info[:supply_fan][:fan_efficiency] = fan.fanEfficiency
      air_loop_info[:supply_fan][:motor_efficiency] = fan.motorEfficiency
      air_loop_info[:supply_fan][:pressure_rise] = fan.pressureRise
      air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] = -1.0

      max_air_flow_info = model.sqlFile.get.execAndReturnVectorOfString("SELECT RowName FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s' ")
      max_air_flow_info = validate_optional(max_air_flow_info, model, 'N/A')
      if max_air_flow_info == 'N/A'
        error_warning << "max_air_flow_info is nil because the following sql statement returned nil: RowName FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s' "
      else
        if max_air_flow_info.include? air_loop_info[:supply_fan][:name].to_s.upcase.to_s
          air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s' AND RowName='#{air_loop_info[:supply_fan][:name].upcase}' ").get
          air_loop_info[:supply_fan][:rated_electric_power_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Rated Electric Power' AND Units='W' AND RowName='#{air_loop_info[:supply_fan][:name].upcase}' ")
          # Version 3.2.0 has renamed  rated electric_power  to rated electricity rate
          if air_loop_info[:supply_fan][:rated_electric_power_w].empty?
            air_loop_info[:supply_fan][:rated_electric_power_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Rated Electricity Rate' AND Units='W' AND RowName='#{air_loop_info[:supply_fan][:name].upcase}' ")
          end
          air_loop_info[:supply_fan][:rated_electric_power_w] = air_loop_info[:supply_fan][:rated_electric_power_w].get
          else
          error_warning << "#{air_loop_info[:supply_fan][:name]} does not exist in sql file WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s'"
        end
      end
    end

    # economizer
    air_loop_info[:economizer] = {}
    air_loop_info[:economizer][:name] = air_loop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.name.get unless air_loop.airLoopHVACOutdoorAirSystem.empty? or air_loop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.name.empty?
    air_loop_info[:economizer][:control_type] = air_loop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.getEconomizerControlType unless air_loop.airLoopHVACOutdoorAirSystem.empty?
    # DX cooling coils
    air_loop_info[:cooling_coils] = {}
    air_loop_info[:cooling_coils][:dx_single_speed] = []
    air_loop_info[:cooling_coils][:dx_two_speed] = []
    air_loop_info[:cooling_coils][:coil_cooling_water] = []

    # Heating Coil
    air_loop_info[:heating_coils] = {}
    air_loop_info[:heating_coils][:coil_heating_gas] = []
    air_loop_info[:heating_coils][:coil_heating_electric] = []
    air_loop_info[:heating_coils][:coil_heating_water] = []

    # Heat Excahnger
    air_loop_info[:heat_exchanger] = {}

    air_loop.supplyComponents.each do |supply_comp|
      if supply_comp.to_CoilHeatingGas.is_initialized
        coil = {}
        air_loop_info[:heating_coils][:coil_heating_gas] << coil
        gas = supply_comp.to_CoilHeatingGas.get
        coil[:name] = gas.name.get
        coil[:type] = 'Gas'
        coil[:efficency] = gas.gasBurnerEfficiency
        # coil[:nominal_capacity]= gas.nominalCapacity()
        coil[:nominal_capacity] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Heating Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].to_s.upcase}'")
        coil[:nominal_capacity] = validate_optional(coil[:nominal_capacity], model, -1.0)
      end
      if supply_comp.to_CoilHeatingElectric.is_initialized
        coil = {}
        air_loop_info[:heating_coils][:coil_heating_electric] << coil
        electric = supply_comp.to_CoilHeatingElectric.get
        coil[:name] = electric.name.get
        coil[:type] = 'Electric'
        coil[:nominal_capacity] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Heating Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].to_s.upcase}'")
        coil[:nominal_capacity] = validate_optional(coil[:nominal_capacity], model, -1.0)
      end
      if supply_comp.to_CoilHeatingWater.is_initialized
        coil = {}
        air_loop_info[:heating_coils][:coil_heating_water] << coil
        water = supply_comp.to_CoilHeatingWater.get
        coil[:name] = water.name.get
        coil[:type] = 'Water'
        coil[:nominal_capacity] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Heating Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].to_s.upcase}'")
        coil[:nominal_capacity] = validate_optional(coil[:nominal_capacity], model, -1.0)
      end
      if supply_comp.to_HeatExchangerAirToAirSensibleAndLatent.is_initialized
        heatExchanger = supply_comp.to_HeatExchangerAirToAirSensibleAndLatent.get
        air_loop_info[:heat_exchanger][:name] = heatExchanger.name.get
      end
    end
    # I dont think i need to get the type of heating coil from the sql file, because the coils are differentiated by class, and I have hard coded the information
    # model.sqlFile().get().execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName= 'Heating Coils' AND ColumnName='Type' ").get #padmussen to complete #AND RowName='#{air_loop_info[:heating_coils][:name].upcase}'

    # Collect all the fans into the the array.
    air_loop.supplyComponents.each do |supply_comp|
      if supply_comp.to_CoilCoolingDXSingleSpeed.is_initialized
        coil = {}
        air_loop_info[:cooling_coils][:dx_single_speed] << coil
        single_speed = supply_comp.to_CoilCoolingDXSingleSpeed.get
        coil[:name] = single_speed.name.get
        coil[:cop] = single_speed.ratedCOP.to_f
        coil[:nominal_total_capacity_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Cooling Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].upcase}' ")
        coil[:nominal_total_capacity_w] = validate_optional(coil[:nominal_total_capacity_w], model, -1.0)
      end
      if supply_comp.to_CoilCoolingDXTwoSpeed.is_initialized
        coil = {}
        air_loop_info[:cooling_coils][:dx_two_speed] << coil
        two_speed = supply_comp.to_CoilCoolingDXTwoSpeed.get
        coil[:name] = two_speed.name.get
        coil[:cop_low] = two_speed.ratedLowSpeedCOP.to_f
        coil[:cop_high] = two_speed.ratedHighSpeedCOP.to_f
        coil[:nominal_total_capacity_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Cooling Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].upcase}' ")
        coil[:nominal_total_capacity_w] = validate_optional(coil[:nominal_total_capacity_w], model, -1.0)
      end
      if supply_comp.to_CoilCoolingWater.is_initialized
        coil = {}
        air_loop_info[:cooling_coils][:coil_cooling_water] << coil
        coil_cooling_water = supply_comp.to_CoilCoolingWater.get
        coil[:name] = coil_cooling_water.name.get
        coil[:nominal_total_capacity_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Cooling Coils' AND ColumnName='Nominal Total Capacity' AND RowName='#{coil[:name].upcase}' ")
        coil[:nominal_total_capacity_w] = validate_optional(coil[:nominal_total_capacity_w], model, -1.0)
        coil[:nominal_sensible_heat_ratio] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Cooling Coils' AND ColumnName='Nominal Sensible Heat Ratio' AND RowName='#{coil[:name].upcase}' ")
        coil[:nominal_sensible_heat_ratio] = validate_optional(coil[:nominal_sensible_heat_ratio], model, -1.0)
      end
    end
    qaqc[:air_loops] << air_loop_info
  end

  qaqc[:plant_loops] = []
  model.getPlantLoops.sort.each do |plant_loop|
    plant_loop_info = {}
    qaqc[:plant_loops] << plant_loop_info
    plant_loop_info[:name] = plant_loop.name.get

    sizing = plant_loop.sizingPlant
    plant_loop_info[:design_loop_exit_temperature] = sizing.designLoopExitTemperature
    plant_loop_info[:loop_design_temperature_difference] = sizing.loopDesignTemperatureDifference

    # Create Container for plant equipment arrays.
    plant_loop_info[:pumps] = []
    plant_loop_info[:boilers] = []
    plant_loop_info[:chiller_electric_eir] = []
    plant_loop_info[:cooling_tower_single_speed] = []
    plant_loop_info[:water_heater_mixed] = []
    plant_loop.supplyComponents.each do |supply_comp|
      # Collect Constant Speed
      if supply_comp.to_PumpConstantSpeed.is_initialized
        pump = supply_comp.to_PumpConstantSpeed.get
        pump_info = {}
        plant_loop_info[:pumps] << pump_info
        pump_info[:name] = pump.name.get
        pump_info[:type] = 'Pump:ConstantSpeed'
        pump_info[:head_pa] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Head' AND RowName='#{pump_info[:name].upcase}' ")
        pump_info[:head_pa] = validate_optional(pump_info[:head_pa], model, -1.0)
        pump_info[:water_flow_m3_per_s] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Water Flow' AND RowName='#{pump_info[:name].upcase}' ")
        pump_info[:water_flow_m3_per_s] = validate_optional(pump_info[:water_flow_m3_per_s], model, -1.0)
        pump_info[:electric_power_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Electric Power' AND RowName='#{pump_info[:name].upcase}' ")
        pump_info[:electric_power_w] = validate_optional(pump_info[:electric_power_w], model, -1.0)
        pump_info[:motor_efficency] = pump.motorEfficiency
      end

      # Collect Variable Speed
      if supply_comp.to_PumpVariableSpeed.is_initialized
        pump = supply_comp.to_PumpVariableSpeed.get
        pump_info = {}
        plant_loop_info[:pumps] << pump_info
        pump_info[:name] = pump.name.get
        pump_info[:type] = 'Pump:VariableSpeed'
        pump_info[:head_pa] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Head' AND RowName='#{pump_info[:name].upcase}' ")
        pump_info[:head_pa] = validate_optional(pump_info[:head_pa], model, -1.0)
        pump_info[:water_flow_m3_per_s] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Water Flow' AND RowName='#{pump_info[:name].upcase}' ")
        pump_info[:water_flow_m3_per_s] = validate_optional(pump_info[:water_flow_m3_per_s], model, -1.0)
        pump_info[:electric_power_w] = model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Pumps' AND ColumnName='Electric Power' AND RowName='#{pump_info[:name].upcase}' ")
        pump_info[:electric_power_w] = validate_optional(pump_info[:electric_power_w], model, -1.0)
        pump_info[:motor_efficency] = pump.motorEfficiency
      end

      # Collect HotWaterBoilers
      if supply_comp.to_BoilerHotWater.is_initialized
        boiler = supply_comp.to_BoilerHotWater.get
        boiler_info = {}
        plant_loop_info[:boilers] << boiler_info
        boiler_info[:name] = boiler.name.get
        boiler_info[:type] = 'Boiler:HotWater'
        boiler_info[:fueltype] = boiler.fuelType
        boiler_info[:nominal_capacity] = boiler.nominalCapacity
        boiler_info[:nominal_capacity] = validate_optional(boiler_info[:nominal_capacity], model, -1.0)
      end

      # Collect ChillerElectricEIR
      if supply_comp.to_ChillerElectricEIR.is_initialized
        chiller = supply_comp.to_ChillerElectricEIR.get
        chiller_info = {}
        plant_loop_info[:chiller_electric_eir] << chiller_info
        chiller_info[:name] = chiller.name.get
        chiller_info[:type] = 'Chiller:Electric:EIR'
        chiller_info[:reference_capacity] = validate_optional(chiller.referenceCapacity, model, -1.0)
        chiller_info[:reference_leaving_chilled_water_temperature] = chiller.referenceLeavingChilledWaterTemperature
      end

      # Collect CoolingTowerSingleSpeed
      if supply_comp.to_CoolingTowerSingleSpeed.is_initialized
        coolingTower = supply_comp.to_CoolingTowerSingleSpeed.get
        coolingTower_info = {}
        plant_loop_info[:cooling_tower_single_speed] << coolingTower_info
        coolingTower_info[:name] = coolingTower.name.get
        coolingTower_info[:type] = 'CoolingTower:SingleSpeed'
        coolingTower_info[:fan_power_at_design_air_flow_rate] = validate_optional(coolingTower.fanPoweratDesignAirFlowRate, model, -1.0)

      end

      # Collect WaterHeaterMixed
      if supply_comp.to_WaterHeaterMixed.is_initialized
        waterHeaterMixed = supply_comp.to_WaterHeaterMixed.get
        waterHeaterMixed_info = {}
        plant_loop_info[:water_heater_mixed] << waterHeaterMixed_info
        waterHeaterMixed_info[:name] = waterHeaterMixed.name.get
        waterHeaterMixed_info[:type] = 'WaterHeater:Mixed'
        waterHeaterMixed_info[:heater_thermal_efficiency] = waterHeaterMixed.heaterThermalEfficiency.get unless waterHeaterMixed.heaterThermalEfficiency.empty?
        waterHeaterMixed_info[:heater_fuel_type] = waterHeaterMixed.heaterFuelType
      end
    end

    qaqc[:eplusout_err] = {}
    warnings = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='0' ")
    warnings = validate_optional(warnings, model, 'N/A')
    unless warnings == 'N/A'
      qaqc[:eplusout_err][:warnings] = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='0' ").get
      qaqc[:eplusout_err][:fatal] = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='2' ").get
      qaqc[:eplusout_err][:severe] = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='1' ").get
    end

    qaqc[:ruby_warnings] = error_warning
  end

  qaqc[:code_metrics] = {}
  qaqc[:code_metrics]['heating_gj']  = qaqc[:end_uses]['heating_gj']
  qaqc[:code_metrics]['cooling_gj']  = qaqc[:end_uses]['cooling_gj']
  qaqc[:code_metrics][:ep_conditioned_floor_area_m2] = qaqc[:building][:conditioned_floor_area_m2]
  qaqc[:code_metrics][:os_conditioned_floor_area_m2] = qaqc[:envelope][:interior_floors_area_m2] +
                                                       qaqc[:envelope][:outdoor_floors_area_m2] +
                                                       qaqc[:envelope][:ground_floors_area_m2]
  # TEDI
  unless qaqc[:building][:conditioned_floor_area_m2].nil?
    qaqc[:code_metrics][:building_tedi_gj_per_m2] = (qaqc[:end_uses]['heating_gj'] + qaqc[:end_uses]['cooling_gj']
                                                    ) / qaqc[:building][:conditioned_floor_area_m2]
    # Mech TEDI?
    qaqc[:code_metrics][:building_medi_gj_per_m2] = (qaqc[:end_uses]['fans_gj'] +
        qaqc[:end_uses]['pumps_gj'] +
        qaqc[:end_uses]['heat_rejection_gj'] +
        qaqc[:end_uses]['humidification_gj'] +
        qaqc[:end_uses]['heat_recovery_gj']
    ) / qaqc[:building][:conditioned_floor_area_m2]
  end


  return qaqc
end
create_ems_to_turn_on_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_for_night_cycle(multi_speed_heat_pump) click to toggle source

Create EMS to turn on “AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed” in response to a call from the night cycle availability manager of the air loop. It was found that this object doesn’t respond properly to this call from the night cycle

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1088
  def create_ems_to_turn_on_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_for_night_cycle(multi_speed_heat_pump)
    model = multi_speed_heat_pump.model
    avail_manager_name = nil
    if multi_speed_heat_pump.airLoopHVAC.is_initialized
      if !multi_speed_heat_pump.airLoopHVAC.get.availabilityManagers.empty?
        avail_manager_name = multi_speed_heat_pump.airLoopHVAC.get.availabilityManagers[0].name.to_s
      end
    end
    return unless avail_manager_name

    avail_manager_out_var_name = 'Availability Manager Night Cycle Control Status'
    avail_manager_out_var = OpenStudio::Model::OutputVariable.new(avail_manager_out_var_name, model)
    avail_manager_out_var.setKeyValue(avail_manager_name)
    avail_manager_out_var.setReportingFrequency('Timestep')
    night_cycle_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, avail_manager_out_var)
    heat_pump_avail_sch = nil
    if multi_speed_heat_pump.availabilitySchedule.is_initialized
      heat_pump_avail_sch = multi_speed_heat_pump.availabilitySchedule.get
    elsif multi_speed_heat_pump.airLoopHVAC.get.availabilitySchedule.is_initialized
      heat_pump_avail_sch = multi_speed_heat_pump.airLoopHVAC.get.availabilitySchedule.get
    else
      heat_pump_avail_sch = OpenStudio::Model::ScheduleConstant.new(model)
      heat_pump_avail_sch.setValue(1.0)
    end
    heat_pump_avail_sch_var = OpenStudio::Model::OutputVariable.new('Schedule Value', model)
    heat_pump_avail_sch_var.setKeyValue(heat_pump_avail_sch.name.to_s)
    heat_pump_avail_sch_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, heat_pump_avail_sch_var)
    updated_heat_pump_avail_sch = OpenStudio::Model::ScheduleConstant.new(model)
    multi_speed_heat_pump.setAvailabilitySchedule(updated_heat_pump_avail_sch)
    # This method will seem like an error in number of args..but this is due to swig voodoo.
    heat_pump_avail_sch_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(updated_heat_pump_avail_sch, 'Schedule:Constant', 'Schedule Value')
    heat_pump_avail_sch_prog = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
    heat_pump_avail_sch_prog.setName("#{multi_speed_heat_pump.name.to_s.gsub(/[ +-.]/, '_')} Availability Schedule Program by Line")
    heat_pump_avail_sch_prog_body = <<-EMS
        IF #{heat_pump_avail_sch_sensor.handle} > 0.0
          SET #{heat_pump_avail_sch_actuator.handle} = #{heat_pump_avail_sch_sensor.handle}
        ELSEIF #{night_cycle_sensor.handle} == 2.0
          SET #{heat_pump_avail_sch_actuator.handle} = 1.0
        ELSE
          SET #{heat_pump_avail_sch_actuator.handle} = 0.0
        ENDIF
    EMS
    heat_pump_avail_sch_prog.setBody(heat_pump_avail_sch_prog_body)
    pcm = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
    pcm.setName("#{heat_pump_avail_sch_prog.name.to_s.gsub(/[ +-.]/, '_')} Calling Manager")
    pcm.setCallingPoint('InsideHVACSystemIterationLoop')
    pcm.addProgram(heat_pump_avail_sch_prog)
  end
create_heating_cooling_on_off_availability_schedule(model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2134
def create_heating_cooling_on_off_availability_schedule(model)
  # @todo Create a feature to derive start and end heating and cooling seasons from weather file.
  avail_data = [{ start_month: 1, start_day: 1, end_month: 6, end_day: 30, htg_value: 1, clg_value: 0 },
                { start_month: 7, start_day: 1, end_month: 10, end_day: 31, htg_value: 0, clg_value: 1 },
                { start_month: 11, start_day: 1, end_month: 12, end_day: 31, htg_value: 1, clg_value: 0 }]

  # Heating coil availability schedule for tpfc
  htg_availability_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  htg_availability_sch.setName('tpfc_htg_availability')
  # Cooling coil availability schedule for tpfc
  clg_availability_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  clg_availability_sch.setName('tpfc_clg_availability')
  avail_data.each do |data|
    htg_availability_sch_rule = OpenStudio::Model::ScheduleRule.new(htg_availability_sch)
    htg_availability_sch_rule.setName('tpfc_htg_availability_sch_rule')
    htg_availability_sch_rule.setStartDate(model.getYearDescription.makeDate(data[:start_month], data[:start_day]))
    htg_availability_sch_rule.setEndDate(model.getYearDescription.makeDate(data[:end_month], data[:end_day]))
    htg_availability_sch_rule.setApplySunday(true)
    htg_availability_sch_rule.setApplyMonday(true)
    htg_availability_sch_rule.setApplyTuesday(true)
    htg_availability_sch_rule.setApplyWednesday(true)
    htg_availability_sch_rule.setApplyThursday(true)
    htg_availability_sch_rule.setApplyFriday(true)
    htg_availability_sch_rule.setApplySaturday(true)
    day_schedule = htg_availability_sch_rule.daySchedule
    day_schedule.setName('tpfc_htg_availability_sch_rule_day')
    day_schedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), data[:htg_value])

    clg_availability_sch_rule = OpenStudio::Model::ScheduleRule.new(clg_availability_sch)
    clg_availability_sch_rule.setName('tpfc_clg_availability_sch_rule')
    clg_availability_sch_rule.setStartDate(model.getYearDescription.makeDate(data[:start_month], data[:start_day]))
    clg_availability_sch_rule.setEndDate(model.getYearDescription.makeDate(data[:end_month], data[:end_day]))
    clg_availability_sch_rule.setApplySunday(true)
    clg_availability_sch_rule.setApplyMonday(true)
    clg_availability_sch_rule.setApplyTuesday(true)
    clg_availability_sch_rule.setApplyWednesday(true)
    clg_availability_sch_rule.setApplyThursday(true)
    clg_availability_sch_rule.setApplyFriday(true)
    clg_availability_sch_rule.setApplySaturday(true)
    day_schedule = clg_availability_sch_rule.daySchedule
    day_schedule.setName('tpfc_clg_availability_sch_rule_day')
    day_schedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), data[:clg_value])
  end
  return clg_availability_sch, htg_availability_sch
end
create_hw_loop_if_required(baseboard_type, boiler_fueltype, mau_heating_coil_type, model) click to toggle source

Method will create a hot water loop if systems default fuel and medium sources require it.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 826
def create_hw_loop_if_required(baseboard_type, boiler_fueltype, mau_heating_coil_type, model)
  # get systems that will be used in the model based on the space types to determine if a hw_loop is required.
  systems_used = []
  model.getSpaces.sort.each do |space|
    systems_used << get_necb_spacetype_system_selection(space)
    systems_used.uniq!
  end

  # See if we need to create a hot water loop based on fueltype and systems used.
  hw_loop_needed = false
  systems_used.each do |system|
    case system.to_s
    when '2', '5', '7'
      hw_loop_needed = true
    when '1', '6'
      if (mau_heating_coil_type == 'Hot Water') || (baseboard_type == 'Hot Water')
        hw_loop_needed = true
      end
    when '3', '4'
      if (mau_heating_coil_type == 'Hot Water') || (baseboard_type == 'Hot Water')
        hw_loop_needed = true if baseboard_type == 'Hot Water'
      end
    end
    if hw_loop_needed
      # just need one true condition to need a boiler.
      break
    end
    # each
  end
  # create hw_loop as needed.. Assuming one loop per model.
  if hw_loop_needed
    @hw_loop = OpenStudio::Model::PlantLoop.new(model)
    always_on = model.alwaysOnDiscreteSchedule
    setup_hw_loop_with_components(model, @hw_loop, boiler_fueltype, always_on)
  end
  return @hw_loop
end
create_necb_system(baseboard_type:, boiler_fueltype:, chiller_type:, fan_type:, heating_coil_type_sys3:, heating_coil_type_sys4:, heating_coil_type_sys6:, hw_loop:, mau_cooling_type:, mau_heating_coil_type:, mau_type:, model:, zones:, necb_reference_hp:false, necb_reference_hp_supp_fuel:'DefaultFuel') click to toggle source

Default method to create a necb system and assign array of zones to be supported by it. It will try to bring zones with similar loads on the same airloops and set control zones where possible for single zone systems and will create monolithic system 6 multizones where possible.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 867
def create_necb_system(baseboard_type:,
                       boiler_fueltype:,
                       chiller_type:,
                       fan_type:,
                       heating_coil_type_sys3:,
                       heating_coil_type_sys4:,
                       heating_coil_type_sys6:,
                       hw_loop:,
                       mau_cooling_type:,
                       mau_heating_coil_type:,
                       mau_type:,
                       model:,
                       zones:,
                       necb_reference_hp:false,
                       necb_reference_hp_supp_fuel:'DefaultFuel')
  # The goal is to minimize the number of system when possible.
  system_zones_hash = {}
  zones.each do |zone|
    system_zones_hash[get_necb_thermal_zone_system_selection(zone)] = [] if system_zones_hash[get_necb_thermal_zone_system_selection(zone)].nil?
    system_zones_hash[get_necb_thermal_zone_system_selection(zone)] << zone
  end
  # puts JSON.pretty_generate(system_zones_hash)
  # go through each system and zones pairs to
  system_zones_hash.each_pair do |system, sys_zones|
    case system
    when 0, nil
      # Do nothing no system assigned to zone. Used for Unconditioned spaces
    when 1
      group_similar_zones_together(sys_zones).each do |curr_zones|
        mau_air_loop = add_sys1_unitary_ac_baseboard_heating(model: model,
                                                             necb_reference_hp: necb_reference_hp,
                                                             necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                             zones: curr_zones,
                                                             mau_type: mau_type,
                                                             mau_heating_coil_type: mau_heating_coil_type,
                                                             baseboard_type: baseboard_type,
                                                             hw_loop: @hw_loop,
                                                             multispeed: false)
      end
    when 2
      group_similar_zones_together(sys_zones).each do |curr_zones|
        add_sys2_FPFC_sys5_TPFC(model: model,
                                zones: curr_zones,
                                chiller_type: chiller_type,
                                mau_cooling_type: mau_cooling_type,
                                fan_coil_type: 'FPFC',
                                hw_loop: @hw_loop)
      end
    when 3
      group_similar_zones_together(sys_zones).each do |curr_zones|
        add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model,
                                                                              necb_reference_hp: necb_reference_hp,
                                                                              necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                                              zones: curr_zones,
                                                                              heating_coil_type: heating_coil_type_sys3,
                                                                              baseboard_type: baseboard_type,
                                                                              hw_loop: @hw_loop,
                                                                              multispeed: false)
      end
    when 4
      group_similar_zones_together(sys_zones).each do |curr_zones|
        add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model: model,
                                                                     necb_reference_hp: necb_reference_hp,
                                                                     necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel,
                                                                     zones: curr_zones,
                                                                     heating_coil_type: heating_coil_type_sys4,
                                                                     baseboard_type: baseboard_type,
                                                                     hw_loop: @hw_loop)
        #          add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating(model: model,
        #                                                                                zones: zones,
        #                                                                                heating_coil_type: heating_coil_type_sys4,
        #                                                                                baseboard_type: baseboard_type,
        #                                                                                hw_loop: @hw_loop,
        #                                                                                multispeed: false)
      end
    when 5
      group_similar_zones_together(sys_zones).each do |curr_zones|
        add_sys2_FPFC_sys5_TPFC(model: model,
                                zones: curr_zones,
                                chiller_type: chiller_type,
                                mau_cooling_type: mau_cooling_type,
                                fan_coil_type: 'TPFC',
                                hw_loop: @hw_loop)
      end
    when 6
      if necb_reference_hp
        add_sys6_multi_zone_reference_hp_with_baseboard_heating(model: model,
                                                                zones: sys_zones,
                                                                heating_coil_type: heating_coil_type_sys6,
                                                                baseboard_type: baseboard_type,
                                                                hw_loop:@hw_loop,
                                                                necb_reference_hp_supp_fuel: necb_reference_hp_supp_fuel)
      else
        add_sys6_multi_zone_built_up_system_with_baseboard_heating(model: model,
                                                                  zones: sys_zones,
                                                                  heating_coil_type: heating_coil_type_sys6,
                                                                  baseboard_type: baseboard_type,
                                                                  chiller_type: chiller_type,
                                                                  fan_type: fan_type,
                                                                  hw_loop: @hw_loop)
      end
    when 7
      group_similar_zones_together(sys_zones).each do |curr_zones|
        add_sys2_FPFC_sys5_TPFC(model: model,
                                zones: curr_zones,
                                chiller_type: chiller_type,
                                fan_coil_type: 'FPFC',
                                mau_cooling_type: mau_cooling_type,
                                hw_loop: @hw_loop)
      end
    end
  end
end
determine_control_zone(zones) click to toggle source

This method will determine the control zone from the last sizing run space loads.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1212
def determine_control_zone(zones)
  # In this case the control zone is the load with the largest heating loads. This may cause overheating of some zones.
  # but this is preferred to unmet heating.
  # Iterate through zones.
  zone_heating_load_hash = {}
  zones.each { |zone| zone_heating_load_hash[zone] = stored_zone_heating_load(zone) }
  return zone_heating_load_hash.max_by(&:last).first
end
determine_dominant_necb_schedule_type(model) click to toggle source

This model determines the dominant NECB schedule type @param model [OpenStudio::Model::Model] A model object return s.each [String]

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 806
def determine_dominant_necb_schedule_type(model)
  return determine_dominant_schedule(model.getSpaces)
end
determine_dominant_schedule(spaces) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 765
def determine_dominant_schedule(spaces)
  # lookup necb space type properties
  space_type_properties = @standards_data['space_types']
  # Here is a hash to keep track of the m2 running total of spacetypes for each
  # sched type.
  # 2018-04-11:  Not sure if this is still used but the list was expanded to incorporate additional existing or potential
  # future schedules.
  schedule_hash = Hash[
      'A', 0,
      'B', 0,
      'C', 0,
      'D', 0,
      'E', 0,
      'F', 0,
      'G', 0,
      'H', 0,
      'I', 0,
      'J', 0,
      'K', 0,
      'L', 0,
      'M', 0,
      'N', 0,
      'O', 0,
      'P', 0,
      'Q', 0
  ]
  # iterate through spaces in building.
  spaces.select { |space| !is_an_necb_wildcard_space?(space) && (space.spaceType.get.standardsSpaceType.get != '- undefined -') }.each do |space|
    # Ensure space floors are multiplied.
    mult = @space_multiplier_map[space.name.to_s].nil? ? 1.0 : @space_multiplier_map[space.name.to_s]
    # puts "this #{determine_necb_schedule_type(space)}"
    schedule_hash[determine_necb_schedule_type(space)] += space.floorArea * mult
  end
  # finds max value and returns NECB schedule letter.
  # determine dominant letter schedule.
  return schedule_hash.max_by(&:last).first
end
determine_necb_schedule_type(space) click to toggle source

This method determines the spacetype schedule type. This will re @author phylroy.lopez@nrcan.gc.ca @param space [String] @return [String]: spacetype

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 814
def determine_necb_schedule_type(space)
  spacetype_data = @standards_data['space_types']
  raise "Spacetype not defined for space #{space.get.name}) if space.spaceType.empty?" if space.spaceType.empty?
  raise "Undefined standardsSpaceType or StandardsBuildingType for space #{space.spaceType.get.name}) if space.spaceType.empty?" if space.spaceType.get.standardsSpaceType.empty? | space.spaceType.get.standardsBuildingType.empty?

  space_type_properties = spacetype_data.detect { |st| (st['space_type'] == space.spaceType.get.standardsSpaceType.get) && (st['building_type'] == space.spaceType.get.standardsBuildingType.get) }
  return space_type_properties['necb_schedule_type'].strip
end
determine_spacetype_vintage(model) click to toggle source

this method will determine the vintage of NECB spacetypes the model contains. It will return nil if it can’t determine it.

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1108
def determine_spacetype_vintage(model)
  #this code is the list of available vintages
  space_type_vintage_list = ['NECB2011', 'NECB2015', 'NECB2017', 'NECB2020', 'BTAPPRE1980', 'BTAP1980TO2010']
  #this reorders the list to do the current class first.
  space_type_vintage_list.insert(0, space_type_vintage_list.delete(self.class.name))
  # Set the space_type
  space_type_vintage = nil
  # get list of space types used in model and a mapped string.
  model_space_type_names = model.getSpaceTypes.map do |spacetype|
    [spacetype.standardsBuildingType.get.to_s + '-' + spacetype.standardsSpaceType.get.to_s]
  end
  # Now iterate though each vintage
  space_type_vintage_list.each do |template|
    # Create the standard object and get a list of all the spacetypes available for that vintage.
    standard_space_type_list = Standard.build(template).get_all_spacetype_names.map { |spacetype| [spacetype[0].to_s + '-' + spacetype[1].to_s] }
    # set array to contain unknown spacetypes.
    unknown_spacetypes = []
    # iterate though all space types that the model is using
    model_space_type_names.each do |space_type_name|
      # push unknown spacetypes into the array.
      unknown_spacetypes << space_type_name unless standard_space_type_list.include?(space_type_name)
    end
    if unknown_spacetypes.empty?
      # No unknowns, so return the template and don't bother looking for others.
      return template
    end
  end
  return space_type_vintage
end
distance(loc1, loc2) click to toggle source

Enter in [latitude, longitude] for each loc and this method will return the distance.

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 150
def distance(loc1, loc2)
  rad_per_deg = Math::PI / 180 # PI / 180
  rkm = 6371 # Earth radius in kilometers
  rm = rkm * 1000 # Radius in meters

  dlat_rad = (loc2[0] - loc1[0]) * rad_per_deg # Delta, converted to rad
  dlon_rad = (loc2[1] - loc1[1]) * rad_per_deg

  lat1_rad, lon1_rad = loc1.map { |i| i * rad_per_deg }
  lat2_rad, lon2_rad = loc2.map { |i| i * rad_per_deg }

  a = Math.sin(dlat_rad / 2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad / 2)**2
  c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
  rm * c # Delta in meters
end
download_and_save_file(weather_list_url:, weather_loc:, git_folder:) click to toggle source

Arguments: weather_list_url (string): the web address of the json file containing the list of weather files on the repository weather_loc (string): the name of the epw file we are looking for without the .epw extension git_folder (string): the url of the folder containing the weather files. As of 2023-07-07 this this is either the

url of the historical weather data folder or the future weather data folder.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2276
def download_and_save_file(weather_list_url:, weather_loc:, git_folder:)
  status = false
  attempt = 1
  # Try to download the list of weather files 5 times, waiting 5 seconds between each attempt.
  until attempt > 5
    begin
      puts "Beginning attempt #{attempt} to download #{weather_list_url}"
      # Download the list of weather files on the repository
      URI.open(weather_list_url) do |web_data|
        # Convert the weather file list to an array
        if web_data.size <= 100
          raise("Could not read #{weather_list_url}!")
        end
        weather_files = (JSON.parse(web_data.read)).to_a
        # Check to see if the requested weather file is on the list
        zip_name = weather_files.find{ |weather_file| weather_file.match(weather_loc) }
        # If the weather file is on the list proceed, otherwise report that it could not be found
        unless zip_name.nil?
          # Found the weather file on the list
          # Define the full url of the weather zip file we want to download
          save_file_url = git_folder + zip_name
          # Define the local location of where the weather zip file will be saved
          weather_dir = File.absolute_path(File.join(__FILE__, '..', '..', '..', '..', '..' , '..', "data/weather"))
          save_file = File.join(weather_dir, zip_name)
          attemptb = 1
          # Try to download the weather file up to 5 times, waiting 5 seconds between each attempt.
          until attemptb > 5
            begin
              puts "Beginning attempt #{attemptb} to download #{save_file_url}"
              # Download the weather zip file from the repository
              URI.open(save_file_url) do |file_url|
                if file_url.size <= 100
                  raise("Could not read #{save_file_url}!")
                end
                # Save the zip file in the /data/weather folder
                File.open(save_file, 'wb') { |f| f.write(file_url.read) }
                puts "Downloaded #{save_file_url} to #{save_file}"
                # Extract the individual weother files from the zip file
                status = extract_weather_data(zipped_file: save_file, weather_dir: weather_dir)
              end
              attemptb = 10
            rescue
              sleep(30)
              attemptb += 1
            end
          end
        end
      end
      attempt = 10
    rescue
      sleep(30)
      attempt += 1
    end
  end
  return status
end
extract_weather_data(zipped_file:, weather_dir:) click to toggle source

This method extracts data from a zip file. The name implies it is to be used for zip files containing weather data but really it can be used to extract any zip file. Arguments: zipped_file (string): As the name implies, this is the name and path to the zip file we want to expand. weather_dir (string): This is the folder where the zipped files will be extracted to. Don’t let the name fool you.

it can be any folder not just a weather directory.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2369
def extract_weather_data(zipped_file:, weather_dir:)
  # Set a flag to check if the weather data is for future weather.
  future_file = false
  # Start expanding the data.
  Zip::File.open(zipped_file) do |zip_file|
    puts "Expanding #{zipped_file}"
    # Cycle through each file in the zip file
    zip_file.each do |entry|
      # Define the location of where the file will be saved locally
      curr_save_file = File.join(weather_dir, entry.name.to_s)
      puts "Extracting #{entry.name} to #{curr_save_file}"
      # Check if the file includes a '_ASHRAE.ddy' term.  If there is, later on we will rename the '_ASHRAE.ddy' file
      # to just '.ddy'. This is so the rest of BTAP uses the _ASHRAE.ddy file.
      entry_name = entry.name.to_s
      if entry_name.length > 11
        future_file = true if entry_name[-11..-1] == '_ASHRAE.ddy'
      end
      # entry.extract # This was required before but now it isn't.  I'm confused so am saving this comment to
      # remind me if there are problems later.
      # Read the data from the file
      content = entry.get_input_stream.read
      # Save the data locally
      File.open(curr_save_file, 'wb') { |save_f| save_f.write(content) }
    end
  end
  if future_file
    # Rename the non ASHRAE.ddy as '_non_ASHRAE.ddy' and save the '_ASHRAE.ddy' as the regular '.ddy' file.  This is
    # because the ASHRAE .ddy file includes sizing information not included in the regular .ddy file for future
    # weather data files.  Unfortunately, openstudio-standards just looks for the regular .ddy file for sizing
    # information which is why the switch is done.
    weather_loc = (File.basename(zipped_file).to_s)[0..-5]
    puts "Renaming #{weather_loc}.ddy as #{weather_loc}_orig.ddy and #{weather_loc}_ASHRAE.ddy as #{weather_loc}.ddy."
    puts "This is so that the design weather information in the #{weather_loc}_ASHRAE.ddy file is used."
    orig_ddy_name = File.join(weather_dir, (weather_loc + ".ddy"))
    ashrae_ddy_name = File.join(weather_dir, (weather_loc + "_ASHRAE.ddy"))
    rev_ddy_name = File.join(weather_dir, (weather_loc + "_orig.ddy"))
    FileUtils.cp(orig_ddy_name, rev_ddy_name)
    FileUtils.cp(ashrae_ddy_name, orig_ddy_name)
  end
  # Return true if everything worked out
  return true
end
fan_baseline_impeller_efficiency(fan) click to toggle source

Determines the baseline fan impeller efficiency based on the specified fan type.

@return [Double] impeller efficiency (0.0 to 1.0) @todo Add fan type to data model and modify this method

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1323
def fan_baseline_impeller_efficiency(fan)
  # Assume that the fan efficiency is 65% for normal fans
  # @todo add fan type to fan data model
  # and infer impeller efficiency from that?
  # or do we always assume a certain type of
  # fan impeller for the baseline system?
  # @todo check COMNET and T24 ACM and PNNL 90.1 doc
  fan_impeller_eff = 0.65

  return fan_impeller_eff
end
fan_constant_volume_apply_prototype_fan_pressure_rise(fan_constant_volume) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1522
def fan_constant_volume_apply_prototype_fan_pressure_rise(fan_constant_volume)
  fan_constant_volume.setPressureRise(get_standards_constant('fan_constant_volume_pressure_rise_value'))
  return true
end
fan_standard_minimum_motor_efficiency_and_size(fan, motor_bhp) click to toggle source

Determines the minimum fan motor efficiency and nominal size for a given motor bhp. This should be the total brake horsepower with any desired safety factor already included. This method picks the next nominal motor catgory larger than the required brake horsepower, and the efficiency is based on that size. For example, if the bhp = 6.3, the nominal size will be 7.5HP and the efficiency for 90.1-2010 will be 91.7% from Table 10.8B. This method assumes 4-pole, 1800rpm totally-enclosed fan-cooled motors.

@param motor_bhp [Double] motor brake horsepower (hp) @return [Array<Double>] minimum motor efficiency (0.0 to 1.0), nominal horsepower

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1346
def fan_standard_minimum_motor_efficiency_and_size(fan, motor_bhp)
  fan_motor_eff = 0.85
  nominal_hp = motor_bhp

  # Don't attempt to look up motor efficiency
  # for zero-hp fans, which may occur when there is no
  # airflow required for a particular system, typically
  # heated-only spaces with high internal gains
  # and no OA requirements such as elevator shafts.
  return [fan_motor_eff, 0] if motor_bhp == 0.0

  # Lookup the minimum motor efficiency
  motors_table = @standards_data['motors']

  # Assuming all fan motors are 4-pole ODP
  motor_use = 'FAN'
  motor_type = ''
  if (fan.class.name == 'OpenStudio::Model::FanConstantVolume') || (fan.class.name == 'OpenStudio::Model::FanOnOff')
    motor_type = 'CONSTANT'
  elsif fan.class.name == 'OpenStudio::Model::FanVariableVolume'
    # Is this a return or supply fan
    if fan.name.to_s.include?('Supply')
      motor_type += 'VARIABLE-SUPPLY'
    elsif fan.name.to_s.include?('Return')
      motor_type += 'VARIABLE-RETURN'
    end
    # 0.909 corrects for 10% over sizing implemented upstream
    # 0.7457 is to convert from bhp to kW
    fan_power_kw = 0.909 * 0.7457 * motor_bhp
    power_vs_flow_curve_name = if fan_power_kw >= 25.0
                                 'VarVolFan-FCInletVanes-NECB2011-FPLR'
                               elsif fan_power_kw >= 7.5 && fan_power_kw < 25
                                 'VarVolFan-AFBIInletVanes-NECB2011-FPLR'
                               else
                                 'VarVolFan-AFBIFanCurve-NECB2011-FPLR'
                               end
    power_vs_flow_curve = model_add_curve(fan.model, power_vs_flow_curve_name)
    fan.setFanPowerMinimumFlowRateInputMethod('Fraction')
    fan.setFanPowerCoefficient5(0.0)
    fan.setFanPowerMinimumFlowFraction(power_vs_flow_curve.minimumValueofx)
    fan.setFanPowerCoefficient1(power_vs_flow_curve.coefficient1Constant)
    fan.setFanPowerCoefficient2(power_vs_flow_curve.coefficient2x)
    fan.setFanPowerCoefficient3(power_vs_flow_curve.coefficient3xPOW2)
    fan.setFanPowerCoefficient4(power_vs_flow_curve.coefficient4xPOW3)
  elsif fan.class.name == 'OpenStudio::Model::FanZoneExhaust'
    motor_type = 'CONSTANT-RETURN'
  else
    raise('')
  end

  search_criteria = {
    'motor_use' => motor_use,
    'motor_type' => motor_type,
    'number_of_poles' => 4.0,
    'type' => 'Enclosed'
  }

  # Exception for small fans, including
  # zone exhaust, fan coil, and fan powered terminals.
  # In this case, use the 0.5 HP for the lookup.
  if fan_small_fan?(fan)
    nominal_hp = 0.5
  else
    motor_properties = model_find_object(motors_table, search_criteria, motor_bhp)
    if motor_properties.nil?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Fan', "For #{fan.name}, could not find motor properties using search criteria: #{search_criteria}, motor_bhp = #{motor_bhp} hp.")
      return [fan_motor_eff, nominal_hp]
    end

    nominal_hp = motor_properties['maximum_capacity'].to_f.round(1)
    # If the biggest fan motor size is hit, use the highest category efficiency
    if nominal_hp == 9999.0
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Fan', "For #{fan.name}, there is no greater nominal HP.  Use the efficiency of the largest motor category.")
      nominal_hp = motor_bhp
    end

    # Round to nearest whole HP for niceness
    if nominal_hp >= 2
      nominal_hp = nominal_hp.round
    end
  end

  # Get the efficiency based on the nominal horsepower
  # Add 0.01 hp to avoid search errors.
  motor_properties = model_find_object(motors_table, search_criteria, nominal_hp + 0.01)

  if motor_properties.nil?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Fan', "For #{fan.name}, could not find nominal motor properties using search criteria: #{search_criteria}, motor_hp = #{nominal_hp} hp.")
    return [fan_motor_eff, nominal_hp]
  end
  fan_motor_eff = motor_properties['nominal_full_load_efficiency']

  return [fan_motor_eff, nominal_hp]
end
fan_variable_volume_apply_prototype_fan_pressure_rise(fan_variable_volume) click to toggle source

Sets the fan pressure rise based on the Prototype buildings inputs which are governed by the flow rate coming through the fan and whether the fan lives inside a unit heater, PTAC, etc.

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1539
def fan_variable_volume_apply_prototype_fan_pressure_rise(fan_variable_volume)
  # 1000 Pa for supply fan and 458.33 Pa for return fan (accounts for efficiency differences between two fans)
  if fan_variable_volume.name.to_s.include?('Supply')
    sfan_deltaP = get_standards_constant('supply_fan_variable_volume_pressure_rise_value')
    fan_variable_volume.setPressureRise(sfan_deltaP)
  elsif fan_variable_volume.name.to_s.include?('Return')
    rfan_deltaP = get_standards_constant('return_fan_variable_volume_pressure_rise_value')
    fan_variable_volume.setPressureRise(rfan_deltaP)
  end
  return true
end
fan_variable_volume_part_load_fan_power_limitation?(fan_variable_volume) click to toggle source

Determines whether there is a requirement to have a VSD or some other method to reduce fan power at low part load ratios.

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1499
def fan_variable_volume_part_load_fan_power_limitation?(fan_variable_volume)
  part_load_control_required = false
  return part_load_control_required
end
find_mech_room(model) click to toggle source

This method determines where the mechanical room is in a building. Mechanical rooms are assumed to be conditioned spaces that are not plenums. It goes through all of the spaces in a model ignoring unconditioned spaces and plenums. It then determines the floor of the space, and the centroid of the floor. It accumulates space floor area*floor centroid information and total floor area. It uses this to determine the centroid of the building. It also notes if the space has a space type with ‘Electrical/Mechanical’ in it. If it does then it assumes that this is a mechanical room and tracks it separately. Once it has gone through all of the spaces it determines if any mechanical rooms were found. If some were found it finds the lowest one in the building (if more than one) and returns that along with all of the conditioned, non-plenum, spaces. If none were found then it searches for the lowest space in the building closest to the building centroid and returns that as the centroid (along with eth conditioned, non-plenum, spaces).

# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 582
def find_mech_room(model)
  cond_spaces = []
  total_peak_flow = 0
  mech_rooms = []
  check_spaces = nil
  building_centre = Array.new(3, 0)
  total_peak_flow = 0
  lowest_space = 1000000000000000
  sp_func_regex = Regexp.new('Space Function')
  mech_regex = Regexp.new('Electrical/Mechanical')
  mech_flag = false
  index = 0
  model.getSpaces.sort.each do |space|
    spaceType_name = space.spaceType.get.nameString
    sp_type = spaceType_name[15..-1]
    # Including regular expressions in the following match for cases where extra characters, which do not belong, are
    # added to either the space type in the model or the space type reference file.
    sp_type_info = @standards_data['tables']['space_types']['table'].detect do |data|
      (Regexp.new(data['space_type'].to_s.upcase).match(sp_type.upcase) || Regexp.new(sp_type.upcase).match(data['space_type'].to_s.upcase) || (data['space_type'].to_s.upcase == sp_type.upcase)) &&
        (data['building_type'].to_s == 'Space Function')
    end
    if sp_type_info.nil?
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.model_add_swh', "The space type called #{sp_type} could not be found.  Please check that the schedules.json file is available and that the space types are spelled correctly")
      next
    end
    # Determine if space is heated or cooled via spacetype heating or cooling setpoints also checking if the space is
    # a plenum by checking if there is a hvac system associtated with it
    if sp_type_info['heating_setpoint_schedule'].nil?
      heated = false
    else
      heated = true
    end
    if sp_type_info['cooling_setpoint_schedule'].nil?
      cooled = false
    else
      cooled = true
    end
    if (sp_type_info['necb_hvac_system_selection_type'] == '- undefined -') || /undefined/.match(sp_type_info['necb_hvac_system_selection_type'])
      not_plenum = false
    else
      not_plenum = true
    end

    # Determine the bottom surface of the space and calculate it's centroid.  Although the mech room is assumed to
    # be in a space that conditioned and is not a plenum (or attic space) all spaces in the building may not be.
    # Doing the following to determine the centroid for the entire building (including unconditioned or plenum
    # spaces).

    # Get the coordinates of the origin for the space (the coordinates of points in the space are relative to this).
    xOrigin = space.xOrigin
    yOrigin = space.yOrigin
    zOrigin = space.zOrigin
    # Get the surfaces for the space.
    space_surfaces = space.surfaces
    # Find the floor (aka the surface with the lowest centroid).
    min_surf = space_surfaces.min_by { |sp_surface| sp_surface.centroid.z.to_f }
    # The following is added to determine the overall floor centroid because some spaces have floors composed of more than one surface.
    floor_centroid = [0, 0, 0]
    space_surfaces.each do |sp_surface|
      if min_surf.centroid.z.to_f == sp_surface.centroid.z.to_f
        floor_centroid[0] = floor_centroid[0] + sp_surface.centroid.x.to_f * sp_surface.grossArea.to_f
        floor_centroid[1] = floor_centroid[1] + sp_surface.centroid.y.to_f * sp_surface.grossArea.to_f
        floor_centroid[2] = floor_centroid[2] + sp_surface.grossArea
      end
    end

    floor_centroid[0] = floor_centroid[0] / floor_centroid[2]
    floor_centroid[1] = floor_centroid[1] / floor_centroid[2]

    if lowest_space > (min_surf.centroid.z.to_f + zOrigin)
      lowest_space = min_surf.centroid.z.to_f + zOrigin
    end
    # This part is used to determine the overall x, y centre of the building.  This is determined by summing the x
    # and y components times the floor area and diving by the total floor area.  This is only for conditioned spaces.
    building_centre[0] += (floor_centroid[0] + xOrigin) * floor_centroid[2]
    building_centre[1] += (floor_centroid[1] + yOrigin) * floor_centroid[2]
    building_centre[2] += (floor_centroid[2])

    # Check if the space is conditioned and not a plenum.  If it is then add it to the list of conditioned, non-plenum,
    # spaces and check if it has a 'Mechanical/Electrical' space type.  Note that the mech room is assumed to
    # be in a space that conditioned and is not a plenum (or attic space).

    if (heated == true || cooled == true) && (not_plenum == true)
      if mech_regex.match(spaceType_name)
        mech_rooms << index
      end
      cond_space = {
        'space_name' => space.nameString,
        'space' => space,
        'space_centroid' => [floor_centroid[0] + xOrigin, floor_centroid[1] + yOrigin, min_surf.centroid.z.to_f + zOrigin],
        'building_cent_dist' => 0
      }
      cond_spaces << cond_space
      index += 1
    end
  end

  return [cond_spaces[mech_rooms[0]], cond_spaces] if mech_rooms.size == 1

  if mech_rooms.size > 1
    check_spaces = []
    lowest_space = 10000000000000
    mech_rooms.each do |mech_room|
      check_spaces << cond_spaces[mech_room]
      if cond_spaces[mech_room]['space_centroid'][2].to_f < lowest_space
        lowest_space = cond_spaces[mech_room]['space_centroid'][2].to_f
      end
    end
  else
    check_spaces = cond_spaces
  end

  # If no heated or cooled spaces were found then return false
  if cond_spaces.empty?
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.model_add_swh', 'No heated or cooled spaces types could be found which were not also a plenum.')
    return false
  end

  # This is where the average happens
  building_centre[0] /= building_centre[2]
  building_centre[1] /= building_centre[2]
  # Go through each space on the lowest floor of the building and determine the distance between the centroid of the
  # space's floors and the center of the building I calculated just above.
  centre_spaces = []
  check_spaces.each do |check_space|
    if check_space['space_centroid'][2] == lowest_space
      check_space['building_cent_dist'] = Math.sqrt(((check_space['space_centroid'][0] - building_centre[0])**2) + ((check_space['space_centroid'][1] - building_centre[1])**2))
      centre_spaces << check_space
    end
  end
  # Determine which of the floor spaces is closest to the centre of the building and that one becomes the location of
  # the mechanical room.
  centre_space = centre_spaces.min_by { |dist| dist['building_cent_dist'].round(1) }
  return [centre_space, cond_spaces]
end
friction_factor(re_pipe, relative_rough) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 543
def friction_factor(re_pipe, relative_rough)
  # This method determines the Darcy-Weisbach friction factor assuming the pipe is circular and filled.
  if re_pipe <= 2100
    # Laminar flow use the Uagen-Poiseuille equation.  https://neutrium.net/fluid_flow/pressure-loss-in-pipe
    # accessed 2018-07-25.
    f = 64.to_f / re_pipe.to_f
  elsif re_pipe > 2100 && re_pipe <= 4000
    # In the transition flow region I interpolate by Reynolds number between laminar and turbulent regimes.  Yeah, that's
    # crap but if you can come up with something better you are welcome to replace what I have below.
    flam = 64.to_f / 2100.to_f
    pipe_rough_fact = relative_rough / 3.7
    factor_A = -2 * Math.log10(pipe_rough_fact + (12.to_f / 4000.to_f))
    factor_B = -2 * Math.log10(pipe_rough_fact + ((2.51 * factor_A) / 4000))
    factor_C = -2 * Math.log10(pipe_rough_fact + ((2.51 * factor_B) / 4000))
    fturb = 1 / ((factor_A - (((factor_B - factor_A)**2) / (factor_C - 2 * factor_B + factor_A)))**2)
    re_int = (re_pipe - 2100.to_f) / 1900.to_f
    f = ((fturb - flam) * re_int) + flam
  elsif re_pipe > 4000
    # Turbulent flow use Serghide's Equation which I got from https://neutrium.net/fluid_flow/pressure-loss-in-pipe
    # accessed 2018-07-25.  Apparently it is good for 4000 < Re < 1x10^10 and relative roughness between 1x10-7 and 1.
    pipe_rough_fact = relative_rough / 3.7
    factor_A = -2 * Math.log10(pipe_rough_fact + (12 / re_pipe))
    factor_B = -2 * Math.log10(pipe_rough_fact + ((2.51 * factor_A) / re_pipe))
    factor_C = -2 * Math.log10(pipe_rough_fact + ((2.51 * factor_B) / re_pipe))
    f = 1 / ((factor_A - (((factor_B - factor_A)**2) / (factor_C - 2 * factor_B + factor_A)))**2)
  end
  return f
end
get_all_spacetype_names() click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 145
def get_all_spacetype_names
  return @standards_data['space_types'].map { |space_types| [space_types['building_type'], space_types['space_type']] }
end
get_any_number_ppm(model) click to toggle source

Define ScheduleTypeLimits for Any_Number_ppm @todo (upon other BTAP tasks) This function can be added to btap/schedules.rb > module StandardScheduleTypeLimits

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1715
def get_any_number_ppm(model)
  name = 'Any_Number_ppm'
  any_number_ppm_schedule_type_limits = model.getScheduleTypeLimitsByName(name)
  return any_number_ppm_schedule_type_limits.get unless any_number_ppm_schedule_type_limits.empty?

  any_number_ppm_schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
  any_number_ppm_schedule_type_limits.setName(name)
  any_number_ppm_schedule_type_limits.setNumericType('CONTINUOUS')
  any_number_ppm_schedule_type_limits.setUnitType('Dimensionless')
  any_number_ppm_schedule_type_limits.setLowerLimitValue(400.0)
  any_number_ppm_schedule_type_limits.setUpperLimitValue(1000.0)
  return any_number_ppm_schedule_type_limits
end
get_climate_zone_index(hdd) click to toggle source

This model gets the climate zone column index from tables 3.2.2.x @author phylroy.lopez@nrcan.gc.ca @param hdd [Float] @return [Fixnum] climate zone 4-8

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1307
def get_climate_zone_index(hdd)
  # check for climate zone index from NECB 3.2.2.X
  case hdd
  when 0..2999 then
    return 0 # climate zone 4
  when 3000..3999 then
    return 1 # climate zone 5
  when 4000..4999 then
    return 2 # climate zone 6
  when 5000..5999 then
    return 3 # climate zone 7a
  when 6000..6999 then
    return 4 # climate zone 7b
  when 7000..1000000 then
    return 5 # climate zone 8
  end
end
get_climate_zone_name(hdd) click to toggle source

This model gets the climate zone name and returns the climate zone string. @author phylroy.lopez@nrcan.gc.ca @param hdd [Float] @return [Fixnum] climate zone 4-8

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1329
def get_climate_zone_name(hdd)
  case get_climate_zone_index(hdd)
  when 0 then
    return '4'
  when 1 then
    return '5' # climate zone 5
  when 2 then
    return '6' # climate zone 6
  when 3 then
    return '7a' # climate zone 7a
  when 4 then
    return '7b' # climate zone 7b
  when 5 then
    return '8' # climate zone 8
  end
end
get_max_space_height_for_space_type(space_type:) click to toggle source

This method is needed to the space_type_apply_internal_loads space needed for the calculation of atriums’ LPD when LED lighting is used in atriums. ***END***

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1926
def get_max_space_height_for_space_type(space_type:)
  # initialize return value to zero.
  max_space_height_for_space_type = 0.0
  # Interate through all spaces in model.. not just ones that have space type defined.. Is this right sara?
  space_type.spaces.sort.each do |space|
    # Get only the wall type surfaces and iterate throught them.
    space.surfaces.sort.select(&:surfaceType == 'Wall').each do |wall_surface|
      # Find the vertex with the max z value.
      vertex_with_max_height = wall_surface.vertices.max_by(&:z)
      # replace max if this surface has something bigger.
      max_space_height_for_space_type = vertex_with_max_height.z if vertex_with_max_height.z > max_space_height_for_space_type
    end
  end
  return max_space_height_for_space_type
end
get_necb_hdd18(model:, necb_hdd: true) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 166
def get_necb_hdd18(model:, necb_hdd: true)
  max_distance_tolerance = 500000
  min_distance = 100000000000000.0
  necb_closest = nil
  weather_file_path = model.weatherFile.get.path.get.to_s
  epw_file = model.weatherFile.get.file.get
  stat_file_path = weather_file_path.gsub('.epw', '.stat')
  stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
  # If necb_hdd is false use the information in the .stat file associated with the.epw file.
  unless necb_hdd
    return stat_file.hdd18
  end
  # this extracts the table from the json database.
  necb_2015_table_c1 = @standards_data['tables']['necb_2015_table_c1']['table']
  necb_2015_table_c1.each do |necb|
    next if necb['lat_long'].nil? # Need this until Tyson cleans up table.

    dist = distance([epw_file.latitude, epw_file.longitude], necb['lat_long'])
    if min_distance > dist
      min_distance = dist
      necb_closest = necb
    end
  end
  if ((min_distance / 1000.0) > max_distance_tolerance) && !stat_file.hdd18.nil?
    puts "Could not find close NECB HDD from Table C1 < #{max_distance_tolerance}km. Closest city is #{min_distance / 1000.0}km away. Using epw hdd18 instead."
    return stat_file.hdd18
  else
    dist_clause = "%.2f % #{(min_distance / 1000.0)}"
    puts "INFO:NECB HDD18 of #{necb_closest['degree_days_below_18_c'].to_f}  at nearest city #{necb_closest['city']},#{necb_closest['province']}, at a distance of " + dist_clause + 'km from epw location. Ref: nbc_2015_table_c1'
    return necb_closest['degree_days_below_18_c'].to_f
  end
end
get_necb_spacetype_system_selection(space) click to toggle source

Determines what system index number is required for the space’s spacetype by NECB rules.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 682
def get_necb_spacetype_system_selection(space)
  space_type_table = @standards_data['space_types']
  space_type_data = model_find_object(space_type_table, 'space_type' => space.spaceType.get.standardsSpaceType.get,
                                                        'building_type' => space.spaceType.get.standardsBuildingType.get)
  if space_type_data.nil?
    raise("Could not find space_type_data for #{{ 'space_type' => space.spaceType.get.standardsSpaceType.get,
                                                  'building_type' => space.spaceType.get.standardsBuildingType.get }} ")
  end

  # identify space-system_index and assign the right NECB system type 1-7.
  necb_hvac_system_selection_table = @standards_data['necb_hvac_system_selection_type']
  necb_hvac_system_select = necb_hvac_system_selection_table.detect do |curr_necb_hvac_system_select|
    curr_necb_hvac_system_select['necb_hvac_system_selection_type'] == space_type_data['necb_hvac_system_selection_type'] &&
      curr_necb_hvac_system_select['min_stories'] <= space.model.getBuilding.standardsNumberOfAboveGroundStories.get &&
      curr_necb_hvac_system_select['max_stories'] >= space.model.getBuilding.standardsNumberOfAboveGroundStories.get &&
      curr_necb_hvac_system_select['min_cooling_capacity_kw'] <= stored_space_cooling_load(space) &&
      curr_necb_hvac_system_select['max_cooling_capacity_kw'] >= stored_space_cooling_load(space)
  end
  raise('could not find system for given spacetype') if necb_hvac_system_select.nil?

  return necb_hvac_system_select['system_type']
end
get_necb_thermal_zone_system_selection(tz) click to toggle source

Determines what system index number is required for the thermal zone based on the spacetypes it contains

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 706
def get_necb_thermal_zone_system_selection(tz)
  systems = []
  tz.spaces.each do |space|
    systems << get_necb_spacetype_system_selection(space)
  end
  systems.uniq!
  systems.compact!
  raise('This thermal zone spaces require different systems.') if systems.size > 1

  return systems.first
end
get_parameters_sidelighting(daylight_space:, floor_surface:, floor_vertices:, floor_area:, primary_sidelighted_area:, area_weighted_vt_handle:, window_area_sum:) click to toggle source

The below method is for the ‘model_add_daylighting_controls’ method

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1943
def get_parameters_sidelighting(daylight_space:, floor_surface:, floor_vertices:, floor_area:, primary_sidelighted_area:, area_weighted_vt_handle:, window_area_sum:)
  daylight_space.surfaces.sort.each do |surface|
    ##### Get the vertices of each exterior wall of the daylight_space on the floor
    ##### (these vertices will be used to calculate daylight_space depth in relation to the exterior wall, and
    ##### the distance of the window to vertical walls on each side of the window)
    if surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'Wall'
      wall_vertices_x_on_floor = []
      wall_vertices_y_on_floor = []
      surface_z_min = [surface.vertices[0].z, surface.vertices[1].z, surface.vertices[2].z, surface.vertices[3].z].min
      surface.vertices.each do |vertex|
        # puts vertex.z
        if vertex.z == surface_z_min && surface_z_min == floor_vertices[0][0].z
          wall_vertices_x_on_floor << vertex.x
          wall_vertices_y_on_floor << vertex.y
        end
      end
    end

    if surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'Wall' && surface_z_min == floor_vertices[0][0].z

      ##### Calculate the daylight_space depth in relation to the considered exterior wall.
      ##### To calculate daylight_space depth, first get the floor vertices which are on the opposite side of the considered exterior wall.
      floor_vertices_x_wall_opposite = []
      floor_vertices_y_wall_opposite = []
      floor_vertices[0].each do |floor_vertex|
        if (floor_vertex.x != wall_vertices_x_on_floor[0] && floor_vertex.y != wall_vertices_y_on_floor[0]) || (floor_vertex.x != wall_vertices_x_on_floor[1] && floor_vertex.y != wall_vertices_y_on_floor[1])
          floor_vertices_x_wall_opposite << floor_vertex.x
          floor_vertices_y_wall_opposite << floor_vertex.y
        end
      end

      ##### To calculate daylight_space depth, second calculate floor length on both sides: (1) exterior wall side, (2) on the opposite side of the exterior wall
      floor_width_wall_side = Math.sqrt((wall_vertices_x_on_floor[0] - wall_vertices_x_on_floor[1])**2 + (wall_vertices_y_on_floor[0] - wall_vertices_y_on_floor[1])**2)
      floor_width_wall_opposite = Math.sqrt((floor_vertices_x_wall_opposite[0] - floor_vertices_x_wall_opposite[1])**2 + (floor_vertices_y_wall_opposite[0] - floor_vertices_y_wall_opposite[1])**2)

      ##### Now, daylight_space depth can be calculated using the floor area and two lengths of the floor (note that these two lengths are in parallel to each other).
      daylight_space_depth = 2 * floor_area / (floor_width_wall_side + floor_width_wall_opposite)

      ##### Loop through the windows (including fixed and operable ones) to get window specification (width, height, area, visible transmittance (VT)), and area-weighted VT
      surface.subSurfaces.sort.each do |subsurface|
        # puts subsurface.name.to_s
        if subsurface.subSurfaceType == 'FixedWindow' || subsurface.subSurfaceType == 'OperableWindow'
          window_vt = subsurface.visibleTransmittance
          window_vt = window_vt.get
          window_area = subsurface.netArea
          window_area_sum += window_area
          area_weighted_vt_handle += window_area * window_vt
          window_vertices = subsurface.vertices
          if window_vertices[0].z.round(2) == window_vertices[1].z.round(2)
            window_width = Math.sqrt((window_vertices[0].x - window_vertices[1].x)**2.0 + (window_vertices[0].y - window_vertices[1].y)**2.0)
          else
            window_width = Math.sqrt((window_vertices[1].x - window_vertices[2].x)**2.0 + (window_vertices[1].y - window_vertices[2].y)**2.0)
          end
          window_head_height = [window_vertices[0].z, window_vertices[1].z, window_vertices[2].z, window_vertices[3].z].max.round(2)
          primary_sidelighted_area_depth = [window_head_height, daylight_space_depth].min # as per NECB2011: 4.2.2.9.

          ##### Calculate the  distance of the window to vertical walls on each side of the window (this is used to determine the sidelighted area's width).
          window_vertices_on_floor = []
          window_vertices.each do |vertex|
            window_vertices_on_floor << floor_surface.plane.project(vertex)
          end
          window_wall_distance_side1 = [Math.sqrt((wall_vertices_x_on_floor[0] - window_vertices_on_floor[0].x)**2.0 + (wall_vertices_y_on_floor[0] - window_vertices_on_floor[0].y)**2.0),
                                        Math.sqrt((wall_vertices_x_on_floor[0] - window_vertices_on_floor[2].x)**2.0 + (wall_vertices_y_on_floor[0] - window_vertices_on_floor[2].y)**2.0),
                                        0.6].min # 0.6 m as per NECB2011: 4.2.2.9.
          window_wall_distance_side2 = [Math.sqrt((wall_vertices_x_on_floor[1] - window_vertices_on_floor[0].x)**2.0 + (wall_vertices_y_on_floor[1] - window_vertices_on_floor[0].y)**2.0),
                                        Math.sqrt((wall_vertices_x_on_floor[1] - window_vertices_on_floor[2].x)**2.0 + (wall_vertices_y_on_floor[1] - window_vertices_on_floor[2].y)**2.0),
                                        0.6].min # 0.6 m as per NECB2011: 4.2.2.9.
          primary_sidelighted_area_width = window_wall_distance_side1 + window_width + window_wall_distance_side2
          primary_sidelighted_area += primary_sidelighted_area_depth * primary_sidelighted_area_width
          # if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
        end
        # surface.subSurfaces.each do |subsurface|
      end
      # if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall" && surface_z_min == floor_vertices[0][0].z
    end
    # daylight_space.surfaces.each do |surface|
  end

  return primary_sidelighted_area, area_weighted_vt_handle, window_area_sum
end
get_parameters_skylight(daylight_space:, skylight_area_weighted_vt_handle:, skylight_area_sum:, daylighted_under_skylight_area:) click to toggle source

The below method is for the ‘model_add_daylighting_controls’ method

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2025
def get_parameters_skylight(daylight_space:, skylight_area_weighted_vt_handle:, skylight_area_sum:, daylighted_under_skylight_area:)
  daylight_space.surfaces.sort.each do |surface|
    ##### Get roof vertices
    if surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'RoofCeiling'
      roof_vertices = surface.vertices
    end

    ##### Loop through each subsurface to calculate daylighted_area_under_skylights and skylight_effective_aperture for each daylight_space
    surface.subSurfaces.sort.each do |subsurface|
      if subsurface.subSurfaceType == 'Skylight'
        skylight_vt = subsurface.visibleTransmittance
        skylight_vt = skylight_vt.get
        skylight_area = subsurface.netArea
        skylight_area_sum += skylight_area
        skylight_area_weighted_vt_handle += skylight_area * skylight_vt

        ##### Get skylight vertices
        skylight_vertices = subsurface.vertices

        ##### Calculate skylight width and height
        skylight_width = Math.sqrt((skylight_vertices[0].x - skylight_vertices[1].x)**2.0 + (skylight_vertices[0].y - skylight_vertices[1].y)**2.0)
        skylight_length = Math.sqrt((skylight_vertices[0].x - skylight_vertices[3].x)**2.0 + (skylight_vertices[0].y - skylight_vertices[3].y)**2.0)

        ##### Get ceiling height assuming the skylight is flush with the ceiling
        ceiling_height = skylight_vertices[0].z

        ##### Calculate roof lengths
        ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
        roof_length_0 = Math.sqrt((roof_vertices[0].x - roof_vertices[1].x)**2.0 + (roof_vertices[0].y - roof_vertices[1].y)**2.0)
        roof_length_1 = Math.sqrt((roof_vertices[1].x - roof_vertices[2].x)**2.0 + (roof_vertices[1].y - roof_vertices[2].y)**2.0)
        roof_length_2 = Math.sqrt((roof_vertices[2].x - roof_vertices[3].x)**2.0 + (roof_vertices[2].y - roof_vertices[3].y)**2.0)
        roof_length_3 = Math.sqrt((roof_vertices[3].x - roof_vertices[0].x)**2.0 + (roof_vertices[3].y - roof_vertices[0].y)**2.0)

        ##### Find the skylight point that is the closest one to roof_vertex_0
        ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
        roof_vertex_0_skylight_vertex_0 = Math.sqrt((roof_vertices[0].x - skylight_vertices[0].x)**2.0 + (roof_vertices[0].y - skylight_vertices[0].y)**2.0)
        roof_vertex_0_skylight_vertex_1 = Math.sqrt((roof_vertices[0].x - skylight_vertices[1].x)**2.0 + (roof_vertices[0].y - skylight_vertices[1].y)**2.0)
        roof_vertex_0_skylight_vertex_2 = Math.sqrt((roof_vertices[0].x - skylight_vertices[2].x)**2.0 + (roof_vertices[0].y - skylight_vertices[2].y)**2.0)
        roof_vertex_0_skylight_vertex_3 = Math.sqrt((roof_vertices[0].x - skylight_vertices[3].x)**2.0 + (roof_vertices[0].y - skylight_vertices[3].y)**2.0)
        roof_vertex_0_closest_distance = [roof_vertex_0_skylight_vertex_0, roof_vertex_0_skylight_vertex_1, roof_vertex_0_skylight_vertex_2, roof_vertex_0_skylight_vertex_3].min
        if roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_0
          roof_vertex_0_closest_point = skylight_vertices[0]
        elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_1
          roof_vertex_0_closest_point = skylight_vertices[1]
        elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_2
          roof_vertex_0_closest_point = skylight_vertices[2]
        elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_3
          roof_vertex_0_closest_point = skylight_vertices[3]
        end

        ##### Find the skylight point that is the closest one to roof_vertex_2
        ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
        roof_vertex_2_skylight_vertex_0 = Math.sqrt((roof_vertices[2].x - skylight_vertices[0].x)**2.0 + (roof_vertices[2].y - skylight_vertices[0].y)**2.0)
        roof_vertex_2_skylight_vertex_1 = Math.sqrt((roof_vertices[2].x - skylight_vertices[1].x)**2.0 + (roof_vertices[2].y - skylight_vertices[1].y)**2.0)
        roof_vertex_2_skylight_vertex_2 = Math.sqrt((roof_vertices[2].x - skylight_vertices[2].x)**2.0 + (roof_vertices[2].y - skylight_vertices[2].y)**2.0)
        roof_vertex_2_skylight_vertex_3 = Math.sqrt((roof_vertices[2].x - skylight_vertices[3].x)**2.0 + (roof_vertices[2].y - skylight_vertices[3].y)**2.0)
        roof_vertex_2_closest_distance = [roof_vertex_2_skylight_vertex_0, roof_vertex_2_skylight_vertex_1, roof_vertex_2_skylight_vertex_2, roof_vertex_2_skylight_vertex_3].min
        if roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_0
          roof_vertex_2_closest_point = skylight_vertices[0]
        elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_1
          roof_vertex_2_closest_point = skylight_vertices[1]
        elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_2
          roof_vertex_2_closest_point = skylight_vertices[2]
        elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_3
          roof_vertex_2_closest_point = skylight_vertices[3]
        end

        ##### Calculate the vertical distance from the closest skylight points (projection onto the roof) to the wall (projection onto the roof) for roof_vertex_0 and roof_vertex_2
        ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
        ##### For the calculation of each vertical distance: (1) first the area of the triangle is calculated knowing the cooridantes of its three corners;
        ##### (2) the vertical distance (i.e. triangle height) is calculated knowing the triangle area and the associated roof length.
        rv_0_triangle_0_area = 0.5 * (((roof_vertex_0_closest_point.x - roof_vertices[1].x) * (roof_vertex_0_closest_point.y - roof_vertices[0].y)) -
            ((roof_vertex_0_closest_point.x - roof_vertices[0].x) * (roof_vertex_0_closest_point.y - roof_vertices[1].y))).abs
        rv_0_distance_0 = (2.0 * rv_0_triangle_0_area) / roof_length_0
        rv_0_triangle_3_area = 0.5 * (((roof_vertex_0_closest_point.x - roof_vertices[3].x) * (roof_vertex_0_closest_point.y - roof_vertices[0].y)) -
            ((roof_vertex_0_closest_point.x - roof_vertices[0].x) * (roof_vertex_0_closest_point.y - roof_vertices[3].y))).abs
        rv_0_distance_3 = (2.0 * rv_0_triangle_3_area) / roof_length_3

        rv_2_triangle_1_area = 0.5 * (((roof_vertex_2_closest_point.x - roof_vertices[1].x) * (roof_vertex_2_closest_point.y - roof_vertices[2].y)) -
            ((roof_vertex_2_closest_point.x - roof_vertices[2].x) * (roof_vertex_2_closest_point.y - roof_vertices[1].y))).abs
        rv_2_distance_1 = (2.0 * rv_2_triangle_1_area) / roof_length_1
        rv_2_triangle_2_area = 0.5 * (((roof_vertex_2_closest_point.x - roof_vertices[3].x) * (roof_vertex_2_closest_point.y - roof_vertices[2].y)) -
            ((roof_vertex_2_closest_point.x - roof_vertices[2].x) * (roof_vertex_2_closest_point.y - roof_vertices[3].y))).abs
        rv_2_distance_2 = (2.0 * rv_2_triangle_2_area) / roof_length_2

        ##### Set the vertical distances from the closest skylight points (projection onto the roof) to the wall (projection onto the roof) for roof_vertex_0 and roof_vertex_2
        distance_1 = rv_0_distance_0
        distance_2 = rv_0_distance_3
        distance_3 = rv_2_distance_1
        distance_4 = rv_2_distance_2

        ##### Calculate the width and length of the daylighted area under the skylight as per NECB2011: 4.2.2.5.
        ##### Note: In the below loops, if any exterior walls has window(s), the width and length of the daylighted area under the skylight are re-calculated as per NECB2011: 4.2.2.5.
        daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1].min + [0.7 * ceiling_height, distance_4].min
        daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2].min + [0.7 * ceiling_height, distance_3].min

        ##### As noted above, the width and length of the daylighted area under the skylight are re-calculated (as per NECB2011: 4.2.2.5.), if any exterior walls has window(s).
        ##### To this end, the window_head_height should be calculated, as below:
        daylight_space.surfaces.sort.each do |curr_surface|
          if curr_surface.outsideBoundaryCondition == 'Outdoors' && curr_surface.surfaceType == 'Wall'
            wall_vertices_on_floor_x = []
            wall_vertices_on_floor_y = []
            wall_vertices = curr_surface.vertices
            if wall_vertices[0].z == wall_vertices[1].z
              wall_vertices_on_floor_x << wall_vertices[0].x
              wall_vertices_on_floor_x << wall_vertices[1].x
              wall_vertices_on_floor_y << wall_vertices[0].y
              wall_vertices_on_floor_y << wall_vertices[1].y
            elsif wall_vertices[0].z == wall_vertices[3].z
              wall_vertices_on_floor_x << wall_vertices[0].x
              wall_vertices_on_floor_x << wall_vertices[3].x
              wall_vertices_on_floor_y << wall_vertices[0].y
              wall_vertices_on_floor_y << wall_vertices[3].y
            end
            window_vertices = subsurface.vertices
            window_head_height = [window_vertices[0].z, window_vertices[1].z, window_vertices[2].z, window_vertices[3].z].max.round(2)

            ##### Calculate the exterior wall length (on the floor)
            exterior_wall_length = Math.sqrt((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1])**2.0 + (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1])**2.0)

            ##### Calculate the vertical distance of skylight_vertices[0] projection onto the roof/floor to the exterior wall
            skylight_vertex_0_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[0].y)) -
                ((wall_vertices_on_floor_x[0] - skylight_vertices[0].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs
            skylight_vertex_0_distance = (2.0 * skylight_vertex_0_triangle_area) / exterior_wall_length

            ##### Calculate the vertical distance of skylight_vertices[1] projection onto the roof/floor to the exterior wall
            skylight_vertex_1_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[1].y)) -
                ((wall_vertices_on_floor_x[0] - skylight_vertices[1].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs
            skylight_vertex_1_distance = (2.0 * skylight_vertex_1_triangle_area) / exterior_wall_length

            ##### Calculate the vertical distance of skylight_vertices[3] projection onto the roof/floor to the exterior wall
            skylight_vertex_3_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[3].y)) -
                ((wall_vertices_on_floor_x[0] - skylight_vertices[3].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs
            skylight_vertex_3_distance = (2.0 * skylight_vertex_3_triangle_area) / exterior_wall_length

            ##### Loop through the subsurfaces that has exterior windows to re-calculate the width and length of the daylighted area under the skylight
            curr_surface.subSurfaces.sort.each do |curr_subsurface|
              if curr_subsurface.subSurfaceType == 'FixedWindow' || curr_subsurface.subSurfaceType == 'OperableWindow'

                if skylight_vertex_0_distance == skylight_vertex_1_distance # skylight_01 is in parellel to the exterior wall
                  if skylight_vertex_0_distance.round(2) == distance_2.round(2)
                    daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2, distance_2 - window_head_height].min + [0.7 * ceiling_height, distance_3].min
                  elsif skylight_vertex_0_distance.round(2) == distance_3.round(2)
                    daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2].min + [0.7 * ceiling_height, distance_3, distance_3 - window_head_height].min
                  end
                elsif skylight_vertex_0_distance == skylight_vertex_3_distance # skylight_03 is in parellel to the exterior wall
                  if skylight_vertex_0_distance.round(2) == distance_1.round(2)
                    daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1, distance_1 - window_head_height].min + [0.7 * ceiling_height, distance_4].min
                  elsif skylight_vertex_0_distance.round(2) == distance_4.round(2)
                    daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1].min + [0.7 * ceiling_height, distance_4, distance_4 - window_head_height].min
                  end
                  # if skylight_vertex_0_distance == skylight_vertex_1_distance
                end

                daylighted_under_skylight_area += daylighted_under_skylight_length * daylighted_under_skylight_width
                # if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
              end
              # surface.subSurfaces.each do |subsurface|
            end
            # if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall"
          end
          # daylight_space.surfaces.each do |surface|
        end
        # if subsurface.subSurfaceType == "Skylight"
      end
      # surface.subSurfaces.each do |subsurface|
    end
    # daylight_space.surfaces.each do |surface|
  end

  return daylighted_under_skylight_area, skylight_area_weighted_vt_handle, skylight_area_sum
end
get_qaqc_table(table_name:, search_criteria: nil) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 140
def get_qaqc_table(table_name:, search_criteria: nil)
  return_objects = nil
  table = @qaqc_data['tables'][table_name]
  raise("could not find #{table_name} in qaqc table database. ") if table.nil?

  return table if search_criteria.nil? # removed table beause need to use the object['refs']

  rows = table['table']
  search_criteria.each do |key, value|
    rows = rows.select { |row| row[key] == value }
  end
  return rows
end
get_sql_table_to_json(model, report_name, report_for_string, table_name) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 4
  def get_sql_table_to_json(model, report_name, report_for_string, table_name)
    table = []
    query_row_names = "
     SELECT DISTINCT
        RowName
     FROM
        tabulardatawithstrings
      WHERE
        ReportName='#{report_name}'
      AND
        ReportForString='#{report_for_string}'
      AND
        TableName='#{table_name}'"
    row_names = model.sqlFile.get.execAndReturnVectorOfString(query_row_names).get

    # get Columns
    query_col_names = "
     SELECT DISTINCT
        ColumnName
     FROM tabulardatawithstrings
      WHERE ReportName='#{report_name}'
      AND ReportForString='#{report_for_string}'
      AND TableName='#{table_name}'"
    col_names = model.sqlFile.get.execAndReturnVectorOfString(query_col_names).get

    # get units
    query_unit_names = "
     SELECT DISTINCT
        Units
     FROM tabulardatawithstrings
      WHERE ReportName='#{report_name}'
      AND ReportForString='#{report_for_string}'
      AND TableName='#{table_name}'"
    unit_names = model.sqlFile.get.execAndReturnVectorOfString(query_unit_names).get

    row_names.each do |row|
      next if row.nil? || row == ''

      row_hash = {}
      row_hash[:name] = row
      col_names.each do |col|
        unit_names.each do |unit|
          query = "
        SELECT
          Value
        FROM
          tabulardatawithstrings
        WHERE
          ReportName='#{report_name}'
        AND
          ReportForString='#{report_for_string}'
        AND
          TableName='#{table_name}'
        AND
          RowName='#{row}'
        AND
          ColumnName='#{col}'
        AND
          Units='#{unit}'
"
          column_name = col.to_s.gsub(/\s+/, '_').downcase
          # If the column name is "additional_fuel" and the file contains a boiler with a FuelOilNo2 fuel type assume
          # the column name should be "fueloilno2".
          if column_name.include? 'additional_fuel'
            model.getPlantLoops.sort.each do |iplantloop|
              boilers = iplantloop.components.select { |icomponent| icomponent.to_BoilerHotWater.is_initialized }
              column_name = 'fueloilno2' unless boilers.select { |boiler| boiler.to_BoilerHotWater.get.fuelType.to_s == 'FuelOilNo2' }.empty?
            end
          end
          column_name += "_#{unit}" if unit != ''
          value = model.sqlFile.get.execAndReturnFirstString(query)
          next if value.empty? || value.get.nil?

          value = value.get.strip
          # check is value is a number
          if (begin
                Float(value)
              rescue StandardError
                false
              end) && value.to_f != 0
            row_hash[column_name] = value.to_f
            # Check if value is a date
          elsif unit == '' && value =~ /\d\d-\D\D\D-\d\d:\d\d/
            row_hash[column_name] = DateTime.parse(value)
            # skip if value in an empty string or a zero value
          elsif value != '' && value != '0.00'
            row_hash[column_name] = value
          end
        end
      end
      if row_hash.size > 1
        table << row_hash
      end
    end
    result = { report_name: report_name, report_for_string: report_for_string, table_name: table_name, table: table }
    return result
  end
get_sql_tables_to_json(model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 106
def get_sql_tables_to_json(model)
  sql_data = []
  sql_data << get_sql_table_to_json(model, 'AnnualBuildingUtilityPerformanceSummary', 'Entire Facility', 'End Uses')
  sql_data << get_sql_table_to_json(model, 'AnnualBuildingUtilityPerformanceSummary', 'Entire Facility', 'Site and Source Energy')
  # sql_data << get_sql_table_to_json(model, "AnnualBuildingUtilityPerformanceSummary", "Entire Facility", "On-Site Thermal Sources")
  # sql_data << get_sql_table_to_json(model, "AnnualBuildingUtilityPerformanceSummary", "Entire Facility", "Comfort and Setpoint Not Met Summary")
  # sql_data << get_sql_table_to_json(model, "InputVerificationandResultsSummary", "Entire Facility", "Window-Wall Ratio")
  # sql_data << get_sql_table_to_json(model, "InputVerificationandResultsSummary", "Entire Facility", "Conditioned Window-Wall Ratio")
  # sql_data << get_sql_table_to_json(model, "InputVerificationandResultsSummary", "Entire Facility", "Skylight-Roof Ratio")
  # sql_data << get_sql_table_to_json(model, "DemandEndUseComponentsSummary", "Entire Facility", "End Uses")
  # sql_data << get_sql_table_to_json(model, "ComponentSizingSummary", "Entire Facility", "AirLoopHVAC")
  return sql_data
end
get_standard_constant_value(constant_name:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 46
def get_standard_constant_value(constant_name:)
  puts 'do nothing'
end
get_standards_constant(name) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 117
def get_standards_constant(name)
  object = @standards_data['constants'][name]

  if object.nil? || object['value'].nil?
    raise("could not find #{name} in standards constants database. ")
  end

  return object['value']
end
get_standards_formula(name) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 127
def get_standards_formula(name)
  object = @standards_data['formulas'][name]
  raise("could not find #{name} in standards formual database. ") if object.nil? || object['value'].nil?

  return object['value']
end
get_standards_table(table_name:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 38
def get_standards_table(table_name:)
  if @standards_data['tables'][table_name].nil?
    message = "Could not find table #{table_name} in database."
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.Standards.NECB', message)
  end
  @standards_data['tables'][table_name]
end
get_surface_exp_per(floor,walls) click to toggle source

Find the exposed perimeter of a floor surface. For each side of the floor loop through the walls and find the walls that share sides with the floor. Then sum the lengths of the sides of the walls that come in contact with sides of the floor. created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 870
def get_surface_exp_per(floor,walls)
  floor_exp_per = 0.0
  vert1 = floor.vertices[0]
  # loop through the indices of the floor surface
  for index in 1..floor.vertices.size
    if index < floor.vertices.size
      vert2 = floor.vertices[index]
    else
      vert2 = floor.vertices[0]
    end
    side_length = ((vert2.x-vert1.x)**2+(vert2.y-vert1.y)**2+(vert2.z-vert1.z)**2)**0.5
    walls_exp_per = 0.0
    walls.each do |wall|
      vert3 = wall.vertices[0]
      # loop through the indices of the wall surface
      for index2 in 1..wall.vertices.size-1
        if index2 < wall.vertices.size
          vert4 = wall.vertices[index2]
        else
          vert4 = wall.vertices[0]
        end
        vert1_2_3_on_same_line = three_vertices_same_line_and_dir?(vert1,vert2,vert3)
        if vert1_2_3_on_same_line
          vert1_2_4_on_same_line = three_vertices_same_line_and_dir?(vert1,vert2,vert4)
          if vert1_2_4_on_same_line
            wall_width = ((vert4.x-vert3.x)**2+(vert4.y-vert3.y)**2+(vert4.z-vert3.z)**2)**0.5
            walls_exp_per += wall_width
          end
        end
        vert3 = vert4
      end
    end
    # increment the exposed perimeter of the floor. Limit the length of the walls in contact with the
    # side of the floor to the length of the side of the floor.
    floor_exp_per += [walls_exp_per,side_length].min
    vert1 = vert2
  end

  return floor_exp_per
end
get_weather_file_from_repo(epw_file:) click to toggle source

This method handles looking for the epw_file in the github.com/canmet-energy/btap_weather repository. It checks for the epw_file in the historical data first. If it is not there then it looks in the future weather data. If it is not there either, it throws an error. epw_file (String): The name of the epw file. The different weather files all share the same name as the epw file,

only the extension changes.
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2246
def get_weather_file_from_repo(epw_file:)
  # Get just the weather file name without the extension
  weather_loc = epw_file[0..-5]
  # Get the url of the file containing the historical weather data file names in the repository and the repository
  # folder containing the files
  historic_weather_files_loc = @standards_data['constants']['historic_weather_file_list']['value'].to_s
  historic_git_folder = @standards_data['constants']['historic_weather_folder_url']['value'].to_s
  # Get the files from the repository
  success_flag = download_and_save_file(weather_list_url: historic_weather_files_loc, weather_loc: weather_loc, git_folder: historic_git_folder)
  return if success_flag
  # If the file could not be found in the historical data look for it with the future weather data.
  puts "Could not find #{epw_file} in historical weather data files, looking in future weather data files."
  future_weather_files_loc = @standards_data['constants']['future_weather_file_list']['value'].to_s
  future_git_folder = @standards_data['constants']['future_weather_folder_url']['value'].to_s
  success_flag = download_and_save_file(weather_list_url: future_weather_files_loc, weather_loc: weather_loc, git_folder: future_git_folder)
  return if success_flag
  raise("Could not locate the following file in the canmet/btap_weather repository or could not extract the data: #{epw_file}.  Please check the spelling of the file or visit https://github.com/canmet-energy/btap_weather to see if the file exists.")
end
group_similar_zones_together(zones) click to toggle source

This method is used to determine if there are single zones that can be grouped with zones of similar loads.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1222
def group_similar_zones_together(zones)
  total_zones_input = zones.size
  array_of_array_of_zones = []
  accounted_for = []
  # Go through other zones to see if there are similar zones with similar loads on the same floor that can be grouped.
  zones.each do |zone|
    similar_array_of_zones = []
    next if accounted_for.include?(zone.name.to_s)

    similar_array_of_zones << zone
    accounted_for << zone.name.to_s
    zones.each do |zone_target|
      unless accounted_for.include?(zone_target.name.to_s)
        if are_zone_loads_similar?(zone_1: zone,
                                   zone_2: zone_target)
          similar_array_of_zones << zone_target
          accounted_for << zone_target.name.to_s
        end
      end
    end
    array_of_array_of_zones << similar_array_of_zones
  end
  total_zones_output = 0
  array_of_array_of_zones.each do |curr_zones|
    total_zones_output += curr_zones.size
  end
  # puts total_zones_output
  # puts accounted_for.sort
  # sanity check.
  if total_zones_output != total_zones_input
    # puts JSON.pretty_generate(array_of_array_of_zones)
    # puts JSON.pretty_generate(accounted_for.sort)
    raise('')
  end

  return array_of_array_of_zones
end
heat_exchanger_air_to_air_sensible_and_latent_apply_effectiveness(heat_exchanger_air_to_air_sensible_and_latent, erv_name = nil) click to toggle source

Sets the minimum effectiveness of the heat exchanger per the standard.

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 376
def heat_exchanger_air_to_air_sensible_and_latent_apply_effectiveness(heat_exchanger_air_to_air_sensible_and_latent, erv_name = nil)
  # Assumed to be sensible and latent at all flow
  # This will now get data of the erv from the json file instead of hardcoding it. Defaults to NECB2011 erv we have been using.
  erv_name = 'NECB_Default' if erv_name.nil?
  erv_info = @standards_data['tables']['erv']['table'].detect { |item| item['erv_name'] == erv_name }
  raise("Could not find #{erv_name} in #{self.class.name} class' erv.json file or it's parents. The available ervs are #{@standards_data['tables']['erv']['table'].map { |item| item['erv_name'] }}") if erv_info.nil?

  heat_exchanger_air_to_air_sensible_and_latent.setHeatExchangerType(erv_info['HeatExchangerType'])
  heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100HeatingAirFlow(erv_info['SensibleEffectivenessat100HeatingAirFlow'])
  heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100HeatingAirFlow(erv_info['LatentEffectivenessat100HeatingAirFlow'])
  heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100CoolingAirFlow(erv_info['SensibleEffectivenessat100CoolingAirFlow'])
  heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100CoolingAirFlow(erv_info['LatentEffectivenessat100CoolingAirFlow'])
  if heat_exchanger_air_to_air_sensible_and_latent.model.version < OpenStudio::VersionString.new('3.8.0')
    heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(erv_info['SensibleEffectivenessat75HeatingAirFlow'])
    heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(erv_info['LatentEffectivenessat75HeatingAirFlow'])
    heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(erv_info['SensibleEffectivenessat75CoolingAirFlow'])
    heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(erv_info['LatentEffectivenessat75CoolingAirFlow'])
  else
    heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(erv_info['SensibleEffectivenessat75HeatingAirFlow']) unless erv_info['SensibleEffectivenessat75HeatingAirFlow'].zero?
    heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(erv_info['LatentEffectivenessat75HeatingAirFlow']) unless erv_info['LatentEffectivenessat75HeatingAirFlow'].zero?
    heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(erv_info['SensibleEffectivenessat75CoolingAirFlow']) unless erv_info['SensibleEffectivenessat75CoolingAirFlow'].zero?
    heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(erv_info['LatentEffectivenessat75CoolingAirFlow']) unless erv_info['LatentEffectivenessat75CoolingAirFlow'].zero?
  end
  heat_exchanger_air_to_air_sensible_and_latent.setSupplyAirOutletTemperatureControl(erv_info['SupplyAirOutletTemperatureControl'])
  heat_exchanger_air_to_air_sensible_and_latent.setFrostControlType(erv_info['FrostControlType'])
  heat_exchanger_air_to_air_sensible_and_latent.setEconomizerLockout(erv_info['EconomizerLockout'])
  heat_exchanger_air_to_air_sensible_and_latent.setThresholdTemperature(erv_info['ThresholdTemperature'])
  heat_exchanger_air_to_air_sensible_and_latent.setInitialDefrostTimeFraction(erv_info['InitialDefrostTimeFraction'])
  update_sys_name(heat_exchanger_air_to_air_sensible_and_latent.airLoopHVAC.get, sys_hr: 'erv')

  return true
end
init_qaqc(model) click to toggle source

generates full qaqc.json

# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 155
def init_qaqc(model)
  # load the qaqc.json files
  # This is currently disabled as most tests are now done using regression and unit tests.. but we may bring this back.
  # @qaqc_data = self.load_qaqc_database_new()

  # generate base qaqc hash
  qaqc = create_base_data(model)
  # performs the qaqc on the given base qaqc hash
  # necb_qaqc(qaqc, model)
  return qaqc
end
is_a_necb_dwelling_unit?(space) click to toggle source

Check if the space spactype is a dwelling unit as per NECB.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 665
def is_a_necb_dwelling_unit?(space)
  space_type_table = @standards_data['space_types']
  space_type_data = model_find_object(space_type_table,
                                      'template' => self.class.name,
                                      'space_type' => space.spaceType.get.standardsSpaceType.get,
                                      'building_type' => space.spaceType.get.standardsBuildingType.get)

  necb_hvac_system_selection_table = @standards_data['necb_hvac_system_selection_type']
  necb_hvac_system_select = necb_hvac_system_selection_table.detect do |curr_necb_hvac_system_select|
    curr_necb_hvac_system_select['necb_hvac_system_selection_type'] == space_type_data['necb_hvac_system_selection_type'] &&
      curr_necb_hvac_system_select['min_stories'] <= space.model.getBuilding.standardsNumberOfAboveGroundStories.get &&
      curr_necb_hvac_system_select['max_stories'] >= space.model.getBuilding.standardsNumberOfAboveGroundStories.get
  end
  return necb_hvac_system_select['dwelling'] == true
end
is_an_necb_storage_space?(space) click to toggle source

Check to see if this is a wet space that the NECB does not have a specified schedule or system for. Currently hardcoded to Locker room and washroom.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 659
def is_an_necb_storage_space?(space)
  # Hack! Should replace this with a proper table lookup.
  return space.spaceType.get.standardsSpaceType.get.include?('Storage')
end
is_an_necb_wet_space?(space) click to toggle source

Check to see if this is a wet space that the NECB does not have a specified schedule or system for. Currently hardcoded to Locker room and washroom.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 652
def is_an_necb_wet_space?(space)
  # Hack! Should replace this with a proper table lookup.
  return space.spaceType.get.standardsSpaceType.get.include?('Locker room') || space.spaceType.get.standardsSpaceType.get.include?('Washroom')
end
is_an_necb_wildcard_space?(space) click to toggle source

Check to see if this is a wildcard space that the NECB does not have a specified schedule or system for.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 639
def is_an_necb_wildcard_space?(space)
  space_type_table = @standards_data['space_types']
  space_type_data = model_find_object(space_type_table,
                                      'template' => self.class.name,
                                      'space_type' => space.spaceType.get.standardsSpaceType.get,
                                      'building_type' => space.spaceType.get.standardsBuildingType.get)
  raise(space.to_s) if space_type_data.nil?

  return space_type_data['necb_hvac_system_selection_type'] == 'Wildcard'
end
load_building_type_from_library(building_type:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 323
def load_building_type_from_library(building_type:)
  osm_model_path = File.absolute_path(File.join(__FILE__, '..', '..', '..', "necb/NECB2011/data/geometry/#{building_type}.osm"))
  model = false
  if File.file?(osm_model_path)
    model = BTAP::FileIO.load_osm(osm_model_path)
    model.getBuilding.setName(building_type)
  end
  return model
end
load_qaqc_database_new() click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 120
def load_qaqc_database_new
  # Combine the data from the JSON files into a single hash
  files = Dir.glob("#{File.dirname(__FILE__)}/qaqc_data/*.json").select { |e| File.file? e }
  @qaqc_data = {}
  @qaqc_data['tables'] = {}
  files.each do |file|
    # puts "loading qaqc data from #{file}"
    data = JSON.parse(File.read(file))
    if !data['tables'].nil?
      @qaqc_data['tables'] = [*@qaqc_data['tables'], *data['tables']].to_h
    else
      @qaqc_data[data.keys.first] = data[data.keys.first]
    end
  end
  # Write test report file.
  test_result_file = File.join(File.dirname(__FILE__), '..', 'NECB2011_QAQC.json')
  File.open(test_result_file, 'w') { |f| f.write(JSON.pretty_generate(@qaqc_data)) }
  return @qaqc_data
end
load_standards_database_new() click to toggle source

Combine the data from the JSON files into a single hash Load JSON files differently depending on whether loading from the OpenStudio CLI embedded filesystem or from typical gem installation

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 53
def load_standards_database_new
  @standards_data = {}
  @standards_data['tables'] = {}

  if __dir__[0] == ':' # Running from OpenStudio CLI
    embedded_files_relative('../common', /.*\.json/).each do |file|
      data = JSON.parse(EmbeddedScripting.getFileAsString(file))
      if !data['tables'].nil?
        @standards_data['tables'] = [*@standards_data['tables'], *data['tables']].to_h
      else
        @standards_data[data.keys.first] = data[data.keys.first]
      end
    end
  else
    path = "#{File.dirname(__FILE__)}/../common/"
    raise 'Could not find common folder' unless Dir.exist?(path)

    files = Dir.glob("#{path}/*.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
      else
        @standards_data[data.keys.first] = data[data.keys.first]
      end
    end
  end

  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
      else
        @standards_data[data.keys.first] = data[data.keys.first]
      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
      else
        @standards_data[data.keys.first] = data[data.keys.first]
      end
    end
  end
  # Write database to file.
  # File.open(File.join(File.dirname(__FILE__), '..', 'NECB2011.json'), 'w') {|f| f.write(JSON.pretty_generate(@standards_data))}

  return @standards_data
end
look_up_csv_data(csv_fname, search_criteria) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1911
def look_up_csv_data(csv_fname, search_criteria)
  options = { headers: :first_row,
              converters: [:numeric] }
  unless File.exist?(csv_fname)
    raise "File: [#{csv_fname}] Does not exist"
  end

  # we'll save the matches here
  matches = nil
  # save a copy of the headers
  headers = nil
  CSV.open(csv_fname, 'r', options) do |csv|
    # Since CSV includes Enumerable we can use 'find_all'
    # which will return all the elements of the Enumerble for
    # which the block returns true

    matches = csv.find_all do |row|
      match = true
      search_criteria.keys.each do |key|
        match &&= (row[key].strip == search_criteria[key].strip)
      end
      match
    end
    headers = csv.headers
  end
  # puts matches
  raise('More than one match') if matches.size > 1

  puts "Zero matches found for [#{search_criteria}]" if matches.empty?
  # return matches[0]
  return matches[0]
end
max_fwdr(hdd) click to toggle source

@author phylroy.lopez@nrcan.gc.ca @param hdd [Float] @return [Double] a constant float

# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 286
def max_fwdr(hdd)
  #  get formula from json database.
  return eval(get_standards_formula('fdwr_formula'))
end
merge_recursively(a, b) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 102
def merge_recursively(a, b)
  a.merge(b) { |key, a_item, b_item| merge_recursively(a_item, b_item) }
end
model_add_construction_set_from_osm(model:, construction_set_name: 'BTAP-Mass', osm_path: File.absolute_path(File.join(__FILE__, '..', '..', 'common/construction_defaults.osm'))) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 524
def model_add_construction_set_from_osm(model:,
                                        construction_set_name: 'BTAP-Mass',
                                        osm_path: File.absolute_path(File.join(__FILE__, '..', '..', 'common/construction_defaults.osm')))
  # load resources model
  construction_library = BTAP::FileIO.load_osm(osm_path)

  if !construction_library.getDefaultConstructionSetByName(construction_set_name.to_s).is_initialized
    runner.registerError('Did not find the expected construction in library.')
    return false
  end
  selected_construction_set = construction_library.getDefaultConstructionSetByName(construction_set_name.to_s).get
  new_construction_set = selected_construction_set.clone(model).to_DefaultConstructionSet.get
  return new_construction_set
end
model_add_constructions(model) click to toggle source

Adds code-minimum constructions based on the building type as defined in the OpenStudio_Standards_construction_sets.json file. Where there is a separate construction set specified for the individual space type, this construction set will be created and applied to this space type, overriding the whole-building construction set.

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 471
def model_add_constructions(model)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying constructions')

  # Assign construction to adiabatic construction
  # Assign a material to all internal mass objects
  assign_contruction_to_adiabatic_surfaces(model)
  # The constructions lookup table uses a slightly different list of
  # building types.
  apply_building_default_constructionset(model)
  # Make a construction set for each space type, if one is specified
  # apply_default_constructionsets_to_spacetypes(climate_zone, model)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying constructions')
  return true
end
model_add_daylighting_controls(model:, daylighting_type:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1346
def model_add_daylighting_controls(model:, daylighting_type:)

  return if daylighting_type == 'none'
  ##### Find spaces with exterior fenestration including fixed window, operable window, and skylight.
  daylight_spaces = []
  daylight_spaces_target_illuminance_setpoint_hash = {}
  model.getSpaces.sort.each do |space|
    space.surfaces.sort.each do |surface|
      surface.subSurfaces.sort.each do |subsurface|
        if subsurface.outsideBoundaryCondition == 'Outdoors' &&
          (subsurface.subSurfaceType == 'FixedWindow' ||
            subsurface.subSurfaceType == 'OperableWindow' ||
            subsurface.subSurfaceType == 'Skylight')
          daylight_spaces << space
          space_type = space.spaceType.get
          space_type_name = space.spaceType.get.name.to_s
          space_type_name = space_type_name.gsub('Space Function', '')
          # puts "space_type_name is #{space_type_name}"

          # Gather minimum illuminance level as per NECB
          lux_spacetype_data = @standards_data['tables']['space_types']['table']
          standards_building_type = space_type.standardsBuildingType.is_initialized ? space_type.standardsBuildingType.get : nil
          standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil
          lux_space_type_properties = lux_spacetype_data.detect { |s| (s['building_type'] == standards_building_type) && (s['space_type'] == standards_space_type) }
          if lux_space_type_properties.nil?
            raise("#{standards_building_type} for #{standards_space_type} was not found please verify the target_illuminance_setpoint database names match the space type names.")
          end

          target_illuminance_setpoint = lux_space_type_properties['target_illuminance_setpoint'].to_f
          daylight_spaces_target_illuminance_setpoint_hash[space.name.to_s] = target_illuminance_setpoint

          # subsurface.outsideBoundaryCondition == "Outdoors" && (subsurface.subSurfaceType == "FixedWindow" || "OperableWindow")
        end
        # surface.subSurfaces.each do |subsurface|
      end
      # space.surfaces.each do |surface|
    end
    # model.getSpaces.sort.each do |space|
  end

  ##### Remove duplicate spaces from the "daylight_spaces" array, as a daylighted space may have various fenestration types.
  daylight_spaces = daylight_spaces.uniq
  # puts "daylight_spaces are #{daylight_spaces}"

  if daylighting_type.nil? || daylighting_type == false || daylighting_type == 'none' || daylighting_type == 'NECB_Default' # puts daylighting sensors in the spaces as per NECB requirements; so some spaces may not have sensors

    ##### Create hashes for "Primary Sidelighted Areas", "Sidelighting Effective Aperture", "Daylighted Area Under Skylights",
    ##### and "Skylight Effective Aperture" for the whole model.
    ##### Each of these hashes will be used later in this function (i.e. model_add_daylighting_controls)
    ##### to provide a dictionary of daylighted space names and the associated value (i.e. daylighted area or effective aperture).
    primary_sidelighted_area_hash = {}
    sidelighting_effective_aperture_hash = {}
    daylighted_area_under_skylights_hash = {}
    skylight_effective_aperture_hash = {}

    ##### Calculate "Primary Sidelighted Areas" AND "Sidelighting Effective Aperture" as per NECB2011. # @todo consider removing overlapped sidelighted area
    daylight_spaces.sort.each do |daylight_space|
      primary_sidelighted_area = 0.0
      area_weighted_vt_handle = 0.0
      area_weighted_vt = 0.0
      window_area_sum = 0.0

      ##### Calculate floor area of the daylight_space and get floor vertices of the daylight_space (to be used for the calculation of daylight_space depth)
      floor_surface = nil
      floor_area = 0.0
      floor_vertices = []
      daylight_space.surfaces.sort.each do |surface|
        if surface.surfaceType == 'Floor'
          floor_surface = surface
          floor_area += surface.netArea
          floor_vertices << surface.vertices
        end
      end

      ##### Loop through the surfaces of each daylight_space to calculate primary_sidelighted_area and
      ##### area-weighted visible transmittance and window_area_sum which are used to calculate sidelighting_effective_aperture
      primary_sidelighted_area, area_weighted_vt_handle, window_area_sum =
        get_parameters_sidelighting(daylight_space: daylight_space,
                                    floor_surface: floor_surface,
                                    floor_vertices: floor_vertices,
                                    floor_area: floor_area,
                                    primary_sidelighted_area: primary_sidelighted_area,
                                    area_weighted_vt_handle: area_weighted_vt_handle,
                                    window_area_sum: window_area_sum)

      primary_sidelighted_area_hash[daylight_space.name.to_s] = primary_sidelighted_area

      ##### Calculate area-weighted VT of glazing (this is used to calculate sidelighting effective aperture; see NECB2011: 4.2.2.10.).
      area_weighted_vt = area_weighted_vt_handle / window_area_sum
      sidelighting_effective_aperture_hash[daylight_space.name.to_s] = window_area_sum * area_weighted_vt / primary_sidelighted_area
      # daylight_spaces.each do |daylight_space|
    end

    ##### Calculate "Daylighted Area Under Skylights" AND "Skylight Effective Aperture"
    daylight_spaces.sort.each do |daylight_space|
      # puts daylight_space.name.to_s
      skylight_area = 0.0
      skylight_area_weighted_vt_handle = 0.0
      skylight_area_weighted_vt = 0.0
      skylight_area_sum = 0.0
      daylighted_under_skylight_area = 0.0

      ##### Loop through the surfaces of each daylight_space to calculate daylighted_area_under_skylights and skylight_effective_aperture for each daylight_space
      daylighted_under_skylight_area, skylight_area_weighted_vt_handle, skylight_area_sum =
        get_parameters_skylight(daylight_space: daylight_space,
                                skylight_area_weighted_vt_handle: skylight_area_weighted_vt_handle,
                                skylight_area_sum: skylight_area_sum,
                                daylighted_under_skylight_area: daylighted_under_skylight_area)

      daylighted_area_under_skylights_hash[daylight_space.name.to_s] = daylighted_under_skylight_area

      ##### Calculate skylight_effective_aperture as per NECB2011: 4.2.2.7.
      ##### Note that it was assumed that the skylight is flush with the ceiling. Therefore, area-weighted average well factor (WF) was set to 0.9 in the below Equation.
      skylight_area_weighted_vt = skylight_area_weighted_vt_handle / skylight_area_sum
      skylight_effective_aperture_hash[daylight_space.name.to_s] = 0.85 * skylight_area_sum * skylight_area_weighted_vt * 0.9 / daylighted_under_skylight_area
      # daylight_spaces.each do |daylight_space|
    end
    # puts "primary_sidelighted_area_hash is #{primary_sidelighted_area_hash}"
    # puts sidelighting_effective_aperture_hash
    # puts daylighted_area_under_skylights_hash
    # puts skylight_effective_aperture_hash

    ##### Find office spaces >= 25m2 among daylight_spaces
    offices_larger_25m2 = []
    daylight_spaces.sort.each do |daylight_space|
      ## The following steps are for in case an office has multiple floors at various heights
      ## 1. Calculate number of floors of each daylight_space
      ## 2. Find the lowest z among all floors of each daylight_space
      ## 3. Find lowest floors of each daylight_space (these floors are at the same level)
      ## 4. Calculate 'daylight_space_area' as sum of area of all the lowest floors of each daylight_space, and gather the vertices of all the lowest floors of each daylight_space

      ## 1. Calculate number of floors of daylight_space
      floor_vertices = []
      number_floor = 0
      daylight_space.surfaces.sort.each do |surface|
        if surface.surfaceType == 'Floor'
          floor_vertices << surface.vertices
          number_floor += 1
        end
      end

      ## 2. Loop through all floors of daylight_space, and find the lowest z among all floors of daylight_space
      lowest_floor_z = []
      highest_floor_z = []
      for i in 0..number_floor - 1
        if i == 0
          lowest_floor_z = floor_vertices[i][0].z
          highest_floor_z = floor_vertices[i][0].z
        else
          if lowest_floor_z > floor_vertices[i][0].z
            lowest_floor_z = floor_vertices[i][0].z
          else
            lowest_floor_z = lowest_floor_z
          end
          if highest_floor_z < floor_vertices[i][0].z
            highest_floor_z = floor_vertices[i][0].z
          else
            highest_floor_z = highest_floor_z
          end
        end
      end

      ## 3 and 4. Loop through all floors of daylight_space, and calculate the sum of area of all the lowest floors of daylight_space,
      ## and gather the vertices of all the lowest floors of daylight_space
      daylight_space_area = 0
      lowest_floors_vertices = []
      floor_vertices = []
      daylight_space.surfaces.sort.each do |surface|
        if surface.surfaceType == 'Floor'
          floor_vertices = surface.vertices
          if floor_vertices[0].z == lowest_floor_z
            lowest_floors_vertices << floor_vertices
            daylight_space_area += surface.netArea
          end
        end
      end

      if daylight_space.spaceType.get.standardsSpaceType.get.to_s == 'Office - enclosed' && daylight_space_area >= 25.0
        offices_larger_25m2 << daylight_space.name.to_s
      end
    end

    ##### find daylight_spaces which do not need daylight sensor controls based on the primary_sidelighted_area as per NECB2011: 4.2.2.8.
    ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their primary_sidelighted_area <= 100m2), as per NECB2011: 4.2.2.2.
    daylight_spaces_exception = []
    primary_sidelighted_area_hash.sort.each do |key_daylight_space_name, value_primary_sidelighted_area|
      if value_primary_sidelighted_area <= 100.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
        daylight_spaces_exception << key_daylight_space_name
      end
    end

    ##### find daylight_spaces which do not need daylight sensor controls based on the sidelighting_effective_aperture as per NECB2011: 4.2.2.8.
    ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their sidelighting_effective_aperture <= 10%), as per NECB2011: 4.2.2.2.
    sidelighting_effective_aperture_hash.sort.each do |key_daylight_space_name, value_sidelighting_effective_aperture|
      if value_sidelighting_effective_aperture <= 0.1 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
        daylight_spaces_exception << key_daylight_space_name
      end
    end

    ##### find daylight_spaces which do not need daylight sensor controls based on the daylighted_area_under_skylights as per NECB2011: 4.2.2.4.
    ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their daylighted_area_under_skylights <= 400m2), as per NECB2011: 4.2.2.2.
    daylighted_area_under_skylights_hash.sort.each do |key_daylight_space_name, value_daylighted_area_under_skylights|
      if value_daylighted_area_under_skylights <= 400.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
        daylight_spaces_exception << key_daylight_space_name
      end
    end

    ##### find daylight_spaces which do not need daylight sensor controls based on the skylight_effective_aperture criterion as per NECB2011: 4.2.2.4.
    ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their skylight_effective_aperture <= 0.6%), as per NECB2011: 4.2.2.2.
    skylight_effective_aperture_hash.sort.each do |key_daylight_space_name, value_skylight_effective_aperture|
      if value_skylight_effective_aperture <= 0.006 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
        daylight_spaces_exception << key_daylight_space_name
      end
    end
    # puts daylight_spaces_exception

    ##### Loop through the daylight_spaces and exclude the daylight_spaces that do not meet the criteria (see above) as per NECB2011: 4.2.2.4. and 4.2.2.8.
    daylight_spaces_exception.sort.each do |daylight_space_exception|
      daylight_spaces.sort.each do |daylight_space|
        if daylight_space.name.to_s == daylight_space_exception
          daylight_spaces.delete(daylight_space)
        end
      end
    end
    # puts daylight_spaces

    # elsif daylighting_type == 'add_daylighting_controls' # puts daylighting sensors in all spaces regardless of NECB requirements

  end  #if daylighting_type.nil? || daylighting_type == false || daylighting_type == 'none' || daylighting_type == 'NECB_Default'

  ##### Create one daylighting sensor and put it at the center of each daylight_space if the space area < 250m2;
  ##### otherwise, create two daylight sensors, divide the space into two parts and put each of the daylight sensors at the center of each part of the space.
  daylight_spaces.sort.each do |daylight_space|
    # puts daylight_space.name.to_s
    ##### 1. Calculate number of floors of each daylight_space
    ##### 2. Find the lowest z among all floors of each daylight_space
    ##### 3. Find lowest floors of each daylight_space (these floors are at the same level)
    ##### 4. Calculate 'daylight_space_area' as sum of area of all the lowest floors of each daylight_space, and gather the vertices of all the lowest floors of each daylight_space
    ##### 5. Find min and max of x and y among vertices of all the lowest floors of each daylight_space

    ##### Calculate number of floors of daylight_space
    floor_vertices = []
    number_floor = 0
    daylight_space.surfaces.sort.each do |surface|
      if surface.surfaceType == 'Floor'
        floor_vertices << surface.vertices
        number_floor += 1
      end
    end

    ##### Loop through all floors of daylight_space, and find the lowest z among all floors of daylight_space
    lowest_floor_z = []
    highest_floor_z = []
    for i in 0..number_floor - 1
      if i == 0
        lowest_floor_z = floor_vertices[i][0].z
        highest_floor_z = floor_vertices[i][0].z
      else
        if lowest_floor_z > floor_vertices[i][0].z
          lowest_floor_z = floor_vertices[i][0].z
        else
          lowest_floor_z = lowest_floor_z
        end
        if highest_floor_z < floor_vertices[i][0].z
          highest_floor_z = floor_vertices[i][0].z
        else
          highest_floor_z = highest_floor_z
        end
      end
    end
    # puts lowest_floor_z

    ##### Loop through all floors of daylight_space, and calculate the sum of area of all the lowest floors of daylight_space,
    ##### and gather the vertices of all the lowest floors of daylight_space
    daylight_space_area = 0
    lowest_floors_vertices = []
    floor_vertices = []
    daylight_space.surfaces.sort.each do |surface|
      if surface.surfaceType == 'Floor'
        floor_vertices = surface.vertices
        if floor_vertices[0].z == lowest_floor_z
          lowest_floors_vertices << floor_vertices
          daylight_space_area += surface.netArea
        end
      end
    end
    # puts daylight_space.name.to_s
    # puts number_floor
    # puts lowest_floors_vertices
    # puts daylight_space_area

    ##### Loop through all lowest floors of daylight_space and find the min and max of x and y among their vertices
    xmin = lowest_floors_vertices[0][0].x
    ymin = lowest_floors_vertices[0][0].y
    xmax = lowest_floors_vertices[0][0].x
    ymax = lowest_floors_vertices[0][0].y
    zmin = lowest_floor_z
    for i in 0..lowest_floors_vertices.count - 1 # this loops through each of the lowers floors of daylight_space
      for j in 0..lowest_floors_vertices[i].count - 1 # this loops through each of vertices of each of the lowers floors of daylight_space

        if xmin > lowest_floors_vertices[i][j].x
          xmin = lowest_floors_vertices[i][j].x
        end
        if ymin > lowest_floors_vertices[i][j].y
          ymin = lowest_floors_vertices[i][j].y
        end
        if xmax < lowest_floors_vertices[i][j].x
          xmax = lowest_floors_vertices[i][j].x
        end
        if ymax < lowest_floors_vertices[i][j].y
          ymax = lowest_floors_vertices[i][j].y
        end
      end
    end
    # puts daylight_space.name.to_s
    # puts xmin
    # puts xmax
    # puts ymin
    # puts ymax

    ##### Get the thermal zone of daylight_space (this is used later to assign daylighting sensor)
    zone = daylight_space.thermalZone
    # puts "zone name is #{zone}"
    if !zone.empty?
      zone = daylight_space.thermalZone.get
      ##### Get the floor of the daylight_space
      floors = []
      daylight_space.surfaces.sort.each do |surface|
        if surface.surfaceType == 'Floor'
          floors << surface
        end
      end

      ##### Set daylighting controls illuminance setpoint and number of stepped control steps
      number_of_stepped_control_steps = 2   ##### Note that the minimum number of stepped control steps is two steps as per NECB2011.
      illuminance_setpoint =  daylight_spaces_target_illuminance_setpoint_hash.select {|key| key == daylight_space.name.to_s }
      illuminance_setpoint = illuminance_setpoint[daylight_space.name.to_s]

      ##### Create daylighting sensor control
      ##### NOTE: NECB2011 has some requirements on the number of sensors in spaces based on the area of the spaces.
      ##### However, EnergyPlus/OpenStudio allows to put maximum two built-in sensors in each thermal zone rather than in each space.
      ##### Since a thermal zone may include several spaces which are not next to each other on the same floor, or
      ##### a thermal zone may include spaces on different floors, a simplified method has been used to create a daylighting sensor.
      ##### So, in each thermal zone, only one daylighting sensor has been created even if the area of that thermal zone requires more than one daylighting sensor.
      ##### Also, it has been assumed that a thermal zone includes spaces which are next to each other and are on the same floor.
      ##### Furthermore, the one daylighting sensor in each thermal zone (where the thermal zone needs daylighting sensor),
      ##### the sensor has been put at the intersection of the minimum and maximum x and y of the lowest floor of that thermal zones.
      sensor = OpenStudio::Model::DaylightingControl.new(daylight_space.model)
      sensor.setName("#{daylight_space.name} daylighting control")
      sensor.setSpace(daylight_space)
      sensor.setIlluminanceSetpoint(illuminance_setpoint)
      sensor.setLightingControlType('Stepped')
      sensor.setNumberofSteppedControlSteps(number_of_stepped_control_steps)
      x_pos = (xmin + xmax) / 2.0
      y_pos = (ymin + ymax) / 2.0
      z_pos = zmin + 0.8 # put it 0.8 meter above the floor
      sensor_vertex = OpenStudio::Point3d.new(x_pos, y_pos, z_pos)
      sensor.setPosition(sensor_vertex)
      zone.setPrimaryDaylightingControl(sensor)
      zone.setFractionofZoneControlledbyPrimaryDaylightingControl(1.0)
      # if !zone.empty?
    end
    # daylight_spaces.each do |daylight_space|
  end # END if daylighting_controls_type.nil? || daylighting_controls_type == false || daylighting_controls_type == 'none' || daylighting_controls_type == 'NECB_Default'

end
model_add_hvac(model:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2
def model_add_hvac(model:)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding HVAC')
  system_fuel_defaults = get_canadian_system_defaults_by_weatherfile_name(model)
  necb_autozone_and_autosystem(model: model, runner: nil, use_ideal_air_loads: false, system_fuel_defaults: system_fuel_defaults)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding HVAC')
  return true
end
model_add_loads(model, lights_type, lights_scale) click to toggle source

Adds the loads and associated schedules for each space type as defined in the OpenStudio_Standards_space_types.json file. This includes lights, plug loads, occupants, ventilation rate requirements, infiltration, gas equipment (for kitchens, etc.) and typical schedules for each. Some loads are governed by the standard, others are typical values pulled from sources such as the DOE Reference and DOE Prototype Buildings.

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

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1904
def model_add_loads(model, lights_type, lights_scale)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying space types (loads)')

  # Loop through all the space types currently in the model,
  # which are placeholders, and give them appropriate loads and schedules
  model.getSpaceTypes.sort.each do |space_type|
    # Rendering color
    space_type_apply_rendering_color(space_type)

    # Loads
    space_type_apply_internal_loads(space_type: space_type, lights_type: lights_type, lights_scale: lights_scale)

    # Schedules
    space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, true)
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying space types (loads)')

  return true
end
model_add_schedule(model, schedule_name) click to toggle source

Create a schedule from the openstudio standards dataset and add it to the model.

@param schedule_name [String} name of the schedule @return [ScheduleRuleset] the resulting schedule ruleset @todo make return an OptionalScheduleRuleset

Calls superclass method Standard#model_add_schedule
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 113
def model_add_schedule(model, schedule_name)
  super(model, schedule_name)
end
model_add_swh(model:, swh_fueltype: 'DefaultFuel', shw_scale:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 2
def model_add_swh(model:, swh_fueltype: 'DefaultFuel', shw_scale:)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding Service Water Heating')
  # Get default fuel based on epw location province.
  if swh_fueltype == 'DefaultFuel'
    epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get)
    swh_fueltype = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set']
  end

  # Calculate the tank size and service water pump information
  shw_sizing = auto_size_shw_capacity(model: model, shw_scale: shw_scale)
  if shw_sizing['loop_peak_flow_rate_SI'] == 0
    # Only add a shw_loop if at least one space calls for shw.  If no space calls for shw put out a warning but do not
    # add a shw loop.
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'No Service Water Heating Added')
    return true
  else
    shw_pump_head = auto_size_shw_pump_head(model, default: false)
  end

  # Add the main service water heating loop
  shw_pump_motor_eff = 0.9

  main_swh_loop = model_add_swh_loop(model,
                                     'Main Service Water Loop',
                                     nil,
                                     shw_sizing['max_temp_SI'],
                                     shw_pump_head,
                                     shw_pump_motor_eff,
                                     shw_sizing['tank_capacity_SI'],
                                     shw_sizing['tank_volume_SI'],
                                     swh_fueltype,
                                     shw_sizing['parasitic_loss'],
                                     nil)

  # Note that when water use equipment is assigned to spaces then the water used by the equipment is multiplied by
  # the space (ultimately thermal zone) multiplier.  Note that there is a separate water use equipment multiplier
  # as well which is different than the space (ultimately thermal zone) multiplier.
  shw_sizing['spaces_w_dhw'].each { |space| model_add_swh_end_uses_by_spaceonly(model, space, main_swh_loop) }
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding Service Water Heating')

  return true
end
model_add_swh_end_uses_by_spaceonly(model, space, swh_loop) click to toggle source

This method will add an swh water fixture to the model for the space. if the it will return a water fixture object, or NIL if there is no water load at all.

@param model [OpenStudio::Model::Model] OpenStudio model object @param space [Hash] hash of shw space information @param swh_loop [OpenStudio::Model::PlantLoop] plant loop to add swh @return [OpenStudio::Model::WaterUseEquipment] water use equipment

# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 52
def model_add_swh_end_uses_by_spaceonly(model, space, swh_loop)
  # Water use connection
  swh_connection = OpenStudio::Model::WaterUseConnections.new(model)

  # Water fixture definition
  water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)

  # water_use_sensible_frac_sch = OpenStudio::Model::ScheduleConstant.new(self)
  # water_use_sensible_frac_sch.setValue(0.2)
  # water_use_latent_frac_sch = OpenStudio::Model::ScheduleConstant.new(self)
  # water_use_latent_frac_sch.setValue(0.05)
  # Note that when water use equipment is assigned to spaces then the water used by the equipment is multiplied by the
  # space (ultimately thermal zone) multiplier.  Note that there is a separate water use equipment multiplier as well
  # which is different than the space (ultimately thermal zone) multiplier.
  rated_flow_rate_gal_per_min = OpenStudio.convert(space['shw_peakflow_ind_SI'], 'm^3/s', 'gal/min').get
  water_use_sensible_frac_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  water_use_sensible_frac_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.2)
  water_use_latent_frac_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  water_use_latent_frac_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.05)
  water_fixture_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
  water_fixture_def.setLatentFractionSchedule(water_use_latent_frac_sch)
  water_fixture_def.setPeakFlowRate(space['shw_peakflow_ind_SI'])
  water_fixture_def.setName("#{space['shw_spaces'].name.to_s.capitalize} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gal/min")
  # Target mixed water temperature
  mixed_water_temp_c = space['shw_temp_SI']
  mixed_water_temp_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  mixed_water_temp_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), mixed_water_temp_c)
  water_fixture_def.setTargetTemperatureSchedule(mixed_water_temp_sch)

  # Water use equipment
  water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
  schedule = model_add_schedule(model, space['shw_sched'])
  water_fixture.setFlowRateFractionSchedule(schedule)
  water_fixture.setName("#{space['shw_spaces'].name.to_s.capitalize} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gal/min")
  swh_connection.addWaterUseEquipment(water_fixture)
  # Assign water fixture to a space
  water_fixture.setSpace(space['shw_spaces']) if model_attach_water_fixtures_to_spaces?(model)

  # Connect the water use connection to the SWH loop
  swh_loop.addDemandBranchForComponent(swh_connection)
  return water_fixture
end
model_apply_sizing_parameters(model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1516
def model_apply_sizing_parameters(model)
  model.getSizingParameters.setHeatingSizingFactor(get_standards_constant('sizing_factor_max_heating'))
  model.getSizingParameters.setCoolingSizingFactor(get_standards_constant('sizing_factor_max_cooling'))
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Set sizing factors to #{get_standards_constant('sizing_factor_max_heating')} for heating and #{get_standards_constant('sizing_factor_max_heating')} for cooling.")
end
model_apply_standard(model:, tbd_option: nil, tbd_interpolate: nil, epw_file:, custom_weather_folder: nil, sizing_run_dir: Dir.pwd, necb_reference_hp: false, necb_reference_hp_supp_fuel: 'DefaultFuel', primary_heating_fuel: 'DefaultFuel', dcv_type: 'NECB_Default', lights_type: 'NECB_Default', lights_scale: 'NECB_Default', daylighting_type: 'NECB_Default', ecm_system_name: 'NECB_Default', ecm_system_zones_map_option: 'NECB_Default', erv_package: 'NECB_Default', boiler_eff: nil, furnace_eff: nil, unitary_cop: nil, shw_eff: nil, ext_wall_cond: nil, ext_floor_cond: nil, ext_roof_cond: nil, ground_wall_cond: nil, ground_floor_cond: nil, ground_roof_cond: nil, door_construction_cond: nil, fixed_window_cond: nil, glass_door_cond: nil, overhead_door_cond: nil, skylight_cond: nil, glass_door_solar_trans: nil, fixed_wind_solar_trans: nil, skylight_solar_trans: nil, fdwr_set: nil, srr_set: nil, rotation_degrees: nil, scale_x: nil, scale_y: nil, scale_z: nil, nv_type: nil, nv_opening_fraction: nil, nv_temp_out_min: nil, nv_delta_temp_in_out: nil, pv_ground_type: nil, pv_ground_total_area_pv_panels_m2: nil, pv_ground_tilt_angle: nil, pv_ground_azimuth_angle: nil, pv_ground_module_description: nil, chiller_type: nil, occupancy_loads_scale: nil, electrical_loads_scale: nil, oa_scale: nil, infiltration_scale: nil, output_variables: nil, shw_scale: nil, output_meters: nil, airloop_economizer_type: nil, baseline_system_zones_map_option: nil, necb_hdd: true) click to toggle source

Created this method so that additional methods can be addded for bulding the prototype model in later code versions without modifying the build_protoype_model method or copying it wholesale for a few changes.

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 335
def model_apply_standard(model:,
                         tbd_option: nil,
                         tbd_interpolate: nil,
                         epw_file:,
                         custom_weather_folder: nil,
                         sizing_run_dir: Dir.pwd,
                         necb_reference_hp: false,
                         necb_reference_hp_supp_fuel: 'DefaultFuel',
                         primary_heating_fuel: 'DefaultFuel',
                         dcv_type: 'NECB_Default',
                         lights_type: 'NECB_Default',
                         lights_scale: 'NECB_Default',
                         daylighting_type: 'NECB_Default',
                         ecm_system_name: 'NECB_Default',
                         ecm_system_zones_map_option: 'NECB_Default',
                         erv_package: 'NECB_Default',
                         boiler_eff: nil,
                         furnace_eff: nil,
                         unitary_cop: nil,
                         shw_eff: nil,
                         ext_wall_cond: nil,
                         ext_floor_cond: nil,
                         ext_roof_cond: nil,
                         ground_wall_cond: nil,
                         ground_floor_cond: nil,
                         ground_roof_cond: nil,
                         door_construction_cond: nil,
                         fixed_window_cond: nil,
                         glass_door_cond: nil,
                         overhead_door_cond: nil,
                         skylight_cond: nil,
                         glass_door_solar_trans: nil,
                         fixed_wind_solar_trans: nil,
                         skylight_solar_trans: nil,
                         fdwr_set: nil,
                         srr_set: nil,
                         rotation_degrees: nil,
                         scale_x: nil,
                         scale_y: nil,
                         scale_z: nil,
                         nv_type: nil,
                         nv_opening_fraction: nil,
                         nv_temp_out_min: nil,
                         nv_delta_temp_in_out: nil,
                         pv_ground_type: nil,
                         pv_ground_total_area_pv_panels_m2: nil,
                         pv_ground_tilt_angle: nil,
                         pv_ground_azimuth_angle: nil,
                         pv_ground_module_description: nil,
                         chiller_type: nil,
                         occupancy_loads_scale: nil,
                         electrical_loads_scale: nil,
                         oa_scale: nil,
                         infiltration_scale: nil,
                         output_variables: nil,
                         shw_scale: nil,
                         output_meters: nil,
                         airloop_economizer_type: nil,
                         baseline_system_zones_map_option: nil,
                         necb_hdd: true)
  self.fuel_type_set = SystemFuels.new()
  self.fuel_type_set.set_defaults(standards_data: @standards_data, primary_heating_fuel: primary_heating_fuel)
  clean_and_scale_model(model: model, rotation_degrees: rotation_degrees, scale_x: scale_x, scale_y: scale_y, scale_z: scale_z)
  fdwr_set = convert_arg_to_f(variable: fdwr_set, default: -1)
  srr_set = convert_arg_to_f(variable: srr_set, default: -1)
  necb_hdd = convert_arg_to_bool(variable: necb_hdd, default: true)

  # Ensure the volume calculation in all spaces is done automatically
  model.getSpaces.sort.each do |space|
    space.autocalculateVolume
  end

  apply_weather_data(model: model, epw_file: epw_file, custom_weather_folder: custom_weather_folder)
  apply_loads(model: model,
              lights_type: lights_type,
              lights_scale: lights_scale,
              occupancy_loads_scale: occupancy_loads_scale,
              electrical_loads_scale: electrical_loads_scale,
              oa_scale: oa_scale)
  apply_envelope(model: model,
                 ext_wall_cond: ext_wall_cond,
                 ext_floor_cond: ext_floor_cond,
                 ext_roof_cond: ext_roof_cond,
                 ground_wall_cond: ground_wall_cond,
                 ground_floor_cond: ground_floor_cond,
                 ground_roof_cond: ground_roof_cond,
                 door_construction_cond: door_construction_cond,
                 fixed_window_cond: fixed_window_cond,
                 glass_door_cond: glass_door_cond,
                 overhead_door_cond: overhead_door_cond,
                 skylight_cond: skylight_cond,
                 glass_door_solar_trans: glass_door_solar_trans,
                 fixed_wind_solar_trans: fixed_wind_solar_trans,
                 skylight_solar_trans: skylight_solar_trans,
                 infiltration_scale: infiltration_scale,
                 necb_hdd: necb_hdd)
  apply_fdwr_srr_daylighting(model: model,
                             fdwr_set: fdwr_set,
                             srr_set: srr_set,
                             necb_hdd: necb_hdd)
  apply_thermal_bridging(model: model,
                         tbd_option: tbd_option,
                         tbd_interpolate: tbd_interpolate,
                         wall_U: ext_wall_cond,
                         floor_U: ext_floor_cond,
                         roof_U: ext_roof_cond)
  apply_auto_zoning(model: model,
                    sizing_run_dir: sizing_run_dir,
                    lights_type: lights_type,
                    lights_scale: lights_scale)
  apply_kiva_foundation(model)
  apply_systems_and_efficiencies(model: model,
                                 sizing_run_dir: sizing_run_dir,
                                 primary_heating_fuel: primary_heating_fuel,
                                 dcv_type: dcv_type,
                                 ecm_system_name: ecm_system_name,
                                 ecm_system_zones_map_option: ecm_system_zones_map_option,
                                 erv_package: erv_package,
                                 boiler_eff: boiler_eff,
                                 unitary_cop: unitary_cop,
                                 furnace_eff: furnace_eff,
                                 shw_eff: shw_eff,
                                 daylighting_type: daylighting_type,
                                 nv_type: nv_type,
                                 nv_opening_fraction: nv_opening_fraction,
                                 nv_temp_out_min: nv_temp_out_min,
                                 nv_delta_temp_in_out: nv_delta_temp_in_out,
                                 pv_ground_type: pv_ground_type,
                                 pv_ground_total_area_pv_panels_m2: pv_ground_total_area_pv_panels_m2,
                                 pv_ground_tilt_angle: pv_ground_tilt_angle,
                                 pv_ground_azimuth_angle: pv_ground_azimuth_angle,
                                 pv_ground_module_description: pv_ground_module_description,
                                 chiller_type: chiller_type,
                                 shw_scale: shw_scale,
                                 airloop_economizer_type: airloop_economizer_type,
                                 baseline_system_zones_map_option: baseline_system_zones_map_option)
  self.set_output_variables(model: model, output_variables: output_variables)
  self.set_output_meters(model: model, output_meters: output_meters)

  return model
end
model_attach_water_fixtures_to_spaces?(model) click to toggle source

Determine whether or not water fixtures are attached to spaces

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1172
def model_attach_water_fixtures_to_spaces?(model)
  return true
end
model_create_prototype_model(template:, building_type:, epw_file:, custom_weather_folder: nil, debug: false, sizing_run_dir: Dir.pwd, primary_heating_fuel: 'Electricity', dcv_type: 'NECB_Default', lights_type: 'NECB_Default', lights_scale: 1.0, daylighting_type: 'NECB_Default', ecm_system_name: 'NECB_Default', ecm_system_zones_map_option: 'NECB_Default', erv_package: 'NECB_Default', boiler_eff: nil, unitary_cop: nil, furnace_eff: nil, shw_eff: nil, ext_wall_cond: nil, ext_floor_cond: nil, ext_roof_cond: nil, ground_wall_cond: nil, ground_floor_cond: nil, ground_roof_cond: nil, door_construction_cond: nil, fixed_window_cond: nil, glass_door_cond: nil, overhead_door_cond: nil, skylight_cond: nil, glass_door_solar_trans: nil, fixed_wind_solar_trans: nil, skylight_solar_trans: nil, rotation_degrees: nil, fdwr_set: -1.0, srr_set: -1.0, nv_type: nil, nv_opening_fraction: nil, nv_temp_out_min: nil, nv_delta_temp_in_out: nil, scale_x: nil, scale_y: nil, scale_z: nil, pv_ground_type: nil, pv_ground_total_area_pv_panels_m2: nil, pv_ground_tilt_angle: nil, pv_ground_azimuth_angle: nil, pv_ground_module_description: nil, chiller_type: nil, occupancy_loads_scale: nil, electrical_loads_scale: nil, oa_scale: nil, infiltration_scale: nil, output_variables: nil, shw_scale: nil, output_meters: nil, airloop_economizer_type: nil, baseline_system_zones_map_option: nil, tbd_option: nil, tbd_interpolate: false, necb_hdd: true) click to toggle source

This method is a wrapper to create the 16 archetypes easily. # 55 args

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 200
def model_create_prototype_model(template:,
                                 building_type:,
                                 epw_file:,
                                 custom_weather_folder: nil,
                                 debug: false,
                                 sizing_run_dir: Dir.pwd,
                                 primary_heating_fuel: 'Electricity',
                                 dcv_type: 'NECB_Default',
                                 lights_type: 'NECB_Default',
                                 lights_scale: 1.0,
                                 daylighting_type: 'NECB_Default',
                                 ecm_system_name: 'NECB_Default',
                                 ecm_system_zones_map_option: 'NECB_Default',
                                 erv_package: 'NECB_Default',
                                 boiler_eff: nil,
                                 unitary_cop: nil,
                                 furnace_eff: nil,
                                 shw_eff: nil,
                                 ext_wall_cond: nil,
                                 ext_floor_cond: nil,
                                 ext_roof_cond: nil,
                                 ground_wall_cond: nil,
                                 ground_floor_cond: nil,
                                 ground_roof_cond: nil,
                                 door_construction_cond: nil,
                                 fixed_window_cond: nil,
                                 glass_door_cond: nil,
                                 overhead_door_cond: nil,
                                 skylight_cond: nil,
                                 glass_door_solar_trans: nil,
                                 fixed_wind_solar_trans: nil,
                                 skylight_solar_trans: nil,
                                 rotation_degrees: nil,
                                 fdwr_set: -1.0,
                                 srr_set: -1.0,
                                 nv_type: nil,
                                 nv_opening_fraction: nil,
                                 nv_temp_out_min: nil,
                                 nv_delta_temp_in_out: nil,
                                 scale_x: nil,
                                 scale_y: nil,
                                 scale_z: nil,
                                 pv_ground_type: nil,
                                 pv_ground_total_area_pv_panels_m2: nil,
                                 pv_ground_tilt_angle: nil,
                                 pv_ground_azimuth_angle: nil,
                                 pv_ground_module_description: nil,
                                 chiller_type: nil,
                                 occupancy_loads_scale: nil,
                                 electrical_loads_scale: nil,
                                 oa_scale: nil,
                                 infiltration_scale: nil,
                                 output_variables: nil,
                                 shw_scale: nil,
                                 output_meters: nil,
                                 airloop_economizer_type: nil,
                                 baseline_system_zones_map_option: nil,
                                 tbd_option: nil,
                                 tbd_interpolate: false,
                                 necb_hdd: true)
  model = load_building_type_from_library(building_type: building_type)
  return model_apply_standard(model: model,
                              tbd_option: tbd_option,
                              tbd_interpolate: tbd_interpolate,
                              epw_file: epw_file,
                              custom_weather_folder: custom_weather_folder,
                              sizing_run_dir: sizing_run_dir,
                              primary_heating_fuel: primary_heating_fuel,
                              dcv_type: dcv_type, # Four options: (1) 'NECB_Default', (2) 'No_DCV', (3) 'Occupancy_based_DCV' , (4) 'CO2_based_DCV'
                              lights_type: lights_type, # Two options: (1) 'NECB_Default', (2) 'LED'
                              lights_scale: lights_scale,
                              daylighting_type: daylighting_type, # Two options: (1) nil/none/false/'NECB_Default' (Option #1 puts daylighting sensors in the spaces as per NECB requirements; so some spaces may not have sensors), (2) 'add_daylighting_controls' (Option #2 puts daylighting sensors in all spaces regardless of NECB requirements)
                              ecm_system_name: ecm_system_name,
                              ecm_system_zones_map_option: ecm_system_zones_map_option, # (1) 'NECB_Default' (2) 'one_sys_per_floor' (3) 'one_sys_per_bldg'
                              erv_package: erv_package,
                              boiler_eff: boiler_eff,
                              unitary_cop: unitary_cop,
                              furnace_eff: furnace_eff,
                              shw_eff: shw_eff,
                              ext_wall_cond: ext_wall_cond,
                              ext_floor_cond: ext_floor_cond,
                              ext_roof_cond: ext_roof_cond,
                              ground_wall_cond: ground_wall_cond,
                              ground_floor_cond: ground_floor_cond,
                              ground_roof_cond: ground_roof_cond,
                              door_construction_cond: door_construction_cond,
                              fixed_window_cond: fixed_window_cond,
                              glass_door_cond: glass_door_cond,
                              overhead_door_cond: overhead_door_cond,
                              skylight_cond: skylight_cond,
                              glass_door_solar_trans: glass_door_solar_trans,
                              fixed_wind_solar_trans: fixed_wind_solar_trans,
                              skylight_solar_trans: skylight_solar_trans,
                              rotation_degrees: rotation_degrees,
                              fdwr_set: fdwr_set,
                              srr_set: srr_set,
                              nv_type: nv_type, # Two options: (1) nil/none/false/'NECB_Default', (2) 'add_nv'
                              nv_opening_fraction: nv_opening_fraction, # options: (1) nil/none/false (2) 'NECB_Default' (i.e. 0.1), (3) opening fraction of windows, which can be a float number between 0.0 and 1.0
                              nv_temp_out_min: nv_temp_out_min, # options: (1) nil/none/false(2) 'NECB_Default' (i.e. 13.0 based on inputs from Michel Tardif re a real school in QC), (3) minimum outdoor air temperature (in Celsius) below which natural ventilation is shut down
                              nv_delta_temp_in_out: nv_delta_temp_in_out, # options: (1) nil/none/false (2) 'NECB_Default' (i.e. 1.0 based on inputs from Michel Tardif re a real school in QC), (3) temperature difference (in Celsius) between the indoor and outdoor air temperatures below which ventilation is shut down
                              scale_x: scale_x,
                              scale_y: scale_y,
                              scale_z: scale_z,
                              pv_ground_type: pv_ground_type, # Two options: (1) nil/none/false/'NECB_Default', (2) 'add_pv_ground'
                              pv_ground_total_area_pv_panels_m2: pv_ground_total_area_pv_panels_m2, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. building footprint), (3) area value (e.g. 50)
                              pv_ground_tilt_angle: pv_ground_tilt_angle, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. latitude), (3) tilt angle value (e.g. 20)
                              pv_ground_azimuth_angle: pv_ground_azimuth_angle, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. south), (3) azimuth angle value (e.g. 90)
                              pv_ground_module_description: pv_ground_module_description, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. Standard), (3) other options ('Standard', 'Premium', ThinFilm')
                              occupancy_loads_scale: occupancy_loads_scale,
                              electrical_loads_scale: electrical_loads_scale,
                              oa_scale: oa_scale,
                              infiltration_scale: infiltration_scale,
                              chiller_type: chiller_type, # Options: (1) 'NECB_Default'/nil/'none'/false (i.e. do nothing), (2) e.g. 'VSD'
                              output_variables: output_variables,
                              shw_scale: shw_scale,  # Options: (1) 'NECB_Default'/nil/'none'/false (i.e. do nothing), (2) a float number larger than 0.0
                              output_meters: output_meters,
                              airloop_economizer_type: airloop_economizer_type, # (1) 'NECB_Default'/nil/' (2) 'DifferentialEnthalpy' (3) 'DifferentialTemperature'
                              baseline_system_zones_map_option: baseline_system_zones_map_option,  # Three options: (1) 'NECB_Default'/'none'/nil (i.e. 'one_sys_per_bldg'), (2) 'one_sys_per_dwelling_unit', (3) 'one_sys_per_bldg'
                              necb_hdd: necb_hdd
                              )

end
model_create_thermal_zones(model, space_multiplier_map = nil) click to toggle source

Creates thermal zones to contain each space, as defined for each building in the system_to_space_map inside the Prototype.building_name e.g. (Prototype.secondary_school.rb) file.

@param (see add_constructions) @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 8
def model_create_thermal_zones(model, space_multiplier_map = nil)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started creating thermal zones')
  space_multiplier_map = {} if space_multiplier_map.nil?

  # Remove any Thermal zones assigned
  model.getThermalZones.each(&:remove)

  # Create a thermal zone for each space in the self
  model.getSpaces.sort.each do |space|
    zone = OpenStudio::Model::ThermalZone.new(model)
    zone.setName("#{space.name} ZN")
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end
    space.setThermalZone(zone)

    # Skip thermostat for spaces with no space type
    next if space.spaceType.empty?

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

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished creating thermal zones')
end
model_enable_demand_controlled_ventilation(model, dcv_type = 'No_DCV') click to toggle source

Note: Values for dcv_type are: ‘Occupancy_based_DCV’, ‘CO2_based_DCV’, ‘No_DCV’, ‘NECB_Default’

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1730
def model_enable_demand_controlled_ventilation(model, dcv_type = 'No_DCV')
  return if dcv_type == 'NECB_Defualt'

  if dcv_type == 'Occupancy_based_DCV' || dcv_type == 'CO2_based_DCV'
    # @todo IMPORTANT: (upon other BTAP tasks) Set a value for the "Outdoor Air Flow per Person" field of the "OS:DesignSpecification:OutdoorAir" object
    # Note: The "Outdoor Air Flow per Person" field is required for occupancy-based DCV.
    # Note: The "Outdoor Air Flow per Person" values should be based on ASHRAE 62.1: Article 6.2.2.1.
    # Note: The "Outdoor Air Flow per Person" should be entered for "ventilation_per_person" in "lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json"

    ##### Define indoor CO2 availability schedule (required for CO2-based DCV)
    ##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction
    ##### Note: the defined schedule here is redundant as the schedule says it is always on AND
    ##### the "ZoneControl:ContaminantController" object says that "If this field is left blank, the schedule has a value of 1 for all time periods".
    indoor_co2_availability_schedule = OpenStudio::Model::ScheduleCompact.new(model)
    indoor_co2_availability_schedule.setName('indoor_co2_availability_schedule')
    indoor_co2_availability_schedule.setScheduleTypeLimits(BTAP::Resources::Schedules::StandardScheduleTypeLimits.get_fraction(model))
    indoor_co2_availability_schedule.to_ScheduleCompact.get
    # indoor_co2_availability_schedule.setString(1,"indoor_co2_availability_schedule")
    indoor_co2_availability_schedule.setString(3, 'Through: 12/31')
    indoor_co2_availability_schedule.setString(4, 'For: Weekdays SummerDesignDay')
    indoor_co2_availability_schedule.setString(5, 'Until: 07:00')
    indoor_co2_availability_schedule.setString(6, '0.0')
    indoor_co2_availability_schedule.setString(7, 'Until: 22:00')
    indoor_co2_availability_schedule.setString(8, '1.0')
    indoor_co2_availability_schedule.setString(9, 'Until: 24:00')
    indoor_co2_availability_schedule.setString(10, '0.0')
    indoor_co2_availability_schedule.setString(11, 'For: Saturday WinterDesignDay')
    indoor_co2_availability_schedule.setString(12, 'Until: 07:00')
    indoor_co2_availability_schedule.setString(13, '0.0')
    indoor_co2_availability_schedule.setString(14, 'Until: 18:00')
    indoor_co2_availability_schedule.setString(15, '1.0')
    indoor_co2_availability_schedule.setString(16, 'Until: 24:00')
    indoor_co2_availability_schedule.setString(17, '0.0')
    indoor_co2_availability_schedule.setString(18, 'For: AllOtherDays')
    indoor_co2_availability_schedule.setString(19, 'Until: 24:00')
    indoor_co2_availability_schedule.setString(20, '0.0')

    ##### Define indoor CO2 setpoint schedule (required for CO2-based DCV)
    ##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction
    indoor_co2_setpoint_schedule = OpenStudio::Model::ScheduleCompact.new(model)
    indoor_co2_setpoint_schedule.setName('indoor_co2_setpoint_schedule')
    indoor_co2_setpoint_schedule.setScheduleTypeLimits(get_any_number_ppm(model))
    indoor_co2_setpoint_schedule.to_ScheduleCompact.get
    indoor_co2_setpoint_schedule.setString(3, 'Through: 12/31')
    indoor_co2_setpoint_schedule.setString(4, 'For: AllDays')
    indoor_co2_setpoint_schedule.setString(5, 'Until: 24:00')
    indoor_co2_setpoint_schedule.setString(6, '1000.0')
    # indoor_co2_setpoint_schedule.setToConstantValue(1000.0) #1000 ppm

    ##### Define outdoor CO2 schedule (required for CO2-based DCV
    ##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction
    outdoor_co2_schedule = OpenStudio::Model::ScheduleCompact.new(model)
    outdoor_co2_schedule.setName('outdoor_co2_schedule')
    outdoor_co2_schedule.setScheduleTypeLimits(get_any_number_ppm(model))
    outdoor_co2_schedule.to_ScheduleCompact.get
    outdoor_co2_schedule.setString(3, 'Through: 12/31')
    outdoor_co2_schedule.setString(4, 'For: AllDays')
    outdoor_co2_schedule.setString(5, 'Until: 24:00')
    outdoor_co2_schedule.setString(6, '400.0')
    # outdoor_co2_schedule.setToConstantValue(400.0) #400 ppm

    ##### Define ZoneAirContaminantBalance (required for CO2-based DCV)
    zone_air_contaminant_balance = model.getZoneAirContaminantBalance
    zone_air_contaminant_balance.setCarbonDioxideConcentration(true)
    zone_air_contaminant_balance.setOutdoorCarbonDioxideSchedule(outdoor_co2_schedule)

    ##### Set CO2 controller in each space (required for CO2-based DCV)
    model.getSpaces.sort.each do |space|
      # puts space.name.to_s
      zone = space.thermalZone
      if !zone.empty?
        zone = space.thermalZone.get
      end
      zone_control_co2 = OpenStudio::Model::ZoneControlContaminantController.new(zone.model)
      zone_control_co2.setName("#{space.name} Zone Control Contaminant Controller")
      zone_control_co2.setCarbonDioxideControlAvailabilitySchedule(indoor_co2_availability_schedule)
      zone_control_co2.setCarbonDioxideSetpointSchedule(indoor_co2_setpoint_schedule)
      zone.setZoneControlContaminantController(zone_control_co2)
    end
    # if dcv_type == "Occupancy_based_DCV" || dcv_type == "CO2_based_DCV"
  end

  ##### Loop through AirLoopHVACs
  model.getAirLoopHVACs.sort.each do |air_loop|
    ##### Loop through AirLoopHVAC's supply nodes to:
    ##### (1) Find its AirLoopHVAC:OutdoorAirSystem using the supply node;
    ##### (2) Find Controller:OutdoorAir using AirLoopHVAC:OutdoorAirSystem;
    ##### (3) Get "Controller Mechanical Ventilation" from Controller:OutdoorAir.
    air_loop.supplyComponents.sort.each do |supply_component|
      ##### Find AirLoopHVAC:OutdoorAirSystem of AirLoopHVAC using the supply node.
      hvac_component = supply_component.to_AirLoopHVACOutdoorAirSystem

      if !hvac_component.empty?
        ##### Find Controller:OutdoorAir using AirLoopHVAC:OutdoorAirSystem.
        hvac_component = hvac_component.get
        controller_oa = hvac_component.getControllerOutdoorAir

        ##### Get "Controller Mechanical Ventilation" from Controller:OutdoorAir.
        controller_mv = controller_oa.controllerMechanicalVentilation

        ##### Set "Demand Controlled Ventilation" to "Yes" or "No" in Controller:MechanicalVentilation depending on dcv_type.
        if (dcv_type == 'CO2_based_DCV') || (dcv_type == 'Occupancy_based_DCV') # Occupancy
          controller_mv.setDemandControlledVentilation(true)
          ##### Set the "System Outdoor Air Method" field based on dcv_type in the Controller:MechanicalVentilation object
          if dcv_type == 'CO2_based_DCV'
            controller_mv.setSystemOutdoorAirMethod('IndoorAirQualityProcedure')
          else # dcv_type == 'Occupancy_based_DCV'
            controller_mv.setSystemOutdoorAirMethod('ZoneSum')
          end
        elsif dcv_type == 'No_DCV'
          controller_mv.setDemandControlledVentilation(false)
        end
        # puts controller_mv
        # if !hvac_component.empty?
      end
      # air_loop.supplyComponents.each do |supply_component|
    end
    # model.getAirLoopHVACs.each do |air_loop|
  end
end
model_find_climate_zone_set(model, clim) click to toggle source

Helper method to find out which climate zone set contains a specific climate zone. Returns climate zone set name as String if success, nil if not found.

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1646
def model_find_climate_zone_set(model, clim)
  return 'NECB-CNEB ClimatZone 4-8'
end
necb_design_supply_temp_compliance(qaqc) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1466
def necb_design_supply_temp_compliance(qaqc)
  necb_section_name = get_qaqc_table(table_name: 'design_supply_temp_compliance')['refs'].join(',')
  qaqc_table = get_qaqc_table(table_name: 'design_supply_temp_compliance')
  tolerance = 3
  qaqc[:thermal_zones].each do |zoneinfo|
    #    skipping undefined schedules
    if (qaqc_table['exclude']['exclude_string'].any? { |ex_string| zoneinfo[:name].to_s.include? ex_string }) && !qaqc_table['exclude']['exclude_string'].empty?
      puts "#{zoneinfo[:name]} was skipped in necb_zone_sizing_compliance because it contains #{qaqc_table['exclude']['exclude_string'].join(',')}"
      next
    end
    design_supply_temp_compliance = qaqc_table['table']

    design_supply_temp_compliance.each do |compliance|
      if compliance['var'] == 'heating_design_supply_air_temp'
        result_value = zoneinfo[:zone_heating_design_supply_air_temperature]
      elsif compliance['var'] == 'cooling_design_supply_temp'
        result_value = zoneinfo[:zone_cooling_design_supply_air_temperature]
      end

      next if result_value.nil?

      test_text = "[ZONE][#{zoneinfo[:name]}] #{compliance['var']}"
      # puts key
      necb_section_test(
        qaqc,
        result_value,
        compliance['bool_operator'],
        compliance['expected_value'],
        necb_section_name,
        test_text,
        tolerance
      )
    end
  end
  # Design supply temp test
  # necb_section_name = "NECB2011-?"
  # round_precision = 3
  # qaqc[:thermal_zones].each do |zoneinfo|
  #   #    skipping undefined schedules
  #   if zoneinfo[:name].to_s.include?"- undefined -"
  #     next
  #   end
  #   data = {}
  #   #data[:heating_sizing_factor] = [1.3 , zoneinfo[:heating_sizing_factor]]
  #   #data[:cooling_sizing_factor] = [1.1 ,zoneinfo[:cooling_sizing_factor]]
  #   data[:heating_design_supply_air_temp] =   [43.0, zoneinfo[:zone_heating_design_supply_air_temperature] ] #unless zoneinfo[:zone_heating_design_supply_air_temperature].nil?
  #   data[:cooling_design_supply_temp]   =   [13.0, zoneinfo[:zone_cooling_design_supply_air_temperature] ]
  #   data.each do |key,value|
  #     #puts key
  #     necb_section_test(
  #       qaqc,
  #       value[0],
  #       '==',
  #       value[1],
  #       necb_section_name,
  #       "[ZONE][#{zoneinfo[:name]}] #{key}",
  #       round_precision
  #     )
  #   end
  # end
end
necb_economizer_compliance(qaqc) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1528
def necb_economizer_compliance(qaqc)
  # determine correct economizer usage according to section 5.2.2.7 of NECB2011
  necb_section_name = get_qaqc_table(table_name: 'economizer_compliance')['refs'].join(',')
  qaqc_table = get_qaqc_table(table_name: 'economizer_compliance') # stores the full hash of qaqc for economizer_compliance
  # necb_section_name = "NECB2011-5.2.2.7"

  qaqc[:air_loops].each do |air_loop_info|
    capacity = -1.0
    if !air_loop_info[:cooling_coils][:dx_single_speed][0].nil?
      puts 'capacity = air_loop_info[:cooling_coils][:dx_single_speed][0][:nominal_total_capacity_w]'
      capacity = air_loop_info[:cooling_coils][:dx_single_speed][0][:nominal_total_capacity_w]
    elsif !air_loop_info[:cooling_coils][:dx_two_speed][0].nil?
      puts 'capacity = air_loop_info[:cooling_coils][:dx_two_speed][0][:cop_high]'
      capacity = air_loop_info[:cooling_coils][:dx_two_speed][0][:cop_high]
    elsif !air_loop_info[:cooling_coils][:coil_cooling_water][0].nil?
      puts 'capacity = air_loop_info[:cooling_coils][:coil_cooling_water][0][:nominal_total_capacity_w]'
      capacity = air_loop_info[:cooling_coils][:coil_cooling_water][0][:nominal_total_capacity_w]
    end
    puts capacity
    if capacity == -1.0
      # This should not happen
      qaqc[:errors] << "[necb_economizer_compliance] air_loop_info[:cooling_coils] for #{air_loop_info[:name]} does not have a capacity "
    else
      # check for correct economizer usage
      # puts "air_loop_info[:supply_fan][:max_air_flow_rate]: #{air_loop_info[:supply_fan][:max_air_flow_rate]}"
      unless air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0
        # capacity should be in kW
        max_air_flow_rate_m3_per_s = air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s]
        necb_section_test(
          qaqc,
          eval(qaqc_table['table'][0]['expected_value']),
          '==',
          air_loop_info[:economizer][:control_type],
          necb_section_name,
          "[AIR LOOP][#{air_loop_info[:name]}][:economizer][:control_type]"
        )
      end
    end
  end
end
necb_envelope_compliance(qaqc) click to toggle source

checks envelope compliance fenestration_to_door_and_window_percentage, skylight_to_roof_percentage

# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1164
def necb_envelope_compliance(qaqc)
  # Envelope
  necb_section_name = 'NECB2011-Section 3.2.1.4'
  # store hdd in short form
  hdd = qaqc[:geography][:hdd]
  # calculate fdwr based on hdd.
  fdwr = 0
  if hdd < 4000
    fdwr = 0.40
  elsif (hdd >= 4000) && (hdd <= 7000)
    fdwr = (2000 - 0.2 * hdd) / 3000
  elsif hdd > 7000
    fdwr = 0.20
  end
  # hardset srr to 0.05
  srr = 0.05
  # create table of expected values and results.
  data = {}
  data[:fenestration_to_door_and_window_percentage] = [fdwr * 100, qaqc[:envelope][:fdwr].round(3)]
  data[:skylight_to_roof_percentage] = [srr * 100, qaqc[:envelope][:srr].round(3)]
  # perform test. result must be less than or equal to.
  data.each do |key, value|
    necb_section_test(
      qaqc,
      value[0],
      '>=',
      value[1],
      necb_section_name,
      "[ENVELOPE]#{key}",
      1 # padmassun added tollerance
    )
  end
end
necb_exterior_fenestration_compliance(qaqc) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1304
def necb_exterior_fenestration_compliance(qaqc)
  # Exterior Fenestration
  necb_section_name = get_qaqc_table(table_name: 'exterior_fenestration_compliance')['refs'].join(',')
  climate_index = NECB2011.new.get_climate_zone_index(qaqc[:geography][:hdd])
  tolerance = 3
  # puts "\n\n"
  # puts "climate_index: #{climate_index}"
  # puts get_qaqc_table("exterior_fenestration_compliance", {"var" => "ext_window_conductances", "climate_index" => 2})

  ['ext_window_conductances', 'ext_door_conductances', 'ext_overhead_door_conductances', 'ext_skylight_conductances'].each do |compliance_var|
    qaqc_table = get_qaqc_table(table_name: 'exterior_fenestration_compliance', search_criteria: { 'var' => compliance_var, 'climate_index' => climate_index }).first
    # puts "\n#{qaqc_table}\n"
    if compliance_var == 'ext_window_conductances'
      result_value = qaqc[:envelope][:windows_average_conductance_w_per_m2_k]
    elsif compliance_var == 'ext_door_conductances'
      result_value = qaqc[:envelope][:doors_average_conductance_w_per_m2_k]
    elsif compliance_var == 'ext_overhead_door_conductances'
      result_value = qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k]
    elsif compliance_var == 'ext_skylight_conductances'
      result_value = qaqc[:envelope][:skylights_average_conductance_w_per_m2_k]
    end
    test_text = "[ENVELOPE] #{compliance_var}"
    next if result_value.nil?

    necb_section_test(
      qaqc,
      result_value,
      qaqc_table['bool_operator'],
      qaqc_table['expected_value'],
      necb_section_name,
      test_text,
      tolerance
    )
  end
  # necb_section_name = "NECB2011-Section 3.2.2.3"
  # climate_index = BTAP::Compliance::NECB2011::get_climate_zone_index(qaqc[:geography][:hdd])
  # result_value_index = 6
  # round_precision = 3
  # data = {}
  # data[:ext_window_conductances]      =     [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:windows_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:windows_average_conductance_w_per_m2_k].nil?
  # data[:ext_door_conductances]        =     [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:doors_average_conductance_w_per_m2_k]]   unless qaqc[:envelope][:doors_average_conductance_w_per_m2_k].nil?
  # data[:ext_overhead_door_conductances] =   [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k].nil?
  # data[:ext_skylight_conductances]  =       [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:skylights_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:skylights_average_conductance_w_per_m2_k].nil?

  # data.each do |key,value|
  #   #puts key
  #   necb_section_test(
  #     qaqc,
  #     value[result_value_index].round(round_precision),
  #     '==',
  #     value[climate_index].round(round_precision),
  #     necb_section_name,
  #     "[ENVELOPE]#{key}",
  #     round_precision
  #   )
  # end
end
necb_exterior_ground_surfaces_compliance(qaqc) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1362
def necb_exterior_ground_surfaces_compliance(qaqc)
  # Exterior Ground surfaces
  necb_section_name = get_qaqc_table(table_name: 'exterior_ground_surfaces_compliance')['refs'].join(',')
  climate_index = NECB2011.new.get_climate_zone_index(qaqc[:geography][:hdd])
  tolerance = 3
  # puts "\n\n"
  # puts "climate_index: #{climate_index}"
  # puts get_qaqc_table("exterior_ground_surfaces_compliance", {"var" => "ground_wall_conductances", "climate_index" => 2})

  ['ground_wall_conductances', 'ground_roof_conductances', 'ground_floor_conductances'].each do |compliance_var|
    qaqc_table = get_qaqc_table(table_name: 'exterior_ground_surfaces_compliance', search_criteria: { 'var' => compliance_var, 'climate_index' => climate_index }).first
    # puts "\n#{qaqc_table}\n"
    if compliance_var == 'ground_wall_conductances'
      result_value = qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k]
    elsif compliance_var == 'ground_roof_conductances'
      result_value = qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k]
    elsif compliance_var == 'ground_floor_conductances'
      result_value = qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k]
    end
    test_text = "[ENVELOPE] #{compliance_var}"
    next if result_value.nil?

    necb_section_test(
      qaqc,
      result_value,
      qaqc_table['bool_operator'],
      qaqc_table['expected_value'],
      necb_section_name,
      test_text,
      tolerance
    )
  end
  # necb_section_name = "NECB2011-Section 3.2.3.1"
  # climate_index = BTAP::Compliance::NECB2011::get_climate_zone_index(qaqc[:geography][:hdd])
  # result_value_index = 6
  # round_precision = 3
  # data = {}
  # data[:ground_wall_conductances]  = [ 0.568,0.379,0.284,0.284,0.284,0.210, qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k] ]  unless qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k].nil?
  # data[:ground_roof_conductances]  = [ 0.568,0.379,0.284,0.284,0.284,0.210, qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k] ]  unless qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k].nil?
  # data[:ground_floor_conductances] = [ 0.757,0.757,0.757,0.757,0.757,0.379, qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k] ] unless qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k].nil?
  # data.each {|key,value| necb_section_test(
  #     qaqc,
  #     value[result_value_index],
  #     '==',
  #     value[climate_index],
  #     necb_section_name,
  #     "[ENVELOPE]#{key}",
  #     round_precision
  #   )
  # }
end
necb_exterior_opaque_compliance(qaqc) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1251
def necb_exterior_opaque_compliance(qaqc)
  # puts JSON.pretty_generate @qaqc_data
  # Exterior Opaque
  necb_section_name = get_qaqc_table(table_name: 'exterior_opaque_compliance')['refs'].join(',')
  climate_index = NECB2011.new.get_climate_zone_index(qaqc[:geography][:hdd])
  puts "HDD #{qaqc[:geography][:hdd]}"
  tolerance = 3
  # puts "\n\n"
  # puts "climate_index: #{climate_index}"
  # puts get_qaqc_table("exterior_opaque_compliance", {"var" => "ext_wall_conductances", "climate_index" => 2})
  ['ext_wall_conductances', 'ext_roof_conductances', 'ext_floor_conductances'].each do |compliance_var|
    qaqc_table = get_qaqc_table(table_name: 'exterior_opaque_compliance', search_criteria: { 'var' => compliance_var, 'climate_index' => climate_index }).first
    # puts "\n#{qaqc_table}\n"
    if compliance_var == 'ext_wall_conductances'
      result_value = qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k]
    elsif compliance_var == 'ext_roof_conductances'
      result_value = qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k]
    elsif compliance_var == 'ext_floor_conductances'
      result_value = qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k]
    end

    test_text = "[ENVELOPE] #{compliance_var}"
    next if result_value.nil?

    necb_section_test(
      qaqc,
      result_value,
      qaqc_table['bool_operator'],
      qaqc_table['expected_value'],
      necb_section_name,
      test_text,
      tolerance
    )
  end
  # result_value_index = 6
  # round_precision = 3
  # data = {}
  # data[:ext_wall_conductances]        =  [0.315,0.278,0.247,0.210,0.210,0.183,qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k].nil?
  # data[:ext_roof_conductances]        =  [0.227,0.183,0.183,0.162,0.162,0.142,qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k].nil?
  # data[:ext_floor_conductances]       =  [0.227,0.183,0.183,0.162,0.162,0.142,qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k].nil?

  # data.each {|key,value| necb_section_test(
  #     qaqc,
  #     value[result_value_index],
  #     '==',
  #     value[climate_index],
  #     necb_section_name,
  #     "[ENVELOPE]#{key}",
  #     round_precision
  #   )
  # }
end
necb_hrv_compliance(qaqc, model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1569
def necb_hrv_compliance(qaqc, model)
  # HRV check
  hrv_compliance = get_qaqc_table(table_name: 'hrv_compliance')['table']
  necb_section_name = get_qaqc_table(table_name: 'hrv_compliance')['refs'].join(',')
  qaqc[:air_loops].each do |air_loop_info|
    hrv_compliance.each do |compliance|
      data = {}

      # puts "\nspaceinfo[#{compliance['var']}]"
      result_value = !air_loop_info[:heat_exchanger].empty?
      # puts "#{compliance['test_text']}"
      test_text = "[AIR LOOP][:heat_exchanger] for [#{air_loop_info[:name]}] is present?"
      # puts "result_value: #{result_value}"
      # puts "test_text: #{test_text}\n"
      # data[:infiltration_method]    = [ "Flow/ExteriorArea", spaceinfo[:infiltration_method] , nil ]
      # data[:infiltration_flow_per_m2] = [ 0.00025,       spaceinfo[:infiltration_flow_per_m2], 5 ]
      # data.each do |key,value|
      # puts key
      outdoor_air_L_per_s = air_loop_info[:outdoor_air_L_per_s]
      weather_file_path = model.weatherFile.get.path.get.to_s
      stat_file_path = weather_file_path.gsub('.epw', '.stat')
      stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
      db990 = stat_file.heating_design_info[2]
      necb_section_test(
        qaqc,
        result_value,
        '==',
        eval(compliance['expected_value']),
        necb_section_name,
        test_text,
        compliance['tolerance']
      )
    end
  end
  # necb_section_name = "NECB2011-5.2.10.1"
  # qaqc[:air_loops].each do |air_loop_info|
  #   unless air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0
  #     weather_file_path = model.weatherFile.get.path.get.to_s
  #     stat_file_path = weather_file_path.gsub('.epw', '.stat')
  #     stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
  #     db990 = stat_file.heating_design_info[2]
  #     hrv_calc = 0.00123 * air_loop_info[:outdoor_air_L_per_s] * (21 - db990) #=AP46*(21-O$1)
  #     hrv_reqd = hrv_calc > 150 ? true : false
  #     #qaqc[:information] << "[Info][TEST-PASS][#{necb_section_name}]:#{test_text} result value:#{result_value} #{bool_operator} expected value:#{expected_value}"
  #     hrv_present = false
  #     unless air_loop_info[:heat_exchanger].empty?
  #       hrv_present = true
  #     end
  #     necb_section_test(
  #       qaqc,
  #       hrv_reqd,
  #       '==',
  #       hrv_present,
  #       necb_section_name,
  #       "[AIR LOOP][:heat_exchanger] for [#{air_loop_info[:name]}] is present?"
  #     )
  #   else
  #     qaqc['warnings'] << "[hrv_compliance] air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0 for [#{air_loop_info[:name]}]"
  #   end
  # end
end
necb_hrv_compliance_for_single_airloop(qaqc, model, air_loop_info) click to toggle source

This methos is not used as part of the QAQC process, because support for MURBS has not been implemented for HRV in NECB 2011 and 2015

This method will run the HRV compliance for a single air loop

@param qaqc [:hash] Hash that contains the base data with qaqc keys @param model [:OS:Model] OpenStudio Model @param air_loop_info [:hash] single air_loop object from the qaqc hash

# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1724
def necb_hrv_compliance_for_single_airloop(qaqc, model, air_loop_info)
  # HRV check
  hrv_compliance = get_qaqc_table('hrv_compliance')['table']
  necb_section_name = get_qaqc_table('hrv_compliance')['refs'].join(',')
  hrv_compliance.each do |compliance|
    data = {}

    # puts "\nspaceinfo[#{compliance['var']}]"
    result_value = !air_loop_info[:heat_exchanger].empty?
    # puts "#{compliance['test_text']}"
    test_text = "[AIR LOOP][:heat_exchanger] for [#{air_loop_info[:name]}] is present?"
    # puts "result_value: #{result_value}"
    # puts "test_text: #{test_text}\n"
    # data[:infiltration_method]    = [ "Flow/ExteriorArea", spaceinfo[:infiltration_method] , nil ]
    # data[:infiltration_flow_per_m2] = [ 0.00025,       spaceinfo[:infiltration_flow_per_m2], 5 ]
    # data.each do |key,value|
    # puts key
    outdoor_air_L_per_s = air_loop_info[:outdoor_air_L_per_s]
    weather_file_path = model.weatherFile.get.path.get.to_s
    stat_file_path = weather_file_path.gsub('.epw', '.stat')
    stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
    db990 = stat_file.heating_design_info[2]
    necb_section_test(
      qaqc,
      result_value,
      '==',
      eval(compliance['expected_value']),
      necb_section_name,
      test_text,
      compliance['tolerance']
    )
  end
  # necb_section_name = "NECB2011-5.2.10.1"
  # qaqc[:air_loops].each do |air_loop_info|
  #   unless air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0
  #     weather_file_path = model.weatherFile.get.path.get.to_s
  #     stat_file_path = weather_file_path.gsub('.epw', '.stat')
  #     stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
  #     db990 = stat_file.heating_design_info[2]
  #     hrv_calc = 0.00123 * air_loop_info[:outdoor_air_L_per_s] * (21 - db990) #=AP46*(21-O$1)
  #     hrv_reqd = hrv_calc > 150 ? true : false
  #     #qaqc[:information] << "[Info][TEST-PASS][#{necb_section_name}]:#{test_text} result value:#{result_value} #{bool_operator} expected value:#{expected_value}"
  #     hrv_present = false
  #     unless air_loop_info[:heat_exchanger].empty?
  #       hrv_present = true
  #     end
  #     necb_section_test(
  #       qaqc,
  #       hrv_reqd,
  #       '==',
  #       hrv_present,
  #       necb_section_name,
  #       "[AIR LOOP][:heat_exchanger] for [#{air_loop_info[:name]}] is present?"
  #     )
  #   else
  #     qaqc['warnings'] << "[hrv_compliance] air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0 for [#{air_loop_info[:name]}]"
  #   end
  # end
end
necb_hrv_compliance_inc_murb(qaqc, model) click to toggle source

This methos is not used as part of the QAQC process, because support for MURBS has not been implemented for HRV in NECB 2011 and 2015

# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1634
def necb_hrv_compliance_inc_murb(qaqc, model)
  murb_hrv_compliance = get_qaqc_table('murb_hrv_compliance')
  hrv_spacetpye_ignore_regex = murb_hrv_compliance['ignored_spacetypes_regex']
  hrv_dwelling_unit_spacetpye_regex = murb_hrv_compliance['dwelling_unit_spacetype_regex']
  necb_section_name = murb_hrv_compliance['refs'].join(',')

  model.getAirLoopHVACs.sort.each do |air_loop|
    air_loop_info = {}

    qaqc[:air_loops].each do |air_loop_i|
      next unless air_loop_i[:name] == air_loop.name.get

      air_loop_info = air_loop_i
    end

    zones = air_loop.thermalZones
    if zones.length == 1
      # here the Airloop is serving only one zone

      # So, next we need to determine if the zone has only
      # one dwelling unit and no other space types other than stairs, corridor, and lobby

      zone = zones.first
      # get the spaces and keep track of the number of spaces and dewlling units
      num_of_served_spaces = zone.spaces.length
      contains_dwelling_unit = false
      if num_of_served_spaces == 0
        qaqc[:warnings] << "[necb_murb_hrv_compliance] Thermal Zone [#{zone.name}] does not serve any Spaces"
      else
        spaces = zone.spaces()
        spaces.each do |z_space|
          spacetype = z_space.spaceType
          spacetype = validate_optional(spacetype, model, nil)
          if spacetype.nil?
            qaqc[:warnings] << "[necb_murb_hrv_compliance] Space [#{z_space.name}] does not have a SpaceType"
          else
            # reduce the number of spaces if the space served by the thermal zone is a
            # stairwell/staircase/lobby/corridor
            spacetype_name = spacetype.name.to_s
            ignored_spacetypes_regex = Regexp.new(hrv_spacetpye_ignore_regex, Regexp::IGNORECASE)
            if ignored_spacetypes_regex =~ spacetype_name
              num_of_served_spaces -= 1
            end
            # detect is the thermal zone serves a Dwelling Unit
            dwelling_unit_regex = Regexp.new(hrv_dwelling_unit_spacetpye_regex, Regexp::IGNORECASE)
            if dwelling_unit_regex =~ spacetype_name
              contains_dwelling_unit = true
            end
          end
        end
        if (num_of_served_spaces == 1) && contains_dwelling_unit
          # here the Thermal zone serves one space that is a dwelling unit
          # and other space types such as lobby, stairs, or corridors are ignored
          # So in this case, an HRV is required
          test_text = "[AIR LOOP][:heat_exchanger] (murb) for [#{air_loop_info[:name]}] is present?"
          result_value = murb_hrv_compliance['table']['expected_value']
          necb_section_test(
            qaqc,
            result_value,
            '==',
            true,
            necb_section_name,
            test_text,
            nil
          )
        else
          # Here either the number of served spaces exceed 1 or
          # does not contain a dwelling unit, So a regular HRV check has to be done for this air loop
          qaqc[:warnings] << "[necb_murb_hrv_compliance] Regular HRV compliance check for airloop: [#{air_loop.name}], because (it does not serve a single dwelling unit) OR (serves multiple spacetypes)"
          necb_hrv_compliance_for_single_airloop(qaqc, model, air_loop_info)
        end
      end
    else
      # here the Airloop does not serve any zones, or it serves more than one zone
      # So, a regular hrv compliance must be done for this air loop
      qaqc[:warnings] << "[necb_murb_hrv_compliance] Regular HRV compliance check for airloop: [#{air_loop.name}], because it serves multiple Thermal zones"
      necb_hrv_compliance_for_single_airloop(qaqc, model, air_loop_info)
    end
  end
end
necb_infiltration_compliance(qaqc, model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1198
def necb_infiltration_compliance(qaqc, model)
  # Infiltration
  # puts "\n"
  # puts get_qaqc_table("infiltration_compliance")
  # puts "\n"
  # puts "\n"
  # puts get_qaqc_table("infiltration_compliance", {"var" => ":infiltration_method"} )
  # puts "\n"
  # puts "\n"
  infiltration_compliance = get_qaqc_table(table_name: 'infiltration_compliance')['table']
  necb_section_name = get_qaqc_table(table_name: 'infiltration_compliance')['refs'].join(',')
  qaqc[:spaces].each do |spaceinfo|
    model.getSpaces.sort.each do |space|
      next unless space.name.get == spaceinfo[:name]

      found = false
      space.surfaces.each do |surface|
        next unless surface.outsideBoundaryCondition == 'Outdoors'

        found = true
        # peform this infiltration qaqc if and only if the space's surface is in contact with outdoors
        infiltration_compliance.each do |compliance|
          # puts "\nspaceinfo[#{compliance['var']}]"
          eval_string = "spaceinfo[:#{compliance['var']}]"
          result_value = eval(eval_string)
          # puts "#{compliance['test_text']}"
          test_text = "[SPACE][#{spaceinfo[:name]}]-#{compliance['var']}"
          # puts "result_value: #{result_value}"
          # puts "test_text: #{test_text}\n"
          # data[:infiltration_method]    = [ "Flow/ExteriorArea", spaceinfo[:infiltration_method] , nil ]
          # data[:infiltration_flow_per_m2] = [ 0.00025,       spaceinfo[:infiltration_flow_per_m2], 5 ]
          # data.each do |key,value|
          # puts key
          necb_section_test(
            qaqc,
            result_value,
            compliance['bool_operator'],
            compliance['expected_value'],
            necb_section_name,
            test_text,
            compliance['tolerance']
          )
        end
        # peform qaqc only once per space
        break
      end
      if !found
        qaqc[:warnings] << "necb_infiltration_compliance for SPACE:[#{spaceinfo[:name]}] was skipped because it does not contain surfaces with 'Outside' boundary condition."
      end
    end
  end
end
necb_plantloop_sanity(qaqc) click to toggle source

checks the pump power using pressure, and flowrate

# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1031
def necb_plantloop_sanity(qaqc)
  necb_section_name = 'SANITY-??'
  qaqc[:plant_loops].each do |plant_loop_info|
    pump_head = plant_loop_info[:pumps][0][:head_pa]
    flow_rate = plant_loop_info[:pumps][0][:water_flow_m3_per_s] * 1000
    hp_check = ((flow_rate * 60 * 60) / 1000 * 1000 * 9.81 * pump_head * 0.000101997) / 3600000
    puts "\npump_head #{pump_head}"
    puts "name: #{qaqc[:building][:name]}"
    puts "name: #{plant_loop_info[:name]}"
    puts "flow_rate #{flow_rate}"
    puts "hp_check #{hp_check}\n"
    pump_power_hp = plant_loop_info[:pumps][0][:electric_power_w] / 1000 * 0.746
    percent_diff = (hp_check - pump_power_hp).to_f.abs / hp_check * 100

    if percent_diff.nan?
      qaqc[:ruby_warnings] << "(hp_check - pump_power_hp).to_f.abs/hp_check * 100 for #{plant_loop_info[:name]} is NaN"
      next
    end

    if pump_power_hp < 1.0
      qaqc[:warnings] << "necb_plantloop_sanity [SKIP] [PLANT LOOP][#{plant_loop_info[:name]}][:pumps][0][:electric_power_hp] because  pump_power_hp: [#{pump_power_hp}] < 1 hp"
      next
    end

    necb_section_test(
      qaqc,
      percent_diff,
      '<=',
      20, # diff of 20%
      necb_section_name,
      "[PLANT LOOP][#{plant_loop_info[:name]}][:pumps][0][:electric_power_hp] [#{pump_power_hp}]; NECB value [#{hp_check}]; Percent Diff"
    )
  end
end
necb_qaqc(qaqc, model) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1837
def necb_qaqc(qaqc, model)
  puts "\n\nin necb_qaqc 2011 now\n\n"
  # Now perform basic QA/QC on items for NECB2011
  qaqc[:information] = []
  qaqc[:warnings] = []
  qaqc[:errors] = []
  qaqc[:unique_errors] = []

  necb_space_compliance(qaqc)

  necb_envelope_compliance(qaqc)

  necb_infiltration_compliance(qaqc, model)

  necb_exterior_opaque_compliance(qaqc)

  necb_exterior_fenestration_compliance(qaqc)

  necb_exterior_ground_surfaces_compliance(qaqc)

  necb_zone_sizing_compliance(qaqc)

  necb_design_supply_temp_compliance(qaqc)

  necb_economizer_compliance(qaqc)

  necb_hrv_compliance(qaqc, model)

  necb_vav_fan_power_compliance(qaqc)

  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_section_test(qaqc, result_value, bool_operator, expected_value, necb_section_name, test_text, tolerance = nil) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1878
def necb_section_test(qaqc, result_value, bool_operator, expected_value, necb_section_name, test_text, tolerance = nil)
  test = 'eval_failed'
  command = ''
  if tolerance.is_a?(Integer)
    command = "#{result_value}.round(#{tolerance}) #{bool_operator} #{expected_value}.round(#{tolerance})"
  elsif expected_value.is_a?(String) && result_value.is_a?(String)
    command = "'#{result_value}' #{bool_operator} '#{expected_value}'"
  else
    command = "#{result_value} #{bool_operator} #{expected_value}"
  end
  test = eval(command)
  test_res = nil
  test_res = true if test.to_s.downcase == 'true'
  test_res = false if test.to_s.downcase == 'false'
  raise "Eval command failed #{test}" if test_res.nil?

  if test_res
    qaqc[:information] << "[Info][TEST-PASS][#{necb_section_name}]:#{test_text} result value:#{result_value} #{bool_operator} expected value:#{expected_value}"
  else
    qaqc[:errors] << "[ERROR][TEST-FAIL][#{necb_section_name}]:#{test_text} expected value:#{expected_value} #{bool_operator} result value:#{result_value}"
    unless (expected_value == -1.0) || (expected_value == 'N/A')
      qaqc[:unique_errors] << "[ERROR][TEST-FAIL][#{necb_section_name}]:#{test_text} expected value:#{expected_value} #{bool_operator} result value:#{result_value}"
    end
  end
end
necb_space_compliance(qaqc) click to toggle source

checks space compliance Re: lighting_per_area, occupancy_per_area, occupancy_schedule, electric_equipment_per_area

# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1069
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

    ['lighting_per_area_w_per_m2', 'occupancy_per_area_people_per_m2', 'occupancy_schedule', 'electric_equipment_per_area_w_per_m2'].each do |compliance_var|
      qaqc_table = get_qaqc_table(table_name: 'space_compliance', search_criteria: { 'building_type' => building_type, 'space_type' => space_type }).first
      puts "\n#{qaqc_table}\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 == 'lighting_per_area_w_per_m2'
        if space[:lighting_w_per_m2].nil?
          result_value = 0
        else
          result_value = space[:lighting_w_per_m2] * qaqc_table['lpd_ratio']
        end
      elsif 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

    # row = look_up_csv_data(csv_file_name,{2 => space_type, 1 => building_type})
    # if row.nil?
    #   #raise ("space type of [#{space_type}] and/or building type of [#{building_type}] was not found in the excel sheet for space: [#{space[:name]}]")
    #   qaqc[:ruby_warnings] << "space type of [#{space_type}] and/or building type of [#{building_type}] was not found in the excel sheet for space: [#{space[:name]}]"
    #   puts "space type of [#{space_type}] and/or building type of [#{building_type}] was not found in the excel sheet for space: [#{space[:name]}]"
    # else
    #   #correct the data from the csv file to include a multiplier of 0.9 for specific space types.

    #   reduceLPDSpaces = ["Classroom/lecture/training", "Conf./meet./multi-purpose", "Lounge/recreation",
    #     "Washroom-sch-A", "Washroom-sch-B", "Washroom-sch-C", "Washroom-sch-D", "Washroom-sch-E",
    #     "Washroom-sch-F", "Washroom-sch-G", "Washroom-sch-H", "Washroom-sch-I", "Dress./fitt. - performance arts",
    #     "Locker room", "Retail - dressing/fitting","Locker room-sch-A","Locker room-sch-B","Locker room-sch-C",
    #     "Locker room-sch-D","Locker room-sch-E","Locker room-sch-F","Locker room-sch-G","Locker room-sch-H",
    #     "Locker room-sch-I", "Office - open plan - occsens", "Office - enclosed - occsens", "Storage area - occsens",
    #     "Hospital - medical supply - occsens", "Storage area - refrigerated - occsens"]

    #   if reduceLPDSpaces.include?(space_type)
    #     row[3] = row[3]*0.9
    #     puts "\n============================\nspace_type: #{space_type}\n============================\n"
    #   end

    #   # Start of Space Compliance
    #   necb_section_name = "NECB2011-Section 8.4.3.6"
    #   data = {}
    #   data[:lighting_per_area]            = [ row[3],'==',space[:lighting_w_per_m2] , "Table 4.2.1.6"     ,1 ] unless space[:lighting_w_per_m2].nil?
    #   data[:occupancy_per_area]           = [ row[4],'==',space[:occ_per_m2]        , "Table A-8.4.3.3.1" ,3 ] unless space[:occ_per_m2].nil?
    #   data[:occupancy_schedule]           = [ row[5],'==',space[:occupancy_schedule], "Table A-8.4.3.3.1" ,nil ] unless space[:occupancy_schedule].nil?
    #   data[:electric_equipment_per_area]  = [ row[6],'==',space[:electric_w_per_m2] , "Table A-8.4.3.3.1" ,1 ] unless space[:electric_w_per_m2].nil?
    #   data.each do |key,value|
    #     #puts key
    #     necb_section_test(
    #       qaqc,
    #       value[0],
    #       value[1],
    #       value[2],
    #       value[3],
    #       "[SPACE][#{space[:name]}]-[TYPE:][#{space_type}]#{key}",
    #       value[4]
    #     )
    #   end
    # end#space Compliance
  end
  # Padmassun's Code End
end
necb_vav_fan_power_compliance(qaqc) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1784
def necb_vav_fan_power_compliance(qaqc)
  necb_section_name = get_qaqc_table(table_name: 'vav_fan_power_compliance')['refs'].join(',')
  qaqc_table = get_qaqc_table(table_name: 'vav_fan_power_compliance')
  # necb_section_name = "NECB2011-5.2.3.3"
  qaqc[:air_loops].each do |air_loop_info|
    # necb_clg_cop = air_loop_info[:cooling_coils][:dx_single_speed][:cop] #*assuming that the cop is defined correctly*
    if air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s].nil?
      qaqc[:warnings] << '[vav_fan_power_compliance] air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] is nil'
      next
    end

    max_air_flow_rate_m3_per_s = air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s]
    necb_supply_fan_w = -1

    if air_loop_info[:name].include? 'PSZ'
      necb_supply_fan_w = eval(qaqc_table['formulas']['NECB PSZ fan power (W)']).round(2)
    elsif air_loop_info[:name].include? 'VAV'
      necb_supply_fan_w = eval(qaqc_table['formulas']['NECB VAV fan power (W)']).round(2)
    end

    if air_loop_info[:supply_fan][:rated_electric_power_w].nil?
      qaqc[:warnings] << '[vav_fan_power_compliance] air_loop_info[:supply_fan][:rated_electric_power_w] is nil'
      next
    end

    supply_fan_w = (air_loop_info[:supply_fan][:rated_electric_power_w]).round(3)
    absolute_diff = (necb_supply_fan_w - supply_fan_w).to_f.abs
    if absolute_diff < 10
      # This case should ALWAYS PASS
      necb_section_test(
        qaqc,
        10,
        '>=',
        absolute_diff,
        necb_section_name,
        "[AIR LOOP][#{air_loop_info[:name]}][:supply_fan][:rated_electric_power_w] [#{supply_fan_w}] Absolute Difference from NECB value [#{necb_supply_fan_w}]"
      )
      next
    else
      # The test should pass if and only if the percent difference is less than 10%
      percent_diff = ((necb_supply_fan_w - supply_fan_w).to_f.abs / necb_supply_fan_w * 100).round(3)
      necb_section_test(
        qaqc,
        10,
        '>=',
        percent_diff,
        necb_section_name,
        "[AIR LOOP][#{air_loop_info[:name]}][:supply_fan][:rated_electric_power_w] [#{supply_fan_w}] Percent Diff from NECB value [#{necb_supply_fan_w}]"
      )
    end
  end
end
necb_zone_sizing_compliance(qaqc) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 1414
def necb_zone_sizing_compliance(qaqc)
  # Zone Sizing test
  necb_section_name = get_qaqc_table(table_name: 'zone_sizing_compliance')['refs'].join(',')
  qaqc_table = get_qaqc_table(table_name: 'zone_sizing_compliance')
  tolerance = 3
  # necb_section_name = "NECB2011-?"
  # round_precision = 3
  qaqc[:thermal_zones].each do |zoneinfo|
    #    skipping undefined schedules
    if (qaqc_table['exclude']['exclude_string'].any? { |ex_string| zoneinfo[:name].to_s.include? ex_string }) && !qaqc_table['exclude']['exclude_string'].empty?
      # if zoneinfo[:name].to_s.include?"- undefined -"
      puts "#{zoneinfo[:name]} was skipped in necb_zone_sizing_compliance because it contains #{qaqc_table['exclude']['exclude_string'].join(',')}"
      next
    end
    zone_sizing_compliance = qaqc_table['table']
    zone_sizing_compliance.each do |compliance|
      eval_string = "zoneinfo[:#{compliance['var']}]"
      result_value = eval(eval_string)
      next if result_value.nil?

      test_text = "[ZONE][#{zoneinfo[:name]}] #{compliance['var']}"
      # puts key
      necb_section_test(
        qaqc,
        result_value,
        compliance['bool_operator'],
        compliance['expected_value'],
        necb_section_name,
        test_text,
        tolerance
      )
    end
    # data = {}
    # data[:heating_sizing_factor] = [1.3 , zoneinfo[:heating_sizing_factor]]
    # data[:cooling_sizing_factor] = [1.1 ,zoneinfo[:cooling_sizing_factor]]
    # #data[:heating_design_supply_air_temp] =   [43.0, zoneinfo[:zone_heating_design_supply_air_temperature] ] #unless zoneinfo[:zone_heating_design_supply_air_temperature].nil?
    # #data[:cooling_design_supply_temp]   =   [13.0, zoneinfo[:zone_cooling_design_supply_air_temperature] ]
    # data.each do |key,value|
    #   #puts key
    #   necb_section_test(
    #     qaqc,
    #     value[0],
    #     '==',
    #     value[1],
    #     necb_section_name,
    #     "[ZONE][#{zoneinfo[:name]}] #{key}",
    #     round_precision
    #   )
    # end
  end
end
new_add_sys6_multi_zone_built_up_system_with_baseboard_heating(model:, zones:, heating_coil_type:, baseboard_type:, chiller_type:, fan_type:, hw_loop:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb, line 163
def new_add_sys6_multi_zone_built_up_system_with_baseboard_heating(model:,
                                                                   zones:,
                                                                   heating_coil_type:,
                                                                   baseboard_type:,
                                                                   chiller_type:,
                                                                   fan_type:,
                                                                   hw_loop:)
  # System Type 6: VAV w/ Reheat
  # This measure creates:
  # a single hot water loop with a natural gas or electric boiler or for the building
  # a single chilled water loop with water cooled chiller for the building
  # a single condenser water loop for heat rejection from the chiller
  # a VAV system w/ hot water or electric heating, chilled water cooling, and
  # hot water or electric reheat for each story of the building
  # Arguments:
  # "boiler_fueltype" choices match OS choices for boiler fuel type:
  # "NaturalGas","Electricity","PropaneGas","FuelOilNo1","FuelOilNo2","Coal","Diesel","Gasoline","OtherFuel1"
  # "heating_coil_type": "Electric" or "Hot Water"
  # "baseboard_type": "Electric" and "Hot Water"
  # "chiller_type": "Scroll";"Centrifugal";""Screw";"Reciprocating"
  # "fan_type": "AF_or_BI_rdg_fancurve";"AF_or_BI_inletvanes";"fc_inletvanes";"var_speed_drive"
  system_6_data = {}
  system_6_data[:name] = 'Sys_6_VAV with Reheat'
  system_6_data[:CentralCoolingDesignSupplyAirTemperature] = 13.0
  system_6_data[:CentralHeatingDesignSupplyAirTemperature] = 43.0
  system_6_data[:AllOutdoorAirinCooling] = false
  system_6_data[:AllOutdoorAirinHeating] = false
  system_6_data[:MinimumSystemAirFlowRatio] = 0.03
  # zone data
  system_6_data[:system_supply_air_temperature] = 13.0
  system_6_data[:ZoneCoolingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_6_data[:ZoneCoolingDesignSupplyAirTemperatureDifference] = 11.0
  system_6_data[:ZoneHeatingDesignSupplyAirTemperatureInputMethod] = 'TemperatureDifference'
  system_6_data[:ZoneHeatingDesignSupplyAirTemperatureDifference] = 21.0
  system_6_data[:ZoneCoolingSizingFactor] = 1.1
  system_6_data[:ZoneHeatingSizingFactor] = 1.3
  system_6_data[:ZoneVAVMinFlowFactorPerFloorArea] = 0.002
  system_6_data[:ZoneVAVMaxReheatTemp] = 43.0
  system_6_data[:ZoneVAVDamperAction] = 'Normal'
  system_data = system_6_data

  always_on = model.alwaysOnDiscreteSchedule

  # Chilled Water Plant

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

  # Condenser System

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

  # Make a Packaged VAV w/ PFP Boxes for each story of the building
  model.getBuildingStorys.sort.each do |story|
    unless (OpenstudioStandards::Geometry.building_story_get_thermal_zones(story) & zones).empty?
      air_loop = common_air_loop(model: model, system_data: system_data)
      air_loop.setName(system_data[:name])
      air_loop_sizing = air_loop.sizingSystem
      air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(system_data[:CentralCoolingDesignSupplyAirTemperature])
      air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(system_data[:CentralHeatingDesignSupplyAirTemperature])
      air_loop_sizing.setAllOutdoorAirinCooling(system_data[:AllOutdoorAirinCooling])
      air_loop_sizing.setAllOutdoorAirinHeating(system_data[:AllOutdoorAirinHeating])
      if model.version < OpenStudio::VersionString.new('2.7.0')
        air_loop_sizing.setMinimumSystemAirFlowRatio(system_data[:MinimumSystemAirFlowRatio])
      else
        air_loop_sizing.setCentralHeatingMaximumSystemAirFlowRatio(system_data[:MinimumSystemAirFlowRatio])
      end

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

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

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

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

      # Set mechanical ventilation controller outdoor air to ZoneSum (used to be defaulted to ZoneSum but now should be
      # set explicitly)
      oa_controller.controllerMechanicalVentilation.setSystemOutdoorAirMethod('ZoneSum')

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

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

      # Add a setpoint manager to control the
      # supply air to a constant temperature

      sat_sch = OpenStudio::Model::ScheduleRuleset.new(model)
      sat_sch.setName('Supply Air Temp')
      sat_sch.defaultDaySchedule.setName('Supply Air Temp Default')
      sat_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), system_data[:system_supply_air_temperature])
      sat_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, sat_sch)
      sat_stpt_manager.addToNode(supply_outlet_node)

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

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

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

        # Set zone baseboards
        add_zone_baseboards(model: model,
                            zone: zone,
                            baseboard_type: baseboard_type,
                            hw_loop: hw_loop)
      end
    end
    # next story
  end
  # for debugging
  # puts "end add_sys6_multi_zone_built_up_with_baseboard_heating"
  return true
end
percentage_difference(value_1, value_2) click to toggle source

Math fundtion to determine percent difference.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 719
def percentage_difference(value_1, value_2)
  return 0.0 if value_1 == value_2

  return ((value_1 - value_2).abs / ((value_1 + value_2) / 2) * 100)
end
pump_standard_minimum_motor_efficiency_and_size(pump, motor_bhp) click to toggle source

Determines the minimum pump motor efficiency and nominal size for a given motor bhp. This should be the total brake horsepower with any desired safety factor already included. This method picks the next nominal motor catgory larger than the required brake horsepower, and the efficiency is based on that size. For example, if the bhp = 6.3, the nominal size will be 7.5HP and the efficiency for 90.1-2010 will be 91.7% from Table 10.8B. This method assumes 4-pole, 1800rpm totally-enclosed fan-cooled motors.

@param motor_bhp [Double] motor brake horsepower (hp) @return [Array<Double>] minimum motor efficiency (0.0 to 1.0), nominal horsepower

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1452
def pump_standard_minimum_motor_efficiency_and_size(pump, motor_bhp)
  motor_eff = 0.85
  nominal_hp = motor_bhp

  # Don't attempt to look up motor efficiency
  # for zero-hp pumps (required for circulation-pump-free
  # service water heating systems).
  return [1.0, 0] if motor_bhp == 0.0

  # Lookup the minimum motor efficiency
  motors = @standards_data['motors']

  # Assuming all pump motors are 4-pole ODP
  search_criteria = {
    'motor_use' => 'PUMP',
    'number_of_poles' => 4.0,
    'type' => 'Enclosed'
  }

  motor_properties = model_find_object(motors, search_criteria, motor_bhp)
  if motor_properties.nil?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Pump', "For #{pump.name}, could not find motor properties using search criteria: #{search_criteria}, motor_bhp = #{motor_bhp} hp.")
    return [motor_eff, nominal_hp]
  end

  motor_eff = motor_properties['nominal_full_load_efficiency']
  nominal_hp = motor_properties['maximum_capacity'].to_f.round(1)
  # Round to nearest whole HP for niceness
  if nominal_hp >= 2
    nominal_hp = nominal_hp.round
  end

  # Get the efficiency based on the nominal horsepower
  # Add 0.01 hp to avoid search errors.
  motor_properties = model_find_object(motors, search_criteria, nominal_hp + 0.01)
  if motor_properties.nil?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Fan', "For #{pump.name}, could not find nominal motor properties using search criteria: #{search_criteria}, motor_hp = #{nominal_hp} hp.")
    return [motor_eff, nominal_hp]
  end
  motor_eff = motor_properties['nominal_full_load_efficiency']

  return [motor_eff, nominal_hp]
end
pump_variable_speed_control_type(pump) click to toggle source

Determine and set type of part load control type for heating and chilled water variable speed pumps

@param pump [OpenStudio::Model::PumpVariableSpeed] OpenStudio pump object @return [Boolean] Returns true if applicable, false otherwise

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1532
def pump_variable_speed_control_type(pump)
  return false
end
qaqc_only(model) click to toggle source

generates only qaqc component

# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 168
def qaqc_only(model)
  # load the qaqc.json files
  @qaqc_data = load_qaqc_database_new

  # generate base qaqc hash
  qaqc = create_base_data(model)
  # performs the qaqc on the given base qaqc hash.
  # using `qaqc.clone` as an argument to pass in a shallow copy, so that the argument passed can stay unmodified.
  necb_qaqc_with_base = necb_qaqc(qaqc.clone, model)

  # subract base data from qaqc
  return (necb_qaqc_with_base.to_a - qaqc.to_a).to_h
end
replace_massless_material_with_std_material(model,surf) click to toggle source

Loop through the layers of the construction of the surface and replace any massless material with a standard one. The material used instead is from the EnergyPlus dataset file ‘ASHRAE_2005_HOF_Materials.idf’ with the name: ‘Insulation: Expanded polystyrene - extruded (smooth skin surface) (HCFC-142b exp.)’. The thickness of the new material is based on the thermal resistance of the massless material it replaces. created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 830
def replace_massless_material_with_std_material(model,surf)
  std_const_name = "#{surf.construction.get.name.to_s}_std"
  std_const = model.getLayeredConstructions.select {|const| const.name.to_s == std_const_name}
  new_const = nil
  if !std_const.empty?
    new_const = std_const[0]
  else
    new_layers = {}
    has_massless_mat = false
    layer_index = 0
    surf.construction.get.to_LayeredConstruction.get.layers.each do |layer|
      if layer.to_MasslessOpaqueMaterial.is_initialized then
        has_massless_mat = true
        new_mat = OpenStudio::Model::StandardOpaqueMaterial.new(model)
        new_mat.setName("Expanded Polystyrene")
        new_mat.setThermalConductivity(0.029)
        new_mat.setDensity(29.0)
        new_mat.setSpecificHeat(1210.0)
        new_mat.setRoughness('MediumSmooth')
        new_mat.setThickness(layer.to_MasslessOpaqueMaterial.get.thermalResistance.to_f * new_mat.thermalConductivity.to_f)
      else
        new_mat = layer
      end
      new_layers[layer_index] = new_mat
      layer_index += 1
    end
    if has_massless_mat
      new_const = OpenStudio::Model::Construction.new(model)
      new_layers.keys.sort.each {|layer_index| new_const.to_LayeredConstruction.get.insertLayer(layer_index,new_layers[layer_index])}
      new_const.setName("#{surf.construction.get.name.to_s}_std")
    end
  end
  surf.setConstruction(new_const) if !new_const.nil?

end
sanity_check(qaqc) click to toggle source

Checks if a space with a proper schedule is conditioned or not

# File lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb, line 998
def sanity_check(qaqc)
  qaqc[:sanity_check] = {}
  qaqc[:sanity_check][:fail] = []
  qaqc[:sanity_check][:pass] = []
  # Padmassun's code for isConditioned start
  qaqc[:thermal_zones].each do |zoneinfo|
    zoneinfo[:spaces].each do |space|
      # skip plenums and undefined spaces/zones
      if zoneinfo[:name].to_s.include? '- undefined -'
        next
      end

      if zoneinfo[:space_type_name].to_s.include? 'Space Function - undefined -'
        if zoneinfo[:is_conditioned].to_s == 'No'
          qaqc[:sanity_check][:pass] << "[TEST-PASS][SANITY_CHECK-PASS] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [" 'No' "] and found as #{zoneinfo[:is_conditioned]}"
        else
          qaqc[:sanity_check][:fail] << "[ERROR][SANITY_CHECK-FAIL] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [" 'No' "] but found as #{zoneinfo[:is_conditioned]}"
        end
      else
        if zoneinfo[:is_conditioned].to_s == 'Yes'
          qaqc[:sanity_check][:pass] << "[TEST-PASS][SANITY_CHECK-PASS] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [" 'Yes' "] and found as #{zoneinfo[:is_conditioned]}"
        elsif zoneinfo[:name]
          qaqc[:sanity_check][:fail] << "[ERROR][SANITY_CHECK-FAIL] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [" 'Yes' "] but found as #{zoneinfo[:is_conditioned]}"
        end
      end
    end
  end
  qaqc[:sanity_check][:fail] = qaqc[:sanity_check][:fail].sort
  qaqc[:sanity_check][:pass] = qaqc[:sanity_check][:pass].sort
  # Padmassun's code for isConditioned end
end
scale_model_geometry(model, x_scale, y_scale, z_scale) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 631
def scale_model_geometry(model, x_scale, y_scale, z_scale)
  # Identity matrix for setting space origins
  m = OpenStudio::Matrix.new(4, 4, 0)

  m[0, 0] = 1.0 / x_scale
  m[1, 1] = 1.0 / y_scale
  m[2, 2] = 1.0 / z_scale
  m[3, 3] = 1.0
  t = OpenStudio::Transformation.new(m)
  model.getPlanarSurfaceGroups.each do |planar_surface|
    planar_surface.changeTransformation(t)
  end
  return model
end
set_lighting_per_area(space_type:, definition:, lighting_per_area:, lights_scale:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/lighting.rb, line 146
def set_lighting_per_area(space_type:,
                          definition:,
                          lighting_per_area:,
                          lights_scale:)
  occ_sens_lpd_frac = 1.0
  # NECB2011 space types that require a reduction in the LPD to account for
  # the requirement of an occupancy sensor (8.4.4.6(3) and 4.2.2.2(2))
  reduce_lpd_spaces = ['Classroom/lecture/training', 'Conf./meet./multi-purpose', 'Lounge/recreation',
                       'Conf./meet./multi-purpose', 'Washroom-sch-A', 'Washroom-sch-B', 'Washroom-sch-C', 'Washroom-sch-D',
                       'Washroom-sch-E', 'Washroom-sch-F', 'Washroom-sch-G', 'Washroom-sch-H', 'Washroom-sch-I',
                       'Dress./fitt. - performance arts', 'Locker room', 'Locker room-sch-A', 'Locker room-sch-B',
                       'Locker room-sch-C', 'Locker room-sch-D', 'Locker room-sch-E', 'Locker room-sch-F', 'Locker room-sch-G',
                       'Locker room-sch-H', 'Locker room-sch-I', 'Retail - dressing/fitting']
  if reduce_lpd_spaces.include?(space_type.standardsSpaceType.get)
    # Note that "Storage area", "Storage area - refrigerated", "Hospital - medical supply" and "Office - enclosed"
    # LPD should only be reduced if their space areas are less than specific area values.
    # This is checked in a space loop after this function in the calling routine.
    occ_sens_lpd_frac = 0.9
  end
  definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area.to_f * occ_sens_lpd_frac * 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/NECB2011/necb_2011.rb, line 1851
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}"
  occ_sens_lpd_frac = 1.0
  # NECB2011 space types that require a reduction in the LPD to account for
  # the requirement of an occupancy sensor (8.4.4.6(3) and 4.2.2.2(2))
  reduce_lpd_spaces = ['Classroom/lecture/training', 'Conf./meet./multi-purpose', 'Lounge/recreation',
                       'Conf./meet./multi-purpose', 'Washroom-sch-A', 'Washroom-sch-B', 'Washroom-sch-C', 'Washroom-sch-D',
                       'Washroom-sch-E', 'Washroom-sch-F', 'Washroom-sch-G', 'Washroom-sch-H', 'Washroom-sch-I',
                       'Dress./fitt. - performance arts', 'Locker room', 'Locker room-sch-A', 'Locker room-sch-B',
                       'Locker room-sch-C', 'Locker room-sch-D', 'Locker room-sch-E', 'Locker room-sch-F', 'Locker room-sch-G',
                       'Locker room-sch-H', 'Locker room-sch-I', 'Retail - dressing/fitting']
  if reduce_lpd_spaces.include?(space_type.standardsSpaceType.get)
    # Note that "Storage area", "Storage area - refrigerated", "Hospital - medical supply" and "Office - enclosed"
    # LPD should only be reduced if their space areas are less than specific area values.
    # This is checked in a space loop after this function in the calling routine.
    occ_sens_lpd_frac = 0.9
  end

  # ##### 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
  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
    # Get the max height for the spacetype.
    max_space_height_for_spacetype = get_max_space_height_for_space_type(space_type: space_type)
    if max_space_height_for_spacetype < 12.0 # @todo Note that since none of the archetypes has Atrium, this was tested for 'Dining' with the threshold of 5.0 m for space_height.
      # @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 * 12.0) * 0.092903 # W/ft2 @todo Note that for NECB2011, a constant LPD is used for atrium based on NECB2015's equations. NECB2011's threshold for height is 13.0 m.
    elsif max_space_height_for_spacetype >= 12.0 && max_space_height_for_spacetype < 13.0
      lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_larger_12_intercept + atrium_lpd_eq_larger_12_slope * 12.5) * 0.092903 # W/ft2
    else # i.e. space_height >= 13.0
      lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_larger_12_intercept + atrium_lpd_eq_larger_12_slope * 13.0) * 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 * occ_sens_lpd_frac, '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_necb_external_subsurface_conductance(subsurface, hdd) click to toggle source

Set all external subsurfaces (doors, windows, skylights) to NECB values. @author phylroy.lopez@nrcan.gc.ca @param subsurface [String] @param hdd [Float]

# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 450
def set_necb_external_subsurface_conductance(subsurface, hdd)
  conductance_value = 0
  return unless subsurface.outsideBoundaryCondition.downcase.match('outdoors')

  case subsurface.subSurfaceType.downcase
  when /window/
    conductance_value = @standards_data['conductances']['Window'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
  when /door/
    conductance_value = @standards_data['conductances']['Door'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
  end
  subsurface.setRSI(1 / conductance_value)
end
set_necb_external_surface_conductance(surface, hdd, is_radiant = false, scaling_factor = 1.0) click to toggle source

Set all external surface conductances to NECB values. @author phylroy.lopez@nrcan.gc.ca @param surface [String] @param hdd [Float] @param is_radiant [Boolian] @param scaling_factor [Float] @return [String] surface as RSI

# File lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb, line 412
def set_necb_external_surface_conductance(surface, hdd, is_radiant = false, scaling_factor = 1.0)
  conductance_value = 0
  if surface.outsideBoundaryCondition.casecmp('outdoors').zero?

    case surface.surfaceType.downcase
    when 'wall'
      conductance_value = @standards_data['conductances']['Wall'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
    when 'floor'
      conductance_value = @standards_data['conductances']['Floor'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
    when 'roofceiling'
      conductance_value = @standards_data['conductances']['Roof'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
    end
    if is_radiant
      conductance_value *= 0.80
    end
    return BTAP::Geometry::Surfaces.set_surfaces_construction_conductance([surface], conductance_value)
  end

  return unless surface.outsideBoundaryCondition.downcase =~ /ground/

  case surface.surfaceType.downcase
  when 'wall'
    conductance_value = @standards_data['conductances']['GroundWall'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
  when 'floor'
    conductance_value = @standards_data['conductances']['GroundFloor'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
  when 'roofceiling'
    conductance_value = @standards_data['conductances']['GroundRoof'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
  end
  if is_radiant
    conductance_value *= 0.80
  end
  return BTAP::Geometry::Surfaces.set_surfaces_construction_conductance([surface], conductance_value)
end
set_occ_sensor_spacetypes(model, space_type_map) click to toggle source

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

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1246
def set_occ_sensor_spacetypes(model, space_type_map)
  building_type = 'Space Function'
  space_type_map.each do |space_type_name, space_names|
    space_names.sort.each do |space_name|
      space = model.getSpaceByName(space_name)
      next if space.empty?

      space = space.get

      # Check if space type for this space matches NECB2011 specific space type
      # for occupancy sensor that is area dependent. Note: space.floorArea in m2.

      # Evaluate the formula in the database.
      standard_space_type_name = space_type_name
      floor_area = space.floorArea
      if eval(@standards_data['formulas']['occupancy_sensors_space_types_formula']['value'])
        # If there is only one space assigned to this space type, then reassign this stub
        # to the @@template duplicate with appendage " - occsens", otherwise create a new stub
        # for this space. Required to use reduced LPD by NECB2011 0.9 factor.
        space_type_name_occsens = space_type_name + ' - occsens'
        stub_space_type_occsens = model.getSpaceTypeByName("#{building_type} #{space_type_name_occsens}")

        if stub_space_type_occsens.empty?
          # create a new space type just once for space_type_name appended with " - occsens"
          stub_space_type_occsens = OpenStudio::Model::SpaceType.new(model)
          stub_space_type_occsens.setStandardsBuildingType(building_type)
          stub_space_type_occsens.setStandardsSpaceType(space_type_name_occsens)
          stub_space_type_occsens.setName("#{building_type} #{space_type_name_occsens}")
          space_type_apply_rendering_color(stub_space_type_occsens)
          space.setSpaceType(stub_space_type_occsens)
        else
          # reassign occsens space type stub already created...
          stub_space_type_occsens = stub_space_type_occsens.get
          space.setSpaceType(stub_space_type_occsens)
        end
      end
    end
  end
  return true
end
set_output_meters(model:,output_meters:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2212
def set_output_meters(model:,output_meters:)
  unless output_meters.nil? or output_meters.empty?
    # remove existing output meters
    existing_meters = model.getOutputMeters

    # OpenStudio doesn't seemt to like two meters of the same name, even if they have different reporting frequencies.
    output_meters.each do |new_meter|
      #check if meter already exists
      result = existing_meters.select { |e_m| e_m.name == new_meter['name'] }
      puts("More and one output meter named #{new_meter['name']}") if result.size > 1
      if result.size >= 1
        existing_meter = result[0]
        puts("A meter named #{new_meter['name']} already exists. One will not be added to the model.")
        if existing_meter.reportingFrequency != new_meter['frequency']
          existing_meter.setReportingFrequency(new_meter['frequency'])
          puts("Changing reporting frequency of existing meter to #{new_meter['frequency']}.")
        end
      end
      if result.size == 0
        meter = OpenStudio::Model::OutputMeter.new(model)
        meter.setName(new_meter['name'])
        meter.setReportingFrequency(new_meter['frequency'])
        puts("Adding meter for #{meter.name} reporting #{new_meter['frequency']}")
      end
    end
  end
  return model
end
set_output_variables(model:,output_variables:) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 2198
def set_output_variables(model:,output_variables:)
  unless output_variables.nil? or output_variables.empty?
    output_variables.each do |output_variable|
      puts output_variable
      puts output_variable['frequency']
      raise("Frequency is not valid. Must by \"hourly\" or \"timestep\" but got #{output_variable}.") unless ["timestep","hourly",'daily','monthly','annual'].include?(output_variable['frequency'])
      output = OpenStudio::Model::OutputVariable.new(output_variable['variable'],model)
      output.setKeyValue(output_variable['key'])
      output.setReportingFrequency(output_variable['frequency'])
    end
  end
  return model
end
set_random_rendering_color(object, random) click to toggle source

This method will create a color object used in SU, 3D Viewer and Floorspace.js

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 1261
def set_random_rendering_color(object, random)
  rendering_color = OpenStudio::Model::RenderingColor.new(object.model)
  rendering_color.setName(object.name.get)
  rendering_color.setRenderingRedValue(random.rand(255))
  rendering_color.setRenderingGreenValue(random.rand(255))
  rendering_color.setRenderingBlueValue(random.rand(255))
  return rendering_color
end
set_wildcard_schedules_to_dominant_building_schedule(model, runner = nil) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 761
def set_wildcard_schedules_to_dominant_building_schedule(model, runner = nil)
  # Get rid of.
end
set_zones_thermostat_schedule_based_on_space_type_schedules(model, runner = nil) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1584
def set_zones_thermostat_schedule_based_on_space_type_schedules(model, runner = nil)
  puts 'in set_zones_thermostat_schedule_based_on_space_type_schedules'
  BTAP.runner_register('DEBUG', 'Start-set_zones_thermostat_schedule_based_on_space_type_schedules', runner)
  model.getThermalZones.sort.each do |zone|
    BTAP.runner_register('DEBUG', "Zone = #{zone.name} Spaces =#{zone.spaces.size} ", runner)
    array = []

    zone.spaces.sort.each do |space|
      schedule_type = determine_necb_schedule_type(space).to_s
      BTAP.runner_register('DEBUG', "space name/type:#{space.name}/#{schedule_type}", runner)

      # if wildcard space type, need to get dominant schedule type
      if '*'.to_s == schedule_type
        dominant_sched_type = determine_dominant_necb_schedule_type(model)
        schedule_type = dominant_sched_type
      end

      array << schedule_type
    end
    array.uniq!
    if array.size > 1
      BTAP.runner_register('Error', "#{zone.name} has spaces with different schedule types. Please ensure that all the spaces are of the same schedule type A to I.", runner)
      return false
    end

    htg_search_string = "NECB-#{array[0]}-Thermostat Setpoint-Heating"
    clg_search_string = "NECB-#{array[0]}-Thermostat Setpoint-Cooling"

    if model.getScheduleRulesetByName(htg_search_string).empty? == false
      htg_sched = model.getScheduleRulesetByName(htg_search_string).get
    else
      BTAP.runner_register('ERROR', "heating_thermostat_setpoint_schedule NECB-#{array[0]} does not exist", runner)
      return false
    end

    if model.getScheduleRulesetByName(clg_search_string).empty? == false
      clg_sched = model.getScheduleRulesetByName(clg_search_string).get
    else
      BTAP.runner_register('ERROR', "cooling_thermostat_setpoint_schedule NECB-#{array[0]} does not exist", runner)
      return false
    end

    name = "NECB-#{array[0]}-Thermostat Dual Setpoint Schedule"

    # If dual setpoint already exists, use that one, else create one
    ds = if model.getThermostatSetpointDualSetpointByName(name).empty? == false
           model.getThermostatSetpointDualSetpointByName(name).get
         else
           BTAP::Resources::Schedules.create_annual_thermostat_setpoint_dual_setpoint(model, name, htg_sched, clg_sched)
         end

    thermostat_clone = ds.clone.to_ThermostatSetpointDualSetpoint.get
    zone.setThermostatSetpointDualSetpoint(thermostat_clone)
    BTAP.runner_register('Info', "ThermalZone #{zone.name} set to DualSetpoint Schedule NECB-#{array[0]}", runner)
  end

  BTAP.runner_register('DEBUG', 'END-set_zones_thermostat_schedule_based_on_space_type_schedules', runner)
  return true
end
setup_chw_loop_with_components(model, chw_loop, chiller_type) click to toggle source

of setup_hw_loop_with_components

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1707
def setup_chw_loop_with_components(model, chw_loop, chiller_type)
  chw_loop.setName('Chilled Water Loop')
  sizing_plant = chw_loop.sizingPlant
  sizing_plant.setLoopType('Cooling')
  sizing_plant.setDesignLoopExitTemperature(7.0)
  sizing_plant.setLoopDesignTemperatureDifference(6.0)

  # Note: pump of 'chilled water loop' has been changed to the variable one as the constant one caused fatal errors for LargeOffice-Yellowknife-NaturalGas for some ECMs and inputs.
  # Fatal error was: 'CheckForRunawayPlantTemps: Simulation terminated because of run away plant temperatures, too cold' OR '..., too hot' for the PlantLoop of 'Chilled Water Loop'.
  # Note that the variable speed pump has been already used for 'Hot Water Loop'.
  chw_pump = OpenStudio::Model::PumpVariableSpeed.new(model)

  chiller1 = OpenStudio::Model::ChillerElectricEIR.new(model)
  chiller2 = OpenStudio::Model::ChillerElectricEIR.new(model)
  chiller1.setCondenserType('WaterCooled')
  chiller2.setCondenserType('WaterCooled')
  chiller1_name = "Primary Chiller WaterCooled #{chiller_type}".strip
  chiller1.setName(chiller1_name)
  chiller2_name = "Secondary Chiller WaterCooled #{chiller_type}".strip
  chiller2.setName(chiller2_name)

  chiller_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  chw_supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  # Add the components to the chilled water loop
  chw_supply_inlet_node = chw_loop.supplyInletNode
  chw_supply_outlet_node = chw_loop.supplyOutletNode
  chw_pump.addToNode(chw_supply_inlet_node)
  chw_loop.addSupplyBranchForComponent(chiller1)
  chw_loop.addSupplyBranchForComponent(chiller2)
  chw_loop.addSupplyBranchForComponent(chiller_bypass_pipe)
  chw_supply_outlet_pipe.addToNode(chw_supply_outlet_node)

  # Add a setpoint manager to control the
  # chilled water to a constant temperature
  chw_t_c = 7.0
  chw_t_sch = BTAP::Resources::Schedules.create_annual_constant_ruleset_schedule(model, 'CHW Temp', 'Temperature', chw_t_c)
  chw_t_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, chw_t_sch)
  chw_t_stpt_manager.addToNode(chw_supply_outlet_node)

  return chiller1, chiller2
end
setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2) click to toggle source

of setup_chw_loop_with_components

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1753
def setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2)
  cw_loop.setName('Condenser Water Loop')
  cw_sizing_plant = cw_loop.sizingPlant
  cw_sizing_plant.setLoopType('Condenser')
  cw_sizing_plant.setDesignLoopExitTemperature(29.0)
  cw_sizing_plant.setLoopDesignTemperatureDifference(6.0)

  # Note: pump of 'Condenser water loop' has been changed to the variable one as the constant one caused fatal errors for LargeOffice-Montreal-NaturalGas for some ECMs and inputs.
  # Fatal error was: 'Plant temperatures are getting far too cold, check controls and relative loads and capacities'.
  # Note that the variable speed pump has been already used for 'Hot Water Loop' and 'Chilled Water Loop'.
  cw_pump = OpenStudio::Model::PumpVariableSpeed.new(model)

  clg_tower = OpenStudio::Model::CoolingTowerSingleSpeed.new(model)

  # TO DO: Need to define and set cooling tower curves

  clg_tower_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  cw_supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  # Add the components to the condenser water loop
  cw_supply_inlet_node = cw_loop.supplyInletNode
  cw_supply_outlet_node = cw_loop.supplyOutletNode
  cw_pump.addToNode(cw_supply_inlet_node)
  clg_tower.setDesignInletAirWetBulbTemperature(24.0)
  clg_tower.setDesignInletAirDryBulbTemperature(35.0)
  clg_tower.setDesignApproachTemperature(5.0)
  clg_tower.setDesignRangeTemperature(6.0)
  cw_loop.addSupplyBranchForComponent(clg_tower)
  cw_loop.addSupplyBranchForComponent(clg_tower_bypass_pipe)
  cw_supply_outlet_pipe.addToNode(cw_supply_outlet_node)
  cw_loop.addDemandBranchForComponent(chiller1)
  cw_loop.addDemandBranchForComponent(chiller2)

  # Add a setpoint manager to control the
  # condenser water to constant temperature
  cw_t_c = 29.0
  cw_t_sch = BTAP::Resources::Schedules.create_annual_constant_ruleset_schedule(model, 'CW Temp', 'Temperature', cw_t_c)
  cw_t_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, cw_t_sch)
  cw_t_stpt_manager.addToNode(cw_supply_outlet_node)

  return clg_tower
end
setup_hw_loop_with_components(model, hw_loop, boiler_fueltype, pump_flow_sch) click to toggle source
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1650
def setup_hw_loop_with_components(model,
                                  hw_loop,
                                  boiler_fueltype,
                                  pump_flow_sch)
  hw_loop.setName('Hot Water Loop')
  sizing_plant = hw_loop.sizingPlant
  sizing_plant.setLoopType('Heating')
  sizing_plant.setDesignLoopExitTemperature(82.0) #@todo units
  sizing_plant.setLoopDesignTemperatureDifference(16.0)

  # pump (set to variable speed for now till fix to run away plant temperature is found)
  # pump = OpenStudio::Model::PumpConstantSpeed.new(model)
  pump = OpenStudio::Model::PumpVariableSpeed.new(model)
  # @todo the keyword "setPumpFlowRateSchedule" does not seem to work. A message
  # was sent to NREL to let them know about this. Once there is a fix for this,
  # use the proper pump schedule depending on whether we have two-pipe or four-pipe
  # fan coils.
  #            pump.resetPumpFlowRateSchedule()
  #            pump.setPumpFlowRateSchedule(pump_flow_sch)

  # boiler
  boiler1 = OpenStudio::Model::BoilerHotWater.new(model)
  boiler2 = OpenStudio::Model::BoilerHotWater.new(model)
  boiler1.setFuelType(boiler_fueltype)
  boiler2.setFuelType(boiler_fueltype)
  boiler1.setName('Primary Boiler')
  boiler2.setName('Secondary Boiler')

  # boiler_bypass_pipe
  boiler_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  # supply_outlet_pipe
  supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  # Add the components to the hot water loop
  hw_supply_inlet_node = hw_loop.supplyInletNode
  hw_supply_outlet_node = hw_loop.supplyOutletNode
  pump.addToNode(hw_supply_inlet_node)

  hw_loop.addSupplyBranchForComponent(boiler1)
  hw_loop.addSupplyBranchForComponent(boiler2)
  hw_loop.addSupplyBranchForComponent(boiler_bypass_pipe)
  supply_outlet_pipe.addToNode(hw_supply_outlet_node)

  # Add a setpoint manager to control the
  # hot water based on outdoor temperature
  hw_oareset_stpt_manager = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(model)
  hw_oareset_stpt_manager.setControlVariable('Temperature')
  hw_oareset_stpt_manager.setSetpointatOutdoorLowTemperature(82.0)
  hw_oareset_stpt_manager.setOutdoorLowTemperature(-16.0)
  hw_oareset_stpt_manager.setSetpointatOutdoorHighTemperature(60.0)
  hw_oareset_stpt_manager.setOutdoorHighTemperature(0.0)
  hw_oareset_stpt_manager.addToNode(hw_supply_outlet_node)
end
space_apply_infiltration_rate(space) click to toggle source

Set the infiltration rate for this space to include the impact of air leakage requirements in the standard.

@return [Double] true if successful, false if not @todo handle doors and vestibules

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1181
def space_apply_infiltration_rate(space)
  # Remove infiltration rates set at the space type.
  infiltration_data = @standards_data['infiltration']
  unless space.spaceType.empty?
    space.spaceType.get.spaceInfiltrationDesignFlowRates.each(&:remove)
  end
  # Remove infiltration rates set at the space object.
  space.spaceInfiltrationDesignFlowRates.each(&:remove)

  exterior_wall_and_roof_and_subsurface_area = OpenstudioStandards::Geometry.space_get_exterior_wall_and_subsurface_and_roof_area(space) # To do
  # Don't create an object if there is no exterior wall area
  if exterior_wall_and_roof_and_subsurface_area <= 0.0
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "For #{template}, no exterior wall area was found, no infiltration will be added.")
    return true
  end
  # Calculate the total infiltration, assuming
  # that it only occurs through exterior walls and roofs (not floors as
  # explicit stated in the NECB2011 so overhang/cantilevered floors will
  # have no effective infiltration)
  tot_infil_m3_per_s = get_standards_constant('infiltration_rate_m3_per_s_per_m2') * exterior_wall_and_roof_and_subsurface_area
  # Now spread the total infiltration rate over all
  # exterior surface area (for the E+ input field) this will include the exterior floor if present.
  all_ext_infil_m3_per_s_per_m2 = tot_infil_m3_per_s / space.exteriorArea

  OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.Space', "For #{space.name}, adj infil = #{all_ext_infil_m3_per_s_per_m2.round(8)} m^3/s*m^2.")

  # Get any infiltration schedule already assigned to this space or its space type
  # If not, the always on schedule will be applied.
  infil_sch = nil
  unless space.spaceInfiltrationDesignFlowRates.empty?
    old_infil = space.spaceInfiltrationDesignFlowRates[0]
    if old_infil.schedule.is_initialized
      infil_sch = old_infil.schedule.get
    end
  end

  if infil_sch.nil? && space.spaceType.is_initialized
    space_type = space.spaceType.get
    unless space_type.spaceInfiltrationDesignFlowRates.empty?
      old_infil = space_type.spaceInfiltrationDesignFlowRates[0]
      if old_infil.schedule.is_initialized
        infil_sch = old_infil.schedule.get
      end
    end
  end

  if infil_sch.nil?
    infil_sch = space.model.alwaysOnDiscreteSchedule
  end

  # Create an infiltration rate object for this space
  infiltration = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(space.model)
  infiltration.setName("#{space.name} Infiltration")
  infiltration.setFlowperExteriorSurfaceArea(all_ext_infil_m3_per_s_per_m2)
  infiltration.setSchedule(infil_sch)
  infiltration.setConstantTermCoefficient(get_standards_constant('infiltration_constant_term_coefficient'))
  infiltration.setTemperatureTermCoefficient(get_standards_constant('infiltration_constant_term_coefficient'))
  infiltration.setVelocityTermCoefficient(get_standards_constant('infiltration_velocity_term_coefficient'))
  infiltration.setVelocitySquaredTermCoefficient(get_standards_constant('infiltration_velocity_squared_term_coefficient'))
  infiltration.setSpace(space)

  return true
end
space_surface_report(space) click to toggle source

This method gathers the surface information for the space to determine if spaces are the same.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 584
def space_surface_report(space)
  surface_report = []
  space_floor_area = space.floorArea
  ['Outdoors', 'Ground'].each do |bc|
    surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(space.surfaces, [bc]).each do |surface|
      # sum wall area and subsurface area by direction. This is the old way so excluding top and bottom surfaces.
      # new way
      glazings = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(surface.subSurfaces, ['FixedWindow',
                                                                                            'OperableWindow',
                                                                                            'GlassDoor',
                                                                                            'Skylight',
                                                                                            'TubularDaylightDiffuser',
                                                                                            'TubularDaylightDome'])
      doors = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(surface.subSurfaces, ['Door',
                                                                                         'OverheadDoor'])
      azimuth = (surface.azimuth() * 180.0 / Math::PI)
      tilt = (surface.tilt() * 180.0 / Math::PI)
      surface_data = surface_report.detect do |curr_surface_data|
        curr_surface_data[:surface_type] == surface.surfaceType &&
          curr_surface_data[:azimuth] == azimuth &&
          curr_surface_data[:tilt] == tilt &&
          curr_surface_data[:boundary_condition] == bc
      end

      if surface_data.nil?
        surface_data = {
          surface_type: surface.surfaceType,
          azimuth: azimuth,
          tilt: tilt,
          boundary_condition: bc,
          surface_area: 0,
          surface_area_to_floor_ratio: 0,
          glazed_subsurface_area: 0,
          glazed_subsurface_area_to_floor_ratio: 0,
          opaque_subsurface_area: 0,
          opaque_subsurface_area_to_floor_ratio: 0
        }
        surface_report << surface_data
      end
      surface_data[:surface_area] += surface.grossArea.to_i
      surface_data[:surface_area_to_floor_ratio] += surface.grossArea / space.floorArea

      surface_data[:glazed_subsurface_area] += glazings.map { |subsurface| subsurface.grossArea * subsurface.multiplier }.inject(0) { |sum, x| sum + x }.to_i
      surface_data[:glazed_subsurface_area_to_floor_ratio] += glazings.map { |subsurface| subsurface.grossArea * subsurface.multiplier }.inject(0) { |sum, x| sum + x } / space.floorArea

      surface_data[:surface_area] += doors.map { |subsurface| subsurface.grossArea * subsurface.multiplier }.inject(0) { |sum, x| sum + x }.to_i
      surface_data[:surface_area_to_floor_ratio] += doors.map { |subsurface| subsurface.grossArea * subsurface.multiplier }.inject(0) { |sum, x| sum + x } / space.floorArea
    end
  end
  surface_report.sort! { |a, b| [a[:surface_type], a[:azimuth], a[:tilt], a[:boundary_condition]] <=> [b[:surface_type], b[:azimuth], b[:tilt], b[:boundary_condition]] }

  return surface_report
end
space_type_apply_internal_loads(space_type:, set_people: true, set_lights: true, set_electric_equipment: true, set_gas_equipment: true, set_ventilation: true, set_infiltration: true, lights_type: 'NECB_Default', lights_scale: 1.0) click to toggle source

Sets the selected internal loads to standards-based or typical values. For each category that is selected get all load instances. Remove all but the first instance if multiple instances. Add a new instance/definition if no instance exists. Modify the definition for the remaining instance to have the specified values. This method does not alter any loads directly assigned to spaces. This method skips plenums.

@param set_people [Boolean] if true, set the people density. Also, assign reasonable clothing, air velocity, and work efficiency inputs to allow reasonable thermal comfort metrics to be calculated. @param set_lights [Boolean] if true, set the lighting density, lighting fraction to return air, fraction radiant, and fraction visible. @param set_electric_equipment [Boolean] if true, set the electric equipment density @param set_gas_equipment [Boolean] if true, set the gas equipment density @param set_ventilation [Boolean] if true, set the ventilation rates (per-person and per-area) @param set_infiltration [Boolean] if true, set the infiltration rates @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb, line 47
def space_type_apply_internal_loads(space_type:,
                                    set_people: true,
                                    set_lights: true,
                                    set_electric_equipment: true,
                                    set_gas_equipment: true,
                                    set_ventilation: true,
                                    set_infiltration: true,
                                    lights_type: 'NECB_Default',
                                    lights_scale: 1.0)

  # Skip plenums
  # Check if the space type name
  # contains the word plenum.
  if space_type.name.get.to_s.downcase.include?('plenum')
    return false
  end

  if space_type.standardsSpaceType.is_initialized
    if space_type.standardsSpaceType.get.downcase.include?('plenum')
      return false
    end
  end

  # Get the space Type data from @standards data

  spacetype_data = @standards_data['tables']['space_types']['table']

  standards_building_type = space_type.standardsBuildingType.is_initialized ? space_type.standardsBuildingType.get : nil
  standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil
  space_type_properties = spacetype_data.detect { |s| (s['building_type'] == standards_building_type) && (s['space_type'] == standards_space_type) }

  # Need to add a check, or it'll crash on space_type_properties['occupancy_per_area'].to_f below
  if space_type_properties.nil?
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} was not found in the standards data.")
    return false
  end
  # People
  people_have_info = false
  occupancy_per_area = space_type_properties['occupancy_per_area'].to_f
  people_have_info = true unless occupancy_per_area.zero?

  if set_people && people_have_info

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

        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
        inst.remove
      end
    end

    # Modify the definition of the instance
    space_type.people.sort.each do |inst|
      definition = inst.peopleDefinition
      unless occupancy_per_area.zero?
        definition.setPeopleperSpaceFloorArea(OpenStudio.convert(occupancy_per_area / 1000, 'people/ft^2', 'people/m^2').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set occupancy to #{occupancy_per_area} people/1000 ft^2.")
      end

      # set fraction radiant  ##
      definition.setFractionRadiant(0.3)

      # Clothing schedule for thermal comfort metrics
      clothing_sch = space_type.model.getScheduleRulesetByName('Clothing Schedule')
      if clothing_sch.is_initialized
        clothing_sch = clothing_sch.get
      else
        clothing_sch = OpenStudio::Model::ScheduleRuleset.new(space_type.model)
        clothing_sch.setName('Clothing Schedule')
        clothing_sch.defaultDaySchedule.setName('Clothing Schedule Default Winter Clothes')
        clothing_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0)
        sch_rule = OpenStudio::Model::ScheduleRule.new(clothing_sch)
        sch_rule.daySchedule.setName('Clothing Schedule Summer Clothes')
        sch_rule.daySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.5)
        sch_rule.setStartDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(5), 1))
        sch_rule.setEndDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(9), 30))
      end
      inst.setClothingInsulationSchedule(clothing_sch)

      # Air velocity schedule for thermal comfort metrics
      air_velo_sch = space_type.model.getScheduleRulesetByName('Air Velocity Schedule')
      if air_velo_sch.is_initialized
        air_velo_sch = air_velo_sch.get
      else
        air_velo_sch = OpenStudio::Model::ScheduleRuleset.new(space_type.model)
        air_velo_sch.setName('Air Velocity Schedule')
        air_velo_sch.defaultDaySchedule.setName('Air Velocity Schedule Default')
        air_velo_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.2)
      end
      inst.setAirVelocitySchedule(air_velo_sch)

      # Work efficiency schedule for thermal comfort metrics
      work_efficiency_sch = space_type.model.getScheduleRulesetByName('Work Efficiency Schedule')
      if work_efficiency_sch.is_initialized
        work_efficiency_sch = work_efficiency_sch.get
      else
        work_efficiency_sch = OpenStudio::Model::ScheduleRuleset.new(space_type.model)
        work_efficiency_sch.setName('Work Efficiency Schedule')
        work_efficiency_sch.defaultDaySchedule.setName('Work Efficiency Schedule Default')
        work_efficiency_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0)
      end
      inst.setWorkEfficiencySchedule(work_efficiency_sch)
    end

  end

  # Lights
  apply_standard_lights(set_lights: set_lights,
                        space_type: space_type,
                        space_type_properties: space_type_properties,
                        lights_type: lights_type,
                        lights_scale: lights_scale)

  # Electric Equipment
  elec_equip_have_info = false
  elec_equip_per_area = space_type_properties['electric_equipment_per_area'].to_f
  elec_equip_frac_latent = space_type_properties['electric_equipment_fraction_latent'].to_f
  elec_equip_frac_radiant = space_type_properties['electric_equipment_fraction_radiant'].to_f
  elec_equip_frac_lost = space_type_properties['electric_equipment_fraction_lost'].to_f
  elec_equip_have_info = true unless elec_equip_per_area.zero?

  if set_electric_equipment && elec_equip_have_info

    # Remove all but the first instance
    instances = space_type.electricEquipment.sort
    if instances.size.zero?
      definition = OpenStudio::Model::ElectricEquipmentDefinition.new(space_type.model)
      definition.setName("#{space_type.name} Elec Equip Definition")
      instance = OpenStudio::Model::ElectricEquipment.new(definition)
      instance.setName("#{space_type.name} Elec Equip")
      instance.setSpaceType(space_type)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no electric equipment, one has been created.")
      instances << instance
    elsif instances.size > 1
      instances.each_with_index do |inst, i|
        next if i.zero?

        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
        inst.remove
      end
    end

    # Modify the definition of the instance
    space_type.electricEquipment.sort.each do |inst|
      definition = inst.electricEquipmentDefinition
      unless elec_equip_per_area.zero?
        definition.setWattsperSpaceFloorArea(OpenStudio.convert(elec_equip_per_area.to_f, 'W/ft^2', 'W/m^2').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set electric EPD to #{elec_equip_per_area} W/ft^2.")
      end
      unless elec_equip_frac_latent.zero?
        definition.setFractionLatent(elec_equip_frac_latent)
      end
      unless elec_equip_frac_radiant.zero?
        definition.setFractionRadiant(elec_equip_frac_radiant)
      end
      unless elec_equip_frac_lost.zero?
        definition.setFractionLost(elec_equip_frac_lost)
      end
    end

  end

  # Gas Equipment
  gas_equip_have_info = false
  gas_equip_per_area = space_type_properties['gas_equipment_per_area'].to_f
  gas_equip_frac_latent = space_type_properties['gas_equipment_fraction_latent'].to_f
  gas_equip_frac_radiant = space_type_properties['gas_equipment_fraction_radiant'].to_f
  gas_equip_frac_lost = space_type_properties['gas_equipment_fraction_lost'].to_f
  gas_equip_have_info = true unless gas_equip_per_area.zero?

  if set_gas_equipment && gas_equip_have_info

    # Remove all but the first instance
    instances = space_type.gasEquipment.sort
    if instances.size.zero?
      definition = OpenStudio::Model::GasEquipmentDefinition.new(space_type.model)
      definition.setName("#{space_type.name} Gas Equip Definition")
      instance = OpenStudio::Model::GasEquipment.new(definition)
      instance.setName("#{space_type.name} Gas Equip")
      instance.setSpaceType(space_type)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no gas equipment, one has been created.")
      instances << instance
    elsif instances.size > 1
      instances.each_with_index do |inst, i|
        next if i.zero?

        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
        inst.remove
      end
    end

    # Modify the definition of the instance
    space_type.gasEquipment.sort.each do |inst|
      definition = inst.gasEquipmentDefinition
      unless gas_equip_per_area.zero?
        definition.setWattsperSpaceFloorArea(OpenStudio.convert(gas_equip_per_area.to_f, 'Btu/hr*ft^2', 'W/m^2').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set gas EPD to #{elec_equip_per_area} Btu/hr*ft^2.")
      end
      unless gas_equip_frac_latent.zero?
        definition.setFractionLatent(gas_equip_frac_latent)
      end
      unless gas_equip_frac_radiant.zero?
        definition.setFractionRadiant(gas_equip_frac_radiant)
      end
      unless gas_equip_frac_lost.zero?
        definition.setFractionLost(gas_equip_frac_lost)
      end
    end

  end

  # Ventilation
  ventilation_have_info = false
  ventilation_per_area = space_type_properties['ventilation_per_area'].to_f
  ventilation_per_person = space_type_properties['ventilation_per_person'].to_f
  ventilation_ach = space_type_properties['ventilation_air_changes'].to_f
  ventilation_occupancy_per_area = space_type_properties['ventilation_occupancy_rate_people_per_1000ft2'].to_f
  ventilation_have_info = true unless ventilation_per_area.zero?
  ventilation_have_info = true unless ventilation_per_person.zero?
  ventilation_have_info = true unless ventilation_ach.zero?

  # Get the design OA or create a new one if none exists
  ventilation = space_type.designSpecificationOutdoorAir
  if ventilation.is_initialized
    ventilation = ventilation.get
  else
    ventilation = OpenStudio::Model::DesignSpecificationOutdoorAir.new(space_type.model)
    ventilation.setName("#{space_type.name} Ventilation")
    space_type.setDesignSpecificationOutdoorAir(ventilation)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no ventilation specification, one has been created.")
  end

  if set_ventilation && ventilation_have_info

    # Modify the ventilation properties
    ventilation.setOutdoorAirMethod('Sum')
    unless ventilation_per_area.zero?
      ventilation.setOutdoorAirFlowperFloorArea(OpenStudio.convert(ventilation_per_area.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set ventilation per area to #{ventilation_per_area} cfm/ft^2.")
    end
    unless ventilation_per_person.zero?
      # For BTAP we often use an occupancy per area rate for ventilation which is different from the one used for
      # everything else.  The mod_ventilation_per_person rate adjusts the per person ventilation rate so that the
      # proper ventilation rate is calculated when using the general occupant per area rate.
      mod_ventilation_per_person = ventilation_per_person * ventilation_occupancy_per_area / occupancy_per_area
      ventilation.setOutdoorAirFlowperPerson(OpenStudio.convert(mod_ventilation_per_person.to_f, 'ft^3/min*person', 'm^3/s*person').get)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set ventilation per person to #{mod_ventilation_per_person} cfm/person.")
    end
    unless ventilation_ach.zero?
      ventilation.setOutdoorAirFlowAirChangesperHour(ventilation_ach)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set ventilation to #{ventilation_ach} ACH.")
    end

  elsif set_ventilation && !ventilation_have_info

    # All space types must have a design spec OA
    # object for ventilation controls to work correctly,
    # even if the values are all zero.
    ventilation.setOutdoorAirFlowperFloorArea(0)
    ventilation.setOutdoorAirFlowperPerson(0)
    ventilation.setOutdoorAirFlowAirChangesperHour(0)

  end

  # Infiltration
  infiltration_have_info = false
  infiltration_per_area_ext = space_type_properties['infiltration_per_exterior_area'].to_f
  infiltration_per_area_ext_wall = space_type_properties['infiltration_per_exterior_wall_area'].to_f
  infiltration_ach = space_type_properties['infiltration_air_changes'].to_f
  unless infiltration_per_area_ext.zero? && infiltration_per_area_ext_wall.zero? && infiltration_ach.zero?
    infiltration_have_info = true
  end

  return unless set_infiltration && infiltration_have_info

  # Remove all but the first instance
  instances = space_type.spaceInfiltrationDesignFlowRates.sort
  if instances.size.zero?
    instance = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(space_type.model)
    instance.setName("#{space_type.name} Infiltration")
    instance.setSpaceType(space_type)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no infiltration objects, one has been created.")
    instances << instance
  elsif instances.size > 1
    instances.each_with_index do |inst, i|
      next if i.zero?

      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
      inst.remove
    end
  end

  # Modify each instance
  space_type.spaceInfiltrationDesignFlowRates.sort.each do |inst|
    unless infiltration_per_area_ext.zero?
      inst.setFlowperExteriorSurfaceArea(OpenStudio.convert(infiltration_per_area_ext.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set infiltration to #{ventilation_ach} per ft^2 exterior surface area.")
    end
    unless infiltration_per_area_ext_wall.zero?
      inst.setFlowperExteriorWallArea(OpenStudio.convert(infiltration_per_area_ext_wall.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set infiltration to #{infiltration_per_area_ext_wall} per ft^2 exterior wall area.")
    end
    unless infiltration_ach.zero?
      inst.setAirChangesperHour(infiltration_ach)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set infiltration to #{ventilation_ach} ACH.")
    end
  end
end
store_space_sizing_loads(model) click to toggle source

Method to store space sizing loads. This is needed because later when the zones are destroyed this information will be lost.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 191
def store_space_sizing_loads(model)
  @stored_space_heating_sizing_loads = {}
  @stored_space_cooling_sizing_loads = {}
  model.getSpaces.sort.each do |space|
    space_type = space.spaceType.get.standardsSpaceType.get

    # error if zone design load methods are not available
    if space.model.version < OpenStudio::VersionString.new('3.6.0')
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.autozone', "Required ThermalZone methods .autosizedHeatingDesignLoad and .autosizedCoolingDesignLoad are not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.")
    end

    @stored_space_heating_sizing_loads[space] = space_type == '- undefined -' ? 0.0 : space.thermalZone.get.autosizedHeatingDesignLoad.get / space.floorArea
    @stored_space_cooling_sizing_loads[space] = space_type == '- undefined -' ? 0.0 : space.thermalZone.get.autosizedCoolingDesignLoad.get / space.floorArea
  end
end
stored_space_cooling_load(space) click to toggle source

Returns the cooling load per area for space after sizing runs has been done.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 220
def stored_space_cooling_load(space)
  if @stored_space_cooling_sizing_loads.nil?
    # do a sizing run.
    raise('autorun sizing run failed!') if model_run_sizing_run(space.model, "#{Dir.pwd}/autozone") == false

    # collect sizing information on each space.
    store_space_sizing_loads(space.model)
  end
  @stored_space_cooling_sizing_loads[space]
end
stored_space_heating_load(space) click to toggle source

Returns heating load per area for space after sizing run has been done.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 208
def stored_space_heating_load(space)
  if @stored_space_heating_sizing_loads.nil?
    # do a sizing run.
    raise('autorun sizing run failed!') if model_run_sizing_run(space.model, "#{Dir.pwd}/autozone") == false

    # collect sizing information on each space.
    store_space_sizing_loads(space.model)
  end
  @stored_space_heating_sizing_loads[space]
end
stored_zone_cooling_load(zone) click to toggle source

Returns the cooling load per area for zone after sizing runs has been done.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 241
def stored_zone_cooling_load(zone)
  total = 0.0
  zone.spaces.each do |space|
    total += stored_space_cooling_load(space)
  end
  return total
end
stored_zone_heating_load(zone) click to toggle source

Returns the heating load per area for zone after sizing runs has been done.

# File lib/openstudio-standards/standards/necb/NECB2011/autozone.rb, line 232
def stored_zone_heating_load(zone)
  total = 0.0
  zone.spaces.each do |space|
    total += stored_space_heating_load(space)
  end
  return total
end
surfaces_are_in_contact?(surf1,surf2) click to toggle source

check if two surfaces are in contact. For every two consecutive vertices on surface 1, loop through two consecutive vertices of surface two. Then check whether the vertices of surfaces 2 are on the same line as the vertices from surface 1. If the two vectors defined by the two vertices on surface 1 and those on surface 2 overlap, then the two surfaces are in contact. If a side from surface 2 is in contact with a side from surface 1, the length of the side from surface 2 is limited to the length of the side from surface 1. created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 789
def surfaces_are_in_contact?(surf1,surf2)
  surfaces_in_contact = false
  vert1 = surf1.vertices[0]
  for index1 in 1..surf1.vertices.size
    if index1 < surf1.vertices.size
      vert2 = surf1.vertices[index1]
    else
      vert2 = surf1.vertices[0]
    end
    seg12_length = ((vert2.x-vert1.x)**2+(vert2.y-vert1.y)**2+(vert2.z-vert1.z)**2)**0.5
    surf2_seg_length = 0.0
    vert3 = surf2.vertices[0]
    for index2 in 1..surf2.vertices.size
      if index2 < surf2.vertices.size
        vert4 = surf2.vertices[index2]
      else
        vert4 = surf2.vertices[0]
      end
      vert1_2_3_same_line_and_dir = three_vertices_same_line_and_dir?(vert1,vert2,vert3)
      if vert1_2_3_same_line_and_dir
        vert1_2_4_same_line_and_dir = three_vertices_same_line_and_dir?(vert1,vert2,vert4)
        if vert1_2_4_same_line_and_dir
          surfaces_in_contact = true
          seg34_length = ((vert4.x-vert3.x)**2+(vert4.y-vert3.y)**2+(vert4.z-vert3.z)**2)**0.5
          surf2_seg_length += seg34_length
          raise("Surface #{surf2.name.to_s} has sides in contact with surface #{surf1.name.to_s} but with a length greater than the max.") if surf2_seg_length > seg12_length
        end
      end
      vert3 = vert4
    end
    vert1 = vert2
  end

  return surfaces_in_contact
end
thermal_zone_demand_control_ventilation_required?(thermal_zone, climate_zone) click to toggle source

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

@return [Boolean] Returns true if required, false if not. @todo Add exception logic for 90.1-2013

for cells, sickrooms, labs, barbers, salons, and bowling alleys
# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1512
def thermal_zone_demand_control_ventilation_required?(thermal_zone, climate_zone)
  return false
end
thermal_zone_get_centroid_per_floor(thermal_zone) click to toggle source

This method cycles through the spaces in a thermal zone and then sorts them by story. The method then cycles through the spaces on a story and then calculates the centroid of the spaces in the thermal zone on that floor. The method returns an array of hashes, one for each story. Each hash has the following structure:

{
    story_name: Name of a given story.
    spaces: Array containing all of the spaces in the thermal zone on the story in story_name.
    centroid: Array containing the x, y, and z coordinates of the centroid of the ceilings of the spaces
              listed in 'spaces:' above.
    ceiling_area: Total area of the ceilings of the spaces in 'spaces:' above.
}

Only spaces which are conditioned (heated or cooled) and are not plenums are included.

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 1808
def thermal_zone_get_centroid_per_floor(thermal_zone)
  stories = []
  thermal_zone.spaces.sort.each do |space|
    spaceType_name = space.spaceType.get.nameString
    sp_type = spaceType_name[15..-1]
    # Including regular expressions in the following match for cases where extra characters, which do not belong, are
    # added to either the space type in the model or the space type reference file.
    sp_type_info = @standards_data['space_types'].detect do |data|
      (Regexp.new(data['space_type'].to_s.upcase).match(sp_type.upcase) || Regexp.new(sp_type.upcase).match(data['space_type'].to_s.upcase) || (data['space_type'].to_s.upcase == sp_type.upcase)) &&
        (data['building_type'].to_s == 'Space Function')
    end
    if sp_type_info.nil?
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.thermal_zone_get_centroid_per_floor', "The space type called #{sp_type} could not be found.  Please check that the schedules.json file is available and that the space types are spelled correctly")
      next
    end
    # Determine if space is heated or cooled via spacetype heating or cooling setpoints also checking if the space is
    # a plenum by checking if there is a hvac system associtated with it
    if sp_type_info['heating_setpoint_schedule'].nil?
      heated = false
    else
      heated = true
    end
    if sp_type_info['cooling_setpoint_schedule'].nil?
      cooled = false
    else
      cooled = true
    end
    if (sp_type_info['necb_hvac_system_selection_type'] == '- undefined -') || /undefined/.match(sp_type_info['necb_hvac_system_selection_type'])
      not_plenum = false
    else
      not_plenum = true
    end
    # If the spaces are heated or cooled and are not a plenum then continue
    if (heated || cooled) && not_plenum
      # Get the story name and sit it to none if there is no story name
      story_name = space.buildingStory.get.nameString
      story_name = 'none' if story_name.nil?
      # If this is the first story in the arry then add a new one.
      if stories.empty?
        stories << {
          story_name: story_name,
          spaces: [space],
          centroid: [0, 0, 0],
          ceiling_area: 0
        }
        next
      else
        # If this is not the first story in the array check if the story already is in the array.
        i = nil
        stories.each_with_index do |storycheck, index|
          if storycheck[:story_name] == story_name
            i = index
          end
        end
        # If the story is not in the array then add it.
        if i.nil?
          stories << {
            story_name: story_name,
            spaces: [space],
            centroid: [0, 0, 0],
            ceiling_area: 0
          }
        else
          # If the story is already in the arry then add the space to the array of spaces for that story
          stories[i][:spaces] << space
        end
      end
    end
  end
  # Go through each story in the array above
  stories.each do |story|
    tz_centre = [0, 0, 0, 0]
    # Go through each space in a given story
    story[:spaces].each do |space|
      # Determine the top surface of the space and calculate it's centroid.
      # Get the coordinates of the origin for the space (the coordinates of points in the space are relative to this).
      xOrigin = space.xOrigin
      yOrigin = space.yOrigin
      zOrigin = space.zOrigin
      # Go through each surface in the space and find ceilings by determining which is called 'RoofCeiing'.  Find the
      # overall centroid of all the ceilings in the spaces.  Find centroid by multiplying the centroid of the surfaces
      # multiplied by the area of the surface and add them all up.  Then divide this by the overall area.  This is the
      # area weighted average of the centroid coordinates.
      ceiling_centroid = [0, 0, 0, 0]
      space.surfaces.each do |sp_surface|
        if sp_surface.surfaceType.to_s.upcase == 'ROOFCEILING'
          ceiling_centroid[0] = ceiling_centroid[0] + sp_surface.centroid.x.to_f * sp_surface.grossArea.to_f
          ceiling_centroid[1] = ceiling_centroid[1] + sp_surface.centroid.y.to_f * sp_surface.grossArea.to_f
          ceiling_centroid[2] = ceiling_centroid[2] + sp_surface.centroid.z.to_f * sp_surface.grossArea.to_f
          ceiling_centroid[3] = ceiling_centroid[3] + sp_surface.grossArea
        end
      end

      ceiling_centroid[0] = ceiling_centroid[0] / ceiling_centroid[3]
      ceiling_centroid[1] = ceiling_centroid[1] / ceiling_centroid[3]
      ceiling_centroid[2] = ceiling_centroid[2] / ceiling_centroid[3]

      # This part is used to determine the overall x, y centre of the thermal zone.  This is determined by summing the
      # x and y components times the ceiling area and diving by the total ceiling area.  I also added z since the
      # ceilings may not be all have the same height.
      tz_centre[0] += (ceiling_centroid[0] + xOrigin) * ceiling_centroid[3]
      tz_centre[1] += (ceiling_centroid[1] + yOrigin) * ceiling_centroid[3]
      tz_centre[2] += (ceiling_centroid[2] + zOrigin) * ceiling_centroid[3]
      tz_centre[3] += (ceiling_centroid[3])
    end
    tz_centre[0] /= tz_centre[3]
    tz_centre[1] /= tz_centre[3]
    tz_centre[2] /= tz_centre[3]
    # Update the :centroid and :ceiling_area hashes for the story to reflect the x, y, and z coordinates of the
    # overall centroid of spaces on that floor.
    story[:centroid] = tz_centre[0..2]
    story[:ceiling_area] = tz_centre[3]
  end
  return stories
end
three_vertices_same_line_and_dir?(vert1,vert2,vert3) click to toggle source

check that three vertices are on the same line. Also check that the vectors from vert1 and vert2 and from vert1 and vert3 are in the same direction. created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 914
def three_vertices_same_line_and_dir?(vert1,vert2,vert3)
  tol = 1.0e-5
  vec12x,vec12y,vec12z = -vert1.x+vert2.x,-vert1.y+vert2.y,-vert1.z+vert2.z # x,y,z of vector 12
  vec12x = 0.0 if vec12x.abs < tol
  vec12y = 0.0 if vec12y.abs < tol
  vec12z = 0.0 if vec12z.abs < tol
  vec13x,vec13y,vec13z = -vert1.x+vert3.x,-vert1.y+vert3.y,-vert1.z+vert3.z # x,y,z of vector 13
  vec13x = 0.0 if vec13x.abs < tol
  vec13y = 0.0 if vec13y.abs < tol
  vec13z = 0.0 if vec13z.abs < tol
  # x,y,z of the cross product of the vectors 12 and 13
  cross_12_13_x = vec12y*vec13z-vec12z*vec13y
  cross_12_13_y = vec12z*vec13x-vec12x*vec13z
  cross_12_13_z = vec12x*vec13y-vec12y*vec13x
  # vectors are in parallel when x,y,z of cross product are 0.0
  vertices_on_same_line = false
  vertices_on_same_line = true if (cross_12_13_x == 0.0) && (cross_12_13_y == 0.0) && (cross_12_13_z == 0.0)
  vectors_same_direction = false
  if vertices_on_same_line
    vec12_13_x_factor = vec13x*vec12x
    vec12_13_y_factor = vec13y*vec12y
    vec12_13_z_factor = vec13z*vec12z
    vectors_same_direction = true if (vec12_13_x_factor >= 0.0) && (vec12_13_y_factor >= 0.0) && (vec12_13_z_factor >= 0.0)
  end
  same_line_same_dir = vertices_on_same_line && vectors_same_direction

  return same_line_same_dir
end
update_sys_name(airloop, sys_abbr: nil, sys_oa: nil, sys_hr: nil, sys_htg: nil, sys_clg: nil, sys_sf: nil, zone_htg: nil, zone_clg: nil, sys_rf: nil) click to toggle source

Method to update the base system name based on the inputs provided. Only the parts of the name with string inputs are updated

# File lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb, line 2290
def update_sys_name(airloop,
                    sys_abbr: nil,
                    sys_oa: nil,
                    sys_hr: nil,
                    sys_htg: nil,
                    sys_clg: nil,
                    sys_sf: nil,
                    zone_htg: nil,
                    zone_clg: nil,
                    sys_rf: nil)
  name_parts = airloop.name.to_s.split('|').reject(&:empty?)
  if sys_abbr.is_a? String then name_parts[0] = sys_abbr end
  if sys_oa.is_a? String then name_parts[1] = sys_oa end
  for i in 0..name_parts.size - 1
    if (name_parts[i].include? 'shr>') && (sys_hr.is_a? String)
      name_parts[i] = "shr>#{sys_hr}"
    elsif (name_parts[i].include? 'sh>') && (sys_htg.is_a? String)
      name_parts[i] = "sh>#{sys_htg}"
    elsif (name_parts[i].include? 'sc>') && (sys_clg.is_a? String)
      name_parts[i] = "sc>#{sys_clg}"
    elsif (name_parts[i].include? 'ssf') && (sys_sf.is_a? String)
      name_parts[i] = "ssf>#{sys_sf}"
    elsif (name_parts[i].include? 'zh>') && (zone_htg.is_a? String)
      name_parts[i] = "zh>#{zone_htg}"
    elsif (name_parts[i].include? 'zc>') && (zone_clg.is_a? String)
      name_parts[i] = "zc>#{zone_clg}"
    elsif (name_parts[i].include? 'srf>') && (sys_rf.is_a? String)
      name_parts[i] = "srf>#{sys_rf}"
    end
  end
  sys_name = ''
  name_parts.each { |part| sys_name += "#{part}|" }

  # Check if the last part of the system name is an integer.  If it is, then remove the last part from the system name.
  check_int = begin
                Integer(name_parts.last.strip)
              rescue StandardError
                nil
              end
  sys_name = sys_name.chop unless check_int.nil?

  airloop.setName(sys_name)
end
validate_and_upate_space_types(model) click to toggle source

This method will validate that the space types in the model are indeed the correct NECB spacetypes names.

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1139
def validate_and_upate_space_types(model)
  space_type_vintage = determine_spacetype_vintage(model)
  if space_type_vintage.nil?
    message = "These some of the spacetypes in the model are not part of any necb standard.\n  Please ensure all spacetype in model are correct."
    puts "Error: #{message}"
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.Standards.NECB', message)
    return false
  elsif space_type_vintage == self.class.name
    # the spacetype in the model match the version we are trying to create.
    # no translation neccesary.
    return true
  else
    # Need to translate to current vintage.
    no_errors = true
    st_model_vintage_string = "#{space_type_vintage}_space_type"
    bt_model_vintage_string = "#{space_type_vintage}_building_type"
    st_target_vintage_string = "#{self.class.name}_space_type"
    bt_target_vintage_string = "#{self.class.name}_building_type"
    space_type_upgrade_map = @standards_data['space_type_upgrade_map']
    model.getSpaceTypes.sort.each do |st|
      space_type_map = space_type_upgrade_map.detect { |row| (row[st_model_vintage_string] == st.standardsSpaceType.get.to_s) && (row[bt_model_vintage_string] == st.standardsBuildingType.get.to_s) }
      st.setStandardsBuildingType(space_type_map[bt_target_vintage_string].to_s.strip)
      raise('could not set buildingtype') unless st.setStandardsBuildingType(space_type_map[bt_target_vintage_string].to_s.strip)
      raise('could not set this') unless st.setStandardsSpaceType(space_type_map[st_target_vintage_string].to_s.strip)

      # Set name of spacetype to new name.
      st.setName("#{st.standardsBuildingType.get} #{st.standardsSpaceType.get}")
    end
    return no_errors
  end
end
water_heater_mixed_apply_efficiency(water_heater_mixed) click to toggle source

Applies the standard efficiency ratings and typical losses and paraisitic loads to this object. Efficiency and skin loss coefficient (UA) Per PNNL www.energycodes.gov/sites/default/files/documents/PrototypeModelEnhancements_2014_0.pdf Appendix A: Service Water Heating

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

# File lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb, line 103
def water_heater_mixed_apply_efficiency(water_heater_mixed)
  # Get the capacity of the water heater
  # @todo add capability to pull autosized water heater capacity
  # if the Sizing:WaterHeater object is ever implemented in OpenStudio.
  capacity_w = water_heater_mixed.heaterMaximumCapacity
  if capacity_w.empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, cannot find capacity, standard will not be applied.")
    return false
  else
    capacity_w = capacity_w.get
  end
  capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
  capacity_kbtu_per_hr = OpenStudio.convert(capacity_w, 'W', 'kBtu/hr').get

  # Get the volume of the water heater
  # @todo add capability to pull autosized water heater volume
  # if the Sizing:WaterHeater object is ever implemented in OpenStudio.
  volume_m3 = water_heater_mixed.tankVolume
  if volume_m3.empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, cannot find volume, standard will not be applied.")
    return false
  else
    volume_m3 = volume_m3.get
  end
  volume_gal = OpenStudio.convert(volume_m3, 'm^3', 'gal').get

  # Get the heater fuel type
  fuel_type = water_heater_mixed.heaterFuelType
  unless fuel_type == 'NaturalGas' || fuel_type == 'Electricity' || fuel_type == 'FuelOilNo2'
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, fuel type of #{fuel_type} is not yet supported, standard will not be applied.")
  end

  # Calculate the water heater efficiency and
  # skin loss coefficient (UA)
  # Calculate the energy factor (EF)
  # From PNNL http://www.energycodes.gov/sites/default/files/documents/PrototypeModelEnhancements_2014_0.pdf
  # Appendix A: Service Water Heating
  water_heater_eff = nil
  ua_btu_per_hr_per_f = nil
  sl_btu_per_hr = nil
  case fuel_type
  when 'Electricity'
    volume_l_per_s = volume_m3 * 1000
    if capacity_btu_per_hr <= OpenStudio.convert(12, 'kW', 'Btu/hr').get
      # Fixed water heater efficiency per PNNL
      water_heater_eff = 1
      # Calculate the max allowable standby loss (SL)
      sl_w = if volume_l_per_s < 270
               40 + 0.2 * volume_l_per_s # assume bottom inlet
             else
               0.472 * volume_l_per_s - 33.5
               # assume bottom inlet
             end
      sl_btu_per_hr = OpenStudio.convert(sl_w, 'W', 'Btu/hr').get
    else
      # Fixed water heater efficiency per PNNL
      water_heater_eff = 1
      # Calculate the max allowable standby loss (SL)   # use this - NECB does not give SL calculation for cap > 12 kW
      sl_btu_per_hr = 20 + (35 * Math.sqrt(volume_gal))
    end
    # Calculate the skin loss coefficient (UA)
    ua_btu_per_hr_per_f = sl_btu_per_hr / 70
  when 'NaturalGas'
    if capacity_btu_per_hr <= 75_000
      # Fixed water heater thermal efficiency per PNNL
      water_heater_eff = 0.82
      # Calculate the minimum Energy Factor (EF)
      base_ef = 0.67
      vol_drt = 0.0019
      ef = base_ef - (vol_drt * volume_gal)
      # Calculate the Recovery Efficiency (RE)
      # based on a fixed capacity of 75,000 Btu/hr
      # and a fixed volume of 40 gallons by solving
      # this system of equations:
      # ua = (1/.95-1/re)/(67.5*(24/41094-1/(re*cap)))
      # 0.82 = (ua*67.5+cap*re)/cap
      cap = 75_000.0
      re = (Math.sqrt(6724 * ef**2 * cap**2 + 40_409_100 * ef**2 * cap - 28_080_900 * ef * cap + 29_318_000_625 * ef**2 - 58_636_001_250 * ef + 29_318_000_625) + 82 * ef * cap + 171_225 * ef - 171_225) / (200 * ef * cap)
      # Calculate the skin loss coefficient (UA)
      # based on the actual capacity.
      ua_btu_per_hr_per_f = (water_heater_eff - re) * capacity_btu_per_hr / 67.5
    else
      # Thermal efficiency requirement from 90.1
      et = 0.8
      # Calculate the max allowable standby loss (SL)
      cap_adj = 800
      vol_drt = 110
      sl_btu_per_hr = (capacity_btu_per_hr / cap_adj + vol_drt * Math.sqrt(volume_gal))
      # Calculate the skin loss coefficient (UA)
      ua_btu_per_hr_per_f = (sl_btu_per_hr * et) / 70
      # Calculate water heater efficiency
      water_heater_eff = (ua_btu_per_hr_per_f * 70 + capacity_btu_per_hr * et) / capacity_btu_per_hr
    end
  end

  # Convert to SI
  ua_btu_per_hr_per_c = OpenStudio.convert(ua_btu_per_hr_per_f, 'Btu/hr*R', 'W/K').get
  # Set the water heater properties
  # Efficiency
  water_heater_mixed.setHeaterThermalEfficiency(water_heater_eff)
  # Skin loss
  water_heater_mixed.setOffCycleLossCoefficienttoAmbientTemperature(ua_btu_per_hr_per_c)
  water_heater_mixed.setOnCycleLossCoefficienttoAmbientTemperature(ua_btu_per_hr_per_c)
  # @todo Parasitic loss (pilot light)
  # PNNL document says pilot lights were removed, but IDFs
  # still have the on/off cycle parasitic fuel consumptions filled in
  water_heater_mixed.setOnCycleParasiticFuelType(fuel_type)
  # self.setOffCycleParasiticFuelConsumptionRate(??)
  water_heater_mixed.setOnCycleParasiticHeatFractiontoTank(0)
  water_heater_mixed.setOffCycleParasiticFuelType(fuel_type)
  # self.setOffCycleParasiticFuelConsumptionRate(??)
  water_heater_mixed.setOffCycleParasiticHeatFractiontoTank(0.8)

  # set part-load performance curve
  if (fuel_type == 'NaturalGas') || (fuel_type == 'FuelOilNo2')
    plf_vs_plr_curve = model_add_curve(water_heater_mixed.model, 'SWH-EFFFPLR-NECB2011')
    water_heater_mixed.setPartLoadFactorCurve(plf_vs_plr_curve)
  end

  # Append the name with standards information
  water_heater_mixed.setName("#{water_heater_mixed.name} #{water_heater_eff.round(3)} Therm Eff")
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.WaterHeaterMixed', "For #{template}: #{water_heater_mixed.name}; thermal efficiency = #{water_heater_eff.round(3)}, skin-loss UA = #{ua_btu_per_hr_per_f.round}Btu/hr-R")
  return true
end
zone_hvac_component_occupancy_ventilation_control(zone_hvac_component) click to toggle source

do not apply zone hvac ventilation control

# File lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb, line 1094
def zone_hvac_component_occupancy_ventilation_control(zone_hvac_component)
  return false
end