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
@@ -0,0 +1,641 @@
1
+ require 'openstudio'
2
+ require 'securerandom'
3
+ require 'yaml'
4
+
5
+ module BTAP
6
+ class Datapoint
7
+ def initialize(input_folder: File.join(__dir__, 'input'),
8
+ output_folder: File.join(__dir__, 'output'),
9
+ weather_folder: File.join(__dir__, 'weather'))
10
+
11
+ @failed = false
12
+
13
+ puts("INPUT FOLDER: #{input_folder}\n" \
14
+ "OUTPUT FOLDER: #{output_folder}")
15
+
16
+ # Set local temporary cache folders. These are created new for each
17
+ # datapoint. Location of the datapoint input folder. Could be local, cloud,
18
+ # or VM host.
19
+ datapoint_folder = File.join(__dir__, 'datapoint/')
20
+ @dp_temp_folder = File.join(datapoint_folder, SecureRandom.uuid + '_' + Process.pid.to_s)
21
+
22
+ # Determine whether the analysis was run on AWS S3.
23
+ @s3 = input_folder.start_with?('s3:')
24
+
25
+ # Clean up stale temp folders whose PID isn't running.
26
+ Dir.glob("#{datapoint_folder}/*").each do |dir|
27
+ begin
28
+ Process.kill(0, dir.rpartition('_').last.to_i)
29
+ rescue Errno::ESRCH, Errno::EPERM
30
+ FileUtils.rm_rf(dir)
31
+ end
32
+ end
33
+ FileUtils.mkdir_p(@dp_temp_folder)
34
+
35
+ # For S3 inputs, download to the temp folder so files are accessible
36
+ # locally. For local inputs, read directly from the input folder.
37
+ if @s3
38
+ match = input_folder.match(%r{s3://(.*?)/(.*)})
39
+ @s3_bucket = match[1]
40
+ @s3_input_folder_object = match[2]
41
+ @s3_output_folder_object = output_folder.match(%r{s3://(.*?)/(.*)})[2]
42
+ puts("s3_bucket: #{@s3_bucket}\n" \
43
+ "s3_input_folder_object: #{@s3_input_folder_object}\n" \
44
+ "s3_output_folder_object: #{@s3_output_folder_object}")
45
+
46
+ s3_copy(source_folder: input_folder, target_folder: @dp_temp_folder)
47
+ run_options_path = File.join(@dp_temp_folder, 'run_options.yml')
48
+ else
49
+ raise("Input folder does not exist: #{input_folder}") unless Dir.exist?(input_folder)
50
+ run_options_path = File.join(input_folder, 'run_options.yml')
51
+ end
52
+ raise("Could not read input from #{run_options_path}") unless File.file?(run_options_path)
53
+
54
+ @options = YAML.load_file(run_options_path)
55
+
56
+ # Location of datapoint output folder. Could be local, cloud, or VM host.
57
+ @dp_output_folder = File.join(output_folder, @options[:datapoint_id])
58
+
59
+ # Show versions in YAML file.
60
+ @options[:os_git_revision] = OpenstudioStandards.git_revision
61
+
62
+ # Get users' inputs for the parameters needed for the calculation af net present value
63
+ @npv_start_year = @options[:npv_start_year]
64
+ @npv_end_year = @options[:npv_end_year]
65
+ @npv_discount_rate = @options[:npv_discount_rate]
66
+ @npv_discount_rate_carbon = @options[:npv_discount_rate_carbon]
67
+
68
+ # Save configuration to temp folder.
69
+ File.open(File.join(@dp_temp_folder, 'run_options.yml'), 'w') { |file| file.write(@options.to_yaml) }
70
+ begin
71
+ @options[:template] = 'NECB2011' if @options[:algorithm_type] == 'osm_batch'
72
+ @standard = Standard.build(@options[:template])
73
+
74
+ # Set use the convert_arg_to_bool method from the standard class to set the @oerd_utility_pricing_flag
75
+ # Always set oerd_utility_pricing to false until it can be shared freely.
76
+ @oerd_utility_pricing = false
77
+ @utility_pricing_year = @standard.convert_arg_to_f(variable: @options[:utility_pricing_year], default: 2020)
78
+
79
+ # This allows you to select the skeleton model from our built-in starting
80
+ # points. You can add a custom file as it will search the libary first.
81
+ model = @standard.load_building_type_from_library(building_type: @options[:building_type])
82
+ if false == model
83
+ osm_model_path = File.absolute_path(File.join(@s3 ? @dp_temp_folder : input_folder, @options[:building_type] + '.osm'))
84
+ raise("File #{osm_model_path} not found") unless File.exist?(osm_model_path)
85
+
86
+ model = BTAP::FileIO.load_osm(osm_model_path)
87
+ end
88
+
89
+ # If analysis type is `osm_batch`, do nothing to the OSM files other than
90
+ # add temporal outputs and weather data. Otherwise, modify OSM input with
91
+ # options.
92
+ if @options[:algorithm_type] == 'osm_batch'
93
+ @standard.set_output_variables(model: model, output_variables: @options[:output_variables])
94
+ @standard.set_output_meters(model: model, output_meters: @options[:output_meters])
95
+ model.getYearDescription.setDayofWeekforStartDay('Sunday')
96
+ OpenstudioStandards::Weather.model_set_building_location(
97
+ model,
98
+ weather_file_path: OpenstudioStandards::Weather.get_standards_weather_file_path(@options[:epw_file]))
99
+ else
100
+ @standard.model_apply_standard(
101
+ model: model,
102
+ construction_opt: @options[:construction_opt],
103
+ epw_file: @options[:epw_file],
104
+ custom_weather_folder: weather_folder,
105
+ btap_weather: @options[:btap_weather],
106
+ sizing_run_dir: File.join(@dp_temp_folder, 'sizing_folder'),
107
+ hvac_system_primary: @options[:hvac_system_primary],
108
+ hvac_system_dwelling_units: @options[:hvac_system_dwelling_units],
109
+ hvac_system_washrooms: @options[:hvac_system_washrooms],
110
+ hvac_system_corridor: @options[:hvac_system_corridor],
111
+ hvac_system_storage: @options[:hvac_system_storage],
112
+ primary_heating_fuel: @options[:primary_heating_fuel],
113
+ necb_reference_hp: @options[:necb_reference_hp],
114
+ necb_reference_hp_supp_fuel: @options[:necb_reference_hp_supp_fuel],
115
+
116
+ # Four options: @options[: (1) 'NECB_Default', (2) 'No DCV',
117
+ # (3) 'Occupancy-based DCV' , (4) 'CO2-based DCV'
118
+ dcv_type: @options[:dcv_type],
119
+
120
+ # Two options: @options[: (1) 'NECB_Default', (2) 'LED'
121
+ lights_type: @options[:lights_type],
122
+ lights_scale: @options[:lights_scale],
123
+ daylighting_type: @options[:daylighting_type],
124
+
125
+ # Two options: @options[: (1) 'NECB_Default', (2) 'add_daylighting_controls'
126
+ ecm_system_name: @options[:ecm_system_name],
127
+ ecm_system_zones_map_option: @options[:ecm_system_zones_map_option],
128
+
129
+ # (1) 'NECB_Default' (2) 'one_sys_per_floor' (3) 'one_sys_per_bldg'
130
+ erv_package: @options[:erv_package],
131
+ boiler_eff: @options[:boiler_eff],
132
+
133
+ # TODO: Inconsistent naming
134
+ unitary_cop: @options[:adv_dx_units],
135
+ furnace_eff: @options[:furnace_eff],
136
+ shw_eff: @options[:shw_eff],
137
+ ext_wall_cond: @options[:ext_wall_cond],
138
+ ext_floor_cond: @options[:ext_floor_cond],
139
+ ext_roof_cond: @options[:ext_roof_cond],
140
+ ground_wall_cond: @options[:ground_wall_cond],
141
+ ground_floor_cond: @options[:ground_floor_cond],
142
+ ground_roof_cond: @options[:ground_roof_cond],
143
+ door_construction_cond: @options[:door_construction_cond],
144
+ fixed_window_cond: @options[:fixed_window_cond],
145
+ glass_door_cond: @options[:glass_door_cond],
146
+ overhead_door_cond: @options[:overhead_door_cond],
147
+ skylight_cond: @options[:skylight_cond],
148
+ glass_door_solar_trans: @options[:glass_door_solar_trans],
149
+ fixed_wind_solar_trans: @options[:fixed_wind_solar_trans],
150
+ skylight_solar_trans: @options[:skylight_solar_trans],
151
+ fdwr_set: @options[:fdwr_set],
152
+ srr_opt: @options[:srr_opt],
153
+ srr_set: @options[:srr_set],
154
+ rotation_degrees: @options[:rotation_degrees],
155
+ scale_x: @options[:scale_x],
156
+ scale_y: @options[:scale_y],
157
+ scale_z: @options[:scale_z],
158
+ footprint_aspect_ratio: @options[:footprint_aspect_ratio],
159
+ pv_ground_type: @options[:pv_ground_type],
160
+ pv_ground_total_area_pv_panels_m2: @options[:pv_ground_total_area_pv_panels_m2],
161
+ pv_ground_tilt_angle: @options[:pv_ground_tilt_angle],
162
+ pv_ground_azimuth_angle: @options[:pv_ground_azimuth_angle],
163
+ pv_ground_module_description: @options[:pv_ground_module_description],
164
+ nv_type: @options[:nv_type],
165
+ nv_opening_fraction: @options[:nv_opening_fraction],
166
+ nv_temp_out_min: @options[:nv_temp_out_min],
167
+ nv_delta_temp_in_out: @options[:nv_delta_temp_in_out],
168
+ occupancy_loads_scale: @options[:occupancy_loads_scale],
169
+ electrical_loads_scale: @options[:electrical_loads_scale],
170
+ oa_scale: @options[:oa_scale],
171
+ infiltration_scale: @options[:infiltration_scale],
172
+ chiller_type: @options[:chiller_type],
173
+ output_variables: @options[:output_variables],
174
+ output_meters: @options[:output_meters],
175
+ airloop_economizer_type: @options[:airloop_economizer_type],
176
+ shw_scale: @options[:shw_scale],
177
+ baseline_system_zones_map_option: @options[:baseline_system_zones_map_option],
178
+ tbd_interpolate: @options[:tbd_interpolate],
179
+ tbd_option: @options[:tbd_option],
180
+ necb_hdd: @options[:necb_hdd],
181
+ boiler_fuel: @options[:boiler_fuel],
182
+ boiler_cap_ratio: @options[:boiler_cap_ratio],
183
+ swh_fuel: @options[:swh_fuel],
184
+ airloop_fancoils_heating: @options[:airloop_fancoils_heating],
185
+ oerd_utility_pricing: @oerd_utility_pricing)
186
+ end
187
+
188
+ # Save model to disk.
189
+ puts "saving model to #{File.join(@dp_temp_folder, 'output.osm')}"
190
+ BTAP::FileIO.save_osm(model, File.join(@dp_temp_folder, 'output.osm'))
191
+
192
+ # Run annual simulation of model.
193
+ if @options[:run_annual_simulation]
194
+ @run_dir = File.join(@dp_temp_folder, 'run_dir')
195
+ puts "running simulation in #{@run_dir}"
196
+ @standard.model_run_simulation_and_log_errors(model, @run_dir)
197
+ raise("No SQL file found.") if model.sqlFile.empty?
198
+ # Create qaqc file and save it.
199
+ @qaqc = BTAP::Datapoint.build_qaqc(model, @standard, @options[:datapoint_id], @options[:analysis_id])
200
+
201
+ # Load the SQL file into model.
202
+ sql_path = OpenStudio::Path.new(File.join(@run_dir, 'run/eplusout.sql'))
203
+ unless OpenStudio.exists(sql_path)
204
+ raise(
205
+ OpenStudio::Error,
206
+ "openstudio.model.Model", "Results for the sizing run couldn't be found here: #{sql_path}.")
207
+ end
208
+
209
+ sql = OpenStudio::SqlFile.new(sql_path)
210
+
211
+ # Check to make sure the sql file is readable,
212
+ # which won't be true if EnergyPlus crashed during simulation.
213
+ unless sql.connectionOpen
214
+ raise(OpenStudio::Error, 'openstudio.model.Model', "The run failed. Look at the eplusout.err file in" \
215
+ "#{File.dirname(sql_path.to_s)} to see the cause.")
216
+ return false
217
+ end
218
+
219
+ # Attach the SQL file from the run to the sizing model
220
+ model.setSqlFile(sql)
221
+
222
+ post_analysis = nil
223
+ if @options[:enable_costing] or @options[:enable_carbon]
224
+ post_analysis = BTAP::DatapointAnalysis.new(
225
+ model: model,
226
+ output_folder: @dp_temp_folder,
227
+ standard: @standard,
228
+ qaqc: @qaqc)
229
+ end
230
+
231
+ @cost_result = nil
232
+ if @options[:enable_costing]
233
+ @cost_result = post_analysis.run_costing(
234
+ costs_csv: Paths.costs_path,
235
+ factors_csv: Paths.costs_local_factors_path)
236
+ end
237
+
238
+ @carbon_result = nil
239
+ if @options[:enable_carbon]
240
+ @carbon_result = post_analysis.run_carbon
241
+ end
242
+
243
+ @qaqc[:options] = @options # Command-line options
244
+ @btap_data = BTAP::Data.new(model: model,
245
+ runner: nil,
246
+ cost_result: @cost_result,
247
+ carbon_result: @carbon_result,
248
+ qaqc: @qaqc,
249
+ npv_start_year: @npv_start_year,
250
+ npv_end_year: @npv_end_year,
251
+ npv_discount_rate: @npv_discount_rate,
252
+ npv_discount_rate_carbon: @npv_discount_rate_carbon,
253
+ oerd_utility_pricing: @oerd_utility_pricing,
254
+ utility_pricing_year: @utility_pricing_year).btap_data
255
+
256
+ File.open(File.join(@dp_temp_folder, 'btap_data.json'), 'w') do |file|
257
+ file.write(JSON.pretty_generate(@btap_data.sort.to_h, allow_nan: true))
258
+ puts "Wrote File btap_data.json in #{Dir.pwd} "
259
+ end
260
+
261
+ File.open(File.join(@dp_temp_folder, 'qaqc.json'), 'w') do |file|
262
+ file.write(JSON.pretty_generate(@qaqc, allow_nan: true))
263
+ puts "Wrote File qaqc.json in #{Dir.pwd} "
264
+ end
265
+
266
+ self.output_hourly_data(model,@dp_temp_folder, @options[:datapoint_id])
267
+ self.output_timestep_data(model,@dp_temp_folder, @options[:datapoint_id])
268
+ end
269
+ rescue StandardError => error
270
+ @error = error
271
+ puts "Error occured: #{@error}"
272
+ File.open(File.join(@dp_temp_folder, 'error.txt'), 'w') do |f|
273
+ f.write(@error.message + "\n" + @error.backtrace.join("\n"))
274
+ end
275
+ @failed = true
276
+ ensure
277
+ if @s3
278
+ @dp_output_folder = File.join(@s3_output_folder_object, @options[:datapoint_id])
279
+ s3_copy_folder_to_s3(bucket_name: @s3_bucket,
280
+ source_folder: @dp_temp_folder,
281
+ target_folder: @dp_output_folder)
282
+ else
283
+
284
+ # Copy results to datapoint output folder.
285
+ @dp_output_folder = File.join(output_folder, @options[:datapoint_id])
286
+ FileUtils.rm_rf(@dp_output_folder)
287
+ FileUtils.mkdir_p(@dp_output_folder)
288
+ FileUtils.cp_r(File.join(@dp_temp_folder, '.'), @dp_output_folder)
289
+ puts "Copied output to your designated output folder in the #{@options[:datapoint_id]} subfolder."
290
+ end
291
+
292
+ # Clean temp/cache folder up.
293
+ FileUtils.rm_rf(@dp_temp_folder)
294
+
295
+ # Do not fail container if running on AWS, handle error on AWS instead to
296
+ # avoid isuses with large analyses.
297
+ unless @failed == false || @s3
298
+ raise(@error)
299
+ end
300
+ end
301
+ end
302
+
303
+ class << self
304
+
305
+ # Initializes the qaqc data structure. Scoped inside of the class so
306
+ # that it can be used in the intialization of this class as well as in
307
+ # BTAP::Analysis.
308
+ def build_qaqc(model, standard, datapoint_id, analysis_id)
309
+ qaqc = standard.init_qaqc(model)
310
+ command = "SELECT Value
311
+ FROM TabularDataWithStrings
312
+ WHERE ReportName='LEEDsummary'
313
+ AND ReportForString='Entire Facility'
314
+ AND TableName='Sec1.1A-General Information'
315
+ AND RowName = 'Principal Heating Source'
316
+ AND ColumnName='Data'"
317
+ value = model.sqlFile.get.execAndReturnFirstString(command)
318
+ qaqc[:building][:principal_heating_source] = 'unknown'
319
+ unless value.empty?
320
+ qaqc[:building][:principal_heating_source] = value.get
321
+ end
322
+
323
+ if qaqc[:building][:principal_heating_source] == 'Additional Fuel'
324
+ model.getPlantLoops.sort.each do |iplantloop|
325
+ boilers = iplantloop.components.select { |icomponent| icomponent.to_BoilerHotWater.is_initialized }
326
+ qaqc[:building][:principal_heating_source] = 'FuelOilNo2' unless boilers.select { |boiler| boiler.to_BoilerHotWater.get.fuelType.to_s == 'FuelOilNo2' }.empty?
327
+ end
328
+ end
329
+
330
+ qaqc[:aws_datapoint_id] = datapoint_id
331
+ qaqc[:aws_analysis_id] = analysis_id
332
+
333
+ return qaqc
334
+ end
335
+ end
336
+
337
+ def s3_copy_file_to_s3(bucket_name:, source_file:, target_file:, n: 0)
338
+ require 'aws-sdk-s3'
339
+ Aws.use_bundled_cert!
340
+ s3_client = Aws::S3::Client.new(region: 'ca-central-1')
341
+ transfer_manager = Aws::S3::TransferManager.new(client: s3_client)
342
+
343
+ puts("Copying File to S3. source_file:#{source_file} bucket:#{bucket_name} target_folder:#{target_file}")
344
+ response = nil
345
+ begin
346
+ result = transfer_manager.upload_file(source_file, bucket: bucket_name, key: target_file)
347
+
348
+ if result == true
349
+ puts "Object '#{source_file}' uploaded to bucket '#{bucket_name}'."
350
+ else
351
+ puts response
352
+ raise("Error:Object '#{source_file}' not uploaded to bucket '#{bucket_name}'.")
353
+ end
354
+ rescue StandardError => error
355
+
356
+ # Implementing exponential backoff timing out after 8 tries.
357
+ if n == 8
358
+ raise("Giving Up. Failed to submit send file #{source_file} #{target_file} in 8 tries while using exponential backoff. Error was:#{error}.")
359
+ end
360
+
361
+ wait_time = 2 ** n + rand
362
+ puts "Implementing exponential backoff for sending #{target_file} for #{wait_time} seconds. Attempt #{n}..."
363
+ sleep(wait_time)
364
+ return s3_copy_file_to_s3(bucket_name: bucket_name, source_file: source_file, target_file: target_file, n: n + 1)
365
+ end
366
+ return response
367
+ end
368
+
369
+ def s3_copy_folder_to_s3(bucket_name:, source_folder:, target_folder:)
370
+ puts("Copying Folder to S3. source_folder:#{source_folder} bucket:#{bucket_name} target_folder:#{target_folder}")
371
+
372
+ # Iterate over files in folder.
373
+ Dir[File.join(source_folder, '**/*')].reject { |fn| File.directory?(fn) }.each do |source_file|
374
+
375
+ # Convert to S3 path. Removing source parent folders and replacing with s3 output folder.
376
+ target_file = File.join(target_folder, source_file.gsub(source_folder, ''))
377
+ s3_copy_file_to_s3(bucket_name: bucket_name, source_file: source_file, target_file: target_file)
378
+ end
379
+ end
380
+
381
+ def s3_copy(source_folder:, target_folder:)
382
+ require 'open3'
383
+ exit_code = nil
384
+ error = nil
385
+ Open3.popen2e('aws', 's3', 'cp', source_folder, target_folder, '--recursive') do |stdin, stdout_stderr, wait_thread|
386
+ error = stdout_stderr.read
387
+ exit_code = wait_thread.value
388
+ end
389
+ if exit_code != 0
390
+ raise(error)
391
+ end
392
+
393
+ return exit_code
394
+ end
395
+
396
+ def output_hourly_data(model, output_folder, datapoint_id)
397
+ osm_path = File.join(output_folder, "run_dir/in.osm")
398
+ sql_path = File.join(output_folder, "run_dir/run/eplusout.sql")
399
+ csv_output = File.join(output_folder, "hourly.csv")
400
+
401
+ hours_of_year = []
402
+ d = Time.new(2006, 1, 1, 1)
403
+ (0...8760).each do |increment|
404
+ hours_of_year << (d + (60 * 60) * increment).strftime('%Y-%m-%d %H:%M')
405
+ end
406
+
407
+ array_of_hashes = []
408
+
409
+ # Find hourly outputs available for this datapoint.
410
+ query = "
411
+ SELECT ReportDataDictionaryIndex
412
+ FROM ReportDataDictionary
413
+ WHERE ReportingFrequency == 'Hourly'
414
+ "
415
+ # Get hourly data for each output.
416
+ model.sqlFile.get.execAndReturnVectorOfInt(query).get.each do |rdd_index|
417
+
418
+ # Get Name
419
+ query = "
420
+ SELECT Name
421
+ FROM ReportDataDictionary
422
+ WHERE ReportDataDictionaryIndex == #{rdd_index}
423
+ "
424
+ name = model.sqlFile.get.execAndReturnFirstString(query).get
425
+
426
+ # Get KeyValue
427
+ query = "
428
+ SELECT KeyValue
429
+ FROM ReportDataDictionary
430
+ WHERE ReportDataDictionaryIndex == #{rdd_index}
431
+ "
432
+
433
+ # In some cases KeyValue has a value and sometimes it does not. In some cases KeyValue is null. If the command
434
+ # below is run and KeyValue is null then the command fails and returns an error. The fix below assumes that if
435
+ # the command below fails it is because KeyValue is null. In that case the "key_value" variable is set to a
436
+ # blank.
437
+ begin
438
+ key_value = model.sqlFile.get.execAndReturnFirstString(query).get
439
+ rescue StandardError => error
440
+ key_value = ""
441
+ end
442
+
443
+ # Get Units
444
+ query = "
445
+ SELECT Units
446
+ FROM ReportDataDictionary
447
+ WHERE ReportDataDictionaryIndex == #{rdd_index}
448
+ "
449
+ units = model.sqlFile.get.execAndReturnFirstString(query).get
450
+
451
+ # Get hourly data
452
+ query = "
453
+ Select Value
454
+ FROM ReportData
455
+ WHERE
456
+ ReportDataDictionaryIndex = #{rdd_index}
457
+ "
458
+ hourly_values = model.sqlFile.get.execAndReturnVectorOfDouble(query).get
459
+
460
+ hourly_hash = Hash[hours_of_year.zip(hourly_values)]
461
+
462
+ data_hash = { "datapoint_id": datapoint_id, "Name": name, "KeyValue": key_value, "Units": units }.merge(hourly_hash)
463
+ array_of_hashes << data_hash
464
+ end
465
+
466
+ CSV.open(csv_output, "wb") do |csv|
467
+ unless array_of_hashes.empty?
468
+ csv << array_of_hashes.first.keys # adds the attributes name on the first line
469
+ array_of_hashes.each do |hash|
470
+ csv << hash.values
471
+ end
472
+ end
473
+ end
474
+ end
475
+ #=====================================================================================================================
476
+ def output_timestep_data(model, output_folder,datapoint_id)
477
+ osm_path = File.join(output_folder, "run_dir/in.osm")
478
+ sql_path = File.join(output_folder, "run_dir/run/eplusout.sql")
479
+ csv_output = File.join(output_folder, "timestep.csv")
480
+ #===================================================================================================================
481
+ # Get number of timesteps from the model
482
+ number_of_timesteps_per_hour = model.getTimestep.numberOfTimestepsPerHour
483
+ #===================================================================================================================
484
+ # Calculate number of timesteps of the whole year
485
+ number_of_timesteps_of_year = 365 * 24 * number_of_timesteps_per_hour
486
+
487
+ timesteps_of_year = []
488
+ d = Time.new(2006, 1, 1, 0)
489
+ (0...number_of_timesteps_of_year).each do |increment|
490
+ timesteps_of_year << (d + (60 * 60 / number_of_timesteps_per_hour) * increment).strftime('%Y-%m-%d %H:%M')
491
+ end
492
+ timesteps_index = Array(0..number_of_timesteps_of_year-1)
493
+ #===================================================================================================================
494
+ # Create a hash with indices ('timesteps_index') as keys and timesteps as values ('timesteps_of_year')
495
+ # Note from Sara Gilani: 'I had to do this, as when I used 'timesteps_of_year' as keys to create all hashes below
496
+ # (e.g. timestep_hash_datapoint_id, timestep_hash_name, timestep_hash_key_value, ...),
497
+ # it did not create the right number of timesteps for the whole year'
498
+ timesteps_of_year_with_index = Hash[timesteps_index.zip(timesteps_of_year)]
499
+ #===================================================================================================================
500
+ array_of_hashes = []
501
+ array_of_hashes_transposed = []
502
+ #===================================================================================================================
503
+ # Find timestep outputs available for this datapoint.
504
+ query = "
505
+ SELECT ReportDataDictionaryIndex
506
+ FROM ReportDataDictionary
507
+ WHERE ReportingFrequency == 'Zone Timestep'
508
+ "
509
+ #===================================================================================================================
510
+ # Get timestep data for each output.
511
+ model.sqlFile.get.execAndReturnVectorOfInt(query).get.each do |rdd_index|
512
+ #=================================================================================================================
513
+ # Get Name
514
+ query = "
515
+ SELECT Name
516
+ FROM ReportDataDictionary
517
+ WHERE ReportDataDictionaryIndex == #{rdd_index}
518
+ "
519
+ name = model.sqlFile.get.execAndReturnFirstString(query).get
520
+ #=================================================================================================================
521
+ # Get KeyValue
522
+ query = "
523
+ SELECT KeyValue
524
+ FROM ReportDataDictionary
525
+ WHERE ReportDataDictionaryIndex == #{rdd_index}
526
+ "
527
+ #=================================================================================================================
528
+ # In some cases KeyValue has a value and sometimes it does not. In some cases KeyValue is null. If the command
529
+ # below is run and KeyValue is null then the command fails and returns an error. The fix below assumes that if
530
+ # the command below fails it is because KeyValue is null. In that case the "key_value" variable is set to a
531
+ # blank.
532
+ begin
533
+ key_value = model.sqlFile.get.execAndReturnFirstString(query).get
534
+ rescue StandardError => error
535
+ key_value = ""
536
+ end
537
+ #=================================================================================================================
538
+ # Get Units
539
+ query = "
540
+ SELECT Units
541
+ FROM ReportDataDictionary
542
+ WHERE ReportDataDictionaryIndex == #{rdd_index}
543
+ "
544
+ units = model.sqlFile.get.execAndReturnFirstString(query).get
545
+ #=================================================================================================================
546
+ # Get timestep data
547
+ query = "
548
+ Select VariableValue
549
+ FROM ReportVariableData
550
+ WHERE
551
+ ReportVariableDataDictionaryIndex = #{rdd_index}
552
+ "
553
+ timestep_values = model.sqlFile.get.execAndReturnVectorOfDouble(query).get
554
+
555
+ timestep_hash = Hash[timesteps_index.zip(timestep_values)]
556
+
557
+ data_hash = { "datapoint_id": datapoint_id, "Name": name, "KeyValue": key_value, "Units": units }.merge(timestep_hash)
558
+
559
+ array_of_hashes << data_hash
560
+
561
+ #=================================================================================================================
562
+ # Create a hash of data for having timesteps as rows instead of columns that is used for hourly outputs START
563
+ number_of_timesteps = timesteps_of_year.length()
564
+
565
+ array = [datapoint_id]
566
+ array_datapoint_id = array.zip(*[array]*number_of_timesteps).flatten
567
+ timestep_hash_datapoint_id = Hash[timesteps_index.zip(array_datapoint_id)]
568
+
569
+ array = [name]
570
+ array_name = array.zip(*[array]*number_of_timesteps).flatten
571
+ timestep_hash_name = Hash[timesteps_index.zip(array_name)]
572
+
573
+ array = [key_value]
574
+ array_key_value = array.zip(*[array]*number_of_timesteps).flatten
575
+ timestep_hash_key_value = Hash[timesteps_index.zip(array_key_value)]
576
+
577
+ array = [units]
578
+ array_units = array.zip(*[array]*number_of_timesteps).flatten
579
+ timestep_hash_units = Hash[timesteps_index.zip(array_units)]
580
+
581
+ timestep_hash_values = Hash[timesteps_index.zip(timestep_values)]
582
+
583
+ data_hash_transposed = Hash.new
584
+ timestep_hash_values.keys.each do |key|
585
+ data_hash_transposed[key] = [
586
+ timesteps_of_year_with_index[key],
587
+ timestep_hash_datapoint_id[key],
588
+ timestep_hash_name[key],
589
+ timestep_hash_key_value[key],
590
+ timestep_hash_units[key],
591
+ timestep_hash_values[key]
592
+ ]
593
+ end
594
+
595
+ array_of_hashes_transposed << data_hash_transposed
596
+ # Create a hash of data for having timesteps as rows instead of columns that is used for hourly outputs END
597
+ #=================================================================================================================
598
+
599
+ end #model.sqlFile.get.execAndReturnVectorOfInt(query).get.each do |rdd_index|
600
+ #===================================================================================================================
601
+ # # This csv file has timesteps as the first row
602
+ # CSV.open(csv_output, "wb") do |csv|
603
+ # unless array_of_hashes.empty?
604
+ # csv << array_of_hashes.first.keys # adds the attributes name on the first line
605
+ # array_of_hashes.each do |hash|
606
+ # csv << hash.values
607
+ # end
608
+ # end
609
+ # end
610
+ #===================================================================================================================
611
+ # This csv file has timesteps as the first column
612
+ header = ['Index', 'Timestep', 'datapoint_id', 'Name', 'KeyValue', 'Units', 'Value']
613
+ CSV.open(csv_output, "wb") do |csv|
614
+ csv << header
615
+ array_of_hashes_transposed.each do |hash|
616
+ hash.keys().each do |index|
617
+ row_index = index
618
+ row_timestep = hash[index][0]
619
+ row_datapoint_id = hash[index][1]
620
+ row_name = hash[index][2]
621
+ row_key_value = hash[index][3]
622
+ row_units = hash[index][4]
623
+ row_value = hash[index][5]
624
+
625
+ row = CSV::Row.new(header,[])
626
+ row['Index'] = row_index
627
+ row['Timestep'] = row_timestep
628
+ row['datapoint_id'] = row_datapoint_id
629
+ row['Name'] = row_name
630
+ row['KeyValue'] = row_key_value
631
+ row['Units'] = row_units
632
+ row['Value'] = row_value
633
+ csv << row
634
+ end
635
+ end
636
+ end
637
+ #===================================================================================================================
638
+ end #output_timestep_data
639
+ #=====================================================================================================================
640
+ end
641
+ end