[Thermo] improve error messages
Incorporate reviewer suggestions for error messages (user feedback) in PureFluid.TPX setter and SolutionArray.restore_data
This commit is contained in:
parent
074fd2cd74
commit
fe7be79552
3 changed files with 51 additions and 34 deletions
|
|
@ -299,7 +299,7 @@ class SolutionArray:
|
|||
states using the same `Solution` object and computing properties for that
|
||||
array of states.
|
||||
|
||||
SolutionArray can represent both 1D and multi-dimensional arrays of states,
|
||||
`SolutionArray` can represent both 1D and multi-dimensional arrays of states,
|
||||
with shapes described in the same way as Numpy arrays. All of the states
|
||||
can be set in a single call::
|
||||
|
||||
|
|
@ -311,7 +311,7 @@ class SolutionArray:
|
|||
>>> states.TPX = T, P, X
|
||||
|
||||
Similar to Numpy arrays, input with fewer non-singleton dimensions than the
|
||||
SolutionArray is 'broadcast' to generate input of the appropriate shape. In
|
||||
`SolutionArray` is 'broadcast' to generate input of the appropriate shape. In
|
||||
the above example, the single value for the mole fraction input is applied
|
||||
to each input, while each row has a constant temperature and each column has
|
||||
a constant pressure.
|
||||
|
|
@ -337,7 +337,7 @@ class SolutionArray:
|
|||
>>> states.equilibrate('HP')
|
||||
>>> states.T # -> adiabatic flame temperature at various equivalence ratios
|
||||
|
||||
SolutionArray objects can also be 'sliced' like Numpy arrays, which can be
|
||||
`SolutionArray` objects can also be 'sliced' like Numpy arrays, which can be
|
||||
used both for accessing and setting properties::
|
||||
|
||||
>>> states = ct.SolutionArray(gas, (6, 10))
|
||||
|
|
@ -347,7 +347,7 @@ class SolutionArray:
|
|||
If many slices or elements of a property are going to be accessed (i.e.
|
||||
within a loop), it is generally more efficient to compute the property array
|
||||
once and access this directly, rather than repeatedly slicing the
|
||||
SolutionArray object, e.g.::
|
||||
`SolutionArray` object, e.g.::
|
||||
|
||||
>>> mu = states.viscosity
|
||||
>>> for i,j in np.ndindex(mu.shape):
|
||||
|
|
@ -368,19 +368,19 @@ class SolutionArray:
|
|||
>>> s.reaction_equation(10)
|
||||
'CH4 + O <=> CH3 + OH'
|
||||
|
||||
Data represented by a SolutionArray can be extracted and saved to a CSV file
|
||||
Data represented by a `SolutionArray` can be extracted and saved to a CSV file
|
||||
using the `write_csv` method::
|
||||
|
||||
>>> states.write_csv('somefile.csv', cols=('T', 'P', 'X', 'net_rates_of_progress'))
|
||||
|
||||
As long as stored columns specify a valid thermodynamic state, the contents of
|
||||
a SolutionArray can be restored using the `read_csv` method::
|
||||
a `SolutionArray` can be restored using the `read_csv` method::
|
||||
|
||||
>>> states = ct.SolutionArray(gas)
|
||||
>>> states.read_csv('somefile.csv')
|
||||
|
||||
As an alternative to comma separated export and import, data extracted from
|
||||
SolutionArray objects can also be saved to and restored from a pandas compatible
|
||||
`SolutionArray` objects can also be saved to and restored from a pandas compatible
|
||||
HDF container file using the `write_hdf`::
|
||||
|
||||
>>> states.write_hdf('somefile.h5', cols=('T', 'P', 'X'), key='some_key')
|
||||
|
|
@ -399,7 +399,7 @@ class SolutionArray:
|
|||
:param phase: The `Solution` object used to compute the thermodynamic,
|
||||
kinetic, and transport properties
|
||||
:param shape: A tuple or integer indicating the dimensions of the
|
||||
SolutionArray. If the shape is 1D, the array may be extended using the
|
||||
`SolutionArray`. If the shape is 1D, the array may be extended using the
|
||||
`append` method. Otherwise, the shape is fixed.
|
||||
:param states: The initial array of states. Used internally to provide
|
||||
slicing support.
|
||||
|
|
@ -597,35 +597,40 @@ class SolutionArray:
|
|||
|
||||
def restore_data(self, data, labels):
|
||||
"""
|
||||
Restores a SolutionArray based on *data* specified in a single
|
||||
Restores a `SolutionArray` based on *data* specified in a single
|
||||
2D Numpy array and a list of corresponding column *labels*. Thus,
|
||||
this method allows to restore data exported by `collect_data`.
|
||||
|
||||
:param data: a 2D Numpy array holding data to be restored.
|
||||
:param labels: a list of labels corresponding to SolutionArray entries.
|
||||
:param labels: a list of labels corresponding to `SolutionArray` entries.
|
||||
|
||||
The receiving SolutionArray either has to be empty or should have
|
||||
The receiving `SolutionArray` either has to be empty or should have
|
||||
matching dimensions. Essential state properties and extra entries
|
||||
are detected automatically whereas stored information of calculated
|
||||
properties is omitted. If the receiving SolutionArray has extra
|
||||
entries already specified, only those will be imported; if *labels* does
|
||||
not contain those entries, an error is raised.
|
||||
properties is omitted. If the receiving `SolutionArray` has extra
|
||||
entries already specified, only those will be imported; if *labels*
|
||||
does not contain those entries, an error is raised.
|
||||
"""
|
||||
|
||||
# check arguments
|
||||
if not isinstance(data, np.ndarray) or data.ndim != 2:
|
||||
raise TypeError("restore_data only works for 2D ndarrays")
|
||||
elif len(labels) != data.shape[1]:
|
||||
raise ValueError("inconsistent data and label dimensions: "
|
||||
raise ValueError("inconsistent data and label dimensions "
|
||||
"({} vs. {})".format(len(labels), data.shape[1]))
|
||||
rows = data.shape[0]
|
||||
if self._shape != (0,) and self._shape != (rows,):
|
||||
raise ValueError('incompatible dimensions.')
|
||||
raise ValueError(
|
||||
"incompatible dimensions ({} vs. {}): the receiving "
|
||||
"SolutionArray object either needs to be empty "
|
||||
"or have a length that matches data rows "
|
||||
"to be restored".format(self._shape[0], rows)
|
||||
)
|
||||
|
||||
# get full state information (may differ depending on type of ThermoPhase)
|
||||
# get full state information (may differ depending on ThermoPhase type)
|
||||
full_states = self._phase._full_states.values()
|
||||
if isinstance(self._phase, PureFluid):
|
||||
# make sure that potentially non-unique state definitions are checked last
|
||||
# ensure that potentially non-unique state definitions are checked last
|
||||
last = ['TP', 'TX', 'PX']
|
||||
full_states = [fs for fs in full_states
|
||||
if fs not in last] + ['TPX'] + last
|
||||
|
|
@ -633,14 +638,13 @@ class SolutionArray:
|
|||
# determine whether complete concentration is available (mass or mole)
|
||||
# assumes that `X` or `Y` is always in last place
|
||||
mode = ''
|
||||
has_species = False
|
||||
for prefix in ['X_', 'Y_']:
|
||||
spc = ['{}{}'.format(prefix, s) for s in self.species_names]
|
||||
# solution species names also found in labels
|
||||
valid_species = {s[2:]: labels.index(s) for s in spc
|
||||
if s in labels}
|
||||
# labels that start with prefix (indicating concentration)
|
||||
all_species = [l for l in labels if l[:2] == prefix]
|
||||
all_species = [l for l in labels if l.startswith(prefix)]
|
||||
if valid_species:
|
||||
# save species mode and remaining full_state candidates
|
||||
mode = prefix[0]
|
||||
|
|
@ -655,7 +659,7 @@ class SolutionArray:
|
|||
full_states = {v[:2] for v in full_states}
|
||||
|
||||
# determine suitable thermo properties for reconstruction
|
||||
basis = {'molar': 'mole', 'mass': 'mass'}[self.basis]
|
||||
basis = 'mass' if self.basis == 'mass' else 'mole'
|
||||
prop = {'T': ('T'), 'P': ('P'),
|
||||
'D': ('density', 'density_{}'.format(basis)),
|
||||
'U': ('u', 'int_energy_{}'.format(basis)),
|
||||
|
|
@ -671,7 +675,10 @@ class SolutionArray:
|
|||
mode = fs + mode
|
||||
break
|
||||
if len(mode) == 1:
|
||||
raise ValueError('invalid/incomplete state information.')
|
||||
raise ValueError(
|
||||
"invalid/incomplete state information (detected "
|
||||
"partial information as mode='{}')".format(mode)
|
||||
)
|
||||
|
||||
# assemble and restore state information
|
||||
state_data = tuple([data[:, state[i][mode[i]]] for i in range(len(state))])
|
||||
|
|
@ -734,8 +741,8 @@ class SolutionArray:
|
|||
are specified, then either the mass or mole fraction of that species
|
||||
will be taken, depending on the value of *species*. *cols* may also
|
||||
include any arrays which were specified as 'extra' variables when
|
||||
defining the SolutionArray object. The special value 'extra' can be
|
||||
used to include all 'extra' variables.
|
||||
defining the `SolutionArray` object. The special value 'extra' can
|
||||
be used to include all 'extra' variables.
|
||||
:param threshold: Relative tolerance for including a particular column.
|
||||
The tolerance is applied by comparing the maximum absolute value for
|
||||
a particular column to the maximum absolute value in all columns for
|
||||
|
|
@ -802,7 +809,7 @@ class SolutionArray:
|
|||
*cols*. The first row of the CSV file will contain column labels.
|
||||
|
||||
Additional arguments are passed on to `collect_data`. This method works
|
||||
only with 1D SolutionArray objects.
|
||||
only with 1D `SolutionArray` objects.
|
||||
"""
|
||||
data, labels = self.collect_data(cols, *args, **kwargs)
|
||||
with open(filename, 'w') as outfile:
|
||||
|
|
@ -813,7 +820,7 @@ class SolutionArray:
|
|||
|
||||
def read_csv(self, filename):
|
||||
"""
|
||||
Read a CSV file named *filename* and restore data to the SolutionArray
|
||||
Read a CSV file named *filename* and restore data to the `SolutionArray`
|
||||
using `restore_data`. This method allows for recreation of data
|
||||
previously exported by `write_csv`.
|
||||
"""
|
||||
|
|
@ -829,7 +836,7 @@ class SolutionArray:
|
|||
Returns the data specified by *cols* in a single pandas DataFrame.
|
||||
|
||||
Additional arguments are passed on to `collect_data`. This method works
|
||||
only with 1D SolutionArray objects and requires a working pandas
|
||||
only with 1D `SolutionArray` objects and requires a working pandas
|
||||
installation. Use pip or conda to install `pandas` to enable this method.
|
||||
"""
|
||||
|
||||
|
|
@ -841,7 +848,7 @@ class SolutionArray:
|
|||
|
||||
def from_pandas(self, df):
|
||||
"""
|
||||
Restores SolutionArray data from a pandas DataFrame *df*.
|
||||
Restores `SolutionArray` data from a pandas DataFrame *df*.
|
||||
|
||||
This method is intendend for loading of data that were previously
|
||||
exported by `to_pandas`. The method requires a working pandas
|
||||
|
|
@ -880,7 +887,7 @@ class SolutionArray:
|
|||
|
||||
Additional arguments (i.e. *args* and *kwargs*) are passed on to
|
||||
`collect_data` via `to_pandas`; see `collect_data` for further
|
||||
information. This method works only with 1D SolutionArray objects
|
||||
information. This method works only with 1D `SolutionArray` objects
|
||||
and requires working installations of pandas and PyTables. These
|
||||
packages can be installed using pip (`pandas` and `tables`) or conda
|
||||
(`pandas` and `pytables`).
|
||||
|
|
@ -895,7 +902,7 @@ class SolutionArray:
|
|||
def read_hdf(self, filename, key=None):
|
||||
"""
|
||||
Read a dataset identified by *key* from a HDF file named *filename*
|
||||
and restore data to the SolutionArray object. This method allows for
|
||||
and restore data to the `SolutionArray` object. This method allows for
|
||||
recreation of data previously exported by `write_hdf`.
|
||||
|
||||
The method imports data using `restore_data` via `from_pandas` and
|
||||
|
|
|
|||
|
|
@ -155,10 +155,12 @@ class TestPureFluid(utilities.CanteraTest):
|
|||
|
||||
self.water.TPX = T, P, X
|
||||
self.assertNear(X, 0.8)
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaisesRegex(ValueError, 'invalid thermodynamic'):
|
||||
self.water.TPX = T, .999*P, X
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaisesRegex(ValueError, 'invalid thermodynamic'):
|
||||
self.water.TPX = T, 1.001*P, X
|
||||
with self.assertRaisesRegex(ValueError, 'numeric value is required'):
|
||||
self.water.TPX = T, P, 'spam'
|
||||
|
||||
|
||||
# To minimize errors when transcribing tabulated data, the input units here are:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import warnings
|
||||
import weakref
|
||||
import numbers as _numbers
|
||||
|
||||
cdef enum ThermoBasis:
|
||||
mass_basis = 0
|
||||
|
|
@ -1565,7 +1566,7 @@ cdef class PureFluid(ThermoPhase):
|
|||
|
||||
property TPX:
|
||||
"""
|
||||
Get/Set the temperature [K], pressure [Pa], and vapor fraction of a
|
||||
Get/Set the temperature [K], pressure [Pa], and vapor fraction of a
|
||||
PureFluid.
|
||||
|
||||
An Exception is raised if the thermodynamic state is not consistent.
|
||||
|
|
@ -1576,6 +1577,11 @@ cdef class PureFluid(ThermoPhase):
|
|||
T = values[0] if values[0] is not None else self.T
|
||||
P = values[1] if values[1] is not None else self.P
|
||||
X = values[2] if values[2] is not None else self.X
|
||||
if not isinstance(X, (np.ndarray, _numbers.Number)):
|
||||
raise ValueError(
|
||||
'a numeric value is required to quanitify '
|
||||
'the vapor fraction (X)'
|
||||
)
|
||||
if np.isclose(P, self.thermo.satPressure(T)):
|
||||
self.TX = T, X
|
||||
elif np.isclose(X, 0.) or np.isclose(X, 1.):
|
||||
|
|
@ -1586,7 +1592,9 @@ cdef class PureFluid(ThermoPhase):
|
|||
'received a combination of property values that '
|
||||
'do not represent a valid state. As an alternative, '
|
||||
'specify the state using two fully independent '
|
||||
'properties (e.g. TD)')
|
||||
'properties (e.g. TD) instead of: '
|
||||
'T={}, P={}, X={}'.format(T, P, X)
|
||||
)
|
||||
|
||||
property UVX:
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue