Multi-phase reaction systems in cantera

41 views
Skip to first unread message

Andrea Locaspi

unread,
May 18, 2026, 11:00:27 AMMay 18
to Cantera Users' Group
Dear all,

I am trying to import a multiphase mechanism of the type:
SPECIES:
- GAS: A, B
- LIQUID: C(L)
REACTIONS:
- GAS: A=>B
- LIQUID: C(L)=>A

I have considered creating two separate mechanisms (gas.yaml, liq.yaml), duplicating shared species with placeholder density values in the liquid file. However, I believe this approach does not allow for a fully coupled treatment of the reactions, and would require manually managing the connection between the two mechanisms at the Python level, which seems less computationally efficient.

I have seen in a previous post ( https://groups.google.com/g/cantera-users/c/MtAvcMooNwo/m/ClnYbmLJAAAJ ) that it was suggested to create a fake surface phase which would allow to then manage the multi-phase reactions. This approach works, but only if the reactions are defined within this fake surface phase, otherwise cantera will throw the same errors as the case above. 

Another post ( https://groups.google.com/g/cantera-users/c/_yYs1mJECzQ) discussed employing the interface for phase-exchange, but this would require considering phase-equilibrium, which complicates the system without adding any relevant information.

None of these approaches seem suitable to model multi-phase systems, as they involve uncoupling one phase from the other, a mechanistic treatment of the interface, or external numerical solvers (e.g., scipy). 

Are there other approaches that would allow a more consistent treatment of these phenomena? I believe that the treatment of surface reactors is done in a consistent manner, is there any possibility to extend the concept of adjacent phases also to bulk phases? Or consider solution of multiple adjacent phases within the reactors of cantera?

Ray Speth

unread,
May 18, 2026, 8:21:22 PMMay 18
to Cantera Users' Group

Hi Andrea,

Is the goal to use your mechanism with a ReactorNet with one reactor representing the gas and another representing the liquid? If so, I think this should be possible with one adjustment to your model, and the use of the current development version of Cantera.

I think you do want to create a surface phase to represent the liquid-gas interface. However, instead of writing the reaction C(L)=>A as occurring on the interface, I would define an additional liquid-phase species for A dissolved in the liquid, e.g. A(aq). The bulk liquid phase reaction would be C(L)=>A(aq) and then on the surface you would have A(aq)=>A. I think you can avoid the phase equilibrium issue by making this latter reaction irreversible, and setting the rate to some high value. You can set it so that the time constant for transfer to the gas phase is short compared to your other reactions, just not so high that it makes the problem unnecessarily stiff.

I recommend doing this in the development version of Cantera (that is, the current main branch on GitHub) since there have been improvements to how interfaces are handled when the phases on both sides of the interface are variable (rather than being static/infinite reservoirs).

Please do let us know if this approach works for your problem.

Regards,
Ray

Andrea Locaspi

unread,
May 22, 2026, 9:24:03 AM (11 days ago) May 22
to Cantera Users' Group
Hi Ray,

thank you for your suggestion. I have tested multiple configurations following your suggestion but I am still currently failing.

I have built cantera from source in a conda environment both with the base configuration and with the additional optional C++ libraries (eigen, sundials, hdf, highfive, yaml-cpp, fmt). I do not see any noticeable difference in computational speed between the two configurations, the second one might be even slightly slower for my case.

With respect to my case, my goal is to simulate a system with a liquid phase whose volume changes due to gas formation from reactions. As a simple test setup, I used a closed batch reactor with a gas volume 1000 times larger than the liquid one, to reduce pressure overshoots. I have tested two mechanisms formulations:
  1. Define a fake surface phase with a species "FAKE(S)" and call all liquid reactions from there as suggested in https://groups.google.com/g/cantera-users/c/MtAvcMooNwo/m/ClnYbmLJAAAJ
  2. Define an interface-phase with the missing species added as "(aq)". Following the previous example and your suggestion, this means the liquid-phase reaction is now C(L)=>A(aq) and then on the interface phase occurs the reaction A(aq)=>A.
To solve the mechanisms, I have tried two reactor setups:
  1. Define a scipy system of equation with the reaction rates being computed through cantera (custom-made function "rhs" being passed to scipy.solve_ivp())
  2. Define an extensible cantera reactor where the volume (or reactive surface) varies with the volume computed from the species densities. I then have two reactors, one with liquid reactions and one with gas reactions that are joined into a reactor net
In both cases, the mechanisms are defined starting from surf = ct.Interface() and then the bulk phase arre called with surf.adjacent[]. For a small test mechanism (2 liquid species, 10 unimolecular liquid reactions), after tuning, I get results with scipy in 0.1 seconds and with cantera in about 20 seconds. 

The issues appear when I try using a real radical mechanism with 50 liquid species and 4000 reactions. In this case, neither solver is able to return any result and they usually crash after one hour without reaching even 1% of conversion. To check if the mechanism itself was problematic, I run an homogeneous single phase pfr, which gave results within less than a minute (although wrong as the "aq" species accumulate diluting the system). 

Are these difficulties expected for this type of multiphase stiff system?

I am considering a different extensible-reactor strategy: explicitly mapping/transferring (aq) source terms into corresponding gas-phase species source terms, so I can avoid explicit surface/interface phases and possibly reduce stiffness. Can this approach be beneficial?

Since I have a source build, I could also implement a custom reactor class in C++. Would that require changes to YAML interpretation/parsing, or can this be done mostly at the reactor-equation level while keeping standard YAML kinetics definitions?

Kind regards,
Andrea

Ray Speth

unread,
May 25, 2026, 10:24:46 AM (8 days ago) May 25
to Cantera Users' Group
Hi Andrea,

Can you share a complete example with the small test mechanism? The integration using Cantera should certainly not be slower than integrating with Scipy. It sounds like there's something providing inconsistent information to the integrator which then results in lots of failed or very small time steps. I don't think there's anything inherently more stiff about this kind of multiphase system than what you get in gas phase combustion chemistry, and I don't think the Scipy BDF integrator does anything more sophisticated than the SUNDIALS version used by Cantera.

There's not meant to be a significant performance difference between using pre-installed versions of the various dependencies versus letting the build system use them from the Git submodules. It's just a matter of what's most convenient or what's needed for compatibility with a given environment (for example, when we build the Condaforge packages, we need to use dependencies that are installed as Condaforge pacakges).

Regards,
Ray
Reply all
Reply to author
Forward
0 new messages