571 lines
16 KiB
C++
571 lines
16 KiB
C++
// MFEM Example 18 - Serial/Parallel Shared Code
|
|
|
|
#include "mfem.hpp"
|
|
|
|
using namespace std;
|
|
using namespace mfem;
|
|
|
|
// Problem definition
|
|
extern int problem;
|
|
|
|
// Maximum characteristic speed (updated by integrators)
|
|
extern double max_char_speed;
|
|
|
|
extern const int num_equation;
|
|
extern const double specific_heat_ratio;
|
|
extern const double gas_constant;
|
|
|
|
// Time-dependent operator for the right-hand side of the ODE representing the
|
|
// DG weak form.
|
|
class FE_Evolution : public TimeDependentOperator
|
|
{
|
|
private:
|
|
const int dim;
|
|
|
|
FiniteElementSpace &vfes;
|
|
Operator &A;
|
|
SparseMatrix &Aflux;
|
|
DenseTensor Me_inv;
|
|
|
|
mutable Vector state;
|
|
mutable DenseMatrix f;
|
|
mutable DenseTensor flux;
|
|
mutable Vector z;
|
|
|
|
void GetFlux(const DenseMatrix &state, DenseTensor &flux) const;
|
|
|
|
public:
|
|
FE_Evolution(FiniteElementSpace &_vfes,
|
|
Operator &_A, SparseMatrix &_Aflux);
|
|
|
|
virtual void Mult(const Vector &x, Vector &y) const;
|
|
|
|
virtual ~FE_Evolution() { }
|
|
};
|
|
|
|
// Implements a simple Rusanov flux
|
|
class RiemannSolver
|
|
{
|
|
private:
|
|
Vector flux1;
|
|
Vector flux2;
|
|
|
|
public:
|
|
RiemannSolver();
|
|
double Eval(const Vector &state1, const Vector &state2,
|
|
const Vector &nor, Vector &flux);
|
|
};
|
|
|
|
|
|
// Constant (in time) mixed bilinear form multiplying the flux grid function.
|
|
// The form is (vec(v), grad(w)) where the trial space = vector L2 space (mesh
|
|
// dim) and test space = scalar L2 space.
|
|
class DomainIntegrator : public BilinearFormIntegrator
|
|
{
|
|
private:
|
|
Vector shape;
|
|
DenseMatrix flux;
|
|
DenseMatrix dshapedr;
|
|
DenseMatrix dshapedx;
|
|
|
|
public:
|
|
DomainIntegrator(const int dim);
|
|
|
|
virtual void AssembleElementMatrix2(const FiniteElement &trial_fe,
|
|
const FiniteElement &test_fe,
|
|
ElementTransformation &Tr,
|
|
DenseMatrix &elmat);
|
|
};
|
|
|
|
// Interior face term: <F.n(u),[w]>
|
|
class FaceIntegrator : public NonlinearFormIntegrator
|
|
{
|
|
private:
|
|
RiemannSolver rsolver;
|
|
Vector shape1;
|
|
Vector shape2;
|
|
Vector funval1;
|
|
Vector funval2;
|
|
Vector nor;
|
|
Vector fluxN;
|
|
IntegrationPoint eip1;
|
|
IntegrationPoint eip2;
|
|
|
|
public:
|
|
FaceIntegrator(RiemannSolver &rsolver_, const int dim);
|
|
|
|
virtual void AssembleFaceVector(const FiniteElement &el1,
|
|
const FiniteElement &el2,
|
|
FaceElementTransformations &Tr,
|
|
const Vector &elfun, Vector &elvect);
|
|
};
|
|
|
|
// Implementation of class FE_Evolution
|
|
FE_Evolution::FE_Evolution(FiniteElementSpace &_vfes,
|
|
Operator &_A, SparseMatrix &_Aflux)
|
|
: TimeDependentOperator(_A.Height()),
|
|
dim(_vfes.GetFE(0)->GetDim()),
|
|
vfes(_vfes),
|
|
A(_A),
|
|
Aflux(_Aflux),
|
|
Me_inv(vfes.GetFE(0)->GetDof(), vfes.GetFE(0)->GetDof(), vfes.GetNE()),
|
|
state(num_equation),
|
|
f(num_equation, dim),
|
|
flux(vfes.GetNDofs(), dim, num_equation),
|
|
z(A.Height())
|
|
{
|
|
// Standard local assembly and inversion for energy mass matrices.
|
|
const int dof = vfes.GetFE(0)->GetDof();
|
|
DenseMatrix Me(dof);
|
|
DenseMatrixInverse inv(&Me);
|
|
MassIntegrator mi;
|
|
for (int i = 0; i < vfes.GetNE(); i++)
|
|
{
|
|
mi.AssembleElementMatrix(*vfes.GetFE(i), *vfes.GetElementTransformation(i), Me);
|
|
inv.Factor();
|
|
inv.GetInverseMatrix(Me_inv(i));
|
|
}
|
|
}
|
|
|
|
void FE_Evolution::Mult(const Vector &x, Vector &y) const
|
|
{
|
|
// 0. Reset wavespeed computation before operator application.
|
|
max_char_speed = 0.;
|
|
|
|
// 1. Create the vector z with the face terms -<F.n(u), [w]>.
|
|
A.Mult(x, z);
|
|
|
|
// 2. Add the element terms.
|
|
// i. computing the flux approximately as a grid function by interpolating
|
|
// at the solution nodes.
|
|
// ii. multiplying this grid function by a (constant) mixed bilinear form for
|
|
// each of the num_equation, computing (F(u), grad(w)) for each equation.
|
|
|
|
DenseMatrix xmat(x.GetData(), vfes.GetNDofs(), num_equation);
|
|
GetFlux(xmat, flux);
|
|
|
|
for (int k = 0; k < num_equation; k++)
|
|
{
|
|
Vector fk(flux(k).GetData(), dim * vfes.GetNDofs());
|
|
Vector zk(z.GetData() + k * vfes.GetNDofs(), vfes.GetNDofs());
|
|
Aflux.AddMult(fk, zk);
|
|
}
|
|
|
|
// 3. Multiply element-wise by the inverse mass matrices.
|
|
Vector zval;
|
|
Array<int> vdofs;
|
|
const int dof = vfes.GetFE(0)->GetDof();
|
|
DenseMatrix zmat, ymat(dof, num_equation);
|
|
|
|
for (int i = 0; i < vfes.GetNE(); i++)
|
|
{
|
|
// Return the vdofs ordered byNODES
|
|
vfes.GetElementVDofs(i, vdofs);
|
|
z.GetSubVector(vdofs, zval);
|
|
zmat.UseExternalData(zval.GetData(), dof, num_equation);
|
|
mfem::Mult(Me_inv(i), zmat, ymat);
|
|
y.SetSubVector(vdofs, ymat.GetData());
|
|
}
|
|
}
|
|
|
|
// Physicality check (at end)
|
|
bool StateIsPhysical(const Vector &state, const int dim);
|
|
|
|
// Pressure (EOS) computation
|
|
inline double ComputePressure(const Vector &state, int dim)
|
|
{
|
|
const double den = state(0);
|
|
const Vector den_vel(state.GetData() + 1, dim);
|
|
const double den_energy = state(1 + dim);
|
|
|
|
double den_vel2 = 0;
|
|
for (int d = 0; d < dim; d++) { den_vel2 += den_vel(d) * den_vel(d); }
|
|
den_vel2 /= den;
|
|
|
|
return (specific_heat_ratio - 1.0) * (den_energy - 0.5 * den_vel2);
|
|
}
|
|
|
|
// Compute the vector flux F(u)
|
|
void ComputeFlux(const Vector &state, int dim, DenseMatrix &flux)
|
|
{
|
|
const double den = state(0);
|
|
const Vector den_vel(state.GetData() + 1, dim);
|
|
const double den_energy = state(1 + dim);
|
|
|
|
MFEM_ASSERT(StateIsPhysical(state, dim), "");
|
|
|
|
const double pres = ComputePressure(state, dim);
|
|
|
|
for (int d = 0; d < dim; d++)
|
|
{
|
|
flux(0, d) = den_vel(d);
|
|
for (int i = 0; i < dim; i++)
|
|
{
|
|
flux(1+i, d) = den_vel(i) * den_vel(d) / den;
|
|
}
|
|
flux(1+d, d) += pres;
|
|
}
|
|
|
|
const double H = (den_energy + pres) / den;
|
|
for (int d = 0; d < dim; d++)
|
|
{
|
|
flux(1+dim, d) = den_vel(d) * H;
|
|
}
|
|
}
|
|
|
|
// Compute the scalar F(u).n
|
|
void ComputeFluxDotN(const Vector &state, const Vector &nor,
|
|
Vector &fluxN)
|
|
{
|
|
// NOTE: nor in general is not a unit normal
|
|
const int dim = nor.Size();
|
|
const double den = state(0);
|
|
const Vector den_vel(state.GetData() + 1, dim);
|
|
const double den_energy = state(1 + dim);
|
|
|
|
MFEM_ASSERT(StateIsPhysical(state, dim), "");
|
|
|
|
const double pres = ComputePressure(state, dim);
|
|
|
|
double den_velN = 0;
|
|
for (int d = 0; d < dim; d++) { den_velN += den_vel(d) * nor(d); }
|
|
|
|
fluxN(0) = den_velN;
|
|
for (int d = 0; d < dim; d++)
|
|
{
|
|
fluxN(1+d) = den_velN * den_vel(d) / den + pres * nor(d);
|
|
}
|
|
|
|
const double H = (den_energy + pres) / den;
|
|
fluxN(1 + dim) = den_velN * H;
|
|
}
|
|
|
|
// Compute the maximum characteristic speed.
|
|
inline double ComputeMaxCharSpeed(const Vector &state, const int dim)
|
|
{
|
|
const double den = state(0);
|
|
const Vector den_vel(state.GetData() + 1, dim);
|
|
|
|
double den_vel2 = 0;
|
|
for (int d = 0; d < dim; d++) { den_vel2 += den_vel(d) * den_vel(d); }
|
|
den_vel2 /= den;
|
|
|
|
const double pres = ComputePressure(state, dim);
|
|
const double sound = sqrt(specific_heat_ratio * pres / den);
|
|
const double vel = sqrt(den_vel2 / den);
|
|
|
|
return vel + sound;
|
|
}
|
|
|
|
// Compute the flux at solution nodes.
|
|
void FE_Evolution::GetFlux(const DenseMatrix &x, DenseTensor &flux) const
|
|
{
|
|
const int dof = flux.SizeI();
|
|
const int dim = flux.SizeJ();
|
|
|
|
for (int i = 0; i < dof; i++)
|
|
{
|
|
for (int k = 0; k < num_equation; k++) { state(k) = x(i, k); }
|
|
ComputeFlux(state, dim, f);
|
|
|
|
for (int d = 0; d < dim; d++)
|
|
{
|
|
for (int k = 0; k < num_equation; k++)
|
|
{
|
|
flux(i, d, k) = f(k, d);
|
|
}
|
|
}
|
|
|
|
// Update max char speed
|
|
const double mcs = ComputeMaxCharSpeed(state, dim);
|
|
if (mcs > max_char_speed) { max_char_speed = mcs; }
|
|
}
|
|
}
|
|
|
|
// Implementation of class RiemannSolver
|
|
RiemannSolver::RiemannSolver() :
|
|
flux1(num_equation),
|
|
flux2(num_equation) { }
|
|
|
|
double RiemannSolver::Eval(const Vector &state1, const Vector &state2,
|
|
const Vector &nor, Vector &flux)
|
|
{
|
|
// NOTE: nor in general is not a unit normal
|
|
const int dim = nor.Size();
|
|
|
|
MFEM_ASSERT(StateIsPhysical(state1, dim), "");
|
|
MFEM_ASSERT(StateIsPhysical(state2, dim), "");
|
|
|
|
const double maxE1 = ComputeMaxCharSpeed(state1, dim);
|
|
const double maxE2 = ComputeMaxCharSpeed(state2, dim);
|
|
|
|
const double maxE = max(maxE1, maxE2);
|
|
|
|
ComputeFluxDotN(state1, nor, flux1);
|
|
ComputeFluxDotN(state2, nor, flux2);
|
|
|
|
double normag = 0;
|
|
for (int i = 0; i < dim; i++)
|
|
{
|
|
normag += nor(i) * nor(i);
|
|
}
|
|
normag = sqrt(normag);
|
|
|
|
for (int i = 0; i < num_equation; i++)
|
|
{
|
|
flux(i) = 0.5 * (flux1(i) + flux2(i))
|
|
- 0.5 * maxE * (state2(i) - state1(i)) * normag;
|
|
}
|
|
|
|
return maxE;
|
|
}
|
|
|
|
// Implementation of class DomainIntegrator
|
|
DomainIntegrator::DomainIntegrator(const int dim) : flux(num_equation, dim) { }
|
|
|
|
void DomainIntegrator::AssembleElementMatrix2(const FiniteElement &trial_fe,
|
|
const FiniteElement &test_fe,
|
|
ElementTransformation &Tr,
|
|
DenseMatrix &elmat)
|
|
{
|
|
// Assemble the form (vec(v), grad(w))
|
|
|
|
// Trial space = vector L2 space (mesh dim)
|
|
// Test space = scalar L2 space
|
|
|
|
const int dof_trial = trial_fe.GetDof();
|
|
const int dof_test = test_fe.GetDof();
|
|
const int dim = trial_fe.GetDim();
|
|
|
|
shape.SetSize(dof_trial);
|
|
dshapedr.SetSize(dof_test, dim);
|
|
dshapedx.SetSize(dof_test, dim);
|
|
|
|
elmat.SetSize(dof_test, dof_trial * dim);
|
|
elmat = 0.0;
|
|
|
|
const int maxorder = max(trial_fe.GetOrder(), test_fe.GetOrder());
|
|
const int intorder = 2 * maxorder;
|
|
const IntegrationRule *ir = &IntRules.Get(trial_fe.GetGeomType(), intorder);
|
|
|
|
for (int i = 0; i < ir->GetNPoints(); i++)
|
|
{
|
|
const IntegrationPoint &ip = ir->IntPoint(i);
|
|
|
|
// Calculate the shape functions
|
|
trial_fe.CalcShape(ip, shape);
|
|
shape *= ip.weight;
|
|
|
|
// Compute the physical gradients of the test functions
|
|
Tr.SetIntPoint(&ip);
|
|
test_fe.CalcDShape(ip, dshapedr);
|
|
Mult(dshapedr, Tr.AdjugateJacobian(), dshapedx);
|
|
|
|
for (int d = 0; d < dim; d++)
|
|
{
|
|
for (int j = 0; j < dof_test; j++)
|
|
{
|
|
for (int k = 0; k < dof_trial; k++)
|
|
{
|
|
elmat(j, k + d * dof_trial) += shape(k) * dshapedx(j, d);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implementation of class FaceIntegrator
|
|
FaceIntegrator::FaceIntegrator(RiemannSolver &rsolver_, const int dim) :
|
|
rsolver(rsolver_),
|
|
funval1(num_equation),
|
|
funval2(num_equation),
|
|
nor(dim),
|
|
fluxN(num_equation) { }
|
|
|
|
void FaceIntegrator::AssembleFaceVector(const FiniteElement &el1,
|
|
const FiniteElement &el2,
|
|
FaceElementTransformations &Tr,
|
|
const Vector &elfun, Vector &elvect)
|
|
{
|
|
// Compute the term <F.n(u),[w]> on the interior faces.
|
|
const int dof1 = el1.GetDof();
|
|
const int dof2 = el2.GetDof();
|
|
|
|
shape1.SetSize(dof1);
|
|
shape2.SetSize(dof2);
|
|
|
|
elvect.SetSize((dof1 + dof2) * num_equation);
|
|
elvect = 0.0;
|
|
|
|
DenseMatrix elfun1_mat(elfun.GetData(), dof1, num_equation);
|
|
DenseMatrix elfun2_mat(elfun.GetData() + dof1 * num_equation, dof2,
|
|
num_equation);
|
|
|
|
DenseMatrix elvect1_mat(elvect.GetData(), dof1, num_equation);
|
|
DenseMatrix elvect2_mat(elvect.GetData() + dof1 * num_equation, dof2,
|
|
num_equation);
|
|
|
|
// Integration order calculation from DGTraceIntegrator
|
|
int intorder;
|
|
if (Tr.Elem2No >= 0)
|
|
intorder = (min(Tr.Elem1->OrderW(), Tr.Elem2->OrderW()) +
|
|
2*max(el1.GetOrder(), el2.GetOrder()));
|
|
else
|
|
{
|
|
intorder = Tr.Elem1->OrderW() + 2*el1.GetOrder();
|
|
}
|
|
if (el1.Space() == FunctionSpace::Pk)
|
|
{
|
|
intorder++;
|
|
}
|
|
const IntegrationRule *ir = &IntRules.Get(Tr.GetGeometryType(), intorder);
|
|
|
|
for (int i = 0; i < ir->GetNPoints(); i++)
|
|
{
|
|
const IntegrationPoint &ip = ir->IntPoint(i);
|
|
|
|
Tr.Loc1.Transform(ip, eip1);
|
|
Tr.Loc2.Transform(ip, eip2);
|
|
|
|
// Calculate basis functions on both elements at the face
|
|
el1.CalcShape(eip1, shape1);
|
|
el2.CalcShape(eip2, shape2);
|
|
|
|
// Interpolate elfun at the point
|
|
elfun1_mat.MultTranspose(shape1, funval1);
|
|
elfun2_mat.MultTranspose(shape2, funval2);
|
|
|
|
Tr.SetIntPoint(&ip);
|
|
|
|
// Get the normal vector and the flux on the face
|
|
CalcOrtho(Tr.Jacobian(), nor);
|
|
const double mcs = rsolver.Eval(funval1, funval2, nor, fluxN);
|
|
|
|
// Update max char speed
|
|
if (mcs > max_char_speed) { max_char_speed = mcs; }
|
|
|
|
fluxN *= ip.weight;
|
|
for (int k = 0; k < num_equation; k++)
|
|
{
|
|
for (int s = 0; s < dof1; s++)
|
|
{
|
|
elvect1_mat(s, k) -= fluxN(k) * shape1(s);
|
|
}
|
|
for (int s = 0; s < dof2; s++)
|
|
{
|
|
elvect2_mat(s, k) += fluxN(k) * shape2(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check that the state is physical - enabled in debug mode
|
|
bool StateIsPhysical(const Vector &state, const int dim)
|
|
{
|
|
const double den = state(0);
|
|
const Vector den_vel(state.GetData() + 1, dim);
|
|
const double den_energy = state(1 + dim);
|
|
|
|
if (den < 0)
|
|
{
|
|
cout << "Negative density: ";
|
|
for (int i = 0; i < state.Size(); i++)
|
|
{
|
|
cout << state(i) << " ";
|
|
}
|
|
cout << endl;
|
|
return false;
|
|
}
|
|
if (den_energy <= 0)
|
|
{
|
|
cout << "Negative energy: ";
|
|
for (int i = 0; i < state.Size(); i++)
|
|
{
|
|
cout << state(i) << " ";
|
|
}
|
|
cout << endl;
|
|
return false;
|
|
}
|
|
|
|
double den_vel2 = 0;
|
|
for (int i = 0; i < dim; i++) { den_vel2 += den_vel(i) * den_vel(i); }
|
|
den_vel2 /= den;
|
|
|
|
const double pres = (specific_heat_ratio - 1.0) * (den_energy - 0.5 * den_vel2);
|
|
|
|
if (pres <= 0)
|
|
{
|
|
cout << "Negative pressure: " << pres << ", state: ";
|
|
for (int i = 0; i < state.Size(); i++)
|
|
{
|
|
cout << state(i) << " ";
|
|
}
|
|
cout << endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Initial condition
|
|
void InitialCondition(const Vector &x, Vector &y)
|
|
{
|
|
MFEM_ASSERT(x.Size() == 2, "");
|
|
|
|
double radius = 0, Minf = 0, beta = 0;
|
|
if (problem == 1)
|
|
{
|
|
// "Fast vortex"
|
|
radius = 0.2;
|
|
Minf = 0.5;
|
|
beta = 1. / 5.;
|
|
}
|
|
else if (problem == 2)
|
|
{
|
|
// "Slow vortex"
|
|
radius = 0.2;
|
|
Minf = 0.05;
|
|
beta = 1. / 50.;
|
|
}
|
|
else
|
|
{
|
|
mfem_error("Cannot recognize problem."
|
|
"Options are: 1 - fast vortex, 2 - slow vortex");
|
|
}
|
|
|
|
const double xc = 0.0, yc = 0.0;
|
|
|
|
// Nice units
|
|
const double vel_inf = 1.;
|
|
const double den_inf = 1.;
|
|
|
|
// Derive remainder of background state from this and Minf
|
|
const double pres_inf = (den_inf / specific_heat_ratio) * (vel_inf / Minf) *
|
|
(vel_inf / Minf);
|
|
const double temp_inf = pres_inf / (den_inf * gas_constant);
|
|
|
|
double r2rad = 0.0;
|
|
r2rad += (x(0) - xc) * (x(0) - xc);
|
|
r2rad += (x(1) - yc) * (x(1) - yc);
|
|
r2rad /= (radius * radius);
|
|
|
|
const double shrinv1 = 1.0 / (specific_heat_ratio - 1.);
|
|
|
|
const double velX = vel_inf * (1 - beta * (x(1) - yc) / radius * exp(
|
|
-0.5 * r2rad));
|
|
const double velY = vel_inf * beta * (x(0) - xc) / radius * exp(-0.5 * r2rad);
|
|
const double vel2 = velX * velX + velY * velY;
|
|
|
|
const double specific_heat = gas_constant * specific_heat_ratio * shrinv1;
|
|
const double temp = temp_inf - 0.5 * (vel_inf * beta) *
|
|
(vel_inf * beta) / specific_heat * exp(-r2rad);
|
|
|
|
const double den = den_inf * pow(temp/temp_inf, shrinv1);
|
|
const double pres = den * gas_constant * temp;
|
|
const double energy = shrinv1 * pres / den + 0.5 * vel2;
|
|
|
|
y(0) = den;
|
|
y(1) = den * velX;
|
|
y(2) = den * velY;
|
|
y(3) = den * energy;
|
|
}
|