openstudio-standards 0.2.16 → 0.2.17.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/standards/manage_OpenStudio_Standards.rb +31 -4
- data/lib/openstudio-standards/btap/geometry.rb +1 -1
- data/lib/openstudio-standards/hvac_sizing/Siz.HeatingCoolingFuels.rb +354 -2
- data/lib/openstudio-standards/hvac_sizing/Siz.ThermalZone.rb +79 -0
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.College.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Laboratory.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/do_not_edit_metaclasses.rb +3313 -0
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Fan.rb +12 -0
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb +3 -4
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.SizingSystem.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +167 -93
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.utilities.rb +2 -4
- data/lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb +1 -0
- data/lib/openstudio-standards/refs/references.rb +3 -0
- data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +279 -6
- data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctParallelPIUReheat.rb +50 -2
- data/lib/openstudio-standards/standards/Standards.ChillerElectricEIR.rb +4 -0
- data/lib/openstudio-standards/standards/Standards.CoilCoolingWaterToAirHeatPumpEquationFit.rb +0 -1
- data/lib/openstudio-standards/standards/Standards.Construction.rb +185 -3
- data/lib/openstudio-standards/standards/Standards.Fan.rb +14 -6
- data/lib/openstudio-standards/standards/Standards.HeatExchangerSensLat.rb +1 -0
- data/lib/openstudio-standards/standards/Standards.Model.rb +1751 -383
- data/lib/openstudio-standards/standards/Standards.PlanarSurface.rb +130 -9
- data/lib/openstudio-standards/standards/Standards.PlantLoop.rb +50 -3
- data/lib/openstudio-standards/standards/Standards.ScheduleCompact.rb +44 -0
- data/lib/openstudio-standards/standards/Standards.ScheduleConstant.rb +27 -0
- data/lib/openstudio-standards/standards/Standards.ScheduleRuleset.rb +543 -0
- data/lib/openstudio-standards/standards/Standards.Space.rb +665 -15
- data/lib/openstudio-standards/standards/Standards.SpaceType.rb +141 -4
- data/lib/openstudio-standards/standards/Standards.SubSurface.rb +2 -1
- data/lib/openstudio-standards/standards/Standards.Surface.rb +117 -0
- data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +197 -49
- data/lib/openstudio-standards/standards/Standards.ZoneHVACComponent.rb +41 -0
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.Model.rb +6 -8
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/comstock_ashrae_90_1_2004/data/ashrae_90_1.schedules.json +45 -45
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/comstock_ashrae_90_1_2004/data/comstock_ashrae_90_1_2004.spc_typ.json +7 -7
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/comstock_ashrae_90_1_2007/data/ashrae_90_1.schedules.json +45 -45
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/comstock_ashrae_90_1_2007/data/comstock_ashrae_90_1_2007.spc_typ.json +7 -7
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/comstock_ashrae_90_1_2010/data/ashrae_90_1.schedules.json +45 -45
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/comstock_ashrae_90_1_2010/data/comstock_ashrae_90_1_2010.spc_typ.json +9 -9
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/comstock_ashrae_90_1_2013/data/ashrae_90_1.schedules.json +45 -45
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/comstock_ashrae_90_1_2013/data/comstock_ashrae_90_1_2013.spc_typ.json +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/comstock_ashrae_90_1_2016/data/ashrae_90_1.schedules.json +45 -45
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/comstock_ashrae_90_1_2016/data/comstock_ashrae_90_1_2016.spc_typ.json +5 -5
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +5 -5
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/comstock_ashrae_90_1_2019/data/ashrae_90_1.schedules.json +45 -45
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/comstock_ashrae_90_1_2019/data/comstock_ashrae_90_1_2019.spc_typ.json +5 -5
- data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.constructions.json +2 -2
- data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.fans.json +12 -0
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/comstock_doe_ref_1980_2004/data/ashrae_90_1.schedules.json +45 -45
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/comstock_doe_ref_1980_2004/data/comstock_doe_ref_1980_2004.spc_typ.json +10 -10
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/comstock_doe_ref_pre_1980/data/ashrae_90_1.schedules.json +45 -45
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/comstock_doe_ref_pre_1980/data/comstock_doe_ref_pre_1980.spc_typ.json +10 -10
- data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.AirLoopHVAC.rb +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb +792 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctParallelPIUReheat.rb +10 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctVAVReheat.rb +31 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.BoilerHotWater.rb +91 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ChillerElectricEIR.rb +84 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXSingleSpeed.rb +145 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXTwoSpeed.rb +106 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilDX.rb +71 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingDXSingleSpeed.rb +194 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingGas.rb +120 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoolingTower.rb +110 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoolingTowerVariableSpeed.rb +5 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Fan.rb +73 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanConstantVolume.rb +5 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanOnOff.rb +5 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanVariableVolume.rb +24 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanZoneExhaust.rb +5 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.HeatExchangerSensLat.rb +55 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb +3045 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlanarSurface.rb +187 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb +450 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Space.rb +106 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb +666 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Surface.rb +54 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ThermalZone.rb +168 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ZoneHVACComponent.rb +132 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb +239 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.Model.rb +176 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.rb +25 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.boilers.json +52 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.chillers.json +112 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.climate_zone_sets.json +210 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.construction_properties.json +10384 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.construction_sets.json +133 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.furnaces.json +43 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.heat_pumps.json +119 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.heat_pumps_heating.json +130 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.heat_rejection.json +13 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.lpd_space_type.json +568 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.motors.json +264 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_baseline_hvac.json +439 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_constructions.json +685 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_economizers.json +213 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_ext_ltg.json +32 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_heat_type.json +136 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_hvac_bldg_type.json +32 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_interior_lighting.json +1837 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_swh_bldg_type.json +184 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_wwr_bldg_type.json +84 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.unitary_acs.json +148 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.water_heaters.json +157 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/data/ashrae_90_1_prm.climate_zone_sets.json +210 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/data/ashrae_90_1_prm.curves.json +18329 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/data/ashrae_90_1_prm.fans.json +340 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/data/ashrae_90_1_prm.materials.json +49924 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/baseline_building_rotation_exception.md +44 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/check_pump_power_and_control.md +71 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/dcv.md +68 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/dcv_implementation.png +0 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/elevators.md +14 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/exhaust_air_energy_recovery.md +36 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/f_c_factors.md +19 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/fan_power_credits.md +15 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/preheat_coil.md +59 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/pump_power_control.md +46 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/return_air_type.md +31 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/set_baseline_wwr.md +191 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/set_hw_and_chw_supply_water_temp_reset_control.md +24 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/set_num_boilers_chillers_towers.md +49 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/set_plug_load_measures.md +80 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/set_space_lpd.md +73 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/unenclosed_and_unconditioned_spaces.md +11 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/unmet_load_hours.md +20 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/vav_parallel_piu_terminals_fan_control.md +23 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/vav_terminals_min_flow_setpoint.md +21 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_airloop_hvac.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_airloop_hvac_doas.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_building.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_design_specification_outdoor_air.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_electric_equipment.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_exterior_lights.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_gas_equipment.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_lights.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_space.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_spacetype.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_thermal_zone.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_wateruse_connections.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_wateruse_equipment.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_wateruse_equipment_definition.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_zone_hvac.csv +1 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_zone_infiltration.csv +1 -0
- data/lib/openstudio-standards/standards/cbes/data/cbes.fans.json +12 -0
- data/lib/openstudio-standards/standards/deer/data/deer.fans.json +12 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/heat_pumps.json +1 -1
- data/lib/openstudio-standards/standards/necb/ECMS/data/heat_pumps_heating.json +1 -1
- data/lib/openstudio-standards/standards/necb/ECMS/data/unitary_acs.json +24 -11
- data/lib/openstudio-standards/standards/necb/ECMS/erv.rb +13 -15
- data/lib/openstudio-standards/standards/necb/NECB2011/data/province_map.json +17 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_4.rb +2 -2
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb +6 -5
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +3 -2
- data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +2 -3
- data/lib/openstudio-standards/standards/necb/NECB2020/data/chillers.json +2 -2
- data/lib/openstudio-standards/standards/necb/NECB2020/data/space_types.json +33 -924
- data/lib/openstudio-standards/standards/necb/NECB2020/data/unitary_acs.json +15 -15
- data/lib/openstudio-standards/standards/necb/common/btap_data.rb +135 -29
- data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +16 -4
- data/lib/openstudio-standards/standards/necb/common/neb_end_use_prices.csv +40 -42
- data/lib/openstudio-standards/standards/necb/common/necb_reference_runs.csv +1 -1
- data/lib/openstudio-standards/standards/necb/common/space_type_upgrade_map.json +89 -89
- data/lib/openstudio-standards/utilities/array.rb +11 -0
- data/lib/openstudio-standards/utilities/logging.rb +48 -0
- data/lib/openstudio-standards/utilities/object_info.rb +20 -0
- data/lib/openstudio-standards/utilities/schedule_translator.rb +348 -0
- data/lib/openstudio-standards/utilities/sqlfile.rb +68 -0
- data/lib/openstudio-standards/version.rb +2 -2
- data/lib/openstudio-standards/weather/Weather.Model.rb +42 -55
- data/lib/openstudio-standards/weather/Weather.stat_file.rb +1 -1
- data/lib/openstudio-standards.rb +35 -1
- metadata +111 -6
- data/data/standards/OpenStudio_Standards-ashrae_90_1.xlsx +0 -0
- data/data/standards/OpenStudio_Standards-ashrae_90_1_28Jan2022.xlsx +0 -0
- data/data/standards/OpenStudio_Standards-ashrae_90_1_28_Jan2022_2.xlsx +0 -0
- data/data/standards/openstudio_standards_duplicates_log.csv +0 -143
@@ -14,229 +14,514 @@ class Standard
|
|
14
14
|
# @!group Model
|
15
15
|
|
16
16
|
# Creates a Performance Rating Method (aka Appendix G aka LEED) baseline building model
|
17
|
-
#
|
17
|
+
# Method used for 90.1-2016 and onward
|
18
18
|
#
|
19
19
|
# @note Per 90.1, the Performance Rating Method "does NOT offer an alternative compliance path for minimum standard compliance."
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
20
|
+
# This means you can't use this method for code compliance to get a permit.
|
21
|
+
# @param user_model [OpenStudio::model::Model] User specified OpenStudio model
|
23
22
|
# @param building_type [String] the building type
|
24
|
-
# @param climate_zone [String]
|
23
|
+
# @param climate_zone [String] the climate zone
|
24
|
+
# @param hvac_building_type [String] the building type for baseline HVAC system determination (90.1-2016 and onward)
|
25
|
+
# @param wwr_building_type [String] the building type for baseline WWR determination (90.1-2016 and onward)
|
26
|
+
# @param swh_building_type [String] the building type for baseline SWH determination (90.1-2016 and onward)
|
25
27
|
# @param custom [String] the custom logic that will be applied during baseline creation. Valid choices are 'Xcel Energy CO EDA' or '90.1-2007 with addenda dn'.
|
26
28
|
# If nothing is specified, no custom logic will be applied; the process will follow the template logic explicitly.
|
27
29
|
# @param sizing_run_dir [String] the directory where the sizing runs will be performed
|
30
|
+
# @param run_all_orients [Boolean] indicate weather a baseline model should be created for all 4 orientations: same as user model, +90 deg, +180 deg, +270 deg
|
28
31
|
# @param debug [Boolean] If true, will report out more detailed debugging output
|
29
32
|
# @return [Bool] returns true if successful, false if not
|
33
|
+
|
34
|
+
# Method used for 90.1-2016 and onward
|
35
|
+
def model_create_prm_stable_baseline_building(model, building_type, climate_zone, hvac_building_type, wwr_building_type, swh_building_type, custom = nil, sizing_run_dir = Dir.pwd, run_all_orients = true, unmet_load_hours_check = true, debug = false)
|
36
|
+
model_create_prm_any_baseline_building(model, building_type, climate_zone, hvac_building_type, wwr_building_type, swh_building_type, true, custom, sizing_run_dir, run_all_orients, unmet_load_hours_check, debug)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates a Performance Rating Method (aka Appendix G aka LEED) baseline building model
|
40
|
+
# Method used for 90.1-2013 and prior
|
41
|
+
# @param user_model [OpenStudio::model::Model] User specified OpenStudio model
|
42
|
+
# @param building_type [String] the building type
|
43
|
+
# @param climate_zone [String] the climate zone
|
44
|
+
# @param custom [String] the custom logic that will be applied during baseline creation. Valid choices are 'Xcel Energy CO EDA' or '90.1-2007 with addenda dn'.
|
45
|
+
# If nothing is specified, no custom logic will be applied; the process will follow the template logic explicitly.
|
46
|
+
# @param sizing_run_dir [String] the directory where the sizing runs will be performed
|
47
|
+
# @param debug [Boolean] If true, will report out more detailed debugging output
|
30
48
|
def model_create_prm_baseline_building(model, building_type, climate_zone, custom = nil, sizing_run_dir = Dir.pwd, debug = false)
|
31
|
-
model
|
49
|
+
model_create_prm_any_baseline_building(model, building_type, climate_zone, 'All others', 'All others', 'All others', false, custom, sizing_run_dir, false, false, debug)
|
50
|
+
end
|
32
51
|
|
33
|
-
|
34
|
-
|
35
|
-
|
52
|
+
# Creates a Performance Rating Method (aka Appendix G aka LEED) baseline building model
|
53
|
+
# based on the inputs currently in the model.
|
54
|
+
#
|
55
|
+
# @note Per 90.1, the Performance Rating Method "does NOT offer an alternative compliance path for minimum standard compliance."
|
56
|
+
# This means you can't use this method for code compliance to get a permit.
|
57
|
+
# @param user_model [OpenStudio::model::Model] User specified OpenStudio model
|
58
|
+
# @param building_type [String] the building type
|
59
|
+
# @param climate_zone [String] the climate zone
|
60
|
+
# @param hvac_building_type [String] the building type for baseline HVAC system determination (90.1-2016 and onward)
|
61
|
+
# @param wwr_building_type [String] the building type for baseline WWR determination (90.1-2016 and onward)
|
62
|
+
# @param swh_building_type [String] the building type for baseline SWH determination (90.1-2016 and onward)
|
63
|
+
# @param model_deep_copy [Boolean] indicate if the baseline model is created based on a deep copy of the user specified model
|
64
|
+
# @param custom [String] the custom logic that will be applied during baseline creation. Valid choices are 'Xcel Energy CO EDA' or '90.1-2007 with addenda dn'.
|
65
|
+
# If nothing is specified, no custom logic will be applied; the process will follow the template logic explicitly.
|
66
|
+
# @param sizing_run_dir [String] the directory where the sizing runs will be performed
|
67
|
+
# @param run_all_orients [Boolean] indicate weather a baseline model should be created for all 4 orientations: same as user model, +90 deg, +180 deg, +270 deg
|
68
|
+
# @param debug [Boolean] If true, will report out more detailed debugging output
|
69
|
+
# @return [Bool] returns true if successful, false if not
|
70
|
+
def model_create_prm_any_baseline_building(user_model, building_type, climate_zone, hvac_building_type = 'All others', wwr_building_type = 'All others', swh_building_type = 'All others', model_deep_copy = false, custom = nil, sizing_run_dir = Dir.pwd, run_all_orients = false, unmet_load_hours_check = true, debug = false)
|
71
|
+
# Check proposed model unmet load hours
|
72
|
+
if unmet_load_hours_check
|
73
|
+
# Run proposed model; need annual simulation to get unmet load hours
|
74
|
+
if model_run_simulation_and_log_errors(user_model, run_dir = "#{sizing_run_dir}/PROP")
|
75
|
+
umlh = model_get_unmet_load_hours(user_model)
|
76
|
+
if umlh > 300
|
77
|
+
OpenStudio.logFree(OpenStudio::Error, 'prm.log', "Proposed model unmet load hours exceed 300. Baseline model(s) won't be created.")
|
78
|
+
raise "Proposed model unmet load hours exceed 300. Baseline model(s) won't be created."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
36
82
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
83
|
+
# User data process
|
84
|
+
# bldg_type_hvac_zone_hash could be an empty hash if all zones in the models are unconditioned
|
85
|
+
bldg_type_hvac_zone_hash = {}
|
86
|
+
handle_user_input_data(user_model, climate_zone, hvac_building_type, wwr_building_type, swh_building_type, bldg_type_hvac_zone_hash)
|
87
|
+
# Define different orientation from original orientation
|
88
|
+
# for each individual baseline models
|
89
|
+
# Need to run proposed model sizing simulation if no sql data is available
|
90
|
+
degs_from_org = run_all_orientations(run_all_orients, user_model) ? [0, 90, 180, 270] : [0]
|
91
|
+
|
92
|
+
# Create baseline model for each orientation
|
93
|
+
degs_from_org.each do |degs|
|
94
|
+
# New baseline model:
|
95
|
+
# Starting point is the original proposed model
|
96
|
+
# Create a deep copy of the user model if requested
|
97
|
+
model = model_deep_copy ? BTAP::FileIO.deep_copy(user_model) : user_model
|
98
|
+
model.getBuilding.setName("#{template}-#{building_type}-#{climate_zone} PRM baseline created: #{Time.new}")
|
99
|
+
|
100
|
+
# Rotate building if requested,
|
101
|
+
# Site shading isn't rotated
|
102
|
+
model_rotate(model, degs) unless degs == 0
|
103
|
+
# Perform a sizing run of the proposed model.
|
104
|
+
#
|
105
|
+
# Among others, one of the goal is to get individual
|
106
|
+
# space load to determine each space's conditioning
|
107
|
+
# type: conditioned, unconditioned, semiheated.
|
108
|
+
if model_create_prm_baseline_building_requires_proposed_model_sizing_run(model)
|
109
|
+
# Set up some special reports to be used for baseline system selection later
|
110
|
+
# Zone return air flows
|
111
|
+
node_list = []
|
112
|
+
var_name = 'System Node Standard Density Volume Flow Rate'
|
113
|
+
frequency = 'hourly'
|
114
|
+
model.getThermalZones.each do |zone|
|
115
|
+
port_list = zone.returnPortList
|
116
|
+
port_list_objects = port_list.modelObjects
|
117
|
+
port_list_objects.each do |node|
|
118
|
+
node_name = node.nameString
|
119
|
+
node_list << node_name
|
120
|
+
output = OpenStudio::Model::OutputVariable.new(var_name, model)
|
121
|
+
output.setKeyValue(node_name)
|
122
|
+
output.setReportingFrequency(frequency)
|
123
|
+
end
|
124
|
+
end
|
41
125
|
|
42
|
-
|
43
|
-
|
126
|
+
# air loop relief air flows
|
127
|
+
var_name = 'System Node Standard Density Volume Flow Rate'
|
128
|
+
frequency = 'hourly'
|
129
|
+
model.getAirLoopHVACs.sort.each do |air_loop_hvac|
|
130
|
+
relief_node = air_loop_hvac.reliefAirNode.get
|
131
|
+
output = OpenStudio::Model::OutputVariable.new(var_name, model)
|
132
|
+
output.setKeyValue(relief_node.nameString)
|
133
|
+
output.setReportingFrequency(frequency)
|
134
|
+
end
|
44
135
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
set_lights = true
|
50
|
-
set_electric_equipment = false
|
51
|
-
set_gas_equipment = false
|
52
|
-
set_ventilation = false
|
53
|
-
set_infiltration = false
|
54
|
-
space_type_apply_internal_loads(space_type, set_people, set_lights, set_electric_equipment, set_gas_equipment, set_ventilation, set_infiltration)
|
55
|
-
end
|
136
|
+
# Run the sizing run
|
137
|
+
if model_run_sizing_run(model, "#{sizing_run_dir}/SR_PROP#{degs}") == false
|
138
|
+
return false
|
139
|
+
end
|
56
140
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
141
|
+
# Set baseline model space conditioning category based on proposed model
|
142
|
+
model.getSpaces.each do |space|
|
143
|
+
# Get conditioning category at the space level
|
144
|
+
space_conditioning_category = space_conditioning_category(space)
|
145
|
+
|
146
|
+
# Set space conditioning category
|
147
|
+
space.additionalProperties.setFeature('space_conditioning_category', space_conditioning_category)
|
148
|
+
end
|
149
|
+
|
150
|
+
# The following should be done after a sizing run of the proposed model
|
151
|
+
# because the proposed model zone design air flow is needed
|
152
|
+
model_identify_return_air_type(model)
|
153
|
+
end
|
154
|
+
# Remove external shading devices
|
155
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Removing External Shading Devices ***')
|
156
|
+
model_remove_external_shading_devices(model)
|
157
|
+
|
158
|
+
# Reduce the WWR and SRR, if necessary
|
159
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Adjusting Window and Skylight Ratios ***')
|
160
|
+
success, wwr_info = model_apply_prm_baseline_window_to_wall_ratio(model, climate_zone, wwr_building_type: wwr_building_type)
|
161
|
+
model_apply_prm_baseline_skylight_to_roof_ratio(model)
|
162
|
+
|
163
|
+
# Assign building stories to spaces in the building where stories are not yet assigned.
|
164
|
+
model_assign_spaces_to_stories(model)
|
165
|
+
|
166
|
+
# Modify the internal loads in each space type, keeping user-defined schedules.
|
167
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Changing Lighting Loads ***')
|
168
|
+
model.getSpaceTypes.sort.each do |space_type|
|
169
|
+
set_people = false
|
170
|
+
set_lights = true
|
171
|
+
set_electric_equipment = false
|
172
|
+
set_gas_equipment = false
|
173
|
+
set_ventilation = false
|
174
|
+
set_infiltration = false
|
175
|
+
# For PRM, it only applies lights for now.
|
176
|
+
space_type_apply_internal_loads(space_type, set_people, set_lights, set_electric_equipment, set_gas_equipment, set_ventilation, set_infiltration)
|
177
|
+
end
|
178
|
+
# Modify the lighting schedule to handle lighting occupancy sensors
|
179
|
+
# Modify the upper limit value of fractional schedule to avoid the fatal error caused by schedule value higher than 1
|
180
|
+
space_type_light_sch_change(model)
|
181
|
+
|
182
|
+
model_apply_baseline_exterior_lighting(model)
|
183
|
+
|
184
|
+
# Modify the elevator motor peak power
|
185
|
+
model_add_prm_elevators(model)
|
186
|
+
|
187
|
+
# Calculate infiltration as per 90.1 PRM rules
|
188
|
+
model_baseline_apply_infiltration_standard(model, climate_zone)
|
189
|
+
|
190
|
+
# If any of the lights are missing schedules, assign an always-off schedule to those lights.
|
191
|
+
# This is assumed to be the user's intent in the proposed model.
|
192
|
+
model.getLightss.sort.each do |lights|
|
193
|
+
if lights.schedule.empty?
|
194
|
+
lights.setSchedule(model.alwaysOffDiscreteSchedule)
|
195
|
+
end
|
62
196
|
end
|
63
|
-
end
|
64
197
|
|
65
|
-
|
198
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Adding Daylighting Controls ***')
|
66
199
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
200
|
+
# Run a sizing run to calculate VLT for layer-by-layer windows.
|
201
|
+
if model_create_prm_baseline_building_requires_vlt_sizing_run(model)
|
202
|
+
if model_run_sizing_run(model, "#{sizing_run_dir}/SRVLT") == false
|
203
|
+
return false
|
204
|
+
end
|
71
205
|
end
|
72
|
-
end
|
73
206
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
207
|
+
# Add or remove daylighting controls to each space
|
208
|
+
# Add daylighting controls for 90.1-2013 and prior
|
209
|
+
# Remove daylighting control for 90.1-PRM-2019 and onward
|
210
|
+
model.getSpaces.sort.each do |space|
|
211
|
+
space_set_baseline_daylighting_controls(space, false, false)
|
212
|
+
end
|
78
213
|
|
79
|
-
|
214
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Applying Baseline Constructions ***')
|
80
215
|
|
81
|
-
|
82
|
-
|
216
|
+
# Modify some of the construction types as necessary
|
217
|
+
model_apply_prm_construction_types(model)
|
83
218
|
|
84
|
-
|
85
|
-
|
219
|
+
# Get the groups of zones that define the baseline HVAC systems for later use.
|
220
|
+
# This must be done before removing the HVAC systems because it requires knowledge of proposed HVAC fuels.
|
221
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Grouping Zones by Fuel Type and Occupancy Type ***')
|
222
|
+
zone_fan_scheds = nil
|
86
223
|
|
87
|
-
|
88
|
-
# This must be done before removing the HVAC systems because it requires knowledge of proposed HVAC fuels.
|
89
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Grouping Zones by Fuel Type and Occupancy Type ***')
|
90
|
-
sys_groups = model_prm_baseline_system_groups(model, custom)
|
224
|
+
sys_groups = model_prm_baseline_system_groups(model, custom, bldg_type_hvac_zone_hash)
|
91
225
|
|
92
|
-
|
93
|
-
|
226
|
+
# Also get hash of zoneName:boolean to record which zones have district heating, if any
|
227
|
+
district_heat_zones = model_get_district_heating_zones(model)
|
94
228
|
|
95
|
-
|
96
|
-
|
229
|
+
# Store occupancy and fan operation schedules for each zone before deleting HVAC objects
|
230
|
+
zone_fan_scheds = get_fan_schedule_for_each_zone(model)
|
97
231
|
|
98
|
-
|
99
|
-
|
100
|
-
model_apply_baseline_swh_loops(model, building_type)
|
232
|
+
# Set the construction properties of all the surfaces in the model
|
233
|
+
model_apply_constructions(model, climate_zone, wwr_building_type, wwr_info)
|
101
234
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
235
|
+
# Update ground temperature profile (for F/C-factor construction objects)
|
236
|
+
model_update_ground_temperature_profile(model, climate_zone)
|
237
|
+
|
238
|
+
# Identify non-mechanically cooled systems if necessary
|
239
|
+
model_identify_non_mechanically_cooled_systems(model)
|
240
|
+
|
241
|
+
# Get supply, return, relief fan power for each air loop
|
242
|
+
if model_get_fan_power_breakdown
|
243
|
+
model.getAirLoopHVACs.sort.each do |air_loop|
|
244
|
+
supply_fan_w = air_loop_hvac_get_supply_fan_power(air_loop)
|
245
|
+
return_fan_w = air_loop_hvac_get_return_fan_power(air_loop)
|
246
|
+
relief_fan_w = air_loop_hvac_get_relief_fan_power(air_loop)
|
247
|
+
|
248
|
+
# Save fan power at the zone to determining
|
249
|
+
# baseline fan power
|
250
|
+
air_loop.thermalZones.sort.each do |zone|
|
251
|
+
zone.additionalProperties.setFeature('supply_fan_w', supply_fan_w.to_f)
|
252
|
+
zone.additionalProperties.setFeature('return_fan_w', return_fan_w.to_f)
|
253
|
+
zone.additionalProperties.setFeature('relief_fan_w', relief_fan_w.to_f)
|
254
|
+
end
|
118
255
|
end
|
119
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "--- #{zone_names.join(', ')}")
|
120
256
|
end
|
121
257
|
|
122
|
-
#
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
sys_group['zones'])
|
129
|
-
end
|
258
|
+
# Compute and marke DCV related information before deleting proposed model HVAC systems
|
259
|
+
model_mark_zone_dcv_existence(model)
|
260
|
+
model_add_dcv_user_exception_properties(model)
|
261
|
+
model_add_dcv_requirement_properties(model)
|
262
|
+
model_add_apxg_dcv_properties(model)
|
263
|
+
model_raise_user_model_dcv_errors(model)
|
130
264
|
|
131
|
-
|
132
|
-
|
133
|
-
thermal_zone_apply_prm_baseline_supply_temperatures(zone)
|
134
|
-
end
|
265
|
+
# Remove all HVAC from model, excluding service water heating
|
266
|
+
model_remove_prm_hvac(model)
|
135
267
|
|
136
|
-
|
137
|
-
|
138
|
-
air_loop_hvac_apply_prm_sizing_temperatures(air_loop)
|
139
|
-
end
|
268
|
+
# Remove all EMS objects from the model
|
269
|
+
model_remove_prm_ems_objects(model)
|
140
270
|
|
141
|
-
|
271
|
+
# Modify the service water heating loops per the baseline rules
|
272
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Cleaning up Service Water Heating Loops ***')
|
273
|
+
model_apply_baseline_swh_loops(model, building_type)
|
142
274
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
275
|
+
# Determine the baseline HVAC system type for each of the groups of zones and add that system type.
|
276
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Adding Baseline HVAC Systems ***')
|
277
|
+
air_loop_name_array = []
|
278
|
+
sys_groups.each do |sys_group|
|
279
|
+
# Determine the primary baseline system type
|
280
|
+
system_type = model_prm_baseline_system_type(model, climate_zone, sys_group, custom, hvac_building_type, district_heat_zones)
|
147
281
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
282
|
+
sys_group['zones'].sort.each_slice(5) do |zone_list|
|
283
|
+
zone_names = []
|
284
|
+
zone_list.each do |zone|
|
285
|
+
zone_names << zone.name.get.to_s
|
286
|
+
end
|
287
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "--- #{zone_names.join(', ')}")
|
288
|
+
end
|
152
289
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
290
|
+
# Add system type reference to zone
|
291
|
+
sys_group['zones'].sort.each do |zone|
|
292
|
+
zone.additionalProperties.setFeature('baseline_system_type', system_type[0])
|
293
|
+
end
|
157
294
|
|
158
|
-
|
159
|
-
|
295
|
+
# Add the system type for these zones
|
296
|
+
model_add_prm_baseline_system(model,
|
297
|
+
system_type[0],
|
298
|
+
system_type[1],
|
299
|
+
system_type[2],
|
300
|
+
system_type[3],
|
301
|
+
sys_group['zones'],
|
302
|
+
zone_fan_scheds)
|
303
|
+
|
304
|
+
model.getAirLoopHVACs.each do |air_loop|
|
305
|
+
air_loop_name = air_loop.name.get
|
306
|
+
unless air_loop_name_array.include?(air_loop_name)
|
307
|
+
air_loop.additionalProperties.setFeature('zone_group_type', sys_group['zone_group_type'] || 'None')
|
308
|
+
air_loop.additionalProperties.setFeature('sys_group_occ', sys_group['occ'] || 'None')
|
309
|
+
air_loop_name_array << air_loop_name
|
310
|
+
end
|
160
311
|
|
161
|
-
|
162
|
-
|
312
|
+
# Determine return air type
|
313
|
+
plenum, return_air_type = model_determine_baseline_return_air_type(model, system_type[0], air_loop.thermalZones)
|
314
|
+
air_loop.thermalZones.sort.each do |zone|
|
315
|
+
# Set up return air plenum
|
316
|
+
zone.setReturnPlenum(model.getThermalZoneByName(plenum).get) if return_air_type == 'return_plenum'
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
163
320
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
321
|
+
# Add system type reference to all air loops
|
322
|
+
model.getAirLoopHVACs.sort.each do |air_loop|
|
323
|
+
if air_loop.thermalZones[0].additionalProperties.hasFeature('baseline_system_type')
|
324
|
+
sys_type = air_loop.thermalZones[0].additionalProperties.getFeatureAsString('baseline_system_type').get
|
325
|
+
air_loop.additionalProperties.setFeature('baseline_system_type', sys_type)
|
326
|
+
else
|
327
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Thermal zone #{air_loop.thermalZones[0].name} is not associated to a particular system type.")
|
328
|
+
end
|
329
|
+
end
|
168
330
|
|
169
|
-
|
170
|
-
|
171
|
-
|
331
|
+
# Set the zone sizing SAT for each zone in the model
|
332
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Applying Baseline HVAC System Sizing Settings ***')
|
333
|
+
model.getThermalZones.each do |zone|
|
334
|
+
thermal_zone_apply_prm_baseline_supply_temperatures(zone)
|
335
|
+
end
|
172
336
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
337
|
+
# Set the system sizing properties based on the zone sizing information
|
338
|
+
model.getAirLoopHVACs.each do |air_loop|
|
339
|
+
air_loop_hvac_apply_prm_sizing_temperatures(air_loop)
|
340
|
+
end
|
177
341
|
|
178
|
-
|
179
|
-
|
180
|
-
zone_hvac_component_apply_prm_baseline_fan_power(zone_hvac)
|
181
|
-
end
|
342
|
+
# Set internal load sizing run schedules
|
343
|
+
model_apply_prm_baseline_sizing_schedule(model)
|
182
344
|
|
183
|
-
|
184
|
-
|
185
|
-
# Skip the SWH loops
|
186
|
-
next if plant_loop_swh_loop?(plant_loop)
|
345
|
+
# Set the heating and cooling sizing parameters
|
346
|
+
model_apply_prm_sizing_parameters(model)
|
187
347
|
|
188
|
-
|
189
|
-
plant_loop_apply_prm_number_of_chillers(plant_loop)
|
190
|
-
end
|
348
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Applying Baseline HVAC System Controls ***')
|
191
349
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
next if plant_loop_swh_loop?(plant_loop)
|
350
|
+
# SAT reset, economizers
|
351
|
+
model.getAirLoopHVACs.sort.each do |air_loop|
|
352
|
+
air_loop_hvac_apply_prm_baseline_controls(air_loop, climate_zone)
|
353
|
+
end
|
197
354
|
|
198
|
-
|
199
|
-
|
355
|
+
# Apply the baseline system water loop temperature reset control
|
356
|
+
model.getPlantLoops.sort.each do |plant_loop|
|
357
|
+
# Skip the SWH loops
|
358
|
+
next if plant_loop_swh_loop?(plant_loop)
|
200
359
|
|
201
|
-
|
202
|
-
|
203
|
-
return false
|
204
|
-
end
|
360
|
+
plant_loop_apply_prm_baseline_temperatures(plant_loop)
|
361
|
+
end
|
205
362
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
next if plant_loop_swh_loop?(plant_loop)
|
363
|
+
# Run sizing run with the HVAC equipment
|
364
|
+
if model_run_sizing_run(model, "#{sizing_run_dir}/SR1") == false
|
365
|
+
return false
|
366
|
+
end
|
211
367
|
|
212
|
-
|
213
|
-
|
214
|
-
|
368
|
+
# Apply the minimum damper positions, assuming no DDC control of VAV terminals
|
369
|
+
model.getAirLoopHVACs.sort.each do |air_loop|
|
370
|
+
air_loop_hvac_apply_minimum_vav_damper_positions(air_loop, false)
|
371
|
+
end
|
372
|
+
|
373
|
+
# If there are any multi-zone systems, reset damper positions to achieve a 60% ventilation effectiveness minimum for the system
|
374
|
+
# following the ventilation rate procedure from 62.1
|
375
|
+
model_apply_multizone_vav_outdoor_air_sizing(model)
|
376
|
+
|
377
|
+
# Set the baseline fan power for all air loops
|
378
|
+
model.getAirLoopHVACs.sort.each do |air_loop|
|
379
|
+
air_loop_hvac_apply_prm_baseline_fan_power(air_loop)
|
380
|
+
end
|
381
|
+
|
382
|
+
# Set the baseline fan power for all zone HVAC
|
383
|
+
model.getZoneHVACComponents.sort.each do |zone_hvac|
|
384
|
+
zone_hvac_component_apply_prm_baseline_fan_power(zone_hvac)
|
385
|
+
end
|
386
|
+
|
387
|
+
# Set the baseline number of boilers and chillers
|
388
|
+
model.getPlantLoops.sort.each do |plant_loop|
|
389
|
+
# Skip the SWH loops
|
390
|
+
next if plant_loop_swh_loop?(plant_loop)
|
391
|
+
|
392
|
+
plant_loop_apply_prm_number_of_boilers(plant_loop)
|
393
|
+
plant_loop_apply_prm_number_of_chillers(plant_loop, sizing_run_dir)
|
394
|
+
end
|
395
|
+
|
396
|
+
# Set the baseline number of cooling towers
|
397
|
+
# Must be done after all chillers are added
|
398
|
+
model.getPlantLoops.sort.each do |plant_loop|
|
399
|
+
# Skip the SWH loops
|
400
|
+
next if plant_loop_swh_loop?(plant_loop)
|
401
|
+
|
402
|
+
plant_loop_apply_prm_number_of_cooling_towers(plant_loop)
|
403
|
+
end
|
404
|
+
|
405
|
+
# Run sizing run with the new chillers, boilers, and cooling towers to determine capacities
|
406
|
+
if model_run_sizing_run(model, "#{sizing_run_dir}/SR2") == false
|
407
|
+
return false
|
408
|
+
end
|
409
|
+
|
410
|
+
# Set the pumping control strategy and power
|
411
|
+
# Must be done after sizing components
|
412
|
+
model.getPlantLoops.sort.each do |plant_loop|
|
413
|
+
# Skip the SWH loops
|
414
|
+
next if plant_loop_swh_loop?(plant_loop)
|
415
|
+
|
416
|
+
plant_loop_apply_prm_baseline_pump_power(plant_loop)
|
417
|
+
plant_loop_apply_prm_baseline_pumping_type(plant_loop)
|
418
|
+
end
|
419
|
+
|
420
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', '*** Applying Prescriptive HVAC Controls and Equipment Efficiencies ***')
|
215
421
|
|
216
|
-
|
422
|
+
# Apply the HVAC efficiency standard
|
423
|
+
model_apply_hvac_efficiency_standard(model, climate_zone)
|
217
424
|
|
218
|
-
|
219
|
-
|
425
|
+
# Set baseline DCV system
|
426
|
+
model_set_baseline_demand_control_ventilation(model, climate_zone)
|
220
427
|
|
221
|
-
|
222
|
-
|
223
|
-
model_temp_fix_ems_references(model)
|
428
|
+
# Final sizing run and adjustements to values that need refinement
|
429
|
+
model_refine_size_dependent_values(model, sizing_run_dir)
|
224
430
|
|
225
|
-
|
226
|
-
|
431
|
+
# Fix EMS references.
|
432
|
+
# Temporary workaround for OS issue #2598
|
433
|
+
model_temp_fix_ems_references(model)
|
227
434
|
|
228
|
-
|
229
|
-
|
230
|
-
# It won't be taken into account, while it should: only self shading from the building itself should be turned off but to my knowledge there isn't a way to do this in E+
|
435
|
+
# Delete all the unused resource objects
|
436
|
+
model_remove_unused_resource_objects(model)
|
231
437
|
|
232
|
-
|
233
|
-
|
438
|
+
# Add reporting tolerances
|
439
|
+
model_add_reporting_tolerances(model)
|
234
440
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
441
|
+
# @todo: turn off self shading
|
442
|
+
# Set Solar Distribution to MinimalShadowing... problem is when you also have detached shading such as surrounding buildings etc
|
443
|
+
# It won't be taken into account, while it should: only self shading from the building itself should be turned off but to my knowledge there isn't a way to do this in E+
|
444
|
+
|
445
|
+
model_status = degs > 0 ? "final_#{degs}" : 'final'
|
446
|
+
model.save(OpenStudio::Path.new("#{sizing_run_dir}/#{model_status}.osm"), true)
|
447
|
+
|
448
|
+
# Translate to IDF and save for debugging
|
449
|
+
forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
|
450
|
+
idf = forward_translator.translateModel(model)
|
451
|
+
idf_path = OpenStudio::Path.new("#{sizing_run_dir}/#{model_status}.idf")
|
452
|
+
idf.save(idf_path, true)
|
453
|
+
|
454
|
+
# Check unmet load hours
|
455
|
+
if unmet_load_hours_check
|
456
|
+
nb_adjustments = 0
|
457
|
+
loop do
|
458
|
+
model_run_simulation_and_log_errors(model, "#{sizing_run_dir}/final#{degs}") == false
|
459
|
+
# If UMLH are greater than the threshold allowed by Appendix G,
|
460
|
+
# increase zone air flow and load as per the recommendation in
|
461
|
+
# the PRM-RM; Note that the PRM-RM only suggest to increase
|
462
|
+
# air zone air flow, but the zone sizing factor in EnergyPlus
|
463
|
+
# increase both air flow and load.
|
464
|
+
if model_get_unmet_load_hours(model) > 300
|
465
|
+
# Limit the number of zone sizing factor adjustment to 8
|
466
|
+
unless nb_adjustments < 8
|
467
|
+
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "After 8 rounds of zone sizing factor adjustments the unmet load hours for the baseline model (#{degs} degree of rotation) still exceed 300 hours. Please open an issue on GitHub (https://github.com/NREL/openstudio-standards/issues) and share your user model with the developers.")
|
468
|
+
break
|
469
|
+
end
|
470
|
+
model.getThermalZones.each do |thermal_zone|
|
471
|
+
# Cooling adjustments
|
472
|
+
clg_umlh = thermal_zone_get_unmet_load_hours(thermal_zone, 'Cooling')
|
473
|
+
if clg_umlh > 50
|
474
|
+
# Get zone cooling sizing factor
|
475
|
+
if thermal_zone.sizingZone.zoneCoolingSizingFactor.is_initialized
|
476
|
+
sizing_factor = thermal_zone.sizingZone.zoneCoolingSizingFactor.get
|
477
|
+
else
|
478
|
+
sizing_factor = 1.0
|
479
|
+
end
|
480
|
+
|
481
|
+
# Make adjustment to zone cooling sizing factor
|
482
|
+
# Do not adjust factors greater or equal to 2
|
483
|
+
if sizing_factor < 2.0
|
484
|
+
if clg_umlh > 150
|
485
|
+
sizing_factor *= 1.1
|
486
|
+
elsif clg_umlh > 50
|
487
|
+
sizing_factor *= 1.05
|
488
|
+
end
|
489
|
+
thermal_zone.sizingZone.setZoneCoolingSizingFactor(sizing_factor)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# Heating adjustments
|
494
|
+
htg_umlh = thermal_zone_get_unmet_load_hours(thermal_zone, 'Heating')
|
495
|
+
if htg_umlh > 50
|
496
|
+
# Get zone cooling sizing factor
|
497
|
+
if thermal_zone.sizingZone.zoneHeatingSizingFactor.is_initialized
|
498
|
+
sizing_factor = thermal_zone.sizingZone.zoneHeatingSizingFactor.get
|
499
|
+
else
|
500
|
+
sizing_factor = 1.0
|
501
|
+
end
|
502
|
+
|
503
|
+
# Make adjustment to zone heating sizing factor
|
504
|
+
# Do not adjust factors greater or equal to 2
|
505
|
+
if sizing_factor < 2.0
|
506
|
+
if htg_umlh > 150
|
507
|
+
sizing_factor *= 1.1
|
508
|
+
elsif htg_umlh > 50
|
509
|
+
sizing_factor *= 1.05
|
510
|
+
end
|
511
|
+
thermal_zone.sizingZone.setZoneHeatingSizingFactor(sizing_factor)
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
else
|
516
|
+
break
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
if debug
|
523
|
+
generate_baseline_log(sizing_run_dir)
|
524
|
+
end
|
240
525
|
|
241
526
|
return true
|
242
527
|
end
|
@@ -251,6 +536,17 @@ class Standard
|
|
251
536
|
return false # Not required for most templates
|
252
537
|
end
|
253
538
|
|
539
|
+
# Determine if there is a need for a proposed model sizing run.
|
540
|
+
# A typical application of such sizing run is to determine space
|
541
|
+
# conditioning type.
|
542
|
+
#
|
543
|
+
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
544
|
+
#
|
545
|
+
# @return [Boolean] Returns true if a sizing run is required
|
546
|
+
def model_create_prm_baseline_building_requires_proposed_model_sizing_run(model)
|
547
|
+
return false
|
548
|
+
end
|
549
|
+
|
254
550
|
# Determine the residential and nonresidential floor areas based on the space type properties for each space.
|
255
551
|
# For spaces with no space type, assume nonresidential.
|
256
552
|
#
|
@@ -302,13 +598,23 @@ class Standard
|
|
302
598
|
return num_stories
|
303
599
|
end
|
304
600
|
|
601
|
+
# Add design day schedule objects for space loads,
|
602
|
+
# not used for 2013 and earlier
|
603
|
+
# @author Xuechen (Jerry) Lei, PNNL
|
604
|
+
# @param model [OpenStudio::model::Model] OpenStudio model object
|
605
|
+
#
|
606
|
+
def model_apply_prm_baseline_sizing_schedule(model)
|
607
|
+
return true
|
608
|
+
end
|
609
|
+
|
305
610
|
# Categorize zones by occupancy type and fuel type, where the types depend on the standard.
|
306
611
|
#
|
307
612
|
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
308
613
|
# @param custom [String] custom fuel type
|
614
|
+
# @param applicable_zones [list of zone objects]
|
309
615
|
# @return [Array<Hash>] an array of hashes, one for each zone,
|
310
616
|
# with the keys 'zone', 'type' (occ type), 'fuel', and 'area'
|
311
|
-
def model_zones_with_occ_and_fuel_type(model, custom)
|
617
|
+
def model_zones_with_occ_and_fuel_type(model, custom, applicable_zones = nil)
|
312
618
|
zones = []
|
313
619
|
|
314
620
|
model.getThermalZones.sort.each do |zone|
|
@@ -318,6 +624,14 @@ class Standard
|
|
318
624
|
next
|
319
625
|
end
|
320
626
|
|
627
|
+
if !applicable_zones.nil?
|
628
|
+
# This is only used for the stable baseline (2016 and later)
|
629
|
+
if !applicable_zones.include?(zone)
|
630
|
+
# This zone is not part of the current hvac_building_type
|
631
|
+
next
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
321
635
|
# Skip unconditioned zones
|
322
636
|
heated = thermal_zone_heated?(zone)
|
323
637
|
cooled = thermal_zone_cooled?(zone)
|
@@ -341,7 +655,9 @@ class Standard
|
|
341
655
|
zn_hash['bldg_type'] = thermal_zone_building_type(zone)
|
342
656
|
|
343
657
|
# Fuel type
|
344
|
-
|
658
|
+
# for 2013 and prior, baseline fuel = proposed fuel
|
659
|
+
# for 2016 and later, use fuel to identify zones with district energy
|
660
|
+
zn_hash['fuel'] = thermal_zone_get_zone_fuels_for_occ_and_fuel_type(zone)
|
345
661
|
|
346
662
|
zones << zn_hash
|
347
663
|
end
|
@@ -355,7 +671,7 @@ class Standard
|
|
355
671
|
# @param custom [String] custom fuel type
|
356
672
|
# @return [Array<Hash>] an array of hashes of area information,
|
357
673
|
# with keys area_ft2, type, fuel, and zones (an array of zones)
|
358
|
-
def model_prm_baseline_system_groups(model, custom)
|
674
|
+
def model_prm_baseline_system_groups(model, custom, bldg_type_hvac_zone_hash = nil)
|
359
675
|
# Define the minimum area for the
|
360
676
|
# exception that allows a different
|
361
677
|
# system type in part of the building.
|
@@ -611,6 +927,209 @@ class Standard
|
|
611
927
|
return final_groups
|
612
928
|
end
|
613
929
|
|
930
|
+
# Before deleting proposed HVAC components, determine for each zone if it has district heating
|
931
|
+
# @return [Hash] of boolean with zone name as key
|
932
|
+
def model_get_district_heating_zones(model)
|
933
|
+
has_district_hash = {}
|
934
|
+
model.getThermalZones.sort.each do |zone|
|
935
|
+
has_district_hash['building'] = false
|
936
|
+
htg_fuels = zone.heating_fuels
|
937
|
+
if htg_fuels.include?('DistrictHeating')
|
938
|
+
has_district_hash[zone.name] = true
|
939
|
+
has_district_hash['building'] = true
|
940
|
+
else
|
941
|
+
has_district_hash[zone.name] = false
|
942
|
+
end
|
943
|
+
end
|
944
|
+
return has_district_hash
|
945
|
+
end
|
946
|
+
|
947
|
+
# Get list of heat types across a list of zones
|
948
|
+
# @param zones [array of objects] array of zone objects
|
949
|
+
# @return [string] concatenated string showing different fuel types in a group of zones
|
950
|
+
def get_group_heat_types(model, zones)
|
951
|
+
heat_list = ''
|
952
|
+
has_district_heat = false
|
953
|
+
has_fuel_heat = false
|
954
|
+
has_elec_heat = false
|
955
|
+
zones.each do |zone|
|
956
|
+
if zone.heating_fuels.include?('DistrictHeating')
|
957
|
+
has_district_heat = true
|
958
|
+
end
|
959
|
+
other_heat = thermal_zone_fossil_or_electric_type(zone, '')
|
960
|
+
if other_heat == 'fossil'
|
961
|
+
has_fuel_heat = true
|
962
|
+
elsif other_heat == 'electric'
|
963
|
+
has_elec_heat = true
|
964
|
+
end
|
965
|
+
end
|
966
|
+
if has_district_heat
|
967
|
+
heat_list = 'districtheating'
|
968
|
+
end
|
969
|
+
if has_fuel_heat
|
970
|
+
heat_list += '_fuel'
|
971
|
+
end
|
972
|
+
if has_elec_heat
|
973
|
+
heat_list += '_electric'
|
974
|
+
end
|
975
|
+
return heat_list
|
976
|
+
end
|
977
|
+
|
978
|
+
# Store fan operation schedule for each zone before deleting HVAC objects
|
979
|
+
# @author Doug Maddox, PNNL
|
980
|
+
# @param model [object]
|
981
|
+
# @return [hash] of zoneName:fan_schedule_8760
|
982
|
+
def get_fan_schedule_for_each_zone(model)
|
983
|
+
fan_sch_names = {}
|
984
|
+
|
985
|
+
# Start with air loops
|
986
|
+
model.getAirLoopHVACs.sort.each do |air_loop_hvac|
|
987
|
+
fan_schedule_8760 = []
|
988
|
+
# Check for availability managers
|
989
|
+
# Assume only AvailabilityManagerScheduled will control fan schedule
|
990
|
+
# TODO: also check AvailabilityManagerScheduledOn
|
991
|
+
avail_mgrs = air_loop_hvac.availabilityManagers
|
992
|
+
# if avail_mgrs.is_initialized
|
993
|
+
if !avail_mgrs.nil?
|
994
|
+
avail_mgrs.each do |avail_mgr|
|
995
|
+
# avail_mgr = avail_mgr.get
|
996
|
+
# Check each type of AvailabilityManager
|
997
|
+
# If the current one matches, get the fan schedule
|
998
|
+
if avail_mgr.to_AvailabilityManagerScheduled.is_initialized
|
999
|
+
avail_mgr = avail_mgr.to_AvailabilityManagerScheduled.get
|
1000
|
+
fan_schedule = avail_mgr.schedule
|
1001
|
+
# fan_sch_translator = ScheduleTranslator.new(model, fan_schedule)
|
1002
|
+
# fan_sch_ruleset = fan_sch_translator.translate
|
1003
|
+
fan_schedule_8760 = get_8760_values_from_schedule(model, fan_schedule)
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
end
|
1007
|
+
if fan_schedule_8760.empty?
|
1008
|
+
# If there are no availability managers, then use the schedule in the supply fan object
|
1009
|
+
# Note: testing showed that the fan object schedule is not used by OpenStudio
|
1010
|
+
# Instead, get the fan schedule from the air_loop_hvac object
|
1011
|
+
# fan_object = nil
|
1012
|
+
# fan_object = get_fan_object_for_airloop(model, air_loop_hvac)
|
1013
|
+
fan_object = 'nothing'
|
1014
|
+
if !fan_object.nil?
|
1015
|
+
# fan_schedule = fan_object.availabilitySchedule
|
1016
|
+
fan_schedule = air_loop_hvac.availabilitySchedule
|
1017
|
+
else
|
1018
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Failed to retreive fan object for AirLoop #{air_loop_hvac.name}")
|
1019
|
+
end
|
1020
|
+
fan_schedule_8760 = get_8760_values_from_schedule(model, fan_schedule)
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
# Assign this schedule to each zone on this air loop
|
1024
|
+
air_loop_hvac.thermalZones.each do |zone|
|
1025
|
+
fan_sch_names[zone.name.get] = fan_schedule_8760
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
# Handle Zone equipment
|
1030
|
+
model.getThermalZones.sort.each do |zone|
|
1031
|
+
if !fan_sch_names.key?(zone.name.get)
|
1032
|
+
# This zone was not assigned a schedule via air loop
|
1033
|
+
# Check for zone equipment fans
|
1034
|
+
zone.equipment.each do |zone_equipment|
|
1035
|
+
next if zone_equipment.to_FanZoneExhaust.is_initialized
|
1036
|
+
|
1037
|
+
# get fan schedule
|
1038
|
+
fan_object = zone_hvac_get_fan_object(zone_equipment)
|
1039
|
+
if !fan_object.nil?
|
1040
|
+
fan_schedule = fan_object.availabilitySchedule
|
1041
|
+
fan_schedule_8760 = get_8760_values_from_schedule(model, fan_schedule)
|
1042
|
+
fan_sch_names[zone.name.get] = fan_schedule_8760
|
1043
|
+
break
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
return fan_sch_names
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
# Get the supply fan object for an air loop
|
1053
|
+
# @author Doug Maddox, PNNL
|
1054
|
+
# @param model [object]
|
1055
|
+
# @param air_loop [object]
|
1056
|
+
# @return [object] supply fan of zone equipment component
|
1057
|
+
def get_fan_object_for_airloop(model, air_loop)
|
1058
|
+
if !air_loop.supplyFan.empty?
|
1059
|
+
fan_component = air_loop.supplyFan.get
|
1060
|
+
else
|
1061
|
+
# Check if system has unitary wrapper
|
1062
|
+
air_loop.supplyComponents.each do |component|
|
1063
|
+
# Get the object type, getting the internal coil
|
1064
|
+
# type if inside a unitary system.
|
1065
|
+
obj_type = component.iddObjectType.valueName.to_s
|
1066
|
+
fan_component = nil
|
1067
|
+
case obj_type
|
1068
|
+
when 'OS_AirLoopHVAC_UnitaryHeatCool_VAVChangeoverBypass'
|
1069
|
+
component = component.to_AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass.get
|
1070
|
+
fan_component = component.supplyFan.get
|
1071
|
+
when 'OS_AirLoopHVAC_UnitaryHeatPump_AirToAir'
|
1072
|
+
component = component.to_AirLoopHVACUnitaryHeatPumpAirToAir.get
|
1073
|
+
fan_component = component.supplyFan.get
|
1074
|
+
when 'OS_AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed'
|
1075
|
+
component = component.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.get
|
1076
|
+
fan_component = component.supplyFan.get
|
1077
|
+
when 'OS_AirLoopHVAC_UnitarySystem'
|
1078
|
+
component = component.to_AirLoopHVACUnitarySystem.get
|
1079
|
+
fan_component = component.supplyFan.get
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
if !fan_component.nil?
|
1083
|
+
break
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
# Get the fan object for this fan
|
1089
|
+
fan_obj_type = fan_component.iddObjectType.valueName.to_s
|
1090
|
+
case fan_obj_type
|
1091
|
+
when 'OS_Fan_OnOff'
|
1092
|
+
fan_obj = fan_component.to_FanOnOff.get
|
1093
|
+
when 'OS_Fan_ConstantVolume'
|
1094
|
+
fan_obj = fan_component.to_FanConstantVolume.get
|
1095
|
+
when 'OS_Fan_SystemModel'
|
1096
|
+
fan_obj = fan_component.to_FanSystemModel.get
|
1097
|
+
when 'OS_Fan_VariableVolume'
|
1098
|
+
fan_obj = fan_component.to_FanVariableVolume.get
|
1099
|
+
end
|
1100
|
+
return fan_obj
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
# Convert from schedule object to array of hourly values for entire year
|
1104
|
+
# Array will include extra 24 values for leap year
|
1105
|
+
# Array will also include extra 24 values at end for holiday day type
|
1106
|
+
# @author: Doug Maddox, PNNL
|
1107
|
+
# @TODO: consider moving this to Standards.Schedule.rb
|
1108
|
+
# @param: model [Object]
|
1109
|
+
# @param: fan_schedule [Object]
|
1110
|
+
# @return: [Array<String>] annual hourly values from schedule
|
1111
|
+
def get_8760_values_from_schedule(model, fan_schedule)
|
1112
|
+
sch_object_type = fan_schedule.iddObjectType.valueName.to_s
|
1113
|
+
fan_8760 = nil
|
1114
|
+
case sch_object_type
|
1115
|
+
when 'OS_Schedule_Ruleset'
|
1116
|
+
fan_8760 = get_8760_values_from_schedule_ruleset(model, fan_schedule)
|
1117
|
+
when 'OS_Schedule_Constant'
|
1118
|
+
fan_schedule_constant = fan_schedule.to_ScheduleConstant.get
|
1119
|
+
fan_8760 = get_8760_values_from_schedule_constant(model, fan_schedule_constant)
|
1120
|
+
when 'OS_Schedule_Compact'
|
1121
|
+
# First convert to ScheduleRuleset
|
1122
|
+
sch_translator = ScheduleTranslator.new(model, fan_schedule)
|
1123
|
+
fan_schedule_ruleset = sch_translator.convert_schedule_compact_to_schedule_ruleset
|
1124
|
+
fan_8760 = get_8760_values_from_schedule_ruleset(model, fan_schedule_ruleset)
|
1125
|
+
when 'OS_Schedule_Year'
|
1126
|
+
# TODO: add function for ScheduleYear
|
1127
|
+
# fan_8760 = get_8760_values_from_schedule_year(model, fan_schedule)
|
1128
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', 'Automated baseline measure does not support use of Schedule Year')
|
1129
|
+
end
|
1130
|
+
return fan_8760
|
1131
|
+
end
|
1132
|
+
|
614
1133
|
# Determines the area of the building above which point
|
615
1134
|
# the non-dominant area type gets it's own HVAC system type.
|
616
1135
|
#
|
@@ -629,16 +1148,17 @@ class Standard
|
|
629
1148
|
#
|
630
1149
|
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
631
1150
|
# @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
|
632
|
-
# @param
|
633
|
-
# @param fuel_type [String] Valid choices are electric, fossil, fossilandelectric,
|
634
|
-
# purchasedheat, purchasedcooling, purchasedheatandcooling
|
635
|
-
# @param area_ft2 [Double] Area in ft^2
|
636
|
-
# @param num_stories [Integer] Number of stories
|
1151
|
+
# @param sys_group [hash] Hash defining a group of zones that have the same Appendix G system type
|
637
1152
|
# @param custom [String] custom fuel type
|
638
1153
|
# @return [String] The system type. Possibilities are PTHP, PTAC, PSZ_AC, PSZ_HP, PVAV_Reheat, PVAV_PFP_Boxes,
|
639
1154
|
# VAV_Reheat, VAV_PFP_Boxes, Gas_Furnace, Electric_Furnace
|
640
1155
|
# @todo add 90.1-2013 systems 11-13
|
641
|
-
def model_prm_baseline_system_type(model, climate_zone,
|
1156
|
+
def model_prm_baseline_system_type(model, climate_zone, sys_group, custom, hvac_building_type = nil, district_heat_zones = nil)
|
1157
|
+
area_type = sys_group['occ']
|
1158
|
+
fuel_type = sys_group['fuel']
|
1159
|
+
area_ft2 = sys_group['area_ft2']
|
1160
|
+
num_stories = sys_group['stories']
|
1161
|
+
|
642
1162
|
# [type, central_heating_fuel, zone_heating_fuel, cooling_fuel]
|
643
1163
|
system_type = [nil, nil, nil, nil]
|
644
1164
|
|
@@ -752,6 +1272,72 @@ class Standard
|
|
752
1272
|
return fuel_type # Don't change fuel type for most templates
|
753
1273
|
end
|
754
1274
|
|
1275
|
+
# Determine whether heating type is fuel or electric
|
1276
|
+
# @param hvac_building_type [String] Key for lookup of baseline system type
|
1277
|
+
# @param climate_zone [String] full name of climate zone
|
1278
|
+
# @return [String] fuel or electric
|
1279
|
+
def find_prm_heat_type(hvac_building_type, climate_zone)
|
1280
|
+
climate_code = get_climate_zone_code(climate_zone)
|
1281
|
+
heat_type_props = model_find_object(standards_data['prm_heat_type'],
|
1282
|
+
'template' => template,
|
1283
|
+
'hvac_building_type' => hvac_building_type,
|
1284
|
+
'climate_zone' => climate_code)
|
1285
|
+
if !heat_type_props
|
1286
|
+
# try again with wild card for climate
|
1287
|
+
heat_type_props = model_find_object(standards_data['prm_heat_type'],
|
1288
|
+
'template' => template,
|
1289
|
+
'hvac_building_type' => hvac_building_type,
|
1290
|
+
'climate_zone' => 'any')
|
1291
|
+
end
|
1292
|
+
if !heat_type_props
|
1293
|
+
# try again with wild card for building type
|
1294
|
+
heat_type_props = model_find_object(standards_data['prm_heat_type'],
|
1295
|
+
'template' => template,
|
1296
|
+
'hvac_building_type' => 'all others',
|
1297
|
+
'climate_zone' => climate_code)
|
1298
|
+
end
|
1299
|
+
if !heat_type_props
|
1300
|
+
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Could not find baseline heat type for: #{template}-#{hvac_building_type}-#{climate_zone}.")
|
1301
|
+
else
|
1302
|
+
return heat_type_props['heat_type']
|
1303
|
+
end
|
1304
|
+
end
|
1305
|
+
|
1306
|
+
# Get ASHRAE ID code for climate zone
|
1307
|
+
# @param climate_zone [String] full name of climate zone
|
1308
|
+
# @return [String] ASHRAE ID code for climate zone
|
1309
|
+
def get_climate_zone_code(climate_zone)
|
1310
|
+
cz_codes = []
|
1311
|
+
cz_codes << '0A'
|
1312
|
+
cz_codes << '0B'
|
1313
|
+
cz_codes << '1A'
|
1314
|
+
cz_codes << '1B'
|
1315
|
+
cz_codes << '2A'
|
1316
|
+
cz_codes << '2B'
|
1317
|
+
cz_codes << '3A'
|
1318
|
+
cz_codes << '3B'
|
1319
|
+
cz_codes << '3C'
|
1320
|
+
cz_codes << '4A'
|
1321
|
+
cz_codes << '4B'
|
1322
|
+
cz_codes << '4C'
|
1323
|
+
cz_codes << '5A'
|
1324
|
+
cz_codes << '5B'
|
1325
|
+
cz_codes << '5C'
|
1326
|
+
cz_codes << '6A'
|
1327
|
+
cz_codes << '6B'
|
1328
|
+
cz_codes << '7A'
|
1329
|
+
cz_codes << '7B'
|
1330
|
+
cz_codes << '8A'
|
1331
|
+
cz_codes << '8B'
|
1332
|
+
|
1333
|
+
cz_codes.each do |cz|
|
1334
|
+
pattern = Regexp.new(cz, true)
|
1335
|
+
if pattern =~ climate_zone
|
1336
|
+
return cz.to_s
|
1337
|
+
end
|
1338
|
+
end
|
1339
|
+
end
|
1340
|
+
|
755
1341
|
# Add the specified baseline system type to the specified zones based on the specified template.
|
756
1342
|
# For some multi-zone system types, the standards require identifying zones whose loads or schedules
|
757
1343
|
# are outliers and putting these systems on separate single-zone systems. This method does that.
|
@@ -766,7 +1352,7 @@ class Standard
|
|
766
1352
|
# @param zones [Array<OpenStudio::Model::ThermalZone>] an array of zones
|
767
1353
|
# @return [Bool] returns true if successful, false if not
|
768
1354
|
# @todo Add 90.1-2013 systems 11-13
|
769
|
-
def model_add_prm_baseline_system(model, system_type, main_heat_fuel, zone_heat_fuel, cool_fuel, zones)
|
1355
|
+
def model_add_prm_baseline_system(model, system_type, main_heat_fuel, zone_heat_fuel, cool_fuel, zones, zone_fan_scheds)
|
770
1356
|
case system_type
|
771
1357
|
when 'PTAC' # System 1
|
772
1358
|
unless zones.empty?
|
@@ -884,9 +1470,10 @@ class Standard
|
|
884
1470
|
# and add the suplemental system type to the secondary zones.
|
885
1471
|
story_zone_lists.each do |story_group|
|
886
1472
|
# Differentiate primary and secondary zones
|
887
|
-
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group)
|
1473
|
+
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group, zone_fan_scheds)
|
888
1474
|
pri_zones = pri_sec_zone_lists['primary']
|
889
1475
|
sec_zones = pri_sec_zone_lists['secondary']
|
1476
|
+
zone_op_hrs = pri_sec_zone_lists['zone_op_hrs']
|
890
1477
|
|
891
1478
|
# Add a PVAV with Reheat for the primary zones
|
892
1479
|
stories = []
|
@@ -905,11 +1492,12 @@ class Standard
|
|
905
1492
|
hot_water_loop: hot_water_loop,
|
906
1493
|
chilled_water_loop: chilled_water_loop,
|
907
1494
|
electric_reheat: electric_reheat)
|
1495
|
+
model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name)
|
908
1496
|
end
|
909
1497
|
|
910
1498
|
# Add a PSZ_AC for each secondary zone
|
911
1499
|
unless sec_zones.empty?
|
912
|
-
model_add_prm_baseline_system(model, 'PSZ_AC', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones)
|
1500
|
+
model_add_prm_baseline_system(model, 'PSZ_AC', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones, zone_fan_scheds)
|
913
1501
|
end
|
914
1502
|
end
|
915
1503
|
|
@@ -935,9 +1523,10 @@ class Standard
|
|
935
1523
|
# and add the suplemental system type to the secondary zones.
|
936
1524
|
story_zone_lists.each do |story_group|
|
937
1525
|
# Differentiate primary and secondary zones
|
938
|
-
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group)
|
1526
|
+
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group, zone_fan_scheds)
|
939
1527
|
pri_zones = pri_sec_zone_lists['primary']
|
940
1528
|
sec_zones = pri_sec_zone_lists['secondary']
|
1529
|
+
zone_op_hrs = pri_sec_zone_lists['zone_op_hrs']
|
941
1530
|
|
942
1531
|
# Add an VAV for the primary zones
|
943
1532
|
stories = []
|
@@ -955,10 +1544,11 @@ class Standard
|
|
955
1544
|
fan_efficiency: 0.62,
|
956
1545
|
fan_motor_efficiency: 0.9,
|
957
1546
|
fan_pressure_rise: 4.0)
|
1547
|
+
model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name)
|
958
1548
|
end
|
959
1549
|
# Add a PSZ_HP for each secondary zone
|
960
1550
|
unless sec_zones.empty?
|
961
|
-
model_add_prm_baseline_system(model, 'PSZ_HP', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones)
|
1551
|
+
model_add_prm_baseline_system(model, 'PSZ_HP', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones, zone_fan_scheds)
|
962
1552
|
end
|
963
1553
|
end
|
964
1554
|
|
@@ -1013,9 +1603,10 @@ class Standard
|
|
1013
1603
|
# next if zones.empty?
|
1014
1604
|
|
1015
1605
|
# Differentiate primary and secondary zones
|
1016
|
-
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group)
|
1606
|
+
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group, zone_fan_scheds)
|
1017
1607
|
pri_zones = pri_sec_zone_lists['primary']
|
1018
1608
|
sec_zones = pri_sec_zone_lists['secondary']
|
1609
|
+
zone_op_hrs = pri_sec_zone_lists['zone_op_hrs']
|
1019
1610
|
|
1020
1611
|
# Add a VAV for the primary zones
|
1021
1612
|
stories = []
|
@@ -1028,6 +1619,10 @@ class Standard
|
|
1028
1619
|
# If and only if there are primary zones to attach to the loop
|
1029
1620
|
# counter example: floor with only one elevator machine room that get classified as sec_zones
|
1030
1621
|
unless pri_zones.empty?
|
1622
|
+
# if the loop configuration is primary / secondary loop
|
1623
|
+
if chilled_water_loop.additionalProperties.hasFeature('secondary_loop_name')
|
1624
|
+
chilled_water_loop = model.getPlantLoopByName(chilled_water_loop.additionalProperties.getFeatureAsString('secondary_loop_name').get).get
|
1625
|
+
end
|
1031
1626
|
model_add_vav_reheat(model,
|
1032
1627
|
pri_zones,
|
1033
1628
|
system_name: system_name,
|
@@ -1037,11 +1632,12 @@ class Standard
|
|
1037
1632
|
fan_efficiency: 0.62,
|
1038
1633
|
fan_motor_efficiency: 0.9,
|
1039
1634
|
fan_pressure_rise: 4.0)
|
1635
|
+
model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name)
|
1040
1636
|
end
|
1041
1637
|
|
1042
1638
|
# Add a PSZ_AC for each secondary zone
|
1043
1639
|
unless sec_zones.empty?
|
1044
|
-
model_add_prm_baseline_system(model, 'PSZ_AC', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones)
|
1640
|
+
model_add_prm_baseline_system(model, 'PSZ_AC', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones, zone_fan_scheds)
|
1045
1641
|
end
|
1046
1642
|
end
|
1047
1643
|
|
@@ -1080,9 +1676,10 @@ class Standard
|
|
1080
1676
|
# and add the suplemental system type to the secondary zones.
|
1081
1677
|
story_zone_lists.each do |story_group|
|
1082
1678
|
# Differentiate primary and secondary zones
|
1083
|
-
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group)
|
1679
|
+
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group, zone_fan_scheds)
|
1084
1680
|
pri_zones = pri_sec_zone_lists['primary']
|
1085
1681
|
sec_zones = pri_sec_zone_lists['secondary']
|
1682
|
+
zone_op_hrs = pri_sec_zone_lists['zone_op_hrs']
|
1086
1683
|
|
1087
1684
|
# Add an VAV for the primary zones
|
1088
1685
|
stories = []
|
@@ -1093,6 +1690,9 @@ class Standard
|
|
1093
1690
|
system_name = "#{story_name} VAV_PFP_Boxes (Sys8)"
|
1094
1691
|
# If and only if there are primary zones to attach to the loop
|
1095
1692
|
unless pri_zones.empty?
|
1693
|
+
if chilled_water_loop.additionalProperties.hasFeature('secondary_loop_name')
|
1694
|
+
chilled_water_loop = model.getPlantLoopByName(chilled_water_loop.additionalProperties.getFeatureAsString('secondary_loop_name').get).get
|
1695
|
+
end
|
1096
1696
|
model_add_vav_pfp_boxes(model,
|
1097
1697
|
pri_zones,
|
1098
1698
|
system_name: system_name,
|
@@ -1100,10 +1700,12 @@ class Standard
|
|
1100
1700
|
fan_efficiency: 0.62,
|
1101
1701
|
fan_motor_efficiency: 0.9,
|
1102
1702
|
fan_pressure_rise: 4.0)
|
1703
|
+
|
1704
|
+
model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name)
|
1103
1705
|
end
|
1104
1706
|
# Add a PSZ_HP for each secondary zone
|
1105
1707
|
unless sec_zones.empty?
|
1106
|
-
model_add_prm_baseline_system(model, 'PSZ_HP', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones)
|
1708
|
+
model_add_prm_baseline_system(model, 'PSZ_HP', main_heat_fuel, zone_heat_fuel, cool_fuel, sec_zones, zone_fan_scheds)
|
1107
1709
|
end
|
1108
1710
|
end
|
1109
1711
|
|
@@ -1137,6 +1739,83 @@ class Standard
|
|
1137
1739
|
heating_type: main_heat_fuel)
|
1138
1740
|
end
|
1139
1741
|
|
1742
|
+
when 'SZ_CV' # System 12 (gas or district heat) or System 13 (electric resistance heat)
|
1743
|
+
unless zones.empty?
|
1744
|
+
hot_water_loop = nil
|
1745
|
+
if zone_heat_fuel == 'DistrictHeating' || zone_heat_fuel == 'NaturalGas'
|
1746
|
+
heating_type = 'Water'
|
1747
|
+
hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized
|
1748
|
+
model.getPlantLoopByName('Hot Water Loop').get
|
1749
|
+
else
|
1750
|
+
model_add_hw_loop(model, main_heat_fuel)
|
1751
|
+
end
|
1752
|
+
else
|
1753
|
+
# If no hot water loop is defined, heat will default to electric resistance
|
1754
|
+
heating_type = 'Electric'
|
1755
|
+
end
|
1756
|
+
cooling_type = 'Water'
|
1757
|
+
chilled_water_loop = if model.getPlantLoopByName('Chilled Water Loop').is_initialized
|
1758
|
+
model.getPlantLoopByName('Chilled Water Loop').get
|
1759
|
+
else
|
1760
|
+
model_add_chw_loop(model,
|
1761
|
+
cooling_fuel: cool_fuel,
|
1762
|
+
chw_pumping_type: 'const_pri')
|
1763
|
+
end
|
1764
|
+
|
1765
|
+
model_add_four_pipe_fan_coil(model,
|
1766
|
+
zones,
|
1767
|
+
chilled_water_loop,
|
1768
|
+
hot_water_loop: hot_water_loop,
|
1769
|
+
ventilation: true,
|
1770
|
+
capacity_control_method: 'ConstantVolume')
|
1771
|
+
end
|
1772
|
+
when 'SZ_VAV' # System 11, chilled water, heating type varies by climate zone
|
1773
|
+
unless zones.empty?
|
1774
|
+
# htg type
|
1775
|
+
climate_zone = model_standards_climate_zone(model)
|
1776
|
+
case climate_zone
|
1777
|
+
when 'ASHRAE 169-2006-0A',
|
1778
|
+
'ASHRAE 169-2006-0B',
|
1779
|
+
'ASHRAE 169-2006-1A',
|
1780
|
+
'ASHRAE 169-2006-1B',
|
1781
|
+
'ASHRAE 169-2006-2A',
|
1782
|
+
'ASHRAE 169-2006-2B',
|
1783
|
+
'ASHRAE 169-2013-0A',
|
1784
|
+
'ASHRAE 169-2013-0B',
|
1785
|
+
'ASHRAE 169-2013-1A',
|
1786
|
+
'ASHRAE 169-2013-1B',
|
1787
|
+
'ASHRAE 169-2013-2A',
|
1788
|
+
'ASHRAE 169-2013-2B'
|
1789
|
+
heating_type = 'Electric'
|
1790
|
+
hot_water_loop = nil
|
1791
|
+
else
|
1792
|
+
hot_water_loop = if model.getPlantLoopByName('Hot Water Loop').is_initialized
|
1793
|
+
model.getPlantLoopByName('Hot Water Loop').get
|
1794
|
+
else
|
1795
|
+
hot_water_loop = model_add_hw_loop(model, main_heat_fuel)
|
1796
|
+
end
|
1797
|
+
heating_type = 'Water'
|
1798
|
+
end
|
1799
|
+
|
1800
|
+
# clg type
|
1801
|
+
chilled_water_loop = if model.getPlantLoopByName('Chilled Water Loop').is_initialized
|
1802
|
+
model.getPlantLoopByName('Chilled Water Loop').get
|
1803
|
+
else
|
1804
|
+
chilled_water_loop = model_add_chw_loop(model, chw_pumping_type: 'const_pri')
|
1805
|
+
end
|
1806
|
+
|
1807
|
+
model_add_psz_vav(model,
|
1808
|
+
zones,
|
1809
|
+
heating_type: heating_type,
|
1810
|
+
cooling_type: 'WaterCooled',
|
1811
|
+
supplemental_heating_type: nil,
|
1812
|
+
hvac_op_sch: nil,
|
1813
|
+
fan_type: 'PSZ_VAV_System_Fan',
|
1814
|
+
oa_damper_sch: nil,
|
1815
|
+
hot_water_loop: hot_water_loop,
|
1816
|
+
chilled_water_loop: chilled_water_loop,
|
1817
|
+
minimum_volume_setpoint: 0.5)
|
1818
|
+
end
|
1140
1819
|
else
|
1141
1820
|
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "System type #{system_type} is not a valid choice, nothing will be added to the model.")
|
1142
1821
|
return false
|
@@ -1179,13 +1858,7 @@ class Standard
|
|
1179
1858
|
# the groups of zones and add that system type.
|
1180
1859
|
sys_groups.each do |sys_group|
|
1181
1860
|
# Determine the primary baseline system type
|
1182
|
-
pri_system_type = model_prm_baseline_system_type(model,
|
1183
|
-
climate_zone,
|
1184
|
-
sys_group['occ'],
|
1185
|
-
sys_group['fuel'],
|
1186
|
-
sys_group['area_ft2'],
|
1187
|
-
sys_group['stories'],
|
1188
|
-
custom)[0]
|
1861
|
+
pri_system_type = model_prm_baseline_system_type(model, climate_zone, sys_group, custom)[0]
|
1189
1862
|
|
1190
1863
|
# Record the zone-by-zone system type assignments
|
1191
1864
|
case pri_system_type
|
@@ -1214,7 +1887,7 @@ class Standard
|
|
1214
1887
|
# and add the suplemental system type to the secondary zones.
|
1215
1888
|
story_zone_lists.each do |zones|
|
1216
1889
|
# Differentiate primary and secondary zones
|
1217
|
-
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model,
|
1890
|
+
pri_sec_zone_lists = model_differentiate_primary_secondary_thermal_zones(model, story_group, zone_fan_scheds)
|
1218
1891
|
# Record the primary zone system types
|
1219
1892
|
pri_sec_zone_lists['primary'].each do |zone|
|
1220
1893
|
zone_to_sys_type[zone] = pri_system_type
|
@@ -1318,8 +1991,8 @@ class Standard
|
|
1318
1991
|
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
1319
1992
|
# @param zones [Array<OpenStudio::Model::ThermalZone>] an array of zones
|
1320
1993
|
# @return [Hash] A hash of two arrays of ThermalZones,
|
1321
|
-
#
|
1322
|
-
def model_differentiate_primary_secondary_thermal_zones(model, zones)
|
1994
|
+
# where the keys are 'primary' and 'secondary'
|
1995
|
+
def model_differentiate_primary_secondary_thermal_zones(model, zones, zone_fan_scheds = nil)
|
1323
1996
|
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', 'Determining which zones are served by the primary vs. secondary HVAC system.')
|
1324
1997
|
|
1325
1998
|
# Determine the operational hours (proxy is annual
|
@@ -1431,7 +2104,58 @@ class Standard
|
|
1431
2104
|
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Secondary system zones = #{sec_zone_names.join(', ')}.")
|
1432
2105
|
end
|
1433
2106
|
|
1434
|
-
|
2107
|
+
zone_op_hrs = []
|
2108
|
+
return { 'primary' => pri_zones, 'secondary' => sec_zones, 'zone_op_hrs' => zone_op_hrs }
|
2109
|
+
end
|
2110
|
+
|
2111
|
+
# For a multizone system, get straight average of hash values excluding the reference zone
|
2112
|
+
# @author Doug Maddox, PNNL
|
2113
|
+
# @param value_hash [Hash<String>] of zoneName:Value
|
2114
|
+
# @param ref_zone [String] name of reference zone
|
2115
|
+
def get_avg_of_other_zones(value_hash, ref_zone)
|
2116
|
+
num_others = value_hash.size - 1
|
2117
|
+
value_sum = 0
|
2118
|
+
value_hash.each do |key, val|
|
2119
|
+
value_sum += val unless key == ref_zone
|
2120
|
+
end
|
2121
|
+
if num_others == 0
|
2122
|
+
value_avg = value_hash[ref_zone]
|
2123
|
+
else
|
2124
|
+
value_avg = value_sum / num_others
|
2125
|
+
end
|
2126
|
+
return value_avg
|
2127
|
+
end
|
2128
|
+
|
2129
|
+
# For a multizone system, get area weighted average of hash values excluding the reference zone
|
2130
|
+
# @author Doug Maddox, PNNL
|
2131
|
+
# @param value_hash [Hash<String>] of zoneName:Value
|
2132
|
+
# @param area_hash [Hash<String>] of zoneName:Area
|
2133
|
+
# @param ref_zone [String] name of reference zone
|
2134
|
+
def get_wtd_avg_of_other_zones(value_hash, area_hash, ref_zone)
|
2135
|
+
num_others = value_hash.size - 1
|
2136
|
+
value_sum = 0
|
2137
|
+
area_sum = 0
|
2138
|
+
value_hash.each do |key, val|
|
2139
|
+
value_sum += val * area_hash[key] unless key == ref_zone
|
2140
|
+
area_sum += area_hash[key] unless key == ref_zone
|
2141
|
+
end
|
2142
|
+
if num_others == 0
|
2143
|
+
value_avg = value_hash[ref_zone]
|
2144
|
+
else
|
2145
|
+
value_avg = value_sum / area_sum
|
2146
|
+
end
|
2147
|
+
return value_avg
|
2148
|
+
end
|
2149
|
+
|
2150
|
+
# For a multizone system, create the fan schedule based on zone occupancy/fan schedules
|
2151
|
+
# @author Doug Maddox, PNNL
|
2152
|
+
# @param model
|
2153
|
+
# @param zone_fan_scheds [Hash] of hash of zoneName:8760FanSchedPerZone
|
2154
|
+
# @param pri_zones [Array<String>] names of zones served by the multizone system
|
2155
|
+
# @param system_name [String] name of air loop
|
2156
|
+
def model_create_multizone_fan_schedule(model, zone_op_hrs, pri_zones, system_name)
|
2157
|
+
# Not applicable if not stable baseline
|
2158
|
+
return
|
1435
2159
|
end
|
1436
2160
|
|
1437
2161
|
# Group an array of zones into multiple arrays, one for each story in the building.
|
@@ -1638,6 +2362,12 @@ class Standard
|
|
1638
2362
|
return true
|
1639
2363
|
end
|
1640
2364
|
|
2365
|
+
# For backward compatibility, infiltration standard not used for 2013 and earlier
|
2366
|
+
# @return [Bool] true if successful, false if not
|
2367
|
+
def model_baseline_apply_infiltration_standard(model, climate_zone)
|
2368
|
+
return true
|
2369
|
+
end
|
2370
|
+
|
1641
2371
|
# Apply the air leakage requirements to the model, as described in PNNL section 5.2.1.6.
|
1642
2372
|
# This method creates customized infiltration objects for each space
|
1643
2373
|
# and removes the SpaceType-level infiltration objects.
|
@@ -1679,7 +2409,7 @@ class Standard
|
|
1679
2409
|
# OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Cannot find data for schedule: #{schedule_name}, will not be created.")
|
1680
2410
|
# return false
|
1681
2411
|
# end
|
1682
|
-
def model_find_objects(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil)
|
2412
|
+
def model_find_objects(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil, fan_motor_bhp = nil)
|
1683
2413
|
matching_objects = []
|
1684
2414
|
if hash_of_objects.is_a?(Hash) && hash_of_objects.key?('table')
|
1685
2415
|
hash_of_objects = hash_of_objects['table']
|
@@ -1733,6 +2463,30 @@ class Standard
|
|
1733
2463
|
end
|
1734
2464
|
end
|
1735
2465
|
|
2466
|
+
# If fan_motor_bhp was specified, narrow down the matching objects
|
2467
|
+
unless fan_motor_bhp.nil?
|
2468
|
+
# Skip objects that don't have fields for minimum_capacity and maximum_capacity
|
2469
|
+
matching_objects = matching_objects.reject { |object| !object.key?('minimum_capacity') || !object.key?('maximum_capacity') }
|
2470
|
+
|
2471
|
+
# Skip objects that don't have values specified for minimum_capacity and maximum_capacity
|
2472
|
+
matching_objects = matching_objects.reject { |object| object['minimum_capacity'].nil? || object['maximum_capacity'].nil? }
|
2473
|
+
|
2474
|
+
# Skip objects whose the minimum capacity is below or maximum capacity above the specified fan_motor_bhp
|
2475
|
+
matching_capacity_objects = matching_objects.reject { |object| fan_motor_bhp.to_f <= object['minimum_capacity'].to_f || fan_motor_bhp.to_f > object['maximum_capacity'].to_f }
|
2476
|
+
|
2477
|
+
# Filter based on motor type
|
2478
|
+
matching_capacity_objects = matching_capacity_objects.select { |object| object['type'].downcase == search_criteria['type'].downcase } if search_criteria.keys.include?('type')
|
2479
|
+
|
2480
|
+
# If no object was found, round the fan_motor_bhp down in case the number fell between the limits in the json file.
|
2481
|
+
if matching_capacity_objects.size.zero?
|
2482
|
+
fan_motor_bhp *= 0.99
|
2483
|
+
# Skip objects whose minimum capacity is below or maximum capacity above the specified fan_motor_bhp
|
2484
|
+
matching_objects = matching_objects.reject { |object| fan_motor_bhp.to_f <= object['minimum_capacity'].to_f || fan_motor_bhp.to_f > object['maximum_capacity'].to_f }
|
2485
|
+
else
|
2486
|
+
matching_objects = matching_capacity_objects
|
2487
|
+
end
|
2488
|
+
end
|
2489
|
+
|
1736
2490
|
# If date was specified, narrow down the matching objects
|
1737
2491
|
unless date.nil?
|
1738
2492
|
# Skip objects that don't have fields for start_date and end_date
|
@@ -1798,8 +2552,8 @@ class Standard
|
|
1798
2552
|
# 'type' => 'Enclosed',
|
1799
2553
|
# }
|
1800
2554
|
# motor_properties = self.model.find_object(motors, search_criteria, capacity: 2.5)
|
1801
|
-
def model_find_object(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil)
|
1802
|
-
matching_objects = model_find_objects(hash_of_objects, search_criteria, capacity, date, area, num_floors)
|
2555
|
+
def model_find_object(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil, fan_motor_bhp = nil)
|
2556
|
+
matching_objects = model_find_objects(hash_of_objects, search_criteria, capacity, date, area, num_floors, fan_motor_bhp)
|
1803
2557
|
|
1804
2558
|
# Check the number of matching objects found
|
1805
2559
|
if matching_objects.size.zero?
|
@@ -2344,7 +3098,7 @@ class Standard
|
|
2344
3098
|
# @param construction_props [Hash] hash of construction properties
|
2345
3099
|
# @return [OpenStudio::Model::Construction] construction object
|
2346
3100
|
# @todo make return an OptionalConstruction
|
2347
|
-
def model_add_construction(model, construction_name, construction_props = nil)
|
3101
|
+
def model_add_construction(model, construction_name, construction_props = nil, surface = nil)
|
2348
3102
|
# First check model and return construction if it already exists
|
2349
3103
|
model.getConstructions.sort.each do |construction|
|
2350
3104
|
if construction.name.get.to_s == construction_name
|
@@ -2356,14 +3110,34 @@ class Standard
|
|
2356
3110
|
OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "Adding construction: #{construction_name}")
|
2357
3111
|
|
2358
3112
|
# Get the object data
|
2359
|
-
|
3113
|
+
if standards_data.keys.include?('prm_constructions')
|
3114
|
+
data = model_find_object(standards_data['prm_constructions'], 'name' => construction_name)
|
3115
|
+
else
|
3116
|
+
data = model_find_object(standards_data['constructions'], 'name' => construction_name)
|
3117
|
+
end
|
3118
|
+
|
2360
3119
|
unless data
|
2361
3120
|
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Cannot find data for construction: #{construction_name}, will not be created.")
|
2362
3121
|
return OpenStudio::Model::OptionalConstruction.new
|
2363
3122
|
end
|
2364
3123
|
|
2365
3124
|
# Make a new construction and set the standards details
|
2366
|
-
|
3125
|
+
if data['intended_surface_type'] == 'GroundContactFloor' && !surface.nil?
|
3126
|
+
construction = OpenStudio::Model::FFactorGroundFloorConstruction.new(model)
|
3127
|
+
elsif data['intended_surface_type'] == 'GroundContactWall' && !surface.nil?
|
3128
|
+
construction = OpenStudio::Model::CFactorUndergroundWallConstruction.new(model)
|
3129
|
+
else
|
3130
|
+
construction = OpenStudio::Model::Construction.new(model)
|
3131
|
+
# Add the material layers to the construction
|
3132
|
+
layers = OpenStudio::Model::MaterialVector.new
|
3133
|
+
data['materials'].each do |material_name|
|
3134
|
+
material = model_add_material(model, material_name)
|
3135
|
+
if material
|
3136
|
+
layers << material
|
3137
|
+
end
|
3138
|
+
end
|
3139
|
+
construction.setLayers(layers)
|
3140
|
+
end
|
2367
3141
|
construction.setName(construction_name)
|
2368
3142
|
standards_info = construction.standardsInformation
|
2369
3143
|
|
@@ -2377,16 +3151,6 @@ class Standard
|
|
2377
3151
|
|
2378
3152
|
# @todo could put construction rendering color in the spreadsheet
|
2379
3153
|
|
2380
|
-
# Add the material layers to the construction
|
2381
|
-
layers = OpenStudio::Model::MaterialVector.new
|
2382
|
-
data['materials'].each do |material_name|
|
2383
|
-
material = model_add_material(model, material_name)
|
2384
|
-
if material
|
2385
|
-
layers << material
|
2386
|
-
end
|
2387
|
-
end
|
2388
|
-
construction.setLayers(layers)
|
2389
|
-
|
2390
3154
|
# Modify the R value of the insulation to hit the specified U-value, C-Factor, or F-Factor.
|
2391
3155
|
# Doesn't currently operate on glazing constructions
|
2392
3156
|
if construction_props
|
@@ -2403,11 +3167,19 @@ class Standard
|
|
2403
3167
|
if target_u_value_ip
|
2404
3168
|
|
2405
3169
|
# Handle Opaque and Fenestration Constructions differently
|
2406
|
-
if construction.isFenestration && construction_simple_glazing?(construction)
|
2407
|
-
|
2408
|
-
|
2409
|
-
|
2410
|
-
|
3170
|
+
# if construction.isFenestration && construction_simple_glazing?(construction)
|
3171
|
+
if construction.isFenestration
|
3172
|
+
if construction_simple_glazing?(construction)
|
3173
|
+
# Set the U-Value and SHGC
|
3174
|
+
construction_set_glazing_u_value(construction, target_u_value_ip.to_f, data['intended_surface_type'], u_includes_int_film, u_includes_ext_film)
|
3175
|
+
construction_set_glazing_shgc(construction, target_shgc.to_f)
|
3176
|
+
else # if !data['intended_surface_type'] == 'ExteriorWindow' && !data['intended_surface_type'] == 'Skylight'
|
3177
|
+
# Set the U-Value
|
3178
|
+
construction_set_u_value(construction, target_u_value_ip.to_f, data['insulation_layer'], data['intended_surface_type'], u_includes_int_film, u_includes_ext_film)
|
3179
|
+
# else
|
3180
|
+
# OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Not modifying U-value for #{data['intended_surface_type']} u_val #{target_u_value_ip} f_fac #{target_f_factor_ip} c_fac #{target_c_factor_ip}")
|
3181
|
+
end
|
3182
|
+
else
|
2411
3183
|
# Set the U-Value
|
2412
3184
|
construction_set_u_value(construction, target_u_value_ip.to_f, data['insulation_layer'], data['intended_surface_type'], u_includes_int_film, u_includes_ext_film)
|
2413
3185
|
# else
|
@@ -2415,19 +3187,27 @@ class Standard
|
|
2415
3187
|
end
|
2416
3188
|
|
2417
3189
|
elsif target_f_factor_ip && data['intended_surface_type'] == 'GroundContactFloor'
|
2418
|
-
|
2419
|
-
#
|
2420
|
-
|
2421
|
-
|
2422
|
-
|
2423
|
-
|
2424
|
-
|
2425
|
-
|
2426
|
-
|
2427
|
-
|
2428
|
-
|
2429
|
-
|
2430
|
-
|
3190
|
+
# F-factor objects are unique to each surface, so a surface needs to be passed
|
3191
|
+
# If not surface is passed, use the older approach to model ground contact floors
|
3192
|
+
if surface.nil?
|
3193
|
+
# Set the F-Factor (only applies to slabs on grade)
|
3194
|
+
# @todo figure out what the prototype buildings did about ground heat transfer
|
3195
|
+
# construction_set_slab_f_factor(construction, target_f_factor_ip.to_f, data['insulation_layer'])
|
3196
|
+
construction_set_u_value(construction, 0.0, data['insulation_layer'], data['intended_surface_type'], u_includes_int_film, u_includes_ext_film)
|
3197
|
+
else
|
3198
|
+
construction_set_surface_slab_f_factor(construction, target_f_factor_ip, surface)
|
3199
|
+
end
|
3200
|
+
elsif target_c_factor_ip && (data['intended_surface_type'] == 'GroundContactWall' || data['intended_surface_type'] == 'GroundContactRoof')
|
3201
|
+
# C-factor objects are unique to each surface, so a surface needs to be passed
|
3202
|
+
# If not surface is passed, use the older approach to model ground contact walls
|
3203
|
+
if surface.nil?
|
3204
|
+
# Set the C-Factor (only applies to underground walls)
|
3205
|
+
# @todo figure out what the prototype buildings did about ground heat transfer
|
3206
|
+
# construction_set_underground_wall_c_factor(construction, target_c_factor_ip.to_f, data['insulation_layer'])
|
3207
|
+
construction_set_u_value(construction, 0.0, data['insulation_layer'], data['intended_surface_type'], u_includes_int_film, u_includes_ext_film)
|
3208
|
+
else
|
3209
|
+
construction_set_surface_underground_wall_c_factor(construction, target_c_factor_ip, surface)
|
3210
|
+
end
|
2431
3211
|
end
|
2432
3212
|
|
2433
3213
|
# If the construction is fenestration,
|
@@ -2518,23 +3298,32 @@ class Standard
|
|
2518
3298
|
# @param intended_surface_type [String] intended surface type
|
2519
3299
|
# @param standards_construction_type [String] standards construction type
|
2520
3300
|
# @param building_category [String] building category
|
3301
|
+
# @param wwr_building_type [String] building type used to determine WWR for the PRM baseline model
|
3302
|
+
# @param wwr_info [Hash] @Todo - check what this is used for
|
3303
|
+
# @param surface [OpenStudio::Model::Surface] OpenStudio surface object, only used for surface specific construction, e.g F/C-factor constructions
|
2521
3304
|
# @return [OpenStudio::Model::Construction] construction object
|
2522
|
-
def model_find_and_add_construction(model, climate_zone_set, intended_surface_type, standards_construction_type, building_category)
|
3305
|
+
def model_find_and_add_construction(model, climate_zone_set, intended_surface_type, standards_construction_type, building_category, wwr_building_type: nil, wwr_info: {}, surface: nil)
|
2523
3306
|
# Get the construction properties,
|
2524
3307
|
# which specifies properties by construction category by climate zone set.
|
2525
3308
|
# AKA the info in Tables 5.5-1-5.5-8
|
2526
3309
|
|
2527
|
-
wwr = model_get_percent_of_surface_range(model, intended_surface_type)
|
2528
|
-
|
2529
3310
|
search_criteria = { 'template' => template,
|
2530
3311
|
'climate_zone_set' => climate_zone_set,
|
2531
3312
|
'intended_surface_type' => intended_surface_type,
|
2532
3313
|
'standards_construction_type' => standards_construction_type,
|
2533
3314
|
'building_category' => building_category }
|
2534
3315
|
|
2535
|
-
if
|
2536
|
-
|
2537
|
-
|
3316
|
+
# Check if WWR criteria is needed for the construction search
|
3317
|
+
wwr_parameter = { 'intended_surface_type' => intended_surface_type }
|
3318
|
+
if wwr_building_type
|
3319
|
+
wwr_parameter['wwr_building_type'] = wwr_building_type
|
3320
|
+
wwr_parameter['wwr_info'] = wwr_info
|
3321
|
+
end
|
3322
|
+
wwr_range = model_get_percent_of_surface_range(model, wwr_parameter)
|
3323
|
+
|
3324
|
+
if !wwr_range['minimum_percent_of_surface'].nil? && !wwr_range['maximum_percent_of_surface'].nil?
|
3325
|
+
search_criteria['minimum_percent_of_surface'] = wwr_range['minimum_percent_of_surface']
|
3326
|
+
search_criteria['maximum_percent_of_surface'] = wwr_range['maximum_percent_of_surface']
|
2538
3327
|
end
|
2539
3328
|
|
2540
3329
|
# First search
|
@@ -2555,8 +3344,6 @@ class Standard
|
|
2555
3344
|
almost_adiabatic = OpenStudio::Model::MasslessOpaqueMaterial.new(model, 'Smooth', 500)
|
2556
3345
|
construction.insertLayer(0, almost_adiabatic)
|
2557
3346
|
return construction
|
2558
|
-
# else
|
2559
|
-
# OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Construction properties for: #{template}-#{climate_zone_set}-#{intended_surface_type}-#{standards_construction_type}-#{building_category} = #{props}.")
|
2560
3347
|
end
|
2561
3348
|
|
2562
3349
|
# Make sure that a construction is specified
|
@@ -2569,7 +3356,7 @@ class Standard
|
|
2569
3356
|
end
|
2570
3357
|
|
2571
3358
|
# Add the construction, modifying properties as necessary
|
2572
|
-
construction = model_add_construction(model, props['construction'], props)
|
3359
|
+
construction = model_add_construction(model, props['construction'], props, surface)
|
2573
3360
|
|
2574
3361
|
return construction
|
2575
3362
|
end
|
@@ -2971,11 +3758,12 @@ class Standard
|
|
2971
3758
|
# @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
|
2972
3759
|
# @param building_type [String] the building type
|
2973
3760
|
# @param run_type [String] design day is dd-only, otherwise annual run
|
3761
|
+
# @param lkp_template [String] The standards template, e.g.'90.1-2013'
|
2974
3762
|
# @return [Hash] a hash of results for each fuel, where the keys are in the form 'End Use|Fuel Type',
|
2975
3763
|
# e.g. Heating|Electricity, Exterior Equipment|Water. All end use/fuel type combos are present,
|
2976
3764
|
# with values of 0.0 if none of this end use/fuel type combo was used by the simulation.
|
2977
3765
|
# Returns nil if the legacy results couldn't be found.
|
2978
|
-
def model_legacy_results_by_end_use_and_fuel_type(model, climate_zone, building_type, run_type)
|
3766
|
+
def model_legacy_results_by_end_use_and_fuel_type(model, climate_zone, building_type, run_type, lkp_template: nil)
|
2979
3767
|
# Load the legacy idf results CSV file into a ruby hash
|
2980
3768
|
top_dir = File.expand_path('../../..', File.dirname(__FILE__))
|
2981
3769
|
standards_data_dir = "#{top_dir}/data/standards"
|
@@ -2999,10 +3787,14 @@ class Standard
|
|
2999
3787
|
legacy_idf_csv = CSV.new(temp, headers: true, converters: :all)
|
3000
3788
|
legacy_idf_results = legacy_idf_csv.to_a.map(&:to_hash)
|
3001
3789
|
|
3790
|
+
if lkp_template.nil?
|
3791
|
+
lkp_template = template
|
3792
|
+
end
|
3793
|
+
|
3002
3794
|
# Get the results for this building
|
3003
3795
|
search_criteria = {
|
3004
3796
|
'Building Type' => building_type,
|
3005
|
-
'Template' =>
|
3797
|
+
'Template' => lkp_template,
|
3006
3798
|
'Climate Zone' => climate_zone
|
3007
3799
|
}
|
3008
3800
|
energy_values = model_find_object(legacy_idf_results, search_criteria)
|
@@ -3019,9 +3811,10 @@ class Standard
|
|
3019
3811
|
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
3020
3812
|
# @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
|
3021
3813
|
# @param building_type [String] the building type
|
3814
|
+
# @param lkp_template [String] The standards template, e.g.'90.1-2013'
|
3022
3815
|
# @return [Hash] Returns a hash with data presented in various bins.
|
3023
3816
|
# Returns nil if no search results
|
3024
|
-
def model_process_results_for_datapoint(model, climate_zone, building_type)
|
3817
|
+
def model_process_results_for_datapoint(model, climate_zone, building_type, lkp_template: nil)
|
3025
3818
|
# Hash to store the legacy results by fuel and by end use
|
3026
3819
|
legacy_results_hash = {}
|
3027
3820
|
legacy_results_hash['total_legacy_energy_val'] = 0
|
@@ -3030,7 +3823,7 @@ class Standard
|
|
3030
3823
|
legacy_results_hash['total_energy_by_end_use'] = {}
|
3031
3824
|
|
3032
3825
|
# Get the legacy simulation results
|
3033
|
-
legacy_values = model_legacy_results_by_end_use_and_fuel_type(model, climate_zone, building_type, 'annual')
|
3826
|
+
legacy_values = model_legacy_results_by_end_use_and_fuel_type(model, climate_zone, building_type, 'annual', lkp_template: lkp_template)
|
3034
3827
|
if legacy_values.nil?
|
3035
3828
|
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Could not find legacy idf results for #{search_criteria}")
|
3036
3829
|
return legacy_results_hash
|
@@ -3159,8 +3952,8 @@ class Standard
|
|
3159
3952
|
#
|
3160
3953
|
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
3161
3954
|
# @param remap_office [bool] re-map small office or leave it alone
|
3162
|
-
# @return [hash] key for climate zone
|
3163
|
-
def
|
3955
|
+
# @return [hash] key for climate zone, building type, and standards template. All values are strings.
|
3956
|
+
def model_get_building_properties(model, remap_office = true)
|
3164
3957
|
# get climate zone from model
|
3165
3958
|
climate_zone = model_standards_climate_zone(model)
|
3166
3959
|
|
@@ -3176,9 +3969,15 @@ class Standard
|
|
3176
3969
|
building_type = model_remap_office(model, open_studio_area)
|
3177
3970
|
end
|
3178
3971
|
|
3972
|
+
# get standards template
|
3973
|
+
if model.getBuilding.standardsTemplate.is_initialized
|
3974
|
+
standards_template = model.getBuilding.standardsTemplate.get
|
3975
|
+
end
|
3976
|
+
|
3179
3977
|
results = {}
|
3180
3978
|
results['climate_zone'] = climate_zone
|
3181
3979
|
results['building_type'] = building_type
|
3980
|
+
results['standards_template'] = standards_template
|
3182
3981
|
|
3183
3982
|
return results
|
3184
3983
|
end
|
@@ -3210,12 +4009,13 @@ class Standard
|
|
3210
4009
|
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
3211
4010
|
# @return [Double] EUI (MJ/m^2) for target template for given OSM. Returns nil if can't calculate EUI
|
3212
4011
|
def model_find_target_eui(model)
|
3213
|
-
building_data =
|
4012
|
+
building_data = model_get_building_properties(model)
|
3214
4013
|
climate_zone = building_data['climate_zone']
|
3215
4014
|
building_type = building_data['building_type']
|
4015
|
+
building_template = building_data['standards_template']
|
3216
4016
|
|
3217
4017
|
# look up results
|
3218
|
-
target_consumption = model_process_results_for_datapoint(model, climate_zone, building_type)
|
4018
|
+
target_consumption = model_process_results_for_datapoint(model, climate_zone, building_type, lkp_template: building_template)
|
3219
4019
|
|
3220
4020
|
# lookup target floor area for prototype buildings
|
3221
4021
|
target_floor_area = model_find_prototype_floor_area(model, building_type)
|
@@ -3243,12 +4043,13 @@ class Standard
|
|
3243
4043
|
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
3244
4044
|
# @return [Hash] EUI (MJ/m^2) This will return a hash of end uses. key is end use, value is eui
|
3245
4045
|
def model_find_target_eui_by_end_use(model)
|
3246
|
-
building_data =
|
4046
|
+
building_data = model_get_building_properties(model)
|
3247
4047
|
climate_zone = building_data['climate_zone']
|
3248
4048
|
building_type = building_data['building_type']
|
4049
|
+
building_template = building_data['standards_template']
|
3249
4050
|
|
3250
4051
|
# look up results
|
3251
|
-
target_consumption = model_process_results_for_datapoint(model, climate_zone, building_type)
|
4052
|
+
target_consumption = model_process_results_for_datapoint(model, climate_zone, building_type, lkp_template: building_template)
|
3252
4053
|
|
3253
4054
|
# lookup target floor area for prototype buildings
|
3254
4055
|
target_floor_area = model_find_prototype_floor_area(model, building_type)
|
@@ -3520,7 +4321,7 @@ class Standard
|
|
3520
4321
|
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
3521
4322
|
# @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
|
3522
4323
|
# @return [Bool] returns true if successful, false if not
|
3523
|
-
def model_apply_standard_constructions(model, climate_zone)
|
4324
|
+
def model_apply_standard_constructions(model, climate_zone, wwr_building_type: nil, wwr_info: {})
|
3524
4325
|
types_to_modify = []
|
3525
4326
|
|
3526
4327
|
# Possible boundary conditions are
|
@@ -3557,12 +4358,20 @@ class Standard
|
|
3557
4358
|
|
3558
4359
|
# Find just those surfaces
|
3559
4360
|
surfaces_to_modify = []
|
4361
|
+
surface_category = {}
|
3560
4362
|
types_to_modify.each do |boundary_condition, surface_type|
|
3561
4363
|
# Surfaces
|
3562
4364
|
model.getSurfaces.sort.each do |surf|
|
3563
4365
|
next unless surf.outsideBoundaryCondition == boundary_condition
|
3564
4366
|
next unless surf.surfaceType == surface_type
|
3565
4367
|
|
4368
|
+
if boundary_condition == 'Outdoors'
|
4369
|
+
surface_category[surf] = 'ExteriorSurface'
|
4370
|
+
elsif boundary_condition == 'Ground'
|
4371
|
+
surface_category[surf] = 'GroundSurface'
|
4372
|
+
else
|
4373
|
+
surface_category[surf] = 'NA'
|
4374
|
+
end
|
3566
4375
|
surfaces_to_modify << surf
|
3567
4376
|
end
|
3568
4377
|
|
@@ -3571,6 +4380,7 @@ class Standard
|
|
3571
4380
|
next unless surf.outsideBoundaryCondition == boundary_condition
|
3572
4381
|
next unless surf.subSurfaceType == surface_type
|
3573
4382
|
|
4383
|
+
surface_category[surf] = 'ExteriorSubSurface'
|
3574
4384
|
surfaces_to_modify << surf
|
3575
4385
|
end
|
3576
4386
|
end
|
@@ -3578,7 +4388,7 @@ class Standard
|
|
3578
4388
|
# Modify these surfaces
|
3579
4389
|
prev_created_consts = {}
|
3580
4390
|
surfaces_to_modify.sort.each do |surf|
|
3581
|
-
prev_created_consts = planar_surface_apply_standard_construction(surf, climate_zone, prev_created_consts)
|
4391
|
+
prev_created_consts = planar_surface_apply_standard_construction(surf, climate_zone, prev_created_consts, wwr_building_type, wwr_info, surface_category[surf])
|
3582
4392
|
end
|
3583
4393
|
|
3584
4394
|
# List the unique array of constructions
|
@@ -3603,7 +4413,7 @@ class Standard
|
|
3603
4413
|
# @return [Hash] hash of construction properties
|
3604
4414
|
def model_get_construction_properties(model, intended_surface_type, standards_construction_type, building_category, climate_zone = nil)
|
3605
4415
|
# get climate_zone_set
|
3606
|
-
climate_zone =
|
4416
|
+
climate_zone = model_get_building_properties(model)['climate_zone'] if climate_zone.nil?
|
3607
4417
|
climate_zone_set = model_find_climate_zone_set(model, climate_zone)
|
3608
4418
|
|
3609
4419
|
# populate search hash
|
@@ -3658,25 +4468,75 @@ class Standard
|
|
3658
4468
|
# Currently just using existing window geometry, and shrinking as necessary if WWR is above limit.
|
3659
4469
|
# @todo support semiheated spaces as a separate WWR category
|
3660
4470
|
# @todo add window frame area to calculation of WWR
|
3661
|
-
def model_apply_prm_baseline_window_to_wall_ratio(model, climate_zone)
|
3662
|
-
#
|
3663
|
-
#
|
3664
|
-
#
|
3665
|
-
|
3666
|
-
|
3667
|
-
|
3668
|
-
|
3669
|
-
|
3670
|
-
|
3671
|
-
|
3672
|
-
total_wall_m2 = 0.001
|
3673
|
-
total_subsurface_m2 = 0.0
|
4471
|
+
def model_apply_prm_baseline_window_to_wall_ratio(model, climate_zone, wwr_building_type: nil)
|
4472
|
+
# Define a Hash that will contain wall and window area for all
|
4473
|
+
# building area types included in the model
|
4474
|
+
# bat = building area type
|
4475
|
+
bat_win_wall_info = {}
|
4476
|
+
|
4477
|
+
# Store the baseline wwr, only used for 90.1-PRM-2019,
|
4478
|
+
# it is necessary for looking up baseline fenestration
|
4479
|
+
# U-factor and SHGC requirements
|
4480
|
+
base_wwr = {}
|
4481
|
+
|
3674
4482
|
# Store the space conditioning category for later use
|
3675
4483
|
space_cats = {}
|
4484
|
+
|
3676
4485
|
model.getSpaces.sort.each do |space|
|
4486
|
+
# Get standards space type and
|
4487
|
+
# catch spaces without space types
|
4488
|
+
#
|
4489
|
+
# Currently, priority is given to the wwr_building_type,
|
4490
|
+
# meaning that only one building area type is used. The
|
4491
|
+
# method can however handle models with multiple building
|
4492
|
+
# area type, if they are specified through each space's
|
4493
|
+
# space type standards building type.
|
4494
|
+
if space.hasAdditionalProperties && space.additionalProperties.hasFeature('building_type_for_wwr')
|
4495
|
+
std_spc_type = space.additionalProperties.getFeatureAsString('building_type_for_wwr').get
|
4496
|
+
else
|
4497
|
+
std_spc_type = 'no_space_type'
|
4498
|
+
if !wwr_building_type.nil?
|
4499
|
+
std_spc_type = wwr_building_type
|
4500
|
+
elsif space.spaceType.is_initialized
|
4501
|
+
std_spc_type = space.spaceType.get.standardsBuildingType.to_s
|
4502
|
+
end
|
4503
|
+
# insert space wwr type as additional properties for later search
|
4504
|
+
space.additionalProperties.setFeature('building_type_for_wwr', std_spc_type)
|
4505
|
+
end
|
4506
|
+
|
4507
|
+
# Initialize intermediate variables if space type hasn't
|
4508
|
+
# been encountered yet
|
4509
|
+
if !bat_win_wall_info.key?(std_spc_type)
|
4510
|
+
bat_win_wall_info[std_spc_type] = {}
|
4511
|
+
bat = bat_win_wall_info[std_spc_type]
|
4512
|
+
|
4513
|
+
# Loop through all spaces in the model, and
|
4514
|
+
# per the PNNL PRM Reference Manual, find the areas
|
4515
|
+
# of each space conditioning category (res, nonres, semi-heated)
|
4516
|
+
# separately. Include space multipliers.
|
4517
|
+
bat.store('nr_wall_m2', 0.001) # Avoids divide by zero errors later
|
4518
|
+
bat.store('nr_fene_only_wall_m2', 0.001)
|
4519
|
+
bat.store('nr_plenum_wall_m2', 0.001)
|
4520
|
+
bat.store('nr_wind_m2', 0)
|
4521
|
+
bat.store('res_wall_m2', 0.001)
|
4522
|
+
bat.store('res_fene_only_wall_m2', 0.001)
|
4523
|
+
bat.store('res_wind_m2', 0)
|
4524
|
+
bat.store('res_plenum_wall_m2', 0.001)
|
4525
|
+
bat.store('sh_wall_m2', 0.001)
|
4526
|
+
bat.store('sh_fene_only_wall_m2', 0.001)
|
4527
|
+
bat.store('sh_wind_m2', 0)
|
4528
|
+
bat.store('sh_plenum_wall_m2', 0.001)
|
4529
|
+
bat.store('total_wall_m2', 0.001)
|
4530
|
+
bat.store('total_plenum_m2', 0.001)
|
4531
|
+
else
|
4532
|
+
bat = bat_win_wall_info[std_spc_type]
|
4533
|
+
end
|
4534
|
+
|
3677
4535
|
# Loop through all surfaces in this space
|
3678
4536
|
wall_area_m2 = 0
|
3679
4537
|
wind_area_m2 = 0
|
4538
|
+
# save wall area from walls that have fenestrations (subsurfaces)
|
4539
|
+
wall_only_area_m2 = 0
|
3680
4540
|
space.surfaces.sort.each do |surface|
|
3681
4541
|
# Skip non-outdoor surfaces
|
3682
4542
|
next unless surface.outsideBoundaryCondition == 'Outdoors'
|
@@ -3685,142 +4545,202 @@ class Standard
|
|
3685
4545
|
|
3686
4546
|
# This wall's gross area (including window area)
|
3687
4547
|
wall_area_m2 += surface.grossArea * space.multiplier
|
3688
|
-
|
3689
|
-
|
3690
|
-
|
4548
|
+
unless surface.subSurfaces.empty?
|
4549
|
+
# Subsurfaces in this surface
|
4550
|
+
surface.subSurfaces.sort.each do |ss|
|
4551
|
+
next unless ss.subSurfaceType == 'FixedWindow' || ss.subSurfaceType == 'OperableWindow' || ss.subSurfaceType == 'GlassDoor'
|
3691
4552
|
|
3692
|
-
|
4553
|
+
# Only add wall surfaces when the wall actually have windows
|
4554
|
+
wind_area_m2 += ss.netArea * space.multiplier
|
4555
|
+
end
|
4556
|
+
end
|
4557
|
+
if wind_area_m2 > 0.0
|
4558
|
+
wall_only_area_m2 += surface.grossArea * space.multiplier
|
3693
4559
|
end
|
3694
4560
|
end
|
3695
4561
|
|
3696
4562
|
# Determine the space category
|
3697
|
-
|
3698
|
-
|
3699
|
-
|
3700
|
-
|
3701
|
-
# The full-bore method is on the next line in case needed.
|
3702
|
-
# cat = thermal_zone_conditioning_category(space, template, climate_zone)
|
3703
|
-
cooled = space_cooled?(space)
|
3704
|
-
heated = space_heated?(space)
|
3705
|
-
cat = 'Unconditioned'
|
3706
|
-
# Unconditioned
|
3707
|
-
if !heated && !cooled
|
3708
|
-
cat = 'Unconditioned'
|
3709
|
-
# Heated-Only
|
3710
|
-
elsif heated && !cooled
|
3711
|
-
cat = 'Semiheated'
|
3712
|
-
# Heated and Cooled
|
4563
|
+
if model_create_prm_baseline_building_requires_proposed_model_sizing_run(model)
|
4564
|
+
# For PRM 90.1-2019 and onward, determine space category
|
4565
|
+
# based on sizing run results
|
4566
|
+
cat = space_conditioning_category(space)
|
3713
4567
|
else
|
3714
|
-
|
3715
|
-
|
3716
|
-
|
3717
|
-
|
3718
|
-
|
3719
|
-
|
4568
|
+
# TODO: This should really use the heating/cooling loads from the proposed building.
|
4569
|
+
# However, in an attempt to avoid another sizing run just for this purpose,
|
4570
|
+
# conditioned status is based on heating/cooling setpoints.
|
4571
|
+
# If heated-only, will be assumed Semiheated.
|
4572
|
+
# The full-bore method is on the next line in case needed.
|
4573
|
+
# cat = thermal_zone_conditioning_category(space, template, climate_zone)
|
4574
|
+
cooled = space_cooled?(space)
|
4575
|
+
heated = space_heated?(space)
|
4576
|
+
cat = 'Unconditioned'
|
4577
|
+
# Unconditioned
|
4578
|
+
if !heated && !cooled
|
4579
|
+
cat = 'Unconditioned'
|
4580
|
+
# Heated-Only
|
4581
|
+
elsif heated && !cooled
|
4582
|
+
cat = 'Semiheated'
|
4583
|
+
# Heated and Cooled
|
4584
|
+
else
|
4585
|
+
res = space_residential?(space)
|
4586
|
+
cat = if res
|
4587
|
+
'ResConditioned'
|
4588
|
+
else
|
4589
|
+
'NonResConditioned'
|
4590
|
+
end
|
4591
|
+
end
|
3720
4592
|
end
|
3721
4593
|
space_cats[space] = cat
|
3722
4594
|
|
3723
|
-
# Add to the correct category
|
3724
|
-
case cat
|
3725
|
-
when 'Unconditioned'
|
3726
|
-
next # Skip unconditioned spaces
|
3727
|
-
when 'NonResConditioned'
|
3728
|
-
nr_wall_m2 += wall_area_m2
|
3729
|
-
nr_wind_m2 += wind_area_m2
|
3730
|
-
when 'ResConditioned'
|
3731
|
-
res_wall_m2 += wall_area_m2
|
3732
|
-
res_wind_m2 += wind_area_m2
|
3733
|
-
when 'Semiheated'
|
3734
|
-
sh_wall_m2 += wall_area_m2
|
3735
|
-
sh_wind_m2 += wind_area_m2
|
3736
|
-
end
|
3737
|
-
end
|
3738
|
-
|
3739
|
-
# Calculate the WWR of each category
|
3740
|
-
wwr_nr = ((nr_wind_m2 / nr_wall_m2) * 100.0).round(1)
|
3741
|
-
wwr_res = ((res_wind_m2 / res_wall_m2) * 100).round(1)
|
3742
|
-
wwr_sh = ((sh_wind_m2 / sh_wall_m2) * 100).round(1)
|
3743
|
-
|
3744
|
-
# Convert to IP and report
|
3745
|
-
nr_wind_ft2 = OpenStudio.convert(nr_wind_m2, 'm^2', 'ft^2').get
|
3746
|
-
nr_wall_ft2 = OpenStudio.convert(nr_wall_m2, 'm^2', 'ft^2').get
|
3747
|
-
|
3748
|
-
res_wind_ft2 = OpenStudio.convert(res_wind_m2, 'm^2', 'ft^2').get
|
3749
|
-
res_wall_ft2 = OpenStudio.convert(res_wall_m2, 'm^2', 'ft^2').get
|
3750
|
-
|
3751
|
-
sh_wind_ft2 = OpenStudio.convert(sh_wind_m2, 'm^2', 'ft^2').get
|
3752
|
-
sh_wall_ft2 = OpenStudio.convert(sh_wall_m2, 'm^2', 'ft^2').get
|
3753
|
-
|
3754
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR NonRes = #{wwr_nr.round}%; window = #{nr_wind_ft2.round} ft2, wall = #{nr_wall_ft2.round} ft2.")
|
3755
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Res = #{wwr_res.round}%; window = #{res_wind_ft2.round} ft2, wall = #{res_wall_ft2.round} ft2.")
|
3756
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Semiheated = #{wwr_sh.round}%; window = #{sh_wind_ft2.round} ft2, wall = #{sh_wall_ft2.round} ft2.")
|
3757
|
-
|
3758
|
-
# WWR limit
|
3759
|
-
wwr_lim = 40.0
|
3760
|
-
|
3761
|
-
# Check against WWR limit
|
3762
|
-
red_nr = wwr_nr > wwr_lim
|
3763
|
-
red_res = wwr_res > wwr_lim
|
3764
|
-
red_sh = wwr_sh > wwr_lim
|
3765
|
-
|
3766
|
-
# Stop here unless windows need reducing
|
3767
|
-
return true unless red_nr || red_res || red_sh
|
3768
|
-
|
3769
|
-
# Determine the factors by which to reduce the window area
|
3770
|
-
mult_nr_red = wwr_lim / wwr_nr
|
3771
|
-
mult_res_red = wwr_lim / wwr_res
|
3772
|
-
mult_sh_red = wwr_lim / wwr_sh
|
3773
|
-
|
3774
|
-
# Reduce the window area if any of the categories necessary
|
3775
|
-
model.getSpaces.sort.each do |space|
|
3776
|
-
# Determine the space category
|
3777
|
-
# from the previously stored values
|
3778
|
-
cat = space_cats[space]
|
3779
|
-
|
3780
|
-
# Get the correct multiplier
|
4595
|
+
# Add to the correct category is_space_plenum?
|
3781
4596
|
case cat
|
3782
4597
|
when 'Unconditioned'
|
3783
4598
|
next # Skip unconditioned spaces
|
3784
4599
|
when 'NonResConditioned'
|
3785
|
-
|
3786
|
-
|
3787
|
-
|
4600
|
+
space_is_plenum(space) ? bat['nr_plenum_wall_m2'] += wall_area_m2 : bat['nr_plenum_wall_m2'] += 0.0
|
4601
|
+
bat['nr_wall_m2'] += wall_area_m2
|
4602
|
+
bat['nr_fene_only_wall_m2'] += wall_only_area_m2
|
4603
|
+
bat['nr_wind_m2'] += wind_area_m2
|
3788
4604
|
when 'ResConditioned'
|
3789
|
-
|
3790
|
-
|
3791
|
-
|
4605
|
+
space_is_plenum(space) ? bat['res_plenum_wall_m2'] += wall_area_m2 : bat['res_plenum_wall_m2'] += 0.0
|
4606
|
+
bat['res_wall_m2'] += wall_area_m2
|
4607
|
+
bat['res_fene_only_wall_m2'] += wall_only_area_m2
|
4608
|
+
bat['res_wind_m2'] += wind_area_m2
|
3792
4609
|
when 'Semiheated'
|
3793
|
-
|
3794
|
-
|
3795
|
-
|
3796
|
-
|
3797
|
-
|
3798
|
-
|
3799
|
-
|
3800
|
-
|
3801
|
-
|
3802
|
-
|
3803
|
-
|
4610
|
+
space_is_plenum(space) ? bat['sh_plenum_wall_m2'] += wall_area_m2 : bat['sh_plenum_wall_m2'] += 0.0
|
4611
|
+
bat['sh_wall_m2'] += wall_area_m2
|
4612
|
+
bat['sh_fene_only_wall_m2'] += wall_only_area_m2
|
4613
|
+
bat['sh_wind_m2'] += wind_area_m2
|
4614
|
+
end
|
4615
|
+
end
|
4616
|
+
|
4617
|
+
# Retrieve WWR info for all Building Area Types included in the model
|
4618
|
+
# and perform adjustements if
|
4619
|
+
# bat = building area type
|
4620
|
+
bat_win_wall_info.each do |bat, vals|
|
4621
|
+
# Calculate the WWR of each category
|
4622
|
+
vals.store('wwr_nr', ((vals['nr_wind_m2'] / vals['nr_wall_m2']) * 100.0).round(1))
|
4623
|
+
vals.store('wwr_res', ((vals['res_wind_m2'] / vals['res_wall_m2']) * 100).round(1))
|
4624
|
+
vals.store('wwr_sh', ((vals['sh_wind_m2'] / vals['sh_wall_m2']) * 100).round(1))
|
4625
|
+
|
4626
|
+
# Convert to IP and report
|
4627
|
+
vals.store('nr_wind_ft2', OpenStudio.convert(vals['nr_wind_m2'], 'm^2', 'ft^2').get)
|
4628
|
+
vals.store('nr_wall_ft2', OpenStudio.convert(vals['nr_wall_m2'], 'm^2', 'ft^2').get)
|
4629
|
+
|
4630
|
+
vals.store('res_wind_ft2', OpenStudio.convert(vals['res_wind_m2'], 'm^2', 'ft^2').get)
|
4631
|
+
vals.store('res_wall_ft2', OpenStudio.convert(vals['res_wall_m2'], 'm^2', 'ft^2').get)
|
4632
|
+
|
4633
|
+
vals.store('sh_wind_ft2', OpenStudio.convert(vals['sh_wind_m2'], 'm^2', 'ft^2').get)
|
4634
|
+
vals.store('sh_wall_ft2', OpenStudio.convert(vals['sh_wall_m2'], 'm^2', 'ft^2').get)
|
4635
|
+
|
4636
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR NonRes = #{vals['wwr_nr'].round}%; window = #{vals['nr_wind_ft2'].round} ft2, wall = #{vals['nr_wall_ft2'].round} ft2.")
|
4637
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Res = #{vals['wwr_res'].round}%; window = #{vals['res_wind_ft2'].round} ft2, wall = #{vals['res_wall_ft2'].round} ft2.")
|
4638
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Semiheated = #{vals['wwr_sh'].round}%; window = #{vals['sh_wind_ft2'].round} ft2, wall = #{vals['sh_wall_ft2'].round} ft2.")
|
4639
|
+
|
4640
|
+
# WWR limit or target
|
4641
|
+
wwr_lim = model_get_bat_wwr_target(bat, [vals['wwr_nr'], vals['wwr_res'], vals['wwr_sh']])
|
4642
|
+
|
4643
|
+
# Check against WWR limit
|
4644
|
+
vals['red_nr'] = vals['wwr_nr'] > wwr_lim
|
4645
|
+
vals['red_res'] = vals['wwr_res'] > wwr_lim
|
4646
|
+
vals['red_sh'] = vals['wwr_sh'] > wwr_lim
|
4647
|
+
|
4648
|
+
# Stop here unless windows need reducing or increasing
|
4649
|
+
return true, base_wwr unless model_does_require_wwr_adjustment?(wwr_lim, [vals['wwr_nr'], vals['wwr_res'], vals['wwr_sh']])
|
4650
|
+
|
4651
|
+
# Determine the factors by which to reduce the window area
|
4652
|
+
vals['mult_nr_red'] = wwr_lim / vals['wwr_nr']
|
4653
|
+
vals['mult_res_red'] = wwr_lim / vals['wwr_res']
|
4654
|
+
vals['mult_sh_red'] = wwr_lim / vals['wwr_sh']
|
4655
|
+
|
4656
|
+
# Report baseline WWR
|
4657
|
+
vals['wwr_nr'] *= vals['mult_nr_red']
|
4658
|
+
vals['wwr_res'] *= vals['mult_res_red']
|
4659
|
+
vals['wwr_sh'] *= vals['mult_sh_red']
|
4660
|
+
wwrs = [vals['wwr_nr'], vals['wwr_res'], vals['wwr_sh']]
|
4661
|
+
wwrs = wwrs.reject! &:nan?
|
4662
|
+
base_wwr[bat] = wwrs.max
|
4663
|
+
|
4664
|
+
# Reduce the window area if any of the categories necessary
|
4665
|
+
model.getSpaces.sort.each do |space|
|
4666
|
+
# Catch spaces without space types
|
4667
|
+
std_spc_type = space.additionalProperties.getFeatureAsString('building_type_for_wwr').get
|
4668
|
+
# skip process the space unless the space wwr type matched.
|
4669
|
+
next unless bat == std_spc_type
|
4670
|
+
# supply and return plenum is now conditioned space but should be excluded from window adjustment
|
4671
|
+
next if space_is_plenum(space)
|
4672
|
+
|
4673
|
+
# Determine the space category
|
4674
|
+
# from the previously stored values
|
4675
|
+
cat = space_cats[space]
|
4676
|
+
|
4677
|
+
# Get the correct multiplier
|
4678
|
+
case cat
|
4679
|
+
when 'Unconditioned'
|
4680
|
+
next # Skip unconditioned spaces
|
4681
|
+
when 'NonResConditioned'
|
4682
|
+
mult = vals['mult_nr_red']
|
4683
|
+
total_wall_area = vals['nr_wall_m2']
|
4684
|
+
total_wall_with_fene_area = vals['nr_fene_only_wall_m2']
|
4685
|
+
total_plenum_wall_area = vals['nr_plenum_wall_m2']
|
4686
|
+
total_fene_area = vals['nr_wind_m2']
|
4687
|
+
when 'ResConditioned'
|
4688
|
+
mult = vals['mult_res_red']
|
4689
|
+
total_wall_area = vals['res_wall_m2']
|
4690
|
+
total_wall_with_fene_area = vals['res_fene_only_wall_m2']
|
4691
|
+
total_plenum_wall_area = vals['res_plenum_wall_m2']
|
4692
|
+
total_fene_area = vals['res_wind_m2']
|
4693
|
+
when 'Semiheated'
|
4694
|
+
mult = vals['mult_sh_red']
|
4695
|
+
total_wall_area = vals['sh_wall_m2']
|
4696
|
+
total_wall_with_fene_area = vals['sh_fene_only_wall_m2']
|
4697
|
+
total_plenum_wall_area = vals['sh_plenum_wall_m2']
|
4698
|
+
total_fene_area = vals['sh_wind_m2']
|
4699
|
+
end
|
3804
4700
|
|
3805
|
-
#
|
3806
|
-
|
3807
|
-
|
4701
|
+
# used for counting how many window area is left for doors
|
4702
|
+
residual_fene = 0.0
|
4703
|
+
# Loop through all surfaces in this space
|
4704
|
+
space.surfaces.sort.each do |surface|
|
4705
|
+
# Skip non-outdoor surfaces
|
4706
|
+
next unless surface.outsideBoundaryCondition == 'Outdoors'
|
4707
|
+
# Skip non-walls
|
4708
|
+
next unless surface.surfaceType.casecmp('wall').zero?
|
3808
4709
|
|
3809
4710
|
# Reduce the size of the window
|
3810
4711
|
# If a vertical rectangle, raise sill height to avoid
|
3811
4712
|
# impacting daylighting areas, otherwise
|
3812
4713
|
# reduce toward centroid.
|
3813
|
-
|
3814
|
-
|
3815
|
-
|
3816
|
-
|
3817
|
-
|
4714
|
+
#
|
4715
|
+
# daylighting control isn't modeled
|
4716
|
+
surface_wwr = surface_get_wwr_of_a_surface(surface)
|
4717
|
+
red = model_get_wwr_reduction_ratio(mult,
|
4718
|
+
surface_wwr: surface_wwr,
|
4719
|
+
surface_dr: surface_get_door_ratio_of_a_surface(surface),
|
4720
|
+
wwr_building_type: bat,
|
4721
|
+
wwr_target: wwr_lim / 100, # divide by 100 to revise it to decimals
|
4722
|
+
total_wall_m2: total_wall_area,
|
4723
|
+
total_wall_with_fene_m2: total_wall_with_fene_area,
|
4724
|
+
total_fene_m2: total_fene_area,
|
4725
|
+
total_plenum_wall_m2: total_plenum_wall_area)
|
4726
|
+
|
4727
|
+
if red < 0.0
|
4728
|
+
# surface with fenestration to its maximum but adjusted by door areas when need to add windows in surfaces no fenestration
|
4729
|
+
# turn negative to positive to get the correct adjustment factor.
|
4730
|
+
red = -red
|
4731
|
+
residual_fene += (0.9 - red * surface_wwr) * surface.grossArea
|
3818
4732
|
end
|
4733
|
+
surface_adjust_fenestration_in_a_surface(surface, red, model)
|
4734
|
+
end
|
4735
|
+
|
4736
|
+
if residual_fene > 0.0
|
4737
|
+
residual_ratio = residual_fene / (total_wall_area - total_wall_with_fene_area)
|
4738
|
+
model_readjust_surface_wwr(residual_ratio, space, model)
|
3819
4739
|
end
|
3820
4740
|
end
|
3821
4741
|
end
|
3822
4742
|
|
3823
|
-
return true
|
4743
|
+
return true, base_wwr
|
3824
4744
|
end
|
3825
4745
|
|
3826
4746
|
# Reduces the SRR to the values specified by the PRM. SRR reduction will be done by shrinking vertices toward the centroid.
|
@@ -3968,6 +4888,21 @@ class Standard
|
|
3968
4888
|
return srr_lim
|
3969
4889
|
end
|
3970
4890
|
|
4891
|
+
# Apply baseline values to exterior lights objects
|
4892
|
+
# Only implemented for stable baseline
|
4893
|
+
#
|
4894
|
+
# @param model [OpenStudio::model::Model] OpenStudio model object
|
4895
|
+
def model_apply_baseline_exterior_lighting(model)
|
4896
|
+
return false
|
4897
|
+
end
|
4898
|
+
|
4899
|
+
# Function to add baseline elevators based on user data
|
4900
|
+
# Only applicable to stable baseline
|
4901
|
+
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
4902
|
+
def model_add_prm_elevators(model)
|
4903
|
+
return false
|
4904
|
+
end
|
4905
|
+
|
3971
4906
|
# Remove all HVAC that will be replaced during the performance rating method baseline generation.
|
3972
4907
|
# This does not include plant loops that serve WaterUse:Equipment or Fan:ZoneExhaust
|
3973
4908
|
#
|
@@ -3983,9 +4918,28 @@ class Standard
|
|
3983
4918
|
end
|
3984
4919
|
|
3985
4920
|
# Air loops
|
3986
|
-
model.getAirLoopHVACs.each
|
3987
|
-
|
3988
|
-
|
4921
|
+
model.getAirLoopHVACs.each do |air_loop|
|
4922
|
+
# Don't remove airloops representing non-mechanically cooled systems
|
4923
|
+
if !air_loop.additionalProperties.hasFeature('non_mechanically_cooled')
|
4924
|
+
air_loop.remove
|
4925
|
+
else
|
4926
|
+
# Remove heating coil on
|
4927
|
+
air_loop.supplyComponents.each do |supply_comp|
|
4928
|
+
# Remove standalone heating coils
|
4929
|
+
if supply_comp.iddObjectType.valueName.to_s.include?('OS_Coil_Heating')
|
4930
|
+
supply_comp.remove
|
4931
|
+
# Remove heating coils wrapped in a unitary system
|
4932
|
+
elsif supply_comp.iddObjectType.valueName.to_s.include?('OS_AirLoopHVAC_UnitarySystem')
|
4933
|
+
unitary_system = supply_comp.to_AirLoopHVACUnitarySystem.get
|
4934
|
+
htg_coil = unitary_system.heatingCoil
|
4935
|
+
if htg_coil.is_initialized
|
4936
|
+
htg_coil = htg_coil.get
|
4937
|
+
unitary_system.resetCoolingCoil
|
4938
|
+
htg_coil.remove
|
4939
|
+
end
|
4940
|
+
end
|
4941
|
+
end
|
4942
|
+
end
|
3989
4943
|
end
|
3990
4944
|
|
3991
4945
|
# Zone equipment
|
@@ -3993,13 +4947,16 @@ class Standard
|
|
3993
4947
|
zone.equipment.each do |zone_equipment|
|
3994
4948
|
next if zone_equipment.to_FanZoneExhaust.is_initialized
|
3995
4949
|
|
3996
|
-
zone_equipment.remove
|
4950
|
+
zone_equipment.remove unless zone.additionalProperties.hasFeature('non_mechanically_cooled')
|
3997
4951
|
end
|
3998
4952
|
end
|
3999
4953
|
|
4000
4954
|
# Outdoor VRF units (not in zone, not in loops)
|
4001
4955
|
model.getAirConditionerVariableRefrigerantFlows.each(&:remove)
|
4002
4956
|
|
4957
|
+
# Air loop dedicated outdoor air systems
|
4958
|
+
model.getAirLoopHVACDedicatedOutdoorAirSystems.each(&:remove)
|
4959
|
+
|
4003
4960
|
return true
|
4004
4961
|
end
|
4005
4962
|
|
@@ -4098,7 +5055,7 @@ class Standard
|
|
4098
5055
|
# @todo for types not in table use standards area normalized swh values
|
4099
5056
|
|
4100
5057
|
# get building type
|
4101
|
-
building_data =
|
5058
|
+
building_data = model_get_building_properties(model)
|
4102
5059
|
building_type = building_data['building_type']
|
4103
5060
|
|
4104
5061
|
result = []
|
@@ -4403,6 +5360,7 @@ class Standard
|
|
4403
5360
|
# update count of ground wall areas
|
4404
5361
|
next if surface.surfaceType != 'Wall'
|
4405
5362
|
next if surface.outsideBoundaryCondition != 'Ground'
|
5363
|
+
|
4406
5364
|
# @todo make more flexible for slab/basement model.modeling
|
4407
5365
|
|
4408
5366
|
story_ground_wall_area += surface.grossArea
|
@@ -4770,7 +5728,7 @@ class Standard
|
|
4770
5728
|
# @param model [OpenStudio::Model::Model] the model
|
4771
5729
|
# @return [String] the ventilation method, either Sum or Maximum
|
4772
5730
|
def model_ventilation_method(model)
|
4773
|
-
building_data =
|
5731
|
+
building_data = model_get_building_properties(model)
|
4774
5732
|
building_type = building_data['building_type']
|
4775
5733
|
if building_type != 'Laboratory' # Laboratory has multiple criteria on ventilation, pick the greatest
|
4776
5734
|
ventilation_method = 'Sum'
|
@@ -5168,6 +6126,40 @@ class Standard
|
|
5168
6126
|
|
5169
6127
|
private
|
5170
6128
|
|
6129
|
+
# This function checks whether it is required to adjust the window to wall ratio based on the model WWR and wwr limit.
|
6130
|
+
# @param wwr_limit [Float] return wwr_limit
|
6131
|
+
# @param wwr_list [Array] list of wwr of zone conditioning category in a building area type category - residential, nonresidential and semiheated
|
6132
|
+
# @return require_adjustment [Boolean] True, require adjustment, false not require adjustment.
|
6133
|
+
def model_does_require_wwr_adjustment?(wwr_limit, wwr_list)
|
6134
|
+
require_adjustment = false
|
6135
|
+
wwr_list.each do |wwr|
|
6136
|
+
require_adjustment = true unless wwr > wwr_limit
|
6137
|
+
end
|
6138
|
+
return require_adjustment
|
6139
|
+
end
|
6140
|
+
|
6141
|
+
# The function is used for codes that requires to adjusted wwr based on building categories for all other types
|
6142
|
+
#
|
6143
|
+
# @param bat [String] building area type category
|
6144
|
+
# @param wwr_list [Array] list of wwr of zone conditioning category in a building area type category - residential, nonresidential and semiheated
|
6145
|
+
# @return wwr_limit [Float] return adjusted wwr_limit
|
6146
|
+
def model_get_bat_wwr_target(bat, wwr_list)
|
6147
|
+
return 40.0
|
6148
|
+
end
|
6149
|
+
|
6150
|
+
# Readjusted the WWR for surfaces previously has no windows to meet the
|
6151
|
+
# overall WWR requirement.
|
6152
|
+
# This function shall only be called if the maximum WWR value for surfaces with fenestration is lower than 90% due to
|
6153
|
+
# accommodating the total door surface areas
|
6154
|
+
#
|
6155
|
+
# @param residual_ratio: [Float] the ratio of residual surfaces among the total wall surface area with no fenestrations
|
6156
|
+
# @param space [OpenStudio::Model:Space] a space
|
6157
|
+
# @param model [OpenStudio::Model::Model] openstudio model
|
6158
|
+
# @return [Bool] return true if successful, false if not
|
6159
|
+
def model_readjust_surface_wwr(residual_ratio, space, model)
|
6160
|
+
return true
|
6161
|
+
end
|
6162
|
+
|
5171
6163
|
# Helper method to fill in hourly values
|
5172
6164
|
#
|
5173
6165
|
# @param model [OpenStudio::Model::Model] the model
|
@@ -5300,6 +6292,28 @@ class Standard
|
|
5300
6292
|
return true
|
5301
6293
|
end
|
5302
6294
|
|
6295
|
+
# This method is a catch-all run at the end of create-baseline to make final adjustements to HVAC capacities
|
6296
|
+
# to account for recent model changes
|
6297
|
+
# @author Doug Maddox, PNNL
|
6298
|
+
# @param model
|
6299
|
+
def model_refine_size_dependent_values(model, sizing_run_dir)
|
6300
|
+
return true
|
6301
|
+
end
|
6302
|
+
|
6303
|
+
# This method rotates the building model from its original position
|
6304
|
+
#
|
6305
|
+
# @param model [OpenStudio::Model::Model] OpenStudio Model object
|
6306
|
+
# @param degs [Integer] Degress of rotation from original position
|
6307
|
+
#
|
6308
|
+
# @return [OpenStudio::Model::Model] OpenStudio Model object
|
6309
|
+
def model_rotate(model, degs)
|
6310
|
+
building = model.getBuilding
|
6311
|
+
org_north_axis = building.northAxis
|
6312
|
+
building.setNorthAxis(org_north_axis + degs)
|
6313
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "The model was rotated of #{degs} degrees from its original position.")
|
6314
|
+
return model
|
6315
|
+
end
|
6316
|
+
|
5303
6317
|
# Loads a geometry osm as a starting point.
|
5304
6318
|
#
|
5305
6319
|
# @param osm_model_path [String] path to the .osm file, relative to the /data folder
|
@@ -5805,12 +6819,74 @@ class Standard
|
|
5805
6819
|
return parametric_inputs
|
5806
6820
|
end
|
5807
6821
|
|
6822
|
+
# Retrieves the lowest story in a model
|
6823
|
+
#
|
6824
|
+
# @param model [OpenStudio::model::Model] OpenStudio model object
|
6825
|
+
# @return [OpenStudio::model::BuildingStory] Lowest story included in the model
|
6826
|
+
def find_lowest_story(model)
|
6827
|
+
min_z_story = 1E+10
|
6828
|
+
lowest_story = nil
|
6829
|
+
model.getSpaces.sort.each do |space|
|
6830
|
+
story = space.buildingStory.get
|
6831
|
+
lowest_story = story if lowest_story.nil?
|
6832
|
+
space_min_z = building_story_minimum_z_value(story)
|
6833
|
+
if space_min_z < min_z_story
|
6834
|
+
min_z_story = space_min_z
|
6835
|
+
lowest_story = story
|
6836
|
+
end
|
6837
|
+
end
|
6838
|
+
return lowest_story
|
6839
|
+
end
|
6840
|
+
|
6841
|
+
# Utility function that returns the min and max value in a design day schedule.
|
6842
|
+
#
|
6843
|
+
# TODO: move this to Standards.Schedule.rb
|
6844
|
+
# @param schedule [OpenStudio::Model::Schedule] can be ScheduleCompact, ScheduleRuleset, ScheduleConstant
|
6845
|
+
# @param type [String] 'heating' for winter design day, 'cooling' for summer design day
|
6846
|
+
# @return [Hash] Hash has two keys, min and max. if failed, return 999.9 for min and max.
|
6847
|
+
def search_min_max_value_from_design_day_schedule(schedule, type = 'winter')
|
6848
|
+
if schedule.is_initialized
|
6849
|
+
schedule = schedule.get
|
6850
|
+
if schedule.to_ScheduleRuleset.is_initialized
|
6851
|
+
schedule = schedule.to_ScheduleRuleset.get
|
6852
|
+
setpoint_min_max = schedule_ruleset_design_day_min_max_value(schedule, type)
|
6853
|
+
elsif schedule.to_ScheduleConstant.is_initialized
|
6854
|
+
schedule = schedule.to_ScheduleConstant.get
|
6855
|
+
# for constant schedule, there is only one value, so the annual should be equal to design condition.
|
6856
|
+
setpoint_min_max = schedule_constant_annual_min_max_value(schedule)
|
6857
|
+
elsif schedule.to_ScheduleCompact.is_initialized
|
6858
|
+
schedule = schedule.to_ScheduleCompact.get
|
6859
|
+
setpoint_min_max = schedule_compact_design_day_min_max_value(schedule, type)
|
6860
|
+
end
|
6861
|
+
return setpoint_min_max
|
6862
|
+
end
|
6863
|
+
OpenStudio.logFree(OpenStudio::Error, 'openstudio::standards::Schedule', 'Schedule is not exist, or wrong type of schedule (not Ruleset, Compact or Constant), or cannot found the design day schedules. Return 999.9 for min and max')
|
6864
|
+
return { 'min' => 999.9, 'max' => 999.9 }
|
6865
|
+
end
|
6866
|
+
|
6867
|
+
# Identifies non mechanically cooled ("nmc") systems, if applicable
|
6868
|
+
#
|
6869
|
+
# @param model [OpenStudio::model::Model] OpenStudio model object
|
6870
|
+
# @return zone_nmc_sys_type [Hash] Zone to nmc system type mapping
|
6871
|
+
def model_identify_non_mechanically_cooled_systems(model)
|
6872
|
+
return true
|
6873
|
+
end
|
6874
|
+
|
6875
|
+
# Indicate if fan power breakdown (supply, return, and relief)
|
6876
|
+
# are needed
|
6877
|
+
#
|
6878
|
+
# @return [Boolean] true if necessary, false otherwise
|
6879
|
+
def model_get_fan_power_breakdown
|
6880
|
+
return false
|
6881
|
+
end
|
6882
|
+
|
5808
6883
|
# Determine the surface range of a baseline model.
|
5809
6884
|
# The method calculates the window to wall ratio (assuming all spaces are conditioned)
|
5810
6885
|
# and select the range based on the calculated window to wall ratio
|
5811
6886
|
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
5812
|
-
# @param
|
5813
|
-
|
6887
|
+
# @param wwr_parameter [Hash] parameters to choose min and max percent of surfaces,
|
6888
|
+
# could be different set in different standard
|
6889
|
+
def model_get_percent_of_surface_range(model, wwr_parameter = {})
|
5814
6890
|
return { 'minimum_percent_of_surface' => nil, 'maximum_percent_of_surface' => nil }
|
5815
6891
|
end
|
5816
6892
|
|
@@ -5821,4 +6897,296 @@ class Standard
|
|
5821
6897
|
def air_loop_hvac_supply_air_temperature_reset_type(air_loop_hvac)
|
5822
6898
|
return 'warmest_zone'
|
5823
6899
|
end
|
6900
|
+
|
6901
|
+
# Calculate the window to wall ratio reduction factor
|
6902
|
+
#
|
6903
|
+
# @param multiplier [Float] multiplier of the wwr
|
6904
|
+
# @param surface_wwr [Float] the surface window to wall ratio
|
6905
|
+
# @param surface_dr [Float] the surface door to wall ratio
|
6906
|
+
# @param wwr_building_type[String] building type for wwr
|
6907
|
+
# @param wwr_target [Float] target window to wall ratio
|
6908
|
+
# @param total_wall_m2 [Float] total wall area of the category in m2.
|
6909
|
+
# @param total_wall_with_fene_m2 [Float] total wall area of the category with fenestrations in m2.
|
6910
|
+
# @param total_fene_m2 [Float] total fenestration area
|
6911
|
+
# @param total_plenum_wall_m2 [Float] total sqaure meter of a plenum
|
6912
|
+
# @return [Float] reduction factor
|
6913
|
+
def model_get_wwr_reduction_ratio(multiplier,
|
6914
|
+
surface_wwr: 0.0,
|
6915
|
+
surface_dr: 0.0,
|
6916
|
+
wwr_building_type: 'All others',
|
6917
|
+
wwr_target: 0.0,
|
6918
|
+
total_wall_m2: 0.0,
|
6919
|
+
total_wall_with_fene_m2: 0.0,
|
6920
|
+
total_fene_m2: 0.0,
|
6921
|
+
total_plenum_wall_m2: 0.0)
|
6922
|
+
return 1.0 - multiplier
|
6923
|
+
end
|
6924
|
+
|
6925
|
+
# A template method that handles the loading of user input data from multiple sources
|
6926
|
+
# include data source from:
|
6927
|
+
# 1. user data csv files
|
6928
|
+
# 2. data from measure and OpenStudio interface
|
6929
|
+
# @param [Openstudio:model:Model] model
|
6930
|
+
# @param [String] climate_zone
|
6931
|
+
# @param [String] default_hvac_building_type
|
6932
|
+
# @param [String] default_wwr_building_type
|
6933
|
+
# @param [String] default_swh_building_type
|
6934
|
+
# @param [Hash] bldg_type_hvac_zone_hash A hash maps building type for hvac to a list of thermal zones
|
6935
|
+
# @return True
|
6936
|
+
def handle_user_input_data(model, climate_zone, default_hvac_building_type, default_wwr_building_type, default_swh_building_type, bldg_type_hvac_zone_hash)
|
6937
|
+
return true
|
6938
|
+
end
|
6939
|
+
|
6940
|
+
# Template method for adding a setpoint manager for a coil control logic to a heating coil.
|
6941
|
+
# ASHRAE 90.1-2019 Appendix G.
|
6942
|
+
#
|
6943
|
+
# @param model [OpenStudio::Model::Model] Openstudio model
|
6944
|
+
# @param thermalZones Array([OpenStudio::Model::ThermalZone]) thermal zone array
|
6945
|
+
# @param coil Heating Coils
|
6946
|
+
# @return [Boolean] true
|
6947
|
+
def model_set_central_preheat_coil_spm(model, thermal_zones, coil)
|
6948
|
+
return true
|
6949
|
+
end
|
6950
|
+
|
6951
|
+
# Template method for adding zone additional property "zone DCV implemented in user model"
|
6952
|
+
#
|
6953
|
+
# @author Xuechen (Jerry) Lei, PNNL
|
6954
|
+
# @param model [OpenStudio::Model::Model] Openstudio model
|
6955
|
+
def model_mark_zone_dcv_existence(model)
|
6956
|
+
return true
|
6957
|
+
end
|
6958
|
+
|
6959
|
+
# Check whether the baseline model generation needs to run all four orientations
|
6960
|
+
# The default shall be true
|
6961
|
+
#
|
6962
|
+
# @param [Boolean] run_all_orients: user inputs to indicate whether it is required to run all orientations
|
6963
|
+
# @param [OpenStudio::Model::Model] Openstudio model
|
6964
|
+
def run_all_orientations(run_all_orients, user_model)
|
6965
|
+
return run_all_orients
|
6966
|
+
end
|
6967
|
+
|
6968
|
+
# Template method for reading user data and adding to zone additional properties
|
6969
|
+
#
|
6970
|
+
# @author Xuechen (Jerry) Lei, PNNL
|
6971
|
+
# @param model [OpenStudio::Model::Model] Openstudio model
|
6972
|
+
def model_add_dcv_user_exception_properties(model)
|
6973
|
+
return true
|
6974
|
+
end
|
6975
|
+
|
6976
|
+
# Template method for raising user model DCV warning and errors
|
6977
|
+
#
|
6978
|
+
# @author Xuechen (Jerry) Lei, PNNL
|
6979
|
+
# @param model [OpenStudio::Model::Model] Openstudio model
|
6980
|
+
def model_raise_user_model_dcv_errors(model)
|
6981
|
+
return true
|
6982
|
+
end
|
6983
|
+
|
6984
|
+
# Template method for adding zone additional property "airloop dcv required by 901" and "zone dcv required by 901"
|
6985
|
+
#
|
6986
|
+
# @author Xuechen (Jerry) Lei, PNNL
|
6987
|
+
# @param model [OpenStudio::Model::Model] Openstudio model
|
6988
|
+
def model_add_dcv_requirement_properties(model)
|
6989
|
+
return true
|
6990
|
+
end
|
6991
|
+
|
6992
|
+
# Template method for checking if zones in the baseline model should have DCV based on 90.1 2019 G3.1.2.5.
|
6993
|
+
# Zone additional property 'apxg no need to have DCV' added
|
6994
|
+
#
|
6995
|
+
# @author Xuechen (Jerry) Lei, PNNL
|
6996
|
+
# @param model [OpenStudio::Model::Model] Openstudio model
|
6997
|
+
def model_add_apxg_dcv_properties(model)
|
6998
|
+
return true
|
6999
|
+
end
|
7000
|
+
|
7001
|
+
# Template method for setting DCV in baseline HVAC system if required
|
7002
|
+
#
|
7003
|
+
# @author Xuechen (Jerry) Lei, PNNL
|
7004
|
+
# @param model [OpenStudio::Model::Model] Openstudio model
|
7005
|
+
def model_set_baseline_demand_control_ventilation(model, climate_zone)
|
7006
|
+
return true
|
7007
|
+
end
|
7008
|
+
|
7009
|
+
# Identify the return air type associated with each thermal zone
|
7010
|
+
#
|
7011
|
+
# @param model [OpenStudio::Model::Model] Openstudio model object
|
7012
|
+
def model_identify_return_air_type(model)
|
7013
|
+
# air-loop based system
|
7014
|
+
model.getThermalZones.each do |zone|
|
7015
|
+
# Conditioning category won't include indirectly conditioned thermal zones
|
7016
|
+
cond_cat = thermal_zone_conditioning_category(zone, model_standards_climate_zone(model))
|
7017
|
+
|
7018
|
+
# Initialize the return air type
|
7019
|
+
return_air_type = nil
|
7020
|
+
|
7021
|
+
# The thermal zone is conditioned by zonal system
|
7022
|
+
if (cond_cat != 'Unconditioned') && zone.airLoopHVACs.empty?
|
7023
|
+
return_air_type = 'ducted_return_or_direct_to_unit'
|
7024
|
+
end
|
7025
|
+
|
7026
|
+
# Assume that the primary heating and cooling (PHC) system
|
7027
|
+
# is last in the heating and cooling order (ignore DOAS)
|
7028
|
+
#
|
7029
|
+
# Get the heating and cooling PHC components
|
7030
|
+
heating_equipment = zone.equipmentInHeatingOrder[-1]
|
7031
|
+
cooling_equipment = zone.equipmentInCoolingOrder[-1]
|
7032
|
+
if heating_equipment.nil? && cooling_equipment.nil?
|
7033
|
+
next
|
7034
|
+
end
|
7035
|
+
|
7036
|
+
unless heating_equipment.nil?
|
7037
|
+
if heating_equipment.to_ZoneHVACComponent.is_initialized
|
7038
|
+
heating_equipment_type = 'ZoneHVACComponent'
|
7039
|
+
elsif heating_equipment.to_StraightComponent.is_initialized
|
7040
|
+
heating_equipment_type = 'StraightComponent'
|
7041
|
+
end
|
7042
|
+
end
|
7043
|
+
unless cooling_equipment.nil?
|
7044
|
+
if cooling_equipment.to_ZoneHVACComponent.is_initialized
|
7045
|
+
cooling_equipment_type = 'ZoneHVACComponent'
|
7046
|
+
elsif cooling_equipment.to_StraightComponent.is_initialized
|
7047
|
+
cooling_equipment_type = 'StraightComponent'
|
7048
|
+
end
|
7049
|
+
end
|
7050
|
+
|
7051
|
+
# Determine return configuration
|
7052
|
+
if (heating_equipment_type == 'ZoneHVACComponent') && (cooling_equipment_type == 'ZoneHVACComponent')
|
7053
|
+
return_air_type = 'ducted_return_or_direct_to_unit'
|
7054
|
+
else
|
7055
|
+
# Check heating air loop first
|
7056
|
+
unless heating_equipment.nil?
|
7057
|
+
if heating_equipment.to_StraightComponent.is_initialized
|
7058
|
+
air_loop = heating_equipment.to_StraightComponent.get.airLoopHVAC.get
|
7059
|
+
return_plenum = air_loop_hvac_return_air_plenum(air_loop)
|
7060
|
+
return_air_type = return_plenum.nil? ? 'ducted_return_or_direct_to_unit' : 'return_plenum'
|
7061
|
+
return_plenum = return_plenum.nil? ? nil : return_plenum.name.to_s
|
7062
|
+
end
|
7063
|
+
end
|
7064
|
+
|
7065
|
+
# Check cooling air loop second; Assume that return air plenum is the dominant case
|
7066
|
+
unless cooling_equipment.nil?
|
7067
|
+
if (return_air_type != 'return_plenum') && cooling_equipment.to_StraightComponent.is_initialized
|
7068
|
+
air_loop = cooling_equipment.to_StraightComponent.get.airLoopHVAC.get
|
7069
|
+
return_plenum = air_loop_hvac_return_air_plenum(air_loop)
|
7070
|
+
return_air_type = return_plenum.nil? ? 'ducted_return_or_direct_to_unit' : 'return_plenum'
|
7071
|
+
return_plenum = return_plenum.nil? ? nil : return_plenum.name.to_s
|
7072
|
+
end
|
7073
|
+
end
|
7074
|
+
end
|
7075
|
+
|
7076
|
+
# Catch all
|
7077
|
+
if return_air_type.nil?
|
7078
|
+
return_air_type = 'ducted_return_or_direct_to_unit'
|
7079
|
+
end
|
7080
|
+
|
7081
|
+
zone.additionalProperties.setFeature('return_air_type', return_air_type)
|
7082
|
+
zone.additionalProperties.setFeature('plenum', return_plenum) unless return_plenum.nil?
|
7083
|
+
zone.additionalProperties.setFeature('proposed_model_zone_design_air_flow', zone.designAirFlowRate.to_f)
|
7084
|
+
end
|
7085
|
+
end
|
7086
|
+
|
7087
|
+
# Determine the baseline return air type associated with each zone
|
7088
|
+
#
|
7089
|
+
# @param model [OpenStudio::Model::model] OpenStudio model object
|
7090
|
+
# @param baseline_system_type [String] Baseline system type name
|
7091
|
+
# @param zones [Array] List of zone associated with a system
|
7092
|
+
# @return [Array] Array of length 2, the first item is the name
|
7093
|
+
# of the plenum zone and the second the return air type
|
7094
|
+
def model_determine_baseline_return_air_type(model, baseline_system_type, zones)
|
7095
|
+
return ['', 'ducted_return_or_direct_to_unit'] unless ['PSZ_AC', 'PSZ_HP', 'PVAV_Reheat', 'PVAV_PFP_Boxes', 'VAV_Reheat', 'VAV_PFP_Boxes', 'SZ_VAV', 'SZ_CV'].include?(baseline_system_type)
|
7096
|
+
|
7097
|
+
zone_return_air_type = {}
|
7098
|
+
zones.each do |zone|
|
7099
|
+
if zone.additionalProperties.hasFeature('proposed_model_zone_design_air_flow')
|
7100
|
+
zone_design_air_flow = zone.additionalProperties.getFeatureAsDouble('proposed_model_zone_design_air_flow').get
|
7101
|
+
|
7102
|
+
if zone.additionalProperties.hasFeature('return_air_type')
|
7103
|
+
return_air_type = zone.additionalProperties.getFeatureAsString('return_air_type').get
|
7104
|
+
|
7105
|
+
if zone_return_air_type.keys.include?(return_air_type)
|
7106
|
+
zone_return_air_type[return_air_type] += zone_design_air_flow
|
7107
|
+
else
|
7108
|
+
zone_return_air_type[return_air_type] = zone_design_air_flow
|
7109
|
+
end
|
7110
|
+
|
7111
|
+
if zone.additionalProperties.hasFeature('plenum')
|
7112
|
+
plenum = zone.additionalProperties.getFeatureAsString('plenum').get
|
7113
|
+
|
7114
|
+
if zone_return_air_type.keys.include?('plenum')
|
7115
|
+
if zone_return_air_type['plenum'].keys.include?(plenum)
|
7116
|
+
zone_return_air_type['plenum'][plenum] += zone_design_air_flow
|
7117
|
+
end
|
7118
|
+
else
|
7119
|
+
zone_return_air_type['plenum'] = { plenum => zone_design_air_flow }
|
7120
|
+
end
|
7121
|
+
end
|
7122
|
+
end
|
7123
|
+
end
|
7124
|
+
end
|
7125
|
+
|
7126
|
+
# Find dominant zone return air type and plenum zone
|
7127
|
+
# if the return air type is return air plenum
|
7128
|
+
return_air_types = zone_return_air_type.keys - ['plenum']
|
7129
|
+
return_air_types_score = 0
|
7130
|
+
return_air_type = nil
|
7131
|
+
plenum_score = 0
|
7132
|
+
plenum = nil
|
7133
|
+
return_air_types.each do |return_type|
|
7134
|
+
if zone_return_air_type[return_type] > return_air_types_score
|
7135
|
+
return_air_type = return_type
|
7136
|
+
return_air_types_score = zone_return_air_type[return_type]
|
7137
|
+
end
|
7138
|
+
if return_air_type == 'return_plenum'
|
7139
|
+
zone_return_air_type['plenum'].keys.each do |p|
|
7140
|
+
if zone_return_air_type['plenum'][p] > plenum_score
|
7141
|
+
plenum = p
|
7142
|
+
plenum_score = zone_return_air_type['plenum'][p]
|
7143
|
+
end
|
7144
|
+
end
|
7145
|
+
end
|
7146
|
+
end
|
7147
|
+
|
7148
|
+
return plenum, return_air_type
|
7149
|
+
end
|
7150
|
+
|
7151
|
+
# Add reporting tolerances. Default values are based on the suggestions from the PRM-RM.
|
7152
|
+
#
|
7153
|
+
# @param model [OpenStudio::Model::Model] OpenStudio Model
|
7154
|
+
# @param heating_tolerance_deg_f [Float] Tolerance for time heating setpoint not met in degree F
|
7155
|
+
# @param cooling_tolerance_deg_f [Float] Tolerance for time cooling setpoint not met in degree F
|
7156
|
+
def model_add_reporting_tolerances(model, heating_tolerance_deg_f: 1.0, cooling_tolerance_deg_f: 1.0)
|
7157
|
+
reporting_tolerances = model.getOutputControlReportingTolerances
|
7158
|
+
heating_tolerance_deg_c = OpenStudio.convert(heating_tolerance_deg_f, 'R', 'K').get
|
7159
|
+
cooling_tolerance_deg_c = OpenStudio.convert(cooling_tolerance_deg_f, 'R', 'K').get
|
7160
|
+
reporting_tolerances.setToleranceforTimeHeatingSetpointNotMet(heating_tolerance_deg_c)
|
7161
|
+
reporting_tolerances.setToleranceforTimeCoolingSetpointNotMet(cooling_tolerance_deg_c)
|
7162
|
+
|
7163
|
+
return true
|
7164
|
+
end
|
7165
|
+
|
7166
|
+
# Apply the standard construction to each surface in the model, based on the construction type currently assigned.
|
7167
|
+
#
|
7168
|
+
# @return [Bool] true if successful, false if not
|
7169
|
+
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
7170
|
+
# @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
|
7171
|
+
# @return [Bool] returns true if successful, false if not
|
7172
|
+
def model_apply_constructions(model, climate_zone, wwr_building_type, wwr_info)
|
7173
|
+
model_apply_standard_constructions(model, climate_zone, wwr_building_type: nil, wwr_info: {})
|
7174
|
+
|
7175
|
+
return true
|
7176
|
+
end
|
7177
|
+
|
7178
|
+
# Generate baseline log to a specific file directory
|
7179
|
+
# @param file_directory [String] file directory
|
7180
|
+
def generate_baseline_log(file_directory)
|
7181
|
+
return true
|
7182
|
+
end
|
7183
|
+
|
7184
|
+
# Update ground temperature profile based on the weather file specified in the model
|
7185
|
+
#
|
7186
|
+
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
7187
|
+
# @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
|
7188
|
+
# @return [Bool] returns true if successful, false if not
|
7189
|
+
def model_update_ground_temperature_profile(model, climate_zone)
|
7190
|
+
return true
|
7191
|
+
end
|
5824
7192
|
end
|