Skip to content

Commit c38e0a3

Browse files
authored
Merge pull request #613 from NREL/multiple-building-elements
Support HPXMLs w/ multiple Building elements
2 parents 21cc5ed + 8dc5fc2 commit c38e0a3

14 files changed

Lines changed: 5091 additions & 50 deletions

File tree

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ __New Features__
1515
- Allows more defaulting (optional inputs) for a variety of HPXML elements.
1616
- Allows requesting timeseries unmet heating/cooling loads.
1717
- Allows skipping schema/schematron validation (for speed); should only be used if the HPXML was already validated upstream.
18+
- Allows HPXML files w/ multiple `Building` elements; requires providing the ID of the single building to be simulated.
1819
- Includes hot water loads (in addition to heating/cooling loads) when timeseries total loads are requested.
1920
- The `in.xml` HPXML file is now always produced for inspection of default values (e.g., autosized HVAC capacities). **Breaking change**: The `output_dir` HPXMLtoOpenStudio measure argument is now required.
2021
- Overhauls documentation to be more comprehensive and standardized.

HPXMLtoOpenStudio/measure.rb

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ def arguments(model)
7676
arg.setDefaultValue(false)
7777
args << arg
7878

79+
arg = OpenStudio::Measure::OSArgument.makeStringArgument('building_id', false)
80+
arg.setDisplayName('BuildingID')
81+
arg.setDescription('The ID of the HPXML Building. Only required if there are multiple Building elements in the HPXML file.')
82+
args << arg
83+
7984
return args
8085
end
8186

@@ -97,6 +102,7 @@ def run(model, runner, user_arguments)
97102
output_dir = runner.getStringArgumentValue('output_dir', user_arguments)
98103
debug = runner.getBoolArgumentValue('debug', user_arguments)
99104
skip_validation = runner.getBoolArgumentValue('skip_validation', user_arguments)
105+
building_id = runner.getOptionalStringArgumentValue('building_id', user_arguments)
100106

101107
unless (Pathname.new hpxml_path).absolute?
102108
hpxml_path = File.expand_path(File.join(File.dirname(__FILE__), hpxml_path))
@@ -109,6 +115,12 @@ def run(model, runner, user_arguments)
109115
output_dir = File.expand_path(File.join(File.dirname(__FILE__), output_dir))
110116
end
111117

118+
if building_id.is_initialized
119+
building_id = building_id.get
120+
else
121+
building_id = nil
122+
end
123+
112124
begin
113125
if skip_validation
114126
stron_paths = []
@@ -117,7 +129,7 @@ def run(model, runner, user_arguments)
117129
stron_paths = [File.join(File.dirname(__FILE__), 'resources', 'HPXMLvalidator.xml'),
118130
File.join(File.dirname(__FILE__), 'resources', 'EPvalidator.xml')]
119131
end
120-
hpxml = HPXML.new(hpxml_path: hpxml_path, schematron_validators: stron_paths)
132+
hpxml = HPXML.new(hpxml_path: hpxml_path, schematron_validators: stron_paths, building_id: building_id)
121133
hpxml.errors.each do |error|
122134
runner.registerError(error)
123135
end
@@ -133,7 +145,7 @@ def run(model, runner, user_arguments)
133145
FileUtils.cp(epw_path, epw_output_path)
134146
end
135147

136-
OSModel.create(hpxml, runner, model, hpxml_path, epw_path, cache_path, output_dir, debug)
148+
OSModel.create(hpxml, runner, model, hpxml_path, epw_path, cache_path, output_dir, building_id, debug)
137149
rescue Exception => e
138150
runner.registerError("#{e.message}\n#{e.backtrace.join("\n")}")
139151
return false
@@ -178,7 +190,7 @@ def process_weather(hpxml, runner, model, hpxml_path)
178190
end
179191

180192
class OSModel
181-
def self.create(hpxml, runner, model, hpxml_path, epw_path, cache_path, output_dir, debug)
193+
def self.create(hpxml, runner, model, hpxml_path, epw_path, cache_path, output_dir, building_id, debug)
182194
@hpxml = hpxml
183195
@debug = debug
184196

@@ -249,7 +261,7 @@ def self.create(hpxml, runner, model, hpxml_path, epw_path, cache_path, output_d
249261
add_airflow(runner, model, weather, spaces)
250262
add_photovoltaics(runner, model)
251263
add_generators(runner, model)
252-
add_additional_properties(runner, model, hpxml_path)
264+
add_additional_properties(runner, model, hpxml_path, building_id)
253265

254266
# Output
255267

@@ -2438,10 +2450,11 @@ def self.add_generators(runner, model)
24382450
end
24392451
end
24402452

2441-
def self.add_additional_properties(runner, model, hpxml_path)
2453+
def self.add_additional_properties(runner, model, hpxml_path, building_id)
24422454
# Store some data for use in reporting measure
24432455
additionalProperties = model.getBuilding.additionalProperties
24442456
additionalProperties.setFeature('hpxml_path', hpxml_path)
2457+
additionalProperties.setFeature('building_id', building_id.to_s)
24452458
additionalProperties.setFeature('hvac_map', map_to_string(@hvac_map))
24462459
additionalProperties.setFeature('dhw_map', map_to_string(@dhw_map))
24472460
end

HPXMLtoOpenStudio/measure.xml

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<schema_version>3.0</schema_version>
44
<name>hpxm_lto_openstudio</name>
55
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
6-
<version_id>6085bbf8-bb54-4f9e-801c-18bef7f78534</version_id>
7-
<version_modified>20210209T012056Z</version_modified>
6+
<version_id>af03201c-079e-476a-b899-8452cc5eec12</version_id>
7+
<version_modified>20210209T153531Z</version_modified>
88
<xml_checksum>D8922A73</xml_checksum>
99
<class_name>HPXMLtoOpenStudio</class_name>
1010
<display_name>HPXML to OpenStudio Translator</display_name>
@@ -65,6 +65,14 @@
6565
</choice>
6666
</choices>
6767
</argument>
68+
<argument>
69+
<name>building_id</name>
70+
<display_name>BuildingID</display_name>
71+
<description>The ID of the HPXML Building. Only required if there are multiple Building elements in the HPXML file.</description>
72+
<type>String</type>
73+
<required>false</required>
74+
<model_dependent>false</model_dependent>
75+
</argument>
6876
</arguments>
6977
<outputs />
7078
<provenances />
@@ -286,12 +294,6 @@
286294
<usage_type>resource</usage_type>
287295
<checksum>7039FF35</checksum>
288296
</file>
289-
<file>
290-
<filename>version.rb</filename>
291-
<filetype>rb</filetype>
292-
<usage_type>resource</usage_type>
293-
<checksum>A1532F79</checksum>
294-
</file>
295297
<file>
296298
<filename>test_hvac_sizing.rb</filename>
297299
<filetype>rb</filetype>
@@ -472,12 +474,6 @@
472474
<usage_type>resource</usage_type>
473475
<checksum>8F8EE993</checksum>
474476
</file>
475-
<file>
476-
<filename>test_validation.rb</filename>
477-
<filetype>rb</filetype>
478-
<usage_type>test</usage_type>
479-
<checksum>23B21918</checksum>
480-
</file>
481477
<file>
482478
<filename>test_defaults.rb</filename>
483479
<filetype>rb</filetype>
@@ -490,6 +486,30 @@
490486
<usage_type>test</usage_type>
491487
<checksum>558ED24D</checksum>
492488
</file>
489+
<file>
490+
<filename>hpxml_defaults.rb</filename>
491+
<filetype>rb</filetype>
492+
<usage_type>resource</usage_type>
493+
<checksum>29415E12</checksum>
494+
</file>
495+
<file>
496+
<filename>hvac.rb</filename>
497+
<filetype>rb</filetype>
498+
<usage_type>resource</usage_type>
499+
<checksum>B6455F85</checksum>
500+
</file>
501+
<file>
502+
<filename>hvac_sizing.rb</filename>
503+
<filetype>rb</filetype>
504+
<usage_type>resource</usage_type>
505+
<checksum>594B97B1</checksum>
506+
</file>
507+
<file>
508+
<filename>test_validation.rb</filename>
509+
<filetype>rb</filetype>
510+
<usage_type>test</usage_type>
511+
<checksum>E5FE44AD</checksum>
512+
</file>
493513
<file>
494514
<version>
495515
<software_program>OpenStudio</software_program>
@@ -499,37 +519,25 @@
499519
<filename>measure.rb</filename>
500520
<filetype>rb</filetype>
501521
<usage_type>script</usage_type>
502-
<checksum>7202ED9E</checksum>
503-
</file>
504-
<file>
505-
<filename>hpxml_defaults.rb</filename>
506-
<filetype>rb</filetype>
507-
<usage_type>resource</usage_type>
508-
<checksum>29415E12</checksum>
522+
<checksum>98746501</checksum>
509523
</file>
510524
<file>
511525
<filename>EPvalidator.xml</filename>
512526
<filetype>xml</filetype>
513527
<usage_type>resource</usage_type>
514-
<checksum>60910D75</checksum>
515-
</file>
516-
<file>
517-
<filename>hvac.rb</filename>
518-
<filetype>rb</filetype>
519-
<usage_type>resource</usage_type>
520-
<checksum>B6455F85</checksum>
528+
<checksum>248B7FAF</checksum>
521529
</file>
522530
<file>
523531
<filename>hpxml.rb</filename>
524532
<filetype>rb</filetype>
525533
<usage_type>resource</usage_type>
526-
<checksum>ACBC38B7</checksum>
534+
<checksum>9C4E7CE7</checksum>
527535
</file>
528536
<file>
529-
<filename>hvac_sizing.rb</filename>
537+
<filename>version.rb</filename>
530538
<filetype>rb</filetype>
531539
<usage_type>resource</usage_type>
532-
<checksum>594B97B1</checksum>
540+
<checksum>4F621F55</checksum>
533541
</file>
534542
</files>
535543
</measure>

HPXMLtoOpenStudio/resources/EPvalidator.xml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@
99
<sch:assert role='ERROR' test='count(h:XMLTransactionHeaderInformation) = 1'>Expected 1 element(s) for xpath: XMLTransactionHeaderInformation</sch:assert> <!-- See [XMLTransactionHeaderInformation] -->
1010
<sch:assert role='ERROR' test='count(h:SoftwareInfo/h:extension/h:SimulationControl) &lt;= 1'>Expected 0 or 1 element(s) for xpath: SoftwareInfo/extension/SimulationControl</sch:assert> <!-- See [SimulationControl] -->
1111
<sch:assert role='ERROR' test='count(h:SoftwareInfo/h:extension/h:HVACSizingControl) &lt;= 1'>Expected 0 or 1 element(s) for xpath: SoftwareInfo/extension/HVACSizingControl</sch:assert> <!-- See [HVACSizingControl] -->
12-
<sch:assert role='ERROR' test='count(h:Building) = 1'>Expected 1 element(s) for xpath: Building</sch:assert>
13-
<sch:assert role='ERROR' test='count(h:Building/h:BuildingID) = 1'>Expected 1 element(s) for xpath: Building/BuildingID</sch:assert>
14-
<sch:assert role='ERROR' test='count(h:Building/h:ProjectStatus/h:EventType) = 1'>Expected 1 element(s) for xpath: Building/ProjectStatus/EventType</sch:assert>
15-
<sch:assert role='ERROR' test='count(h:Building/h:BuildingDetails) = 1'>Expected 1 element(s) for xpath: Building/BuildingDetails</sch:assert> <!-- See [BuildingDetails] -->
12+
<sch:assert role='ERROR' test='count(h:Building) &gt;= 1'>Expected 1 or more element(s) for xpath: Building</sch:assert> <!-- See [Building] -->
13+
</sch:rule>
14+
</sch:pattern>
15+
16+
<sch:pattern>
17+
<sch:title>[Building]</sch:title>
18+
<sch:rule context='/h:HPXML/h:Building'>
19+
<sch:assert role='ERROR' test='count(h:BuildingID) = 1'>Expected 1 element(s) for xpath: Building/BuildingID</sch:assert>
20+
<sch:assert role='ERROR' test='count(h:ProjectStatus/h:EventType) = 1'>Expected 1 element(s) for xpath: Building/ProjectStatus/EventType</sch:assert>
21+
<sch:assert role='ERROR' test='count(h:BuildingDetails) = 1'>Expected 1 element(s) for xpath: Building/BuildingDetails</sch:assert> <!-- See [BuildingDetails] -->
1622
</sch:rule>
1723
</sch:pattern>
1824

HPXMLtoOpenStudio/resources/hpxml.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ class HPXML < Object
278278
WindowLayersSinglePane = 'single-pane'
279279
WindowLayersTriplePane = 'triple-pane'
280280

281-
def initialize(hpxml_path: nil, schematron_validators: [], collapse_enclosure: true)
281+
def initialize(hpxml_path: nil, schematron_validators: [], collapse_enclosure: true, building_id: nil)
282282
@doc = nil
283283
@hpxml_path = hpxml_path
284284
@errors = []
@@ -295,6 +295,25 @@ def initialize(hpxml_path: nil, schematron_validators: [], collapse_enclosure: t
295295
# Validate against Schematron docs
296296
@errors, @warnings = validate_against_schematron(schematron_validators: schematron_validators)
297297
return unless @errors.empty?
298+
299+
# Handle multiple buildings
300+
if XMLHelper.get_elements(hpxml, 'Building').size > 1
301+
if building_id.nil?
302+
@errors << 'Multiple Building elements defined in HPXML file; Building ID argument must be provided.'
303+
return unless @errors.empty?
304+
end
305+
306+
# Discard all Building elements except the one of interest
307+
XMLHelper.get_elements(hpxml, 'Building').reverse_each do |building|
308+
next if XMLHelper.get_attribute_value(XMLHelper.get_element(building, 'BuildingID'), 'id') == building_id
309+
310+
building.remove
311+
end
312+
if XMLHelper.get_elements(hpxml, 'Building').size == 0
313+
@errors << "Could not find Building element with ID '#{building_id}'."
314+
return unless @errors.empty?
315+
end
316+
end
298317
end
299318

300319
# Create/populate child objects

HPXMLtoOpenStudio/tests/test_validation.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def before_setup
2222
@hpxml_docs = {}
2323
hpxml_file_dirs.each do |hpxml_file_dir|
2424
Dir["#{hpxml_file_dir}/*.xml"].sort.each do |xml|
25-
@hpxml_docs[File.basename(xml)] = HPXML.new(hpxml_path: File.join(hpxml_file_dir, File.basename(xml))).to_oga()
25+
@hpxml_docs[File.basename(xml)] = HPXML.new(hpxml_path: File.join(hpxml_file_dir, File.basename(xml)), building_id: 'MyBuilding').to_oga()
2626
end
2727
end
2828

SimulationOutputReport/measure.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,8 @@ def run(runner, user_arguments)
361361
@model.setSqlFile(@sqlFile)
362362

363363
hpxml_path = @model.getBuilding.additionalProperties.getFeatureAsString('hpxml_path').get
364-
@hpxml = HPXML.new(hpxml_path: hpxml_path)
364+
building_id = @model.getBuilding.additionalProperties.getFeatureAsString('building_id').get
365+
@hpxml = HPXML.new(hpxml_path: hpxml_path, building_id: building_id)
365366
HVAC.apply_shared_systems(@hpxml) # Needed for ERI shared HVAC systems
366367
get_object_maps()
367368
@eri_design = @hpxml.header.eri_design

SimulationOutputReport/measure.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<schema_version>3.0</schema_version>
44
<name>simulation_output_report</name>
55
<uid>df9d170c-c21a-4130-866d-0d46b06073fd</uid>
6-
<version_id>96dbe7b7-ca46-4070-9f9c-7c4ac99ab410</version_id>
7-
<version_modified>20210202T022101Z</version_modified>
6+
<version_id>4653592a-a4b4-47cd-bfc0-22e7cae2f720</version_id>
7+
<version_modified>20210208T224231Z</version_modified>
88
<xml_checksum>9BF1E6AC</xml_checksum>
99
<class_name>SimulationOutputReport</class_name>
1010
<display_name>HPXML Simulation Output Report</display_name>
@@ -1108,7 +1108,7 @@
11081108
<filename>measure.rb</filename>
11091109
<filetype>rb</filetype>
11101110
<usage_type>script</usage_type>
1111-
<checksum>F34684AF</checksum>
1111+
<checksum>F29271E9</checksum>
11121112
</file>
11131113
</files>
11141114
</measure>

tasks.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ def create_hpxmls
105105
'invalid_files/multifamily-reference-duct.xml' => 'base.xml',
106106
'invalid_files/multifamily-reference-surface.xml' => 'base.xml',
107107
'invalid_files/multifamily-reference-water-heater.xml' => 'base.xml',
108+
'invalid_files/multiple-buildings-without-building-id.xml' => 'base.xml',
109+
'invalid_files/multiple-buildings-wrong-building-id.xml' => 'base.xml',
108110
'invalid_files/multiple-shared-cooling-systems.xml' => 'base-bldgtype-multifamily-shared-chiller-only-baseboard.xml',
109111
'invalid_files/multiple-shared-heating-systems.xml' => 'base-bldgtype-multifamily-shared-boiler-only-baseboard.xml',
110112
'invalid_files/net-area-negative-roof.xml' => 'base-enclosure-skylights.xml',
@@ -416,6 +418,7 @@ def create_hpxmls
416418
'base-misc-neighbor-shading.xml' => 'base.xml',
417419
'base-misc-shelter-coefficient.xml' => 'base.xml',
418420
'base-misc-usage-multiplier.xml' => 'base.xml',
421+
'base-multiple-buildings.xml' => 'base.xml',
419422
'base-pv.xml' => 'base.xml',
420423
'base-simcontrol-calendar-year-custom.xml' => 'base.xml',
421424
'base-simcontrol-daylight-saving-custom.xml' => 'base.xml',
@@ -517,6 +520,20 @@ def create_hpxmls
517520

518521
XMLHelper.write_file(hpxml_doc, hpxml_path)
519522

523+
if ['base-multiple-buildings.xml',
524+
'invalid_files/multiple-buildings-without-building-id.xml',
525+
'invalid_files/multiple-buildings-wrong-building-id.xml'].include? derivative
526+
# HPXML class doesn't support multiple buildings, so we'll stitch together manually.
527+
hpxml_element = XMLHelper.get_element(hpxml_doc, '/HPXML')
528+
building_element = XMLHelper.get_element(hpxml_element, 'Building')
529+
for i in 2..3
530+
new_building_element = Marshal.load(Marshal.dump(building_element))
531+
XMLHelper.add_attribute(XMLHelper.get_element(new_building_element, 'BuildingID'), 'id', "MyBuilding#{i}")
532+
hpxml_element.children << new_building_element
533+
end
534+
XMLHelper.write_file(hpxml_doc, hpxml_path)
535+
end
536+
520537
if not hpxml_path.include? 'invalid_files'
521538
# Validate file against HPXML schema
522539
schemas_dir = File.absolute_path(File.join(File.dirname(__FILE__), 'HPXMLtoOpenStudio/resources'))

workflow/run_simulation.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
basedir = File.expand_path(File.dirname(__FILE__))
1212

13-
def run_workflow(basedir, rundir, hpxml, debug, timeseries_output_freq, timeseries_outputs, skip_validation, output_format)
13+
def run_workflow(basedir, rundir, hpxml, debug, timeseries_output_freq, timeseries_outputs, skip_validation, output_format, building_id)
1414
measures_dir = File.join(basedir, '..')
1515

1616
measures = {}
@@ -22,6 +22,7 @@ def run_workflow(basedir, rundir, hpxml, debug, timeseries_output_freq, timeseri
2222
args['output_dir'] = rundir
2323
args['debug'] = debug
2424
args['skip_validation'] = skip_validation
25+
args['building_id'] = building_id
2526
update_args_hash(measures, measure_subdir, args)
2627

2728
# Add reporting measure to workflow
@@ -84,10 +85,14 @@ def run_workflow(basedir, rundir, hpxml, debug, timeseries_output_freq, timeseri
8485
end
8586

8687
options[:skip_validation] = false
87-
opts.on('-s', '--skip-validation') do |t|
88+
opts.on('-s', '--skip-validation', 'Skip Schema/Schematron validation') do |t|
8889
options[:skip_validation] = true
8990
end
9091

92+
opts.on('-b', '--building-id <ID>', 'ID of Building to simulate (required when multiple HPXML Building elements)') do |t|
93+
options[:building_id] = t
94+
end
95+
9196
options[:version] = false
9297
opts.on('-v', '--version', 'Reports the version') do |t|
9398
options[:version] = true
@@ -166,7 +171,8 @@ def run_workflow(basedir, rundir, hpxml, debug, timeseries_output_freq, timeseri
166171

167172
# Run design
168173
puts "HPXML: #{options[:hpxml]}"
169-
success = run_workflow(basedir, rundir, options[:hpxml], options[:debug], timeseries_output_freq, timeseries_outputs, options[:skip_validation], options[:output_format])
174+
success = run_workflow(basedir, rundir, options[:hpxml], options[:debug], timeseries_output_freq, timeseries_outputs,
175+
options[:skip_validation], options[:output_format], options[:building_id])
170176

171177
if not success
172178
exit! 1

0 commit comments

Comments
 (0)