Files
mfem/examples/dfem/laghos.cpp
T
2025-05-01 10:36:43 -07:00

1946 lines
59 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.
#include <mfem.hpp>
// TODO: Do we want this to be included from mfem.hpp automatically now?
#include <fem/dfem/doperator.hpp>
#include <linalg/tensor.hpp>
#include <limits>
#include <memory>
using namespace mfem;
using mfem::future::tuple;
using mfem::future::tensor;
using future::DifferentiableOperator;
using future::DerivativeOperator;
using future::ParametricFunction;
using future::ParametricSpace;
using future::FieldDescriptor;
using future::Gradient;
using future::Value;
using future::Weight;
using future::None;
constexpr int VELOCITY = 0;
constexpr int DENSITY0 = 1;
constexpr int COORDINATES0 = 2;
constexpr int COORDINATES = 3;
constexpr int MATERIAL = 4;
constexpr int SPECIFIC_INTERNAL_ENERGY = 5;
constexpr int ELEMENT_SIZE0 = 6;
constexpr int ORDER_VEL = 7;
constexpr int DT_EST = 8;
constexpr int STRESS_TENSOR = 9;
constexpr int DIMENSION = 2;
int problem = 0;
void threshold(Vector &v)
{
for (int i = 0; i < v.Size(); i++)
{
if (abs(v(i)) <= 1e-12)
{
v(i) = 0.0;
}
}
}
MFEM_HOST_DEVICE inline
real_t taylor_source(const Vector &x)
{
return 3.0 / 8.0 * M_PI * ( cos(3.0*M_PI*x(0)) * cos(M_PI*x(1)) -
cos(M_PI*x(0)) * cos(3.0*M_PI*x(1)) );
};
// Smooth transition between 0 and 1 for x in [-eps, eps].
MFEM_HOST_DEVICE inline
real_t smooth_step_01(real_t x, real_t eps)
{
const real_t y = (x + eps) / (2.0 * eps);
if (y < 0.0) { return 0.0; }
if (y > 1.0) { return 1.0; }
return (3.0 - 2.0 * y) * y * y;
}
MFEM_HOST_DEVICE inline
void ComputeMaterialProperties(const real_t &gamma, const real_t &rho,
const real_t &E, real_t &p, real_t &cs)
{
p = (gamma - 1.0) * rho * E;
cs = sqrt(gamma * (gamma - 1.0) * E);
}
using vecd = tensor<real_t, DIMENSION>;
using matd = tensor<real_t, DIMENSION, DIMENSION>;
template <bool compute_dtest = false>
MFEM_HOST_DEVICE inline
tuple<matd, real_t> qdata_setup(
const matd &dvdxi,
const real_t &rho0,
const matd &J0,
const matd &J,
const real_t &gamma,
const real_t &E,
const real_t &h0,
const real_t &order_v,
const real_t &w,
const real_t &cfl,
const bool &use_viscosity)
{
constexpr real_t eps = 1e-12;
constexpr real_t vorticity_coeff = 1.0;
real_t p, cs;
real_t detJ = det(J);
matd invJ = inv(J);
matd stress{{{0.0}}};
const real_t rho = rho0 * det(J0) / detJ;
const real_t Ez = fmax(0.0, E);
real_t visc_coeff = 0.0;
real_t dt_est = std::numeric_limits<real_t>::infinity();
ComputeMaterialProperties(gamma, rho, Ez, p, cs);
for (int d = 0; d < DIMENSION; d++)
{
stress(d, d) = -p;
}
if (use_viscosity)
{
auto symdvdx = sym(dvdxi * invJ);
auto [eigvals, eigvecs] = eig(symdvdx);
vecd compr_dir = get_col(eigvecs, 0);
auto ph_dir = (J * inv(J0)) * compr_dir;
const real_t h = h0 * norm(ph_dir) / norm(compr_dir);
// Measure of maximal compression.
const real_t mu = eigvals(0);
visc_coeff = 2.0 * rho * h * h * fabs(mu);
visc_coeff += 0.5 * rho * h * cs * vorticity_coeff *
(1.0 - smooth_step_01(mu - 2.0 * eps, eps));
stress += visc_coeff * symdvdx;
}
if constexpr (compute_dtest)
{
if (detJ < 0.0)
{
// This will force repetition of the step with smaller dt.
dt_est = 0.0;
}
else
{
const real_t h_min = calcsv(J, DIMENSION-1) / static_cast<real_t>(order_v);
const real_t idt = cs / h_min + 2.5 * visc_coeff / rho / h_min / h_min;
if (idt > 0.0)
{
dt_est = cfl / idt;
}
else
{
dt_est = std::numeric_limits<real_t>::infinity();
}
}
}
matd stressJiT = stress * transpose(invJ) * detJ * w;
return tuple{stressJiT, dt_est};
}
struct TimeStepEstimateQFunction
{
TimeStepEstimateQFunction(const real_t *external_data) :
external_data(external_data) {}
MFEM_HOST_DEVICE inline
auto operator()(
const matd &dvdxi,
const real_t &rho0,
const matd &J0,
const matd &J,
const real_t &gamma,
const real_t &E,
const real_t &h0,
const real_t &order_v,
const real_t &w) const
{
real_t dt_est = get<1>(
qdata_setup<true>(dvdxi, rho0, J0, J, gamma, E, h0, order_v, w,
external_data[0], static_cast<bool>(external_data[2])));
return tuple{dt_est};
}
const real_t *external_data;
};
struct UpdateQuadratureDataQFunction
{
UpdateQuadratureDataQFunction(const real_t *external_data) :
external_data(external_data) {}
MFEM_HOST_DEVICE inline
auto operator()(
const matd &dvdxi,
const real_t &rho0,
const matd &J0,
const matd &J,
const real_t &gamma,
const real_t &E,
const real_t &h0,
const real_t &order_v,
const real_t &w) const
{
matd stressJiT = get<0>(
qdata_setup<false>(dvdxi, rho0, J0, J, gamma, E, h0, order_v, w,
external_data[0], static_cast<bool>(external_data[2])));
return tuple{stressJiT};
}
const real_t *external_data;
};
class MomentumQFunction
{
public:
MomentumQFunction(const real_t *external_data) :
external_data(external_data) {}
MFEM_HOST_DEVICE inline
auto operator()(
const matd &dvdxi,
const real_t &rho0,
const matd &J0,
const matd &J,
const real_t &gamma,
const real_t &E,
const real_t &h0,
const real_t &order_v,
const real_t &w) const
{
auto stressJiT = get<0>(
qdata_setup(dvdxi, rho0, J0, J, gamma, E, h0, order_v, w, external_data[0],
static_cast<bool>(external_data[2])));
// out << gamma << " " << rho << " " << Ez << " " << p << " " << cs << "\n";
// out << stressJiT << "\n";
// TODO-bug: investigate transpose of matrices in return types
// return tuple{transpose(stressJiT)};
return tuple{stressJiT};
}
const real_t *external_data;
};
class MomentumPAQFunction
{
public:
MomentumPAQFunction() = default;
MFEM_HOST_DEVICE inline
auto operator()(
const matd &stressJiT) const
{
return tuple{stressJiT};
}
};
class EnergyConservationQFunction
{
public:
EnergyConservationQFunction(const real_t *external_data) :
external_data(external_data) {}
MFEM_HOST_DEVICE inline
auto operator()(
const matd &dvdxi,
const real_t &rho0,
const matd &J0,
const matd &J,
const real_t &gamma,
const real_t &E,
const real_t &h0,
const real_t &order_v,
const real_t &w) const
{
auto stressJiT = get<0>(
qdata_setup(dvdxi, rho0, J0, J, gamma, E, h0, order_v, w, external_data[0],
static_cast<bool>(external_data[2])));
return tuple{ddot(stressJiT, dvdxi)};
}
const real_t *external_data;
};
class EnergyConservationPAQFunction
{
public:
EnergyConservationPAQFunction() = default;
MFEM_HOST_DEVICE inline
auto operator()(
const matd &dvdxi,
const matd &stressJiT) const
{
return tuple{ddot(stressJiT, dvdxi)};
}
};
class TotalInternalEnergyQFunction
{
public:
TotalInternalEnergyQFunction() = default;
MFEM_HOST_DEVICE inline
auto operator() (
const real_t &E,
const real_t &rho0,
const matd &J0,
const real_t &w) const
{
return tuple{rho0 * E * det(J0) * w};
}
};
class TotalKineticEnergyQFunction
{
public:
TotalKineticEnergyQFunction() = default;
MFEM_HOST_DEVICE inline
auto operator() (
const vecd &v,
const real_t &rho0,
const matd &J0,
const real_t &w) const
{
return tuple{rho0 * 0.5 * v * v * det(J0) * w};
}
};
class DensityQFunction
{
public:
DensityQFunction() = default;
MFEM_HOST_DEVICE inline
auto operator() (
const real_t &rho0,
const matd &J0,
const real_t &w) const
{
return tuple{rho0 * det(J0) * w};
}
};
struct QuadratureData
{
static constexpr int aux_dim = 1;
QuadratureData(const ParMesh &mesh, const IntegrationRule &ir) :
StressSpace(mesh.Dimension(), mesh.Dimension()*mesh.Dimension(),
ir.GetNPoints(),
mesh.Dimension()*mesh.Dimension()*ir.GetNPoints()*mesh.GetNE()),
stressp(StressSpace),
R(mesh.Dimension(),
aux_dim,
ir.GetNPoints(),
aux_dim*ir.GetNPoints()*mesh.GetNE()),
h0(R),
order_v(R),
dt_est(R)
{
h0.UseDevice(true);
order_v.UseDevice(true);
dt_est.UseDevice(true);
stressp.UseDevice(true);
}
ParametricSpace StressSpace;
ParametricFunction stressp;
ParametricSpace R;
ParametricFunction h0, order_v, dt_est;
};
class MassPAOperator : public Operator
{
public:
MassPAOperator(ParFiniteElementSpace &pfes,
const IntegrationRule &ir,
Coefficient &Q) :
Operator(pfes.GetTrueVSize()),
comm(pfes.GetParMesh()->GetComm()),
dim(pfes.GetMesh()->Dimension()),
NE(pfes.GetMesh()->GetNE()),
vsize(pfes.GetVSize()),
pabf(&pfes),
ess_tdofs_count(0),
ess_tdofs(0)
{
if (dim > 1)
{
pabf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
}
pabf.AddDomainIntegrator(new mfem::MassIntegrator(Q, &ir));
pabf.Assemble();
pabf.FormSystemMatrix(mfem::Array<int>(), mass);
}
void SetEssentialTrueDofs(Array<int> &dofs)
{
ess_tdofs_count = dofs.Size();
if (ess_tdofs.Size() == 0)
{
int ess_tdofs_sz;
MPI_Allreduce(&ess_tdofs_count,&ess_tdofs_sz, 1, MPI_INT, MPI_SUM, comm);
MFEM_ASSERT(ess_tdofs_sz > 0, "ess_tdofs_sz should be positive!");
ess_tdofs.SetSize(ess_tdofs_sz);
}
if (ess_tdofs_count == 0) { return; }
ess_tdofs = dofs;
}
void EliminateRHS(Vector &b) const
{
if (ess_tdofs_count > 0) { b.SetSubVector(ess_tdofs, 0.0); }
}
void Mult(const Vector &x, Vector &y) const override
{
mass->Mult(x, y);
if (ess_tdofs_count > 0) { y.SetSubVector(ess_tdofs, 0.0); }
}
void FullAddMult(const Vector &x, Vector &y) const
{
mass->AddMult(x, y);
}
const ParBilinearForm &GetBF() const { return pabf; }
const MPI_Comm comm;
const int dim, NE, vsize;
ParBilinearForm pabf;
int ess_tdofs_count;
Array<int> ess_tdofs;
OperatorPtr mass;
};
class LagrangianHydroJacobianOperator : public Operator
{
public:
LagrangianHydroJacobianOperator(real_t h, int H1tsize, int L2tsize) :
Operator(2*H1tsize + L2tsize), h(h), H1tsize(H1tsize), L2tsize(L2tsize) {}
void Mult(const Vector &k, Vector &y) const override
{
jvp(k, y);
}
template <typename hydro_t>
void Setup(hydro_t &hydro,
std::shared_ptr<DerivativeOperator> dRvdx,
std::shared_ptr<DerivativeOperator> dRvdv,
std::shared_ptr<DerivativeOperator> dRvde,
std::shared_ptr<DerivativeOperator> dRedx,
std::shared_ptr<DerivativeOperator> dRedv,
std::shared_ptr<DerivativeOperator> dRede)
{
w.SetSize(this->height);
z.SetSize(this->height);
jvp = [dRvdx, dRvdv, dRvde, dRedx, dRedv, dRede, this, &hydro]
(const Vector &u, Vector &y)
{
w = u;
Vector wx, wv, we;
wx.MakeRef(w, 0, H1tsize);
wv.MakeRef(w, H1tsize, H1tsize);
we.MakeRef(w, 2*H1tsize, L2tsize);
Vector zx, zv, ze;
zx.MakeRef(z, 0, H1tsize);
zv.MakeRef(z, H1tsize, H1tsize);
ze.MakeRef(z, 2*H1tsize, L2tsize);
Vector yx, yv, ye;
yx.MakeRef(y, 0, H1tsize);
yv.MakeRef(y, H1tsize, H1tsize);
ye.MakeRef(y, 2*H1tsize, L2tsize);
// position
yx = wv;
yx *= -h;
yx += wx;
// velocity
// wv.SetSubVector(hydro.ess_tdof, 0.0);
dRvdx->Mult(wx, zv);
zv *= h;
yv = zv;
dRvdv->Mult(wv, zv);
zv *= h;
yv += zv;
// hydro.Mv.TrueAddMult(wv, yv);
Vector wvc, yvc;
for (int c = 0; c < hydro.H1.GetMesh()->Dimension(); c++)
{
wvc.MakeRef(wv, c*hydro.H1c.GetVSize(), hydro.H1c.GetVSize());
yvc.MakeRef(yv, c*hydro.H1c.GetVSize(), hydro.H1c.GetVSize());
hydro.Mv->FullAddMult(wvc, yvc);
yvc.SyncAliasMemory(yv);
}
yv.SyncAliasMemory(y);
dRvde->Mult(we, zv);
zv *= h;
yv += zv;
yv.SetSubVector(hydro.ess_tdof, 0.0);
// for (int i = 0; i < hydro.ess_tdof.Size(); i++)
// {
// // yv(hydro.ess_tdof[i]) = uv(hydro.ess_tdof[i]);
// yv(hydro.ess_tdof[i]) = 0.0;
// }
// yv = 0.0;
// energy
// [ wx ]
// [ dRe/dx dRe/dv dRe/de ] [ wv ]
// [ we ]
//
dRedx->Mult(wx, ze);
ze *= -h;
ye = ze;
dRedv->Mult(wv, ze);
ze *= -h;
ye += ze;
dRede->Mult(we, ze);
ze *= -h;
// hydro.Me.TrueAddMult(we, ze);
hydro.Me->FullAddMult(we, ze);
ye += ze;
yx.SyncAliasMemory(y);
yv.SyncAliasMemory(y);
ye.SyncAliasMemory(y);
};
}
virtual MemoryClass GetMemoryClass() const override
{
return Device::GetDeviceMemoryClass();
}
real_t h;
std::function<void(const Vector &, Vector &)> jvp;
const int H1tsize;
const int L2tsize;
Vector w, z;
};
template <typename hydro_t>
class LagrangianHydroResidualOperator : public Operator
{
public:
LagrangianHydroResidualOperator(hydro_t &hydro, const real_t dt,
const Vector &x, bool fd_gradient) :
Operator(2*hydro.H1.GetTrueVSize()+hydro.L2.GetTrueVSize()),
hydro(hydro),
dt(dt),
x(x),
u(x.Size()),
H1tsize(hydro.H1.GetTrueVSize()),
L2tsize(hydro.L2.GetTrueVSize()),
fd_gradient(fd_gradient) {}
void Mult(const Vector &k, Vector &R) const override
{
hydro.UpdateMesh(u);
u = k;
u *= dt;
u += x;
hydro.mesh_nodes.SyncMemory(u);
auto kptr = const_cast<Vector*>(&k);
Vector kx, kv, ke;
kx.MakeRef(*kptr, 0, H1tsize);
kv.MakeRef(*kptr, H1tsize, H1tsize);
ke.MakeRef(*kptr, 2*H1tsize, L2tsize);
Vector ux, uv, ue;
ux.MakeRef(u, 0, H1tsize);
uv.MakeRef(u, H1tsize, H1tsize);
ue.MakeRef(u, 2*H1tsize, L2tsize);
Vector Rx, Rv, Re;
Rx.MakeRef(R, 0, H1tsize);
Rv.MakeRef(R, H1tsize, H1tsize);
Re.MakeRef(R, 2*H1tsize, L2tsize);
Rx = kx;
Rx -= uv;
hydro.momentum_mf->SetParameters({&hydro.rho0, &hydro.x0, &ux, &hydro.material, &ue, &hydro.qdata->h0, &hydro.qdata->order_v});
hydro.momentum_mf->Mult(uv, Rv);
// hydro.Mv.TrueAddMult(kv, Rv);
Vector kvc, Rvc;
for (int c = 0; c < hydro.H1.GetMesh()->Dimension(); c++)
{
kvc.MakeRef(kv, c*hydro.H1c.GetVSize(), hydro.H1c.GetVSize());
Rvc.MakeRef(Rv, c*hydro.H1c.GetVSize(), hydro.H1c.GetVSize());
hydro.Mv->FullAddMult(kvc, Rvc);
Rvc.SyncAliasMemory(Rv);
}
Rv.SyncAliasMemory(R);
Rv.SetSubVector(hydro.ess_tdof, 0.0);
// Rv = 0.0;
hydro.energy_conservation_mf->SetParameters({&uv, &hydro.rho0, &hydro.x0, &ux, &hydro.material, &hydro.qdata->h0, &hydro.qdata->order_v});
hydro.energy_conservation_mf->Mult(ue, Re);
Re.Neg();
if (problem == 0)
{
LinearForm e_source(&hydro.L2);
hydro.L2.GetMesh()->DeleteGeometricFactors();
FunctionCoefficient coeff(taylor_source);
DomainLFIntegrator *d = new DomainLFIntegrator(coeff, &hydro.ir);
e_source.AddDomainIntegrator(d);
e_source.UseFastAssembly(true);
e_source.Assemble();
Re -= e_source;
}
// hydro.Me.TrueAddMult(ke, Re);
hydro.Me->FullAddMult(ke, Re);
Rx.SyncAliasMemory(R);
Rv.SyncAliasMemory(R);
Re.SyncAliasMemory(R);
}
Operator& GetGradient(const Vector &k) const override
{
jacobian.reset(new LagrangianHydroJacobianOperator(dt, H1tsize, L2tsize));
u = k;
u *= dt;
u += x;
auto kptr = const_cast<Vector*>(&k);
Vector kx, kv, ke;
kx.MakeRef(*kptr, 0, H1tsize);
kv.MakeRef(*kptr, H1tsize, H1tsize);
ke.MakeRef(*kptr, 2*H1tsize, L2tsize);
Vector ux, uv, ue;
ux.MakeRef(u, 0, H1tsize);
uv.MakeRef(u, H1tsize, H1tsize);
ue.MakeRef(u, 2*H1tsize, L2tsize);
if (fd_gradient)
{
fd_jacobian.reset(new future::FDJacobian(*this, k));
return *fd_jacobian;
}
else
{
auto dRvdx = hydro.momentum_mf->GetDerivative(COORDINATES, {&uv},
{&hydro.rho0, &hydro.x0, &ux, &hydro.material, &ue, &hydro.qdata->h0, &hydro.qdata->order_v});
auto dRvdv = hydro.momentum_mf->GetDerivative(VELOCITY, {&uv},
{&hydro.rho0, &hydro.x0, &ux, &hydro.material, &ue, &hydro.qdata->h0, &hydro.qdata->order_v});
auto dRvde = hydro.momentum_mf->GetDerivative(SPECIFIC_INTERNAL_ENERGY, {&uv},
{&hydro.rho0, &hydro.x0, &ux, &hydro.material, &ue, &hydro.qdata->h0, &hydro.qdata->order_v});
auto dRedx = hydro.energy_conservation_mf->GetDerivative(COORDINATES, {&ue},
{&uv, &hydro.rho0, &hydro.x0, &ux, &hydro.material, &hydro.qdata->h0, &hydro.qdata->order_v});
auto dRedv = hydro.energy_conservation_mf->GetDerivative(VELOCITY, {&ue},
{&uv, &hydro.rho0, &hydro.x0, &ux, &hydro.material, &hydro.qdata->h0, &hydro.qdata->order_v});
auto dRede = hydro.energy_conservation_mf->GetDerivative(
SPECIFIC_INTERNAL_ENERGY, {&ue},
{&uv, &hydro.rho0, &hydro.x0, &ux, &hydro.material, &hydro.qdata->h0, &hydro.qdata->order_v});
jacobian->Setup(hydro, dRvdx, dRvdv, dRvde, dRedx, dRedv, dRede);
return *jacobian;
}
}
hydro_t &hydro;
const real_t dt;
const Vector &x;
mutable Vector u;
const int H1tsize;
const int L2tsize;
mutable std::shared_ptr<future::FDJacobian> fd_jacobian;
mutable std::shared_ptr<LagrangianHydroJacobianOperator> jacobian;
bool fd_gradient;
};
class LagrangianHydroOperator : public TimeDependentOperator
{
public:
LagrangianHydroOperator(
ParFiniteElementSpace &H1,
ParFiniteElementSpace &L2,
const Array<int> &ess_tdof,
const IntegrationRule &ir,
FunctionCoefficient &rho0_coeff,
ParGridFunction &x0_gf,
ParGridFunction &rho0_gf,
ParGridFunction &material_gf,
std::shared_ptr<DifferentiableOperator> update_qdata,
std::shared_ptr<DifferentiableOperator> dtest_mf,
std::shared_ptr<DifferentiableOperator> momentum_mf,
std::shared_ptr<DifferentiableOperator> momentum_pa,
std::shared_ptr<DifferentiableOperator> energy_conservation_mf,
std::shared_ptr<DifferentiableOperator> energy_conservation_pa,
std::shared_ptr<DifferentiableOperator> total_internal_energy_mf,
std::shared_ptr<DifferentiableOperator> total_kinetic_energy_mf,
std::shared_ptr<DifferentiableOperator> density_mf,
std::shared_ptr<QuadratureData> qdata,
bool fd_gradient,
const int nonlinear_maximum_iterations,
const real_t nonlinear_relative_tolerance) :
TimeDependentOperator(2*H1.GetVSize()+L2.GetVSize()),
H1(H1),
L2(L2),
H1c(H1.GetParMesh(), H1.FEColl(), 1),
ess_tdof(ess_tdof),
ir(ir),
x0(x0_gf),
rho0(rho0_gf),
material(material_gf),
update_qdata(update_qdata),
dtest_mf(dtest_mf),
momentum_mf(momentum_mf),
momentum_pa(momentum_pa),
energy_conservation_mf(energy_conservation_mf),
energy_conservation_pa(energy_conservation_pa),
total_internal_energy_mf(total_internal_energy_mf),
total_kinetic_energy_mf(total_kinetic_energy_mf),
density_mf(density_mf),
qdata(qdata),
mesh_nodes(&H1),
rhsvc(&H1c),
dvc(&H1c),
rho0_coeff(rho0_coeff),
RHSv(H1.GetTrueVSize()),
rhsv(H1.GetVSize()),
X(2*H1.GetTrueVSize()+L2.GetTrueVSize()),
Xv(H1.GetTrueVSize()),
Xvc(H1c.GetTrueVSize()),
Xe(L2.GetTrueVSize()),
K(2*H1.GetTrueVSize()+L2.GetTrueVSize()),
B(H1c.GetTrueVSize()),
RHSe(L2.GetTrueVSize()),
rhse(L2.GetVSize()),
nl2dofs(L2.GetFE(0)->GetDof()),
fd_gradient(fd_gradient),
nonlinear_maximum_iterations(nonlinear_maximum_iterations),
nonlinear_relative_tolerance(nonlinear_relative_tolerance)
{
Mv = new MassPAOperator(H1c, ir, rho0_coeff);
Array<int> empty_tdofs;
Mv_Jprec = new OperatorJacobiSmoother(Mv->GetBF(), empty_tdofs);
Me = new MassPAOperator(L2, ir, rho0_coeff);
// Inside the above constructors for mass, there is reordering of the mesh
// nodes which is performed on the host. Since the mesh nodes are a
// subvector, so we need to sync with the rest of the base vector (which
// is assumed to be in the memory space used by the mfem::Device).
H1.GetParMesh()->GetNodes()->ReadWrite();
// Attributes 1/2/3 correspond to fixed-x/y/z boundaries, i.e.,
// we must enforce v_x/y/z = 0 for the velocity components.
const int bdr_attr_max = H1.GetMesh()->bdr_attributes.Max();
Array<int> ess_bdr(bdr_attr_max);
for (int c = 0; c < H1.GetMesh()->Dimension(); c++)
{
ess_bdr = 0;
ess_bdr[c] = 1;
H1c.GetEssentialTrueDofs(ess_bdr, c_tdofs[c]);
c_tdofs[c].Read();
}
}
void Mult(const Vector &S, Vector &dSdt) const override
{
UpdateMesh(S);
UpdateQuadratureData(S);
auto sptr = const_cast<Vector*>(&S);
const int H1vsize = H1.GetVSize();
ParGridFunction x, v, e;
x.MakeRef(&H1, *sptr, 0);
v.MakeRef(&H1, *sptr, H1vsize);
e.MakeRef(&L2, *sptr, 2*H1vsize);
ParGridFunction dx, dv, de;
dx.MakeRef(&H1, dSdt, 0);
dv.MakeRef(&H1, dSdt, H1vsize);
de.MakeRef(&L2, dSdt, 2*H1vsize);
// solve position
dx = v;
// solve velocity
{
dv = 0.0;
// momentum_mf->SetParameters({&rho0, &x0, &x, &material, &e, &qdata->h0, &qdata->order_v});
momentum_pa->SetParameters({&qdata->stressp});
H1.GetRestrictionMatrix()->Mult(v, Xv);
// momentum_mf->Mult(Xv, RHSv);
momentum_pa->Mult(Xv, RHSv);
RHSv.Neg();
H1.GetRestrictionMatrix()->MultTranspose(RHSv, rhsv);
// solve for each velocity component
const int size = H1c.GetVSize();
const Operator *Pconf = H1c.GetProlongationMatrix();
for (int c = 0; c < H1.GetMesh()->Dimension(); c++)
{
dvc.MakeRef(&H1c, dSdt, H1vsize + c*size);
rhsvc.MakeRef(&H1c, rhsv, c*size);
if (Pconf)
{
Pconf->MultTranspose(rhsvc, B);
}
else
{
B = rhsvc;
}
CGSolver cg(H1c.GetParMesh()->GetComm());
cg.SetPreconditioner(*Mv_Jprec);
cg.SetOperator(*Mv);
cg.SetRelTol(1e-8);
cg.SetAbsTol(0.0);
cg.SetMaxIter(300);
cg.SetPrintLevel(-1);
H1c.GetRestrictionMatrix()->Mult(dvc, Xvc);
Mv->SetEssentialTrueDofs(c_tdofs[c]);
Mv->EliminateRHS(B);
cg.Mult(B, Xvc);
if (Pconf)
{
Pconf->Mult(Xvc, dvc);
}
else
{
dvc = Xvc;
}
dvc.GetMemory().SyncAlias(dSdt.GetMemory(), dvc.Size());
}
}
// solve energy
{
de = 0.0;
// energy_conservation_mf->SetParameters({&v, &rho0, &x0, &x, &material, &qdata->h0, &qdata->order_v});
energy_conservation_pa->SetParameters({&v, &qdata->stressp});
L2.GetRestrictionMatrix()->Mult(e, Xe);
// energy_conservation_mf->Mult(Xe, RHSe);
energy_conservation_pa->Mult(Xe, RHSe);
L2.GetRestrictionMatrix()->MultTranspose(RHSe, rhse);
if (problem == 0)
{
LinearForm e_source(&L2);
L2.GetMesh()->DeleteGeometricFactors();
FunctionCoefficient coeff(taylor_source);
DomainLFIntegrator *d = new DomainLFIntegrator(coeff, &ir);
e_source.AddDomainIntegrator(d);
e_source.UseFastAssembly(true);
e_source.Assemble();
rhse += e_source;
}
CGSolver cg(L2.GetParMesh()->GetComm());
cg.SetOperator(*Me);
cg.iterative_mode = false;
cg.SetRelTol(1e-8);
cg.SetAbsTol(0.0);
cg.SetMaxIter(300);
cg.SetPrintLevel(-1);
cg.Mult(rhse, de);
de.GetMemory().SyncAlias(dSdt.GetMemory(), de.Size());
}
}
void ImplicitSolve(const real_t dt, const Vector &x, Vector &k) override
{
auto xptr = const_cast<Vector*>(&x);
Vector xx, xv, xe;
xx.MakeRef(*xptr, 0, H1.GetVSize());
xv.MakeRef(*xptr, H1.GetVSize(), H1.GetVSize());
xe.MakeRef(*xptr, 2*H1.GetVSize(), L2.GetVSize());
Vector Xx, Xv, Xe;
Xx.MakeRef(X, 0, H1.GetTrueVSize());
Xv.MakeRef(X, H1.GetTrueVSize(), H1.GetTrueVSize());
Xe.MakeRef(X, 2*H1.GetTrueVSize(), L2.GetTrueVSize());
H1.GetRestrictionMatrix()->Mult(xx, Xx);
H1.GetRestrictionMatrix()->Mult(xv, Xv);
L2.GetRestrictionMatrix()->Mult(xe, Xe);
Xx.SyncAliasMemory(X);
Xv.SyncAliasMemory(X);
Xe.SyncAliasMemory(X);
auto residual = LagrangianHydroResidualOperator(*this, dt, X, fd_gradient);
GMRESSolver gmres(MPI_COMM_WORLD);
gmres.SetMaxIter(500);
gmres.SetKDim(500);
gmres.SetRelTol(1e-8);
gmres.SetAbsTol(1e-12);
gmres.SetPrintLevel(IterativeSolver::PrintLevel().None());
NewtonSolver newton(MPI_COMM_WORLD);
newton.SetPrintLevel(IterativeSolver::PrintLevel().None());
newton.SetOperator(residual);
newton.SetSolver(gmres);
newton.SetAdaptiveLinRtol();
newton.SetMaxIter(nonlinear_maximum_iterations);
newton.SetRelTol(nonlinear_relative_tolerance);
newton.SetAbsTol(1e-12);
Vector zero;
K = X;
newton.Mult(zero, K);
Vector Kx, Kv, Ke;
Kx.MakeRef(K, 0, H1.GetTrueVSize());
Kv.MakeRef(K, H1.GetTrueVSize(), H1.GetTrueVSize());
Ke.MakeRef(K, 2*H1.GetTrueVSize(), L2.GetTrueVSize());
Vector kx, kv, ke;
kx.MakeRef(k, 0, H1.GetVSize());
kv.MakeRef(k, H1.GetVSize(), H1.GetVSize());
ke.MakeRef(k, 2*H1.GetVSize(), L2.GetVSize());
H1.GetProlongationMatrix()->Mult(Kx, kx);
H1.GetProlongationMatrix()->Mult(Kv, kv);
L2.GetProlongationMatrix()->Mult(Ke, ke);
// kx.SyncAliasMemory(k);
// kv.SyncAliasMemory(k);
// ke.SyncAliasMemory(k);
}
void UpdateMesh(const Vector &S) const
{
Vector* sptr = const_cast<Vector*>(&S);
mesh_nodes.MakeRef(&H1, *sptr, 0);
H1.GetParMesh()->NewNodes(mesh_nodes, false);
}
real_t GetTimeStepEstimate(const Vector &S)
{
UpdateMesh(S);
auto sptr = const_cast<Vector*>(&S);
const int H1vsize = H1.GetVSize();
ParGridFunction x, v, e;
x.MakeRef(&H1, *sptr, 0);
v.MakeRef(&H1, *sptr, H1vsize);
e.MakeRef(&L2, *sptr, 2*H1vsize);
dtest_mf->SetParameters({&v, &rho0, &x0, &x, &material, &e, &qdata->h0, &qdata->order_v});
auto &dt_est = qdata->dt_est;
dtest_mf->Mult(dt_est, dt_est);
real_t dt_est_local = std::numeric_limits<real_t>::infinity();
for (int i = 0; i < dt_est.Size(); i++)
{
if (dt_est(i) == 0.0)
{
return 0.0;
}
dt_est_local = fmin(dt_est_local, dt_est(i));
}
real_t dt_est_global;
MPI_Allreduce(&dt_est_local, &dt_est_global, 1, MPI_DOUBLE, MPI_MIN,
L2.GetComm());
return dt_est_global;
}
real_t InternalEnergy(ParGridFunction &e)
{
const auto mt = Device::GetDeviceMemoryType();
Vector E(L2.GetTrueVSize(), mt), Y(L2.GetTrueVSize(), mt);
total_internal_energy_mf->SetParameters({&rho0, &x0});
L2.GetRestrictionMatrix()->Mult(e, E);
total_internal_energy_mf->Mult(E, Y);
const real_t ie_local = Y.Sum();
real_t ie_global = 0.0;
MPI_Allreduce(&ie_local, &ie_global, 1, MPI_DOUBLE, MPI_SUM,
L2.GetParMesh()->GetComm());
return ie_global;
}
real_t KineticEnergy(ParGridFunction &v)
{
const auto mt = Device::GetDeviceMemoryType();
Vector V(H1.GetTrueVSize(), mt), Y(L2.GetTrueVSize(), mt);
total_kinetic_energy_mf->SetParameters({&rho0, &x0});
H1.GetRestrictionMatrix()->Mult(v, V);
total_kinetic_energy_mf->Mult(V, Y);
const real_t ke_local = Y.Sum();
real_t ke_global = 0.0;
MPI_Allreduce(&ke_local, &ke_global, 1, MPI_DOUBLE, MPI_SUM,
H1.GetParMesh()->GetComm());
return ke_global;
}
void ComputeDensity(ParGridFunction &rho)
{
rho.SetSpace(&L2);
ParGridFunction rhs_l(&L2);
Vector rho0_t(L2.GetTrueVSize()),
rho_t(L2.GetTrueVSize()),
rhs(L2.GetTrueVSize());
const int l2dofs_cnt = L2.GetFE(0)->GetDof();
DenseMatrix Mrho(l2dofs_cnt);
DenseMatrixInverse inv(&Mrho);
Vector rhs_e(l2dofs_cnt), rho_z(l2dofs_cnt);
Array<int> dofs(l2dofs_cnt);
MassIntegrator mi(&ir);
density_mf->SetParameters({&x0});
L2.GetProlongationMatrix()->MultTranspose(rho0, rho0_t);
density_mf->Mult(rho0_t, rhs);
L2.GetProlongationMatrix()->Mult(rhs, rhs_l);
for (int e = 0; e < L2.GetParMesh()->GetNE(); e++)
{
const FiniteElement &fe = *L2.GetFE(e);
ElementTransformation &eltr = *L2.GetElementTransformation(e);
L2.GetElementDofs(e, dofs);
mi.AssembleElementMatrix(fe, eltr, Mrho);
inv.Factor();
rhs_l.GetElementDofValues(e, rhs_e);
inv.Mult(rhs_e, rho_z);
rho.SetSubVector(dofs, rho_z);
}
}
void UpdateQuadratureData(const Vector &S) const
{
auto sptr = const_cast<Vector*>(&S);
const int H1vsize = H1.GetVSize();
ParGridFunction x, v, e;
x.MakeRef(&H1, *sptr, 0);
v.MakeRef(&H1, *sptr, H1vsize);
e.MakeRef(&L2, *sptr, 2*H1vsize);
update_qdata->SetParameters({&v, &rho0, &x0, &x, &material, &e, &qdata->h0, &qdata->order_v});
update_qdata->Mult(qdata->stressp, qdata->stressp);
}
virtual MemoryClass GetMemoryClass() const override
{
return Device::GetDeviceMemoryClass();
}
ParFiniteElementSpace &H1;
ParFiniteElementSpace &L2;
mutable ParFiniteElementSpace H1c;
const Array<int> &ess_tdof;
mutable Array<int> c_tdofs[3];
const IntegrationRule &ir;
ParGridFunction &x0;
ParGridFunction &rho0;
ParGridFunction &material;
std::shared_ptr<DifferentiableOperator> update_qdata;
std::shared_ptr<DifferentiableOperator> dtest_mf;
std::shared_ptr<DifferentiableOperator> momentum_mf;
std::shared_ptr<DifferentiableOperator> momentum_pa;
std::shared_ptr<DifferentiableOperator> energy_conservation_mf;
std::shared_ptr<DifferentiableOperator> energy_conservation_pa;
std::shared_ptr<DifferentiableOperator> total_internal_energy_mf;
std::shared_ptr<DifferentiableOperator> total_kinetic_energy_mf;
std::shared_ptr<DifferentiableOperator> density_mf;
std::shared_ptr<QuadratureData> qdata;
mutable ParGridFunction mesh_nodes, rhsvc, dvc;
mutable MassPAOperator *Mv = nullptr, *Me = nullptr;
mutable FunctionCoefficient rho0_coeff;
OperatorJacobiSmoother *Mv_Jprec = nullptr;
mutable Vector RHSv, rhsv, X, Xx, Xv, Xvc, Xe, K, Kx, Kv, Ke, B, RHSe, rhse;
const int nl2dofs;
bool fd_gradient;
const int nonlinear_maximum_iterations;
const real_t nonlinear_relative_tolerance;
};
static auto CreateLagrangianHydroOperator(
ParFiniteElementSpace &H1,
ParFiniteElementSpace &L2,
const Array<int> &ess_tdof,
FunctionCoefficient &rho0_coeff,
ParGridFunction &x0_gf,
ParGridFunction &rho0_gf,
ParGridFunction &material_gf,
Vector &external_data,
const IntegrationRule &ir,
bool fd_gradient,
const int nonlinear_maximum_iterations,
const real_t nonlinear_relative_tolerance)
{
const int order_v = H1.GetOrder(0);
ParMesh &mesh = *H1.GetParMesh();
auto qdata = std::make_shared<QuadratureData>(mesh, ir);
int ne_loc = mesh.GetNE(), ne_global = 0;
real_t vol_loc = 0.0, vol_global = 0.0;
for (int e = 0; e < mesh.GetNE(); e++)
{
vol_loc += mesh.GetElementVolume(e);
}
MPI_Allreduce(&vol_loc, &vol_global, 1, MPI_DOUBLE, MPI_SUM, mesh.GetComm());
MPI_Allreduce(&ne_loc, &ne_global, 1, MPI_INT, MPI_SUM, mesh.GetComm());
switch (mesh.GetElementBaseGeometry(0))
{
case Geometry::SEGMENT: qdata->h0 = vol_global / ne_global; break;
case Geometry::SQUARE: qdata->h0 = sqrt(vol_global / ne_global); break;
case Geometry::TRIANGLE: qdata->h0 = sqrt(2.0 * vol_global / ne_global); break;
case Geometry::CUBE: qdata->h0 = pow(vol_global / ne_global, 1./3.); break;
case Geometry::TETRAHEDRON: qdata->h0 = pow(6.0 * vol_global / ne_global,
1./3.); break;
default: MFEM_ABORT("Unknown zone type!");
}
qdata->h0 /= (double) H1.GetOrder(0);
// const real_t h0 = sqrt(vol_global / ne_global) /
// static_cast<real_t>(H1.GetOrder(0));
qdata->order_v = order_v;
qdata->dt_est = std::numeric_limits<real_t>::infinity();
// external_data(2) = qdata->h0;
const auto d_external_data = external_data.Read();
Array<int> all_domain_attr(mesh.attributes.Max());
all_domain_attr = 1;
std::shared_ptr<DifferentiableOperator> dt_est;
{
tuple dt_est_kernel_ao =
{
Gradient<VELOCITY>{},
Value<DENSITY0>{},
Gradient<COORDINATES0>{},
Gradient<COORDINATES>{},
Value<MATERIAL>{},
Value<SPECIFIC_INTERNAL_ENERGY>{},
None<ELEMENT_SIZE0>{},
None<ORDER_VEL>{},
Weight{}
};
tuple dt_est_kernel_oo = {None<DT_EST>{}};
std::vector dt_est_solutions =
{
FieldDescriptor{DT_EST, &qdata->R}
};
std::vector dt_est_parameters =
{
FieldDescriptor{VELOCITY, &H1},
FieldDescriptor{DENSITY0, &L2},
FieldDescriptor{COORDINATES0, &H1},
FieldDescriptor{COORDINATES, &H1},
FieldDescriptor{MATERIAL, material_gf.ParFESpace()},
FieldDescriptor{SPECIFIC_INTERNAL_ENERGY, &L2},
FieldDescriptor{ELEMENT_SIZE0, &qdata->R},
FieldDescriptor{ORDER_VEL, &qdata->R}
};
dt_est = std::make_shared<DifferentiableOperator>(
dt_est_solutions, dt_est_parameters, mesh);
TimeStepEstimateQFunction dt_est_qf(d_external_data);
dt_est->AddDomainIntegrator(dt_est_qf, dt_est_kernel_ao,
dt_est_kernel_oo,
ir,
all_domain_attr);
}
std::shared_ptr<DifferentiableOperator> update_qdata;
{
tuple update_qdata_kernel_ao =
{
Gradient<VELOCITY>{},
Value<DENSITY0>{},
Gradient<COORDINATES0>{},
Gradient<COORDINATES>{},
Value<MATERIAL>{},
Value<SPECIFIC_INTERNAL_ENERGY>{},
None<ELEMENT_SIZE0>{},
None<ORDER_VEL>{},
Weight{}
};
tuple update_qdata_kernel_oo = {None<STRESS_TENSOR>{}};
std::vector<FieldDescriptor> update_qdata_solutions =
{
{STRESS_TENSOR, &qdata->StressSpace}
};
std::vector<FieldDescriptor> update_qdata_parameters =
{
{VELOCITY, &H1},
{DENSITY0, &L2},
{COORDINATES0, &H1},
{COORDINATES, &H1},
{MATERIAL, material_gf.ParFESpace()},
{SPECIFIC_INTERNAL_ENERGY, &L2},
{ELEMENT_SIZE0, &qdata->R},
{ORDER_VEL, &qdata->R}
};
update_qdata = std::make_shared<DifferentiableOperator>(
update_qdata_solutions, update_qdata_parameters, mesh);
UpdateQuadratureDataQFunction update_qdata_qf(d_external_data);
update_qdata->AddDomainIntegrator(update_qdata_qf, update_qdata_kernel_ao,
update_qdata_kernel_oo,
ir,
all_domain_attr);
}
// Create momentum operator
std::shared_ptr<DifferentiableOperator> momentum_mf;
{
tuple momentum_mf_kernel_ao =
{
Gradient<VELOCITY>{},
Value<DENSITY0>{},
Gradient<COORDINATES0>{},
Gradient<COORDINATES>{},
Value<MATERIAL>{},
Value<SPECIFIC_INTERNAL_ENERGY>{},
None<ELEMENT_SIZE0>{},
None<ORDER_VEL>{},
Weight{}
};
tuple momentum_mf_kernel_oo = {Gradient<VELOCITY>{}};
// <sigma, grad(w) * J^-T> * det(J) * weights
// <sigma(J^-T det(J) weights), grad(w)>
std::vector momentum_mf_solutions =
{
FieldDescriptor{VELOCITY, &H1}
};
std::vector momentum_mf_parameters =
{
FieldDescriptor{DENSITY0, &L2},
FieldDescriptor{COORDINATES0, &H1},
FieldDescriptor{COORDINATES, &H1},
FieldDescriptor{MATERIAL, material_gf.ParFESpace()},
FieldDescriptor{SPECIFIC_INTERNAL_ENERGY, &L2},
FieldDescriptor{ELEMENT_SIZE0, &qdata->R},
FieldDescriptor{ORDER_VEL, &qdata->R}
};
momentum_mf = std::make_shared<DifferentiableOperator>(
momentum_mf_solutions, momentum_mf_parameters, mesh);
MomentumQFunction momentum_qf(d_external_data);
auto derivatives =
std::integer_sequence<size_t, VELOCITY, COORDINATES, SPECIFIC_INTERNAL_ENERGY> {};
momentum_mf->AddDomainIntegrator(momentum_qf, momentum_mf_kernel_ao,
momentum_mf_kernel_oo, ir, all_domain_attr, derivatives);
}
std::shared_ptr<DifferentiableOperator> momentum_pa;
{
tuple momentum_pa_kernel_ao = {None<STRESS_TENSOR>{}};
tuple momentum_pa_kernel_oo = {Gradient<VELOCITY>{}};
std::vector<FieldDescriptor> momentum_pa_solutions = {{VELOCITY, &H1}};
std::vector<FieldDescriptor> momentum_pa_parameters = {{STRESS_TENSOR, &qdata->StressSpace}};
momentum_pa = std::make_shared<DifferentiableOperator>(
momentum_pa_solutions, momentum_pa_parameters, mesh);
MomentumPAQFunction momentum_pa_qf;
momentum_pa->AddDomainIntegrator(momentum_pa_qf, momentum_pa_kernel_ao,
momentum_pa_kernel_oo, ir, all_domain_attr);
}
// Create energy conservation operator
std::shared_ptr<DifferentiableOperator> energy_conservation_mf;
{
tuple energy_conservation_mf_kernel_ao =
{
Gradient<VELOCITY>{},
Value<DENSITY0>{},
Gradient<COORDINATES0>{},
Gradient<COORDINATES>{},
Value<MATERIAL>{},
Value<SPECIFIC_INTERNAL_ENERGY>{},
None<ELEMENT_SIZE0>{},
None<ORDER_VEL>{},
Weight{}
};
tuple energy_conservation_mf_kernel_oo = {Value<SPECIFIC_INTERNAL_ENERGY>{}};
// <sigma, grad(v) * inv(J) * phi> * det(J) * w
// <sigma(J^-T det(J) w), grad(v) * inv(J)>
std::vector energy_conservation_mf_solutions =
{
FieldDescriptor{SPECIFIC_INTERNAL_ENERGY, &L2}
};
std::vector energy_conservation_mf_parameters =
{
FieldDescriptor{VELOCITY, &H1},
FieldDescriptor{DENSITY0, &L2},
FieldDescriptor{COORDINATES0, &H1},
FieldDescriptor{COORDINATES, &H1},
FieldDescriptor{MATERIAL, material_gf.ParFESpace()},
FieldDescriptor{ELEMENT_SIZE0, &qdata->R},
FieldDescriptor{ORDER_VEL, &qdata->R}
};
energy_conservation_mf =
std::make_shared<DifferentiableOperator>(
energy_conservation_mf_solutions, energy_conservation_mf_parameters, mesh);
EnergyConservationQFunction energy_conservation_qf(d_external_data);
auto derivatives =
std::integer_sequence<size_t, VELOCITY, COORDINATES, SPECIFIC_INTERNAL_ENERGY> {};
energy_conservation_mf->AddDomainIntegrator(
energy_conservation_qf, energy_conservation_mf_kernel_ao,
energy_conservation_mf_kernel_oo, ir, all_domain_attr, derivatives);
}
std::shared_ptr<DifferentiableOperator> energy_conservation_pa;
{
tuple energy_conservation_pa_kernel_ao = {Gradient<VELOCITY>{}, None<STRESS_TENSOR>{}};
tuple energy_conservation_pa_kernel_oo = {Value<SPECIFIC_INTERNAL_ENERGY>{}};
std::vector<FieldDescriptor> energy_conservation_pa_solutions =
{
{SPECIFIC_INTERNAL_ENERGY, &L2}
};
std::vector<FieldDescriptor> energy_conservation_pa_parameters =
{
{VELOCITY, &H1},
{STRESS_TENSOR, &qdata->StressSpace}
};
energy_conservation_pa =
std::make_shared<DifferentiableOperator>(
energy_conservation_pa_solutions, energy_conservation_pa_parameters, mesh);
EnergyConservationPAQFunction energy_conservation_qf;
energy_conservation_pa->AddDomainIntegrator(
energy_conservation_qf, energy_conservation_pa_kernel_ao,
energy_conservation_pa_kernel_oo, ir, all_domain_attr);
}
// Create total internal energy operator
std::shared_ptr<DifferentiableOperator> total_internal_energy_mf;
{
tuple total_internal_energy_kernel_ao =
{
Value<SPECIFIC_INTERNAL_ENERGY>{},
Value<DENSITY0>{},
Gradient<COORDINATES0>{},
Weight{}
};
tuple total_internal_energy_kernel_oo = {Value<SPECIFIC_INTERNAL_ENERGY>{}};
std::vector total_internal_energy_solutions =
{
FieldDescriptor{SPECIFIC_INTERNAL_ENERGY, &L2}
};
std::vector total_internal_energy_parameters =
{
FieldDescriptor{DENSITY0, &L2},
FieldDescriptor{COORDINATES0, &H1}
};
total_internal_energy_mf =
std::make_shared<DifferentiableOperator>(
total_internal_energy_solutions,
total_internal_energy_parameters,
mesh);
TotalInternalEnergyQFunction total_internal_energy_qf;
total_internal_energy_mf->AddDomainIntegrator(
total_internal_energy_qf, total_internal_energy_kernel_ao,
total_internal_energy_kernel_oo, ir, all_domain_attr);
}
// Create total kinetic energy operator
std::shared_ptr<DifferentiableOperator> total_kinetic_energy_mf;
{
tuple total_kinetic_energy_kernel_ao =
{
Value<VELOCITY>{},
Value<DENSITY0>{},
Gradient<COORDINATES0>{},
Weight{}
};
tuple total_kinetic_energy_kernel_oo = {Value<DENSITY0>{}};
std::vector total_kinetic_energy_solutions =
{
FieldDescriptor{VELOCITY, &H1}
};
std::vector total_kinetic_energy_parameters =
{
FieldDescriptor{DENSITY0, &L2},
FieldDescriptor{COORDINATES0, &H1}
};
total_kinetic_energy_mf =
std::make_shared<DifferentiableOperator>(
total_kinetic_energy_solutions,
total_kinetic_energy_parameters, mesh);
TotalKineticEnergyQFunction total_kinetic_energy_qf;
total_kinetic_energy_mf->AddDomainIntegrator(
total_kinetic_energy_qf, total_kinetic_energy_kernel_ao,
total_kinetic_energy_kernel_oo, ir, all_domain_attr);
}
// Create density operator
std::shared_ptr<DifferentiableOperator> density_mf;
{
tuple density_kernel_ao =
{
Value<DENSITY0>{},
Gradient<COORDINATES0>{},
Weight{}
};
tuple density_kernel_oo = {Value<DENSITY0>{}};
std::vector density_solutions =
{
FieldDescriptor{DENSITY0, &L2}
};
std::vector density_parameters =
{
FieldDescriptor{COORDINATES0, &H1}
};
density_mf = std::make_shared<DifferentiableOperator>(
density_solutions, density_parameters, mesh);
DensityQFunction density_qf;
density_mf->AddDomainIntegrator(density_qf, density_kernel_ao,
density_kernel_oo, ir, all_domain_attr);
}
return LagrangianHydroOperator(
H1,
L2,
ess_tdof,
ir,
rho0_coeff,
x0_gf,
rho0_gf,
material_gf,
update_qdata,
dt_est,
momentum_mf,
momentum_pa,
energy_conservation_mf,
energy_conservation_pa,
total_internal_energy_mf,
total_kinetic_energy_mf,
density_mf,
qdata,
fd_gradient,
nonlinear_maximum_iterations,
nonlinear_relative_tolerance);
}
int main(int argc, char *argv[])
{
Mpi::Init();
Hypre::Init();
const char *device_config = "cpu";
const char *mesh_file =
"/Users/andrej1/repos/Laghos/data/rectangle01_quad.mesh";
int refinements = 0;
int order_v = 2;
int order_e = 1;
int order_q = -1;
real_t t_final = 0.0;
real_t blast_energy = 0.25;
real_t blast_position[] = {0.0, 0.0, 0.0};
int ode_solver_type = 4;
bool fd_gradient = false;
bool use_viscosity = false;
real_t cfl = 0.5;
real_t nonlinear_relative_tolerance = 1e-5;
int nonlinear_maximum_iterations = 10;
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&refinements, "-rs", "--ref", "");
args.AddOption(&order_v, "-ov", "--ov", "");
args.AddOption(&order_e, "-oe", "--oe", "");
args.AddOption(&order_q, "-oq", "--oq", "");
args.AddOption(&t_final, "-tf", "--tf", "");
args.AddOption(&problem, "-p", "--p", "");
args.AddOption(&cfl, "-cfl", "--cfl", "");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&use_viscosity, "-av", "--av", "-no-av", "--no-av", "");
args.AddOption(&fd_gradient, "-fd", "--fd", "-no-fd", "--no-fd", "");
args.AddOption(&ode_solver_type, "-s", "--ode-solver",
"ODE solver: 1 - Forward Euler,\n\t"
" 2 - RK2 SSP, 3 - RK3 SSP, 4 - RK4, 6 - RK6,\n\t"
" 7 - RK2Avg."
" 11 - Backward Euler"
" 12 - Implicit Midpoint"
" 13 - SDIRK33Solver");
args.AddOption(&nonlinear_maximum_iterations, "-nmi", "--nmi",
"Maximum number of nonlinear iterations.");
args.AddOption(&nonlinear_relative_tolerance, "-nrt", "--nrt",
"Nonlinear relative tolerance.");
args.ParseCheck();
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
Mesh serial_mesh = Mesh(mesh_file, true, true);
if (problem == 0 || problem == 1)
{
serial_mesh = Mesh(Mesh::MakeCartesian2D(1, 1, Element::QUADRILATERAL,
true));
const int NBE = serial_mesh.GetNBE();
for (int b = 0; b < NBE; b++)
{
Element *bel = serial_mesh.GetBdrElement(b);
const int attr = (b < NBE/2) ? 2 : 1;
bel->SetAttribute(attr);
}
}
if (problem == 2)
{
serial_mesh = Mesh(Mesh::MakeCartesian1D(2));
serial_mesh.GetBdrElement(0)->SetAttribute(1);
serial_mesh.GetBdrElement(1)->SetAttribute(1);
}
for (int i = 0; i < refinements; i++)
{
serial_mesh.UniformRefinement();
}
// serial_mesh.EnsureNCMesh();
// serial_mesh.RandomRefinement(0.1);
ParMesh mesh = ParMesh(MPI_COMM_WORLD, serial_mesh);
const int dim = mesh.Dimension();
MFEM_ASSERT(dim == DIMENSION, "mesh dimension inconsistency");
// Define the parallel finite element spaces. We use:
// - H1 (Gauss-Lobatto, continuous) for position and velocity.
// - L2 (Bernstein, discontinuous) for specific internal energy.
H1_FECollection H1FEC(order_v, dim);
ParFiniteElementSpace H1FESpace(&mesh, &H1FEC, dim);
L2_FECollection L2FEC(order_e, dim, BasisType::Positive);
ParFiniteElementSpace L2FESpace(&mesh, &L2FEC);
const auto global_ne = mesh.GetGlobalNE();
const auto global_h1tsize = H1FESpace.GlobalTrueVSize();
const auto global_l2tsize = L2FESpace.GlobalTrueVSize();
if (Mpi::Root())
{
out << "num el: " << global_ne << "\n";
out << "num kinematic dofs: " << global_h1tsize << "\n";
out << "num thermodynamic dofs: " << global_l2tsize << "\n";
}
Array<int> ess_tdof, ess_vdofs;
{
Array<int> ess_bdr(mesh.bdr_attributes.Max()), dofs_marker, dofs_list;
for (int d = 0; d < mesh.Dimension(); d++)
{
// Attributes 1/2/3 correspond to fixed-x/y/z boundaries,
// i.e., we must enforce v_x/y/z = 0 for the velocity components.
ess_bdr = 0; ess_bdr[d] = 1;
H1FESpace.GetEssentialTrueDofs(ess_bdr, dofs_list, d);
ess_tdof.Append(dofs_list);
H1FESpace.GetEssentialVDofs(ess_bdr, dofs_marker, d);
FiniteElementSpace::MarkerToList(dofs_marker, dofs_list);
ess_vdofs.Append(dofs_list);
}
}
// The monolithic BlockVector stores unknown fields as:
// - 0 -> position
// - 1 -> velocity
// - 2 -> specific internal energy
const int Vsize_l2 = L2FESpace.GetVSize();
const int Vsize_h1 = H1FESpace.GetVSize();
Array<int> offset(4);
offset[0] = 0;
offset[1] = offset[0] + Vsize_h1;
offset[2] = offset[1] + Vsize_h1;
offset[3] = offset[2] + Vsize_l2;
BlockVector S(offset, Device::GetDeviceMemoryType());
ParGridFunction x_gf, v_gf, e_gf;
x_gf.MakeRef(&H1FESpace, S, offset[0]);
v_gf.MakeRef(&H1FESpace, S, offset[1]);
e_gf.MakeRef(&L2FESpace, S, offset[2]);
mesh.SetNodalGridFunction(&x_gf);
x_gf.SyncAliasMemory(S);
ParGridFunction x0_gf = x_gf;
auto v0 = [](const Vector &x, Vector &v)
{
switch (problem)
{
case 0:
v(0) = sin(M_PI*x(0)) * cos(M_PI*x(1));
v(1) = -cos(M_PI*x(0)) * sin(M_PI*x(1));
if (x.Size() == 3)
{
v(0) *= cos(M_PI*x(2));
v(1) *= cos(M_PI*x(2));
v(2) = 0.0;
}
break;
case 1: v = 0.0; break;
case 2: v = 0.0; break;
case 3: v = 0.0; break;
default: MFEM_ABORT("error");
}
};
VectorFunctionCoefficient v_coeff(dim, v0);
v_gf.ProjectCoefficient(v_coeff);
for (int i = 0; i < ess_vdofs.Size(); i++)
{
v_gf(ess_vdofs[i]) = 0.0;
}
v_gf.SyncAliasMemory(S);
auto rho0 = [&dim](const Vector &x)
{
switch (problem)
{
case 0: return 1.0;
case 1: return 1.0;
case 2: return (x(0) < 0.5) ? 1.0 : 0.1;
case 3: return (dim == 2) ? (x(0) > 1.0 && x(1) > 1.5) ? 0.125 : 1.0
: x(0) > 1.0 && ((x(1) < 1.5 && x(2) < 1.5) ||
(x(1) > 1.5 && x(2) > 1.5)) ? 0.125 : 1.0;
default: MFEM_ABORT("error");
}
};
ParGridFunction rho0_gf(&L2FESpace);
FunctionCoefficient rho0_coeff(rho0);
L2_FECollection l2_fec(order_e, mesh.Dimension());
ParFiniteElementSpace l2_fes(&mesh, &l2_fec);
ParGridFunction l2_rho0_gf(&l2_fes), l2_e(&l2_fes);
l2_rho0_gf.ProjectCoefficient(rho0_coeff);
rho0_gf.ProjectGridFunction(l2_rho0_gf);
auto gamma_func = [](const Vector &x)
{
switch (problem)
{
case 0: return 5.0 / 3.0;
case 1: return 1.4;
case 2: return 1.4;
case 3: return (x(0) > 1.0 && x(1) <= 1.5) ? 1.4 : 1.5;
default: MFEM_ABORT("error");
}
};
auto e0 = [&rho0, &gamma_func](const Vector &x)
{
switch (problem)
{
case 0:
{
const real_t denom = 2.0 / 3.0; // (5/3 - 1) * density.
real_t val;
if (x.Size() == 2)
{
val = 1.0 + (cos(2*M_PI*x(0)) + cos(2*M_PI*x(1))) / 4.0;
}
else
{
val = 100.0 + ((cos(2*M_PI*x(2)) + 2) *
(cos(2*M_PI*x(0)) + cos(2*M_PI*x(1))) - 2) / 16.0;
}
return val/denom;
}
case 1: return 0.0; // This case in initialized in main().
case 2: return (x(0) < 0.5) ? 1.0 / rho0(x) / (gamma_func(x) - 1.0)
: 0.1 / rho0(x) / (gamma_func(x) - 1.0);
case 3: return (x(0) > 1.0) ? 0.1 / rho0(x) / (gamma_func(x) - 1.0)
: 1.0 / rho0(x) / (gamma_func(x) - 1.0);
default: MFEM_ABORT("error");
}
};
if (problem == 1)
{
DeltaCoefficient e_coeff(blast_position[0], blast_position[1],
blast_position[2], blast_energy);
l2_e.ProjectCoefficient(e_coeff);
}
else
{
FunctionCoefficient e_coeff(e0);
l2_e.ProjectCoefficient(e_coeff);
}
e_gf.ProjectGridFunction(l2_e);
e_gf.SyncAliasMemory(S);
L2_FECollection material_fec(0, dim);
ParFiniteElementSpace L2CFESpace(&mesh, &material_fec);
ParGridFunction material_gf(&L2CFESpace);
FunctionCoefficient material_coeff(gamma_func);
material_gf.ProjectCoefficient(material_coeff);
ParGridFunction rho_gf(&L2FESpace);
IntegrationRule ir = IntRules.Get(mesh.GetElementBaseGeometry(0),
3 * H1FESpace.GetOrder(0) + L2FESpace.GetOrder(0) - 1);
if (Mpi::Root())
{
out << "num qp: " << ir.GetNPoints() << "\n";
}
// Create external data vector
// Layout is [cfl, order_velocity, use_viscosity, h0]
Vector external_data(4);
external_data[0] = cfl;
external_data[1] = order_v;
external_data[2] = use_viscosity;
auto hydro = CreateLagrangianHydroOperator(H1FESpace,
L2FESpace,
ess_tdof,
rho0_coeff,
x0_gf,
rho0_gf,
material_gf,
external_data,
ir,
fd_gradient,
nonlinear_maximum_iterations,
nonlinear_relative_tolerance);
ODESolver *ode_solver = NULL;
switch (ode_solver_type)
{
case 1: ode_solver = new ForwardEulerSolver; break;
case 2: ode_solver = new RK2Solver(0.5); break;
case 3: ode_solver = new RK3SSPSolver; break;
case 4: ode_solver = new RK4Solver; break;
case 6: ode_solver = new RK6Solver; break;
case 11: ode_solver = new BackwardEulerSolver; break;
case 12: ode_solver = new ImplicitMidpointSolver; break;
case 13: ode_solver = new SDIRK33Solver; break;
default:
out << "Unknown ODE solver type: " << ode_solver_type << '\n';
return -1;
}
ode_solver->Init(hydro);
hydro.ComputeDensity(rho_gf);
const real_t energy_init = hydro.InternalEnergy(e_gf) +
hydro.KineticEnergy(v_gf);
if (Mpi::Root())
{
out << "energy initial: " << energy_init << "\n";
}
out << "IE " << hydro.InternalEnergy(e_gf) << "\n"
<< "KE "<< hydro.KineticEnergy(v_gf) << "\n";
real_t t = 0.0;
real_t dt = hydro.GetTimeStepEstimate(S);
out << "time step estimate: " << dt << "\n";
real_t t_old;
bool last_step = false;
[[maybe_unused]] int steps = 0;
BlockVector S_old(S);
ParGridFunction verr_gf(v_gf);
verr_gf.ProjectCoefficient(v_coeff);
v_gf.SyncAliasMemory(S);
v_gf.HostRead();
verr_gf.HostReadWrite();
for (int i = 0; i < verr_gf.Size(); i++)
{
verr_gf(i) = abs(verr_gf(i) - std::as_const(v_gf)(i));
}
ParaViewDataCollection paraview_dc("dfem", &mesh);
paraview_dc.SetPrefixPath("ParaView");
paraview_dc.SetLevelsOfDetail(order_v);
paraview_dc.SetDataFormat(VTKFormat::BINARY);
paraview_dc.SetHighOrderOutput(true);
paraview_dc.SetCycle(0);
paraview_dc.SetTime(0.0);
paraview_dc.RegisterField("velocity", &v_gf);
paraview_dc.RegisterField("density", &rho_gf);
paraview_dc.RegisterField("specific_internal_energy", &e_gf);
paraview_dc.RegisterField("material", &material_gf);
// paraview_dc.RegisterField("velocity_error", &verr_gf);
paraview_dc.SetCycle(0);
paraview_dc.SetTime(0);
paraview_dc.Save();
for (int ti = 1; !last_step; ti++)
{
if (t + dt >= t_final)
{
dt = t_final - t;
last_step = true;
}
S_old = S;
t_old = t;
// S is the vector of dofs, t is the current time, and dt is the time step
// to advance.
ode_solver->Step(S, t, dt);
steps++;
// Adaptive time step control.
const real_t dt_est = hydro.GetTimeStepEstimate(S);
if (dt_est < dt)
{
// Repeat (solve again) with a decreased time step - decrease of the
// time estimate suggests appearance of oscillations.
dt *= 0.85;
if (dt < std::numeric_limits<real_t>::epsilon())
{ MFEM_ABORT("The time step crashed!"); }
t = t_old;
S = S_old;
if (Mpi::Root()) { out << "Repeating step " << ti << std::endl; }
ti--; continue;
}
else if (dt_est > 1.25 * dt) { dt *= 1.02; }
x_gf.SyncAliasMemory(S);
v_gf.SyncAliasMemory(S);
e_gf.SyncAliasMemory(S);
// Make sure that the mesh corresponds to the new solution state. This is
// needed, because some time integrators use different S-type vectors
// and the oper object might have redirected the mesh positions to those.
mesh.NewNodes(x_gf, false);
// out << "x_gf outer loop\n";
// print_vector(x_gf);
if (Mpi::Root())
{
out << "step " << std::setw(5) << ti
<< ",\tt = " << std::setw(5) << std::setprecision(4) << t
<< ",\tdt = " << std::setw(5) << std::setprecision(6) << dt;
out << std::endl;
}
// verr_gf.ProjectCoefficient(v_coeff);
// for (int i = 0; i < verr_gf.Size(); i++)
// {
// verr_gf(i) = abs(verr_gf(i) - v_gf(i));
// }
hydro.ComputeDensity(rho_gf);
paraview_dc.SetCycle(ti);
paraview_dc.SetTime(t);
paraview_dc.Save();
}
const real_t energy_final = hydro.InternalEnergy(e_gf)
+ hydro.KineticEnergy(v_gf);
const real_t v_err_max = v_gf.ComputeMaxError(v_coeff);
const real_t v_err_l1 = v_gf.ComputeL1Error(v_coeff);
const real_t v_err_l2 = v_gf.ComputeL2Error(v_coeff);
if (Mpi::Root())
{
out << std::scientific << std::setprecision(2)
<< "Energy diff: " << fabs(energy_init - energy_final) << std::endl
<< "L_inf error: " << v_err_max << std::endl
<< "L_1 error: " << v_err_l1 << std::endl
<< "L_2 error: " << v_err_l2 << std::endl;
}
return 0;
}