Files
mfem/miniapps/adjoint/cvsRoberts_ASAi_dns.cpp
T

378 lines
10 KiB
C++

// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
//
// --------------------------------------------------------
// cvsRoberts-ASAi-DNS Miniapp: Serial MFEM CVODES Example
// --------------------------------------------------------
//
// Compile with: make cvsRoberts_ASAi_dns
//
// Sample runs: cvsRoberts_ASAi_dns -dt 0.01
// cvsRoberts_ASAi_dns -dt 0.005
//
// Description: This example is a port of cvodes/serial/cvsRoberts_ASAi_dns
// example that is part of SUNDIALS. The goal is to demonstrate
// how to use the adjoint SUNDIALS CVODES interface from MFEM.
// Below is an excerpt description from the aforementioned file.
//
// Adjoint sensitivity example problem:
//
// The following is a simple example problem, with the coding needed for its
// solution by CVODES. The problem is from chemical kinetics, and consists of
// the following three rate equations
//
// dy1/dt = -p1*y1 + p2*y2*y3
// dy2/dt = p1*y1 - p2*y2*y3 - p3*(y2)^2
// dy3/dt = p3*(y2)^2
//
// on the interval from t = 0.0 to t = 4e10, with initial conditions: y1 = 1.0,
// y2 = y3 = 0. The reaction rates are: p1=0.04, p2=1e4, and p3=3e7. The problem
// is stiff. This program solves the problem with the BDF method, Newton
// iteration with the DENSE linear solver, and a user-supplied Jacobian routine.
// It uses a scalar relative tolerance and a vector absolute tolerance. Output
// is printed in decades from t = 0.4 to t = 4e10. Run statistics (optional
// outputs) are printed at the end.
//
// Optionally, CVODES can compute sensitivities with respect to the problem
// parameters p1, p2, and p3 of the following quantity:
//
// G = int_t0^t1 g(t,p,y) dt
//
// where g(t,p,y) = y3.
//
// The gradient dG/dp is obtained as:
//
// dG/dp = int_t0^t1 (g_p - lambda^T f_p ) dt - lambda^T(t0)*y0_p
// = - xi^T(t0) - lambda^T(t0)*y0_p
//
// where lambda and xi are solutions of:
//
// d(lambda)/dt = - (f_y)^T * lambda - (g_y)^T
// lambda(t1) = 0
//
// and
//
// d(xi)/dt = - (f_p)^T * lambda + (g_p)^T
// xi(t1) = 0
//
// During the backward integration, CVODES also evaluates G as
//
// G = - phi(t0)
//
// where d(phi)/dt = g(t,y,p), phi(t1) = 0.
#include "mfem.hpp"
#include <fstream>
#include <iostream>
#include <algorithm>
#ifndef MFEM_USE_SUNDIALS
#error This example requires that MFEM is built with MFEM_USE_SUNDIALS=YES
#endif
using namespace std;
using namespace mfem;
// We create a TimeDependentAdjointOperator implementation of the rate equations
// to recreate the cvsRoberts_ASAi_dns problem
class RobertsTDAOperator : public TimeDependentAdjointOperator
{
public:
RobertsTDAOperator(int dim, Vector p) :
TimeDependentAdjointOperator(dim, 3),
p_(p),
adjointMatrix(NULL)
{}
// Rate equation for forward problem
void Mult(const Vector &x, Vector &y) const override;
// Quadrature integration for G
void QuadratureIntegration(const Vector &x, Vector &y) const override;
// Adjoint rate equation corresponding to d(lambda)/dt
void AdjointRateMult(const Vector &y, Vector &yB,
Vector &yBdot) const override;
// Quadrature sensitivity equations corresponding to dG/dp
void QuadratureSensitivityMult(const Vector &y, const Vector &yB,
Vector &qbdot) const override;
// Setup custom MFEM solvers using GMRES since the Jacobian matrix is not
// symmetric
int SUNImplicitSetupB(const real_t t, const Vector &y,
const Vector &yB, const Vector &fyB, int jokB,
int *jcurB, real_t gammaB) override;
// Setup custom MFEM solve
int SUNImplicitSolveB(Vector &x, const Vector &b, real_t tol) override;
~RobertsTDAOperator()
{
delete adjointMatrix;
}
protected:
Vector p_;
// Solvers
GMRESSolver adjointSolver;
SparseMatrix* adjointMatrix;
};
int main(int argc, char *argv[])
{
// Parse command-line options.
real_t t_final = 4e7;
real_t dt = 0.01;
// Relative tolerance for CVODES.
const real_t reltol = 1e-4;
int precision = 8;
cout.precision(precision);
OptionsParser args(argc, argv);
args.AddOption(&dt, "-dt", "--time-step", "Time step.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(cout);
return 1;
}
args.PrintOptions(cout);
// The original cvsRoberts_ASAi_dns problem is a fixed sized problem of size
// 3. Define the solution vector.
Vector u(3);
u = 0.;
u[0] = 1.;
// Define the TimeDependentAdjointOperator which implements the various rate
// equations: rate, quadrature rate, adjoint rate, and quadrature sensitivity
// rate equation
// Define material parameters p
Vector p(3);
p[0] = 0.04;
p[1] = 1.0e4;
p[2] = 3.0e7;
// 3 is the size of the adjoint solution vector
RobertsTDAOperator adv(3, p);
// Set the initial time
real_t t = 0.0;
adv.SetTime(t);
// Create the CVODES solver and set the various tolerances. Set absolute
// tolerances for the solution
Vector abstol_v(3);
abstol_v[0] = 1.0e-8;
abstol_v[1] = 1.0e-14;
abstol_v[2] = 1.0e-6;
// Initialize the quadrature result
Vector q(1);
q = 0.;
// Create the solver
CVODESSolver *cvodes = new CVODESSolver(CV_BDF);
// Initialize the forward problem (this must be done first)
cvodes->Init(adv);
// Set error control function
cvodes->SetWFTolerances([reltol, abstol_v]
(Vector y, Vector w, CVODESolver * self)
{
for (int i = 0; i < y.Size(); i++)
{
real_t ww = reltol * abs(y[i]) + abstol_v[i];
if (ww <= 0.) { return -1; }
w[i] = 1./ww;
}
return 0;
}
);
// Set weighted tolerances
cvodes->SetSVtolerances(reltol, abstol_v);
// Use the builtin sundials solver for the forward solver
cvodes->UseSundialsLinearSolver();
// Initialize the quadrature integration and set the tolerances
cvodes->InitQuadIntegration(q, 1.e-6, 1.e-6);
// Initialize the adjoint solve
cvodes->InitAdjointSolve(150, CV_HERMITE);
// Perform time-integration (looping over the time iterations, ti, with a
// time-step dt).
bool done = false;
while (!done)
{
real_t dt_real = max(dt, t_final - t);
cvodes->Step(u, t, dt_real);
done = (t >= t_final - 1e-8*dt);
if (done)
{
cvodes->PrintInfo();
}
}
cout << "Final Solution: " << t << endl;
u.Print();
q = 0.;
cout << " Final Quadrature " << endl;
cvodes->EvalQuadIntegration(t, q);
q.Print();
// Solve the adjoint problem at different points in time. Create the adjoint
// solution vector
Vector w(3);
w=0.;
real_t TBout1 = 40.;
Vector dG_dp(3);
dG_dp=0.;
if (cvodes)
{
t = t_final;
adv.SetTime(t);
cvodes->InitB(adv);
cvodes->InitQuadIntegrationB(dG_dp, 1.e-6, 1.e-6);
cvodes->SetMaxNStepsB(5000);
// Results at time TBout1
real_t dt_real = max(dt, t - TBout1);
cvodes->StepB(w, t, dt_real);
cout << "t: " << t << endl;
cout << "w:" << endl;
w.Print();
cvodes->GetForwardSolution(t, u);
cout << "u:" << endl;
u.Print();
// Results at T0
dt_real = max(dt, t - 0.);
cvodes->StepB(w, t, dt_real);
cout << "t: " << t << endl;
cout << "w:" << endl;
cvodes->GetForwardSolution(t, u);
w.Print();
cout << "u:" << endl;
u.Print();
// Evaluate Sensitivity
cvodes->EvalQuadIntegrationB(t, dG_dp);
cout << "dG/dp:" << endl;
dG_dp.Print();
}
// Free the used memory.
delete cvodes;
return 0;
}
// cvsRoberts_ASAi_dns rate equation
void RobertsTDAOperator::Mult(const Vector &x, Vector &y) const
{
y[0] = -p_[0]*x[0] + p_[1]*x[1]*x[2];
y[2] = p_[2]*x[1]*x[1];
y[1] = -y[0] - y[2];
}
// cvsRoberts_ASAi_dns quadrature rate equation
void RobertsTDAOperator::QuadratureIntegration(const Vector &y,
Vector &qdot) const
{
qdot[0] = y[2];
}
// cvsRoberts_ASAi_dns adjoint rate equation
void RobertsTDAOperator::AdjointRateMult(const Vector &y, Vector & yB,
Vector &yBdot) const
{
real_t l21 = (yB[1]-yB[0]);
real_t l32 = (yB[2]-yB[1]);
real_t p1 = p_[0];
real_t p2 = p_[1];
real_t p3 = p_[2];
yBdot[0] = -p1 * l21;
yBdot[1] = p2 * y[2] * l21 - 2. * p3 * y[1] * l32;
yBdot[2] = p2 * y[1] * l21 - 1.0;
}
// cvsRoberts_ASAi_dns quadrature sensitivity rate equation
void RobertsTDAOperator::QuadratureSensitivityMult(const Vector &y,
const Vector &yB,
Vector &qBdot) const
{
real_t l21 = (yB[1]-yB[0]);
real_t l32 = (yB[2]-yB[1]);
real_t y23 = y[1] * y[2];
qBdot[0] = y[0] * l21;
qBdot[1] = -y23 * l21;
qBdot[2] = y[1]*y[1]*l32;
}
// cvsRoberts_ASAi_dns implicit solve setup for adjoint
int RobertsTDAOperator::SUNImplicitSetupB(const real_t t, const Vector &y,
const Vector &yB,
const Vector &fyB, int jokB,
int *jcurB, real_t gammaB)
{
// M = I- gamma J
// J = dfB/dyB
delete adjointMatrix;
adjointMatrix = new SparseMatrix(y.Size(), yB.Size());
for (int j = 0; j < y.Size(); j++)
{
Vector JacBj(yB.Size());
Vector yBone(yB.Size());
yBone = 0.;
yBone[j] = 1.;
AdjointRateMult(y, yBone, JacBj);
JacBj[2] += 1.;
for (int i = 0; i < y.Size(); i++)
{
adjointMatrix->Set(i,j, (i == j ? 1.0 : 0.) - gammaB * JacBj[i]);
}
}
*jcurB = 1;
adjointMatrix->Finalize();
adjointSolver.SetOperator(*adjointMatrix);
return 0;
}
// cvsRoberts_ASAi_dns implicit solve for adjoint
int RobertsTDAOperator::SUNImplicitSolveB(Vector &x, const Vector &b,
real_t tol)
{
// The argument "tol" is ignored in this implementation for simplicity.
adjointSolver.SetRelTol(1e-14);
adjointSolver.Mult(b, x);
return (0);
}