522 lines
15 KiB
C++
522 lines
15 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.
|
|
//
|
|
// ----------------------------------------------------------
|
|
// Advection-Diffusion Miniapp: Parallel MFEM CVODES Example
|
|
// ----------------------------------------------------------
|
|
//
|
|
// Compile with: make adjoint_advection_diffusion
|
|
//
|
|
// Sample runs: adjoint_advection_diffusion -dt 0.01 -tf 2.5
|
|
// adjoint_advection_diffusion -dt 0.005
|
|
//
|
|
// Description: This example is a port of cvodes/parallel/cvsAdvDiff_ASAp_non_p
|
|
// example that is part of SUNDIALS. The goal is to demonstrate
|
|
// how to use the adjoint SUNDIALS CVODES interface with MFEM.
|
|
// Below is an excerpt description from the aforementioned file.
|
|
//
|
|
// Example problem:
|
|
//
|
|
// The following is a simple example problem, with the program for its solution
|
|
// by CVODE. The problem is the semi-discrete form of the advection-diffusion
|
|
// equation in 1-D:
|
|
//
|
|
// du/dt = p1 * d^2u / dx^2 + p2 * du / dx
|
|
//
|
|
// on the interval 0 <= x <= 2, and the time interval 0 <= t <= 5. Homogeneous
|
|
// Dirichlet boundary conditions are posed, and the initial condition is:
|
|
//
|
|
// u(x,t=0) = x(2-x)exp(2x).
|
|
//
|
|
// The nominal values of the two parameters are: p1=1.0, p2=0.5. The PDE is
|
|
// discretized on a uniform grid of size MX+2 with central differencing, and
|
|
// with boundary values eliminated, leaving an ODE system of size NEQ = MX.
|
|
//
|
|
// The program solves the problem with the option for nonstiff systems: ADAMS
|
|
// method and functional iteration. It uses scalar relative and absolute
|
|
// tolerances. In addition to the solution, sensitivities with respect to p1 and
|
|
// p2 as well as with respect to initial conditions are computed for the
|
|
// quantity:
|
|
//
|
|
// g(t, u, p) = int_x u(x,t) at t = 5
|
|
//
|
|
// These sensitivities are obtained by solving the adjoint system:
|
|
//
|
|
// dv/dt = -p1 * d^2 v / dx^2 + p2 * dv / dx
|
|
//
|
|
// with homogeneous Dirichlet boundary conditions and the final condition:
|
|
//
|
|
// v(x,t=5) = 1.0
|
|
//
|
|
// Then, v(x, t=0) represents the sensitivity of g(5) with respect to u(x, t=0)
|
|
// and the gradient of g(5) with respect to p1, p2 is
|
|
//
|
|
// (dg/dp)^T = [ int_t int_x (v * d^2u / dx^2) dx dt ]
|
|
// [ int_t int_x (v * du / dx) dx dt ]
|
|
//
|
|
// This version uses MPI for user routines.
|
|
// Execute with number of processors = N, with 1 <= N <= MX.
|
|
|
|
#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;
|
|
|
|
// Implement the adjoint rate equations in AdvDiffSUNDIALS
|
|
class AdvDiffSUNDIALS : public TimeDependentAdjointOperator
|
|
{
|
|
public:
|
|
AdvDiffSUNDIALS(int ydot_dim, int ybdot_dim, Vector p,
|
|
ParFiniteElementSpace *fes, Array<int> & ess_tdof) :
|
|
TimeDependentAdjointOperator(ydot_dim, ybdot_dim),
|
|
p_(p),
|
|
ess_tdof_list(ess_tdof),
|
|
pfes(fes),
|
|
Mf(NULL),
|
|
M_solver(fes->GetComm())
|
|
{
|
|
int skip_zeros = 0;
|
|
|
|
cout << "Essential tdofs: " << endl;
|
|
ess_tdof_list.Print();
|
|
|
|
m = new ParBilinearForm(pfes);
|
|
m->AddDomainIntegrator(new MassIntegrator());
|
|
m->Assemble(skip_zeros);
|
|
m->Finalize(skip_zeros);
|
|
|
|
// Define coefficients
|
|
mp0 = new ConstantCoefficient(-p_[0]);
|
|
p0 = new ConstantCoefficient(p_[0]);
|
|
Vector p2vec(fes->GetParMesh()->SpaceDimension());
|
|
p2vec = p_[1];
|
|
p2 = new VectorConstantCoefficient(p2vec);
|
|
|
|
k = new ParBilinearForm(pfes);
|
|
k->AddDomainIntegrator(new DiffusionIntegrator(*mp0));
|
|
k->AddDomainIntegrator(new ConvectionIntegrator(*p2));
|
|
k->Assemble(skip_zeros);
|
|
k->Finalize(skip_zeros);
|
|
|
|
k1 = new ParBilinearForm(pfes);
|
|
k1->AddDomainIntegrator(new DiffusionIntegrator(*p0));
|
|
k1->AddDomainIntegrator(new ConvectionIntegrator(*p2));
|
|
k1->Assemble(skip_zeros);
|
|
k1->Finalize(skip_zeros);
|
|
|
|
M = m->ParallelAssemble();
|
|
HypreParMatrix *temp = M->EliminateRowsCols(ess_tdof_list);
|
|
delete temp;
|
|
|
|
K = k->ParallelAssemble();
|
|
temp = K->EliminateRowsCols(ess_tdof_list);
|
|
delete temp;
|
|
|
|
K_adj = k1->ParallelAssemble();
|
|
temp = K_adj->EliminateRowsCols(ess_tdof_list);
|
|
delete temp;
|
|
|
|
M_prec.SetType(HypreSmoother::Jacobi);
|
|
M_solver.SetPreconditioner(M_prec);
|
|
M_solver.SetOperator(*M);
|
|
|
|
M_solver.SetRelTol(1e-14);
|
|
M_solver.SetAbsTol(0.0);
|
|
M_solver.SetMaxIter(1000);
|
|
M_solver.SetPrintLevel(0);
|
|
|
|
}
|
|
|
|
void Mult(const Vector &x, Vector &y) const override;
|
|
|
|
void AdjointRateMult(const Vector &y, Vector &yB,
|
|
Vector &yBdot) const override;
|
|
|
|
int SUNImplicitSetup(const Vector &y,
|
|
const Vector &fy, int jok, int *jcur,
|
|
real_t gamma) override;
|
|
|
|
int SUNImplicitSolve(const Vector &b, Vector &x, real_t tol) override;
|
|
|
|
void QuadratureSensitivityMult(const Vector &y, const Vector &yB,
|
|
Vector &qbdot) const override;
|
|
|
|
~AdvDiffSUNDIALS()
|
|
{
|
|
delete m;
|
|
delete k;
|
|
delete k1;
|
|
delete M;
|
|
delete K;
|
|
delete K_adj;
|
|
delete Mf;
|
|
delete p0;
|
|
delete mp0;
|
|
delete p2;
|
|
}
|
|
|
|
protected:
|
|
Vector p_;
|
|
Array<int> ess_tdof_list;
|
|
ParFiniteElementSpace *pfes;
|
|
|
|
// Internal matrices
|
|
ParBilinearForm *m;
|
|
ParBilinearForm *k;
|
|
ParBilinearForm *k1;
|
|
|
|
HypreParMatrix *M;
|
|
HypreParMatrix *K;
|
|
HypreParMatrix *K_adj;
|
|
|
|
HypreParMatrix *Mf;
|
|
|
|
CGSolver M_solver;
|
|
HypreSmoother M_prec;
|
|
ConstantCoefficient *p0;
|
|
ConstantCoefficient *mp0;
|
|
VectorConstantCoefficient *p2;
|
|
};
|
|
|
|
// Initial conditions for the problem
|
|
real_t u_init(const Vector &x)
|
|
{
|
|
return x[0]*(2. - x[0])*exp(2.*x[0]);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
// Initialize MPI and HYPRE.
|
|
Mpi::Init(argc, argv);
|
|
int myid = Mpi::WorldRank();
|
|
Hypre::Init();
|
|
|
|
// Parse command-line options.
|
|
int ser_ref_levels = 0;
|
|
int par_ref_levels = 0;
|
|
real_t t_final = 2.5;
|
|
real_t dt = 0.01;
|
|
int mx = 20;
|
|
bool step_mode = true;
|
|
|
|
int precision = 8;
|
|
cout.precision(precision);
|
|
|
|
OptionsParser args(argc, argv);
|
|
|
|
args.AddOption(&mx, "-m", "--mx", "The number of mesh elements in the x-dir");
|
|
args.AddOption(&ser_ref_levels, "-r", "--refine",
|
|
"Number of times to refine the mesh uniformly.");
|
|
args.AddOption(&step_mode, "-a", "--adams", "-no-a","--no-adams",
|
|
"A switch to toggle between CV_ADAMS, and CV_BDF stepping modes");
|
|
args.AddOption(&t_final, "-tf", "--t-final",
|
|
"Final time; start time is 0.");
|
|
args.AddOption(&dt, "-dt", "--time-step",
|
|
"Time step.");
|
|
|
|
args.Parse();
|
|
if (!args.Good())
|
|
{
|
|
if (myid == 0)
|
|
{
|
|
args.PrintUsage(cout);
|
|
}
|
|
return 1;
|
|
}
|
|
if (myid == 0)
|
|
{
|
|
args.PrintOptions(cout);
|
|
}
|
|
|
|
// Create a small 1D mesh with a length of 2. This mesh corresponds with the
|
|
// cvsAdvDiff_ASA_p_non_p example.
|
|
Mesh mesh = Mesh::MakeCartesian1D(mx+1, 2.);
|
|
|
|
// Refine the mesh to increase the resolution. In this example we do
|
|
// 'ref_levels' of uniform refinement, where 'ref_levels' is a
|
|
// command-line parameter. If the mesh is of NURBS type, we convert it to
|
|
// a (piecewise-polynomial) high-order mesh.
|
|
for (int lev = 0; lev < ser_ref_levels; lev++)
|
|
{
|
|
mesh.UniformRefinement();
|
|
}
|
|
|
|
ParMesh *pmesh = new ParMesh(MPI_COMM_WORLD, mesh);
|
|
mesh.Clear();
|
|
for (int lev = 0; lev < par_ref_levels; lev++)
|
|
{
|
|
pmesh->UniformRefinement();
|
|
}
|
|
|
|
// Finite Element Spaces
|
|
H1_FECollection fec(1, pmesh->SpaceDimension());
|
|
ParFiniteElementSpace *fes = new ParFiniteElementSpace(pmesh, &fec);
|
|
|
|
HYPRE_BigInt global_vSize = fes->GlobalTrueVSize();
|
|
if (myid == 0)
|
|
{
|
|
cout << "Number of unknowns: " << global_vSize << endl;
|
|
}
|
|
|
|
// Set up material properties, and primal and adjoint variables
|
|
// p are the fixed material properties
|
|
Vector p(2);
|
|
p[0] = 1.0;
|
|
p[1] = 0.5;
|
|
|
|
// U is the size of the solution/primal vector
|
|
// Set U with the initial conditions
|
|
ParGridFunction u(fes);
|
|
FunctionCoefficient u0(u_init);
|
|
u.ProjectCoefficient(u0);
|
|
|
|
cout << "Init u: " << endl;
|
|
u.Print();
|
|
|
|
// TimeDependentOperators need to be TrueDOF Size
|
|
HypreParVector *U = u.GetTrueDofs();
|
|
|
|
// Get boundary conditions
|
|
Array<int> ess_tdof_list;
|
|
Array<int> essential_attr(pmesh->bdr_attributes.Size());
|
|
essential_attr[0] = 1;
|
|
essential_attr[1] = 1;
|
|
fes->GetEssentialTrueDofs(essential_attr, ess_tdof_list);
|
|
|
|
// Setup the TimeDependentAdjointOperator and the CVODESSolver
|
|
AdvDiffSUNDIALS adv(U->Size(), U->Size(), p, fes, ess_tdof_list);
|
|
|
|
// Set the initial time to the TimeDependentAdjointOperator
|
|
real_t t = 0.0;
|
|
adv.SetTime(t);
|
|
|
|
// Create the CVODES solver corresponding to the selected step method
|
|
CVODESSolver *cvodes = new CVODESSolver(fes->GetComm(),
|
|
step_mode ? CV_ADAMS : CV_BDF);
|
|
cvodes->Init(adv);
|
|
cvodes->UseSundialsLinearSolver();
|
|
cvodes->SetMaxNSteps(5000);
|
|
|
|
// Relative and absolute tolerances for CVODES
|
|
real_t reltol = 1e-8, abstol = 1e-6;
|
|
cvodes->SetSStolerances(reltol, abstol);
|
|
|
|
// Initialize adjoint problem settings
|
|
int checkpoint_steps = 50; // steps between checkpoints
|
|
cvodes->InitAdjointSolve(checkpoint_steps, CV_HERMITE);
|
|
|
|
// Perform time-integration for the problem (looping over the time
|
|
// iterations, ti, with a time-step dt).
|
|
bool done = false;
|
|
for ( ; !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 && myid == 0)
|
|
{
|
|
cvodes->PrintInfo();
|
|
}
|
|
}
|
|
|
|
u = *U;
|
|
if (myid == 0)
|
|
{
|
|
cout << "Final Solution: " << t << endl;
|
|
}
|
|
|
|
cout << "u (" << myid << "):" << endl;
|
|
u.Print();
|
|
cout << flush;
|
|
MPI_Barrier(MPI_COMM_WORLD);
|
|
|
|
// Calculate the quadrature int_x u dx at t = 5
|
|
// Since it's only a spatial quadrature we evaluate it at t=5
|
|
ParLinearForm obj(fes);
|
|
ConstantCoefficient one(1.0);
|
|
obj.AddDomainIntegrator(new DomainLFIntegrator(one));
|
|
obj.Assemble();
|
|
|
|
real_t g = obj(u);
|
|
if (myid == 0)
|
|
{
|
|
cout << "g: " << g << endl;
|
|
}
|
|
|
|
// Solve the adjoint problem. v is the adjoint solution
|
|
ParGridFunction v(fes);
|
|
v = 1.;
|
|
v.SetSubVector(ess_tdof_list, 0.0);
|
|
HypreParVector *V = v.GetTrueDofs();
|
|
|
|
// Initialize quadrature sensitivity values to zero
|
|
Vector qBdot(p.Size());
|
|
qBdot = 0.;
|
|
|
|
t = t_final;
|
|
cvodes->InitB(adv);
|
|
cvodes->InitQuadIntegrationB(qBdot, 1.e-6, 1.e-6);
|
|
cvodes->UseSundialsLinearSolverB();
|
|
cvodes->SetSStolerancesB(reltol, abstol);
|
|
|
|
// Results at time TBout1
|
|
real_t dt_real = max(dt, t);
|
|
cvodes->StepB(*V, t, dt_real);
|
|
if (myid == 0)
|
|
{
|
|
cout << "t: " << t << endl;
|
|
}
|
|
|
|
cout << "v (" << myid << "):" << endl;
|
|
V->Vector::Print();
|
|
cout << flush;
|
|
MPI_Barrier(MPI_COMM_WORLD);
|
|
|
|
// Evaluate the sensitivity
|
|
cvodes->EvalQuadIntegrationB(t, qBdot);
|
|
|
|
MPI_Barrier(MPI_COMM_WORLD);
|
|
if (myid == 0)
|
|
{
|
|
cout << "sensitivity:" << endl;
|
|
qBdot.Print();
|
|
}
|
|
|
|
// Free the used memory.
|
|
delete fes;
|
|
delete pmesh;
|
|
delete U;
|
|
delete V;
|
|
delete cvodes;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// AdvDiff rate equation
|
|
void AdvDiffSUNDIALS::Mult(const Vector &x, Vector &y) const
|
|
{
|
|
Vector z(x.Size());
|
|
Vector x1(x);
|
|
|
|
// Set boundary conditions to zero
|
|
x1.SetSubVector(ess_tdof_list, 0.0);
|
|
|
|
K->Mult(x1, z);
|
|
|
|
y = 0.;
|
|
M_solver.Mult(z, y);
|
|
}
|
|
|
|
// AdvDiff Rate equation setup
|
|
int AdvDiffSUNDIALS::SUNImplicitSetup(const Vector &y,
|
|
const Vector &fy,
|
|
int jok, int *jcur, real_t gamma)
|
|
{
|
|
// Mf = M(I - gamma J) = M - gamma * M * J
|
|
// J = df/dy => K
|
|
*jcur = 1; // We've updated the jacobian
|
|
|
|
delete Mf;
|
|
Mf = Add(1., *M, -gamma, *K);
|
|
HypreParMatrix *temp = Mf->EliminateRowsCols(ess_tdof_list);
|
|
delete temp;
|
|
return 0;
|
|
}
|
|
|
|
// AdvDiff Rate equation solve
|
|
int AdvDiffSUNDIALS::SUNImplicitSolve(const Vector &b, Vector &x, real_t tol)
|
|
{
|
|
Vector z(b.Size());
|
|
M->Mult(b,z);
|
|
|
|
CGSolver solver(pfes->GetComm());
|
|
HypreSmoother prec;
|
|
prec.SetType(HypreSmoother::Jacobi);
|
|
solver.SetPreconditioner(prec);
|
|
solver.SetOperator(*Mf);
|
|
solver.SetRelTol(1E-14);
|
|
solver.SetMaxIter(1000);
|
|
|
|
solver.Mult(z, x);
|
|
|
|
return (0);
|
|
}
|
|
|
|
// AdvDiff adjoint rate equation
|
|
void AdvDiffSUNDIALS::AdjointRateMult(const Vector &y, Vector & yB,
|
|
Vector &yBdot) const
|
|
{
|
|
Vector z(yB.Size());
|
|
|
|
// Set boundary conditions to zero
|
|
yB.SetSubVector(ess_tdof_list, 0.0);
|
|
K_adj->Mult(yB, z);
|
|
M_solver.Mult(z, yBdot);
|
|
}
|
|
|
|
// AdvDiff quadrature sensitivity rate equation
|
|
void AdvDiffSUNDIALS::QuadratureSensitivityMult(const Vector &y,
|
|
const Vector &yB,
|
|
Vector &qBdot) const
|
|
{
|
|
// Now we have both the adjoint, yB, and y, at the same point in time
|
|
// We calculate
|
|
/*
|
|
* to u(x, t=0) and the gradient of g(5) with respect to p1, p2 is
|
|
* (dg/dp)^T = [ int_t int_x (v * d^2u / dx^2) dx dt ]
|
|
* [ int_t int_x (v * du / dx) dx dt ]
|
|
*/
|
|
|
|
ParBilinearForm dp1(pfes);
|
|
ConstantCoefficient mone(-1.);
|
|
dp1.AddDomainIntegrator(new DiffusionIntegrator(mone));
|
|
dp1.Assemble();
|
|
dp1.Finalize();
|
|
|
|
HypreParMatrix * dP1 = dp1.ParallelAssemble();
|
|
HypreParMatrix *temp = dP1->EliminateRowsCols(ess_tdof_list);
|
|
delete temp;
|
|
|
|
Vector b1(y.Size());
|
|
dP1->Mult(y, b1);
|
|
delete dP1;
|
|
|
|
ParBilinearForm dp2(pfes);
|
|
Vector p2vec(pfes->GetParMesh()->SpaceDimension()); p2vec = 1.;
|
|
VectorConstantCoefficient dp2_coef(p2vec);
|
|
dp2.AddDomainIntegrator(new ConvectionIntegrator(dp2_coef));
|
|
dp2.Assemble();
|
|
dp2.Finalize();
|
|
|
|
HypreParMatrix * dP2 = dp2.ParallelAssemble();
|
|
temp = dP2->EliminateRowsCols(ess_tdof_list);
|
|
delete temp;
|
|
|
|
Vector b2(y.Size());
|
|
dP2->Mult(y, b2);
|
|
delete dP2;
|
|
|
|
real_t dp1_result = InnerProduct(pfes->GetComm(), yB, b1);
|
|
real_t dp2_result = InnerProduct(pfes->GetComm(), yB, b2);
|
|
|
|
qBdot[0] = -dp1_result;
|
|
qBdot[1] = -dp2_result;
|
|
}
|