Skip to content

Commit

Permalink
Improve SolutionArray IO documentation and exceptions
Browse files Browse the repository at this point in the history
Resolve discrepancies of nomenclature in C++/Python
  • Loading branch information
ischoegl committed Jun 22, 2023
1 parent 9c60e9e commit 5a4e3f8
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 79 deletions.
108 changes: 67 additions & 41 deletions include/cantera/base/SolutionArray.h
Expand Up @@ -22,8 +22,6 @@ class ThermoPhase;
* stored, reshaping operations need to be implemented in high-level API's.
*
* @since New in Cantera 3.0.
* @warning This class is an experimental part of the %Cantera API and may be
* changed or removed without notice.
*/
class SolutionArray
{
Expand Down Expand Up @@ -189,119 +187,147 @@ class SolutionArray
void append(const vector<double>& state, const AnyMap& extra);

/*!
* Write header data to container file.
* Write header data to a HDF container file.
*
* @param fname Name of HDF container file
* @param id Identifier of root location within the container file
* @param desc Description
* @param overwrite Force overwrite if id exists; optional (default=false)
* @param id Identifier of group holding header information
* @param desc Custom comment describing dataset
* @param overwrite Force overwrite if file/group exists; optional (default=false)
*/
static void writeHeader(const string& fname, const string& id, const string& desc,
bool overwrite=false);

/*!
* Write header data to AnyMap.
* Write header data to AnyMap; used by YAML serialization.
*
* @param root Root node of AnyMap structure
* @param id Identifier of root location within the container file
* @param desc Description
* @param overwrite Force overwrite if id exists; optional (default=false)
* @param id Identifier of node holding header information
* @param desc Custom comment describing dataset
* @param overwrite Force overwrite if node exists; optional (default=false)
*/
static void writeHeader(AnyMap& root, const string& id, const string& desc,
bool overwrite=false);

/*!
* Write SolutionArray data to comma-separated data file.
* Write SolutionArray data to a CSV file.
*
* @param fname Name of CSV file
* @param overwrite Force overwrite if file exists; optional (default=false)
* @param basis Output mass ("Y"/"mass") or mole ("X"/"mole") fractions;
* if not specified (""), the native storage mode is used
* if omitted (default=""), the native basis of the underlying ThermoPhase
* manager is used - @see nativeState
*/
void writeEntry(const string& fname, bool overwrite=false, const string& basis="");

/*!
* Write SolutionArray data to container file.
* Write SolutionArray data to a HDF container file.
*
* @param fname Name of HDF container file
* @param id Identifier of root location within the container file
* @param sub Name identifier for the subgroup holding actual data
* @param overwrite Force overwrite if sub exists; optional (default=false)
* @param id Identifier of group holding header information
* @param sub Name identifier of subgroup holding SolutionArray data
* @param overwrite Force overwrite if subgroup exists; optional (default=false)
* @param compression Compression level; optional (default=0; HDF only)
*/
void writeEntry(const string& fname, const string& id, const string& sub,
bool overwrite=false, int compression=0);

/*!
* Write SolutionArray data to AnyMap.
* Write SolutionArray data to AnyMap; used by YAML serialization.
*
* @param root Root node of AnyMap structure
* @param id Identifier of root location within the container file
* @param sub Name identifier for the subgroup holding actual data
* @param overwrite Force overwrite if sub exists; optional (default=false)
* @param id Identifier of node holding header information and subgroup
* @param sub Name identifier of subgroup holding SolutionArray data
* @param overwrite Force overwrite if subgroup exists; optional (default=false)
*/
void writeEntry(AnyMap& root, const string& id, const string& sub,
bool overwrite=false);

/*!
* Save current SolutionArray and header to a data file.
* Save current SolutionArray contents to a data file.
*
* @param fname Name of output container file (CSV, YAML or HDF)
* @param id Identifier of root location within container file (YAML/HDF only)
* @param sub Name identifier for subgroup holding actual data (YAML/HDF only)
* Data can be saved either in CSV format (extension '*.csv'), YAML container
* format (extension '*.yaml'/'*.yml') or HDF container format (extension
* '*.h5'/'*.hdf5'/'*.hdf'). The output format is automatically inferred from the
* file extension.
*
* CSV files preserve state data and auxiliary data for a single SolutionArray in a
* comma-separated text format, container files may hold multiple SolutionArray
* entries in an internal hierarchical structure. While YAML is a human-readable
* text format, HDF is a binary format that supports compression and is recommended
* for large datasets.
*
* For container files (YAML and HDF), header information contains automatically
* generated time stamps, version information and an optional description.
* Container files also preserve SolutionArray metadata (example: SolutionArray
* objects generated by Sim1D hold simulation settings).
*
* @param fname Name of output file (CSV, YAML or HDF)
* @param id Identifier of location within the container file; this node/group
* contains header information and a subgroup holding actual SolutionArray data
* (YAML/HDF only)
* @param sub Name identifier for the subgroup holding the SolutionArray data and
* metadata objects. If omitted (""), the subgroup name defaults to "data"
* (YAML/HDF only)
* @param desc Custom comment describing dataset to be stored (YAML/HDF only)
* @param overwrite Force overwrite if file and/or data entry exists; optional
* (default=false)
* @param compression Compression level; optional (default=0; HDF only)
* @param basis Output mass ("Y"/"mass") or mole ("X"/"mole") fractions (CSV
* only); if omitted (default=""), the native storage mode is used
* @param compression Compression level (0-9); (default=0; HDF only)
* @param basis Output mass ("Y"/"mass") or mole ("X"/"mole") fractions;
* if not specified (default=""), the native basis of the underlying
* ThermoPhase manager is used - @see nativeState (CSV only)
*/
void save(const string& fname, const string& id="", const string& sub="",
const string& desc="", bool overwrite=false, int compression=0,
const string& basis="");

/*!
* Read header data from container file.
* Read header information from a HDF container file.
*
* @param fname Name of HDF container file
* @param id Identifier of root location within the container file
* @param id Identifier of group holding header information
*/
static AnyMap readHeader(const string& fname, const string& id);

/*!
* Read header data from AnyMap.
* Read header information from AnyMap; used by YAML serialization.
*
* @param root Root node of AnyMap structure
* @param id Identifier of root location within the container file
* @param id Identifier of node holding header information
*/
static AnyMap readHeader(const AnyMap& root, const string& id);

/*!
* Restore SolutionArray entry from a container file.
* Restore SolutionArray data from a HDF container file.
*
* @param fname Name of HDF container file
* @param id Identifier of root location within the container file
* @param sub Name of the subgroup holding actual data
* @param id Identifier of group holding header information
* @param sub Name identifier of subgroup holding SolutionArray data
*/
void readEntry(const string& fname, const string& id, const string& sub);

/*!
* Restore SolutionArray entry from AnyMap.
* Restore SolutionArray data from AnyMap; used by YAML serialization.
*
* @param root Root node of AnyMap structure
* @param id Identifier of root location within the container file
* @param sub Name of the subgroup holding actual data
* @param id Identifier of node holding header information
* @param sub Name identifier of subgroup holding SolutionArray data
*/
void readEntry(const AnyMap& root, const string& id, const string& sub);

/*!
* Restore SolutionArray entry and header from a container file.
* Restore SolutionArray data and header information from a container file.
*
* This method retrieves data from a YAML or HDF files that were previously saved
* using the @see save method.
*
* @param fname Name of container file (YAML or HDF)
* @param id Identifier of SolutionArray within the container file
* @param sub Name of the subgroup holding actual data
* @param id Identifier of location within the container file; this node/group
* contains header information and a subgroup holding actual SolutionArray data
* @param sub Name identifier for the subgroup holding the SolutionArray data and
* metadata objects. If omitted (""), the subgroup name defaults to "data"
* @return AnyMap containing header information
*/
AnyMap restore(const string& fname, const string& id, const string& sub);
AnyMap restore(const string& fname, const string& id, const string& sub="");

protected:
//! Service function used to resize SolutionArray
Expand Down
8 changes: 6 additions & 2 deletions interfaces/cython/cantera/_onedim.pyx
Expand Up @@ -1541,8 +1541,12 @@ cdef class Sim1D:
"""
Save the solution to a container or CSV format.
For HDF and YAML files, the entire content of the `Sim1D` object is saved; for
CSV, only the main 1D domain is saved.
In order to save the content of a `Sim1D` object, individual domains are
converted to `SolutionArray` objects and saved using the `SolutionArray.save`
method. For HDF and YAML output, all domains are written to a single container
file with shared header information. Simulation settings of individual domains
are preserved as meta data of the corresponding `SolutionArray` objects.
For CSV files, only state and auxiliary data of the main 1D domain are saved.
:param filename:
solution file
Expand Down
62 changes: 41 additions & 21 deletions interfaces/cython/cantera/composite.py
Expand Up @@ -1251,53 +1251,73 @@ def from_pandas(self, df, normalize=True):
data_dict[label] = data_dict[label].astype('U')
self.restore_data(data_dict, normalize)

def save(self, fname, name=None, key=None, description=None, *,
def save(self, fname, name=None, sub=None, description=None, *,
overwrite=False, compression=0, basis=None):
"""
Save current `SolutionArray` to a container or CSV file.
Save current `SolutionArray` contents to a data file.
Data can be saved either in CSV format (extension ``*.csv``), YAML container
format (extension ``*.yaml``/``*.yml``) or HDF container format (extension
``*.h5``/``*.hdf5``/``*.hdf``). The output format is automatically inferred from
the file extension.
CSV files preserve state data and auxiliary data for a single `SolutionArray` in
a comma-separated text format, container files may hold multiple `SolutionArray`
entries in an internal hierarchical structure. While YAML is a human-readable
text format, HDF is a binary format that supports compression and is recommended
for large datasets.
For container files (YAML and HDF), header information contains automatically
generated time stamps, version information and an optional description.
Container files also preserve `SolutionArray` metadata (example:
`SolutionArray` objects generated by `Sim1D` store simulation settings).
:param fname:
Name of output container file (YAML or HDF)
Name of output file (CSV, YAML or HDF)
:param name:
Identifier of root location within the container file; the root location
contains header data and a subgroup holding the actual `SolutionArray`
(HDF/YAML only).
:param key:
Identifier of location within the container file; this node/group contains
header information and a subgroup holding actual `SolutionArray` data
(YAML/HDF only).
:param sub:
Name identifier for the subgroup holding the `SolutionArray` data and
metadata objects. If `None`, the subgroup name default to ``data``
(HDF/YAML only).
metadata objects. If `None`, the subgroup name defaults to ``data``
(YAML/HDF only).
:param description:
Custom comment describing the dataset to be stored (HDF/YAML only).
Custom comment describing the dataset to be stored (YAML/HDF only).
:param overwrite:
Force overwrite if name exists; optional (default=`False`)
:param compression:
Compression level (0-9); optional (default=0; HDF only)
:param basis:
Output mass (``Y``/``mass``) or mole (``X``/``mole``) fractions (CSV only);
if not specified (`None`), the native storage mode is used
Output mass (``Y``/``mass``) or mole (``Y``/``mass``) fractions;
if not specified (`None`), the native basis of the underlying `ThermoPhase`
manager is used.
.. versionadded:: 3.0
"""
self._cxx_save(fname, name, key, description, overwrite, compression, basis)
self._cxx_save(fname, name, sub, description, overwrite, compression, basis)

def restore(self, fname, name=None, key=None):
def restore(self, fname, name=None, sub=None):
"""
Retrieve `SolutionArray` and header from a container file.
Restore `SolutionArray` data and header information from a container file.
This method retrieves data from a YAML or HDF files that were previously saved
using the `save` method.
:param fname:
Name of container file (YAML or HDF)
:param name:
Identifier of root location within the container file; the root location
contains header data and a subgroup holding the actual `SolutionArray`.
:param key:
Identifier of location within the container file; this node/group contains
header information and a subgroup holding actual `SolutionArray` data
:param sub:
Name identifier for the subgroup holding the `SolutionArray` data and
metadata objects.
metadata objects. If `None`, the subgroup name defaults to ``data``
:return:
Dictionary holding `SolutionArray` meta data.
.. versionadded:: 3.0
"""
meta = self._cxx_restore(fname, name, key)
meta = self._cxx_restore(fname, name, sub)

# ensure self._indices and self._output_dummy are set
self.shape = self._api_shape()
Expand Down Expand Up @@ -1384,7 +1404,7 @@ def write_hdf(self, filename, *args, cols=None, group=None, subgroup=None,

if group is None:
raise KeyError("Missing required parameter 'group'.")
self.save(filename, name=group, key=subgroup)
self.save(filename, name=group, sub=subgroup)
return group

def read_hdf(self, filename, group=None, subgroup=None, force=False, normalize=True):
Expand Down
4 changes: 2 additions & 2 deletions interfaces/cython/cantera/onedim.py
Expand Up @@ -138,12 +138,12 @@ def set_initial_guess(self, *args, data=None, group=None, **kwargs):
if any(data.endswith(suffix) for suffix in [".hdf5", ".h5", ".hdf"]):
# data source identifies a HDF file
if "native" in hdf_support():
arr.restore(data, name=group, key=self.domains[1].name)
arr.restore(data, name=group, sub=self.domains[1].name)
else:
arr.read_hdf(data, group=group, subgroup=self.domains[1].name)
elif data.endswith(".yaml") or data.endswith(".yml"):
# data source identifies a YAML file
arr.restore(data, name=group, key=self.domains[1].name)
arr.restore(data, name=group, sub=self.domains[1].name)
elif data.endswith('.csv'):
# data source identifies a CSV file
arr.read_csv(data)
Expand Down
12 changes: 1 addition & 11 deletions src/base/SolutionArray.cpp
Expand Up @@ -994,7 +994,6 @@ void SolutionArray::writeEntry(const string& fname, bool overwrite, const string
vector<AnyValue> components;
vector<bool> isSpecies;
std::stringstream header;
bool escaped = false;
for (const auto& key : names) {
string name = key;
size_t col;
Expand Down Expand Up @@ -1029,7 +1028,6 @@ void SolutionArray::writeEntry(const string& fname, bool overwrite, const string
}
if (name.find(",") != string::npos) {
header << "\"" << name << "\"";
escaped = true;
} else {
header << name;
}
Expand Down Expand Up @@ -1081,7 +1079,6 @@ void SolutionArray::writeEntry(const string& fname, bool overwrite, const string
}
if (value.find(",") != string::npos) {
output << "\"" << value << "\"";
escaped = true;
} else {
output << value;
}
Expand All @@ -1094,13 +1091,6 @@ void SolutionArray::writeEntry(const string& fname, bool overwrite, const string
output << std::endl;
}
output << std::endl << std::setprecision(default_precision);

if (escaped) {
warn_user("SolutionArray::writeEntry",
"One or more CSV column names or values contain commas.\n"
"Values have been escaped with double quotes, which may not be supported "
"by all CSV readers.");
}
}

void SolutionArray::writeEntry(const string& fname, const string& id, const string& sub,
Expand Down Expand Up @@ -1294,7 +1284,7 @@ void SolutionArray::save(const string& fname, const string& id, const string& su
}
if (basis != "") {
warn_user("SolutionArray::save",
"Species basis '{}' not implemented for HDF5 or YAML output.", basis);
"Argument 'basis' is not used for HDF or YAML output.", basis);
}
if (extension == "h5" || extension == "hdf" || extension == "hdf5") {
writeHeader(fname, id, desc, overwrite);
Expand Down
3 changes: 1 addition & 2 deletions test/python/test_composite.py
Expand Up @@ -574,8 +574,7 @@ def test_write_csv_escaped(self):
arr = ct.SolutionArray(self.gas, 7, extra=extra)
arr.TPX = np.linspace(300, 1000, 7), 2e5, "H2:0.5, O2:0.4"
arr.equilibrate("HP")
with pytest.warns(UserWarning, match="escaped"):
arr.save(outfile, basis="mass")
arr.save(outfile, basis="mass")

with open(outfile, "r") as fid:
header = fid.readline()
Expand Down

0 comments on commit 5a4e3f8

Please sign in to comment.