1"""
2Test module for chemked.py
3"""
4# Standard libraries
5import os
6import pkg_resources
7import warnings
8from tempfile import TemporaryDirectory
9import xml.etree.ElementTree as etree
10from copy import deepcopy
11
12# Third-party libraries
13import numpy as np
14import pytest
15
16# Local imports
17from ..validation import schema, OurValidator, yaml, Q_
18from ..chemked import ChemKED, DataPoint, Composition
19from ..converters import get_datapoints, get_common_properties
20from .._version import __version__
21
22schema['chemked-version']['allowed'].append(__version__)
23
24warnings.simplefilter('always')
25
26
27class TestChemKED(object):
28    """
29    """
30    def test_create_chemked(self):
31        file_path = os.path.join('testfile_st.yaml')
32        filename = pkg_resources.resource_filename(__name__, file_path)
33        ChemKED(filename)
34
35    def test_skip_validation(self):
36        file_path = os.path.join('testfile_bad.yaml')
37        filename = pkg_resources.resource_filename(__name__, file_path)
38        ChemKED(filename, skip_validation=True)
39
40    def test_datapoints(self):
41        file_path = os.path.join('testfile_st.yaml')
42        filename = pkg_resources.resource_filename(__name__, file_path)
43        c = ChemKED(filename)
44        assert len(c.datapoints) == 5
45
46        temperatures = Q_([1164.48, 1164.97, 1264.2, 1332.57, 1519.18], 'K')
47        ignition_delays = Q_([471.54, 448.03, 291.57, 205.93, 88.11], 'us')
48
49        for i, d in enumerate(c.datapoints):
50            assert np.isclose(d.ignition_delay, ignition_delays[i])
51            assert np.isclose(d.pressure, Q_(220., 'kPa'))
52            assert np.isclose(d.temperature, temperatures[i])
53            assert d.pressure_rise is None
54            assert d.volume_history is None
55            assert d.rcm_data is None
56            assert d.ignition_type['type'] == 'd/dt max'
57            assert d.ignition_type['target'] == 'pressure'
58
59    def test_no_input(self):
60        """Test that no input raises an exception
61        """
62        with pytest.raises(NameError):
63            ChemKED()
64
65    def test_dict_input(self):
66        file_path = os.path.join('testfile_required.yaml')
67        filename = pkg_resources.resource_filename(__name__, file_path)
68        with open(filename, 'r') as f:
69            properties = yaml.safe_load(f)
70
71        ChemKED(dict_input=properties)
72
73    def test_unallowed_input(self, capfd):
74        file_path = os.path.join('testfile_required.yaml')
75        filename = pkg_resources.resource_filename(__name__, file_path)
76        with open(filename, 'r') as f:
77            properties = yaml.safe_load(f)
78
79        properties['experiment-type'] = 'Ignition Delay'  # should be 'ignition delay'
80
81        with pytest.raises(ValueError):
82            ChemKED(dict_input=properties)
83
84        out, err = capfd.readouterr()
85        assert out == ("experiment-type has an illegal value. Allowed values are ['ignition "
86                       "delay'] and are case sensitive.\n")
87
88    def test_missing_input(self, capfd):
89        file_path = os.path.join('testfile_required.yaml')
90        filename = pkg_resources.resource_filename(__name__, file_path)
91        with open(filename, 'r') as f:
92            properties = yaml.safe_load(f)
93
94        properties.pop('apparatus')
95
96        with pytest.raises(ValueError):
97            ChemKED(dict_input=properties)
98
99
100class TestDataFrameOutput(object):
101    """
102    """
103    @pytest.fixture(scope='session')
104    def pd(self):
105        return pytest.importorskip('pandas')
106
107    @pytest.fixture(scope='session')
108    def pdt(self):
109        return pytest.importorskip('pandas.util.testing')
110
111    def test_get_dataframe(self, pd, pdt):
112        yaml_file = os.path.join('testfile_st.yaml')
113        yaml_filename = pkg_resources.resource_filename(__name__, yaml_file)
114        c = ChemKED(yaml_filename).get_dataframe()
115        csv_file = os.path.join('dataframe_st.csv')
116        csv_filename = pkg_resources.resource_filename(__name__, csv_file)
117        converters = {
118            'Ignition Delay': Q_,
119            'Temperature': Q_,
120            'Pressure': Q_,
121            'H2': Q_,
122            'Ar': Q_,
123            'O2': Q_,
124        }
125        df = pd.read_csv(csv_filename, index_col=0, converters=converters)
126        pdt.assert_frame_equal(c.sort_index(axis=1), df.sort_index(axis=1), check_names=True)
127
128    def test_custom_dataframe(self, pd, pdt):
129        yaml_file = os.path.join('testfile_st.yaml')
130        yaml_filename = pkg_resources.resource_filename(__name__, yaml_file)
131        cols_to_get = ['composition', 'Reference', 'apparatus', 'temperature', 'ignition delay']
132        c = ChemKED(yaml_filename).get_dataframe(cols_to_get)
133        csv_file = os.path.join('dataframe_st.csv')
134        csv_filename = pkg_resources.resource_filename(__name__, csv_file)
135        converters = {
136            'Ignition Delay': Q_,
137            'Temperature': Q_,
138            'Pressure': Q_,
139            'H2': Q_,
140            'Ar': Q_,
141            'O2': Q_,
142        }
143        use_cols = ['Apparatus:Kind', 'Apparatus:Institution', 'Apparatus:Facility',
144                    'Reference:Volume', 'Reference:Journal', 'Reference:Doi', 'Reference:Authors',
145                    'Reference:Detail', 'Reference:Year', 'Reference:Pages', 'Temperature',
146                    'Ignition Delay', 'H2', 'Ar', 'O2', 'Composition:Kind'
147                    ]
148        df = pd.read_csv(csv_filename, converters=converters, usecols=use_cols)
149        pdt.assert_frame_equal(c.sort_index(axis=1), df.sort_index(axis=1), check_names=True)
150
151    def test_custom_dataframe_2(self, pd, pdt):
152        yaml_file = os.path.join('testfile_st.yaml')
153        yaml_filename = pkg_resources.resource_filename(__name__, yaml_file)
154        cols_to_get = ['temperature', 'ignition delay', 'Pressure']
155        c = ChemKED(yaml_filename).get_dataframe(cols_to_get)
156        csv_file = os.path.join('dataframe_st.csv')
157        csv_filename = pkg_resources.resource_filename(__name__, csv_file)
158        converters = {
159            'Ignition Delay': Q_,
160            'Temperature': Q_,
161            'Pressure': Q_,
162            'H2': Q_,
163            'Ar': Q_,
164            'O2': Q_,
165        }
166        use_cols = ['Temperature', 'Ignition Delay', 'Pressure']
167        df = pd.read_csv(csv_filename, converters=converters, usecols=use_cols)
168        pdt.assert_frame_equal(c.sort_index(axis=1), df.sort_index(axis=1), check_names=True)
169
170    def test_invalid_column(self, pd):
171        yaml_file = os.path.join('testfile_st.yaml')
172        yaml_filename = pkg_resources.resource_filename(__name__, yaml_file)
173        with pytest.raises(ValueError):
174            ChemKED(yaml_filename).get_dataframe(['bad column'])
175
176    def test_many_species(self, pd):
177        yaml_file = os.path.join('testfile_many_species.yaml')
178        yaml_filename = pkg_resources.resource_filename(__name__, yaml_file)
179        c = ChemKED(yaml_filename).get_dataframe()
180        assert c.iloc[0]['New-Species-1'] == Q_(0.0, 'dimensionless')
181        assert c.iloc[0]['New-Species-2'] == Q_(0.0, 'dimensionless')
182        assert c.iloc[1]['H2'] == Q_(0.0, 'dimensionless')
183        assert c.iloc[1]['O2'] == Q_(0.0, 'dimensionless')
184
185
186class TestWriteFile(object):
187    """
188    """
189    def test_file_exists(self):
190        """
191        """
192        yaml_file = 'testfile_st.yaml'
193        yaml_filename = pkg_resources.resource_filename(__name__, yaml_file)
194        c = ChemKED(yaml_filename)
195
196        with pytest.raises(OSError):
197            c.write_file(yaml_filename)
198
199    def test_overwrite(self):
200        """
201        """
202        yaml_file = 'testfile_st.yaml'
203        yaml_filename = pkg_resources.resource_filename(__name__, yaml_file)
204        with open(yaml_filename, 'r') as f:
205            lines = f.readlines()
206
207        with TemporaryDirectory() as temp_dir:
208            newfile_path = os.path.join(temp_dir, 'testfile.yaml')
209            with open(newfile_path, 'w') as f:
210                f.writelines(lines)
211            c = ChemKED(newfile_path)
212
213            # Expected error
214            with pytest.raises(OSError):
215                c.write_file(newfile_path)
216
217            # Now successful
218            assert c.write_file(newfile_path, overwrite=True) is None
219
220    @pytest.mark.parametrize("filename", [
221        'testfile_st.yaml', 'testfile_st2.yaml', 'testfile_rcm.yaml',
222        'testfile_required.yaml', 'testfile_uncertainty.yaml'
223        ])
224    @pytest.mark.filterwarnings('ignore:Asymmetric uncertainties')
225    def test_write_files(self, filename):
226        """Test proper writing of ChemKED files.
227        """
228        file_path = os.path.join(filename)
229        filename = pkg_resources.resource_filename(__name__, file_path)
230        c = ChemKED(filename)
231
232        with TemporaryDirectory() as temp_dir:
233            c.write_file(os.path.join(temp_dir, 'testfile.yaml'))
234
235            # Now read in the file
236            with open(os.path.join(temp_dir, 'testfile.yaml'), 'r') as f:
237                properties = yaml.safe_load(f)
238
239        assert properties == c._properties
240
241
242class TestConvertToReSpecTh(object):
243    """Tests for conversion of ChemKED to ReSpecTh
244    """
245    @pytest.mark.parametrize('filename_ck', ['testfile_st.yaml', 'testfile_rcm.yaml'])
246    def test_conversion_to_respecth(self, filename_ck):
247        """Test proper conversion to ReSpecTh XML.
248        """
249        file_path = os.path.join(filename_ck)
250        filename = pkg_resources.resource_filename(__name__, file_path)
251        c_true = ChemKED(filename)
252
253        with TemporaryDirectory() as temp_dir:
254            newfile = os.path.join(temp_dir, 'test.xml')
255            c_true.convert_to_ReSpecTh(newfile)
256            with pytest.warns(UserWarning) as record:
257                c = ChemKED.from_respecth(newfile)
258
259        m = str(record.pop(UserWarning).message)
260        assert m == 'Using DOI to obtain reference information, rather than preferredKey.'
261
262        assert c.file_authors[0]['name'] == c_true.file_authors[0]['name']
263
264        assert c.reference.detail == 'Converted from ReSpecTh XML file {}'.format(os.path.split(newfile)[1])
265
266        assert c.apparatus.kind == c_true.apparatus.kind
267        assert c.experiment_type == c_true.experiment_type
268        assert c.reference.doi == c_true.reference.doi
269        assert len(c.datapoints) == len(c_true.datapoints)
270
271    @pytest.mark.parametrize('history_type, unit',
272                             [('volume', 'cm3'), ('temperature', 'K'), ('pressure', 'bar')])
273    def test_time_history_conversion_to_respecth(self, history_type, unit):
274        """Test proper conversion to ReSpecTh XML with time histories.
275        """
276        file_path = os.path.join('testfile_rcm.yaml')
277        filename = pkg_resources.resource_filename(__name__, file_path)
278        with open(filename, 'r') as yaml_file:
279            properties = yaml.safe_load(yaml_file)
280        properties['datapoints'][0]['time-histories'][0]['type'] = history_type
281        properties['datapoints'][0]['time-histories'][0]['quantity']['units'] = unit
282        c_true = ChemKED(dict_input=properties)
283
284        with TemporaryDirectory() as temp_dir:
285            newfile = os.path.join(temp_dir, 'test.xml')
286            c_true.convert_to_ReSpecTh(newfile)
287            with pytest.warns(UserWarning) as record:
288                c = ChemKED.from_respecth(newfile)
289
290        m = str(record.pop(UserWarning).message)
291        assert m == 'Using DOI to obtain reference information, rather than preferredKey.'
292
293        assert c.file_authors[0]['name'] == c_true.file_authors[0]['name']
294
295        assert c.reference.detail == 'Converted from ReSpecTh XML file {}'.format(os.path.split(newfile)[1])
296
297        assert c.apparatus.kind == c_true.apparatus.kind
298        assert c.experiment_type == c_true.experiment_type
299        assert c.reference.doi == c_true.reference.doi
300        assert len(c.datapoints) == len(c_true.datapoints)
301        assert getattr(c.datapoints[0], '{}_history'.format(history_type)) is not None
302
303    @pytest.mark.parametrize('history_type, unit',
304                             zip(['piston position', 'light emission', 'OH emission', 'absorption'],
305                                 ['cm', 'dimensionless', 'dimensionless', 'dimensionless']))
306    def test_time_history_conversion_to_respecth_unsupported(self, history_type, unit):
307        """Test proper conversion to ReSpecTh XML with unsupported time histories.
308        """
309        file_path = os.path.join('testfile_rcm.yaml')
310        filename = pkg_resources.resource_filename(__name__, file_path)
311        with open(filename, 'r') as yaml_file:
312            properties = yaml.safe_load(yaml_file)
313        properties['datapoints'][0]['time-histories'][0]['type'] = history_type
314        properties['datapoints'][0]['time-histories'][0]['quantity']['units'] = unit
315        c_true = ChemKED(dict_input=properties)
316        with TemporaryDirectory() as temp_dir:
317            newfile = os.path.join(temp_dir, 'test.xml')
318            with pytest.warns(UserWarning) as record:
319                c_true.convert_to_ReSpecTh(newfile)
320            m = str(record.pop(UserWarning).message)
321            assert m == ('The time-history type {} is not supported by ReSpecTh for '
322                         'ignition delay experiments'.format(history_type))
323            with pytest.warns(UserWarning) as record:
324                c = ChemKED.from_respecth(newfile)
325
326        m = str(record.pop(UserWarning).message)
327        assert m == 'Using DOI to obtain reference information, rather than preferredKey.'
328
329        assert c.file_authors[0]['name'] == c_true.file_authors[0]['name']
330
331        assert c.reference.detail == 'Converted from ReSpecTh XML file {}'.format(os.path.split(newfile)[1])
332
333        assert c.apparatus.kind == c_true.apparatus.kind
334        assert c.experiment_type == c_true.experiment_type
335        assert c.reference.doi == c_true.reference.doi
336        assert len(c.datapoints) == len(c_true.datapoints)
337        assert getattr(c.datapoints[0], '{}_history'.format(history_type.replace(' ', '_'))) is None
338
339    @pytest.mark.parametrize('experiment_type', [
340        'Laminar flame speed measurement', 'Species profile measurement',
341        'Outlet concentration measurement', 'Burner stabilized flame speciation measurement',
342        'Jet-stirred reactor measurement', 'Reaction rate coefficient measurement'
343        ])
344    def test_conversion_to_respecth_error(self, experiment_type):
345        """Test for conversion errors.
346        """
347        file_path = os.path.join('testfile_st.yaml')
348        filename = pkg_resources.resource_filename(__name__, file_path)
349        c = ChemKED(filename)
350
351        c.experiment_type = experiment_type
352
353        with pytest.raises(NotImplementedError) as excinfo:
354            c.convert_to_ReSpecTh('test.xml')
355        assert 'Only ignition delay type supported for conversion.' in str(excinfo.value)
356
357    def test_conversion_datapoints_composition_missing_inchi(self):
358        """Test for appropriate handling of composition with missing InChI.
359        """
360        file_path = os.path.join('testfile_st.yaml')
361        filename = pkg_resources.resource_filename(__name__, file_path)
362        c = ChemKED(filename)
363
364        for idx, dp in enumerate(c.datapoints):
365            c.datapoints[idx].composition = dict(
366                H2=Composition(**{'amount': Q_(0.1, 'dimensionless'), 'species_name': 'H2',
367                                  'InChI': None, 'SMILES': None, 'atomic_composition': None}),
368                O2=Composition(**{'amount': Q_(0.1, 'dimensionless'), 'species_name': 'O2',
369                                  'InChI': None, 'SMILES': None, 'atomic_composition': None}),
370                Ar=Composition(**{'amount': Q_(0.8, 'dimensionless'), 'species_name': 'Ar',
371                                  'InChI': None, 'SMILES': None, 'atomic_composition': None})
372            )
373
374        with TemporaryDirectory() as temp_dir:
375            newfile = os.path.join(temp_dir, 'test.xml')
376            c.convert_to_ReSpecTh(newfile)
377            tree = etree.parse(newfile)
378        root = tree.getroot()
379
380        with pytest.warns(UserWarning) as record:
381            common = get_common_properties(root)
382        messages = [str(record.pop(UserWarning).message) for i in range(3)]
383        assert 'Missing InChI for species H2' in messages
384        assert 'Missing InChI for species O2' in messages
385        assert 'Missing InChI for species Ar' in messages
386        assert len(common['composition']['species']) == 3
387        for spec in common['composition']['species']:
388            assert spec in [{'amount': [0.1], 'species-name': 'H2'},
389                            {'amount': [0.1], 'species-name': 'O2'},
390                            {'amount': [0.8], 'species-name': 'Ar'}
391                            ]
392
393    def test_conversion_datapoints_different_composition(self):
394        """Test for appropriate handling of datapoints with different composition.
395        """
396        file_path = os.path.join('testfile_st.yaml')
397        filename = pkg_resources.resource_filename(__name__, file_path)
398        c = ChemKED(filename)
399
400        c.datapoints[0].composition = {'H2': Composition(**{'InChI': '1S/H2/h1H',
401                                        'amount': Q_(0.1, 'dimensionless'),
402                                        'species_name': 'H2', 'SMILES': None, 'atomic_composition': None}),
403                                       'O2': Composition(**{'InChI': '1S/O2/c1-2',
404                                        'amount': Q_(0.1, 'dimensionless'),
405                                        'species_name': 'O2', 'SMILES': None, 'atomic_composition': None}),
406                                       'N2': Composition(**{'amount': Q_(0.8, 'dimensionless'),
407                                        'species_name': 'N2',
408                                        'SMILES': 'N#N', 'InChI': None, 'atomic_composition': None})
409                                       }
410
411        with TemporaryDirectory() as temp_dir:
412            newfile = os.path.join(temp_dir, 'test.xml')
413            c.convert_to_ReSpecTh(newfile)
414
415            tree = etree.parse(newfile)
416        root = tree.getroot()
417        with pytest.warns(UserWarning) as record:
418            datapoints = get_datapoints(root)
419        m = str(record.pop(UserWarning).message)
420        assert m == 'Missing InChI for species N2'
421
422        assert len(datapoints[0]['composition']['species']) == 3
423        for spec in datapoints[0]['composition']['species']:
424            assert spec in [{'InChI': '1S/H2/h1H',
425                             'amount': [0.1],
426                             'species-name': 'H2'},
427                            {'InChI': '1S/O2/c1-2',
428                             'amount': [0.1],
429                             'species-name': 'O2'},
430                            {'amount': [0.8],
431                             'species-name': 'N2',
432                             'InChI': None}
433                            ]
434
435    def test_conversion_error_datapoints_different_composition_type(self):
436        """Test for appropriate erorr of datapoints with different composition type.
437        """
438        file_path = os.path.join('testfile_st.yaml')
439        filename = pkg_resources.resource_filename(__name__, file_path)
440        c = ChemKED(filename)
441        c.datapoints[0].composition_type = 'mass fraction'
442
443        with pytest.raises(NotImplementedError) as excinfo:
444            c.convert_to_ReSpecTh('test.xml')
445        assert ('Error: ReSpecTh does not support varying composition '
446                'type among datapoints.') in str(excinfo.value)
447
448    def test_conversion_to_respecth_error_volume_history_datapoints(self):
449        """Test for error raised if RCM with multiple datapoints with volume history.
450        """
451        file_path = os.path.join('testfile_rcm.yaml')
452        filename = pkg_resources.resource_filename(__name__, file_path)
453        c = ChemKED(filename)
454
455        # Repeat datapoint, such that two with volume histories
456        c.datapoints.append(c.datapoints[0])
457
458        with pytest.raises(NotImplementedError) as excinfo:
459            c.convert_to_ReSpecTh('test.xml')
460        assert ('Error: ReSpecTh files do not support multiple datapoints with a '
461                'time history.' in str(excinfo.value)
462                )
463
464    @pytest.mark.parametrize('ignition_target', ['pressure', 'temperature', 'OH', 'CH', 'OH*', 'CH*'])
465    def test_conversion_to_respecth_ignition_targets(self, ignition_target):
466        """Test proper conversion for different ignition targets.
467        """
468        file_path = os.path.join('testfile_st.yaml')
469        filename = pkg_resources.resource_filename(__name__, file_path)
470        c = ChemKED(filename)
471
472        for dp in c.datapoints:
473            dp.ignition_type['target'] = ignition_target
474
475        with TemporaryDirectory() as temp_dir:
476            newfile = os.path.join(temp_dir, 'test.xml')
477            c.convert_to_ReSpecTh(newfile)
478
479            tree = etree.parse(newfile)
480        root = tree.getroot()
481        elem = root.find('ignitionType')
482        elem = elem.attrib
483
484        if ignition_target == 'pressure':
485            assert elem['target'] == 'P'
486        elif ignition_target == 'temperature':
487            assert elem['target'] == 'T'
488        else:
489            assert elem['target'] == ignition_target
490
491    @pytest.mark.parametrize('ignition_type', ['d/dt max', 'max', '1/2 max', 'min', 'd/dt max extrapolated'])
492    def test_conversion_to_respecth_ignition_types(self, ignition_type):
493        """Test proper conversion for different ignition types.
494        """
495        file_path = os.path.join('testfile_st.yaml')
496        filename = pkg_resources.resource_filename(__name__, file_path)
497        c = ChemKED(filename)
498
499        for dp in c.datapoints:
500            dp.ignition_type['type'] = ignition_type
501
502        with TemporaryDirectory() as temp_dir:
503            newfile = os.path.join(temp_dir, 'test.xml')
504            c.convert_to_ReSpecTh(newfile)
505
506            tree = etree.parse(newfile)
507        root = tree.getroot()
508        elem = root.find('ignitionType')
509        elem = elem.attrib
510
511        if ignition_type == 'd/dt max extrapolated':
512            assert elem['type'] == 'baseline max intercept from d/dt'
513        else:
514            assert elem['type'] == ignition_type
515
516    def test_conversion_multiple_ignition_targets(self):
517        """Test that multiple ignition targets for datapoints fails
518        """
519        file_path = os.path.join('testfile_st.yaml')
520        filename = pkg_resources.resource_filename(__name__, file_path)
521        c = ChemKED(filename)
522
523        c.datapoints[0].ignition_type['target'] = 'temperature'
524        with TemporaryDirectory() as temp_dir:
525            newfile = os.path.join(temp_dir, 'test.xml')
526            with pytest.raises(NotImplementedError) as e:
527                c.convert_to_ReSpecTh(newfile)
528
529        assert ('Different ignition targets or types for multiple datapoints are not supported in '
530                'ReSpecTh.' in str(e.value))
531
532
533class TestDataPoint(object):
534    """
535    """
536    def load_properties(self, test_file):
537        file_path = os.path.join(test_file)
538        filename = pkg_resources.resource_filename(__name__, file_path)
539        with open(filename, 'r') as f:
540            properties = yaml.safe_load(f)
541
542        v = OurValidator(schema)
543        if not v.validate(properties):
544            raise ValueError(v.errors)
545
546        return properties['datapoints']
547
548    def test_create_datapoint(self):
549        properties = self.load_properties('testfile_required.yaml')
550        DataPoint(properties[0])
551
552    def test_cantera_unknown_composition_type(self):
553        properties = self.load_properties('testfile_required.yaml')
554        d = DataPoint(properties[0])
555        d.composition_type = 'unknown type'
556        with pytest.raises(ValueError):
557            d.get_cantera_composition_string()
558
559    def test_cantera_composition_mole_fraction(self):
560        properties = self.load_properties('testfile_required.yaml')
561        d = DataPoint(properties[0])
562        # The order of the keys should not change between calls provided the contents of the
563        # dictionary don't change. Therefore, spec_order should be the same order as the
564        # Cantera mole fraction string constructed in a loop in the code
565        comps = {'H2': 'H2:4.4400e-03', 'O2': 'O2:5.5600e-03', 'Ar': 'Ar:9.9000e-01'}
566        compare_str = ', '.join([comps[s] for s in d.composition.keys()])
567        assert d.composition_type == 'mole fraction'
568        assert d.get_cantera_mole_fraction() == compare_str
569
570    def test_cantera_composition_mole_fraction_bad(self):
571        properties = self.load_properties('testfile_required.yaml')
572        d = DataPoint(properties[1])
573        assert d.composition_type == 'mass fraction'
574        with pytest.raises(ValueError):
575            d.get_cantera_mole_fraction()
576
577    def test_cantera_composition_mass_fraction(self):
578        properties = self.load_properties('testfile_required.yaml')
579        d = DataPoint(properties[1])
580        # The order of the keys should not change between calls provided the contents of the
581        # dictionary don't change. Therefore, spec_order should be the same order as the
582        # Cantera mole fraction string constructed in a loop in the code
583        comps = {'H2': 'H2:2.2525e-04', 'O2': 'O2:4.4775e-03', 'Ar': 'Ar:9.9530e-01'}
584        compare_str = ', '.join([comps[s] for s in d.composition.keys()])
585        assert d.composition_type == 'mass fraction'
586        assert d.get_cantera_mass_fraction() == compare_str
587
588    def test_cantera_composition_mass_fraction_bad(self):
589        properties = self.load_properties('testfile_required.yaml')
590        d = DataPoint(properties[0])
591        assert d.composition_type == 'mole fraction'
592        with pytest.raises(ValueError):
593            d.get_cantera_mass_fraction()
594
595    def test_cantera_composition_mole_percent(self):
596        properties = self.load_properties('testfile_required.yaml')
597        d = DataPoint(properties[2])
598        # The order of the keys should not change between calls provided the contents of the
599        # dictionary don't change. Therefore, spec_order should be the same order as the
600        # Cantera mole fraction string constructed in a loop in the code
601        comps = {'H2': 'H2:4.4400e-03', 'O2': 'O2:5.5600e-03', 'Ar': 'Ar:9.9000e-01'}
602        compare_str = ', '.join([comps[s] for s in d.composition.keys()])
603        assert d.composition_type == 'mole percent'
604        assert d.get_cantera_mole_fraction() == compare_str
605
606    def test_cantera_change_species_by_name_mole_fraction(self):
607        properties = self.load_properties('testfile_required.yaml')
608        d = DataPoint(properties[0])
609        # The order of the keys should not change between calls provided the contents of the
610        # dictionary don't change. Therefore, spec_order should be the same order as the
611        # Cantera mole fraction string constructed in a loop in the code
612        comps = {'H2': 'h2:4.4400e-03', 'O2': 'o2:5.5600e-03', 'Ar': 'Ar:9.9000e-01'}
613        compare_str = ', '.join([comps[s] for s in d.composition.keys()])
614        species_conversion = {'H2': 'h2', 'O2': 'o2'}
615        assert d.get_cantera_mole_fraction(species_conversion) == compare_str
616
617    def test_cantera_change_species_by_inchi_mole_fraction(self):
618        properties = self.load_properties('testfile_required.yaml')
619        d = DataPoint(properties[0])
620        # The order of the keys should not change between calls provided the contents of the
621        # dictionary don't change. Therefore, spec_order should be the same order as the
622        # Cantera mole fraction string constructed in a loop in the code
623        comps = {'H2': 'h2:4.4400e-03', 'O2': 'o2:5.5600e-03', 'Ar': 'Ar:9.9000e-01'}
624        compare_str = ', '.join([comps[s] for s in d.composition.keys()])
625        species_conversion = {'1S/H2/h1H': 'h2', '1S/O2/c1-2': 'o2'}
626        assert d.get_cantera_mole_fraction(species_conversion) == compare_str
627
628    def test_cantera_change_species_by_name_mole_percent(self):
629        properties = self.load_properties('testfile_required.yaml')
630        d = DataPoint(properties[2])
631        # The order of the keys should not change between calls provided the contents of the
632        # dictionary don't change. Therefore, spec_order should be the same order as the
633        # Cantera mole fraction string constructed in a loop in the code
634        comps = {'H2': 'h2:4.4400e-03', 'O2': 'o2:5.5600e-03', 'Ar': 'Ar:9.9000e-01'}
635        compare_str = ', '.join([comps[s] for s in d.composition.keys()])
636        species_conversion = {'H2': 'h2', 'O2': 'o2'}
637        assert d.get_cantera_mole_fraction(species_conversion) == compare_str
638
639    def test_cantera_change_species_by_inchi_mole_percent(self):
640        properties = self.load_properties('testfile_required.yaml')
641        d = DataPoint(properties[2])
642        # The order of the keys should not change between calls provided the contents of the
643        # dictionary don't change. Therefore, spec_order should be the same order as the
644        # Cantera mole fraction string constructed in a loop in the code
645        comps = {'H2': 'h2:4.4400e-03', 'O2': 'o2:5.5600e-03', 'Ar': 'Ar:9.9000e-01'}
646        compare_str = ', '.join([comps[s] for s in d.composition.keys()])
647        species_conversion = {'1S/H2/h1H': 'h2', '1S/O2/c1-2': 'o2'}
648        assert d.get_cantera_mole_fraction(species_conversion) == compare_str
649
650    def test_cantera_change_species_by_name_mass_fraction(self):
651        properties = self.load_properties('testfile_required.yaml')
652        d = DataPoint(properties[1])
653        # The order of the keys should not change between calls provided the contents of the
654        # dictionary don't change. Therefore, spec_order should be the same order as the
655        # Cantera mole fraction string constructed in a loop in the code
656        comps = {'H2': 'h2:2.2525e-04', 'O2': 'o2:4.4775e-03', 'Ar': 'Ar:9.9530e-01'}
657        compare_str = ', '.join([comps[s] for s in d.composition.keys()])
658        species_conversion = {'H2': 'h2', 'O2': 'o2'}
659        assert d.get_cantera_mass_fraction(species_conversion) == compare_str
660
661    def test_cantera_change_species_by_inchi_mass_fraction(self):
662        properties = self.load_properties('testfile_required.yaml')
663        d = DataPoint(properties[1])
664        # The order of the keys should not change between calls provided the contents of the
665        # dictionary don't change. Therefore, spec_order should be the same order as the
666        # Cantera mole fraction string constructed in a loop in the code
667        comps = {'H2': 'h2:2.2525e-04', 'O2': 'o2:4.4775e-03', 'Ar': 'Ar:9.9530e-01'}
668        compare_str = ', '.join([comps[s] for s in d.composition.keys()])
669        species_conversion = {'1S/H2/h1H': 'h2', '1S/O2/c1-2': 'o2'}
670        assert d.get_cantera_mass_fraction(species_conversion) == compare_str
671
672    def test_cantera_change_species_missing_mole_fraction(self):
673        properties = self.load_properties('testfile_required.yaml')
674        d = DataPoint(properties[0])
675        species_conversion = {'this-does-not-exist': 'h2', 'O2': 'o2'}
676        with pytest.raises(ValueError):
677            d.get_cantera_mole_fraction(species_conversion)
678
679    def test_cantera_change_species_missing_mass_fraction(self):
680        properties = self.load_properties('testfile_required.yaml')
681        d = DataPoint(properties[1])
682        species_conversion = {'this-does-not-exist': 'h2', 'O2': 'o2'}
683        with pytest.raises(ValueError):
684            d.get_cantera_mass_fraction(species_conversion)
685
686    def test_cantera_change_species_duplicate_mole_fraction(self):
687        properties = self.load_properties('testfile_required.yaml')
688        d = DataPoint(properties[0])
689        species_conversion = {'H2': 'h2', '1S/H2/h1H': 'h2'}
690        with pytest.raises(ValueError):
691            d.get_cantera_mole_fraction(species_conversion)
692
693    def test_cantera_change_species_duplicate_mass_fraction(self):
694        properties = self.load_properties('testfile_required.yaml')
695        d = DataPoint(properties[1])
696        species_conversion = {'H2': 'h2', '1S/H2/h1H': 'h2'}
697        with pytest.raises(ValueError):
698            d.get_cantera_mass_fraction(species_conversion)
699
700    def test_composition(self):
701        properties = self.load_properties('testfile_required.yaml')
702        d = DataPoint(properties[2])
703        assert len(d.composition) == 3
704        assert np.isclose(d.composition['H2'].amount, Q_(0.444))
705        assert d.composition['H2'].species_name == 'H2'
706        assert np.isclose(d.composition['O2'].amount, Q_(0.556))
707        assert d.composition['O2'].species_name == 'O2'
708        assert np.isclose(d.composition['Ar'].amount, Q_(99.0))
709        assert d.composition['Ar'].species_name == 'Ar'
710
711    def test_ignition_delay(self):
712        properties = self.load_properties('testfile_required.yaml')
713        d = DataPoint(properties[0])
714        assert np.isclose(d.ignition_delay, Q_(471.54, 'us'))
715
716    def test_first_stage_ignition_delay(self):
717        properties = self.load_properties('testfile_rcm2.yaml')
718        d = DataPoint(properties[0])
719        assert np.isclose(d.first_stage_ignition_delay.value, Q_(0.5, 'ms'))
720        assert np.isclose(d.first_stage_ignition_delay.error, Q_(0.005, 'ms'))
721
722    def test_temperature(self):
723        properties = self.load_properties('testfile_required.yaml')
724        d = DataPoint(properties[0])
725        assert np.isclose(d.temperature, Q_(1164.48, 'K'))
726
727    def test_rcm_data(self):
728        properties = self.load_properties('testfile_rcm2.yaml')
729        d = DataPoint(properties[0])
730        assert np.isclose(d.rcm_data.compression_time, Q_(38.0, 'ms'))
731        assert np.isclose(d.rcm_data.compressed_temperature.value, Q_(765, 'K'))
732        assert np.isclose(d.rcm_data.compressed_temperature.error, Q_(7.65, 'K'))
733        assert np.isclose(d.rcm_data.compressed_pressure, Q_(7.1, 'bar'))
734        assert np.isclose(d.rcm_data.stroke, Q_(10.0, 'inch'))
735        assert np.isclose(d.rcm_data.clearance, Q_(2.5, 'cm'))
736        assert np.isclose(d.rcm_data.compression_ratio, Q_(12.0, 'dimensionless'))
737
738    def test_pressure(self):
739        properties = self.load_properties('testfile_required.yaml')
740        d = DataPoint(properties[0])
741        assert np.isclose(d.pressure, Q_(220.0, 'kPa'))
742
743    def test_pressure_rise(self):
744        properties = self.load_properties('testfile_st2.yaml')
745        d = DataPoint(properties[0])
746        assert np.isclose(d.pressure_rise, Q_(0.1, '1/ms'))
747
748    @pytest.mark.filterwarnings('ignore:Asymmetric uncertainties')
749    def test_absolute_sym_uncertainty(self):
750        properties = self.load_properties('testfile_uncertainty.yaml')
751        d = DataPoint(properties[0])
752        assert np.isclose(d.temperature.value, Q_(1164.48, 'K'))
753        assert np.isclose(d.temperature.error, Q_(10, 'K'))
754
755    @pytest.mark.filterwarnings('ignore:Asymmetric uncertainties')
756    def test_absolute_sym_comp_uncertainty(self):
757        properties = self.load_properties('testfile_uncertainty.yaml')
758        d = DataPoint(properties[0])
759        assert np.isclose(d.composition['O2'].amount.value, Q_(0.556))
760        assert np.isclose(d.composition['O2'].amount.error, Q_(0.002))
761
762    @pytest.mark.filterwarnings('ignore:Asymmetric uncertainties')
763    def test_relative_sym_uncertainty(self):
764        properties = self.load_properties('testfile_uncertainty.yaml')
765        d = DataPoint(properties[1])
766        assert np.isclose(d.ignition_delay.value, Q_(471.54, 'us'))
767        assert np.isclose(d.ignition_delay.error, Q_(47.154, 'us'))
768        assert np.isclose(d.ignition_delay.rel, 0.1)
769
770    @pytest.mark.filterwarnings('ignore:Asymmetric uncertainties')
771    def test_relative_sym_comp_uncertainty(self):
772        properties = self.load_properties('testfile_uncertainty.yaml')
773        d = DataPoint(properties[0])
774        assert np.isclose(d.composition['H2'].amount.value, Q_(0.444))
775        assert np.isclose(d.composition['H2'].amount.error, Q_(0.00444))
776        assert np.isclose(d.composition['H2'].amount.rel, 0.01)
777
778    def test_absolute_asym_uncertainty(self):
779        properties = self.load_properties('testfile_uncertainty.yaml')
780        with pytest.warns(UserWarning) as record:
781            d = DataPoint(properties[2])
782        m = str(record.pop(UserWarning).message)
783        assert m == ('Asymmetric uncertainties are not supported. The maximum of lower-uncertainty '
784                     'and upper-uncertainty has been used as the symmetric uncertainty.')
785        assert np.isclose(d.temperature.value, Q_(1164.48, 'K'))
786        assert np.isclose(d.temperature.error, Q_(10, 'K'))
787        assert np.isclose(d.ignition_delay.value, Q_(471.54, 'us'))
788        assert np.isclose(d.ignition_delay.error, Q_(10, 'us'))
789
790    def test_relative_asym_uncertainty(self):
791        properties = self.load_properties('testfile_uncertainty.yaml')
792        with pytest.warns(UserWarning) as record:
793            d = DataPoint(properties[3])
794        m = str(record.pop(UserWarning).message)
795        assert m == ('Asymmetric uncertainties are not supported. The maximum of lower-uncertainty '
796                     'and upper-uncertainty has been used as the symmetric uncertainty.')
797        assert np.isclose(d.ignition_delay.value, Q_(471.54, 'us'))
798        assert np.isclose(d.ignition_delay.error, Q_(47.154, 'us'))
799        assert np.isclose(d.ignition_delay.rel, 0.1)
800        assert np.isclose(d.temperature.value, Q_(1164.48, 'K'))
801        assert np.isclose(d.temperature.error, Q_(116.448, 'K'))
802        assert np.isclose(d.temperature.rel, 0.1)
803
804    def test_absolute_asym_comp_uncertainty(self):
805        properties = self.load_properties('testfile_uncertainty.yaml')
806        with pytest.warns(UserWarning) as record:
807            d = DataPoint(properties[0])
808        m = str(record.pop(UserWarning).message)
809        assert m == ('Asymmetric uncertainties are not supported. The maximum of lower-uncertainty '
810                     'and upper-uncertainty has been used as the symmetric uncertainty.')
811        assert np.isclose(d.composition['Ar'].amount.value, Q_(99.0))
812        assert np.isclose(d.composition['Ar'].amount.error, Q_(1.0))
813
814        with pytest.warns(UserWarning) as record:
815            d = DataPoint(properties[1])
816        m = str(record.pop(UserWarning).message)
817        assert m == ('Asymmetric uncertainties are not supported. The maximum of lower-uncertainty '
818                     'and upper-uncertainty has been used as the symmetric uncertainty.')
819        assert np.isclose(d.composition['Ar'].amount.value, Q_(99.0))
820        assert np.isclose(d.composition['Ar'].amount.error, Q_(1.0))
821
822    def test_relative_asym_comp_uncertainty(self):
823        properties = self.load_properties('testfile_uncertainty.yaml')
824        with pytest.warns(UserWarning) as record:
825            d = DataPoint(properties[1])
826        m = str(record.pop(UserWarning).message)
827        assert m == ('Asymmetric uncertainties are not supported. The maximum of lower-uncertainty '
828                     'and upper-uncertainty has been used as the symmetric uncertainty.')
829        assert np.isclose(d.composition['H2'].amount.value, Q_(0.444))
830        assert np.isclose(d.composition['H2'].amount.error, Q_(0.0444))
831        assert np.isclose(d.composition['H2'].amount.rel, 0.1)
832
833        assert np.isclose(d.composition['O2'].amount.value, Q_(0.556))
834        assert np.isclose(d.composition['O2'].amount.error, Q_(0.0556))
835        assert np.isclose(d.composition['O2'].amount.rel, 0.1)
836
837    @pytest.mark.filterwarnings('ignore:Asymmetric uncertainties')
838    def test_missing_uncertainty_parts(self):
839        properties = self.load_properties('testfile_uncertainty.yaml')
840        for prop in ['uncertainty', 'uncertainty-type']:
841            save = properties[0]['temperature'][1].pop(prop)
842            with pytest.raises(ValueError):
843                DataPoint(properties[0])
844            properties[0]['temperature'][1][prop] = save
845
846            save = properties[1]['ignition-delay'][1].pop(prop)
847            with pytest.raises(ValueError):
848                DataPoint(properties[1])
849            properties[1]['ignition-delay'][1][prop] = save
850
851        for prop in ['upper-uncertainty', 'lower-uncertainty']:
852            save = properties[2]['temperature'][1].pop(prop)
853            with pytest.raises(ValueError):
854                DataPoint(properties[2])
855            properties[0]['temperature'][1][prop] = save
856
857            save = properties[3]['ignition-delay'][1].pop(prop)
858            with pytest.raises(ValueError):
859                DataPoint(properties[3])
860            properties[1]['ignition-delay'][1][prop] = save
861
862    @pytest.mark.filterwarnings('ignore:Asymmetric uncertainties')
863    def test_missing_comp_uncertainty_parts(self):
864        properties = self.load_properties('testfile_uncertainty.yaml')
865        for prop in ['uncertainty', 'uncertainty-type']:
866            save = properties[0]['composition']['species'][0]['amount'][1].pop(prop)
867            with pytest.raises(ValueError):
868                DataPoint(properties[0])
869            properties[0]['composition']['species'][0]['amount'][1][prop] = save
870
871            save = properties[0]['composition']['species'][1]['amount'][1].pop(prop)
872            with pytest.raises(ValueError):
873                DataPoint(properties[0])
874            properties[0]['composition']['species'][1]['amount'][1][prop] = save
875
876        for prop in ['upper-uncertainty', 'lower-uncertainty']:
877            save = properties[0]['composition']['species'][2]['amount'][1].pop(prop)
878            with pytest.raises(ValueError):
879                DataPoint(properties[0])
880            properties[0]['composition']['species'][2]['amount'][1][prop] = save
881
882            save = properties[1]['composition']['species'][2]['amount'][1].pop(prop)
883            with pytest.raises(ValueError):
884                DataPoint(properties[1])
885            properties[1]['composition']['species'][2]['amount'][1][prop] = save
886
887    def test_volume_history(self):
888        """Test that volume history works properly.
889
890        Tests for deprecated code, to be removed after PyKED 0.4
891        """
892        properties = self.load_properties('testfile_rcm_old.yaml')
893        with pytest.warns(DeprecationWarning) as record:
894            d = DataPoint(properties[0])
895        m = str(record.pop(DeprecationWarning).message)
896        assert m == ('The volume-history field should be replaced by time-histories. '
897                     'volume-history will be removed after PyKED 0.4')
898        # Check other data group with volume history
899        np.testing.assert_allclose(d.volume_history.time,
900                                   Q_(np.arange(0, 9.7e-2, 1.e-3), 's')
901                                   )
902
903        volumes = Q_(np.array([
904            5.47669375000E+002, 5.46608789894E+002, 5.43427034574E+002,
905            5.38124109043E+002, 5.30700013298E+002, 5.21154747340E+002,
906            5.09488311170E+002, 4.95700704787E+002, 4.79791928191E+002,
907            4.61761981383E+002, 4.41610864362E+002, 4.20399162234E+002,
908            3.99187460106E+002, 3.77975757979E+002, 3.56764055851E+002,
909            3.35552353723E+002, 3.14340651596E+002, 2.93128949468E+002,
910            2.71917247340E+002, 2.50705545213E+002, 2.29493843085E+002,
911            2.08282140957E+002, 1.87070438830E+002, 1.65858736702E+002,
912            1.44647034574E+002, 1.23435332447E+002, 1.02223630319E+002,
913            8.10119281915E+001, 6.33355097518E+001, 5.27296586879E+001,
914            4.91943750000E+001, 4.97137623933E+001, 5.02063762048E+001,
915            5.06454851923E+001, 5.10218564529E+001, 5.13374097598E+001,
916            5.16004693977E+001, 5.18223244382E+001, 5.20148449242E+001,
917            5.21889350372E+001, 5.23536351113E+001, 5.25157124459E+001,
918            5.26796063730E+001, 5.28476160610E+001, 5.30202402028E+001,
919            5.31965961563E+001, 5.33748623839E+001, 5.35527022996E+001,
920            5.37276399831E+001, 5.38973687732E+001, 5.40599826225E+001,
921            5.42141273988E+001, 5.43590751578E+001, 5.44947289126E+001,
922            5.46215686913E+001, 5.47405518236E+001, 5.48529815402E+001,
923            5.49603582190E+001, 5.50642270863E+001, 5.51660349836E+001,
924            5.52670070646E+001, 5.53680520985E+001, 5.54697025392E+001,
925            5.55720927915E+001, 5.56749762728E+001, 5.57777790517E+001,
926            5.58796851466E+001, 5.59797461155E+001, 5.60770054561E+001,
927            5.61706266985E+001, 5.62600130036E+001, 5.63449057053E+001,
928            5.64254496625E+001, 5.65022146282E+001, 5.65761642150E+001,
929            5.66485675508E+001, 5.67208534842E+001, 5.67944133373E+001,
930            5.68703658198E+001, 5.69493069272E+001, 5.70310785669E+001,
931            5.71146023893E+001, 5.71978399741E+001, 5.72779572372E+001,
932            5.73517897984E+001, 5.74167271960E+001, 5.74721573687E+001,
933            5.75216388520E+001, 5.75759967785E+001, 5.76575701358E+001,
934            5.78058719368E+001, 5.80849611077E+001, 5.85928651155E+001,
935            5.94734357453E+001, 6.09310671165E+001, 6.32487551103E+001,
936            6.68100309742E+001
937            ]), 'cm**3')
938        np.testing.assert_allclose(d.volume_history.volume, volumes)
939
940    def test_time_and_volume_histories_error(self):
941        """Test that time-histories and volume-history together raise an error"""
942        properties = self.load_properties('testfile_rcm.yaml')
943        properties[0]['volume-history'] = {}
944        with pytest.raises(TypeError) as record:
945            DataPoint(properties[0])
946
947        assert 'time-histories and volume-history are mutually exclusive' in str(record.value)
948
949    time_history_types = ['volume', 'temperature', 'pressure', 'piston_position',
950                          'light_emission', 'OH_emission', 'absorption']
951
952    @pytest.mark.parametrize('history_type', time_history_types)
953    def test_time_histories_array(self, history_type):
954        """Check that all of the history types are set properly"""
955        properties = self.load_properties('testfile_rcm.yaml')
956        properties[0]['time-histories'][0]['type'] = history_type
957        d = DataPoint(properties[0])
958
959        np.testing.assert_allclose(getattr(d, '{}_history'.format(history_type)).time,
960                                   Q_(np.arange(0, 9.7e-2, 1.e-3), 's')
961                                   )
962
963        quants = Q_(np.array([
964            5.47669375000E+002, 5.46608789894E+002, 5.43427034574E+002,
965            5.38124109043E+002, 5.30700013298E+002, 5.21154747340E+002,
966            5.09488311170E+002, 4.95700704787E+002, 4.79791928191E+002,
967            4.61761981383E+002, 4.41610864362E+002, 4.20399162234E+002,
968            3.99187460106E+002, 3.77975757979E+002, 3.56764055851E+002,
969            3.35552353723E+002, 3.14340651596E+002, 2.93128949468E+002,
970            2.71917247340E+002, 2.50705545213E+002, 2.29493843085E+002,
971            2.08282140957E+002, 1.87070438830E+002, 1.65858736702E+002,
972            1.44647034574E+002, 1.23435332447E+002, 1.02223630319E+002,
973            8.10119281915E+001, 6.33355097518E+001, 5.27296586879E+001,
974            4.91943750000E+001, 4.97137623933E+001, 5.02063762048E+001,
975            5.06454851923E+001, 5.10218564529E+001, 5.13374097598E+001,
976            5.16004693977E+001, 5.18223244382E+001, 5.20148449242E+001,
977            5.21889350372E+001, 5.23536351113E+001, 5.25157124459E+001,
978            5.26796063730E+001, 5.28476160610E+001, 5.30202402028E+001,
979            5.31965961563E+001, 5.33748623839E+001, 5.35527022996E+001,
980            5.37276399831E+001, 5.38973687732E+001, 5.40599826225E+001,
981            5.42141273988E+001, 5.43590751578E+001, 5.44947289126E+001,
982            5.46215686913E+001, 5.47405518236E+001, 5.48529815402E+001,
983            5.49603582190E+001, 5.50642270863E+001, 5.51660349836E+001,
984            5.52670070646E+001, 5.53680520985E+001, 5.54697025392E+001,
985            5.55720927915E+001, 5.56749762728E+001, 5.57777790517E+001,
986            5.58796851466E+001, 5.59797461155E+001, 5.60770054561E+001,
987            5.61706266985E+001, 5.62600130036E+001, 5.63449057053E+001,
988            5.64254496625E+001, 5.65022146282E+001, 5.65761642150E+001,
989            5.66485675508E+001, 5.67208534842E+001, 5.67944133373E+001,
990            5.68703658198E+001, 5.69493069272E+001, 5.70310785669E+001,
991            5.71146023893E+001, 5.71978399741E+001, 5.72779572372E+001,
992            5.73517897984E+001, 5.74167271960E+001, 5.74721573687E+001,
993            5.75216388520E+001, 5.75759967785E+001, 5.76575701358E+001,
994            5.78058719368E+001, 5.80849611077E+001, 5.85928651155E+001,
995            5.94734357453E+001, 6.09310671165E+001, 6.32487551103E+001,
996            6.68100309742E+001
997            ]), 'cm**3')
998        np.testing.assert_allclose(getattr(d, '{}_history'.format(history_type)).quantity, quants)
999        assert all([getattr(d, '{}_history'.format(h)) is None for h in self.time_history_types if h != history_type])
1000
1001    @pytest.mark.parametrize('history_type', time_history_types)
1002    def test_time_histories_file(self, history_type):
1003        """Check that all of the history types are set properly"""
1004        properties = self.load_properties('testfile_rcm.yaml')
1005        properties[0]['time-histories'][0]['type'] = history_type
1006        file_path = os.path.join('rcm_history.csv')
1007        filename = pkg_resources.resource_filename(__name__, file_path)
1008        properties[0]['time-histories'][0]['values'] = {'filename': filename}
1009        d = DataPoint(properties[0])
1010
1011        np.testing.assert_allclose(getattr(d, '{}_history'.format(history_type)).time,
1012                                   Q_(np.arange(0, 9.7e-2, 1.e-3), 's')
1013                                   )
1014
1015        quants = Q_(np.array([
1016            5.47669375000E+002, 5.46608789894E+002, 5.43427034574E+002,
1017            5.38124109043E+002, 5.30700013298E+002, 5.21154747340E+002,
1018            5.09488311170E+002, 4.95700704787E+002, 4.79791928191E+002,
1019            4.61761981383E+002, 4.41610864362E+002, 4.20399162234E+002,
1020            3.99187460106E+002, 3.77975757979E+002, 3.56764055851E+002,
1021            3.35552353723E+002, 3.14340651596E+002, 2.93128949468E+002,
1022            2.71917247340E+002, 2.50705545213E+002, 2.29493843085E+002,
1023            2.08282140957E+002, 1.87070438830E+002, 1.65858736702E+002,
1024            1.44647034574E+002, 1.23435332447E+002, 1.02223630319E+002,
1025            8.10119281915E+001, 6.33355097518E+001, 5.27296586879E+001,
1026            4.91943750000E+001, 4.97137623933E+001, 5.02063762048E+001,
1027            5.06454851923E+001, 5.10218564529E+001, 5.13374097598E+001,
1028            5.16004693977E+001, 5.18223244382E+001, 5.20148449242E+001,
1029            5.21889350372E+001, 5.23536351113E+001, 5.25157124459E+001,
1030            5.26796063730E+001, 5.28476160610E+001, 5.30202402028E+001,
1031            5.31965961563E+001, 5.33748623839E+001, 5.35527022996E+001,
1032            5.37276399831E+001, 5.38973687732E+001, 5.40599826225E+001,
1033            5.42141273988E+001, 5.43590751578E+001, 5.44947289126E+001,
1034            5.46215686913E+001, 5.47405518236E+001, 5.48529815402E+001,
1035            5.49603582190E+001, 5.50642270863E+001, 5.51660349836E+001,
1036            5.52670070646E+001, 5.53680520985E+001, 5.54697025392E+001,
1037            5.55720927915E+001, 5.56749762728E+001, 5.57777790517E+001,
1038            5.58796851466E+001, 5.59797461155E+001, 5.60770054561E+001,
1039            5.61706266985E+001, 5.62600130036E+001, 5.63449057053E+001,
1040            5.64254496625E+001, 5.65022146282E+001, 5.65761642150E+001,
1041            5.66485675508E+001, 5.67208534842E+001, 5.67944133373E+001,
1042            5.68703658198E+001, 5.69493069272E+001, 5.70310785669E+001,
1043            5.71146023893E+001, 5.71978399741E+001, 5.72779572372E+001,
1044            5.73517897984E+001, 5.74167271960E+001, 5.74721573687E+001,
1045            5.75216388520E+001, 5.75759967785E+001, 5.76575701358E+001,
1046            5.78058719368E+001, 5.80849611077E+001, 5.85928651155E+001,
1047            5.94734357453E+001, 6.09310671165E+001, 6.32487551103E+001,
1048            6.68100309742E+001
1049            ]), 'cm**3')
1050        np.testing.assert_allclose(getattr(d, '{}_history'.format(history_type)).quantity, quants)
1051        assert all([getattr(d, '{}_history'.format(h)) is None for h in self.time_history_types if h != history_type])
1052
1053    @pytest.mark.parametrize('history_type', zip(time_history_types[:-1], time_history_types[1:]))
1054    def test_multiple_time_histories(self, history_type):
1055        """Check that multiple of the history types are set properly.
1056
1057        Note the units aren't correct for the history types, but that doesn't get checked here, it
1058        gets checked in the validation of the YAML file by Cerberus.
1059        """
1060        properties = self.load_properties('testfile_rcm.yaml')
1061        properties[0]['time-histories'][0]['type'] = history_type[0]
1062        properties[0]['time-histories'].append(deepcopy(properties[0]['time-histories'][0]))
1063        properties[0]['time-histories'][1]['type'] = history_type[1]
1064        d = DataPoint(properties[0])
1065
1066        np.testing.assert_allclose(getattr(d, '{}_history'.format(history_type[0])).time,
1067                                   Q_(np.arange(0, 9.7e-2, 1.e-3), 's'))
1068
1069        np.testing.assert_allclose(getattr(d, '{}_history'.format(history_type[1])).time,
1070                                   Q_(np.arange(0, 9.7e-2, 1.e-3), 's'))
1071
1072        quants = Q_(np.array([
1073            5.47669375000E+002, 5.46608789894E+002, 5.43427034574E+002,
1074            5.38124109043E+002, 5.30700013298E+002, 5.21154747340E+002,
1075            5.09488311170E+002, 4.95700704787E+002, 4.79791928191E+002,
1076            4.61761981383E+002, 4.41610864362E+002, 4.20399162234E+002,
1077            3.99187460106E+002, 3.77975757979E+002, 3.56764055851E+002,
1078            3.35552353723E+002, 3.14340651596E+002, 2.93128949468E+002,
1079            2.71917247340E+002, 2.50705545213E+002, 2.29493843085E+002,
1080            2.08282140957E+002, 1.87070438830E+002, 1.65858736702E+002,
1081            1.44647034574E+002, 1.23435332447E+002, 1.02223630319E+002,
1082            8.10119281915E+001, 6.33355097518E+001, 5.27296586879E+001,
1083            4.91943750000E+001, 4.97137623933E+001, 5.02063762048E+001,
1084            5.06454851923E+001, 5.10218564529E+001, 5.13374097598E+001,
1085            5.16004693977E+001, 5.18223244382E+001, 5.20148449242E+001,
1086            5.21889350372E+001, 5.23536351113E+001, 5.25157124459E+001,
1087            5.26796063730E+001, 5.28476160610E+001, 5.30202402028E+001,
1088            5.31965961563E+001, 5.33748623839E+001, 5.35527022996E+001,
1089            5.37276399831E+001, 5.38973687732E+001, 5.40599826225E+001,
1090            5.42141273988E+001, 5.43590751578E+001, 5.44947289126E+001,
1091            5.46215686913E+001, 5.47405518236E+001, 5.48529815402E+001,
1092            5.49603582190E+001, 5.50642270863E+001, 5.51660349836E+001,
1093            5.52670070646E+001, 5.53680520985E+001, 5.54697025392E+001,
1094            5.55720927915E+001, 5.56749762728E+001, 5.57777790517E+001,
1095            5.58796851466E+001, 5.59797461155E+001, 5.60770054561E+001,
1096            5.61706266985E+001, 5.62600130036E+001, 5.63449057053E+001,
1097            5.64254496625E+001, 5.65022146282E+001, 5.65761642150E+001,
1098            5.66485675508E+001, 5.67208534842E+001, 5.67944133373E+001,
1099            5.68703658198E+001, 5.69493069272E+001, 5.70310785669E+001,
1100            5.71146023893E+001, 5.71978399741E+001, 5.72779572372E+001,
1101            5.73517897984E+001, 5.74167271960E+001, 5.74721573687E+001,
1102            5.75216388520E+001, 5.75759967785E+001, 5.76575701358E+001,
1103            5.78058719368E+001, 5.80849611077E+001, 5.85928651155E+001,
1104            5.94734357453E+001, 6.09310671165E+001, 6.32487551103E+001,
1105            6.68100309742E+001
1106            ]), 'cm**3')
1107        np.testing.assert_allclose(getattr(d, '{}_history'.format(history_type[0])).quantity, quants)
1108        np.testing.assert_allclose(getattr(d, '{}_history'.format(history_type[1])).quantity, quants)
1109        assert all([getattr(d, '{}_history'.format(h)) is None for h in self.time_history_types if h not in history_type])
1110
1111    @pytest.mark.parametrize('history_type', zip(time_history_types, time_history_types))
1112    def test_duplicate_time_histories(self, history_type):
1113        """Check that duplicates of the history types raise an error"""
1114        properties = self.load_properties('testfile_rcm.yaml')
1115        properties[0]['time-histories'][0]['type'] = history_type[0]
1116        properties[0]['time-histories'].append(deepcopy(properties[0]['time-histories'][0]))
1117        properties[0]['time-histories'][1]['type'] = history_type[1]
1118        with pytest.raises(ValueError) as record:
1119            DataPoint(properties[0])
1120        assert ('Each history type may only be specified once. {} was '
1121                'specified multiple times'.format(history_type[0])) in str(record.value)
1122
1123    def test_supported_ignition_types(self):
1124        # pressure d/dt max
1125        properties = self.load_properties('testfile_st.yaml')
1126        datapoints = [DataPoint(d) for d in properties]
1127        for d in datapoints:
1128            assert d.ignition_type['target'] == 'pressure'
1129            assert d.ignition_type['type'] == 'd/dt max'
1130
1131        # OH, max
1132        properties = self.load_properties('testfile_st2.yaml')
1133        datapoints = [DataPoint(d) for d in properties]
1134        for d in datapoints:
1135            assert d.ignition_type['target'] == 'OH'
1136            assert d.ignition_type['type'] == 'max'
1137
1138        # OH*, 1/2 max
1139        properties = self.load_properties('testfile_st_p5.yaml')
1140        datapoints = [DataPoint(d) for d in properties]
1141        for d in datapoints:
1142            assert d.ignition_type['target'] == 'OH*'
1143            assert d.ignition_type['type'] == '1/2 max'
1144
1145        # CH, min
1146        properties = self.load_properties('testfile_required.yaml')
1147        datapoints = [DataPoint(d) for d in properties]
1148        assert datapoints[0].ignition_type['target'] == 'CH'
1149        assert datapoints[0].ignition_type['type'] == 'min'
1150
1151        # CH*, d/dt max extrapolated
1152        assert datapoints[1].ignition_type['target'] == 'CH*'
1153        assert datapoints[1].ignition_type['type'] == 'd/dt max extrapolated'
1154
1155    def test_changing_ignition_type(self):
1156        properties = self.load_properties('testfile_st.yaml')
1157        datapoints = [DataPoint(d) for d in properties]
1158        datapoints[0].ignition_type['target'] = 'temperature'
1159        assert datapoints[0].ignition_type['target'] == 'temperature'
1160        for d in datapoints[1:]:
1161            assert d.ignition_type['target'] == 'pressure'
1162