Skip to content

Commit

Permalink
add include tag capability for input files (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
kip-hart committed Oct 3, 2020
1 parent 6942b87 commit 691eda1
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 5 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog`_,
and this project adheres to `Semantic Versioning`_.

`Unreleased`_
--------------------------
Added
'''''''
- References within XML input files using the ``<include>`` tag.

`1.3.5`_ - 2020-09-20
--------------------------
Fixed
Expand Down
127 changes: 125 additions & 2 deletions docs/source/cli/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ User-generated input files can be run in a number of ways::

Both relative and absolute filepaths are acceptable.

The following pages describe in detail the various uses and options for the
material, domain, and settings fields of a MicroStructPy input file.

.. cli-end
Command Line Procedure
Expand Down Expand Up @@ -81,5 +84,125 @@ Paired.
When fields are repeated, such as including multiple materials, the order
is preserved.

The following pages describe in detail the various uses and options for the
material, domain, and settings fields of a MicroStructPy input file.

Including References to Other Input Files
-----------------------------------------

The input file can optionally *include* references to other input files.
For example if the file ``materials.xml`` contains:

.. code-block:: xml
<input>
<material>
<shape> circle </shape>
<size> 0.1 </size>
</material>
</input>
and another file, ``domain_1.xml``, contains:

.. code-block:: xml
<input>
<include> materials.xml </include>
<domain>
<shape> square </shape>
<side_length> 10 </side_length>
</domain>
</input>
then MicroStructPy will read the contents of ``materials.xml`` when
``microstructpy domain_1.xml`` is called. This functionality can allows multiple
input files to reference the same material properties. For example, a mesh
convergence study could keep the materials and domain definitions in a single
file, then the input files for each mesh size would contain the run settings
and a reference to the definitions file.

This way, if a parameter such as the grain size distribution needs to be
updated, it only needs to be changed in a single file.

Advanced Usage
++++++++++++++

The ``<include>`` tag can be included at any heirarchical level of the
input file. It can also be nested, with ``<include>`` tags in the file being
included. For example, if the file ``fine_grained.xml`` contains:

.. code-block:: xml
<material>
<shape> circle </shape>
<size> 0.1 </size>
</material>
and the file ``materials.xml`` contains:



.. code-block:: xml
<input>
<material>
<name> Fine 1 </name>
<include> fine_grained.xml </include>
</material>
<material>
<name> Fine 2 </name>
<include> fine_grained.xml </include>
</material>
<material>
<name> Coarse </name>
<shape> circle </shape>
<size> 0.3 </size>
</material>
</input>
and the file ``input.xml`` contains:

.. code-block:: xml
<input>
<include> materials.xml </include>
<domain>
<shape> square </shape>
<side_length> 20 </side_length>
</domain>
</input>
then running ``microstructpy input.xml`` would be equivalent to running this
file:

.. code-block:: xml
<input>
<material>
<name> Fine 1 </name>
<shape> circle </shape>
<size> 0.1 </size>
</material>
<material>
<name> Fine 2 </name>
<shape> circle </shape>
<size> 0.1 </size>
</material>
<material>
<name> Coarse </name>
<shape> circle </shape>
<size> 0.3 </size>
</material>
<domain>
<shape> square </shape>
<side_length> 20 </side_length>
</domain>
</input>
The ``<include>`` tag can reduce file sizes and the amount of copy/paste for
microstructures with multiple materials of the same size distribution,
or multiple runs with the same material.
52 changes: 49 additions & 3 deletions src/microstructpy/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,7 @@ def read_input(filename):
"""
# Read in the file
file_path = os.path.dirname(filename)

with open(filename, 'r') as file:
file_dict = xmltodict.parse(file.read())
file_dict = input2dict(filename)

assert 'input' in file_dict, 'Root <input> not found in input file.'
in_data = dict_convert(file_dict['input'], file_path)
Expand All @@ -173,6 +171,54 @@ def read_input(filename):
return in_data


def input2dict(filename, root_tag='input'):
"""Read input file into a dictionary
This function reads an input file and creates a dictionary of strings
contained within the file.
Args:
filename: Name of the input file.
Returns:
collections.OrderedDict: Dictionary of input strings.
"""

# Read in the file
with open(filename, 'r') as file:
file_dict = xmltodict.parse(file.read())
tag = list(file_dict.keys())[0]
assert tag == root_tag, repr(tag) + ' != ' + repr(root_tag)

return _include_expand(file_dict, filename, root_tag)


def _include_expand(inp, filename, key):
if isinstance(inp, str):
return inp
if isinstance(inp, list):
return [_include_expand(inp_i, filename, key) for inp_i in inp]

file_path = os.path.dirname(filename)
exp_dict = collections.OrderedDict()
for inp_key, inp_val in inp.items():
if inp_key == 'include':
includes = inp_val
if not isinstance(includes, list):
includes = [includes]
for inc_filename in includes:
if os.path.isabs(inc_filename):
fname = inc_filename
else:
fname = os.path.join(file_path, inc_filename)
inc_dict = input2dict(fname, key)
exp_dict.update(inc_dict[key])
else:
exp_dict[inp_key] = _include_expand(inp_val, filename, inp_key)
return exp_dict


def run(phases, domain, verbose=False, restart=True, directory='.',
filetypes={}, rng_seeds={}, plot_axes=True, rtol='fit', edge_opt=False,
edge_opt_n_iter=100,
Expand Down
16 changes: 16 additions & 0 deletions tests/cli/test_includes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import os

import xmltodict

from microstructpy import cli


def test_equivalence():
print(os.getcwd())
files_dir = os.path.join(os.path.dirname(__file__), 'test_includes_files')
with open(os.path.join(files_dir, 'expected_input.xml'), 'r') as file:
expected = xmltodict.parse(file.read())

for fname in ['input.xml', 'different_dir/input.xml']:
actual = cli.input2dict(os.path.join(files_dir, fname))
assert expected == actual
10 changes: 10 additions & 0 deletions tests/cli/test_includes_files/different_dir/input.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<input>
<include> ../materials.xml </include>
<domain>
<shape> square </shape>
<side_length> 20 </side_length>
</domain>
<settings>
<verbose> True </verbose>
</settings>
</input>
26 changes: 26 additions & 0 deletions tests/cli/test_includes_files/expected_input.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<input>
<material>
<name> Fine 1 </name>
<shape> circle </shape>
<size> 0.1 </size>
</material>

<material>
<name> Fine 2 </name>
<shape> circle </shape>
<size> 0.1 </size>
</material>

<material>
<name> Coarse </name>
<shape> circle </shape>
<size> 0.3 </size>
</material>
<domain>
<shape> square </shape>
<side_length> 20 </side_length>
</domain>
<settings>
<verbose> True </verbose>
</settings>
</input>
4 changes: 4 additions & 0 deletions tests/cli/test_includes_files/fine_grained.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<material>
<shape> circle </shape>
<size> 0.1 </size>
</material>
10 changes: 10 additions & 0 deletions tests/cli/test_includes_files/input.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<input>
<include> materials.xml </include>
<domain>
<shape> square </shape>
<side_length> 20 </side_length>
</domain>
<settings>
<verbose> True </verbose>
</settings>
</input>
17 changes: 17 additions & 0 deletions tests/cli/test_includes_files/materials.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<input>
<material>
<name> Fine 1 </name>
<include> fine_grained.xml </include>
</material>

<material>
<name> Fine 2 </name>
<include> fine_grained.xml </include>
</material>

<material>
<name> Coarse </name>
<shape> circle </shape>
<size> 0.3 </size>
</material>
</input>

0 comments on commit 691eda1

Please sign in to comment.