diff --git a/interfaces/cython/cantera/composite.py b/interfaces/cython/cantera/composite.py index f1172f9180a..b614bf67cc9 100644 --- a/interfaces/cython/cantera/composite.py +++ b/interfaces/cython/cantera/composite.py @@ -447,6 +447,7 @@ class SolutionArray: # From Transport 'viscosity', 'electrical_conductivity', 'thermal_conductivity', ] + _strings = ['phase_of_matter'] _n_species = [ # from ThermoPhase 'Y', 'X', 'concentrations', 'partial_molar_enthalpies', @@ -955,14 +956,22 @@ def read_csv(self, filename): using `restore_data`. This method allows for recreation of data previously exported by `write_csv`. """ - # read data block and header separately - data = np.genfromtxt(filename, skip_header=1, delimiter=',') - labels = np.genfromtxt(filename, max_rows=1, delimiter=',', dtype=str) - - data_dict = OrderedDict() - for i, label in enumerate(labels): - data_dict[label] = data[:, i] - + if np.lib.NumpyVersion(np.__version__) < "1.14.0": + # bytestring needs to be converted for columns containing strings + data = np.genfromtxt(filename, delimiter=',', + dtype=None, names=True) + data_dict = OrderedDict() + for label in data.dtype.names: + if data[label].dtype.type == np.bytes_: + data_dict[label] = data[label].astype('U') + else: + data_dict[label] = data[label] + else: + # the 'encoding' parameter introduced with NumPy 1.14 simplifies import + data = np.genfromtxt(filename, delimiter=',', + dtype=None, names=True, encoding=None) + data_dict = OrderedDict({label: data[label] + for label in data.dtype.names}) self.restore_data(data_dict) def to_pandas(self, cols=None, *args, **kwargs): @@ -1115,8 +1124,12 @@ def write_hdf(self, filename, *args, cols=None, group=None, subgroup=None, # store SolutionArray data for key, val in self._meta.items(): dgroup.attrs[key] = val - for header, col in data.items(): - dgroup.create_dataset(header, data=col, **hdf_kwargs) + for header, value in data.items(): + if value.dtype.type == np.str_: + dgroup.create_dataset(header, data=value.astype('S'), + **hdf_kwargs) + else: + dgroup.create_dataset(header, data=value, **hdf_kwargs) return group @@ -1204,7 +1217,11 @@ def strip_ext(source): # load data data = OrderedDict() for name, value in dgroup.items(): - if name != 'phase': + if name == 'phase': + continue + elif value.dtype.type == np.bytes_: + data[name] = np.array(value).astype('U') + else: data[name] = np.array(value) self.restore_data(data) @@ -1306,6 +1323,12 @@ def _make_functions(): def empty_scalar(self): return np.empty(self._shape) + def empty_strings(self): + # The maximum length of strings assigned by built-in methods is + # currently limited to 50 characters; an attempt to assign longer + # character arrays will result in truncated strings. + return np.empty(self._shape, dtype='U50') + def empty_species(self): return np.empty(self._shape + (self._phase.n_selected_species,)) @@ -1332,6 +1355,9 @@ def getter(self): for name in SolutionArray._scalar: setattr(SolutionArray, name, make_prop(name, empty_scalar, Solution)) + for name in SolutionArray._strings: + setattr(SolutionArray, name, make_prop(name, empty_strings, Solution)) + for name in SolutionArray._n_species: setattr(SolutionArray, name, make_prop(name, empty_species, Solution)) diff --git a/interfaces/cython/cantera/test/test_composite.py b/interfaces/cython/cantera/test/test_composite.py index fa75296afef..974d822aa09 100644 --- a/interfaces/cython/cantera/test/test_composite.py +++ b/interfaces/cython/cantera/test/test_composite.py @@ -160,6 +160,16 @@ def test_write_csv(self): self.assertTrue(np.allclose(states.P, b.P)) self.assertTrue(np.allclose(states.X, b.X)) + def test_write_csv_str_column(self): + states = ct.SolutionArray(self.gas, 3, extra={'spam': 'eggs'}) + + outfile = pjoin(self.test_work_dir, 'solutionarray.csv') + states.write_csv(outfile) + + b = ct.SolutionArray(self.gas, extra={'spam'}) + b.read_csv(outfile) + self.assertEqual(list(states.spam), list(b.spam)) + @utilities.unittest.skipIf(isinstance(_pandas, ImportError), "pandas is not installed") def test_to_pandas(self): @@ -219,6 +229,17 @@ def test_write_hdf(self): c.read_hdf(outfile, group='foo/bar/baz') self.assertTrue(np.allclose(states.T, c.T)) + def test_write_hdf_str_column(self): + states = ct.SolutionArray(self.gas, 3, extra={'spam': 'eggs'}) + + outfile = pjoin(self.test_work_dir, 'solutionarray.h5') + states.write_hdf(outfile, mode='w') + + b = ct.SolutionArray(self.gas, extra={'spam'}) + b.read_hdf(outfile) + self.assertEqual(list(states.spam), list(b.spam)) + + class TestRestoreIdealGas(utilities.CanteraTest): """ Test restoring of the IdealGas class """ @classmethod diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index 7e67c84ad7f..0cea69dc691 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -1787,6 +1787,16 @@ def test_purefluid(self): states.TP = np.linspace(400, 500, 5), 101325 self.assertArrayNear(states.Q.squeeze(), np.ones(5)) + def test_phase_of_matter(self): + water = ct.Water() + states = ct.SolutionArray(water, 5) + T = [300, 500, water.critical_temperature*2, 300] + P = [101325, 101325, 101325, water.critical_pressure*2] + states[:4].TP = T, P + states[4].TQ = 300, .4 + pom = ['liquid', 'gas', 'supercritical', 'supercritical', 'liquid-gas-mix'] + self.assertEqual(list(states.phase_of_matter), pom) + def test_purefluid_getters(self): N = 11 water = ct.Water()