class ASHRAE901PRM

This abstract class holds methods that many versions of ASHRAE 90.1 share. If a method in this class is redefined by a subclass, the implementation in the subclass is used. @abstract

Public Class Methods

new() click to toggle source
Calls superclass method Standard::new
# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 8
def initialize
  super()
  load_standards_database
  @sizing_run_dir = Dir.pwd
end

Public Instance Methods

add_ems_for_multiple_chiller_pumps_w_secondary_plant(model, primary_plant) click to toggle source

Adds EMS program for pumps serving 3 chillers on primary + secondary loop. This was due to an issue when modeling two dedicated loops. The headered pumps or dedicated constant speed pumps operate at full flow as long as there’s a load on the loop unless this EMS is in place. @param model [OpenStudio::Model] OpenStudio model with plant loops @param primary_plant [OpenStudio::Model::PlantLoop] Primary chilled water loop with chillers

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb, line 252
def add_ems_for_multiple_chiller_pumps_w_secondary_plant(model, primary_plant)
  # Aggregate array of chillers on primary plant supply side
  chiller_list = []

  primary_plant.supplyComponents.each do |sc|
    if sc.to_ChillerElectricEIR.is_initialized
      chiller_list << sc.to_ChillerElectricEIR.get
    end
  end

  num_of_chillers = chiller_list.length # Either 2 or 3

  return if num_of_chillers <= 1

  plant_name = primary_plant.name.to_s

  # Make a variable to track the chilled water demand
  chw_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Plant Supply Side Cooling Demand Rate')
  chw_sensor.setKeyName(plant_name)
  chw_sensor.setName("#{plant_name.gsub(/[-\s]+/, '_')}_CHW_DEMAND")

  # Sort chillers by their reference capacity
  sorted_chiller_list = chiller_list.sort_by { |chiller| chiller.referenceCapacity.get.to_f}

  # Make pump specific parameters for EMS. Use counter
  sorted_chiller_list.each_with_index do |chiller, i|
    # Get chiller pump
    pump_name = "#{chiller.name} Inlet Pump"
    pump = model.getPumpVariableSpeedByName(pump_name).get

    # Set EMS names
    ems_pump_flow_name   =      "CHILLER_PUMP_#{i + 1}_FLOW"
    ems_pump_status_name =      "CHILLER_PUMP_#{i + 1}_STATUS"
    ems_pump_design_flow_name = "CHILLER_PUMP_#{i + 1}_DES_FLOW"

    # ---- Actuators ----

    # Pump Flow Actuator
    actuator_pump_flow = OpenStudio::Model::EnergyManagementSystemActuator.new(pump, 'Pump', 'Pump Mass Flow Rate')
    actuator_pump_flow.setName(ems_pump_flow_name)

    # Pump Status Actuator
    actuator_pump_status = OpenStudio::Model::EnergyManagementSystemActuator.new(pump,
                                                                                 'Plant Component Pump:VariableSpeed',
                                                                                 'On/Off Supervisory')
    actuator_pump_status.setName(ems_pump_status_name)

    # ---- Internal Variable ----

    internal_variable = OpenStudio::Model::EnergyManagementSystemInternalVariable.new(model, 'Pump Maximum Mass Flow Rate')
    internal_variable.setInternalDataIndexKeyName(pump_name)
    internal_variable.setName(ems_pump_design_flow_name)
  end

  # Write EMS program
  if num_of_chillers > 3
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "EMS Code for multiple chiller pump has not been written for greater than 2 chillers. This has #{num_of_chillers} chillers")
  elsif num_of_chillers == 3
    add_ems_program_for_3_pump_chiller_plant(model, sorted_chiller_list, primary_plant)
  elsif num_of_chillers == 2
    add_ems_program_for_2_pump_chiller_plant(model, sorted_chiller_list, primary_plant)
  end

  # Update chilled water loop operation scheme to work with updated EMS ranges
  stage_chilled_water_loop_operation_schemes(model, primary_plant)
end
add_ems_program_for_2_pump_chiller_plant(model, sorted_chiller_list, primary_plant) click to toggle source

Adds EMS program for pumps serving 2 chillers on primary + secondary loop. This was due to an issue when modeling two dedicated loops. The headered pumps or dedicated constant speed pumps operate at full flow as long as there’s a load on the loop unless this EMS is in place. @param model [OpenStudio::Model] OpenStudio model with plant loops @param sorted_chiller_list [Array] Array of chillers in primary_plant sorted by capacity @param primary_plant [OpenStudio::Model::PlantLoop] Primary chilled water loop with chillers

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb, line 416
def add_ems_program_for_2_pump_chiller_plant(model, sorted_chiller_list, primary_plant)
  plant_name = primary_plant.name.to_s

  # Break out sorted chillers and get their respective capacities
  small_chiller = sorted_chiller_list[0]
  large_chiller = sorted_chiller_list[1]

  capacity_small_chiller = small_chiller.referenceCapacity.get
  capacity_large_chiller = large_chiller.referenceCapacity.get

  chw_demand = "#{primary_plant.name.to_s.gsub(/[-\s]+/, '_')}_CHW_DEMAND"

  ems_pump_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
  ems_pump_program.setName("#{plant_name.gsub(/[-\s]+/, '_')}_Pump_EMS")
  ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = NULL,  !- Program Line 1')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = NULL,  !- Program Line 2')
  ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = NULL,  !- A3')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = NULL,  !- A4')
  ems_pump_program.addLine("IF #{chw_demand} <= #{0.8 * capacity_small_chiller},  !- A5")
  ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 0,  !- A6')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = 0,  !- A7')
  ems_pump_program.addLine("ELSEIF #{chw_demand} <= #{capacity_large_chiller},  !- A8")
  ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 0,  !- A9')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1,  !- A10')
  ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = 0,  !- A11')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW,  !- A12')
  ems_pump_program.addLine("ELSEIF #{chw_demand} > #{capacity_large_chiller},  !- A13")
  ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 1,  !- A14')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1,  !- A15')
  ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = CHILLER_PUMP_1_DES_FLOW,  !- A16')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW,  !- A17')
  ems_pump_program.addLine('ENDIF  !- A18')

  ems_pump_program_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
  ems_pump_program_manager.setName("#{plant_name.gsub(/[-\s]+/, '_')}_Pump_Program_Manager")
  ems_pump_program_manager.setCallingPoint('InsideHVACSystemIterationLoop')
  ems_pump_program_manager.addProgram(ems_pump_program)
end
add_ems_program_for_3_pump_chiller_plant(model, sorted_chiller_list, primary_plant) click to toggle source

Adds EMS program for pumps serving 3 chillers on primary + secondary loop. This was due to an issue when modeling two dedicated loops. The headered pumps or dedicated constant speed pumps operate at full flow as long as there’s a load on the loop unless this EMS is in place. @param model [OpenStudio::Model] OpenStudio model with plant loops @param sorted_chiller_list [Array] Array of chillers in primary_plant sorted by capacity @param primary_plant [OpenStudio::Model::PlantLoop] Primary chilled water loop with chillers

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb, line 461
def add_ems_program_for_3_pump_chiller_plant(model, sorted_chiller_list, primary_plant)
  plant_name = primary_plant.name.to_s

  # Break out sorted chillers and get their respective capacities
  primary_chiller = sorted_chiller_list[0]
  medium_chiller = sorted_chiller_list[1]
  large_chiller = sorted_chiller_list[2]

  capacity_80_pct_small = 0.8 * primary_chiller.referenceCapacity.get
  capacity_medium_chiller = medium_chiller.referenceCapacity.get
  capacity_large_chiller = large_chiller.referenceCapacity.get

  if capacity_80_pct_small >= capacity_medium_chiller
    first_stage_capacity = capacity_medium_chiller
  else
    first_stage_capacity = capacity_80_pct_small
  end

  chw_demand = "#{primary_plant.name.to_s.gsub(/[-\s]+/, '_')}_CHW_DEMAND"

  ems_pump_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
  ems_pump_program.setName("#{plant_name.gsub(/[-\s]+/, '_')}_Pump_EMS")
  ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = NULL,  !- Program Line 1')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = NULL,  !- Program Line 2')
  ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = NULL,  !- A4')
  ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = NULL,  !- A5')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = NULL,  !- A6')
  ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = NULL,  !- A7')
  ems_pump_program.addLine("IF #{chw_demand} <= #{first_stage_capacity},  !- A8")
  ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 0,  !- A9')
  ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = 0,  !- A10')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = 0,  !- A11')
  ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = 0,  !- A12')

  if capacity_80_pct_small < capacity_medium_chiller
    ems_pump_program.addLine("ELSEIF #{chw_demand} <= #{capacity_medium_chiller},  !- A13")
    ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 0,  !- A14')
    ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1,  !- A15')
    ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = 0,  !- A16')
    ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = 0,  !- A17')
    ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW,  !- A18')
    ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = 0,  !- A19')
  end

  ems_pump_program.addLine("ELSEIF #{chw_demand} <= #{capacity_medium_chiller + capacity_large_chiller},  !- A20")
  ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 0,  !- A21')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1,  !- A22')
  ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = 1,  !- A23')
  ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = 0,  !- A24')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW,  !- A25')
  ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = CHILLER_PUMP_3_DES_FLOW,  !- A26')
  ems_pump_program.addLine("ELSEIF #{chw_demand} > #{capacity_medium_chiller + capacity_large_chiller},  !- A27")
  ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 1,  !- A28')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1,  !- A29')
  ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = 1,  !- A30')
  ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = CHILLER_PUMP_1_DES_FLOW,  !- A31')
  ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW,  !- A32')
  ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = CHILLER_PUMP_3_DES_FLOW,  !- A33')
  ems_pump_program.addLine('ENDIF  !- A34')

  ems_pump_program_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
  ems_pump_program_manager.setName("#{plant_name.gsub(/[-\s]+/, '_')}_Pump_Program_Manager")
  ems_pump_program_manager.setCallingPoint('InsideHVACSystemIterationLoop')
  ems_pump_program_manager.addProgram(ems_pump_program)
end
air_loop_hvac_allowable_system_brake_horsepower(air_loop_hvac) click to toggle source

Determine the allowable fan system brake horsepower Per Section G3.1.2.9

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] @return [Double] allowable fan system brake horsepower

units = horsepower
# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 407
def air_loop_hvac_allowable_system_brake_horsepower(air_loop_hvac)
  # Get design supply air flow rate (whether autosized or hard-sized)
  dsn_air_flow_m3_per_s = 0
  dsn_air_flow_cfm = 0
  if air_loop_hvac.autosizedDesignSupplyAirFlowRate.is_initialized
    dsn_air_flow_m3_per_s = air_loop_hvac.autosizedDesignSupplyAirFlowRate.get
    dsn_air_flow_cfm = OpenStudio.convert(dsn_air_flow_m3_per_s, 'm^3/s', 'cfm').get
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.ashrae_90_1_prm.AirLoopHVAC', "* #{dsn_air_flow_cfm.round} cfm = Autosized Design Supply Air Flow Rate.")
  else
    dsn_air_flow_m3_per_s = air_loop_hvac.designSupplyAirFlowRate.get
    dsn_air_flow_cfm = OpenStudio.convert(dsn_air_flow_m3_per_s, 'm^3/s', 'cfm').get
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.ashrae_90_1_prm.AirLoopHVAC', "* #{dsn_air_flow_cfm.round} cfm = Hard sized Design Supply Air Flow Rate.")
  end

  # Get the fan limitation pressure drop adjustment bhp
  fan_pwr_adjustment_bhp = air_loop_hvac_fan_power_limitation_pressure_drop_adjustment_brake_horsepower(air_loop_hvac)

  # Get system type associated with air loop
  system_type = air_loop_hvac.additionalProperties.getFeatureAsString('baseline_system_type').get

  # Calculate the Allowable Fan System brake horsepower per Table G3.1.2.9
  allowable_fan_bhp = 0.0
  case system_type
    when 'PSZ_HP', 'PSZ_AC', 'SZ_CV' # 3, 4, 12, 13
      allowable_fan_bhp = (dsn_air_flow_cfm * 0.00094) + fan_pwr_adjustment_bhp
    when 'PVAV_Reheat', 'PVAV_PFP_Boxes', 'VAV_Reheat', 'VAV_PFP_Boxes', 'SZ_VAV' # 5, 6, 7, 8, 11
      allowable_fan_bhp = (dsn_air_flow_cfm * 0.0013) + fan_pwr_adjustment_bhp
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.ashrae_90_1_prm.AirLoopHVAC', "Air loop #{air_loop_hvac.name} is not associated with a baseline system.")
  end

  return allowable_fan_bhp
end
air_loop_hvac_apply_energy_recovery_ventilator_efficiency(erv, erv_type: 'ERV', heat_exchanger_type: 'Rotary') click to toggle source

Set effectiveness value of an ERV’s heat exchanger

@param erv [OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent] ERV to apply efficiency values @param erv_type [String] ERV type: ERV or HRV @param heat_exchanger_type [String] Heat exchanger type: Rotary or Plate @return [OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent] ERV to apply efficiency values

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 666
def air_loop_hvac_apply_energy_recovery_ventilator_efficiency(erv, erv_type: 'ERV', heat_exchanger_type: 'Rotary')
  heat_exchanger_air_to_air_sensible_and_latent_apply_effectiveness(erv)

  return erv
end
air_loop_hvac_apply_minimum_vav_damper_positions(air_loop_hvac, has_ddc = true) click to toggle source

Set the minimum VAV damper positions.

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param has_ddc [Boolean] if true, will assume that there is DDC control of vav terminals.

If false, assumes otherwise.

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 557
def air_loop_hvac_apply_minimum_vav_damper_positions(air_loop_hvac, has_ddc = true)
  air_loop_hvac.thermalZones.each do |zone|
    zone.equipment.each do |equip|
      if equip.to_AirTerminalSingleDuctVAVReheat.is_initialized
        zone_oa = OpenstudioStandards::ThermalZone.thermal_zone_get_outdoor_airflow_rate(zone)
        vav_terminal = equip.to_AirTerminalSingleDuctVAVReheat.get
        air_terminal_single_duct_vav_reheat_apply_minimum_damper_position(vav_terminal, zone_oa, has_ddc)
      elsif equip.to_AirTerminalSingleDuctParallelPIUReheat.is_initialized
        zone_oa = OpenstudioStandards::ThermalZone.thermal_zone_get_outdoor_airflow_rate(zone)
        fp_vav_terminal = equip.to_AirTerminalSingleDuctParallelPIUReheat.get
        air_terminal_single_duct_parallel_piu_reheat_apply_minimum_primary_airflow_fraction(fp_vav_terminal, zone_oa)
      end
    end
  end

  return true
end
air_loop_hvac_apply_prm_baseline_fan_power(air_loop_hvac) click to toggle source

Calculate and apply the performance rating method baseline fan power to this air loop based on the system type that it represents.

Fan motor efficiency will be set, and then fan pressure rise adjusted so that the fan power is the maximum allowable.

Also adjusts the fan power and flow rates of any parallel PIU terminals on the system.

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 218
def air_loop_hvac_apply_prm_baseline_fan_power(air_loop_hvac)
  # Get system type associated with air loop
  system_type = air_loop_hvac.additionalProperties.getFeatureAsString('baseline_system_type').get

  # Find out if air loop represents a non mechanically cooled system
  is_nmc = false
  is_nmc = true if air_loop_hvac.additionalProperties.hasFeature('non_mechanically_cooled')

  # Get all air loop fans
  all_fans = air_loop_hvac_supply_return_exhaust_relief_fans(air_loop_hvac)

  allowable_fan_bhp = 0.0
  allowable_power_w = 0.0
  fan_efficacy_w_per_cfm = 0.0
  supply_fan_power_fraction = 0.0
  return_fan_power_fraction = 0.0
  relief_fan_power_fraction = 0.0
  if system_type == 'PSZ_AC' ||
     system_type == 'PSZ_HP' ||
     system_type == 'PVAV_Reheat' ||
     system_type == 'PVAV_PFP_Boxes' ||
     system_type == 'VAV_Reheat' ||
     system_type == 'VAV_PFP_Boxes' ||
     system_type == 'SZ_VAV' ||
     system_type == 'SZ_CV'

    # Calculate the allowable fan motor bhp for the air loop
    allowable_fan_bhp = air_loop_hvac_allowable_system_brake_horsepower(air_loop_hvac)

    # Divide the allowable power based
    # individual zone air flow
    air_loop_total_zone_design_airflow = 0
    air_loop_hvac.thermalZones.sort.each do |zone|
      # error if zone design air flow rate is not available
      if zone.model.version < OpenStudio::VersionString.new('3.6.0')
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.ashrae_90_1_prm.AirLoopHVAC', 'Required ThermalZone method .autosizedDesignAirFlowRate is not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.')
      end

      zone_air_flow = zone.autosizedDesignAirFlowRate.to_f
      air_loop_total_zone_design_airflow += zone_air_flow
      # Fractions variables are actually power at that point
      supply_fan_power_fraction += zone_air_flow * zone.additionalProperties.getFeatureAsDouble('supply_fan_w').get
      return_fan_power_fraction += zone_air_flow * zone.additionalProperties.getFeatureAsDouble('return_fan_w').get
      relief_fan_power_fraction += zone_air_flow * zone.additionalProperties.getFeatureAsDouble('relief_fan_w').get
    end
    if air_loop_total_zone_design_airflow > 0
      # Get average power for each category of fan
      supply_fan_power_fraction /= air_loop_total_zone_design_airflow
      return_fan_power_fraction /= air_loop_total_zone_design_airflow
      relief_fan_power_fraction /= air_loop_total_zone_design_airflow
      # Convert to power fraction
      total_fan_avg_fan_w = (supply_fan_power_fraction + return_fan_power_fraction + relief_fan_power_fraction)
      supply_fan_power_fraction /= total_fan_avg_fan_w
      return_fan_power_fraction /= total_fan_avg_fan_w
      relief_fan_power_fraction /= total_fan_avg_fan_w
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.ashrae_90_1_prm.AirLoopHVAC', "Total zone design airflow for #{air_loop_hvac.name} is 0.")
    end
  elsif system_type == 'PTAC' ||
        system_type == 'PTHP' ||
        system_type == 'Gas_Furnace' ||
        system_type == 'Electric_Furnace'

    # Determine allowable fan power
    if is_nmc
      fan_efficacy_w_per_cfm = 0.054
    else
      fan_efficacy_w_per_cfm = 0.3
    end

    # Configuration is supply fan only
    supply_fan_power_fraction = 1.0
  end

  supply_fan = air_loop_hvac_get_supply_fan(air_loop_hvac)
  if supply_fan.nil?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.ashrae_90_1_prm.AirLoopHVAC', "Supply not found on #{airloop.name}.")
  end
  supply_fan_max_flow = if supply_fan.maximumFlowRate.is_initialized
                          supply_fan.maximumFlowRate.get
                        elsif supply_fan.autosizedMaximumFlowRate.is_initialized
                          supply_fan.autosizedMaximumFlowRate.get
                        end

  # Check that baseline system has the same
  # types of fans as the proposed model, if
  # not, create them. We assume that the
  # system has at least a supply fan.
  if return_fan_power_fraction > 0.0 && !air_loop_hvac.returnFan.is_initialized
    # Create return fan
    return_fan = supply_fan.clone(air_loop_hvac.model)
    if return_fan.to_FanConstantVolume.is_initialized
      return_fan = return_fan.to_FanConstantVolume.get
    elsif return_fan.to_FanVariableVolume.is_initialized
      return_fan = return_fan.to_FanVariableVolume.get
    elsif return_fan.to_FanOnOff.is_initialized
      return_fan = return_fan.to_FanOnOff.get
    elsif return_fan.to_FanSystemModel.is_initialized
      return_fan = return_fan.to_FanSystemModel.get
    end
    return_fan.setName("#{air_loop_hvac.name} Return Fan")
    return_fan.addToNode(air_loop_hvac.returnAirNode.get)
    return_fan.setMaximumFlowRate(supply_fan_max_flow)
  end
  if relief_fan_power_fraction > 0.0 && !air_loop_hvac.reliefFan.is_initialized
    # Create return fan
    relief_fan = supply_fan.clone(air_loop_hvac.model)
    if relief_fan.to_FanConstantVolume.is_initialized
      relief_fan = relief_fan.to_FanConstantVolume.get
    elsif relief_fan.to_FanVariableVolume.is_initialized
      relief_fan = relief_fan.to_FanVariableVolume.get
    elsif relief_fan.to_FanOnOff.is_initialized
      relief_fan = relief_fan.to_FanOnOff.get
    elsif relief_fan.to_FanSystemModel.is_initialized
      relief_fan = relief_fan.to_FanSystemModel.get
    end
    relief_fan.setName("#{air_loop_hvac.name} Relief Fan")
    relief_fan.addToNode(air_loop_hvac.reliefAirNode.get)
    relief_fan.setMaximumFlowRate(supply_fan_max_flow)
  end

  # Get all air loop fans
  all_fans = air_loop_hvac_supply_return_exhaust_relief_fans(air_loop_hvac)

  # Set the motor efficiencies
  # for all fans based on the calculated
  # allowed brake hp.  Then calculate the allowable
  # fan power for each fan and adjust
  # the fan pressure rise accordingly
  all_fans.each do |fan|
    # Efficacy requirement
    if fan_efficacy_w_per_cfm > 0
      # Convert efficacy to metric
      fan_efficacy_w_per_m3_per_s = OpenStudio.convert(fan_efficacy_w_per_cfm, 'm^3/s', 'cfm').get
      fan_change_impeller_efficiency(fan, fan_baseline_impeller_efficiency(fan))

      # Get fan BHP
      fan_bhp = fan_brake_horsepower(fan)

      # Set the motor efficiency, preserving the impeller efficiency.
      # For zone HVAC fans, a bhp lookup of 0.5bhp is always used because
      # they are assumed to represent a series of small fans in reality.
      fan_apply_standard_minimum_motor_efficiency(fan, fan_bhp)

      # Calculate a new pressure rise to hit the target W/cfm
      fan_tot_eff = fan.fanEfficiency
      fan_rise_new_pa = fan_efficacy_w_per_m3_per_s * fan_tot_eff
      fan.setPressureRise(fan_rise_new_pa)
    end

    # BHP requirements
    if allowable_fan_bhp > 0
      fan_apply_standard_minimum_motor_efficiency(fan, allowable_fan_bhp)
      allowable_power_w = allowable_fan_bhp * 746 / fan.motorEfficiency

      # Breakdown fan power based on fan type
      if supply_fan.name.to_s == fan.name.to_s
        allowable_power_w *= supply_fan_power_fraction
      elsif fan.airLoopHVAC.is_initialized
        if fan.airLoopHVAC.get.returnFan.is_initialized && fan.airLoopHVAC.get.returnFan.get.name.to_s == fan.name.to_s
          allowable_power_w *= return_fan_power_fraction
        end
        if fan.airLoopHVAC.get.reliefFan.is_initialized && fan.airLoopHVAC.get.reliefFan.get.name.to_s == fan.name.to_s
          allowable_power_w *= relief_fan_power_fraction
        end
      end
      fan_adjust_pressure_rise_to_meet_fan_power(fan, allowable_power_w)
    end
  end

  return true unless system_type == 'PVAV_PFP_Boxes' || system_type == 'VAV_PFP_Boxes'

  # Adjust fan powered terminal fans power
  air_loop_hvac.demandComponents.each do |dc|
    next if dc.to_AirTerminalSingleDuctParallelPIUReheat.empty?

    pfp_term = dc.to_AirTerminalSingleDuctParallelPIUReheat.get
    air_terminal_single_duct_parallel_piu_reheat_apply_prm_baseline_fan_power(pfp_term)
  end

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

Determine the limits for the type of economizer present on the AirLoopHVAC, if any.

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Array<Double>] [drybulb_limit_f, enthalpy_limit_btu_per_lb, dewpoint_limit_f]

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 580
def air_loop_hvac_economizer_limits(air_loop_hvac, climate_zone)
  drybulb_limit_f = nil
  enthalpy_limit_btu_per_lb = nil
  dewpoint_limit_f = nil

  # Get the OA system and OA controller
  oa_sys = air_loop_hvac.airLoopHVACOutdoorAirSystem
  return [nil, nil, nil] unless oa_sys.is_initialized

  oa_sys = oa_sys.get
  oa_control = oa_sys.getControllerOutdoorAir
  economizer_type = oa_control.getEconomizerControlType

  case economizer_type
  when 'NoEconomizer'
    return [nil, nil, nil]
  when 'FixedDryBulb'
    climate_zone_code = climate_zone.split('-')[-1]
    climate_zone_code = 7 if ['7A', '7B'].include? climate_zone_code
    climate_zone_code = 8 if ['8A', '8B'].include? climate_zone_code
    search_criteria = {
      'template' => template,
      'climate_id' => climate_zone_code
    }
    econ_limits = model_find_object(standards_data['prm_economizers'], search_criteria)
    drybulb_limit_f = econ_limits['high_limit_shutoff']
  when 'FixedEnthalpy'
    enthalpy_limit_btu_per_lb = 28
  when 'FixedDewPointAndDryBulb'
    drybulb_limit_f = 75
    dewpoint_limit_f = 55
  end

  return [drybulb_limit_f, enthalpy_limit_btu_per_lb, dewpoint_limit_f]
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. 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. For stable baseline, schedule is Always-On for computer rooms and when health and safety exception is used

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param min_occ_pct [Double] the fractional value below which the system will be considered unoccupied. @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 14
def air_loop_hvac_enable_unoccupied_fan_shutoff(air_loop_hvac, min_occ_pct = 0.05)
  if air_loop_hvac.additionalProperties.hasFeature('zone_group_type')
    zone_group_type = air_loop_hvac.additionalProperties.getFeatureAsString('zone_group_type').get
  else
    zone_group_type = 'None'
  end

  if zone_group_type == 'computer_zones'
    # Computer rooms are exempt from night cycle control
    return false
  end

  # Check for user data exceptions for night cycling
  # If any zone has the exception, then system will not cycle
  health_safety_exception = false
  air_loop_hvac.thermalZones.each do |thermal_zone|
    if thermal_zone.additionalProperties.hasFeature('has_health_safety_night_cycle_exception')
      exception = thermal_zone.additionalProperties.getFeatureAsBoolean('has_health_safety_night_cycle_exception').get
      return false if exception == true
    end
  end

  # Set the system to night cycle
  # The fan of a parallel PIU terminal are set to only cycle during heating operation
  # This is achieved using the CycleOnAnyCoolingOrHeatingZone; During cooling operation
  # the load is met by running the central system which stays off during heating
  # operation
  air_loop_hvac.setNightCycleControlType('CycleOnAny')
  if air_loop_hvac_has_parallel_piu_air_terminals?(air_loop_hvac)
    avail_mgrs = air_loop_hvac.availabilityManagers
    if !avail_mgrs.nil?
      avail_mgrs.each do |avail_mgr|
        if avail_mgr.to_AvailabilityManagerNightCycle.is_initialized
          avail_mgr_nc = avail_mgr.to_AvailabilityManagerNightCycle.get
          avail_mgr_nc.setControlType('CycleOnAnyCoolingOrHeatingZone')
          zones = air_loop_hvac.thermalZones
          avail_mgr_nc.setCoolingControlThermalZones(zones)
          avail_mgr_nc.setHeatingZoneFansOnlyThermalZones(zones)
        end
      end
    end
  end

  model = air_loop_hvac.model
  # Check if schedule was stored in an additionalProperties field of the air loop
  air_loop_name = air_loop_hvac.name
  if air_loop_hvac.hasAdditionalProperties && air_loop_hvac.additionalProperties.hasFeature('fan_sched_name')
    fan_sched_name = air_loop_hvac.additionalProperties.getFeatureAsString('fan_sched_name').get
    fan_sched = model.getScheduleRulesetByName(fan_sched_name).get
    air_loop_hvac.setAvailabilitySchedule(fan_sched)
    return true
  end

  # 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_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.is_initialized
      comp.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.get.setSupplyAirFanOperatingModeSchedule(loop_occ_sch)
    elsif comp.to_AirLoopHVACUnitarySystem.is_initialized
      comp.to_AirLoopHVACUnitarySystem.get.setSupplyAirFanOperatingModeSchedule(loop_occ_sch)
    end
  end

  return true
end
air_loop_hvac_energy_recovery_ventilator_flow_limit(air_loop_hvac, climate_zone, pct_oa) click to toggle source

Determine the airflow limits that govern whether or not an ERV is required. Based on climate zone and % OA.

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @param pct_oa [Double] percentage of outdoor air @return [Double] the flow rate above which an ERV is required. if nil, ERV is never required.

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 679
def air_loop_hvac_energy_recovery_ventilator_flow_limit(air_loop_hvac, climate_zone, pct_oa)
  if pct_oa < 0.7
    erv_cfm = nil
  else
    # Heating thermostat setpoint threshold
    temp_c = OpenStudio.convert(60, 'F', 'C').get

    # Check for exceptions for each zone
    air_loop_hvac.thermalZones.each do |thermal_zone|
      # Get heating thermosat setpoint and comparing to heating thermostat setpoint threshold
      tstat = thermal_zone.thermostat.get
      if tstat.to_ThermostatSetpointDualSetpoint
        tstat = tstat.to_ThermostatSetpointDualSetpoint.get
        htg_sch = tstat.getHeatingSchedule
        if htg_sch.is_initialized
          htg_sch = htg_sch.get
          if htg_sch.to_ScheduleRuleset.is_initialized
            htg_sch = htg_sch.to_ScheduleRuleset.get
            max_c = OpenstudioStandards::Schedules.schedule_ruleset_get_min_max(htg_sch)['max']
            if max_c > temp_c
              htd = true
            end
          elsif htg_sch.to_ScheduleConstant.is_initialized
            htg_sch = htg_sch.to_ScheduleConstant.get
            max_c = OpenstudioStandards::Schedules.schedule_constant_get_min_max(htg_sch)['max']
            if max_c > temp_c
              htd = true
            end
          elsif htg_sch.to_ScheduleCompact.is_initialized
            htg_sch = htg_sch.to_ScheduleCompact.get
            max_c = OpenstudioStandards::Schedules.schedule_compact_get_min_max(htg_sch)['max']
            if max_c > temp_c
              htd = true
            end
          else
            OpenStudio.logFree(OpenStudio::Error, 'prm.log', "Zone #{thermal_zone.name} used an unknown schedule type for the heating setpoint; assuming heated.")
            htd = true
          end
        end
      elsif tstat.to_ZoneControlThermostatStagedDualSetpoint
        tstat = tstat.to_ZoneControlThermostatStagedDualSetpoint.get
        htg_sch = tstat.heatingTemperatureSetpointSchedule
        if htg_sch.is_initialized
          htg_sch = htg_sch.get
          if htg_sch.to_ScheduleRuleset.is_initialized
            htg_sch = htg_sch.to_ScheduleRuleset.get
            max_c = OpenstudioStandards::Schedules.schedule_ruleset_get_min_max(htg_sch)['max']
            if max_c > temp_c
              htd = true
            end
          end
        end
      end

      # Exception 1 - Systems heated to less than 60F since all baseline system provide cooling
      if !htd
        return nil
      end

      # Exception 2 - System exhausting toxic fumes
      if thermal_zone.additionalProperties.hasFeature('exhaust_energy_recovery_exception_for_toxic_fumes_etc') && thermal_zone.additionalProperties.getFeatureAsBoolean('exhaust_energy_recovery_exception_for_toxic_fumes_etc')
        return nil
      end

      # Exception 3 - Commercial kitchen hoods
      if thermal_zone.additionalProperties.hasFeature('exhaust_energy_recovery_exception_for_type1_kitchen_hoods') && thermal_zone.additionalProperties.getFeatureAsBoolean('exhaust_energy_recovery_exception_for_type1_kitchen_hoods')
        return nil
      end

      # Exception 6 - Distributed exhaust
      if thermal_zone.additionalProperties.hasFeature('exhaust_energy_recovery_exception_for_type_distributed_exhaust') && thermal_zone.additionalProperties.getFeatureAsBoolean('exhaust_energy_recovery_exception_for_type_distributed_exhaust')
        return nil
      end

      # Exception 7 - Dehumidification
      if thermal_zone.additionalProperties.hasFeature('exhaust_energy_recovery_exception_for_dehumidifcation_with_series_cooling_recovery') && thermal_zone.additionalProperties.getFeatureAsBoolean('exhaust_energy_recovery_exception_for_dehumidifcation_with_series_cooling_recovery')
        return nil
      end
    end

    # Exception 4 - Heating systems in certain climate zones
    if ['ASHRAE 169-2006-0A', 'ASHRAE 169-2006-0B', 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2013-0A', 'ASHRAE 169-2013-0B', 'ASHRAE 169-2013-1A', 'ASHRAE 169-2013-1B', 'ASHRAE 169-2013-2A', 'ASHRAE 169-2013-2B', 'ASHRAE 169-2013-3A', 'ASHRAE 169-2013-3B', 'ASHRAE 169-2013-3C'].include?(climate_zone) && air_loop_hvac.additionalProperties.hasFeature('baseline_system_type')
      system_type = air_loop_hvac.additionalProperties.getFeatureAsString('baseline_system_type').get
      if system_type == 'Gas_Furnace' || system_type == 'Electric_Furnace'
        return nil
      end
    end

    erv_cfm = 5000
  end

  return erv_cfm
end
air_loop_hvac_fan_power_limitation_pressure_drop_adjustment_brake_horsepower(air_loop_hvac) click to toggle source

Determine the fan power limitation pressure drop adjustment Per Table 6.5.3.1-2 (90.1-2019)

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @return [Double] fan power limitation pressure drop adjustment, in units of horsepower

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 621
def air_loop_hvac_fan_power_limitation_pressure_drop_adjustment_brake_horsepower(air_loop_hvac)
  # Calculate Fan Power Limitation Pressure Drop Adjustment
  fan_pwr_adjustment_bhp = 0

  # Retrieve climate zone
  climate_zone = air_loop_hvac.model.getClimateZones.getClimateZone(0)

  # Check if energy recovery is required
  is_energy_recovery_required = air_loop_hvac_energy_recovery_ventilator_required?(air_loop_hvac, climate_zone)

  system_type = ''
  # Get baseline system type if applicable
  if air_loop_hvac.additionalProperties.hasFeature('baseline_system_type')
    system_type = air_loop_hvac.additionalProperties.getFeatureAsString('baseline_system_type').to_s
  end

  air_loop_hvac.thermalZones.each do |zone|
    # Take fan power deductions into account;
    # Deductions are calculated based on the
    # baseline model design.
    # The only deduction that's applicable
    # is the "System with central electric
    # resistance heat" for system 6 and 8
    if system_type == 'PVAV_PFP_Boxes' || system_type == 'VAV_PFP_Boxes'
      if zone.additionalProperties.hasFeature('has_fan_power_deduction_system_with_central_electric_resistance_heat')
        current_value = zone.additionalProperties.getFeatureAsDouble('has_fan_power_deduction_system_with_central_electric_resistance_heat')
        zone.additionalProperties.setFeature('has_fan_power_deduction_system_with_central_electric_resistance_heat', current_value + 1.0)
      else
        zone.additionalProperties.setFeature('has_fan_power_deduction_system_with_central_electric_resistance_heat', 1.0)
      end
    end

    # Determine fan power adjustment
    fan_pwr_adjustment_bhp += thermal_zone_get_fan_power_limitations(zone, is_energy_recovery_required)
  end

  return fan_pwr_adjustment_bhp
end
air_loop_hvac_integrated_economizer_required?(air_loop_hvac, climate_zone) click to toggle source

Determine if the system economizer must be integrated or not. Always required for stable baseline if there is an economizer

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] returns true if required, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 123
def air_loop_hvac_integrated_economizer_required?(air_loop_hvac, climate_zone)
  return true
end
air_loop_hvac_multizone_vav_system?(air_loop_hvac) click to toggle source

Determine if the system is a multizone VAV system @param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @return [Boolean] Returns true if required, false if not.

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 95
def air_loop_hvac_multizone_vav_system?(air_loop_hvac)
  return true if air_loop_hvac.name.to_s.include?('Sys5') || air_loop_hvac.name.to_s.include?('Sys6') || air_loop_hvac.name.to_s.include?('Sys7') || air_loop_hvac.name.to_s.include?('Sys8')

  return false
end
air_loop_hvac_optimum_start_required?(air_loop_hvac) click to toggle source

Determines if optimum start control is required. PRM does not require optimum start - override it to false.

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @return [Boolean] returns true if required, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 202
def air_loop_hvac_optimum_start_required?(air_loop_hvac)
  return false
end
air_loop_hvac_prm_baseline_economizer_required?(air_loop_hvac, climate_zone) click to toggle source

Determine if an economizer is required per the PRM.

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] returns true if required, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 156
def air_loop_hvac_prm_baseline_economizer_required?(air_loop_hvac, climate_zone)
  economizer_required = false
  baseline_system_type = air_loop_hvac.additionalProperties.getFeatureAsString('baseline_system_type').get
  climate_zone_code = climate_zone.split('-')[-1]
  # System type 3 through 8 and 11, 12 and 13
  if ['SZ_AC', 'PSZ_AC', 'PVAV_Reheat', 'VAV_Reheat', 'SZ_VAV', 'PSZ_HP', 'SZ_CV', 'PSZ_HP', 'PVAV_PFP_Boxes', 'VAV_PFP_Boxes'].include? baseline_system_type
    unless ['0A', '0B', '1A', '1B', '2A', '3A', '4A'].include? climate_zone_code
      economizer_required = true
    end
  end

  # System type 3 and 4 in computer rooms are subject to exceptions
  if baseline_system_type == 'PSZ_AC' || baseline_system_type == 'PSZ_HP'
    if air_loop_hvac.additionalProperties.hasFeature('zone_group_type') && air_loop_hvac.additionalProperties.getFeatureAsString('zone_group_type').get == 'computer_zones'
      economizer_required = false
    end
  end

  # Check user_data in the zones
  gas_phase_exception = false
  open_refrigeration_exception = false
  air_loop_hvac.thermalZones.each do |thermal_zone|
    if thermal_zone.additionalProperties.hasFeature('economizer_exception_for_gas_phase_air_cleaning')
      gas_phase_exception = true
    end
    if thermal_zone.additionalProperties.hasFeature('economizer_exception_for_open_refrigerated_cases')
      open_refrigeration_exception = true
    end
  end
  if gas_phase_exception || open_refrigeration_exception
    economizer_required = false
  end
  return economizer_required
end
air_loop_hvac_prm_economizer_type_and_limits(air_loop_hvac, climate_zone) click to toggle source

Determine the economizer type and limits for the the PRM Defaults to 90.1-2007 logic.

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Array<Double>] [economizer_type, drybulb_limit_f, enthalpy_limit_btu_per_lb, dewpoint_limit_f]

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 133
def air_loop_hvac_prm_economizer_type_and_limits(air_loop_hvac, climate_zone)
  economizer_type = 'NoEconomizer'
  drybulb_limit_f = nil
  enthalpy_limit_btu_per_lb = nil
  dewpoint_limit_f = nil
  climate_zone_code = climate_zone.split('-')[-1]

  if ['0B', '1B', '2B', '3B', '3C', '4B', '4C', '5B', '5C', '6B', '7A', '7B', '8A', '8B'].include? climate_zone_code
    economizer_type = 'FixedDryBulb'
    drybulb_limit_f = 75
  elsif ['5A', '6A'].include? climate_zone_code
    economizer_type = 'FixedDryBulb'
    drybulb_limit_f = 70
  end

  return [economizer_type, drybulb_limit_f, enthalpy_limit_btu_per_lb, dewpoint_limit_f]
end
air_loop_hvac_set_vsd_curve_type() click to toggle source

Set fan curve for stable baseline to be VSD with fixed static pressure setpoint @return [String name of appropriate curve for this code version

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 193
def air_loop_hvac_set_vsd_curve_type
  return 'Multi Zone VAV with VSD and Fixed SP Setpoint'
end
air_loop_hvac_unoccupied_threshold() click to toggle source

Default occupancy fraction threshold for determining if the spaces on the air loop are occupied

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 112
def air_loop_hvac_unoccupied_threshold
  # Use 10% based on PRM-RM
  return 0.10
end
air_loop_hvac_vav_damper_action(air_loop_hvac) click to toggle source

Determine whether the VAV damper control is single maximum or dual maximum control. Defaults to Single Maximum for stable baseline.

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @return [String] the damper control type: Single Maximum, Dual Maximum

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 106
def air_loop_hvac_vav_damper_action(air_loop_hvac)
  damper_action = 'Single Maximum'
  return damper_action
end
air_terminal_single_duct_parallel_piu_reheat_fan_on_flow_fraction() click to toggle source

Return the fan on flow fraction for a parallel PIU terminal

@return [Double] returns nil or a float representing the fraction

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctParallelPIUReheat.rb, line 7
def air_terminal_single_duct_parallel_piu_reheat_fan_on_flow_fraction
  return 0.0 # will make the secondary fans run for heating loads
end
air_terminal_single_duct_vav_reheat_apply_minimum_damper_position(air_terminal_single_duct_vav_reheat, zone_min_oa = nil, has_ddc = true) click to toggle source

Set the minimum damper position based on OA rate of the space and the template. Zones with low OA per area get lower initial guesses. Final position will be adjusted upward as necessary by Standards.AirLoopHVAC.adjust_minimum_vav_damper_positions

@param air_terminal_single_duct_vav_reheat [OpenStudio::Model::AirTerminalSingleDuctVAVReheat] the air terminal object @param zone_min_oa [Double] the zone outdoor air flow rate, in m^3/s.

If supplied, this will be set as a minimum limit in addition to the minimum
damper position.  EnergyPlus will use the larger of the two values during sizing.

@param has_ddc [Boolean] whether or not there is DDC control of the VAV terminal,

which impacts the minimum damper position requirement.

@return [Boolean] returns true if successful, false if not @todo remove exception where older vintages don’t have minimum positions adjusted.

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctVAVReheat.rb, line 16
def air_terminal_single_duct_vav_reheat_apply_minimum_damper_position(air_terminal_single_duct_vav_reheat, zone_min_oa = nil, has_ddc = true)
  # Minimum damper position
  min_damper_position = air_terminal_single_duct_vav_reheat_minimum_damper_position(air_terminal_single_duct_vav_reheat, has_ddc)
  air_terminal_single_duct_vav_reheat.setConstantMinimumAirFlowFraction(min_damper_position)
  OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirTerminalSingleDuctVAVReheat', "For #{air_terminal_single_duct_vav_reheat.name}: set minimum damper position to #{min_damper_position}.")

  # Minimum OA flow rate
  # If specified, set the MDP as the larger of the two
  unless zone_min_oa.nil?
    min_oa_damp_position = [zone_min_oa / air_terminal_single_duct_vav_reheat.autosizedMaximumAirFlowRate.get, min_damper_position].max
    air_terminal_single_duct_vav_reheat.setConstantMinimumAirFlowFraction(min_oa_damp_position)
  end

  return true
end
baseline_air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac) click to toggle source

Check if the air loop in baseline model needs to have DCV

@author Xuechen (Jerry) Lei, PNNL @param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @return [Boolean] flag of whether the air loop in baseline is required to have DCV

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 507
def baseline_air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac)
  any_zone_req_dcv = false
  air_loop_hvac.thermalZones.each do |zone|
    if baseline_thermal_zone_demand_control_ventilation_required?(zone)
      any_zone_req_dcv = true
    end
  end
  return any_zone_req_dcv # baseline airloop needs dcv if any zone it serves needs dcv
end
baseline_thermal_zone_demand_control_ventilation_required?(thermal_zone) click to toggle source

Check if the thermal zone in baseline model needs to have DCV

@author Xuechen (Jerry) Lei, PNNL @param thermal_zone [OpenStudio::Model::ThermalZone] the thermal zone @return [Boolean] flag of whether thermal zone in baseline is required to have DCV

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 522
def baseline_thermal_zone_demand_control_ventilation_required?(thermal_zone)
  # zone needs dcv if user model has dcv and baseline does not meet apxg exception
  if thermal_zone.additionalProperties.hasFeature('apxg no need to have DCV')
    # meaning it was served by an airloop in the user model, does not mean much here, conditional as a safeguard
    # in case it was not served by an airloop in the user model
    if !thermal_zone.additionalProperties.getFeatureAsBoolean('apxg no need to have DCV').get && # does not meet apxg exception (need to have dcv if user model has it
       thermal_zone.additionalProperties.getFeatureAsBoolean('zone DCV implemented in user model').get
      return true
    end
  end
  return false
end
boiler_get_eff_fplr(boiler_hot_water) click to toggle source

Determine what part load efficiency degredation curve should be used for a boiler

@param boiler_hot_water [OpenStudio::Model::BoilerHotWater] hot water boiler object @return [String] returns name of the boiler curve to be used, or nil if not applicable

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.BoilerHotWater.rb, line 23
def boiler_get_eff_fplr(boiler_hot_water)
  return 'Boiler with Minimum Turndown'
end
boiler_hot_water_apply_efficiency_and_curves(boiler_hot_water) click to toggle source

Applies the standard efficiency ratings to this object.

@param boiler_hot_water [OpenStudio::Model::BoilerHotWater] hot water boiler object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.BoilerHotWater.rb, line 8
def boiler_hot_water_apply_efficiency_and_curves(boiler_hot_water)
  # Get the minimum efficiency standards
  thermal_eff = boiler_hot_water_standard_minimum_thermal_efficiency(boiler_hot_water)

  # Set the efficiency values
  unless thermal_eff.nil?
    boiler_hot_water.setNominalThermalEfficiency(thermal_eff)
  end
  return true
end
calculate_electric_value_by_userdata(user_equip_data, power_equipment, power_schedule_hash, space_type, user_space_data = nil) click to toggle source

A function to calculate electric value for an electric equipment. The function will check whether this electric equipment is motor, refrigeration, elevator or generic electric equipment and decide actions based on the equipment types

@param user_equip_data [Hash] user equipment data @param power_equipment [OpenStudio::Model::ElectricEquipment] equipment @param power_schedule_hash [Hash] equipment operation schedule hash @param space_type [OpenStudio::Model:SpaceType] space type @param user_space_data [Hash] user space data @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 156
def calculate_electric_value_by_userdata(user_equip_data, power_equipment, power_schedule_hash, space_type, user_space_data = nil)
  # Check if the plug load represents a motor (check if motorhorsepower exist), if so, record the motor HP and efficiency.
  if !user_equip_data['motor_horsepower'].nil?
    # Pre-processing will ensure these three user data are added correctly (float, float, boolean)
    # @todo move this part to user data processing.
    power_equipment.additionalProperties.setFeature('motor_horsepower', user_equip_data['motor_horsepower'].to_f)
    power_equipment.additionalProperties.setFeature('motor_efficiency', user_equip_data['motor_efficiency'].to_f)
    power_equipment.additionalProperties.setFeature('motor_is_exempt', user_equip_data['motor_is_exempt'])
  elsif !(user_equip_data['fraction_of_controlled_receptacles'].nil? && user_equip_data['receptacle_power_savings'].nil?)
    # If not a motor - update.
    # Update the electric equipment occupancy credit (if it has)
    update_power_equipment_credits(power_equipment, user_equip_data, power_schedule_hash, space_type, user_space_data)
  else
    # The electric equipment is either an elevator or refrigeration
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ElectricEquipment', "#{power_equipment.name} is an elevator or refrigeration according to the user data provided. Skip receptacle power credit.")
    return false
  end
  return true
end
calculate_lpd_by_space(space_type, space) click to toggle source

calculate the lighting power density per area based on space type The function will calculate the LPD based on the space type (STRING) It considers lighting per area, lighting per length as well as occupancy factors in the database. @param space_type [OpenStudio::Model::SpaceType] @param space [OpenStudio::Model::Space] @return [Double] lighting power density in the space

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 536
def calculate_lpd_by_space(space_type, space)
  # get interior lighting data
  space_type_properties = interior_lighting_get_prm_data(space_type)
  OpenStudio.logFree(OpenStudio::Info, 'prm.log', "The lighting properties for space: #{space.name.get} is based on lighting_space_type: #{space_type_properties['lpd_space_type']}, primary_space_type: #{space_type_properties['primary_space_type']}, secondary_space_type: #{space_type_properties['secondary_space_type']}.")
  space_lighting_per_area = 0.0
  # Assign data
  lights_have_info = false
  lighting_per_area = space_type_properties['w/ft^2'].to_f
  lighting_per_length = space_type_properties['w/ft'].to_f
  manon_or_partauto = space_type_properties['manon_or_partauto'].to_i
  lights_have_info = true unless lighting_per_area.zero? && lighting_per_length.zero?
  occ_control_reduction_factor = 0.0

  if lights_have_info
    # Space height
    space_volume = space.volume
    space_area = space.floorArea
    space_height = OpenStudio.convert(space_volume / space_area, 'm', 'ft').get
    # calculate the new lpd values
    space_lighting_per_area = ((lighting_per_length * space_height) / space_area) + lighting_per_area

    # Adjust the occupancy control sensor reduction factor from dataset
    if manon_or_partauto == 1
      occ_control_reduction_factor = space_type_properties['occup_sensor_savings'].to_f
    else
      occ_control_reduction_factor = space_type_properties['occup_sensor_auto_on_svgs'].to_f
    end
  end
  # add calculated occupancy control credit for later ltg schedule adjustment
  space.additionalProperties.setFeature('occ_control_credit', occ_control_reduction_factor)
  return space_lighting_per_area
end
calculate_lpd_from_userdata(user_data, space) click to toggle source

Calculate the lighting power density per area based on user data (space_based) The function will calculate the LPD based on the space type (STRING) It considers lighting per area, lighting per length as well as occupancy factors in the database. The sum of each space fraction in the user_data is assumed to be 1.0 @param user_data [Hash] user data from the user csv @param space [OpenStudio::Model::Space] @return [Double] space lighting per area in W per m2

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 626
def calculate_lpd_from_userdata(user_data, space)
  num_std_ltg_types = user_data['num_std_ltg_types'].to_i
  space_lighting_per_area = 0.0
  occupancy_control_credit_sum = 0.0
  std_ltg_index = 0 # loop index
  # Loop through standard lighting type in a space
  while std_ltg_index < num_std_ltg_types
    # Retrieve data from user_data
    type_key = format('std_ltg_type%02d', (std_ltg_index + 1))
    frac_key = format('std_ltg_type_frac%02d', (std_ltg_index + 1))
    sub_space_type = user_data[type_key]
    sub_space_type_frac = user_data[frac_key].to_f
    # Adjust while loop condition factors
    std_ltg_index += 1
    # get interior lighting data
    sub_space_type_properties = interior_lighting_get_prm_data(sub_space_type)
    # Assign data
    lights_have_info = false
    lighting_per_area = sub_space_type_properties['w/ft^2'].to_f
    lighting_per_length = sub_space_type_properties['w/ft'].to_f
    lights_have_info = true unless lighting_per_area.zero? && lighting_per_length.zero?
    manon_or_partauto = sub_space_type_properties['manon_or_partauto'].to_i

    if lights_have_info
      # Space height
      space_volume = space.volume
      space_area = space.floorArea
      space_height = OpenStudio.convert(space_volume / space_area, 'm', 'ft').get
      # calculate and add new lpd values
      user_space_type_lighting_per_area = (((lighting_per_length * space_height) / space_area) + lighting_per_area) * sub_space_type_frac
      space_lighting_per_area += user_space_type_lighting_per_area

      # Adjust the occupancy control sensor reduction factor from dataset
      occ_control_reduction_factor = 0.0
      if manon_or_partauto == 1
        occ_control_reduction_factor = sub_space_type_properties['occup_sensor_savings'].to_f
      else
        occ_control_reduction_factor = sub_space_type_properties['occup_sensor_auto_on_svgs'].to_f
      end
      # Now calculate the occupancy control credit factor (weighted by frac_lpd)
      occupancy_control_credit_sum += occ_control_reduction_factor * user_space_type_lighting_per_area
    end
  end
  # add calculated occupancy control credit for later ltg schedule adjustment
  # If space_lighting_per_area = 0, it means there is no lights_have_info, and subsequently, the occupancy_control_credit_sum should be 0
  space.additionalProperties.setFeature('occ_control_credit', space_lighting_per_area > 0 ? occupancy_control_credit_sum / space_lighting_per_area : occupancy_control_credit_sum)
  return space_lighting_per_area
end
check_userdata_airloop_hvac(object_name, user_data) click to toggle source

Check for incorrect data in [UserDataFiles::AIRLOOP_HVAC] @param object_name [String] name of user data csv file to check @param user_data [Hash] hash of data from user data csv file @return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 407
def check_userdata_airloop_hvac(object_name, user_data)
  userdata_valid = true
  user_data.each do |user_airloop|
    name = prm_read_user_data(user_airloop, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: air loop name is missing or empty. Air loop user data has not validated.")
      return false
    end
    # gas phase air cleaning is system base - add proposed hvac system name to zones
    economizer_exception_for_gas_phase_air_cleaning = prm_read_user_data(user_airloop, 'economizer_exception_for_gas_phase_air_cleaning', UserDataBoolean::FALSE)
    unless economizer_exception_for_gas_phase_air_cleaning.nil? || UserDataBoolean.matched_any?(economizer_exception_for_gas_phase_air_cleaning)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Air Loop name #{name}, economizer_exception_for_gas_phase_air_cleaning shall be either True or False. Got #{economizer_exception_for_gas_phase_air_cleaning}")
      userdata_valid = false
    end

    economizer_exception_for_open_refrigerated_cases = prm_read_user_data(user_airloop, 'economizer_exception_for_open_refrigerated_cases', UserDataBoolean::FALSE)
    unless economizer_exception_for_open_refrigerated_cases.nil? || UserDataBoolean.matched_any?(economizer_exception_for_open_refrigerated_cases)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Air Loop name #{name}, economizer_exception_for_open_refrigerated_cases shall be either True or False. Got #{economizer_exception_for_open_refrigerated_cases}")
      userdata_valid = false
    end

    # Fan power credits, exhaust air energy recovery
    user_airloop.each_key do |info_key|
      # Fan power credits
      if info_key.include?('has_fan_power_credit')
        has_fan_power_credit = prm_read_user_data(user_airloop, info_key)
        unless has_fan_power_credit.nil? || UserDataBoolean.matched_any?(has_fan_power_credit)
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Air Loop name #{name}, #{info_key} shall be either True or False. Got #{has_fan_power_credit}.")
          userdata_valid = false
        end
      elsif info_key.include?('fan_power_credit')
        fan_power_credit = prm_read_user_data(user_airloop, info_key)
        unless fan_power_credit.nil? || Float(fan_power_credit, exception: false)
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Air Loop name #{name}, #{info_key} shall be a numeric value. Got #{fan_power_credit}.")
          userdata_valid = false
        end
      end
      # Exhaust air energy recovery
      if info_key.include?('exhaust_energy_recovery_exception')
        exhaust_energy_recovery_exception = prm_read_user_data(user_airloop, info_key)
        unless exhaust_energy_recovery_exception.nil? || UserDataBoolean.matched_any?(exhaust_energy_recovery_exception)
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Air Loop name #{name}, #{info_key} shall be either True or False. Got #{has_fan_power_credit}.")
          userdata_valid = false
        end
      end
    end
  end
  return userdata_valid
end
check_userdata_airloop_hvac_doas(object_name, user_data) click to toggle source

Check for incorrect data in [UserDataFiles::AIRLOOP_HVAC_DOAS] @param object_name [String] name of user data csv file to check @param user_data [Hash] hash of data from user data csv file @return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 326
def check_userdata_airloop_hvac_doas(object_name, user_data)
  userdata_valid = true
  user_data.each do |user_airloop|
    name = prm_read_user_data(user_airloop, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: air loop name is missing or empty. Air loop user data has not validated.")
      return false
    end
    # Fan power credits, exhaust air energy recovery
    user_airloop.each_key do |info_key|
      # Fan power credits
      if info_key.include?('has_fan_power_credit')
        has_fan_power_credit = prm_read_user_data(user_airloop, info_key)
        unless has_fan_power_credit.nil? || UserDataBoolean.matched_any?(has_fan_power_credit)
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Air Loop name #{name}, #{info_key} shall be either True or False. Got #{has_fan_power_credit}.")
          userdata_valid = false
        end
      elsif info_key.include?('fan_power_credit')
        fan_power_credit = prm_read_user_data(user_airloop, info_key)
        unless fan_power_credit.nil? || Float(fan_power_credit, exception: false)
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Air Loop name #{name}, #{info_key} shall be a numeric value. Got #{fan_power_credit}.")
          userdata_valid = false
        end
      end
      # Exhaust air energy recovery
      if info_key.include?('exhaust_energy_recovery_exception')
        exhaust_energy_recovery_exception = prm_read_user_data(user_airloop, info_key)
        unless exhaust_energy_recovery_exception.nil? || UserDataBoolean.matched_any?(exhaust_energy_recovery_exception)
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Air Loop name #{name}, #{info_key} shall be either True or False. Got #{exhaust_energy_recovery_exception}.")
          userdata_valid = false
        end
      end
    end
  end
  return userdata_valid
end
check_userdata_building(object_name, user_data) click to toggle source

Check for incorrect data in [UserDataFiles::BUILDING] @param object_name [String] name of user data csv file to check @param user_data [Hash] hash of data from user data csv file @return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 224
def check_userdata_building(object_name, user_data)
  userdata_valid = true
  user_data.each do |user_building|
    name = prm_read_user_data(user_building, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: building name is missing or empty. Building user data has not validated.")
      return false
    end

    building_type_for_hvac = prm_read_user_data(user_building, 'building_type_for_hvac')
    unless building_type_for_hvac.nil? || UserDataHVACBldgType.matched_any?(building_type_for_hvac)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "Unknown building type #{building_type_for_hvac} for prescribed HVAC system type. For full list of the building type, see: https://pnnl.github.io/BEM-for-PRM/user_guide/prm_api_ref/baseline_generation_api/#--default_hvac_bldg_type")
      userdata_valid = false
    end

    building_type_for_wwr = prm_read_user_data(user_building, 'building_type_for_wwr')
    unless building_type_for_wwr.nil? || UserDataWWRBldgType.matched_any?(building_type_for_wwr)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "Unknown building type #{building_type_for_hvac} for prescribed window-to-wall ratio. For full list of the building type, see: https://pnnl.github.io/BEM-for-PRM/user_guide/prm_api_ref/baseline_generation_api/#--default_wwr_bldg_type")
      userdata_valid = false
    end

    building_type_for_swh = prm_read_user_data(user_building, 'building_type_for_swh')
    unless building_type_for_swh.nil? || UserDataSHWBldgType.matched_any?(building_type_for_swh)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "Unknown building type #{building_type_for_hvac} for prescribed service hot water system. For full list of the building type, see: https://pnnl.github.io/BEM-for-PRM/user_guide/prm_api_ref/baseline_generation_api/#--default_swh_bldg_type")
      userdata_valid = false
    end

    is_exempt_from_rotations = prm_read_user_data(user_building, 'is_exempt_from_rotations')
    unless is_exempt_from_rotations.nil? || UserDataBoolean.matched_any?(is_exempt_from_rotations)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Building name #{name}, is_exempt_from_rotations shall be either True or False. Got #{is_exempt_from_rotations}.")
      userdata_valid = false
    end
  end
  return userdata_valid
end
check_userdata_electric_equipment(object_name, user_data) click to toggle source

@return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 520
def check_userdata_electric_equipment(object_name, user_data)
  userdata_valid = true
  user_data.each do |electric_row|
    name = prm_read_user_data(electric_row, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: electric equipoment name is missing or empty. Electric equipment user data has not validated.")
      return false
    end
    # check for fractions
    fraction_of_controlled_receptacles = prm_read_user_data(electric_row, 'fraction_of_controlled_receptacles')
    unless fraction_of_controlled_receptacles.nil? || Float(fraction_of_controlled_receptacles, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: electric equipment definition #{name}'s fraction of controlled receptacles shall be a float, Got #{fraction_of_controlled_receptacles}.")
    end
    receptacle_power_savings = prm_read_user_data(electric_row, 'receptacle_power_savings')
    unless receptacle_power_savings.nil? || Float(receptacle_power_savings, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: electric equipment definition #{name}'s receptacle power savings shall be a float, Got #{receptacle_power_savings}.")
    end
    # check for data type
    # unless fan_power_credit.nil? || Float(fan_power_credit, exception: false)
    motor_horsepower = prm_read_user_data(electric_row, 'motor_horsepower')
    unless motor_horsepower.nil? || Float(motor_horsepower, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Motor #{electric_row['name']}'s horsepower data is either 0.0 or unavailable. Check the inputs.")
    end
    motor_efficiency = prm_read_user_data(electric_row, 'motor_efficiency')
    unless motor_efficiency.nil? || Float(motor_efficiency, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Motor #{electric_row['name']}'s efficiency data is either 0.0 or unavailable. Check the inputs.")
    end
    motor_is_exempt = prm_read_user_data(electric_row, 'motor_is_exempt')
    unless motor_is_exempt.nil? || UserDataBoolean.matched_any?(motor_is_exempt)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Motor #{electric_row['name']} is exempt data should be either True or False. But get data #{electric_row['motor_is_exempt']}")
    end
    # We may need to do the same for refrigeration and elevator?
    # Check elevator
    elevator_weight_of_car = prm_read_user_data(electric_row, 'elevator_weight_of_car')
    unless elevator_weight_of_car.nil? || Float(elevator_weight_of_car, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Elevator #{electric_row['name']}'s weight of car data is either 0.0 or unavailable. Check the inputs.")
    end
    elevator_rated_load = prm_read_user_data(electric_row, 'elevator_rated_load')
    unless elevator_rated_load.nil? || Float(elevator_rated_load, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Elevator #{electric_row['name']}'s rated load data is either 0.0 or unavailable. Check the inputs.")
    end
    elevator_counter_weight_of_car = prm_read_user_data(electric_row, 'elevator_counter_weight_of_car')
    unless elevator_counter_weight_of_car.nil? || Float(elevator_counter_weight_of_car, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Elevator #{electric_row['name']}'s counter weight of car data is either 0.0 or unavailable. Check the inputs.")
    end
    elevator_speed_of_car = prm_read_user_data(electric_row, 'elevator_speed_of_car')
    unless elevator_speed_of_car.nil? || Float(elevator_speed_of_car, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Elevator #{electric_row['name']}'s speed of car data is either 0.0 or unavailable. Check the inputs.")
    end
    elevator_number_of_stories = prm_read_user_data(electric_row, 'elevator_number_of_stories')
    unless elevator_number_of_stories.nil? || Integer(elevator_number_of_stories, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Elevator #{electric_row['name']}'s serves number of stories data is either smaller or equal to 1 or unavailable. Check the inputs.")
    end
    # Check refrigeration
    # Check data type
    # The equipment class shall be verified at the implementation level
    refrigeration_equipment_volume = prm_read_user_data(electric_row, 'refrigeration_equipment_volume')
    unless refrigeration_equipment_volume.nil? || Float(refrigeration_equipment_volume, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Refrigeration #{electric_row['name']}'s equipment volume data is either 0.0 or unavailable. Check the inputs.")
    end
    refrigeration_equipment_total_display_area = prm_read_user_data(electric_row, 'refrigeration_equipment_total_display_area')
    unless refrigeration_equipment_total_display_area.nil? || Float(refrigeration_equipment_total_display_area, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: Refrigeration #{electric_row['name']}'s total display area data is either 0.0 or unavailable. Check the inputs.")
    end
  end
  return userdata_valid
end
check_userdata_exterior_lighting(object_name, user_data) click to toggle source

@return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 463
def check_userdata_exterior_lighting(object_name, user_data)
  userdata_valid = true
  user_data.each do |exterior_light|
    name = prm_read_user_data(exterior_light, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: exterior light name is missing or empty. Exterior light user data has not validated.")
      return false
    end

    num_cats = prm_read_user_data(exterior_light, 'num_ext_lights_subcats', '0').to_i
    (1..num_cats).each do |icat|
      cat_key = format('end_use_subcategory_%02d', icat)
      subcat = prm_read_user_data(exterior_light, cat_key, nil)
      unless subcat
        OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Exterior light: #{name}, #{cat_key} is either missing or empty.")
        userdata_valid = false
      end
    end
  end
  return userdata_valid
end
check_userdata_gas_equipment(object_name, user_data) click to toggle source

@return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 491
def check_userdata_gas_equipment(object_name, user_data)
  userdata_valid = true
  user_data.each do |gas_row|
    name = prm_read_user_data(gas_row, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: gas equipoment name is missing or empty. Gas equipment user data has not validated.")
      return false
    end
    # check for fractions
    fraction_of_controlled_receptacles = prm_read_user_data(gas_row, 'fraction_of_controlled_receptacles')
    unless fraction_of_controlled_receptacles.nil? || Float(fraction_of_controlled_receptacles, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: gas equipment definition #{name}'s fraction of controlled receptacles shall be a float, Got #{fraction_of_controlled_receptacles}.")
    end
    receptacle_power_savings = prm_read_user_data(gas_row, 'receptacle_power_savings')
    unless receptacle_power_savings.nil? || Float(receptacle_power_savings, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: gas equipment definition #{name}'s receptacle power savings shall be a float, Got #{receptacle_power_savings}.")
    end
  end
  return userdata_valid
end
check_userdata_lights(object_name, user_data) click to toggle source

Check for incorrect data in [UserDataFiles::LIGHTS] @param object_name [String] name of user data csv file to check @param user_data [Hash] hash of data from user data csv file @return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 195
def check_userdata_lights(object_name, user_data)
  userdata_valid = true
  user_data.each do |user_light|
    name = prm_read_user_data(user_light, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: lights name is missing or empty. Lights user data has not validated.")
      return false
    end

    has_retail_display_exception = prm_read_user_data(user_light, 'has_retail_display_exception')
    unless has_retail_display_exception.nil? || UserDataBoolean.matched_any?(has_retail_display_exception)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Lights name #{name}, has_retail_display_exception shall be either True or False. Got #{has_retail_display_exception}")
      userdata_valid = false
    end

    has_unregulated_exception = prm_read_user_data(user_light, 'has_unregulated_exception')
    unless has_unregulated_exception.nil? || UserDataBoolean.matched_any?(has_unregulated_exception)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Lights name #{name}, has_unregulated_exception shall be either True or False. Got #{has_unregulated_exception}")
      userdata_valid = false
    end
  end
  # do we need to regulate the unregulated_category?
  return userdata_valid
end
check_userdata_outdoor_air(object_name, user_data) click to toggle source

Check for incorrect data in [UserDataFiles::DESIGN_SPECIFICATION_OUTDOOR_AIR] @param object_name [String] name of user data csv file to check @param user_data [Hash] hash of data from user data csv file @return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 367
def check_userdata_outdoor_air(object_name, user_data)
  userdata_valid = true
  user_data.each do |user_oa|
    name = prm_read_user_data(user_oa, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: air loop name is missing or empty. Air loop user data has not validated.")
      return false
    end

    outdoor_airflow_per_person = prm_read_user_data(user_oa, 'outdoor_airflow_per_person')
    unless outdoor_airflow_per_person.nil? || Float(outdoor_airflow_per_person, exception: false)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Design outdoor air name #{name}, outdoor_airflow_per_person shall be a numeric value. Got #{outdoor_airflow_per_person}.")
      userdata_valid = false
    end

    outdoor_airflow_per_floor_area = prm_read_user_data(user_oa, 'outdoor_airflow_per_floor_area')
    unless outdoor_airflow_per_floor_area.nil? || Float(outdoor_airflow_per_floor_area, exception: false)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Design outdoor air name #{name}, outdoor_airflow_per_floor_area shall be a numeric value. Got #{outdoor_airflow_per_floor_area}.")
      userdata_valid = false
    end

    outdoor_air_flowrate = prm_read_user_data(user_oa, 'outdoor_air_flowrate')
    unless outdoor_air_flowrate.nil? || Float(outdoor_air_flowrate, exception: false)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Design outdoor air name #{name}, outdoor_air_flowrate shall be a numeric value. Got #{outdoor_air_flowrate}.")
      userdata_valid = false
    end

    outdoor_air_flow_air_changes_per_hour = prm_read_user_data(user_oa, 'outdoor_air_flow_air_changes_per_hour')
    unless outdoor_air_flow_air_changes_per_hour.nil? || Float(outdoor_air_flow_air_changes_per_hour, exception: false)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Design outdoor air name #{name}, outdoor_air_flow_air_changes_per_hour shall be a numeric value. Got #{outdoor_air_flow_air_changes_per_hour}.")
      userdata_valid = false
    end
  end
  return userdata_valid
end
check_userdata_space_and_spacetype(object_name, user_data) click to toggle source

@return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 606
def check_userdata_space_and_spacetype(object_name, user_data)
  userdata_valid = true
  user_data.each do |row|
    building_type_for_wwr = prm_read_user_data(row, 'building_type_for_wwr')
    unless building_type_for_wwr.nil? || UserDataWWRBldgType.matched_any?(building_type_for_wwr)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "Unknown building type #{building_type_for_wwr} for prescribed window to wall ratio. For full list of the building type, see: https://pnnl.github.io/BEM-for-PRM/user_guide/prm_api_ref/baseline_generation_api/#--default_wwr_bldg_type")
      userdata_valid = false
    end
    unless prm_read_user_data(row, 'num_std_ltg_types', '0').to_i == 0
      num_ltg_type = row['num_std_ltg_types'].to_i
      total_ltg_percent = 0.0
      std_ltg_index = 0
      while std_ltg_index < num_ltg_type
        frac_key = format('std_ltg_type_frac%02d', (std_ltg_index + 1))
        total_ltg_percent += prm_read_user_data(row, frac_key, '0.0').to_f
        std_ltg_index += 1
      end
      if (total_ltg_percent - 1.0).abs > 0.01
        OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data #{object_name}: The fraction of user defined lighting types in Space/SpaceType: #{row['name']} does not add up to 1.0. The calculated fraction is #{total_ltg_percent}.")
        userdata_valid = false
      end
    end
  end
  return userdata_valid
end
check_userdata_thermal_zone(object_name, user_data) click to toggle source

Check for incorrect data in [UserDataFiles::THERMAL_ZONE] @param object_name [String] name of user data csv file to check @param user_data [Hash] hash of data from user data csv file @return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 264
def check_userdata_thermal_zone(object_name, user_data)
  userdata_valid = true
  user_data.each do |user_thermal_zone|
    name = prm_read_user_data(user_thermal_zone, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: thermal zone name is missing or empty. Thermal zone user data has not validated.")
      return false
    end
    has_health_safety_night_cycle_exception = prm_read_user_data(user_thermal_zone, 'has_health_safety_night_cycle_exception')
    unless has_health_safety_night_cycle_exception.nil? || UserDataBoolean.matched_any?(has_health_safety_night_cycle_exception)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, Thermal zone name #{name}, has_health_safety_night_cycle_exception shall be either True or False. Got #{has_health_safety_night_cycle_exception}")
      userdata_valid = false
    end
  end
  return userdata_valid
end
check_userdata_wateruse_connections(object_name, user_data) click to toggle source

@return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 638
def check_userdata_wateruse_connections(object_name, user_data)
  userdata_valid = true
  user_data.each do |row|
    name = prm_read_user_data(row, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: water use connection name is missing or empty. user data is not validated.")
      return false
    end
  end
  return userdata_valid
end
check_userdata_wateruse_equipment(object_name, user_data) click to toggle source

@return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 656
def check_userdata_wateruse_equipment(object_name, user_data)
  userdata_valid = true
  user_data.each do |row|
    name = prm_read_user_data(row, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: water use equipment name is missing or empty. user data is not validated.")
      return false
    end

    building_swh_type = prm_read_user_data(row, 'building_type_swh', nil)
    # gas phase air cleaning is system base - add proposed hvac system name to zones
    unless building_swh_type.nil? || UserDataSHWBldgType.matched_any?(building_swh_type)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, water equipment name #{name}, building_type_swh shall be one of the string listed in https://pnnl.github.io/BEM-for-PRM/user_guide/prm_api_ref/baseline_generation_api/#--default_swh_bldg_type. Got #{building_swh_type}")
      userdata_valid = false
    end
  end
  return userdata_valid
end
check_userdata_wateruse_equipment_definition(object_name, user_data) click to toggle source

@return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 681
def check_userdata_wateruse_equipment_definition(object_name, user_data)
  userdata_valid = true
  user_data.each do |row|
    name = prm_read_user_data(row, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: water use equipment name is missing or empty. user data is not validated.")
      return false
    end
    # check for data type
    peak_flow_rate = prm_read_user_data(row, 'peak_flow_rate', nil)
    unless peak_flow_rate.nil? || Float(peak_flow_rate, exception: false)
      userdata_valid = false
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: water use equipment definition #{name}'s peak flow rate shall be a float, Got #{peak_flow_rate}.")
    end
  end
  return userdata_valid
end
check_userdata_zone_hvac(object_name, user_data) click to toggle source

Check for incorrect data in [UserDataFiles::ZONE_HVAC] @param object_name [String] name of user data csv file to check @param user_data [Hash] hash of data from user data csv file @return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 285
def check_userdata_zone_hvac(object_name, user_data)
  userdata_valid = true
  user_data.each do |user_zone_hvac|
    name = prm_read_user_data(user_zone_hvac, 'name')
    unless name
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}: zone HVAC name is missing or empty. Zone HVAC user data has not validated.")
      return false
    end
    # Fan power credits, exhaust air energy recovery
    user_zone_hvac.each_key do |info_key|
      # Fan power credits
      if info_key.include?('has_fan_power_credit')
        has_fan_power_credit = prm_read_user_data(user_zone_hvac, info_key)
        unless has_fan_power_credit.nil? || UserDataBoolean.matched_any?(has_fan_power_credit)
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, zone HVAC name #{name}, #{info_key} shall be either True or False. Got #{has_fan_power_credit}.")
          userdata_valid = false
        end
      elsif info_key.include?('fan_power_credit')
        fan_power_credit = prm_read_user_data(user_zone_hvac, info_key)
        unless fan_power_credit.nil? || Float(fan_power_credit, exception: false)
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, zone HVAC name #{name}, #{info_key} shall be a numeric value. Got #{fan_power_credit}.")
          userdata_valid = false
        end
      end
      # Exhaust air energy recovery
      if info_key.include?('exhaust_energy_recovery_exception')
        exhaust_energy_recovery_exception = prm_read_user_data(user_zone_hvac, info_key)
        unless exhaust_energy_recovery_exception.nil? || UserDataBoolean.matched_any?(exhaust_energy_recovery_exception)
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', "User data: #{object_name}, zone HVAC name #{name}, #{info_key} shall be either True or False. Got #{exhaust_energy_recovery_exception}.")
          userdata_valid = false
        end
      end
    end
  end
  return userdata_valid
end
chiller_electric_eir_apply_efficiency_and_curves(chiller_electric_eir) click to toggle source

Applies the standard efficiency ratings to this object.

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ChillerElectricEIR.rb, line 8
def chiller_electric_eir_apply_efficiency_and_curves(chiller_electric_eir)
  # Get the chiller capacity
  capacity_w = chiller_electric_eir_find_capacity(chiller_electric_eir)

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

  # Set the efficiency value
  cop = chiller_electric_eir_standard_minimum_full_load_efficiency(chiller_electric_eir)
  unless cop
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find minimum full load efficiency, will not be set.")
    return false
  end
  chiller_electric_eir.setReferenceCOP(cop)

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

  return true
end
chw_sizing_control(model, chilled_water_loop, dsgn_sup_wtr_temp, dsgn_sup_wtr_temp_delt) click to toggle source

Apply sizing and controls to chilled water loop

@param model [OpenStudio::Model::Model] OpenStudio model object @param chilled_water_loop [OpenStudio::Model::PlantLoop] chilled water loop @param dsgn_sup_wtr_temp [Double] design chilled water supply T @param dsgn_sup_wtr_temp_delt [Double] design chilled water supply delta T @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb, line 619
def chw_sizing_control(model, chilled_water_loop, dsgn_sup_wtr_temp, dsgn_sup_wtr_temp_delt)
  design_chilled_water_temperature = 44 # Loop design chilled water temperature (F)
  design_chilled_water_temperature_delta = 10.1 # Loop design chilled water temperature  (deltaF)
  chw_outdoor_temperature_high = 80 # Chilled water temperature reset at high outdoor air temperature (F)
  chw_outdoor_temperature_low = 60 # Chilled water temperature reset at low outdoor air temperature (F)
  chw_outdoor_high_setpoint = 44 # Chilled water setpoint temperature at high outdoor air temperature (F)
  chw_outdoor_low_setpoint = 54 # Chilled water setpoint temperature at low outdoor air temperature (F)
  chiller_chw_low_temp_limit = 36 # Chiller leaving chilled water lower temperature limit (F)
  chiller_chw_cond_temp = 95 # Chiller entering condenser fluid temperature (F)
  primary_pump_power = 9 # primary pump power (W/gpm)

  if dsgn_sup_wtr_temp.nil?
    dsgn_sup_wtr_temp_c = OpenStudio.convert(design_chilled_water_temperature, 'F', 'C').get
  else
    dsgn_sup_wtr_temp_c = OpenStudio.convert(dsgn_sup_wtr_temp, 'F', 'C').get
  end
  if dsgn_sup_wtr_temp_delt.nil?
    dsgn_sup_wtr_temp_delt_k = OpenStudio.convert(design_chilled_water_temperature_delta, 'R', 'K').get
  else
    dsgn_sup_wtr_temp_delt_k = OpenStudio.convert(dsgn_sup_wtr_temp_delt, 'R', 'K').get
  end
  chilled_water_loop.setMinimumLoopTemperature(1.0)
  chilled_water_loop.setMaximumLoopTemperature(40.0)

  sizing_plant = chilled_water_loop.sizingPlant
  sizing_plant.setLoopType('Cooling')
  sizing_plant.setDesignLoopExitTemperature(dsgn_sup_wtr_temp_c)
  sizing_plant.setLoopDesignTemperatureDifference(dsgn_sup_wtr_temp_delt_k)
  # Use OA reset setpoint manager
  outdoor_low_temperature_c = OpenStudio.convert(chw_outdoor_temperature_low, 'F', 'C').get.round(1)
  outdoor_high_temperature_c = OpenStudio.convert(chw_outdoor_temperature_high, 'F', 'C').get.round(1)
  setpoint_temperature_outdoor_high_c = OpenStudio.convert(chw_outdoor_high_setpoint, 'F', 'C').get.round(1)
  setpoint_temperature_outdoor_low_c = OpenStudio.convert(chw_outdoor_low_setpoint, 'F', 'C').get.round(1)

  chw_stpt_manager = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(model)
  chw_stpt_manager.setName("#{chilled_water_loop.name} Setpoint Manager")
  chw_stpt_manager.setOutdoorHighTemperature(outdoor_high_temperature_c) # Degrees Celsius
  chw_stpt_manager.setSetpointatOutdoorHighTemperature(setpoint_temperature_outdoor_high_c) # Degrees Celsius
  chw_stpt_manager.setOutdoorLowTemperature(outdoor_low_temperature_c) # Degrees Celsius
  chw_stpt_manager.setSetpointatOutdoorLowTemperature(setpoint_temperature_outdoor_low_c) # Degrees Celsius
  chw_stpt_manager.addToNode(chilled_water_loop.supplyOutletNode)

  return true
end
coil_cooling_dx_single_speed_apply_efficiency_and_curves(coil_cooling_dx_single_speed, sql_db_vars_map, sys_type) click to toggle source

Applies the standard efficiency ratings to this object.

@param coil_cooling_dx_single_speed [OpenStudio::Model::CoilCoolingDXSingleSpeed] coil cooling dx single speed object @param sql_db_vars_map [Hash] hash map @param sys_type [String] HVAC system type @return [Hash] hash of coil objects

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXSingleSpeed.rb, line 119
def coil_cooling_dx_single_speed_apply_efficiency_and_curves(coil_cooling_dx_single_speed, sql_db_vars_map, sys_type)
  # Preserve the original name
  orig_name = coil_cooling_dx_single_speed.name.to_s

  # Find the minimum COP and rename with efficiency rating
  # Set last argument to false to avoid renaming coil, since that complicates lookup of HP heating coil efficiency later
  cop = coil_cooling_dx_single_speed_standard_minimum_cop(coil_cooling_dx_single_speed, sys_type, false)

  # Map the original name to the new name
  sql_db_vars_map[coil_cooling_dx_single_speed.name.to_s] = orig_name

  # Set the efficiency values
  unless cop.nil?
    coil_cooling_dx_single_speed.setRatedCOP(OpenStudio::OptionalDouble.new(cop))
  end

  return sql_db_vars_map
end
coil_cooling_dx_single_speed_find_capacity(coil_cooling_dx_single_speed, sys_type) click to toggle source

Finds capacity in W

@param coil_cooling_dx_single_speed [OpenStudio::Model::CoilCoolingDXSingleSpeed] coil cooling dx single speed object @param sys_type [String] HVAC system type @return [Double] capacity in W to be used for find object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXSingleSpeed.rb, line 11
def coil_cooling_dx_single_speed_find_capacity(coil_cooling_dx_single_speed, sys_type)
  capacity_w = nil
  if coil_cooling_dx_single_speed.ratedTotalCoolingCapacity.is_initialized
    capacity_w = coil_cooling_dx_single_speed.ratedTotalCoolingCapacity.get
  elsif coil_cooling_dx_single_speed.autosizedRatedTotalCoolingCapacity.is_initialized
    capacity_w = coil_cooling_dx_single_speed.autosizedRatedTotalCoolingCapacity.get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXSingleSpeed', "For #{coil_cooling_dx_single_speed.name} capacity is not available, cannot apply efficiency standard.")
    return 0.0
  end

  # Check for user data that indicates multiple systems per thermal zone
  # This could be true for a data center where this is common practice
  # Or it could be for a thermal zone that represents multiple real building zones
  mult = 1
  thermal_zone = nil
  comp = coil_cooling_dx_single_speed.containingHVACComponent
  if comp.is_initialized && comp.get.to_AirLoopHVACUnitarySystem.is_initialized
    unitary = comp.get.to_AirLoopHVACUnitarySystem.get
    thermal_zone = unitary.controllingZoneorThermostatLocation.get
  end
  # meth = comp.methods
  comp = coil_cooling_dx_single_speed.containingZoneHVACComponent
  if comp.is_initialized && comp.get.thermalZone.is_initialized
    thermal_zone = comp.get.thermalZone.get
  end

  if !thermal_zone.nil? && standards_data.key?('userdata_thermal_zone')
    standards_data['userdata_thermal_zone'].each do |row|
      next unless row['name'].to_s.downcase.strip == thermal_zone.name.to_s.downcase.strip

      if row['number_of_systems'].to_s.upcase.strip != ''
        mult = row['number_of_systems'].to_s
        if mult.to_i.to_s == mult
          mult = mult.to_i
          capacity_w /= mult
        else
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', 'In userdata_thermalzone, number_of_systems requires integer input.')
        end
        break
      end
    end
  end

  # If it's a PTAC or PTHP System, we need to divide the capacity by the potential zone multiplier
  # because the COP is dependent on capacity, and the capacity should be the capacity of a single zone, not all the zones
  if sys_type == 'PTAC' || sys_type == 'PTHP'
    mult = 1
    comp = coil_cooling_dx_single_speed.containingZoneHVACComponent
    if comp.is_initialized && comp.get.thermalZone.is_initialized
      mult = comp.get.thermalZone.get.multiplier
      if mult > 1
        total_cap = capacity_w
        capacity_w /= mult
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXSingleSpeed', "For #{coil_cooling_dx_single_speed.name}, total capacity of #{OpenStudio.convert(total_cap, 'W', 'kBtu/hr').get.round(2)}kBTU/hr was divided by the zone multiplier of #{mult} to give #{capacity_kbtu_per_hr = OpenStudio.convert(capacity_w, 'W', 'kBtu/hr').get.round(2)}kBTU/hr.")
      end
    end
  end

  return capacity_w
end
coil_cooling_dx_single_speed_standard_minimum_cop(coil_cooling_dx_single_speed, sys_type, rename = false) click to toggle source

Finds lookup object in standards and return efficiency

@param coil_cooling_dx_single_speed [OpenStudio::Model::CoilCoolingDXSingleSpeed] coil cooling dx single speed object @param sys_type [String] HVAC system type @param rename [Boolean] if true, object will be renamed to include capacity and efficiency level @return [Double] full load efficiency (COP)

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXSingleSpeed.rb, line 79
def coil_cooling_dx_single_speed_standard_minimum_cop(coil_cooling_dx_single_speed, sys_type, rename = false)
  # find properties
  capacity_w = coil_cooling_dx_single_speed_find_capacity(coil_cooling_dx_single_speed, sys_type)
  capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
  search_criteria = coil_dx_find_search_criteria(coil_cooling_dx_single_speed, capacity_btu_per_hr, sys_type)

  # Lookup efficiencies depending on whether it is a unitary AC or a heat pump
  ac_props = nil
  ac_props = if sys_type == 'PSZ_HP' || sys_type == 'PTHP'
               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

  # Get the minimum efficiency standards
  cop = nil

  # Check to make sure properties were found
  if ac_props.nil?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXSingleSpeed', "For #{coil_cooling_dx_single_speed.name}, cannot find efficiency info using #{search_criteria}, cannot apply efficiency standard.")
    return cop # value of nil
  end

  cop = ac_props['copnfcooling']
  new_comp_name = "#{coil_cooling_dx_single_speed.name} #{capacity_btu_per_hr.round}Btu/hr #{cop}COP"

  # Rename
  if rename
    coil_cooling_dx_single_speed.setName(new_comp_name)
  end

  return cop
end
coil_cooling_dx_two_speed_apply_efficiency_and_curves(coil_cooling_dx_two_speed, sql_db_vars_map, sys_type) click to toggle source

Applies the standard efficiency ratings to this object.

@param coil_cooling_dx_two_speed [OpenStudio::Model::CoilCoolingDXTwoSpeed] coil cooling dx two speed object @param sql_db_vars_map [Hash] hash map @param sys_type [String] HVAC system type @return [Hash] hash of coil objects

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXTwoSpeed.rb, line 86
def coil_cooling_dx_two_speed_apply_efficiency_and_curves(coil_cooling_dx_two_speed, sql_db_vars_map, sys_type)
  # Preserve the original name
  orig_name = coil_cooling_dx_two_speed.name.to_s

  # Find the minimum COP and rename with efficiency rating
  cop = coil_cooling_dx_two_speed_standard_minimum_cop(coil_cooling_dx_two_speed, sys_type, true)

  # Map the original name to the new name
  sql_db_vars_map[coil_cooling_dx_two_speed.name.to_s] = orig_name

  # Set the efficiency values
  unless cop.nil?
    coil_cooling_dx_two_speed.setRatedHighSpeedCOP(cop)
    coil_cooling_dx_two_speed.setRatedLowSpeedCOP(cop)
  end

  return sql_db_vars_map
end
coil_cooling_dx_two_speed_find_capacity(coil_cooling_dx_two_speed, sys_type) click to toggle source

Finds capacity in W

@param coil_cooling_dx_two_speed [OpenStudio::Model::CoilCoolingDXTwoSpeed] coil cooling dx two speed object @param sys_type [String] HVAC system type @return [Double] capacity in W to be used for find object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXTwoSpeed.rb, line 11
def coil_cooling_dx_two_speed_find_capacity(coil_cooling_dx_two_speed, sys_type)
  capacity_w = nil
  if coil_cooling_dx_two_speed.ratedHighSpeedTotalCoolingCapacity.is_initialized
    capacity_w = coil_cooling_dx_two_speed.ratedHighSpeedTotalCoolingCapacity.get
  elsif coil_cooling_dx_two_speed.autosizedRatedHighSpeedTotalCoolingCapacity.is_initialized
    capacity_w = coil_cooling_dx_two_speed.autosizedRatedHighSpeedTotalCoolingCapacity.get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXTwoSpeed', "For #{coil_cooling_dx_two_speed.name} capacity is not available, cannot apply efficiency standard.")
    return 0.0
  end

  # If it's a PTAC or PTHP System, we need to divide the capacity by the potential zone multiplier
  # because the COP is dependent on capacity, and the capacity should be the capacity of a single zone, not all the zones
  if sys_type == 'PTAC' || sys_type == 'PTHP'
    mult = 1
    comp = coil_cooling_dx_two_speed.containingZoneHVACComponent
    if comp.is_initialized && comp.get.thermalZone.is_initialized
      mult = comp.get.thermalZone.get.multiplier
      if mult > 1
        total_cap = capacity_w
        capacity_w /= mult
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXTwoSpeed', "For #{coil_cooling_dx_single_speed.name}, total capacity of #{OpenStudio.convert(total_cap, 'W', 'kBtu/hr').get.round(2)}kBTU/hr was divided by the zone multiplier of #{mult} to give #{capacity_kbtu_per_hr = OpenStudio.convert(capacity_w, 'W', 'kBtu/hr').get.round(2)}kBTU/hr.")
      end
    end
  end

  return capacity_w
end
coil_cooling_dx_two_speed_standard_minimum_cop(coil_cooling_dx_two_speed, sys_type, rename = false) click to toggle source

Finds lookup object in standards and return efficiency

@param coil_cooling_dx_two_speed [OpenStudio::Model::CoilCoolingDXTwoSpeed] coil cooling dx two speed object @param sys_type [String] HVAC system type @param rename [Boolean] if true, object will be renamed to include capacity and efficiency level @return [Double] full load efficiency (COP)

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXTwoSpeed.rb, line 46
def coil_cooling_dx_two_speed_standard_minimum_cop(coil_cooling_dx_two_speed, sys_type, rename = false)
  # find properties
  capacity_w = coil_cooling_dx_two_speed_find_capacity(coil_cooling_dx_two_speed, sys_type)
  capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
  search_criteria = coil_dx_find_search_criteria(coil_cooling_dx_two_speed, capacity_btu_per_hr, sys_type)

  # Lookup efficiencies depending on whether it is a unitary AC or a heat pump
  ac_props = nil
  ac_props = if sys_type == 'PSZ_HP' || sys_type == 'PTHP'
               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

  # Get the minimum efficiency standards
  cop = nil

  # Check to make sure properties were found
  if ac_props.nil?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXTwoSpeed', "For #{coil_cooling_dx_two_speed.name}, cannot find efficiency info using #{search_criteria}, cannot apply efficiency standard.")
    return cop # value of nil
  end

  cop = ac_props['copnfcooling']
  new_comp_name = "#{coil_cooling_dx_two_speed.name} #{capacity_btu_per_hr.round}Btu/hr #{cop}COP"

  # Rename
  if rename
    coil_cooling_dx_two_speed.setName(new_comp_name)
  end

  return cop
end
coil_heating_dx_single_speed_apply_efficiency_and_curves(coil_heating_dx_single_speed, sql_db_vars_map, sys_type) click to toggle source

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

@param coil_heating_dx_single_speed [OpenStudio::Model::CoilHeatingDXSingleSpeed] coil heating dx single speed object @param sql_db_vars_map [Hash] hash map @param sys_type [String] HVAC system type @return [Hash] hash of coil objects

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingDXSingleSpeed.rb, line 169
def coil_heating_dx_single_speed_apply_efficiency_and_curves(coil_heating_dx_single_speed, sql_db_vars_map, sys_type)
  # Preserve the original name
  orig_name = coil_heating_dx_single_speed.name.to_s

  # Find the minimum COP and rename with efficiency rating
  cop = coil_heating_dx_single_speed_standard_minimum_cop(coil_heating_dx_single_speed, sys_type, false)

  # Map the original name to the new name
  sql_db_vars_map[coil_heating_dx_single_speed.name.to_s] = orig_name

  # Set the efficiency values
  unless cop.nil?
    coil_heating_dx_single_speed.setRatedCOP(cop)
  end

  return sql_db_vars_map
end
coil_heating_dx_single_speed_find_capacity(coil_heating_dx_single_speed, sys_type) click to toggle source

Finds capacity in W. This is the cooling capacity of the paired DX cooling coil.

@param coil_heating_dx_single_speed [OpenStudio::Model::CoilHeatingDXSingleSpeed] coil heating dx single speed object @param sys_type [String] HVAC system type @return [Double] capacity in W to be used for find object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingDXSingleSpeed.rb, line 11
def coil_heating_dx_single_speed_find_capacity(coil_heating_dx_single_speed, sys_type)
  capacity_w = nil

  # Get the paired cooling coil
  clg_coil = nil

  # Unitary and zone equipment
  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
    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
  end

  # On AirLoop directly
  if 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']
    clg_types.each do |ct|
      coils = air_loop.supplyComponents(ct.to_IddObjectType)
      next if coils.empty?

      clg_coil = coils[0]
      break # Stop on first DX cooling coil found
    end
  end

  # If no paired cooling coil was found, throw an error and fall back to the heating capacity of the DX heating coil
  if clg_coil.nil?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingDXSingleSpeed', "For #{coil_heating_dx_single_speed.name}, the paired DX cooling coil could not be found to determine capacity. Efficiency will incorrectly be based on DX coil's heating capacity.")
    if coil_heating_dx_single_speed.ratedTotalHeatingCapacity.is_initialized
      capacity_w = coil_heating_dx_single_speed.ratedTotalHeatingCapacity.get
    elsif coil_heating_dx_single_speed.autosizedRatedTotalHeatingCapacity.is_initialized
      capacity_w = coil_heating_dx_single_speed.autosizedRatedTotalHeatingCapacity.get
    else
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingDXSingleSpeed', "For #{coil_heating_dx_single_speed.name} capacity is not available, cannot apply efficiency standard to paired DX heating coil.")
      return 0.0
    end
    return capacity_w
  end

  # If a coil was found, cast to the correct type
  if clg_coil.to_CoilCoolingDXSingleSpeed.is_initialized
    clg_coil = clg_coil.to_CoilCoolingDXSingleSpeed.get
    capacity_w = coil_cooling_dx_single_speed_find_capacity(clg_coil, sys_type)
  elsif clg_coil.to_CoilCoolingDXTwoSpeed.is_initialized
    clg_coil = clg_coil.to_CoilCoolingDXTwoSpeed.get
    capacity_w = coil_cooling_dx_two_speed_find_capacity(clg_coil, sys_type)
  end

  # Check for user data that indicates multiple systems per thermal zone
  # This could be true for a data center where this is common practice
  # Or it could be for a thermal zone that represents multiple real building zones
  mult = 1
  thermal_zone = nil
  comp = coil_heating_dx_single_speed.containingHVACComponent
  if comp.is_initialized && comp.get.to_AirLoopHVACUnitarySystem.is_initialized
    unitary = comp.get.to_AirLoopHVACUnitarySystem.get
    thermal_zone = unitary.controllingZoneorThermostatLocation.get
  end
  # meth = comp.methods
  comp = coil_heating_dx_single_speed.containingZoneHVACComponent
  if comp.is_initialized && comp.get.thermalZone.is_initialized
    thermal_zone = comp.get.thermalZone.get
  end

  if !thermal_zone.nil? && standards_data.key?('userdata_thermal_zone')
    standards_data['userdata_thermal_zone'].each do |row|
      next unless row['name'].to_s.downcase.strip == thermal_zone.name.to_s.downcase.strip

      if row['number_of_systems'].to_s.upcase.strip != ''
        mult = row['number_of_systems'].to_s
        if mult.to_i.to_s == mult
          mult = mult.to_i
          capacity_w /= mult
        else
          OpenStudio.logFree(OpenStudio::Error, 'prm.log', 'In userdata_thermalzone, number_of_systems requires integer input.')
        end
        break
      end
    end
  end

  # If it's a PTAC or PTHP System, we need to divide the capacity by the potential zone multiplier
  # because the COP is dependent on capacity, and the capacity should be the capacity of a single zone, not all the zones
  if sys_type == 'PTHP'
    mult = 1
    comp = coil_heating_dx_single_speed.containingZoneHVACComponent
    if comp.is_initialized && comp.get.thermalZone.is_initialized
      mult = comp.get.thermalZone.get.multiplier
      if mult > 1
        total_cap = capacity_w
        capacity_w /= mult
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilHeatingDXSingleSpeed', "For #{coil_heating_dx_single_speed.name}, total capacity of #{OpenStudio.convert(total_cap, 'W', 'kBtu/hr').get.round(2)}kBTU/hr was divided by the zone multiplier of #{mult} to give #{capacity_kbtu_per_hr = OpenStudio.convert(capacity_w, 'W', 'kBtu/hr').get.round(2)}kBTU/hr.")
      end
    end
  end

  return capacity_w
end
coil_heating_dx_single_speed_standard_minimum_cop(coil_heating_dx_single_speed, sys_type, rename = false) click to toggle source

Finds lookup object in standards and return efficiency

@param coil_heating_dx_single_speed [OpenStudio::Model::CoilHeatingDXSingleSpeed] coil heating dx single speed object @param sys_type [String] HVAC system type @param rename [Boolean] if true, object will be renamed to include capacity and efficiency level @return [Double] full load efficiency (COP)

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingDXSingleSpeed.rb, line 134
def coil_heating_dx_single_speed_standard_minimum_cop(coil_heating_dx_single_speed, sys_type, rename = false)
  # find ac properties
  capacity_w = coil_heating_dx_single_speed_find_capacity(coil_heating_dx_single_speed, sys_type)
  capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
  search_criteria = coil_dx_find_search_criteria(coil_heating_dx_single_speed, capacity_btu_per_hr, sys_type)

  # find object
  ac_props = nil
  ac_props = model_find_object(standards_data['heat_pumps_heating'], search_criteria, capacity_btu_per_hr, Date.today)
  # Get the minimum efficiency standards
  cop = nil

  # Check to make sure properties were found
  if ac_props.nil?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingDXSingleSpeed', "For #{coil_heating_dx_single_speed.name}, cannot find efficiency info using #{search_criteria}, cannot apply efficiency standard.")
    return cop # value of nil
  end

  cop = ac_props['copnfcooling']
  new_comp_name = "#{coil_heating_dx_single_speed.name} #{capacity_btu_per_hr.round}Btu/hr #{cop}COP"

  # Rename
  if rename
    coil_heating_dx_single_speed.setName(new_comp_name)
  end

  return cop
end
coil_heating_gas_apply_efficiency_and_curves(coil_heating_gas, sql_db_vars_map, sys_type) click to toggle source

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

@param coil_heating_gas [OpenStudio::Model::CoilHeatingGas] coil heating gas object @param sql_db_vars_map [Hash] hash map @param sys_type [String] HVAC system type @return [Hash] hash of coil objects

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingGas.rb, line 87
def coil_heating_gas_apply_efficiency_and_curves(coil_heating_gas, sql_db_vars_map, sys_type)
  # Thermal efficiency
  thermal_eff = coil_heating_gas_standard_minimum_thermal_efficiency(coil_heating_gas, sys_type)

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

  return sql_db_vars_map
end
coil_heating_gas_find_search_criteria(coil_heating_gas, sys_type) click to toggle source

find search criteria

@param coil_heating_gas [OpenStudio::Model::CoilHeatingGas] coil heating gas object @param sys_type [String] HVAC system type @return [Hash] used for standards_lookup_table(model)

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingGas.rb, line 9
def coil_heating_gas_find_search_criteria(coil_heating_gas, sys_type)
  # Define the criteria to find the furnace properties
  # in the hvac standards data set.
  search_criteria = {}
  search_criteria['fuel_type'] = 'NaturalGas'
  if sys_type == 'Gas_Furnace'
    search_criteria['equipment_type'] = 'Warm Air Unit Heaters'
  else
    search_criteria['equipment_type'] = 'Warm Air Furnace'
  end
  return search_criteria
end
coil_heating_gas_standard_minimum_thermal_efficiency(coil_heating_gas, sys_type, rename = false) click to toggle source

Finds lookup object in standards and return minimum thermal efficiency

@param coil_heating_gas [OpenStudio::Model::CoilHeatingGas] coil heating gas object @param sys_type [String] HVAC system type @param rename [Boolean] if true, object will be renamed to include capacity and efficiency level @return [Double] minimum thermal efficiency

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingGas.rb, line 28
def coil_heating_gas_standard_minimum_thermal_efficiency(coil_heating_gas, sys_type, rename = false)
  # Get the coil properties
  search_criteria = coil_heating_gas_find_search_criteria(coil_heating_gas, sys_type)
  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)

  # 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
    return successfully_set_all_properties
  end

  # New name initial value
  new_comp_name = coil_heating_gas.name

  # 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
convert_userdata_csv_to_json(user_data_path, project_path) click to toggle source

Convert user data csv files to json format and save to project folder Method will create the json_folder in the project_path @author Doug Maddox, PNNL @param user_data_path [String path to folder containing csv files @param project_path [String path to project folder @return [String] path to json files

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 57
def convert_userdata_csv_to_json(user_data_path, project_path)
  # Get list of possible files from lib\openstudio-standards\standards\ashrae_90_1_prm\userdata_csv
  stds_dir = __dir__
  src_csv_dir = "#{stds_dir}/userdata_csv/*.csv"
  json_objs = {}

  Dir.glob(src_csv_dir).each do |csv_full_name|
    json_rows = []
    csv_file_name = File.basename(csv_full_name, File.extname(csv_full_name))
    unless UserDataFiles.matched_any?(csv_file_name)
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "user data file: #{csv_file_name} is not a valid file name. See the full list of acceptable file names in https://pnnl.github.io/BEM-for-PRM/user_guide/add_compliance_data/")
    end
    json_objs[csv_file_name] = json_rows
  end

  # Read all valid files in user_data_folder and load into json array
  unless user_data_path == ''
    user_data_validation_outcome = true
    Dir.glob("#{user_data_path.gsub('\\', '/')}/*.csv").each do |csv_full_name|
      csv_file_name = File.basename(csv_full_name, File.extname(csv_full_name))
      if json_objs.key?(csv_file_name)
        # Load csv file into array of hashes
        json_rows = CSV.foreach(csv_full_name, headers: true).map { |row| user_data_preprocessor(row) }
        next if json_rows.empty?

        # validate the user_data in json_rows
        unless user_data_validation(csv_file_name, json_rows)
          user_data_validation_outcome = false
        end

        # remove file extension
        file_name = File.basename(csv_full_name, File.extname(csv_full_name))
        json_objs[file_name] = json_rows
      end
    end
    unless user_data_validation_outcome
      terminate_prm_write_log('Error found in the user data. Check output log to see detail error messages', project_path, false)
    end
  end

  # Make folder for json files; remove pre-existing first, if needed
  json_path = "#{project_path}/user_data_json"
  FileUtils.rm_rf(json_path)
  FileUtils.mkdir_p(json_path)

  # Write all json files
  json_objs.each do |file_name, json_rows|
    json_obj = {}
    json_obj[file_name] = json_rows
    json_path_file = "#{json_path}/#{file_name}.json"
    File.open(json_path_file, 'w:UTF-8') do |file|
      file << JSON.pretty_generate(json_obj)
    end
  end

  return json_path
end
deep_copy_schedule(new_schedule_name, schedule, adjustment_factor, model) click to toggle source
# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 509
def deep_copy_schedule(new_schedule_name, schedule, adjustment_factor, model)
  OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Creating a new lighting schedule that applies occupancy sensor adjustment factor: #{adjustment_factor} based on #{schedule.name.get} schedule")
  sch = OpenstudioStandards::Schedules
  multiplier = 1.0 / (1.0 - adjustment_factor.to_f)
  case schedule.iddObjectType.valueName.to_s
  when 'OS_Schedule_Constant'
    schedule_constant = schedule.to_ScheduleConstant.get
    schedule_value = schedule_constant.value
    return sch.create_constant_schedule_ruleset(model, schedule_value * multiplier, name: new_schedule_name)
  when 'OS_Schedule_Ruleset'
    new_schedule = schedule.clone(model)
    new_schedule.setName(new_schedule_name)
    schedule_ruleset = new_schedule.to_ScheduleRuleset.get
    return sch.schedule_ruleset_simple_value_adjust(schedule_ruleset, multiplier, modification_type = 'Multiplier')
  when 'OS_Schedule_Compact'
    prm_raise(false, @sizing_run_dir, 'PRM does not support using Compact schedule for lighting schedules. Please update it to ruleset based or constant schedules.')
  else
    prm_raise(false, @sizing_run_dir, 'PRM only supports ruleset based or constant schedules for lighting schedules')
  end
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. Required for all VAV fans for stable baseline @param fan_variable_volume [OpenStudio::Model::FanVariableVolume] variable volume fan object @return [Boolean] returns true if required, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanVariableVolume.rb, line 10
def fan_variable_volume_part_load_fan_power_limitation?(fan_variable_volume)
  part_load_control_required = true
  return part_load_control_required
end
fan_variable_volume_part_load_fan_power_limitation_hp_limit(fan_variable_volume) click to toggle source

The threhold horsepower below which part load control is not required. always required for stable baseline, so threshold is zero

@param fan_variable_volume [OpenStudio::Model::FanVariableVolume] variable volume fan object @return [Double] the limit, in horsepower. Return zero for no limit by default.

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanVariableVolume.rb, line 20
def fan_variable_volume_part_load_fan_power_limitation_hp_limit(fan_variable_volume)
  hp_limit = 0
  return hp_limit
end
find_prm_heat_type(hvac_building_type, climate_zone) click to toggle source

Determine whether heating type is fuel or electric @param hvac_building_type [String] Key for lookup of baseline system type @param climate_zone [String] full name of climate zone @return [String] fuel or electric

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 3320
def find_prm_heat_type(hvac_building_type, climate_zone)
  climate_code = climate_zone.split('-')[-1]
  heat_type_props = model_find_object(standards_data['prm_heat_type'],
                                      'template' => template,
                                      'hvac_building_type' => hvac_building_type,
                                      'climate_zone' => climate_code)
  if !heat_type_props
    # try again with wild card for climate
    heat_type_props = model_find_object(standards_data['prm_heat_type'],
                                        'template' => template,
                                        'hvac_building_type' => hvac_building_type,
                                        'climate_zone' => 'any')
  end

  if !heat_type_props
    # try again with wild card for building type
    heat_type_props = model_find_object(standards_data['prm_heat_type'],
                                        'template' => template,
                                        'hvac_building_type' => 'all others',
                                        'climate_zone' => climate_code)
  end
  prm_raise(heat_type_props, @sizing_run_dir, "Could not find baseline heat type for: #{template}-#{hvac_building_type}-#{climate_zone}.")
  return heat_type_props['heat_type']
end
generate_baseline_log(file_directory) click to toggle source

Generate baseline log to a specific file directory @param file_directory [String] file directory

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2417
def generate_baseline_log(file_directory)
  log_messages_to_file_prm("#{file_directory}/prm.log", false)
end
generate_userdata_to_csv(user_model, user_data_path, user_data_file = nil) click to toggle source

Method to generate user data from a user model and save the csvs to the user_data_path This method can generate one user data csv based on the matching name or a full set of user data if leave it as nil @param user_model [OpenStudio::Model::Model] OpenStudio model object @param user_data_path [String] data path @param user_data_file [String] the name of the user data file.

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 24
def generate_userdata_to_csv(user_model, user_data_path, user_data_file = nil)
  user_data_list = [UserDataCSVAirLoopHVAC.new(user_model, user_data_path),
                    UserDataCSVBuilding.new(user_model, user_data_path),
                    UserDataCSVSpace.new(user_model, user_data_path),
                    UserDataCSVSpaceTypes.new(user_model, user_data_path),
                    UserDataCSVAirLoopHVACDOAS.new(user_model, user_data_path),
                    UserDataCSVExteriorLights.new(user_model, user_data_path),
                    UserDataCSVLights.new(user_model, user_data_path),
                    UserDataCSVThermalZone.new(user_model, user_data_path),
                    UserDataCSVElectricEquipment.new(user_model, user_data_path),
                    UserDataCSVGasEquipment.new(user_model, user_data_path),
                    UserDataCSVOutdoorAir.new(user_model, user_data_path),
                    UserDataWaterUseConnection.new(user_model, user_data_path),
                    UserDataWaterUseEquipment.new(user_model, user_data_path),
                    UserDataWaterUseEquipmentDefinition.new(user_model, user_data_path)]

  if user_data_file.nil?
    user_data_list.each(&:write_csv)
  else
    user_data_list.each do |user_data|
      if user_data.file_name == user_data_file
        user_data.write_csv
      end
    end
  end
end
get_airloop_hvac_design_oa_from_sql(air_loop_hvac) click to toggle source

Get the air loop HVAC design outdoor air flow rate by reading Standard 62.1 Summary from the sizing sql @author Xuechen (Jerry) Lei, PNNL @param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @return [Double] Design outdoor air flow rate (m^3/s)

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 539
def get_airloop_hvac_design_oa_from_sql(air_loop_hvac)
  return false unless air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized

  cooling_oa = air_loop_hvac.model.sqlFile.get.execAndReturnFirstDouble(
    "SELECT Value FROM TabularDataWithStrings WHERE ReportName='Standard62.1Summary' AND ReportForString='Entire Facility' AND TableName = 'System Ventilation Requirements for Cooling' AND ColumnName LIKE 'Outdoor Air Intake Flow%Vot' AND RowName='#{air_loop_hvac.name.to_s.upcase}'"
  )
  heating_oa = air_loop_hvac.model.sqlFile.get.execAndReturnFirstDouble(
    "SELECT Value FROM TabularDataWithStrings WHERE ReportName='Standard62.1Summary' AND ReportForString='Entire Facility' AND TableName = 'System Ventilation Requirements for Heating' AND ColumnName LIKE 'Outdoor Air Intake Flow%Vot' AND RowName='#{air_loop_hvac.name.to_s.upcase}'"
  )
  return [cooling_oa.to_f, heating_oa.to_f].max
end
get_baseline_system_groups_for_one_building_type(model, hvac_building_type, zones_in_building_type) click to toggle source

Assign spaces to system groups for one hvac building type One group contains all zones associated with one HVAC type Separate groups are made for laboratories, computer rooms, district cooled zones, heated-only zones, or hybrids of these Groups may include zones from multiple floors; separating by floor is handled later For stable baseline, heating type is based on climate, not proposed heating type Isolate zones that have heating-only or district (purchased) heat or chilled water @param hvac_building_type [String] Chosen by user via measure interface or user data files @param zones_in_building_type [Array<OpenStudio::Model::ThermalZone>] array of thermal zones @return [Array<Hash>] an array of hashes of area information,

with keys area_ft2, type, fuel, and zones (an array of zones)
# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2582
def get_baseline_system_groups_for_one_building_type(model, hvac_building_type, zones_in_building_type)
  # Build zones hash of [zone, zone area, occupancy type, building type, fuel]
  zones = model_zones_with_occ_and_fuel_type(model, 'custom')

  # Ensure that there is at least one conditioned zone
  prm_raise(!zones.empty?, @sizing_run_dir, 'The building does not appear to have any conditioned zones. Make sure zones have thermostat with appropriate heating and cooling setpoint schedules.')

  # Consider special rules for computer rooms
  # need load of all
  # Get cooling load of all computer rooms to establish system types
  comp_room_loads = {}
  bldg_comp_room_load = 0
  zones.each do |zn|
    zone_load = 0.0
    has_computer_room = false
    # First check if any space in zone has a computer room
    zn['zone'].spaces.each do |space|
      if prm_get_optional_handler(space, @sizing_run_dir, 'spaceType', 'standardsSpaceType') == 'computer room'
        has_computer_room = true
        break
      end
    end
    if has_computer_room
      # Collect load for entire zone
      if zn['zone'].model.version < OpenStudio::VersionString.new('3.6.0')
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.ashrae_90_1_prm.Model', 'Required ThermalZone method .autosizedCoolingDesignLoad is not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.')
      end
      zone_load_w = zn['zone'].autosizedCoolingDesignLoad.get
      zone_load_w *= zn['zone'].multiplier
      zone_load = OpenStudio.convert(zone_load_w, 'W', 'Btu/hr').get
    end
    comp_room_loads[zn['zone'].name.get] = zone_load
    bldg_comp_room_load += zone_load
  end

  # Lab zones are grouped separately if total lab exhaust in building > 15000 cfm
  # Make list of zone objects that contain laboratory spaces
  lab_zones = []
  has_lab_spaces = {}
  model.getThermalZones.each do |zone|
    # Check if this zone includes laboratory space
    zone.spaces.each do |space|
      space_type = prm_get_optional_handler(space, @sizing_run_dir, 'spaceType', 'standardsSpaceType')
      zone_name = zone.name.get
      has_lab_spaces[zone_name] = false
      if space_type == 'laboratory'
        lab_zones << zone
        has_lab_spaces[zone_name] = true
        break
      end
    end
  end

  lab_exhaust_si = 0
  lab_relief_si = 0
  if !lab_zones.empty?
    # Build a hash of return_node:zone_name
    node_list = {}
    zone_return_flow_si = Hash.new(0)
    var_name = 'System Node Standard Density Volume Flow Rate'
    frequency = 'hourly'
    model.getThermalZones.each do |zone|
      port_list = zone.returnPortList
      port_list_objects = port_list.modelObjects
      port_list_objects.each do |node|
        node_name = node.nameString
        node_list[node_name] = zone.name.get
      end
      zone_return_flow_si[zone.name.get] = 0
    end

    # Get return air flow for each zone (even non-lab zones are needed)
    # Take from hourly reports created during sizing run
    node_list.each do |node_name, zone_name|
      sql = model.sqlFile
      prm_raise(sql.is_initialized, @sizing_run_dir, 'Model is missing SQL file. It is likely caused by: 1. unsuccessful simulation, 2. SQL is not set as one of the output file.')
      sql = sql.get
      query = "SELECT ReportDataDictionaryIndex FROM ReportDataDictionary WHERE KeyValue = '#{node_name}' COLLATE NOCASE"
      val = sql.execAndReturnFirstDouble(query)
      prm_raise(val.is_initialized, @sizing_run_dir, "No hourly return air flow data reported for node #{node_name}")
      report_data_dict_index = val.get
      query = "SELECT MAX(Value) FROM ReportData WHERE ReportDataDictionaryIndex = '#{report_data_dict_index}'"
      val = sql.execAndReturnFirstDouble(query)
      prm_raise(val.is_initialized, @sizing_run_dir, "No hourly return air flow data reported at report index #{report_data_dict_index}")
      zone_return_flow_si[zone_name] += OpenStudio::OptionalDouble.new(val.get).to_f
    end

    # Calc ratio of Air Loop relief to sum of zone return for each air loop
    # and store in zone hash

    # For each air loop, get relief air flow and calculate lab exhaust from the central air handler
    # Take from hourly reports created during sizing run
    zone_relief_flow_si = {}
    model.getAirLoopHVACs.each do |air_loop_hvac|
      # First get relief air flow from sizing run sql file
      relief_node = prm_get_optional_handler(air_loop_hvac, @sizing_run_dir, 'reliefAirNode')
      node_name = relief_node.nameString
      relief_flow_si = 0
      relief_fraction = 0
      sql = model.sqlFile
      prm_raise(sql.is_initialized, @sizing_run_dir, 'Model is missing SQL file. It is likely caused by: 1. unsuccessful simulation, 2. SQL is not set as one of the output file.')
      sql = sql.get
      query = "SELECT ReportDataDictionaryIndex FROM ReportDataDictionary WHERE KeyValue = '#{node_name}' COLLATE NOCASE"
      val = sql.execAndReturnFirstDouble(query)
      query = "SELECT MAX(Value) FROM ReportData WHERE ReportDataDictionaryIndex = '#{val.get}'"
      val = sql.execAndReturnFirstDouble(query)
      if val.is_initialized
        result = OpenStudio::OptionalDouble.new(val.get)
      end
      relief_flow_si = result.to_f

      # Get total flow of zones on this air loop
      total_zone_return_si = 0
      air_loop_hvac.thermalZones.each do |zone|
        total_zone_return_si += zone_return_flow_si[zone.name.get]
      end

      relief_fraction = relief_flow_si / total_zone_return_si unless total_zone_return_si == 0

      # For each zone calc total effective exhaust
      air_loop_hvac.thermalZones.each do |zone|
        zone_relief_flow_si[zone.name.get] = relief_fraction * zone_return_flow_si[zone.name.get]
      end
    end

    # Now check for exhaust driven by zone exhaust fans
    lab_zones.each do |zone|
      zone.equipment.each do |zone_equipment|
        # Get tally of exhaust fan flow
        if zone_equipment.to_FanZoneExhaust.is_initialized
          zone_exh_fan = zone_equipment.to_FanZoneExhaust.get
          # Check if any spaces in this zone are laboratory
          lab_exhaust_si += zone_exh_fan.maximumFlowRate.get
        end
      end

      # Also account for outdoor air exhausted from this zone via return/relief
      lab_relief_si += zone_relief_flow_si[zone.name.get]
    end
  end

  lab_exhaust_si += lab_relief_si
  lab_exhaust_cfm = OpenStudio.convert(lab_exhaust_si, 'm^3/s', 'cfm').get

  # Isolate computer rooms onto separate groups
  # Computer rooms may need to be split to two groups, depending on load
  # Isolate heated-only and destrict cooling zones onto separate groups
  # District heating does not require separate group
  final_groups = []
  # Initialize arrays of zone objects by category
  heated_only_zones = []
  heated_cooled_zones = []
  district_cooled_zones = []
  comp_room_svav_zones = []
  comp_room_psz_zones = []
  dist_comp_room_svav_zones = []
  dist_comp_room_psz_zones = []
  lab_zones = []

  total_area_ft2 = 0
  zones.each do |zn|
    if OpenstudioStandards::ThermalZone.thermal_zone_heated?(zn['zone']) && !OpenstudioStandards::ThermalZone.thermal_zone_cooled?(zn['zone'])
      # this will occur when there is no cooling tstat, or when min cooling setpoint is above 91 F
      heated_only_zones << zn['zone']
    elsif comp_room_loads[zn['zone'].name.get] > 0
      # This is a computer room zone
      if bldg_comp_room_load > 3_000_000 || comp_room_loads[zn['zone'].name.get] > 600_000
        # System 11
        if zn['fuel'].include?('DistrictCooling')
          dist_comp_room_svav_zones << zn['zone']
        else
          comp_room_svav_zones << zn['zone']
        end
      else
        # PSZ
        if zn['fuel'].include?('DistrictCooling')
          dist_comp_room_psz_zones << zn['zone']
        else
          comp_room_psz_zones << zn['zone']
        end
      end

    elsif has_lab_spaces[zn['zone'].name.get] && lab_exhaust_cfm > 15_000
      lab_zones << zn['zone']
    elsif zn['fuel'].include?('DistrictCooling')
      district_cooled_zones << zn['zone']
    else
      heated_cooled_zones << zn['zone']
    end
    # Collect total floor area of all zones for this building area type
    area_m2 = zn['zone'].floorArea * zn['zone'].multiplier
    total_area_ft2 += OpenStudio.convert(area_m2, 'm^2', 'ft^2').get
  end

  # Build final_groups array
  unless heated_only_zones.empty?
    htd_only_group = {}
    htd_only_group['occ'] = 'heated-only storage'
    htd_only_group['fuel'] = 'any'
    htd_only_group['zone_group_type'] = 'heated_only_zones'
    area_m2 = 0
    heated_only_zones.each do |zone|
      area_m2 += zone.floorArea * zone.multiplier
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get
    htd_only_group['group_area_ft2'] = area_ft2
    htd_only_group['building_area_type_ft2'] = total_area_ft2
    htd_only_group['zones'] = heated_only_zones
    final_groups << htd_only_group
  end
  unless district_cooled_zones.empty?
    district_cooled_group = {}
    district_cooled_group['occ'] = hvac_building_type
    district_cooled_group['fuel'] = 'districtcooling'
    district_cooled_group['zone_group_type'] = 'district_cooled_zones'
    area_m2 = 0
    district_cooled_zones.each do |zone|
      area_m2 += zone.floorArea * zone.multiplier
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get
    district_cooled_group['group_area_ft2'] = area_ft2
    district_cooled_group['building_area_type_ft2'] = total_area_ft2
    district_cooled_group['zones'] = district_cooled_zones
    # store info if any zone has district, fuel, or electric heating
    district_cooled_group['fuel'] = get_group_heat_types(model, district_cooled_zones)
    final_groups << district_cooled_group
  end
  unless heated_cooled_zones.empty?
    heated_cooled_group = {}
    heated_cooled_group['occ'] = hvac_building_type
    heated_cooled_group['fuel'] = 'any'
    heated_cooled_group['zone_group_type'] = 'heated_cooled_zones'
    area_m2 = 0
    heated_cooled_zones.each do |zone|
      area_m2 += zone.floorArea * zone.multiplier
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get
    heated_cooled_group['group_area_ft2'] = area_ft2
    heated_cooled_group['building_area_type_ft2'] = total_area_ft2
    heated_cooled_group['zones'] = heated_cooled_zones
    # store info if any zone has district, fuel, or electric heating
    heated_cooled_group['fuel'] = get_group_heat_types(model, heated_cooled_zones)
    final_groups << heated_cooled_group
  end
  unless lab_zones.empty?
    lab_group = {}
    lab_group['occ'] = hvac_building_type
    lab_group['fuel'] = 'any'
    lab_group['zone_group_type'] = 'lab_zones'
    area_m2 = 0
    lab_zones.each do |zone|
      area_m2 += zone.floorArea * zone.multiplier
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get
    lab_group['group_area_ft2'] = area_ft2
    lab_group['building_area_type_ft2'] = total_area_ft2
    lab_group['zones'] = lab_zones
    # store info if any zone has district, fuel, or electric heating
    lab_group['fuel'] = get_group_heat_types(model, lab_zones)
    final_groups << lab_group
  end
  unless comp_room_svav_zones.empty?
    comp_room_svav_group = {}
    comp_room_svav_group['occ'] = 'computer room szvav'
    comp_room_svav_group['fuel'] = 'any'
    comp_room_svav_group['zone_group_type'] = 'computer_zones'
    area_m2 = 0
    comp_room_svav_zones.each do |zone|
      area_m2 += zone.floorArea * zone.multiplier
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get
    comp_room_svav_group['group_area_ft2'] = area_ft2
    comp_room_svav_group['building_area_type_ft2'] = total_area_ft2
    comp_room_svav_group['zones'] = comp_room_svav_zones
    # store info if any zone has district, fuel, or electric heating
    comp_room_svav_group['fuel'] = get_group_heat_types(model, comp_room_svav_zones)
    final_groups << comp_room_svav_group
  end
  unless comp_room_psz_zones.empty?
    comp_room_psz_group = {}
    comp_room_psz_group['occ'] = 'computer room psz'
    comp_room_psz_group['fuel'] = 'any'
    comp_room_psz_group['zone_group_type'] = 'computer_zones'
    area_m2 = 0
    comp_room_psz_zones.each do |zone|
      area_m2 += zone.floorArea * zone.multiplier
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get
    comp_room_psz_group['group_area_ft2'] = area_ft2
    comp_room_psz_group['building_area_type_ft2'] = total_area_ft2
    comp_room_psz_group['zones'] = comp_room_psz_zones
    # store info if any zone has district, fuel, or electric heating
    comp_room_psz_group['fuel'] = get_group_heat_types(model, comp_room_psz_zones)
    final_groups << comp_room_psz_group
  end
  unless dist_comp_room_svav_zones.empty?
    dist_comp_room_svav_group = {}
    dist_comp_room_svav_group['occ'] = hvac_building_type
    dist_comp_room_svav_group['fuel'] = 'districtcooling'
    dist_comp_room_svav_group['zone_group_type'] = 'computer_zones'
    area_m2 = 0
    dist_comp_room_svav_zones.each do |zone|
      area_m2 += zone.floorArea * zone.multiplier
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get
    dist_comp_room_svav_group['group_area_ft2'] = area_ft2
    dist_comp_room_svav_group['building_area_type_ft2'] = total_area_ft2
    dist_comp_room_svav_group['zones'] = dist_comp_room_svav_zones
    # store info if any zone has district, fuel, or electric heating
    dist_comp_room_svav_group['fuel'] = get_group_heat_types(model, dist_comp_room_svav_zones)
    final_groups << dist_comp_room_svav_group
  end
  unless dist_comp_room_psz_zones.empty?
    dist_comp_room_psz_group = {}
    dist_comp_room_psz_group['occ'] = hvac_building_type
    dist_comp_room_psz_group['fuel'] = 'districtcooling'
    dist_comp_room_psz_group['zone_group_type'] = 'computer_zones'
    area_m2 = 0
    dist_comp_room_psz_zones.each do |zone|
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get
    dist_comp_room_psz_group['group_area_ft2'] = area_ft2
    dist_comp_room_psz_group['building_area_type_ft2'] = total_area_ft2
    dist_comp_room_psz_group['zones'] = dist_comp_room_psz_zones
    # store info if any zone has district, fuel, or electric heating
    dist_comp_room_psz_group['fuel'] = get_group_heat_types(model, dist_comp_room_psz_zones)
    final_groups << dist_comp_room_psz_group
  end

  ngrps = final_groups.count
  # Determine the number of stories spanned by each group and report out info.
  final_groups.each do |group|
    # Determine the number of stories this group spans
    group['stories'] = OpenstudioStandards::Geometry.thermal_zones_get_number_of_stories_spanned(group['zones'])
    # Report out the final grouping
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Final system type group: occ = #{group['occ']}, fuel = #{group['fuel']}, area = #{group['group_area_ft2'].round} ft2, num stories = #{group['stories']}, zones:")
    group['zones'].sort.each_slice(5) do |zone_list|
      zone_names = []
      zone_list.each do |zone|
        zone_names << zone.name.get.to_s
      end
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "--- #{zone_names.join(', ')}")
    end
  end

  return final_groups
end
get_model_fenestration_area_by_orientation(user_model) click to toggle source

Function that extract the total fenestration area from a model by orientations. Orientation is identified as N (North), S (South), E (East), W (West)

@param user_model [OpenStudio::Model::Model] OpenStudio model @return [Hash] Hash map that contains the total area of fenestration at each orientation (N, S, E, W)

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2326
def get_model_fenestration_area_by_orientation(user_model)
  # First index is wall, second index is window
  fenestration_area_hash = {
    'N' => 0.0,
    'S' => 0.0,
    'E' => 0.0,
    'W' => 0.0
  }
  user_model.getSpaces.each do |space|
    space_cond_type = space_conditioning_category(space)
    next if space_cond_type == 'Unconditioned'

    # Get zone multiplier
    multiplier = prm_get_optional_handler(space, @sizing_run_dir, 'thermalZone').multiplier
    space.surfaces.each do |surface|
      next if surface.surfaceType != 'Wall'
      next if surface.outsideBoundaryCondition != 'Outdoors'

      orientation = OpenstudioStandards::Geometry.surface_get_cardinal_direction(surface)
      surface.subSurfaces.each do |subsurface|
        subsurface_type = subsurface.subSurfaceType.to_s.downcase
        # Do not count doors
        next unless (subsurface_type.include? 'window') || (subsurface_type.include? 'glass')

        fenestration_area_hash[orientation] += subsurface.grossArea * subsurface.multiplier * multiplier
      end
    end
  end
  return fenestration_area_hash
end
handle_airloop_doas_user_input_data(model) click to toggle source

A function to load airloop DOAS data from userdata csv files @param model [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1903
def handle_airloop_doas_user_input_data(model)
  # Get user data
  user_airloop_doass = get_userdata(UserDataFiles::AIRLOOP_HVAC_DOAS)
  model.getAirLoopHVACDedicatedOutdoorAirSystems.each do |air_loop_doas|
    if user_airloop_doass
      user_data_updated = false
      user_airloop_doass.each do |user_airloop_doas|
        next unless UserData.compare(user_airloop_doas['name'], air_loop_doas.name.get)

        # Parse fan power credits data
        user_airloop_doas.each_key do |info_key|
          if info_key.include?('has_fan_power_credit') && UserDataBoolean.compare(user_airloop_doas[info_key], UserDataBoolean::TRUE)
            air_loop_doas.airLoops.each do |air_loop|
              air_loop.thermalZones.each do |thermal_zone|
                current_value = get_additional_property_as_double(thermal_zone, info_key, 0.0)
                thermal_zone.additionalProperties.setFeature(info_key, current_value + 1.0)
              end
            end
          elsif info_key.include?('fan_power_credit')
            # Case 2: user provided value
            air_loop_doas.airLoops.each do |air_loop|
              air_loop.thermalZones.each do |thermal_zone|
                current_value = get_additional_property_as_double(thermal_zone, info_key, 0.0)
                thermal_zone.additionalProperties.setFeature(info_key, current_value + prm_read_user_data(user_airloop_doas, info_key, '0.0').to_f)
              end
            end
          end
        end
        user_data_updated = true
      end
      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Air Loop DOAS name #{air_loop_doas.name.get} was not found in user data file: #{UserDataFiles::AIRLOOP_HVAC_DOAS}; No user data applied.")
      end
    end
  end
end
handle_airloop_user_input_data(model) click to toggle source

A function to load airloop data from userdata csv files The function works with validated user data only.

@param model [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1772
def handle_airloop_user_input_data(model)
  # ============================Process airloop info ============================================
  user_airloops = get_userdata(UserDataFiles::AIRLOOP_HVAC)
  model.getAirLoopHVACs.each do |air_loop|
    if user_airloops
      user_data_updated = false
      user_airloops.each do |user_airloop|
        next unless UserData.compare(air_loop.name.get, user_airloop['name'])

        air_loop.thermalZones.each do |thermal_zone|
          # gas phase air cleaning is system base - add proposed hvac system name to zones
          economizer_exception_for_gas_phase_air_cleaning = user_airloop['economizer_exception_for_gas_phase_air_cleaning']
          economizer_exception_for_open_refrigerated_cases = user_airloop['economizer_exception_for_open_refrigerated_cases']
          user_airloop.each_key do |info_key|
            if info_key.include?('has_fan_power_credit') && UserData.compare(user_airloop[info_key], UserDataBoolean::TRUE)
              current_value = get_additional_property_as_double(thermal_zone, info_key, 0.0)
              thermal_zone.additionalProperties.setFeature(info_key, current_value + 1.0)
            elsif info_key.include?('fan_power_credit')
              # Case 2: user provided value
              fan_power_credit = prm_read_user_data(user_airloop, info_key, '0.0').to_f
              current_value = get_additional_property_as_double(thermal_zone, info_key, 0.0)
              thermal_zone.additionalProperties.setFeature(info_key, current_value + fan_power_credit)
            end

            # Exhaust air energy recovery
            if info_key.include?('exhaust_energy_recovery_exception')
              if UserData.compare(user_airloop[info_key], UserDataBoolean::TRUE)
                thermal_zone.additionalProperties.setFeature(info_key, true)
              else
                thermal_zone.additionalProperties.setFeature(info_key, false)
              end
            end
          end
          if UserData.compare(economizer_exception_for_gas_phase_air_cleaning, UserDataBoolean::TRUE)
            thermal_zone.additionalProperties.setFeature('economizer_exception_for_gas_phase_air_cleaning', true)
          else
            thermal_zone.additionalProperties.setFeature('economizer_exception_for_gas_phase_air_cleaning', false)
          end

          if UserData.compare(economizer_exception_for_open_refrigerated_cases, UserDataBoolean::TRUE)
            thermal_zone.additionalProperties.setFeature('economizer_exception_for_open_refrigerated_cases', true)
          else
            thermal_zone.additionalProperties.setFeature('economizer_exception_for_open_refrigerated_cases', false)
          end
        end
        user_data_updated = true
      end
      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Air loop name #{air_loop.name.get} was not found in user data file: #{UserDataFiles::AIRLOOP_HVAC}; No user data applied.")
      end
    end
  end
end
handle_electric_equipment_user_input_data(model) click to toggle source

A function to load electric equipment csv files The file name is userdata_electric_equipment.csv @param [OpenStudio::Model::Model] model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1669
def handle_electric_equipment_user_input_data(model)
  user_data_plug_load = get_userdata(UserDataFiles::ELECTRIC_EQUIPMENT)
  model.getElectricEquipments.each do |elevator_equipment|
    if user_data_plug_load
      user_data_updated = false
      user_data_plug_load.each do |user_plug_load|
        next unless UserData.compare(elevator_equipment.name.get, user_plug_load['name'])

        fraction_of_controlled_receptacles = prm_read_user_data(user_plug_load, 'fraction_of_controlled_receptacles', '0.0').to_f
        elevator_equipment.additionalProperties.setFeature('fraction_of_controlled_receptacles', fraction_of_controlled_receptacles)

        receptacle_power_savings = prm_read_user_data(user_plug_load, 'receptacle_power_savings', '0.0').to_f
        elevator_equipment.additionalProperties.setFeature('receptacle_power_savings', receptacle_power_savings)

        num_lifts = prm_read_user_data(user_plug_load, 'elevator_number_of_lifts', '0').to_i
        if num_lifts > 0
          elevator_equipment.additionalProperties.setFeature('elevator_number_of_lifts', num_lifts)
          number_of_levels = prm_read_user_data(user_plug_load, 'elevator_number_of_stories', '0').to_i
          elevator_equipment.additionalProperties.setFeature('elevator_number_of_stories', number_of_levels)
          elevator_weight_of_car = prm_read_user_data(user_plug_load, 'elevator_weight_of_car', '0.0').to_f
          elevator_equipment.additionalProperties.setFeature('elevator_weight_of_car', elevator_weight_of_car)
          elevator_weight_of_car = prm_read_user_data(user_plug_load, 'elevator_counter_weight_of_car', '0.0').to_f
          elevator_equipment.additionalProperties.setFeature('elevator_counter_weight_of_car', elevator_weight_of_car)
          elevator_rated_load = prm_read_user_data(user_plug_load, 'elevator_rated_load', '0.0').to_f
          elevator_equipment.additionalProperties.setFeature('elevator_rated_load', elevator_rated_load)
          elevator_speed_of_car = prm_read_user_data(user_plug_load, 'elevator_speed_of_car', '0.0').to_f
          elevator_equipment.additionalProperties.setFeature('elevator_speed_of_car', elevator_speed_of_car)
          elevator_ventilation_cfm = prm_read_user_data(user_plug_load, 'elevator_ventilation_cfm', '0.0').to_f
          elevator_equipment.additionalProperties.setFeature('elevator_ventilation_cfm', elevator_ventilation_cfm)
          elevator_area_ft2 = prm_read_user_data(user_plug_load, 'elevator_area_ft2', '0.0').to_f
          elevator_equipment.additionalProperties.setFeature('elevator_area_ft2', elevator_area_ft2)
        end
        user_data_updated = true
      end

      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Electric equipment name #{elevator_equipment.name.get} was not found in user data file: #{UserDataFiles::ELECTRIC_EQUIPMENT}; No user data applied.")
      end
    end
  end
end
handle_exterior_lighting_user_input_data(model) click to toggle source

A function to load exterior lighting data from user data csv files The file name is userdata_exterior_lighting.csv @param [OpenStudio::Model::Model] model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1600
def handle_exterior_lighting_user_input_data(model)
  user_data_exterior_lighting_objects = get_userdata(UserDataFiles::EXTERIOR_LIGHTS)

  search_criteria = {
    'template' => template
  }
  ext_ltg_baseline_values = standards_lookup_table_first(table_name: 'prm_exterior_lighting', search_criteria: search_criteria)

  model.getExteriorLightss.each do |exterior_light|
    if user_data_exterior_lighting_objects
      user_data_updated = false
      # get exterior lighting object.
      user_data_exterior_lighting_objects.each do |user_exterior_lighting|
        next unless UserData.compare(exterior_light.name.get, user_exterior_lighting['name'])

        num_cats = prm_read_user_data(user_exterior_lighting, 'num_ext_lights_subcats', '0').to_i
        # Make sure none of the categories are nontradeable and not a mix of tradeable and nontradeable
        num_trade = 0
        num_notrade = 0
        ext_ltg_cats = {}
        (1..num_cats).each do |icat|
          cat_key = format('end_use_subcategory_%02d', icat)
          # validated
          subcat = user_exterior_lighting[cat_key]
          # handle the userdata missing value issue.
          if UserDataNonTradableLightsCategory.matched_any?(subcat)
            num_notrade += 1
          else
            num_trade += 1
            meas_val_key = format('end_use_measurement_value_%02d', icat)
            meas_val = prm_read_user_data(user_exterior_lighting, meas_val_key, '0.0').to_f
            unless meas_val == 0
              OpenStudio.logFree(OpenStudio::Info, 'prm.log', "End use subcategory #{subcat} has either missing measurement value or invalid measurement value, set to 0.0")
            end
            ext_ltg_cats[subcat] = meas_val
          end
        end

        # skip this if all lights are non-tradeable
        if num_trade == 0
          exterior_light.additionalProperties.setFeature('design_level', 0.0)
          next
        end

        if (num_trade > 0) && (num_notrade > 0)
          OpenStudio.logFree(OpenStudio::Warn, 'prm.log', "ExteriorLights object named #{user_exterior_lighting['name']} from user data file has a mix of tradeable and non-tradeable lighting types. All will be treated as non-tradeable.")
          next
        end

        ext_ltg_pwr = 0
        ext_ltg_cats.each do |subcat, meas_val|
          # Get baseline power for this type of exterior lighting
          baseline_value = ext_ltg_baseline_values[subcat].to_f
          ext_ltg_pwr += baseline_value * meas_val
        end

        exterior_light.additionalProperties.setFeature('design_level', ext_ltg_pwr)
        user_data_updated = true
      end
      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Exterior Lights name #{exterior_light.name.get} was not found in user data file: #{UserDataFiles::EXTERIOR_LIGHTS}; No user data applied.")
      end
    end
  end
end
handle_gas_equipment_user_input_data(model) click to toggle source

A function to load gas equipment csv files The file name is userdata_gas_equipment.csv @param [OpenStudio::Model::Model] model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1714
def handle_gas_equipment_user_input_data(model)
  user_data_gas_equipment = get_userdata(UserDataFiles::GAS_EQUIPMENT)
  model.getGasEquipments.each do |gas_equipment|
    if user_data_gas_equipment
      user_data_updated = false
      user_data_gas_equipment.each do |user_gas_equipment|
        next unless UserData.compare(gas_equipment.name.get, user_gas_equipment['name'])

        fraction_of_controlled_receptacles = prm_read_user_data(user_gas_equipment, 'fraction_of_controlled_receptacles', '0.0').to_f
        prm_raise(fraction_of_controlled_receptacles > 1.0, 'The fraction of all controlled receptacles cannot be higher than 1.0')
        gas_equipment.additionalProperties.setFeature('fraction_of_controlled_receptacles', fraction_of_controlled_receptacles)

        receptacle_power_savings = prm_read_user_data(user_gas_equipment, 'receptacle_power_savings', '0.0').to_f
        gas_equipment.additionalProperties.setFeature('receptacle_power_savings', receptacle_power_savings)
        user_data_updated = true
      end

      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Gas equipment name #{gas_equipment.name.get} was not found in user data file: #{UserDataFiles::GAS_EQUIPMENT}; No user data applied.")
      end
    end
  end
end
handle_lights_user_input_data(model) click to toggle source

A function to load lights from user data csv files The file name is userdata_lights.csv @param [OpenStudio::Model::Model] model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1441
def handle_lights_user_input_data(model)
  user_lights = get_userdata(UserDataFiles::LIGHTS)
  model.getLightss.each do |light|
    if user_lights
      user_data_updated = false
      user_lights.each do |user_light|
        next unless UserData.compare(light.name.get, user_light['name'])

        has_retail_display_exception = prm_read_user_data(user_light, 'has_retail_display_exception', false)
        if has_retail_display_exception
          light.additionalProperties.setFeature('has_retail_display_exception', true)
        else
          light.additionalProperties.setFeature('has_retail_display_exception', false)
        end

        has_unregulated_exception = prm_read_user_data(user_light, 'has_unregulated_exception', false)
        if has_unregulated_exception
          light.additionalProperties.setFeature('has_unregulated_exception', true)
        else
          light.additionalProperties.setFeature('has_unregulated_exception', false)
        end

        unregulated_category = prm_read_user_data(user_light, 'unregulated_category')
        if unregulated_category
          light.additionalProperties.setFeature('unregulated_category', unregulated_category)
        end

        user_data_updated = true
      end
      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "WaterUseConnections name #{light.name.get} was not found in user data file: #{UserDataFiles::LIGHTS}; No user data applied.")
      end
    end
  end
end
handle_multi_building_area_types(model, climate_zone, default_hvac_building_type, default_wwr_building_type, default_swh_building_type, bldg_type_hvac_zone_hash) click to toggle source

Analyze HVAC, window-to-wall ratio and SWH building (area) types from user data inputs in the @standard_data library This function returns True, but the values are stored in the multi-building_data argument. The hierarchy for process the building types

  1. Highest: PRM rules - if rules applied against user inputs, the function will use the calculated value to reset the building type

  2. Second: User defined building type in the csv file.

  3. Third: User defined userdata_building.csv file. If an object (e.g. space, thermalzone) are not defined in their correspondent userdata csv file, use the building csv file

  4. Fourth: Dropdown list in the measure GUI. If none presented, use the data from the dropdown list.

NOTE! This function will add building types to OpenStudio objects as an additional features for hierarchy 1-3 The object additional feature is empty when the function determined it uses fourth hierarchy.

@param model [OpenStudio::Model::Model] OpenStudio model object @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @param default_hvac_building_type [String] (Fourth Hierarchy hvac building type) @param default_wwr_building_type [String] (Fourth Hierarchy wwr building type) @param default_swh_building_type [String] (Fourth Hierarchy swh building type) @param bldg_type_hvac_zone_hash [Hash] An empty hash that maps building type for hvac to a list of thermal zones @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1989
def handle_multi_building_area_types(model, climate_zone, default_hvac_building_type, default_wwr_building_type, default_swh_building_type, bldg_type_hvac_zone_hash)
  # Construct the user_building hashmap
  user_buildings = get_userdata(UserDataFiles::BUILDING)

  # Build up a hvac_building_type : thermal zone hash map
  # =============================HVAC user data process===========================================
  user_thermal_zones = get_userdata(UserDataFiles::THERMAL_ZONE)
  # First construct hvac building type -> thermal Zone hash and hvac building type -> floor area
  bldg_type_zone_hash = {}
  bldg_type_zone_area_hash = {}
  model.getThermalZones.each do |thermal_zone|
    # get climate zone to check the conditioning category
    thermal_zone_condition_category = thermal_zone_conditioning_category(thermal_zone, climate_zone)
    if thermal_zone_condition_category == 'Semiheated' || thermal_zone_condition_category == 'Unconditioned'
      next
    end

    # Check for Second hierarchy
    hvac_building_type = nil
    if user_thermal_zones
      user_thermal_zone_index = user_thermal_zones.index { |user_thermal_zone| UserData.compare(user_thermal_zone['name'], thermal_zone.name.get) }
      # make sure the thermal zone has assigned a building_type_for_hvac
      unless user_thermal_zone_index.nil? || user_thermal_zones[user_thermal_zone_index]['building_type_for_hvac'].nil?
        # Only thermal zone in the user data and have building_type_for_hvac data will be assigned.
        hvac_building_type = user_thermal_zones[user_thermal_zone_index]['building_type_for_hvac']
      end
    end
    # Second hierarchy does not apply, check Third hierarchy
    if hvac_building_type.nil? && user_buildings
      building_name = prm_get_optional_handler(thermal_zone.model, @sizing_run_dir, 'building', 'name')
      user_building_index = user_buildings.index { |user_building| UserData.compare(user_building['name'], building_name) }
      unless user_building_index.nil? || user_buildings[user_building_index]['building_type_for_hvac'].nil?
        # Only thermal zone in the buildings user data and have building_type_for_hvac data will be assigned.
        hvac_building_type = user_buildings[user_building_index]['building_type_for_hvac']
      end
    end
    # Third hierarchy does not apply, apply Fourth hierarchy
    if hvac_building_type.nil?
      hvac_building_type = default_hvac_building_type
    end
    # Add data to the hash map
    unless bldg_type_zone_hash.key?(hvac_building_type)
      bldg_type_zone_hash[hvac_building_type] = []
    end
    unless bldg_type_zone_area_hash.key?(hvac_building_type)
      bldg_type_zone_area_hash[hvac_building_type] = 0.0
    end
    # calculate floor area for the thermal zone
    part_of_floor_area = false
    thermal_zone.spaces.sort.each do |space|
      next unless space.partofTotalFloorArea

      # a space in thermal zone is part of floor area.
      part_of_floor_area = true
      bldg_type_zone_area_hash[hvac_building_type] += space.floorArea * space.multiplier
    end
    if part_of_floor_area
      # Only add the thermal_zone if it is part of the floor area
      bldg_type_zone_hash[hvac_building_type].append(thermal_zone)
    end
  end

  if bldg_type_zone_hash.empty?
    # Build hash with all zones assigned to default hvac building type
    zone_array = []
    model.getThermalZones.each do |thermal_zone|
      zone_array.append(thermal_zone)
      thermal_zone.additionalProperties.setFeature('building_type_for_hvac', default_hvac_building_type)
    end
    bldg_type_hvac_zone_hash[default_hvac_building_type] = zone_array
  else
    # Calculate the total floor area.
    # If the max tie, this algorithm will pick the first encountered hvac building type as the maximum.
    total_floor_area = 0.0
    hvac_bldg_type_with_max_floor = nil
    hvac_bldg_type_max_floor_area = 0.0
    bldg_type_zone_area_hash.each do |key, value|
      if value > hvac_bldg_type_max_floor_area
        hvac_bldg_type_with_max_floor = key
        hvac_bldg_type_max_floor_area = value
      end
      total_floor_area += value
    end

    # Reset the thermal zones by going through the hierarchy 1 logics
    bldg_type_hvac_zone_hash.clear
    # Add the thermal zones for the maximum floor (primary system)
    bldg_type_hvac_zone_hash[hvac_bldg_type_with_max_floor] = bldg_type_zone_hash[hvac_bldg_type_with_max_floor]
    bldg_type_zone_hash.each do |bldg_type, bldg_type_zone|
      # loop the rest bldg_types
      unless bldg_type.eql? hvac_bldg_type_with_max_floor
        if OpenStudio.convert(total_floor_area, 'm^2', 'ft^2').get <= 40000
          # Building is smaller than 40k sqft, it could only have one hvac_building_type, reset all the thermal zones.
          bldg_type_hvac_zone_hash[hvac_bldg_type_with_max_floor].push(*bldg_type_zone)
          OpenStudio.logFree(OpenStudio::Info, 'prm.log', "The building floor area is less than 40,000 square foot. Thermal zones under hvac building type #{bldg_type} is reset to #{hvac_bldg_type_with_max_floor}")
        else
          if OpenStudio.convert(bldg_type_zone_area_hash[bldg_type], 'm^2', 'ft^2').get < 20000
            # in this case, all thermal zones shall be categorized as the primary hvac_building_type
            bldg_type_hvac_zone_hash[hvac_bldg_type_with_max_floor].push(*bldg_type_zone)
            OpenStudio.logFree(OpenStudio::Info, 'prm.log', "The floor area in hvac building type #{bldg_type} is less than 20,000 square foot. Thermal zones under this hvac building type is reset to #{hvac_bldg_type_with_max_floor}")
          else
            bldg_type_hvac_zone_hash[bldg_type] = bldg_type_zone
          end
        end
      end
    end

    # Write in hvac building type thermal zones by thermal zone
    bldg_type_hvac_zone_hash.each do |h1_bldg_type, bldg_type_zone_array|
      bldg_type_zone_array.each do |thermal_zone|
        thermal_zone.additionalProperties.setFeature('building_type_for_hvac', h1_bldg_type)
      end
    end
  end

  # =============================SPACE user data process===========================================
  user_spaces = get_userdata(UserDataFiles::SPACE)
  model.getSpaces.each do |space|
    type_for_wwr = nil
    # Check for 2nd level hierarchy
    if user_spaces
      user_spaces.each do |user_space|
        unless user_space['building_type_for_wwr'].nil?
          if UserData.compare(space.name.get, user_space['name'])
            type_for_wwr = user_space['building_type_for_wwr']
          end
        end
      end
    end

    if type_for_wwr.nil?
      # 2nd Hierarchy does not apply, check for 3rd level hierarchy
      building_name = prm_get_optional_handler(space.model, @sizing_run_dir, 'building', 'name')
      if user_buildings
        user_buildings.each do |user_building|
          unless user_building['building_type_for_wwr'].nil?
            if UserData.compare(user_building['name'], building_name)
              type_for_wwr = user_building['building_type_for_wwr']
            end
          end
        end
      end
    end

    if type_for_wwr.nil?
      # 3rd level hierarchy does not apply, Apply 4th level hierarchy
      type_for_wwr = default_wwr_building_type
    end
    # add wwr type to space:
    space.additionalProperties.setFeature('building_type_for_wwr', type_for_wwr)
  end
  # =============================SWH user data process===========================================
  user_wateruse_equipments = get_userdata(UserDataFiles::WATERUSE_EQUIPMENT)
  model.getWaterUseEquipments.each do |wateruse_equipment|
    type_for_swh = nil
    # Check for 2nd hierarchy
    if user_wateruse_equipments
      user_wateruse_equipments.each do |user_wateruse_equipment|
        unless user_wateruse_equipment['building_type_for_swh'].nil?
          if UserData.compare(wateruse_equipment.name.get, user_wateruse_equipment['name'])
            type_for_swh = user_wateruse_equipment['building_type_for_swh']
          end
        end
      end
    end

    if type_for_swh.nil?
      # 2nd hierarchy does not apply, check for 3rd hierarchy
      # get space building type
      building_name = prm_get_optional_handler(wateruse_equipment.model, @sizing_run_dir, 'building', 'name')
      if user_buildings
        user_buildings.each do |user_building|
          unless user_building['building_type_for_swh'].nil?
            if UserData.compare(user_building['name'], building_name)
              type_for_swh = user_building['building_type_for_swh']
            end
          end
        end
      end
    end

    if type_for_swh.nil?
      # 3rd hierarchy does not apply, apply 4th hierarchy
      type_for_swh = default_swh_building_type
    end
    # add swh type to wateruse equipment:
    wateruse_equipment.additionalProperties.setFeature('building_type_for_swh', type_for_swh)
  end
  return true
end
handle_outdoor_air_user_input_data(model) click to toggle source

A function to load outdoor air data from user data csv files The file name is userdata_design_specification_outdoor_air.csv @param [OpenStudio::Model::Model] model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1741
def handle_outdoor_air_user_input_data(model)
  user_data_oas = get_userdata(UserDataFiles::DESIGN_SPECIFICATION_OUTDOOR_AIR)
  model.getDesignSpecificationOutdoorAirs.each do |zone_oa|
    if user_data_oas
      user_data_updated = false
      user_data_oas.each do |user_oa|
        next unless UserData.compare(zone_oa.name.get, user_oa['name'])

        user_oa.each_key do |info_key|
          if info_key == 'name'
            zone_oa.additionalProperties.setFeature('has_user_data', true)
          else
            # this will capture the invalid string to 0.0, need to add note
            OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Add user provided outdoor air field: #{info_key}, value: #{user_oa[info_key].to_f} to DesignSpecification:OutdoorAir #{zone_oa.name.get} ")
            zone_oa.additionalProperties.setFeature(info_key, user_oa[info_key].to_f)
          end
        end
        user_data_updated = true
      end

      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Zone outdoor air name #{zone_oa.name.get} was not found in user data file: #{UserDataFiles::DESIGN_SPECIFICATION_OUTDOOR_AIR}; No user data applied.")
      end
    end
  end
end
handle_thermal_zone_user_input_data(model) click to toggle source

A function to load thermal zone data from userdata csv files @param model [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1942
def handle_thermal_zone_user_input_data(model)
  userdata_thermal_zones = get_userdata(UserDataFiles::THERMAL_ZONE)
  model.getThermalZones.each do |thermal_zone|
    nightcycle_exception = false
    if userdata_thermal_zones
      user_data_updated = false
      userdata_thermal_zones.each do |row|
        next unless UserData.compare(row['name'], thermal_zone.name.get)

        if UserData.compare(row['has_health_safety_night_cycle_exception'], UserDataBoolean::TRUE)
          nightcycle_exception = true
          break
        end
        user_data_updated = true
      end
      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Thermal Zone name #{thermal_zone.name.get} was not found in user data file: #{UserDataFiles::THERMAL_ZONE}.")
      end
    end
    if nightcycle_exception
      thermal_zone.additionalProperties.setFeature('has_health_safety_night_cycle_exception', true)
    end

    # mark unmarked zones
    unless thermal_zone.additionalProperties.hasFeature('has_health_safety_night_cycle_exception')
      thermal_zone.additionalProperties.setFeature('has_health_safety_night_cycle_exception', false)
    end
  end
end
handle_user_input_data(model, climate_zone, sizing_run_dir, default_hvac_building_type, default_wwr_building_type, default_swh_building_type, bldg_type_hvac_zone_hash) click to toggle source

A template method that handles the loading of user input data from multiple sources include data source from:

  1. user data csv files

  2. data from measure and OpenStudio interface

@param [OpenStudio:model:Model] model @param [String] climate_zone @param [String] default_hvac_building_type @param [String] default_wwr_building_type @param [String] default_swh_building_type @param [Hash] bldg_type_hvac_zone_hash A hash maps building type for hvac to a list of thermal zones @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1406
def handle_user_input_data(model, climate_zone, sizing_run_dir, default_hvac_building_type, default_wwr_building_type, default_swh_building_type, bldg_type_hvac_zone_hash)
  # Set sizing run directory
  @sizing_run_dir = sizing_run_dir
  # load the multiple building area types from user data
  handle_multi_building_area_types(model, climate_zone, default_hvac_building_type, default_wwr_building_type, default_swh_building_type, bldg_type_hvac_zone_hash)
  # load user data from proposed model
  handle_airloop_user_input_data(model)
  # exterior lighting handler
  handle_exterior_lighting_user_input_data(model)
  # load lights data from user data
  handle_lights_user_input_data(model)
  # load OA data from user data
  handle_outdoor_air_user_input_data(model)
  # load air loop DOAS user data from the proposed model
  handle_airloop_doas_user_input_data(model)
  # load zone HVAC user data from proposed model
  handle_zone_hvac_user_input_data(model)
  # load thermal zone user data from proposed model
  handle_thermal_zone_user_input_data(model)
  # load electric equipment user data
  handle_electric_equipment_user_input_data(model)
  # load gas equipment user data
  handle_gas_equipment_user_input_data(model)
  # load water use connection user data
  handle_wateruse_connections_user_input_data(model)
  # load water use equipment user data
  handle_wateruse_equipment_user_input_data(model, default_swh_building_type)
  # load water use equipment definition user data
  handle_wateruse_equipment_definition_user_input_data(model)
  return true
end
handle_wateruse_connections_user_input_data(model) click to toggle source

A function to load water use connections schedules from user data csv files The file name is userdata_wateruse_connections.csv @param [OpenStudio::Model::Model] model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1563
def handle_wateruse_connections_user_input_data(model)
  user_data_wateruse_connections = get_userdata(UserDataFiles::WATERUSE_CONNECTIONS)
  model.getWaterUseConnectionss.each do |wateruse_connections|
    if user_data_wateruse_connections
      user_data_updated = false
      user_data_wateruse_connections.each do |user_wateruse|
        next unless UserData.compare(wateruse_connections.name.get, user_wateruse['name'])

        hot_water_supply_temperature_schedule_name = prm_read_user_data(user_wateruse, 'hot_water_supply_temperature_schedule', '')
        # verify the schedule exist in the model
        prm_raise(model.getScheduleRulesetByName(hot_water_supply_temperature_schedule_name) ||
                    model.getScheduleCompactByName(hot_water_supply_temperature_schedule_name) ||
                    model.getScheduleConstantByName(hot_water_supply_temperature_schedule_name),
                  @sizing_run_dir,
                  "Cannot find #{hot_water_supply_temperature_schedule_name} in the model. Note, such schedule shall be one of the following type: RuleSet, Compact and Constant")
        wateruse_connections.additionalProperties.setFeature('hot_water_supply_temperature_schedule', hot_water_supply_temperature_schedule_name)

        cold_water_supply_temperature_schedule_name = prm_read_user_data(user_wateruse, 'cold_water_supply_temperature_schedule', '')
        # verify the schedule exist in the model
        prm_raise(model.getScheduleRulesetByName(cold_water_supply_temperature_schedule_name) ||
                    model.getScheduleCompactByName(cold_water_supply_temperature_schedule_name) ||
                    model.getScheduleConstantByName(cold_water_supply_temperature_schedule_name),
                  @sizing_run_dir,
                  "Cannot find #{cold_water_supply_temperature_schedule_name} in the model. Note, such schedule shall be one of the following type: RuleSet, Compact and Constant")
        wateruse_connections.additionalProperties.setFeature('cold_water_supply_temperature_schedule', cold_water_supply_temperature_schedule_name)
        user_data_updated = true
      end
      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "WaterUseConnections name #{wateruse_connections.name.get} was not found in user data file: #{UserDataFiles::WATERUSE_CONNECTIONS}; No user data applied.")
      end
    end
  end
end
handle_wateruse_equipment_definition_user_input_data(model) click to toggle source

A function to load water use equipment definition from user data csv files The file name is userdata_wateruse_equipment_definition.csv @param [OpenStudio::Model::Model] model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1480
def handle_wateruse_equipment_definition_user_input_data(model)
  user_data_wateruse_equipment_definition = get_userdata(UserDataFiles::WATERUSE_EQUIPMENT_DEFINITION)
  model.getWaterUseEquipmentDefinitions.each do |wateruse_equipment|
    if user_data_wateruse_equipment_definition
      user_data_updated = false
      user_data_wateruse_equipment_definition.each do |user_wateruse|
        next unless UserData.compare(wateruse_equipment.name.get, user_wateruse['name'])

        peak_flow_rate = prm_read_user_data(user_wateruse, 'peak_flow_rate', nil)
        if peak_flow_rate
          wateruse_equipment.additionalProperties.setFeature('peak_flow_rate', peak_flow_rate)
        end

        flow_rate_fraction_schedule_name = prm_read_user_data(user_wateruse, 'flow_rate_fraction_schedule', '')
        # verify the schedule exist in the model
        prm_raise(model.getScheduleRulesetByName(flow_rate_fraction_schedule_name) ||
                    model.getScheduleCompactByName(flow_rate_fraction_schedule_name) ||
                    model.getScheduleConstantByName(flow_rate_fraction_schedule_name),
                  @sizing_run_dir,
                  "Cannot find #{flow_rate_fraction_schedule_name} in the model. Note, such schedule shall be one of the following type: RuleSet, Compact and Constant")
        wateruse_equipment.additionalProperties.setFeature('flow_rate_fraction_schedule', flow_rate_fraction_schedule_name)

        target_temperature_schedule_name = prm_read_user_data(user_wateruse, 'target_temperature_schedule', '')
        # verify the schedule exist in the model
        prm_raise(model.getScheduleRulesetByName(target_temperature_schedule_name) ||
                    model.getScheduleCompactByName(target_temperature_schedule_name) ||
                    model.getScheduleConstantByName(target_temperature_schedule_name),
                  @sizing_run_dir,
                  "Cannot find #{target_temperature_schedule_name} in the model. Note, such schedule shall be one of the following type: RuleSet, Compact and Constant")
        wateruse_equipment.additionalProperties.setFeature('target_temperature_schedule', target_temperature_schedule_name)
        user_data_updated = true
      end
      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "WaterUseConnections name #{wateruse_equipment.name.get} was not found in user data file: #{UserDataFiles::WATERUSE_EQUIPMENT_DEFINITION}; No user data applied.")
      end
    end
  end
end
handle_wateruse_equipment_user_input_data(model, default_swh_building_type) click to toggle source

A function to load water use equipment from user data csv files The file name is userdata_wateruse_equipment.csv @param model [OpenStudio::Model::Model] OpenStudio model @param default_swh_building_type [String] SWH building type

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1523
def handle_wateruse_equipment_user_input_data(model, default_swh_building_type)
  user_data_wateruse_equipment = get_userdata(UserDataFiles::WATERUSE_EQUIPMENT)
  user_data_building = get_userdata(UserDataFiles::BUILDING)
  # get swh building type from user data building
  default_type = default_swh_building_type
  if user_data_building
    building_name = prm_get_optional_handler(model, @sizing_run_dir, 'building', 'name')
    user_building_index = user_data_building.index { |user_building| UserData.compare(user_building['name'], building_name) }
    unless user_building_index.nil? || prm_read_user_data(user_data_building[user_building_index], 'building_type_swh', nil)
      # Only thermal zone in the buildings user data and have building_type_for_hvac data will be assigned.
      default_type = prm_read_user_data(user_data_building[user_building_index], 'building_type_swh', default_type)
      OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Building type swh found in #{UserDataFiles::WATERUSE_EQUIPMENT} for building #{building_name}, set default building type swh to #{default_type}")
    end
  end
  model.getWaterUseEquipments.each do |wateruse_equipment|
    user_data_updated = false
    if user_data_wateruse_equipment
      user_data_wateruse_equipment.each do |user_wateruse|
        if UserData.compare(wateruse_equipment.name.get, user_wateruse['name'])
          building_type_swh = prm_read_user_data(user_wateruse, 'building_type_swh', nil)
          if building_type_swh
            wateruse_equipment.additionalProperties.setFeature('building_type_swh', building_type_swh)
          end
          user_data_updated = true
        end
      end
      unless user_data_updated
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "WaterUseEquipment name #{wateruse_equipment.name.get} was not found in user data file: #{UserDataFiles::WATERUSE_EQUIPMENT}; default building swh type #{default_type} applied.")
      end
    end
    # No user data updated, use default type
    unless user_data_updated
      wateruse_equipment.additionalProperties.setFeature('building_type_swh', default_type)
    end
  end
end
handle_zone_hvac_user_input_data(model) click to toggle source

Retrieve zone HVAC user specified compliance inputs from CSV file

@param model [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1829
def handle_zone_hvac_user_input_data(model)
  user_zone_hvac = get_userdata(UserDataFiles::ZONE_HVAC)
  return unless user_zone_hvac && !user_zone_hvac.empty?

  zone_hvac_equipment = model.getZoneHVACComponents
  if zone_hvac_equipment.empty?
    OpenStudio.logFree(OpenStudio::Error, 'prm.log', 'No zone HVAC equipment is present in the proposed model, user provided information cannot be used to generate the baseline building model.')
    return
  end

  user_zone_hvac.each do |zone_hvac_eqp_info|
    user_defined_zone_hvac_obj_name = zone_hvac_eqp_info['name']
    user_defined_zone_hvac_obj_type_name = zone_hvac_eqp_info['zone_hvac_object_type_name']

    # Check that the object type name do exist
    begin
      user_defined_zone_hvac_obj_type_name_idd = user_defined_zone_hvac_obj_type_name.to_IddObjectType
    rescue StandardError => e
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "#{user_defined_zone_hvac_obj_type_name}, provided in the user zone HVAC user data, is not a valid OpenStudio model object.")
    end

    # Retrieve zone HVAC object(s) by name
    zone_hvac_eqp = model.getZoneHVACComponentsByName(user_defined_zone_hvac_obj_name, false)

    # If multiple object have the same name
    if zone_hvac_eqp.empty?
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "The #{user_defined_zone_hvac_obj_type_name} object named #{user_defined_zone_hvac_obj_name} provided in the user zone HVAC user data could not be found in the model.")
    elsif zone_hvac_eqp.length == 1
      zone_hvac_eqp = zone_hvac_eqp[0]
      zone_hvac_eqp_idd = zone_hvac_eqp.iddObjectType.to_s
      if zone_hvac_eqp_idd != user_defined_zone_hvac_obj_type_name
        OpenStudio.logFree(OpenStudio::Error, 'prm.log', "The object type name provided in the zone HVAC user data (#{user_defined_zone_hvac_obj_type_name}) does not match with the one in the model: #{zone_hvac_eqp_idd}.")
      end
    else
      zone_hvac_eqp.each do |eqp|
        zone_hvac_eqp_idd = eqp.iddObjectType
        if zone_hvac_eqp_idd == user_defined_zone_hvac_obj_type_name
          zone_hvac_eqp = eqp
          break
        end
      end
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "A #{user_defined_zone_hvac_obj_type_name} object named #{user_defined_zone_hvac_obj_name} (as specified in the user zone HVAC data) could not be found in the model.")
    end

    if zone_hvac_eqp.thermalZone.is_initialized
      thermal_zone = zone_hvac_eqp.thermalZone.get

      zone_hvac_eqp_info.each_key do |info_key|
        if info_key.include?('fan_power_credit')
          if !zone_hvac_eqp_info[info_key].to_s.empty?
            if info_key.include?('has_')
              if thermal_zone.additionalProperties.hasFeature(info_key)
                current_value = thermal_zone.additionalProperties.getFeatureAsDouble(info_key).to_f
                thermal_zone.additionalProperties.setFeature(info_key, current_value + 1.0)
              else
                thermal_zone.additionalProperties.setFeature(info_key, 1.0)
              end
            else
              if thermal_zone.additionalProperties.hasFeature(info_key)
                current_value = thermal_zone.additionalProperties.getFeatureAsDouble(info_key).to_f
                thermal_zone.additionalProperties.setFeature(info_key, current_value + zone_hvac_eqp_info[info_key])
              else
                thermal_zone.additionalProperties.setFeature(info_key, zone_hvac_eqp_info[info_key])
              end
            end
          end
        end
      end
    end
  end
end
has_multi_lpd_values_space_type(space_type) click to toggle source

Function checks whether there are multi lpd values in the space type multi-lpd value means there are multiple spaces and the lighting_per_length > 0 @param space_type [OpenStudio::Model::SpaceType] @return [Boolean] True if there is lighting power defined by w/ft, False otherwise.

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 585
def has_multi_lpd_values_space_type(space_type)
  space_type_properties = interior_lighting_get_prm_data(space_type)
  lighting_per_length = space_type_properties['w/ft'].to_f

  return space_type.spaces.size > 1 && lighting_per_length > 0
end
has_multi_lpd_values_user_data(user_data, space_type) click to toggle source

Function checks whether there are multi lpd values in the space type from user’s data The sum of each space fraction in the user_data is assumed to be 1.0 multi-lpd value means lighting per area > 0 and lighting_per_length > 0 @param user_data [Hash] user data from the user csv @param space_type [OpenStudio::Model::SpaceType] @return [Boolean]

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 598
def has_multi_lpd_values_user_data(user_data, space_type)
  num_std_ltg_types = user_data['num_std_ltg_types'].to_i
  std_ltg_index = 0 # loop index
  # Loop through standard lighting type in a space
  sum_lighting_per_area = 0
  sum_lighting_per_length = 0
  while std_ltg_index < num_std_ltg_types
    # Retrieve data from user_data
    type_key = format('std_ltg_type%02d', (std_ltg_index + 1))
    sub_space_type = user_data[type_key]
    # Adjust while loop condition factors
    std_ltg_index += 1
    # get interior lighting data
    sub_space_type_properties = interior_lighting_get_prm_data(sub_space_type)
    # Assign data
    lighting_per_length = sub_space_type_properties['w/ft'].to_f
    sum_lighting_per_length += lighting_per_length
  end
  return space_type.spaces.size > 1 && sum_lighting_per_length > 0
end
has_user_lpd_values(user_space_data) click to toggle source

Function checks whether the user data contains lighting data @param user_space_data [Hash] space data extracted from user csv. @return [Boolean] True if there are user lpd values, False otherwise.

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 572
def has_user_lpd_values(user_space_data)
  user_space_data.each do |user_data|
    if user_data.key?('num_std_ltg_types') && user_data['num_std_ltg_types'].to_f > 0
      return true
    end
  end
  return false
end
heat_exchanger_air_to_air_sensible_and_latent_design_conditions(heat_exchanger_air_to_air_sensible_and_latent, climate_zone) click to toggle source

Determine the heat exchanger design conditions for a specific climate zones

@param heat_exchanger_air_to_air_sensible_and_latent [OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent] OpenStudio heat exchanger object @param climate_zone [String] Climate zone used to generate the model @return [String] Heat exchanger design conditions

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.HeatExchangerSensLat.rb, line 29
def heat_exchanger_air_to_air_sensible_and_latent_design_conditions(heat_exchanger_air_to_air_sensible_and_latent, climate_zone)
  case climate_zone
  when 'ASHRAE 169-2006-6B',
    'ASHRAE 169-2013-6B',
    'ASHRAE 169-2006-7A',
    'ASHRAE 169-2013-7A',
    'ASHRAE 169-2006-7B',
    'ASHRAE 169-2013-7B',
    'ASHRAE 169-2006-8A',
    'ASHRAE 169-2013-8A',
    'ASHRAE 169-2006-8B',
    'ASHRAE 169-2013-8B'
    design_conditions = 'heating'
  else
    design_conditions = 'cooling'
  end
  return design_conditions
end
heat_exchanger_air_to_air_sensible_and_latent_enthalpy_recovery_ratio(heat_exchanger_air_to_air_sensible_and_latent) click to toggle source

Determine the required enthalpy recovery ratio (ERR)

@param heat_exchanger_air_to_air_sensible_and_latent [OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent] OpenStudio heat exchanger object @return [Double] enthalpy recovery ratio

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.HeatExchangerSensLat.rb, line 52
def heat_exchanger_air_to_air_sensible_and_latent_enthalpy_recovery_ratio(heat_exchanger_air_to_air_sensible_and_latent)
  return 0.5
end
heat_exchanger_air_to_air_sensible_and_latent_minimum_effectiveness(heat_exchanger_air_to_air_sensible_and_latent) click to toggle source

Defines the minimum sensible and latent effectiveness of the heat exchanger. Assumed to apply to sensible and latent effectiveness at all flow rates.

@param heat_exchanger_air_to_air_sensible_and_latent [OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent] OpenStudio heat exchanger object @return [Array] List of full and part load heat exchanger effectiveness

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.HeatExchangerSensLat.rb, line 9
def heat_exchanger_air_to_air_sensible_and_latent_minimum_effectiveness(heat_exchanger_air_to_air_sensible_and_latent)
  # Get required ERR
  enthalpy_recovery_ratio = heat_exchanger_air_to_air_sensible_and_latent_enthalpy_recovery_ratio(heat_exchanger_air_to_air_sensible_and_latent)

  # Get design condition for climate zones
  climate_zone = OpenstudioStandards::Weather.model_get_climate_zone(heat_exchanger_air_to_air_sensible_and_latent.model)
  design_conditions = heat_exchanger_air_to_air_sensible_and_latent_design_conditions(heat_exchanger_air_to_air_sensible_and_latent, climate_zone)

  # Adjust, and convert ERR to Effectiveness for input to the model
  enthalpy_recovery_ratio = enthalpy_recovery_ratio_design_to_typical_adjustment(enthalpy_recovery_ratio, climate_zone)
  full_htg_sens_eff, full_htg_lat_eff, part_htg_sens_eff, part_htg_lat_eff, full_cool_sens_eff, full_cool_lat_eff, part_cool_sens_eff, part_cool_lat_eff = heat_exchanger_air_to_air_sensible_and_latent_enthalpy_recovery_ratio_to_effectiveness(enthalpy_recovery_ratio, design_conditions)

  return full_htg_sens_eff, full_htg_lat_eff, part_htg_sens_eff, part_htg_lat_eff, full_cool_sens_eff, full_cool_lat_eff, part_cool_sens_eff, part_cool_lat_eff
end
load_standards_database(data_directories = []) click to toggle source
Calls superclass method Standard#load_standards_database
# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 14
def load_standards_database(data_directories = [])
  super([__dir__] + data_directories)
end
load_userdata_to_standards_database(json_path) click to toggle source

Load user data from project folder into standards database data structure Each user data object type is a new item in the @standards_data hash @author Doug Maddox, PNNL @param json_path [String path to folder containing json files

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 119
def load_userdata_to_standards_database(json_path)
  files = Dir.glob("#{json_path}/*.json").select { |e| File.file? e }
  files.each do |file|
    data = JSON.parse(File.read(file))
    data.each_pair do |key, objs|
      # Override the template in inherited files to match the instantiated template
      if @standards_data[key].nil?
        OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.standard', "Adding #{key} from #{File.basename(file)}")
      else
        OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.standard', "Overriding #{key} with #{File.basename(file)}")
      end
      @standards_data[key] = objs
    end
  end
end
model_add_apxg_dcv_properties(model) click to toggle source

Check if zones in the baseline model (to be created) should have DCV based on 90.1 2019 G3.1.2.5. Zone additional property ‘apxg no need to have DCV’ added

@author Xuechen (Jerry) Lei, PNNL @param model [OpenStudio::Model::Model] OpenStudio model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1344
def model_add_apxg_dcv_properties(model)
  model.getAirLoopHVACs.each do |air_loop_hvac|
    if air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized
      oa_flow_m3_per_s = get_airloop_hvac_design_oa_from_sql(air_loop_hvac)
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, DCV not applicable because it has no OA intake.")
      return false
    end
    oa_flow_cfm = OpenStudio.convert(oa_flow_m3_per_s, 'm^3/s', 'cfm').get
    if oa_flow_cfm <= 3000
      air_loop_hvac.thermalZones.each do |thermal_zone|
        thermal_zone.additionalProperties.setFeature('apxg no need to have DCV', true)
      end
    else # oa_flow_cfg > 3000, check zone people density
      air_loop_hvac.thermalZones.each do |thermal_zone|
        area_served_m2 = 0
        num_people = 0
        thermal_zone.spaces.each do |space|
          area_served_m2 += space.floorArea
          num_people += space.numberOfPeople
        end
        area_served_ft2 = OpenStudio.convert(area_served_m2, 'm^2', 'ft^2').get
        occ_per_1000_ft2 = num_people / area_served_ft2 * 1000
        if occ_per_1000_ft2 <= 100
          thermal_zone.additionalProperties.setFeature('apxg no need to have DCV', true)
        else
          thermal_zone.additionalProperties.setFeature('apxg no need to have DCV', false)
        end
      end
    end
  end
  # if a zone does not have this additional property, it means it was not served by airloop.
end
model_add_dcv_requirement_properties(model) click to toggle source

add zone additional property “airloop dcv required by 901”

  • “true” if the airloop supporting this zone is required by 90.1 (non-exception requirement + user provided exception flag) to have DCV regarding user model

  • “false” otherwise

add zone additional property “zone dcv required by 901”

  • “true” if the zone is required by 90.1(non-exception requirement + user provided exception flag) to have DCV regarding user model

  • ‘flase’ otherwise

@author Xuechen (Jerry) Lei, PNNL @param model [OpenStudio::Model::Model] OpenStudio model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1286
def model_add_dcv_requirement_properties(model)
  model.getAirLoopHVACs.each do |air_loop_hvac|
    if user_model_air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac)
      air_loop_hvac.thermalZones.each do |thermal_zone|
        thermal_zone.additionalProperties.setFeature('airloop dcv required by 901', true)

        # the zone level dcv requirement can only be true if it is in an airloop that is required to have DCV
        if user_model_zone_demand_control_ventilation_required?(thermal_zone)
          thermal_zone.additionalProperties.setFeature('zone dcv required by 901', true)
        end
      end
    end
  end

  # mark unmarked zones
  model.getThermalZones.each do |zone|
    unless zone.additionalProperties.hasFeature('airloop dcv required by 901')
      zone.additionalProperties.setFeature('airloop dcv required by 901', false)
    end

    unless zone.additionalProperties.hasFeature('zone dcv required by 901')
      zone.additionalProperties.setFeature('zone dcv required by 901', false)
    end
  end
end
model_add_dcv_user_exception_properties(model) click to toggle source

read user data and add to zone additional properties “airloop user specified DCV exception” “one user specified DCV exception”

@author Xuechen (Jerry) Lei, PNNL @param model [OpenStudio::Model::Model] OpenStudio model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1226
def model_add_dcv_user_exception_properties(model)
  model.getAirLoopHVACs.each do |air_loop_hvac|
    dcv_airloop_user_exception = false
    if standards_data.key?('userdata_airloop_hvac')
      standards_data['userdata_airloop_hvac'].each do |row|
        next unless row['name'].to_s.downcase.strip == air_loop_hvac.name.to_s.downcase.strip

        if row['dcv_exception_airloop'].to_s.upcase.strip == 'TRUE'
          dcv_airloop_user_exception = true
          break
        end
      end
    end
    air_loop_hvac.thermalZones.each do |thermal_zone|
      if dcv_airloop_user_exception
        thermal_zone.additionalProperties.setFeature('airloop user specified DCV exception', true)
      end
    end
  end

  # zone level exception tagging is put outside of airloop because it directly reads from user data and
  # a zone not under an airloop in user model may be in an airloop in baseline
  model.getThermalZones.each do |thermal_zone|
    dcv_zone_user_exception = false
    if standards_data.key?('userdata_thermal_zone')
      standards_data['userdata_thermal_zone'].each do |row|
        next unless row['name'].to_s.downcase.strip == thermal_zone.name.to_s.downcase.strip

        if row['dcv_exception_thermal_zone'].to_s.upcase.strip == 'TRUE'
          dcv_zone_user_exception = true
          break
        end
      end
    end
    if dcv_zone_user_exception
      thermal_zone.additionalProperties.setFeature('zone user specified DCV exception', true)
    end
  end

  # mark unmarked zones
  model.getThermalZones.each do |zone|
    unless zone.additionalProperties.hasFeature('airloop user specified DCV exception')
      zone.additionalProperties.setFeature('airloop user specified DCV exception', false)
    end

    unless zone.additionalProperties.hasFeature('zone user specified DCV exception')
      zone.additionalProperties.setFeature('zone user specified DCV exception', false)
    end
  end
end
model_add_prm_elevators(model) click to toggle source

Function to add baseline elevators based on user data @param model [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 727
def model_add_prm_elevators(model)
  # Load elevator data from userdata csv files
  equipment_array = model.getElectricEquipments + model.getExteriorFuelEquipments
  equipment_array.each do |equipment|
    elevator_number_of_lifts = get_additional_property_as_integer(equipment, 'elevator_number_of_lifts', 0)
    next unless elevator_number_of_lifts > 0.0

    elevator_name = equipment.name.get
    elevator_number_of_stories = get_additional_property_as_integer(equipment, 'elevator_number_of_stories', 0)
    elevator_weight_of_car = get_additional_property_as_double(equipment, 'elevator_weight_of_car', 0.0)
    elevator_rated_load = get_additional_property_as_double(equipment, 'elevator_rated_load', 0.0)
    elevator_speed_of_car = get_additional_property_as_double(equipment, 'elevator_speed_of_car', 0.0)
    elevator_counter_weight_of_car = get_additional_property_as_double(equipment, 'elevator_counter_weight_of_car', 0.0)

    if elevator_number_of_stories < 5
      # From Table G3.9.2 performance rating method baseline elevator motor
      elevator_mech_eff = 0.58
      elevator_counter_weight_of_car = 0.0
      search_criteria = {
        'template' => template,
        'type' => 'Hydraulic'
      }
    else
      # From Table G3.9.2 performance rating method baseline elevator motor
      elevator_mech_eff = 0.64
      # Determine the elevator counterweight
      if elevator_counter_weight_of_car < 0.001
        # When the proposed design counterweight is not specified
        # it is determined as per Table G3.9.2
        elevator_counter_weight_of_car = elevator_weight_of_car + (0.4 * elevator_rated_load)
      end
      search_criteria = {
        'template' => template,
        'type' => 'Any'
      }
    end
    elevator_motor_bhp = (elevator_weight_of_car + elevator_rated_load - elevator_counter_weight_of_car) * elevator_speed_of_car / (33000 * elevator_mech_eff) # Lookup the minimum motor efficiency
    elevator_motor_eff = standards_data['motors']
    motor_properties = model_find_object(elevator_motor_eff, search_criteria, nil, nil, nil, nil, elevator_motor_bhp)
    if motor_properties.nil?
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{elevator_name}, could not find motor properties using search criteria: #{search_criteria}, motor_bhp = #{elevator_motor_bhp} hp.")
      return false
    end
    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(elevator_motor_eff, search_criteria, nil, nil, nil, nil, nominal_hp + 0.01)
    if motor_properties.nil?
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{elevator_name}, could not find nominal motor properties using search criteria: #{search_criteria}, motor_hp = #{nominal_hp} hp.")
      return false
    end
    motor_eff = motor_properties['nominal_full_load_efficiency'].to_f
    elevator_power = elevator_number_of_lifts * elevator_motor_bhp * 746 / motor_eff

    if equipment.is_a?(OpenStudio::Model::ElectricEquipment)
      equipment.electricEquipmentDefinition.setDesignLevel(elevator_power)
    else
      equipment.exteriorFuelEquipmentDefinition.setDesignLevel(elevator_power)
    end
    elevator_space = prm_get_optional_handler(equipment, @sizing_run_dir, 'space')
    # Add ventilation and lighting process loads if modeled in the proposed model
    misc_elevator_process_loads = 0.0
    misc_elevator_process_loads += get_additional_property_as_double(equipment, 'elevator_ventilation_cfm', 0.0) * 0.33
    misc_elevator_process_loads += get_additional_property_as_double(equipment, 'elevator_area_ft2', 0.0) * 3.14
    if misc_elevator_process_loads > 0
      misc_elevator_process_loads_def = OpenStudio::Model::ElectricEquipmentDefinition.new(model)
      misc_elevator_process_loads_def.setName("#{elevator_name} - Misc Process Loads - Def")
      misc_elevator_process_loads_def.setDesignLevel(misc_elevator_process_loads)
      misc_elevator_process_loads = OpenStudio::Model::ElectricEquipment.new(misc_elevator_process_loads_def)
      misc_elevator_process_loads.setName("#{elevator_name} - Misc Process Loads")
      misc_elevator_process_loads.setEndUseSubcategory('Elevators')
      misc_elevator_process_loads.setSchedule(model.alwaysOnDiscreteSchedule)
      misc_elevator_process_loads.setSpace(elevator_space)
    end
  end
end
model_adjusted_building_envelope_infiltration(building_envelope_area_m2, specific_space_infiltration_rate_75_pa = nil) click to toggle source

This method calculates the building envelope infiltration, this approach uses the 90.1 PRM rules

@param building_envelope_area_m2 [Double] Building envelope area as per 90.1 in m^2 @param specific_space_infiltration_rate_75_pa [Double] Specific space infiltration rate at 75 pa @return [Double] building envelope infiltration

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 323
def model_adjusted_building_envelope_infiltration(building_envelope_area_m2, specific_space_infiltration_rate_75_pa = nil)
  # Determine the total building baseline infiltration rate in cfm per ft2 of the building envelope at 75 Pa
  if specific_space_infiltration_rate_75_pa.nil?
    basic_infil_rate_cfm_per_ft2 = space_infiltration_rate_75_pa
  else
    basic_infil_rate_cfm_per_ft2 = specific_space_infiltration_rate_75_pa
  end

  # Conversion factor
  conv_fact = OpenStudio.convert(1.0, 'm^3/s', 'ft^3/min').get / OpenStudio.convert(1.0, 'm^2', 'ft^2').get

  # Adjust the infiltration rate to the average pressure for the prototype buildings.
  # adj_infil_rate_cfm_per_ft2 = 0.112 * basic_infil_rate_cfm_per_ft2
  adj_infil_rate_cfm_per_ft2 = OpenstudioStandards::Infiltration.adjust_infiltration_to_prototype_building_conditions(basic_infil_rate_cfm_per_ft2)
  adj_infil_rate_m3_per_s_per_m2 = adj_infil_rate_cfm_per_ft2 / conv_fact

  # Calculate the total infiltration
  tot_infil_m3_per_s = adj_infil_rate_m3_per_s_per_m2 * building_envelope_area_m2

  return tot_infil_m3_per_s
end
model_apply_baseline_exterior_lighting(model) click to toggle source

Apply baseline values to exterior lights objects Characterization of objects must be done via user data

@param model [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 711
def model_apply_baseline_exterior_lighting(model)
  model.getExteriorLightss.each do |ext_lights_obj|
    # Update existing exterior lights object: control, schedule, power
    ext_lights_obj.setControlOption('AstronomicalClock')
    ext_lights_obj.setSchedule(model.alwaysOnDiscreteSchedule)
    ext_lights_obj.setMultiplier(1)
    ext_lights_def = ext_lights_obj.exteriorLightsDefinition
    ext_ltg_pwr = get_additional_property_as_double(ext_lights_obj, 'design_level', 0.0)
    if ext_ltg_pwr > 0.0
      ext_lights_def.setDesignLevel(ext_ltg_pwr)
    end
  end
end
model_apply_baseline_swh_loops(model, building_type) click to toggle source

Modify the existing service water heating loops to match the baseline required heating type.

@param model [OpenStudio::Model::Model] OpenStudio model object @param building_type [String] the building type @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2185
def model_apply_baseline_swh_loops(model, building_type)
  model.getPlantLoops.each do |plant_loop|
    # Skip non service water heating loops
    next unless plant_loop_swh_loop?(plant_loop)

    # Rename the loop to avoid accidentally hooking up the HVAC systems to this loop later.
    plant_loop.setName('Service Water Heating Loop')

    htg_fuels, combination_system, storage_capacity, total_heating_capacity = plant_loop_swh_system_type(plant_loop)

    electric = true
    if htg_fuels.include?('NaturalGas') ||
       htg_fuels.include?('PropaneGas') ||
       htg_fuels.include?('FuelOilNo1') ||
       htg_fuels.include?('FuelOilNo2') ||
       htg_fuels.include?('Coal') ||
       htg_fuels.include?('Diesel') ||
       htg_fuels.include?('Gasoline')
      electric = false
    end

    # Per Table G3.1 11.e, if the baseline system was a combination of heating and service water heating,
    # delete all heating equipment and recreate a WaterHeater:Mixed.
    if combination_system
      a = plant_loop.supplyComponents
      b = plant_loop.demandComponents
      plantloop_components = a += b
      plantloop_components.each do |component|
        # Get the object type
        obj_type = component.iddObjectType.valueName.to_s
        next if ['OS_Node', 'OS_Pump_ConstantSpeed', 'OS_Pump_VariableSpeed', 'OS_Connector_Splitter', 'OS_Connector_Mixer', 'OS_Pipe_Adiabatic'].include?(obj_type)

        component.remove
      end

      water_heater = OpenStudio::Model::WaterHeaterMixed.new(model)
      water_heater.setName('Baseline Water Heater')
      water_heater.setHeaterMaximumCapacity(total_heating_capacity)
      water_heater.setTankVolume(storage_capacity)
      plant_loop.addSupplyBranchForComponent(water_heater)

      if electric
        # G3.1.11.b: If electric, WaterHeater:Mixed with electric resistance
        water_heater.setHeaterFuelType('Electricity')
        water_heater.setHeaterThermalEfficiency(1.0)
      else
        # @todo for now, just get the first fuel that isn't Electricity
        # A better way would be to count the capacities associated
        # with each fuel type and use the preponderant one
        fuels = htg_fuels - ['Electricity']
        fossil_fuel_type = fuels[0]
        water_heater.setHeaterFuelType(fossil_fuel_type)
        water_heater.setHeaterThermalEfficiency(0.8)
      end
      # If it's not a combination heating and service water heating system
      # just change the fuel type of all water heaters on the system
      # to electric resistance if it's electric
    else
      # Per Table G3.1 11.i, piping losses was deleted
      plant_loop_adiabatic_pipes_only(plant_loop)

      if electric
        plant_loop.supplyComponents.each do |component|
          next unless component.to_WaterHeaterMixed.is_initialized

          water_heater = component.to_WaterHeaterMixed.get
          # G3.1.11.b: If electric, WaterHeater:Mixed with electric resistance
          water_heater.setHeaterFuelType('Electricity')
          water_heater.setHeaterThermalEfficiency(1.0)
        end
      end
    end
  end

  # Set the water heater fuel types if it's 90.1-2013
  model.getWaterHeaterMixeds.sort.each do |water_heater|
    water_heater_mixed_apply_prm_baseline_fuel_type(water_heater, building_type)
  end

  return true
end
model_apply_constructions(model, climate_zone, wwr_building_type, wwr_info) click to toggle source

Apply the standard construction to each surface in the model, based on the construction type currently assigned.

@param model [OpenStudio::Model::Model] OpenStudio model object @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2362
def model_apply_constructions(model, climate_zone, wwr_building_type, wwr_info)
  model_apply_standard_constructions(model, climate_zone, wwr_building_type: wwr_building_type, wwr_info: wwr_info)

  return true
end
model_apply_hvac_efficiency_standard(model, climate_zone, apply_controls: true, sql_db_vars_map: nil) click to toggle source

Applies the HVAC parts of the template to all objects in the model using the the template specified in the model.

@param model [OpenStudio::Model::Model] OpenStudio model object @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @param apply_controls [Boolean] toggle whether to apply air loop and plant loop controls @param sql_db_vars_map [Hash] hash map @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 970
def model_apply_hvac_efficiency_standard(model, climate_zone, apply_controls: true, sql_db_vars_map: nil)
  sql_db_vars_map = {} if sql_db_vars_map.nil?

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Started applying HVAC efficiency standards for #{template} template.")

  # Air Loop Controls
  if apply_controls.nil? || apply_controls == true
    model.getAirLoopHVACs.sort.each { |obj| air_loop_hvac_apply_standard_controls(obj, climate_zone) }
  end

  # Plant Loop Controls
  if apply_controls.nil? || apply_controls == true
    model.getPlantLoops.sort.each { |obj| plant_loop_apply_standard_controls(obj, climate_zone) }
  end

  # Zone HVAC Controls
  model.getZoneHVACComponents.sort.each { |obj| zone_hvac_component_apply_standard_controls(obj) }

  # @todo The fan and pump efficiency will be done by another task.
  # Fans
  # model.getFanVariableVolumes.sort.each { |obj| fan_apply_standard_minimum_motor_efficiency(obj, fan_brake_horsepower(obj)) }
  # model.getFanConstantVolumes.sort.each { |obj| fan_apply_standard_minimum_motor_efficiency(obj, fan_brake_horsepower(obj)) }
  # model.getFanOnOffs.sort.each { |obj| fan_apply_standard_minimum_motor_efficiency(obj, fan_brake_horsepower(obj)) }
  # model.getFanZoneExhausts.sort.each { |obj| fan_apply_standard_minimum_motor_efficiency(obj, fan_brake_horsepower(obj)) }

  # Pumps
  # model.getPumpConstantSpeeds.sort.each { |obj| pump_apply_standard_minimum_motor_efficiency(obj) }
  # model.getPumpVariableSpeeds.sort.each { |obj| pump_apply_standard_minimum_motor_efficiency(obj) }
  # model.getHeaderedPumpsConstantSpeeds.sort.each { |obj| pump_apply_standard_minimum_motor_efficiency(obj) }
  # model.getHeaderedPumpsVariableSpeeds.sort.each { |obj| pump_apply_standard_minimum_motor_efficiency(obj) }

  # Zone level systems/components
  model.getThermalZones.each do |zone|
    if zone.additionalProperties.getFeatureAsString('baseline_system_type').is_initialized
      sys_type = zone.additionalProperties.getFeatureAsString('baseline_system_type').get
    end
    zone.equipment.each do |zone_equipment|
      if zone_equipment.to_ZoneHVACPackagedTerminalAirConditioner.is_initialized
        ptac = zone_equipment.to_ZoneHVACPackagedTerminalAirConditioner.get
        cooling_coil = ptac.coolingCoil
        sql_db_vars_map = set_coil_cooling_efficiency_and_curves(cooling_coil, sql_db_vars_map, sys_type)
      elsif zone_equipment.to_ZoneHVACPackagedTerminalHeatPump.is_initialized
        pthp = zone_equipment.to_ZoneHVACPackagedTerminalHeatPump.get
        cooling_coil = pthp.coolingCoil
        heating_coil = pthp.heatingCoil
        sql_db_vars_map = set_coil_cooling_efficiency_and_curves(cooling_coil, sql_db_vars_map, sys_type)
        sql_db_vars_map = set_coil_heating_efficiency_and_curves(heating_coil, sql_db_vars_map, sys_type)
      elsif zone_equipment.to_ZoneHVACUnitHeater.is_initialized
        unit_heater = zone_equipment.to_ZoneHVACUnitHeater.get
        heating_coil = unit_heater.heatingCoil
        sql_db_vars_map = set_coil_heating_efficiency_and_curves(heating_coil, sql_db_vars_map, sys_type)
      end
    end
  end

  # Airloop HVAC level components
  model.getAirLoopHVACs.sort.each do |air_loop|
    sys_type = air_loop.additionalProperties.getFeatureAsString('baseline_system_type').get
    air_loop.components.each do |icomponent|
      if icomponent.to_AirLoopHVACUnitarySystem.is_initialized
        unitary_system = icomponent.to_AirLoopHVACUnitarySystem.get
        if unitary_system.coolingCoil.is_initialized
          cooling_coil = unitary_system.coolingCoil.get
          sql_db_vars_map = set_coil_cooling_efficiency_and_curves(cooling_coil, sql_db_vars_map, sys_type)
        end
        if unitary_system.heatingCoil.is_initialized
          heating_coil = unitary_system.heatingCoil.get
          sql_db_vars_map = set_coil_heating_efficiency_and_curves(heating_coil, sql_db_vars_map, sys_type)
        end
      elsif icomponent.to_CoilCoolingDXSingleSpeed.is_initialized
        cooling_coil = icomponent.to_CoilCoolingDXSingleSpeed.get
        sql_db_vars_map = coil_cooling_dx_single_speed_apply_efficiency_and_curves(cooling_coil, sql_db_vars_map, sys_type)
      elsif icomponent.to_CoilCoolingDXTwoSpeed.is_initialized
        cooling_coil = icomponent.to_CoilCoolingDXTwoSpeed.get
        sql_db_vars_map = coil_cooling_dx_two_speed_apply_efficiency_and_curves(cooling_coil, sql_db_vars_map, sys_type)
      elsif icomponent.to_CoilHeatingDXSingleSpeed.is_initialized
        heating_coil = icomponent.to_CoilHeatingDXSingleSpeed.get
        sql_db_vars_map = coil_heating_dx_single_speed_apply_efficiency_and_curves(heating_coil, sql_db_vars_map, sys_type)
      elsif icomponent.to_CoilHeatingGas.is_initialized
        heating_coil = icomponent.to_CoilHeatingGas.get
        sql_db_vars_map = coil_heating_gas_apply_efficiency_and_curves(heating_coil, sql_db_vars_map, sys_type)
      end
    end
  end

  # Chillers
  model.getChillerElectricEIRs.sort.each { |obj| chiller_electric_eir_apply_efficiency_and_curves(obj) }

  # Boilers
  model.getBoilerHotWaters.sort.each { |obj| boiler_hot_water_apply_efficiency_and_curves(obj) }

  # Cooling Towers
  model.getCoolingTowerVariableSpeeds.sort.each { |obj| cooling_tower_variable_speed_apply_efficiency_and_curves(obj) }

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Finished applying HVAC efficiency standards for #{template} template.")
  return true
end
model_apply_multizone_vav_outdoor_air_sizing(model) click to toggle source

Applies the multi-zone VAV outdoor air sizing requirements to all applicable air loops in the model. @note This is not applicable to the stable baseline; hence no action in this method

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 898
def model_apply_multizone_vav_outdoor_air_sizing(model)
  return true
end
model_apply_prm_baseline_sizing_schedule(model) click to toggle source

Add design day schedule objects for space loads, for PRM 2019 baseline models @author Xuechen (Jerry) Lei, PNNL @param model [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 812
def model_apply_prm_baseline_sizing_schedule(model)
  space_loads = model.getSpaceLoads
  loads = []
  space_loads.sort.each do |space_load|
    casted_load = model_cast_model_object(space_load)
    loads << casted_load unless casted_load.nil?
  end

  load_schedule_name_hash = {
    'People' => 'numberofPeopleSchedule',
    'Lights' => 'schedule',
    'ElectricEquipment' => 'schedule',
    'GasEquipment' => 'schedule',
    'SpaceInfiltration_DesignFlowRate' => 'schedule'
  }

  loads.each do |load|
    load_type = load.iddObjectType.valueName.sub('OS_', '').strip
    load_schedule_name = load_schedule_name_hash[load_type]
    next if load_schedule_name.nil?

    # check if the load is in a dwelling space
    if load.spaceType.is_initialized
      space_type = load.spaceType.get
    elsif load.space.is_initialized && load.space.get.spaceType.is_initialized
      space_type = load.space.get.spaceType.get
    else
      space_type = nil
      puts "No hosting space/spacetype found for load: #{load.name}"
    end
    if !space_type.nil? && /apartment/i =~ space_type.standardsSpaceType.to_s
      load_in_dwelling = true
    else
      load_in_dwelling = false
    end

    load_schedule = load.public_send(load_schedule_name).get
    schedule_type = load_schedule.iddObjectType.valueName.sub('OS_', '').strip.sub('_', '')
    load_schedule = load_schedule.public_send("to_#{schedule_type}").get

    case schedule_type
    when 'ScheduleRuleset'
      load_schmax = OpenstudioStandards::Schedules.schedule_get_min_max(load_schedule)['max']
      load_schmin = OpenstudioStandards::Schedules.schedule_get_min_max(load_schedule)['min']
      load_schmode = get_weekday_values_from_8760(model,
                                                  Array(OpenstudioStandards::Schedules.schedule_get_hourly_values(load_schedule)),
                                                  value_includes_holiday = true).mode[0]

      # AppendixG-2019 G3.1.2.2.1
      if load_type == 'SpaceInfiltration_DesignFlowRate'
        summer_value = load_schmax
        winter_value = load_schmax
      else
        summer_value = load_schmax
        winter_value = load_schmin
      end

      # AppendixG-2019 Exception to G3.1.2.2.1
      if load_in_dwelling
        summer_value = load_schmode
      end

      # set cooling design day schedule
      summer_dd_schedule = OpenStudio::Model::ScheduleDay.new(model)
      summer_dd_schedule.setName("#{load.name} Summer Design Day")
      summer_dd_schedule.addValue(OpenStudio::Time.new(1.0), summer_value)
      load_schedule.setSummerDesignDaySchedule(summer_dd_schedule)

      # set heating design day schedule
      winter_dd_schedule = OpenStudio::Model::ScheduleDay.new(model)
      winter_dd_schedule.setName("#{load.name} Winter Design Day")
      winter_dd_schedule.addValue(OpenStudio::Time.new(1.0), winter_value)
      load_schedule.setWinterDesignDaySchedule(winter_dd_schedule)

    when 'ScheduleConstant'
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Space load #{load.name} has schedule type of ScheduleConstant. Nothing to be done for ScheduleConstant")
      next
    end
  end
end
model_apply_prm_baseline_skylight_to_roof_ratio(model) 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.

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 629
def model_apply_prm_baseline_skylight_to_roof_ratio(model)
  # Loop through all spaces in the model, and
  # per the 90.1-2019 PRM User Manual, only
  # account for exterior roofs for enclosed
  # spaces. Include space multipliers.
  roof_m2 = 0.001 # Avoids divide by zero errors later
  sky_m2 = 0
  total_roof_m2 = 0.001
  total_subsurface_m2 = 0
  model.getSpaces.sort.each do |space|
    next if space_conditioning_category(space) == 'Unconditioned'

    # Loop through all surfaces in this space
    roof_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 roof's gross area (including skylight area)
      roof_area_m2 += surface.grossArea * space.multiplier
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        next unless ss.subSurfaceType == 'Skylight'

        sky_area_m2 += ss.netArea * space.multiplier
      end
    end

    total_roof_m2 += roof_area_m2
    total_subsurface_m2 += sky_area_m2
  end

  # Calculate the SRR of each category
  srr = ((total_subsurface_m2 / total_roof_m2) * 100.0).round(1)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The skylight to roof ratios (SRRs) is: : #{srr.round}%.")

  # SRR limit
  srr_lim = model_prm_skylight_to_roof_ratio_limit(model)

  # Check against SRR limit
  red = srr > srr_lim

  # Stop here unless skylights need reducing
  return true unless red

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all skylights equally down to the limit of #{srr_lim.round}%.")

  # Determine the factors by which to reduce the skylight area
  mult = srr_lim / srr

  # Reduce the skylight area if any of the categories necessary
  model.getSpaces.sort.each do |space|
    next if space_conditioning_category(space) == 'Unconditioned'

    # 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|
        next unless ss.subSurfaceType == 'Skylight'

        # Reduce the size of the skylight
        red = 1.0 - mult
        OpenstudioStandards::Geometry.sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red)
      end
    end
  end

  return true
end
model_apply_prm_construction_types(model) 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-2019 @param model [OpenStudio::Model::Model] OpenStudio model object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 504
def model_apply_prm_construction_types(model)
  types_to_modify = []

  # Possible boundary conditions are
  # Adiabatic
  # Surface
  # Outdoors
  # Ground
  # Foundation
  # GroundFCfactorMethod
  # OtherSideCoefficients
  # OtherSideConditionsModel
  # GroundSlabPreprocessorAverage
  # GroundSlabPreprocessorCore
  # GroundSlabPreprocessorPerimeter
  # GroundBasementPreprocessorAverageWall
  # GroundBasementPreprocessorAverageFloor
  # GroundBasementPreprocessorUpperWall
  # GroundBasementPreprocessorLowerWall

  # Possible surface types are
  # AtticFloor
  # AtticWall
  # AtticRoof
  # DemisingFloor
  # DemisingWall
  # DemisingRoof
  # ExteriorFloor
  # ExteriorWall
  # ExteriorRoof
  # ExteriorWindow
  # ExteriorDoor
  # GlassDoor
  # GroundContactFloor
  # GroundContactWall
  # GroundContactRoof
  # InteriorFloor
  # InteriorWall
  # InteriorCeiling
  # InteriorPartition
  # InteriorWindow
  # InteriorDoor
  # OverheadDoor
  # Skylight
  # TubularDaylightDome
  # TubularDaylightDiffuser

  # Possible standards construction types
  # Mass
  # SteelFramed
  # WoodFramed
  # IEAD
  # View
  # Daylight
  # Swinging
  # NonSwinging
  # Heated
  # Unheated
  # RollUp
  # Sliding
  # Metal
  # Nonmetal framing (all)
  # Metal framing (curtainwall/storefront)
  # Metal framing (entrance door)
  # Metal framing (all other)
  # Metal Building
  # Attic and Other
  # Glass with Curb
  # Plastic with Curb
  # Without Curb

  # Create an array of types
  types_to_modify << ['Outdoors', 'ExteriorWall', 'SteelFramed']
  types_to_modify << ['Outdoors', 'ExteriorRoof', 'IEAD']
  types_to_modify << ['Outdoors', 'ExteriorFloor', 'SteelFramed']
  types_to_modify << ['Outdoors', 'ExteriorWindow', 'Any Vertical Glazing']
  types_to_modify << ['Outdoors', 'GlassDoor', 'Any Vertical Glazing']
  types_to_modify << ['Outdoors', 'ExteriorDoor', 'NonSwinging']
  types_to_modify << ['Outdoors', 'ExteriorDoor', 'Swinging']
  types_to_modify << ['Ground', 'GroundContactFloor', 'Unheated']
  types_to_modify << ['Ground', 'GroundContactWall', 'Mass']

  # Foundation
  types_to_modify << ['Foundation', 'GroundContactFloor', 'Unheated']
  types_to_modify << ['Foundation', 'GroundContactWall', 'Mass']

  # F/C-Factor methods
  types_to_modify << ['GroundFCfactorMethod', 'GroundContactFloor', 'Unheated']
  types_to_modify << ['GroundFCfactorMethod', 'GroundContactWall', 'Mass']

  # Other side coefficients
  types_to_modify << ['OtherSideCoefficients', 'GroundContactFloor', 'Unheated']
  types_to_modify << ['OtherSideConditionsModel', 'GroundContactFloor', 'Unheated']
  types_to_modify << ['OtherSideCoefficients', 'GroundContactWall', 'Mass']
  types_to_modify << ['OtherSideConditionsModel', 'GroundContactWall', 'Mass']

  # Slab preprocessor
  types_to_modify << ['GroundSlabPreprocessorAverage', 'GroundContactFloor', 'Unheated']
  types_to_modify << ['GroundSlabPreprocessorCore', 'GroundContactFloor', 'Unheated']
  types_to_modify << ['GroundSlabPreprocessorPerimeter', 'GroundContactFloor', 'Unheated']

  # Basement preprocessor
  types_to_modify << ['GroundBasementPreprocessorAverageWall', 'GroundContactWall', 'Mass']
  types_to_modify << ['GroundBasementPreprocessorAverageFloor', 'GroundContactFloor', 'Unheated']
  types_to_modify << ['GroundBasementPreprocessorUpperWall', 'GroundContactWall', 'Mass']
  types_to_modify << ['GroundBasementPreprocessorLowerWall', 'GroundContactWall', 'Mass']

  # Modify all constructions of each type
  types_to_modify.each do |boundary_cond, surf_type, const_type|
    constructions = OpenstudioStandards::Constructions.model_get_constructions(model, boundary_cond, surf_type)

    constructions.sort.each do |const|
      standards_info = const.standardsInformation
      standards_info.setIntendedSurfaceType(surf_type)
      standards_info.setStandardsConstructionType(const_type)
    end
  end

  return true
end
model_apply_standard_constructions(model, climate_zone, wwr_building_type: nil, wwr_info: {}) click to toggle source

Apply the standard construction to each surface in the model, based on the construction type currently assigned.

@param model [OpenStudio::Model::Model] OpenStudio model object @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @param wwr_building_type [String] building type used for defining window to wall ratio, e.g. ‘Office > 50,000 sq ft’ @param wwr_info [Hash] A map that maps each building area type to its correspondent wwr. @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 352
def model_apply_standard_constructions(model, climate_zone, wwr_building_type: nil, wwr_info: {})
  types_to_modify = []

  # Possible boundary conditions are
  # Adiabatic
  # Surface
  # Outdoors
  # Ground
  # Foundation
  # GroundFCfactorMethod
  # OtherSideCoefficients
  # OtherSideConditionsModel
  # GroundSlabPreprocessorAverage
  # GroundSlabPreprocessorCore
  # GroundSlabPreprocessorPerimeter
  # GroundBasementPreprocessorAverageWall
  # GroundBasementPreprocessorAverageFloor
  # GroundBasementPreprocessorUpperWall
  # GroundBasementPreprocessorLowerWall

  # Possible surface types are
  # Floor
  # Wall
  # RoofCeiling
  # FixedWindow
  # OperableWindow
  # Door
  # GlassDoor
  # OverheadDoor
  # Skylight
  # TubularDaylightDome
  # TubularDaylightDiffuser

  # Create an array of surface types
  types_to_modify << ['Outdoors', 'Floor']
  types_to_modify << ['Outdoors', 'Wall']
  types_to_modify << ['Outdoors', 'RoofCeiling']
  types_to_modify << ['Outdoors', 'FixedWindow']
  types_to_modify << ['Outdoors', 'OperableWindow']
  types_to_modify << ['Outdoors', 'Door']
  types_to_modify << ['Outdoors', 'GlassDoor']
  types_to_modify << ['Outdoors', 'OverheadDoor']
  types_to_modify << ['Outdoors', 'Skylight']
  types_to_modify << ['Surface', 'Floor']
  types_to_modify << ['Surface', 'Wall']
  types_to_modify << ['Surface', 'RoofCeiling']
  types_to_modify << ['Surface', 'FixedWindow']
  types_to_modify << ['Surface', 'OperableWindow']
  types_to_modify << ['Surface', 'Door']
  types_to_modify << ['Surface', 'GlassDoor']
  types_to_modify << ['Surface', 'OverheadDoor']
  types_to_modify << ['Ground', 'Floor']
  types_to_modify << ['Ground', 'Wall']
  types_to_modify << ['Foundation', 'Wall']
  types_to_modify << ['GroundFCfactorMethod', 'Wall']
  types_to_modify << ['OtherSideCoefficients', 'Wall']
  types_to_modify << ['OtherSideConditionsModel', 'Wall']
  types_to_modify << ['GroundBasementPreprocessorAverageWall', 'Wall']
  types_to_modify << ['GroundBasementPreprocessorUpperWall', 'Wall']
  types_to_modify << ['GroundBasementPreprocessorLowerWall', 'Wall']
  types_to_modify << ['Foundation', 'Floor']
  types_to_modify << ['GroundFCfactorMethod', 'Floor']
  types_to_modify << ['OtherSideCoefficients', 'Floor']
  types_to_modify << ['OtherSideConditionsModel', 'Floor']
  types_to_modify << ['GroundSlabPreprocessorAverage', 'Floor']
  types_to_modify << ['GroundSlabPreprocessorCore', 'Floor']
  types_to_modify << ['GroundSlabPreprocessorPerimeter', 'Floor']

  # Find just those surfaces
  surfaces_to_modify = []
  surface_category = {}
  org_surface_boundary_conditions = {}
  types_to_modify.each do |boundary_condition, surface_type|
    # Surfaces
    model.getSurfaces.sort.each do |surf|
      next unless surf.outsideBoundaryCondition == boundary_condition
      next unless surf.surfaceType == surface_type

      # Check if surface is adjacent to an unenclosed or unconditioned space (e.g. attic or parking garage)
      if surf.outsideBoundaryCondition == 'Surface'
        adj_space = surf.adjacentSurface.get.space.get
        adj_space_cond_type = space_conditioning_category(adj_space)
        if adj_space_cond_type == 'Unconditioned'
          # Get adjacent surface
          adjacent_surf = surf.adjacentSurface.get

          # Store original boundary condition type
          org_surface_boundary_conditions[surf.name.to_s] = adjacent_surf

          # Identify this surface as exterior
          surface_category[surf] = 'ExteriorSurface'

          # Temporary change the surface's boundary condition to 'Outdoors' so it can be assigned a baseline construction
          surf.setOutsideBoundaryCondition('Outdoors')
          adjacent_surf.setOutsideBoundaryCondition('Outdoors')
        end
      end

      if boundary_condition == 'Outdoors'
        surface_category[surf] = 'ExteriorSurface'
      elsif ['Ground', 'Foundation', 'GroundFCfactorMethod', 'OtherSideCoefficients', 'OtherSideConditionsModel', 'GroundSlabPreprocessorAverage', 'GroundSlabPreprocessorCore', 'GroundSlabPreprocessorPerimeter', 'GroundBasementPreprocessorAverageWall', 'GroundBasementPreprocessorAverageFloor', 'GroundBasementPreprocessorUpperWall', 'GroundBasementPreprocessorLowerWall'].include?(boundary_condition)
        surface_category[surf] = 'GroundSurface'
      else
        surface_category[surf] = 'NA'
      end
      surfaces_to_modify << surf
    end

    # SubSurfaces
    model.getSubSurfaces.sort.each do |surf|
      next unless surf.outsideBoundaryCondition == boundary_condition
      next unless surf.subSurfaceType == surface_type

      surface_category[surf] = 'ExteriorSubSurface'
      surfaces_to_modify << surf
    end
  end

  # Modify these surfaces
  prev_created_consts = {}
  surfaces_to_modify.sort.each do |surf|
    # Get space conditioning
    space = surf.space.get
    space_cond_type = space_conditioning_category(space)

    # Do not modify constructions for unconditioned spaces
    prev_created_consts = planar_surface_apply_standard_construction(surf, climate_zone, prev_created_consts, wwr_building_type, wwr_info, surface_category[surf]) unless space_cond_type == 'Unconditioned'

    # Reset boundary conditions to original if they were temporary modified
    if org_surface_boundary_conditions.include?(surf.name.to_s)
      surf.setAdjacentSurface(org_surface_boundary_conditions[surf.name.to_s])
    end
  end

  # List the unique array of constructions
  if prev_created_consts.empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', 'None of the constructions in your proposed model have both Intended Surface Type and Standards Construction Type')
  else
    prev_created_consts.each do |surf_type, construction|
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "For #{surf_type.join(' ')}, applied #{construction.name}.")
    end
  end

  return true
end
model_apply_standard_infiltration(model, specific_space_infiltration_rate_75_pa = nil) click to toggle source

This method creates customized infiltration objects for each space and removes the SpaceType-level infiltration objects. @param model [OpenStudio::Model::Model] openstudio model @param specific_space_infiltration_rate_75_pa [Double] space infiltration rate at a pressure differential of 75 Pa @return [Boolean] true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 136
def model_apply_standard_infiltration(model, specific_space_infiltration_rate_75_pa = nil)
  # Model shouldn't use SpaceInfiltrationEffectiveLeakageArea
  # Excerpt from the EnergyPlus Input/Output reference manual:
  #     "This model is based on work by Sherman and Grimsrud (1980)
  #     and is appropriate for smaller, residential-type buildings."
  # Raise exception if the model does use this object
  ela = 0
  model.getSpaceInfiltrationEffectiveLeakageAreas.sort.each do |eff_la|
    ela += 1
  end
  if ela > 0
    OpenStudio.logFree(OpenStudio::Warn, 'prm.log', 'The current model cannot include SpaceInfiltrationEffectiveLeakageArea. These objects will be skipped in modeling infiltration according to the 90.1-PRM rules.')
  end

  # Get the space building envelope area
  building_envelope_area_m2 = model_building_envelope_area(model)
  prm_raise(building_envelope_area_m2 > 0.0, @sizing_run_dir, 'Calculated building envelope area is 0 m2, Please check model inputs.')

  # Calculate current model air leakage rate @ 75 Pa and report it
  curr_tot_infil_m3_per_s_per_envelope_area = model_current_building_envelope_infiltration_at_75pa(model, building_envelope_area_m2)
  OpenStudio.logFree(OpenStudio::Info, 'prm.log', "The model's I_75Pa is estimated to be #{curr_tot_infil_m3_per_s_per_envelope_area} m3/s per m2 of total building envelope.")

  # Calculate building adjusted building envelope
  # air infiltration following the 90.1 PRM rules
  tot_infil_m3_per_s = model_adjusted_building_envelope_infiltration(building_envelope_area_m2, specific_space_infiltration_rate_75_pa)

  # Find infiltration method used in the model, if any.
  #
  # If multiple methods are used, use per above grade wall
  # area (i.e. exterior wall area), if air/changes per hour
  # or exterior surface area is used, use Flow/ExteriorWallArea
  infil_method = model_get_infiltration_method(model)
  infil_method = 'Flow/ExteriorWallArea' if infil_method != 'Flow/Area' || infil_method != 'Flow/ExteriorWallArea'
  infil_coefficients = model_get_infiltration_coefficients(model)

  # Set the infiltration rate at each space
  model.getSpaces.each do |space|
    space_apply_infiltration_rate(space, tot_infil_m3_per_s, infil_method, infil_coefficients)
  end

  # Remove infiltration rates set at the space type
  model.getSpaceTypes.each do |space_type|
    space_type.spaceInfiltrationDesignFlowRates.each(&:remove)
  end

  return true
end
model_baseline_system_vav_fan_type(model) click to toggle source

Determines the fan type used by VAV_Reheat and VAV_PFP_Boxes systems. Variable speed fan for 90.1-2019 @param model [OpenStudio::Model::Model] OpenStudio model object @return [String] the fan type: TwoSpeed Fan, Variable Speed Fan

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 102
def model_baseline_system_vav_fan_type(model)
  fan_type = 'Variable Speed Fan'
  return fan_type
end
model_building_envelope_area(model) click to toggle source

Calculate the building envelope area according to the 90.1 definition

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Double] Building envelope area in m2

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 111
def model_building_envelope_area(model)
  # Get the space building envelope area
  # According to the 90.1 definition, building envelope include:
  # - "the elements of a building that separate conditioned spaces from the exterior"
  # - "the elements of a building that separate conditioned space from unconditioned
  #    space or that enclose semiheated spaces through which thermal energy may be
  #    transferred to or from the exterior, to or from unconditioned spaces or to or
  #    from conditioned spaces."
  building_envelope_area_m2 = 0
  model.getSpaces.each do |space|
    building_envelope_area_m2 += OpenstudioStandards::Geometry.space_get_envelope_area(space)
  end
  if building_envelope_area_m2 < 0.01
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', 'Calculated building envelope area is 0 m2, no infiltration will be added.')
    return 0.0
  end

  return building_envelope_area_m2
end
model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name) click to toggle source

For a multizone system, create the fan schedule based on zone occupancy/fan schedules @author Doug Maddox, PNNL @param model [OpenStudio::Model::Model] openstudio model @param zone_op_hrs [Hash] of hash of zoneName zone_op_hrs @param pri_zones [Array<String>] names of zones served by the multizone system @param system_name [String] name of air loop

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 3067
def model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name)
  # Create fan schedule for multizone system
  fan_8760 = []
  # If any zone is on for an hour, then the system fan must be on for that hour
  pri_zones.each do |zone|
    zone_name = zone.name.get.to_s
    if fan_8760.empty?
      fan_8760 = zone_op_hrs[zone_name]
    else
      (0..fan_8760.size - 1).each do |ihr|
        if zone_op_hrs[zone_name][ihr] > 0
          fan_8760[ihr] = 1
        end
      end
    end
  end

  # Convert 8760 array to schedule ruleset
  fan_sch_limits = model.getScheduleTypeLimitsByName('fan schedule limits for prm')
  if fan_sch_limits.empty?
    fan_sch_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
    fan_sch_limits.setName('fan schedule limits for prm')
    fan_sch_limits.setNumericType('DISCRETE')
    fan_sch_limits.setUnitType('Dimensionless')
    fan_sch_limits.setLowerLimitValue(0)
    fan_sch_limits.setUpperLimitValue(1)
  else
    fan_sch_limits = fan_sch_limits.get
  end
  sch_name = "#{system_name} fan schedule"
  make_ruleset_sched_from_8760(model, fan_8760, sch_name, fan_sch_limits)

  air_loop = model.getAirLoopHVACByName(system_name).get
  air_loop.additionalProperties.setFeature('fan_sched_name', sch_name)
end
model_current_building_envelope_infiltration_at_75pa(model, building_envelope_area_m2) click to toggle source

This methods calculate the current model air leakage rate @ 75 Pa. It assumes that the model follows the PRM methods, see G3.1.1.4 in 90.1-2019 for reference.

@param model [OpenStudio::Model::Model] OpenStudio Model object @param building_envelope_area_m2 [Double] Building envelope area as per 90.1 in m^2 @return [Double] building model air leakage rate

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 305
def model_current_building_envelope_infiltration_at_75pa(model, building_envelope_area_m2)
  bldg_air_leakage_rate = 0
  model.getSpaces.each do |space|
    bldg_air_leakage_rate += model_get_space_air_leakage(space)
  end

  # adjust_infiltration_to_prototype_building_conditions(1) corresponds
  # to the 0.112 shown in G3.1.1.4
  curr_tot_infil_m3_per_s_per_envelope_area = bldg_air_leakage_rate / OpenstudioStandards::Infiltration.adjust_infiltration_to_prototype_building_conditions(1) / building_envelope_area_m2
  return curr_tot_infil_m3_per_s_per_envelope_area
end
model_differentiate_primary_secondary_thermal_zones(model, zones, zone_fan_scheds) click to toggle source

For a multizone system, identify any zones to isolate to separate PSZ systems isolated zones are on the ‘secondary’ list This version of the method applies to standard years 2016 and later (stable baseline) @author Doug Maddox, PNNL @param model @param zones [Array<Object>] @param zone_fan_scheds [Hash] hash of zoneName 8760FanSchedPerZone @return [Hash] A hash of two arrays of ThermalZones,

where the keys are 'primary' and 'secondary'
# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 3112
def model_differentiate_primary_secondary_thermal_zones(model, zones, zone_fan_scheds)
  pri_zones = []
  sec_zones = []
  pri_zone_names = []
  sec_zone_names = []
  zone_op_hrs = {} # hash of zoneName: 8760 array of operating hours

  # If there is only one zone, then set that as primary
  if zones.size == 1
    zones.each do |zone|
      pri_zones << zone
      pri_zone_names << zone.name.get.to_s
      zone_name = zone.name.get.to_s
      if zone_fan_scheds.key?(zone_name)
        zone_fan_sched = zone_fan_scheds[zone_name]
      else
        zone_fan_sched = nil
      end
      zone_op_hrs[zone.name.get.to_s] = thermal_zone_get_annual_operating_hours(model, zone, zone_fan_sched)
    end
    # Report out the primary vs. secondary zones
    unless sec_zone_names.empty?
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Secondary system zones = #{sec_zone_names.join(', ')}.")
    end

    return { 'primary' => pri_zones, 'secondary' => sec_zones, 'zone_op_hrs' => zone_op_hrs }
  end

  zone_eflh = {} # hash of zoneName: eflh for zone
  zone_max_load = {}  # hash of zoneName: coincident max internal load
  load_limit = 10     # differ by 10 Btu/hr-sf or more
  eflh_limit = 40     # differ by more than 40 EFLH/week from average of other zones
  zone_area = {} # hash of zoneName:area

  # Get coincident peak internal load for each zone
  zones.each do |zone|
    zone_name = zone.name.get.to_s
    if zone_fan_scheds.key?(zone_name)
      zone_fan_sched = zone_fan_scheds[zone_name]
    else
      zone_fan_sched = nil
    end
    zone_op_hrs[zone_name] = thermal_zone_get_annual_operating_hours(model, zone, zone_fan_sched)
    zone_eflh[zone_name] = thermal_zone_occupancy_eflh(zone, zone_op_hrs[zone_name])
    zone_max_load_w = thermal_zone_peak_internal_load(model, zone)
    zone_max_load_w_m2 = zone_max_load_w / zone.floorArea
    zone_max_load[zone_name] = OpenStudio.convert(zone_max_load_w_m2, 'W/m^2', 'Btu/hr*ft^2').get
    zone_area[zone_name] = zone.floorArea
  end

  # Eliminate all zones for which both max load and EFLH exceed limits
  zones.each do |zone|
    zone_name = zone.name.get.to_s
    max_load = zone_max_load[zone_name]
    avg_max_load = get_wtd_avg_of_other_zones(zone_max_load, zone_area, zone_name)
    max_load_diff = (max_load - avg_max_load).abs
    avg_eflh = get_avg_of_other_zones(zone_eflh, zone_name)
    eflh_diff = (avg_eflh - zone_eflh[zone_name]).abs

    if max_load_diff >= load_limit && eflh_diff > eflh_limit
      # Add zone to secondary list, and remove from hashes
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Zone moved to PSZ due to load AND eflh: #{zone_name}; load limit = #{load_limit}, eflh_limit = #{eflh_limit}")
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "load diff = #{max_load_diff}, this zone load = #{max_load}, avg zone load = #{avg_max_load}")
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "eflh diff = #{eflh_diff}, this zone load = #{zone_eflh[zone_name]}, avg zone eflh = #{avg_eflh}")

      sec_zones << zone
      sec_zone_names << zone_name
      zone_eflh.delete(zone_name)
      zone_max_load.delete(zone_name)
    end
  end

  # Eliminate worst zone where EFLH exceeds limit
  # Repeat until all zones are within limit
  num_zones = zone_eflh.size
  avg_eflh_save = 0
  max_zone_name = ''
  max_eflh_diff = 0
  max_zone = nil
  (1..num_zones).each do |izone|
    # This loop is to iterate to eliminate one zone at a time
    max_eflh_diff = 0
    zones.each do |zone|
      # This loop finds the worst remaining zone to eliminate if above threshold
      zone_name = zone.name.get.to_s
      next if !zone_eflh.key?(zone_name)

      avg_eflh = get_avg_of_other_zones(zone_eflh, zone_name)
      eflh_diff = (avg_eflh - zone_eflh[zone_name]).abs
      if eflh_diff > max_eflh_diff
        max_eflh_diff = eflh_diff
        max_zone_name = zone_name
        max_zone = zone
        avg_eflh_save = avg_eflh
      end
    end

    # All zones are now within the limit, exit the iteration
    break unless max_eflh_diff > eflh_limit

    # Move the max Zone to the secondary list
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Zone moved to PSZ due to eflh: #{max_zone_name}; limit = #{eflh_limit}")
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "eflh diff = #{max_eflh_diff}, this zone load = #{zone_eflh[max_zone_name]}, avg zone eflh = #{avg_eflh_save}")
    sec_zones << max_zone
    sec_zone_names << max_zone_name
    zone_eflh.delete(max_zone_name)
    zone_max_load.delete(max_zone_name)
  end

  # Eliminate worst zone where max load exceeds limit and repeat until all pass
  num_zones = zone_eflh.size
  highest_max_load_diff = -1
  highest_zone = nil
  highest_zone_name = ''
  highest_max_load = 0
  avg_max_load_save = 0

  (1..num_zones).each do |izone|
    # This loop is to iterate to eliminate one zone at a time
    highest_max_load_diff = 0
    zones.each do |zone|
      # This loop finds the worst remaining zone to eliminate if above threshold
      zone_name = zone.name.get.to_s
      next if !zone_max_load.key?(zone_name)

      max_load = zone_max_load[zone_name]
      avg_max_load = get_wtd_avg_of_other_zones(zone_max_load, zone_area, zone_name)
      max_load_diff = (max_load - avg_max_load).abs
      if max_load_diff >= highest_max_load_diff
        highest_max_load_diff = max_load_diff
        highest_zone_name = zone_name
        highest_zone = zone
        highest_max_load = max_load
        avg_max_load_save = avg_max_load
      end
    end

    # All zones are now within the limit, exit the iteration
    break unless highest_max_load_diff > load_limit

    # Move the max Zone to the secondary list
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Zone moved to PSZ due to load: #{highest_zone_name}; load limit = #{load_limit}")
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "load diff = #{highest_max_load_diff}, this zone load = #{highest_max_load}, avg zone load = #{avg_max_load_save}")
    sec_zones << highest_zone
    sec_zone_names << highest_zone_name
    zone_eflh.delete(highest_zone_name)
    zone_max_load.delete(highest_zone_name)
  end

  # Place remaining zones in multizone system list
  zone_eflh.each_key do |key|
    zones.each do |zone|
      if key == zone.name.get.to_s
        pri_zones << zone
        pri_zone_names << key
      end
    end
  end

  # Report out the primary vs. secondary zones
  unless pri_zone_names.empty?
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Primary system zones = #{pri_zone_names.join(', ')}.")
  end
  unless sec_zone_names.empty?
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Secondary system zones = #{sec_zone_names.join(', ')}.")
  end

  return { 'primary' => pri_zones, 'secondary' => sec_zones, 'zone_op_hrs' => zone_op_hrs }
end
model_does_require_wwr_adjustment?(wwr_limit, wwr_list) click to toggle source

This function checks whether it is required to adjust the window to wall ratio based on the model WWR and wwr limit. @param wwr_limit [Double] window to wall ratio limit @param wwr_list [Array] list of wwr of zone conditioning category in a building area type category - residential, nonresidential and semiheated @return [Boolean] True, require adjustment, false not require adjustment

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2425
def model_does_require_wwr_adjustment?(wwr_limit, wwr_list)
  # 90.1 PRM routine requires
  return true
end
model_evaluate_dcv_requirements(model) click to toggle source

Template method for evaluate DCV requirements in the user model

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1112
def model_evaluate_dcv_requirements(model)
  model_mark_zone_dcv_existence(model)
  model_add_dcv_user_exception_properties(model)
  model_add_dcv_requirement_properties(model)
  model_add_apxg_dcv_properties(model)
  model_raise_user_model_dcv_errors(model)
  return true
end
model_get_bat_wwr_target(bat, wwr_list) click to toggle source

For 2019, it is required to adjusted wwr based on building categories for all other types

@param bat [String] building category @param wwr_list [Array] list of zone conditioning category-based WWR - residential, nonresidential and semiheated @return [Double] return adjusted wwr_limit

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2435
def model_get_bat_wwr_target(bat, wwr_list)
  wwr_limit = 40.0
  # Lookup WWR target from stable baseline table
  wwr_lib = standards_data['prm_wwr_bldg_type']
  search_criteria = {
    'template' => template,
    'wwr_building_type' => bat
  }
  wwr_limit_bat = model_find_object(wwr_lib, search_criteria)
  # If building type isn't found, assume that it's
  # the same as 'All Others'
  if wwr_limit_bat.nil? || bat.casecmp?('all others')
    wwr = wwr_list.max
    # All others type
    # use the min of 40% and the max wwr in the ZCC-wwr list.
    wwr_limit = [wwr_limit, wwr].min
  else
    # Matched type: use WWR from database.
    wwr_limit = wwr_limit_bat['wwr'] * 100.0
  end
  return wwr_limit
end
model_get_fan_power_breakdown() click to toggle source

Indicate if fan power breakdown (supply, return, and relief) are needed

@return [Boolean] true if necessary, false otherwise

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 959
def model_get_fan_power_breakdown
  return true
end
model_get_infiltration_coefficients(model) click to toggle source

This method retrieves the infiltration coefficients used in the model. If input is inconsitent, returns

0, 0, 0.224, 0

as per PRM user manual

@param model [OpenStudio::Model::Model] OpenStudio model object @return [String] infiltration input type

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 224
def model_get_infiltration_coefficients(model)
  cst = nil
  temp = nil
  vel = nil
  vel_2 = nil
  infil_coeffs = [cst, temp, vel, vel_2]
  model.getSpaces.each do |space|
    # Infiltration at the space level
    unless space.spaceInfiltrationDesignFlowRates.empty?
      old_infil = space.spaceInfiltrationDesignFlowRates[0]
      cst = old_infil.constantTermCoefficient
      temp = old_infil.temperatureTermCoefficient
      vel = old_infil.velocityTermCoefficient
      vel_2 = old_infil.velocitySquaredTermCoefficient
      old_infil_coeffs = [cst, temp, vel, vel_2] if !(cst.nil? && temp.nil? && vel.nil? && vel_2.nil?)
      # Return flow per space floor area if method is inconsisten in proposed model
      return [0.0, 0.0, 0.224, 0.0] if infil_coeffs != old_infil_coeffs && !(infil_coeffs[0].nil? &&
                                                                                  infil_coeffs[1].nil? &&
                                                                                  infil_coeffs[2].nil? &&
                                                                                  infil_coeffs[3].nil?)

      infil_coeffs = old_infil_coeffs
    end

    # Infiltration at the space type level
    if infil_coeffs == [nil, nil, nil, nil] && space.spaceType.is_initialized
      space_type = space.spaceType.get
      unless space_type.spaceInfiltrationDesignFlowRates.empty?
        old_infil = space_type.spaceInfiltrationDesignFlowRates[0]
        cst = old_infil.constantTermCoefficient
        temp = old_infil.temperatureTermCoefficient
        vel = old_infil.velocityTermCoefficient
        vel_2 = old_infil.velocitySquaredTermCoefficient
        old_infil_coeffs = [cst, temp, vel, vel_2] if !(cst.nil? && temp.nil? && vel.nil? && vel_2.nil?)
        # Return flow per space floor area if method is inconsisten in proposed model
        return [0.0, 0.0, 0.224, 0.0] unless infil_coeffs != old_infil_coeffs && !(infil_coeffs[0].nil? &&
                                                                                    infil_coeffs[1].nil? &&
                                                                                    infil_coeffs[2].nil? &&
                                                                                    infil_coeffs[3].nil?)

        infil_coeffs = old_infil_coeffs
      end
    end
  end
  return infil_coeffs
end
model_get_infiltration_method(model) click to toggle source

This method retrieves the type of infiltration input used in the model. If input is inconsistent, returns Flow/Area @param model [OpenStudio::Model::Model] OpenStudio model object @return [String] infiltration input type

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 189
def model_get_infiltration_method(model)
  infil_method = nil
  model.getSpaces.each do |space|
    # Infiltration at the space level
    unless space.spaceInfiltrationDesignFlowRates.empty?
      old_infil = space.spaceInfiltrationDesignFlowRates[0]
      old_infil_method = old_infil.designFlowRateCalculationMethod.to_s
      # Return flow per space floor area if method is inconsisten in proposed model
      return 'Flow/Area' if infil_method != old_infil_method && !infil_method.nil?

      infil_method = old_infil_method
    end

    # Infiltration at the space type level
    if infil_method.nil? && space.spaceType.is_initialized
      space_type = space.spaceType.get
      unless space_type.spaceInfiltrationDesignFlowRates.empty?
        old_infil = space_type.spaceInfiltrationDesignFlowRates[0]
        old_infil_method = old_infil.designFlowRateCalculationMethod.to_s
        # Return flow per space floor area if method is inconsisten in proposed model
        return 'Flow/Area' if infil_method != old_infil_method && !infil_method.nil?

        infil_method = old_infil_method
      end
    end
  end

  return infil_method
end
model_get_space_air_leakage(space) click to toggle source

This methods calculate the air leakage rate of a space

@param space [OpenStudio::Model::Space] OpenStudio Space object @return [Double] Space air leakage rate

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 275
def model_get_space_air_leakage(space)
  space_air_leakage = 0
  space_multipler = space.multiplier
  # Infiltration at the space level
  unless space.spaceInfiltrationDesignFlowRates.empty?
    space.spaceInfiltrationDesignFlowRates.each do |infil_obj|
      unless infil_obj.designFlowRate.is_initialized
        if infil_obj.flowperSpaceFloorArea.is_initialized
          space_air_leakage += infil_obj.flowperSpaceFloorArea.get * space.floorArea * space_multipler
        elsif infil_obj.flowperExteriorSurfaceArea.is_initialized
          space_air_leakage += infil_obj.flowperExteriorSurfaceArea.get * space.exteriorArea * space_multipler
        elsif infil_obj.flowperExteriorWallArea.is_initialized
          space_air_leakage += infil_obj.flowperExteriorWallArea.get * space.exteriorWallArea * space_multipler
        elsif infil_obj.airChangesperHour.is_initialized
          space_air_leakage += infil_obj.airChangesperHour.get * space.volume * space_multipler / 3600
        end
      end
    end
  end

  return space_air_leakage
end
model_identify_non_mechanically_cooled_systems(model) click to toggle source

Identifies non mechanically cooled (“nmc”) systems, if applicable and add a flag to the zone’s and air loop’s additional properties. @todo Zone-level evaporative cooler is not currently supported by OpenStudio, will need to be added to the method when supported.

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Hash] Zone to nmc system type mapping

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 909
def model_identify_non_mechanically_cooled_systems(model)
  # Iterate through zones to find out if they are served by nmc systems
  model.getThermalZones.each do |zone|
    # Check if airloop has economizer and either:
    # - No cooling coil and/or,
    # - An evaporative cooling coil
    zone.airLoopHVACs.each do |air_loop|
      if (!air_loop_hvac_include_cooling_coil?(air_loop) &&
        air_loop_hvac_include_evaporative_cooler?(air_loop)) ||
         (!air_loop_hvac_include_cooling_coil?(air_loop) &&
           air_loop_hvac_include_economizer?(air_loop))
        air_loop.additionalProperties.setFeature('non_mechanically_cooled', true)
        zone.additionalProperties.setFeature('non_mechanically_cooled', true)
      end
    end
  end
end
model_mark_zone_dcv_existence(model) click to toggle source

Add zone additional property “zone DCV implemented in user model”:

- 'true' if zone OA flow requirement is specified as per person & airloop supporting this zone has DCV enabled
- 'false' otherwise

@author Xuechen (Jerry) Lei, PNNL @param model [OpenStudio::Model::Model] OpenStudio model @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1180
def model_mark_zone_dcv_existence(model)
  model.getAirLoopHVACs.each do |air_loop_hvac|
    next unless air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized

    oa_system = air_loop_hvac.airLoopHVACOutdoorAirSystem.get
    controller_oa = oa_system.getControllerOutdoorAir
    controller_mv = controller_oa.controllerMechanicalVentilation
    next unless controller_mv.demandControlledVentilation == true

    air_loop_hvac.thermalZones.each do |thermal_zone|
      zone_dcv = false
      thermal_zone.spaces.each do |space|
        dsn_oa = space.designSpecificationOutdoorAir
        next if dsn_oa.empty?

        dsn_oa = dsn_oa.get
        next if dsn_oa.outdoorAirMethod == 'Maximum'

        if dsn_oa.outdoorAirFlowperPerson > 0
          # only in this case the thermal zone is considered to be implemented with DCV
          zone_dcv = true
        end
      end

      if zone_dcv
        thermal_zone.additionalProperties.setFeature('zone DCV implemented in user model', true)
      end
    end
  end

  # mark unmarked zones
  model.getThermalZones.each do |zone|
    next if zone.additionalProperties.hasFeature('zone DCV implemented in user model')

    zone.additionalProperties.setFeature('zone DCV implemented in user model', false)
  end

  return true
end
model_prm_baseline_system_change_fuel_type(model, fuel_type, climate_zone) click to toggle source

Change the fuel type based on climate zone, depending on the standard. For 90.1-2013, fuel type is based on climate zone, not the proposed model. @param model [OpenStudio::Model::Model] OpenStudio model object @param fuel_type [String] Valid choices are electric, fossil, fossilandelectric,

purchasedheat, purchasedcooling, purchasedheatandcooling

@param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [String] the revised fuel type

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 77
def model_prm_baseline_system_change_fuel_type(model, fuel_type, climate_zone)
  # For 90.1-2013 the fuel type is determined based on climate zone.
  # Don't change the fuel if it purchased heating or cooling.
  if fuel_type == 'electric' || fuel_type == 'fossil'
    case climate_zone
    when 'ASHRAE 169-2006-1A',
         'ASHRAE 169-2006-2A',
         'ASHRAE 169-2006-3A',
         'ASHRAE 169-2013-1A',
         'ASHRAE 169-2013-2A',
         'ASHRAE 169-2013-3A'
      fuel_type = 'electric'
    else
      fuel_type = 'fossil'
    end
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Heating fuel is #{fuel_type} for 90.1-2013, climate zone #{climate_zone}.  This is independent of the heating fuel type in the proposed building, per G3.1.1-3.  This is different than previous versions of 90.1.")
  end

  return fuel_type
end
model_prm_baseline_system_groups(model, custom, bldg_type_hvac_zone_hash) click to toggle source

Assign spaces to system groups based on building area type Get zone groups separately for each hvac building type

@param model [OpenStudio::Model::Model] openstudio model @param custom [String] identifier for custom programs, not used here, but included for backwards compatibility @param bldg_type_hvac_zone_hash [Hash of bldg_type:list of zone objects] association of zones to each hvac building type @return [Array<Hash>] an array of hashes of area information,

with keys area_ft2, type, fuel, and zones (an array of zones)
# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2556
def model_prm_baseline_system_groups(model, custom, bldg_type_hvac_zone_hash)
  bldg_groups = []

  bldg_type_hvac_zone_hash.each_key do |hvac_building_type, zones_in_building_type|
    # Get all groups for this hvac building type
    new_groups = get_baseline_system_groups_for_one_building_type(model, hvac_building_type, zones_in_building_type)

    # Add the groups for this hvac building type to the full list
    new_groups.each do |group|
      bldg_groups << group
    end
  end

  return bldg_groups
end
model_prm_baseline_system_number(model, climate_zone, area_type, fuel_type, area_ft2, num_stories, custom) click to toggle source

Determines which system number is used for the baseline system. @param model [OpenStudio::Model::Model] OpenStudio model object @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [String] the system number: 1_or_2, 3_or_4, 5_or_6, 7_or_8, 9_or_10

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 8
def model_prm_baseline_system_number(model, climate_zone, area_type, fuel_type, area_ft2, num_stories, custom)
  sys_num = nil

  # Customization - Xcel EDA Program Manual 2014
  # Table 3.2.2 Baseline HVAC System Types
  if custom == 'Xcel Energy CO EDA'
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', 'Custom; per Xcel EDA Program Manual 2014 Table 3.2.2 Baseline HVAC System Types, the 90.1-2010 lookup for HVAC system types shall be used.')

    # Set the area limit
    limit_ft2 = 25_000

    case area_type
    when 'residential'
      sys_num = '1_or_2'
    when 'nonresidential'
      # nonresidential and 3 floors or less and <25,000 ft2
      if num_stories <= 3 && area_ft2 < limit_ft2
        sys_num = '3_or_4'
        # nonresidential and 4 or 5 floors or 5 floors or less and 25,000 ft2 to 150,000 ft2
      elsif ((num_stories == 4 || num_stories == 5) && area_ft2 < limit_ft2) || (num_stories <= 5 && (area_ft2 >= limit_ft2 && area_ft2 <= 150_000))
        sys_num = '5_or_6'
        # nonresidential and more than 5 floors or >150,000 ft2
      elsif num_stories >= 5 || area_ft2 > 150_000
        sys_num = '7_or_8'
      end
    when 'heatedonly'
      sys_num = '9_or_10'
    when 'retail'
      # Should only be hit by Xcel EDA
      sys_num = '3_or_4'
    end

  else

    # Set the area limit
    limit_ft2 = 25_000

    case area_type
    when 'residential'
      sys_num = '1_or_2'
    when 'nonresidential'
      # nonresidential and 3 floors or less and <25,000 ft2
      if num_stories <= 3 && area_ft2 < limit_ft2
        sys_num = '3_or_4'
      # nonresidential and 4 or 5 floors or 5 floors or less and 25,000 ft2 to 150,000 ft2
      elsif ((num_stories == 4 || num_stories == 5) && area_ft2 < limit_ft2) || (num_stories <= 5 && (area_ft2 >= limit_ft2 && area_ft2 <= 150_000))
        sys_num = '5_or_6'
      # nonresidential and more than 5 floors or >150,000 ft2
      elsif num_stories >= 5 || area_ft2 > 150_000
        sys_num = '7_or_8'
      end
    when 'heatedonly'
      sys_num = '9_or_10'
    when 'retail'
      sys_num = '3_or_4'
    end

  end

  return sys_num
end
model_prm_baseline_system_type(model, climate_zone, sys_group, custom, hvac_building_type, district_heat_zones) click to toggle source

Alternate method for 2016 and later stable baseline Limits for each building area type are taken from data table Heating fuel is based on climate zone, unless district heat is in proposed

@note Select system type from data table base on key parameters @param climate_zone [String] id code for the climate @param sys_group [Hash] Hash defining a group of zones that have the same Appendix G system type @param custom [String] included here for backwards compatibility (not used here) @param hvac_building_type [String] Chosen by user via measure interface or user data files @param district_heat_zones [Hash] of zone name => true for has district heat, false for has not @return [String] The system type. Possibilities are PTHP, PTAC, PSZ_AC, PSZ_HP, PVAV_Reheat, PVAV_PFP_Boxes,

VAV_Reheat, VAV_PFP_Boxes, Gas_Furnace, Electric_Furnace
# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2942
def model_prm_baseline_system_type(model, climate_zone, sys_group, custom, hvac_building_type, district_heat_zones)
  area_type = sys_group['occ']
  fuel_type = sys_group['fuel']
  area_ft2 = sys_group['building_area_type_ft2']
  num_stories = sys_group['stories']

  #             [type, central_heating_fuel, zone_heating_fuel, cooling_fuel]
  system_type = [nil, nil, nil, nil]

  # Find matching record from prm baseline hvac table
  # First filter by number of stories
  i_story_group = 0
  props = {}
  0.upto(9) do |i|
    i_story_group += 1
    props = model_find_object(standards_data['prm_baseline_hvac'],
                              'template' => template,
                              'hvac_building_type' => area_type,
                              'flrs_range_group' => i_story_group,
                              'area_range_group' => 1)

    prm_raise(props, @sizing_run_dir, "Could not find baseline HVAC type for: #{template}-#{area_type}.")
    if num_stories <= props['bldg_flrs_max']
      # Story Group Is found
      break
    end
  end

  # Next filter by floor area
  i_area_group = 0
  loop do
    i_area_group += 1
    props = model_find_object(standards_data['prm_baseline_hvac'],
                              'template' => template,
                              'hvac_building_type' => area_type,
                              'flrs_range_group' => i_story_group,
                              'area_range_group' => i_area_group)

    prm_raise(props && i_area_group <= 9, @sizing_run_dir, "Could not find baseline HVAC type for: #{template}-#{area_type}.")
    below_max = false
    above_min = false
    # check if actual building floor area is within range for this area group
    if props['max_area_qual'] == 'LT'
      if area_ft2 < props['bldg_area_max']
        below_max = true
      end
    elsif props['max_area_qual'] == 'LE'
      if area_ft2 <= props['bldg_area_max']
        below_max = true
      end
    end
    if props['min_area_qual'] == 'GT'
      if area_ft2 > props['bldg_area_min']
        above_min = true
      end
    elsif props['min_area_qual'] == 'GE'
      if area_ft2 >= props['bldg_area_min']
        above_min = true
      end
    end
    if above_min && below_max
      # break condition.
      break
    end
  end

  heat_type = find_prm_heat_type(hvac_building_type, climate_zone)

  # hash to relate apx G systype categories to sys types for model
  sys_hash = {}
  if heat_type == 'fuel'
    sys_hash['PTAC'] = 'PTAC'
    sys_hash['PSZ'] = 'PSZ_AC'
    sys_hash['SZ-CV'] = 'SZ_CV'
    sys_hash['Heating and ventilation'] = 'Gas_Furnace'
    sys_hash['PSZ-AC'] = 'PSZ_AC'
    sys_hash['Packaged VAV'] = 'PVAV_Reheat'
    sys_hash['VAV'] = 'VAV_Reheat'
    sys_hash['Unconditioned'] = 'None'
    sys_hash['SZ-VAV'] = 'SZ_VAV'
  else
    sys_hash['PTAC'] = 'PTHP'
    sys_hash['PSZ'] = 'PSZ_HP'
    sys_hash['SZ-CV'] = 'SZ_CV'
    sys_hash['Heating and ventilation'] = 'Electric_Furnace'
    sys_hash['PSZ-AC'] = 'PSZ_HP'
    sys_hash['Packaged VAV'] = 'PVAV_PFP_Boxes'
    sys_hash['VAV'] = 'VAV_PFP_Boxes'
    sys_hash['Unconditioned'] = 'None'
    sys_hash['SZ-VAV'] = 'SZ_VAV'
  end

  model_sys_type = sys_hash[props['system_type']]

  if /districtheating/i =~ fuel_type
    central_heat = 'DistrictHeating'
  elsif heat_type =~ /fuel/i
    central_heat = 'NaturalGas'
  else
    central_heat = 'Electricity'
  end
  if /districtheating/i =~ fuel_type && /elec/i !~ fuel_type && /fuel/i !~ fuel_type
    # if no zone has fuel or elect, set default to district for zones
    zone_heat = 'DistrictHeating'
  elsif heat_type =~ /fuel/i
    zone_heat = 'NaturalGas'
  else
    zone_heat = 'Electricity'
  end
  if /districtcooling/i =~ fuel_type
    cool_type = 'DistrictCooling'
  elsif props['system_type'] =~ /Heating and ventilation/i || props['system_type'] =~ /unconditioned/i
    cool_type = nil
  end

  system_type = [model_sys_type, central_heat, zone_heat, cool_type]
  return system_type
end
model_raise_user_model_dcv_errors(model) click to toggle source

based on previously added flag, raise error if DCV is required but not implemented in zones, in which case baseline generation will be terminated; raise warning if DCV is not required but implemented, and continue baseline generation

@author Xuechen (Jerry) Lei, PNNL @param model [OpenStudio::Model::Model] OpenStudio model @todo JXL add log msgs to PRM logger

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1319
def model_raise_user_model_dcv_errors(model)
  model.getThermalZones.each do |thermal_zone|
    if thermal_zone.additionalProperties.getFeatureAsBoolean('zone DCV implemented in user model').get &&
       (!thermal_zone.additionalProperties.getFeatureAsBoolean('zone dcv required by 901').get ||
         !thermal_zone.additionalProperties.getFeatureAsBoolean('airloop dcv required by 901').get)
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "For thermal zone #{thermal_zone.name}, ASHRAE 90.1 2019 6.4.3.8 does NOT require this zone to have demand control ventilation, but it was implemented in the user model, Appendix G baseline generation will continue!")
      if thermal_zone.additionalProperties.hasFeature('apxg no need to have DCV')
        if !thermal_zone.additionalProperties.getFeatureAsBoolean('apxg no need to have DCV').get
          OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Moreover, for thermal zone #{thermal_zone.name}, Appendix G baseline model will have DCV based on ASHRAE 90.1 2019 G3.1.2.5")
        end
      end
    end
    if thermal_zone.additionalProperties.getFeatureAsBoolean('zone dcv required by 901').get &&
       thermal_zone.additionalProperties.getFeatureAsBoolean('airloop dcv required by 901').get &&
       !thermal_zone.additionalProperties.getFeatureAsBoolean('zone DCV implemented in user model').get
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "For thermal zone #{thermal_zone.name}, ASHRAE 90.1 2019 6.4.3.8 requires this zone to have demand control ventilation, but it was not implemented in the user model, Appendix G baseline generation should be terminated!")
    end
  end
end
model_readjust_surface_wwr(residual_ratio, space, model) click to toggle source

Readjusted the WWR for surfaces previously has no windows to meet the overall WWR requirement. This function shall only be called if the maximum WWR value for surfaces with fenestration is lower than 90% due to accommodating the total door surface areas

@param residual_ratio [Double] the ratio of residual surfaces among the total wall surface area with no fenestrations @param space [OpenStudio::Model:Space] a space @param model [OpenStudio::Model::Model] openstudio model @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2535
def model_readjust_surface_wwr(residual_ratio, space, model)
  # In this loop, we will focus on the surfaces with newly added a fenestration.
  space.surfaces.sort.each do |surface|
    next unless surface.additionalProperties.hasFeature('added_wwr')

    added_wwr = surface.additionalProperties.getFeatureAsDouble('added_wwr').to_f
    # The full calculation of adjustment is:
    # ((residual_ratio * surface_area + added_wwr * surface_area) / surface_area ) / added_wwr
    adjustment_ratio = (residual_ratio / added_wwr) + 1.0
    surface_adjust_fenestration_in_a_surface(surface, adjustment_ratio, model)
  end
end
model_refine_size_dependent_values(model, sizing_run_dir) click to toggle source

This method is a catch-all run at the end of create-baseline to make final adjustements to HVAC capacities to account for recent model changes @author Doug Maddox, PNNL @param model @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 3287
def model_refine_size_dependent_values(model, sizing_run_dir)
  # Final sizing run before refining size-dependent values
  if model_run_sizing_run(model, "#{sizing_run_dir}/SR3") == false
    return false
  end

  model.getAirLoopHVACs.sort.each do |air_loop_hvac|
    # Reset secondary design secondary flow rate based on updated primary flow
    air_loop_hvac.demandComponents.each do |dc|
      next if dc.to_AirTerminalSingleDuctParallelPIUReheat.empty?

      pfp_term = dc.to_AirTerminalSingleDuctParallelPIUReheat.get
      sec_flow_frac = 0.5

      # Get the maximum flow rate through the terminal
      max_primary_air_flow_rate = nil
      if pfp_term.maximumPrimaryAirFlowRate.is_initialized
        max_primary_air_flow_rate = pfp_term.maximumPrimaryAirFlowRate.get
      elsif pfp_term.autosizedMaximumPrimaryAirFlowRate.is_initialized
        max_primary_air_flow_rate = pfp_term.autosizedMaximumPrimaryAirFlowRate.get
      end

      max_sec_flow_rate_m3_per_s = max_primary_air_flow_rate * sec_flow_frac
      pfp_term.setMaximumSecondaryAirFlowRate(max_sec_flow_rate_m3_per_s)
    end
  end
  return true
end
model_set_baseline_demand_control_ventilation(model, climate_zone) click to toggle source

Set DCV in baseline HVAC system if required

@author Xuechen (Jerry) Lei, PNNL @param model [OpenStudio::Model::Model] OpenStudio model

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1382
def model_set_baseline_demand_control_ventilation(model, climate_zone)
  model.getAirLoopHVACs.each do |air_loop_hvac|
    if baseline_air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac)
      air_loop_hvac_enable_demand_control_ventilation(air_loop_hvac, climate_zone)
      air_loop_hvac.thermalZones.sort.each do |zone|
        unless baseline_thermal_zone_demand_control_ventilation_required?(zone)
          OpenstudioStandards::ThermalZone.thermal_zone_convert_outdoor_air_to_per_area(zone)
        end
      end
    end
  end
end
model_set_central_preheat_coil_spm(model, thermal_zones, coil) click to toggle source

Template method for adding a setpoint manager for a coil control logic to a heating coil. ASHRAE 90.1-2019 Appendix G.

@param model [OpenStudio::Model::Model] OpenStudio model @param thermal_zones Array() thermal zone array @param coil [OpenStudio::Model::StraightComponent] heating coil @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1128
def model_set_central_preheat_coil_spm(model, thermal_zones, coil)
  # search for the highest zone setpoint temperature
  max_heat_setpoint = 0.0
  coil_name = coil.name.get.to_s
  thermal_zones.each do |zone|
    tstat = zone.thermostatSetpointDualSetpoint
    if tstat.is_initialized
      tstat = tstat.get
      setpoint_sch = tstat.heatingSetpointTemperatureSchedule
      setpoint_c = OpenstudioStandards::Schedules.schedule_get_design_day_min_max(setpoint_sch.get, 'winter')['max']
      next if setpoint_c.nil?

      if setpoint_c > max_heat_setpoint
        max_heat_setpoint = setpoint_c
      end
    end
  end
  # in this situation, we hard set the temperature to be 22 F
  # (ASHRAE 90.1 Room heating stepoint temperature is 72 F)
  max_heat_setpoint = 22.2 if max_heat_setpoint.zero?

  max_heat_setpoint_f = OpenStudio.convert(max_heat_setpoint, 'C', 'F').get
  preheat_setpoint_f = max_heat_setpoint_f - 20
  preheat_setpoint_c = OpenStudio.convert(preheat_setpoint_f, 'F', 'C').get

  # create a new constant schedule and this method will add schedule limit type
  preheat_coil_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                     preheat_setpoint_c,
                                                                                     name: "#{coil_name} Setpoint Temp - #{preheat_setpoint_f.round}F",
                                                                                     schedule_type_limit: 'Temperature')
  preheat_coil_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, preheat_coil_sch)
  preheat_coil_manager.setName("#{coil_name} Preheat Coil Setpoint Manager")

  if coil.to_CoilHeatingWater.is_initialized
    preheat_coil_manager.addToNode(coil.airOutletModelObject.get.to_Node.get)
  elsif coil.to_CoilHeatingElectric.is_initialized
    preheat_coil_manager.addToNode(coil.outletModelObject.get.to_Node.get)
  elsif coil.to_CoilHeatingGas.is_initialized
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.models.CoilHeatingGas', 'Preheat coils in baseline system shall only be electric or hydronic. Current coil type: Natural Gas')
    preheat_coil_manager.addToNode(coil.airOutletModelObject.get.to_Node.get)
  end

  return true
end
model_update_ground_temperature_profile(model, climate_zone) click to toggle source

Update ground temperature profile based on the weather file specified in the model

@param model [OpenStudio::Model::Model] OpenStudio model object @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] surfaces_with_fc_factor_boundary, returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2373
def model_update_ground_temperature_profile(model, climate_zone)
  # Check if the ground temperature profile is needed
  surfaces_with_fc_factor_boundary = false
  model.getSurfaces.each do |surface|
    if surface.outsideBoundaryCondition.to_s == 'GroundFCfactorMethod'
      surfaces_with_fc_factor_boundary = true
      break
    end
  end

  if surfaces_with_fc_factor_boundary
    # Remove existing FCFactor temperature profile
    model.getSiteGroundTemperatureFCfactorMethod.remove

    # Get path to weather file specified in the model
    weather_file_path = prm_get_optional_handler(model.getWeatherFile, @sizing_run_dir, 'path').to_s

    # Look for stat file corresponding to the weather file
    stat_file_path = weather_file_path.sub('.epw', '.stat').to_s
    if !File.exist? stat_file_path
      # When the stat file corresponding with the weather file in the model is missing,
      # use the weather file that represent the climate zone
      climate_zone_weather_file_map = OpenstudioStandards::Weather.climate_zone_weather_file_map
      prm_raise(climate_zone_weather_file_map.key?(climate_zone),
                @sizing_run_dir,
                "Failed to find a matching climate zone #{climate_zone} from the climate zone weather files.")
      weather_file = climate_zone_weather_file_map[climate_zone]
      stat_file_path = OpenstudioStandards::Weather.get_standards_weather_file_path(weather_file).sub('.epw', '.stat').to_s
    end

    ground_temp = OpenStudio::Model::SiteGroundTemperatureFCfactorMethod.new(model)
    stat_file = OpenstudioStandards::Weather::StatFile.load(stat_file_path)
    ground_temperatures = stat_file.monthly_lagged_dry_bulb
    unless ground_temperatures.empty?
      # set the site ground temperature building surface
      ground_temp.setAllMonthlyTemperatures(ground_temperatures)
    end
  end

  return surfaces_with_fc_factor_boundary
end
planar_surface_apply_standard_construction(planar_surface, climate_zone, previous_construction_map = {}, wwr_building_type = nil, wwr_info = {}, surface_category) click to toggle source

If construction properties can be found based on the template, the standards intended surface type, the standards construction type, the climate zone, and the occupancy type, create a construction that meets those properties and assign it to this surface. 90.1-PRM-2019

@param planar_surface [OpenStudio::Model:PlanarSurface] surface object @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @param previous_construction_map [Hash] a hash where the keys are an array of inputs

[template, climate_zone, intended_surface_type, standards_construction_type, occ_type]
and the values are the constructions.  If supplied, constructions will be pulled
from this hash if already created to avoid creating duplicate constructions.

@param wwr_building_type [String | Nil] building type that identifies the prescribed window to wall ratio @param wwr_info [Hash] A map that maps the building area type to window to wall ratio @param surface_category [String] surface category e.g., ‘ExteriorSubSurface’ @return [Hash] returns a hash where the key is an array of inputs

[template, climate_zone, intended_surface_type, standards_construction_type, occ_type]
and the value is the newly created construction.
This can be used to avoid creating duplicate constructions.

@todo Align the standard construction enumerations in the spreadsheet with the enumerations in OpenStudio (follow CBECC-Com).

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlanarSurface.rb, line 25
def planar_surface_apply_standard_construction(planar_surface, climate_zone, previous_construction_map = {}, wwr_building_type = nil, wwr_info = {}, surface_category)
  # Skip surfaces not in a space
  return previous_construction_map if planar_surface.space.empty?

  space = planar_surface.space.get
  if surface_category == 'ExteriorSubSurface'
    surface_type = planar_surface.subSurfaceType
  else
    surface_type = planar_surface.surfaceType
  end

  # Skip surfaces that don't have a construction
  # return previous_construction_map if planar_surface.construction.empty?
  if planar_surface.construction.empty?
    # Get appropriate default construction if not defined inside surface object
    construction = nil
    space_type = space.spaceType.get
    if space.defaultConstructionSet.is_initialized
      cons_set = space.defaultConstructionSet.get
      construction = get_default_surface_cons_from_surface_type(surface_category, surface_type, cons_set)
    end
    if construction.nil? && space_type.defaultConstructionSet.is_initialized
      cons_set = space_type.defaultConstructionSet.get
      construction = get_default_surface_cons_from_surface_type(surface_category, surface_type, cons_set)
    end
    if construction.nil? && space.buildingStory.get.defaultConstructionSet.is_initialized
      cons_set = space.buildingStory.get.defaultConstructionSet.get
      construction = get_default_surface_cons_from_surface_type(surface_category, surface_type, cons_set)
    end
    if construction.nil? && space.model.building.get.defaultConstructionSet.is_initialized
      cons_set = space.model.building.get.defaultConstructionSet.get
      construction = get_default_surface_cons_from_surface_type(surface_category, surface_type, cons_set)
    end
    OpenStudio.logFree(OpenStudio::Error, 'prm.log',
                       "Surface #{planar_surface.name.get} does not have a construction. Failed to find defaultConstructionSet for #{planar_surface.name.get}. Add a construction for the surface or add a defaultConstructionSet to Space #{space.name.get} or SpaceType #{space_type.name.get}.")
    prm_raise(construction,
              @sizing_run_dir,
              "Failed to find defaultConstructionSet for #{planar_surface.name.get}. Check inputs.")
  else
    construction = planar_surface.construction.get
  end

  # Determine if residential or nonresidential
  # based on the space type.
  occ_type = 'Nonresidential'
  if OpenstudioStandards::Space.space_residential?(space)
    occ_type = 'Residential'
  end

  # Get the climate zone set
  climate_zone_set = model_find_climate_zone_set(planar_surface.model, climate_zone)

  # Get the intended surface type
  standards_info = construction.standardsInformation
  surf_type = standards_info.intendedSurfaceType

  if surf_type.empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlanarSurface', "Could not determine the intended surface type for #{planar_surface.name} from #{construction.name}.  This surface will not have the standard applied.")
    return previous_construction_map
  end
  surf_type = surf_type.get

  # Get the standards type, which is based on different fields
  # if is intended for a window, a skylight, or something else.
  # Mapping is between standards-defined enumerations and the
  # enumerations available in OpenStudio.
  stds_type = nil
  case surf_type
  when 'ExteriorWindow', 'GlassDoor'
    # Windows and Glass Doors
    stds_type = standards_info.fenestrationFrameType
    if stds_type.is_initialized
      stds_type = stds_type.get
      if !wwr_building_type.nil?
        stds_type = 'Any Vertical Glazing'
      end
      case stds_type
      when 'Metal Framing', 'Metal Framing with Thermal Break'
        stds_type = 'Metal framing (all other)'
      when 'Non-Metal Framing'
        stds_type = 'Nonmetal framing (all)'
      when 'Any Vertical Glazing'
        stds_type = 'Any Vertical Glazing'
      else
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlanarSurface', "The standards fenestration frame type #{stds_type} cannot be used on #{surf_type} in #{planar_surface.name}.  This surface will not have the standard applied.")
        return previous_construction_map
      end
    else
      if wwr_building_type.nil?
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlanarSurface', "Could not determine the standards fenestration frame type for #{planar_surface.name} from #{construction.name}.  This surface will not have the standard applied.")
        return previous_construction_map
      else
        stds_type = 'Any Vertical Glazing'
      end
    end
  when 'ExteriorDoor'
    # Exterior Doors
    stds_type = standards_info.standardsConstructionType
    if stds_type.is_initialized
      stds_type = stds_type.get
      case stds_type
      when 'RollUp'
        stds_type = 'NonSwinging'
      end
    else
      stds_type = 'Swinging'
    end
  when 'Skylight'
    # Skylights
    # There is only one type for AppendixG stable baseline
    stds_type = 'Any Skylight'
  else
    # All other surface types
    stds_type = standards_info.standardsConstructionType
    if stds_type.is_initialized
      stds_type = stds_type.get
    else
      if planar_surface.outsideBoundaryCondition == 'Surface' && surface_category == 'NA'
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlanarSurface', "Standards construction is not needed and not applied for interior wall: #{planar_surface.name}.")
      else
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlanarSurface', "Could not determine the standards construction type for #{planar_surface.name}.  This surface will not have the standard applied.")
      end
      return previous_construction_map
    end
  end

  # Check if the construction type was already created.
  # If yes, use that construction.  If no, make a new one.

  # for multi-building type - search for the surface wwr type
  surface_std_wwr_type = wwr_building_type
  new_construction = nil
  type = [template, climate_zone, surf_type, stds_type, occ_type]
  # Only apply the surface_std_wwr_type update when wwr_building_type has Truthy values
  if !wwr_building_type.nil? && (surf_type == 'ExteriorWindow' || surf_type == 'GlassDoor')
    space = planar_surface.space.get
    if space.hasAdditionalProperties && space.additionalProperties.hasFeature('building_type_for_wwr')
      surface_std_wwr_type = space.additionalProperties.getFeatureAsString('building_type_for_wwr').get
    end
    type.push(surface_std_wwr_type)
  end

  if previous_construction_map[type] && !previous_construction_map[type].iddObjectType.valueName.to_s.include?('factorGround')
    new_construction = previous_construction_map[type]
  else
    new_construction = model_find_and_add_construction(planar_surface.model,
                                                       climate_zone_set,
                                                       surf_type,
                                                       stds_type,
                                                       occ_type,
                                                       wwr_building_type: surface_std_wwr_type,
                                                       wwr_info: wwr_info,
                                                       surface: planar_surface)
    if !new_construction == false
      previous_construction_map[type] = new_construction
    end
  end

  # Assign the new construction to the surface
  if new_construction
    planar_surface.setConstruction(new_construction)
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.PlanarSurface', "Set the construction for #{planar_surface.name} to #{new_construction.name}.")
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlanarSurface', "Could not generate a standard construction for #{planar_surface.name}.")
    return previous_construction_map
  end

  return previous_construction_map
end
plant_loop_apply_prm_baseline_pump_power(plant_loop) click to toggle source

Apply prm baseline pump power @note I think it makes more sense to sense the motor efficiency right there…

But actually it's completely irrelevant...
you could set at 0.9 and just calculate the pressure rise to have your 19 W/GPM or whatever

@param plant_loop [OpenStudio::Model::PlantLoop] plant loop @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb, line 534
def plant_loop_apply_prm_baseline_pump_power(plant_loop)
  hot_water_pump_power = 19 # W/gpm
  hot_water_district_pump_power = 14 # W/gpm
  chilled_water_primary_pump_power = 9 # W/gpm
  chilled_water_secondary_pump_power = 13 # W/gpm
  chilled_water_district_pump_power = 16 # W/gpm
  condenser_water_pump_power = 19 # W/gpm
  # Determine the pumping power per
  # flow based on loop type.
  w_per_gpm = nil
  chiller_counter = 0

  sizing_plant = plant_loop.sizingPlant
  loop_type = sizing_plant.loopType

  case loop_type
  when 'Heating'

    has_district_heating = false
    plant_loop.supplyComponents.each do |sc|
      if sc.iddObjectType.valueName.to_s.include?('DistrictHeating')
        has_district_heating = true
      end
    end

    w_per_gpm = if has_district_heating # District HW
                  hot_water_district_pump_power
                else # HW
                  hot_water_pump_power
                end

  when 'Cooling'
    has_district_cooling = false
    plant_loop.supplyComponents.each do |sc|
      if sc.to_DistrictCooling.is_initialized
        has_district_cooling = true
      elsif sc.to_ChillerElectricEIR.is_initialized
        chiller_counter += 1
      end
    end

    if has_district_cooling # District CHW
      w_per_gpm = chilled_water_district_pump_power
    elsif plant_loop.additionalProperties.hasFeature('is_primary_loop') # The primary loop of the primary/secondary CHW
      w_per_gpm = chilled_water_primary_pump_power
    elsif plant_loop.additionalProperties.hasFeature('is_secondary_loop') # The secondary loop of the primary/secondary CHW
      w_per_gpm = chilled_water_secondary_pump_power
    else # Primary only CHW combine 9W/gpm + 13W/gpm
      w_per_gpm = chilled_water_primary_pump_power + chilled_water_secondary_pump_power
    end

  when 'Condenser'
    # @todo prm condenser loop pump power
    w_per_gpm = condenser_water_pump_power
  end

  # Modify all the primary pumps
  plant_loop.supplyComponents.each do |sc|
    if sc.to_PumpConstantSpeed.is_initialized
      pump = sc.to_PumpConstantSpeed.get
      if chiller_counter > 0
        w_per_gpm /= chiller_counter
      end
      pump_apply_prm_pressure_rise_and_motor_efficiency(pump, w_per_gpm)
    elsif sc.to_PumpVariableSpeed.is_initialized
      pump = sc.to_PumpVariableSpeed.get
      pump_apply_prm_pressure_rise_and_motor_efficiency(pump, w_per_gpm)
    elsif sc.to_HeaderedPumpsConstantSpeed.is_initialized
      pump = sc.to_HeaderedPumpsConstantSpeed.get
      pump_apply_prm_pressure_rise_and_motor_efficiency(pump, w_per_gpm)
    elsif sc.to_HeaderedPumpsVariableSpeed.is_initialized
      pump = sc.to_HeaderedPumpsVariableSpeed.get
      pump_apply_prm_pressure_rise_and_motor_efficiency(pump, w_per_gpm)
    end
  end
  return true
end
plant_loop_apply_prm_number_of_chillers(plant_loop) click to toggle source

Splits the single chiller used for the initial sizing run into multiple separate chillers based on Appendix G. Also applies EMS to stage chillers properly @param plant_loop [OpenStudio::Model::PlantLoop] chilled water loop @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb, line 99
def plant_loop_apply_prm_number_of_chillers(plant_loop)
  # Skip non-cooling plants & secondary cooling loop
  return true unless plant_loop.sizingPlant.loopType == 'Cooling'
  # If the loop is cooling but it is a secondary loop, then skip.
  return true if plant_loop.additionalProperties.hasFeature('is_secondary_loop')

  # Set the equipment to stage sequentially or uniformload if there is secondary loop
  if plant_loop.additionalProperties.hasFeature('is_primary_loop')
    plant_loop.setLoadDistributionScheme('UniformLoad')
  else
    plant_loop.setLoadDistributionScheme('SequentialLoad')
  end

  model = plant_loop.model

  # Get all existing chillers and pumps. Copy chiller properties needed when duplicating existing settings
  chillers = []
  pumps = []
  default_cop = nil
  condenser_water_loop = nil
  dsgn_sup_wtr_temp_c = nil

  plant_loop.supplyComponents.each do |sc|
    if sc.to_ChillerElectricEIR.is_initialized
      chiller = sc.to_ChillerElectricEIR.get

      # Copy the last chillers COP, leaving chilled water temperature, and reference cooling tower. These will be the
      # default for any extra chillers.
      default_cop = chiller.referenceCOP
      dsgn_sup_wtr_temp_c = chiller.referenceLeavingChilledWaterTemperature
      condenser_water_loop = chiller.condenserWaterLoop
      chillers << chiller

    elsif sc.to_PumpConstantSpeed.is_initialized
      pumps << sc.to_PumpConstantSpeed.get
    elsif sc.to_PumpVariableSpeed.is_initialized
      pumps << sc.to_PumpVariableSpeed.get
    end
  end

  # Get existing plant loop pump. We'll copy this pumps parameters before removing it. Throw exception for multiple pumps on supply side
  if pumps.size.zero?
    OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, found #{pumps.size} pumps. A loop must have at least one pump.")
    return false
  elsif pumps.size > 1
    OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, found #{pumps.size} pumps, cannot split up per performance rating method baseline requirements.")
    return false
  else
    original_pump = pumps[0]
  end

  return true if chillers.empty?

  # Determine the capacity of the loop
  cap_w = plant_loop_total_cooling_capacity(plant_loop)
  cap_tons = OpenStudio.convert(cap_w, 'W', 'ton').get

  # Throw exception for > 2,400 tons as this breaks our staging strategy cap of 3 chillers
  if cap_tons > 2400
    OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, the total capacity (#{cap_w}) exceeded 2400 tons and would require more than 3 chillers. The existing code base cannot accommodate the staging required for this")
  end

  if cap_tons <= 300
    num_chillers = 1
    chiller_cooling_type = 'WaterCooled'
    chiller_compressor_type = 'Rotary Screw'
  elsif cap_tons > 300 && cap_tons < 600
    num_chillers = 2
    chiller_cooling_type = 'WaterCooled'
    chiller_compressor_type = 'Rotary Screw'
  else
    # Max capacity of a single chiller
    max_cap_ton = 800.0
    num_chillers = (cap_tons / max_cap_ton).floor + 1
    # Must be at least 2 chillers
    num_chillers += 1 if num_chillers == 1
    chiller_cooling_type = 'WaterCooled'
    chiller_compressor_type = 'Centrifugal'
  end

  if chillers.length > num_chillers
    OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, the existing number of chillers exceeds the recommended amount. We have not accounted for this in the codebase yet.")
  end

  # Determine the per-chiller capacity and sizing factor
  per_chiller_sizing_factor = (1.0 / num_chillers).round(2)
  per_chiller_cap_w = cap_w / num_chillers

  # Set the sizing factor and the chiller types
  # chillers.each_with_index do |chiller, i|
  for i in 0..num_chillers - 1
    # if not enough chillers exist, create a new one. Else reference the i'th chiller
    if i <= chillers.length - 1
      chiller = chillers[i]
    else
      chiller = OpenStudio::Model::ChillerElectricEIR.new(model)
      plant_loop.addSupplyBranchForComponent(chiller)
      chiller.setReferenceLeavingChilledWaterTemperature(dsgn_sup_wtr_temp_c)
      chiller.setLeavingChilledWaterLowerTemperatureLimit(OpenStudio.convert(36.0, 'F', 'C').get)
      chiller.setReferenceEnteringCondenserFluidTemperature(OpenStudio.convert(95.0, 'F', 'C').get)
      chiller.setMinimumPartLoadRatio(0.15)
      chiller.setMaximumPartLoadRatio(1.0)
      chiller.setOptimumPartLoadRatio(1.0)
      chiller.setMinimumUnloadingRatio(0.25)
      chiller.setChillerFlowMode('ConstantFlow')
      chiller.setReferenceCOP(default_cop)

      condenser_water_loop.get.addDemandBranchForComponent(chiller) if condenser_water_loop.is_initialized

    end

    chiller.setName("#{template} #{chiller_cooling_type} #{chiller_compressor_type} Chiller #{i + 1} of #{num_chillers}")
    chiller.setSizingFactor(per_chiller_sizing_factor)
    chiller.setReferenceCapacity(per_chiller_cap_w)
    chiller.setCondenserType(chiller_cooling_type)
    chiller.additionalProperties.setFeature('compressor_type', chiller_compressor_type)

    # Add inlet pump
    new_pump = OpenStudio::Model::PumpVariableSpeed.new(plant_loop.model)
    new_pump.setName("#{chiller.name} Inlet Pump")
    new_pump.setRatedPumpHead(original_pump.ratedPumpHead / num_chillers)

    pump_variable_speed_set_control_type(new_pump, control_type = 'Riding Curve')
    chiller_inlet_node = chiller.connectedObject(chiller.supplyInletPort).get.to_Node.get
    new_pump.addToNode(chiller_inlet_node)

  end

  # Remove original pump, dedicated chiller pumps have all been added
  original_pump.remove

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{plant_loop.name}, there are #{chillers.size} #{chiller_cooling_type} #{chiller_compressor_type} chillers.")

  # Check for a heat exchanger fluid to fluid-- that lets you know if this is a primary loop
  has_secondary_plant_loop = !plant_loop.demandComponents(OpenStudio::Model::HeatExchangerFluidToFluid.iddObjectType).empty?

  if has_secondary_plant_loop
    # Add EMS to stage chillers if there's a primary/secondary configuration
    if num_chillers > 3
      OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name} has more than 3 chillers. We do not have an EMS strategy for that yet.")
    elsif num_chillers > 1
      add_ems_for_multiple_chiller_pumps_w_secondary_plant(model, plant_loop)
    end
  end

  return true
end
plant_loop_apply_prm_number_of_cooling_towers(plant_loop) click to toggle source

@param plant_loop [OpenStudio::Model::PlantLoop] plant loop @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb, line 6
def plant_loop_apply_prm_number_of_cooling_towers(plant_loop)
  # Skip non-cooling plants
  return true unless plant_loop.sizingPlant.loopType == 'Condenser'

  # Determine the number of chillers
  # already in the model
  num_chillers = plant_loop.model.getChillerElectricEIRs.size

  # Get all existing cooling towers and pumps
  clg_twrs = []
  pumps = []
  plant_loop.supplyComponents.each do |sc|
    if sc.to_CoolingTowerSingleSpeed.is_initialized
      clg_twrs << sc.to_CoolingTowerSingleSpeed.get
    elsif sc.to_CoolingTowerTwoSpeed.is_initialized
      clg_twrs << sc.to_CoolingTowerTwoSpeed.get
    elsif sc.to_CoolingTowerVariableSpeed.is_initialized
      clg_twrs << sc.to_CoolingTowerVariableSpeed.get
    elsif sc.to_PumpConstantSpeed.is_initialized
      pumps << sc.to_PumpConstantSpeed.get
    elsif sc.to_PumpVariableSpeed.is_initialized
      pumps << sc.to_PumpVariableSpeed.get
    end
  end

  # Ensure there is only 1 cooling tower to start
  orig_twr = nil
  if clg_twrs.empty?
    return true
  elsif clg_twrs.size > 1
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{plant_loop.name}, found #{clg_twrs.size} cooling towers, cannot split up per performance rating method baseline requirements.")
    return false
  else
    orig_twr = clg_twrs[0]
  end

  # Ensure there is only 1 pump to start
  orig_pump = nil
  if pumps.empty?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{plant_loop.name}, found #{pumps.size} pumps.  A loop must have at least one pump.")
    return false
  elsif pumps.size > 1
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{plant_loop.name}, found #{pumps.size} pumps, cannot split up per performance rating method baseline requirements.")
    return false
  else
    orig_pump = pumps[0]
  end

  # Determine the per-cooling_tower sizing factor
  clg_twr_sizing_factor = (1.0 / num_chillers).round(2)

  final_twrs = [orig_twr]

  # return unless there is more than one chiller
  return true unless num_chillers > 1

  # If there is more than one chiller, replace the original pump with a headered pump of the same type and properties.
  num_pumps = num_chillers
  new_pump = nil
  if orig_pump.to_PumpConstantSpeed.is_initialized
    new_pump = OpenStudio::Model::HeaderedPumpsConstantSpeed.new(plant_loop.model)
    new_pump.setNumberofPumpsinBank(num_pumps)
    new_pump.setName("#{orig_pump.name} Bank of #{num_pumps}")
    new_pump.setRatedPumpHead(orig_pump.ratedPumpHead)
    new_pump.setMotorEfficiency(orig_pump.motorEfficiency)
    new_pump.setFractionofMotorInefficienciestoFluidStream(orig_pump.fractionofMotorInefficienciestoFluidStream)
    new_pump.setPumpControlType(orig_pump.pumpControlType)
  elsif orig_pump.to_PumpVariableSpeed.is_initialized
    new_pump = OpenStudio::Model::HeaderedPumpsVariableSpeed.new(plant_loop.model)
    new_pump.setNumberofPumpsinBank(num_pumps)
    new_pump.setName("#{orig_pump.name} Bank of #{num_pumps}")
    new_pump.setRatedPumpHead(orig_pump.ratedPumpHead)
    new_pump.setMotorEfficiency(orig_pump.motorEfficiency)
    new_pump.setFractionofMotorInefficienciestoFluidStream(orig_pump.fractionofMotorInefficienciestoFluidStream)
    new_pump.setPumpControlType(orig_pump.pumpControlType)
    new_pump.setCoefficient1ofthePartLoadPerformanceCurve(orig_pump.coefficient1ofthePartLoadPerformanceCurve)
    new_pump.setCoefficient2ofthePartLoadPerformanceCurve(orig_pump.coefficient2ofthePartLoadPerformanceCurve)
    new_pump.setCoefficient3ofthePartLoadPerformanceCurve(orig_pump.coefficient3ofthePartLoadPerformanceCurve)
    new_pump.setCoefficient4ofthePartLoadPerformanceCurve(orig_pump.coefficient4ofthePartLoadPerformanceCurve)
  end
  # Remove the old pump
  orig_pump.remove
  # Attach the new headered pumps
  new_pump.addToNode(plant_loop.supplyInletNode)

  return true
end
plant_loop_set_chw_pri_sec_configuration(model) click to toggle source

Set configuration in model for chilled water primary/secondary loop interface Use heat_exchanger for stable baseline

@param model [OpenStudio::Model::Model] OpenStudio model object @return [String] common_pipe or heat_exchanger

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb, line 669
def plant_loop_set_chw_pri_sec_configuration(model)
  pri_sec_config = 'heat_exchanger'
  return pri_sec_config
end
run_all_orientations(run_all_orients, user_model) click to toggle source

Check whether the baseline model generation needs to run all four orientations The default shall be true The orientation takes priority of:

  1. Appx G

  2. Method user input.

  3. User data override.

@param run_all_orients [Boolean] user inputs to indicate whether it is required to run all orientations @param user_model [OpenStudio::Model::Model] OpenStudio model @return [Boolean] True if run all orientation is required, false otherwise

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2277
def run_all_orientations(run_all_orients, user_model)
  run_orients_flag = false
  # Step 1 check orientation variations - priority 3
  fenestration_area_hash = get_model_fenestration_area_by_orientation(user_model)
  fenestration_area_hash.each do |orientation, fenestration_area|
    OpenStudio.logFree(OpenStudio::Info, 'prm.log',
                       "#{orientation} orientation has total fenestration area of #{fenestration_area} m2")
    fenestration_area_hash.each do |other_orientation, other_fenestration_area|
      next unless orientation != other_orientation

      variance = (other_fenestration_area - fenestration_area) / fenestration_area
      if variance.abs > 0.05
        # if greater then 0.05
        OpenStudio.logFree(OpenStudio::Info,
                           'prm.log',
                           "#{orientation} has total fenestration area of #{fenestration_area} m2, which is higher than 5% variance compare to #{other_fenestration_area} at #{other_orientation}")
        run_orients_flag = true
      end
    end
  end
  # Step 2, assign method user input if it is provided as false.
  unless run_all_orients
    OpenStudio.logFree(OpenStudio::Error,
                       'prm.log',
                       'The run_all_orientation flag is set to False, update the run to a single orientation PRM generation.')
    run_orients_flag = run_all_orients
  end
  # Step 3 read user data - priority 1 - user data will override the priority 2
  user_buildings = @standards_data.key?('userdata_building') ? @standards_data['userdata_building'] : nil
  if user_buildings
    building_name = user_model.building.get.name.get
    user_building_index = user_buildings.index { |user_building| building_name.include? user_building['name'] }
    unless user_building_index.nil? || user_buildings[user_building_index]['is_exempt_from_rotations'].nil?
      # user data exempt the rotation, No indicates true for running orients.
      OpenStudio.logFree(OpenStudio::Error,
                         'prm.log',
                         "User data in the userdata_building.csv indicate building #{building_name} is exempted from rotation. Update the run to a single orientation PRM generation.")
      # @todo need to use user data enums later.
      run_orients_flag = user_buildings[user_building_index]['is_exempt_from_rotations'].casecmp('False') == 0
    end
  end
  return run_orients_flag
end
set_coil_cooling_efficiency_and_curves(cooling_coil, sql_db_vars_map, sys_type) click to toggle source

This function returns the cooling dx coil efficiency and curve coefficient in a Hashmap.

@param cooling_coil [OpenStudio::Model::ModeObject] @param sql_db_vars_map [Hash] hash map @param sys_type [String] baseline system type string @return [Hash] sql_db_vars_map

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1074
def set_coil_cooling_efficiency_and_curves(cooling_coil, sql_db_vars_map, sys_type)
  if cooling_coil.to_CoilCoolingDXSingleSpeed.is_initialized
    # single speed coil
    sql_db_vars_map = coil_cooling_dx_single_speed_apply_efficiency_and_curves(cooling_coil.to_CoilCoolingDXSingleSpeed.get, sql_db_vars_map, sys_type)
  elsif cooling_coil.to_CoilCoolingDXTwoSpeed.is_initialized
    # two speed coil
    sql_db_vars_map = coil_cooling_dx_two_speed_apply_efficiency_and_curves(cooling_coil.to_CoilCoolingDXTwoSpeed.get, sql_db_vars_map, sys_type)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "#{cooling_coil.name} is not single speed or two speed DX cooling coil. Nothing to be done for efficiency")
  end

  return sql_db_vars_map
end
set_coil_heating_efficiency_and_curves(heating_coil, sql_db_vars_map, sys_type) click to toggle source

This function returns the heating dx coil efficiency and curve coefficient in a Hashmap.

@param heating_coil [OpenStudio::Model::ModeObject] @param sql_db_vars_map [Hash] hash map @param sys_type [String] baseline system type string @return [Hash] the hashmap contains the heating efficiency and curve coefficient for the heating_coil

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 1094
def set_coil_heating_efficiency_and_curves(heating_coil, sql_db_vars_map, sys_type)
  if heating_coil.to_CoilHeatingDXSingleSpeed.is_initialized
    # single speed coil
    sql_db_vars_map = coil_heating_dx_single_speed_apply_efficiency_and_curves(heating_coil.to_CoilHeatingDXSingleSpeed.get, sql_db_vars_map, sys_type)
  elsif heating_coil.to_CoilHeatingGas.is_initialized
    # single speed coil
    sql_db_vars_map = coil_heating_gas_apply_efficiency_and_curves(heating_coil.to_CoilHeatingGas.get, sql_db_vars_map, sys_type)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "#{heating_coil.name} is not single speed DX heating coil. Nothing to be done for efficiency")
  end

  return sql_db_vars_map
end
set_lpd_on_space_type(space_type, user_spaces, user_spacetypes) click to toggle source

Function to test LPD on default space type. The function assigns lighting power density to an light object. @param space_type [OpenStudio::Model::SpaceType] @param user_spaces [Hash] @param user_spacetypes [Hash] @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 364
def set_lpd_on_space_type(space_type, user_spaces, user_spacetypes)
  if has_multi_lpd_values_space_type(space_type)
    # If multiple LPD value exist - then enforce space-space_type one on one relationship
    space_to_space_type_apply_lighting(user_spaces, user_spacetypes, space_type)
  else
    # use default - loop through space to assign occupancy credit to each space.
    space_type_lighting_per_area = 0.0
    space_type.spaces.each do |space|
      space_lighting_per_area = calculate_lpd_by_space(space_type, space)
      space_type_lighting_per_area = space_lighting_per_area
    end
    if space_type.hasAdditionalProperties && space_type.additionalProperties.hasFeature('regulated_lights_name')
      lights_name = space_type.additionalProperties.getFeatureAsString('regulated_lights_name').to_s
      lights_obj = space_type.model.getLightsByName(lights_name).get
      OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Setting lighting object #{lights_obj.name.get} lighting per area to #{space_type_lighting_per_area} W/ft^2")
      lights_obj.lightsDefinition.setWattsperSpaceFloorArea(OpenStudio.convert(space_type_lighting_per_area.to_f, 'W/ft^2', 'W/m^2').get)
    end
  end
  return true
end
space_add_prm_computer_room_equipment_schedule(space) click to toggle source

Create and assign PRM computer room electric equipment schedule

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Space.rb, line 115
def space_add_prm_computer_room_equipment_schedule(space)
  # Get proposed or baseline model
  model = space.model

  # Get space type associated with the space
  standard_space_type = prm_get_optional_handler(space, @sizing_run_dir, 'spaceType', 'standardsSpaceType').delete(' ').downcase

  # Check if the PRM computer room schedule is already in the model
  schedule_name = 'ASHRAE 90.1 Appendix G - Computer Room Equipment Schedule'
  schedule_found = model.getScheduleRulesetByName(schedule_name)

  # Create and assign the the electric equipment schedule
  if standard_space_type == 'computerroom'
    space.spaceType.get.electricEquipment.each do |elec_equipment|
      # Only create the schedule if it could not be found
      if schedule_found.is_initialized
        computer_room_equipment_schedule_ruleset = model.getScheduleRulesetByName(schedule_name).get
      else
        computer_room_equipment_schedule_ruleset = OpenStudio::Model::ScheduleRuleset.new(model)
        computer_room_equipment_schedule_ruleset.setName(schedule_name)
        schedule_fractions = [0.25, 0.5, 0.75, 1.0, 0.25, 0.5, 0.75, 1.0, 0.25, 0.5, 0.75, 1.0]
        # Weekdays and weekends schedules
        schedule_fractions.each_with_index do |frac, i|
          sch_rule = OpenStudio::Model::ScheduleRule.new(computer_room_equipment_schedule_ruleset)
          sch_rule.setStartDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(i.to_i + 1), 1))
          # No leap year according to PRM-RM
          sch_rule.setEndDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(i.to_i + 1), Date.new(2006, i.to_i + 1, -1).day))
          day_sch = sch_rule.daySchedule
          day_sch.setName("#{schedule_name} - Month #{i + 1} - Fraction #{frac}")
          model_add_vals_to_sch(model, day_sch, 'Constant', [frac])
          sch_rule.setApplyAllDays(true)
        end
        # Special days schedules
        equipment_on = OpenStudio::Model::ScheduleDay.new(model)
        model_add_vals_to_sch(model, equipment_on, 'Constant', [1])
        equipment_off = OpenStudio::Model::ScheduleDay.new(model)
        model_add_vals_to_sch(model, equipment_off, 'Constant', [0])
        computer_room_equipment_schedule_ruleset.setHolidaySchedule(equipment_on)
        computer_room_equipment_schedule_ruleset.setCustomDay1Schedule(equipment_on)
        computer_room_equipment_schedule_ruleset.setCustomDay2Schedule(equipment_on)
        computer_room_equipment_schedule_ruleset.setSummerDesignDaySchedule(equipment_on)
        computer_room_equipment_schedule_ruleset.setWinterDesignDaySchedule(equipment_off)
      end
      elec_equipment.setSchedule(computer_room_equipment_schedule_ruleset)
    end
  end

  return true
end
space_apply_infiltration_rate(space, tot_infil_m3_per_s, infil_method, infil_coefficients) click to toggle source

Set the infiltration rate for this space to include the impact of air leakage requirements in the standard.

@param space [OpenStudio::Model::Space] space object @param tot_infil_m3_per_s [Float] total infiltration in m3/s @param infil_method [String] infiltration method @param infil_coefficients [Array] Array of 4 items

[Constant Term Coefficient, Temperature Term Coefficient,
  Velocity Term Coefficient, Velocity Squared Term Coefficient]

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Space.rb, line 13
def space_apply_infiltration_rate(space, tot_infil_m3_per_s, infil_method, infil_coefficients)
  # Calculate infiltration rate
  case infil_method.to_s
    when 'Flow/ExteriorWallArea'
      # Spread the total infiltration rate
      total_exterior_wall_area = 0
      space.model.getSpaces.each do |spc|
        # Get the space conditioning type
        space_cond_type = space_conditioning_category(spc)
        total_exterior_wall_area += spc.exteriorWallArea * spc.multiplier unless space_cond_type == 'Unconditioned'
      end
      prm_raise(total_exterior_wall_area > 0, @sizing_run_dir, 'Total exterior wall area in the model is 0. Check your model inputs')
      adj_infil_flow_ext_wall_area = tot_infil_m3_per_s / total_exterior_wall_area
      OpenStudio.logFree(OpenStudio::Debug, 'prm.log', "For #{space.name}, adj infil = #{adj_infil_flow_ext_wall_area.round(8)} m^3/s*m^2 of above grade wall area.")
    when 'Flow/Area'
      # Spread the total infiltration rate
      total_floor_area = 0
      space.model.getSpaces.each do |spc|
        # Get the space conditioning type
        space_cond_type = space_conditioning_category(spc)
        total_floor_area += spc.floorArea * spc.multipler unless space_cond_type == 'Unconditioned' || space.exteriorArea == 0
      end
      prm_raise(total_floor_area > 0, @sizing_run_dir, 'Sum of the floor area in exterior spaces in the model is 0. Check your model inputs')
      adj_infil_flow_area = tot_infil_m3_per_s / total_floor_area
      OpenStudio.logFree(OpenStudio::Debug, 'prm.log', "For #{space.name}, adj infil = #{adj_infil_flow_area.round(8)} m^3/s*m^2 of space floor area.")
  end

  # Get any infiltration schedule already assigned to this space or its space type
  # If not, the always on schedule will be applied.
  # @todo Infiltration schedules should be based on HVAC operation
  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
  else
    # Add specific schedule type object to insure compatibility with the OpenStudio infiltration object
    infil_sch_limit_type = OpenstudioStandards::Schedules.create_schedule_type_limits(space.model,
                                                                                      name: 'Infiltration Schedule Type Limits',
                                                                                      lower_limit_value: 0.0,
                                                                                      upper_limit_value: 1.0,
                                                                                      numeric_type: 'Continuous',
                                                                                      unit_type: 'Dimensionless')
    infil_sch.setScheduleTypeLimits(infil_sch_limit_type)
  end

  # Remove all pre-existing space infiltration objects
  space.spaceInfiltrationDesignFlowRates.each(&:remove)

  # Get the space conditioning type
  space_cond_type = space_conditioning_category(space)
  if space_cond_type != 'Unconditioned'
    # Create an infiltration rate object for this space
    infiltration = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(space.model)
    infiltration.setName("#{space.name} Infiltration")
    case infil_method.to_s
      when 'Flow/ExteriorWallArea'
        infiltration.setFlowperExteriorWallArea(adj_infil_flow_ext_wall_area.round(13)) if space.exteriorWallArea > 0
      when 'Flow/Area'
        infiltration.setFlowperSpaceFloorArea(adj_infil_flow_area.round(13)) if space.exteriorArea > 0
    end
    infiltration.setSchedule(infil_sch)
    infiltration.setConstantTermCoefficient(infil_coefficients[0])
    infiltration.setTemperatureTermCoefficient(infil_coefficients[1])
    infiltration.setVelocityTermCoefficient(infil_coefficients[2])
    infiltration.setVelocitySquaredTermCoefficient(infil_coefficients[3])

    infiltration.setSpace(space)
  end

  return true
end
space_set_baseline_daylighting_controls(space, remove_existing = false, draw_areas_for_debug = false) click to toggle source

For stable baseline, remove all daylighting controls (sidelighting and toplighting) @param space [OpenStudio::Model::Space] the space with daylighting @param remove_existing [Boolean] if true, will remove existing controls then add new ones @param draw_areas_for_debug [Boolean] If this argument is set to true, @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Space.rb, line 106
def space_set_baseline_daylighting_controls(space, remove_existing = false, draw_areas_for_debug = false)
  removed = space_remove_daylighting_controls(space)
  return removed
end
space_to_space_type_apply_lighting(user_spaces, user_spacetypes, space_type) click to toggle source

Function that applies user LPD to each space by duplicating space types This function is used when there are user space data available or the spaces under space type has lighting per length value which may cause multiple lighting power densities under one space_type. @param user_spaces [Hash] hash data contained in the user space @param user_spacetypes [Hash] hash data contained in the user spacetypes @param space_type [OpenStudio::Model::SpaceType] object @return [ArrayOpenStudio::Model::Space] List of Spaces

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 393
def space_to_space_type_apply_lighting(user_spaces, user_spacetypes, space_type)
  space_lighting_per_area_hash = {}
  # first priority - user_space data
  if user_spaces && user_spaces.length >= 1
    space_type.spaces.each do |space|
      user_space_index = user_spaces.index { |user_space| user_space['name'] == space.name.get }
      unless user_space_index.nil?
        user_space_data = user_spaces[user_space_index]
        if user_space_data.key?('num_std_ltg_types') && user_space_data['num_std_ltg_types'].to_f > 0
          space_lighting_per_area = calculate_lpd_from_userdata(user_space_data, space)
          space_lighting_per_area_hash[space.name.get] = space_lighting_per_area
        end
      end
    end
  end
  # second priority - user_spacetype
  if user_spacetypes && user_spacetypes.length >= 1
    # if space type has user data
    user_space_type_index = user_spacetypes.index { |user_spacetype| user_spacetype['name'] == space_type.name.get }
    unless user_space_type_index.nil?
      user_space_type_data = user_spacetypes[user_space_type_index]
      if user_space_type_data.key?('num_std_ltg_types') && user_space_type_data['num_std_ltg_types'].to_f > 0
        space_type.spaces.each do |space|
          # unless the space is in the hash, we will add lighting per area to the space
          space_name = space.name.get
          unless space_lighting_per_area_hash.key?(space_name)
            space_lighting_per_area = calculate_lpd_from_userdata(user_space_type_data, space)
            space_lighting_per_area_hash[space_name] = space_lighting_per_area
          end
        end
      end
    end
  end
  # Third priority
  # set space type to every space in the space_type, third priority
  # will also be assigned from the default space type
  space_type.spaces.each do |space|
    space_name = space.name.get
    unless space_lighting_per_area_hash.key?(space_name)
      space_lighting_per_area = calculate_lpd_by_space(space_type, space)
      space_lighting_per_area_hash[space_name] = space_lighting_per_area
    end
  end
  # All space is explored.
  # Now rewrite the space type in each space - might need to change the logic
  space_array = []
  space_type.spaces.each do |space|
    space_name = space.name.get
    new_space_type = space_type.clone.to_SpaceType.get
    space.setSpaceType(new_space_type)
    lighting_per_area = space_lighting_per_area_hash[space_name]
    new_space_type.lights.each do |inst|
      lights_name = inst.name.get
      new_space_type.additionalProperties.setFeature('regulated_lights_name', lights_name)
      definition = inst.lightsDefinition
      unless lighting_per_area.zero?
        new_definition = definition.clone.to_LightsDefinition.get
        new_definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area.to_f, 'W/ft^2', 'W/m^2').get)
        inst.setLightsDefinition(new_definition)
        OpenStudio.logFree(OpenStudio::Info, 'log.prm', "#{space_type.name} set LPD to #{lighting_per_area} W/ft^2.")
      end
    end
    space_array.push(space)
  end
  return space_array
end
space_to_space_type_apply_power_equipment(user_spacetypes, user_spaces, space_array) click to toggle source

Apply space to space type power equipment adjustment. NOTE! this function shall only be used if the space to space type is one to one relationship. This function can process both electric equipment and gas equipment and this function will process user data from electric equipment and gas equipment user data

@param user_spacetypes [Hash] spacetype user data @param user_spaces [Hash] space user data @param space_array [Array OpenStudio::Model:Space] list of spaces need for process @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 221
def space_to_space_type_apply_power_equipment(user_spacetypes, user_spaces, space_array)
  # Step 1: Set electric / gas equipment
  # save schedules in a hash in case it is needed for new electric equipment
  power_schedule_hash = {}
  # check if electric equipment data is available.
  user_electric_equipment_data = @standards_data.key?('userdata_electric_equipment') ? @standards_data['userdata_electric_equipment'] : nil
  user_gas_equipment_data = @standards_data.key?('userdata_gas_equipment') ? @standards_data['userdata_gas_equipment'] : nil
  if user_electric_equipment_data && user_electric_equipment_data.length >= 1
    space_array.each do |space|
      # Each space has a unique space type
      space_type = space.spaceType.get
      user_spacestypes_index = user_spacetypes.index { |user_spacetype| /#{user_spacetype['name']}/i =~ space_type.name.get }
      user_space_index = user_spaces.index { |user_space| user_space['name'] == space.name.get }
      # Initialize with standard space_type
      user_space_data = space_type.name.get
      unless user_spacestypes_index.nil?
        # override with user space type if specified
        user_space_data = user_spacetypes[user_spacestypes_index]
      end
      unless user_space_index.nil?
        # override with user space if specified
        user_space_data = user_spaces[user_space_index]
      end
      space_type_electric_equipments = space_type.electricEquipment
      space_type_electric_equipments.each do |sp_electric_equipment|
        electric_equipment_name = sp_electric_equipment.name.get
        select_user_electric_equipment_array = user_electric_equipment_data.select { |elec| /#{elec['name']}/i =~ electric_equipment_name }
        unless select_user_electric_equipment_array.empty?
          select_user_electric_equipment = select_user_electric_equipment_array[0]
          calculate_electric_value_by_userdata(select_user_electric_equipment, sp_electric_equipment, power_schedule_hash, space_type, user_space_data)
        end
      end
    end
  elsif user_gas_equipment_data && user_gas_equipment_data.length >= 1
    space_array.each do |space|
      space_type = space.spaceType.get
      user_spacestypes_index = user_spacetypes.index { |user_spacetype| user_spacetype['name'] == space_type.name.get }
      user_space_index = user_spaces.index { |user_space| user_space['name'] == space.name.get }
      user_space_data = space_type.name.get
      unless user_spacestypes_index.nil?
        user_space_data = user_spacetypes[user_spacestypes_index]
      end
      unless user_space_index.nil?
        user_space_data = user_spaces[user_space_index]
      end
      space_type_gas_equipments = space_type.gasEquipment
      space_type_gas_equipments.each do |sp_gas_equipment|
        gas_equipment_name = sp_gas_equipment.name.get
        select_user_gas_equipment_array = user_gas_equipment_data.select { |gas| gas['name'].casecmp(gas_equipment_name) == 0 }
        unless select_user_gas_equipment_array.empty?
          select_user_gas_equipment = select_user_gas_equipment_array[0]
          # Update the gas equipment occupancy credit (if it has)
          update_power_equipment_credits(sp_gas_equipment, select_user_gas_equipment, power_schedule_hash, space_type.model, user_space_data)
        end
      end
    end
  end
  return true
end
space_type_apply_internal_loads(space_type, set_people, set_lights, set_electric_equipment, set_gas_equipment, set_ventilation, set_infiltration) 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 space_type [OpenStudio::Model::SpaceType] space type object @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/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 22
def space_type_apply_internal_loads(space_type, set_people, set_lights, set_electric_equipment, set_gas_equipment, set_ventilation, set_infiltration)
  # 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 && space_type.standardsSpaceType.get.downcase.include?('plenum')
    return false
  end

  # Save information about lighting exceptions before removing extra lights objects
  # First get list of all lights objects that are exempt
  regulated_lights = []
  unregulated_lights = []
  user_lights = @standards_data.key?('userdata_lights') ? @standards_data['userdata_lights'] : nil
  if user_lights && user_lights.length >= 1
    user_lights.each do |user_data|
      lights_name = user_data['name']
      lights_obj = space_type.model.getLightsByName(lights_name).get

      if user_data['has_retail_display_exception'].to_s.downcase == 'yes' || user_data['has_unregulated_exception'].to_s.downcase == 'yes'
        # If either exception is applicable
        # Put this one on the unregulated list
        unregulated_lights.push(lights_name)
      end
    end
  end

  # Get all lights objects that are not exempt
  space_type.lights.sort.each do |lights_obj|
    lights_name = lights_obj.name.get
    if !unregulated_lights.include? lights_name
      regulated_lights << lights_obj
    end
  end

  # Pre-process the light instances in the space type
  # Remove all regulated instances but leave one in the space type
  if regulated_lights.empty?
    definition = OpenStudio::Model::LightsDefinition.new(space_type.model)
    definition.setName("#{space_type.name} Lights Definition")
    instance = OpenStudio::Model::Lights.new(definition)
    lights_name = "#{space_type.name} Lights"
    instance.setName(lights_name)
    instance.setSpaceType(space_type)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no lights, one has been created.")
    space_type.additionalProperties.setFeature('regulated_lights_name', lights_name)
    regulated_lights << instance
  else
    regulated_lights.each_with_index do |inst, i|
      if i.zero?
        # Save the name of the first instance to use as the baseline lights object
        lights_name = inst.name.get
        space_type.additionalProperties.setFeature('regulated_lights_name', lights_name)
        next
      end

      # Remove all other lights objects that have not been identified as unregulated
      if i == 1
        ref_name = space_type.additionalProperties.getFeatureAsString('regulated_lights_name').to_s
        OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Multiple lights objects found in user model for #{space_type.name}. Baseline schedule will be determined from #{ref_name}")
      end

      OpenStudio.logFree(OpenStudio::Info, 'prm.log', "Removed lighting object #{inst.name} from #{space_type.name}. ")
      inst.remove
    end
  end

  # Get userdata from userdata_space and userdata_spacetype
  user_spaces = @standards_data.key?('userdata_space') ? @standards_data['userdata_space'] : nil
  user_spacetypes = @standards_data.key?('userdata_spacetype') ? @standards_data['userdata_spacetype'] : nil
  if user_spaces && user_spaces.length >= 1 && has_user_lpd_values(user_spaces)
    # if space type has user data & data has lighting data for user space
    # call this function to enforce space-space_type one on one relationship
    new_space_array = space_to_space_type_apply_lighting(user_spaces, user_spacetypes, space_type)
    # process power equipment with new spaces.
    space_to_space_type_apply_power_equipment(user_spacetypes, user_spaces, new_space_array)
    # remove the old space
    space_type.remove
  else
    if user_spacetypes && user_spacetypes.length >= 1 && has_user_lpd_values(user_spacetypes)
      # if space type has user data & data has lighting data for user space type
      user_space_type_index = user_spacetypes.index { |user_spacetype| user_spacetype['name'] == space_type.name.get }
      if user_space_type_index.nil?
        # cannot find a matched user_spacetype to space_type, use space_type to set LPD
        set_lpd_on_space_type(space_type, user_spaces, user_spacetypes)
        space_type_apply_power_equipment(space_type)
      else
        user_space_type = user_spacetypes[user_space_type_index]
        # If multiple LPD value exist - then enforce space-space_type one on one relationship
        if has_multi_lpd_values_user_data(user_space_type, space_type)
          new_space_array = space_to_space_type_apply_lighting(user_spaces, user_spacetypes, space_type)
          space_to_space_type_apply_power_equipment(user_spacetypes, user_spaces, new_space_array)
          space_type.remove
        else
          # Process the user_space type data - at this point, we are sure there is no lighting per length
          # So all the LPD should be identical by space
          # Loop because we need to assign the occupancy control credit to each space for
          # Schedule processing.
          space_type_lighting_per_area = 0.0
          space_type.spaces.each do |space|
            space_lighting_per_area = calculate_lpd_from_userdata(user_space_type, space)
            space_type_lighting_per_area = space_lighting_per_area
          end
          if space_type.hasAdditionalProperties && space_type.additionalProperties.hasFeature('regulated_lights_name')
            lights_name = space_type.additionalProperties.getFeatureAsString('regulated_lights_name').to_s
            lights_obj = space_type.model.getLightsByName(lights_name).get
            lights_obj.lightsDefinition.setWattsperSpaceFloorArea(OpenStudio.convert(space_type_lighting_per_area.to_f, 'W/ft^2', 'W/m^2').get)
          end
        end
        # process power equipment
        space_type_apply_power_equipment(space_type)
      end
    else
      # no user data, set space_type LPD
      set_lpd_on_space_type(space_type, user_spaces, user_spacetypes)
      # process power equipment
      space_type_apply_power_equipment(space_type)
    end
  end
end
space_type_apply_power_equipment(space_type) click to toggle source

Apply power equipment to space type This is utility function for applying user data to space type

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 181
def space_type_apply_power_equipment(space_type)
  # save schedules in a hash in case it is needed for new electric equipment
  power_schedule_hash = {}
  # @todo move this part to user data processing
  user_electric_equipment_data = @standards_data.key?('userdata_electric_equipment') ? @standards_data['userdata_electric_equipment'] : nil
  user_gas_equipment_data = @standards_data.key?('userdata_gas_equipment') ? @standards_data['userdata_gas_equipment'] : nil
  if user_electric_equipment_data && user_electric_equipment_data.length >= 1
    space_type_electric_equipments = space_type.electricEquipment
    space_type_electric_equipments.each do |sp_electric_equipment|
      electric_equipment_name = sp_electric_equipment.name.get
      select_user_electric_equipment_array = user_electric_equipment_data.select { |elec| elec['name'].casecmp(electric_equipment_name) == 0 }
      unless select_user_electric_equipment_array.empty?
        select_user_electric_equipment = select_user_electric_equipment_array[0]
        calculate_electric_value_by_userdata(select_user_electric_equipment, sp_electric_equipment, power_schedule_hash, space_type, nil)
      end
    end
  elsif user_gas_equipment_data && user_gas_equipment_data.length >= 1
    space_type_gas_equipments = space_type.gasEquipment
    space_type_gas_equipments.each do |sp_gas_equipment|
      gas_equipment_name = sp_gas_equipment.name.get
      select_user_gas_equipment_array = user_gas_equipment_data.select { |gas| gas['name'].casecmp(gas_equipment_name) == 0 }
      unless select_user_gas_equipment_array.empty?
        select_user_gas_equipment = select_user_gas_equipment_array[0]
        # Update the gas equipment occupancy credit (if it has)
        update_power_equipment_credits(sp_gas_equipment, select_user_gas_equipment, power_schedule_hash, space_type.model, nil)
      end
    end
  end
  return true
end
space_type_light_sch_change(model) click to toggle source

Modify the lighting schedules for Appendix G PRM for 2016 and later

@param model [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 463
def space_type_light_sch_change(model)
  # set schedule for lighting
  schedule_hash = {}
  model.getSpaces.each do |space|
    space_type = prm_get_optional_handler(space, @sizing_run_dir, 'spaceType')
    if space_type.hasAdditionalProperties && space_type.additionalProperties.hasFeature('regulated_lights_name')
      lights_name = space_type.additionalProperties.getFeatureAsString('regulated_lights_name').to_s
      ltg_option = space_type.model.getLightsByName(lights_name)
      if ltg_option.is_initialized
        ltg = ltg_option.get
      else
        # raise exception if we cannot find the lights in the model
        prm_raise(false, @sizing_run_dir, "Cannot find the lights #{lights_name} in the model")
      end
      # this will raise exception if the ltg has no schedule assigned.
      if ltg.schedule.is_initialized
        ltg_schedule = ltg.schedule.get
      else
        # case such as Attic may have light object but no light schedule assigned
        # Eplus use default 0 so in here we raise Error but continue processing.
        ltg_schedule = nil
        OpenStudio.logFree(OpenStudio::Warn, 'prm.log',
                           "schedule is not available in component #{ltg.name.get}. Skip processing")
      end

      if ltg_schedule
        ltg_schedule_name = ltg_schedule.name.get
        occupancy_sensor_credit = get_additional_property_as_double(space, 'occ_control_credit', 0.0)
        if schedule_hash.key?(ltg_schedule_name)
          # In this case, there is a schedule created, can retrieve the schedule object and reset in this space type
          schedule_rule = schedule_hash[ltg_schedule_name]
          ltg.setSchedule(schedule_rule)
        else
          # In this case, create a new schedule
          # 1. Clone the existing schedule
          new_ltg_schedule_name = format("#{ltg_schedule_name}_%.4f", occupancy_sensor_credit)
          new_rule_set_schedule = deep_copy_schedule(new_ltg_schedule_name, ltg_schedule, occupancy_sensor_credit, model)
          if ltg.setSchedule(new_rule_set_schedule)
            schedule_hash[new_ltg_schedule_name] = new_rule_set_schedule
          end
        end
      end
    end
  end
end
stage_chilled_water_loop_operation_schemes(model, chilled_water_loop) click to toggle source

Updates a chilled water plant’s operation scheme to match the EMS written by either add_ems_program_for_3_pump_chiller_plant or add_ems_program_for_2_pump_chiller_plant @param model [OpenStudio::Model] OpenStudio model with plant loops @param chilled_water_loop [OpenStudio::Model::PlantLoop] chilled water loop

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb, line 323
def stage_chilled_water_loop_operation_schemes(model, chilled_water_loop)
  # Initialize array of cooling plant systems
  chillers = []

  # Gets all associated chillers from the supply side and adds them to the chillers list
  chilled_water_loop.supplyComponents(OpenStudio::Model::ChillerElectricEIR.iddObjectType).each do |chiller|
    chillers << chiller.to_ChillerElectricEIR.get
  end

  # Skip those without chillers or only 1 (i.e., nothing to stage)
  return if chillers.empty?
  return if chillers.length == 1

  # Sort chillers by capacity
  sorted_chillers = chillers.sort_by { |chiller| chiller.referenceCapacity.get }

  primary_chiller = sorted_chillers[0]
  secondary_1_chiller = sorted_chillers[1]
  secondary_2_chiller = sorted_chillers[2] if chillers.length == 3

  equip_operation_cool_load = OpenStudio::Model::PlantEquipmentOperationCoolingLoad.new(model)

  # Calculate load ranges into the PlantEquipmentOperation:CoolingLoad
  loading_factor = 0.8
  # # when the capacity of primary chiller is larger than the capacity of secondary chiller - the loading factor
  # # will need to be adjusted to avoid load range intersect.
  # if secondary_1_chiller.referenceCapacity.get <= primary_chiller.referenceCapacity.get * loading_factor
  #   # Adjustment_factor can creates a bandwidth for step 2 staging strategy.
  #   # set adjustment_factor = 1.0 means the step 2 staging strategy is skipped
  #   adjustment_factor = 1.0
  #   loading_factor = secondary_1_chiller.referenceCapacity.get / primary_chiller.referenceCapacity.get * adjustment_factor
  # end

  if chillers.length == 3

    # Add four ranges for small, medium, and large chiller capacities
    # 1: 0 W -> 80% of smallest chiller capacity
    # 2: 80% of primary chiller -> medium size chiller capacity
    # 3: medium chiller capacity -> medium + large chiller capacity
    # 4: medium + large chiller capacity -> infinity
    # Control strategy first stage
    equipment_list = [primary_chiller]
    range = primary_chiller.referenceCapacity.get * loading_factor
    equip_operation_cool_load.addLoadRange(range, equipment_list)

    # Control strategy second stage
    equipment_list = [secondary_1_chiller]
    range = secondary_1_chiller.referenceCapacity.get
    equip_operation_cool_load.addLoadRange(range, equipment_list)

    # Control strategy third stage
    equipment_list = [secondary_1_chiller, secondary_2_chiller]
    range = secondary_1_chiller.referenceCapacity.get + secondary_2_chiller.referenceCapacity.get
    equip_operation_cool_load.addLoadRange(range, equipment_list)

    equipment_list = [primary_chiller, secondary_1_chiller, secondary_2_chiller]
    range = 999999999
    equip_operation_cool_load.addLoadRange(range, equipment_list)

  elsif chillers.length == 2

    # Add three ranges for primary and secondary chiller capacities
    # 1: 0 W -> 80% of smallest chiller capacity
    # 2: 80% of primary chiller -> secondary chiller capacity
    # 3: secondary chiller capacity -> infinity
    # Control strategy first stage
    equipment_list = [primary_chiller]
    range = primary_chiller.referenceCapacity.get * loading_factor
    equip_operation_cool_load.addLoadRange(range, equipment_list)

    # Control strategy second stage
    equipment_list = [secondary_1_chiller]
    range = secondary_1_chiller.referenceCapacity.get
    equip_operation_cool_load.addLoadRange(range, equipment_list)

    # Control strategy third stage
    equipment_list = [primary_chiller, secondary_1_chiller]
    range = 999999999
    equip_operation_cool_load.addLoadRange(range, equipment_list)

  else
    raise "Failed to stage chillers, #{chillers.length} chillers found in the loop.Logic for staging chillers has only been done for either 2 or 3 chillers"
  end

  chilled_water_loop.setPlantEquipmentOperationCoolingLoad(equip_operation_cool_load)
end
surface_adjust_fenestration_in_a_surface(surface, reduction, model) click to toggle source

Adjust the fenestration area to the values specified by the reduction value in a surface

@param surface [OpenStudio::Model:Surface] openstudio surface object @param reduction [Double] ratio of adjustments @param model [OpenStudio::Model::Model] openstudio model @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Surface.rb, line 8
def surface_adjust_fenestration_in_a_surface(surface, reduction, model)
  # do nothing for cases when reduction == 1.0
  if reduction < 1.0
    surface.subSurfaces.each do |ss|
      next unless ss.subSurfaceType == 'FixedWindow' || ss.subSurfaceType == 'OperableWindow' || ss.subSurfaceType == 'GlassDoor'

      OpenstudioStandards::Geometry.sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, reduction)
    end
  elsif reduction > 1.0
    # case increase the window
    surface_wwr = OpenstudioStandards::Geometry.surface_get_window_to_wall_ratio(surface)
    if surface_wwr < 0.0001
      # In this case, we are adding fenestration
      wwr_adjusted = reduction - 1.0
      # add the value to additional properties in case of readjusting WWR for doors
      surface.additionalProperties.setFeature('added_wwr', wwr_adjusted)
    else
      wwr_adjusted = surface_wwr * reduction
    end
    # Save doors to a temp list
    door_list = []
    surface.subSurfaces.each do |sub|
      if sub.subSurfaceType == 'Door'
        door = {}
        door['name'] = sub.name.get
        door['vertices'] = sub.vertices
        door['construction'] = sub.construction.get
        door_list << door
      end
    end
    # remove all existing windows and set the window to wall ratio to the calculated new WWR
    # Remove all sub-surfaces including doors
    surface.subSurfaces.each(&:remove)
    # Apply default construction to the subsurface - the standard construction will be applied later.
    surface.setWindowToWallRatio(wwr_adjusted, 0.6, true)
    # add door back.
    unless door_list.empty?
      door_list.each do |door|
        os_door = OpenStudio::Model::SubSurface.new(door['vertices'], model)
        os_door.setName(door['name'])
        os_door.setConstruction(door['construction'])
        os_door.setSurface(surface)
      end
    end
  end
end
surface_get_wwr_reduction_ratio(multiplier, surface, wwr_building_type: 'All others', wwr_target: nil, total_wall_m2: 0.0, total_wall_with_fene_m2: 0.0, total_fene_m2: 0.0, total_plenum_wall_m2: 0.0) click to toggle source

Calculate the window to wall ratio reduction factor

@param multiplier [Double] multiplier of the wwr @param surface [OpenStudio::Model:Surface] the surface object @param wwr_building_type building type for wwr @param wwr_target [Double] target window to wall ratio @param total_wall_m2 [Double] total wall area of the category in m2. @param total_wall_with_fene_m2 [Double] total wall area of the category with fenestrations in m2. @param total_fene_m2 [Double] total fenestration area @return [Double] reduction factor

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 2468
def surface_get_wwr_reduction_ratio(multiplier,
                                    surface,
                                    wwr_building_type: 'All others',
                                    wwr_target: nil,
                                    total_wall_m2: 0.0, # prevent 0.0 division
                                    total_wall_with_fene_m2: 0.0,
                                    total_fene_m2: 0.0,
                                    total_plenum_wall_m2: 0.0)

  surface_name = surface.name.get
  surface_wwr = OpenstudioStandards::Geometry.surface_get_window_to_wall_ratio(surface)
  surface_dr = OpenstudioStandards::Geometry.surface_get_door_to_wall_ratio(surface)

  if multiplier < 1.0
    # Case when reduction is required
    reduction_ratio = 1.0 - multiplier
    OpenStudio.logFree(OpenStudio::Info, 'prm.log',
                       "Surface #{surface_name} WWR is #{surface_wwr}. Reduce its WWR to #{surface_wwr * reduction_ratio}%")
  else
    # Case when increase is required - takes the door area into consideration.
    # The target is to increase each surface to maximum 90% WWR deduct the total door area.
    exist_max_wwr = 0.0
    if total_wall_m2 > 0 then exist_max_wwr = total_wall_with_fene_m2 * 0.9 / total_wall_m2 end
    if exist_max_wwr < wwr_target
      # In this case, it is required to add vertical fenestration to other surfaces
      if surface_wwr < 0.001
        # delta_fenestration_surface_area / delta_wall_surface_area + 1.0 = increase_ratio for a surface with no windows.
        # ASSUMPTION!! assume adding windows to surface with no windows will never be window_m2 + door_m2 > surface_m2.
        reduction_ratio = ((wwr_target - exist_max_wwr) * total_wall_m2 / (total_wall_m2 - total_wall_with_fene_m2 - total_plenum_wall_m2)) + 1.0
        OpenStudio.logFree(OpenStudio::Info, 'prm.log',
                           "The max window to wall ratio is #{exist_max_wwr}, smaller than the target window to wall ratio #{wwr_target}.
                            Surface #{surface_name} has no fenestration subsurfaces. Adding new fenestration band with WWR of #{(reduction_ratio - 1) * 100}%")
      else
        # surface has fenestration - expand it to 90% WWR or surface area minus door area, whichever is smaller.
        if (1.0 - surface_dr) < 0.9
          # A negative reduction ratio as a flat to main function that this reduction ratio is adjusted by doors
          # and it is needed to adjust the WWR of the no fenestration surfaces to meet the lost
          reduction_ratio = (surface_dr - 1.0) / surface_wwr
        else
          reduction_ratio = 0.9 / surface_wwr
        end
        OpenStudio.logFree(OpenStudio::Info, 'prm.log',
                           "The max window to wall ratio is #{exist_max_wwr}, smaller than the target window to wall ratio #{wwr_target}.
                            Surface #{surface_name} will expand its WWR to 90%")
      end
    else
      # multiplier will be negative number thus resulting in > 1 reduction_ratio
      if surface_wwr < 0.001
        # 1.0 means remain the original form
        reduction_ratio = 1.0
      else
        reduction_ratio = multiplier
      end
    end
  end
  return reduction_ratio
end
thermal_zone_get_fan_power_limitations(thermal_zone, is_energy_recovery_required) click to toggle source

Determine the fan power limitation pressure drop adjustment Per Table 6.5.3.1-2 (90.1-2019)

@param thermal_zone

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ThermalZone.rb, line 8
def thermal_zone_get_fan_power_limitations(thermal_zone, is_energy_recovery_required)
  fan_pwr_adjustment_in_wc = 0

  # error if zone design air flow rate is not available
  if thermal_zone.model.version < OpenStudio::VersionString.new('3.6.0')
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.ashrae_90_1_prm.ThermalZone', 'Required ThermalZone method .autosizedDesignAirFlowRate is not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.')
  end

  # Get autosized zone design supply air flow rate
  dsn_zone_air_flow_m3_per_s = thermal_zone.autosizedDesignAirFlowRate.to_f
  dsn_zone_air_flow_cfm = OpenStudio.convert(dsn_zone_air_flow_m3_per_s, 'm^3/s', 'cfm').get

  # Retrieve credits from zone additional features
  # Fully ducted return and/or exhaust air systems
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_credit_fully_ducted')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_credit_fully_ducted').to_f
    adj_in_wc = 0.5 * mult
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for fully ducted return and/or exhaust air systems")
  end

  # Return and/or exhaust airflow control devices
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_credit_return_or_exhaust_flow_control')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_credit_return_or_exhaust_flow_control').to_f
    adj_in_wc = 0.5 * mult
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for return and/or exhaust airflow control devices")
  end

  # Exhaust filters, scrubbers, or other exhaust treatment
  if thermal_zone.additionalProperties.hasFeature('fan_power_credit_exhaust_treatment')
    adj_in_wc = thermal_zone.additionalProperties.getFeatureAsDouble('fan_power_credit_exhaust_treatment').to_f
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for exhaust filters, scrubbers, or other exhaust treatment")
  end

  # MERV 9 through 12
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_credit_filtration_m9to12')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_credit_filtration_m9to12').to_f
    adj_in_wc = 0.5 * mult
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for particulate Filtration Credit: MERV 9 through 12")
  end

  # MERV 13 through 15
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_credit_filtration_m13to15')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_credit_filtration_m13to15').to_f
    adj_in_wc = 0.9 * mult
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for particulate Filtration Credit: MERV 13 through 15")
  end

  # MERV 16 and greater and electronically enhanced filters
  if thermal_zone.additionalProperties.hasFeature('clean_filter_pressure_drop_for_fan_power_credit_filtration_m16plus')
    adj_in_wc = thermal_zone.additionalProperties.getFeatureAsDouble('clean_filter_pressure_drop_for_fan_power_credit_filtration_m16plus').to_f
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for particulate Filtration Credit: MERV 16 and greater and electronically enhanced filters")
  end

  # Carbon and other gas-phase air cleaners
  if thermal_zone.additionalProperties.hasFeature('fan_power_credit_gas_phase_cleaners')
    adj_in_wc = thermal_zone.additionalProperties.getFeatureAsDouble('fan_power_credit_gas_phase_cleaners').to_f
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for carbon and other gas-phase air cleaners")
  end

  # Biosafety cabinet
  if thermal_zone.additionalProperties.hasFeature('fan_power_credit_biosafety')
    adj_in_wc = thermal_zone.additionalProperties.getFeatureAsDouble('fan_power_credit_biosafety').to_f
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for biosafety cabinet")
  end

  # Energy recovery device, other than coil runaround loop
  if thermal_zone.additionalProperties.hasFeature('fan_power_credit_other_than_coil_runaround') && is_energy_recovery_required
    adj_in_wc = thermal_zone.additionalProperties.getFeatureAsDouble('fan_power_credit_other_than_coil_runaround').to_f
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for energy recovery device other than coil runaround loop")
  end

  # Coil runaround loop
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_credit_coil_runaround') && is_energy_recovery_required
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_credit_coil_runaround').to_f
    adj_in_wc = 0.6 * 2 * mult # for each stream
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for coil runaround loop")
  end

  # Evaporative humidifier/cooler in series with another cooling coil
  if thermal_zone.additionalProperties.hasFeature('fan_power_credit_evaporative_humidifier_or_cooler')
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', '--Added 0 in wc for evaporative humidifier/cooler in series with another coil as per Table G3.1.2.9 Note 2')
  end

  # Sound attenuation section (fans serving spoaces with design background noise goals below NC35)
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_credit_sound_attenuation')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_credit_sound_attenuation').to_f
    adj_in_wc = 0.15 * mult
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for sound attenuation section")
  end

  # Exhaust system serving fume hoods
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_credit_exhaust_serving_fume_hoods')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_credit_exhaust_serving_fume_hoods').to_f
    adj_in_wc = 0.35 * mult
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for exhaust system serving fume hoods")
  end

  # Laboratory and vivarium exhaust systems in high-rise buildings
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_credit_lab_or_vivarium_highrise_vertical_duct')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_credit_lab_or_vivarium_highrise_vertical_duct').to_f
    adj_in_wc = 0.35 * mult
    fan_pwr_adjustment_in_wc += adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Added #{adj_in_wc} in wc for laboratory and vivarium exhaust systems in high-rise buildings")
  end

  # Deductions
  # Systems without central cooling device
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_deduction_system_without_central_cooling_device')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_deduction_system_without_central_cooling_device').to_f
    adj_in_wc = 0.60 * mult
    fan_pwr_adjustment_in_wc -= adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Removed #{adj_in_wc} in wc for system without central cooling device")
  end

  # Systems without central heating device
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_deduction_system_without_central_heating_device')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_deduction_system_without_central_heating_device').to_f
    adj_in_wc = 0.30 * mult
    fan_pwr_adjustment_in_wc -= adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Removed #{adj_in_wc} in wc for system without central heating device")
  end

  # Systems with central electric resistance heat
  if thermal_zone.additionalProperties.hasFeature('has_fan_power_deduction_system_with_central_electric_resistance_heat')
    mult = thermal_zone.additionalProperties.getFeatureAsDouble('has_fan_power_deduction_system_with_central_electric_resistance_heat').to_f
    adj_in_wc = 0.20 * mult
    fan_pwr_adjustment_in_wc -= adj_in_wc
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "--Removed #{adj_in_wc} in wc for system with central electric resistance heat")
  end

  # Convert the pressure drop adjustment to brake horsepower (bhp)
  # assuming that all supply air passes through all devices
  return fan_pwr_adjustment_in_wc * dsn_zone_air_flow_cfm / 4131
end
thermal_zone_get_zone_fuels_for_occ_and_fuel_type(thermal_zone) click to toggle source

Identify if zone has district energy for occ_and_fuel_type method @param thermal_zone @return [String with applicable DistrictHeating and/or DistrictCooling

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ThermalZone.rb, line 158
def thermal_zone_get_zone_fuels_for_occ_and_fuel_type(thermal_zone)
  zone_fuels = ''

  # error if HVACComponent heating fuels method is not available
  if thermal_zone.model.version < OpenStudio::VersionString.new('3.6.0')
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.ashrae_90_1_prm.ThermalZone', 'Required HVACComponent methods .heatingFuelTypes and .coolingFuelTypes are not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.')
  end

  htg_fuels = thermal_zone.heatingFuelTypes.map(&:valueName)
  if htg_fuels.include?('DistrictHeating')
    zone_fuels = 'DistrictHeating'
  end
  clg_fuels = thermal_zone.coolingFuelTypes.map(&:valueName)
  if clg_fuels.include?('DistrictCooling')
    zone_fuels += 'DistrictCooling'
  end
  return zone_fuels
end
thermal_zone_prm_lab_delta_t(thermal_zone) click to toggle source

Specify supply to room delta for laboratory spaces based on 90.1 Appendix G Exception to G3.1.2.8.1

@param thermal_zone [OpenStudio::Model::ThermalZone] OpenStudio ThermalZone Object @return [Double] for zone with laboratory space, return 17; otherwise, return nil

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 944
def thermal_zone_prm_lab_delta_t(thermal_zone)
  # For labs, add 17 delta-T; otherwise, add 20 delta-T
  thermal_zone.spaces.each do |space|
    space_std_type = space.spaceType.get.standardsSpaceType.get
    if space_std_type == 'laboratory'
      return 17
    end
  end
  return nil
end
thermal_zone_prm_unitheater_design_supply_temperature(thermal_zone) click to toggle source

Specify supply air temperature setpoint for unit heaters based on 90.1 Appendix G G3.1.2.8.2

@param thermal_zone [OpenStudio::Model::ThermalZone] OpenStudio ThermalZone Object @return [Double] for zone with unit heaters, return design supply temperature; otherwise, return nil

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 931
def thermal_zone_prm_unitheater_design_supply_temperature(thermal_zone)
  thermal_zone.equipment.each do |eqt|
    if eqt.to_ZoneHVACUnitHeater.is_initialized
      return OpenStudio.convert(105, 'F', 'C').get
    end
  end
  return nil
end
update_power_equipment_credits(power_equipment, user_power_equipment, schedule_hash, space_type, user_data = nil) click to toggle source

Function update a power equipment schedule based on user data. This function works with both electric equipment and gas equipment and applies the ruleset on power equipment The function process user data including the fraction of controlled receptacles and receptacle power savings.

@param power_equipment [OpenStudio::Model::ElectricEquipment] or [OpenStudio::Model:GasEquipment] @param user_power_equipment [Hash] user data for the power equipment @param schedule_hash [Hash] power equipment operation schedules in a hash @param space_type [OpenStudio::Model:SpaceType] space type @param user_data [Hash] user space data @return [Boolean] returns true it adjusted, false if not

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb, line 291
def update_power_equipment_credits(power_equipment, user_power_equipment, schedule_hash, space_type, user_data = nil)
  exception_list = ['office - enclosed <= 250 sf', 'conference/meeting/multipurpose', 'copy/print',
                    'lounge/breakroom - all other', 'lounge/breakroom - healthcare facility', 'classroom/lecture/training - all other',
                    'classroom/lecture/training - preschool to 12th', 'office - open']

  receptacle_power_credits = 0.0
  # Check fraction_of_controlled_receptacles or receptacle_power_savings exist
  if user_power_equipment.key?('fraction_of_controlled_receptacles') && !user_power_equipment['fraction_of_controlled_receptacles'].nil?
    rc = user_power_equipment['fraction_of_controlled_receptacles'].to_f
    # receptacle power credits = percent of all controlled receptacles * 10%
    receptacle_power_credits = rc * 0.1
  elsif user_power_equipment.key?('receptacle_power_savings') && !user_power_equipment['receptacle_power_savings'].nil?
    receptacle_power_credits = user_power_equipment['receptacle_power_savings'].to_f
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ElectricEquipment', "#{power_equipment.name.get} has a user specified receptacle power saving credit #{receptacle_power_credits}. The modeler needs to make sure the credit is approved by a rating authority per Table G3.1 section 12.")
  end

  # process user space data
  if user_data.is_a?(Hash)
    if user_data.key?('num_std_ltg_types') && user_data['num_std_ltg_types'].to_f > 0
      adjusted_receptacle_power_credits = 0.0
      num_std_space_types = user_data['num_std_ltg_types'].to_i
      std_space_index = 0 # loop index
      # Loop through standard lighting type in a space
      while std_space_index < num_std_space_types
        std_space_index += 1
        # Retrieve data from user_data
        type_key = format('std_ltg_type%02d', std_space_index)
        frac_key = format('std_ltg_type_frac%02d', std_space_index)
        sub_space_type = user_data[type_key]
        next if exception_list.include?(sub_space_type)

        adjusted_receptacle_power_credits += user_data[frac_key].to_f * receptacle_power_credits
        # Adjust while loop condition factors
      end
      receptacle_power_credits = adjusted_receptacle_power_credits
    end
  elsif user_data.is_a?(String)
    if exception_list.include?(space_type.standardsSpaceType.get)
      # the space type is in the exception list, no credit to the space type
      receptacle_power_credits = 0.0
    end
  end

  # return false if no receptacle power credits
  unless receptacle_power_credits > 0.0
    return false
  end

  # Step 2: check if need to adjust the electric equipment schedule. - apply credit if needed.
  # get current schedule
  power_schedule = power_equipment.schedule.get
  power_schedule_name = power_schedule.name.get
  new_power_schedule_name = format("#{power_schedule_name}_%.4f", receptacle_power_credits)
  if schedule_hash.key?(new_power_schedule_name)
    # In this case, there is a schedule created, can retrieve the schedule object and reset in this space type.
    schedule_rule = schedule_hash[new_power_schedule_name]
    power_equipment.setSchedule(schedule_rule)
  else
    # In this case, create a new schedule
    # 1. Clone the existing schedule
    new_rule_set_schedule = deep_copy_schedule(new_power_schedule_name, power_schedule, receptacle_power_credits, space_type.model)
    if power_equipment.setSchedule(new_rule_set_schedule)
      schedule_hash[new_power_schedule_name] = new_rule_set_schedule
    end
  end
  return true
end
user_data_preprocessor(row) click to toggle source

Perform user data preprocessing @param [CSV::ROW] row 2D array for each row.

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 137
def user_data_preprocessor(row)
  new_array = []

  # Strip the strings in the value
  row.each do |sub_array|
    new_array << sub_array.collect { |e| e ? e.strip : e }
  end
  # @todo Future expansion can added to here.
  # Convert the 2d array to hash
  return new_array.to_h
end
user_data_validation(object_name, user_data) click to toggle source

@return [Boolean] true if data is valid, false if error found

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb, line 155
def user_data_validation(object_name, user_data)
  # 1. Check user_spacetype and user_space LPD total % = 1.0
  case object_name
  when UserDataFiles::BUILDING
    return check_userdata_building(object_name, user_data)
  when UserDataFiles::SPACE, UserDataFiles::SPACETYPE
    return check_userdata_space_and_spacetype(object_name, user_data)
  when UserDataFiles::ELECTRIC_EQUIPMENT
    return check_userdata_electric_equipment(object_name, user_data)
  when UserDataFiles::GAS_EQUIPMENT
    return check_userdata_gas_equipment(object_name, user_data)
  when UserDataFiles::LIGHTS
    return check_userdata_lights(object_name, user_data)
  when UserDataFiles::EXTERIOR_LIGHTS
    return check_userdata_exterior_lighting(object_name, user_data)
  when UserDataFiles::AIRLOOP_HVAC
    return check_userdata_airloop_hvac(object_name, user_data)
  when UserDataFiles::DESIGN_SPECIFICATION_OUTDOOR_AIR
    return check_userdata_outdoor_air(object_name, user_data)
  when UserDataFiles::AIRLOOP_HVAC_DOAS
    return check_userdata_airloop_hvac_doas(object_name, user_data)
  when UserDataFiles::ZONE_HVAC
    return check_userdata_zone_hvac(object_name, user_data)
  when UserDataFiles::THERMAL_ZONE
    return check_userdata_thermal_zone(object_name, user_data)
  when UserDataFiles::WATERUSE_CONNECTIONS
    return check_userdata_wateruse_connections(object_name, user_data)
  when UserDataFiles::WATERUSE_EQUIPMENT
    return check_userdata_wateruse_equipment(object_name, user_data)
  when UserDataFiles::WATERUSE_EQUIPMENT_DEFINITION
    return check_userdata_wateruse_equipment_definition(object_name, user_data)
  else
    return true
  end
end
user_model_air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac) click to toggle source

Check if an air loop in user model needs to have DCV per air loop related requiremends in ASHRAE 90.1-2019 6.4.3.8

@author Xuechen (Jerry) Lei, PNNL @param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @return [Boolean] flag of whether air loop in user model is required to have DCV

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 446
def user_model_air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac)
  # all zones in the same airloop in user model are set with the same value, so use the first zone under the loop
  dcv_airloop_user_exception = air_loop_hvac.thermalZones[0].additionalProperties.getFeatureAsBoolean('airloop user specified DCV exception').get
  return false if dcv_airloop_user_exception

  # check the following conditions at airloop level
  # has air economizer OR design outdoor airflow > 3000 cfm

  has_economizer = air_loop_hvac_economizer?(air_loop_hvac)

  if air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized
    oa_flow_m3_per_s = get_airloop_hvac_design_oa_from_sql(air_loop_hvac)
  else
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, DCV not applicable because it has no OA intake.")
    return false
  end
  oa_flow_cfm = OpenStudio.convert(oa_flow_m3_per_s, 'm^3/s', 'cfm').get

  any_zones_req_dcv = false
  air_loop_hvac.thermalZones.sort.each do |zone|
    if user_model_zone_demand_control_ventilation_required?(zone)
      any_zones_req_dcv = true
      break
    end
  end

  return true if any_zones_req_dcv && (has_economizer || (oa_flow_cfm > 3000))

  return false
end
user_model_zone_demand_control_ventilation_required?(thermal_zone) click to toggle source

Check if a zone in user model needs to have DCV per zone related requiremends in ASHRAE 90.1-2019 6.4.3.8 @author Xuechen (Jerry) Lei, PNNL @param thermal_zone [OpenStudio::Model::ThermalZone] the thermal zone @return [Boolean] flag of whether thermal zone in user model is required to have DCV

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb, line 481
def user_model_zone_demand_control_ventilation_required?(thermal_zone)
  dcv_zone_user_exception = thermal_zone.additionalProperties.getFeatureAsBoolean('zone user specified DCV exception').get
  return false if dcv_zone_user_exception

  # check the following conditions at zone level
  # zone > 500 sqft AND design occ > 25 ppl/ksqft

  area_served_m2 = 0
  num_people = 0
  thermal_zone.spaces.each do |space|
    area_served_m2 += space.floorArea
    num_people += space.numberOfPeople
  end
  area_served_ft2 = OpenStudio.convert(area_served_m2, 'm^2', 'ft^2').get
  occ_per_1000_ft2 = num_people / area_served_ft2 * 1000

  return true if (area_served_ft2 > 500) && (occ_per_1000_ft2 > 25)

  return false
end
zone_hvac_component_apply_prm_baseline_fan_power(zone_hvac_component) click to toggle source

Sets the fan power of zone level HVAC equipment (Fan coils, Unit Heaters, PTACs, PTHPs, VRF Terminals, WSHPs, ERVs) based on the W/cfm specified in the standard.

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

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ZoneHVACComponent.rb, line 9
def zone_hvac_component_apply_prm_baseline_fan_power(zone_hvac_component)
  OpenStudio.logFree(OpenStudio::Debug, 'openstudio.ashrae_90_1_prm.ZoneHVACComponent', "Setting fan power for #{zone_hvac_component.name}.")

  # Convert this to the actual class type
  zone_hvac = if zone_hvac_component.to_ZoneHVACFourPipeFanCoil.is_initialized
                zone_hvac_component.to_ZoneHVACFourPipeFanCoil.get
              elsif zone_hvac_component.to_ZoneHVACUnitHeater.is_initialized
                zone_hvac_component.to_ZoneHVACUnitHeater.get
              elsif zone_hvac_component.to_ZoneHVACPackagedTerminalAirConditioner.is_initialized
                zone_hvac_component.to_ZoneHVACPackagedTerminalAirConditioner.get
              elsif zone_hvac_component.to_ZoneHVACPackagedTerminalHeatPump.is_initialized
                zone_hvac_component.to_ZoneHVACPackagedTerminalHeatPump.get
              elsif zone_hvac_component.to_ZoneHVACTerminalUnitVariableRefrigerantFlow.is_initialized
                zone_hvac_component.to_ZoneHVACTerminalUnitVariableRefrigerantFlow.get
              elsif zone_hvac_component.to_ZoneHVACWaterToAirHeatPump.is_initialized
                zone_hvac_component.to_ZoneHVACWaterToAirHeatPump.get
              elsif zone_hvac_component.to_ZoneHVACEnergyRecoveryVentilator.is_initialized
                zone_hvac_component.to_ZoneHVACEnergyRecoveryVentilator.get
              end

  # Do nothing for other types of zone HVAC equipment
  return false if zone_hvac.nil?

  # Do nothing if zone hav component isn't assigned to thermal zone
  return false unless zone_hvac.thermalZone.is_initialized

  # Get baseline system type
  system_type = zone_hvac.thermalZone.get.additionalProperties.getFeatureAsString('baseline_system_type').get

  # Get non-mechanically cooled flag
  if zone_hvac.thermalZone.get.additionalProperties.hasFeature('non_mechanically_cooled')
    nmc_flag = zone_hvac.thermalZone.get.additionalProperties.getFeatureAsString('non_mechanically_cooled')
  else
    nmc_flag = false
  end

  # Get the fan
  fan = if zone_hvac.supplyAirFan.to_FanConstantVolume.is_initialized
          zone_hvac.supplyAirFan.to_FanConstantVolume.get
        elsif zone_hvac.supplyAirFan.to_FanVariableVolume.is_initialized
          zone_hvac.supplyAirFan.to_FanVariableVolume.get
        elsif zone_hvac.supplyAirFan.to_FanOnOff.is_initialized
          zone_hvac.supplyAirFan.to_FanOnOff.get
        elsif zone_hvac.supplyAirFan.to_FanSystemModel.is_initialized
          zone_hvac.supplyAirFan.to_FanSystemModel.get
        end

  if system_type == 'SZ_CV' # System 12, 13
    # Get design supply air flow rate (whether autosized or hard-sized)
    dsn_air_flow_m3_per_s = 0
    dsn_air_flow_cfm = 0
    if fan.maximumFlowRate.is_initialized
      dsn_air_flow_m3_per_s = fan.maximumFlowRate.get
    elsif fan.isMaximumFlowRateAutosized
      dsn_air_flow_m3_per_s = fan.autosizedMaximumFlowRate.get
    end
    dsn_air_flow_cfm = OpenStudio.convert(dsn_air_flow_m3_per_s, 'm^3/s', 'cfm').get

    # Determine allowable fan BHP and power
    allowable_fan_bhp = (0.00094 * dsn_air_flow_cfm) + thermal_zone_get_fan_power_limitations(zone_hvac.thermalZone.get, false)
    fan_apply_standard_minimum_motor_efficiency(fan, allowable_fan_bhp)
    allowable_power_w = (allowable_fan_bhp * 746) / fan.motorEfficiency

    # Modify fan pressure rise to match target fan power
    fan_adjust_pressure_rise_to_meet_fan_power(fan, allowable_power_w)
  else # System 1, 2
    # Determine the W/cfm
    fan_efficacy_w_per_cfm = 0.0
    case system_type
    when 'PTAC', 'PTHP'
      fan_efficacy_w_per_cfm = 0.3 # System 9, 10
    when 'Gas_Furnace', 'Electric_Furnace'
      # Zone heater cannot provide cooling
      if nmc_flag && !zone_hvac_component.to_ZoneHVACUnitHeater.is_initialized
        fan_efficacy_w_per_cfm = 0.054
      else
        fan_efficacy_w_per_cfm = 0.3
      end
    else OpenStudio.logFree(OpenStudio::Error, 'openstudio.ashrae_90_1_prm.ZoneHVACComponent', 'Zone HVAC system fan power lookup missing.')
    end

    # Convert efficacy to metric
    fan_efficacy_w_per_m3_per_s = OpenStudio.convert(fan_efficacy_w_per_cfm, 'm^3/s', 'cfm').get

    # Get the maximum flow rate through the fan
    max_air_flow_rate = nil
    if fan.maximumFlowRate.is_initialized
      max_air_flow_rate = fan.maximumFlowRate.get
    elsif fan.autosizedMaximumFlowRate.is_initialized
      max_air_flow_rate = fan.autosizedMaximumFlowRate.get
    end
    max_air_flow_rate_cfm = OpenStudio.convert(max_air_flow_rate, 'm^3/s', 'ft^3/min').get

    # Set the impeller efficiency
    fan_change_impeller_efficiency(fan, fan_baseline_impeller_efficiency(fan))

    # Get fan BHP
    fan_bhp = fan_brake_horsepower(fan)

    # Set the motor efficiency, preserving the impeller efficiency.
    # For zone HVAC fans, a bhp lookup of 0.5bhp is always used because
    # they are assumed to represent a series of small fans in reality.
    fan_apply_standard_minimum_motor_efficiency(fan, fan_bhp)

    # Calculate a new pressure rise to hit the target W/cfm
    fan_tot_eff = fan.fanEfficiency
    fan_rise_new_pa = fan_efficacy_w_per_m3_per_s * fan_tot_eff
    fan.setPressureRise(fan_rise_new_pa)

    # Calculate the newly set efficacy
    fan_power_new_w = fan_rise_new_pa * max_air_flow_rate / fan_tot_eff
    fan_efficacy_new_w_per_cfm = fan_power_new_w / max_air_flow_rate_cfm
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.ashrae_90_1_prm.ZoneHVACComponent', "For #{zone_hvac_component.name}: fan efficacy set to #{fan_efficacy_new_w_per_cfm.round(2)} W/cfm.")
  end
  return true
end
zone_hvac_unoccupied_threshold() click to toggle source

Default occupancy fraction threshold for determining if the spaces served by the zone hvac are occupied

@return [Double] unoccupied threshold

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ZoneHVACComponent.rb, line 129
def zone_hvac_unoccupied_threshold
  # Use 10% based on PRM-RM
  return 0.10
end

Private Instance Methods

get_userdata(user_data_csv) click to toggle source

Check if the PRM process uses user data. The function returns a hash when

  1. There is a matching user data

  2. The matching user data is not nil saved in the @standards_data

  3. The matching user data hash is not empty

The function returns nil if none of the above matched.

@param user_data_csv [String] the name of the user data csv file @return [hash | nil] Returns hash or nil.

# File lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb, line 3355
def get_userdata(user_data_csv)
  return @standards_data.key?(user_data_csv) && @standards_data[user_data_csv].length >= 1 ? @standards_data[user_data_csv] : nil
end