diff --git a/docs/technical_development_guide/source/basic_tutorial/resstock_architecture.rst b/docs/technical_development_guide/source/basic_tutorial/resstock_architecture.rst index fd9fbd42e1..4cc0b600a7 100644 --- a/docs/technical_development_guide/source/basic_tutorial/resstock_architecture.rst +++ b/docs/technical_development_guide/source/basic_tutorial/resstock_architecture.rst @@ -65,6 +65,7 @@ The BuildExistingModel and ApplyUpgrade meta measures call the following model m 1 ResStockArguments Model No ResStock 2 BuildResidentialHPXML Model No OS-HPXML 3 BuildResidentialScheduleFile Model No OS-HPXML + 4 ResStockArgumentsPostHPXML Model No ResStock ===== ============================= ================== ========= ============= ========================== .. _model-measures: @@ -117,6 +118,16 @@ They contribute to the generation of the model. :start-after: :end-before: < +**ResStockArgumentsPostHPXML** + + .. include:: ../../../../measures/ResStockArgumentsPostHPXML/measure.xml + :start-after: + :end-before: < + + .. include:: ../../../../measures/ResStockArgumentsPostHPXML/measure.xml + :start-after: + :end-before: < + .. _tutorial-apply-upgrade: **ApplyUpgrade** diff --git a/docs/technical_development_guide/source/changelog/changelog_dev.rst b/docs/technical_development_guide/source/changelog/changelog_dev.rst index cdbb0dbcab..e8d3293069 100644 --- a/docs/technical_development_guide/source/changelog/changelog_dev.rst +++ b/docs/technical_development_guide/source/changelog/changelog_dev.rst @@ -6,6 +6,24 @@ Development Changelog :version: v3.5.0 :released: pending + .. change:: + :tags: workflow, feature + :pullreq: 929 + :tickets: 927 + + **Date**: 2025-02-04 + + Title: + New ResStockArgumentsPostHPXML measure + + Description: + This measure is introduced to the workflow for postprocessing the output of the BuildResidentialHPXML and BuildResidentialScheduleFile measures. + In short, we can use generated schedules (e.g., occupant schedule) to create other detailed schedules (e.g., setpoint schedules). + Currently, this is just a stubbed version of the measure -- future versions will actually take advantage of the new functionality. + + Assignees: Joe Robertson, Rajendra Adhikari + + .. change:: :tags: characteristics :pullreq: 1339 diff --git a/measures/ApplyUpgrade/measure.rb b/measures/ApplyUpgrade/measure.rb index 3373047bbb..3fed2b399a 100644 --- a/measures/ApplyUpgrade/measure.rb +++ b/measures/ApplyUpgrade/measure.rb @@ -524,10 +524,20 @@ def run(model, runner, user_arguments) return false end + measures['ResStockArgumentsPostHPXML'] = [{}] if !measures.keys.include?('ResStockArgumentsPostHPXML') + measures['ResStockArgumentsPostHPXML'][0]['hpxml_path'] = hpxml_path + measures['ResStockArgumentsPostHPXML'][0]['output_csv_path'] = File.expand_path('../schedules.csv') + measures['ResStockArgumentsPostHPXML'][0]['building_id'] = values['building_id'] + measures_hash = { 'ResStockArgumentsPostHPXML' => measures['ResStockArgumentsPostHPXML'] } + if not apply_measures(measures_dir, measures_hash, new_runner, model, true, 'OpenStudio::Measure::ModelMeasure', nil) + register_logs(runner, new_runner) + return false + end + # Specify measures to run measures_to_apply_hash = { measures_dir => {} } - upgrade_measures = measures.keys - ['ResStockArguments', 'BuildResidentialHPXML', 'BuildResidentialScheduleFile'] + upgrade_measures = measures.keys - ['ResStockArguments', 'BuildResidentialHPXML', 'BuildResidentialScheduleFile', 'ResStockArgumentsPostHPXML'] upgrade_measures.each do |upgrade_measure| measures_to_apply_hash[measures_dir][upgrade_measure] = measures[upgrade_measure] end @@ -549,6 +559,7 @@ def run(model, runner, user_arguments) FileUtils.cp(hpxml_path, in_path) register_logs(runner, resstock_arguments_runner) + register_logs(runner, new_runner) return true end diff --git a/measures/ApplyUpgrade/measure.xml b/measures/ApplyUpgrade/measure.xml index 4a904ffba3..33df97fe27 100644 --- a/measures/ApplyUpgrade/measure.xml +++ b/measures/ApplyUpgrade/measure.xml @@ -3,8 +3,8 @@ 3.1 apply_upgrade 33f1654c-f734-43d1-b35d-9d2856e41b5a - 9e554ddb-2d72-4a51-b9c1-fcd24c95f33d - 2025-01-30T17:46:21Z + 8ce6c8df-ccf4-4d0d-a717-bc5d7367c7ca + 2025-02-04T21:34:51Z 9339BE01 ApplyUpgrade Apply Upgrade @@ -25025,7 +25025,7 @@ measure.rb rb script - 42D55698 + 414B0950 constants.rb diff --git a/measures/BuildExistingModel/measure.rb b/measures/BuildExistingModel/measure.rb index da8f8dfc25..c470e8a4fd 100644 --- a/measures/BuildExistingModel/measure.rb +++ b/measures/BuildExistingModel/measure.rb @@ -755,6 +755,16 @@ def run(model, runner, user_arguments) end end + measures['ResStockArgumentsPostHPXML'] = [{}] if !measures.keys.include?('ResStockArgumentsPostHPXML') + measures['ResStockArgumentsPostHPXML'][0]['hpxml_path'] = hpxml_path + measures['ResStockArgumentsPostHPXML'][0]['output_csv_path'] = File.expand_path('../schedules.csv') + measures['ResStockArgumentsPostHPXML'][0]['building_id'] = args[:building_id] + measures_hash = { 'ResStockArgumentsPostHPXML' => measures['ResStockArgumentsPostHPXML'] } + if not apply_measures(measures_dir, measures_hash, new_runner, model, true, 'OpenStudio::Measure::ModelMeasure', nil) + register_logs(runner, new_runner) + return false + end + # Copy existing.xml to home.xml for downstream HPXMLtoOpenStudio # We need existing.xml (and not just home.xml) for UpgradeCosts in_path = File.expand_path('../home.xml') @@ -809,6 +819,7 @@ def run(model, runner, user_arguments) end register_logs(runner, resstock_arguments_runner) + register_logs(runner, new_runner) return true end diff --git a/measures/BuildExistingModel/measure.xml b/measures/BuildExistingModel/measure.xml index 4977b5807e..5363654c01 100644 --- a/measures/BuildExistingModel/measure.xml +++ b/measures/BuildExistingModel/measure.xml @@ -3,8 +3,8 @@ 3.1 build_existing_model dedf59bb-3b88-4f16-8755-2c1ff5519cbf - 8d7c2d5f-2ee5-4f23-bd93-debbead74238 - 2025-01-30T17:46:22Z + 4545129e-a6bf-44ce-b091-3db9fad11222 + 2025-01-31T03:46:19Z 2C38F48B BuildExistingModel Build Existing Model @@ -357,7 +357,7 @@ measure.rb rb script - 32867677 + 2F739BE5 diff --git a/measures/ResStockArguments/README.md b/measures/ResStockArguments/README.md index 5ea98c37e1..3dacd0ca7d 100644 --- a/measures/ResStockArguments/README.md +++ b/measures/ResStockArguments/README.md @@ -6,7 +6,7 @@ ## Description Measure that pre-processes the arguments passed to the BuildResidentialHPXML and BuildResidentialScheduleFile measures. -Passes in all arguments from the options lookup, processes them, and then registers values to the runner to be used by other measures. +Passes in all ResStockArguments arguments from the options lookup, processes them, and then registers values to the runner to be used by other measures. ## Arguments diff --git a/measures/ResStockArguments/measure.rb b/measures/ResStockArguments/measure.rb index 9e14aea8db..974859c551 100644 --- a/measures/ResStockArguments/measure.rb +++ b/measures/ResStockArguments/measure.rb @@ -21,7 +21,7 @@ def description # human readable description of modeling approach def modeler_description - return 'Passes in all arguments from the options lookup, processes them, and then registers values to the runner to be used by other measures.' + return 'Passes in all ResStockArguments arguments from the options lookup, processes them, and then registers values to the runner to be used by other measures.' end # define the arguments that the user will input diff --git a/measures/ResStockArguments/measure.xml b/measures/ResStockArguments/measure.xml index 45949b75fb..03c0cf3eeb 100644 --- a/measures/ResStockArguments/measure.xml +++ b/measures/ResStockArguments/measure.xml @@ -3,13 +3,13 @@ 3.1 res_stock_arguments c984bb9e-4ac4-4930-a399-9d23f8f6936a - 87f66003-9d8d-48be-9ea5-17006046955d - 2025-01-30T17:46:23Z + d4503561-5947-467e-a470-7e3a3244d991 + 2025-02-04T20:54:44Z 2C38F48B ResStockArguments ResStock Arguments Measure that pre-processes the arguments passed to the BuildResidentialHPXML and BuildResidentialScheduleFile measures. - Passes in all arguments from the options lookup, processes them, and then registers values to the runner to be used by other measures. + Passes in all ResStockArguments arguments from the options lookup, processes them, and then registers values to the runner to be used by other measures. simulation_control_daylight_saving_enabled @@ -7684,7 +7684,7 @@ README.md md readme - B3F720DE + 39109EF9 README.md.erb @@ -7701,7 +7701,7 @@ measure.rb rb script - C85AA9D8 + 315B55D1 constants.rb @@ -7713,7 +7713,7 @@ measure.txt txt resource - 96A4BB9E + 5505BBDE resstock_arguments_test.rb diff --git a/measures/ResStockArguments/resources/measure.txt b/measures/ResStockArguments/resources/measure.txt index d8c93791e1..02453588b0 100644 --- a/measures/ResStockArguments/resources/measure.txt +++ b/measures/ResStockArguments/resources/measure.txt @@ -1 +1 @@ -3827e629e35c9fbbbc53363b9886e22d \ No newline at end of file +357944431a5a88e46e2b12c30da3f703 \ No newline at end of file diff --git a/measures/ResStockArgumentsPostHPXML/measure.rb b/measures/ResStockArgumentsPostHPXML/measure.rb new file mode 100644 index 0000000000..dc78e53fda --- /dev/null +++ b/measures/ResStockArgumentsPostHPXML/measure.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +# see the URL below for information on how to write OpenStudio measures +# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/ + +# start the measure +class ResStockArgumentsPostHPXML < OpenStudio::Measure::ModelMeasure + # human readable name + def name + return 'ResStock Arguments Post-HPXML' + end + + # human readable description + def description + return 'Measure that post-processes the output of the BuildResidentialHPXML and BuildResidentialScheduleFile measures.' + end + + # human readable description of modeling approach + def modeler_description + return 'Passes in all ResStockArgumentsPostHPXML arguments from the options lookup, processes them, and then modifies output of other measures.' + end + + # define the arguments that the user will input + def arguments(model) # rubocop:disable Lint/UnusedMethodArgument + args = OpenStudio::Measure::OSArgumentVector.new + + arg = OpenStudio::Measure::OSArgument.makeStringArgument('hpxml_path', false) + arg.setDisplayName('HPXML File Path') + arg.setDescription('Absolute/relative path of the HPXML file.') + args << arg + + arg = OpenStudio::Measure::OSArgument::makeStringArgument('building_id', false) + arg.setDisplayName('Building Unit ID') + arg.setDescription('The building unit number (between 1 and the number of samples).') + args << arg + + arg = OpenStudio::Measure::OSArgument::makeStringArgument('output_csv_path', false) + arg.setDisplayName('Schedules: Output CSV Path') + arg.setDescription('Absolute/relative path of the csv file containing occupancy schedules. Relative paths are relative to the HPXML output path.') + args << arg + + return args + end + + # define what happens when the measure is run + def run(model, runner, user_arguments) + super(model, runner, user_arguments) + + # use the built-in error checking + if !runner.validateUserArguments(arguments(model), user_arguments) + return false + end + + # assign the user inputs to variables + args = runner.getArgumentValues(arguments(model), user_arguments) + + hpxml_path = args[:hpxml_path] + unless (Pathname.new hpxml_path).absolute? + hpxml_path = File.expand_path(File.join(File.dirname(__FILE__), hpxml_path)) + end + unless File.exist?(hpxml_path) && hpxml_path.downcase.end_with?('.xml') + fail "'#{hpxml_path}' does not exist or is not an .xml file." + end + + _hpxml = HPXML.new(hpxml_path: hpxml_path) + + # init + new_schedules = {} + + # TODO: populate new_schedules + + # return if not writing schedules + return true if new_schedules.empty? + + # write schedules + schedules_filepath = File.join(File.dirname(args[:output_csv_path].get), 'schedules2.csv') + write_new_schedules(new_schedules, schedules_filepath) + + # modify the hpxml with the schedules path + doc = XMLHelper.parse_file(hpxml_path) + extension = XMLHelper.create_elements_as_needed(XMLHelper.get_element(doc, '/HPXML'), ['SoftwareInfo', 'extension']) + schedules_filepaths = XMLHelper.get_values(extension, 'SchedulesFilePath', :string) + if !schedules_filepaths.include?(schedules_filepath) + XMLHelper.add_element(extension, 'SchedulesFilePath', schedules_filepath, :string) + + # write out the modified hpxml + XMLHelper.write_file(doc, hpxml_path) + runner.registerInfo("Wrote file: #{hpxml_path}") + end + + return true + end + + def write_new_schedules(schedules, schedules_filepath) + CSV.open(schedules_filepath, 'w') do |csv| + csv << schedules.keys + rows = schedules.values.transpose + rows.each do |row| + csv << row.map { |x| '%.3g' % x } + end + end + end +end + +# register the measure to be used by the application +ResStockArgumentsPostHPXML.new.registerWithApplication diff --git a/measures/ResStockArgumentsPostHPXML/measure.xml b/measures/ResStockArgumentsPostHPXML/measure.xml new file mode 100644 index 0000000000..021ac80281 --- /dev/null +++ b/measures/ResStockArgumentsPostHPXML/measure.xml @@ -0,0 +1,70 @@ + + + 3.1 + res_stock_arguments_post_hpxml + db102ce5-ac96-4ef9-90d3-abbe53478716 + 6566f1b1-977e-47da-9d62-b4cd3fae0553 + 2025-02-04T21:19:03Z + 2C38F48B + ResStockArgumentsPostHPXML + ResStock Arguments Post-HPXML + Measure that post-processes the output of the BuildResidentialHPXML and BuildResidentialScheduleFile measures. + Passes in all ResStockArgumentsPostHPXML arguments from the options lookup, processes them, and then modifies output of other measures. + + + hpxml_path + HPXML File Path + Absolute/relative path of the HPXML file. + String + false + false + + + building_id + Building Unit ID + The building unit number (between 1 and the number of samples). + String + false + false + + + output_csv_path + Schedules: Output CSV Path + Absolute/relative path of the csv file containing occupancy schedules. Relative paths are relative to the HPXML output path. + String + false + false + + + + + + Whole Building.Space Types + + + + Measure Type + ModelMeasure + string + + + + + + OpenStudio + 3.3.0 + 3.3.0 + + measure.rb + rb + script + A81E9245 + + + test_resstock_arguments_post_hpxml.rb + rb + test + 31C4A0A8 + + + diff --git a/measures/ResStockArgumentsPostHPXML/tests/test_resstock_arguments_post_hpxml.rb b/measures/ResStockArgumentsPostHPXML/tests/test_resstock_arguments_post_hpxml.rb new file mode 100644 index 0000000000..45968a3dc3 --- /dev/null +++ b/measures/ResStockArgumentsPostHPXML/tests/test_resstock_arguments_post_hpxml.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'openstudio' +require 'openstudio/measure/ShowRunnerOutput' +require 'minitest/autorun' +require 'fileutils' + +require_relative '../measure' + +class ModelMeasureNameTest < Minitest::Test + def test_number_of_arguments_and_argument_names + measure = ResStockArgumentsPostHPXML.new + model = OpenStudio::Model::Model.new + arguments = measure.arguments(model) + argument_names = arguments.collect { |a| a.name } + expected_argument_names = ['hpxml_path', 'building_id', 'output_csv_path'] + expected_argument_names.each do |expected_name| + assert_includes(argument_names, expected_name, "Expected argument '#{expected_name}' not found") + end + end +end diff --git a/measures/UpgradeCosts/measure.xml b/measures/UpgradeCosts/measure.xml index 0b8364d177..f253cccedb 100644 --- a/measures/UpgradeCosts/measure.xml +++ b/measures/UpgradeCosts/measure.xml @@ -3,8 +3,8 @@ 3.1 upgrade_costs ef51212c-acc4-48d7-9b29-cf2a5c6c4449 - 73179d58-f009-4c1d-aa51-5d693db0af39 - 2025-01-30T17:46:24Z + aec141ff-f641-4463-b7e1-bb5b3a8a928c + 2025-02-04T20:54:47Z B1F14CB4 UpgradeCosts Upgrade Costs @@ -215,18 +215,6 @@ test 083B023F - - results_annual.csv - csv - test - 5687044D - - - results_design_load_details.csv - csv - test - 19F07CC2 - upgrade_costs_test.rb rb diff --git a/test/test_run_analysis.rb b/test/test_run_analysis.rb index 381c219b21..5fa00c6fcd 100644 --- a/test/test_run_analysis.rb +++ b/test/test_run_analysis.rb @@ -282,7 +282,7 @@ def test_testing_baseline assert(File.exist?(cli_output_log)) cli_output = File.readlines(cli_output_log) _assert_and_puts(cli_output, 'ERROR', false) - _verify_outputs(cli_output_log, true) + _verify_outputs(cli_output_log) _test_measure_order(File.join(@testing_baseline, 'testing_baseline-Baseline.osw')) results_baseline = File.join(@testing_baseline, 'results-Baseline.csv') @@ -351,7 +351,7 @@ def test_sdr_upgrades_tmy3 assert(File.exist?(cli_output_log)) cli_output = File.readlines(cli_output_log) _assert_and_puts(cli_output, 'ERROR', false) - _verify_outputs(cli_output_log, true) + _verify_outputs(cli_output_log) _test_measure_order(File.join(@sdr_upgrades_tmy3, 'sdr_upgrades_tmy3-Baseline.osw')) results_baseline = File.join(@sdr_upgrades_tmy3, 'results-Baseline.csv') @@ -423,7 +423,7 @@ def _assert_and_puts(output, msg, expect = true) end end - def _verify_outputs(cli_output_log, testing = false) + def _verify_outputs(cli_output_log) # Check cli_output.log warnings File.readlines(cli_output_log).each do |message| next if message.strip.empty? @@ -483,25 +483,20 @@ def _verify_outputs(cli_output_log, testing = false) next if _expected_warning_message(message, 'Not calculating emissions because an electricity filepath for at least one emissions scenario could not be located.') # these are AK/HI samples next if _expected_warning_message(message, 'Could not find State=AK') # these are AK samples next if _expected_warning_message(message, 'No design condition info found; calculating design conditions from EPW weather data.') - - if !testing - next if _expected_warning_message(message, 'The garage pitch was changed to accommodate garage ridge >= house ridge') - end - if testing - next if _expected_warning_message(message, 'Could not find County=') # we intentionally leave some fields blank in resources/data/simple_rates/County.tsv - next if _expected_warning_message(message, 'Battery without PV specified, and no charging/discharging schedule provided; battery is assumed to operate as backup and will not be modeled.') - next if _expected_warning_message(message, "Request for output variable 'Zone People Occupant Count' returned no key values.") - next if _expected_warning_message(message, 'No windows specified, the model will not include window heat transfer. [context: /HPXML/Building/BuildingDetails, id: "MyBuilding"]') - next if _expected_warning_message(message, 'No interior lighting specified, the model will not include interior lighting energy use. [context: /HPXML/Building/BuildingDetails, id: "MyBuilding"]') - next if _expected_warning_message(message, 'No exterior lighting specified, the model will not include exterior lighting energy use. [context: /HPXML/Building/BuildingDetails, id: "MyBuilding"]') - next if _expected_warning_message(message, 'Home with unconditioned basement/crawlspace foundation type has both foundation wall insulation and floor insulation.') - next if _expected_warning_message(message, 'Cooling capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/CoolingSystem[CoolingSystemType="room air conditioner" or CoolingSystemType="packaged terminal air conditioner"]') - next if _expected_warning_message(message, 'Cooling capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/CoolingSystem[CoolingSystemType="central air conditioner"]') - next if _expected_warning_message(message, 'Cooling capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/CoolingSystem[CoolingSystemType="mini-split"]') - next if _expected_warning_message(message, 'Heating capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/HeatingSystem[HeatingSystemType/Fireplace]') - next if _expected_warning_message(message, 'Heating capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/HeatingSystem[HeatingSystemType/SpaceHeater]') - next if _expected_warning_message(message, 'Backup heating capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/HeatPump[BackupType="integrated" or BackupSystemFuel]') - end + next if _expected_warning_message(message, 'The garage pitch was changed to accommodate garage ridge >= house ridge') + next if _expected_warning_message(message, 'Could not find County=') # we intentionally leave some fields blank in resources/data/simple_rates/County.tsv + next if _expected_warning_message(message, 'Battery without PV specified, and no charging/discharging schedule provided; battery is assumed to operate as backup and will not be modeled.') + next if _expected_warning_message(message, "Request for output variable 'Zone People Occupant Count' returned no key values.") + next if _expected_warning_message(message, 'No windows specified, the model will not include window heat transfer. [context: /HPXML/Building/BuildingDetails, id: "MyBuilding"]') + next if _expected_warning_message(message, 'No interior lighting specified, the model will not include interior lighting energy use. [context: /HPXML/Building/BuildingDetails, id: "MyBuilding"]') + next if _expected_warning_message(message, 'No exterior lighting specified, the model will not include exterior lighting energy use. [context: /HPXML/Building/BuildingDetails, id: "MyBuilding"]') + next if _expected_warning_message(message, 'Home with unconditioned basement/crawlspace foundation type has both foundation wall insulation and floor insulation.') + next if _expected_warning_message(message, 'Cooling capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/CoolingSystem[CoolingSystemType="room air conditioner" or CoolingSystemType="packaged terminal air conditioner"]') + next if _expected_warning_message(message, 'Cooling capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/CoolingSystem[CoolingSystemType="central air conditioner"]') + next if _expected_warning_message(message, 'Cooling capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/CoolingSystem[CoolingSystemType="mini-split"]') + next if _expected_warning_message(message, 'Heating capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/HeatingSystem[HeatingSystemType/Fireplace]') + next if _expected_warning_message(message, 'Heating capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/HeatingSystem[HeatingSystemType/SpaceHeater]') + next if _expected_warning_message(message, 'Backup heating capacity should typically be greater than or equal to 1000 Btu/hr. [context: /HPXML/Building/BuildingDetails/Systems/HVAC/HVACPlant/HeatPump[BackupType="integrated" or BackupSystemFuel]') flunk "Unexpected cli_output.log message found: #{message}" end