Class: NECB2011

Inherits:
Standard show all
Defined in:
lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb,
lib/openstudio-standards/standards/necb/necb_2011/lighting.rb,
lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb,
lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb,
lib/openstudio-standards/standards/necb/necb_2011/beps_compliance_path.rb,
lib/openstudio-standards/standards/necb/necb_2011/service_water_heating.rb

Overview

This class holds methods that apply NECB2011 rules.

Constant Summary

Constants inherited from Standard

Standard::STANDARDS_LIST

Instance Attribute Summary collapse

Attributes inherited from Standard

#space_multiplier_map

Instance Method Summary collapse

Methods inherited from Standard

#adjust_infiltration_to_lower_pressure, #adjust_infiltration_to_prototype_building_conditions, #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_allowable_system_brake_horsepower, #air_loop_hvac_apply_baseline_fan_pressure_rise, #air_loop_hvac_apply_economizer_limits, #air_loop_hvac_apply_maximum_reheat_temperature, #air_loop_hvac_apply_minimum_vav_damper_positions, #air_loop_hvac_apply_prm_baseline_controls, #air_loop_hvac_apply_prm_baseline_economizer, #air_loop_hvac_apply_prm_baseline_fan_power, #air_loop_hvac_apply_prm_sizing_temperatures, #air_loop_hvac_apply_standard_controls, #air_loop_hvac_data_center_area_served, #air_loop_hvac_dcv_required_when_erv, #air_loop_hvac_demand_control_ventilation_limits, #air_loop_hvac_disable_multizone_vav_optimization, #air_loop_hvac_dx_cooling?, #air_loop_hvac_economizer?, #air_loop_hvac_economizer_limits, #air_loop_hvac_economizer_type_allowable?, #air_loop_hvac_enable_demand_control_ventilation, #air_loop_hvac_enable_multizone_vav_optimization, #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_enable_unoccupied_fan_shutoff, #air_loop_hvac_energy_recovery?, #air_loop_hvac_energy_recovery_ventilator_flow_limit, #air_loop_hvac_fan_power_limitation_pressure_drop_adjustment_brake_horsepower, #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_integrated_economizer_required?, #air_loop_hvac_motorized_oa_damper_required?, #air_loop_hvac_multi_stage_dx_cooling?, #air_loop_hvac_multizone_vav_optimization_required?, #air_loop_hvac_multizone_vav_system?, #air_loop_hvac_prm_baseline_economizer_required?, #air_loop_hvac_prm_economizer_type_and_limits, #air_loop_hvac_remove_motorized_oa_damper, #air_loop_hvac_single_zone_controls_num_stages, #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_unoccupied_fan_shutoff_required?, #air_loop_hvac_vav_damper_action, #air_loop_hvac_vav_system?, #air_terminal_single_duct_parallel_piu_reheat_apply_prm_baseline_fan_power, #air_terminal_single_duct_vav_reheat_apply_initial_prototype_damper_position, #air_terminal_single_duct_vav_reheat_apply_minimum_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, #boiler_hot_water_find_capacity, #boiler_hot_water_find_search_criteria, #boiler_hot_water_standard_minimum_thermal_efficiency, build, #building_story_floor_multiplier, #building_story_minimum_z_value, #chiller_electric_eir_find_capacity, #chiller_electric_eir_find_search_criteria, #chiller_electric_eir_standard_minimum_full_load_efficiency, #coil_cooling_dx_single_speed_apply_efficiency_and_curves, #coil_cooling_dx_single_speed_find_capacity, #coil_cooling_dx_single_speed_standard_minimum_cop, #coil_cooling_dx_two_speed_apply_efficiency_and_curves, #coil_cooling_dx_two_speed_find_capacity, #coil_cooling_dx_two_speed_standard_minimum_cop, #coil_heating_dx_multi_speed_apply_efficiency_and_curves, #coil_heating_dx_single_speed_apply_efficiency_and_curves, #coil_heating_dx_single_speed_find_capacity, #coil_heating_dx_single_speed_standard_minimum_cop, #coil_heating_gas_apply_prototype_efficiency, #combustion_eff_to_thermal_eff, #construction_calculated_solar_heat_gain_coefficient, #construction_calculated_u_factor, #construction_calculated_visible_transmittance, #construction_set_glazing_shgc, #construction_set_glazing_u_value, #construction_set_slab_f_factor, #construction_set_u_value, #construction_set_underground_wall_c_factor, #construction_simple_glazing?, #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_to_eer, #cop_to_kw_per_ton, #cop_to_seer, #create_curve_bicubic, #create_curve_biquadratic, #create_curve_cubic, #create_curve_exponent, #create_curve_quadratic, #define_space_multiplier, #eer_to_cop, #fan_constant_volume_airloop_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_cooling_system_type, #fan_variable_volume_part_load_fan_power_limitation_capacity_limit, #fan_variable_volume_part_load_fan_power_limitation_hp_limit, #fan_variable_volume_set_control_type, #fan_zone_exhaust_apply_prototype_fan_pressure_rise, #film_coefficients_r_value, #headered_pumps_variable_speed_set_control_type, #heat_exchanger_air_to_air_sensible_and_latent_apply_efficiency, #heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_nominal_electric_power, #heat_exchanger_air_to_air_sensible_and_latent_minimum_efficiency, #heating_design_outdoor_temperatures, #hspf_to_cop_heating_no_fan, #intialize, #kw_per_ton_to_cop, #load_hvac_map, #load_standards_database, #model_add_baseboard, #model_add_booster_swh_end_uses, #model_add_cav, #model_add_central_air_source_heat_pump, #model_add_chw_loop, #model_add_constant_schedule_ruleset, #model_add_construction, #model_add_curve, #model_add_cw_loop, #model_add_data_center_hvac, #model_add_data_center_load, #model_add_daylighting_controls, #model_add_design_days_and_weather_file, #model_add_district_ambient_loop, #model_add_doas, #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_ground_temperatures, #model_add_high_temp_radiant, #model_add_hp_loop, #model_add_hvac_system, #model_add_hw_loop, #model_add_ideal_air_loads, #model_add_material, #model_add_prm_baseline_system, #model_add_prm_construction_set, #model_add_psz_ac, #model_add_psz_vav, #model_add_ptac, #model_add_pthp, #model_add_pvav, #model_add_pvav_pfp_boxes, #model_add_refrigeration, #model_add_refrigeration_case, #model_add_refrigeration_compressor, #model_add_refrigeration_system, #model_add_refrigeration_walkin, #model_add_split_ac, #model_add_swh_booster, #model_add_swh_end_uses, #model_add_swh_end_uses_by_space, #model_add_swh_loop, #model_add_typical_exterior_lights, #model_add_typical_swh, #model_add_unitheater, #model_add_vav_pfp_boxes, #model_add_vav_reheat, #model_add_water_heater, #model_add_water_source_hp, #model_add_window_ac, #model_add_zone_erv, #model_add_zone_ventilation, #model_apply_hvac_efficiency_standard, #model_apply_infiltration_standard, #model_apply_multizone_vav_outdoor_air_sizing, #model_apply_prm_baseline_skylight_to_roof_ratio, #model_apply_prm_baseline_window_to_wall_ratio, #model_apply_prm_construction_types, #model_apply_prm_sizing_parameters, #model_apply_standard_constructions, #model_assign_spaces_to_stories, #model_baseline_system_vav_fan_type, #model_create_exterior_lighting_area_length_count_hash, #model_create_prm_baseline_building, #model_create_prm_baseline_building_requires_vlt_sizing_run, #model_create_space_type_hash, #model_create_story_hash, #model_cw_loop_cooling_tower_fan_type, #model_differentiate_primary_secondary_thermal_zones, #model_effective_num_stories, #model_elevator_fan_pwr, #model_elevator_lift_power, #model_elevator_lighting_pct_incandescent, #model_eliminate_outlier_zones, #model_find_ashrae_hot_water_demand, #model_find_constructions, #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_climate_zone_and_building_type, #model_get_climate_zone_set_from_list, #model_get_construction_properties, #model_get_full_weather_file_path, #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_get_story_for_nominal_z_coordinate, #model_group_zones_by_story, #model_make_name, #model_num_stories_spanned, #model_prm_baseline_system_change_fuel_type, #model_prm_baseline_system_group_minimum_area, #model_prm_baseline_system_groups, #model_prm_baseline_system_number, #model_prm_baseline_system_type, #model_prm_skylight_to_roof_ratio_limit, #model_process_results_for_datapoint, #model_remap_office, #model_remove_external_shading_devices, #model_remove_prm_hvac, #model_residential_and_nonresidential_floor_areas, #model_swh_pump_type, #model_typical_hvac_system_type, #model_validate_standards_spacetypes_in_model, #model_walkin_freezer_latent_case_credit_curve, #model_zones_with_occ_and_fuel_type, #planar_surface_apply_standard_construction, #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_pump_power, #plant_loop_apply_prm_baseline_pumping_type, #plant_loop_apply_prm_baseline_temperatures, #plant_loop_apply_prm_number_of_boilers, #plant_loop_apply_prm_number_of_chillers, #plant_loop_apply_prm_number_of_cooling_towers, #plant_loop_apply_standard_controls, #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?, #pump_variable_speed_set_control_type, register_standard, #safe_load_model, #safe_load_sql, #schedule_compact_annual_min_max_value, #schedule_constant_annual_equivalent_full_load_hrs, #schedule_constant_annual_min_max_value, #schedule_ruleset_annual_equivalent_full_load_hrs, #schedule_ruleset_annual_hours_above_value, #schedule_ruleset_annual_min_max_value, #seer_to_cop_cooling_no_fan, #space_add_daylighting_controls, #space_conditioning_category, #space_cooled?, #space_daylighted_area_window_width, #space_daylighted_areas, #space_daylighting_control_required?, #space_daylighting_fractions_and_windows, #space_design_internal_load, #space_exterior_wall_and_roof_and_subsurface_area, #space_exterior_wall_and_window_area, #space_get_adjacent_space_with_most_shared_wall_area, #space_get_adjacent_spaces_with_shared_wall_areas, #space_heated?, #space_infiltration_rate_75_pa, #space_plenum?, #space_residential?, #space_sidelighting_effective_aperture, #space_skylight_effective_aperture, #space_type_apply_internal_load_schedules, #space_type_apply_rendering_color, #space_type_get_construction_properties, #space_type_get_standards_data, #strip_model, #sub_surface_component_infiltration_rate, #sub_surface_reduce_area_by_percent_by_raising_sill, #sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid, #sub_surface_vertical_rectangle?, #surface_component_infiltration_rate, #thermal_eff_to_afue, #thermal_eff_to_comb_eff, #thermal_zone_add_exhaust, #thermal_zone_add_exhaust_fan_dcv, #thermal_zone_add_unconditioned_thermostat, #thermal_zone_apply_prm_baseline_supply_temperatures, #thermal_zone_conditioning_category, #thermal_zone_convert_oa_req_to_per_area, #thermal_zone_cooled?, #thermal_zone_demand_control_ventilation_limits, #thermal_zone_design_internal_load, #thermal_zone_exhaust_fan_dcv_required?, #thermal_zone_floor_area_with_zone_multipliers, #thermal_zone_fossil_hybrid_or_purchased_heat?, #thermal_zone_fossil_or_electric_type, #thermal_zone_get_adjacent_zones_with_shared_wall_areas, #thermal_zone_get_occupancy_schedule, #thermal_zone_heated?, #thermal_zone_infer_system_type, #thermal_zone_majority_space_type, #thermal_zone_mixed_heating_fuel?, #thermal_zone_occupancy_type, #thermal_zone_outdoor_airflow_rate, #thermal_zone_outdoor_airflow_rate_per_area, #thermal_zone_plenum?, #thermal_zone_prm_baseline_cooling_design_supply_temperature, #thermal_zone_prm_baseline_heating_design_supply_temperature, #thermal_zone_residential?, #water_heater_mixed_apply_prm_baseline_fuel_type, #water_heater_mixed_find_capacity, #zone_hvac_component_apply_prm_baseline_fan_power

Methods included from PrototypeFan

#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_brake_horsepower, #fan_change_impeller_efficiency, #fan_change_motor_efficiency, #fan_fanpower, #fan_motor_horsepower, #fan_rated_w_per_cfm, #fan_small_fan?

Constructor Details

#initializeNECB2011

Returns a new instance of NECB2011.



81
82
83
84
85
86
87
88
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 81

def initialize
  super()
  @template = self.class.name
  @standards_data = self.load_standards_database_new()
  #puts "loaded these tables..."
  #puts @standards_data.keys.size
  #raise("tables not all loaded in parent #{}") if @standards_data.keys.size < 24
end

Instance Attribute Details

#standards_dataObject

Returns the value of attribute standards_data.



7
8
9
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 7

def standards_data
  @standards_data
end

#templateObject (readonly)

Returns the value of attribute template.



6
7
8
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 6

def template
  @template
end

Instance Method Details

#add_sys1_unitary_ac_baseboard_heating(model, zones, boiler_fueltype, mau, mau_heating_coil_type, baseboard_type, hw_loop) ⇒ Object



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
1219
1220
1221
1222
1223
1224
1225
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
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
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
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1189

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

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

  always_on = model.alwaysOnDiscreteSchedule

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

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

  if mau == true

    mau_air_loop = OpenStudio::Model::AirLoopHVAC.new(model)

    mau_air_loop.setName('Make-up air unit')

    # When an air_loop is constructed, its constructor creates a sizing:system object
    # the default sizing:system constructor makes a system:sizing object
    # appropriate for a multizone VAV system
    # this systems is a constant volume system with no VAV terminals,
    # and therfore needs different default settings
    air_loop_sizing = mau_air_loop.sizingSystem # TODO units
    air_loop_sizing.setTypeofLoadtoSizeOn('VentilationRequirement')
    air_loop_sizing.autosizeDesignOutdoorAirFlowRate
    air_loop_sizing.setMinimumSystemAirFlowRatio(1.0)
    air_loop_sizing.setPreheatDesignTemperature(7.0)
    air_loop_sizing.setPreheatDesignHumidityRatio(0.008)
    air_loop_sizing.setPrecoolDesignTemperature(13.0)
    air_loop_sizing.setPrecoolDesignHumidityRatio(0.008)
    air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(13)
    air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(43)
    air_loop_sizing.setSizingOption('NonCoincident')
    air_loop_sizing.setAllOutdoorAirinCooling(true)
    air_loop_sizing.setAllOutdoorAirinHeating(true)
    air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(0.0085)
    air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(0.0080)
    air_loop_sizing.setCoolingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setCoolingDesignAirFlowRate(0.0)
    air_loop_sizing.setHeatingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setHeatingDesignAirFlowRate(0.0)
    air_loop_sizing.setSystemOutdoorAirMethod('ZoneSum')

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

    if mau_heating_coil_type == 'Electric' # electric coil
      mau_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
    end

    if mau_heating_coil_type == 'Hot Water'
      mau_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on)
      hw_loop.addDemandBranchForComponent(mau_htg_coil)
    end

    # Set up DX coil with default curves (set to NECB);

    mau_clg_coil = BTAP::Resources::HVAC::Plant.add_onespeed_DX_coil(model, always_on)

    # oa_controller
    oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model)
    # oa_controller.setEconomizerControlType("DifferentialEnthalpy")

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

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

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

  end # Create MAU

  # Create a PTAC for each zone:
  # PTAC DX Cooling with electric heating coil; electric heating coil is always off

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

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

  zones.each do |zone|
    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(13.0)
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(43.0)
    sizing_zone.setZoneCoolingSizingFactor(1.1)
    sizing_zone.setZoneHeatingSizingFactor(1.3)

    # Set up PTAC heating coil; apply always off schedule

    # htg_coil_elec = OpenStudio::Model::CoilHeatingElectric.new(model,always_on)
    htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_off)

    # Set up PTAC DX coil with NECB performance curve characteristics;
    clg_coil = BTAP::Resources::HVAC::Plant.add_onespeed_DX_coil(model, always_on)

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

    ptac = OpenStudio::Model::ZoneHVACPackagedTerminalAirConditioner.new(model,
                                                                         always_on,
                                                                         fan,
                                                                         htg_coil,
                                                                         clg_coil)
    ptac.setName("#{zone.name} PTAC")
    ptac.addToThermalZone(zone)

    # add zone baseboards
    if baseboard_type == 'Electric'

      #  zone_elec_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.new(model)
      zone_elec_baseboard = BTAP::Resources::HVAC::Plant.add_elec_baseboard(model)
      zone_elec_baseboard.addToThermalZone(zone)

    end

    if baseboard_type == 'Hot Water'
      baseboard_coil = BTAP::Resources::HVAC::Plant.add_hw_baseboard_coil(model)
      # Connect baseboard coil to hot water loop
      hw_loop.addDemandBranchForComponent(baseboard_coil)

      zone_baseboard = BTAP::Resources::HVAC::ZoneEquipment.add_zone_baseboard_convective_water(model, always_on, baseboard_coil)
      # add zone_baseboard to zone
      zone_baseboard.addToThermalZone(zone)

    end

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

      diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
      mau_air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)

    end # components for MAU
  end # of zone loop

  return true
end

#add_sys1_unitary_ac_baseboard_heating_multi_speed(model, zones, boiler_fueltype, mau, mau_heating_coil_type, baseboard_type, hw_loop) ⇒ Object

sys1_unitary_ac_baseboard_heating



1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
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
1437
1438
1439
1440
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
1476
1477
1478
1479
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
1518
1519
1520
1521
1522
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
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1362

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

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

  always_on = model.alwaysOnDiscreteSchedule

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

  # TODO: Heating and cooling temperature set point schedules are set somewhere else
  # TODO: For now fetch the schedules and use them in setting up the heat pump system
  # TODO: Later on these schedules need to be passed on to this method
  htg_temp_sch = nil
  clg_temp_sch = nil
  zones.each do |izone|
    if izone.thermostat.is_initialized
      zone_thermostat = izone.thermostat.get
      if zone_thermostat.to_ThermostatSetpointDualSetpoint.is_initialized
        dual_thermostat = zone_thermostat.to_ThermostatSetpointDualSetpoint.get
        htg_temp_sch = dual_thermostat.heatingSetpointTemperatureSchedule.get
        clg_temp_sch = dual_thermostat.coolingSetpointTemperatureSchedule.get
        break
      end
    end
  end

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

  if mau == true

    staged_thermostat = OpenStudio::Model::ZoneControlThermostatStagedDualSetpoint.new(model)
    staged_thermostat.setHeatingTemperatureSetpointSchedule(htg_temp_sch)
    staged_thermostat.setNumberofHeatingStages(4)
    staged_thermostat.setCoolingTemperatureSetpointBaseSchedule(clg_temp_sch)
    staged_thermostat.setNumberofCoolingStages(4)

    mau_air_loop = OpenStudio::Model::AirLoopHVAC.new(model)

    mau_air_loop.setName('Make-up air unit')

    # When an air_loop is constructed, its constructor creates a sizing:system object
    # the default sizing:system constructor makes a system:sizing object
    # appropriate for a multizone VAV system
    # this systems is a constant volume system with no VAV terminals,
    # and therfore needs different default settings
    air_loop_sizing = mau_air_loop.sizingSystem # TODO units
    air_loop_sizing.setTypeofLoadtoSizeOn('Sensible')
    air_loop_sizing.autosizeDesignOutdoorAirFlowRate
    air_loop_sizing.setMinimumSystemAirFlowRatio(1.0)
    air_loop_sizing.setPreheatDesignTemperature(7.0)
    air_loop_sizing.setPreheatDesignHumidityRatio(0.008)
    air_loop_sizing.setPrecoolDesignTemperature(12.8)
    air_loop_sizing.setPrecoolDesignHumidityRatio(0.008)
    air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(13.0)
    air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(43.0)
    air_loop_sizing.setSizingOption('NonCoincident')
    air_loop_sizing.setAllOutdoorAirinCooling(false)
    air_loop_sizing.setAllOutdoorAirinHeating(false)
    air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(0.0085)
    air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(0.0080)
    air_loop_sizing.setCoolingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setCoolingDesignAirFlowRate(0.0)
    air_loop_sizing.setHeatingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setHeatingDesignAirFlowRate(0.0)
    air_loop_sizing.setSystemOutdoorAirMethod('ZoneSum')

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

    # Multi-stage gas heating coil
    if mau_heating_coil_type == 'Electric' || mau_heating_coil_type == 'Hot Water'

      mau_htg_coil = OpenStudio::Model::CoilHeatingGasMultiStage.new(model)
      mau_htg_stage_1 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      mau_htg_stage_2 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      mau_htg_stage_3 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      mau_htg_stage_4 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)

      if mau_heating_coil_type == 'Electric'

        mau_supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)

      elsif mau_heating_coil_type == 'Hot Water'

        mau_supplemental_htg_coil = OpenStudio::Model::CoilHeatingWater.new(model, always_on)
        hw_loop.addDemandBranchForComponent(mau_supplemental_htg_coil)

      end

      mau_htg_stage_1.setNominalCapacity(0.1)
      mau_htg_stage_2.setNominalCapacity(0.2)
      mau_htg_stage_3.setNominalCapacity(0.3)
      mau_htg_stage_4.setNominalCapacity(0.4)

    end

    # Add stages to heating coil
    mau_htg_coil.addStage(mau_htg_stage_1)
    mau_htg_coil.addStage(mau_htg_stage_2)
    mau_htg_coil.addStage(mau_htg_stage_3)
    mau_htg_coil.addStage(mau_htg_stage_4)

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

    # Set up DX cooling coil
    mau_clg_coil = OpenStudio::Model::CoilCoolingDXMultiSpeed.new(model)
    mau_clg_coil.setFuelType('Electricity')
    mau_clg_stage_1 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_stage_2 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_stage_3 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_stage_4 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    mau_clg_coil.addStage(mau_clg_stage_1)
    mau_clg_coil.addStage(mau_clg_stage_2)
    mau_clg_coil.addStage(mau_clg_stage_3)
    mau_clg_coil.addStage(mau_clg_stage_4)

    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.new(model, mau_fan, mau_htg_coil, mau_clg_coil, mau_supplemental_htg_coil)
    #              air_to_air_heatpump.setName("#{zone.name} ASHP")
    air_to_air_heatpump.setControllingZoneorThermostatLocation(zones[1])
    air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
    air_to_air_heatpump.setNumberofSpeedsforHeating(4)
    air_to_air_heatpump.setNumberofSpeedsforCooling(4)

    # oa_controller
    oa_controller = OpenStudio::Model::ControllerOutdoorAir.new(model)
    # oa_controller.setEconomizerControlType("DifferentialEnthalpy")

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

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

  end # Create MAU

  # Create a PTAC for each zone:
  # PTAC DX Cooling with electric heating coil; electric heating coil is always off

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

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

  zones.each do |zone|
    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(13.0)
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(43.0)
    sizing_zone.setZoneCoolingSizingFactor(1.1)
    sizing_zone.setZoneHeatingSizingFactor(1.3)

    # Set up PTAC heating coil; apply always off schedule

    # htg_coil_elec = OpenStudio::Model::CoilHeatingElectric.new(model,always_on)
    htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_off)

    # Set up PTAC DX coil with NECB performance curve characteristics;
    clg_coil = BTAP::Resources::HVAC::Plant.add_onespeed_DX_coil(model, always_on)

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

    ptac = OpenStudio::Model::ZoneHVACPackagedTerminalAirConditioner.new(model,
                                                                         always_on,
                                                                         fan,
                                                                         htg_coil,
                                                                         clg_coil)
    ptac.setName("#{zone.name} PTAC")
    ptac.addToThermalZone(zone)

    # add zone baseboards
    if baseboard_type == 'Electric'

      #  zone_elec_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.new(model)
      zone_elec_baseboard = BTAP::Resources::HVAC::Plant.add_elec_baseboard(model)
      zone_elec_baseboard.addToThermalZone(zone)

    end

    if baseboard_type == 'Hot Water'
      baseboard_coil = BTAP::Resources::HVAC::Plant.add_hw_baseboard_coil(model)
      # Connect baseboard coil to hot water loop
      hw_loop.addDemandBranchForComponent(baseboard_coil)

      zone_baseboard = BTAP::Resources::HVAC::ZoneEquipment.add_zone_baseboard_convective_water(model, always_on, baseboard_coil)
      # add zone_baseboard to zone
      zone_baseboard.addToThermalZone(zone)

    end

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

      diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
      mau_air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)

    end # components for MAU
  end # of zone loop

  return true
end

#add_sys2_FPFC_sys5_TPFC(model, zones, boiler_fueltype, chiller_type, fan_coil_type, mua_cooling_type, hw_loop) ⇒ Object

sys1_unitary_ac_baseboard_heating



1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
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
1665
1666
1667
1668
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
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
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
1767
1768
1769
1770
1771
1772
1773
1774
1775
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1588

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

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

  always_on = model.alwaysOnDiscreteSchedule

  # schedule for two-pipe fan coil operation

  twenty_four_hrs = OpenStudio::Time.new(0, 24, 0, 0)

  # Heating coil availability schedule for tpfc
  tpfc_htg_availability_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  tpfc_htg_availability_sch.setName('tpfc_htg_availability')
  # Cooling coil availability schedule for tpfc
  tpfc_clg_availability_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  tpfc_clg_availability_sch.setName('tpfc_clg_availability')
  istart_month = [1, 7, 11]
  istart_day = [1, 1, 1]
  iend_month = [6, 10, 12]
  iend_day = [30, 31, 31]
  sch_htg_value = [1, 0, 1]
  sch_clg_value = [0, 1, 0]
  for i in 0..2
    tpfc_htg_availability_sch_rule = OpenStudio::Model::ScheduleRule.new(tpfc_htg_availability_sch)
    tpfc_htg_availability_sch_rule.setName('tpfc_htg_availability_sch_rule')
    tpfc_htg_availability_sch_rule.setStartDate(model.getYearDescription.makeDate(istart_month[i], istart_day[i]))
    tpfc_htg_availability_sch_rule.setEndDate(model.getYearDescription.makeDate(iend_month[i], iend_day[i]))
    tpfc_htg_availability_sch_rule.setApplySunday(true)
    tpfc_htg_availability_sch_rule.setApplyMonday(true)
    tpfc_htg_availability_sch_rule.setApplyTuesday(true)
    tpfc_htg_availability_sch_rule.setApplyWednesday(true)
    tpfc_htg_availability_sch_rule.setApplyThursday(true)
    tpfc_htg_availability_sch_rule.setApplyFriday(true)
    tpfc_htg_availability_sch_rule.setApplySaturday(true)
    day_schedule = tpfc_htg_availability_sch_rule.daySchedule
    day_schedule.setName('tpfc_htg_availability_sch_rule_day')
    day_schedule.addValue(twenty_four_hrs, sch_htg_value[i])

    tpfc_clg_availability_sch_rule = OpenStudio::Model::ScheduleRule.new(tpfc_clg_availability_sch)
    tpfc_clg_availability_sch_rule.setName('tpfc_clg_availability_sch_rule')
    tpfc_clg_availability_sch_rule.setStartDate(model.getYearDescription.makeDate(istart_month[i], istart_day[i]))
    tpfc_clg_availability_sch_rule.setEndDate(model.getYearDescription.makeDate(iend_month[i], iend_day[i]))
    tpfc_clg_availability_sch_rule.setApplySunday(true)
    tpfc_clg_availability_sch_rule.setApplyMonday(true)
    tpfc_clg_availability_sch_rule.setApplyTuesday(true)
    tpfc_clg_availability_sch_rule.setApplyWednesday(true)
    tpfc_clg_availability_sch_rule.setApplyThursday(true)
    tpfc_clg_availability_sch_rule.setApplyFriday(true)
    tpfc_clg_availability_sch_rule.setApplySaturday(true)
    day_schedule = tpfc_clg_availability_sch_rule.daySchedule
    day_schedule.setName('tpfc_clg_availability_sch_rule_day')
    day_schedule.addValue(twenty_four_hrs, sch_clg_value[i])

  end

  # Create a chilled water loop

  chw_loop = OpenStudio::Model::PlantLoop.new(model)
  chiller1, chiller2 = BTAP::Resources::HVAC::HVACTemplates::NECB2011.setup_chw_loop_with_components(model, chw_loop, chiller_type)

  # Create a condenser Loop

  cw_loop = OpenStudio::Model::PlantLoop.new(model)
  ctower = BTAP::Resources::HVAC::HVACTemplates::NECB2011.setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2)

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

  air_loop = OpenStudio::Model::AirLoopHVAC.new(model)

  air_loop.setName('Make-up air unit')

  # When an air_loop is contructed, its constructor creates a sizing:system object
  # the default sizing:system constructor makes a system:sizing object
  # appropriate for a multizone VAV system
  # this systems is a constant volume system with no VAV terminals,
  # and therfore needs different default settings
  air_loop_sizing = air_loop.sizingSystem # TODO units
  air_loop_sizing.setTypeofLoadtoSizeOn('Sensible')
  air_loop_sizing.autosizeDesignOutdoorAirFlowRate
  air_loop_sizing.setMinimumSystemAirFlowRatio(1.0)
  air_loop_sizing.setPreheatDesignTemperature(7.0)
  air_loop_sizing.setPreheatDesignHumidityRatio(0.008)
  air_loop_sizing.setPrecoolDesignTemperature(13.0)
  air_loop_sizing.setPrecoolDesignHumidityRatio(0.008)
  air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(13.0)
  air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(13.1)
  air_loop_sizing.setSizingOption('NonCoincident')
  air_loop_sizing.setAllOutdoorAirinCooling(false)
  air_loop_sizing.setAllOutdoorAirinHeating(false)
  air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(0.008)
  air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(0.008)
  air_loop_sizing.setCoolingDesignAirFlowMethod('DesignDay')
  air_loop_sizing.setCoolingDesignAirFlowRate(0.0)
  air_loop_sizing.setHeatingDesignAirFlowMethod('DesignDay')
  air_loop_sizing.setHeatingDesignAirFlowRate(0.0)
  air_loop_sizing.setSystemOutdoorAirMethod('ZoneSum')

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

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

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

  # Add DX or hydronic cooling coil
  if mua_cooling_type == 'DX'
    clg_coil = BTAP::Resources::HVAC::Plant.add_onespeed_DX_coil(model, tpfc_clg_availability_sch)
  elsif mua_cooling_type == 'Hydronic'
    clg_coil = OpenStudio::Model::CoilCoolingWater.new(model, tpfc_clg_availability_sch)
    chw_loop.addDemandBranchForComponent(clg_coil)
  end

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

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

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

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

  setpoint_mgr_single_zone_reheat = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model)
  setpoint_mgr_single_zone_reheat.setMinimumSupplyAirTemperature(13.0)
  setpoint_mgr_single_zone_reheat.setMaximumSupplyAirTemperature(13.1)
  setpoint_mgr_single_zone_reheat.addToNode(air_loop.supplyOutletNode)

  # Set up FC (ZoneHVAC,cooling coil, heating coil, fan) in each zone

  zones.each do |zone|
    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(13.0)
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(43.0)
    sizing_zone.setZoneCoolingSizingFactor(1.1)
    sizing_zone.setZoneHeatingSizingFactor(1.3)

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

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

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

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

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

    zone_fc = OpenStudio::Model::ZoneHVACFourPipeFanCoil.new(model, always_on, fc_fan, fc_clg_coil, fc_htg_coil)
    zone_fc.addToThermalZone(zone)

    # Create a diffuser and attach the zone/diffuser pair to the air loop (make-up air unit)
    diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
    air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)
  end # zone loop
end

#add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed(model, zones, boiler_fueltype, heating_coil_type, baseboard_type, hw_loop) ⇒ Object

end add_sys3_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed



1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
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
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1919

def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed(model, zones, boiler_fueltype, heating_coil_type, baseboard_type, hw_loop)
  # System Type 3: PSZ-AC
  # This measure creates:
  # -a constant volume packaged single-zone A/C unit
  # for each zone in the building; DX cooling with
  # heating coil: fuel-fired or electric, depending on argument heating_coil_type
  # heating_coil_type choices are "Electric", "Gas", "DX"
  # zone baseboards: hot water or electric, depending on argument baseboard_type
  # baseboard_type choices are "Hot Water" or "Electric"
  # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e.
  # "NaturalGas","Electricity","PropaneGas","FuelOil#1","FuelOil#2","Coal","Diesel","Gasoline","OtherFuel1"

  always_on = model.alwaysOnDiscreteSchedule

  # TODO: Heating and cooling temperature set point schedules are set somewhere else
  # TODO: For now fetch the schedules and use them in setting up the heat pump system
  # TODO: Later on these schedules need to be passed on to this method
  htg_temp_sch = nil
  clg_temp_sch = nil
  zones.each do |izone|
    if izone.thermostat.is_initialized
      zone_thermostat = izone.thermostat.get
      if zone_thermostat.to_ThermostatSetpointDualSetpoint.is_initialized
        dual_thermostat = zone_thermostat.to_ThermostatSetpointDualSetpoint.get
        htg_temp_sch = dual_thermostat.heatingSetpointTemperatureSchedule.get
        clg_temp_sch = dual_thermostat.coolingSetpointTemperatureSchedule.get
        break
      end
    end
  end

  zones.each do |zone|
    air_loop = OpenStudio::Model::AirLoopHVAC.new(model)

    air_loop.setName("#{zone.name} NECB System 3 PSZ")

    # When an air_loop is constructed, its constructor creates a sizing:system object
    # the default sizing:system constructor makes a system:sizing object
    # appropriate for a multizone VAV system
    # this systems is a constant volume system with no VAV terminals,
    # and therfore needs different default settings
    air_loop_sizing = air_loop.sizingSystem # TODO units
    air_loop_sizing.setTypeofLoadtoSizeOn('Sensible')
    air_loop_sizing.autosizeDesignOutdoorAirFlowRate
    air_loop_sizing.setMinimumSystemAirFlowRatio(1.0)
    air_loop_sizing.setPreheatDesignTemperature(7.0)
    air_loop_sizing.setPreheatDesignHumidityRatio(0.008)
    air_loop_sizing.setPrecoolDesignTemperature(13.0)
    air_loop_sizing.setPrecoolDesignHumidityRatio(0.008)
    air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(13.0)
    air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(43.0)
    air_loop_sizing.setSizingOption('NonCoincident')
    air_loop_sizing.setAllOutdoorAirinCooling(false)
    air_loop_sizing.setAllOutdoorAirinHeating(false)
    air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(0.0085)
    air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(0.0080)
    air_loop_sizing.setCoolingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setCoolingDesignAirFlowRate(0.0)
    air_loop_sizing.setHeatingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setHeatingDesignAirFlowRate(0.0)
    air_loop_sizing.setSystemOutdoorAirMethod('ZoneSum')

    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(13.0)
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(43.0)
    sizing_zone.setZoneCoolingSizingFactor(1.1)
    sizing_zone.setZoneHeatingSizingFactor(1.3)

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

    staged_thermostat = OpenStudio::Model::ZoneControlThermostatStagedDualSetpoint.new(model)
    staged_thermostat.setHeatingTemperatureSetpointSchedule(htg_temp_sch)
    staged_thermostat.setNumberofHeatingStages(4)
    staged_thermostat.setCoolingTemperatureSetpointBaseSchedule(clg_temp_sch)
    staged_thermostat.setNumberofCoolingStages(4)
    zone.setThermostat(staged_thermostat)

    # Multi-stage gas heating coil
    if heating_coil_type == 'Gas' || heating_coil_type == 'Electric'
      htg_coil = OpenStudio::Model::CoilHeatingGasMultiStage.new(model)
      htg_stage_1 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      htg_stage_2 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      htg_stage_3 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      htg_stage_4 = OpenStudio::Model::CoilHeatingGasMultiStageStageData.new(model)
      if heating_coil_type == 'Gas'
        supplemental_htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
      elsif heating_coil_type == 'Electric'
        supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
        htg_stage_1.setNominalCapacity(0.1)
        htg_stage_2.setNominalCapacity(0.2)
        htg_stage_3.setNominalCapacity(0.3)
        htg_stage_4.setNominalCapacity(0.4)
      end

      # Multi-Stage DX or Electric heating coil
    elsif heating_coil_type == 'DX'
      htg_coil = OpenStudio::Model::CoilHeatingDXMultiSpeed.new(model)
      htg_stage_1 = OpenStudio::Model::CoilHeatingDXMultiSpeedStageData.new(model)
      htg_stage_2 = OpenStudio::Model::CoilHeatingDXMultiSpeedStageData.new(model)
      htg_stage_3 = OpenStudio::Model::CoilHeatingDXMultiSpeedStageData.new(model)
      htg_stage_4 = OpenStudio::Model::CoilHeatingDXMultiSpeedStageData.new(model)
      supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
      sizing_zone.setZoneHeatingSizingFactor(1.3)
      sizing_zone.setZoneCoolingSizingFactor(1.0)
    else
      raise("#{heating_coil_type} is not a valid heating coil type.)")
    end

    # Add stages to heating coil
    htg_coil.addStage(htg_stage_1)
    htg_coil.addStage(htg_stage_2)
    htg_coil.addStage(htg_stage_3)
    htg_coil.addStage(htg_stage_4)

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

    # Set up DX cooling coil
    clg_coil = OpenStudio::Model::CoilCoolingDXMultiSpeed.new(model)
    clg_coil.setFuelType('Electricity')
    clg_stage_1 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_stage_2 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_stage_3 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_stage_4 = OpenStudio::Model::CoilCoolingDXMultiSpeedStageData.new(model)
    clg_coil.addStage(clg_stage_1)
    clg_coil.addStage(clg_stage_2)
    clg_coil.addStage(clg_stage_3)
    clg_coil.addStage(clg_stage_4)

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

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

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

    air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.new(model, fan, htg_coil, clg_coil, supplemental_htg_coil)
    air_to_air_heatpump.setName("#{zone.name} ASHP")
    air_to_air_heatpump.setControllingZoneorThermostatLocation(zone)
    air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
    air_to_air_heatpump.addToNode(supply_inlet_node)
    air_to_air_heatpump.setNumberofSpeedsforHeating(4)
    air_to_air_heatpump.setNumberofSpeedsforCooling(4)

    oa_system.addToNode(supply_inlet_node)

    # Create a diffuser and attach the zone/diffuser pair to the air loop
    # diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model,always_on)
    diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
    air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)

    if baseboard_type == 'Electric'

      #  zone_elec_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.new(model)
      zone_elec_baseboard = BTAP::Resources::HVAC::Plant.add_elec_baseboard(model)
      zone_elec_baseboard.addToThermalZone(zone)

    end

    if baseboard_type == 'Hot Water'
      baseboard_coil = BTAP::Resources::HVAC::Plant.add_hw_baseboard_coil(model)
      # Connect baseboard coil to hot water loop
      hw_loop.addDemandBranchForComponent(baseboard_coil)

      zone_baseboard = BTAP::Resources::HVAC::ZoneEquipment.add_zone_baseboard_convective_water(model, always_on, baseboard_coil)
      # add zone_baseboard to zone
      zone_baseboard.addToThermalZone(zone)
    end
  end # zone loop

  return true
end

#add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model, zones, boiler_fueltype, heating_coil_type, baseboard_type, hw_loop) ⇒ Object

add_sys2_FPFC_sys5_TPFC



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
1825
1826
1827
1828
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
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1779

def add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model, zones, boiler_fueltype, heating_coil_type, baseboard_type, hw_loop)
  # System Type 3: PSZ-AC
  # This measure creates:
  # -a constant volume packaged single-zone A/C unit
  # for each zone in the building; DX cooling with
  # heating coil: fuel-fired or electric, depending on argument heating_coil_type
  # heating_coil_type choices are "Electric", "Gas", "DX"
  # zone baseboards: hot water or electric, depending on argument baseboard_type
  # baseboard_type choices are "Hot Water" or "Electric"
  # boiler_fueltype choices match OS choices for Boiler component fuel type, i.e.
  # "NaturalGas","Electricity","PropaneGas","FuelOil#1","FuelOil#2","Coal","Diesel","Gasoline","OtherFuel1"

  always_on = model.alwaysOnDiscreteSchedule

  zones.each do |zone|
    air_loop = OpenStudio::Model::AirLoopHVAC.new(model)

    air_loop.setName("#{zone.name} NECB System 3 PSZ")

    # When an air_loop is constructed, its constructor creates a sizing:system object
    # the default sizing:system constructor makes a system:sizing object
    # appropriate for a multizone VAV system
    # this systems is a constant volume system with no VAV terminals,
    # and therfore needs different default settings
    air_loop_sizing = air_loop.sizingSystem # TODO units
    air_loop_sizing.setTypeofLoadtoSizeOn('Sensible')
    air_loop_sizing.autosizeDesignOutdoorAirFlowRate
    air_loop_sizing.setMinimumSystemAirFlowRatio(1.0)
    air_loop_sizing.setPreheatDesignTemperature(7.0)
    air_loop_sizing.setPreheatDesignHumidityRatio(0.008)
    air_loop_sizing.setPrecoolDesignTemperature(13.0)
    air_loop_sizing.setPrecoolDesignHumidityRatio(0.008)
    air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(13.0)
    air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(43)
    air_loop_sizing.setSizingOption('NonCoincident')
    air_loop_sizing.setAllOutdoorAirinCooling(false)
    air_loop_sizing.setAllOutdoorAirinHeating(false)
    air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(0.0085)
    air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(0.0080)
    air_loop_sizing.setCoolingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setCoolingDesignAirFlowRate(0.0)
    air_loop_sizing.setHeatingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setHeatingDesignAirFlowRate(0.0)
    air_loop_sizing.setSystemOutdoorAirMethod('ZoneSum')

    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(13.0)
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(43.0)
    sizing_zone.setZoneCoolingSizingFactor(1.1)
    sizing_zone.setZoneHeatingSizingFactor(1.3)

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

    case heating_coil_type
      when 'Electric' # electric coil
        htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)

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

      when 'DX'
        htg_coil = OpenStudio::Model::CoilHeatingDXSingleSpeed.new(model)
        supplemental_htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
        htg_coil.setMinimumOutdoorDryBulbTemperatureforCompressorOperation(-10.0)
        sizing_zone.setZoneHeatingSizingFactor(1.3)
        sizing_zone.setZoneCoolingSizingFactor(1.0)
      else
        raise("#{heating_coil_type} is not a valid heating coil type.)")
    end

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

    # Set up DX coil with NECB performance curve characteristics;
    clg_coil = OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model)

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

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

    # Add the components to the air loop
    # in order from closest to zone to furthest from zone
    supply_inlet_node = air_loop.supplyInletNode
    #              fan.addToNode(supply_inlet_node)
    #              supplemental_htg_coil.addToNode(supply_inlet_node) if heating_coil_type == "DX"
    #              htg_coil.addToNode(supply_inlet_node)
    #              clg_coil.addToNode(supply_inlet_node)
    #              oa_system.addToNode(supply_inlet_node)
    if heating_coil_type == 'DX'
      air_to_air_heatpump = OpenStudio::Model::AirLoopHVACUnitaryHeatPumpAirToAir.new(model, always_on, fan, htg_coil, clg_coil, supplemental_htg_coil)
      air_to_air_heatpump.setName("#{zone.name} ASHP")
      air_to_air_heatpump.setControllingZone(zone)
      air_to_air_heatpump.setSupplyAirFanOperatingModeSchedule(always_on)
      air_to_air_heatpump.addToNode(supply_inlet_node)
    else
      fan.addToNode(supply_inlet_node)
      htg_coil.addToNode(supply_inlet_node)
      clg_coil.addToNode(supply_inlet_node)
    end
    oa_system.addToNode(supply_inlet_node)

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

    # Create a diffuser and attach the zone/diffuser pair to the air loop
    # diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model,always_on)
    diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
    air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)

    if baseboard_type == 'Electric'

      #  zone_elec_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.new(model)
      zone_elec_baseboard = BTAP::Resources::HVAC::Plant.add_elec_baseboard(model)
      zone_elec_baseboard.addToThermalZone(zone)

    end

    if baseboard_type == 'Hot Water'
      baseboard_coil = BTAP::Resources::HVAC::Plant.add_hw_baseboard_coil(model)
      # Connect baseboard coil to hot water loop
      hw_loop.addDemandBranchForComponent(baseboard_coil)

      zone_baseboard = BTAP::Resources::HVAC::ZoneEquipment.add_zone_baseboard_convective_water(model, always_on, baseboard_coil)
      # add zone_baseboard to zone
      zone_baseboard.addToThermalZone(zone)
    end
  end # zone loop

  return true
end

#add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model, zones, boiler_fueltype, heating_coil_type, baseboard_type, hw_loop) ⇒ Object

end add_sys3_single_zone_packaged_rooftop_unit_with_baseboard_heating_multi_speed



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
2179
2180
2181
2182
2183
2184
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 2097

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

  always_on = model.alwaysOnDiscreteSchedule

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

  zones.each do |zone|
    air_loop = OpenStudio::Model::AirLoopHVAC.new(model)

    air_loop.setName("#{zone.name} NECB System 4 PSZ")

    # When an air_loop is constructed, its constructor creates a sizing:system object
    # the default sizing:system constructor makes a system:sizing object
    # appropriate for a multizone VAV system
    # this systems is a constant volume system with no VAV terminals,
    # and therfore needs different default settings
    air_loop_sizing = air_loop.sizingSystem # TODO units
    air_loop_sizing.setTypeofLoadtoSizeOn('Sensible')
    air_loop_sizing.autosizeDesignOutdoorAirFlowRate
    air_loop_sizing.setMinimumSystemAirFlowRatio(1.0)
    air_loop_sizing.setPreheatDesignTemperature(7.0)
    air_loop_sizing.setPreheatDesignHumidityRatio(0.008)
    air_loop_sizing.setPrecoolDesignTemperature(13.0)
    air_loop_sizing.setPrecoolDesignHumidityRatio(0.008)
    air_loop_sizing.setCentralCoolingDesignSupplyAirTemperature(13.0)
    air_loop_sizing.setCentralHeatingDesignSupplyAirTemperature(43.0)
    air_loop_sizing.setSizingOption('NonCoincident')
    air_loop_sizing.setAllOutdoorAirinCooling(false)
    air_loop_sizing.setAllOutdoorAirinHeating(false)
    air_loop_sizing.setCentralCoolingDesignSupplyAirHumidityRatio(0.0085)
    air_loop_sizing.setCentralHeatingDesignSupplyAirHumidityRatio(0.0080)
    air_loop_sizing.setCoolingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setCoolingDesignAirFlowRate(0.0)
    air_loop_sizing.setHeatingDesignAirFlowMethod('DesignDay')
    air_loop_sizing.setHeatingDesignAirFlowRate(0.0)
    air_loop_sizing.setSystemOutdoorAirMethod('ZoneSum')

    # Zone sizing temperature
    sizing_zone = zone.sizingZone
    sizing_zone.setZoneCoolingDesignSupplyAirTemperature(13.0)
    sizing_zone.setZoneHeatingDesignSupplyAirTemperature(43.0)
    sizing_zone.setZoneCoolingSizingFactor(1.1)
    sizing_zone.setZoneHeatingSizingFactor(1.3)

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

    if heating_coil_type == 'Electric' # electric coil
      htg_coil = OpenStudio::Model::CoilHeatingElectric.new(model, always_on)
    end

    if heating_coil_type == 'Gas'
      htg_coil = OpenStudio::Model::CoilHeatingGas.new(model, always_on)
    end

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

    # Set up DX coil with NECB performance curve characteristics;

    clg_coil = BTAP::Resources::HVAC::Plant.add_onespeed_DX_coil(model, always_on)

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

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

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

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

    # Create sensible heat exchanger
    #              heat_exchanger = BTAP::Resources::HVAC::Plant::add_hrv(model)
    #              heat_exchanger.setSensibleEffectivenessat100HeatingAirFlow(0.5)
    #              heat_exchanger.setSensibleEffectivenessat75HeatingAirFlow(0.5)
    #              heat_exchanger.setSensibleEffectivenessat100CoolingAirFlow(0.5)
    #              heat_exchanger.setSensibleEffectivenessat75CoolingAirFlow(0.5)
    #              heat_exchanger.setLatentEffectivenessat100HeatingAirFlow(0.0)
    #              heat_exchanger.setLatentEffectivenessat75HeatingAirFlow(0.0)
    #              heat_exchanger.setLatentEffectivenessat100CoolingAirFlow(0.0)
    #              heat_exchanger.setLatentEffectivenessat75CoolingAirFlow(0.0)
    #              heat_exchanger.setSupplyAirOutletTemperatureControl(false)
    #
    #              Connect heat exchanger
    #              oa_node = oa_system.outboardOANode
    #              heat_exchanger.addToNode(oa_node.get)

    # Create a diffuser and attach the zone/diffuser pair to the air loop
    # diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model,always_on)
    diffuser = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, always_on)
    air_loop.addBranchForZone(zone, diffuser.to_StraightComponent)

    if baseboard_type == 'Electric'

      #  zone_elec_baseboard = OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.new(model)
      zone_elec_baseboard = BTAP::Resources::HVAC::Plant.add_elec_baseboard(model)
      zone_elec_baseboard.addToThermalZone(zone)

    end

    if baseboard_type == 'Hot Water'
      baseboard_coil = BTAP::Resources::HVAC::Plant.add_hw_baseboard_coil(model)
      # Connect baseboard coil to hot water loop
      hw_loop.addDemandBranchForComponent(baseboard_coil)

      zone_baseboard = BTAP::Resources::HVAC::ZoneEquipment.add_zone_baseboard_convective_water(model, always_on, baseboard_coil)
      # add zone_baseboard to zone
      zone_baseboard.addToThermalZone(zone)
    end
  end # zone loop

  return true
end

#add_sys6_multi_zone_built_up_system_with_baseboard_heating(model, zones, boiler_fueltype, heating_coil_type, baseboard_type, chiller_type, fan_type, hw_loop) ⇒ Object

end add_sys4_single_zone_make_up_air_unit_with_baseboard_heating



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
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
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
2320
2321
2322
2323
2324
2325
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
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 2240

def add_sys6_multi_zone_built_up_system_with_baseboard_heating(model, zones, boiler_fueltype, heating_coil_type, baseboard_type, chiller_type, fan_type, hw_loop)
  # System Type 6: VAV w/ Reheat
  # This measure creates:
  # a single hot water loop with a natural gas or electric boiler or for the building
  # a single chilled water loop with water cooled chiller for the building
  # a single condenser water loop for heat rejection from the chiller
  # a VAV system w/ hot water or electric heating, chilled water cooling, and
  # hot water or electric reheat for each story of the building
  # Arguments:
  # "boiler_fueltype" choices match OS choices for boiler fuel type:
  # "NaturalGas","Electricity","PropaneGas","FuelOil#1","FuelOil#2","Coal","Diesel","Gasoline","OtherFuel1"
  # "heating_coil_type": "Electric" or "Hot Water"
  # "baseboard_type": "Electric" and "Hot Water"
  # "chiller_type": "Scroll";"Centrifugal";""Screw";"Reciprocating"
  # "fan_type": "AF_or_BI_rdg_fancurve";"AF_or_BI_inletvanes";"fc_inletvanes";"var_speed_drive"

  always_on = model.alwaysOnDiscreteSchedule

  # Chilled Water Plant

  chw_loop = OpenStudio::Model::PlantLoop.new(model)
  chiller1, chiller2 = BTAP::Resources::HVAC::HVACTemplates::NECB2011.setup_chw_loop_with_components(model, chw_loop, chiller_type)

  # Condenser System

  cw_loop = OpenStudio::Model::PlantLoop.new(model)
  ctower = BTAP::Resources::HVAC::HVACTemplates::NECB2011.setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2)

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

      air_loop = OpenStudio::Model::AirLoopHVAC.new(model)
      air_loop.setName('VAV with Reheat')
      sizing_system = air_loop.sizingSystem
      sizing_system.setCentralCoolingDesignSupplyAirTemperature(13.0)
      sizing_system.setCentralHeatingDesignSupplyAirTemperature(13.1)
      sizing_system.autosizeDesignOutdoorAirFlowRate
      sizing_system.setMinimumSystemAirFlowRatio(0.3)
      sizing_system.setPreheatDesignTemperature(7.0)
      sizing_system.setPreheatDesignHumidityRatio(0.008)
      sizing_system.setPrecoolDesignTemperature(13.0)
      sizing_system.setPrecoolDesignHumidityRatio(0.008)
      sizing_system.setSizingOption('NonCoincident')
      sizing_system.setAllOutdoorAirinCooling(false)
      sizing_system.setAllOutdoorAirinHeating(false)
      sizing_system.setCentralCoolingDesignSupplyAirHumidityRatio(0.0085)
      sizing_system.setCentralHeatingDesignSupplyAirHumidityRatio(0.0080)
      sizing_system.setCoolingDesignAirFlowMethod('DesignDay')
      sizing_system.setCoolingDesignAirFlowRate(0.0)
      sizing_system.setHeatingDesignAirFlowMethod('DesignDay')
      sizing_system.setHeatingDesignAirFlowRate(0.0)
      sizing_system.setSystemOutdoorAirMethod('ZoneSum')

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

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

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

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

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

      # Add the components to the air loop
      # in order from closest to zone to furthest from zone
      # TODO: still need to define the return fan (tried to access the air loop "returnAirNode" without success)
      # TODO: The OS sdk indicates that this keyword should be active but I get a "Not implemented" error when I
      # TODO: try to access it through "air_loop.returnAirNode"
      supply_inlet_node = air_loop.supplyInletNode
      supply_outlet_node = air_loop.supplyOutletNode
      fan.addToNode(supply_inlet_node)
      htg_coil.addToNode(supply_inlet_node)
      clg_coil.addToNode(supply_inlet_node)
      oa_system.addToNode(supply_inlet_node)

      # return_inlet_node = air_loop.returnAirNode

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

      # TO-do ask Kamel about zonal assignments per storey.

      # Make a VAV terminal with HW reheat for each zone on this story that is in intersection with the zones array.
      # and hook the reheat coil to the HW loop
      (BTAP::Geometry::BuildingStoreys.get_zones_from_storey(story) & zones).each do |zone|
        # Zone sizing parameters
        sizing_zone = zone.sizingZone
        sizing_zone.setZoneCoolingDesignSupplyAirTemperature(13.0)
        sizing_zone.setZoneHeatingDesignSupplyAirTemperature(43.0)
        sizing_zone.setZoneCoolingSizingFactor(1.1)
        sizing_zone.setZoneHeatingSizingFactor(1.3)

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

        vav_terminal = OpenStudio::Model::AirTerminalSingleDuctVAVReheat.new(model, always_on, reheat_coil)
        air_loop.addBranchForZone(zone, vav_terminal.to_StraightComponent)
        # NECB2011 minimum zone airflow setting
        min_flow_rate = 0.002 * zone.floorArea
        vav_terminal.setFixedMinimumAirFlowRate(min_flow_rate)
        vav_terminal.setMaximumReheatAirTemperature(43.0)
        vav_terminal.setDamperHeatingAction('Normal')

        # Set zone baseboards
        if baseboard_type == 'Electric'
          zone_elec_baseboard = BTAP::Resources::HVAC::Plant.add_elec_baseboard(model)
          zone_elec_baseboard.addToThermalZone(zone)
        end
        if baseboard_type == 'Hot Water'
          baseboard_coil = BTAP::Resources::HVAC::Plant.add_hw_baseboard_coil(model)
          # Connect baseboard coil to hot water loop
          hw_loop.addDemandBranchForComponent(baseboard_coil)
          zone_baseboard = BTAP::Resources::HVAC::ZoneEquipment.add_zone_baseboard_convective_water(model, always_on, baseboard_coil)
          # add zone_baseboard to zone
          zone_baseboard.addToThermalZone(zone)
        end
      end
    end
  end # next story

  # for debugging
  # puts "end add_sys6_multi_zone_built_up_with_baseboard_heating"

  return true
end

#air_loop_hvac_apply_economizer_integration(air_loop_hvac, climate_zone) ⇒ Bool

Note:

this method assumes you previously checked that an economizer is required at all via #economizer_required?

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

Returns:

  • (Bool)

    returns true if successful, false if not



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 72

def air_loop_hvac_apply_economizer_integration(air_loop_hvac, climate_zone)
  # Get the OA system and OA controller
  oa_sys = air_loop_hvac.airLoopHVACOutdoorAirSystem
  if oa_sys.is_initialized
    oa_sys = oa_sys.get
  else
    return false # No OA system
  end
  oa_control = oa_sys.getControllerOutdoorAir

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

  return true
end

#air_loop_hvac_apply_energy_recovery_ventilator(air_loop_hvac) ⇒ Bool

TODO:

Add exception logic for systems serving parking garage, warehouse, or multifamily

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

Returns:

  • (Bool)

    Returns true if required, false if not.



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

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

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

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

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

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

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

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

  return true
end

#air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(air_loop_hvac) ⇒ Object

NECB does not change damper positions

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



13
14
15
16
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 13

def air_loop_hvac_apply_multizone_vav_outdoor_air_sizing(air_loop_hvac)
  # Do not change anything.
  return true
end

#air_loop_hvac_apply_single_zone_controls(air_loop_hvac, climate_zone) ⇒ Bool

NECB has no single zone air loop control requirements

Returns:

  • (Bool)

    returns true if successful, false if not



417
418
419
420
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 417

def air_loop_hvac_apply_single_zone_controls(air_loop_hvac, climate_zone)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: No special economizer controls were modeled.")
  return true
end

#air_loop_hvac_apply_vav_damper_action(air_loop_hvac) ⇒ Bool

TODO:

see if this impacts the sizing run.

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

Returns:

  • (Bool)

    Returns true if successful, false if not



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

def air_loop_hvac_apply_vav_damper_action(air_loop_hvac)
  damper_action = 'Single Maximum'

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

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

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

  return true
end

#air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac, climate_zone) ⇒ Bool

TODO:

Add exception logic for systems that serve multifamily, parking garage, warehouse

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

Returns:

  • (Bool)

    Returns true if required, false if not.



358
359
360
361
362
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 358

def air_loop_hvac_demand_control_ventilation_required?(air_loop_hvac, climate_zone)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{template} #{climate_zone}:  #{air_loop_hvac.name}: DCV is not required for any system.")
  dcv_required = false
  return dcv_required
end

#air_loop_hvac_economizer_required?(air_loop_hvac) ⇒ Bool

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

Returns:

  • (Bool)

    returns true if an economizer is required, 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
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 22

def air_loop_hvac_economizer_required?(air_loop_hvac)
  economizer_required = false

  # need a better way to determine if an economizer is needed.
  return economizer_required if air_loop_hvac.name.to_s.include? 'Outpatient F1'

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

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

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

  # Check whether the system requires an economizer by comparing
  # the system capacity to the minimum capacity.
  total_cooling_capacity_w = air_loop_hvac_total_cooling_capacity(air_loop_hvac)
  total_cooling_capacity_btu_per_hr = OpenStudio.convert(total_cooling_capacity_w, 'W', 'Btu/hr').get
  if total_cooling_capacity_btu_per_hr >= minimum_capacity_btu_per_hr
    if is_dc
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr for data centers.")
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr.")
    end
    economizer_required = true
  else
    if is_dc
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} does not require an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr is less than the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr for data centers.")
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{air_loop_hvac.name} does not require an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr is less than the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr.")
    end
  end

  return economizer_required
end

#air_loop_hvac_energy_recovery_ventilator_required?(air_loop_hvac, climate_zone) ⇒ Bool

TODO:

Add exception logic for systems serving parking garage, warehouse, or multifamily

Check if ERV is required on this airloop.

Returns:

  • (Bool)

    Returns true if required, false if not.



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
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 93

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

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

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

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

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

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

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

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

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

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

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

  # initialize counters
  sum_zone_oa = 0.0
  sum_zone_oa_times_heat_design_t = 0.0

  # zone loop
  zones.each do |zone|
    # get design heat temperature for each zone; this is equivalent to design exhaust temperature
    heat_design_t = 21.0
    zone_thermostat = zone.thermostat.get
    if zone_thermostat.to_ThermostatSetpointDualSetpoint.is_initialized
      dual_thermostat = zone_thermostat.to_ThermostatSetpointDualSetpoint.get
      htg_temp_sch = dual_thermostat.heatingSetpointTemperatureSchedule.get
      htg_temp_sch_ruleset = htg_temp_sch.to_ScheduleRuleset.get
      winter_dd_sch = htg_temp_sch_ruleset.winterDesignDaySchedule
      heat_design_t = winter_dd_sch.values.max
    end

    # initialize counter
    zone_oa = 0.0
    # outdoor defined at space level; get OA flow for all spaces within zone
    spaces = zone.spaces

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

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

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

  # Get January winter design temperature
  # get model weather file name
  weather_file = BTAP::Environment::WeatherFile.new(air_loop_hvac.model.weatherFile.get.path.get)

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

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

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

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

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

  return erv_required
end

#air_loop_hvac_motorized_oa_damper_limits(air_loop_hvac, climate_zone) ⇒ Array<Double>

Determine the air flow and number of story limits for whether motorized OA damper is required. If both nil, never required

Returns:

  • (Array<Double>)

    [minimum_oa_flow_cfm, maximum_stories].



435
436
437
438
439
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 435

def air_loop_hvac_motorized_oa_damper_limits(air_loop_hvac, climate_zone)
  minimum_oa_flow_cfm = 0
  maximum_stories = 0
  return [minimum_oa_flow_cfm, maximum_stories]
end

#air_loop_hvac_static_pressure_reset_required?(air_loop_hvac, has_ddc) ⇒ Boolean

NECB doesn’t require static pressure reset.

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

Returns:

  • (Boolean)


425
426
427
428
429
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 425

def air_loop_hvac_static_pressure_reset_required?(air_loop_hvac, has_ddc)
  # static pressure reset not required
  sp_reset_required = false
  return sp_reset_required
end

#apply_building_default_constructionset(building_type, climate_zone, model) ⇒ Object



421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 421

def apply_building_default_constructionset(building_type, climate_zone, model)
  @lookup_building_type = model_get_lookup_name(building_type)
  # TODO: this is a workaround.  Need to synchronize the building type names
  # across different parts of the code, including splitting of Office types
  case building_type
    when 'SmallOffice', 'MediumOffice', 'LargeOffice'
      new_lookup_building_type = building_type
    else
      new_lookup_building_type = model_get_lookup_name(building_type)
  end
  # Make the default construction set for the building
  spc_type = 'WholeBuilding'
  bldg_def_const_set = model_add_construction_set(model, climate_zone, new_lookup_building_type, spc_type)

  if bldg_def_const_set.is_initialized
    model.getBuilding.setDefaultConstructionSet(bldg_def_const_set.get)
  else
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Could not create default construction set for the building.')
    raise('hell')
  end
end

#apply_default_constructionsets_to_spacetypes(climate_zone, model) ⇒ Object



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
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 443

def apply_default_constructionsets_to_spacetypes(climate_zone, model)
  model.getSpaceTypes.sort.each do |space_type|
    # Get the standards building type
    stds_building_type = nil
    if space_type.standardsBuildingType.is_initialized
      stds_building_type = space_type.standardsBuildingType.get
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards building type.")
    end

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

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

    # Attempt to make a construction set for this space type
    # and assign it if it can be created.
    spc_type_const_set = model_add_construction_set(model, climate_zone, stds_building_type, stds_spc_type)
    if spc_type_const_set.is_initialized
      space_type.setDefaultConstructionSet(spc_type_const_set.get)
    end
  end
end

#apply_economizers(climate_zone, model) ⇒ Object



1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1090

def apply_economizers(climate_zone, model)
  # NECB2011 prescribes ability to provide 100% OA (5.2.2.7-5.2.2.9)
  econ_max_100_pct_oa_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  econ_max_100_pct_oa_sch.setName('Economizer Max OA Fraction 100 pct')
  econ_max_100_pct_oa_sch.defaultDaySchedule.setName('Economizer Max OA Fraction 100 pct Default')
  econ_max_100_pct_oa_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0)

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

      # NECB 5.2.2.8 states that economizer can be controlled based on difference betweeen
      # return air temperature and outside air temperature OR return air enthalpy
      # and outside air enthalphy; latter chosen to be consistent with MNECB and CAN-QUEST implementation
      economizer_type = 'DifferentialEnthalpy'
      # Set the economizer type
      # Get the OA system and OA controller
      oa_sys = air_loop.airLoopHVACOutdoorAirSystem
      if oa_sys.is_initialized
        oa_sys = oa_sys.get
      else
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', "#{air_loop.name} is required to have an economizer, but it has no OA system.")
        next
      end
      oa_control = oa_sys.getControllerOutdoorAir
      oa_control.setEconomizerControlType(economizer_type)
    end
  end
end

#apply_standard_construction_properties(model, runner = nil, scale_wall = 1.0, scale_floor = 1.0, scale_roof = 1.0, scale_ground_wall = 1.0, scale_ground_floor = 1.0, scale_ground_roof = 1.0, scale_door = 1.0, scale_window = 1.0) ⇒ Bool

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

90.1-2007, 90.1-2010, 90.1-2013

Returns:

  • (Bool)

    returns true if successful, false if not



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
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 254

def apply_standard_construction_properties(model,
                                           runner = nil,
                                           scale_wall = 1.0,
                                           scale_floor = 1.0,
                                           scale_roof = 1.0,
                                           scale_ground_wall = 1.0,
                                           scale_ground_floor = 1.0,
                                           scale_ground_roof = 1.0,
                                           scale_door = 1.0,
                                           scale_window = 1.0)

  model.getDefaultConstructionSets.sort.each do |set|
    set_construction_set_to_necb!(model,
                                  set,
                                  runner,
                                  scale_wall,
                                  scale_floor,
                                  scale_roof,
                                  scale_ground_wall,
                                  scale_ground_floor,
                                  scale_ground_roof,
                                  scale_door,
                                  scale_window)
  end
  # sets all surfaces to use default constructions sets except adiabatic, where it does a hard assignment of the interior wall construction type.
  model.getPlanarSurfaces.sort.each(&:resetConstruction)
  # if the default construction set is defined..try to assign the interior wall to the adiabatic surfaces
  BTAP::Resources::Envelope.assign_interior_surface_construction_to_adiabatic_surfaces(model, nil)
end

#apply_standard_lights(set_lights, space_type, space_type_properties) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/lighting.rb', line 2

def apply_standard_lights(set_lights, space_type, space_type_properties)
  lights_have_info = false
  lighting_per_area = space_type_properties['lighting_per_area'].to_f
  lighting_per_person = space_type_properties['lighting_per_person'].to_f
  lights_frac_to_return_air = space_type_properties['lighting_fraction_to_return_air'].to_f
  lights_frac_radiant = space_type_properties['lighting_fraction_radiant'].to_f
  lights_frac_visible = space_type_properties['lighting_fraction_visible'].to_f
  lights_frac_replaceable = space_type_properties['lighting_fraction_replaceable'].to_f
  lights_have_info = true unless lighting_per_area.zero?
  lights_have_info = true unless lighting_per_person.zero?

  if set_lights && lights_have_info

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

    # Modify the definition of the instance
    space_type.lights.sort.each do |inst|
      definition = inst.lightsDefinition
      unless lighting_per_area.zero?
        occ_sens_lpd_frac = 1.0
        # NECB2011 space types that require a reduction in the LPD to account for
        # the requirement of an occupancy sensor (8.4.4.6(3) and 4.2.2.2(2))
        reduce_lpd_spaces = ['Classroom/lecture/training', 'Conf./meet./multi-purpose', 'Lounge/recreation',
                           'Conf./meet./multi-purpose', 'Washroom-sch-A', 'Washroom-sch-B', 'Washroom-sch-C', 'Washroom-sch-D',
                           'Washroom-sch-E', 'Washroom-sch-F', 'Washroom-sch-G', 'Washroom-sch-H', 'Washroom-sch-I',
                           'Dress./fitt. - performance arts', 'Locker room', 'Locker room-sch-A', 'Locker room-sch-B',
                           'Locker room-sch-C', 'Locker room-sch-D', 'Locker room-sch-E', 'Locker room-sch-F', 'Locker room-sch-G',
                           'Locker room-sch-H', 'Locker room-sch-I', 'Retail - dressing/fitting']
        if reduce_lpd_spaces.include?(space_type.standardsSpaceType.get)
          # Note that "Storage area", "Storage area - refrigerated", "Hospital - medical supply" and "Office - enclosed"
          # LPD should only be reduced if their space areas are less than specific area values.
          # This is checked in a space loop after this function in the calling routine.
          occ_sens_lpd_frac = 0.9
        end
        definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area.to_f * occ_sens_lpd_frac, 'W/ft^2', 'W/m^2').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set LPD to #{lighting_per_area} W/ft^2.")
      end
      unless lighting_per_person.zero?
        definition.setWattsperPerson(OpenStudio.convert(lighting_per_person.to_f, 'W/person', 'W/person').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set lighting to #{lighting_per_person} W/person.")
      end
      unless lights_frac_to_return_air.zero?
        definition.setReturnAirFraction(lights_frac_to_return_air)
      end
      unless lights_frac_radiant.zero?
        definition.setFractionRadiant(lights_frac_radiant)
      end
      unless lights_frac_visible.zero?
        definition.setFractionVisible(lights_frac_visible)
      end
      # unless lights_frac_replaceable.zero?
      #  definition.setFractionReplaceable(lights_frac_replaceable)
      # end
    end

    # If additional lights are specified, add those too
    additional_lighting_per_area = space_type_properties['additional_lighting_per_area'].to_f
    unless additional_lighting_per_area.zero?
      # Create the lighting definition
      additional_lights_def = OpenStudio::Model::LightsDefinition.new(space_type.model)
      additional_lights_def.setName("#{space_type.name} Additional Lights Definition")
      additional_lights_def.setWattsperSpaceFloorArea(OpenStudio.convert(additional_lighting_per_area.to_f, 'W/ft^2', 'W/m^2').get)
      additional_lights_def.setReturnAirFraction(lights_frac_to_return_air)
      additional_lights_def.setFractionRadiant(lights_frac_radiant)
      additional_lights_def.setFractionVisible(lights_frac_visible)

      # Create the lighting instance and hook it up to the space type
      additional_lights = OpenStudio::Model::Lights.new(additional_lights_def)
      additional_lights.setName("#{space_type.name} Additional Lights")
      additional_lights.setSpaceType(space_type)
    end

  end
end

#apply_standard_skylight_to_roof_ratio(model) ⇒ Object

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



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
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 143

def apply_standard_skylight_to_roof_ratio(model)
  # Loop through all spaces in the model, and
  # per the PNNL PRM Reference Manual, find the areas
  # of each space conditioning category (res, nonres, semi-heated)
  # separately.  Include space multipliers.
  nr_wall_m2 = 0.001 # Avoids divide by zero errors later
  nr_sky_m2 = 0
  res_wall_m2 = 0.001
  res_sky_m2 = 0
  sh_wall_m2 = 0.001
  sh_sky_m2 = 0
  total_roof_m2 = 0.001
  total_subsurface_m2 = 0
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    wall_area_m2 = 0
    sky_area_m2 = 0
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType == 'RoofCeiling'
      # This wall's gross area (including skylight area)
      wall_area_m2 += surface.grossArea * space.multiplier
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        sky_area_m2 += ss.netArea * space.multiplier
      end
    end

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

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

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

  # SRR limit
  srr_lim = self.get_standards_constant('skylight_to_roof_ratio_max_value') * 100.0
  # Check against SRR limit
  red_nr = srr_nr > srr_lim
  red_res = srr_res > srr_lim
  red_sh = srr_sh > srr_lim

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

  # Reduce the subsurface areas
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType == 'RoofCeiling'
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        # Reduce the size of the subsurface
        red = 1.0 - mult
        sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red)
      end
    end
  end

  return true
end

#apply_standard_window_to_wall_ratio(model) ⇒ Object

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



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 4

def apply_standard_window_to_wall_ratio(model)
  # Loop through all spaces in the model, and
  # per the PNNL PRM Reference Manual, find the areas
  # of each space conditioning category (res, nonres, semi-heated)
  # separately.  Include space multipliers.
  nr_wall_m2 = 0.001 # Avoids divide by zero errors later
  nr_wind_m2 = 0
  res_wall_m2 = 0.001
  res_wind_m2 = 0
  sh_wall_m2 = 0.001
  sh_wind_m2 = 0
  total_wall_m2 = 0.001
  total_subsurface_m2 = 0.0
  # Store the space conditioning category for later use
  space_cats = {}
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    wall_area_m2 = 0
    wind_area_m2 = 0
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType.casecmp('wall').zero?
      # This wall's gross area (including window area)
      wall_area_m2 += surface.grossArea * space.multiplier
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        wind_area_m2 += ss.netArea * space.multiplier
      end
    end

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

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

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

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

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

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

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

  # WWR limit
  wwr_lim = 40.0

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

  # NECB FDWR limit
  hdd = self.get_necb_hdd18(model)
  fdwr_lim = (max_fwdr(hdd) * 100.0).round(1)
  # puts "Current FDWR is #{fdwr}, must be less than #{fdwr_lim}."
  # puts "Current subsurf area is #{total_subsurface_m2} and gross surface area is #{total_wall_m2}"
  # Stop here unless windows / doors need reducing
  return true unless fdwr > fdwr_lim
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{wwr_lim.round}%.")
  # Determine the factors by which to reduce the window / door area
  mult = fdwr_lim / fdwr
  # Reduce the window area if any of the categories necessary
  model.getSpaces.sort.each do |space|
    # Loop through all surfaces in this space
    space.surfaces.sort.each do |surface|
      # Skip non-outdoor surfaces
      next unless surface.outsideBoundaryCondition == 'Outdoors'
      # Skip non-walls
      next unless surface.surfaceType == 'Wall'
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |ss|
        # Reduce the size of the window
        red = 1.0 - mult
        sub_surface_reduce_area_by_percent_by_raising_sill(ss, red)
      end
    end
  end
  return true
end

#assign_contruction_to_adiabatic_surfaces(model) ⇒ Object



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
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 697

def assign_contruction_to_adiabatic_surfaces(model)
  cp02_carpet_pad = OpenStudio::Model::MasslessOpaqueMaterial.new(model)
  cp02_carpet_pad.setName('CP02 CARPET PAD')
  cp02_carpet_pad.setRoughness('VeryRough')
  cp02_carpet_pad.setThermalResistance(0.21648)
  cp02_carpet_pad.setThermalAbsorptance(0.9)
  cp02_carpet_pad.setSolarAbsorptance(0.7)
  cp02_carpet_pad.setVisibleAbsorptance(0.8)

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

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

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

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

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

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

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

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

  model.getSurfaces.sort.each do |surface|
    if surface.outsideBoundaryCondition.to_s == 'Adiabatic'
      if surface.surfaceType.to_s == 'Wall'
        surface.setConstruction(wall_adiabatic_construction)
      else
        surface.setConstruction(floor_adiabatic_construction)
      end
    elsif surface.outsideBoundaryCondition.to_s == 'OtherSideCoefficients'
      # Ground
      if surface.surfaceType.to_s == 'Wall'
        surface.setOutsideBoundaryCondition('Ground')
        surface.setConstruction(basement_wall_construction)
      else
        surface.setOutsideBoundaryCondition('Ground')
        surface.setConstruction(basement_floor_construction)
      end
    end
  end
end

#boiler_hot_water_apply_efficiency_and_curves(boiler_hot_water) ⇒ Bool

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

Parameters:

Returns:

  • (Bool)

    true if successful, false if not



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
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
526
527
528
529
530
531
532
533
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 445

def boiler_hot_water_apply_efficiency_and_curves(boiler_hot_water)
  successfully_set_all_properties = false

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

  # Get the capacity
  capacity_w = boiler_hot_water_find_capacity(boiler_hot_water)

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

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

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

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

  # Get the minimum efficiency standards
  thermal_eff = nil

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

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

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

  # Set the name
  boiler_hot_water.setName(new_comp_name)

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

  return successfully_set_all_properties
end

#chiller_electric_eir_apply_efficiency_and_curves(chiller_electric_eir, clg_tower_objs) ⇒ Bool

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

Returns:

  • (Bool)

    true if successful, false if not



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
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 538

def chiller_electric_eir_apply_efficiency_and_curves(chiller_electric_eir, clg_tower_objs)
  chillers = standards_data['chillers']

  # Define the criteria to find the chiller properties
  # in the hvac standards data set.
  search_criteria = chiller_electric_eir_find_search_criteria(chiller_electric_eir)
  cooling_type = search_criteria['cooling_type']
  condenser_type = search_criteria['condenser_type']
  compressor_type = search_criteria['compressor_type']

  # Get the chiller capacity
  capacity_w = chiller_electric_eir_find_capacity(chiller_electric_eir)

  # All chillers must be modulating down to 25% of their capacity
  chiller_electric_eir.setChillerFlowMode('LeavingSetpointModulated')
  chiller_electric_eir.setMinimumPartLoadRatio(0.25)
  chiller_electric_eir.setMinimumUnloadingRatio(0.25)
  if (capacity_w / 1000.0) < 2100.0
    if chiller_electric_eir.name.to_s.include? 'Primary Chiller'
      chiller_capacity = capacity_w
    elsif chiller_electric_eir.name.to_s.include? 'Secondary Chiller'
      chiller_capacity = 0.001
    end
  else
    chiller_capacity = capacity_w / 2.0
  end
  chiller_electric_eir.setReferenceCapacity(chiller_capacity)

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

  # Get the chiller properties
  chlr_props = model_find_object(chillers, search_criteria, capacity_tons, Date.today)
  unless chlr_props
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find chiller properties, cannot apply standard efficiencies or curves.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # Make the CAPFT curve
  cool_cap_ft = model_add_curve(chiller_electric_eir.model, chlr_props['capft'])
  if cool_cap_ft
    chiller_electric_eir.setCoolingCapacityFunctionOfTemperature(cool_cap_ft)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find cool_cap_ft curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Make the EIRFT curve
  cool_eir_ft = model_add_curve(chiller_electric_eir.model, chlr_props['eirft'])
  if cool_eir_ft
    chiller_electric_eir.setElectricInputToCoolingOutputRatioFunctionOfTemperature(cool_eir_ft)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find cool_eir_ft curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Make the EIRFPLR curve
  # which may be either a CurveBicubic or a CurveQuadratic based on chiller type
  cool_plf_fplr = model_add_curve(chiller_electric_eir.model, chlr_props['eirfplr'])
  if cool_plf_fplr
    chiller_electric_eir.setElectricInputToCoolingOutputRatioFunctionOfPLR(cool_plf_fplr)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find cool_plf_fplr curve, will not be set.")
    successfully_set_all_properties = false
  end

  # Set the efficiency value
  kw_per_ton = nil
  cop = nil
  if chlr_props['minimum_full_load_efficiency']
    kw_per_ton = chlr_props['minimum_full_load_efficiency']
    cop = kw_per_ton_to_cop(kw_per_ton)
    chiller_electric_eir.setReferenceCOP(cop)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ChillerElectricEIR', "For #{chiller_electric_eir.name}, cannot find minimum full load efficiency, will not be set.")
    successfully_set_all_properties = false
  end

  # Set cooling tower properties now that the new COP of the chiller is set
  if chiller_electric_eir.name.to_s.include? 'Primary Chiller'
    # Single speed tower model assumes 25% extra for compressor power
    tower_cap = capacity_w * (1.0 + 1.0 / chiller_electric_eir.referenceCOP)
    if (tower_cap / 1000.0) < 1750
      clg_tower_objs[0].setNumberofCells(1)
    else
      clg_tower_objs[0].setNumberofCells((tower_cap / (1000 * 1750) + 0.5).round)
    end
    clg_tower_objs[0].setFanPoweratDesignAirFlowRate(0.015 * tower_cap)
  end

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

  return successfully_set_all_properties
end

#coil_cooling_dx_multi_speed_apply_efficiency_and_curves(coil_cooling_dx_multi_speed, sql_db_vars_map) ⇒ Bool

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

Returns:

  • (Bool)

    true if successful, false if not



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
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
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
808
809
810
811
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 639

def coil_cooling_dx_multi_speed_apply_efficiency_and_curves(coil_cooling_dx_multi_speed, sql_db_vars_map)
  successfully_set_all_properties = true

  # Define the criteria to find the chiller properties
  # in the hvac standards data set.
  search_criteria = {}
  search_criteria['template'] = template
  cooling_type = coil_cooling_dx_multi_speed.condenserType
  search_criteria['cooling_type'] = cooling_type

  # TODO: Standards - add split system vs single package to model
  # For now, assume single package as default
  sub_category = 'Single Package'

  # Determine the heating type if unitary or zone hvac
  heat_pump = false
  heating_type = nil
  containing_comp = nil
  if coil_cooling_dx_multi_speed.airLoopHVAC.empty?
    if coil_cooling_dx_multi_speed.containingHVACComponent.is_initialized
      containing_comp = coil_cooling_dx_multi_speed.containingHVACComponent.get
      if containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.is_initialized
        htg_coil = containing_comp.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.get.heatingCoil
        if htg_coil.to_CoilHeatingDXMultiSpeed.is_initialized
          heat_pump = true
          heating_type = 'Electric Resistance or None'
        elsif htg_coil.to_CoilHeatingGasMultiStage.is_initialized
          heating_type = 'All Other'
        end
      end # TODO: Add other unitary systems
    elsif coil_cooling_dx_multi_speed.containingZoneHVACComponent.is_initialized
      containing_comp = coil_cooling_dx_multi_speed.containingZoneHVACComponent.get
      if containing_comp.to_ZoneHVACPackagedTerminalAirConditioner.is_initialized
        sub_category = 'PTAC'
        htg_coil = containing_comp.to_ZoneHVACPackagedTerminalAirConditioner.get.heatingCoil
        if htg_coil.to_CoilHeatingElectric.is_initialized
          heating_type = 'Electric Resistance or None'
        elsif htg_coil.to_CoilHeatingWater.is_initialized || htg_coil.to_CoilHeatingGas.is_initialized || htg_col.to_CoilHeatingGasMultiStage
          heating_type = 'All Other'
        end
      end # TODO: Add other zone hvac systems
    end
  end

  # Add the heating type to the search criteria
  unless heating_type.nil?
    search_criteria['heating_type'] = heating_type
  end

  search_criteria['subcategory'] = sub_category

  # Get the coil capacity
  capacity_w = nil
  clg_stages = stages
  if clg_stages.last.grossRatedTotalCoolingCapacity.is_initialized
    capacity_w = clg_stages.last.grossRatedTotalCoolingCapacity.get
  elsif coil_cooling_dx_multi_speed.autosizedSpeed4GrossRatedTotalCoolingCapacity.is_initialized
    capacity_w = coil_cooling_dx_multi_speed.autosizedSpeed4GrossRatedTotalCoolingCapacity.get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_cooling_dx_multi_speed.name} capacity is not available, cannot apply efficiency standard.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # Volume flow rate
  flow_rate4 = nil
  if clg_stages.last.ratedAirFlowRate.is_initialized
    flow_rate4 = clg_stages.last.ratedAirFlowRate.get
  elsif coil_cooling_dx_multi_speed.autosizedSpeed4RatedAirFlowRate.is_initialized
    flow_rate4 = coil_cooling_dx_multi_speed.autosizedSpeed4RatedAirFlowRate.get
  end

  # Set number of stages
  stage_cap = []
  num_stages = (capacity_w / (66.0 * 1000.0) + 0.5).round
  num_stages = [num_stages, 4].min
  if num_stages == 1
    stage_cap[0] = capacity_w / 2.0
    stage_cap[1] = 2.0 * stage_cap[0]
    stage_cap[2] = stage_cap[1] + 0.1
    stage_cap[3] = stage_cap[2] + 0.1
  else
    stage_cap[0] = 66.0 * 1000.0
    stage_cap[1] = 2.0 * stage_cap[0]
    if num_stages == 2
      stage_cap[2] = stage_cap[1] + 0.1
      stage_cap[3] = stage_cap[2] + 0.1
    elsif num_stages == 3
      stage_cap[2] = 3.0 * stage_cap[0]
      stage_cap[3] = stage_cap[2] + 0.1
    elsif num_stages == 4
      stage_cap[2] = 3.0 * stage_cap[0]
      stage_cap[3] = 4.0 * stage_cap[0]
    end
  end
  # set capacities, flow rates, and sensible heat ratio for stages
  (0..3).each do |istage|
    clg_stages[istage].setGrossRatedTotalCoolingCapacity(stage_cap[istage])
    clg_stages[istage].setRatedAirFlowRate(flow_rate4 * stage_cap[istage] / capacity_w)
  end

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

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

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

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

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

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

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

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

  # Get the minimum efficiency standards
  cop = nil

  if coil_dx_subcategory(coil_cooling_dx_multi_speed) == 'PTAC'
    ptac_eer_coeff_1 = ac_props['ptac_eer_coefficient_1']
    ptac_eer_coeff_2 = ac_props['ptac_eer_coefficient_2']
    capacity_btu_per_hr = 7000 if capacity_btu_per_hr < 7000
    capacity_btu_per_hr = 15_000 if capacity_btu_per_hr > 15_000
    ptac_eer = ptac_eer_coeff_1 + (ptac_eer_coeff_2 * capacity_btu_per_hr)
    cop = eer_to_cop(ptac_eer)
    # self.setName("#{self.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{ptac_eer}EER")
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{ptac_eer}EER"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; EER = #{ptac_eer}")
  end

  # If specified as SEER
  unless ac_props['minimum_seasonal_energy_efficiency_ratio'].nil?
    min_seer = ac_props['minimum_seasonal_energy_efficiency_ratio']
    cop = seer_to_cop(min_seer)
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_seer}SEER"
    #      self.setName("#{self.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_seer}SEER")
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; SEER = #{min_seer}")
  end

  # If specified as EER
  unless ac_props['minimum_energy_efficiency_ratio'].nil?
    min_eer = ac_props['minimum_energy_efficiency_ratio']
    cop = eer_to_cop(min_eer)
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_eer}EER"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; EER = #{min_eer}")
  end

  # if specified as SEER (heat pump)
  unless ac_props['minimum_seasonal_efficiency'].nil?
    min_seer = ac_props['minimum_seasonal_efficiency']
    cop = seer_to_cop(min_seer)
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_seer}SEER"
    #      self.setName("#{self.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_seer}SEER")
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; SEER = #{min_seer}")
  end

  # If specified as EER (heat pump)
  unless ac_props['minimum_full_load_efficiency'].nil?
    min_eer = ac_props['minimum_full_load_efficiency']
    cop = eer_to_cop(min_eer)
    new_comp_name = "#{coil_cooling_dx_multi_speed.name} #{capacity_kbtu_per_hr.round}kBtu/hr #{min_eer}EER"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{template}: #{coil_cooling_dx_multi_speed.name}: #{cooling_type} #{heating_type} #{subcategory} Capacity = #{capacity_kbtu_per_hr.round}kBtu/hr; EER = #{min_eer}")
  end

  sql_db_vars_map[new_comp_name] = name.to_s
  coil_cooling_dx_multi_speed.setName(new_comp_name)

  # Set the efficiency values

  unless cop.nil?
    clg_stages.each do |istage|
      istage.setGrossRatedCoolingCOP(cop)
    end
  end

  return sql_db_vars_map
end

#coil_heating_gas_multi_stage_apply_efficiency_and_curves(coil_heating_gas_multi_stage, standards) ⇒ Bool

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

Returns:

  • (Bool)

    true if successful, false if not



880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 880

def coil_heating_gas_multi_stage_apply_efficiency_and_curves(coil_heating_gas_multi_stage, standards)
  successfully_set_all_properties = true

  # Get the coil capacity
  capacity_w = nil
  htg_stages = stages
  if htg_stages.last.nominalCapacity.is_initialized
    capacity_w = htg_stages.last.nominalCapacity.get
  elsif coil_heating_gas_multi_stage.autosizedStage4NominalCapacity.is_initialized
    capacity_w = coil_heating_gas_multi_stage.autosizedStage4NominalCapacity.get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilCoolingDXMultiSpeed', "For #{coil_heating_gas_multi_stage.name} capacity is not available, cannot apply efficiency standard.")
    successfully_set_all_properties = false
    return successfully_set_all_properties
  end

  # Set number of stages
  num_stages = (capacity_w / (66.0 * 1000.0) + 0.5).round
  num_stages = [num_stages, 4].min
  stage_cap = []
  if num_stages == 1
    stage_cap[0] = capacity_w / 2.0
    stage_cap[1] = 2.0 * stage_cap[0]
    stage_cap[2] = stage_cap[1] + 0.1
    stage_cap[3] = stage_cap[2] + 0.1
  else
    stage_cap[0] = 66.0 * 1000.0
    stage_cap[1] = 2.0 * stage_cap[0]
    if num_stages == 2
      stage_cap[2] = stage_cap[1] + 0.1
      stage_cap[3] = stage_cap[2] + 0.1
    elsif num_stages == 3
      stage_cap[2] = 3.0 * stage_cap[0]
      stage_cap[3] = stage_cap[2] + 0.1
    elsif num_stages == 4
      stage_cap[2] = 3.0 * stage_cap[0]
      stage_cap[3] = 4.0 * stage_cap[0]
    end
  end
  # set capacities, flow rates, and sensible heat ratio for stages
  (0..3).each do |istage|
    htg_stages[istage].setNominalCapacity(stage_cap[istage])
  end
  # PLF vs PLR curve
  furnace_plffplr_curve_name = 'FURNACE-EFFPLR-NECB2011'

  # plf vs plr curve for furnace
  furnace_plffplr_curve = model_add_curve(coil_heating_gas_multi_stage.model, furnace_plffplr_curve_name, standards)
  if furnace_plffplr_curve
    coil_heating_gas_multi_stage.setPartLoadFractionCorrelationCurve(furnace_plffplr_curve)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.CoilHeatingGasMultiStage', "For #{coil_heating_gas_multi_stage.name}, cannot find plffplr curve, will not be set.")
    successfully_set_all_properties = false
  end
end

#determine_dominant_necb_schedule_type(model) ⇒ Object

This model determines the dominant NECB schedule type return s.each [String]

Parameters:

  • model (OpenStudio::model::Model)

    A model object



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

def determine_dominant_necb_schedule_type(model)
  # lookup necb space type properties
  space_type_properties = @standards_data['space_types']

  # Here is a hash to keep track of the m2 running total of spacetypes for each
  # sched type.
  s = Hash[
      'A', 0,
      'B', 0,
      'C', 0,
      'D', 0,
      'E', 0,
      'F', 0,
      'G', 0,
      'H', 0,
      'I', 0
  ]
  # iterate through spaces in building.
  wildcard_spaces = 0
  model.getSpaces.sort.each do |space|
    found_space_type = false
    # iterate through the NECB spacetype property table
    space_type_properties.each do |spacetype|
      unless space.spaceType.empty?
        if space.spaceType.get.standardsSpaceType.empty? || space.spaceType.get.standardsBuildingType.empty?
          OpenStudio.logFree(OpenStudio::Error, 'openstudio.Standards.Model', "Space #{space.name} does not have a standardSpaceType defined")
          found_space_type = false
        elsif space.spaceType.get.standardsSpaceType.get == spacetype['space_type'] && space.spaceType.get.standardsBuildingType.get == spacetype['building_type']
          if spacetype['necb_schedule_type'] == '*'
            wildcard_spaces = +1
          else
            s[spacetype['necb_schedule_type']] = s[spacetype['necb_schedule_type']] + space.floorArea if (spacetype['necb_schedule_type'] != '*') && (spacetype['necb_schedule_type'] != '- undefined -')
          end
          # puts "Found #{space.spaceType.get.name} schedule #{spacetype[2]} match with floor area of #{space.floorArea()}"
          found_space_type = true
        elsif spacetype['necb_schedule_type'] != '*'
          # found wildcard..will not count to total.
          found_space_type = true
        end
      end
    end
    raise "Did not find #{space.spaceType.get.name} in NECB space types." if found_space_type == false
  end
  # finds max value and returns NECB schedule letter.
  raise('Only wildcard spaces in model. You need to define the actual spaces. ') if wildcard_spaces == model.getSpaces.size
  dominant_schedule = s.each {|k, v| return k.to_s if v == s.values.max}
  return dominant_schedule
end

#determine_necb_schedule_type(space) ⇒ String

This method determines the spacetype schedule type. This will re

Parameters:

Returns:

  • (String)

    :[“A”,“B”,“C”,“D”,“E”,“F”,“G”,“H”,“I”] spacetype

Author:



313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 313

def determine_necb_schedule_type(space)
  spacetype_data = nil
  if @standards_data['space_types'].is_a?(Hash) == true
    spacetype_data = @standards_data['space_types']['table']
  else
    spacetype_data = @standards_data['space_types']
  end
  raise "Undefined spacetype for space #{space.get.name}) if space.spaceType.empty?" if space.spaceType.empty?
  raise "Undefined standardsSpaceType or StandardsBuildingType for space #{space.spaceType.get.name}) if space.spaceType.empty?" if space.spaceType.get.standardsSpaceType.empty? | space.spaceType.get.standardsBuildingType.empty?
  space_type_properties = spacetype_data.detect {|st| (st['space_type'] == space.spaceType.get.standardsSpaceType.get) && (st['building_type'] == space.spaceType.get.standardsBuildingType.get)}
  return space_type_properties['necb_schedule_type'].strip
end

#distance(loc1, loc2) ⇒ Object

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



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 95

def distance(loc1, loc2)
  rad_per_deg = Math::PI/180 # PI / 180
  rkm = 6371 # Earth radius in kilometers
  rm = rkm * 1000 # Radius in meters

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

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

  a = Math.sin(dlat_rad/2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad/2)**2
  c = 2 * Math::atan2(Math::sqrt(a), Math::sqrt(1-a))
  rm * c # Delta in meters
end

#fan_baseline_impeller_efficiency(fan) ⇒ Double

TODO:

Add fan type to data model and modify this method

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

Returns:

  • (Double)

    impeller efficiency (0.0 to 1.0)



941
942
943
944
945
946
947
948
949
950
951
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 941

def fan_baseline_impeller_efficiency(fan)
  # Assume that the fan efficiency is 65% for normal fans
  # TODO add fan type to fan data model
  # and infer impeller efficiency from that?
  # or do we always assume a certain type of
  # fan impeller for the baseline system?
  # TODO check COMNET and T24 ACM and PNNL 90.1 doc
  fan_impeller_eff = 0.65

  return fan_impeller_eff
end

#fan_constant_volume_apply_prototype_fan_pressure_rise(fan_constant_volume) ⇒ Object



1076
1077
1078
1079
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1076

def fan_constant_volume_apply_prototype_fan_pressure_rise(fan_constant_volume)
  fan_constant_volume.setPressureRise( self.get_standards_constant('fan_constant_volume_pressure_rise_value'))
  return true
end

#fan_standard_minimum_motor_efficiency_and_size(fan, motor_bhp) ⇒ Array<Double>

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

Parameters:

  • motor_bhp (Double)

    motor brake horsepower (hp)

Returns:

  • (Array<Double>)

    minimum motor efficiency (0.0 to 1.0), nominal horsepower



964
965
966
967
968
969
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 964

def fan_standard_minimum_motor_efficiency_and_size(fan, motor_bhp)
  fan_motor_eff = 0.85
  nominal_hp = motor_bhp

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

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

  # Assuming all fan motors are 4-pole ODP
  template_mod = @template
  if fan.class.name == 'OpenStudio::Model::FanConstantVolume'
    template_mod += '-CONSTANT'
  elsif fan.class.name == 'OpenStudio::Model::FanVariableVolume'
    template_mod += '-VARIABLE'
    # 0.909 corrects for 10% over sizing implemented upstream
    # 0.7457 is to convert from bhp to kW
    fan_power_kw = 0.909 * 0.7457 * motor_bhp
    power_vs_flow_curve_name = if fan_power_kw >= 25.0
                                 'VarVolFan-FCInletVanes-NECB2011-FPLR'
                               elsif fan_power_kw >= 7.5 && fan_power_kw < 25
                                 'VarVolFan-AFBIInletVanes-NECB2011-FPLR'
                               else
                                 'VarVolFan-AFBIFanCurve-NECB2011-FPLR'
                               end
    power_vs_flow_curve = model_add_curve(fan.model, power_vs_flow_curve_name)
    fan.setFanPowerMinimumFlowRateInputMethod('Fraction')
    fan.setFanPowerCoefficient5(0.0)
    fan.setFanPowerMinimumFlowFraction(power_vs_flow_curve.minimumValueofx)
    fan.setFanPowerCoefficient1(power_vs_flow_curve.coefficient1Constant)
    fan.setFanPowerCoefficient2(power_vs_flow_curve.coefficient2x)
    fan.setFanPowerCoefficient3(power_vs_flow_curve.coefficient3xPOW2)
    fan.setFanPowerCoefficient4(power_vs_flow_curve.coefficient4xPOW3)
  else
    raise('')
  end

  search_criteria = {
      'template' => template_mod,
      'number_of_poles' => 4.0,
      'type' => 'Enclosed'
  }

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

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

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

  # Get the efficiency based on the nominal horsepower
  # Add 0.01 hp to avoid search errors.
  motor_properties = model_find_object(motors, search_criteria, nominal_hp + 0.01)
  if motor_properties.nil?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Fan', "For #{fan.name}, could not find nominal motor properties using search criteria: #{search_criteria}, motor_hp = #{nominal_hp} hp.")
    return [fan_motor_eff, nominal_hp]
  end
  fan_motor_eff = motor_properties['nominal_full_load_efficiency']

  return [fan_motor_eff, nominal_hp]
end

#fan_variable_volume_apply_prototype_fan_pressure_rise(fan_variable_volume) ⇒ Object

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



1084
1085
1086
1087
1088
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1084

def fan_variable_volume_apply_prototype_fan_pressure_rise(fan_variable_volume)
  # 1000 Pa for supply fan and 458.33 Pa for return fan (accounts for efficiency differences between two fans)
  fan_variable_volume.setPressureRise(self.get_standards_constant('fan_variable_volume_pressure_rise_value'))
  return true
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.

Returns:

  • (Boolean)


1052
1053
1054
1055
1056
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1052

def fan_variable_volume_part_load_fan_power_limitation?(fan_variable_volume)
  part_load_control_required = false

  return part_load_control_required
end

#get_all_spacetype_namesObject



90
91
92
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 90

def get_all_spacetype_names
  return @standards_data['space_types']['table'].map {|space_types| [space_types['building_type'], space_types['space_type']]}
end

#get_canadian_system_defaults_by_weatherfile_name(model) ⇒ Object

this method returns the default system fuel types by epw_file.



112
113
114
115
116
117
118
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 112

def get_canadian_system_defaults_by_weatherfile_name(model)
  #get models weather object to get the province. Then use that to look up the province.
  epw = BTAP::Environment::WeatherFile.new(model.weatherFile.get.path.get)
  fuel_sources = @standards_data['tables'].detect {|table| table['name'] == 'regional_fuel_use'}['table'].detect {|fuel_sources| fuel_sources['state_province_regions'].include?(epw.state_province_region)}
  raise("Could not find fuel sources for weather file, make sure it is a Canadian weather file.") if fuel_sources.nil? #this should never happen since we are using only canadian weather files.
  return fuel_sources
end

#get_necb_hdd18(model) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 120

def get_necb_hdd18(model)
  max_distance_tolerance = 500000
  min_distance = 100000000000000.0
  necb_closest = nil
  epw = BTAP::Environment::WeatherFile.new(model.weatherFile.get.path.get)
  #this extracts the table from the json database.
  necb_2015_table_c1 = @standards_data['tables'].detect {|table| table['name'] == 'necb_2015_table_c1'}['table']
  necb_2015_table_c1.each do |necb|
    next if necb['lat_long'].nil? #Need this until Tyson cleans up table.
    dist = distance([epw.latitude.to_f, epw.longitude.to_f], necb['lat_long'])
    if min_distance > dist
      min_distance = dist
      necb_closest = necb
    end
  end
  if (min_distance / 1000.0) > max_distance_tolerance and not epw.hdd18.nil?
    puts "Could not find close NECB HDD from Table C1 < #{max_distance_tolerance}km. Closest city is #{min_distance/1000.0}km away. Using epw hdd18 instead."
    return epw.hdd18.to_f
  else
    puts "INFO:NECB HDD18 of #{necb_closest['degree_days_below_18_c'].to_f}  at nearest city #{necb_closest['city']},#{necb_closest['province']}, at a distance of #{'%.2f' % (min_distance/1000.0)}km from epw location. Ref:necb_2015_table_c1"
    return necb_closest['degree_days_below_18_c'].to_f
  end
end

#get_standards_constant(name) ⇒ Object



56
57
58
59
60
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 56

def get_standards_constant(name)
  object = @standards_data['constants'].detect {|constant| constant['name'] == name}
  raise("could not find #{name} in standards constants database. ") if object.nil? or object['value'].nil?
  return object['value']
end

#get_standards_formula(name) ⇒ Object



62
63
64
65
66
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 62

def get_standards_formula(name)
  object = @standards_data['formulas'].detect {|formula| formula['name'] == name}
  raise("could not find #{name} in standards formual database. ") if object.nil? or object['value'].nil?
  return object['value']
end

#get_standards_table(table_name, search_criteria = nil) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 68

def get_standards_table(table_name, search_criteria = nil)
  return_objects = nil
  object = @standards_data['tables'].detect {|table| table['name'] == table_name}
  raise("could not find #{table_name} in standards table database. ") if object.nil? or object['table'].nil?
  if search_criteria.nil?
    return object['table']
  else
    return_objects = model_find_objects(object['table'], search_criteria)
    return return_objects
  end
end

#load_standards_database_newObject

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



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

def load_standards_database_new()
  @standards_data = {}
  @standards_data["tables"] = []

  if __dir__[0] == ':' # Running from OpenStudio CLI
    embedded_files_relative('data/', /.*\.json/).each do |file|
      data = JSON.parse(EmbeddedScripting.getFileAsString(file))
      if not data["tables"].nil? and data["tables"].first["data_type"] =="table"
        @standards_data["tables"] << data["tables"].first
      else
        @standards_data[data.keys.first] = data[data.keys.first]
      end
    end
  else
    files = Dir.glob("#{File.dirname(__FILE__)}/data/*.json").select {|e| File.file? e}
    files.each do |file|
      data = JSON.parse(File.read(file))
      if not data["tables"].nil? and data["tables"].first["data_type"] =="table"
        @standards_data["tables"] << data["tables"].first
      else
        @standards_data[data.keys.first] = data[data.keys.first]
      end
    end
  end

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

#max_fwdr(hdd) ⇒ Double

Returns a constant float.

Parameters:

  • hdd (Float)

Returns:

  • (Double)

    a constant float

Author:



241
242
243
244
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 241

def max_fwdr(hdd)
  #get formula from json database.
  return eval(self.get_standards_formula('fdwr_formula'))
end

#model_add_construction_set(model, clim, building_type, spc_type, is_residential = 'No') ⇒ Object

Create a construction set from the openstudio standards dataset. Returns an Optional DefaultConstructionSet



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
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
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 478

def model_add_construction_set(model, clim, building_type, spc_type, is_residential = 'No')
  construction_set = OpenStudio::Model::OptionalDefaultConstructionSet.new

  # Find the climate zone set that this climate zone falls into
  climate_zone_set = model_find_climate_zone_set(model, clim)
  unless climate_zone_set
    return construction_set
  end

  # Get the object data
  data = model_find_object(@standards_data['construction_sets'], 'template' => template, 'building_type' => building_type, 'space_type' => spc_type)
  unless data
    # if nothing matches say that we could not find it.
    message = "Construction set for template =#{template}, building type = #{building_type}, space type = #{spc_type}, is residential = #{is_residential} was not found in standards_data['construction_sets']"
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model',message )
    puts message
    return construction_set
  end


  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Adding construction set: #{template}-#{clim}-#{building_type}-#{spc_type}-is_residential#{is_residential}")

  name = model_make_name(model, clim, building_type, spc_type)

  # Create a new construction set and name it
  construction_set = OpenStudio::Model::DefaultConstructionSet.new(model)
  construction_set.setName(name)

  # Exterior surfaces constructions
  exterior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model)
  construction_set.setDefaultExteriorSurfaceConstructions(exterior_surfaces)
  if data['exterior_floor_standards_construction_type']
    exterior_surfaces.setFloorConstruction(model_find_and_add_construction(model,
                                                                           climate_zone_set,
                                                                           'ExteriorFloor',
                                                                           data['exterior_floor_standards_construction_type'],
                                                                           data['exterior_floor_building_category']))
  end
  if data['exterior_wall_standards_construction_type'] && data['exterior_wall_building_category']
    exterior_surfaces.setWallConstruction(model_find_and_add_construction(model,
                                                                          climate_zone_set,
                                                                          'ExteriorWall',
                                                                          data['exterior_wall_standards_construction_type'],
                                                                          data['exterior_wall_building_category']))
  end
  if data['exterior_roof_standards_construction_type'] && data['exterior_roof_building_category']
    exterior_surfaces.setRoofCeilingConstruction(model_find_and_add_construction(model,
                                                                                 climate_zone_set,
                                                                                 'ExteriorRoof',
                                                                                 data['exterior_roof_standards_construction_type'],
                                                                                 data['exterior_roof_building_category']))
  end

  # Interior surfaces constructions
  interior_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model)
  construction_set.setDefaultInteriorSurfaceConstructions(interior_surfaces)
  construction_name = data['interior_floors']
  unless construction_name.nil?
    interior_surfaces.setFloorConstruction(model_add_construction(model, construction_name))
  end
  construction_name = data['interior_walls']
  unless construction_name.nil?
    interior_surfaces.setWallConstruction(model_add_construction(model, construction_name))
  end
  construction_name = data['interior_ceilings']
  unless construction_name.nil?
    interior_surfaces.setRoofCeilingConstruction(model_add_construction(model, construction_name))
  end

  # Ground contact surfaces constructions
  ground_surfaces = OpenStudio::Model::DefaultSurfaceConstructions.new(model)
  construction_set.setDefaultGroundContactSurfaceConstructions(ground_surfaces)
  if data['ground_contact_floor_standards_construction_type'] && data['ground_contact_floor_building_category']
    ground_surfaces.setFloorConstruction(model_find_and_add_construction(model,
                                                                         climate_zone_set,
                                                                         'GroundContactFloor',
                                                                         data['ground_contact_floor_standards_construction_type'],
                                                                         data['ground_contact_floor_building_category']))
  end
  if data['ground_contact_wall_standards_construction_type'] && data['ground_contact_wall_building_category']
    ground_surfaces.setWallConstruction(model_find_and_add_construction(model,
                                                                        climate_zone_set,
                                                                        'GroundContactWall',
                                                                        data['ground_contact_wall_standards_construction_type'],
                                                                        data['ground_contact_wall_building_category']))
  end
  if data['ground_contact_ceiling_standards_construction_type'] && data['ground_contact_ceiling_building_category']
    ground_surfaces.setRoofCeilingConstruction(model_find_and_add_construction(model,
                                                                               climate_zone_set,
                                                                               'GroundContactRoof',
                                                                               data['ground_contact_ceiling_standards_construction_type'],
                                                                               data['ground_contact_ceiling_building_category']))

  end

  # Exterior sub surfaces constructions
  exterior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(model)
  construction_set.setDefaultExteriorSubSurfaceConstructions(exterior_subsurfaces)
  if data['exterior_fixed_window_standards_construction_type'] && data['exterior_fixed_window_building_category']
    exterior_subsurfaces.setFixedWindowConstruction(model_find_and_add_construction(model,
                                                                                    climate_zone_set,
                                                                                    'ExteriorWindow',
                                                                                    data['exterior_fixed_window_standards_construction_type'],
                                                                                    data['exterior_fixed_window_building_category']))
  end
  if data['exterior_operable_window_standards_construction_type'] && data['exterior_operable_window_building_category']
    exterior_subsurfaces.setOperableWindowConstruction(model_find_and_add_construction(model,
                                                                                       climate_zone_set,
                                                                                       'ExteriorWindow',
                                                                                       data['exterior_operable_window_standards_construction_type'],
                                                                                       data['exterior_operable_window_building_category']))
  end
  if data['exterior_door_standards_construction_type'] && data['exterior_door_building_category']
    exterior_subsurfaces.setDoorConstruction(model_find_and_add_construction(model,
                                                                             climate_zone_set,
                                                                             'ExteriorDoor',
                                                                             data['exterior_door_standards_construction_type'],
                                                                             data['exterior_door_building_category']))
  end
  construction_name = data['exterior_glass_doors']
  unless construction_name.nil?
    exterior_subsurfaces.setGlassDoorConstruction(model_add_construction(model, construction_name))
  end
  if data['exterior_overhead_door_standards_construction_type'] && data['exterior_overhead_door_building_category']
    exterior_subsurfaces.setOverheadDoorConstruction(model_find_and_add_construction(model,
                                                                                     climate_zone_set,
                                                                                     'ExteriorDoor',
                                                                                     data['exterior_overhead_door_standards_construction_type'],
                                                                                     data['exterior_overhead_door_building_category']))
  end
  if data['exterior_skylight_standards_construction_type'] && data['exterior_skylight_building_category']
    exterior_subsurfaces.setSkylightConstruction(model_find_and_add_construction(model,
                                                                                 climate_zone_set,
                                                                                 'Skylight',
                                                                                 data['exterior_skylight_standards_construction_type'],
                                                                                 data['exterior_skylight_building_category']))
  end
  if (construction_name = data['tubular_daylight_domes'])
    exterior_subsurfaces.setTubularDaylightDomeConstruction(model_add_construction(model, construction_name))
  end
  if (construction_name = data['tubular_daylight_diffusers'])
    exterior_subsurfaces.setTubularDaylightDiffuserConstruction(model_add_construction(model, construction_name))
  end

  # Interior sub surfaces constructions
  interior_subsurfaces = OpenStudio::Model::DefaultSubSurfaceConstructions.new(model)
  construction_set.setDefaultInteriorSubSurfaceConstructions(interior_subsurfaces)
  if (construction_name = data['interior_fixed_windows'])
    interior_subsurfaces.setFixedWindowConstruction(model_add_construction(model, construction_name))
  end
  if (construction_name = data['interior_operable_windows'])
    interior_subsurfaces.setOperableWindowConstruction(model_add_construction(model, construction_name))
  end
  if (construction_name = data['interior_doors'])
    interior_subsurfaces.setDoorConstruction(model_add_construction(model, construction_name))
  end

  # Other constructions
  if (construction_name = data['interior_partitions'])
    construction_set.setInteriorPartitionConstruction(model_add_construction(model, construction_name))
  end
  if (construction_name = data['space_shading'])
    construction_set.setSpaceShadingConstruction(model_add_construction(model, construction_name))
  end
  if (construction_name = data['building_shading'])
    construction_set.setBuildingShadingConstruction(model_add_construction(model, construction_name))
  end
  if (construction_name = data['site_shading'])
    construction_set.setSiteShadingConstruction(model_add_construction(model, construction_name))
  end

  # componentize the construction set
  # construction_set_component = construction_set.createComponent

  # Return the construction set
  return OpenStudio::Model::OptionalDefaultConstructionSet.new(construction_set)
end

#model_add_constructions(model, building_type, climate_zone) ⇒ Bool

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

Parameters:

  • building_type (String)

    the type of building

  • climate_zone (String)

    the name of the climate zone the building is in

Returns:

  • (Bool)

    returns true if successful, false if not



406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 406

def model_add_constructions(model, building_type, climate_zone)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying constructions')

  # Assign construction to adiabatic construction
  # Assign a material to all internal mass objects
  assign_contruction_to_adiabatic_surfaces(model)
  # The constructions lookup table uses a slightly different list of
  # building types.
  apply_building_default_constructionset(building_type, climate_zone, model)
  # Make a construction set for each space type, if one is specified
  #apply_default_constructionsets_to_spacetypes(climate_zone, model)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying constructions')
  return true
end

#model_add_hvac(model, epw_file) ⇒ Object



2
3
4
5
6
7
8
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 2

def model_add_hvac(model, epw_file)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding HVAC')
  system_fuel_defaults = self.get_canadian_system_defaults_by_weatherfile_name(model)
  necb_autozone_and_autosystem(model, nil, false, system_fuel_defaults)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding HVAC')
  return true
end

#model_add_schedule(model, schedule_name) ⇒ ScheduleRuleset

TODO:

make return an OptionalScheduleRuleset

Create a schedule from the openstudio standards dataset and add it to the model.

Parameters:

  • schedule_name (String)

    name of the schedule

Returns:

  • (ScheduleRuleset)

    the resulting schedule ruleset



51
52
53
54
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 51

def model_add_schedule(model, schedule_name)

  super(model, schedule_name)
end

#model_add_swh(model, building_type, climate_zone, prototype_input, epw_file) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/service_water_heating.rb', line 2

def model_add_swh(model, building_type, climate_zone, prototype_input, epw_file)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding Service Water Heating')

  # Add the main service water heating loop, if specified
  unless prototype_input['main_water_heater_volume'].nil?


    swh_fueltype = self.get_canadian_system_defaults_by_weatherfile_name(model)['swh_fueltype']

    # Add the main service water loop
    unless building_type == 'RetailStripmall' && ( template != 'NECB2011' &&  template != 'NECB2015')
      main_swh_loop = model_add_swh_loop(model,
                                         'Main Service Water Loop',
                                         nil,
                                         OpenStudio.convert(prototype_input['main_service_water_temperature'], 'F', 'C').get,
                                         prototype_input['main_service_water_pump_head'],
                                         prototype_input['main_service_water_pump_motor_efficiency'],
                                         OpenStudio.convert(prototype_input['main_water_heater_capacity'], 'Btu/hr', 'W').get,
                                         OpenStudio.convert(prototype_input['main_water_heater_volume'], 'gal', 'm^3').get,
                                         swh_fueltype,
                                         OpenStudio.convert(prototype_input['main_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get,
                                         building_type)
    end

    # Attach the end uses if specified in prototype inputs
    # TODO remove special logic for large office SWH end uses
    # TODO remove special logic for stripmall SWH end uses and service water loops
    # TODO remove special logic for large hotel SWH end uses

    if prototype_input['main_service_water_peak_flowrate']

      # Attaches the end uses if specified as a lump value in the prototype_input
      model_add_swh_end_uses(model,
                             'Main',
                             main_swh_loop,
                             OpenStudio.convert(prototype_input['main_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
                             prototype_input['main_service_water_flowrate_schedule'],
                             OpenStudio.convert(prototype_input['main_water_use_temperature'], 'F', 'C').get,
                             nil,
                             building_type)

    else

      # Attaches the end uses if specified by space type
      space_type_map = @space_type_map
      building_type = 'Space Function'
      puts space_type_map

      space_type_map.sort.each do |space_type_name, space_names|
        search_criteria = {
          'template' => template,
          'building_type' => model_get_lookup_name(building_type),
          'space_type' => space_type_name
        }
        data = model_find_object(standards_data['space_types'], search_criteria)

        # Skip space types with no data
        next if data.nil?

        # Skip space types with no water use, unless it is a NECB archetype (these do not have peak flow rates defined)

        # Add a service water use for each space

        space_names.sort.each do |space_name|
          space = model.getSpaceByName(space_name).get
          space_multiplier = nil

          # Added this to prevent double counting of zone multipliers.. space multipliers are never used in NECB archtypes.
          space_multiplier = 1

          model_add_swh_end_uses_by_space(model, model_get_lookup_name(building_type),
                                          climate_zone,
                                          main_swh_loop,
                                          space_type_name,
                                          space_name,
                                          space_multiplier)
        end
      end
    end
  end

  # Add the booster water heater, if specified
  unless prototype_input['booster_water_heater_volume'].nil?

    # Add the booster water loop
    swh_booster_loop = model_add_swh_booster(model,
                                             main_swh_loop,
                                             OpenStudio.convert(prototype_input['booster_water_heater_capacity'], 'Btu/hr', 'W').get,
                                             OpenStudio.convert(prototype_input['booster_water_heater_volume'], 'gal', 'm^3').get,
                                             prototype_input['booster_water_heater_fuel'],
                                             OpenStudio.convert(prototype_input['booster_water_temperature'], 'F', 'C').get,
                                             0,
                                             nil,
                                             building_type)

    # Attach the end uses
    model_add_booster_swh_end_uses(model,
                                   swh_booster_loop,
                                   OpenStudio.convert(prototype_input['booster_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
                                   prototype_input['booster_service_water_flowrate_schedule'],
                                   OpenStudio.convert(prototype_input['booster_water_use_temperature'], 'F', 'C').get,
                                   building_type)

  end

  # Add the laundry water heater, if specified
  unless prototype_input['laundry_water_heater_volume'].nil?

    # Add the laundry service water heating loop
    laundry_swh_loop = model_add_swh_loop(model,
                                          'Laundry Service Water Loop',
                                          nil,
                                          OpenStudio.convert(prototype_input['laundry_service_water_temperature'], 'F', 'C').get,
                                          prototype_input['laundry_service_water_pump_head'],
                                          prototype_input['laundry_service_water_pump_motor_efficiency'],
                                          OpenStudio.convert(prototype_input['laundry_water_heater_capacity'], 'Btu/hr', 'W').get,
                                          OpenStudio.convert(prototype_input['laundry_water_heater_volume'], 'gal', 'm^3').get,
                                          prototype_input['laundry_water_heater_fuel'],
                                          OpenStudio.convert(prototype_input['laundry_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get,
                                          building_type)

    # Attach the end uses if specified in prototype inputs
    model_add_swh_end_uses(model,
                           'Laundry',
                           laundry_swh_loop,
                           OpenStudio.convert(prototype_input['laundry_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
                           prototype_input['laundry_service_water_flowrate_schedule'],
                           OpenStudio.convert(prototype_input['laundry_water_use_temperature'], 'F', 'C').get,
                           nil,
                           building_type)

  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding Service Water Heating')

  return true
end

#model_apply_sizing_parameters(model) ⇒ Object



1070
1071
1072
1073
1074
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1070

def model_apply_sizing_parameters(model)
  model.getSizingParameters.setHeatingSizingFactor(self.get_standards_constant('sizing_factor_max_heating'))
  model.getSizingParameters.setCoolingSizingFactor(self.get_standards_constant('sizing_factor_max_cooling'))
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Set sizing factors to #{self.get_standards_constant('sizing_factor_max_heating')} for heating and #{self.get_standards_constant('sizing_factor_max_heating')} for cooling.")
end

#model_attach_water_fixtures_to_spaces?(model) ⇒ Boolean

Determine whether or not water fixtures are attached to spaces

Returns:

  • (Boolean)


327
328
329
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 327

def model_attach_water_fixtures_to_spaces?(model)
  return true
end

#model_create_prototype_model(climate_zone, epw_file, sizing_run_dir = Dir.pwd, debug = false, measure_model = nil) ⇒ Object



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

def model_create_prototype_model(climate_zone, epw_file, sizing_run_dir = Dir.pwd, debug = false, measure_model = nil)
  building_type = @instvarbuilding_type
  raise 'no building_type!' if @instvarbuilding_type.nil?
  model = nil
  # prototype generation.
  model = load_geometry_osm(@geometry_file) # standard candidate
  model.getThermostatSetpointDualSetpoints(&:remove)
  model.yearDescription.get.setDayofWeekforStartDay('Sunday')
  model_add_design_days_and_weather_file(model, climate_zone, epw_file) # Standards
  model_add_ground_temperatures(model, @instvarbuilding_type, climate_zone) # prototype candidate
  model.getBuilding.setName(self.class.to_s)
  model.getBuilding.setName("-#{@instvarbuilding_type}-#{climate_zone}-#{epw_file} created: #{Time.new}")
  set_occ_sensor_spacetypes(model, @space_type_map)
  model_add_loads(model) # standards candidate
  model_apply_infiltration_standard(model) # standards candidate
  model_modify_surface_convection_algorithm(model) # standards
  model_add_constructions(model, @instvarbuilding_type, climate_zone) # prototype candidate
  apply_standard_construction_properties(model) # standards candidate
  apply_standard_window_to_wall_ratio(model) # standards candidate
  apply_standard_skylight_to_roof_ratio(model) # standards candidate
  model_create_thermal_zones(model, @space_multiplier_map) # standards candidate
  # For some building types, stories are defined explicitly

  if model_run_sizing_run(model, "#{sizing_run_dir}/SR0") == false
    raise("sizing run 0 failed!")
  end
  # Create Reference HVAC Systems.
  model_add_hvac(model, epw_file) # standards for NECB Prototype for NREL candidate
  model_add_swh(model, @instvarbuilding_type, climate_zone, @prototype_input, epw_file)
  model_apply_sizing_parameters(model)

  # set a larger tolerance for unmet hours from default 0.2 to 1.0C
  model.getOutputControlReportingTolerances.setToleranceforTimeHeatingSetpointNotMet(1.0)
  model.getOutputControlReportingTolerances.setToleranceforTimeCoolingSetpointNotMet(1.0)
  if model_run_sizing_run(model, "#{sizing_run_dir}/SR1") == false
    raise("sizing run 1 failed!")
  end

  # This is needed for NECB2011 as a workaround for sizing the reheat boxes
  model.getAirTerminalSingleDuctVAVReheats.each {|iobj| air_terminal_single_duct_vav_reheat_set_heating_cap(iobj)}
  # Apply the prototype HVAC assumptions
  # which include sizing the fan pressure rises based
  # on the flow rate of the system.
  model_apply_prototype_hvac_assumptions(model, building_type, climate_zone)
  # for 90.1-2010 Outpatient, AHU2 set minimum outdoor air flow rate as 0
  # AHU1 doesn't have economizer
  model_modify_oa_controller(model)
  # For operating room 1&2 in 2010 and 2013, VAV minimum air flow is set by schedule
  model_reset_or_room_vav_minimum_damper(@prototype_input, model)
  model_modify_oa_controller(model)
  # Apply the HVAC efficiency standard
  model_apply_hvac_efficiency_standard(model, climate_zone)
  # Fix EMS references.
  # Temporary workaround for OS issue #2598
  model_temp_fix_ems_references(model)
  # Add daylighting controls per standard
  # only four zones in large hotel have daylighting controls
  # todo: YXC to merge to the main function
  model_add_daylighting_controls(model) # to be removed after refactor.
  # Add output variables for debugging
  model_request_timeseries_outputs(model) if debug
  # If measure model is passed, then replace measure model with new model created here.
  if measure_model.nil?
    return model
  else
    model_replace_model(measure_model, model)
    return measure_model
  end
end

#model_create_thermal_zones(model, space_multiplier_map = nil) ⇒ Bool

Creates thermal zones to contain each space, as defined for each building in the system_to_space_map inside the Prototype.building_name e.g. (Prototype.secondary_school.rb) file.

Returns:

  • (Bool)

    returns true if successful, false if not



2938
2939
2940
2941
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 2938

def model_create_thermal_zones(model, space_multiplier_map = nil)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started creating thermal zones')
  space_multiplier_map = {} if space_multiplier_map.nil?

  # Remove any Thermal zones assigned
  model.getThermalZones.each(&:remove)

  # Create a thermal zone for each space in the self
  model.getSpaces.sort.each do |space|
    zone = OpenStudio::Model::ThermalZone.new(model)
    zone.setName("#{space.name} ZN")
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end
    space.setThermalZone(zone)

    # Skip thermostat for spaces with no space type
    next if space.spaceType.empty?

    # Add a thermostat
    space_type_name = space.spaceType.get.name.get
    thermostat_name = space_type_name + ' Thermostat'
    thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name)
    if thermostat.empty?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}")
    else
      thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get
      zone.setThermostatSetpointDualSetpoint(thermostat_clone)
        # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing.
        ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model)
        ideal_loads.addToThermalZone(zone)
    end
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished creating thermal zones')
end

#model_find_and_add_construction(model, climate_zone_set, intended_surface_type, standards_construction_type, building_category) ⇒ Object

Helper method to find a particular construction and add it to the model after modifying the insulation value if necessary.



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
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 658

def model_find_and_add_construction(model, climate_zone_set, intended_surface_type, standards_construction_type, building_category)
  # Get the construction properties,
  # which specifies properties by construction category by climate zone set.
  # AKA the info in Tables 5.5-1-5.5-8

  props = model_find_object(standards_data['construction_properties'], 'template' => template,
                            'climate_zone_set' => climate_zone_set,
                            'intended_surface_type' => intended_surface_type,
                            'standards_construction_type' => standards_construction_type,
                            'building_category' => building_category)

  if !props
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Could not find construction properties for: #{template}-#{climate_zone_set}-#{intended_surface_type}-#{standards_construction_type}-#{building_category}.")
    # Return an empty construction
    construction = OpenStudio::Model::Construction.new(model)
    construction.setName('Could not find construction properties set to Adiabatic ')
    almost_adiabatic = OpenStudio::Model::MasslessOpaqueMaterial.new(model, 'Smooth', 500)
    construction.insertLayer(0, almost_adiabatic)
    return construction
  else
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Construction properties for: #{template}-#{climate_zone_set}-#{intended_surface_type}-#{standards_construction_type}-#{building_category} = #{props}.")
  end

  # Make sure that a construction is specified
  if props['construction'].nil?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "No typical construction is specified for construction properties of: #{template}-#{climate_zone_set}-#{intended_surface_type}-#{standards_construction_type}-#{building_category}.  Make sure it is entered in the spreadsheet.")
    # Return an empty construction
    construction = OpenStudio::Model::Construction.new(model)
    construction.setName('No typical construction was specified')
    return construction
  end

  # Add the construction, modifying properties as necessary
  construction = model_add_construction(model, props['construction'], props)

  return construction
end

#model_find_climate_zone_set(model, clim) ⇒ Object

Helper method to find out which climate zone set contains a specific climate zone. Returns climate zone set name as String if success, nil if not found.



1185
1186
1187
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1185

def model_find_climate_zone_set(model, clim)
  return "NECB-CNEB ClimatZone 4-8"
end

#necb_autozone_and_autosystem(model = nil, runner = nil, use_ideal_air_loads = false, system_fuel_defaults) ⇒ String

This method will take a model that uses NECB2011 spacetypes , and..

  1. Create a building story schema.

  2. Remove all existing Thermal Zone defintions.

  3. Create new thermal zones based on the following definitions.

Rule1 all zones must contain only the same schedule / occupancy schedule. Rule2 zones must cater to similar solar gains (N,E,S,W) Rule3 zones must not pass from floor to floor. They must be contained to a single floor or level. Rule4 Wildcard spaces will be associated with the nearest zone of similar schedule type in which is shared most of it’s internal surface with. Rule5 For NECB zones must contain spaces of similar system type only. Rule6 Residential / dwelling units must not share systems with other space types.

Parameters:

  • model (OpenStudio::model::Model) (defaults to: nil)

    A model object

Returns:

  • (String)

    system_zone_array

Author:



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
2929
2930
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 2689

def necb_autozone_and_autosystem(
    model = nil,
    runner = nil,
    use_ideal_air_loads = false,
    system_fuel_defaults
)

  # Create a data struct for the space to system to placement information.

  # system assignment.
  unless ['NaturalGas', 'Electricity', 'PropaneGas', 'FuelOil#1', 'FuelOil#2', 'Coal', 'Diesel', 'Gasoline', 'OtherFuel1'].include?(system_fuel_defaults['boiler_fueltype'])
    BTAP.runner_register('ERROR', "boiler_fueltype = #{system_fuel_defaults['boiler_fueltype']}", runner)
    return
  end

  unless [true, false].include?(system_fuel_defaults['mau_type'])
    BTAP.runner_register('ERROR', "mau_type = #{system_fuel_defaults['mau_type']}", runner)
    return
  end

  unless ['Hot Water', 'Electric'].include?(system_fuel_defaults['mau_heating_coil_type'])
    BTAP.runner_register('ERROR', "mau_heating_coil_type = #{system_fuel_defaults['mau_heating_coil_type']}", runner)
    return false
  end

  unless ['Hot Water', 'Electric'].include?(system_fuel_defaults['baseboard_type'])
    BTAP.runner_register('ERROR', "baseboard_type = #{system_fuel_defaults['baseboard_type']}", runner)
    return false
  end

  unless ['Scroll', 'Centrifugal', 'Rotary Screw', 'Reciprocating'].include?(system_fuel_defaults['chiller_type'])
    BTAP.runner_register('ERROR', "chiller_type = #{system_fuel_defaults['chiller_type']}", runner)
    return false
  end
  unless ['DX', 'Hydronic'].include?(system_fuel_defaults['mau_cooling_type'])
    BTAP.runner_register('ERROR', "mau_cooling_type = #{system_fuel_defaults['mau_cooling_type']}", runner)
    return false
  end

  unless ['Electric', 'Gas', 'DX'].include?(system_fuel_defaults['heating_coil_type_sys3'])
    BTAP.runner_register('ERROR', "heating_coil_type_sys3 = #{system_fuel_defaults['heating_coil_type_sys3']}", runner)
    return false
  end

  unless ['Electric', 'Gas', 'DX'].include?(system_fuel_defaults['heating_coil_type_sys4'])
    BTAP.runner_register('ERROR', "heating_coil_type_sys4 = #{system_fuel_defaults['heating_coil_type_sys4']}", runner)
    return false
  end

  unless ['Hot Water', 'Electric'].include?(system_fuel_defaults['heating_coil_type_sys6'])
    BTAP.runner_register('ERROR', "heating_coil_type_sys6 = #{system_fuel_defaults['heating_coil_type_sys6']}", runner)
    return false
  end

  unless ['AF_or_BI_rdg_fancurve', 'AF_or_BI_inletvanes', 'fc_inletvanes', 'var_speed_drive'].include?(system_fuel_defaults['fan_type'])
    BTAP.runner_register('ERROR', "fan_type = #{system_fuel_defaults['fan_type']}", runner)
    return false
  end
  # REPEATED CODE!!
  unless ['Electric', 'Hot Water'].include?(system_fuel_defaults['heating_coil_type_sys6'])
    BTAP.runner_register('ERROR', "heating_coil_type_sys6 = #{system_fuel_defaults['heating_coil_type_sys6']}", runner)
    return false
  end
  # REPEATED CODE!!
  unless ['Electric', 'Gas'].include?(system_fuel_defaults['heating_coil_type_sys4'])
    BTAP.runner_register('ERROR', "heating_coil_type_sys4 = #{system_fuel_defaults['heating_coil_type_sys4']}", runner)
    return false
  end

  # Ensure that floors have been assigned by user.
  raise('No building stories have been defined.. User must define building stories and spaces in model.') if model.getBuildingStorys.empty?
  # BTAP::Geometry::BuildingStoreys::auto_assign_stories(model)

  # this method will determine the spaces that should be set to each system
  schedule_type_array, space_zoning_data_array = necb_spacetype_system_selection(model, nil, nil)

  # Deal with Wildcard spaces. Might wish to have logic to do coridors first.
  space_zoning_data_array.sort_by(&:space_name).each do |space_zone_data|
    # If it is a wildcard space.
    if space_zone_data.system_number.nil?
      # iterate through all adjacent spaces from largest shared wall area to smallest.
      # Set system type to match first space system that is not nil.
      adj_spaces = space_get_adjacent_spaces_with_shared_wall_areas(space_zone_data.space, true)
      if adj_spaces.nil?
        puts "Warning: No adjacent spaces for #{space_zone_data.space.name} on same floor, looking for others above and below to set system"
        adj_spaces = space_get_adjacent_spaces_with_shared_wall_areas(space_zone_data.space, false)
      end
      adj_spaces.sort.each do |adj_space|
        # if there are no adjacent spaces. Raise an error.
        raise "Could not determine adj space to space #{space_zone_data.space.name.get}" if adj_space.nil?
        adj_space_data = space_zoning_data_array.find {|data| data.space == adj_space[0]}
        if adj_space_data.system_number.nil?
          next
        else
          space_zone_data.system_number = adj_space_data.system_number
          puts space_zone_data.space.name.get.to_s
          break
        end
      end
      raise "Could not determine adj space system to space #{space_zone_data.space.name.get}" if space_zone_data.system_number.nil?
    end
  end

  # remove any thermal zones used for sizing to start fresh. Should only do this after the above system selection method.
  model.getThermalZones.sort.each(&:remove)

  # now lets apply the rules.
  # Rule1 all zones must contain only the same schedule / occupancy schedule.
  # Rule2 zones must cater to similar solar gains (N,E,S,W)
  # Rule3 zones must not pass from floor to floor. They must be contained to a single floor or level.
  # Rule4 Wildcard spaces will be associated with the nearest zone of similar schedule type in which is shared most of it's internal surface with.
  # Rule5 NECB zones must contain spaces of similar system type only.
  # Rule6 Multiplier zone will be part of the floor and orientation of the base space.
  # Rule7 Residential / dwelling units must not share systems with other space types.
  # Array of system types of Array of Spaces
  system_zone_array = []
  # Lets iterate by system
  (0..7).each do |system_number|
    system_zone_array[system_number] = []
    # iterate by story
    story_counter = 0
    model.getBuildingStorys.sort.each do |story|
      # puts "Story:#{story}"
      story_counter += 1
      # iterate by operation schedule type.
      schedule_type_array.each do |schedule_type|
        # iterate by horizontal location
        ['north', 'east', 'west', 'south', 'core'].each do |horizontal_placement|
          # puts "horizontal_placement:#{horizontal_placement}"
          [true, false].each do |is_dwelling_unit|
            space_array = []
            space_zoning_data_array.each do |space_info|
              # puts "Spacename: #{space_info.space.name}:#{space_info.space.spaceType.get.name}"
              if (space_info.system_number == system_number) &&
                  (space_info.space.buildingStory.get == story) &&
                  (determine_necb_schedule_type(space_info.space).to_s == schedule_type) &&
                  (space_info.horizontal_placement == horizontal_placement) &&
                  (space_info.is_dwelling_unit == is_dwelling_unit)
                space_array << space_info.space
              end
            end

            # create Thermal Zone if space_array is not empty.
            unless space_array.empty?
              # Process spaces that have multipliers associated with them first.
              # This map define the multipliers for spaces with multipliers not equals to 1
              space_multiplier_map = @space_multiplier_map

              # create new zone and add the spaces to it.
              space_array.each do |space|
                # Create thermalzone for each space.
                thermal_zone = OpenStudio::Model::ThermalZone.new(model)
                # Create a more informative space name.
                thermal_zone.setName("Sp-#{space.name} Sys-#{system_number} Flr-#{story_counter} Sch-#{schedule_type} HPlcmt-#{horizontal_placement} ZN")
                # Add zone mulitplier if required.
                thermal_zone.setMultiplier(space_multiplier_map[space.name.to_s]) unless space_multiplier_map[space.name.to_s].nil?
                # Space to thermal zone. (for archetype work it is one to one)
                space.setThermalZone(thermal_zone)
                # Get thermostat for space type if it already exists.
                space_type_name = space.spaceType.get.name.get
                thermostat_name = space_type_name + ' Thermostat'
                thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name)
                if thermostat.empty?
                  OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name} ZN")
                  raise " Thermostat #{thermostat_name} not found for space name: #{space.name}"
                else
                  thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get
                  thermal_zone.setThermostatSetpointDualSetpoint(thermostat_clone)
                end
                # Add thermal to zone system number.
                system_zone_array[system_number] << thermal_zone
              end
            end
          end
        end
      end
    end
  end # system iteration

  # Create and assign the zones to the systems.
  if use_ideal_air_loads == true
    # otherwise use ideal loads.
    model.getThermalZones.sort.each do |thermal_zone|
      thermal_zone_ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model)
      thermal_zone_ideal_loads.addToThermalZone(thermal_zone)
    end
  else
    hw_loop_needed = false
    system_zone_array.each_with_index do |zones, system_index|
      next if zones.empty?
      if system_index == 1 && (system_fuel_defaults['mau_heating_coil_type'] == 'Hot Water' || system_fuel_defaults['baseboard_type'] == 'Hot Water')
        hw_loop_needed = true
      elsif system_index == 2 || system_index == 5 || system_index == 7
        hw_loop_needed = true
      elsif (system_index == 3 || system_index == 4) && system_fuel_defaults['baseboard_type'] == 'Hot Water'
        hw_loop_needed = true
      elsif system_index == 6 && (system_fuel_defaults['mau_heating_coil_type'] == 'Hot Water' || system_fuel_defaults['baseboard_type'] == 'Hot Water')
        hw_loop_needed = true
      end
      if hw_loop_needed
        break
      end
    end
    if hw_loop_needed
      hw_loop = OpenStudio::Model::PlantLoop.new(model)
      always_on = model.alwaysOnDiscreteSchedule
      setup_hw_loop_with_components( model, hw_loop, system_fuel_defaults['boiler_fueltype'], always_on )
    end
    system_zone_array.each_with_index do |zones, system_index|
      # skip if no thermal zones for this system.
      next if zones.empty?
      case system_index
        when 0, nil
          # Do nothing no system assigned to zone. Used for Unconditioned spaces
        when 1
          add_sys1_unitary_ac_baseboard_heating(model, zones, system_fuel_defaults['boiler_fueltype'], system_fuel_defaults['mau_type'], system_fuel_defaults['mau_heating_coil_type'], system_fuel_defaults['baseboard_type'], hw_loop)
        when 2
          add_sys2_FPFC_sys5_TPFC(model, zones, system_fuel_defaults['boiler_fueltype'], system_fuel_defaults['chiller_type'], 'FPFC', system_fuel_defaults['mau_cooling_type'], hw_loop)
        when 3
          add_sys3and8_single_zone_packaged_rooftop_unit_with_baseboard_heating_single_speed(model, zones, system_fuel_defaults['boiler_fueltype'], system_fuel_defaults['heating_coil_type_sys3'], system_fuel_defaults['baseboard_type'], hw_loop)
        when 4
          add_sys4_single_zone_make_up_air_unit_with_baseboard_heating(model, zones, system_fuel_defaults['boiler_fueltype'], system_fuel_defaults['heating_coil_type_sys4'], system_fuel_defaults['baseboard_type'], hw_loop)
        when 5
          add_sys2_FPFC_sys5_TPFC(model, zones, system_fuel_defaults['boiler_fueltype'], system_fuel_defaults['chiller_type'], 'TPFC', system_fuel_defaults['mau_cooling_type'], hw_loop)
        when 6
          add_sys6_multi_zone_built_up_system_with_baseboard_heating(model, zones, system_fuel_defaults['boiler_fueltype'], system_fuel_defaults['heating_coil_type_sys6'], system_fuel_defaults['baseboard_type'], system_fuel_defaults['chiller_type'], system_fuel_defaults['fan_type'], hw_loop)
        when 7
          add_sys2_FPFC_sys5_TPFC(model, zones, system_fuel_defaults['boiler_fueltype'], system_fuel_defaults['chiller_type'], 'FPFC', system_fuel_defaults['mau_cooling_type'], hw_loop)
      end
    end
  end
  # Check to ensure that all spaces are assigned to zones except undefined ones.
  errors = []
  model.getSpaces.sort.each do |space|
    if space.thermalZone.empty? && (space.spaceType.get.name.get != 'Space Function - undefined -')
      errors << "space #{space.name} with spacetype #{space.spaceType.get.name.get} was not assigned a thermalzone."
    end
  end
  unless errors.empty?
    raise(" #{errors}")
  end
end

#necb_spacetype_system_selection(model, heating_design_load = nil, cooling_design_load = nil) ⇒ Object



2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 2520

def necb_spacetype_system_selection(model, heating_design_load = nil, cooling_design_load = nil)
  spacezoning_data = Struct.new(
      :space, # the space object
      :space_name, # the space name
      :building_type_name, # space type name
      :space_type_name, # space type name
      :necb_hvac_system_selection_type, #
      :system_number, # the necb system type
      :number_of_stories, # number of stories
      :horizontal_placement, # the horizontal placement (norht, south, east, west, core)
      :vertical_placment, # the vertical placement ( ground, top, both, middle )
      :people_obj, # Spacetype people object
      :heating_capacity,
      :cooling_capacity,
      :is_dwelling_unit, # Checks if it is a dwelling unit.
      :is_wildcard
  )

  # Array to store schedule objects
  schedule_type_array = []


  # find the number of stories in the model this include multipliers.
  number_of_stories = model.getBuilding.standardsNumberOfAboveGroundStories
  if number_of_stories.empty?
    raise 'Number of above ground stories not present in geometry model. Please ensure this is defined in your Building Object'
  else
    number_of_stories = number_of_stories.get
  end

  # set up system array containers. These will contain the spaces associated with the system types.
  space_zoning_data_array = []

  # First pass of spaces to collect information into the space_zoning_data_array .
  model.getSpaces.sort.each do |space|
    # this will get the spacetype system index 8.4.4.8A  from the SpaceTypeData and BuildingTypeData in  (1-12)
    space_system_index = nil
    if space.spaceType.empty?
      space_system_index = nil
    else
      # gets row information from standards spreadsheet.
      space_type_property = model_find_object(standards_data['space_types'], 'template' => @template, 'space_type' => space.spaceType.get.standardsSpaceType.get, 'building_type' => space.spaceType.get.standardsBuildingType.get)
      raise("could not find necb system selection type for space: #{space.name} and spacetype #{space.spaceType.get.standardsSpaceType.get}") if space_type_property.nil?
      # stores the Building or SpaceType System type name.
      necb_hvac_system_selection_type = space_type_property['necb_hvac_system_selection_type']
    end

    # Get the heating and cooling load for the space. Only Zones with a defined thermostat will have a load.
    # Make sure we don't have sideeffects by changing the argument variables.
    cooling_load = cooling_design_load
    heating_load = heating_design_load
    if space.spaceType.get.standardsSpaceType.get == '- undefined -'
      cooling_load = 0.0
      heating_load = 0.0
    else
      cooling_load = space.thermalZone.get.coolingDesignLoad.get * space.floorArea * space.multiplier / 1000.0 if cooling_load.nil?
      heating_load = space.thermalZone.get.heatingDesignLoad.get * space.floorArea * space.multiplier / 1000.0 if heating_load.nil?
    end

    # identify space-system_index and assign the right NECB system type 1-7.
    system = nil
    is_dwelling_unit = false
    case necb_hvac_system_selection_type
      when nil
        raise "#{space.name} does not have an NECB system association. Please define a NECB HVAC System Selection Type in the google docs standards database."
      when 0, '- undefined -'
        # These are spaces are undefined...so they are unconditioned and have no loads other than infiltration and no systems
        system = 0
      when 'Assembly Area' # Assembly Area.
        system = if number_of_stories <= 4
                   3
                 else
                   6
                 end

      when 'Automotive Area'
        system = 4

      when 'Data Processing Area'
        system = if cooling_design_load > 20 # KW...need a sizing run.
                   2
                 else
                   1
                 end

      when 'General Area' # [3,6]
        system = if number_of_stories <= 2
                   3
                 else
                   6
                 end

      when 'Historical Collections Area' # [2],
        system = 2

      when 'Hospital Area' # [3],
        system = 3

      when 'Indoor Arena' # ,[7],
        system = 7

      when 'Industrial Area' #  [3] this need some thought.
        system = 3

      when 'Residential/Accomodation Area' # ,[1], this needs some thought.
        system = 1
        is_dwelling_unit = true

      when 'Sleeping Area' # [3],
        system = 3
        is_dwelling_unit = true

      when 'Supermarket/Food Services Area' # [3,4],
        system = 3

      when 'Supermarket/Food Services Area - vented'
        system = 4

      when 'Warehouse Area'
        system = 4

      when 'Warehouse Area - refrigerated'
        system = 5
      when 'Wildcard'
        system = nil
        is_wildcard = true
      else
        raise "NECB HVAC System Selection Type #{necb_hvac_system_selection_type} not valid"
    end
    # get placement on floor, core or perimeter and if a top, bottom, middle or single story.
    horizontal_placement, vertical_placement = BTAP::Geometry::Spaces.get_space_placement(space)
    # dump all info into an array for debugging and iteration.
    unless space.spaceType.empty?
      space_type_name = space.spaceType.get.standardsSpaceType.get
      building_type_name = space.spaceType.get.standardsBuildingType.get
      space_zoning_data_array << spacezoning_data.new(space,
                                                      space.name.get,
                                                      building_type_name,
                                                      space_type_name,
                                                      necb_hvac_system_selection_type,
                                                      system,
                                                      number_of_stories,
                                                      horizontal_placement,
                                                      vertical_placement,
                                                      space.spaceType.get.people,
                                                      heating_load,
                                                      cooling_load,
                                                      is_dwelling_unit,
                                                      is_wildcard)
      schedule_type_array << determine_necb_schedule_type(space).to_s
    end
  end

  return schedule_type_array.uniq!, space_zoning_data_array
end

#set_construction_set_to_necb!(model, default_surface_construction_set, runner = nil, scale_wall = 1.0, scale_floor = 1.0, scale_roof = 1.0, scale_ground_wall = 1.0, scale_ground_floor = 1.0, scale_ground_roof = 1.0, scale_door = 1.0, scale_window = 1.0) ⇒ Boolean

this will create a copy and convert all construction sets to NECB reference conductances.

Parameters:

  • model (OpenStudio::model::Model)

    A model object

  • default_surface_construction_set (String)

Returns:

  • (Boolean)

    returns true if sucessful, false if not

Author:



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
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 289

def set_construction_set_to_necb!(model, default_surface_construction_set,
                                  runner = nil,
                                  scale_wall = 1.0,
                                  scale_floor = 1.0,
                                  scale_roof = 1.0,
                                  scale_ground_wall = 1.0,
                                  scale_ground_floor = 1.0,
                                  scale_ground_roof = 1.0,
                                  scale_door = 1.0,
                                  scale_window = 1.0)
  BTAP.runner_register('Info', 'set_construction_set_to_necb!', runner)
  if model.weatherFile.empty? || model.weatherFile.get.path.empty? || !File.exist?(model.weatherFile.get.path.get.to_s)

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

  #Note:hdd needs to be defined for eval to work on table eval below.
  hdd = self.get_necb_hdd18(model)

  old_name = default_surface_construction_set.name.get.to_s
  new_name = "#{old_name} at hdd = #{hdd}"

  # convert conductance values to rsi values. (Note: we should really be only using conductances in)
  wall_rsi = 1.0 / (scale_wall * eval(self.get_standards_table('surface_thermal_transmittance', {'boundary_condition' => 'Outdoors', 'surface' => 'Wall'})[0]['formula']))
  floor_rsi = 1.0 / (scale_floor * eval(self.get_standards_table('surface_thermal_transmittance', {'boundary_condition' => 'Outdoors', 'surface' => 'Floor'})[0]['formula']))
  roof_rsi = 1.0 / (scale_roof * eval(self.get_standards_table('surface_thermal_transmittance', {'boundary_condition' => 'Outdoors', 'surface' => 'RoofCeiling'})[0]['formula']))
  ground_wall_rsi = 1.0 / (scale_ground_wall * eval(self.get_standards_table('surface_thermal_transmittance', {'boundary_condition' => 'Ground', 'surface' => 'Wall'})[0]['formula']))
  ground_floor_rsi = 1.0 / (scale_ground_floor * eval(self.get_standards_table('surface_thermal_transmittance', {'boundary_condition' => 'Ground', 'surface' => 'Floor'})[0]['formula']))
  ground_roof_rsi = 1.0 / (scale_ground_roof * eval(self.get_standards_table('surface_thermal_transmittance', {'boundary_condition' => 'Ground', 'surface' => 'RoofCeiling'})[0]['formula']))
  door_rsi = 1.0 / (scale_door * eval(self.get_standards_table('surface_thermal_transmittance', {'boundary_condition' => 'Outdoors', 'surface' => 'Door'})[0]['formula']))
  window_rsi = 1.0 / (scale_window * eval(self.get_standards_table('surface_thermal_transmittance', {'boundary_condition' => 'Outdoors', 'surface' => 'Window'})[0]['formula']))
  BTAP::Resources::Envelope::ConstructionSets.customize_default_surface_construction_set_rsi!(model, new_name, default_surface_construction_set,
                                                                                              wall_rsi, floor_rsi, roof_rsi,
                                                                                              ground_wall_rsi, ground_floor_rsi, ground_roof_rsi,
                                                                                              window_rsi, nil, nil,
                                                                                              window_rsi, nil, nil,
                                                                                              door_rsi,
                                                                                              door_rsi, nil, nil,
                                                                                              door_rsi,
                                                                                              window_rsi, nil, nil,
                                                                                              window_rsi, nil, nil,
                                                                                              window_rsi, nil, nil)
  BTAP.runner_register('Info', 'set_construction_set_to_necb! was sucessful.', runner)
  return true
end

#set_necb_external_subsurface_conductance(subsurface, hdd) ⇒ Object

Set all external subsurfaces (doors, windows, skylights) to NECB values.

Parameters:

  • subsurface (String)
  • hdd (Float)

Author:



382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 382

def set_necb_external_subsurface_conductance(subsurface, hdd)
  conductance_value = 0

  if subsurface.outsideBoundaryCondition.downcase.match('outdoors')
    case subsurface.subSurfaceType.downcase
      when /window/
        conductance_value = @standards_data['conductances']['Window'].find {|i| i['hdd'] > hdd}['thermal_transmittance'] * scaling_factor
      when /door/
        conductance_value = @standards_data['conductances']['Door'].find {|i| i['hdd'] > hdd}['thermal_transmittance'] * scaling_factor
    end
    subsurface.setRSI(1 / conductance_value)
  end
end

#set_necb_external_surface_conductance(surface, hdd, is_radiant = false, scaling_factor = 1.0) ⇒ String

Set all external surface conductances to NECB values.

Parameters:

  • surface (String)
  • hdd (Float)
  • is_radiant (Boolian) (defaults to: false)
  • scaling_factor (Float) (defaults to: 1.0)

Returns:

Author:



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
# File 'lib/openstudio-standards/standards/necb/necb_2011/building_envelope.rb', line 343

def set_necb_external_surface_conductance(surface, hdd, is_radiant = false, scaling_factor = 1.0)
  conductance_value = 0

  if surface.outsideBoundaryCondition.casecmp('outdoors').zero?

    case surface.surfaceType.downcase
      when 'wall'
        conductance_value = @standards_data['conductances']['Wall'].find {|i| i['hdd'] > hdd}['thermal_transmittance'] * scaling_factor
      when 'floor'
        conductance_value = @standards_data['conductances']['Floor'].find {|i| i['hdd'] > hdd}['thermal_transmittance'] * scaling_factor
      when 'roofceiling'
        conductance_value = @standards_data['conductances']['Roof'].find {|i| i['hdd'] > hdd}['thermal_transmittance'] * scaling_factor
    end
    if is_radiant
      conductance_value *= 0.80
    end
    return BTAP::Geometry::Surfaces.set_surfaces_construction_conductance([surface], conductance_value)
  end

  if surface.outsideBoundaryCondition.downcase =~ /ground/
    case surface.surfaceType.downcase
      when 'wall'
        conductance_value = @standards_data['conductances']['GroundWall'].find {|i| i['hdd'] > hdd}['thermal_transmittance'] * scaling_factor
      when 'floor'
        conductance_value = @standards_data['conductances']['GroundFloor'].find {|i| i['hdd'] > hdd}['thermal_transmittance'] * scaling_factor
      when 'roofceiling'
        conductance_value = @standards_data['conductances']['GroundRoof'].find {|i| i['hdd'] > hdd}['thermal_transmittance'] * scaling_factor
    end
    if is_radiant
      conductance_value *= 0.80
    end
    return BTAP::Geometry::Surfaces.set_surfaces_construction_conductance([surface], conductance_value)
  end
end

#set_occ_sensor_spacetypes(model, space_type_map) ⇒ Bool

Returns true if successful, false if not

Returns:

  • (Bool)

    returns true if successful, false if not



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

def set_occ_sensor_spacetypes(model, space_type_map)
  building_type = 'Space Function'
  space_type_map.each do |space_type_name, space_names|
    space_names.sort.each do |space_name|
      space = model.getSpaceByName(space_name)
      next if space.empty?
      space = space.get

      # Check if space type for this space matches NECB2011 specific space type
      # for occupancy sensor that is area dependent. Note: space.floorArea in m2.

      if (space_type_name == 'Storage area' && space.floorArea < 100) ||
          (space_type_name == 'Storage area - refrigerated' && space.floorArea < 100) ||
          (space_type_name == 'Hospital - medical supply' && space.floorArea < 100) ||
          (space_type_name == 'Office - enclosed' && space.floorArea < 25)
        # If there is only one space assigned to this space type, then reassign this stub
        # to the @@template duplicate with appendage " - occsens", otherwise create a new stub
        # for this space. Required to use reduced LPD by NECB2011 0.9 factor.
        space_type_name_occsens = space_type_name + ' - occsens'
        stub_space_type_occsens = model.getSpaceTypeByName("#{building_type} #{space_type_name_occsens}")

        if stub_space_type_occsens.empty?
          # create a new space type just once for space_type_name appended with " - occsens"
          stub_space_type_occsens = OpenStudio::Model::SpaceType.new(model)
          stub_space_type_occsens.setStandardsBuildingType(building_type)
          stub_space_type_occsens.setStandardsSpaceType(space_type_name_occsens)
          stub_space_type_occsens.setName("#{building_type} #{space_type_name_occsens}")
          space_type_apply_rendering_color(stub_space_type_occsens)
          space.setSpaceType(stub_space_type_occsens)
        else
          # reassign occsens space type stub already created...
          stub_space_type_occsens = stub_space_type_occsens.get
          space.setSpaceType(stub_space_type_occsens)
        end
      end
    end
  end
  return true
end

#set_wildcard_schedules_to_dominant_building_schedule(model, runner = nil) ⇒ Object



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
246
247
248
249
250
251
252
253
254
255
# File 'lib/openstudio-standards/standards/necb/necb_2011/necb_2011.rb', line 215

def set_wildcard_schedules_to_dominant_building_schedule(model, runner = nil)
  new_sched_ruleset = OpenStudio::Model::DefaultScheduleSet.new(model) # initialize
  BTAP.runner_register('Info', 'set_wildcard_schedules_to_dominant_building_schedule', runner)
  # Set wildcard schedules based on dominant schedule type in building.
  dominant_sched_type = determine_dominant_necb_schedule_type(model)
  # puts "dominant_sched_type = #{dominant_sched_type}"
  # find schedule set that corresponds to dominant schedule type
  model.getDefaultScheduleSets.sort.each do |sched_ruleset|
    # just check people schedule
    # TO DO: should make this smarter: check all schedules
    people_sched = sched_ruleset.numberofPeopleSchedule
    people_sched_name = people_sched.get.name.to_s unless people_sched.empty?

    search_string = "NECB-#{dominant_sched_type}"

    if people_sched.empty? == false
      if people_sched_name.include? search_string
        new_sched_ruleset = sched_ruleset
      end
    end
  end

  # replace the default schedule set for the space type with * to schedule ruleset with dominant schedule type

  model.getSpaces.sort.each do |space|
    # check to see if space space type has a "*" wildcard schedule.
    spacetype_name = space.spaceType.get.name.to_s unless space.spaceType.empty?
    if determine_necb_schedule_type(space).to_s == '*'.to_s
      new_sched = spacetype_name.to_s
      optional_spacetype = model.getSpaceTypeByName(new_sched)
      if optional_spacetype.empty?
        BTAP.runner_register('Error', "Cannot find NECB spacetype #{new_sched}", runner)
      else
        BTAP.runner_register('Info', "Setting wildcard spacetype #{spacetype_name} default schedule set to #{new_sched_ruleset.name}", runner)
        optional_spacetype.get.setDefaultScheduleSet(new_sched_ruleset) # this works!
      end
    end
  end # end of do |space|

  return true
end

#set_zones_thermostat_schedule_based_on_space_type_schedules(model, runner = nil) ⇒ Object



1123
1124
1125
1126
1127
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
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1123

def set_zones_thermostat_schedule_based_on_space_type_schedules(model, runner = nil)
  puts 'in set_zones_thermostat_schedule_based_on_space_type_schedules'
  BTAP.runner_register('DEBUG', 'Start-set_zones_thermostat_schedule_based_on_space_type_schedules', runner)
  model.getThermalZones.sort.each do |zone|
    BTAP.runner_register('DEBUG', "Zone = #{zone.name} Spaces =#{zone.spaces.size} ", runner)
    array = []

    zone.spaces.sort.each do |space|
      schedule_type = determine_necb_schedule_type(space).to_s
      BTAP.runner_register('DEBUG', "space name/type:#{space.name}/#{schedule_type}", runner)

      # if wildcard space type, need to get dominant schedule type
      if '*'.to_s == schedule_type
        dominant_sched_type = determine_dominant_necb_schedule_type(model)
        schedule_type = dominant_sched_type
      end

      array << schedule_type
    end
    array.uniq!
    if array.size > 1
      BTAP.runner_register('Error', "#{zone.name} has spaces with different schedule types. Please ensure that all the spaces are of the same schedule type A to I.", runner)
      return false
    end

    htg_search_string = "NECB-#{array[0]}-Thermostat Setpoint-Heating"
    clg_search_string = "NECB-#{array[0]}-Thermostat Setpoint-Cooling"

    if model.getScheduleRulesetByName(htg_search_string).empty? == false
      htg_sched = model.getScheduleRulesetByName(htg_search_string).get
    else
      BTAP.runner_register('ERROR', "heating_thermostat_setpoint_schedule NECB-#{array[0]} does not exist", runner)
      return false
    end

    if model.getScheduleRulesetByName(clg_search_string).empty? == false
      clg_sched = model.getScheduleRulesetByName(clg_search_string).get
    else
      BTAP.runner_register('ERROR', "cooling_thermostat_setpoint_schedule NECB-#{array[0]} does not exist", runner)
      return false
    end

    name = "NECB-#{array[0]}-Thermostat Dual Setpoint Schedule"

    # If dual setpoint already exists, use that one, else create one
    ds = if model.getThermostatSetpointDualSetpointByName(name).empty? == false
           model.getThermostatSetpointDualSetpointByName(name).get
         else
           BTAP::Resources::Schedules.create_annual_thermostat_setpoint_dual_setpoint(model, name, htg_sched, clg_sched)
         end

    thermostat_clone = ds.clone.to_ThermostatSetpointDualSetpoint.get
    zone.setThermostatSetpointDualSetpoint(thermostat_clone)
    BTAP.runner_register('Info', "ThermalZone #{zone.name} set to DualSetpoint Schedule NECB-#{array[0]}", runner)
  end

  BTAP.runner_register('DEBUG', 'END-set_zones_thermostat_schedule_based_on_space_type_schedules', runner)
  return true
end

#setup_chw_loop_with_components(model, chw_loop, chiller_type) ⇒ Object

of setup_hw_loop_with_components



2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 2439

def setup_chw_loop_with_components(model, chw_loop, chiller_type)
  chw_loop.setName('Chilled Water Loop')
  sizing_plant = chw_loop.sizingPlant
  sizing_plant.setLoopType('Cooling')
  sizing_plant.setDesignLoopExitTemperature(7.0)
  sizing_plant.setLoopDesignTemperatureDifference(6.0)

  # pump = OpenStudio::Model::PumpConstantSpeed.new(model)
  chw_pump = OpenStudio::Model::PumpConstantSpeed.new(model)

  chiller1 = OpenStudio::Model::ChillerElectricEIR.new(model)
  chiller2 = OpenStudio::Model::ChillerElectricEIR.new(model)
  chiller1.setCondenserType('WaterCooled')
  chiller2.setCondenserType('WaterCooled')
  chiller1_name = "Primary Chiller WaterCooled #{chiller_type}"
  chiller1.setName(chiller1_name)
  chiller2_name = "Secondary Chiller WaterCooled #{chiller_type}"
  chiller2.setName(chiller2_name)

  chiller_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  chw_supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  # Add the components to the chilled water loop
  chw_supply_inlet_node = chw_loop.supplyInletNode
  chw_supply_outlet_node = chw_loop.supplyOutletNode
  chw_pump.addToNode(chw_supply_inlet_node)
  chw_loop.addSupplyBranchForComponent(chiller1)
  chw_loop.addSupplyBranchForComponent(chiller2)
  chw_loop.addSupplyBranchForComponent(chiller_bypass_pipe)
  chw_supply_outlet_pipe.addToNode(chw_supply_outlet_node)

  # Add a setpoint manager to control the
  # chilled water to a constant temperature
  chw_t_c = 7.0
  chw_t_sch = BTAP::Resources::Schedules.create_annual_constant_ruleset_schedule(model, 'CHW Temp', 'Temperature', chw_t_c)
  chw_t_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, chw_t_sch)
  chw_t_stpt_manager.addToNode(chw_supply_outlet_node)

  return chiller1, chiller2
end

#setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2) ⇒ Object

of setup_chw_loop_with_components



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

def setup_cw_loop_with_components(model, cw_loop, chiller1, chiller2)
  cw_loop.setName('Condenser Water Loop')
  cw_sizing_plant = cw_loop.sizingPlant
  cw_sizing_plant.setLoopType('Condenser')
  cw_sizing_plant.setDesignLoopExitTemperature(29.0)
  cw_sizing_plant.setLoopDesignTemperatureDifference(6.0)

  cw_pump = OpenStudio::Model::PumpConstantSpeed.new(model)

  clg_tower = OpenStudio::Model::CoolingTowerSingleSpeed.new(model)

  # TO DO: Need to define and set cooling tower curves

  clg_tower_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  cw_supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  # Add the components to the condenser water loop
  cw_supply_inlet_node = cw_loop.supplyInletNode
  cw_supply_outlet_node = cw_loop.supplyOutletNode
  cw_pump.addToNode(cw_supply_inlet_node)
  cw_loop.addSupplyBranchForComponent(clg_tower)
  cw_loop.addSupplyBranchForComponent(clg_tower_bypass_pipe)
  cw_supply_outlet_pipe.addToNode(cw_supply_outlet_node)
  cw_loop.addDemandBranchForComponent(chiller1)
  cw_loop.addDemandBranchForComponent(chiller2)

  # Add a setpoint manager to control the
  # condenser water to constant temperature
  cw_t_c = 29.0
  cw_t_sch = BTAP::Resources::Schedules.create_annual_constant_ruleset_schedule(model, 'CW Temp', 'Temperature', cw_t_c)
  cw_t_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, cw_t_sch)
  cw_t_stpt_manager.addToNode(cw_supply_outlet_node)

  return clg_tower
end

#setup_hw_loop_with_components(model, hw_loop, boiler_fueltype, pump_flow_sch) ⇒ Object



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
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 2385

def setup_hw_loop_with_components(model, hw_loop, boiler_fueltype, pump_flow_sch)
  hw_loop.setName('Hot Water Loop')
  sizing_plant = hw_loop.sizingPlant
  sizing_plant.setLoopType('Heating')
  sizing_plant.setDesignLoopExitTemperature(82.0) # TODO: units
  sizing_plant.setLoopDesignTemperatureDifference(16.0)

  # pump (set to variable speed for now till fix to run away plant temperature is found)
  # pump = OpenStudio::Model::PumpConstantSpeed.new(model)
  pump = OpenStudio::Model::PumpVariableSpeed.new(model)
  # TODO: the keyword "setPumpFlowRateSchedule" does not seem to work. A message
  # was sent to NREL to let them know about this. Once there is a fix for this,
  # use the proper pump schedule depending on whether we have two-pipe or four-pipe
  # fan coils.
  #            pump.resetPumpFlowRateSchedule()
  #            pump.setPumpFlowRateSchedule(pump_flow_sch)

  # boiler
  boiler1 = OpenStudio::Model::BoilerHotWater.new(model)
  boiler2 = OpenStudio::Model::BoilerHotWater.new(model)
  boiler1.setFuelType(boiler_fueltype)
  boiler2.setFuelType(boiler_fueltype)
  boiler1.setName('Primary Boiler')
  boiler2.setName('Secondary Boiler')

  # boiler_bypass_pipe
  boiler_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  # supply_outlet_pipe
  supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)

  # Add the components to the hot water loop
  hw_supply_inlet_node = hw_loop.supplyInletNode
  hw_supply_outlet_node = hw_loop.supplyOutletNode
  pump.addToNode(hw_supply_inlet_node)

  hw_loop.addSupplyBranchForComponent(boiler1)
  hw_loop.addSupplyBranchForComponent(boiler2)
  hw_loop.addSupplyBranchForComponent(boiler_bypass_pipe)
  supply_outlet_pipe.addToNode(hw_supply_outlet_node)

  # Add a setpoint manager to control the
  # hot water based on outdoor temperature
  hw_oareset_stpt_manager = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(model)
  hw_oareset_stpt_manager.setControlVariable('Temperature')
  hw_oareset_stpt_manager.setSetpointatOutdoorLowTemperature(82.0)
  hw_oareset_stpt_manager.setOutdoorLowTemperature(-16.0)
  hw_oareset_stpt_manager.setSetpointatOutdoorHighTemperature(60.0)
  hw_oareset_stpt_manager.setOutdoorHighTemperature(0.0)
  hw_oareset_stpt_manager.addToNode(hw_supply_outlet_node)
end

#space_apply_infiltration_rate(space) ⇒ Double

TODO:

handle doors and vestibules

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

Returns:

  • (Double)

    true if successful, false if not



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

def space_apply_infiltration_rate(space)
  # Remove infiltration rates set at the space type.
  infiltration_data = @standards_data['infiltration']
  unless space.spaceType.empty?
    space.spaceType.get.spaceInfiltrationDesignFlowRates.each(&:remove)
  end
  # Remove infiltration rates set at the space object.
  space.spaceInfiltrationDesignFlowRates.each(&:remove)

  exterior_wall_and_roof_and_subsurface_area = space_exterior_wall_and_roof_and_subsurface_area(space) # To do
  # Don't create an object if there is no exterior wall area
  if exterior_wall_and_roof_and_subsurface_area <= 0.0
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "For #{template}, no exterior wall area was found, no infiltration will be added.")
    return true
  end
  # Calculate the total infiltration, assuming
  # that it only occurs through exterior walls and roofs (not floors as
  # explicit stated in the NECB2011 so overhang/cantilevered floors will
  # have no effective infiltration)
  tot_infil_m3_per_s = self.get_standards_constant('infiltration_rate_m3_per_s_per_m2') * exterior_wall_and_roof_and_subsurface_area
  # Now spread the total infiltration rate over all
  # exterior surface area (for the E+ input field) this will include the exterior floor if present.
  all_ext_infil_m3_per_s_per_m2 = tot_infil_m3_per_s / space.exteriorArea

  OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.Space', "For #{space.name}, adj infil = #{all_ext_infil_m3_per_s_per_m2.round(8)} m^3/s*m^2.")

  # Get any infiltration schedule already assigned to this space or its space type
  # If not, the always on schedule will be applied.
  infil_sch = nil
  unless space.spaceInfiltrationDesignFlowRates.empty?
    old_infil = space.spaceInfiltrationDesignFlowRates[0]
    if old_infil.schedule.is_initialized
      infil_sch = old_infil.schedule.get
    end
  end

  if infil_sch.nil? && space.spaceType.is_initialized
    space_type = space.spaceType.get
    unless space_type.spaceInfiltrationDesignFlowRates.empty?
      old_infil = space_type.spaceInfiltrationDesignFlowRates[0]
      if old_infil.schedule.is_initialized
        infil_sch = old_infil.schedule.get
      end
    end
  end

  if infil_sch.nil?
    infil_sch = space.model.alwaysOnDiscreteSchedule
  end

  # Create an infiltration rate object for this space
  infiltration = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(space.model)
  infiltration.setName("#{space.name} Infiltration")
  infiltration.setFlowperExteriorSurfaceArea(all_ext_infil_m3_per_s_per_m2)
  infiltration.setSchedule(infil_sch)
  infiltration.setConstantTermCoefficient(self.get_standards_constant('infiltration_constant_term_coefficient'))
  infiltration.setTemperatureTermCoefficient(self.get_standards_constant('infiltration_constant_term_coefficient'))
  infiltration.setVelocityTermCoefficient(self.get_standards_constant('infiltration_velocity_term_coefficient'))
  infiltration.setVelocitySquaredTermCoefficient(self.get_standards_constant('infiltration_velocity_squared_term_coefficient'))
  infiltration.setSpace(space)

  return true
end

#space_type_apply_internal_loads(space_type, set_people, set_lights, set_electric_equipment, set_gas_equipment, set_ventilation, set_infiltration) ⇒ Bool

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.

Also, assign reasonable clothing, air velocity, and work efficiency inputs to allow reasonable thermal comfort metrics to be calculated. to return air, fraction radiant, and fraction visible.

Parameters:

  • set_people (Bool)

    if true, set the people density.

  • set_lights (Bool)

    if true, set the lighting density, lighting fraction

  • set_electric_equipment (Bool)

    if true, set the electric equipment density

  • set_gas_equipment (Bool)

    if true, set the gas equipment density

  • set_ventilation (Bool)

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

  • set_infiltration (Bool)

    if true, set the infiltration rates

Returns:

  • (Bool)

    returns true if successful, false if not



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

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
    if space_type.standardsSpaceType.get.downcase.include?('plenum')
      return false
    end
  end

  # Get the space Type data from @standards data
  spacetype_data = nil
  if @standards_data['space_types'].is_a?(Hash) == true
    spacetype_data = @standards_data['space_types']['table']
  else
    spacetype_data = @standards_data['space_types']
  end
  standards_building_type = space_type.standardsBuildingType.is_initialized ? space_type.standardsBuildingType.get : nil
  standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil
  space_type_properties = spacetype_data.select { |s| (s['building_type'] == standards_building_type) && (s['space_type'] == standards_space_type) }[0]

  # Need to add a check, or it'll crash on space_type_properties['occupancy_per_area'].to_f below
  if space_type_properties.nil?
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} was not found in the standards data.")
    return false
  end
  # People
  people_have_info = false
  occupancy_per_area = space_type_properties['occupancy_per_area'].to_f
  people_have_info = true unless occupancy_per_area.zero?

  if set_people && people_have_info

    # Remove all but the first instance
    instances = space_type.people.sort
    if instances.size.zero?
      # Create a new definition and instance
      definition = OpenStudio::Model::PeopleDefinition.new(space_type.model)
      definition.setName("#{space_type.name} People Definition")
      instance = OpenStudio::Model::People.new(definition)
      instance.setName("#{space_type.name} People")
      instance.setSpaceType(space_type)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no people, one has been created.")
      instances << instance
    elsif instances.size > 1
      instances.each_with_index do |inst, i|
        next if i.zero?
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
        inst.remove
      end
    end

    # Modify the definition of the instance
    space_type.people.sort.each do |inst|
      definition = inst.peopleDefinition
      unless occupancy_per_area.zero?
        definition.setPeopleperSpaceFloorArea(OpenStudio.convert(occupancy_per_area / 1000, 'people/ft^2', 'people/m^2').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set occupancy to #{occupancy_per_area} people/1000 ft^2.")
      end

      # set fraction radiant  ##
      definition.setFractionRadiant(0.3)

      # Clothing schedule for thermal comfort metrics
      clothing_sch = space_type.model.getScheduleRulesetByName('Clothing Schedule')
      if clothing_sch.is_initialized
        clothing_sch = clothing_sch.get
      else
        clothing_sch = OpenStudio::Model::ScheduleRuleset.new(space_type.model)
        clothing_sch.setName('Clothing Schedule')
        clothing_sch.defaultDaySchedule.setName('Clothing Schedule Default Winter Clothes')
        clothing_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0)
        sch_rule = OpenStudio::Model::ScheduleRule.new(clothing_sch)
        sch_rule.daySchedule.setName('Clothing Schedule Summer Clothes')
        sch_rule.daySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.5)
        sch_rule.setStartDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(5), 1))
        sch_rule.setEndDate(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(9), 30))
      end
      inst.setClothingInsulationSchedule(clothing_sch)

      # Air velocity schedule for thermal comfort metrics
      air_velo_sch = space_type.model.getScheduleRulesetByName('Air Velocity Schedule')
      if air_velo_sch.is_initialized
        air_velo_sch = air_velo_sch.get
      else
        air_velo_sch = OpenStudio::Model::ScheduleRuleset.new(space_type.model)
        air_velo_sch.setName('Air Velocity Schedule')
        air_velo_sch.defaultDaySchedule.setName('Air Velocity Schedule Default')
        air_velo_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.2)
      end
      inst.setAirVelocitySchedule(air_velo_sch)

      # Work efficiency schedule for thermal comfort metrics
      work_efficiency_sch = space_type.model.getScheduleRulesetByName('Work Efficiency Schedule')
      if work_efficiency_sch.is_initialized
        work_efficiency_sch = work_efficiency_sch.get
      else
        work_efficiency_sch = OpenStudio::Model::ScheduleRuleset.new(space_type.model)
        work_efficiency_sch.setName('Work Efficiency Schedule')
        work_efficiency_sch.defaultDaySchedule.setName('Work Efficiency Schedule Default')
        work_efficiency_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0)
      end
      inst.setWorkEfficiencySchedule(work_efficiency_sch)
    end

  end

  # Lights
  apply_standard_lights(set_lights, space_type, space_type_properties)

  # Electric Equipment
  elec_equip_have_info = false
  elec_equip_per_area = space_type_properties['electric_equipment_per_area'].to_f
  elec_equip_frac_latent = space_type_properties['electric_equipment_fraction_latent'].to_f
  elec_equip_frac_radiant = space_type_properties['electric_equipment_fraction_radiant'].to_f
  elec_equip_frac_lost = space_type_properties['electric_equipment_fraction_lost'].to_f
  elec_equip_have_info = true unless elec_equip_per_area.zero?

  if set_electric_equipment && elec_equip_have_info

    # Remove all but the first instance
    instances = space_type.electricEquipment.sort
    if instances.size.zero?
      definition = OpenStudio::Model::ElectricEquipmentDefinition.new(space_type.model)
      definition.setName("#{space_type.name} Elec Equip Definition")
      instance = OpenStudio::Model::ElectricEquipment.new(definition)
      instance.setName("#{space_type.name} Elec Equip")
      instance.setSpaceType(space_type)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no electric equipment, one has been created.")
      instances << instance
    elsif instances.size > 1
      instances.each_with_index do |inst, i|
        next if i.zero?
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
        inst.remove
      end
    end

    # Modify the definition of the instance
    space_type.electricEquipment.sort.each do |inst|
      definition = inst.electricEquipmentDefinition
      unless elec_equip_per_area.zero?
        definition.setWattsperSpaceFloorArea(OpenStudio.convert(elec_equip_per_area.to_f, 'W/ft^2', 'W/m^2').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set electric EPD to #{elec_equip_per_area} W/ft^2.")
      end
      unless elec_equip_frac_latent.zero?
        definition.setFractionLatent(elec_equip_frac_latent)
      end
      unless elec_equip_frac_radiant.zero?
        definition.setFractionRadiant(elec_equip_frac_radiant)
      end
      unless elec_equip_frac_lost.zero?
        definition.setFractionLost(elec_equip_frac_lost)
      end
    end

  end

  # Gas Equipment
  gas_equip_have_info = false
  gas_equip_per_area = space_type_properties['gas_equipment_per_area'].to_f
  gas_equip_frac_latent = space_type_properties['gas_equipment_fraction_latent'].to_f
  gas_equip_frac_radiant = space_type_properties['gas_equipment_fraction_radiant'].to_f
  gas_equip_frac_lost = space_type_properties['gas_equipment_fraction_lost'].to_f
  gas_equip_have_info = true unless gas_equip_per_area.zero?

  if set_gas_equipment && gas_equip_have_info

    # Remove all but the first instance
    instances = space_type.gasEquipment.sort
    if instances.size.zero?
      definition = OpenStudio::Model::GasEquipmentDefinition.new(space_type.model)
      definition.setName("#{space_type.name} Gas Equip Definition")
      instance = OpenStudio::Model::GasEquipment.new(definition)
      instance.setName("#{space_type.name} Gas Equip")
      instance.setSpaceType(space_type)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no gas equipment, one has been created.")
      instances << instance
    elsif instances.size > 1
      instances.each_with_index do |inst, i|
        next if i.zero?
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
        inst.remove
      end
    end

    # Modify the definition of the instance
    space_type.gasEquipment.sort.each do |inst|
      definition = inst.gasEquipmentDefinition
      unless gas_equip_per_area.zero?
        definition.setWattsperSpaceFloorArea(OpenStudio.convert(gas_equip_per_area.to_f, 'Btu/hr*ft^2', 'W/m^2').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set gas EPD to #{elec_equip_per_area} Btu/hr*ft^2.")
      end
      unless gas_equip_frac_latent.zero?
        definition.setFractionLatent(gas_equip_frac_latent)
      end
      unless gas_equip_frac_radiant.zero?
        definition.setFractionRadiant(gas_equip_frac_radiant)
      end
      unless gas_equip_frac_lost.zero?
        definition.setFractionLost(gas_equip_frac_lost)
      end
    end

  end

  # Ventilation
  ventilation_have_info = false
  ventilation_per_area = space_type_properties['ventilation_per_area'].to_f
  ventilation_per_person = space_type_properties['ventilation_per_person'].to_f
  ventilation_ach = space_type_properties['ventilation_air_changes'].to_f
  ventilation_have_info = true unless ventilation_per_area.zero?
  ventilation_have_info = true unless ventilation_per_person.zero?
  ventilation_have_info = true unless ventilation_ach.zero?

  # Get the design OA or create a new one if none exists
  ventilation = space_type.designSpecificationOutdoorAir
  if ventilation.is_initialized
    ventilation = ventilation.get
  else
    ventilation = OpenStudio::Model::DesignSpecificationOutdoorAir.new(space_type.model)
    ventilation.setName("#{space_type.name} Ventilation")
    space_type.setDesignSpecificationOutdoorAir(ventilation)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no ventilation specification, one has been created.")
  end

  if set_ventilation && ventilation_have_info

    # Modify the ventilation properties
    ventilation.setOutdoorAirMethod('Sum')
    unless ventilation_per_area.zero?
      ventilation.setOutdoorAirFlowperFloorArea(OpenStudio.convert(ventilation_per_area.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set ventilation per area to #{ventilation_per_area} cfm/ft^2.")
    end
    unless ventilation_per_person.zero?
      ventilation.setOutdoorAirFlowperPerson(OpenStudio.convert(ventilation_per_person.to_f, 'ft^3/min*person', 'm^3/s*person').get)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set ventilation per person to #{ventilation_per_person} cfm/person.")
    end
    unless ventilation_ach.zero?
      ventilation.setOutdoorAirFlowAirChangesperHour(ventilation_ach)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set ventilation to #{ventilation_ach} ACH.")
    end

  elsif set_ventilation && !ventilation_have_info

    # All space types must have a design spec OA
    # object for ventilation controls to work correctly,
    # even if the values are all zero.
    ventilation.setOutdoorAirFlowperFloorArea(0)
    ventilation.setOutdoorAirFlowperPerson(0)
    ventilation.setOutdoorAirFlowAirChangesperHour(0)

  end

  # Infiltration
  infiltration_have_info = false
  infiltration_per_area_ext = space_type_properties['infiltration_per_exterior_area'].to_f
  infiltration_per_area_ext_wall = space_type_properties['infiltration_per_exterior_wall_area'].to_f
  infiltration_ach = space_type_properties['infiltration_air_changes'].to_f
  unless infiltration_per_area_ext.zero? && infiltration_per_area_ext_wall.zero? && infiltration_ach.zero?
    infiltration_have_info = true
  end

  if set_infiltration && infiltration_have_info

    # Remove all but the first instance
    instances = space_type.spaceInfiltrationDesignFlowRates.sort
    if instances.size.zero?
      instance = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(space_type.model)
      instance.setName("#{space_type.name} Infiltration")
      instance.setSpaceType(space_type)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no infiltration objects, one has been created.")
      instances << instance
    elsif instances.size > 1
      instances.each_with_index do |inst, i|
        next if i.zero?
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "Removed #{inst.name} from #{space_type.name}.")
        inst.remove
      end
    end

    # Modify each instance
    space_type.spaceInfiltrationDesignFlowRates.sort.each do |inst|
      unless infiltration_per_area_ext.zero?
        inst.setFlowperExteriorSurfaceArea(OpenStudio.convert(infiltration_per_area_ext.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set infiltration to #{ventilation_ach} per ft^2 exterior surface area.")
      end
      unless infiltration_per_area_ext_wall.zero?
        inst.setFlowperExteriorWallArea(OpenStudio.convert(infiltration_per_area_ext_wall.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set infiltration to #{infiltration_per_area_ext_wall} per ft^2 exterior wall area.")
      end
      unless infiltration_ach.zero?
        inst.setAirChangesperHour(infiltration_ach)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set infiltration to #{ventilation_ach} ACH.")
      end
    end
  end
end

#thermal_zone_demand_control_ventilation_required?(thermal_zone, climate_zone) ⇒ Bool

TODO:

Add exception logic for 90.1-2013 for cells, sickrooms, labs, barbers, salons, and bowling alleys

Determine if demand control ventilation (DCV) is required for this zone based on area and occupant density. Does not account for System requirements like ERV, economizer, etc. Those are accounted for in the AirLoopHVAC method of the same name.

Returns:

  • (Bool)

    Returns true if required, false if not.



1066
1067
1068
# File 'lib/openstudio-standards/standards/necb/necb_2011/hvac_systems.rb', line 1066

def thermal_zone_demand_control_ventilation_required?(thermal_zone, climate_zone)
  return false
end

#water_heater_mixed_apply_efficiency(water_heater_mixed) ⇒ Bool

Applies the standard efficiency ratings and typical losses and paraisitic loads to this object. Efficiency and skin loss coefficient (UA) Per PNNL www.energycodes.gov/sites/default/files/documents/PrototypeModelEnhancements_2014_0.pdf Appendix A: Service Water Heating

Returns:

  • (Bool)

    true if successful, false if not



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
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
# File 'lib/openstudio-standards/standards/necb/necb_2011/service_water_heating.rb', line 148

def water_heater_mixed_apply_efficiency(water_heater_mixed)
  # Get the capacity of the water heater
  # TODO add capability to pull autosized water heater capacity
  # if the Sizing:WaterHeater object is ever implemented in OpenStudio.
  capacity_w = water_heater_mixed.heaterMaximumCapacity
  if capacity_w.empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, cannot find capacity, standard will not be applied.")
    return false
  else
    capacity_w = capacity_w.get
  end
  capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
  capacity_kbtu_per_hr = OpenStudio.convert(capacity_w, 'W', 'kBtu/hr').get

  # Get the volume of the water heater
  # TODO add capability to pull autosized water heater volume
  # if the Sizing:WaterHeater object is ever implemented in OpenStudio.
  volume_m3 = water_heater_mixed.tankVolume
  if volume_m3.empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, cannot find volume, standard will not be applied.")
    return false
  else
    volume_m3 = volume_m3.get
  end
  volume_gal = OpenStudio.convert(volume_m3, 'm^3', 'gal').get

  # Get the heater fuel type
  fuel_type = water_heater_mixed.heaterFuelType
  unless fuel_type == 'NaturalGas' || fuel_type == 'Electricity'
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, fuel type of #{fuel_type} is not yet supported, standard will not be applied.")
  end

  # Calculate the water heater efficiency and
  # skin loss coefficient (UA)
  # Calculate the energy factor (EF)
  # From PNNL http://www.energycodes.gov/sites/default/files/documents/PrototypeModelEnhancements_2014_0.pdf
  # Appendix A: Service Water Heating
  water_heater_eff = nil
  ua_btu_per_hr_per_f = nil
  sl_btu_per_hr = nil
  case fuel_type
    when 'Electricity'
      volume_l_per_s = volume_m3 * 1000
      if capacity_btu_per_hr <= OpenStudio.convert(12, 'kW', 'Btu/hr').get
        # Fixed water heater efficiency per PNNL
        water_heater_eff = 1
        # Calculate the max allowable standby loss (SL)
        sl_w = if volume_l_per_s < 270
                 40 + 0.2 * volume_l_per_s # assume bottom inlet
               else
                 0.472 * volume_l_per_s - 33.5
               end # assume bottom inlet
        sl_btu_per_hr = OpenStudio.convert(sl_w, 'W', 'Btu/hr').get
      else
        # Fixed water heater efficiency per PNNL
        water_heater_eff = 1
        # Calculate the max allowable standby loss (SL)   # use this - NECB does not give SL calculation for cap > 12 kW
        sl_btu_per_hr = 20 + (35 * Math.sqrt(volume_gal))
      end
      # Calculate the skin loss coefficient (UA)
      ua_btu_per_hr_per_f = sl_btu_per_hr / 70
    when 'NaturalGas'
      if capacity_btu_per_hr <= 75_000
        # Fixed water heater thermal efficiency per PNNL
        water_heater_eff = 0.82
        # Calculate the minimum Energy Factor (EF)
        base_ef = 0.67
        vol_drt = 0.0019
        ef = base_ef - (vol_drt * volume_gal)
        # Calculate the Recovery Efficiency (RE)
        # based on a fixed capacity of 75,000 Btu/hr
        # and a fixed volume of 40 gallons by solving
        # this system of equations:
        # ua = (1/.95-1/re)/(67.5*(24/41094-1/(re*cap)))
        # 0.82 = (ua*67.5+cap*re)/cap
        cap = 75_000.0
        re = (Math.sqrt(6724 * ef**2 * cap**2 + 40_409_100 * ef**2 * cap - 28_080_900 * ef * cap + 29_318_000_625 * ef**2 - 58_636_001_250 * ef + 29_318_000_625) + 82 * ef * cap + 171_225 * ef - 171_225) / (200 * ef * cap)
        # Calculate the skin loss coefficient (UA)
        # based on the actual capacity.
        ua_btu_per_hr_per_f = (water_heater_eff - re) * capacity_btu_per_hr / 67.5
      else
        # Thermal efficiency requirement from 90.1
        et = 0.8
        # Calculate the max allowable standby loss (SL)
        cap_adj = 800
        vol_drt = 110
        sl_btu_per_hr = (capacity_btu_per_hr / cap_adj + vol_drt * Math.sqrt(volume_gal))
        # Calculate the skin loss coefficient (UA)
        ua_btu_per_hr_per_f = (sl_btu_per_hr * et) / 70
        # Calculate water heater efficiency
        water_heater_eff = (ua_btu_per_hr_per_f * 70 + capacity_btu_per_hr * et) / capacity_btu_per_hr
      end
  end

  # Convert to SI
  ua_btu_per_hr_per_c = OpenStudio.convert(ua_btu_per_hr_per_f, 'Btu/hr*R', 'W/K').get

  # Set the water heater properties
  # Efficiency
  water_heater_mixed.setHeaterThermalEfficiency(water_heater_eff)
  # Skin loss
  water_heater_mixed.setOffCycleLossCoefficienttoAmbientTemperature(ua_btu_per_hr_per_c)
  water_heater_mixed.setOnCycleLossCoefficienttoAmbientTemperature(ua_btu_per_hr_per_c)
  # TODO: Parasitic loss (pilot light)
  # PNNL document says pilot lights were removed, but IDFs
  # still have the on/off cycle parasitic fuel consumptions filled in
  water_heater_mixed.setOnCycleParasiticFuelType(fuel_type)
  # self.setOffCycleParasiticFuelConsumptionRate(??)
  water_heater_mixed.setOnCycleParasiticHeatFractiontoTank(0)
  water_heater_mixed.setOffCycleParasiticFuelType(fuel_type)
  # self.setOffCycleParasiticFuelConsumptionRate(??)
  water_heater_mixed.setOffCycleParasiticHeatFractiontoTank(0.8)

  # set part-load performance curve
  if fuel_type == 'NaturalGas'
    plf_vs_plr_curve = model_add_curve(water_heater_mixed.model, 'SWH-EFFFPLR-NECB2011')
    water_heater_mixed.setPartLoadFactorCurve(plf_vs_plr_curve)
  end

  # Append the name with standards information
  water_heater_mixed.setName("#{water_heater_mixed.name} #{water_heater_eff.round(3)} Therm Eff")
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.WaterHeaterMixed', "For #{template}: #{water_heater_mixed.name}; thermal efficiency = #{water_heater_eff.round(3)}, skin-loss UA = #{ua_btu_per_hr_per_f.round}Btu/hr")

  return true
end