Compare commits

...
Author SHA1 Message Date
camierjs f2a424720b Merge branch 'main' into hpcftools/flowSolver-gpu 2025-06-30 11:17:56 -07:00
camierjs e5fad8067c Merge branch 'master' into hpcftools/flowSolver-gpu 2025-06-30 11:17:45 -07:00
camierjs 0b5bb52996 miniapps/navier/incompressible_navier_dfem 2025-06-30 11:17:11 -07:00
camierjs 563bfe9514 Merge branch 'master' 2025-06-30 08:43:14 -07:00
camierjs 9afef578d5 Merge branch 'master' into hpcftools/flowSolver-gpu 2025-05-02 09:59:44 -07:00
camierjs 9ecc414c62 dot reduced tests 2024-12-04 18:25:05 -08:00
camierjs 263b9d32c1 Merge branch 'hpcftools/flowSolver' 2024-12-04 11:59:05 -08:00
Mathias Rainer Schmidt 74e4ad3e2c - updated flow solver
- added comments to Blf and Lf contributions
- split setup and step into vel, auxiliary and pressure part
2024-11-25 11:04:30 -08:00
camierjs 79039e0f6f Switched to PA 2024-11-21 10:52:39 -08:00
Mathias Rainer Schmidt cf9fcd8dde Merge remote-tracking branch 'origin/master' into hpcftools/flowSolver 2024-11-14 13:52:27 -08:00
camierjs c857fde13b Merge branch 'hpcftools/flowSolver' 2024-11-01 15:59:50 -07:00
Mathias Rainer Schmidt fa8617ada3 - added partial assembly option 2024-11-01 13:20:29 -07:00
camierjs c06cbb69d5 Setup and cleanup 2024-10-30 11:37:00 -07:00
Mathias Rainer Schmidt f7e5db2cea - added ortho solver to phi field 2024-10-24 16:06:37 -07:00
Mathias Rainer Schmidt 60c11776b6 - added executable 2024-10-21 15:57:23 -07:00
Mathias Rainer Schmidt 6238f8ca76 - update solution 2024-10-16 16:00:08 -07:00
Mathias Rainer Schmidt 6a256db9aa - added linear solvers to step 2024-10-16 15:56:46 -07:00
Mathias Rainer Schmidt e0fb9658ca - added linear form integrators 2024-10-15 17:30:09 -07:00
Mathias Rainer Schmidt a1089efac3 - added BilinearForms 2024-10-15 13:46:36 -07:00
Mathias Rainer Schmidt 83f7f769dc - inital flow solver commit 2024-10-15 12:44:25 -07:00
10 changed files with 2138 additions and 6 deletions
+3
View File
@@ -19,6 +19,9 @@ CMakeFiles/
# Clangd server cache
*.cache*
# VSCode configuration
/.vscode/
# Backup files
*~
+2 -1
View File
@@ -101,10 +101,11 @@ public:
// Setup DofToQuad information
dtq.nqpt = (int)floor(std::pow(ir.GetNPoints(), 1.0 / mesh.Dimension()) + 0.5);
dtq.ndof = dtq.nqpt;
dtq.mode = used_in_tensor_product ? DofToQuad::TENSOR : DofToQuad::FULL;
// Calculate sizes
const int num_qp = used_in_tensor_product ?
std::pow(dtq.nqpt, mesh.Dimension()) :
static_cast<int>(std::pow(dtq.nqpt, mesh.Dimension())) :
ir.GetNPoints();
tsize = vdim * num_qp * mesh.GetNE();
+4 -4
View File
@@ -1702,12 +1702,12 @@ std::array<DofToQuadMap, N> load_dtq_mem(
std::array<DofToQuadMap, N> f;
for (std::size_t i = 0; i < N; i++)
{
const auto [nqp_b, dim_b, ndof_b] = dtq[i].B.GetShape();
const auto B = Reshape(&dtq[i].B[0], nqp_b, dim_b, ndof_b);
auto mem_Bi = Reshape(reinterpret_cast<real_t *>(mem) + offset, nqp_b, dim_b,
ndof_b);
if (dtq[i].which_input != -1)
{
const auto [nqp_b, dim_b, ndof_b] = dtq[i].B.GetShape();
const auto B = Reshape(&dtq[i].B[0], nqp_b, dim_b, ndof_b);
auto mem_Bi = Reshape(reinterpret_cast<real_t *>(mem) + offset, nqp_b, dim_b,
ndof_b);
MFEM_FOREACH_THREAD(q, x, nqp_b)
{
MFEM_FOREACH_THREAD(d, y, ndof_b)
+15 -1
View File
@@ -52,6 +52,20 @@ if (MFEM_USE_MPI)
${NAVIER_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(incompressible_navier_dfem
MAIN incompressible_navier_dfem.cpp
EXTRA_HEADERS incompressible_navier_nvtx.hpp
LIBRARIES mfem)
add_mfem_miniapp(incompNS_2Dtest
MAIN incompNS_2Dtest.cpp
EXTRA_SOURCES incompressible_navier_solver.cpp
incompressible_navier_tests.cpp
EXTRA_HEADERS incompressible_navier_solver.hpp
incompressible_navier_nvtx.hpp
EXTRA_DEFINES MFEM_USE_CMAKE_TESTS
LIBRARIES mfem)
add_mfem_miniapp(navier_turbchan
MAIN navier_turbchan.cpp
${NAVIER_COMMON_FILES}
@@ -84,4 +98,4 @@ if (MFEM_USE_MPI)
${MPIEXEC_POSTFLAGS})
endforeach()
endif()
endif ()
endif()
+205
View File
@@ -0,0 +1,205 @@
// Copyright (c) 2010-2024, 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.
// 3D flow over a cylinder benchmark example
#include "incompressible_navier_solver.hpp"
#define NVTX_COLOR ::gpu::nvtx::kLawnGreen
#include "incompressible_navier_nvtx.hpp"
using namespace mfem;
using namespace incompressible_navier;
void vel(const Vector &x, real_t t, Vector &u)
{
// real_t xi = x(0), yi = x(1);
u = 0.0;
}
void vel_inlet(const Vector &x, real_t t, Vector &u)
{
u = 0.0;
if (x(0) < 0.001) { u(0) = -0.001 * (std::pow(x(1) - 0.5, 2.0) - 0.25); }
}
MFEM_EXPORT int navier(int argc, char *argv[], double &u, double &p, double &Ψ)
{
dbg();
static mfem::MPI_Session mpi(argc, argv);
const int myid = mpi.WorldRank();
Hypre::Init();
const char *device_config = "cpu";
int serial_refinements = 1;
int nx = 90, ny = 30;
int v_order = 2;
int p_order = 1;
int t_order = 1;
real_t kin_vis = 20.0;
real_t dt = 1e-2;
real_t t = 0.0;
real_t t_final = 1.0;
bool last_step = false;
bool visualization = true;
bool use_paraview = false;
bool pa = false;
int vis_steps = 100;
int max_tsteps = -1;
constexpr int precision = 8;
std::cout.precision(precision);
OptionsParser args(argc, argv);
args.AddOption(&serial_refinements, "-sr", "--serial-refinements",
"Number serial refinements.");
args.AddOption(&nx, "-nx", "--nx", "Number of elements in X.");
args.AddOption(&ny, "-ny", "--ny", "Number of elements in Y.");
args.AddOption(&v_order, "-vo", "--vo", "Order.");
args.AddOption(&p_order, "-po", "--po", "Order.");
args.AddOption(&t_order, "-to", "--to", "Order.");
args.AddOption(&kin_vis, "-kv", "--kin-vis",
"Kineic viscosity coefficient.");
args.AddOption(&dt, "-dt", "--time-step", "Initial time step size.");
args.AddOption(&t_final, "-tf", "--t-final", "Final time; start time is 0.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&pa, "-pa", "--partial-assembly", "-no-pa",
"--no-partial-assembly",
"Enable or disable partial assembly.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&use_paraview, "-pv", "--paraview", "-no-pv", "--no-paraview",
"Use ParaView.");
args.AddOption(&vis_steps, "-vs", "--visualization-steps",
"Visualize every n-th timestep.");
args.AddOption(&max_tsteps, "-ms", "--max-steps",
"Maximum number of steps (negative means no restriction).");
args.Parse();
if (!args.Good())
{
if (myid == 0) { args.PrintUsage(mfem::out); }
return EXIT_FAILURE;
}
if (myid == 0) { args.PrintOptions(mfem::out); }
// Mesh *mesh = new Mesh("box-cylinder.mesh");
const real_t sx = 3.0, sy = 1.0;
const bool generate_edges = true;
const auto QUAD = Element::QUADRILATERAL;
Mesh mesh = Mesh::MakeCartesian2D(nx, ny, QUAD, generate_edges, sx, sy);
for (int i = 0; i < serial_refinements; ++i) { mesh.UniformRefinement(); }
if (Mpi::Root())
{
std::cout << "Number of elements: " << mesh.GetNE() << std::endl;
}
auto *pmesh = new ParMesh(MPI_COMM_WORLD, mesh);
// Create the flow solver.
IncompressibleNavierSolver flowsolver(pmesh, v_order, p_order, t_order,
kin_vis);
flowsolver.EnablePA(pa);
// // Set the initial condition.
// ParGridFunction *u_ic = flowsolver.GetCurrentVelocity();
// VectorFunctionCoefficient u_excoeff(pmesh->Dimension(), vel);
// u_ic->ProjectCoefficient(u_excoeff);
// Add Dirichlet boundary conditions to velocity space restricted to
// selected attributes on the mesh.
Array<int> attr(pmesh->bdr_attributes.Max());
attr = 0;
Array<int> attr_inlet(pmesh->bdr_attributes.Max());
attr_inlet = 0;
// Inlet is attribute 1.
attr[0] = 1;
// Walls is attribute 3.
attr[2] = 1;
flowsolver.AddVelDirichletBC(vel, attr);
attr_inlet[3] = 1;
flowsolver.AddVelDirichletBC(vel_inlet, attr_inlet);
flowsolver.Setup(dt);
ParGridFunction *u_gf = flowsolver.GetCurrentVelocity();
ParGridFunction *p_gf = flowsolver.GetCurrentPressure();
ParGridFunction *psi_gf = flowsolver.GetCurrentPsi();
ParaViewDataCollection pvdc("3dfoc", pmesh);
if (use_paraview)
{
pvdc.SetDataFormat(VTKFormat::BINARY32);
// pvdc.SetHighOrderOutput(true);
pvdc.SetCycle(0);
pvdc.SetTime(t);
pvdc.RegisterField("velocity", u_gf);
pvdc.RegisterField("pressure", p_gf);
pvdc.RegisterField("psi", psi_gf);
pvdc.Save();
}
for (int step = 0; !last_step; ++step)
{
if (step == max_tsteps) { last_step = true; }
if (t + dt >= t_final - dt / 2) { last_step = true; }
const bool vis_step = last_step || (step % vis_steps) == 0;
flowsolver.Step(t, dt, step, vis_step);
if (vis_step)
{
if (Mpi::Root() && vis_steps)
{
printf("%11s %11s\n", "Time", "dt");
printf("%.5E %.5E\n", t, dt);
}
if (use_paraview)
{
pvdc.SetCycle(step);
pvdc.SetTime(t);
pvdc.Save();
}
}
}
// flowsolver.PrintTimingData();
auto reduce = [](ParGridFunction *gf) -> real_t { return (*gf) * (*gf); };
// auto reduce = [](ParGridFunction *gf) -> real_t { return gf->Norml2(); };
u = reduce(u_gf), p = reduce(p_gf), Ψ = reduce(psi_gf);
fflush(stdout);
delete pmesh;
return EXIT_SUCCESS;
}
///////////////////////////////////////////////////////////////////////////////
#ifndef MFEM_USE_CMAKE_TESTS
int main(int argc, char *argv[])
try
{
dbg();
double u, p, Ψ; // unused
return navier(argc, argv, u, p, Ψ);
}
catch (std::exception &e)
{
std::cerr << "\033[31m..xxxXXX[ERROR]XXXxxx.." << std::endl;
std::cerr << "\033[31m{}" << e.what() << std::endl;
return EXIT_FAILURE;
}
#endif // MFEM_USE_CMAKE_TESTS
@@ -0,0 +1,397 @@
// Copyright (c) 2010-2024, 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"
using namespace mfem;
using namespace mfem::future;
#include "linalg/tensor.hpp"
using mfem::future::tensor;
#define NVTX_COLOR ::gpu::nvtx::kOrchid
#include "incompressible_navier_nvtx.hpp"
///////////////////////////////////////////////////////////////////////////////
template <int DIM>
void DiffusionSetup(const ParFiniteElementSpace &sfes,
const IntegrationRule &ir,
const Array<int> &domain_attributes,
ParameterFunction &qdata)
{
NVTX_MARK_FUNCTION;
auto pmesh = sfes.GetParMesh();
auto nodes = static_cast<ParGridFunction *>(pmesh->GetNodes());
auto mfes = nodes->ParFESpace();
constexpr int U = 0, Ξ = 1, Δ = 2;
DifferentiableOperator dop(
{{ U, &sfes }},
{
{ { Ξ, mfes },
{ Δ, &qdata.GetParameterSpace() }
}
},
*pmesh);
const auto qfunc =
[] MFEM_HOST_DEVICE(const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
auto invJ = inv(J);
tensor<real_t, DIM, DIM> C{};
C(0, 0) = M_PI;
C(0, 1) = 0, C(1, 0) = 0;
assert(C(0, 1) == 0 && C(1, 0) == 0); // diff otherwise
C(1, 1) = 1.0 / M_PI;
return tuple{ C * invJ * transpose(invJ) * det(J) * w };
};
dop.AddDomainIntegrator(qfunc,
tuple{ Gradient<Ξ>{}, Weight{} }, // inputs
tuple{ Identity<Δ>{} }, // outputs
ir, domain_attributes);
dop.SetParameters({ nodes, &qdata });
Vector unused(sfes.GetTrueVSize());
dop.Mult(unused, qdata);
qdata.HostRead();
}
///////////////////////////////////////////////////////////////////////////////
template <int DIM>
void DiffusionApply(const ParFiniteElementSpace &sfes,
const IntegrationRule &ir,
const Array<int> &domain_attributes,
ParameterFunction &qdata,
const Vector &x, Vector &y)
{
NVTX_MARK_FUNCTION;
auto pmesh = sfes.GetParMesh();
constexpr int U = 0, Q = 1;
auto qd_ps = &qdata.GetParameterSpace();
DifferentiableOperator dop({ { U, &sfes } }, { { Q, qd_ps } }, *pmesh);
const auto qfunc =[] MFEM_HOST_DEVICE(const tensor<real_t, DIM> &u,
const tensor<real_t, DIM, DIM> &Q)
{
return tuple{ Q * u };
};
dop.AddDomainIntegrator(
qfunc,
tuple{ Gradient<U>{}, Identity<Q>{} },
tuple{ Gradient<U>{} },
ir, domain_attributes);
dop.SetParameters({ &qdata });
dop.Mult(x, y);
y.HostRead();
}
///////////////////////////////////////////////////////////////////////////////
template <int DIM>
int DiffVerification(ParFiniteElementSpace &h1fes,
const IntegrationRule &ir,
const Vector &qdata,
const Vector &x, const Vector &y)
{
NVTX_MARK_FUNCTION;
constexpr real_t ϵ = 1e-12;
MatrixFunctionCoefficient matrix_coeff(
DIM, [](const Vector &, DenseMatrix &C)
{
C.SetSize(DIM);
C(0, 0) = M_PI;
C(0, 1) = 0, C(1, 0) = 0;
assert(C(0, 1) == 0 && C(1, 0) == 0); // diff otherwise
C(1, 1) = 1.0 / M_PI;
});
ParBilinearForm a(&h1fes);
auto diff_integ = new DiffusionIntegrator(matrix_coeff);
diff_integ->SetIntRule(&ir);
a.AddDomainIntegrator(diff_integ);
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
OperatorPtr A;
a.Assemble(), a.Finalize();
a.FormSystemMatrix(Array<int> {}, A);
Vector y2(h1fes.TrueVSize());
y2 = 0.0;
A->Mult(x, y2);
y2.HostRead();
Vector diff(y2);
diff -= y;
const auto diff_norm = diff.Norml2();
if (diff_norm > ϵ)
{
dbg("\x1B[31m||dFdu_FD u^* - ex||_l2 = {}", diff_norm);
return EXIT_FAILURE;
}
dbg("\x1B[32m||dFdu_FD u^* - ex||_l2 = {}", diff_norm);
return EXIT_SUCCESS;
}
///////////////////////////////////////////////////////////////////////////////
template <int DIM>
void MassApply(const ParFiniteElementSpace &sfes,
const IntegrationRule &ir,
const Array<int> &domain_attributes,
const Vector &x, Vector &y)
{
dbg();
auto &pmesh = *sfes.GetParMesh();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
auto *mfes = nodes->ParFESpace();
constexpr int U = 0, Coords = 1;
DifferentiableOperator dop({{ U, &sfes }}, {{ Coords, mfes }}, pmesh);
const auto mf_mass_qf =
[](const real_t &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
return tuple{ dudxi * w * det(J) };
};
dop.AddDomainIntegrator(
mf_mass_qf,
tuple{ Value<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Value<U>{} },
ir, domain_attributes);
dop.SetParameters({ nodes });
// Vector X(sfes.GetTrueVSize()), Y(sfes.GetTrueVSize());
// sfes.GetRestrictionMatrix()->Mult(x, X);
// dop.Mult(X, Y);
dop.Mult(x, y);
}
///////////////////////////////////////////////////////////////////////////////
template <int DIM>
int MassVerification(ParFiniteElementSpace &h1fes,
const IntegrationRule &ir,
const Vector &x, Vector &y)
{
ParBilinearForm a(&h1fes);
auto mass_integ = new MassIntegrator;
mass_integ->SetIntRule(&ir);
a.AddDomainIntegrator(mass_integ);
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
a.Assemble(), a.Finalize();
Vector y2(h1fes.TrueVSize());
a.Mult(x, y2);
y2.HostRead();
Vector diff(y2);
diff -= y;
const auto diff_norm = diff.Norml2();
constexpr real_t ϵ = 1e-12;
if (diff_norm > ϵ)
{
dbg("\x1B[31m||dFdu_FD u^* - ex||_l2 = {}", diff_norm);
return EXIT_FAILURE;
}
dbg("\x1B[32m||dFdu_FD u^* - ex||_l2 = {}", diff_norm);
return EXIT_SUCCESS;
}
///////////////////////////////////////////////////////////////////////////////
template <int DIM>
void VectorDiffApply(const ParFiniteElementSpace &vfes,
const IntegrationRule &ir,
const Array<int> &domain_attributes,
const Vector &x,
Vector &y)
{
NVTX_MARK_FUNCTION;
auto pmesh = vfes.GetParMesh();
auto nodes = static_cast<ParGridFunction *>(pmesh->GetNodes());
auto mfes = nodes->ParFESpace();
constexpr int U = 0, Coords = 1;
DifferentiableOperator dop({{ U, &vfes }}, {{ Coords, mfes }}, *pmesh);
const auto qfunc =
[] MFEM_HOST_DEVICE(const tensor<real_t, DIM, DIM> &u,
const tensor<real_t, DIM, DIM> &J, const real_t &w)
{
return tuple{ u * inv(J) * det(J) * w * transpose(inv(J)) };
};
dop.AddDomainIntegrator(qfunc,
tuple{ Gradient<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} },
ir, domain_attributes);
dop.SetParameters({ nodes });
dop.Mult(x, y);
y.HostRead();
}
///////////////////////////////////////////////////////////////////////////////
template <int DIM, int VDIM = DIM>
int VectorDiffVerif(ParFiniteElementSpace &h1fes,
const IntegrationRule &ir, const Vector &x,
Vector &y)
{
NVTX_MARK_FUNCTION;
ParBilinearForm a(&h1fes);
auto A_integ = new VectorDiffusionIntegrator(VDIM);
A_integ->SetIntRule(&ir);
a.AddDomainIntegrator(A_integ);
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
a.Assemble(), a.Finalize();
Vector y2(h1fes.TrueVSize());
a.Mult(x, y2);
y2.HostRead();
Vector diff(y2);
diff -= y;
const auto diff_norm = diff.Norml2();
constexpr real_t ϵ = 1e-12;
if (diff_norm > ϵ)
{
dbg("\x1B[31m||dFdu_FD u^* - ex||_l2 = {}", diff_norm);
return EXIT_FAILURE;
}
dbg("\x1B[32m||dFdu_FD u^* - ex||_l2 = {}", diff_norm);
return EXIT_SUCCESS;
}
///////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[]) try
{
NVTX_MARK_FUNCTION;
constexpr int DIM = 2, VDIM = DIM;
static mfem::MPI_Session mpi(argc, argv);
const int myid = mpi.WorldRank();
Hypre::Init();
const char *device_config = "cpu";
const char *mesh_file = "none";
int serial_refinements = 0;
int nx = 1, ny = 1;
int p = 1;
bool visualization = false;
bool pa = false;
std::cout.precision(8);
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
args.AddOption(&p, "-o", "--order", "Finite element order.");
args.AddOption(&serial_refinements, "-sr", "--serial-refinements",
"Number serial refinements.");
args.AddOption(&nx, "-nx", "--nx", "Number of elements in X.");
args.AddOption(&ny, "-ny", "--ny", "Number of elements in Y.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&pa, "-pa", "--partial-assembly", "-no-pa",
"--no-partial-assembly",
"Enable or disable partial assembly.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.Parse();
if (!args.Good())
{
if (myid == 0) { args.PrintUsage(mfem::out); }
return EXIT_FAILURE;
}
if (myid == 0) { args.PrintOptions(mfem::out); }
Mesh smesh;
if (std::string(mesh_file) != "none")
{
smesh = Mesh(mesh_file);
}
else
{
const real_t sx = 3.0, sy = 1.0;
const bool generate_edges = true;
const auto QUAD = Element::QUADRILATERAL;
smesh = Mesh::MakeCartesian2D(nx, ny, QUAD, generate_edges, sx, sy);
}
MFEM_ASSERT(smesh.Dimension() == 2, "2D mesh required!");
for (int i = 0; i < serial_refinements; ++i) { smesh.UniformRefinement(); }
dbg("Number of elements: {}", smesh.GetNE());
ParMesh pmesh(MPI_COMM_WORLD, smesh);
smesh.Clear();
pmesh.EnsureNodes();
pmesh.SetCurvature(p);
assert(DIM == pmesh.Dimension());
Array<int> domain_attributes;
if (pmesh.attributes.Size() > 0)
{
domain_attributes.SetSize(pmesh.attributes.Max());
domain_attributes = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec), vfes(&pmesh, &fec, VDIM);
dbg("#dofs:{} ", fes.GetTrueVSize());
const auto &fe = *fes.GetFE(0);
const auto &ir =
IntRules.Get(fe.GetGeomType(), fe.GetOrder() + fe.GetOrder() + fe.GetDim() - 1);
dbg("#ndof per el = {}", fe.GetDof());
dbg("#nqp = {}", ir.GetNPoints());
dbg("#q1d = {}", (int)floor(pow(ir.GetNPoints(), 1.0 / DIM) + 0.5));
ParGridFunction f1_gf(&fes);
auto f1 = [](const Vector &coords)
{
assert(DIM == 2);
const double x = coords(0), y = coords(1);
return M_PI + x + x * x + x * y + y;
};
FunctionCoefficient f1_c(f1);
f1_gf.ProjectCoefficient(f1_c);
Vector x(f1_gf), y(fes.GetTrueVSize());
UniformParameterSpace qd_ps(pmesh, ir, DIM * DIM);
ParameterFunction qdata(qd_ps);
dbg("Diffusion setup, apply & verification");
DiffusionSetup<DIM>(fes, ir, domain_attributes, qdata);
DiffusionApply<DIM>(fes, ir, domain_attributes, qdata, x, y);
if (DiffVerification<DIM>(fes, ir, qdata, x, y) != EXIT_SUCCESS) { return EXIT_FAILURE; }
dbg("Mass apply & verification");
MassApply<DIM>(fes, ir, domain_attributes, x, y);
if (MassVerification<DIM>(fes, ir, x, y) != EXIT_SUCCESS) { return EXIT_FAILURE; }
dbg("Vector diffusion apply");
VectorFunctionCoefficient vf1_c(VDIM, [](const Vector &coords, Vector &u)
{
assert(DIM == 2);
const double x = coords(0), y = coords(1);
u(0) = M_PI + 0.25 * x * x * y + y * y * x;
u(1) = M_PI - 0.25 * x * y * y + y * x * x;
});
ParGridFunction vf1_gf(&vfes);
vf1_gf.ProjectCoefficient(vf1_c);
Vector vx(vf1_gf), vy(vfes.GetTrueVSize());
VectorDiffApply<DIM>(vfes, ir, domain_attributes, vx, vy);
if (VectorDiffVerif<DIM>(vfes, ir, vx, vy) != EXIT_SUCCESS) { return EXIT_FAILURE; }
return EXIT_SUCCESS;
}
catch (std::exception &e)
{
std::cerr << "\033[31m..xxxXXX[ERROR]XXXxxx.." << std::endl;
std::cerr << "\033[31m{}" << e.what() << std::endl;
return EXIT_FAILURE;
}
@@ -0,0 +1,478 @@
// 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.
#pragma once
#define FMT_HEADER_ONLY
#include <fmt/format.h>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
#include <stack>
#include <string>
#ifdef MFEM_USE_CALIPER
#include <caliper/cali.h>
#endif
#ifdef MFEM_USE_CUDA
#include <cudaProfiler.h>
#include <cuda_runtime_api.h>
#include <nvToolsExt.h>
#else
struct nvtxEventAttributes_t
{
int version;
int size;
int category;
int colorType;
uint32_t color;
int payloadType;
uint64_t payload;
int messageType;
struct
{
std::string ascii;
} message;
};
#define NVTX_VERSION 1
#define NVTX_EVENT_ATTRIB_STRUCT_SIZE 256
#define NVTX_COLOR_ARGB 0
#define NVTX_MESSAGE_TYPE_ASCII 0
#define nvtxRangePushEx(...)
#define nvtxRangePop(...)
#define cudaStreamSynchronize(...)
#endif
namespace gpu::nvtx
{
///////////////////////////////////////////////////////////////////////////////
// https://en.wikipedia.org/wiki/Web_colors#Extended_colors
// http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
// clang-format off
enum color_names
{
kBlack = 0, kNavyBlue, kDarkBlue, kMediumBlue, kBlue, kDarkGreen, kWebGreen, kTeal,
kDarkCyan, kDeepSkyBlue, kDarkTurquoise, kMediumSpringGreen, kGreen, kLime,
kSpringGreen, kAqua, kCyan, kMidnightBlue, kDodgerBlue, kLightSeaGreen, kForestGreen,
kSeaGreen, kDarkSlateGray, kLimeGreen, kMediumSeaGreen, kTurquoise, kRoyalBlue,
kSteelBlue, kDarkSlateBlue, kMediumTurquoise, kIndigo, kDarkOliveGreen, kCadetBlue,
kCornflower, kRebeccaPurple, kMediumAquamarine, kDimGray, kSlateBlue, kOliveDrab,
kSlateGray, kLightSlateGray, kMediumSlateBlue, kLawnGreen, kWebMaroon, kWebPurple,
kChartreuse, kAquamarine, kOlive, kWebGray, kSkyBlue, kLightSkyBlue, kBlueViolet,
kDarkRed, kDarkMagenta, kSaddleBrown, kDarkSeaGreen, kLightGreen, kMediumPurple,
kDarkViolet, kPaleGreen, kDarkOrchid, kYellowGreen, kPurple, kSienna, kBrown,
kDarkGray, kLightBlue, kGreenYellow, kPaleTurquoise, kMaroon, kLightSteelBlue,
kPowderBlue, kFirebrick, kDarkGoldenrod, kMediumOrchid, kRosyBrown, kDarkKhaki,
kGray, kSilver, kMediumVioletRed, kIndianRed, kPeru, kChocolate, kTan, kLightGray,
kThistle, kOrchid, kGoldenrod, kPaleVioletRed, kCrimson, kGainsboro, kPlum, kBurlywood,
kLightCyan, kLavender, kDarkSalmon, kViolet, kPaleGoldenrod, kLightCoral, kKhaki,
kAliceBlue, kHoneydew, kAzure, kSandyBrown, kWheat, kBeige, kWhiteSmoke, kMintCream,
kGhostWhite, kSalmon, kAntiqueWhite, kLinen, kLightGoldenrod, kOldLace, kRed,
kFuchsia, kMagenta, kDeepPink, kOrangeRed, kTomato, kHotPink, kCoral, kDarkOrange,
kLightSalmon, kOrange, kLightPink, kPink, kGold, kPeachPuff, kNavajoWhite, kMoccasin,
kBisque, kMistyRose, kBlanchedAlmond, kPapayaWhip, kLavenderBlush, kSeashell,
kCornsilk, kLemonChiffon, kFloralWhite, kSnow, kYellow, kLightYellow, kIvory, kWhite,
kNvidia
};
// clang-format on
static constexpr int kNumHexColors = 146;
static constexpr std::array<uint32_t, kNumHexColors> kHexColors =
{
{
0x000000, 0x000080, 0x00008B, 0x0000CD, 0x0000FF, 0x006400, 0x008000,
0x008080, 0x008B8B, 0x00BFFF, 0x00CED1, 0x00FA9A, 0x00FF00, 0x00FF00,
0x00FF7F, 0x00FFFF, 0x00FFFF, 0x191970, 0x1E90FF, 0x20B2AA, 0x228B22,
0x2E8B57, 0x2F4F4F, 0x32CD32, 0x3CB371, 0x40E0D0, 0x4169E1, 0x4682B4,
0x483D8B, 0x48D1CC, 0x4B0082, 0x556B2F, 0x5F9EA0, 0x6495ED, 0x663399,
0x66CDAA, 0x696969, 0x6A5ACD, 0x6B8E23, 0x708090, 0x778899, 0x7B68EE,
0x7CFC00, 0x7F0000, 0x7F007F, 0x7FFF00, 0x7FFFD4, 0x808000, 0x808080,
0x87CEEB, 0x87CEFA, 0x8A2BE2, 0x8B0000, 0x8B008B, 0x8B4513, 0x8FBC8F,
0x90EE90, 0x9370DB, 0x9400D3, 0x98FB98, 0x9932CC, 0x9ACD32, 0xA020F0,
0xA0522D, 0xA52A2A, 0xA9A9A9, 0xADD8E6, 0xADFF2F, 0xAFEEEE, 0xB03060,
0xB0C4DE, 0xB0E0E6, 0xB22222, 0xB8860B, 0xBA55D3, 0xBC8F8F, 0xBDB76B,
0xBEBEBE, 0xC0C0C0, 0xC71585, 0xCD5C5C, 0xCD853F, 0xD2691E, 0xD2B48C,
0xD3D3D3, 0xD8BFD8, 0xDA70D6, 0xDAA520, 0xDB7093, 0xDC143C, 0xDCDCDC,
0xDDA0DD, 0xDEB887, 0xE0FFFF, 0xE6E6FA, 0xE9967A, 0xEE82EE, 0xEEE8AA,
0xF08080, 0xF0E68C, 0xF0F8FF, 0xF0FFF0, 0xF0FFFF, 0xF4A460, 0xF5DEB3,
0xF5F5DC, 0xF5F5F5, 0xF5FFFA, 0xF8F8FF, 0xFA8072, 0xFAEBD7, 0xFAF0E6,
0xFAFAD2, 0xFDF5E6, 0xFF0000, 0xFF00FF, 0xFF00FF, 0xFF1493, 0xFF4500,
0xFF6347, 0xFF69B4, 0xFF7F50, 0xFF8C00, 0xFFA07A, 0xFFA500, 0xFFB6C1,
0xFFC0CB, 0xFFD700, 0xFFDAB9, 0xFFDEAD, 0xFFE4B5, 0xFFE4C4, 0xFFE4E1,
0xFFEBCD, 0xFFEFD5, 0xFFF0F5, 0xFFF5EE, 0xFFF8DC, 0xFFFACD, 0xFFFAF0,
0xFFFAFA, 0xFFFF00, 0xFFFFE0, 0xFFFFF0, 0xFFFFFF, 0x76B900
}
};
///////////////////////////////////////////////////////////////////////////////
constexpr size_t static_strlen(const char *str)
{
return *str == '\0' ? 0 : static_strlen(str + 1) + 1;
}
constexpr uint8_t static_checksum8(const char *bfr)
{
unsigned int chk = 0;
size_t len = static_strlen(bfr);
for (; len; len--, bfr++) { chk += static_cast<unsigned int>(*bfr); }
return static_cast<uint8_t>(chk);
}
constexpr char *static_strrnchr(const char *str, const char c, int n)
{
size_t len = static_strlen(str);
char *p = const_cast<char *>(str) + len - 1;
for (; n; n--, p--, len--)
{
for (; len; p--, len--)
{
if (*p == c) { break; }
}
if (!len) { return nullptr; }
if (n == 1) { return p; }
}
return nullptr;
}
inline uint32_t static_color(const uint8_t COLOR, const int RANK,
const char *FILE)
{
constexpr auto kMpiColorShift = 1;
const auto rank_shift = kMpiColorShift * RANK;
if (COLOR > 0) { return kHexColors[COLOR + rank_shift]; }
const auto file_color = static_checksum8(FILE);
return kHexColors[(file_color + rank_shift) % kNumHexColors];
}
///////////////////////////////////////////////////////////////////////////////
// Helpers to generate unique variable names
#define NVTX_FLF __FILE__, __LINE__, __FUNCTION__
#define NVTX_PRIVATE_NAME(prefix) NVTX_PRIVATE_CONCAT(prefix, __LINE__)
#define NVTX_PRIVATE_CONCAT(a, b) NVTX_PRIVATE_CONCAT2(a, b)
#define NVTX_PRIVATE_CONCAT2(a, b) a##b
#ifndef NVTX_COLOR
#define NVTX_COLOR ::gpu::nvtx::kBlack
#endif
///////////////////////////////////////////////////////////////////////////////
struct Debug
{
const bool debug = false, end = true;
inline Debug() = default;
inline Debug(const int RANK, const char *FILE, const int LINE,
const char *FUNC, uint8_t COLOR, bool ini = true,
bool END = true): debug(true), end(END)
{
const char *base = static_strrnchr(FILE, '/', 2);
const char *file = base ? base + 1 : FILE;
const uint32_t rgb = static_color(COLOR, RANK, FILE);
const uint8_t r = (rgb >> 16) & 0xFF, g = (rgb >> 8) & 0xFF,
b = rgb & 0xFF;
std::cout << "\033[38;2;";
std::cout << std::to_string(r) << ";";
std::cout << std::to_string(g) << ";";
std::cout << std::to_string(b) << "m";
if (ini)
{
std::cout << RANK << std::setw(64) << file << ":";
std::cout << "\033[2m" << std::setw(4) << std::left << LINE
<< "\033[22m: ";
if (FUNC) { std::cout << "[" << FUNC << "] "; }
}
std::cout << std::right << "\033[1m";
}
inline ~Debug()
{
if (debug) { std::cout << "\033[m" << (end ? "\n" : "") << std::flush; }
}
template <typename T>
inline void operator<<(const T &arg) const noexcept
{
if (debug) { std::cout << arg; }
}
template <typename T>
inline void operator()(const T &arg) const noexcept
{
if (debug) { this->operator<<(arg); }
}
template <typename... Args>
inline void operator()(const char *fmt, Args &&...args) const noexcept
{
if (debug) { std::cout << fmt::format(fmt, std::forward<Args>(args)...); }
}
inline void operator()() const noexcept {}
static Debug Set(const char *FILE, const int LINE, const char *FUNC,
uint8_t COLOR, bool INI = true, bool END = true)
{
static int mpi_rank = 0, dbg_mpi_rank = 0;
static bool env_mpi = false, env_dbg = false;
if (static bool ini = false; !std::exchange(ini, true))
{
env_dbg = (getenv("MFEM_DEBUG") != nullptr);
env_mpi = getenv("MFEM_DEBUG_MPI") != nullptr;
// int mpi_flag = 0;
// MPI_Initialized(&mpi_flag);
// if (mpi_flag) { MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); }
dbg_mpi_rank = atoi(env_mpi ? getenv("MFEM_DEBUG_MPI") : "0");
}
const bool debug = (env_dbg && (!env_mpi || (dbg_mpi_rank == mpi_rank)));
return debug ? Debug(mpi_rank, FILE, LINE, FUNC, COLOR, INI, END)
: Debug();
}
};
// Debug console traces, unnamed
#define NVTX_DEBUG(...) \
::gpu::nvtx::Debug::Set(NVTX_FLF, NVTX_COLOR).operator()(__VA_ARGS__)
#define NVTX_DEBUG_NO_INI(...) \
::gpu::nvtx::Debug::Set(NVTX_FLF, NVTX_COLOR, false, true) \
.operator()(__VA_ARGS__)
#define NVTX_DEBUG_APPEND(...) \
::gpu::nvtx::Debug::Set(NVTX_FLF, NVTX_COLOR, false, false) \
.operator()(__VA_ARGS__)
#define NVTX_DEBUG_NO_END(...) \
::gpu::nvtx::Debug::Set(NVTX_FLF, NVTX_COLOR, true, false) \
.operator()(__VA_ARGS__)
///////////////////////////////////////////////////////////////////////////////
struct Nvtx
{
const bool nvtx = false, enforce_kernel_sync = false;
const char *base, *file;
const uint32_t color = kBlack;
mutable std::string ascii;
mutable nvtxEventAttributes_t event;
mutable bool pushed = false;
inline Nvtx() = default;
Nvtx(bool enforce_kernel_sync, const char *FILE, const int LINE,
const char *FUNC, uint8_t COLOR):
nvtx(true), enforce_kernel_sync(enforce_kernel_sync),
base(static_strrnchr(FILE, '/', 2)), file(base ? base + 1 : FILE),
color(COLOR), ascii(file), event({})
{
event.version = NVTX_VERSION;
event.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE;
event.colorType = NVTX_COLOR_ARGB;
event.color = static_color(COLOR, 0, FILE);
event.messageType = NVTX_MESSAGE_TYPE_ASCII;
ascii += ":";
ascii += std::to_string(LINE);
ascii += ":[";
ascii += FUNC;
ascii += "] ";
pushed = false;
}
explicit Nvtx(const char *title, uint8_t color = kWheat,
bool enforce_kernel_sync = true):
nvtx(true), enforce_kernel_sync(enforce_kernel_sync), color(color),
ascii(title), event({})
{
event.version = NVTX_VERSION;
event.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE;
event.colorType = NVTX_COLOR_ARGB;
event.color = static_color(color, 0, "");
event.messageType = NVTX_MESSAGE_TYPE_ASCII;
event.message.ascii = ascii.c_str();
nvtxRangePushEx(&event);
pushed = true;
}
inline void operator()() const
{
if (!nvtx) { return; }
event.message.ascii = ascii.c_str();
assert(!pushed);
nvtxRangePushEx(&event);
pushed = true;
}
template <typename T>
inline void operator()(const T &arg) const
{
if (!nvtx) { return; }
this->operator<<(arg);
event.message.ascii = ascii.c_str();
assert(!pushed);
nvtxRangePushEx(&event);
pushed = true;
}
template <typename... Args>
inline void operator()(fmt::format_string<Args...> fmt, Args &&...args) const
{
if (!nvtx) { return; }
ascii += fmt::format(fmt, std::forward<Args>(args)...);
event.message.ascii = ascii.c_str();
assert(!pushed);
nvtxRangePushEx(&event);
pushed = true;
}
template <typename T>
inline void operator<<(const T &arg) const
{
if (nvtx) { ascii += arg; }
}
inline ~Nvtx()
{
if (!nvtx) { return; }
if (enforce_kernel_sync)
{
nvtxEventAttributes_t eks = {};
eks.version = NVTX_VERSION;
eks.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE;
eks.category = 0; // user value
eks.colorType = NVTX_COLOR_ARGB;
eks.messageType = NVTX_MESSAGE_TYPE_ASCII;
eks.message.ascii = "!"; // enforce kernel synchronization
eks.color = kHexColors[kYellow];
nvtxRangePushEx(&eks);
cudaStreamSynchronize(nullptr);
nvtxRangePop(/*eks*/);
}
assert(pushed);
nvtxRangePop(/*event*/);
}
using nvtx_ptr = std::unique_ptr<Nvtx>;
using nvtx_stack_t = std::stack<nvtx_ptr>;
static nvtx_ptr Set(const char *FILE, const int LINE, const char *FUNC,
uint8_t COLOR)
{
static bool nvtx = false, eks = false;
if (static bool ini = false; !std::exchange(ini, true))
{
eks = getenv("MFEM_EKS") != nullptr;
nvtx = getenv("MFEM_NVTX") != nullptr;
Nvtx force_first_eks("Init EKS", kYellow, true);
}
return nvtx_ptr(nvtx ? new Nvtx(eks, FILE, LINE, FUNC, COLOR)
: new Nvtx());
}
static nvtx_stack_t &Stack()
{
auto nvtx_events = []() -> nvtx_stack_t &
{
static nvtx_stack_t events;
return events;
};
static std::once_flag ready;
// one touch to guarantee the object is ready
std::call_once(ready, [&] { nvtx_events(); });
return nvtx_events();
}
};
// Temporary object only alive for the current statement
#define NVTX_(COLOR, ...) \
NVTX_DEBUG(__VA_ARGS__); \
std::unique_ptr<::gpu::nvtx::Nvtx> NVTX_PRIVATE_NAME(nvtx) = \
::gpu::nvtx::Nvtx::Set(NVTX_FLF, COLOR); \
NVTX_PRIVATE_NAME(nvtx)->operator()(__VA_ARGS__)
// Temporary object only alive for the current statement
#define NVTX(...) NVTX_(NVTX_COLOR, __VA_ARGS__)
// Begin(with color)/End NVTX event traces
#define NVTX_BEGIN_(COLOR, ...) \
NVTX_DEBUG(__VA_ARGS__); \
::gpu::nvtx::Nvtx::Stack().push(::gpu::nvtx::Nvtx::Set(NVTX_FLF, COLOR)); \
::gpu::nvtx::Nvtx::Stack().top()->operator()(__VA_ARGS__)
// Begin/End NVTX event traces
#define NVTX_BEGIN(...) NVTX_BEGIN_(NVTX_COLOR, __VA_ARGS__);
#define NVTX_END(...) \
::gpu::nvtx::Nvtx::Stack().top().reset(); \
::gpu::nvtx::Nvtx::Stack().pop()
#ifdef USE_CALIPER
// CALIPER & NVTX marks
#define NVTX_MARK_FUNCTION \
NVTX(); \
std::unique_ptr<cali::Function> __cali_ann##__func__; \
__cali_ann##__func__ = std::make_unique<cali::Function>(__func__);
#define NVTX_MARK(...) \
NVTX(__VA_ARGS__); \
std::unique_ptr<cali::Function> __cali_ann##__func__; \
__cali_ann##__func__ = std::make_unique<cali::Function>(__VA_ARGS__);
#define NVTX_MARK_FUNCTION_NAME(STR_NAME) \
NVTX(STR_NAME); \
std::unique_ptr<cali::Function> __cali_ann##__func__; \
if (g_caliper) \
{ \
__cali_ann##__func__ = std::make_unique<cali::Function>(STR_NAME); \
}
#define NVTX_MARK_BEGIN(...) \
CALI_MARK_BEGIN(__VA_ARGS__); \
NVTX_BEGIN(__VA_ARGS__);
#define NVTX_MARK_END(...) \
NVTX_END(__VA_ARGS__); \
CALI_MARK_END(__VA_ARGS__);
#else
#define NVTX_MARK_FUNCTION NVTX()
#define NVTX_MARK(...) NVTX(__VA_ARGS__)
#define NVTX_MARK_FUNCTION_NAME(...) NVTX(__VA_ARGS__)
#define NVTX_MARK_BEGIN(...) NVTX_BEGIN(__VA_ARGS__)
#define NVTX_MARK_END(...) NVTX_END(__VA_ARGS__)
#endif
} // namespace gpu::nvtx
// Debug console traces, unnamed
#if 1
#define dbg(...) NVTX_DEBUG(__VA_ARGS__)
#define dbl(...) NVTX_DEBUG_NO_END(__VA_ARGS__)
#define dba(...) NVTX_DEBUG_APPEND(__VA_ARGS__)
#define dbc(...) NVTX_DEBUG_NO_INI(__VA_ARGS__)
#else
#define dbg(...)
#define dbl(...) (void)0
#define dba(...)
#define dbc(...)
#endif
inline bool ClearScreen()
{
dbg("\x1B[2J\x1B[3J\x1B[H");
return true;
}
@@ -0,0 +1,490 @@
// Copyright (c) 2010-2024, 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 "incompressible_navier_solver.hpp"
#define NVTX_COLOR ::gpu::nvtx::kCyan
#include "incompressible_navier_nvtx.hpp"
using namespace mfem;
using namespace incompressible_navier;
IncompressibleNavierSolver::IncompressibleNavierSolver(ParMesh *mesh,
int velorder, int porder,
int torder,
real_t kin_vis):
pmesh(mesh), velorder(velorder), porder(porder), torder(torder),
kin_vis(kin_vis), gll_rules(0, Quadrature1D::GaussLobatto),
vfec(new H1_FECollection(velorder, pmesh->Dimension())),
psifec(new H1_FECollection(porder)), pfec(new H1_FECollection(porder)),
vfes(new ParFiniteElementSpace(pmesh, vfec, pmesh->Dimension())),
psifes(new ParFiniteElementSpace(pmesh, pfec)),
pfes(new ParFiniteElementSpace(pmesh, pfec)),
velGF(torder + 1, nullptr), pGF(torder + 1, nullptr)
{
NVTX();
// Check if fully periodic mesh
if (!(pmesh->bdr_attributes.Size() == 0))
{
vel_ess_attr.SetSize(pmesh->bdr_attributes.Max());
vel_ess_attr = 0;
pres_ess_attr.SetSize(pmesh->bdr_attributes.Max());
pres_ess_attr = 0;
}
for (int i = 0; i < torder + 1; i++)
{
velGF[i] = new ParGridFunction(vfes);
*velGF[i] = 0.0;
pGF[i] = new ParGridFunction(pfes);
*pGF[i] = 0.0;
}
psiGF.SetSpace(psifes);
DvGF.SetSpace(vfes);
divVelGF.SetSpace(pfes);
pRHS.SetSpace(pfes);
}
void IncompressibleNavierSolver::Setup(real_t dt)
{
if (verbose && pmesh->GetMyRank() == 0)
{
mfem::out << "Setup" << std::endl;
if (partial_assembly)
{
mfem::out << "Using Partial Assembly" << std::endl;
}
else { mfem::out << "Using Full Assembly" << std::endl; }
}
this->Setup_velocity(dt);
this->Setup_auxiliary(dt);
this->Setup_pressure(dt);
}
void IncompressibleNavierSolver::Setup_velocity(real_t dt)
{
// GLL integration rule (Numerical Integration)
const IntegrationRule &ir_ni =
gll_rules.Get(vfes->GetFE(0)->GetGeomType(), 2 * velorder - 1);
vfes->GetEssentialTrueDofs(vel_ess_attr, vel_ess_tdof);
//-------------------------------------------------------------------------
// Setup of coefficient for mass term of Eq(13)
dtCoeff = new ConstantCoefficient(1.0 / dt);
auto *vmass_blfi = new VectorMassIntegrator(*dtCoeff);
// Setup of coefficient for stiffness term of Eq(13)
kinvisCoeff = new ConstantCoefficient(kin_vis);
auto *vdiff_blfi = new VectorDiffusionIntegrator(*kinvisCoeff);
// setup of Bilinear form of Eq(13)
velBForm = new ParBilinearForm(vfes);
if (numerical_integ)
{
vmass_blfi->SetIntRule(&ir_ni);
vdiff_blfi->SetIntRule(&ir_ni);
}
velBForm->AddDomainIntegrator(vmass_blfi);
velBForm->AddDomainIntegrator(vdiff_blfi);
if (partial_assembly) { velBForm->SetAssemblyLevel(AssemblyLevel::PARTIAL); }
velBForm->Assemble();
velBForm->FormSystemMatrix(vel_ess_tdof, vOp);
//-------------------------------------------------------------------------
// Setup of coefficient for Eq(18)
pUnitVectorCoeff = new UnitVectorGridFunctionCoeff(pmesh->Dimension());
auto *pvel_lfi = new VectorDomainLFGradIntegrator(*pUnitVectorCoeff);
// Setup of coefficient for Eq(20)
nonlinTermCoeff = new NonLinTermVectorGridFunctionCoeff(pmesh->Dimension());
auto *p_nonlintermlfi = new VectorDomainLFIntegrator(*nonlinTermCoeff);
// Setup of coefficient for Eq(21)
prevVelLoadCoeff = new PrevVelVectorGridFunctionCoeff(pmesh->Dimension());
auto *prevVelLoadLFi = new VectorDomainLFIntegrator(*prevVelLoadCoeff);
// Setup of linear form of Eq(13)
velLForm = new ParLinearForm(vfes);
if (numerical_integ)
{
prevVelLoadLFi->SetIntRule(&ir_ni);
pvel_lfi->SetIntRule(&ir_ni);
p_nonlintermlfi->SetIntRule(&ir_ni);
}
velLForm->AddDomainIntegrator(prevVelLoadLFi);
velLForm->AddDomainIntegrator(pvel_lfi);
velLForm->AddDomainIntegrator(p_nonlintermlfi);
//-------------------------------------------------------------------------
if (partial_assembly)
{
Vector diag_pa(vfes->GetTrueVSize());
velBForm->AssembleDiagonal(diag_pa);
velInvPC = new OperatorJacobiSmoother(diag_pa, vel_ess_tdof);
}
else
{
velInvPC = new HypreSmoother(*vOp.As<HypreParMatrix>());
dynamic_cast<HypreSmoother *>(velInvPC)->SetType(HypreSmoother::Jacobi,
1);
}
velInv = new CGSolver(vfes->GetComm());
velInv->iterative_mode = true;
velInv->SetOperator(*vOp);
velInv->SetPreconditioner(*velInvPC);
velInv->SetPrintLevel(pl_velsolve);
velInv->SetRelTol(rtol_velsolve);
velInv->SetAbsTol(0.0);
velInv->SetMaxIter(1200);
}
void IncompressibleNavierSolver::Setup_auxiliary(real_t dt)
{
// GLL integration rule (Numerical Integration)
const IntegrationRule &ir_ni =
gll_rules.Get(vfes->GetFE(0)->GetGeomType(), 2 * velorder - 1);
Array<int> empty;
// setup of Bilinear form of Eq(14)
psiBForm = new ParBilinearForm(psifes);
auto *psidiff_blfi = new DiffusionIntegrator;
if (numerical_integ) { psidiff_blfi->SetIntRule(&ir_ni); }
psiBForm->AddDomainIntegrator(psidiff_blfi);
if (partial_assembly) { psiBForm->SetAssemblyLevel(AssemblyLevel::PARTIAL); }
psiBForm->Assemble();
psiBForm->FormSystemMatrix(empty, psiOp);
//-------------------------------------------------------------------------
// Setup of coefficient for linear form in Eq(14)
DvelCoeff = new VectorGridFunctionCoefficient;
auto *Dvel_lfi = new DomainLFGradIntegrator(*DvelCoeff);
// Setup of linear form of Eq(14)
psiLForm = new ParLinearForm(psifes);
if (numerical_integ) { Dvel_lfi->SetIntRule(&ir_ni); }
psiLForm->AddDomainIntegrator(Dvel_lfi);
//-------------------------------------------------------------------------
if (partial_assembly)
{
int psifes_truevsize = psifes->GetTrueVSize();
mfem::Vector psin(psifes_truevsize);
psin = 0.0;
mfem::Vector respsi(psifes_truevsize);
respsi = 0.0;
lor = new ParLORDiscretization(*psiBForm, empty);
psiInvPC = new HypreBoomerAMG(lor->GetAssembledMatrix());
psiInvPC->SetPrintLevel(0);
psiInvPC->Mult(respsi, psin);
SpInvOrthoPC = new OrthoSolver(psifes->GetComm());
SpInvOrthoPC->SetSolver(*psiInvPC);
}
else
{
psiInvPC = new HypreBoomerAMG(*psiOp.As<HypreParMatrix>());
psiInvPC->SetPrintLevel(0);
SpInvOrthoPC = new OrthoSolver(psifes->GetComm());
SpInvOrthoPC->SetSolver(*psiInvPC);
}
psiInv = new CGSolver(psifes->GetComm());
psiInv->iterative_mode = true;
psiInv->SetOperator(*psiOp);
psiInv->SetPreconditioner(*SpInvOrthoPC);
psiInv->SetPrintLevel(pl_psisolve);
psiInv->SetRelTol(rtol_psisolve);
psiInv->SetAbsTol(0.0);
psiInv->SetMaxIter(1000);
}
void IncompressibleNavierSolver::Setup_pressure(real_t dt)
{
// GLL integration rule (Numerical Integration)
const IntegrationRule &ir_ni =
gll_rules.Get(vfes->GetFE(0)->GetGeomType(), 2 * velorder - 1);
Array<int> empty;
//-------------------------------------------------------------------------
// setup of Bilinear form of Eq(15)
pBForm = new ParBilinearForm(pfes);
auto *pmass_blfi = new MassIntegrator;
if (numerical_integ) { pmass_blfi->SetIntRule(&ir_ni); }
pBForm->AddDomainIntegrator(pmass_blfi);
if (partial_assembly) { pBForm->SetAssemblyLevel(AssemblyLevel::PARTIAL); }
pBForm->Assemble();
pBForm->FormSystemMatrix(empty, pOp);
//-------------------------------------------------------------------------
// Setup of divergence of velocity coefficient for linear form in Eq(15)
divVelCoeff = new DivergenceGridFunctionCoefficient(velGF[0]);
// Setup of coefficient for linear form in Eq(14)
pRHSCoeff = new GridFunctionCoefficient(&pRHS);
auto *p_lfi = new DomainLFIntegrator(*pRHSCoeff);
// Setup of linear form of Eq(15)
pLForm = new ParLinearForm(pfes);
if (numerical_integ) { p_lfi->SetIntRule(&ir_ni); }
pLForm->AddDomainIntegrator(p_lfi);
//-------------------------------------------------------------------------
if (partial_assembly)
{
Vector diag_pa(pfes->GetTrueVSize());
pBForm->AssembleDiagonal(diag_pa);
pInvPC = new OperatorJacobiSmoother(diag_pa, empty);
}
else
{
pInvPC = new HypreSmoother(*pOp.As<HypreParMatrix>());
dynamic_cast<HypreSmoother *>(pInvPC)->SetType(HypreSmoother::Jacobi, 1);
}
pInv = new CGSolver(pfes->GetComm());
pInv->iterative_mode = true;
pInv->SetOperator(*pOp);
pInv->SetPreconditioner(*pInvPC);
pInv->SetPrintLevel(pl_psolve);
pInv->SetRelTol(rtol_psolve);
pInv->SetAbsTol(0.0);
pInv->SetMaxIter(1000);
}
void IncompressibleNavierSolver::UpdateTimestepHistory(real_t dt) {}
void IncompressibleNavierSolver::Step(real_t &time, real_t dt, int current_step,
const bool vis_step)
{
this->Step_velocity(time, dt, current_step);
this->Step_auxiliary(time, dt, current_step);
this->Step_pressure(time, dt, current_step);
*velGF[1] = *velGF[0];
*pGF[1] = *pGF[0];
if (vis_step)
{
mfem::out << "It: " << iter << " | Iter_U: " << iter_vsolve
<< " | Iter_Psi: " << iter_psisolve
<< " | Iter_P: " << iter_psolve << "\n";
mfem::out << "It: " << iter << " | Resid_U: " << res_vsolve
<< " | Resid_Psi: " << res_psisolve
<< " | Resid_P: " << res_psisolve << "\n";
}
time += dt;
iter++;
}
void IncompressibleNavierSolver::Step_velocity(real_t &time, real_t dt,
int current_step)
{
for (auto &vel_dbc : vel_dbcs)
{
velGF[0]->ProjectBdrCoefficient(*vel_dbc.coeff, vel_dbc.attr);
velGF[1]->ProjectBdrCoefficient(*vel_dbc.coeff, vel_dbc.attr);
}
// Update state in coefficient for Eq(18)
pUnitVectorCoeff->SetGridFunction(pGF[1]);
// Update state in coefficient for Eq(20)
nonlinTermCoeff->SetGridFunction(velGF[1]);
// Update state in coefficient for Eq(21)
prevVelLoadCoeff->SetGridFunction(velGF[1], dt);
velLForm->Assemble();
velLForm->ParallelAssemble(velLF);
Vector X1, B1;
if (partial_assembly)
{
auto *vpC = vOp.As<ConstrainedOperator>();
EliminateRHS(*velBForm, *vpC, vel_ess_tdof, *velGF[0], velLF, X1, B1, 1);
}
else
{
velBForm->FormLinearSystem(vel_ess_tdof, *velGF[0], velLF, vOp, X1, B1,
1);
}
velInv->Mult(B1, X1);
iter_vsolve = velInv->GetNumIterations();
res_vsolve = velInv->GetFinalNorm();
velBForm->RecoverFEMSolution(X1, velLF, *velGF[0]);
}
void IncompressibleNavierSolver::Step_auxiliary(real_t &time, real_t dt,
int current_step)
{
// Compute new increment GF for LF of Eq(14) and update state in coefficient
subtract(1.0 / dt, *velGF[0], *velGF[1], DvGF);
DvelCoeff->SetGridFunction(&DvGF);
psiLForm->Assemble();
psiLForm->ParallelAssemble(psiLF);
Vector X2, B2;
Array<int> empty;
if (partial_assembly)
{
auto *psipC = psiOp.As<ConstrainedOperator>();
EliminateRHS(*psiBForm, *psipC, empty, psiGF, psiLF, X2, B2, 1);
}
else { psiBForm->FormLinearSystem(empty, psiGF, psiLF, psiOp, X2, B2, 1); }
psiInv->Mult(B2, X2);
iter_psisolve = psiInv->GetNumIterations();
res_psisolve = psiInv->GetFinalNorm();
psiBForm->RecoverFEMSolution(X2, psiLF, psiGF);
}
void IncompressibleNavierSolver::Step_pressure(real_t &time, real_t dt,
int current_step)
{
Array<int> empty;
// Compute new GF for LF of Eq(15) and update state in coefficient
divVelCoeff->SetGridFunction(velGF[0]);
divVelGF.ProjectCoefficient(*divVelCoeff);
add(*pGF[1], psiGF, pRHS);
add(pRHS, -1.0 * kin_vis, divVelGF, pRHS);
pRHSCoeff->SetGridFunction(&pRHS);
pLForm->Assemble();
pLForm->ParallelAssemble(pLF);
Vector X3, B3;
if (partial_assembly)
{
auto *ppC = pOp.As<ConstrainedOperator>();
EliminateRHS(*pBForm, *ppC, empty, *pGF[0], pLF, X3, B3, 1);
}
else { pBForm->FormLinearSystem(empty, *pGF[0], pLF, pOp, X3, B3, 1); }
pInv->Mult(B3, X3);
iter_psolve = pInv->GetNumIterations();
res_psisolve = pInv->GetFinalNorm();
pBForm->RecoverFEMSolution(X3, pLF, *pGF[0]);
}
void IncompressibleNavierSolver::EliminateRHS(Operator &A,
ConstrainedOperator &constrainedA,
const Array<int> &ess_tdof_list,
Vector &x, Vector &b, Vector &X,
Vector &B, int copy_interior)
{
const Operator *Po = A.GetOutputProlongation();
const Operator *Pi = A.GetProlongation();
const Operator *Ri = A.GetRestriction();
A.InitTVectors(Po, Ri, Pi, x, b, X, B);
if (!copy_interior) { X.SetSubVectorComplement(ess_tdof_list, 0.0); }
constrainedA.EliminateRHS(X, B);
}
real_t IncompressibleNavierSolver::ComputeCFL(ParGridFunction &u, real_t dt)
{
return 0;
}
void IncompressibleNavierSolver::AddVelDirichletBC(VectorCoefficient *coeff,
Array<int> &attr)
{
vel_dbcs.emplace_back(attr, coeff);
if (verbose && pmesh->GetMyRank() == 0)
{
mfem::out << "Adding Velocity Dirichlet BC to attributes ";
for (int i = 0; i < attr.Size(); ++i)
{
if (attr[i] == 1) { mfem::out << i << " "; }
}
mfem::out << std::endl;
}
for (int i = 0; i < attr.Size(); ++i)
{
MFEM_ASSERT((vel_ess_attr[i] && attr[i]) == 0,
"Duplicate boundary definition deteceted.");
if (attr[i] == 1) { vel_ess_attr[i] = 1; }
}
}
void IncompressibleNavierSolver::AddVelDirichletBC(VecFuncT *f,
Array<int> &attr)
{
AddVelDirichletBC(new VectorFunctionCoefficient(pmesh->Dimension(), f),
attr);
}
IncompressibleNavierSolver::~IncompressibleNavierSolver()
{
delete velBForm;
delete psiBForm;
delete pBForm;
delete kinvisCoeff;
delete dtCoeff;
for (int i = 0; i < torder + 1; i++)
{
delete velGF[i];
delete pGF[i];
}
delete DvelCoeff;
delete divVelCoeff;
delete pRHSCoeff;
delete pUnitVectorCoeff;
delete velInv;
delete velInvPC;
delete psiInv;
delete SpInvOrthoPC;
delete psiInvPC;
delete lor;
delete pInv;
delete pInvPC;
delete vfec;
delete psifec;
delete pfec;
delete vfes;
delete psifes;
delete pfes;
}
@@ -0,0 +1,338 @@
// Copyright (c) 2010-2024, 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.
#pragma once
#define INCOMP_NAVIER_VERSION 0.1
#include "mfem.hpp"
namespace mfem::incompressible_navier
{
using VecFuncT = void(const Vector &x, real_t t, Vector &u);
using ScalarFuncT = real_t(const Vector &x, real_t t);
// Coefficient which computed contribution of Eq(18)
class UnitVectorGridFunctionCoeff : public VectorCoefficient
{
public:
UnitVectorGridFunctionCoeff(int dim): VectorCoefficient(dim * dim) {}
void Eval(Vector &V, ElementTransformation &T,
const IntegrationPoint &ip) override
{
real_t coeffVal = gridfunc_->GetValue(T, ip);
V.SetSize(vdim);
V = 0.0; // FIXME
V[0] = coeffVal;
V[3] = coeffVal;
}
void SetGridFunction(GridFunction *gridfunc) { gridfunc_ = gridfunc; }
GridFunction *gridfunc_ = nullptr;
};
// Coefficient which computed contribution of Eq(21)
class PrevVelVectorGridFunctionCoeff : public VectorCoefficient
{
public:
PrevVelVectorGridFunctionCoeff(int dim): VectorCoefficient(dim) {}
void Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip)
{
V.SetSize(vdim);
gridFuncCoeff->Eval(V, T, ip);
V *= 1.0 / dt_;
}
void SetGridFunction(GridFunction *gridfunc, real_t dt)
{
gridfunc_ = gridfunc;
dt_ = dt;
delete gridFuncCoeff;
gridFuncCoeff = new VectorGridFunctionCoefficient(gridfunc);
}
GridFunction *gridfunc_ = nullptr;
VectorGridFunctionCoefficient *gridFuncCoeff = nullptr;
real_t dt_;
};
// Coefficient which computed contribution of Eq(20)
class NonLinTermVectorGridFunctionCoeff : public VectorCoefficient
{
public:
NonLinTermVectorGridFunctionCoeff(int dim): VectorCoefficient(dim) {}
void Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip)
{
Vector val(vdim);
Vector resultVal(vdim);
DenseMatrix vecGrad;
V.SetSize(vdim);
gridFuncCoeff->Eval(val, T, ip);
gridfunc_->GetVectorGradient(T, vecGrad);
vecGrad.MultTranspose(val, V);
V *= -1.0;
}
void SetGridFunction(ParGridFunction *gridfunc)
{
delete gridFuncCoeff;
gridfunc_ = gridfunc;
gridFuncCoeff = new VectorGridFunctionCoefficient(gridfunc);
}
VectorGridFunctionCoefficient *gridFuncCoeff = nullptr;
ParGridFunction *gridfunc_ = nullptr;
};
/// Container for a Dirichlet boundary condition of the velocity field.
class VelDirichletBC_T
{
public:
VelDirichletBC_T(Array<int> attr, VectorCoefficient *coeff):
attr(attr), coeff(coeff)
{
}
VelDirichletBC_T(VelDirichletBC_T &&obj)
{
// Deep copy the attribute array
this->attr = obj.attr;
// Move the coefficient pointer
this->coeff = obj.coeff;
obj.coeff = nullptr;
}
~VelDirichletBC_T() { delete coeff; }
Array<int> attr;
VectorCoefficient *coeff;
};
/// Transient incompressible Navier Stokes solver in a split scheme formulation.
/**
* This implementation of a transient incompressible Navier Stokes solver uses
* the non-dimensionalized formulation. The coupled momentum and
* incompressibility equations are decoupled using the split scheme described in
* [1]. This leads to three solving steps.
*
*/
class IncompressibleNavierSolver
{
public:
/// Initialize data structures, set FE space order and kinematic viscosity.
/**
* The ParMesh @a mesh can be a linear or curved parallel mesh. The @a order
* of the finite element spaces is
*/
IncompressibleNavierSolver(ParMesh *mesh, int velorder, int porder,
int tOrder, real_t kin_vis);
/// Initialize forms, solvers and preconditioners.
void Setup(real_t dt);
void Setup_velocity(real_t dt);
void Setup_auxiliary(real_t dt);
void Setup_pressure(real_t dt);
/// Compute solution at the next time step t+dt.
/**
* This method can
*/
void Step(real_t &time, real_t dt, int cur_step, bool vis_step);
void Step_velocity(real_t &time, real_t dt, int cur_step);
void Step_auxiliary(real_t &time, real_t dt, int cur_step);
void Step_pressure(real_t &time, real_t dt, int cur_step);
/// Return a pointer to the provisional velocity ParGridFunction.
ParGridFunction *GetProvisionalVelocity() { return velGF[1]; }
/// Return a pointer to the current velocity ParGridFunction.
ParGridFunction *GetCurrentVelocity() { return velGF[0]; }
/// Return a pointer to the current pressure ParGridFunction.
ParGridFunction *GetCurrentPressure() { return pGF[0]; }
/// Return a pointer to the current pressure ParGridFunction.
ParGridFunction *GetCurrentPsi() { return &psiGF; }
/// Add a Dirichlet boundary condition to the velocity field.
void AddVelDirichletBC(VectorCoefficient *coeff, Array<int> &attr);
void AddVelDirichletBC(VecFuncT *f, Array<int> &attr);
/// Add a Dirichlet boundary condition to the pressure field.
// void AddPresDirichletBC(Coefficient *coeff, Array<int> &attr);
// void AddPresDirichletBC(ScalarFuncT *f, Array<int> &attr);
/// Enable partial assembly for every operator.
void EnablePA(bool pa) { partial_assembly = pa; }
/// Enable numerical integration rules. This means collocated quadrature at
/// the nodal points.
void EnableNI(bool ni) { numerical_integ = ni; }
/// Print timing summary of the solving routine.
void PrintTimingData();
~IncompressibleNavierSolver();
/// Rotate entries in the time step and solution history arrays.
void UpdateTimestepHistory(real_t dt);
/// Compute CFL
real_t ComputeCFL(ParGridFunction &u, real_t dt);
protected:
/// Eliminate essential BCs in an Operator and apply to RHS.
void EliminateRHS(Operator &A, ConstrainedOperator &constrainedA,
const Array<int> &ess_tdof_list, Vector &x, Vector &b,
Vector &X, Vector &B, int copy_interior = 0);
/// Enable/disable debug output.
bool debug = false;
/// Enable/disable verbose output.
bool verbose = true;
/// Enable/disable partial assembly of forms.
bool partial_assembly = false;
/// Enable/disable numerical integration rules of forms.
bool numerical_integ = false;
/// The parallel mesh.
ParMesh *pmesh = nullptr;
/// The order of the velocity and pressure space.
const int velorder;
const int porder;
const int torder;
/// Kinematic viscosity (dimensionless).
const real_t kin_vis;
Coefficient *kinvisCoeff = nullptr;
Coefficient *dtCoeff = nullptr;
IntegrationRules gll_rules;
/// Velocity $H^1$ finite element collection.
FiniteElementCollection *vfec = nullptr;
/// Psi $H^1$ finite element collection.
FiniteElementCollection *psifec = nullptr;
/// Pressure $H^1$ finite element collection.
FiniteElementCollection *pfec = nullptr;
/// Velocity $(H^1)^d$ finite element space.
ParFiniteElementSpace *vfes = nullptr;
/// Psi $(H^1)^d$ finite element space.
ParFiniteElementSpace *psifes = nullptr;
/// Pressure $H^1$ finite element space.
ParFiniteElementSpace *pfes = nullptr;
ParBilinearForm *velBForm = nullptr; // vmass + vdiff
ParBilinearForm *psiBForm = nullptr; // diffusion
ParBilinearForm *pBForm = nullptr; // mass
ParLinearForm *velLForm = nullptr; // vLF + vLFGrad + vLF
ParLinearForm *psiLForm = nullptr; // LFGrad
ParLinearForm *pLForm = nullptr; // LF
// current (0) and provisional (1) velocity
std::vector<ParGridFunction *> velGF;
// current (0) pressure ParGridFunction.
std::vector<ParGridFunction *> pGF;
ParGridFunction psiGF;
ParGridFunction DvGF, divVelGF, pRHS;
VectorGridFunctionCoefficient *DvelCoeff = nullptr;
DivergenceGridFunctionCoefficient *divVelCoeff = nullptr;
GridFunctionCoefficient *pRHSCoeff = nullptr;
UnitVectorGridFunctionCoeff *pUnitVectorCoeff = nullptr;
NonLinTermVectorGridFunctionCoeff *nonlinTermCoeff = nullptr;
PrevVelVectorGridFunctionCoeff *prevVelLoadCoeff = nullptr;
OperatorHandle vOp;
OperatorHandle psiOp;
OperatorHandle pOp;
Solver *velInvPC = nullptr;
CGSolver *velInv = nullptr;
ParLORDiscretization *lor = nullptr;
HypreBoomerAMG *psiInvPC = nullptr;
OrthoSolver *SpInvOrthoPC = nullptr;
CGSolver *psiInv = nullptr;
Solver *pInvPC = nullptr;
CGSolver *pInv = nullptr;
Vector velLF, psiLF, pLF;
// All essential attributes.
Array<int> vel_ess_attr;
Array<int> pres_ess_attr;
// All essential true dofs.
Array<int> vel_ess_tdof;
Array<int> pres_ess_tdof;
// Bookkeeping for velocity dirichlet bcs.
std::vector<VelDirichletBC_T> vel_dbcs;
// Print levels.
int pl_psolve = 0;
int pl_psisolve = 0;
int pl_velsolve = 0;
int pl_amg = 0;
#if defined(MFEM_USE_DOUBLE)
real_t rtol_psolve = 1e-12;
real_t rtol_psisolve = 1e-12;
real_t rtol_velsolve = 1e-12;
#elif defined(MFEM_USE_SINGLE)
real_t rtol_psolve = 1e-9;
real_t rtol_psisolve = 1e-5;
real_t rtol_velsolve = 1e-7;
#else
#error "Only single and double precision are supported!"
#endif
// Iteration counts.
int iter = 1, iter_vsolve = 0, iter_psolve = 0, iter_psisolve = 0;
// Residuals.
real_t res_vsolve = 0.0, res_psolve = 0.0, res_psisolve = 0.0;
};
} // namespace mfem::incompressible_navier
@@ -0,0 +1,206 @@
#include <algorithm>
#include <iostream>
#include <memory>
#include <sstream>
#include <unistd.h>
#define NVTX_COLOR ::gpu::nvtx::kMagenta
#include "incompressible_navier_nvtx.hpp"
///////////////////////////////////////////////////////////////////////////////
int navier(int argc, char *argv[], double &u, double &p, double &Ψ);
///////////////////////////////////////////////////////////////////////////////
template <class T>
std::enable_if_t<!std::numeric_limits<T>::is_integer, bool>
AlmostEq(T x, T y, T tolerance = 100.0 * std::numeric_limits<T>::epsilon())
{
const T neg = std::abs(x - y);
constexpr T min = std::numeric_limits<T>::min();
constexpr T eps = std::numeric_limits<T>::epsilon();
const T min_abs = std::min(std::abs(x), std::abs(y));
if (std::abs(min_abs) == 0.0) { return neg < eps; }
return (neg / (1.0 + std::max(min, min_abs))) < tolerance;
}
///////////////////////////////////////////////////////////////////////////////
using char_uptr = std::unique_ptr<char[]>;
using args_ptr_t = std::vector<char_uptr>;
using args_t = std::vector<char *>;
///////////////////////////////////////////////////////////////////////////////
struct Results
{
double u{}, p{}, Ψ {};
};
///////////////////////////////////////////////////////////////////////////////
struct Test
{
static constexpr const char *binary = "incompNS_2Dtest ";
static constexpr const char *common = "-no-vis -no-pv";
const std::string options;
const Results results;
Test(const char *args, const Results &res):
options(std::string(args) + " " + common), results(res)
{
dbg("options: {}", options.c_str());
dbg("results: U={:.15e}, P={:.15e}, Ψ={:.15e}",
results.u, results.p, results.Ψ);
}
std::string Command() const { return binary + options; }
};
///////////////////////////////////////////////////////////////////////////////
#if 1 // dot product reduction (miniapps/navier/incompNS_2Dtest.cpp#L182)
static const Test gold[] =
{
{
"-nx 9 -ny 3 -sr 0",
{ 3.056430866716070e-06, 1.504027632950462e-01, 7.132601171183242e-08 }
},
{
"-nx 16 -ny 8 -sr 0",
{ 1.409287729554512e-05, 5.718053801962010e-01, 1.904938419441012e-07 }
},
// {
// "-nx 9 -ny 3 -sr 1",
// { 1.23258426138828e-05, 5.18207619597952e-01, 1.956175418199867e-07 }
// },
// {
// "-nx 9 -ny 3 -sr 2",
// { 4.74381190869396e-05, 1.80653923294262e+00, 5.90778654333642e-07 }
// },
};
#else // Norml2 reduction
///////////////////////////////////////////////////////////////////////////////
const Test runs[] =
{
{
"-nx 9 -ny 3 -sr 0",
{ 1.746844586767688e-03, 3.874421956807944e-01, 2.670296315544763e-04 }
},
// 1.748265101955712e-03, 3.878179512284757e-01, 2.670693013280680e-04 //
// Release { "-nx 9 -ny 3 -sr 1",
// { 1.232584261388279e-05, 5.182076195979519e-01, 1.956175418199866e-07 }
// },
// { "-nx 9 -ny 3 -sr 2",
// { 4.743811908693962e-05, 1.806539232942622e+00, 5.907786543336419e-07 }
// },
};
#endif
///////////////////////////////////////////////////////////////////////////////
int NavierTest(const int k, const Test &run)
{
dbg();
static args_ptr_t args_ptr;
args_t args;
std::istringstream iss(run.Command());
auto add_arg = [&](std::string token) -> char_uptr
{
auto arg_ptr = std::make_unique<char[]>(token.size() + 1);
std::memcpy(arg_ptr.get(), token.c_str(), token.size() + 1);
arg_ptr[token.size()] = '\0';
return arg_ptr;
};
std::string token;
while (iss >> token)
{
auto arg_ptr = add_arg(token);
args.push_back(arg_ptr.get());
args_ptr.emplace_back(std::move(arg_ptr));
}
args.push_back(nullptr);
auto launch = [&args, &run, &k]() -> int
{
// dbg("Launching test #{}: \x1B[33m{}\x1B[m", k, gold.Command().c_str());
Results res{};
navier(args.size() - 1, args.data(), res.u, res.p, res.Ψ);
// dbg("Results: U={:.15e}, P={:.15e}, Ψ={:.15e}", res.u, res.p, res.Ψ);
const bool u = AlmostEq(res.u, run.results.u);
const bool p = AlmostEq(res.p, run.results.p);
const bool Ψ = AlmostEq(res.Ψ, run.results.Ψ);
constexpr auto ok = [](bool ok) -> int { return ok ? 32 : 31; };
constexpr auto to_string = [](args_t &args) -> std::string
{
std::string args_str;
for (auto &arg : args)
{
if (!arg) { break; }
args_str += std::string(arg) + " ";
}
return args_str;
};
dbg("#{} \x1B[33m{}\x1B[m", k, to_string(args).c_str());
dbg("U: \x1B[33m{:.15e} \x1B[{}m{:.15e}", run.results.u, ok(u), res.u );
dbg("P: \x1B[33m{:.15e} \x1B[{}m{:.15e}", run.results.p, ok(p), res.p);
dbg("Ψ: \x1B[33m{:.15e} \x1B[{}m{:.15e}", run.results.Ψ, ok(Ψ), res.Ψ);
if (u && p && Ψ) { return std::cout << "" << std::endl, EXIT_SUCCESS; }
else { return std::cout << "" << std::endl, EXIT_FAILURE; }
};
// first launch with default arguments
if (launch() != EXIT_SUCCESS) { return EXIT_FAILURE; }
// second launch with the same arguments, but with -pa
args.pop_back(); // nullptr
auto arg_pa_ptr = add_arg("-pa");
args.push_back(arg_pa_ptr.get());
args_ptr.emplace_back(std::move(arg_pa_ptr));
args.push_back(nullptr);
if (launch() != EXIT_SUCCESS) { return EXIT_FAILURE; }
return EXIT_SUCCESS;
}
///////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[])
try
{
dbg();
int opt;
int test = -1;
auto show_usage = [](const int ret = EXIT_FAILURE)
{
printf("Usage: program [-a <arg>] [-b <arg>] [-h]\n");
printf(" -t <test> Optional test number \n");
printf(" -h Show this help message\n");
exit(ret);
};
while ((opt = getopt(argc, argv, "t:h")) != -1)
{
switch (opt)
{
case 't': test = std::atoi(optarg); break;
case 'h': show_usage(EXIT_SUCCESS);
default: show_usage(EXIT_FAILURE);
}
}
constexpr int N_TESTS = sizeof(gold) / sizeof(Test);
if (test >= 0 && test < N_TESTS) { return NavierTest(test, gold[test]); }
int k = 0;
for (auto &run : gold)
{
if (NavierTest(k++, run) != EXIT_SUCCESS) { return EXIT_FAILURE; }
}
return EXIT_SUCCESS;
}
catch (std::exception &e)
{
std::cerr << "\033[31m..xxxXXX[ERROR]XXXxxx.." << std::endl;
std::cerr << "\033[31m{}" << e.what() << std::endl;
return EXIT_FAILURE;
}