Skip to content

Analytical jacobians for chemistry and vibrational source terms#289

Open
raghava-davuluri wants to merge 24 commits intomutationpp:masterfrom
hypersonic-lab:feature_jacobians
Open

Analytical jacobians for chemistry and vibrational source terms#289
raghava-davuluri wants to merge 24 commits intomutationpp:masterfrom
hypersonic-lab:feature_jacobians

Conversation

@raghava-davuluri
Copy link
Copy Markdown

Summary
This PR adds temperature-dependent Jacobians for chemical source terms and density- and temperature-dependent Jacobians for vibrational energy source terms. With this addition, Mutation++ provides analytical Jacobians for both chemical and vibrational source terms, along with the corresponding source terms, enabling implicit time integration in coupled CFD solvers.

Scope of changes

  • Extension of chemical source-term Jacobians to include temperature dependence.
  • Analytical Jacobians added for vibrational energy source terms for all vibrational energy transfer models.
  • Jacobians are added alongside existing source-term implementations, without changing how Mutation++ is used.
  • C++ implementation only; no new physical models introduced.

Motivation
Previously, Mutation++ provided only density-dependent Jacobians for chemical source terms, while analytical Jacobians for vibrational energy source terms were not available. As a result, CFD solvers coupled with Mutation++ were typically run in explicit mode, which can be computationally expensive for thermochemical nonequilibrium flows.

By introducing temperature-dependent Jacobians for chemical source terms and density- and temperature-dependent Jacobians for vibrational energy source terms, this work enables the use of Mutation++ in fully implicit CFD simulations. This allows solvers such as SU2 and other external CFD solvers coupled to Mutation++ to model thermochemical nonequilibrium effects efficiently.

Technical details
Analytical Jacobians are provided for finite-rate chemical source terms with respect to the translational–rotational and vibrational–electronic temperatures. Jacobians for the vibrational energy source term are provided with respect to species partial densities, translational–rotational temperature, and vibrational–electronic temperature.

The vibrational energy source-term Jacobians are implemented for all existing vibrational energy transfer models. The implementation primarily targets the two-temperature thermochemical nonequilibrium framework in Mutation++, with an internal flag that enables temperature-dependent chemistry Jacobians within the one-temperature model.

Verification and testing
Unit tests were added to Mutation++ to verify the temperature-dependent Jacobians of the chemical source terms, and they passed.

Dedicated unit tests for the vibrational energy source-term Jacobians were not added at this stage, as defining a simple and sufficiently general unit-test framework for these terms proved nontrivial. The author is open to suggestions from the maintainers about the best approaches for testing these Jacobians and would be happy to incorporate them in a follow-up update.

In the meantime, the vibrational energy-source term Jacobians were verified through coupled SU2 simulations. Heat-bath simulations were performed using both explicit and implicit time integration for Air-5 and Air-11 mixtures, demonstrating consistent behavior and stable convergence in implicit mode. Additional verification and validation cases were also carried out using SU2 with implicit settings. The corresponding heat-bath results are attached to this pull request.

Impact and compatibility
The changes are backward compatible and do not alter existing behavior unless the calling CFD solver explicitly requests them. No changes are introduced to default workflows.

Notes for Reviewers
Feedback on the formulation and implementation of the vibrational energy source-term Jacobians would be especially welcome.

Report_HeatBath_SU2_Davuluri.pdf

Kyle Hanquist and others added 24 commits January 20, 2025 14:01
…alculating temperature-related chemistry jacobians.
…source term jacobians and also made changes to earlier version to minimize the memory leak.
… species. Allows to run SU2 to run for single species. Also, added nitrogen (N2, N) mixture and N2 dissociation mechanism in data.
@Hanquist
Copy link
Copy Markdown

@jbscoggi @grgbellas, this is our first pull request. Can you help us with what our next steps should be? We would like this feature added so we can run CFD codes (e.g., SU2-NEMO) as implicit. Thanks!

Comment thread src/thermo/StateModel.h
mp_X = new double [m_thermo.nSpecies()];
for (int i = 0; i < thermo.nSpecies(); ++i)
mp_X[i] = 0.0;
mp_omegaRho = new double [m_thermo.nSpecies()];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call to m_thermo.nSpecies() can be avoided using nmass

Comment thread src/thermo/StateModel.h
for (int i = 0; i < m_transfer_models.size(); ++i) {
m_transfer_models[i].second->jacobianRho(mp_omegaRho);
for (int j = 0; j < ns; ++j)
p_omegaJRho[j] += mp_omegaRho[j];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this works only in the case of a 2-temperature model, as it computes (domegaVT/drhoi).
Something more general might be:
p_omegaJRho[m_transfer_models[i].first][j] +=
mp_omegaRho[j];

Comment thread src/thermo/StateModel.h
p_omegaJRho[i] = 0.0;

for (int i = 0; i < m_transfer_models.size(); ++i) {
m_transfer_models[i].second->jacobianRho(mp_omegaRho);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to use a tmp variable in spite of mp_omegaRho, as this gets overwritten each call

@mgoodson-cvd
Copy link
Copy Markdown
Collaborator

I haven't dug into the code yet, but I pulled down this branch and ran some comparisons between analytic and numerical Jacobians for the chemistry source terms. This is all one temperature. I specifically wanted to check the analytic temperature Jacobian. Here is the code: chemistry_jacobians.cpp

and here is the output:

--- Baseline State ---
P:       100000
T:       4000
rho:     0.08172649063217517
species:                        O2                        O                       NO                        N                       N2
Y:                             0.2      0.05000000000000001                     0.03                     0.02                      0.7

Analytical jacobianRho:
  -1.9321415933197062e+06    -1.7072758527081933e+03     1.6391519539189783e+04    -1.9195868855776444e+07    -1.6268827806099621e+03
   9.7314665840535669e+05    -3.4285638688878162e+03     7.2694375918974436e+05     1.0703112226517720e+07     1.0199320814021810e+03
   1.7985485653545423e+06     9.6320187175882857e+03    -1.3940893225413621e+06     1.5927753834093792e+07     1.1383066474679426e+03
  -8.3956100199057581e+05     9.9004553667350901e+03    -6.3532414900037437e+05    -9.3653974884386994e+06     4.9309478556908192e+02
   7.3715503830760074e+00    -1.4396634362727364e+04     1.2860781928128023e+06     1.9304002836036305e+06    -1.0244507338292435e+03

Numerical jacobianRho (dwdot/drhoY):
  -1.9321415962622268e+06    -1.7072739865398034e+03     1.6391518875025213e+04    -1.9195868858514585e+07    -1.6268855688394979e+03
   9.7314666054444388e+05    -3.4285683796042576e+03     7.2694375921855681e+05     1.0703112223200150e+07     1.0199335520155728e+03
   1.7985485672397772e+06     9.6320236480096355e+03    -1.3940893222752493e+06     1.5927753846335690e+07     1.1383097444195300e+03
  -8.3956100315845106e+05     9.9004531875834800e+03    -6.3532414933433756e+05    -9.3653974987319093e+06     4.9309346650261432e+02
   7.3717274062801152e+00    -1.4396634105651174e+04     1.2860781929703080e+06     1.9304002885291993e+06    -1.0244505119771929e+03

Analytical jacobianT:
  -3.7277872225890448e+01     2.3849192424309891e+01     2.5184838743590781e+01    -2.0337604110976734e+01     8.5814451689665088e+00

Numerical jacobianT (dwdot/dT):
  -6.1593807913595811e+00     4.0727021041675471e+00     3.9134648613980971e+00    -2.9515549613279290e+00     1.1247688189541805e+00

We can see that the density Jacobians (which were already in Mutation++) agree well, whereas the temperature Jacobian does not.

Again, I haven't dug into the source too deeply, or attempted to compare the 2T source terms.

Comment thread src/thermo/StateModel.h
for (int i = 0; i < m_transfer_models.size(); ++i) {
m_transfer_models[i].second->jacobianTTv(mp_omegaTTv);
for (int j = 0; j < m_nenergy; ++j)
p_omegaJTTv[j] += mp_omegaTTv[j];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment thread src/transfer/OmegaCE.cpp
mixture().jacobianTv(mp_wrk4);
p_jacTTv[0] = cv*mixture().Te()*mp_wrk3[0];
p_jacTTv[1] = mp_wrk1[0]*cv;
p_jacTTv[1] += cv*mixture().Te()*mp_wrk4[0];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here I would generalizing allowing for computing the Jacobian for all the temperatures. If nt>2 p_jacTTv[1] is not initialized. Something like:
for (int i = 0; i < nt; ++i)
p_jacTTv[i] = cv*mixture().Te()*mp_wrk[0][i]; //where mp_wrk[0][i] contains the derivate of w with respect to all the temperatures

p_jacTTv[iTe] += mp_wrk1[0]*cv;


double aux = T*RU;

for(int i = 0; i < m_ns; ++i) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here the same comment of above

Comment thread src/transfer/OmegaCV.cpp
double aux = c1*RU*T;
double aux1 = c1*RU;

for(int i = 0; i < m_ns; ++i) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Comment thread src/transfer/OmegaET.cpp

double dtaudRho;
for(int i = 1; i < ns; ++i) {
dtaudRho = 1.0/(mass(0)*8./3.*ve*(NA/mixture().speciesMw(i))*Q11ei(i)/mass(i));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be?
dtaudRho = 1.0/(mass(0)*8./3.veQ11ei(i)/mass(i)/mass(i));

Comment thread src/transfer/OmegaET.cpp
double ve = sqrt(KB*8.*Te/(PI*mass(0)));
double tau = 1.0/(mass(0)*8./3.*ve*nd*(X*Q11ei/mass).tail(ns-1).sum());

p_jacTTv[0] = 1.5*KB*nd*p_X[0]/tau;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this part might be made more general to handle the case of different internal T

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants