From d4bd5e0047fb5b76265c6390c1a617415c688f59 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Tue, 17 Sep 2024 17:37:27 -0600 Subject: [PATCH 1/3] Reorganizes code so that output files are written last in the HPXMLtoOpenStudio measure. --- HPXMLtoOpenStudio/measure.rb | 42 ++++++++++++------- HPXMLtoOpenStudio/measure.xml | 12 +++--- HPXMLtoOpenStudio/resources/hpxml_defaults.rb | 20 ++++----- HPXMLtoOpenStudio/resources/hvac_sizing.rb | 21 +++------- HPXMLtoOpenStudio/resources/output.rb | 2 +- 5 files changed, 48 insertions(+), 49 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.rb b/HPXMLtoOpenStudio/measure.rb index 7d1b27cd02..578e5effcf 100644 --- a/HPXMLtoOpenStudio/measure.rb +++ b/HPXMLtoOpenStudio/measure.rb @@ -121,18 +121,11 @@ 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 = process_defaults_schedules_emissions_files(runner, weather, hpxml, args) + hpxml_sch_map, hpxml_all_zone_loads, hpxml_all_space_loads = 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]) - # 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]) - # Create OpenStudio unit model(s) hpxml_osm_map = {} hpxml.buildings.each_with_index do |hpxml_bldg, i| @@ -153,12 +146,27 @@ def run(model, runner, user_arguments) Model.merge_unit_models(model, hpxml_osm_map) end - # Create/write output + # Create EnergyPlus outputs Outputs.apply_ems_programs(model, hpxml_osm_map, hpxml.header, args[:add_component_loads]) - Outputs.apply_output_files(model, args[:debug]) + Outputs.apply_output_file_controls(model, args[:debug]) Outputs.apply_additional_properties(model, hpxml, hpxml_osm_map, args[:hpxml_path], args[:building_id], args[:hpxml_defaults_path]) - Outputs.write_debug_files(runner, model, args[:debug], args[:output_dir], epw_path) # Outputs.apply_ems_debug_output(model) # Uncomment to debug EMS + + # Write output files + Outputs.write_debug_files(runner, model, args[:debug], args[:output_dir], epw_path) + + # 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]) + + # 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 rescue Exception => e runner.registerError("#{e.message}\n#{e.backtrace.join("\n")}") return false @@ -265,9 +273,11 @@ 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 [Hash] Map of HPXML Building => SchedulesFile object + # @return [Array] Maps of HPXML Building => SchedulesFile object, HPXML Building => (Map of HPXML::Zones => DesignLoadValues object), HPXML Building => (Map of HPXML::Spaces => DesignLoadValues object) def process_defaults_schedules_emissions_files(runner, weather, hpxml, args) hpxml_sch_map = {} + hpxml_all_zone_loads = {} + hpxml_all_space_loads = {} hpxml.buildings.each_with_index do |hpxml_bldg, i| # Schedules file Schedule.check_schedule_references(hpxml_bldg.header, args[:hpxml_path]) @@ -281,16 +291,16 @@ def process_defaults_schedules_emissions_files(runner, weather, hpxml, args) hpxml_sch_map[hpxml_bldg] = schedules_file # HPXML defaults - HPXMLDefaults.apply(runner, hpxml, hpxml_bldg, weather, schedules_file: schedules_file, - design_load_details_output_file_path: args[:design_load_details_output_file_path], - output_format: args[:output_format]) + all_zone_loads, all_space_loads = HPXMLDefaults.apply(runner, hpxml, hpxml_bldg, weather, schedules_file: schedules_file) + hpxml_all_zone_loads[hpxml_bldg] = all_zone_loads + hpxml_all_space_loads[hpxml_bldg] = all_space_loads end # Emissions files Schedule.check_emissions_references(hpxml.header, args[:hpxml_path]) Schedule.validate_emissions_files(hpxml.header) - return hpxml_sch_map + return hpxml_sch_map, hpxml_all_zone_loads, hpxml_all_space_loads 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 a512400061..18cb0ccf5d 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 3818f9b2-1396-4d2f-8556-5245f035f8e2 - 2024-09-17T21:45:12Z + 05f72384-5d38-4f10-81d0-1fda7397f468 + 2024-09-17T23:36:16Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -183,7 +183,7 @@ measure.rb rb script - AE303996 + 1C013D19 airflow.rb @@ -357,7 +357,7 @@ hpxml_defaults.rb rb resource - 4F6E5BAE + 87C6D4D7 hpxml_schema/HPXML.xsd @@ -393,7 +393,7 @@ hvac_sizing.rb rb resource - 9E3F6A59 + 830F4353 internal_gains.rb @@ -453,7 +453,7 @@ output.rb rb resource - 67DCE0D1 + F19EA74F psychrometrics.rb diff --git a/HPXMLtoOpenStudio/resources/hpxml_defaults.rb b/HPXMLtoOpenStudio/resources/hpxml_defaults.rb index 8620343aeb..7645d74f90 100644 --- a/HPXMLtoOpenStudio/resources/hpxml_defaults.rb +++ b/HPXMLtoOpenStudio/resources/hpxml_defaults.rb @@ -25,11 +25,8 @@ module HPXMLDefaults # @param weather [WeatherFile] Weather object containing EPW information # @param schedules_file [SchedulesFile] SchedulesFile wrapper class instance of detailed schedule files # @param convert_shared_systems [Boolean] Whether to convert shared systems to equivalent in-unit systems per ANSI 301 - # @param design_load_details_output_file_path [String] Detailed HVAC sizing output file path - # @param output_format [String] Detailed HVAC sizing output file format ('csv', 'json', or 'msgpack') - # @return [nil] - def self.apply(runner, hpxml, hpxml_bldg, weather, schedules_file: nil, convert_shared_systems: true, - design_load_details_output_file_path: nil, output_format: 'csv') + # @return [Array] Maps of HPXML::Zones => DesignLoadValues object, HPXML::Spaces => DesignLoadValues object + def self.apply(runner, hpxml, hpxml_bldg, weather, schedules_file: nil, convert_shared_systems: true) cfa = hpxml_bldg.building_construction.conditioned_floor_area nbeds = hpxml_bldg.building_construction.number_of_bedrooms ncfl = hpxml_bldg.building_construction.number_of_conditioned_floors @@ -102,12 +99,14 @@ def self.apply(runner, hpxml, hpxml_bldg, weather, schedules_file: nil, convert_ apply_batteries(hpxml_bldg) # Do HVAC sizing after all other defaults have been applied - apply_hvac_sizing(runner, hpxml_bldg, weather, output_format, design_load_details_output_file_path) + all_zone_loads, all_space_loads = apply_hvac_sizing(runner, hpxml_bldg, weather) # Default detailed performance has to be after sizing to have autosized capacity information apply_detailed_performance_data_for_var_speed_systems(hpxml_bldg) cleanup_zones_spaces(hpxml_bldg) + + return all_zone_loads, all_space_loads end # Returns a list of four azimuths (facing each direction). Determined based @@ -3857,12 +3856,11 @@ def self.apply_fuel_loads(hpxml_bldg, cfa, schedules_file) # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param weather [WeatherFile] Weather object containing EPW information - # @param output_format [String] Detailed output file format ('csv', 'json', or 'msgpack') - # @param design_load_details_output_file_path [String] Detailed HVAC sizing output file path - # @return [nil] - def self.apply_hvac_sizing(runner, hpxml_bldg, weather, output_format, design_load_details_output_file_path) + # @return [Array] Maps of HPXML::Zones => DesignLoadValues object, HPXML::Spaces => DesignLoadValues object + def self.apply_hvac_sizing(runner, hpxml_bldg, weather) hvac_systems = HVAC.get_hpxml_hvac_systems(hpxml_bldg) - HVACSizing.calculate(runner, weather, hpxml_bldg, hvac_systems, output_format: output_format, output_file_path: design_load_details_output_file_path) + _, all_zone_loads, all_space_loads = HVACSizing.calculate(runner, weather, hpxml_bldg, hvac_systems) + return all_zone_loads, all_space_loads end # Converts an HPXML orientation to an HPXML azimuth. diff --git a/HPXMLtoOpenStudio/resources/hvac_sizing.rb b/HPXMLtoOpenStudio/resources/hvac_sizing.rb index a120dd9d94..f577f71468 100644 --- a/HPXMLtoOpenStudio/resources/hvac_sizing.rb +++ b/HPXMLtoOpenStudio/resources/hvac_sizing.rb @@ -11,12 +11,8 @@ module HVACSizing # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hvac_systems [Array] List of HPXML HVAC (heating and/or cooling) systems # @param update_hpxml [Boolean] Whether to update the HPXML object so that in.xml reports capacities/airflows - # @param output_format [String] Detailed output file format ('csv', 'json', or 'msgpack') - # @param output_file_path [String] Detailed output file path - # @return [Hash] Map of HVAC systems => HVACSizingValues objects - def self.calculate(runner, weather, hpxml_bldg, hvac_systems, update_hpxml: true, - output_format: 'csv', output_file_path: nil) - + # @return [Array] Maps of HVAC systems => HVACSizingValues objects, HPXML::Zones => DesignLoadValues object, HPXML::Spaces => DesignLoadValues object + def self.calculate(runner, weather, hpxml_bldg, hvac_systems, update_hpxml: true) check_for_errors(hpxml_bldg, hvac_systems) mj = MJValues.new @@ -111,12 +107,7 @@ def self.calculate(runner, weather, hpxml_bldg, hvac_systems, update_hpxml: true end end - # Write detailed outputs (useful for Form J1) - if not output_file_path.nil? - write_detailed_output(output_format, output_file_path, hpxml_bldg, all_zone_loads, all_space_loads) - end - - return @all_hvac_sizings + return @all_hvac_sizings, all_zone_loads, all_space_loads end # Checks whether we will be performing sizing calculations on the given HPXML HVAC system. @@ -3162,7 +3153,7 @@ def self.process_heat_pump_adjustment(mj, runner, hvac_sizings, weather, hvac_he # Calculate the heating load at the switchover temperature to limit unutilized capacity temp_heat_design_temp = hpxml_bldg.header.manualj_heating_design_temp hpxml_bldg.header.manualj_heating_design_temp = min_compressor_temp - alternate_all_hvac_sizings = calculate(runner, weather, hpxml_bldg, [hvac_system], update_hpxml: false) + alternate_all_hvac_sizings = calculate(runner, weather, hpxml_bldg, [hvac_system], update_hpxml: false)[0] heating_load = alternate_all_hvac_sizings[hvac_system].Heat_Load heating_temp = min_compressor_temp hpxml_bldg.header.manualj_heating_design_temp = temp_heat_design_temp @@ -4794,8 +4785,8 @@ def self.get_surfaces_with_property(obj, additional_property_type) end # Note: Every report name must have the HPXML BuildingID in it in case we are running a whole MF building with multiple Building elements. - if hpxml_bldg.conditioned_zones[0].id.start_with?(Constants::AutomaticallyAdded) - zone_col_names = ["#{hpxml_bldg.building_id}"] # Leave out name of automatically added zone + if hpxml_bldg.conditioned_zones.empty? + zone_col_names = ["#{hpxml_bldg.building_id}"] else zone_col_names = all_zone_loads.keys.map { |zone| "#{hpxml_bldg.building_id}: #{zone.id}" } end diff --git a/HPXMLtoOpenStudio/resources/output.rb b/HPXMLtoOpenStudio/resources/output.rb index 8a4a6dc3e9..6acadfac7c 100644 --- a/HPXMLtoOpenStudio/resources/output.rb +++ b/HPXMLtoOpenStudio/resources/output.rb @@ -917,7 +917,7 @@ def self.apply_total_airflows_ems_program(model, hpxml_osm_map) # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param debug [Boolean] If true, writes in.osm, generates additional log output, and creates all E+ output files # @return [nil] - def self.apply_output_files(model, debug) + def self.apply_output_file_controls(model, debug) oj = model.getOutputJSON oj.setOptionType('TimeSeriesAndTabular') oj.setOutputJSON(debug) From 260f10fdcbe03fcf88ffb3a821897b4892c63ed5 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Tue, 17 Sep 2024 17:58:16 -0600 Subject: [PATCH 2/3] Bugfix. --- HPXMLtoOpenStudio/measure.xml | 6 +++--- HPXMLtoOpenStudio/resources/hvac_sizing.rb | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index 18cb0ccf5d..b82bdeb01d 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 05f72384-5d38-4f10-81d0-1fda7397f468 - 2024-09-17T23:36:16Z + e75588d5-a38c-480a-a4fb-3379313018a1 + 2024-09-17T23:57:51Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -393,7 +393,7 @@ hvac_sizing.rb rb resource - 830F4353 + 9EDA01CC internal_gains.rb diff --git a/HPXMLtoOpenStudio/resources/hvac_sizing.rb b/HPXMLtoOpenStudio/resources/hvac_sizing.rb index f577f71468..6a2dfa5dd8 100644 --- a/HPXMLtoOpenStudio/resources/hvac_sizing.rb +++ b/HPXMLtoOpenStudio/resources/hvac_sizing.rb @@ -4824,27 +4824,32 @@ def self.get_surfaces_with_property(obj, additional_property_type) # Zone results all_zone_loads.keys.each_with_index do |zone, i| + if hpxml_bldg.conditioned_zones.empty? + zone_or_bldg = hpxml_bldg + else + zone_or_bldg = zone + end results_out << [line_break] results_out << ["Report: #{zone_col_names[i]}: Loads", 'Area (ft^2)', 'Length (ft)', 'Wall Area Ratio', 'Heating (Btuh)', 'Cooling Sensible (Btuh)', 'Cooling Latent (Btuh)'] - get_surfaces_with_property(zone, :detailed_output_values_windows).each do |window, fj1| + get_surfaces_with_property(zone_or_bldg, :detailed_output_values_windows).each do |window, fj1| results_out << ["Windows: #{window.id}", fj1.Area, fj1.Length, nil, fj1.Heat_Load, fj1.Cool_Load_Sens] end - get_surfaces_with_property(zone, :detailed_output_values_skylights).each do |skylight, fj1| + get_surfaces_with_property(zone_or_bldg, :detailed_output_values_skylights).each do |skylight, fj1| results_out << ["Skylights: #{skylight.id}", fj1.Area, fj1.Length, nil, fj1.Heat_Load, fj1.Cool_Load_Sens] end - get_surfaces_with_property(zone, :detailed_output_values_doors).each do |door, fj1| + get_surfaces_with_property(zone_or_bldg, :detailed_output_values_doors).each do |door, fj1| results_out << ["Doors: #{door.id}", fj1.Area, fj1.Length, nil, fj1.Heat_Load, fj1.Cool_Load_Sens] end - get_surfaces_with_property(zone, :detailed_output_values_above_grade_walls).each do |wall, fj1| + get_surfaces_with_property(zone_or_bldg, :detailed_output_values_above_grade_walls).each do |wall, fj1| results_out << ["Above Grade Walls: #{wall.id}", fj1.Area, fj1.Length, nil, fj1.Heat_Load, fj1.Cool_Load_Sens] end - get_surfaces_with_property(zone, :detailed_output_values_below_grade_walls).each do |wall, fj1| + get_surfaces_with_property(zone_or_bldg, :detailed_output_values_below_grade_walls).each do |wall, fj1| results_out << ["Below Grade Walls: #{wall.id}", fj1.Area, fj1.Length, nil, fj1.Heat_Load, fj1.Cool_Load_Sens] end - get_surfaces_with_property(zone, :detailed_output_values_ceilings).each do |ceiling, fj1| + get_surfaces_with_property(zone_or_bldg, :detailed_output_values_ceilings).each do |ceiling, fj1| results_out << ["Ceilings: #{ceiling.id}", fj1.Area, fj1.Length, nil, fj1.Heat_Load, fj1.Cool_Load_Sens] end - get_surfaces_with_property(zone, :detailed_output_values_floors).each do |floor, fj1| + get_surfaces_with_property(zone_or_bldg, :detailed_output_values_floors).each do |floor, fj1| results_out << ["Floors: #{floor.id}", fj1.Area, fj1.Length, nil, fj1.Heat_Load, fj1.Cool_Load_Sens] end zone_loads = all_zone_loads[zone] From 3f8efb3bea1620fec9cd7ffead16a793f385ccfc Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Tue, 17 Sep 2024 18:03:26 -0600 Subject: [PATCH 3/3] Bugfix. --- HPXMLtoOpenStudio/measure.xml | 6 +++--- HPXMLtoOpenStudio/resources/output.rb | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index b82bdeb01d..be896b9c6f 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - e75588d5-a38c-480a-a4fb-3379313018a1 - 2024-09-17T23:57:51Z + 4aeccf55-7efe-4b55-b392-a9d5bd7ae8d2 + 2024-09-18T00:03:05Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -453,7 +453,7 @@ output.rb rb resource - F19EA74F + 94DB6377 psychrometrics.rb diff --git a/HPXMLtoOpenStudio/resources/output.rb b/HPXMLtoOpenStudio/resources/output.rb index 6acadfac7c..abf99974ff 100644 --- a/HPXMLtoOpenStudio/resources/output.rb +++ b/HPXMLtoOpenStudio/resources/output.rb @@ -1220,6 +1220,7 @@ def self.write_results_out_to_file(results_out, output_format, output_file_path, require 'json' File.open(output_file_path, mode) { |json| json.write(JSON.pretty_generate(h)) } elsif output_format == 'msgpack' + require 'msgpack' File.open(output_file_path, "#{mode}b") { |json| h.to_msgpack(json) } end end