diff --git a/CITATION.md b/CITATION.md index 6cc32ef..26f7a6b 100644 --- a/CITATION.md +++ b/CITATION.md @@ -11,7 +11,7 @@ A BibTeX entry for LaTeX users is ```TeX @misc{PyKED, author = {Kyle E Niemeyer and Bryan W Weber}, - year = 2017, + year = 2018, title = {PyKED v0.4.1}, doi = {10.5281/zenodo.597935}, url = {https://github.com/pr-omethe-us/PyKED}, diff --git a/pyked/chemked.py b/pyked/chemked.py index a8573e9..f1cd797 100644 --- a/pyked/chemked.py +++ b/pyked/chemked.py @@ -9,6 +9,9 @@ import xml.etree.ElementTree as etree import xml.dom.minidom as minidom from itertools import chain +from pathlib import Path +from distutils.version import LooseVersion +import sys import numpy as np @@ -78,6 +81,15 @@ Composition.amount.__doc__ = '(`~pint.Quantity`) The amount of this species' +if LooseVersion(sys.version) < '3.6': + # Allow old version of python to open Path objects. + oldopen = open + + def open(file, mode='r', buffering=-1, encoding=None, errors=None, + newline=None, closefd=True, opener=None): + return oldopen(str(file), mode, buffering, encoding, errors, newline, closefd, opener) + + class ChemKED(object): """Main ChemKED class. @@ -109,6 +121,7 @@ class ChemKED(object): """ def __init__(self, yaml_file=None, dict_input=None, *, skip_validation=False): if yaml_file is not None: + yaml_file = Path(yaml_file) with open(yaml_file, 'r') as f: self._properties = yaml.safe_load(f) elif dict_input is not None: @@ -121,6 +134,17 @@ def __init__(self, yaml_file=None, dict_input=None, *, skip_validation=False): self.datapoints = [] for point in self._properties['datapoints']: + if 'time-histories' in point: + for th in point['time-histories']: + try: + filename = Path(th['values']['filename']) + except TypeError: + pass + else: + if yaml_file is not None: + th['values']['filename'] = (yaml_file.parent / filename).resolve() + else: + th['values']['filename'] = filename.resolve() self.datapoints.append(DataPoint(point)) self.reference = Reference( diff --git a/pyked/tests/test_chemked.py b/pyked/tests/test_chemked.py index 8a1561f..1e5d004 100644 --- a/pyked/tests/test_chemked.py +++ b/pyked/tests/test_chemked.py @@ -96,6 +96,27 @@ def test_missing_input(self, capfd): with pytest.raises(ValueError): ChemKED(dict_input=properties) + def test_csv_time_history(self): + file_path = os.path.join('testfile_rcm3.yaml') + filename = pkg_resources.resource_filename(__name__, file_path) + csv_filename = pkg_resources.resource_filename(__name__, 'rcm_history.csv') + from_file = ChemKED(filename) + + with open(filename, 'r') as f: + properties = yaml.safe_load(f) + properties['datapoints'][0]['time-histories'][0]['values']['filename'] = csv_filename + from_dict = ChemKED(dict_input=properties) + + for exp in [from_file, from_dict]: + exp_fname = exp._properties['datapoints'][0]['time-histories'][0]['values']['filename'] + assert(str(exp_fname) == csv_filename) + + # Test case where 'values' is a dict, but doesn't contain 'filename' + del properties['datapoints'][0]['time-histories'][0]['values']['filename'] + with pytest.raises(NotImplementedError): + ChemKED(dict_input=properties) + + class TestDataFrameOutput(object): """ diff --git a/pyked/tests/test_validation.py b/pyked/tests/test_validation.py index 50c46f0..e9d2c1b 100644 --- a/pyked/tests/test_validation.py +++ b/pyked/tests/test_validation.py @@ -363,7 +363,7 @@ def properties(self, request): @pytest.mark.parametrize("properties", [ 'testfile_st.yaml', 'testfile_st2.yaml', 'testfile_rcm.yaml', 'testfile_required.yaml', - 'testfile_uncertainty.yaml', 'testfile_rcm2.yaml', + 'testfile_uncertainty.yaml', 'testfile_rcm2.yaml', 'testfile_rcm3.yaml' ], indirect=['properties']) def test_valid_yaml(self, properties): """Ensure ChemKED YAML is validated diff --git a/pyked/tests/testfile_rcm3.yaml b/pyked/tests/testfile_rcm3.yaml new file mode 100644 index 0000000..e5dfefb --- /dev/null +++ b/pyked/tests/testfile_rcm3.yaml @@ -0,0 +1,66 @@ +--- +file-authors: + - name: Kyle E Niemeyer + ORCID: 0000-0003-4425-7097 +file-version: 0 +chemked-version: 0.0.1 +reference: + doi: 10.1002/kin.20180 + authors: + - name: Gaurav Mittal + - name: Chih-Jen Sung + ORCID: 0000-0003-2046-8076 + - name: Richard A Yetter + journal: International Journal of Chemical Kinetics + year: 2006 + volume: 38 + pages: 516-529 + detail: Fig. 6, open circle +experiment-type: ignition delay +apparatus: + kind: rapid compression machine + institution: Case Western Reserve University + facility: CWRU RCM +datapoints: + - temperature: + - 297.4 kelvin + ignition-delay: + - 1.0 ms + pressure: + - 958.0 torr + composition: + kind: mole fraction + species: + - species-name: H2 + InChI: 1S/H2/h1H + amount: + - 0.12500 + - species-name: O2 + InChI: 1S/O2/c1-2 + amount: + - 0.06250 + - species-name: N2 + InChI: 1S/N2/c1-2 + amount: + - 0.18125 + - species-name: Ar + InChI: 1S/Ar + amount: + - 0.63125 + ignition-type: + target: pressure + type: d/dt max + rcm-data: + compression-time: + - 38.0 ms + time-histories: + - type: volume + time: + units: s + column: 0 + quantity: + units: cm3 + column: 1 + values: + filename: rcm_history.csv +... diff --git a/pyked/validation.py b/pyked/validation.py index 4814201..17fd016 100644 --- a/pyked/validation.py +++ b/pyked/validation.py @@ -251,15 +251,21 @@ def _validate_isvalid_history(self, isvalid_history, field, value): self._error(field, 'incompatible units; should be consistent ' 'with ' + property_units['time']) - # Check that the values have the right number of columns - n_cols = len(value['values'][0]) - max_cols = max(value['time']['column'], - value['quantity']['column'], - value.get('uncertainty', {}).get('column', 0)) + 1 - if n_cols > max_cols: - self._error(field, 'too many columns in the values') - elif n_cols < max_cols: - self._error(field, 'not enough columns in the values') + try: + if 'filename' not in value['values'].keys(): + self._error(field, 'must include filename or list of values') + # If reading from a file, the file will not be validated. + # A file can have an arbitrary number of columns, and the columns + # to be used are specified. + except AttributeError: + n_cols = len(value['values'][0]) + max_cols = max(value['time']['column'], + value['quantity']['column'], + value.get('uncertainty', {}).get('column', 0)) + 1 + if n_cols > max_cols: + self._error(field, 'too many columns in the values') + elif n_cols < max_cols: + self._error(field, 'not enough columns in the values') def _validate_isvalid_quantity(self, isvalid_quantity, field, value): """Checks for valid given value and appropriate units.