Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/cantera/kinetics/Kinetics.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class Kinetics
//! Each class derived from Kinetics should override this method to return
//! a meaningful identifier.
virtual std::string kineticsType() const {
return "Kinetics";
return "None";
}

//! Finalize Kinetics object and associated objects
Expand Down
6 changes: 4 additions & 2 deletions include/cantera/thermo/Phase.h
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,8 @@ class Phase
* \rho, Y_1, \dots, Y_K) \f$. Alternatively, it returns a stored value.
*/
virtual double pressure() const {
throw NotImplementedError("Phase::pressure");
throw NotImplementedError("Phase::pressure",
"Not implemented for thermo model '{}'", type());
}

//! Density (kg/m^3).
Expand Down Expand Up @@ -703,7 +704,8 @@ class Phase
* @param p input Pressure (Pa)
*/
virtual void setPressure(double p) {
throw NotImplementedError("Phase::setPressure");
throw NotImplementedError("Phase::setPressure",
"Not implemented for thermo model '{}'", type());
}

//! Set the internally stored temperature of the phase (K).
Expand Down
2 changes: 1 addition & 1 deletion include/cantera/thermo/ThermoPhase.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class ThermoPhase : public Phase
//! @{

virtual std::string type() const {
return "ThermoPhase";
return "None";
}

//! String indicating the mechanical phase of the matter in this Phase.
Expand Down
2 changes: 1 addition & 1 deletion interfaces/cython/cantera/_cantera.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,7 @@ cdef class _SolutionBase:
cdef int thermo_basis
cdef np.ndarray _selected_species
cdef object parent
cdef public object _references

cdef class Species:
cdef shared_ptr[CxxSpecies] _species
Expand All @@ -1261,7 +1262,6 @@ cdef class ThermoPhase(_SolutionBase):
cpdef int species_index(self, species) except *
cdef np.ndarray _getArray1(self, thermoMethod1d method)
cdef void _setArray1(self, thermoMethod1d method, values) except *
cdef public object _references

cdef class InterfacePhase(ThermoPhase):
cdef CxxSurfPhase* surf
Expand Down
55 changes: 48 additions & 7 deletions interfaces/cython/cantera/base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@ cdef class _SolutionBase:
source=None, yaml=None, thermo=None, species=(),
kinetics=None, reactions=(), **kwargs):

# run instantiation only if valid sources are specified
self._references = None
if origin or infile or source or yaml or (thermo and species):

self._cinit(infile=infile, name=name, adjacent=adjacent,
origin=origin, source=source, yaml=yaml,
thermo=thermo, species=species, kinetics=kinetics,
reactions=reactions, **kwargs)
return
elif any([infile, source, adjacent, origin, source, yaml,
thermo, species, kinetics, reactions, kwargs]):
raise ValueError("Arguments are insufficient to define a phase")

self._base = CxxNewSolution()
self.base = self._base.get()
self.base.setSource(stringify("none"))

self.base.setThermo(newThermo(stringify("none")))
self.thermo = self.base.thermo().get()
self.base.setKinetics(newKinetics(stringify("none")))
self.kinetics = self.base.kinetics().get()
self.base.setTransport(newTransport(NULL, stringify("none")))
self.transport = self.base.transport().get()
self._selected_species = np.ndarray(0, dtype=np.uint64)

def _cinit(self, infile='', name='', adjacent=(), origin=None,
source=None, yaml=None, thermo=None, species=(),
kinetics=None, reactions=(), **kwargs):

if 'phaseid' in kwargs:
if name is not '':
raise AttributeError('duplicate specification of phase name')
Expand Down Expand Up @@ -63,10 +92,8 @@ cdef class _SolutionBase:
# Parse inputs
if infile or source:
self._init_cti_xml(infile, name, adjacent, source)
elif thermo and species:
self._init_parts(thermo, species, kinetics, adjacent, reactions)
else:
raise ValueError("Arguments are insufficient to define a phase")
self._init_parts(thermo, species, kinetics, adjacent, reactions)

self._selected_species = np.ndarray(0, dtype=np.uint64)

Expand Down Expand Up @@ -152,6 +179,9 @@ cdef class _SolutionBase:
self.base.setKinetics(newKinetics(stringify("none")))
self.kinetics = self.base.kinetics().get()

# Transport
self.transport = self.base.transport().get()

def _init_cti_xml(self, infile, name, adjacent, source):
"""
Instantiate a set of new Cantera C++ objects from a CTI or XML
Expand Down Expand Up @@ -283,14 +313,14 @@ cdef class _SolutionBase:
"""
self.base.header().clear()

def write_yaml(self, filename, phases=None, units=None, precision=None,
def write_yaml(self, filename=None, phases=None, units=None, precision=None,
skip_user_defined=None, header=True):
"""
Write the definition for this phase, any additional phases specified,
and their species and reactions to the specified file.

:param filename:
The name of the output file
The name of the output file; if ``None``, a YAML string is returned
:param phases:
Additional ThermoPhase / Solution objects to be included in the
output file
Expand Down Expand Up @@ -328,6 +358,8 @@ cdef class _SolutionBase:
Y.precision = precision
if skip_user_defined is not None:
Y.skip_user_defined = skip_user_defined
if filename is None:
return Y.to_string()
Y.to_file(filename)

def __getitem__(self, selection):
Expand All @@ -349,8 +381,17 @@ cdef class _SolutionBase:
for i,spec in enumerate(species):
self._selected_species[i] = self.species_index(spec)

def __reduce__(self):
raise NotImplementedError('Solution object is not picklable')
def __getstate__(self):
"""Save complete information of the current phase for pickling."""
if self.kinetics.nTotalSpecies() > self.thermo.nSpecies():
raise NotImplementedError(
"Pickling of Interface objects is not implemented.")
return self.write_yaml()

def __setstate__(self, pkl):
"""Restore Solution from pickled information."""
yml = pkl
self._cinit(yaml=yml)

def __copy__(self):
raise NotImplementedError('Solution object is not copyable')
12 changes: 9 additions & 3 deletions interfaces/cython/cantera/composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import pandas as _pandas


class Solution(ThermoPhase, Kinetics, Transport):
class Solution(Transport, Kinetics, ThermoPhase):
"""
A class for chemically-reacting solutions. Instances can be created to
represent any type of solution -- a mixture of gases, a liquid solution, or
Expand Down Expand Up @@ -101,7 +101,7 @@ class Solution(ThermoPhase, Kinetics, Transport):
__slots__ = ()


class Interface(InterfacePhase, InterfaceKinetics):
class Interface(InterfaceKinetics, InterfacePhase):
"""
Two-dimensional interfaces.

Expand All @@ -121,7 +121,7 @@ class Interface(InterfacePhase, InterfaceKinetics):
__slots__ = ('_phase_indices',)


class DustyGas(ThermoPhase, Kinetics, DustyGasTransport):
class DustyGas(DustyGasTransport, Kinetics, ThermoPhase):
"""
A composite class which models a gas in a stationary, solid, porous medium.

Expand Down Expand Up @@ -1375,6 +1375,12 @@ def strip_ext(source):

return root_attrs

def __reduce__(self):
raise NotImplementedError('SolutionArray object is not picklable')

def __copy__(self):
raise NotImplementedError('SolutionArray object is not copyable')


def _state2_prop(name, doc_source):
# Factory for creating properties which consist of a tuple of two variables,
Expand Down
11 changes: 11 additions & 0 deletions interfaces/cython/cantera/kinetics.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ from ctypes import c_int
# e.g. class Solution. [Cython 0.16]
cdef np.ndarray get_species_array(Kinetics kin, kineticsMethod1d method):
cdef np.ndarray[np.double_t, ndim=1] data = np.empty(kin.n_total_species)
if kin.n_total_species == 0:
return data
method(kin.kinetics, &data[0])
# @todo: Fix _selected_species to work with interface kinetics
if kin._selected_species.size:
Expand All @@ -17,6 +19,8 @@ cdef np.ndarray get_species_array(Kinetics kin, kineticsMethod1d method):

cdef np.ndarray get_reaction_array(Kinetics kin, kineticsMethod1d method):
cdef np.ndarray[np.double_t, ndim=1] data = np.empty(kin.n_reactions)
if kin.n_reactions == 0:
return data
method(kin.kinetics, &data[0])
return data

Expand Down Expand Up @@ -60,6 +64,13 @@ cdef class Kinetics(_SolutionBase):
of progress, species production rates, and other quantities pertaining to
a reaction mechanism.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self._references is None:
raise ValueError(
"Cannot instantiate stand-alone 'Kinetics' object as it requires an "
"associated thermo phase.\nAll 'Kinetics' methods should be accessed "
"from a 'Solution' object.")

property kinetics_model:
"""
Expand Down
2 changes: 1 addition & 1 deletion interfaces/cython/cantera/liquidvapor.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def Water(backend='Reynolds'):
:ct:WaterSSTP and :ct:WaterTransport in the Cantera C++ source
code documentation.
"""
class WaterWithTransport(PureFluid, _cantera.Transport):
class WaterWithTransport(_cantera.Transport, PureFluid):
__slots__ = ()

if backend == 'Reynolds':
Expand Down
89 changes: 89 additions & 0 deletions interfaces/cython/cantera/test/test_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import numpy as np
from collections import OrderedDict
import pickle

import cantera as ct
from cantera.composite import _h5py, _pandas
Expand Down Expand Up @@ -116,6 +117,94 @@ def check(a, b):
raise TypeError(msg) from inst


class TestPickle(utilities.CanteraTest):

def test_pickle_gas(self):
gas = ct.Solution("h2o2.yaml", transport_model=None)
gas.TPX = 500, 500000, "H2:.75,O2:.25"
with open("gas.pkl", "wb") as pkl:
pickle.dump(gas, pkl)

with open("gas.pkl", "rb") as pkl:
gas2 = pickle.load(pkl)
self.assertNear(gas.T, gas2.T)
self.assertNear(gas.P, gas2.P)
self.assertArrayNear(gas.X, gas2.X)

self.assertEqual(gas2.transport_model, "None")

def test_pickle_gas_with_transport(self):
gas = ct.Solution("h2o2.yaml")
gas.TPX = 500, 500000, "H2:.75,O2:.25"
gas.transport_model = "Multi"
with open("gas.pkl", "wb") as pkl:
pickle.dump(gas, pkl)

with open("gas.pkl", "rb") as pkl:
gas2 = pickle.load(pkl)
self.assertNear(gas.T, gas2.T)
self.assertNear(gas.P, gas2.P)
self.assertArrayNear(gas.X, gas2.X)

self.assertEqual(gas2.transport_model, "Multi")

def test_pickle_interface(self):
gas = ct.Solution("diamond.yaml", "gas")
solid = ct.Solution("diamond.yaml", "diamond")
interface = ct.Interface("diamond.yaml", "diamond_100", (gas, solid))

with self.assertRaises(NotImplementedError):
with open("interface.pkl", "wb") as pkl:
pickle.dump(interface, pkl)


class TestEmptyThermoPhase(utilities.CanteraTest):
""" Test empty Solution object """
@classmethod
def setUpClass(cls):
utilities.CanteraTest.setUpClass()
cls.gas = ct.ThermoPhase()

def test_empty_report(self):
with self.assertRaisesRegex(ct.CanteraError, "NotImplementedError"):
self.gas()

def test_empty_TP(self):
with self.assertRaisesRegex(ct.CanteraError, "NotImplementedError"):
self.gas.TP = 300, ct.one_atm

def test_empty_equilibrate(self):
with self.assertRaisesRegex(ct.CanteraError, "NotImplementedError"):
self.gas.equilibrate("TP")


class TestEmptySolution(TestEmptyThermoPhase):
""" Test empty Solution object """
@classmethod
def setUpClass(cls):
utilities.CanteraTest.setUpClass()
cls.gas = ct.Solution()

def test_empty_composite(self):
self.assertEqual(self.gas.thermo_model, "None")
self.assertEqual(self.gas.composite, ("None", "None", "None"))


class TestEmptyEdgeCases(utilities.CanteraTest):
""" Test for edge cases where constructors are not allowed """
def test_empty_phase(self):
with self.assertRaisesRegex(ValueError, "Arguments are insufficient to define a phase"):
ct.ThermoPhase(thermo="ideal-gas")

def test_empty_kinetics(self):
with self.assertRaisesRegex(ValueError, "Cannot instantiate"):
ct.Kinetics()

def test_empty_transport(self):
with self.assertRaisesRegex(ValueError, "Cannot instantiate"):
ct.Transport()


class TestSolutionArrayIO(utilities.CanteraTest):
""" Test SolutionArray file IO """
@classmethod
Expand Down
5 changes: 0 additions & 5 deletions interfaces/cython/cantera/test/test_thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,11 +740,6 @@ def test_ref_info(self):
self.assertNear(self.phase.min_temp, 300.0)
self.assertNear(self.phase.max_temp, 3500.0)

def test_unpicklable(self):
import pickle
with self.assertRaises(NotImplementedError):
pickle.dumps(self.phase)

def test_uncopyable(self):
import copy
with self.assertRaises(NotImplementedError):
Expand Down
3 changes: 3 additions & 0 deletions interfaces/cython/cantera/thermo.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,9 @@ cdef class ThermoPhase(_SolutionBase):
super().__init__(*args, **kwargs)
if 'source' not in kwargs:
self.thermo_basis = mass_basis
# In composite objects, the ThermoPhase constructor needs to be called first
# to prevent instantiation of stand-alone 'Kinetics' or 'Transport' objects.
# The following is used as a sentinel.
self._references = weakref.WeakKeyDictionary()

property thermo_model:
Expand Down
6 changes: 6 additions & 0 deletions interfaces/cython/cantera/transport.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ cdef class Transport(_SolutionBase):
# The signature of this function causes warnings for Sphinx documentation
def __init__(self, *args, **kwargs):
if self.transport == NULL:
# @todo ... after removal of CTI/XML, this should be handled by base.pyx
if 'transport_model' not in kwargs:
self.base.setTransport(newTransport(self.thermo, stringify("default")))
else:
Expand All @@ -178,6 +179,11 @@ cdef class Transport(_SolutionBase):
self.transport = self.base.transport().get()

super().__init__(*args, **kwargs)
if self._references is None:
raise ValueError(
"Cannot instantiate stand-alone 'Transport' object as it requires an "
"associated thermo phase.\nAll 'Transport' methods should be accessed "
"from a 'Solution' object.")

property transport_model:
"""
Expand Down
Loading