openstudio-standards 0.8.5 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/test_performance_expected_dd_results.csv +90 -90
  3. data/lib/openstudio-standards/btap/activity.rb +858 -61
  4. data/lib/openstudio-standards/btap/attributes.rb +297 -103
  5. data/lib/openstudio-standards/btap/bridging.rb +1335 -593
  6. data/lib/openstudio-standards/btap/btap.rb +60 -181
  7. data/lib/openstudio-standards/btap/carbon/btap_carbon.rb +199 -142
  8. data/lib/openstudio-standards/btap/common/constructions.json +1275 -0
  9. data/lib/openstudio-standards/btap/common/costs.csv +1969 -0
  10. data/lib/openstudio-standards/btap/common/costs_local_factors.csv +2315 -0
  11. data/lib/openstudio-standards/btap/common/materials_glazing.csv +61 -0
  12. data/lib/openstudio-standards/btap/{common_resources → common}/materials_hvac.csv +1685 -1685
  13. data/lib/openstudio-standards/btap/{common_resources → common}/materials_lighting.csv +266 -266
  14. data/lib/openstudio-standards/btap/common/materials_opaque.csv +214 -0
  15. data/lib/openstudio-standards/btap/common/necb_building_types.csv +35 -0
  16. data/lib/openstudio-standards/btap/common/necb_space_types.csv +110 -0
  17. data/lib/openstudio-standards/btap/common/thermal_bridging.csv +73 -0
  18. data/lib/openstudio-standards/btap/costing/btap_costing.rb +346 -429
  19. data/lib/openstudio-standards/btap/costing/database.rb +104 -0
  20. data/lib/openstudio-standards/btap/costing/daylighting_sensor_control_costing.rb +356 -344
  21. data/lib/openstudio-standards/btap/costing/dcv_costing.rb +282 -296
  22. data/lib/openstudio-standards/btap/costing/envelope_costing.rb +179 -192
  23. data/lib/openstudio-standards/btap/costing/heating_cooling_costing.rb +2477 -2316
  24. data/lib/openstudio-standards/btap/costing/led_lighting_costing.rb +127 -142
  25. data/lib/openstudio-standards/btap/costing/lighting_costing.rb +216 -198
  26. data/lib/openstudio-standards/btap/costing/nv_costing.rb +522 -523
  27. data/lib/openstudio-standards/btap/costing/pv_ground_costing.rb +649 -649
  28. data/lib/openstudio-standards/btap/costing/shw_costing.rb +626 -625
  29. data/lib/openstudio-standards/btap/costing/thermal_bridging_costing.rb +72 -0
  30. data/lib/openstudio-standards/btap/costing/ventilation_costing.rb +2363 -2361
  31. data/lib/openstudio-standards/btap/deprecated.rb +1033 -0
  32. data/lib/openstudio-standards/btap/envelope.rb +313 -572
  33. data/lib/openstudio-standards/btap/fileio.rb +152 -1070
  34. data/lib/openstudio-standards/btap/geometry.rb +486 -766
  35. data/lib/openstudio-standards/btap/linear_regression.rb +130 -0
  36. data/lib/openstudio-standards/btap/paths.rb +46 -0
  37. data/lib/openstudio-standards/btap/qaqc/analysis.rb +163 -0
  38. data/lib/openstudio-standards/btap/qaqc/data.rb +2293 -0
  39. data/lib/openstudio-standards/btap/qaqc/datapoint.rb +641 -0
  40. data/lib/openstudio-standards/btap/schedules.rb +11 -611
  41. data/lib/openstudio-standards/btap/structure.rb +574 -189
  42. data/lib/openstudio-standards/constructions/materials/information.rb +1 -1
  43. data/lib/openstudio-standards/create_typical/create_typical.rb +1 -1
  44. data/lib/openstudio-standards/exterior_lighting/create.rb +135 -85
  45. data/lib/openstudio-standards/exterior_lighting/data/typical_exterior_lighting.json +20 -0
  46. data/lib/openstudio-standards/exterior_lighting/information.rb +23 -2
  47. data/lib/openstudio-standards/geometry/create.rb +7 -3
  48. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +3 -2
  49. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +3 -2
  50. data/lib/openstudio-standards/prototypes/common/data/thermostat_schedule_lookup.json +84 -84
  51. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +4 -4
  52. data/lib/openstudio-standards/refrigeration/create_walkin.rb +45 -5
  53. data/lib/openstudio-standards/refrigeration/data/refrigerated_walkins.csv +75 -75
  54. data/lib/openstudio-standards/schedules/parametric.rb +6 -0
  55. data/lib/openstudio-standards/service_water_heating/create_typical.rb +10 -3
  56. data/lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb +30 -14
  57. data/lib/openstudio-standards/service_water_heating/information.rb +4 -1
  58. data/lib/openstudio-standards/sql_file/unmet_hours.rb +2 -1
  59. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +3 -1
  60. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +3 -1
  61. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.boilers.json +12 -0
  62. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_properties.json +18432 -3072
  63. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.boilers.json +26 -0
  64. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.construction_properties.json +8532 -1422
  65. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.boilers.json +26 -0
  66. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.construction_properties.json +8532 -1422
  67. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.boilers.json +16 -0
  68. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.computer_room_acs.json +30 -30
  69. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_properties.json +7560 -1260
  70. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.PumpVariableSpeed.rb +1 -2
  71. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.boilers.json +16 -0
  72. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_properties.json +7560 -1260
  73. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +3 -3
  74. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.PumpVariableSpeed.rb +1 -2
  75. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.boilers.json +22 -0
  76. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_properties.json +7236 -1206
  77. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.furnaces.json +223 -19
  78. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.schedules.json +37 -1
  79. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.boilers.json +3 -0
  80. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.construction_properties.json +6900 -1150
  81. data/lib/openstudio-standards/standards/cbes/data/cbes.schedules.json +1 -1
  82. data/lib/openstudio-standards/standards/deer/data/deer.schedules.json +1 -1
  83. data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/surface_thermal_transmittance.json +76 -40
  84. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/surface_thermal_transmittance.json +76 -40
  85. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +6 -4
  86. data/lib/openstudio-standards/standards/necb/NECB2011/NECB2011_QAQC.json +2988 -0
  87. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +25 -3
  88. data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +672 -534
  89. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Hospital.osm +1791 -1792
  90. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPMultiTower.osm +7 -1
  91. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPTownHouse.osm +7 -1
  92. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeHotel.osm +5 -6
  93. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeOffice.osm +8 -9
  94. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +10 -4
  95. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +14 -15
  96. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Outpatient.osm +7 -1
  97. data/lib/openstudio-standards/standards/necb/NECB2011/data/surface_thermal_transmittance.json +76 -40
  98. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +14 -6
  99. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +542 -130
  100. data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +30 -63
  101. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +7 -2
  102. data/lib/openstudio-standards/standards/necb/NECB2015/data/surface_thermal_transmittance.json +90 -0
  103. data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +8 -6
  104. data/lib/openstudio-standards/standards/necb/NECB2017/data/surface_thermal_transmittance.json +76 -40
  105. data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +2 -1
  106. data/lib/openstudio-standards/standards/necb/NECB2020/building_envelope.rb +0 -117
  107. data/lib/openstudio-standards/standards/necb/NECB2020/data/surface_thermal_transmittance.json +76 -45
  108. data/lib/openstudio-standards/standards/necb/NECB2020/necb_2020.rb +218 -0
  109. data/lib/openstudio-standards/standards/necb/common/construction_defaults.osm +12 -7
  110. data/lib/openstudio-standards/standards/standard.rb +1 -1
  111. data/lib/openstudio-standards/version.rb +1 -1
  112. data/lib/openstudio-standards.rb +9 -4
  113. metadata +59 -66
  114. data/data/inventory/thermal_bridging.csv +0 -90
  115. data/data/standards/OpenStudio_Standards-ashrae_90_1-ALL-comstock(space_types).xlsx +0 -0
  116. data/data/standards/openstudio_standards_duplicates_log.csv +0 -7962
  117. data/lib/openstudio-standards/btap/NECB_building_types.csv +0 -35
  118. data/lib/openstudio-standards/btap/NECB_space_types.csv +0 -109
  119. data/lib/openstudio-standards/btap/WeatherData1.json +0 -2110
  120. data/lib/openstudio-standards/btap/WeatherData1.xlsx +0 -0
  121. data/lib/openstudio-standards/btap/btap_result.rb +0 -1144
  122. data/lib/openstudio-standards/btap/btap_test_helper.rb +0 -86
  123. data/lib/openstudio-standards/btap/common_paths.rb +0 -81
  124. data/lib/openstudio-standards/btap/common_resources/ConstructionProperties.csv +0 -52
  125. data/lib/openstudio-standards/btap/common_resources/Constructions.csv +0 -37
  126. data/lib/openstudio-standards/btap/common_resources/construction_sets.csv +0 -1270
  127. data/lib/openstudio-standards/btap/common_resources/constructions_glazing.csv +0 -121
  128. data/lib/openstudio-standards/btap/common_resources/constructions_opaque.csv +0 -2256
  129. data/lib/openstudio-standards/btap/common_resources/costs.csv +0 -1897
  130. data/lib/openstudio-standards/btap/common_resources/costs_local_factors.csv +0 -2315
  131. data/lib/openstudio-standards/btap/common_resources/materials_glazing.csv +0 -35
  132. data/lib/openstudio-standards/btap/common_resources/materials_opaque.csv +0 -164
  133. data/lib/openstudio-standards/btap/costing/btap_measure_helper.rb +0 -359
  134. data/lib/openstudio-standards/btap/costing/btap_workflow.rb +0 -117
  135. data/lib/openstudio-standards/btap/costing/copy_test_results_files_to_expected_results.rb +0 -11
  136. data/lib/openstudio-standards/btap/costing/cost_building_from_file.rb +0 -136
  137. data/lib/openstudio-standards/btap/costing/costing_database_wrapper.rb +0 -173
  138. data/lib/openstudio-standards/btap/costing/dummy.epw +0 -8768
  139. data/lib/openstudio-standards/btap/costing/dummy.osm +0 -5320
  140. data/lib/openstudio-standards/btap/costing/neb_end_use_prices.csv +0 -42
  141. data/lib/openstudio-standards/btap/costing/necb_2011_spacetype_info.csv +0 -225
  142. data/lib/openstudio-standards/btap/costing/necb_reference_runs.csv +0 -28705
  143. data/lib/openstudio-standards/btap/costing/parallel_tests.rb +0 -92
  144. data/lib/openstudio-standards/btap/costing/test_list.txt +0 -15
  145. data/lib/openstudio-standards/btap/costing/test_run_all_test_locally.rb +0 -27
  146. data/lib/openstudio-standards/btap/costing/test_run_costing_tests.rb +0 -80
  147. data/lib/openstudio-standards/btap/necb_2011_spacetype_info.csv +0 -225
  148. data/lib/openstudio-standards/btap/utilities/Add_template_field_to_json.rb +0 -22
  149. data/lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb +0 -204
  150. data/lib/openstudio-standards/btap/utilities/generate_space_types.rb +0 -8
  151. data/lib/openstudio-standards/btap/utilities/necb_to_epw_map.rb +0 -50
  152. data/lib/openstudio-standards/btap/utilities/os_sim_extract.rb +0 -48
  153. data/lib/openstudio-standards/btap/utilities/rename_surfaces.rb +0 -24
  154. data/lib/openstudio-standards/btap/utilities/round_surf_coords.rb +0 -32
  155. data/lib/openstudio-standards/btap/utilities/sched_create.rb +0 -145
  156. data/lib/openstudio-standards/btap/utilities/set_mult_to_adiabatic.rb +0 -53
  157. data/lib/openstudio-standards/btap/utilities/space_type.rb +0 -78
  158. data/lib/openstudio-standards/btap/utilities/weatherData1_xlsx_to_json.rb +0 -56
  159. data/lib/openstudio-standards/btap/vintagizer.rb +0 -161
  160. data/lib/openstudio-standards/btap/visualization.rb +0 -72
  161. data/lib/openstudio-standards/standards/necb/NECB2011/data/necb_2011_spacetype_info.csv +0 -225
  162. data/lib/openstudio-standards/standards/necb/common/btap_analysis.rb +0 -79
  163. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +0 -2656
  164. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +0 -653
  165. data/lib/openstudio-standards/standards/necb/common/necb_reference_runs.csv +0 -28705
  166. data/lib/openstudio-standards/standards/necb/common/utility_pricing_2025-02-20.csv +0 -92
  167. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_frame.csv +0 -0
  168. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_glazing.csv +0 -0
  169. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_opaque.csv +0 -0
  170. /data/lib/openstudio-standards/btap/{common_resources → common}/hvac_vent_ahu.csv +0 -0
  171. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting.csv +0 -0
  172. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting_sets.csv +0 -0
  173. /data/lib/openstudio-standards/btap/{common_resources → common}/locations.csv +0 -0
  174. /data/lib/openstudio-standards/btap/{costing → common}/mech_sizing.json +0 -0
@@ -293,6 +293,7 @@ class NECB2011 < Standard
293
293
  scale_x: nil,
294
294
  scale_y: nil,
295
295
  scale_z: nil,
296
+ footprint_aspect_ratio: nil,
296
297
  pv_ground_type: nil,
297
298
  pv_ground_total_area_pv_panels_m2: nil,
298
299
  pv_ground_tilt_angle: nil,
@@ -366,6 +367,7 @@ class NECB2011 < Standard
366
367
  scale_x: scale_x,
367
368
  scale_y: scale_y,
368
369
  scale_z: scale_z,
370
+ footprint_aspect_ratio: footprint_aspect_ratio,
369
371
  pv_ground_type: pv_ground_type, # Two options: (1) nil/none/false/'NECB_Default', (2) 'add_pv_ground'
370
372
  pv_ground_total_area_pv_panels_m2: pv_ground_total_area_pv_panels_m2, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. building footprint), (3) area value (e.g. 50)
371
373
  pv_ground_tilt_angle: pv_ground_tilt_angle, # Options: (1) nil/none/false, (2) 'NECB_Default' (i.e. latitude), (3) tilt angle value (e.g. 20)
@@ -402,7 +404,7 @@ class NECB2011 < Standard
402
404
  # Created this method so that additional methods can be addded for bulding the prototype model in later
403
405
  # code versions without modifying the build_protoype_model method or copying it wholesale for a few changes.
404
406
  def model_apply_standard(model:,
405
- construction_opt: '',
407
+ construction_opt: 'structure',
406
408
  tbd_option: 'none',
407
409
  tbd_interpolate: true,
408
410
  epw_file:,
@@ -450,6 +452,7 @@ class NECB2011 < Standard
450
452
  scale_x: nil,
451
453
  scale_y: nil,
452
454
  scale_z: nil,
455
+ footprint_aspect_ratio: nil,
453
456
  nv_type: nil,
454
457
  nv_opening_fraction: nil,
455
458
  nv_temp_out_min: nil,
@@ -475,6 +478,7 @@ class NECB2011 < Standard
475
478
  airloop_fancoils_heating: nil,
476
479
  oerd_utility_pricing: nil)
477
480
 
481
+ oerd_utility_pricing = false
478
482
  apply_weather_data(model: model,
479
483
  epw_file: epw_file,
480
484
  custom_weather_folder: custom_weather_folder,
@@ -482,20 +486,38 @@ class NECB2011 < Standard
482
486
  primary_heating_fuel = validate_primary_heating_fuel(primary_heating_fuel: primary_heating_fuel, model: model)
483
487
  self.fuel_type_set = SystemFuels.new()
484
488
  self.fuel_type_set.set_defaults(standards_data: @standards_data, primary_heating_fuel: primary_heating_fuel)
485
- clean_and_scale_model(model: model, rotation_degrees: rotation_degrees, scale_x: scale_x, scale_y: scale_y, scale_z: scale_z)
486
- fdwr_set = convert_arg_to_f(variable: fdwr_set, default: -1)
487
- srr_set = convert_arg_to_f(variable: srr_set, default: -1)
488
- srr_opt = convert_arg_to_string(variable: srr_opt, default: '')
489
- construction_opt = convert_arg_to_string(variable: construction_opt, default: '')
490
- massive = construction_opt == 'structure'
491
- tbd_option = convert_arg_to_string(variable: tbd_option, default: 'none')
492
- tbd_interpolate = convert_arg_to_bool(variable: tbd_interpolate, default: true)
493
- necb_hdd = convert_arg_to_bool(variable: necb_hdd, default: true)
494
- boiler_fuel = convert_arg_to_string(variable: boiler_fuel, default: nil)
495
- boiler_cap_ratio = convert_arg_to_string(variable: boiler_cap_ratio, default: nil)
496
- swh_fuel = convert_arg_to_string(variable: swh_fuel, default: nil)
489
+ clean_and_scale_model(model: model,
490
+ rotation_degrees: rotation_degrees,
491
+ scale_x: scale_x,
492
+ scale_y: scale_y,
493
+ scale_z: scale_z,
494
+ footprint_aspect_ratio: footprint_aspect_ratio)
495
+
496
+ ext_floor_cond = convert_arg_to_f(variable: ext_floor_cond, default: nil)
497
+ ext_roof_cond = convert_arg_to_f(variable: ext_roof_cond, default: nil)
498
+ ext_wall_cond = convert_arg_to_f(variable: ext_wall_cond, default: nil)
499
+ ground_floor_cond = convert_arg_to_f(variable: ground_floor_cond, default: nil)
500
+ ground_roof_cond = convert_arg_to_f(variable: ground_roof_cond, default: nil)
501
+ ground_wall_cond = convert_arg_to_f(variable: ground_wall_cond, default: nil)
502
+ door_construction_cond = convert_arg_to_f(variable: door_construction_cond, default: nil)
503
+ fixed_window_cond = convert_arg_to_f(variable: fixed_window_cond, default: nil)
504
+ fixed_wind_solar_trans = convert_arg_to_f(variable: fixed_wind_solar_trans, default: nil)
505
+ skylight_solar_trans = convert_arg_to_f(variable: skylight_solar_trans, default: nil)
506
+ glass_door_solar_trans = convert_arg_to_f(variable: glass_door_solar_trans, default: nil)
507
+ fdwr_set = convert_arg_to_f(variable: fdwr_set, default: -1)
508
+ srr_set = convert_arg_to_f(variable: srr_set, default: -1)
509
+ srr_opt = convert_arg_to_string(variable: srr_opt, default: '')
510
+ construction_opt = convert_arg_to_string(variable: construction_opt, default: 'structure')
511
+ tbd_option = convert_arg_to_string(variable: tbd_option, default: 'none')
512
+ tbd_interpolate = convert_arg_to_bool(variable: tbd_interpolate, default: true)
513
+ necb_hdd = convert_arg_to_bool(variable: necb_hdd, default: true)
514
+ boiler_fuel = convert_arg_to_string(variable: boiler_fuel, default: nil)
515
+ boiler_cap_ratio = convert_arg_to_string(variable: boiler_cap_ratio, default: nil)
516
+ swh_fuel = convert_arg_to_string(variable: swh_fuel, default: nil)
497
517
  airloop_fancoils_heating = convert_arg_to_bool(variable: airloop_fancoils_heating, default: false)
498
518
 
519
+ massive = construction_opt == 'structure'
520
+
499
521
  # Check if custom systems are assigned to dwelling units, washrooms, corridors, and storage rooms. If they are, set
500
522
  # them to be the same as the primary system type. If no primary system type is defined then set them to be nil.
501
523
  hvac_system_dwelling_units, hvac_system_corridor, hvac_system_storage, hvac_system_washrooms = reset_hvac_system_if_required(hvac_system_primary: hvac_system_primary,
@@ -517,7 +539,7 @@ class NECB2011 < Standard
517
539
 
518
540
  output_meters = check_output_meters(output_meters: output_meters) if oerd_utility_pricing
519
541
 
520
- assign_building_activity(model: model)
542
+ assign_building_activity(model: model, option: construction_opt)
521
543
  assign_building_structure(model: model, activity: @activity, massive: massive)
522
544
  apply_loads(model: model,
523
545
  lights_type: lights_type,
@@ -527,8 +549,6 @@ class NECB2011 < Standard
527
549
  oa_scale: oa_scale)
528
550
  apply_envelope(model: model,
529
551
  construction_opt: construction_opt,
530
- bldg_category: @activity.category,
531
- bldg_structure: @structure.structure,
532
552
  ext_wall_cond: ext_wall_cond,
533
553
  ext_floor_cond: ext_floor_cond,
534
554
  ext_roof_cond: ext_roof_cond,
@@ -550,6 +570,7 @@ class NECB2011 < Standard
550
570
  srr_set: srr_set,
551
571
  srr_opt: srr_opt,
552
572
  necb_hdd: necb_hdd)
573
+ set_construction_air_film_resistances(model: model)
553
574
  apply_thermal_bridging(model: model,
554
575
  necb_hdd: necb_hdd,
555
576
  structure: @structure,
@@ -562,7 +583,7 @@ class NECB2011 < Standard
562
583
  sizing_run_dir: sizing_run_dir,
563
584
  lights_type: lights_type,
564
585
  lights_scale: lights_scale)
565
- apply_kiva_foundation(model, massive)
586
+ apply_kiva_foundation(model)
566
587
  apply_systems_and_efficiencies(model: model,
567
588
  sizing_run_dir: sizing_run_dir,
568
589
  hvac_system_primary: hvac_system_primary,
@@ -600,7 +621,7 @@ class NECB2011 < Standard
600
621
  end
601
622
 
602
623
  # This method cleans the model of any existing HVAC systems and applies any desired ratation or scaling to the model.
603
- def clean_and_scale_model(model:, rotation_degrees: nil, scale_x: nil, scale_y: nil, scale_z: nil)
624
+ def clean_and_scale_model(model:, rotation_degrees: nil, scale_x: nil, scale_y: nil, scale_z: nil, footprint_aspect_ratio: nil)
604
625
  # clean model..
605
626
  BTAP::Resources::Envelope::remove_all_envelope_information(model)
606
627
  model = OpenstudioStandards::HVAC.remove_all_hvac(model)
@@ -624,9 +645,6 @@ class NECB2011 < Standard
624
645
  model.getSpaces.sort.each { |space| space.designSpecificationOutdoorAir(&:remove) }
625
646
  model.getThermostatSetpointDualSetpoints.each(&:remove)
626
647
 
627
- scale_x = 1.0
628
- scale_y = 1.0
629
- scale_z = 1.0
630
648
  # Rotate to model if requested
631
649
  rotation_degrees = convert_arg_to_f(variable: rotation_degrees, default: 0.0)
632
650
  BTAP::Geometry.rotate_building(model: model, degrees: rotation_degrees) unless rotation_degrees == 0.0
@@ -635,9 +653,10 @@ class NECB2011 < Standard
635
653
  scale_x = convert_arg_to_f(variable: scale_x, default: 1.0)
636
654
  scale_y = convert_arg_to_f(variable: scale_y, default: 1.0)
637
655
  scale_z = convert_arg_to_f(variable: scale_z, default: 1.0)
638
- return unless scale_x != 1.0 || scale_y != 1.0 || scale_z != 1.0
656
+ BTAP::Geometry.scale_model(model, scale_x, scale_y, scale_z) if scale_x != 1.0 || scale_y != 1.0 || scale_z != 1.0
639
657
 
640
- BTAP::Geometry.scale_model(model, scale_x, scale_y, scale_z)
658
+ footprint_aspect_ratio = convert_arg_to_f(variable: footprint_aspect_ratio, default: nil)
659
+ BTAP::Geometry.set_footprint_aspect_ratio(model, footprint_aspect_ratio) unless footprint_aspect_ratio.nil?
641
660
  end
642
661
 
643
662
  def apply_systems_and_efficiencies(model:,
@@ -812,8 +831,6 @@ class NECB2011 < Standard
812
831
 
813
832
  def apply_envelope(model:,
814
833
  construction_opt: '',
815
- bldg_category: '',
816
- bldg_structure: '',
817
834
  ext_wall_cond: nil,
818
835
  ext_floor_cond: nil,
819
836
  ext_roof_cond: nil,
@@ -839,30 +856,99 @@ class NECB2011 < Standard
839
856
  model.getOutsideSurfaceConvectionAlgorithm.setAlgorithm('TARP')
840
857
 
841
858
  construction_opt = '' unless construction_opt.respond_to?(:to_sym)
842
- bldg_structure = '' unless bldg_structure.respond_to?(:to_sym)
843
859
  construction_opt = construction_opt.to_s.downcase
844
- bldg_structure = bldg_structure.to_s.downcase.to_sym
845
- bldg_structures = @structure.data[:structure].keys
846
-
847
- if construction_opt == 'structure' && bldg_structures.include?(bldg_structure)
848
- argh = {}
849
- argh[:eWallU ] = ext_wall_cond if ext_wall_cond
850
- argh[:eFloorU ] = ext_floor_cond if ext_floor_cond
851
- argh[:eRoofU ] = ext_roof_cond if ext_roof_cond
852
- argh[:gWallU ] = ground_wall_cond if ground_wall_cond
853
- argh[:gFloorU ] = ground_floor_cond if ground_floor_cond
854
- argh[:gRoofU ] = ground_roof_cond if ground_roof_cond
855
- argh[:doorU ] = door_construction_cond if door_construction_cond
856
- argh[:fenU ] = fixed_window_cond if fixed_window_cond
857
- argh[:skyU ] = skylight_solar_trans if skylight_solar_trans
858
- argh[:doorSHGC] = glass_door_solar_trans if glass_door_solar_trans
859
- argh[:fenSHGC ] = fixed_wind_solar_trans if fixed_wind_solar_trans
860
- argh[:skySHGC ] = skylight_solar_trans if skylight_solar_trans
861
-
862
- assign_contruction_to_adiabatic_surfaces(model)
863
- ok = add_construction_sets(model, necb_hdd, argh)
864
-
865
- raise('NECB2011: Failed to assign default construction sets') unless ok
860
+
861
+ if construction_opt == 'structure'
862
+ argh = {}
863
+ argh[:eWallU ] = ext_wall_cond if ext_wall_cond
864
+ argh[:eFloorU ] = ext_floor_cond if ext_floor_cond
865
+ argh[:eRoofU ] = ext_roof_cond if ext_roof_cond
866
+ argh[:gWallU ] = ground_wall_cond if ground_wall_cond
867
+ argh[:gFloorU ] = ground_floor_cond if ground_floor_cond
868
+ argh[:gRoofU ] = ground_roof_cond if ground_roof_cond
869
+ argh[:doorU ] = door_construction_cond if door_construction_cond
870
+ argh[:fenU ] = fixed_window_cond if fixed_window_cond
871
+ argh[:skyU ] = skylight_solar_trans if skylight_solar_trans
872
+ argh[:fenSHGC ] = fixed_wind_solar_trans if fixed_wind_solar_trans
873
+ argh[:skySHGC ] = skylight_solar_trans if skylight_solar_trans
874
+ argh[:category ] = @activity.category
875
+ argh[:structure] = @structure.structure
876
+ argh[:framing ] = @structure.framing
877
+ argh[:cladding ] = @structure.cladding
878
+ argh[:finish ] = @structure.finish
879
+
880
+ # Hard-set (full-height) basement floor constructions.
881
+ set_basement_floor_construction(model)
882
+
883
+ # Add default BUILDING construction sets.
884
+ unless add_construction_sets(model.getSpaces, true, necb_hdd, argh)
885
+ raise('NECB: Failed to assign default BUILDING construction sets')
886
+ end
887
+
888
+ # Add default construction sets to customized spaces. Group spaces along
889
+ # customized STRUCTURE, FRAMING, CLADDING and/or FINISH.
890
+ unless @structure.spaces.empty?
891
+ custom = []
892
+ id = @structure.spaces.keys.first
893
+ sp = @structure.spaces.values.first
894
+ espace = model.getSpaceByName(id)
895
+
896
+ unless espace.empty?
897
+ espace = espace.get
898
+
899
+ first = {}
900
+ first[:spaces ] = [espace]
901
+ first[:structure] = sp[:structure]
902
+ first[:framing ] = sp[:framing]
903
+ first[:cladding ] = sp[:cladding]
904
+ first[:finish ] = sp[:finish]
905
+ custom << first
906
+
907
+ # Group spaces when sharing common attributes.
908
+ @structure.spaces.each do |id, sp|
909
+ space = model.getSpaceByName(id)
910
+ next if space.empty?
911
+
912
+ match = false
913
+ space = space.get
914
+ next if space == espace
915
+
916
+ custom.each do |csp|
917
+ break if match
918
+
919
+ if sp[:structure] == csp[:structure] &&
920
+ sp[:framing ] == csp[:framing ] &&
921
+ sp[:cladding ] == csp[:cladding ] &&
922
+ sp[:finish ] == csp[:finish ]
923
+
924
+ csp[:spaces] << space
925
+ match = true
926
+ end
927
+ end
928
+
929
+ unless match
930
+ spx = {}
931
+ spx[:spaces ] = [space]
932
+ spx[:structure] = sp[:structure]
933
+ spx[:framing ] = sp[:framing]
934
+ spx[:cladding ] = sp[:cladding]
935
+ spx[:finish ] = sp[:finish]
936
+ custom << spx
937
+ end
938
+ end
939
+
940
+ custom.each do |csp|
941
+ argh[:structure] = csp[:structure]
942
+ argh[:framing ] = csp[:framing]
943
+ argh[:cladding ] = csp[:cladding]
944
+ argh[:finish ] = csp[:finish]
945
+
946
+ unless add_construction_sets(csp[:spaces], false, necb_hdd, argh)
947
+ raise('NECB: Failed to assign (custom) SPACE construction sets')
948
+ end
949
+ end
950
+ end
951
+ end
866
952
  else
867
953
  model_add_constructions(model)
868
954
  apply_standard_construction_properties(model: model,
@@ -886,82 +972,281 @@ class NECB2011 < Standard
886
972
  model_create_thermal_zones(model, @space_multiplier_map)
887
973
  end
888
974
 
975
+ ##
976
+ # Assign surface area-weighted air film resistances (in m2.K/W) to insulated
977
+ # (opaque) layered constructions (as AdditionalProperty "btap_film"), as well
978
+ # as the (net) area of surfaces referencing each construction. The solution
979
+ # initially targets constructions having a valid AdditionalProperty "btap_uo"
980
+ # (auto-generated with option "structure"). If no constructions are tagged
981
+ # with "btap_uo" in the model, the fallback is to instead target all outdoor-
982
+ # facing, above-grade layered constructions. In case of latter scenario,
983
+ # AdditionalProperties "btap_uo", "btap_ut", "btap_id", "btap_type" &
984
+ # "btap_area" are added. All constructions having (or inherting) a "btap_uo"
985
+ # property are adjusted to match the specified Uo (factoring in "btap_film").
986
+ #
987
+ # @author: denis@rd2.ca
988
+ #
989
+ # @param model [OpenStudio::Model::Model] a model
990
+ #
991
+ # @return [Boolean] true if successful
992
+ def set_construction_air_film_resistances(model: nil)
993
+ return false unless model.is_a?(OpenStudio::Model::Model)
994
+
995
+ ok = false
996
+ hdd = get_necb_hdd18(model: model, necb_hdd: true)
997
+
998
+ model.getLayeredConstructions.each do |lc|
999
+ next unless lc.additionalProperties.hasFeature("btap_uo")
1000
+
1001
+ ok = true
1002
+ fR = lc.isFenestration ? 0 : filmR(lc)
1003
+ lc.additionalProperties.setFeature("btap_film", fR)
1004
+ lc.additionalProperties.setFeature("btap_area", lc.getNetArea)
1005
+ end
1006
+
1007
+ # No "btap_uo" AdditionalProperty?
1008
+ model.getSurfaces.each do |surface|
1009
+ break if ok
1010
+
1011
+ tp = surface.surfaceType.downcase
1012
+ bc = surface.outsideBoundaryCondition.downcase
1013
+ next unless bc == "outdoors"
1014
+
1015
+ lc = surface.construction
1016
+ next if lc.empty?
1017
+
1018
+ lc = lc.get.to_LayeredConstruction
1019
+ next if lc.empty?
1020
+
1021
+ lc = lc.get
1022
+ m2 = lc.getNetArea
1023
+ next unless m2 > 0
1024
+
1025
+ uo = max_u_necb(tp, bc, hdd)
1026
+ ty = case tp
1027
+ when "wall" then :walls
1028
+ when "floor" then :floors
1029
+ else :roofs
1030
+ end
1031
+
1032
+ unless lc.additionalProperties.hasFeature("btap_film")
1033
+ lc.additionalProperties.setFeature("btap_film", filmR(lc))
1034
+ end
1035
+
1036
+ unless lc.additionalProperties.hasFeature("btap_area")
1037
+ lc.additionalProperties.setFeature("btap_area", m2)
1038
+ end
1039
+
1040
+ unless lc.additionalProperties.hasFeature("btap_type")
1041
+ lc.additionalProperties.setFeature("btap_type", ty.to_s)
1042
+ end
1043
+
1044
+ unless lc.additionalProperties.hasFeature("btap_uo")
1045
+ lc.additionalProperties.setFeature("btap_uo", uo)
1046
+ end
1047
+
1048
+ unless lc.additionalProperties.hasFeature("btap_ut")
1049
+ lc.additionalProperties.setFeature("btap_ut", uo)
1050
+ end
1051
+
1052
+ unless lc.additionalProperties.hasFeature("btap_id")
1053
+ argh = {}
1054
+ argh[:perform] = :lp
1055
+ argh[:stype ] = ty
1056
+ argh[:uo ] = uo
1057
+ id = BTAP::Bridging.costed_assembly(argh)
1058
+ lc.additionalProperties.setFeature("btap_id", id)
1059
+ end
1060
+ end
1061
+
1062
+ # Isolate building default exterior wall construction.
1063
+ wlc = nil
1064
+ set = model.getBuilding.defaultConstructionSet
1065
+
1066
+ unless set.empty?
1067
+ ext = set.get.defaultExteriorSurfaceConstructions
1068
+
1069
+ unless ext.empty?
1070
+ wlc = ext.get.wallConstruction
1071
+ wlc = wlc.empty? ? nil : wlc.get
1072
+ end
1073
+ end
1074
+
1075
+ # Adjust tagged insulated constructions.
1076
+ model.getLayeredConstructions.each do |lc|
1077
+ id = lc.additionalProperties.getFeatureAsString("btap_id")
1078
+ uo = lc.additionalProperties.getFeatureAsDouble("btap_uo")
1079
+ ut = lc.additionalProperties.getFeatureAsDouble("btap_ut")
1080
+ fr = lc.additionalProperties.getFeatureAsDouble("btap_film")
1081
+ ty = lc.additionalProperties.getFeatureAsString("btap_type")
1082
+ m2 = lc.additionalProperties.getFeatureAsString("btap_area")
1083
+ next if id.empty?
1084
+ next if uo.empty?
1085
+ next if ut.empty?
1086
+ next if fr.empty?
1087
+ next if ty.empty?
1088
+ next if m2.empty?
1089
+ next if lc.isFenestration
1090
+
1091
+ id = id.get
1092
+ uo = uo.get
1093
+ ut = ut.get
1094
+ fr = fr.get
1095
+ ty = ty.get
1096
+ m2 = m2.get
1097
+ il = TBD.insulatingLayer(lc)
1098
+ next if id.downcase.chr == "g"
1099
+ next unless il[:index]
1100
+
1101
+ TBD.resetUo(lc, fr, il[:index], uo)
1102
+
1103
+ # Add default exterior wall construction tags to building.
1104
+ if lc == wlc
1105
+ next unless ty == "walls"
1106
+
1107
+ model.getBuilding.additionalProperties.setFeature("btap_id", id)
1108
+ model.getBuilding.additionalProperties.setFeature("btap_uo", uo)
1109
+ model.getBuilding.additionalProperties.setFeature("btap_ut", uo)
1110
+ model.getBuilding.additionalProperties.setFeature("btap_film", fr)
1111
+ model.getBuilding.additionalProperties.setFeature("btap_type", ty)
1112
+ model.getBuilding.additionalProperties.setFeature("btap_area", m2)
1113
+ end
1114
+ end
1115
+
1116
+ true
1117
+ end
1118
+
1119
+ ##
889
1120
  # Apply Kiva foundation model to floors/walls with ground boundary condition.
890
- # created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)
891
- # 'massive' edit: denis@rd2.ca
892
1121
  #
893
- # @param massive [Boolean] whether opaque materials are standard (not massless)
894
- def apply_kiva_foundation(model, massive = false)
895
- # define a Kiva model for the whole bldg that's used for the first floor in contact with ground in each zone
1122
+ # @author kamel.haddad@nrcan-rncan.gc.ca
1123
+ # @author denis@rd2.ca
1124
+ #
1125
+ # @param model [OpenStudio::Model::Model] a model
1126
+ #
1127
+ # @return [Float] slab-on-grade perimeter area (0 if not applicable)
1128
+ def apply_kiva_foundation(model)
1129
+ hdd = get_necb_hdd18(model: model, necb_hdd: true)
1130
+ slb = false
1131
+
896
1132
  bldg_kiva_model = OpenStudio::Model::FoundationKiva.new(model)
897
1133
  bldg_kiva_model.setName("Bldg Kiva Foundation")
898
1134
  bldg_kiva_model.setWallHeightAboveGrade(0.0)
899
1135
  bldg_kiva_model.setWallDepthBelowSlab(0.0)
1136
+
900
1137
  model.getThermalZones.sort.each do |zone|
901
1138
  zone_kiva_models = [bldg_kiva_model]
902
1139
  zone_grd_flr_counter = 0
1140
+
903
1141
  zone.spaces.sort.each do |space|
904
- # store space floors and walls in contact with ground and exterior walls
1142
+ # Store space floors and walls in contact with ground and exterior walls.
905
1143
  space_ground_floors = []
906
1144
  space_ground_walls = []
907
1145
  space_ext_walls = []
908
1146
  space_ground_floors += space.surfaces.select {|surf| surf.surfaceType.downcase == 'floor' && surf.isGroundSurface }
909
1147
  space_ground_walls += space.surfaces.select {|surf| surf.surfaceType.downcase == 'wall' && surf.isGroundSurface }
910
1148
  space_ext_walls += space.surfaces.select {|surf| surf.surfaceType.downcase == 'wall' && surf.outsideBoundaryCondition.downcase == 'outdoors'}
911
- # loop through space floors in contact with ground and assing a Kiva model for each
1149
+
1150
+ # Slab-on-grade (not basement) if no ground-facing walls. Add perimeter
1151
+ # insulation to building KIVA foundation.
1152
+ unless space_ground_floors.empty?
1153
+ if space_ground_walls.empty?
1154
+ if hdd < 7000
1155
+ xps38 = model.getStandardOpaqueMaterialByName("XPS 38mm")
1156
+
1157
+ if xps38.empty?
1158
+ xps38 = OpenStudio::Model::StandardOpaqueMaterial.new(model)
1159
+ xps38.setName("XPS 38mm")
1160
+ xps38.setRoughness("Smooth")
1161
+ xps38.setThickness(0.038)
1162
+ xps38.setConductivity(0.029)
1163
+ xps38.setDensity(28)
1164
+ xps38.setSpecificHeat(1450)
1165
+ else
1166
+ xps38 = xps38.get
1167
+ end
1168
+
1169
+ zone_kiva_models.last.setInteriorHorizontalInsulationMaterial(xps38)
1170
+ zone_kiva_models.last.setInteriorHorizontalInsulationWidth(1.2)
1171
+
1172
+ slb = true
1173
+ end
1174
+ end
1175
+ end
1176
+
1177
+ # Loop through space floors in contact with ground and assign a Kiva
1178
+ # model for each.
912
1179
  space_ground_floors.each do |gfloor|
913
1180
  zone_grd_flr_counter += 1
1181
+
1182
+ # A new Kiva model is needed for each additional floor in contact with
1183
+ # the ground in the zone.
914
1184
  if zone_grd_flr_counter > 1
915
- # a new Kiva model is needed for each additional floor in contact with the ground in the zone
916
1185
  kiva_model = OpenStudio::Model::FoundationKiva.new(model)
917
1186
  kiva_model.setName("#{gfloor.name.to_s} Kiva Foundation")
918
1187
  kiva_model.setWallHeightAboveGrade(0.0)
919
1188
  kiva_model.setWallDepthBelowSlab(0.0)
1189
+
1190
+ # Slab-on-grade (not basement) if no ground-facing walls. Add
1191
+ # perimeter insulation to building KIVA foundation.
1192
+ if space_ground_walls.empty?
1193
+ if hdd < 7000
1194
+ xps38 = model.getStandardOpaqueMaterialByName("XPS 38mm")
1195
+
1196
+ if xps38.empty?
1197
+ xps38 = OpenStudio::Model::StandardOpaqueMaterial.new(model)
1198
+ xps38.setName("XPS 38mm")
1199
+ xps38.setRoughness("Smooth")
1200
+ xps38.setThickness(0.038)
1201
+ xps38.setConductivity(0.029)
1202
+ xps38.setDensity(28)
1203
+ xps38.setSpecificHeat(1450)
1204
+ else
1205
+ xps38 = xps38.get
1206
+ end
1207
+
1208
+ kiva_model.setInteriorHorizontalInsulationMaterial(xps38)
1209
+ kiva_model.setInteriorHorizontalInsulationWidth(1.2)
1210
+
1211
+ slb = true
1212
+ end
1213
+ end
1214
+
920
1215
  zone_kiva_models << kiva_model
921
1216
  end
922
- # Kiva model only works with standard materials. Replace constructions
923
- # massless materials with standard ones.
924
- # Kiva model only works with standard materials. Replace constructions
925
- # massless materials with standard ones. Skip check if 'massive'.
926
- if massive
927
- c = gfloor.construction.get.to_LayeredConstruction.get
928
- gfloor.setOutsideBoundaryCondition('Foundation')
929
- gfloor.setAdjacentFoundation(zone_kiva_models.last)
930
- gfloor.setConstruction(c)
931
- else
932
- replace_massless_material_with_std_material(model,gfloor)
933
- gfloor.setOutsideBoundaryCondition('Foundation')
934
- gfloor.setAdjacentFoundation(zone_kiva_models.last)
935
- end
1217
+
1218
+ c = gfloor.construction.get.to_LayeredConstruction.get
1219
+ gfloor.setOutsideBoundaryCondition('Foundation')
1220
+ gfloor.setAdjacentFoundation(zone_kiva_models.last)
1221
+ gfloor.setConstruction(c)
936
1222
 
937
1223
  # Set the exposed perimeter for space floors in contact with the ground.
938
1224
  floor_exp_per = 0.0
1225
+
939
1226
  if !space_ground_walls.empty?
940
- floor_exp_per += get_surface_exp_per(gfloor,space_ground_walls)
1227
+ floor_exp_per += get_surface_exp_per(gfloor, space_ground_walls)
941
1228
  elsif !space_ext_walls.empty?
942
- floor_exp_per += get_surface_exp_per(gfloor,space_ext_walls)
1229
+ floor_exp_per += get_surface_exp_per(gfloor, space_ext_walls)
943
1230
  end
944
- gfloor.createSurfacePropertyExposedFoundationPerimeter('TotalExposedPerimeter',floor_exp_per)
945
- # specify a foundation boundary condition for space walls in contact with the ground and in
946
- # contact with the space floor in contact with ground 'gfloor'
1231
+
1232
+ gfloor.createSurfacePropertyExposedFoundationPerimeter('TotalExposedPerimeter', floor_exp_per)
1233
+
1234
+ # Specify a foundation boundary condition for space walls in contact
1235
+ # with the ground and in contact with the space floor in contact with
1236
+ # ground 'gfloor'.
947
1237
  space_ground_walls.each do |gwall|
948
- if surfaces_are_in_contact?(gfloor,gwall)
949
- if massive
950
- c = gwall.construction.get.to_LayeredConstruction.get
951
- gwall.setOutsideBoundaryCondition('Foundation')
952
- gwall.setAdjacentFoundation(zone_kiva_models.last)
953
- gwall.setConstruction(c)
954
- else
955
- replace_massless_material_with_std_material(model,gwall)
956
- gwall.setOutsideBoundaryCondition('Foundation')
957
- gwall.setAdjacentFoundation(zone_kiva_models.last)
958
- end
1238
+ if surfaces_are_in_contact?(gfloor, gwall)
1239
+ c = gwall.construction.get.to_LayeredConstruction.get
1240
+ gwall.setOutsideBoundaryCondition('Foundation')
1241
+ gwall.setAdjacentFoundation(zone_kiva_models.last)
1242
+ gwall.setConstruction(c)
959
1243
  end
960
1244
  end
961
1245
  end
962
1246
  end
963
1247
  end
964
- kiva_settings = model.getFoundationKivaSettings if !model.getFoundationKivas.empty?
1248
+
1249
+ slb == true ? BTAP::Geometry::Spaces.perimeter_m2(model.getSpaces, 1.2) : 0
965
1250
  end
966
1251
 
967
1252
  # check if two surfaces are in contact. For every two consecutive vertices on surface 1,
@@ -1012,7 +1297,7 @@ class NECB2011 < Standard
1012
1297
  # with the name: 'Insulation: Expanded polystyrene - extruded (smooth skin surface) (HCFC-142b exp.)'.
1013
1298
  # The thickness of the new material is based on the thermal resistance of the massless material it replaces.
1014
1299
  # created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca)
1015
- def replace_massless_material_with_std_material(model,surf)
1300
+ def replace_massless_material_with_std_material(model, surf)
1016
1301
  std_const_name = "#{surf.construction.get.name.to_s}_std"
1017
1302
  std_const = model.getLayeredConstructions.select {|const| const.name.to_s == std_const_name}
1018
1303
  new_const = nil
@@ -1150,10 +1435,11 @@ class NECB2011 < Standard
1150
1435
  # Initiates a BTAP building ACTIVITY.
1151
1436
  #
1152
1437
  # @param model [OpenStudio::Model::Model] a model
1438
+ # @param option ["", "structure"] construction option
1153
1439
  #
1154
1440
  # @return [BTAP::Activity] a BTAP building ACTIVITY (see logs if failed)
1155
- def assign_building_activity(model: nil)
1156
- @activity = BTAP::Activity.new(model)
1441
+ def assign_building_activity(model: nil, option: "")
1442
+ @activity = BTAP::Activity.new(model, option)
1157
1443
  end
1158
1444
 
1159
1445
  # Initiates a BTAP building STRUCTURE.
@@ -1169,7 +1455,7 @@ class NECB2011 < Standard
1169
1455
 
1170
1456
  ##
1171
1457
  # (Optionally) uprates - then derates - envelope surface constructions due to
1172
- # MAJOR thermal bridges (e.g. roof parapets, corners, fenestration
1458
+ # linear thermal bridges (e.g. roof parapets, corners, fenestration
1173
1459
  # perimeters). See lib/openstudio-standards/btap/bridging.rb, which relies on
1174
1460
  # the Thermal Bridging & Derating (TBD) gem.
1175
1461
  #
@@ -1186,14 +1472,14 @@ class NECB2011 < Standard
1186
1472
  def apply_thermal_bridging(model: nil,
1187
1473
  necb_hdd: true,
1188
1474
  structure: nil,
1189
- option: 'none',
1475
+ option: "none",
1190
1476
  interpolate: false,
1191
1477
  wallU: nil,
1192
1478
  floorU: nil,
1193
1479
  roofU: nil)
1194
1480
  necb_hdd = true unless [true, false].include?(necb_hdd)
1195
1481
  return true unless option.respond_to?(:to_sym)
1196
- return true if option.to_s.downcase == 'none'
1482
+ return true if option.to_s.downcase == "none"
1197
1483
 
1198
1484
  hdd = get_necb_hdd18(model: model, necb_hdd: necb_hdd)
1199
1485
  wallU = wallU ? wallU : max_u_necb("wall", "outdoors", hdd)
@@ -1206,19 +1492,19 @@ class NECB2011 < Standard
1206
1492
  argh[:floors ] = { uo: floorU }
1207
1493
  argh[:roofs ] = { uo: roofU }
1208
1494
 
1209
- case option.downcase
1210
- when 'uprate'
1211
- argh[:walls ][:ut] = wallU
1212
- argh[:floors ][:ut] = floorU
1213
- argh[:roofs ][:ut] = roofU
1214
- when 'good'
1495
+ case option.to_s.downcase
1496
+ when "uprate"
1497
+ argh[:walls ][:ut] = wallU
1498
+ argh[:floors][:ut] = floorU
1499
+ argh[:roofs ][:ut] = roofU
1500
+ when "good"
1215
1501
  argh[:quality] = :good
1216
1502
  else
1217
1503
  argh[:quality] = :bad
1218
1504
  end
1219
1505
 
1220
1506
  argh[:interpolate] = interpolate
1221
- argh[:interpolate] = false unless [true, false].include?(interpolate)
1507
+ argh[:interpolate] = false unless argh[:interpolate] == true
1222
1508
 
1223
1509
  @tbd = BTAP::Bridging.new(model, argh)
1224
1510
 
@@ -1317,10 +1603,8 @@ class NECB2011 < Standard
1317
1603
  end
1318
1604
 
1319
1605
  def apply_loop_pump_power(model:, sizing_run_dir:)
1320
- # Remove duplicate materials and constructions
1321
- # Note For NECB2015 This is the 2nd time this method is bieng run.
1322
- # First time it ran in the super() within model_apply_standard() method
1323
- # model = return BTAP::FileIO::remove_duplicate_materials_and_constructions(model)
1606
+ # Note For NECB2015 This is the 2nd time this method is being run.
1607
+ # First time it ran in the super() within model_apply_standard() method.
1324
1608
  return model
1325
1609
  end
1326
1610
 
@@ -1736,15 +2020,41 @@ class NECB2011 < Standard
1736
2020
  end
1737
2021
  end
1738
2022
 
1739
- if daylight_space.spaceType.get.standardsSpaceType.get.to_s == 'Office - enclosed' && daylight_space_area >= 25.0
2023
+ # Match both the NECB2011 name ('Office - enclosed') and the NECB2015+/
2024
+ # NECB2020 names ('Office enclosed <= 25 m2' / 'Office enclosed > 25 m2');
2025
+ # the exact-2011-string comparison meant the >= 25 m2 office exemption
2026
+ # NEVER fired on 2015+ models.
2027
+ if daylight_space.spaceType.get.standardsSpaceType.get.to_s =~ /office\s*-?\s*enclosed/i && daylight_space_area >= 25.0
1740
2028
  offices_larger_25m2 << daylight_space.name.to_s
1741
2029
  end
1742
2030
  end
1743
2031
 
1744
2032
  ##### find daylight_spaces which do not need daylight sensor controls based on the primary_sidelighted_area as per NECB2011: 4.2.2.8.
1745
2033
  ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their primary_sidelighted_area <= 100m2), as per NECB2011: 4.2.2.2.
2034
+ # The exception criteria for sidelighting (4.2.2.8/4.2.2.10) only apply to
2035
+ # spaces WITH exterior windows, and the skylight criteria (4.2.2.4/4.2.2.7)
2036
+ # only to spaces WITH skylights. Previously every criterion was applied to
2037
+ # every space, so a window-only space was ALWAYS excepted (its skylight
2038
+ # area is 0 <= 400 m2) and a skylight-only space likewise — with the
2039
+ # practical result that almost no space ever received a daylighting sensor.
2040
+ spaces_with_windows = []
2041
+ spaces_with_skylights = []
2042
+ daylight_spaces.sort.each do |daylight_space|
2043
+ daylight_space.surfaces.sort.each do |surface|
2044
+ surface.subSurfaces.sort.each do |subsurface|
2045
+ next unless subsurface.outsideBoundaryCondition == 'Outdoors'
2046
+
2047
+ if subsurface.subSurfaceType == 'FixedWindow' || subsurface.subSurfaceType == 'OperableWindow'
2048
+ spaces_with_windows << daylight_space.name.to_s
2049
+ elsif subsurface.subSurfaceType == 'Skylight'
2050
+ spaces_with_skylights << daylight_space.name.to_s
2051
+ end
2052
+ end
2053
+ end
2054
+ end
1746
2055
  daylight_spaces_exception = []
1747
2056
  primary_sidelighted_area_hash.sort.each do |key_daylight_space_name, value_primary_sidelighted_area|
2057
+ next unless spaces_with_windows.include?(key_daylight_space_name)
1748
2058
  if value_primary_sidelighted_area <= 100.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
1749
2059
  daylight_spaces_exception << key_daylight_space_name
1750
2060
  end
@@ -1753,6 +2063,7 @@ class NECB2011 < Standard
1753
2063
  ##### find daylight_spaces which do not need daylight sensor controls based on the sidelighting_effective_aperture as per NECB2011: 4.2.2.8.
1754
2064
  ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their sidelighting_effective_aperture <= 10%), as per NECB2011: 4.2.2.2.
1755
2065
  sidelighting_effective_aperture_hash.sort.each do |key_daylight_space_name, value_sidelighting_effective_aperture|
2066
+ next unless spaces_with_windows.include?(key_daylight_space_name)
1756
2067
  if value_sidelighting_effective_aperture <= 0.1 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
1757
2068
  daylight_spaces_exception << key_daylight_space_name
1758
2069
  end
@@ -1761,6 +2072,7 @@ class NECB2011 < Standard
1761
2072
  ##### find daylight_spaces which do not need daylight sensor controls based on the daylighted_area_under_skylights as per NECB2011: 4.2.2.4.
1762
2073
  ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their daylighted_area_under_skylights <= 400m2), as per NECB2011: 4.2.2.2.
1763
2074
  daylighted_area_under_skylights_hash.sort.each do |key_daylight_space_name, value_daylighted_area_under_skylights|
2075
+ next unless spaces_with_skylights.include?(key_daylight_space_name)
1764
2076
  if value_daylighted_area_under_skylights <= 400.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
1765
2077
  daylight_spaces_exception << key_daylight_space_name
1766
2078
  end
@@ -1769,6 +2081,7 @@ class NECB2011 < Standard
1769
2081
  ##### find daylight_spaces which do not need daylight sensor controls based on the skylight_effective_aperture criterion as per NECB2011: 4.2.2.4.
1770
2082
  ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their skylight_effective_aperture <= 0.6%), as per NECB2011: 4.2.2.2.
1771
2083
  skylight_effective_aperture_hash.sort.each do |key_daylight_space_name, value_skylight_effective_aperture|
2084
+ next unless spaces_with_skylights.include?(key_daylight_space_name)
1772
2085
  if value_skylight_effective_aperture <= 0.006 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
1773
2086
  daylight_spaces_exception << key_daylight_space_name
1774
2087
  end
@@ -2146,7 +2459,7 @@ class NECB2011 < Standard
2146
2459
  # Interate through all spaces in model.. not just ones that have space type defined.. Is this right sara?
2147
2460
  space_type.spaces.sort.each do |space|
2148
2461
  # Get only the wall type surfaces and iterate throught them.
2149
- space.surfaces.sort.select(&:surfaceType == 'Wall').each do |wall_surface|
2462
+ space.surfaces.sort.select { |surface| surface.surfaceType == 'Wall' }.each do |wall_surface|
2150
2463
  # Find the vertex with the max z value.
2151
2464
  vertex_with_max_height = wall_surface.vertices.max_by(&:z)
2152
2465
  # replace max if this surface has something bigger.
@@ -2393,7 +2706,6 @@ class NECB2011 < Standard
2393
2706
  # if skylight_vertex_0_distance == skylight_vertex_1_distance
2394
2707
  end
2395
2708
 
2396
- daylighted_under_skylight_area += daylighted_under_skylight_length * daylighted_under_skylight_width
2397
2709
  # if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
2398
2710
  end
2399
2711
  # surface.subSurfaces.each do |subsurface|
@@ -2402,6 +2714,11 @@ class NECB2011 < Standard
2402
2714
  end
2403
2715
  # daylight_space.surfaces.each do |surface|
2404
2716
  end
2717
+ # Accumulate ONCE per skylight. Previously the accumulation sat inside
2718
+ # the exterior-window loop above, so (a) spaces with skylights but no
2719
+ # exterior windows always computed ZERO daylighted area, and (b) each
2720
+ # additional exterior window double-counted the whole area.
2721
+ daylighted_under_skylight_area += daylighted_under_skylight_length * daylighted_under_skylight_width
2405
2722
  # if subsurface.subSurfaceType == "Skylight"
2406
2723
  end
2407
2724
  # surface.subSurfaces.each do |subsurface|
@@ -2812,45 +3129,140 @@ class NECB2011 < Standard
2812
3129
  # Arguments:
2813
3130
  # model (OpenStudio::Model::Model): The model to run the sizing run on.
2814
3131
  # sizing_run_dir (String): The directory where the sizing run files will be saved.
2815
- def try_sizing_run(model:, sizing_run_dir:, sizing_run_subdir:)
3132
+ def try_sizing_run(model:, sizing_run_dir:, sizing_run_subdir:, retry: 0)
2816
3133
  raise('validation of model failed.') unless validate_initial_model(model)
2817
3134
 
2818
3135
  # Do a sizing run to determine the system capacities. If a sizing run fails, hard size any failing DX heating coils
2819
3136
  # to 1.0 kW and rerun the sizing run until it succeeds. If no DX heating coils are found, or none had a small
2820
3137
  # capacity then raise an error.
3138
+ dx_coil_changed = false
3139
+ num_changes = 0
2821
3140
  loop do
2822
3141
  sizing_run_success = model_run_sizing_run(model, "#{sizing_run_dir}/#{sizing_run_subdir}", true)
2823
3142
  break if sizing_run_success
2824
-
2825
- # Sizing run failed, check all DX heating coils and set their size to 1 if less than 1
2826
- dx_coil_changed = false
2827
-
2828
- model.getCoilHeatingDXSingleSpeeds.each do |coil|
2829
- autosized_capacity = coil.autosizedRatedTotalHeatingCapacity
2830
- if autosized_capacity.is_initialized && autosized_capacity.get < 1.0
2831
- coil.setRatedTotalHeatingCapacity(1.0)
2832
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "DX Heating Coil #{coil.name.to_s} has a rated capacity less than 1.0 kW and has been resized to 1.0 kW to avoid sizing run failure.")
2833
- puts "DX Heating Coil #{coil.name.to_s} has a rated capacity less than 1.0 kW and has been resized to 1.0 kW to avoid sizing run failure."
2834
- dx_coil_changed = true
3143
+ num_changes += 1
3144
+
3145
+ # Report severe or fatal errors in the run
3146
+ error_query = "SELECT ErrorMessage
3147
+ FROM Errors
3148
+ WHERE ErrorType in(1,2)"
3149
+ errs = model.sqlFile.get.execAndReturnVectorOfString(error_query)
3150
+ if errs.is_initialized
3151
+ errs = errs.get
3152
+ end
3153
+ # Get error and get any DX coils with issues
3154
+
3155
+ issue_coils = []
3156
+ errs.each do |err|
3157
+ # Find any DX heating coil names that are in the error message
3158
+ error_heating_coils = model.getCoilHeatingDXSingleSpeeds.select { |coil| err.to_s.downcase.include?(coil.name.to_s.downcase) }
3159
+ # If more the DX heating coil name appears more than one time in the error message, only select the one without a number at the end of the name.
3160
+ if error_heating_coils.length > 1
3161
+ error_heating_coils.each do |coil|
3162
+ if coil.name.to_s.downcase.match(/\d/)
3163
+ # Skip coils if there is another number in the error message after the name of the coil (e.g. coilheatingdx 1 appears but so does coilheatingdx 10 or coilheatingdx 13).
3164
+ error_index = err.to_s.downcase.index(coil.name.to_s.downcase) + coil.name.to_s.length
3165
+ next if err.to_s[error_index].match(/\d/)
3166
+ heating_coil_element = {}
3167
+ heating_coil_element[:heating_coil] = coil
3168
+ heating_coil_element[:is_autosized] = true if coil.isRatedTotalHeatingCapacityAutosized
3169
+ heating_coil_element[:is_autosized] = false unless coil.isRatedTotalHeatingCapacityAutosized
3170
+ heating_coil_element[:capacity] = coil.ratedTotalHeatingCapacity.to_f
3171
+ issue_coils << heating_coil_element
3172
+ end
3173
+ end
3174
+ elsif error_heating_coils.length == 1
3175
+ heating_coil_element = {}
3176
+ heating_coil_element[:heating_coil] = error_heating_coils[0]
3177
+ heating_coil_element[:is_autosized] = true if error_heating_coils[0].isRatedTotalHeatingCapacityAutosized
3178
+ heating_coil_element[:is_autosized] = false unless error_heating_coils[0].isRatedTotalHeatingCapacityAutosized
3179
+ heating_coil_element[:capacity] = error_heating_coils[0].ratedTotalHeatingCapacity.to_f
3180
+ issue_coils << heating_coil_element
3181
+ end
3182
+ # Find any DX cooling coil names that are in the error message
3183
+ error_cooling_coils = model.getCoilCoolingDXSingleSpeeds.select { |coil| err.to_s.downcase.include?(coil.name.to_s.downcase) }
3184
+ # If more the DX cooling coil name appears more than one time in the error message, only select the one without a number at the end of the name.
3185
+ if error_cooling_coils.length > 1
3186
+ error_cooling_coils.each do |coil|
3187
+ if coil.name.to_s.downcase.match(/\d/)
3188
+ # Skip coils if there is another number in the error message after the name of the coil (e.g. coilcoolingdx 1 appears but so does coilcoolingdx 10 or coilcoolingdx 13).
3189
+ error_index = err.to_s.downcase.index(coil.name.to_s.downcase) + coil.name.to_s.downcase.length
3190
+ next if err.to_s[error_index].match(/\d/)
3191
+ cooling_coil_element = {}
3192
+ cooling_coil_element[:cooling_coil] = coil
3193
+ cooling_coil_element[:is_autosized] = true if coil.isRatedTotalCoolingCapacityAutosized
3194
+ cooling_coil_element[:is_autosized] = false unless coil.isRatedTotalCoolingCapacityAutosized
3195
+ cooling_coil_element[:capacity] = coil.ratedTotalCoolingCapacity.to_f
3196
+ issue_coils << cooling_coil_element
3197
+ end
3198
+ end
3199
+ elsif error_cooling_coils.length == 1
3200
+ cooling_coil_element = {}
3201
+ cooling_coil_element[:cooling_coil] = error_cooling_coils[0]
3202
+ cooling_coil_element[:is_autosized] = true if error_cooling_coils[0].isRatedTotalCoolingCapacityAutosized
3203
+ cooling_coil_element[:is_autosized] = false unless error_cooling_coils[0].isRatedTotalCoolingCapacityAutosized
3204
+ cooling_coil_element[:capacity] = error_cooling_coils[0].ratedTotalCoolingCapacity.to_f
3205
+ issue_coils << cooling_coil_element
2835
3206
  end
2836
3207
  end
2837
- model.getCoilHeatingDXMultiSpeeds.each do |coil|
2838
- coil.stages.each do |stage|
2839
- autosized_capacity = stage.autosizedGrossRatedHeatingCapacity
2840
- if autosized_capacity.is_initialized && autosized_capacity.get < 1.0
2841
- stage.setGrossRatedHeatingCapacity(1.0)
2842
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "A DX Heating Coil #{coil.name.to_s} stage has a rated capacity less than 1.0 kW and has been resized to 1.0 kW to avoid sizing run failure.")
2843
- puts "A DX Heating Coil #{coil.name.to_s} stage has a rated capacity less than 1.0 kW and has been resized to 1.0 kW to avoid sizing run failure."
2844
- dx_coil_changed = true
3208
+ issue_coils.uniq!
3209
+
3210
+ # Adjust the capacity of DX coils with issues. This resolves most sizing run issues.
3211
+ issue_coils.each do |issue_coil|
3212
+ # If the coil is autosized and has a capacity less than 1.0 kW, resize the coil (and similar coils) to 1.0 kW
3213
+ if issue_coil[:is_autosized] == true && issue_coil[:capacity].to_f < 1.0
3214
+ if issue_coil.key?(:heating_coil)
3215
+ resize_coils = model.getCoilHeatingDXSingleSpeeds.select { |coil| coil.isRatedTotalHeatingCapacityAutosized && coil.ratedTotalHeatingCapacity.to_f < 1.0 }
3216
+ resize_coils.each do |coil|
3217
+ coil.setRatedTotalHeatingCapacity(1.0)
3218
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "DX Heating Coil #{coil.name.to_s} has a rated capacity less than 1.0 kW and has been resized to 1.0 kW to avoid sizing run failure.")
3219
+ puts "DX Heating Coil #{coil.name.to_s} has a rated capacity less than 1.0 kW and has been resized to 1.0 kW to avoid sizing run failure."
3220
+ dx_coil_changed = true
3221
+ end
3222
+ elsif issue_coil.key?(:cooling_coil)
3223
+ resize_coils = model.getCoilCoolingDXSingleSpeeds.select { |coil| coil.isRatedTotalCoolingCapacityAutosized && coil.ratedTotalCoolingCapacity.to_f < 1.0 }
3224
+ resize_coils.each do |coil|
3225
+ coil.setRatedTotalCoolingCapacity(1.0)
3226
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "DX Cooling Coil #{coil.name.to_s} has a rated capacity less than 1.0 kW and has been resized to 1.0 kW to avoid sizing run failure.")
3227
+ puts "DX Cooling Coil #{coil.name.to_s} has a rated capacity less than 1.0 kW and has been resized to 1.0 kW to avoid sizing run failure."
3228
+ dx_coil_changed = true
3229
+ end
3230
+ end
3231
+ # If the coil is not autosized, or has a capacity larger than 1 kW, increase the capacity by 1% to avoid sizing run issues with rounding
3232
+ else
3233
+ if issue_coil.key?(:heating_coil)
3234
+ cooling_coil_name = issue_coil[:heating_coil].name.to_s.gsub('CoilHeatingDX','CoilCoolingDX')
3235
+ cooling_coil = model.getCoilCoolingDXSingleSpeeds.select { |coil| cooling_coil_name.to_s.downcase == coil.name.to_s.downcase }[0]
3236
+ if cooling_coil.ratedTotalCoolingCapacity.to_f.round(0) == issue_coil[:capacity].to_f.round(0)
3237
+ revised_capacity = issue_coil[:capacity].to_f * 1.01
3238
+ issue_coil[:heating_coil].setRatedTotalHeatingCapacity(revised_capacity)
3239
+ cooling_coil.setRatedTotalCoolingCapacity(revised_capacity)
3240
+ dx_coil_changed = true
3241
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "DX heating and cooling coil #{issue_coil[:heating_coil].name.to_s} has been resized #{revised_capacity}W to avoid sizing run failure.")
3242
+ puts "Adjusted both heating and cooling coil #{issue_coil[:heating_coil].name.to_s} capacity to #{revised_capacity}W to avoid sizing run failure."
3243
+ dx_coil_changed = true
3244
+ end
3245
+ elsif issue_coil.key?(:cooling_coil)
3246
+ heating_coil_name = issue_coil[:cooling_coil].name.to_s.gsub('CoilCoolingDX','CoilHeatingDX')
3247
+ heating_coil = model.getCoilHeatingDXSingleSpeeds.select { |coil| heating_coil_name.to_s.downcase == coil.name.to_s.downcase }[0]
3248
+ if heating_coil.ratedTotalHeatingCapacity.to_f.round(0) == issue_coil[:capacity].to_f.round(0)
3249
+ revised_capacity = issue_coil[:capacity].to_f * 1.01
3250
+ issue_coil[:cooling_coil].setRatedTotalCoolingCapacity(revised_capacity)
3251
+ heating_coil.setRatedTotalHeatingCapacity(revised_capacity)
3252
+ dx_coil_changed = true
3253
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "DX heating and cooling coil #{issue_coil[:cooling_coil].name.to_s} has been resized #{revised_capacity}W to avoid sizing run failure.")
3254
+ puts "Adjusted both heating and cooling coil #{issue_coil[:cooling_coil].name.to_s} capacity to #{revised_capacity}W to avoid sizing run failure."
3255
+ dx_coil_changed = true
3256
+ end
2845
3257
  end
2846
3258
  end
2847
3259
  end
2848
3260
 
2849
- # If no DX coil was changed, break loop and raise error
2850
- if !dx_coil_changed
3261
+ # If no DX coil was changed, or too many changes were made, break loop and raise error
3262
+ if !dx_coil_changed || num_changes > 100
2851
3263
  raise("sizing run failed! check #{sizing_run_dir}/#{sizing_run_subdir} (DX coil sizes adjusted, but no further changes possible)")
2852
3264
  end
2853
- puts "Rerunning sizing run after adjusting DX heating coil capacity to 1.0 kW."
3265
+ puts "Rerunning sizing run after adjusting coil capacity this many times: #{num_changes}."
2854
3266
  # Otherwise, loop will rerun sizing
2855
3267
  end
2856
3268
  end