Skip to content

Commit

Permalink
Merge pull request #613 from NREL/multiple-building-elements
Browse files Browse the repository at this point in the history
Support HPXMLs w/ multiple Building elements
  • Loading branch information
shorowit committed Feb 9, 2021
2 parents 21cc5ed + 8dc5fc2 commit c38e0a3
Show file tree
Hide file tree
Showing 14 changed files with 5,091 additions and 50 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ __New Features__
- Allows more defaulting (optional inputs) for a variety of HPXML elements.
- Allows requesting timeseries unmet heating/cooling loads.
- Allows skipping schema/schematron validation (for speed); should only be used if the HPXML was already validated upstream.
- Allows HPXML files w/ multiple `Building` elements; requires providing the ID of the single building to be simulated.
- Includes hot water loads (in addition to heating/cooling loads) when timeseries total loads are requested.
- 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.
- Overhauls documentation to be more comprehensive and standardized.
Expand Down
23 changes: 18 additions & 5 deletions HPXMLtoOpenStudio/measure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ def arguments(model)
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.')
args << arg

return args
end

Expand All @@ -97,6 +102,7 @@ def run(model, runner, user_arguments)
output_dir = runner.getStringArgumentValue('output_dir', user_arguments)
debug = runner.getBoolArgumentValue('debug', user_arguments)
skip_validation = runner.getBoolArgumentValue('skip_validation', user_arguments)
building_id = runner.getOptionalStringArgumentValue('building_id', user_arguments)

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

if building_id.is_initialized
building_id = building_id.get
else
building_id = nil
end

begin
if skip_validation
stron_paths = []
Expand All @@ -117,7 +129,7 @@ def run(model, runner, user_arguments)
stron_paths = [File.join(File.dirname(__FILE__), 'resources', 'HPXMLvalidator.xml'),
File.join(File.dirname(__FILE__), 'resources', 'EPvalidator.xml')]
end
hpxml = HPXML.new(hpxml_path: hpxml_path, schematron_validators: stron_paths)
hpxml = HPXML.new(hpxml_path: hpxml_path, schematron_validators: stron_paths, building_id: building_id)
hpxml.errors.each do |error|
runner.registerError(error)
end
Expand All @@ -133,7 +145,7 @@ def run(model, runner, user_arguments)
FileUtils.cp(epw_path, epw_output_path)
end

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

class OSModel
def self.create(hpxml, runner, model, hpxml_path, epw_path, cache_path, output_dir, debug)
def self.create(hpxml, runner, model, hpxml_path, epw_path, cache_path, output_dir, building_id, debug)
@hpxml = hpxml
@debug = debug

Expand Down Expand Up @@ -249,7 +261,7 @@ def self.create(hpxml, runner, model, hpxml_path, epw_path, cache_path, output_d
add_airflow(runner, model, weather, spaces)
add_photovoltaics(runner, model)
add_generators(runner, model)
add_additional_properties(runner, model, hpxml_path)
add_additional_properties(runner, model, hpxml_path, building_id)

# Output

Expand Down Expand Up @@ -2438,10 +2450,11 @@ def self.add_generators(runner, model)
end
end

def self.add_additional_properties(runner, model, hpxml_path)
def self.add_additional_properties(runner, model, hpxml_path, building_id)
# Store some data for use in reporting measure
additionalProperties = model.getBuilding.additionalProperties
additionalProperties.setFeature('hpxml_path', hpxml_path)
additionalProperties.setFeature('building_id', building_id.to_s)
additionalProperties.setFeature('hvac_map', map_to_string(@hvac_map))
additionalProperties.setFeature('dhw_map', map_to_string(@dhw_map))
end
Expand Down
70 changes: 39 additions & 31 deletions HPXMLtoOpenStudio/measure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<schema_version>3.0</schema_version>
<name>hpxm_lto_openstudio</name>
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
<version_id>6085bbf8-bb54-4f9e-801c-18bef7f78534</version_id>
<version_modified>20210209T012056Z</version_modified>
<version_id>af03201c-079e-476a-b899-8452cc5eec12</version_id>
<version_modified>20210209T153531Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -65,6 +65,14 @@
</choice>
</choices>
</argument>
<argument>
<name>building_id</name>
<display_name>BuildingID</display_name>
<description>The ID of the HPXML Building. Only required if there are multiple Building elements in the HPXML file.</description>
<type>String</type>
<required>false</required>
<model_dependent>false</model_dependent>
</argument>
</arguments>
<outputs />
<provenances />
Expand Down Expand Up @@ -286,12 +294,6 @@
<usage_type>resource</usage_type>
<checksum>7039FF35</checksum>
</file>
<file>
<filename>version.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>A1532F79</checksum>
</file>
<file>
<filename>test_hvac_sizing.rb</filename>
<filetype>rb</filetype>
Expand Down Expand Up @@ -472,12 +474,6 @@
<usage_type>resource</usage_type>
<checksum>8F8EE993</checksum>
</file>
<file>
<filename>test_validation.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>23B21918</checksum>
</file>
<file>
<filename>test_defaults.rb</filename>
<filetype>rb</filetype>
Expand All @@ -490,6 +486,30 @@
<usage_type>test</usage_type>
<checksum>558ED24D</checksum>
</file>
<file>
<filename>hpxml_defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>29415E12</checksum>
</file>
<file>
<filename>hvac.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>B6455F85</checksum>
</file>
<file>
<filename>hvac_sizing.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>594B97B1</checksum>
</file>
<file>
<filename>test_validation.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>E5FE44AD</checksum>
</file>
<file>
<version>
<software_program>OpenStudio</software_program>
Expand All @@ -499,37 +519,25 @@
<filename>measure.rb</filename>
<filetype>rb</filetype>
<usage_type>script</usage_type>
<checksum>7202ED9E</checksum>
</file>
<file>
<filename>hpxml_defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>29415E12</checksum>
<checksum>98746501</checksum>
</file>
<file>
<filename>EPvalidator.xml</filename>
<filetype>xml</filetype>
<usage_type>resource</usage_type>
<checksum>60910D75</checksum>
</file>
<file>
<filename>hvac.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>B6455F85</checksum>
<checksum>248B7FAF</checksum>
</file>
<file>
<filename>hpxml.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>ACBC38B7</checksum>
<checksum>9C4E7CE7</checksum>
</file>
<file>
<filename>hvac_sizing.rb</filename>
<filename>version.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>594B97B1</checksum>
<checksum>4F621F55</checksum>
</file>
</files>
</measure>
14 changes: 10 additions & 4 deletions HPXMLtoOpenStudio/resources/EPvalidator.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
<sch:assert role='ERROR' test='count(h:XMLTransactionHeaderInformation) = 1'>Expected 1 element(s) for xpath: XMLTransactionHeaderInformation</sch:assert> <!-- See [XMLTransactionHeaderInformation] -->
<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] -->
<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] -->
<sch:assert role='ERROR' test='count(h:Building) = 1'>Expected 1 element(s) for xpath: Building</sch:assert>
<sch:assert role='ERROR' test='count(h:Building/h:BuildingID) = 1'>Expected 1 element(s) for xpath: Building/BuildingID</sch:assert>
<sch:assert role='ERROR' test='count(h:Building/h:ProjectStatus/h:EventType) = 1'>Expected 1 element(s) for xpath: Building/ProjectStatus/EventType</sch:assert>
<sch:assert role='ERROR' test='count(h:Building/h:BuildingDetails) = 1'>Expected 1 element(s) for xpath: Building/BuildingDetails</sch:assert> <!-- See [BuildingDetails] -->
<sch:assert role='ERROR' test='count(h:Building) &gt;= 1'>Expected 1 or more element(s) for xpath: Building</sch:assert> <!-- See [Building] -->
</sch:rule>
</sch:pattern>

<sch:pattern>
<sch:title>[Building]</sch:title>
<sch:rule context='/h:HPXML/h:Building'>
<sch:assert role='ERROR' test='count(h:BuildingID) = 1'>Expected 1 element(s) for xpath: Building/BuildingID</sch:assert>
<sch:assert role='ERROR' test='count(h:ProjectStatus/h:EventType) = 1'>Expected 1 element(s) for xpath: Building/ProjectStatus/EventType</sch:assert>
<sch:assert role='ERROR' test='count(h:BuildingDetails) = 1'>Expected 1 element(s) for xpath: Building/BuildingDetails</sch:assert> <!-- See [BuildingDetails] -->
</sch:rule>
</sch:pattern>

Expand Down
21 changes: 20 additions & 1 deletion HPXMLtoOpenStudio/resources/hpxml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ class HPXML < Object
WindowLayersSinglePane = 'single-pane'
WindowLayersTriplePane = 'triple-pane'

def initialize(hpxml_path: nil, schematron_validators: [], collapse_enclosure: true)
def initialize(hpxml_path: nil, schematron_validators: [], collapse_enclosure: true, building_id: nil)
@doc = nil
@hpxml_path = hpxml_path
@errors = []
Expand All @@ -295,6 +295,25 @@ def initialize(hpxml_path: nil, schematron_validators: [], collapse_enclosure: t
# Validate against Schematron docs
@errors, @warnings = validate_against_schematron(schematron_validators: schematron_validators)
return unless @errors.empty?

# Handle multiple buildings
if XMLHelper.get_elements(hpxml, 'Building').size > 1
if building_id.nil?
@errors << 'Multiple Building elements defined in HPXML file; Building ID argument must be provided.'
return unless @errors.empty?
end

# Discard all Building elements except the one of interest
XMLHelper.get_elements(hpxml, 'Building').reverse_each do |building|
next if XMLHelper.get_attribute_value(XMLHelper.get_element(building, 'BuildingID'), 'id') == building_id

building.remove
end
if XMLHelper.get_elements(hpxml, 'Building').size == 0
@errors << "Could not find Building element with ID '#{building_id}'."
return unless @errors.empty?
end
end
end

# Create/populate child objects
Expand Down
2 changes: 1 addition & 1 deletion HPXMLtoOpenStudio/tests/test_validation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def before_setup
@hpxml_docs = {}
hpxml_file_dirs.each do |hpxml_file_dir|
Dir["#{hpxml_file_dir}/*.xml"].sort.each do |xml|
@hpxml_docs[File.basename(xml)] = HPXML.new(hpxml_path: File.join(hpxml_file_dir, File.basename(xml))).to_oga()
@hpxml_docs[File.basename(xml)] = HPXML.new(hpxml_path: File.join(hpxml_file_dir, File.basename(xml)), building_id: 'MyBuilding').to_oga()
end
end

Expand Down
3 changes: 2 additions & 1 deletion SimulationOutputReport/measure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@ def run(runner, user_arguments)
@model.setSqlFile(@sqlFile)

hpxml_path = @model.getBuilding.additionalProperties.getFeatureAsString('hpxml_path').get
@hpxml = HPXML.new(hpxml_path: hpxml_path)
building_id = @model.getBuilding.additionalProperties.getFeatureAsString('building_id').get
@hpxml = HPXML.new(hpxml_path: hpxml_path, building_id: building_id)
HVAC.apply_shared_systems(@hpxml) # Needed for ERI shared HVAC systems
get_object_maps()
@eri_design = @hpxml.header.eri_design
Expand Down
6 changes: 3 additions & 3 deletions SimulationOutputReport/measure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<schema_version>3.0</schema_version>
<name>simulation_output_report</name>
<uid>df9d170c-c21a-4130-866d-0d46b06073fd</uid>
<version_id>96dbe7b7-ca46-4070-9f9c-7c4ac99ab410</version_id>
<version_modified>20210202T022101Z</version_modified>
<version_id>4653592a-a4b4-47cd-bfc0-22e7cae2f720</version_id>
<version_modified>20210208T224231Z</version_modified>
<xml_checksum>9BF1E6AC</xml_checksum>
<class_name>SimulationOutputReport</class_name>
<display_name>HPXML Simulation Output Report</display_name>
Expand Down Expand Up @@ -1108,7 +1108,7 @@
<filename>measure.rb</filename>
<filetype>rb</filetype>
<usage_type>script</usage_type>
<checksum>F34684AF</checksum>
<checksum>F29271E9</checksum>
</file>
</files>
</measure>
Expand Down
17 changes: 17 additions & 0 deletions tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ def create_hpxmls
'invalid_files/multifamily-reference-duct.xml' => 'base.xml',
'invalid_files/multifamily-reference-surface.xml' => 'base.xml',
'invalid_files/multifamily-reference-water-heater.xml' => 'base.xml',
'invalid_files/multiple-buildings-without-building-id.xml' => 'base.xml',
'invalid_files/multiple-buildings-wrong-building-id.xml' => 'base.xml',
'invalid_files/multiple-shared-cooling-systems.xml' => 'base-bldgtype-multifamily-shared-chiller-only-baseboard.xml',
'invalid_files/multiple-shared-heating-systems.xml' => 'base-bldgtype-multifamily-shared-boiler-only-baseboard.xml',
'invalid_files/net-area-negative-roof.xml' => 'base-enclosure-skylights.xml',
Expand Down Expand Up @@ -416,6 +418,7 @@ def create_hpxmls
'base-misc-neighbor-shading.xml' => 'base.xml',
'base-misc-shelter-coefficient.xml' => 'base.xml',
'base-misc-usage-multiplier.xml' => 'base.xml',
'base-multiple-buildings.xml' => 'base.xml',
'base-pv.xml' => 'base.xml',
'base-simcontrol-calendar-year-custom.xml' => 'base.xml',
'base-simcontrol-daylight-saving-custom.xml' => 'base.xml',
Expand Down Expand Up @@ -517,6 +520,20 @@ def create_hpxmls

XMLHelper.write_file(hpxml_doc, hpxml_path)

if ['base-multiple-buildings.xml',
'invalid_files/multiple-buildings-without-building-id.xml',
'invalid_files/multiple-buildings-wrong-building-id.xml'].include? derivative
# HPXML class doesn't support multiple buildings, so we'll stitch together manually.
hpxml_element = XMLHelper.get_element(hpxml_doc, '/HPXML')
building_element = XMLHelper.get_element(hpxml_element, 'Building')
for i in 2..3
new_building_element = Marshal.load(Marshal.dump(building_element))
XMLHelper.add_attribute(XMLHelper.get_element(new_building_element, 'BuildingID'), 'id', "MyBuilding#{i}")
hpxml_element.children << new_building_element
end
XMLHelper.write_file(hpxml_doc, hpxml_path)
end

if not hpxml_path.include? 'invalid_files'
# Validate file against HPXML schema
schemas_dir = File.absolute_path(File.join(File.dirname(__FILE__), 'HPXMLtoOpenStudio/resources'))
Expand Down
12 changes: 9 additions & 3 deletions workflow/run_simulation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

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

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

measures = {}
Expand All @@ -22,6 +22,7 @@ def run_workflow(basedir, rundir, hpxml, debug, timeseries_output_freq, timeseri
args['output_dir'] = rundir
args['debug'] = debug
args['skip_validation'] = skip_validation
args['building_id'] = building_id
update_args_hash(measures, measure_subdir, args)

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

options[:skip_validation] = false
opts.on('-s', '--skip-validation') do |t|
opts.on('-s', '--skip-validation', 'Skip Schema/Schematron validation') do |t|
options[:skip_validation] = true
end

opts.on('-b', '--building-id <ID>', 'ID of Building to simulate (required when multiple HPXML Building elements)') do |t|
options[:building_id] = t
end

options[:version] = false
opts.on('-v', '--version', 'Reports the version') do |t|
options[:version] = true
Expand Down Expand Up @@ -166,7 +171,8 @@ def run_workflow(basedir, rundir, hpxml, debug, timeseries_output_freq, timeseri

# Run design
puts "HPXML: #{options[:hpxml]}"
success = run_workflow(basedir, rundir, options[:hpxml], options[:debug], timeseries_output_freq, timeseries_outputs, options[:skip_validation], options[:output_format])
success = run_workflow(basedir, rundir, options[:hpxml], options[:debug], timeseries_output_freq, timeseries_outputs,
options[:skip_validation], options[:output_format], options[:building_id])

if not success
exit! 1
Expand Down

0 comments on commit c38e0a3

Please sign in to comment.