Class: ASHRAE901PRM Abstract

Inherits:
Standard show all
Includes:
ASHRAE901PRMFan, ASHRAEPRMCoilDX
Defined in:
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Space.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Surface.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanOnOff.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ThermalZone.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlanarSurface.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.BoilerHotWater.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingGas.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanZoneExhaust.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanConstantVolume.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanVariableVolume.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ZoneHVACComponent.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ChillerElectricEIR.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.HeatExchangerSensLat.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXTwoSpeed.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXSingleSpeed.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingDXSingleSpeed.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctVAVReheat.rb,
lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctParallelPIUReheat.rb

Overview

This class is abstract.

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.

Direct Known Subclasses

ASHRAE901PRM2019

Constant Summary

Constants inherited from Standard

Standard::STANDARDS_LIST

Instance Attribute Summary

Attributes inherited from Standard

#space_multiplier_map, #standards_data, #template

Model collapse

Space collapse

SpaceType collapse

AirLoopHVAC collapse

ThermalZone collapse

PlanarSurface collapse

BoilerHotWater collapse

ZoneHVACComponent collapse

ChillerElectricEIR collapse

HeatExchangerSensLat collapse

AirTerminalSingleDuctVAVReheat collapse

AirTerminalSingleDuctParallelPIUReheat collapse

Instance Method Summary collapse

Methods included from ASHRAEPRMCoilDX

#coil_dx_find_search_criteria, #coil_dx_subcategory

Methods included from ASHRAE901PRMFan

#fan_standard_minimum_motor_efficiency_and_size

Methods inherited from Standard

#adjust_sizing_system, #afue_to_thermal_eff, #air_loop_hvac_add_motorized_oa_damper, #air_loop_hvac_adjust_minimum_vav_damper_positions, #air_loop_hvac_adjust_minimum_vav_damper_positions_outpatient, #air_loop_hvac_apply_baseline_fan_pressure_rise, #air_loop_hvac_apply_economizer_integration, #air_loop_hvac_apply_economizer_limits, #air_loop_hvac_apply_energy_recovery_ventilator, #air_loop_hvac_apply_maximum_reheat_temperature, #air_loop_hvac_apply_multizone_vav_outdoor_air_sizing, #air_loop_hvac_apply_prm_baseline_controls, #air_loop_hvac_apply_prm_baseline_economizer, #air_loop_hvac_apply_prm_sizing_temperatures, #air_loop_hvac_apply_single_zone_controls, #air_loop_hvac_apply_standard_controls, #air_loop_hvac_apply_vav_damper_action, #air_loop_hvac_data_center_area_served, #air_loop_hvac_dcv_required_when_erv, #air_loop_hvac_demand_control_ventilation_limits, #air_loop_hvac_demand_control_ventilation_required?, #air_loop_hvac_disable_multizone_vav_optimization, #air_loop_hvac_dx_cooling?, #air_loop_hvac_economizer?, #air_loop_hvac_economizer_required?, #air_loop_hvac_economizer_type_allowable?, #air_loop_hvac_enable_demand_control_ventilation, #air_loop_hvac_enable_multizone_vav_optimization, #air_loop_hvac_enable_optimum_start, #air_loop_hvac_enable_supply_air_temperature_reset_delta, #air_loop_hvac_enable_supply_air_temperature_reset_outdoor_temperature, #air_loop_hvac_enable_supply_air_temperature_reset_warmest_zone, #air_loop_hvac_energy_recovery?, #air_loop_hvac_energy_recovery_ventilator_heat_exchanger_type, #air_loop_hvac_energy_recovery_ventilator_required?, #air_loop_hvac_energy_recovery_ventilator_type, #air_loop_hvac_find_design_supply_air_flow_rate, #air_loop_hvac_floor_area_served, #air_loop_hvac_floor_area_served_exterior_zones, #air_loop_hvac_floor_area_served_interior_zones, #air_loop_hvac_get_occupancy_schedule, #air_loop_hvac_get_relief_fan_power, #air_loop_hvac_get_return_fan_power, #air_loop_hvac_get_supply_fan, #air_loop_hvac_get_supply_fan_power, #air_loop_hvac_has_parallel_piu_air_terminals?, #air_loop_hvac_has_simple_transfer_air?, #air_loop_hvac_humidifier_count, #air_loop_hvac_include_cooling_coil?, #air_loop_hvac_include_economizer?, #air_loop_hvac_include_evaporative_cooler?, #air_loop_hvac_include_hydronic_cooling_coil?, #air_loop_hvac_include_unitary_system?, #air_loop_hvac_include_wshp?, #air_loop_hvac_minimum_zone_ventilation_efficiency, #air_loop_hvac_motorized_oa_damper_limits, #air_loop_hvac_motorized_oa_damper_required?, #air_loop_hvac_multi_stage_dx_cooling?, #air_loop_hvac_multizone_vav_optimization_required?, #air_loop_hvac_remove_erv, #air_loop_hvac_remove_motorized_oa_damper, #air_loop_hvac_residential_area_served, #air_loop_hvac_return_air_plenum, #air_loop_hvac_set_minimum_damper_position, #air_loop_hvac_single_zone_controls_num_stages, #air_loop_hvac_standby_mode_occupancy_control, #air_loop_hvac_static_pressure_reset_required?, #air_loop_hvac_supply_air_temperature_reset_required?, #air_loop_hvac_supply_return_exhaust_relief_fans, #air_loop_hvac_system_fan_brake_horsepower, #air_loop_hvac_system_multiplier, #air_loop_hvac_terminal_reheat?, #air_loop_hvac_total_cooling_capacity, #air_loop_hvac_unitary_system?, #air_loop_hvac_unoccupied_fan_shutoff_required?, #air_loop_hvac_vav_system?, #air_terminal_single_duct_parallel_piu_reheat_apply_minimum_primary_airflow_fraction, #air_terminal_single_duct_parallel_piu_reheat_apply_prm_baseline_fan_power, #air_terminal_single_duct_parallel_reheat_piu_minimum_primary_airflow_fraction, #air_terminal_single_duct_vav_reheat_apply_initial_prototype_damper_position, #air_terminal_single_duct_vav_reheat_minimum_damper_position, #air_terminal_single_duct_vav_reheat_reheat_type, #air_terminal_single_duct_vav_reheat_set_heating_cap, #apply_lighting_schedule, #apply_limit_to_subsurface_ratio, #boiler_hot_water_find_capacity, #boiler_hot_water_find_design_water_flow_rate, #boiler_hot_water_find_search_criteria, #boiler_hot_water_standard_minimum_thermal_efficiency, build, #chiller_electric_eir_find_capacity, #chiller_electric_eir_find_search_criteria, #chiller_electric_eir_get_cap_f_t_curve_name, #chiller_electric_eir_get_eir_f_plr_curve_name, #chiller_electric_eir_get_eir_f_t_curve_name, #chiller_electric_eir_standard_minimum_full_load_efficiency, #coil_cooling_dx_multi_speed_apply_efficiency_and_curves, #coil_cooling_dx_multi_speed_find_capacity, #coil_cooling_dx_multi_speed_standard_minimum_cop, #coil_cooling_water_to_air_heat_pump_apply_efficiency_and_curves, #coil_cooling_water_to_air_heat_pump_find_capacity, #coil_cooling_water_to_air_heat_pump_standard_minimum_cop, #coil_heating_dx_multi_speed_apply_efficiency_and_curves, #coil_heating_dx_single_speed_apply_defrost_eir_curve_limits, #coil_heating_gas_additional_search_criteria, #coil_heating_gas_apply_prototype_efficiency, #coil_heating_gas_find_capacity, #coil_heating_gas_multi_stage_apply_efficiency_and_curves, #coil_heating_gas_multi_stage_find_capacity, #coil_heating_gas_multi_stage_find_search_criteria, #coil_heating_water_to_air_heat_pump_apply_efficiency_and_curves, #coil_heating_water_to_air_heat_pump_find_capacity, #coil_heating_water_to_air_heat_pump_standard_minimum_cop, #combustion_eff_to_thermal_eff, #controller_water_coil_set_convergence_limits, #convert_curve_biquadratic, #cooling_tower_single_speed_apply_efficiency_and_curves, #cooling_tower_two_speed_apply_efficiency_and_curves, #cooling_tower_variable_speed_apply_efficiency_and_curves, #cop_heating_to_cop_heating_no_fan, #cop_no_fan_to_eer, #cop_no_fan_to_seer, #cop_to_eer, #cop_to_kw_per_ton, #cop_to_seer, #create_air_conditioner_variable_refrigerant_flow, #create_boiler_hot_water, #create_central_air_source_heat_pump, #create_coil_cooling_dx_single_speed, #create_coil_cooling_dx_two_speed, #create_coil_cooling_water, #create_coil_cooling_water_to_air_heat_pump_equation_fit, #create_coil_heating_dx_single_speed, #create_coil_heating_electric, #create_coil_heating_gas, #create_coil_heating_water, #create_coil_heating_water_to_air_heat_pump_equation_fit, #create_curve_bicubic, #create_curve_biquadratic, #create_curve_cubic, #create_curve_exponent, #create_curve_quadratic, #create_fan_constant_volume, #create_fan_constant_volume_from_json, #create_fan_on_off, #create_fan_on_off_from_json, #create_fan_variable_volume, #create_fan_variable_volume_from_json, #create_fan_zone_exhaust, #create_fan_zone_exhaust_from_json, #define_space_multiplier, #eer_to_cop, #eer_to_cop_no_fan, #ems_friendly_name, #enthalpy_recovery_ratio_design_to_typical_adjustment, #fan_constant_volume_airloop_fan_pressure_rise, #fan_constant_volume_apply_prototype_fan_pressure_rise, #fan_on_off_airloop_or_unitary_fan_pressure_rise, #fan_on_off_apply_prototype_fan_pressure_rise, #fan_variable_volume_airloop_fan_pressure_rise, #fan_variable_volume_apply_prototype_fan_pressure_rise, #fan_variable_volume_cooling_system_type, #fan_variable_volume_part_load_fan_power_limitation_capacity_limit, #fan_variable_volume_set_control_type, #fan_zone_exhaust_apply_prototype_fan_pressure_rise, #find_exposed_conditioned_roof_surfaces, #find_exposed_conditioned_vertical_surfaces, #find_highest_roof_centre, #fluid_cooler_apply_minimum_power_per_flow, #get_avg_of_other_zones, #get_default_surface_cons_from_surface_type, #get_fan_object_for_airloop, #get_fan_schedule_for_each_zone, #get_group_heat_types, #get_outdoor_subsurface_ratio, #get_weekday_values_from_8760, #get_wtd_avg_of_other_zones, #headered_pumps_variable_speed_set_control_type, #heat_exchanger_air_to_air_sensible_and_latent_apply_effectiveness, #heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_efficiency, #heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_efficiency_enthalpy_recovery_ratio, #heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_nominal_electric_power, #heat_exchanger_air_to_air_sensible_and_latent_enthalpy_recovery_ratio_to_effectiveness, #heat_exchanger_air_to_air_sensible_and_latent_prototype_default_fan_efficiency, #hspf_to_cop, #hspf_to_cop_no_fan, #interior_lighting_get_prm_data, #kw_per_ton_to_cop, #load_hvac_map, #load_initial_osm, #make_ruleset_sched_from_8760, #make_week_ruleset_sched_from_168, #model_add_baseboard, #model_add_cav, #model_add_central_air_source_heat_pump, #model_add_chw_loop, #model_add_construction, #model_add_construction_set, #model_add_crac, #model_add_crah, #model_add_curve, #model_add_cw_loop, #model_add_data_center_hvac, #model_add_data_center_load, #model_add_daylighting_controls, #model_add_district_ambient_loop, #model_add_doas, #model_add_doas_cold_supply, #model_add_elevator, #model_add_elevators, #model_add_evap_cooler, #model_add_exhaust_fan, #model_add_four_pipe_fan_coil, #model_add_furnace_central_ac, #model_add_ground_hx_loop, #model_add_high_temp_radiant, #model_add_hp_loop, #model_add_hvac, #model_add_hvac_system, #model_add_hw_loop, #model_add_ideal_air_loads, #model_add_low_temp_radiant, #model_add_material, #model_add_minisplit_hp, #model_add_plant_supply_water_temperature_control, #model_add_prm_baseline_system, #model_add_psz_ac, #model_add_psz_vav, #model_add_ptac, #model_add_pthp, #model_add_pvav, #model_add_pvav_pfp_boxes, #model_add_radiant_basic_controls, #model_add_radiant_proportional_controls, #model_add_refrigeration_case, #model_add_refrigeration_compressor, #model_add_refrigeration_system, #model_add_refrigeration_walkin, #model_add_residential_erv, #model_add_residential_ventilator, #model_add_schedule, #model_add_split_ac, #model_add_swh, #model_add_swh_end_uses_by_space, #model_add_transformer, #model_add_typical_exterior_lights, #model_add_typical_refrigeration, #model_add_typical_swh, #model_add_unitheater, #model_add_vav_pfp_boxes, #model_add_vav_reheat, #model_add_vrf, #model_add_water_source_hp, #model_add_waterside_economizer, #model_add_window_ac, #model_add_zone_erv, #model_add_zone_heat_cool_request_count_program, #model_add_zone_ventilation, #model_apply_infiltration_standard, #model_apply_prm_baseline_window_to_wall_ratio, #model_apply_prm_sizing_parameters, #model_create_exterior_lighting_area_length_count_hash, #model_create_prm_any_baseline_building, #model_create_prm_baseline_building, #model_create_prm_baseline_building_requires_proposed_model_sizing_run, #model_create_prm_baseline_building_requires_vlt_sizing_run, #model_create_prm_proposed_building, #model_create_prm_stable_baseline_building, #model_create_space_type_hash, #model_create_story_hash, #model_cw_loop_cooling_tower_fan_type, #model_effective_num_stories, #model_elevator_fan_pwr, #model_elevator_lift_power, #model_elevator_lighting_pct_incandescent, #model_eliminate_outlier_zones, #model_find_and_add_construction, #model_find_ashrae_hot_water_demand, #model_find_climate_zone_set, #model_find_icc_iecc_2015_hot_water_demand, #model_find_icc_iecc_2015_internal_loads, #model_find_object, #model_find_objects, #model_find_prototype_floor_area, #model_find_target_eui, #model_find_target_eui_by_end_use, #model_find_water_heater_capacity_volume_and_parasitic, #model_get_baseline_system_type_by_zone, #model_get_building_properties, #model_get_climate_zone_set_from_list, #model_get_construction_properties, #model_get_construction_set, #model_get_district_heating_zones, #model_get_lookup_name, #model_get_or_add_ambient_water_loop, #model_get_or_add_chilled_water_loop, #model_get_or_add_ground_hx_loop, #model_get_or_add_heat_pump_loop, #model_get_or_add_hot_water_loop, #model_is_hvac_autosized, #model_legacy_results_by_end_use_and_fuel_type, #model_make_name, #model_prm_skylight_to_roof_ratio_limit, #model_process_results_for_datapoint, #model_remap_office, #model_remove_external_shading_devices, #model_remove_prm_ems_objects, #model_remove_prm_hvac, #model_remove_unused_resource_objects, #model_set_vav_terminals_to_control_for_outdoor_air, #model_system_outdoor_air_sizing_vrp_method, #model_two_pipe_loop, #model_typical_display_case_zone, #model_typical_hvac_system_type, #model_typical_walkin_zone, #model_validate_standards_spacetypes_in_model, #model_ventilation_method, #model_walkin_freezer_latent_case_credit_curve, #model_zones_with_occ_and_fuel_type, #plant_loop_adiabatic_pipes_only, #plant_loop_apply_prm_baseline_chilled_water_pumping_type, #plant_loop_apply_prm_baseline_chilled_water_temperatures, #plant_loop_apply_prm_baseline_condenser_water_pumping_type, #plant_loop_apply_prm_baseline_condenser_water_temperatures, #plant_loop_apply_prm_baseline_hot_water_pumping_type, #plant_loop_apply_prm_baseline_hot_water_temperatures, #plant_loop_apply_prm_baseline_pumping_type, #plant_loop_apply_prm_baseline_temperatures, #plant_loop_apply_prm_number_of_boilers, #plant_loop_apply_standard_controls, #plant_loop_capacity_w_by_maxflow_and_delta_t_forwater, #plant_loop_enable_supply_water_temperature_reset, #plant_loop_find_maximum_loop_flow_rate, #plant_loop_prm_baseline_condenser_water_temperatures, #plant_loop_supply_water_temperature_reset_required?, #plant_loop_swh_loop?, #plant_loop_swh_system_type, #plant_loop_total_cooling_capacity, #plant_loop_total_floor_area_served, #plant_loop_total_heating_capacity, #plant_loop_total_rated_w_per_gpm, #plant_loop_variable_flow_system?, #prototype_apply_condenser_water_temperatures, #prototype_condenser_water_temperatures, #pump_variable_speed_control_type, #pump_variable_speed_get_control_type, #pump_variable_speed_set_control_type, register_standard, #remove_air_loops, #remove_all_hvac, #remove_all_plant_loops, #remove_all_zone_equipment, #remove_hvac, #remove_plant_loops, #remove_unused_curves, #remove_vrf, #remove_zone_equipment, #rename_air_loop_nodes, #rename_plant_loop_nodes, #safe_load_model, #seer_to_cop, #seer_to_cop_no_fan, #set_maximum_fraction_outdoor_air_schedule, #space_add_daylighting_controls, #space_conditioning_category, #space_daylighted_area_window_width, #space_daylighted_areas, #space_daylighting_control_required?, #space_daylighting_fractions_and_windows, #space_get_equip_annual_array, #space_get_loads_for_all_equips, #space_infiltration_rate_75_pa, #space_internal_load_annual_array, #space_occupancy_annual_array, #space_remove_daylighting_controls, #space_sidelighting_effective_aperture, #space_skylight_effective_aperture, #space_type_apply_int_loads_prm, #space_type_apply_internal_load_schedules, #space_type_apply_rendering_color, #space_type_get_construction_properties, #space_type_get_standards_data, #standard_design_sizing_temperatures, #standards_lookup_table_first, #standards_lookup_table_many, #strip_model, #sub_surface_create_centered_subsurface_from_scaled_surface, #sub_surface_create_scaled_subsurfaces_from_surface, #surface_subsurface_ua, #thermal_eff_to_afue, #thermal_eff_to_comb_eff, #thermal_zone_add_exhaust, #thermal_zone_add_exhaust_fan_dcv, #thermal_zone_apply_prm_baseline_supply_temperatures, #thermal_zone_conditioning_category, #thermal_zone_demand_control_ventilation_limits, #thermal_zone_demand_control_ventilation_required?, #thermal_zone_exhaust_fan_dcv_required?, #thermal_zone_fossil_or_electric_type, #thermal_zone_get_annual_operating_hours, #thermal_zone_infer_system_type, #thermal_zone_occupancy_eflh, #thermal_zone_occupancy_type, #thermal_zone_peak_internal_load, #thermal_zone_prm_baseline_cooling_design_supply_temperature, #thermal_zone_prm_baseline_heating_design_supply_temperature, #true?, #validate_initial_model, #water_heater_convert_energy_factor_to_thermal_efficiency_and_ua, #water_heater_convert_uniform_energy_factor_to_energy_factor, #water_heater_determine_sub_type, #water_heater_mixed_additional_search_criteria, #water_heater_mixed_apply_efficiency, #water_heater_mixed_apply_prm_baseline_fuel_type, #water_heater_mixed_find_capacity, #water_heater_mixed_get_efficiency_requirement, #zone_hvac_component_apply_standard_controls, #zone_hvac_component_apply_vestibule_heating_control, #zone_hvac_component_occupancy_ventilation_control, #zone_hvac_component_prm_baseline_fan_efficacy, #zone_hvac_component_vestibule_heating_control_required?, #zone_hvac_get_fan_object, #zone_hvac_model_standby_mode_occupancy_control

Methods included from PrototypeFan

apply_base_fan_variables, #create_fan_by_name, #get_fan_from_standards, #lookup_fan_curve_coefficients_from_json, #prototype_fan_apply_prototype_fan_efficiency

Methods included from CoilDX

#coil_dx_find_search_criteria, #coil_dx_heat_pump?, #coil_dx_heating_type, #coil_dx_subcategory

Methods included from CoolingTower

#cooling_tower_apply_minimum_power_per_flow, #cooling_tower_apply_minimum_power_per_flow_gpm_limit

Methods included from Pump

#pump_apply_prm_pressure_rise_and_motor_efficiency, #pump_apply_standard_minimum_motor_efficiency, #pump_brake_horsepower, #pump_motor_horsepower, #pump_pumppower, #pump_rated_w_per_gpm, #pump_standard_minimum_motor_efficiency_and_size

Methods included from Fan

#fan_adjust_pressure_rise_to_meet_fan_power, #fan_apply_standard_minimum_motor_efficiency, #fan_baseline_impeller_efficiency, #fan_brake_horsepower, #fan_change_impeller_efficiency, #fan_change_motor_efficiency, #fan_design_air_flow, #fan_fanpower, #fan_motor_horsepower, #fan_rated_w_per_cfm, #fan_small_fan?, #fan_standard_minimum_motor_efficiency_and_size

Constructor Details

#initializeASHRAE901PRM

Returns a new instance of ASHRAE901PRM.



8
9
10
11
12
# 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

Instance Method Details

#add_ems_for_multiple_chiller_pumps_w_secondary_plant(model, primary_plant) ⇒ Object

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.

Parameters:

  • model (OpenStudio::Model)

    OpenStudio model with plant loops

  • primary_plant (OpenStudio::Model::PlantLoop)

    Primary chilled water loop with chillers



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# 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) ⇒ Object

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.

Parameters:

  • model (OpenStudio::Model)

    OpenStudio model with plant loops

  • sorted_chiller_list (Array)

    Array of chillers in primary_plant sorted by capacity

  • primary_plant (OpenStudio::Model::PlantLoop)

    Primary chilled water loop with chillers



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# 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) ⇒ Object

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.

Parameters:

  • model (OpenStudio::Model)

    OpenStudio model with plant loops

  • sorted_chiller_list (Array)

    Array of chillers in primary_plant sorted by capacity

  • primary_plant (OpenStudio::Model::PlantLoop)

    Primary chilled water loop with chillers



461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# 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) ⇒ Double

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

Returns:

  • (Double)

    allowable fan system brake horsepower units = horsepower



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# 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') ⇒ OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent

Set effectiveness value of an ERV’s heat exchanger

Parameters:

  • erv (OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent)

    ERV to apply efficiency values

  • erv_type (String) (defaults to: 'ERV')

    ERV type: ERV or HRV

  • heat_exchanger_type (String) (defaults to: 'Rotary')

    Heat exchanger type: Rotary or Plate

Returns:

  • (OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent)

    ERV to apply efficiency values



666
667
668
669
670
# 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) ⇒ Boolean

Set the minimum VAV damper positions.

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

  • has_ddc (Boolean) (defaults to: true)

    if true, will assume that there is DDC control of vav terminals. If false, assumes otherwise.

Returns:

  • (Boolean)

    returns true if successful, false if not



557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# 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) ⇒ Object

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.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# 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) ⇒ Array<Double>

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

Returns:

  • (Array<Double>)
    drybulb_limit_f, enthalpy_limit_btu_per_lb, dewpoint_limit_f


580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
# 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) ⇒ Boolean

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

  • min_occ_pct (Double) (defaults to: 0.05)

    the fractional value below which the system will be considered unoccupied.

Returns:

  • (Boolean)

    returns true if successful, false if not



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 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) ⇒ Double

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

  • pct_oa (Double)

    percentage of outdoor air

Returns:

  • (Double)

    the flow rate above which an ERV is required. if nil, ERV is never required.



679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
# File 'lib/openstudio-standards/standards/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) ⇒ Double

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

Returns:

  • (Double)

    fan power limitation pressure drop adjustment, in units of horsepower



621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
# 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) ⇒ Boolean

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

Returns:

  • (Boolean)

    returns true if required, false if not



123
124
125
# 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) ⇒ Boolean

Determine if the system is a multizone VAV system

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

Returns:

  • (Boolean)

    Returns true if required, false if not.



95
96
97
98
99
# 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) ⇒ Boolean

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

Returns:

  • (Boolean)

    returns true if required, false if not



202
203
204
# 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) ⇒ Boolean

Determine if an economizer is required per the PRM.

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

Returns:

  • (Boolean)

    returns true if required, false if not



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# 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) ⇒ Array<Double>

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

Returns:

  • (Array<Double>)
    economizer_type, drybulb_limit_f, enthalpy_limit_btu_per_lb, dewpoint_limit_f


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# 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_typeString name of appropriate curve for this code version

Set fan curve for stable baseline to be VSD with fixed static pressure setpoint

Returns:

  • (String name of appropriate curve for this code version)

    String name of appropriate curve for this code version



193
194
195
# 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_thresholdObject

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



112
113
114
115
# 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) ⇒ String

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

Returns:

  • (String)

    the damper control type: Single Maximum, Dual Maximum



106
107
108
109
# 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_fractionDouble

Return the fan on flow fraction for a parallel PIU terminal

Returns:

  • (Double)

    returns nil or a float representing the fraction



7
8
9
# 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) ⇒ Boolean

TODO:

remove exception where older vintages don’t have minimum positions adjusted.

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

Parameters:

  • air_terminal_single_duct_vav_reheat (OpenStudio::Model::AirTerminalSingleDuctVAVReheat)

    the air terminal object

  • zone_min_oa (Double) (defaults to: nil)

    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.

  • has_ddc (Boolean) (defaults to: true)

    whether or not there is DDC control of the VAV terminal, which impacts the minimum damper position requirement.

Returns:

  • (Boolean)

    returns true if successful, false if not



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 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) ⇒ Boolean

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

Returns:

  • (Boolean)

    flag of whether the air loop in baseline is required to have DCV

Author:

  • Xuechen (Jerry) Lei, PNNL



507
508
509
510
511
512
513
514
515
# 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) ⇒ Boolean

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

Parameters:

  • thermal_zone (OpenStudio::Model::ThermalZone)

    the thermal zone

Returns:

  • (Boolean)

    flag of whether thermal zone in baseline is required to have DCV

Author:

  • Xuechen (Jerry) Lei, PNNL



522
523
524
525
526
527
528
529
530
531
532
533
# 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) ⇒ String

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

Parameters:

  • boiler_hot_water (OpenStudio::Model::BoilerHotWater)

    hot water boiler object

Returns:

  • (String)

    returns name of the boiler curve to be used, or nil if not applicable



23
24
25
# 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) ⇒ Boolean

Applies the standard efficiency ratings to this object.

Parameters:

  • boiler_hot_water (OpenStudio::Model::BoilerHotWater)

    hot water boiler object

Returns:

  • (Boolean)

    returns true if successful, false if not



8
9
10
11
12
13
14
15
16
17
# 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) ⇒ Boolean

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

Parameters:

  • user_equip_data (Hash)

    user equipment data

  • power_equipment (OpenStudio::Model::ElectricEquipment)

    equipment

  • power_schedule_hash (Hash)

    equipment operation schedule hash

  • space_type (OpenStudio::Model:SpaceType)

    space type

  • user_space_data (Hash) (defaults to: nil)

    user space data

Returns:

  • (Boolean)

    returns true if successful, false if not



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# 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) ⇒ Double

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.

Parameters:

  • space_type (OpenStudio::Model::SpaceType)
  • space (OpenStudio::Model::Space)

Returns:

  • (Double)

    lighting power density in the space



536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# 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) ⇒ Double

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

Parameters:

  • user_data (Hash)

    user data from the user csv

  • space (OpenStudio::Model::Space)

Returns:

  • (Double)

    space lighting per area in W per m2



626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
# 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) ⇒ Boolean

Check for incorrect data in [UserDataFiles::AIRLOOP_HVAC]

Parameters:

  • object_name (String)

    name of user data csv file to check

  • user_data (Hash)

    hash of data from user data csv file

Returns:

  • (Boolean)

    true if data is valid, false if error found



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# 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) ⇒ Boolean

Check for incorrect data in [UserDataFiles::AIRLOOP_HVAC_DOAS]

Parameters:

  • object_name (String)

    name of user data csv file to check

  • user_data (Hash)

    hash of data from user data csv file

Returns:

  • (Boolean)

    true if data is valid, false if error found



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# 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) ⇒ Boolean

Check for incorrect data in [UserDataFiles::BUILDING]

Parameters:

  • object_name (String)

    name of user data csv file to check

  • user_data (Hash)

    hash of data from user data csv file

Returns:

  • (Boolean)

    true if data is valid, false if error found



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# 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) ⇒ Boolean

Returns true if data is valid, false if error found.

Returns:

  • (Boolean)

    true if data is valid, false if error found



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# 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) ⇒ Boolean

Returns true if data is valid, false if error found.

Returns:

  • (Boolean)

    true if data is valid, false if error found



463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# 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) ⇒ Boolean

Returns true if data is valid, false if error found.

Returns:

  • (Boolean)

    true if data is valid, false if error found



491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# 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) ⇒ Boolean

Check for incorrect data in [UserDataFiles::LIGHTS]

Parameters:

  • object_name (String)

    name of user data csv file to check

  • user_data (Hash)

    hash of data from user data csv file

Returns:

  • (Boolean)

    true if data is valid, false if error found



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# 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) ⇒ Boolean

Check for incorrect data in [UserDataFiles::DESIGN_SPECIFICATION_OUTDOOR_AIR]

Parameters:

  • object_name (String)

    name of user data csv file to check

  • user_data (Hash)

    hash of data from user data csv file

Returns:

  • (Boolean)

    true if data is valid, false if error found



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# 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) ⇒ Boolean

Returns true if data is valid, false if error found.

Returns:

  • (Boolean)

    true if data is valid, false if error found



606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
# 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) ⇒ Boolean

Check for incorrect data in [UserDataFiles::THERMAL_ZONE]

Parameters:

  • object_name (String)

    name of user data csv file to check

  • user_data (Hash)

    hash of data from user data csv file

Returns:

  • (Boolean)

    true if data is valid, false if error found



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# 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) ⇒ Boolean

Returns true if data is valid, false if error found.

Returns:

  • (Boolean)

    true if data is valid, false if error found



638
639
640
641
642
643
644
645
646
647
648
# 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) ⇒ Boolean

Returns true if data is valid, false if error found.

Returns:

  • (Boolean)

    true if data is valid, false if error found



656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
# 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) ⇒ Boolean

Returns true if data is valid, false if error found.

Returns:

  • (Boolean)

    true if data is valid, false if error found



681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
# 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) ⇒ Boolean

Check for incorrect data in [UserDataFiles::ZONE_HVAC]

Parameters:

  • object_name (String)

    name of user data csv file to check

  • user_data (Hash)

    hash of data from user data csv file

Returns:

  • (Boolean)

    true if data is valid, false if error found



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# 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) ⇒ Boolean

Applies the standard efficiency ratings to this object.

Parameters:

  • chiller_electric_eir (OpenStudio::Model::ChillerElectricEIR)

    chiller object

Returns:

  • (Boolean)

    returns true if successful, false if not



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 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) ⇒ Boolean

Apply sizing and controls to chilled water loop

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • chilled_water_loop (OpenStudio::Model::PlantLoop)

    chilled water loop

  • dsgn_sup_wtr_temp (Double)

    design chilled water supply T

  • dsgn_sup_wtr_temp_delt (Double)

    design chilled water supply delta T

Returns:

  • (Boolean)

    returns true if successful, false if not



619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# 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) ⇒ Hash

Applies the standard efficiency ratings to this object.

Parameters:

  • coil_cooling_dx_single_speed (OpenStudio::Model::CoilCoolingDXSingleSpeed)

    coil cooling dx single speed object

  • sql_db_vars_map (Hash)

    hash map

  • sys_type (String)

    HVAC system type

Returns:

  • (Hash)

    hash of coil objects



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# 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) ⇒ Double

Finds capacity in W

Parameters:

  • coil_cooling_dx_single_speed (OpenStudio::Model::CoilCoolingDXSingleSpeed)

    coil cooling dx single speed object

  • sys_type (String)

    HVAC system type

Returns:

  • (Double)

    capacity in W to be used for find object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 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) ⇒ Double

Finds lookup object in standards and return efficiency

Parameters:

  • coil_cooling_dx_single_speed (OpenStudio::Model::CoilCoolingDXSingleSpeed)

    coil cooling dx single speed object

  • sys_type (String)

    HVAC system type

  • rename (Boolean) (defaults to: false)

    if true, object will be renamed to include capacity and efficiency level

Returns:

  • (Double)

    full load efficiency (COP)



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 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) ⇒ Hash

Applies the standard efficiency ratings to this object.

Parameters:

  • coil_cooling_dx_two_speed (OpenStudio::Model::CoilCoolingDXTwoSpeed)

    coil cooling dx two speed object

  • sql_db_vars_map (Hash)

    hash map

  • sys_type (String)

    HVAC system type

Returns:

  • (Hash)

    hash of coil objects



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# 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) ⇒ Double

Finds capacity in W

Parameters:

  • coil_cooling_dx_two_speed (OpenStudio::Model::CoilCoolingDXTwoSpeed)

    coil cooling dx two speed object

  • sys_type (String)

    HVAC system type

Returns:

  • (Double)

    capacity in W to be used for find object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 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) ⇒ Double

Finds lookup object in standards and return efficiency

Parameters:

  • coil_cooling_dx_two_speed (OpenStudio::Model::CoilCoolingDXTwoSpeed)

    coil cooling dx two speed object

  • sys_type (String)

    HVAC system type

  • rename (Boolean) (defaults to: false)

    if true, object will be renamed to include capacity and efficiency level

Returns:

  • (Double)

    full load efficiency (COP)



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 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) ⇒ Hash

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

Parameters:

  • coil_heating_dx_single_speed (OpenStudio::Model::CoilHeatingDXSingleSpeed)

    coil heating dx single speed object

  • sql_db_vars_map (Hash)

    hash map

  • sys_type (String)

    HVAC system type

Returns:

  • (Hash)

    hash of coil objects



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# 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) ⇒ Double

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

Parameters:

  • coil_heating_dx_single_speed (OpenStudio::Model::CoilHeatingDXSingleSpeed)

    coil heating dx single speed object

  • sys_type (String)

    HVAC system type

Returns:

  • (Double)

    capacity in W to be used for find object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# 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) ⇒ Double

Finds lookup object in standards and return efficiency

Parameters:

  • coil_heating_dx_single_speed (OpenStudio::Model::CoilHeatingDXSingleSpeed)

    coil heating dx single speed object

  • sys_type (String)

    HVAC system type

  • rename (Boolean) (defaults to: false)

    if true, object will be renamed to include capacity and efficiency level

Returns:

  • (Double)

    full load efficiency (COP)



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# 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) ⇒ Hash

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

Parameters:

  • coil_heating_gas (OpenStudio::Model::CoilHeatingGas)

    coil heating gas object

  • sql_db_vars_map (Hash)

    hash map

  • sys_type (String)

    HVAC system type

Returns:

  • (Hash)

    hash of coil objects



87
88
89
90
91
92
93
94
95
# 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) ⇒ Hash

find search criteria

Parameters:

  • coil_heating_gas (OpenStudio::Model::CoilHeatingGas)

    coil heating gas object

  • sys_type (String)

    HVAC system type

Returns:

  • (Hash)

    used for standards_lookup_table(model)



9
10
11
12
13
14
15
16
17
18
19
20
# 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) ⇒ Double

Finds lookup object in standards and return minimum thermal efficiency

Parameters:

  • coil_heating_gas (OpenStudio::Model::CoilHeatingGas)

    coil heating gas object

  • sys_type (String)

    HVAC system type

  • rename (Boolean) (defaults to: false)

    if true, object will be renamed to include capacity and efficiency level

Returns:

  • (Double)

    minimum thermal efficiency



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# 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) ⇒ String

Convert user data csv files to json format and save to project folder Method will create the json_folder in the project_path

Parameters:

  • user_data_path (String path to folder containing csv files)

    ser_data_path [String path to folder containing csv files

  • project_path (String path to project folder)

    roject_path [String path to project folder

Returns:

  • (String)

    path to json files

Author:

  • Doug Maddox, PNNL



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# 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) ⇒ Object



509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
# 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) ⇒ Boolean

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

Parameters:

  • fan_variable_volume (OpenStudio::Model::FanVariableVolume)

    variable volume fan object

Returns:

  • (Boolean)

    returns true if required, false if not



10
11
12
13
# 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) ⇒ Double

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

Parameters:

  • fan_variable_volume (OpenStudio::Model::FanVariableVolume)

    variable volume fan object

Returns:

  • (Double)

    the limit, in horsepower. Return zero for no limit by default.



20
21
22
23
# 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) ⇒ String

Determine whether heating type is fuel or electric

Parameters:

  • hvac_building_type (String)

    Key for lookup of baseline system type

  • climate_zone (String)

    full name of climate zone

Returns:

  • (String)

    fuel or electric



3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
# 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) ⇒ Object

Generate baseline log to a specific file directory

Parameters:

  • file_directory (String)

    file directory



2417
2418
2419
# 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) ⇒ Object

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

Parameters:

  • user_model (OpenStudio::Model::Model)

    OpenStudio model object

  • user_data_path (String)

    data path

  • user_data_file (String) (defaults to: nil)

    the name of the user data file.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 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) ⇒ Double

Get the air loop HVAC design outdoor air flow rate by reading Standard 62.1 Summary from the sizing sql

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

Returns:

  • (Double)

    Design outdoor air flow rate (m^3/s)

Author:

  • Xuechen (Jerry) Lei, PNNL



539
540
541
542
543
544
545
546
547
548
549
# 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) ⇒ Array<Hash>

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

Parameters:

  • hvac_building_type (String)

    Chosen by user via measure interface or user data files

  • zones_in_building_type (Array<OpenStudio::Model::ThermalZone>)

    array of thermal zones

Returns:

  • (Array<Hash>)

    an array of hashes of area information, with keys area_ft2, type, fuel, and zones (an array of zones)



2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
# 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) ⇒ Hash

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

Parameters:

  • user_model (OpenStudio::Model::Model)

    OpenStudio model

Returns:

  • (Hash)

    Hash map that contains the total area of fenestration at each orientation (N, S, E, W)



2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
# 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) ⇒ Object

A function to load airloop DOAS data from userdata csv files

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object



1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
# 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) ⇒ Object

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object



1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
# 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) ⇒ Object

A function to load electric equipment csv files The file name is userdata_electric_equipment.csv

Parameters:

  • model (OpenStudio::Model::Model)


1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
# 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) ⇒ Object

A function to load exterior lighting data from user data csv files The file name is userdata_exterior_lighting.csv

Parameters:

  • model (OpenStudio::Model::Model)


1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
# 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) ⇒ Object

A function to load gas equipment csv files The file name is userdata_gas_equipment.csv

Parameters:

  • model (OpenStudio::Model::Model)


1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
# 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) ⇒ Object

A function to load lights from user data csv files The file name is userdata_lights.csv

Parameters:

  • model (OpenStudio::Model::Model)


1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
# 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) ⇒ Boolean

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.

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

  • default_hvac_building_type (String)

    (Fourth Hierarchy hvac building type)

  • default_wwr_building_type (String)

    (Fourth Hierarchy wwr building type)

  • default_swh_building_type (String)

    (Fourth Hierarchy swh building type)

  • bldg_type_hvac_zone_hash (Hash)

    An empty hash that maps building type for hvac to a list of thermal zones

Returns:

  • (Boolean)

    returns true if successful, false if not



1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
# 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) ⇒ Object

A function to load outdoor air data from user data csv files The file name is userdata_design_specification_outdoor_air.csv

Parameters:

  • model (OpenStudio::Model::Model)


1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
# 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) ⇒ Object

A function to load thermal zone data from userdata csv files

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object



1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
# 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) ⇒ Boolean

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

Parameters:

  • model (OpenStudio:model:Model)
  • climate_zone (String)
  • default_hvac_building_type (String)
  • default_wwr_building_type (String)
  • default_swh_building_type (String)
  • bldg_type_hvac_zone_hash (Hash)

    A hash maps building type for hvac to a list of thermal zones

Returns:

  • (Boolean)

    returns true if successful, false if not



1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
# 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) ⇒ Object

A function to load water use connections schedules from user data csv files The file name is userdata_wateruse_connections.csv

Parameters:

  • model (OpenStudio::Model::Model)


1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
# 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) ⇒ Object

A function to load water use equipment definition from user data csv files The file name is userdata_wateruse_equipment_definition.csv

Parameters:

  • model (OpenStudio::Model::Model)


1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
# 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) ⇒ Object

A function to load water use equipment from user data csv files The file name is userdata_wateruse_equipment.csv

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model

  • default_swh_building_type (String)

    SWH building type



1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
# 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) ⇒ Object

Retrieve zone HVAC user specified compliance inputs from CSV file

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object



1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
# 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) ⇒ Boolean

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

Parameters:

  • space_type (OpenStudio::Model::SpaceType)

Returns:

  • (Boolean)

    True if there is lighting power defined by w/ft, False otherwise.



585
586
587
588
589
590
# 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) ⇒ Boolean

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

Parameters:

  • user_data (Hash)

    user data from the user csv

  • space_type (OpenStudio::Model::SpaceType)

Returns:

  • (Boolean)


598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
# 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) ⇒ Boolean

Function checks whether the user data contains lighting data

Parameters:

  • user_space_data (Hash)

    space data extracted from user csv.

Returns:

  • (Boolean)

    True if there are user lpd values, False otherwise.



572
573
574
575
576
577
578
579
# 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) ⇒ String

Determine the heat exchanger design conditions for a specific climate zones

Parameters:

  • heat_exchanger_air_to_air_sensible_and_latent (OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent)

    OpenStudio heat exchanger object

  • climate_zone (String)

    Climate zone used to generate the model

Returns:

  • (String)

    Heat exchanger design conditions



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 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) ⇒ Double

Determine the required enthalpy recovery ratio (ERR)

Parameters:

  • heat_exchanger_air_to_air_sensible_and_latent (OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent)

    OpenStudio heat exchanger object

Returns:

  • (Double)

    enthalpy recovery ratio



52
53
54
# 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) ⇒ Array

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

Parameters:

  • heat_exchanger_air_to_air_sensible_and_latent (OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent)

    OpenStudio heat exchanger object

Returns:

  • (Array)

    List of full and part load heat exchanger effectiveness



9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 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 = []) ⇒ Object



14
15
16
# 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) ⇒ Object

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

Parameters:

  • json_path (String path to folder containing json files)

    son_path [String path to folder containing json files

Author:

  • Doug Maddox, PNNL



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# 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) ⇒ Object

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model

Author:

  • Xuechen (Jerry) Lei, PNNL



1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
# 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) ⇒ Object

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model

Author:

  • Xuechen (Jerry) Lei, PNNL



1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
# 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) ⇒ Object

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model

Author:

  • Xuechen (Jerry) Lei, PNNL



1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
# 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) ⇒ Object

Function to add baseline elevators based on user data

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object



727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
# 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) ⇒ Double

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

Parameters:

  • building_envelope_area_m2 (Double)

    Building envelope area as per 90.1 in m^2

  • specific_space_infiltration_rate_75_pa (Double) (defaults to: nil)

    Specific space infiltration rate at 75 pa

Returns:

  • (Double)

    building envelope infiltration



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# 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) ⇒ Object

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object



711
712
713
714
715
716
717
718
719
720
721
722
723
# 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) ⇒ Boolean

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • building_type (String)

    the building type

Returns:

  • (Boolean)

    returns true if successful, false if not



2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
# 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) ⇒ Boolean

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

Returns:

  • (Boolean)

    returns true if successful, false if not



2362
2363
2364
2365
2366
# 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) ⇒ Boolean

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

  • apply_controls (Boolean) (defaults to: true)

    toggle whether to apply air loop and plant loop controls

  • sql_db_vars_map (Hash) (defaults to: nil)

    hash map

Returns:

  • (Boolean)

    returns true if successful, false if not



970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
# 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) ⇒ Boolean

Note:

This is not applicable to the stable baseline; hence no action in this method

Applies the multi-zone VAV outdoor air sizing requirements to all applicable air loops in the model.

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Returns:

  • (Boolean)

    returns true if successful, false if not



898
899
900
# 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) ⇒ Object

Add design day schedule objects for space loads, for PRM 2019 baseline models

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Author:

  • Xuechen (Jerry) Lei, PNNL



812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
# 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) ⇒ Boolean

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Returns:

  • (Boolean)

    returns true if successful, false if not



629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
# File 'lib/openstudio-standards/standards/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) ⇒ Boolean

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Returns:

  • (Boolean)

    returns true if successful, false if not



504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
# 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: {}) ⇒ Boolean

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

  • wwr_building_type (String) (defaults to: nil)

    building type used for defining window to wall ratio, e.g. ‘Office > 50,000 sq ft’

  • wwr_info (Hash) (defaults to: {})

    A map that maps each building area type to its correspondent wwr.

Returns:

  • (Boolean)

    returns true if successful, false if not



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# 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) ⇒ Boolean

This method creates customized infiltration objects for each space and removes the SpaceType-level infiltration objects.

Parameters:

  • model (OpenStudio::Model::Model)

    openstudio model

  • specific_space_infiltration_rate_75_pa (Double) (defaults to: nil)

    space infiltration rate at a pressure differential of 75 Pa

Returns:

  • (Boolean)

    true if successful, false if not



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# 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) ⇒ String

Determines the fan type used by VAV_Reheat and VAV_PFP_Boxes systems. Variable speed fan for 90.1-2019

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Returns:

  • (String)

    the fan type: TwoSpeed Fan, Variable Speed Fan



102
103
104
105
# 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) ⇒ Double

Calculate the building envelope area according to the 90.1 definition

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Returns:

  • (Double)

    Building envelope area in m2



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# 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) ⇒ Object

For a multizone system, create the fan schedule based on zone occupancy/fan schedules

Parameters:

  • model (OpenStudio::Model::Model)

    openstudio model

  • zone_op_hrs (Hash)

    of hash of zoneName zone_op_hrs

  • pri_zones (Array<String>)

    names of zones served by the multizone system

  • system_name (String)

    name of air loop

Author:

  • Doug Maddox, PNNL



3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
# 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) ⇒ Double

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.

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio Model object

  • building_envelope_area_m2 (Double)

    Building envelope area as per 90.1 in m^2

Returns:

  • (Double)

    building model air leakage rate



305
306
307
308
309
310
311
312
313
314
315
# 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) ⇒ Hash

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)

Parameters:

  • model
  • zones (Array<Object>)
  • zone_fan_scheds (Hash)

    hash of zoneName 8760FanSchedPerZone

Returns:

  • (Hash)

    A hash of two arrays of ThermalZones, where the keys are ‘primary’ and ‘secondary’

Author:

  • Doug Maddox, PNNL



3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
# 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) ⇒ Boolean

This function checks whether it is required to adjust the window to wall ratio based on the model WWR and wwr limit.

Parameters:

  • wwr_limit (Double)

    window to wall ratio limit

  • wwr_list (Array)

    list of wwr of zone conditioning category in a building area type category - residential, nonresidential and semiheated

Returns:

  • (Boolean)

    True, require adjustment, false not require adjustment



2425
2426
2427
2428
# 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) ⇒ Boolean

Template method for evaluate DCV requirements in the user model

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model

Returns:

  • (Boolean)

    returns true if successful, false if not



1112
1113
1114
1115
1116
1117
1118
1119
# 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) ⇒ Double

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

Parameters:

  • bat (String)

    building category

  • wwr_list (Array)

    list of zone conditioning category-based WWR - residential, nonresidential and semiheated

Returns:

  • (Double)

    return adjusted wwr_limit



2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
# 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_breakdownBoolean

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

Returns:

  • (Boolean)

    true if necessary, false otherwise



959
960
961
# 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) ⇒ String

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Returns:

  • (String)

    infiltration input type



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# 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) ⇒ String

This method retrieves the type of infiltration input used in the model. If input is inconsistent, returns Flow/Area

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Returns:

  • (String)

    infiltration input type



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# 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) ⇒ Double

This methods calculate the air leakage rate of a space

Parameters:

  • space (OpenStudio::Model::Space)

    OpenStudio Space object

Returns:

  • (Double)

    Space air leakage rate



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# 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) ⇒ Hash

TODO:

Zone-level evaporative cooler is not currently supported

Identifies non mechanically cooled (“nmc”) systems, if applicable and add a flag to the zone’s and air loop’s additional properties. by OpenStudio, will need to be added to the method when supported.

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Returns:

  • (Hash)

    Zone to nmc system type mapping



909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
# 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) ⇒ Boolean

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model

Returns:

  • (Boolean)

    returns true if successful, false if not

Author:

  • Xuechen (Jerry) Lei, PNNL



1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
# 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) ⇒ String

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.

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • fuel_type (String)

    Valid choices are electric, fossil, fossilandelectric, purchasedheat, purchasedcooling, purchasedheatandcooling

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

Returns:

  • (String)

    the revised fuel type



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# 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) ⇒ Array<Hash>

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

Parameters:

  • model (OpenStudio::Model::Model)

    openstudio model

  • custom (String)

    identifier for custom programs, not used here, but included for backwards compatibility

  • bldg_type_hvac_zone_hash (Hash of bldg_type:list of zone objects)

    association of zones to each hvac building type

Returns:

  • (Array<Hash>)

    an array of hashes of area information, with keys area_ft2, type, fuel, and zones (an array of zones)



2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
# 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) ⇒ String

Determines which system number is used for the baseline system.

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

Returns:

  • (String)

    the system number: 1_or_2, 3_or_4, 5_or_6, 7_or_8, 9_or_10



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 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) ⇒ String

Note:

Select system type from data table base on key parameters

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

Parameters:

  • climate_zone (String)

    id code for the climate

  • sys_group (Hash)

    Hash defining a group of zones that have the same Appendix G system type

  • custom (String)

    included here for backwards compatibility (not used here)

  • hvac_building_type (String)

    Chosen by user via measure interface or user data files

  • district_heat_zones (Hash)

    of zone name => true for has district heat, false for has not

Returns:

  • (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



2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
# 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) ⇒ Object

TODO:

JXL add log msgs to PRM logger

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model

Author:

  • Xuechen (Jerry) Lei, PNNL



1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
# 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) ⇒ Boolean

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

Parameters:

  • residual_ratio (Double)

    the ratio of residual surfaces among the total wall surface area with no fenestrations

  • space (OpenStudio::Model:Space)

    a space

  • model (OpenStudio::Model::Model)

    openstudio model

Returns:

  • (Boolean)

    returns true if successful, false if not



2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
# 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) ⇒ Boolean

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

Parameters:

  • model

Returns:

  • (Boolean)

    returns true if successful, false if not

Author:

  • Doug Maddox, PNNL



3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
# 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) ⇒ Object

Set DCV in baseline HVAC system if required

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model

Author:

  • Xuechen (Jerry) Lei, PNNL



1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
# 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) ⇒ Boolean

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model

  • thermal_zones

    Array() thermal zone array

  • coil (OpenStudio::Model::StraightComponent)

    heating coil

Returns:

  • (Boolean)

    returns true if successful, false if not



1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
# 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) ⇒ Boolean

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

Returns:

  • (Boolean)

    surfaces_with_fc_factor_boundary, returns true if successful, false if not



2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
# 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) ⇒ Hash

TODO:

Align the standard construction enumerations in the

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

spreadsheet with the enumerations in OpenStudio (follow CBECC-Com).

Parameters:

  • planar_surface (OpenStudio::Model:PlanarSurface)

    surface object

  • climate_zone (String)

    ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’

  • previous_construction_map (Hash) (defaults to: {})

    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.

  • wwr_building_type (String | Nil) (defaults to: nil)

    building type that identifies the prescribed window to wall ratio

  • wwr_info (Hash) (defaults to: {})

    A map that maps the building area type to window to wall ratio

  • surface_category (String)

    surface category e.g., ‘ExteriorSubSurface’

Returns:

  • (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.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# 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) ⇒ Boolean

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

Apply prm baseline pump power

Parameters:

  • plant_loop (OpenStudio::Model::PlantLoop)

    plant loop

Returns:

  • (Boolean)

    returns true if successful, false if not



534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
# 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) ⇒ Boolean

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

Parameters:

  • plant_loop (OpenStudio::Model::PlantLoop)

    chilled water loop

Returns:

  • (Boolean)

    returns true if successful, false if not



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# 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) ⇒ Boolean

Returns true if successful, false if not

Parameters:

  • plant_loop (OpenStudio::Model::PlantLoop)

    plant loop

Returns:

  • (Boolean)

    returns true if successful, false if not



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 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) ⇒ String

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

Returns:

  • (String)

    common_pipe or heat_exchanger



669
670
671
672
# 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) ⇒ Boolean

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.

Parameters:

  • run_all_orients (Boolean)

    user inputs to indicate whether it is required to run all orientations

  • user_model (OpenStudio::Model::Model)

    OpenStudio model

Returns:

  • (Boolean)

    True if run all orientation is required, false otherwise



2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
# 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) ⇒ Hash

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

Parameters:

  • cooling_coil (OpenStudio::Model::ModeObject)
  • sql_db_vars_map (Hash)

    hash map

  • sys_type (String)

    baseline system type string

Returns:

  • (Hash)

    sql_db_vars_map



1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
# 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) ⇒ Hash

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

Parameters:

  • heating_coil (OpenStudio::Model::ModeObject)
  • sql_db_vars_map (Hash)

    hash map

  • sys_type (String)

    baseline system type string

Returns:

  • (Hash)

    the hashmap contains the heating efficiency and curve coefficient for the heating_coil



1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
# 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) ⇒ Boolean

Function to test LPD on default space type. The function assigns lighting power density to an light object.

Parameters:

  • space_type (OpenStudio::Model::SpaceType)
  • user_spaces (Hash)
  • user_spacetypes (Hash)

Returns:

  • (Boolean)

    returns true if successful, false if not



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# 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) ⇒ Boolean

Create and assign PRM computer room electric equipment schedule

Parameters:

  • space (OpenStudio::Model::Space)

    OpenStudio Space object

Returns:

  • (Boolean)

    returns true if successful, false if not



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# 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) ⇒ Boolean

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

Parameters:

  • space (OpenStudio::Model::Space)

    space object

  • tot_infil_m3_per_s (Float)

    total infiltration in m3/s

  • infil_method (String)

    infiltration method

  • infil_coefficients (Array)

    Array of 4 items [Constant Term Coefficient, Temperature Term Coefficient,

    Velocity Term Coefficient, Velocity Squared Term Coefficient]
    

Returns:

  • (Boolean)

    returns true if successful, false if not



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# 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) ⇒ Boolean

For stable baseline, remove all daylighting controls (sidelighting and toplighting)

Parameters:

  • space (OpenStudio::Model::Space)

    the space with daylighting

  • remove_existing (Boolean) (defaults to: false)

    if true, will remove existing controls then add new ones

  • draw_areas_for_debug (Boolean) (defaults to: false)

    If this argument is set to true,

Returns:

  • (Boolean)

    returns true if successful, false if not



106
107
108
109
# 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) ⇒ ArrayOpenStudio::Model::Space

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.

Parameters:

  • user_spaces (Hash)

    hash data contained in the user space

  • user_spacetypes (Hash)

    hash data contained in the user spacetypes

  • space_type (OpenStudio::Model::SpaceType)

    object

Returns:

  • (ArrayOpenStudio::Model::Space)

    List of Spaces



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# 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) ⇒ Boolean

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

Parameters:

  • user_spacetypes (Hash)

    spacetype user data

  • user_spaces (Hash)

    space user data

  • space_array (Array OpenStudio::Model:Space)

    list of spaces need for process

Returns:

  • (Boolean)

    returns true if successful, false if not



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# 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) ⇒ Boolean

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.

Parameters:

  • space_type (OpenStudio::Model::SpaceType)

    space type object

  • 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.

  • set_lights (Boolean)

    if true, set the lighting density, lighting fraction to return air, fraction radiant, and fraction visible.

  • set_electric_equipment (Boolean)

    if true, set the electric equipment density

  • set_gas_equipment (Boolean)

    if true, set the gas equipment density

  • set_ventilation (Boolean)

    if true, set the ventilation rates (per-person and per-area)

  • set_infiltration (Boolean)

    if true, set the infiltration rates

Returns:

  • (Boolean)

    returns true if successful, false if not



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# 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) ⇒ Boolean

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

Parameters:

  • space_type (OpenStudio::Model:SpaceType)

Returns:

  • (Boolean)

    returns true if successful, false if not



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# 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) ⇒ Object

Modify the lighting schedules for Appendix G PRM for 2016 and later

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object



463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# 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) ⇒ Object

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

Parameters:

  • model (OpenStudio::Model)

    OpenStudio model with plant loops

  • chilled_water_loop (OpenStudio::Model::PlantLoop)

    chilled water loop



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# 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) ⇒ Boolean

Adjust the fenestration area to the values specified by the reduction value in a surface

Parameters:

  • surface (OpenStudio::Model:Surface)

    openstudio surface object

  • reduction (Double)

    ratio of adjustments

  • model (OpenStudio::Model::Model)

    openstudio model

Returns:

  • (Boolean)

    returns true if successful, false if not



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 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) ⇒ Double

Calculate the window to wall ratio reduction factor

Parameters:

  • multiplier (Double)

    multiplier of the wwr

  • surface (OpenStudio::Model:Surface)

    the surface object

  • wwr_building_type (String) (defaults to: 'All others')

    building type for wwr

  • wwr_target (Double) (defaults to: nil)

    target window to wall ratio

  • total_wall_m2 (Double) (defaults to: 0.0)

    total wall area of the category in m2.

  • total_wall_with_fene_m2 (Double) (defaults to: 0.0)

    total wall area of the category with fenestrations in m2.

  • total_fene_m2 (Double) (defaults to: 0.0)

    total fenestration area

Returns:

  • (Double)

    reduction factor



2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
# 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) ⇒ Object

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

Parameters:

  • thermal_zone


8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# 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) ⇒ String with applicable DistrictHeating and/or DistrictCooling

Identify if zone has district energy for occ_and_fuel_type method

Parameters:

  • thermal_zone

Returns:

  • (String with applicable DistrictHeating and/or DistrictCooling)

    String with applicable DistrictHeating and/or DistrictCooling



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# 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) ⇒ Double

Specify supply to room delta for laboratory spaces based on 90.1 Appendix G Exception to G3.1.2.8.1

Parameters:

  • thermal_zone (OpenStudio::Model::ThermalZone)

    OpenStudio ThermalZone Object

Returns:

  • (Double)

    for zone with laboratory space, return 17; otherwise, return nil



944
945
946
947
948
949
950
951
952
953
# 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) ⇒ Double

Specify supply air temperature setpoint for unit heaters based on 90.1 Appendix G G3.1.2.8.2

Parameters:

  • thermal_zone (OpenStudio::Model::ThermalZone)

    OpenStudio ThermalZone Object

Returns:

  • (Double)

    for zone with unit heaters, return design supply temperature; otherwise, return nil



931
932
933
934
935
936
937
938
# 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) ⇒ Boolean

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.

Parameters:

  • power_equipment (OpenStudio::Model::ElectricEquipment)

    or [OpenStudio::Model:GasEquipment]

  • user_power_equipment (Hash)

    user data for the power equipment

  • schedule_hash (Hash)

    power equipment operation schedules in a hash

  • space_type (OpenStudio::Model:SpaceType)

    space type

  • user_data (Hash) (defaults to: nil)

    user space data

Returns:

  • (Boolean)

    returns true it adjusted, false if not



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# 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) ⇒ Object

Perform user data preprocessing

Parameters:

  • row (CSV::ROW)

    2D array for each row.



137
138
139
140
141
142
143
144
145
146
147
# 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) ⇒ Boolean

Returns true if data is valid, false if error found.

Returns:

  • (Boolean)

    true if data is valid, false if error found



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# 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) ⇒ Boolean

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

Parameters:

  • air_loop_hvac (OpenStudio::Model::AirLoopHVAC)

    air loop

Returns:

  • (Boolean)

    flag of whether air loop in user model is required to have DCV

Author:

  • Xuechen (Jerry) Lei, PNNL



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# 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) ⇒ Boolean

Check if a zone in user model needs to have DCV per zone related requiremends in ASHRAE 90.1-2019 6.4.3.8

Parameters:

  • thermal_zone (OpenStudio::Model::ThermalZone)

    the thermal zone

Returns:

  • (Boolean)

    flag of whether thermal zone in user model is required to have DCV

Author:

  • Xuechen (Jerry) Lei, PNNL



481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# 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) ⇒ Boolean

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.

Returns:

  • (Boolean)

    returns true if successful, false if not



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# 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_thresholdDouble

Default occupancy fraction threshold for determining if the spaces served by the zone hvac are occupied

Returns:

  • (Double)

    unoccupied threshold



129
130
131
132
# 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