From 36350b91e00471724a77a0f006e7107cf04e5a4f Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Tue, 29 Oct 2024 19:53:39 -0600 Subject: [PATCH 1/3] Fixes possibility of missing surfaces in the results_design_load_details.csv output file. Bug introduced in #1836. --- HPXMLtoOpenStudio/measure.rb | 26 +++++++++++++--------- HPXMLtoOpenStudio/measure.xml | 8 +++---- HPXMLtoOpenStudio/resources/hvac_sizing.rb | 17 +++++++++----- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.rb b/HPXMLtoOpenStudio/measure.rb index 11c5b9e10f..173c9b18a2 100644 --- a/HPXMLtoOpenStudio/measure.rb +++ b/HPXMLtoOpenStudio/measure.rb @@ -121,7 +121,7 @@ def run(model, runner, user_arguments) # Do these once upfront for the entire HPXML object epw_path, weather = process_weather(runner, hpxml, args) process_whole_sfa_mf_inputs(hpxml) - hpxml_sch_map, hpxml_all_zone_loads, hpxml_all_space_loads = process_defaults_schedules_emissions_files(runner, weather, hpxml, args) + hpxml_sch_map, design_loads_results_out = process_defaults_schedules_emissions_files(runner, weather, hpxml, args) # Write updated HPXML object (w/ defaults) to file for inspection XMLHelper.write_file(hpxml.to_doc, args[:hpxml_defaults_path]) @@ -157,16 +157,13 @@ def run(model, runner, user_arguments) # Write annual results output file # This is helpful if the user wants to get these results right away (e.g., - # they might be using the run_simulation.rb --skip-simulation argument. - results_out = [] - Outputs.append_sizing_results(hpxml.buildings, results_out) - Outputs.write_results_out_to_file(results_out, args[:output_format], args[:annual_output_file_path]) + # they might be using the run_simulation.rb --skip-simulation argument). + annual_results_out = [] + Outputs.append_sizing_results(hpxml.buildings, annual_results_out) + Outputs.write_results_out_to_file(annual_results_out, args[:output_format], args[:annual_output_file_path]) # Write design load details output file - hpxml.buildings.each do |hpxml_bldg| - HVACSizing.write_detailed_output(args[:output_format], args[:design_load_details_output_file_path], - hpxml_bldg, hpxml_all_zone_loads[hpxml_bldg], hpxml_all_space_loads[hpxml_bldg]) - end + HVACSizing.write_detailed_output(design_loads_results_out, args[:output_format], args[:design_load_details_output_file_path]) rescue Exception => e runner.registerError("#{e.message}\n#{e.backtrace.join("\n")}") return false @@ -273,7 +270,7 @@ def process_whole_sfa_mf_inputs(hpxml) # @param weather [WeatherFile] Weather object containing EPW information # @param hpxml [HPXML] HPXML object # @param args [Hash] Map of :argument_name => value - # @return [Array] Maps of HPXML Building => SchedulesFile object, HPXML Building => (Map of HPXML::Zones => DesignLoadValues object), HPXML Building => (Map of HPXML::Spaces => DesignLoadValues object) + # @return [Array] Maps of HPXML Building => SchedulesFile object, Rows of design loads output data def process_defaults_schedules_emissions_files(runner, weather, hpxml, args) hpxml_sch_map = {} hpxml_all_zone_loads = {} @@ -300,7 +297,14 @@ def process_defaults_schedules_emissions_files(runner, weather, hpxml, args) Schedule.check_emissions_references(hpxml.header, args[:hpxml_path]) Schedule.validate_emissions_files(hpxml.header) - return hpxml_sch_map, hpxml_all_zone_loads, hpxml_all_space_loads + # Compile design load outputs for subsequent writing + # This needs to come before we collapse enclosure surfaces + design_loads_results_out = [] + hpxml.buildings.each do |hpxml_bldg| + HVACSizing.append_detailed_output(hpxml_bldg, hpxml_all_zone_loads[hpxml_bldg], hpxml_all_space_loads[hpxml_bldg], design_loads_results_out) + end + + return hpxml_sch_map, design_loads_results_out end # Creates a full OpenStudio model that represents the given HPXML individual dwelling by diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index b0b0e9790e..161d7c00de 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 64c3bdea-b4b3-4254-9b2a-23987ae4e9f3 - 2024-10-25T00:51:54Z + 3fc03854-d418-4b79-a205-a24e0a715c1e + 2024-10-30T01:52:33Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -183,7 +183,7 @@ measure.rb rb script - E1E63AE7 + 779C018D airflow.rb @@ -393,7 +393,7 @@ hvac_sizing.rb rb resource - CD77F9C7 + 485A43BF internal_gains.rb diff --git a/HPXMLtoOpenStudio/resources/hvac_sizing.rb b/HPXMLtoOpenStudio/resources/hvac_sizing.rb index d35a98c61d..3babd8a5ed 100644 --- a/HPXMLtoOpenStudio/resources/hvac_sizing.rb +++ b/HPXMLtoOpenStudio/resources/hvac_sizing.rb @@ -4892,17 +4892,16 @@ def self.assign_to_hpxml_obj(hpxml_object, loads) end end - # Writes a output file with additional detailed information needed to fill out, e.g., an ACCA Form J1. + # Appends additional detailed information needed to fill out, e.g., an ACCA Form J1 to the provided array + # for eventual writing to an output file. # - # @param output_format [String] Detailed output file format ('csv', 'json', or 'msgpack') - # @param output_file_path [String] Detailed output file path # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param all_zone_loads [Hash] Map of HPXML::Zones => DesignLoadValues object # @param all_space_loads [Hash] Map of HPXML::Spaces => DesignLoadValues object + # @param results_out [Array] Rows of output data # @return [nil] - def self.write_detailed_output(output_format, output_file_path, hpxml_bldg, all_zone_loads, all_space_loads) + def self.append_detailed_output(hpxml_bldg, all_zone_loads, all_space_loads, results_out) line_break = nil - results_out = [] orientation_map = { HPXML::OrientationEast => 'E', HPXML::OrientationNorth => 'N', @@ -5048,7 +5047,15 @@ def self.get_surfaces_with_property(obj, additional_property_type) all_space_loads.values.each_with_index do |space_loads, i| results_out << [space_col_names[i]] + space_loads.HourlyFenestrationLoads.map { |l| l.round } end + end + # Writes an output file for the given rows of output data. + # + # @param results_out [Array] Rows of output data + # @param output_format [String] Detailed output file format ('csv', 'json', or 'msgpack') + # @param output_file_path [String] Detailed output file path + # @return [nil] + def self.write_detailed_output(results_out, output_format, output_file_path) if ['csv'].include? output_format if File.exist? output_file_path # Separate from existing data From 94ddd8329aa35de8b59b598503dd99d5508a2056 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Tue, 29 Oct 2024 21:26:49 -0600 Subject: [PATCH 2/3] Bugfix. --- HPXMLtoOpenStudio/measure.rb | 3 ++- HPXMLtoOpenStudio/measure.xml | 8 ++++---- HPXMLtoOpenStudio/resources/hvac_sizing.rb | 17 ++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.rb b/HPXMLtoOpenStudio/measure.rb index 173c9b18a2..b22a139b26 100644 --- a/HPXMLtoOpenStudio/measure.rb +++ b/HPXMLtoOpenStudio/measure.rb @@ -301,7 +301,8 @@ def process_defaults_schedules_emissions_files(runner, weather, hpxml, args) # This needs to come before we collapse enclosure surfaces design_loads_results_out = [] hpxml.buildings.each do |hpxml_bldg| - HVACSizing.append_detailed_output(hpxml_bldg, hpxml_all_zone_loads[hpxml_bldg], hpxml_all_space_loads[hpxml_bldg], design_loads_results_out) + HVACSizing.append_detailed_output(args[:output_format], hpxml_bldg, hpxml_all_zone_loads[hpxml_bldg], + hpxml_all_space_loads[hpxml_bldg], design_loads_results_out) end return hpxml_sch_map, design_loads_results_out diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index 161d7c00de..5aee7a0f80 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 3fc03854-d418-4b79-a205-a24e0a715c1e - 2024-10-30T01:52:33Z + 0afcab4c-84c4-48d2-bba4-8be0c8e3f1e1 + 2024-10-30T03:25:11Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -183,7 +183,7 @@ measure.rb rb script - 779C018D + D7F18DFB airflow.rb @@ -393,7 +393,7 @@ hvac_sizing.rb rb resource - 485A43BF + 10626CF1 internal_gains.rb diff --git a/HPXMLtoOpenStudio/resources/hvac_sizing.rb b/HPXMLtoOpenStudio/resources/hvac_sizing.rb index 3babd8a5ed..e76519139e 100644 --- a/HPXMLtoOpenStudio/resources/hvac_sizing.rb +++ b/HPXMLtoOpenStudio/resources/hvac_sizing.rb @@ -4895,14 +4895,20 @@ def self.assign_to_hpxml_obj(hpxml_object, loads) # Appends additional detailed information needed to fill out, e.g., an ACCA Form J1 to the provided array # for eventual writing to an output file. # + # @param output_format [String] Detailed output file format ('csv', 'json', or 'msgpack') # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param all_zone_loads [Hash] Map of HPXML::Zones => DesignLoadValues object # @param all_space_loads [Hash] Map of HPXML::Spaces => DesignLoadValues object # @param results_out [Array] Rows of output data # @return [nil] - def self.append_detailed_output(hpxml_bldg, all_zone_loads, all_space_loads, results_out) + def self.append_detailed_output(output_format, hpxml_bldg, all_zone_loads, all_space_loads, results_out) line_break = nil + if (output_format == 'csv') && (not results_out.empty?) + # Separate from existing data with line break + results_out << [line_break] + end + orientation_map = { HPXML::OrientationEast => 'E', HPXML::OrientationNorth => 'N', HPXML::OrientationNortheast => 'NE', @@ -5056,11 +5062,8 @@ def self.get_surfaces_with_property(obj, additional_property_type) # @param output_file_path [String] Detailed output file path # @return [nil] def self.write_detailed_output(results_out, output_format, output_file_path) + line_break = nil if ['csv'].include? output_format - if File.exist? output_file_path - # Separate from existing data - results_out.insert(0, [line_break]) - end CSV.open(output_file_path, 'a') { |csv| results_out.to_a.each { |elem| csv << elem } } elsif ['json', 'msgpack'].include? output_format h = {} @@ -5088,10 +5091,6 @@ def self.write_detailed_output(results_out, output_format, output_file_path) h[report][name] = items end - if File.exist? output_file_path - h = JSON.parse(File.read(output_file_path)).merge(h) - end - if output_format == 'json' require 'json' File.open(output_file_path, 'w') { |json| json.write(JSON.pretty_generate(h)) } From 4d4fc470662d80da5b74ced67aff3bd514fd50d8 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Tue, 29 Oct 2024 22:56:27 -0600 Subject: [PATCH 3/3] Add test. --- HPXMLtoOpenStudio/measure.xml | 6 ++-- HPXMLtoOpenStudio/tests/test_hvac_sizing.rb | 34 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index 5aee7a0f80..c3baa583be 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 0afcab4c-84c4-48d2-bba4-8be0c8e3f1e1 - 2024-10-30T03:25:11Z + af21de2e-e4ba-4fda-9f29-f4ad94d0475c + 2024-10-30T04:55:55Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -693,7 +693,7 @@ test_hvac_sizing.rb rb test - C88CFFFC + 475611E3 test_lighting.rb diff --git a/HPXMLtoOpenStudio/tests/test_hvac_sizing.rb b/HPXMLtoOpenStudio/tests/test_hvac_sizing.rb index c1317f888d..97c1fc27ef 100644 --- a/HPXMLtoOpenStudio/tests/test_hvac_sizing.rb +++ b/HPXMLtoOpenStudio/tests/test_hvac_sizing.rb @@ -1800,6 +1800,40 @@ def test_gshp_all_g_function_configs_exist end end + def test_detailed_design_load_output_file + args_hash = { 'output_format' => 'json', + 'hpxml_path' => File.absolute_path(File.join(@test_files_path, 'ACCA_Examples', 'Bob_Ross_Residence.xml')) } + _model, _hpxml, hpxml_bldg = _test_measure(args_hash) + design_load_details_path = File.absolute_path(File.join(File.dirname(__FILE__), 'results_design_load_details.json')) + json = JSON.parse(File.read(design_load_details_path)) + + tol = 10 # Btuh, allow some tolerance due to rounding + + num_reports = 0 + json.keys.each do |report| + next unless report.include? 'Loads' + + sum_htg = 0 + sum_clg_sens = 0 + sum_clg_lat = 0 + json[report].keys.each do |component| + if component == 'Total' + num_reports += 1 + assert_in_delta(sum_htg, json[report][component]['Heating (Btuh)'].to_f, tol) + assert_in_delta(sum_clg_sens, json[report][component]['Cooling Sensible (Btuh)'].to_f, tol) + assert_in_delta(sum_clg_lat, json[report][component]['Cooling Latent (Btuh)'].to_f, tol) + else + sum_htg += json[report][component]['Heating (Btuh)'].to_f + sum_clg_sens += json[report][component]['Cooling Sensible (Btuh)'].to_f + sum_clg_lat += json[report][component]['Cooling Latent (Btuh)'].to_f + end + end + end + + num_expected_reports = hpxml_bldg.conditioned_zones.size + hpxml_bldg.conditioned_spaces.size + assert_equal(num_expected_reports, num_reports) + end + def _test_measure(args_hash) # create an instance of the measure measure = HPXMLtoOpenStudio.new