diff --git a/BuildResidentialScheduleFile/measure.rb b/BuildResidentialScheduleFile/measure.rb index 77e680561b..1ea6b6e0fb 100644 --- a/BuildResidentialScheduleFile/measure.rb +++ b/BuildResidentialScheduleFile/measure.rb @@ -68,10 +68,10 @@ def arguments(model) # rubocop:disable Lint/UnusedMethodArgument arg.setDefaultValue(false) args << arg - # arg = OpenStudio::Measure::OSArgument.makeStringArgument('building_id', false) - # arg.setDisplayName('BuildingID') - # arg.setDescription("The ID of the HPXML Building. Only required if there are multiple Building elements in the HPXML file. Use 'ALL' to run all the HPXML Buildings (dwelling units) of a multifamily building in a single model.") - # args << arg + arg = OpenStudio::Measure::OSArgument.makeStringArgument('building_id', false) + arg.setDisplayName('BuildingID') + arg.setDescription("The ID of the HPXML Building. Only required if there are multiple Building elements in the HPXML file. Use 'ALL' to apply schedules to all the HPXML Buildings (dwelling units) of a multifamily building.") + args << arg return args end @@ -104,22 +104,27 @@ def run(model, runner, user_arguments) hpxml = HPXML.new(hpxml_path: hpxml_path, building_id: 'ALL') - # FIXME: Relax this constraint (using a new building_id measure argument?) - # if hpxml.buildings.size > 1 - # runner.registerError('Cannot currently handle an HPXML with multiple Building elements.') - # return false - # end - # hpxml_bldg = hpxml.buildings[0] - debug = false if args[:debug].is_initialized debug = args[:debug].get end args[:debug] = debug + if hpxml.buildings.size > 1 && !args[:building_id].is_initialized + fail "Argument 'building_id' required if there are multiple Building elements in the HPXML file." + end + doc = XMLHelper.parse_file(hpxml_path) hpxml_doc = XMLHelper.get_element(doc, '/HPXML') hpxml.buildings.each do |hpxml_bldg| + building_id = hpxml_bldg.building_id + + if hpxml.buildings.size > 1 + if args[:building_id].get != 'ALL' + next if args[:building_id].get != building_id + end + end + # exit if number of occupants is zero if hpxml_bldg.building_occupancy.number_of_residents == 0 runner.registerInfo('Number of occupants set to zero; skipping generation of stochastic schedules.') @@ -135,7 +140,7 @@ def run(model, runner, user_arguments) return false if not success XMLHelper.get_elements(hpxml_doc, 'Building').each do |building| - next if XMLHelper.get_attribute_value(XMLHelper.get_element(building, 'BuildingID'), 'id') != hpxml_bldg.building_id + next if XMLHelper.get_attribute_value(XMLHelper.get_element(building, 'BuildingID'), 'id') != building_id # modify the hpxml with the schedules path extension = XMLHelper.create_elements_as_needed(building, ['BuildingDetails', 'BuildingSummary', 'extension']) diff --git a/BuildResidentialScheduleFile/measure.xml b/BuildResidentialScheduleFile/measure.xml index 7efb8055c9..411bc0346e 100644 --- a/BuildResidentialScheduleFile/measure.xml +++ b/BuildResidentialScheduleFile/measure.xml @@ -3,8 +3,8 @@ 3.1 build_residential_schedule_file f770b2db-1a9f-4e99-99a7-7f3161a594b1 - eef5a8ee-8396-415f-b0b2-04d0f3e4e108 - 2023-09-05T20:47:06Z + be54fce8-de7a-46b1-a1ba-ec86f5cb12a7 + 2023-09-05T21:27:45Z 03F02484 BuildResidentialScheduleFile Schedule File Builder @@ -71,6 +71,14 @@ + + building_id + BuildingID + The ID of the HPXML Building. Only required if there are multiple Building elements in the HPXML file. Use 'ALL' to apply schedules to all the HPXML Buildings (dwelling units) of a multifamily building. + String + false + false + @@ -94,7 +102,7 @@ measure.rb rb script - 50A182A0 + A1DF0987 README.md @@ -892,7 +900,7 @@ build_residential_schedule_file_test.rb rb test - 5E289C51 + 7BA678BD diff --git a/BuildResidentialScheduleFile/tests/build_residential_schedule_file_test.rb b/BuildResidentialScheduleFile/tests/build_residential_schedule_file_test.rb index 224083ff47..2648f36e7c 100644 --- a/BuildResidentialScheduleFile/tests/build_residential_schedule_file_test.rb +++ b/BuildResidentialScheduleFile/tests/build_residential_schedule_file_test.rb @@ -287,6 +287,7 @@ def test_multiple_buildings XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path) @args_hash['output_csv_path'] = File.absolute_path(File.join(@tmp_output_path, 'occupancy-stochastic.csv')) + @args_hash['building_id'] = 'ALL' model, hpxml, result = _test_measure() info_msgs = result.info.map { |x| x.logMessage } @@ -320,6 +321,51 @@ def test_multiple_buildings end end + def test_multiple_buildings_id + hpxml = _create_hpxml('base-multiple-buildings.xml') + XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path) + + @args_hash['output_csv_path'] = File.absolute_path(File.join(@tmp_output_path, 'occupancy-stochastic.csv')) + @args_hash['building_id'] = 'MyBuilding_2' + model, hpxml, result = _test_measure() + + info_msgs = result.info.map { |x| x.logMessage } + assert(info_msgs.any? { |info_msg| info_msg.include?('stochastic schedule') }) + assert(info_msgs.any? { |info_msg| info_msg.include?('SimYear=2007') }) + assert(info_msgs.any? { |info_msg| info_msg.include?('MinutesPerStep=60') }) + assert(info_msgs.any? { |info_msg| info_msg.include?('State=CO') }) + assert(!info_msgs.any? { |info_msg| info_msg.include?('RandomSeed') }) + assert(info_msgs.any? { |info_msg| info_msg.include?('GeometryNumOccupants=3.0') }) + + hpxml.buildings.each do |hpxml_bldg| + building_id = hpxml_bldg.building_id + + if building_id == @args_hash['building_id'] + sf = SchedulesFile.new(model: model, + schedules_paths: hpxml_bldg.schedules.schedules_filepaths, + year: 2007, + output_path: @tmp_schedule_file_path) + + assert_in_epsilon(6689, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnOccupants, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(2086, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnLightingInterior, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(2086, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnLightingGarage, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(534, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnCookingRange, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(213, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnDishwasher, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(134, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnClothesWasher, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(151, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnClothesDryer, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(3250, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnCeilingFan, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(4840, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnPlugLoadsOther, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(4840, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnPlugLoadsTV, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(298, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnHotWaterDishwasher, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(325, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnHotWaterClothesWasher, schedules: sf.tmp_schedules), 0.1) + assert_in_epsilon(887, sf.annual_equivalent_full_load_hrs(col_name: SchedulesFile::ColumnHotWaterFixtures, schedules: sf.tmp_schedules), 0.1) + assert(!sf.schedules.keys.include?(SchedulesFile::ColumnSleeping)) + else + assert_empty(hpxml_bldg.schedules.schedules_filepaths) + end + end + end + def _test_measure(expect_fail: false) # create an instance of the measure measure = BuildResidentialScheduleFile.new