Files
mfem/miniapps/navier/navier_tgv.cpp
T
2020-04-07 15:11:54 -07:00

406 lines
11 KiB
C++

// Copyright (c) 2010-2020, 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 Taylor-Green vortex benchmark example at Re=1600
// Unsteady flow of a decaying vortex is computed and compared against a known,
// analytical solution.
#include "navier_solver.hpp"
#include <fstream>
using namespace mfem;
using namespace navier;
struct s_NavierContext
{
int element_subdivisions = 1;
int order = 4;
double kinvis = 1.0 / 1600.0;
double t_final = 10 * 1e-3;
double dt = 1e-3;
bool pa = true;
bool ni = false;
bool visualization = false;
bool checkres = false;
} ctx;
void vel_tgv(const Vector &x, double t, Vector &u)
{
double xi = x(0);
double yi = x(1);
double zi = x(2);
u(0) = sin(xi) * cos(yi) * cos(zi);
u(1) = -cos(xi) * sin(yi) * cos(zi);
u(2) = 0.0;
}
class QuantitiesOfInterest
{
public:
QuantitiesOfInterest(ParMesh *pmesh)
{
H1_FECollection h1fec(1);
ParFiniteElementSpace h1fes(pmesh, &h1fec);
onecoeff.constant = 1.0;
mass_lf = new ParLinearForm(&h1fes);
mass_lf->AddDomainIntegrator(new DomainLFIntegrator(onecoeff));
mass_lf->Assemble();
ParGridFunction one_gf(&h1fes);
one_gf.ProjectCoefficient(onecoeff);
volume = mass_lf->operator()(one_gf);
};
double ComputeKineticEnergy(ParGridFunction &v)
{
Vector velx, vely, velz;
double integ = 0.0;
const FiniteElement *fe;
ElementTransformation *T;
FiniteElementSpace *fes = v.FESpace();
for (int i = 0; i < fes->GetNE(); i++)
{
fe = fes->GetFE(i);
double intorder = 2 * fe->GetOrder();
const IntegrationRule *ir = &(
IntRules.Get(fe->GetGeomType(), intorder));
v.GetValues(i, *ir, velx, 1);
v.GetValues(i, *ir, vely, 2);
v.GetValues(i, *ir, velz, 3);
T = fes->GetElementTransformation(i);
for (int j = 0; j < ir->GetNPoints(); j++)
{
const IntegrationPoint &ip = ir->IntPoint(j);
T->SetIntPoint(&ip);
double vel2 = velx(j) * velx(j) + vely(j) * vely(j)
+ velz(j) * velz(j);
integ += ip.weight * T->Weight() * vel2;
}
}
double global_integral = 0.0;
MPI_Allreduce(&integ,
&global_integral,
1,
MPI_DOUBLE,
MPI_SUM,
MPI_COMM_WORLD);
return 0.5 * global_integral / volume;
};
~QuantitiesOfInterest() { delete mass_lf; };
private:
ConstantCoefficient onecoeff;
ParLinearForm *mass_lf;
double volume;
};
template<typename T>
T sq(T x)
{
return x * x;
}
// Computes Q = 0.5*(tr(\nabla u)^2 - tr(\nabla u \cdot \nabla u))
void ComputeQCriterion(ParGridFunction &u, ParGridFunction &q)
{
FiniteElementSpace *v_fes = u.FESpace();
FiniteElementSpace *fes = q.FESpace();
// AccumulateAndCountZones
Array<int> zones_per_vdof;
zones_per_vdof.SetSize(fes->GetVSize());
zones_per_vdof = 0;
q = 0.0;
// Local interpolation
int elndofs;
Array<int> v_dofs, dofs;
Vector vals;
Vector loc_data;
int vdim = v_fes->GetVDim();
DenseMatrix grad_hat;
DenseMatrix dshape;
DenseMatrix grad;
for (int e = 0; e < fes->GetNE(); ++e)
{
fes->GetElementVDofs(e, dofs);
v_fes->GetElementVDofs(e, v_dofs);
u.GetSubVector(v_dofs, loc_data);
vals.SetSize(dofs.Size());
ElementTransformation *tr = fes->GetElementTransformation(e);
const FiniteElement *el = fes->GetFE(e);
elndofs = el->GetDof();
int dim = el->GetDim();
dshape.SetSize(elndofs, dim);
for (int dof = 0; dof < elndofs; ++dof)
{
// Project
const IntegrationPoint &ip = el->GetNodes().IntPoint(dof);
tr->SetIntPoint(&ip);
// Eval
// GetVectorGradientHat
el->CalcDShape(tr->GetIntPoint(), dshape);
grad_hat.SetSize(vdim, dim);
DenseMatrix loc_data_mat(loc_data.GetData(), elndofs, vdim);
MultAtB(loc_data_mat, dshape, grad_hat);
const DenseMatrix &Jinv = tr->InverseJacobian();
grad.SetSize(grad_hat.Height(), Jinv.Width());
Mult(grad_hat, Jinv, grad);
double q_val = 0.5 * (sq(grad(0, 0)) + sq(grad(1, 1)) + sq(grad(2, 2)))
+ grad(0, 1) * grad(1, 0) + grad(0, 2) * grad(2, 0)
+ grad(1, 2) * grad(2, 1);
vals(dof) = q_val;
}
// Accumulate values in all dofs, count the zones.
for (int j = 0; j < dofs.Size(); j++)
{
int ldof = dofs[j];
q(ldof) += vals[j];
zones_per_vdof[ldof]++;
}
}
// Communication
// Count the zones globally.
GroupCommunicator &gcomm = q.ParFESpace()->GroupComm();
gcomm.Reduce<int>(zones_per_vdof, GroupCommunicator::Sum);
gcomm.Bcast(zones_per_vdof);
// Accumulate for all vdofs.
gcomm.Reduce<double>(q.GetData(), GroupCommunicator::Sum);
gcomm.Bcast<double>(q.GetData());
// Compute means
for (int i = 0; i < q.Size(); i++)
{
const int nz = zones_per_vdof[i];
if (nz)
{
q(i) /= nz;
}
}
}
int main(int argc, char *argv[])
{
MPI_Session mpi(argc, argv);
OptionsParser args(argc, argv);
args.AddOption(&ctx.element_subdivisions,
"-es",
"--element-subdivisions",
"Number of 1d uniform subdivisions for each element.");
args.AddOption(&ctx.order,
"-o",
"--order",
"Order (degree) of the finite elements.");
args.AddOption(&ctx.dt, "-dt", "--time-step", "Time step.");
args.AddOption(&ctx.t_final, "-tf", "--final-time", "Final time.");
args.AddOption(&ctx.pa,
"-pa",
"--enable-pa",
"-no-pa",
"--disable-pa",
"Enable partial assembly.");
args.AddOption(&ctx.ni,
"-ni",
"--enable-ni",
"-no-ni",
"--disable-ni",
"Enable numerical integration rules.");
args.AddOption(&ctx.visualization,
"-vis",
"--visualization",
"-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(
&ctx.checkres,
"-cr",
"--checkresult",
"-no-cr",
"--no-checkresult",
"Enable or disable checking of the result. Returns -1 on failure.");
args.Parse();
if (!args.Good())
{
if (mpi.Root())
{
args.PrintUsage(mfem::out);
}
return 1;
}
if (mpi.Root())
{
args.PrintOptions(mfem::out);
}
Mesh *orig_mesh = new Mesh("../../data/periodic-cube.mesh");
Mesh *mesh = new Mesh(orig_mesh,
ctx.element_subdivisions,
BasisType::ClosedUniform);
delete orig_mesh;
mesh->EnsureNodes();
GridFunction *nodes = mesh->GetNodes();
*nodes *= M_PI;
int nel = mesh->GetNE();
if (mpi.Root())
{
mfem::out << "Number of elements: " << nel << std::endl;
}
auto *pmesh = new ParMesh(MPI_COMM_WORLD, *mesh);
delete mesh;
// Create the flow solver.
NavierSolver flowsolver(pmesh, ctx.order, ctx.kinvis);
flowsolver.EnablePA(ctx.pa);
flowsolver.EnableNI(ctx.ni);
// Set the initial condition.
ParGridFunction *u_ic = flowsolver.GetCurrentVelocity();
VectorFunctionCoefficient u_excoeff(pmesh->Dimension(), vel_tgv);
u_ic->ProjectCoefficient(u_excoeff);
double t = 0.0;
double dt = ctx.dt;
double t_final = ctx.t_final;
bool last_step = false;
flowsolver.Setup(dt);
ParGridFunction *u_gf = flowsolver.GetCurrentVelocity();
ParGridFunction *p_gf = flowsolver.GetCurrentPressure();
ParGridFunction w_gf(*u_gf);
ParGridFunction q_gf(*p_gf);
flowsolver.ComputeCurl3D(*u_gf, w_gf);
ComputeQCriterion(*u_gf, q_gf);
QuantitiesOfInterest kin_energy(pmesh);
ParaViewDataCollection pvdc("shear_output", pmesh);
pvdc.SetDataFormat(VTKFormat::BINARY32);
pvdc.SetHighOrderOutput(true);
pvdc.SetLevelsOfDetail(ctx.order);
pvdc.SetCycle(0);
pvdc.SetTime(t);
pvdc.RegisterField("velocity", u_gf);
pvdc.RegisterField("pressure", p_gf);
pvdc.RegisterField("vorticity", &w_gf);
pvdc.RegisterField("qcriterion", &q_gf);
pvdc.Save();
double u_inf_loc = u_gf->Normlinf();
double p_inf_loc = p_gf->Normlinf();
double u_inf = GlobalLpNorm(infinity(), u_inf_loc, MPI_COMM_WORLD);
double p_inf = GlobalLpNorm(infinity(), p_inf_loc, MPI_COMM_WORLD);
double ke = kin_energy.ComputeKineticEnergy(*u_gf);
std::string fname = "tgv_out_p_" + std::to_string(ctx.order) + ".txt";
FILE *f;
if (mpi.Root())
{
int nel1d = std::round(pow(nel, 1.0 / 3.0));
int ngridpts = p_gf->ParFESpace()->GlobalVSize();
printf("%11s %11s %11s %11s %11s\n", "Time", "dt", "u_inf", "p_inf", "ke");
printf("%.5E %.5E %.5E %.5E %.5E\n", t, dt, u_inf, p_inf, ke);
f = fopen(fname.c_str(), "w");
fprintf(f, "3D Taylor Green Vortex\n");
fprintf(f, "order = %d\n", ctx.order);
fprintf(f, "grid = %d x %d x %d\n", nel1d, nel1d, nel1d);
fprintf(f, "dofs per component = %d\n", ngridpts);
fprintf(f, "=================================================\n");
fprintf(f, " time kinetic energy\n");
fprintf(f, "%20.16e %20.16e\n", t, ke);
fflush(f);
fflush(stdout);
}
for (int step = 0; !last_step; ++step)
{
if (t + dt >= t_final - dt / 2)
{
last_step = true;
}
flowsolver.Step(t, dt, step);
if ((step + 1) % 100 == 0 || last_step)
{
flowsolver.ComputeCurl3D(*u_gf, w_gf);
ComputeQCriterion(*u_gf, q_gf);
pvdc.SetCycle(step);
pvdc.SetTime(t);
pvdc.Save();
}
double u_inf_loc = u_gf->Normlinf();
double p_inf_loc = p_gf->Normlinf();
double u_inf = GlobalLpNorm(infinity(), u_inf_loc, MPI_COMM_WORLD);
double p_inf = GlobalLpNorm(infinity(), p_inf_loc, MPI_COMM_WORLD);
double ke = kin_energy.ComputeKineticEnergy(*u_gf);
if (mpi.Root())
{
printf("%.5E %.5E %.5E %.5E %.5E\n", t, dt, u_inf, p_inf, ke);
fprintf(f, "%20.16e %20.16e\n", t, ke);
fflush(f);
fflush(stdout);
}
}
flowsolver.PrintTimingData();
// Test if the result for the test run is as expected.
if (ctx.checkres)
{
double tol = 1e-5;
double ke_expected = 1.25e-1;
if (fabs(ke - ke_expected) > tol)
{
if (mpi.Root())
{
mfem::out << "Result has a larger error than expected."
<< std::endl;
}
return -1;
}
}
delete pmesh;
return 0;
}