libMesh
Typedefs | Functions
vector_fe_ex8.C File Reference

Go to the source code of this file.

Typedefs

typedef MatrixXcd EigenMatrix
 
typedef VectorXcd EigenVector
 

Functions

void fe_assembly (EquationSystems &es, bool global_solve)
 
void assemble_hdg (EquationSystems &es, const std::string &system_name)
 
void alternative_fe_assembly (EquationSystems &es, bool global_solve)
 
void alternative_assemble_hdg (EquationSystems &es, const std::string &system_name)
 
int main (int argc, char **argv)
 
template<typename SolnType , typename PhiType , typename LocalSolutionVector >
void compute_qp_soln (std::vector< SolnType > &qp_vec, const unsigned int n_qps, const std::vector< std::vector< PhiType >> &phi, const LocalSolutionVector &soln)
 
void compute_enriched_soln (const MeshBase &mesh, const DofMap &dof_map, System &system, const Elem *const elem, const EigenVector &vector_soln, const EigenVector &scalar_soln, const EigenVector &Lambda, FEVectorBase &vector_fe, FEVectorBase &vector_fe_face, FEBase &scalar_fe, FEBase &scalar_fe_face, FEBase &lambda_fe_face, QBase &qrule, QBase &qface)
 
void assemble_hdg (EquationSystems &es, const std::string &libmesh_dbg_var(system_name))
 
Real compute_tau (const bool internal_face, bool &tau_found, const Elem *const elem)
 
void alternative_assemble_hdg (EquationSystems &es, const std::string &libmesh_dbg_var(system_name))
 
int main ()
 

Typedef Documentation

◆ EigenMatrix

typedef MatrixXd EigenMatrix

Definition at line 80 of file vector_fe_ex8.C.

◆ EigenVector

typedef VectorXd EigenVector

Definition at line 81 of file vector_fe_ex8.C.

Function Documentation

◆ alternative_assemble_hdg() [1/2]

void alternative_assemble_hdg ( EquationSystems es,
const std::string &  system_name 
)

Referenced by main().

◆ alternative_assemble_hdg() [2/2]

void alternative_assemble_hdg ( EquationSystems es,
const std::string &  libmesh_dbg_varsystem_name 
)

Definition at line 1264 of file vector_fe_ex8.C.

References alternative_fe_assembly().

1265 {
1266  libmesh_assert_equal_to(system_name, "Lambda");
1267  alternative_fe_assembly(es, /*global_solve=*/true);
1268 }
void alternative_fe_assembly(EquationSystems &es, bool global_solve)

◆ alternative_fe_assembly()

void alternative_fe_assembly ( EquationSystems es,
bool  global_solve 
)

Definition at line 833 of file vector_fe_ex8.C.

References libMesh::System::add_matrix(), libMesh::System::add_vector(), libMesh::FEGenericBase< OutputType >::build(), compute_enriched_soln(), libMesh::compute_qp_soln(), compute_tau(), libMesh::System::current_local_solution, libMesh::FEType::default_quadrature_order(), dim, libMesh::FEInterface::dofs_on_side(), MixedExactSolution::forcing(), libMesh::System::get_dof_map(), libMesh::EquationSystems::get_mesh(), libMesh::EquationSystems::get_system(), libMesh::invalid_uint, libMesh::make_range(), mesh, libMesh::MeshBase::mesh_dimension(), libMesh::Real, libMesh::System::reinit(), libMesh::DenseVector< T >::resize(), libMesh::DenseMatrix< T >::resize(), MixedExactSolution::scalar(), libMesh::System::variable_number(), and libMesh::System::variable_type().

Referenced by alternative_assemble_hdg(), and main().

834 {
835  const MeshBase & mesh = es.get_mesh();
836  const unsigned int dim = mesh.mesh_dimension();
837 
838  // The mixed, e.g. vector-scalar system
839  auto & system = es.get_system<System>("Mixed");
840  // Our implicit Lagrange multiplier system
841  auto & lambda_system = es.get_system<LinearImplicitSystem>("Lambda");
842 
843  const auto & dof_map = system.get_dof_map();
844  const auto & lambda_dof_map = lambda_system.get_dof_map();
845 
846  const FEType vector_fe_type = dof_map.variable_type(system.variable_number("q"));
847  const FEType scalar_fe_type = dof_map.variable_type(system.variable_number("u"));
848  const FEType lambda_fe_type =
849  lambda_dof_map.variable_type(lambda_system.variable_number("lambda"));
850 
851  // Volumetric FE objects
852  std::unique_ptr<FEVectorBase> vector_fe(FEVectorBase::build(dim, vector_fe_type));
853  std::unique_ptr<FEBase> scalar_fe(FEBase::build(dim, scalar_fe_type));
854 
855  // Volumetric quadrature rule
856  QGauss qrule(dim, scalar_fe_type.default_quadrature_order());
857 
858  // Attach quadrature rules for the FE objects that we will reinit within the element "volume"
859  vector_fe->attach_quadrature_rule(&qrule);
860  scalar_fe->attach_quadrature_rule(&qrule);
861 
862  // Declare finite element objects for boundary integration
863  std::unique_ptr<FEVectorBase> vector_fe_face(FEVectorBase::build(dim, vector_fe_type));
864  std::unique_ptr<FEBase> scalar_fe_face(FEBase::build(dim, scalar_fe_type));
865  std::unique_ptr<FEBase> lambda_fe_face(FEBase::build(dim, lambda_fe_type));
866 
867  // Boundary integration requires one quadrature rule with dimensionality one
868  // less than the dimensionality of the element.
869  QGauss qface(dim - 1, scalar_fe_type.default_quadrature_order());
870 
871  // Attach quadrature rules for the FE objects that we will reinit on the element faces
872  vector_fe_face->attach_quadrature_rule(&qface);
873  scalar_fe_face->attach_quadrature_rule(&qface);
874  lambda_fe_face->attach_quadrature_rule(&qface);
875 
876  // pre-request our required volumetric data
877  const auto & JxW = vector_fe->get_JxW();
878  const auto & q_point = vector_fe->get_xyz();
879  const auto & vector_phi = vector_fe->get_phi();
880  const auto & scalar_phi = scalar_fe->get_phi();
881  const auto & grad_scalar_phi = scalar_fe->get_dphi();
882  const auto & div_vector_phi = vector_fe->get_div_phi();
883 
884  // pre-request our required element face data
885  const auto & vector_phi_face = vector_fe_face->get_phi();
886  const auto & scalar_phi_face = scalar_fe_face->get_phi();
887  const auto & lambda_phi_face = lambda_fe_face->get_phi();
888  const auto & JxW_face = scalar_fe_face->get_JxW();
889  const auto & qface_point = vector_fe_face->get_xyz();
890  const auto & normals = vector_fe_face->get_normals();
891 
892  //
893  // We will need "Eigen" versions of many of the matrices/vectors because the
894  // libMesh DenseMatrix doesn't have an inverse API
895  //
896 
897  // LM matrix and RHS after eliminating vector and scalar dofs
898  DenseMatrix<Number> K_lm_libmesh;
899  DenseVector<Number> F_lm_libmesh;
900  EigenMatrix K_mixed;
901  EigenMatrix Kinv_mixed;
902  EigenVector F_mixed;
903 
904  // Lambda eigen vector for constructing vector and scalar solutions
905  EigenVector Lambda;
906 
907  // Containers for dof indices
908  std::vector<dof_id_type> vector_dof_indices;
909  std::vector<dof_id_type> scalar_dof_indices;
910  std::vector<dof_id_type> lambda_dof_indices;
911  std::vector<Number> lambda_solution_std_vec;
912 
913  // Helper container for storing a given "mu" (function in the Langrange multiplier space) with
914  // quadrature point evaluations
915  std::vector<Number> mu;
916 
917  // The global system matrix
918  auto & matrix = lambda_system.get_system_matrix();
919 
920  auto compute_and_invert_K =
921  [&](const auto vector_n_dofs_in, const auto scalar_n_dofs_in, const Elem * const elem_in)
922  {
923  const auto mixed_size = vector_n_dofs_in + scalar_n_dofs_in;
924 
925  K_mixed.setZero(mixed_size, mixed_size);
926 
927  for (const auto qp : make_range(qrule.n_points()))
928  {
929  // Vector equation dependence on vector dofs
930  for (const auto i : make_range(vector_n_dofs_in))
931  for (const auto j : make_range(vector_n_dofs_in))
932  K_mixed(i, j) += JxW[qp] * (vector_phi[i][qp] * vector_phi[j][qp]);
933 
934  // Vector equation dependence on scalar dofs
935  for (const auto i : make_range(vector_n_dofs_in))
936  for (const auto j : make_range(scalar_n_dofs_in))
937  K_mixed(i, j + vector_n_dofs_in) -= JxW[qp] * (div_vector_phi[i][qp] * scalar_phi[j][qp]);
938 
939  // Scalar equation dependence on vector dofs
940  for (const auto i : make_range(scalar_n_dofs_in))
941  for (const auto j : make_range(vector_n_dofs_in))
942  K_mixed(i + vector_n_dofs_in, j) -= JxW[qp] * (grad_scalar_phi[i][qp] * vector_phi[j][qp]);
943  }
944 
945  // At the beginning of the loop, we mark that we haven't found our "Single-Face" yet
946  bool tau_found = false;
947  for (auto side : elem_in->side_index_range())
948  {
949  // Reinit our face FE objects
950  vector_fe_face->reinit(elem_in, side);
951  scalar_fe_face->reinit(elem_in, side);
952  const bool internal_face = elem_in->neighbor_ptr(side);
953  const auto tau = compute_tau(internal_face, tau_found, elem_in);
954 
955  for (const auto qp : make_range(qface.n_points()))
956  {
957  const auto normal = normals[qp];
958  const auto normal_sq = normal * normal;
959 
960  // Now do the internal boundary term for <\hat{q} \cdot \vec{n}, \omega> ->
961  // <q + \tau (u - \lambda), \omega> ->
962  // <q, \omega> + <\tau u, \omega> - <\tau \lambda, \omega>
963  for (const auto i : make_range(scalar_n_dofs_in))
964  {
965  for (const auto j : make_range(vector_n_dofs_in))
966  K_mixed(i + vector_n_dofs_in, j) +=
967  JxW_face[qp] * scalar_phi_face[i][qp] * (vector_phi_face[j][qp] * normal);
968 
969  if (tau) // Don't do unnecessary math ops if tau is 0
970  for (const auto j : make_range(scalar_n_dofs_in))
971  K_mixed(i + vector_n_dofs_in, j + vector_n_dofs_in) +=
972  JxW_face[qp] * scalar_phi_face[i][qp] * tau * scalar_phi_face[j][qp] * normal_sq;
973  }
974  }
975  }
976 
977  Kinv_mixed = K_mixed.inverse();
978  };
979 
980  auto compute_rhs = [&](const auto vector_n_dofs_in,
981  const auto scalar_n_dofs_in,
982  const Elem * const elem_in,
983  const unsigned int shape_function)
984  {
985  const auto mixed_size = vector_n_dofs_in + scalar_n_dofs_in;
986  F_mixed.setZero(mixed_size);
987 
988  // If the approximate LM solution was passed in, then we are solving for the elemental solution
989  // of q and u, not the mappings of individual LM shape functions. Consequently, we include the
990  // contribution of the forcing function
991  if (shape_function == libMesh::invalid_uint)
992  for (const auto qp : make_range(qrule.n_points()))
993  {
994  const Real x = q_point[qp](0);
995  const Real y = q_point[qp](1);
996  const Real z = q_point[qp](2);
997 
998  Real f = 0;
999  if (dim == 2)
1000  f = MixedExactSolution().forcing(x, y);
1001  else if (dim == 3)
1002  f = MixedExactSolution().forcing(x, y, z);
1003  for (const auto ii : make_range(scalar_n_dofs_in))
1004  F_mixed(ii + vector_n_dofs_in) += JxW[qp] * f * scalar_phi[ii][qp];
1005  }
1006 
1007  // At the beginning of the loop, we mark that we haven't found our "Single-Face" yet
1008  bool tau_found = false;
1009  std::vector<Number> g;
1010 
1011  for (auto side : elem_in->side_index_range())
1012  {
1013  // Reinit our face FE objects
1014  vector_fe_face->reinit(elem_in, side);
1015  scalar_fe_face->reinit(elem_in, side);
1016  lambda_fe_face->reinit(elem_in, side);
1017 
1018  const auto & qp_mu = [&]()
1019  {
1020  if (shape_function == libMesh::invalid_uint)
1021  {
1022  if (elem_in->neighbor_ptr(side))
1023  {
1024  compute_qp_soln(lambda_solution_std_vec, qface.n_points(), lambda_phi_face, Lambda);
1025  return lambda_solution_std_vec;
1026  }
1027  else
1028  {
1029  g.resize(qface.n_points());
1030  for (const auto qp : make_range(qface.n_points()))
1031  {
1032  auto & g_qp = g[qp];
1033  const Real xf = qface_point[qp](0);
1034  const Real yf = qface_point[qp](1);
1035  const Real zf = qface_point[qp](2);
1036 
1037  // The boundary value for scalar field.
1038  if (dim == 2)
1039  g_qp = MixedExactSolution().scalar(xf, yf);
1040  else if (dim == 3)
1041  g_qp = MixedExactSolution().scalar(xf, yf, zf);
1042  }
1043  return g;
1044  }
1045  }
1046  else
1047  {
1048  auto & real_mu = lambda_phi_face[shape_function];
1049  mu.resize(qface.n_points());
1050  for (const auto qp : make_range(qface.n_points()))
1051  mu[qp] = real_mu[qp];
1052  return mu;
1053  }
1054  }();
1055 
1056  const bool internal_face = elem_in->neighbor_ptr(side);
1057  const auto tau = compute_tau(internal_face, tau_found, elem_in);
1058 
1059  for (const auto qp : make_range(qface.n_points()))
1060  {
1061  const auto normal = normals[qp];
1062  const auto normal_sq = normal * normal;
1063 
1064  // Vector equation dependence on LM/mu
1065  for (const auto ii : make_range(vector_n_dofs_in))
1066  F_mixed(ii) -= JxW_face[qp] * (vector_phi_face[ii][qp] * normal) * qp_mu[qp];
1067 
1068  // Now do the boundary term for <\hat{q} \cdot \vec{n}, \omega> ->
1069  // <q + \tau (u - \lambda), \omega> ->
1070  // <q, \omega> + <\tau u, \omega> - <\tau \lambda, \omega>
1071  for (const auto ii : make_range(scalar_n_dofs_in))
1072  F_mixed(ii + vector_n_dofs_in) +=
1073  JxW_face[qp] * scalar_phi_face[ii][qp] * tau * qp_mu[qp] * normal_sq;
1074  }
1075  }
1076  };
1077 
1078  for (const auto & elem : mesh.active_local_element_ptr_range())
1079  {
1080  std::vector<EigenVector> local_solns;
1081  std::vector<unsigned int> dofs_on_side;
1082  std::unordered_set<unsigned int> external_boundary_indices;
1083  std::vector<std::vector<Gradient>> volumetric_q;
1084  std::vector<std::vector<Number>> volumetric_u;
1085  std::vector<std::vector<Gradient>> face_q;
1086 
1087  // Retrive our dof indices for all fields
1088  dof_map.dof_indices(elem, vector_dof_indices, system.variable_number("q"));
1089  dof_map.dof_indices(elem, scalar_dof_indices, system.variable_number("u"));
1090  lambda_dof_map.dof_indices(elem, lambda_dof_indices, lambda_system.variable_number("lambda"));
1091 
1092  const auto vector_n_dofs = vector_dof_indices.size();
1093  const auto scalar_n_dofs = scalar_dof_indices.size();
1094  const auto lambda_n_dofs = lambda_dof_indices.size();
1095  const std::size_t n_mu_funcs = global_solve ? lambda_n_dofs : 1;
1096 
1097  if (global_solve)
1098  {
1099  K_lm_libmesh.resize(lambda_n_dofs, lambda_n_dofs);
1100  F_lm_libmesh.resize(lambda_n_dofs);
1101 
1102  for (const auto s : elem->side_index_range())
1103  if (!elem->neighbor_ptr(s))
1104  {
1105  FEInterface::dofs_on_side(elem, dim, lambda_fe_type, s, dofs_on_side);
1106  external_boundary_indices.insert(dofs_on_side.begin(), dofs_on_side.end());
1107  }
1108  }
1109 
1110  // Reinit our volume FE objects
1111  vector_fe->reinit(elem);
1112  scalar_fe->reinit(elem);
1113 
1114  libmesh_assert_equal_to(vector_n_dofs, vector_phi.size());
1115  libmesh_assert_equal_to(scalar_n_dofs, scalar_phi.size());
1116 
1117  compute_and_invert_K(vector_n_dofs, scalar_n_dofs, elem);
1118  local_solns.resize(n_mu_funcs);
1119 
1120  if (global_solve)
1121  {
1122  volumetric_q.resize(lambda_n_dofs);
1123  volumetric_u.resize(lambda_n_dofs);
1124  face_q.resize(lambda_n_dofs);
1125 
1126  for (const auto i : make_range(lambda_n_dofs))
1127  {
1128  if (external_boundary_indices.count(i))
1129  continue;
1130 
1131  compute_rhs(vector_n_dofs, scalar_n_dofs, elem, i);
1132  auto & local_soln = local_solns[i];
1133  local_soln = Kinv_mixed * F_mixed;
1134  const auto local_q_soln = local_soln.head(vector_n_dofs);
1135  const auto local_u_soln = local_soln.tail(scalar_n_dofs);
1136  compute_qp_soln(volumetric_q[i], qrule.n_points(), vector_phi, local_q_soln);
1137  compute_qp_soln(volumetric_u[i], qrule.n_points(), scalar_phi, local_u_soln);
1138  }
1139 
1140  // Create the bilinear form for lambda
1141  for (const auto i : make_range(lambda_n_dofs))
1142  if (!external_boundary_indices.count(i))
1143  for (const auto j : make_range(lambda_n_dofs))
1144  if (!external_boundary_indices.count(j))
1145  for (const auto qp : make_range(qrule.n_points()))
1146  K_lm_libmesh(i, j) += JxW[qp] * volumetric_q[i][qp] * volumetric_q[j][qp];
1147 
1148  // Now for the volumetric portion of the RHS
1149  for (const auto qp : make_range(qrule.n_points()))
1150  {
1151  const Real x = q_point[qp](0);
1152  const Real y = q_point[qp](1);
1153  const Real z = q_point[qp](2);
1154 
1155  // "f" is the forcing function for the Poisson equation, which is
1156  // just the divergence of the exact solution for the vector field.
1157  // This is the well-known "method of manufactured solutions".
1158  Real f = 0;
1159  if (dim == 2)
1160  f = MixedExactSolution().forcing(x, y);
1161  else if (dim == 3)
1162  f = MixedExactSolution().forcing(x, y, z);
1163  for (const auto i : make_range(lambda_n_dofs))
1164  if (!external_boundary_indices.count(i))
1165  F_lm_libmesh(i) += JxW[qp] * f * volumetric_u[i][qp];
1166  }
1167 
1168  // Now for the Dirichlet boundary portion of our RHS
1169  for (const auto s : elem->side_index_range())
1170  {
1171  lambda_fe_face->reinit(elem, s);
1172  for (const auto i : make_range(lambda_n_dofs))
1173  if (external_boundary_indices.count(i))
1174  for (const auto j : make_range(lambda_n_dofs))
1175  if (external_boundary_indices.count(j))
1176  for (const auto qp : make_range(qface.n_points()))
1177  K_lm_libmesh(i, j) +=
1178  JxW_face[qp] * lambda_phi_face[i][qp] * lambda_phi_face[j][qp];
1179 
1180  if (!elem->neighbor_ptr(s))
1181  {
1182  vector_fe_face->reinit(elem, s);
1183  for (const auto i : make_range(lambda_n_dofs))
1184  {
1185  if (external_boundary_indices.count(i))
1186  continue;
1187 
1188  const auto local_q_soln = local_solns[i].head(vector_n_dofs);
1189  compute_qp_soln(face_q[i], qface.n_points(), vector_phi_face, local_q_soln);
1190  }
1191 
1192  for (const auto qp : make_range(qface.n_points()))
1193  {
1194  const Real xf = qface_point[qp](0);
1195  const Real yf = qface_point[qp](1);
1196  const Real zf = qface_point[qp](2);
1197 
1198  // The boundary value for scalar field.
1199  Real scalar_value = 0;
1200  if (dim == 2)
1201  scalar_value = MixedExactSolution().scalar(xf, yf);
1202  else if (dim == 3)
1203  scalar_value = MixedExactSolution().scalar(xf, yf, zf);
1204  for (const auto i : make_range(lambda_n_dofs))
1205  if (!external_boundary_indices.count(i))
1206  F_lm_libmesh(i) += JxW_face[qp] * scalar_value * face_q[i][qp] * normals[qp];
1207  }
1208  }
1209  }
1210 
1211  // We were performing our finite element assembly for the implicit solve step of our
1212  // example. Add our local element vectors/matrices into the global system
1213  dof_map.constrain_element_matrix_and_vector(K_lm_libmesh, F_lm_libmesh, lambda_dof_indices);
1214  matrix.add_matrix(K_lm_libmesh, lambda_dof_indices);
1215  lambda_system.rhs->add_vector(F_lm_libmesh, lambda_dof_indices);
1216  }
1217  else
1218  {
1219  // Must populate Lambda before calling compute_rhs
1220  Lambda.resize(lambda_n_dofs);
1221  lambda_system.current_local_solution->get(lambda_dof_indices, lambda_solution_std_vec);
1222  for (const auto i : make_range(lambda_n_dofs))
1223  Lambda(i) = lambda_solution_std_vec[i];
1224 
1225  compute_rhs(vector_n_dofs, scalar_n_dofs, elem, libMesh::invalid_uint);
1226  auto & local_soln = local_solns[0];
1227  local_soln = Kinv_mixed * F_mixed;
1228  const auto vector_soln = local_soln.head(vector_n_dofs);
1229  const auto scalar_soln = local_soln.tail(scalar_n_dofs);
1230  for (const auto i : make_range(vector_n_dofs))
1231  system.solution->set(vector_dof_indices[i], vector_soln(i));
1232  for (const auto i : make_range(scalar_n_dofs))
1233  system.solution->set(scalar_dof_indices[i], scalar_soln(i));
1234 
1235  // Now solve for the enriched scalar solution using our Lagrange
1236  // multiplier solution, q, and our low-order u
1238  dof_map,
1239  system,
1240  elem,
1241  vector_soln,
1242  scalar_soln,
1243  Lambda,
1244  *vector_fe,
1245  *vector_fe_face,
1246  *scalar_fe,
1247  *scalar_fe_face,
1248  *lambda_fe_face,
1249  qrule,
1250  qface);
1251  }
1252  }
1253 
1254  if (!global_solve)
1255  {
1256  system.solution->close();
1257  // Scatter solution into the current_solution which is used in error computation
1258  system.update();
1259  }
1260 }
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition: fe_type.h:196
const unsigned int invalid_uint
A number which is used quite often to represent an invalid or uninitialized value for an unsigned int...
Definition: libmesh.h:310
virtual void reinit()
Reinitializes degrees of freedom and other required data on the current mesh.
Definition: system.C:454
unsigned int dim
Real compute_tau(const bool internal_face, bool &tau_found, const Elem *const elem)
Manages consistently variables, degrees of freedom, coefficient vectors, matrices and linear solvers ...
void resize(const unsigned int n)
Resize the vector.
Definition: dense_vector.h:396
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
Real scalar(Real x, Real y)
Order default_quadrature_order() const
Definition: fe_type.h:371
NumericVector< Number > & add_vector(std::string_view vec_name, const bool projections=true, const ParallelType type=PARALLEL)
Adds the additional vector vec_name to this system.
Definition: system.C:768
VectorXcd EigenVector
const T_sys & get_system(std::string_view name) const
This is the MeshBase class.
Definition: mesh_base.h:75
unsigned int variable_number(std::string_view var) const
Definition: system.C:1609
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition: system.h:96
void compute_enriched_soln(const MeshBase &mesh, const DofMap &dof_map, System &system, const Elem *const elem, const EigenVector &vector_soln, const EigenVector &scalar_soln, const EigenVector &Lambda, FEVectorBase &vector_fe, FEVectorBase &vector_fe_face, FEBase &scalar_fe, FEBase &scalar_fe_face, FEBase &lambda_fe_face, QBase &qrule, QBase &qface)
MatrixXcd EigenMatrix
Real forcing(Real x, Real y)
const FEType & variable_type(const unsigned int i) const
Definition: system.h:2508
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const MeshBase & get_mesh() const
void resize(const unsigned int new_m, const unsigned int new_n)
Resizes the matrix to the specified size and calls zero().
Definition: dense_matrix.h:895
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:140
This class implements specific orders of Gauss quadrature.
unsigned int mesh_dimension() const
Definition: mesh_base.C:372
std::unique_ptr< NumericVector< Number > > current_local_solution
All the values I need to compute my contribution to the simulation at hand.
Definition: system.h:1605
SparseMatrix< Number > & add_matrix(std::string_view mat_name, ParallelType type=PARALLEL, MatrixBuildType mat_build_type=MatrixBuildType::AUTOMATIC)
Adds the additional matrix mat_name to this system.
Definition: system.C:1010
const DofMap & get_dof_map() const
Definition: system.h:2374
void compute_qp_soln(std::vector< SolnType > &qp_vec, const unsigned int n_qps, const std::vector< std::vector< PhiType >> &phi, const LocalSolutionVector &soln)

◆ assemble_hdg() [1/2]

void assemble_hdg ( EquationSystems es,
const std::string &  system_name 
)

Referenced by main().

◆ assemble_hdg() [2/2]

void assemble_hdg ( EquationSystems es,
const std::string &  libmesh_dbg_varsystem_name 
)

Definition at line 805 of file vector_fe_ex8.C.

References fe_assembly().

806 {
807  libmesh_assert_equal_to(system_name, "Lambda");
808  fe_assembly(es, /*global_solve=*/true);
809 }
void fe_assembly(EquationSystems &es, bool global_solve)

◆ compute_enriched_soln()

void compute_enriched_soln ( const MeshBase mesh,
const DofMap dof_map,
System system,
const Elem *const  elem,
const EigenVector vector_soln,
const EigenVector scalar_soln,
const EigenVector Lambda,
FEVectorBase vector_fe,
FEVectorBase vector_fe_face,
FEBase scalar_fe,
FEBase scalar_fe_face,
FEBase lambda_fe_face,
QBase qrule,
QBase qface 
)

Definition at line 291 of file vector_fe_ex8.C.

References libMesh::FEGenericBase< OutputType >::build(), libMesh::compute_qp_soln(), dim, libMesh::DofMap::dof_indices(), MixedExactSolution::forcing(), libMesh::FEAbstract::get_JxW(), libMesh::FEAbstract::get_normals(), libMesh::FEGenericBase< OutputType >::get_phi(), libMesh::FEAbstract::get_xyz(), libMesh::Elem::hmin(), libMesh::DenseMatrix< T >::lu_solve(), libMesh::make_range(), mesh, libMesh::MeshBase::mesh_dimension(), libMesh::QBase::n_points(), libMesh::Elem::neighbor_ptr(), libMesh::Real, libMesh::FEAbstract::reinit(), libMesh::DenseVector< T >::resize(), libMesh::DenseMatrix< T >::resize(), libMesh::Elem::side_index_range(), libMesh::System::solution, libMesh::System::variable_number(), and libMesh::DofMap::variable_type().

Referenced by alternative_fe_assembly(), and fe_assembly().

305 {
306  const unsigned int dim = mesh.mesh_dimension();
307 
308  // Create FE objects
309  const FEType enriched_scalar_fe_type =
310  dof_map.variable_type(system.variable_number("u_enriched"));
311  std::unique_ptr<FEBase> enriched_scalar_fe(FEBase::build(dim, enriched_scalar_fe_type));
312  std::unique_ptr<FEBase> enriched_scalar_fe_face(FEBase::build(dim, enriched_scalar_fe_type));
313 
314  // Attach quadrature rules
315  enriched_scalar_fe->attach_quadrature_rule(&qrule);
316  enriched_scalar_fe_face->attach_quadrature_rule(&qface);
317 
318  // pre-request our required volumetric data
319  const auto & JxW = vector_fe.get_JxW();
320  const auto & q_point = vector_fe.get_xyz();
321  const auto & scalar_phi = scalar_fe.get_phi();
322  const auto & enriched_scalar_phi = enriched_scalar_fe->get_phi();
323  const auto & enriched_scalar_dphi = enriched_scalar_fe->get_dphi();
324 
325  // pre-request our required element face data
326  const auto & vector_phi_face = vector_fe_face.get_phi();
327  const auto & scalar_phi_face = scalar_fe_face.get_phi();
328  const auto & enriched_scalar_phi_face = enriched_scalar_fe_face->get_phi();
329  const auto & lambda_phi_face = lambda_fe_face.get_phi();
330  const auto & JxW_face = scalar_fe_face.get_JxW();
331  const auto & normals = vector_fe_face.get_normals();
332 
333  // Data structures for computing the enriched scalar solution
334  DenseMatrix<Number> K_enriched_scalar;
335  DenseVector<Number> F_enriched_scalar, U_enriched_scalar;
336  std::vector<dof_id_type> enriched_scalar_dof_indices;
337 
338  // The lambda solution at the quadrature points, used for computing the enriched scalar solution
339  std::vector<Number> lambda_qps;
340  // The scalar solution at the quadrature points, used for computing the enriched scalar solution
341  std::vector<Number> scalar_qps;
342  // The vector solution at the quadrature points, used for computing the enriched scalar solution
343  std::vector<Gradient> vector_qps;
344 
345  dof_map.dof_indices(elem, enriched_scalar_dof_indices, system.variable_number("u_enriched"));
346  const auto enriched_scalar_n_dofs = enriched_scalar_dof_indices.size();
347  // We have to add one for the mean value constraint
348  const auto m = enriched_scalar_n_dofs + 1;
349  const auto n = enriched_scalar_n_dofs + 1;
350 
351  K_enriched_scalar.resize(m, n);
352  F_enriched_scalar.resize(m);
353  U_enriched_scalar.resize(n);
354 
355  enriched_scalar_fe->reinit(elem);
356 
357  // We need the u solution for getting the correct mean value
358  compute_qp_soln(scalar_qps, qrule.n_points(), scalar_phi, scalar_soln);
359 
360  //
361  // We solve a modified diffusion problem
362  //
363 
364  for (const auto qp : make_range(qrule.n_points()))
365  {
366  // Diffusion kernel
367  for (const auto i : make_range(enriched_scalar_n_dofs))
368  for (const auto j : make_range(enriched_scalar_n_dofs))
369  K_enriched_scalar(i, j) +=
370  JxW[qp] * (enriched_scalar_dphi[i][qp] * enriched_scalar_dphi[j][qp]);
371 
372  // Forcing function kernel
373  {
374  const Real x = q_point[qp](0);
375  const Real y = q_point[qp](1);
376  const Real z = q_point[qp](2);
377 
378  // "f" is the forcing function for the Poisson equation, which is
379  // just the divergence of the exact solution for the vector field.
380  // This is the well-known "method of manufactured solutions".
381  Real f = 0;
382  if (dim == 2)
383  f = MixedExactSolution().forcing(x, y);
384  else if (dim == 3)
385  f = MixedExactSolution().forcing(x, y, z);
386 
387  // Scalar equation RHS
388  for (const auto i : make_range(enriched_scalar_n_dofs))
389  F_enriched_scalar(i) += JxW[qp] * enriched_scalar_phi[i][qp] * f;
390  }
391 
392  // Mean value part
393  {
394  // u dependence on LM
395  for (const auto i : make_range(enriched_scalar_n_dofs))
396  K_enriched_scalar(i, enriched_scalar_n_dofs) += JxW[qp] * enriched_scalar_phi[i][qp];
397 
398  // LM dependence on u
399  for (const auto j : make_range(enriched_scalar_n_dofs))
400  K_enriched_scalar(enriched_scalar_n_dofs, j) += JxW[qp] * enriched_scalar_phi[j][qp];
401 
402  // And RHS of LM equation
403  F_enriched_scalar(enriched_scalar_n_dofs) += JxW[qp] * scalar_qps[qp];
404  }
405  }
406 
407  bool tau_found = false;
408  for (auto side : elem->side_index_range())
409  {
410  // Reinit our face FE objects
411  vector_fe_face.reinit(elem, side);
412  compute_qp_soln(vector_qps, qface.n_points(), vector_phi_face, vector_soln);
413 
414  enriched_scalar_fe_face->reinit(elem, side);
415 
416  if (elem->neighbor_ptr(side) == nullptr)
417  for (const auto qp : make_range(qface.n_points()))
418  // Now do the external boundary term for <\hat{q} \cdot \vec{n}, \omega> ->
419  // <q + \tau (u - g), \omega> ->
420  // <q, \omega> + <\tau u, \omega> - <\tau g, \omega>
421  // BUT u = g on the boundary so we can drop those terms and just end up with
422  // <q, \omega>
423  for (const auto i : make_range(enriched_scalar_n_dofs))
424  F_enriched_scalar(i) -=
425  JxW_face[qp] * enriched_scalar_phi_face[i][qp] * vector_qps[qp] * normals[qp];
426  else
427  {
428  scalar_fe_face.reinit(elem, side);
429  compute_qp_soln(scalar_qps, qface.n_points(), scalar_phi_face, scalar_soln);
430  lambda_fe_face.reinit(elem, side);
431  compute_qp_soln(lambda_qps, qface.n_points(), lambda_phi_face, Lambda);
432 
433  // Stabilization parameter. In the single face discretization, only a single face has a
434  // non-zero value of tau
435  const Real tau = tau_found ? 0 : 1 / elem->hmin();
436  tau_found = true;
437 
438  for (const auto qp : make_range(qface.n_points()))
439  {
440  const auto normal = normals[qp];
441  const auto qhat = vector_qps[qp] + tau * (scalar_qps[qp] - lambda_qps[qp]) * normal;
442 
443  // Now do the internal boundary term for <\hat{q} \cdot \vec{n}, \omega> ->
444  for (const auto i : make_range(enriched_scalar_n_dofs))
445  F_enriched_scalar(i) -= JxW_face[qp] * enriched_scalar_phi_face[i][qp] * qhat * normal;
446  }
447  }
448  }
449 
450  K_enriched_scalar.lu_solve(F_enriched_scalar, U_enriched_scalar);
451 
452  // Our solution for the local enriched scalar dofs is complete. Insert into the global vector
453  // after eliminating the mean value constraint dof
454  DenseVector<Number> U_insertion(enriched_scalar_n_dofs);
455  for (const auto i : make_range(enriched_scalar_n_dofs))
456  U_insertion(i) = U_enriched_scalar(i);
457  system.solution->insert(U_insertion, enriched_scalar_dof_indices);
458 }
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition: fe_type.h:196
IntRange< unsigned short > side_index_range() const
Definition: elem.h:2710
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
Definition: dof_map.C:2164
unsigned int dim
void resize(const unsigned int n)
Resize the vector.
Definition: dense_vector.h:396
const FEType & variable_type(const unsigned int c) const
Definition: dof_map.h:2220
MeshBase & mesh
unsigned int variable_number(std::string_view var) const
Definition: system.C:1609
virtual Real hmin() const
Definition: elem.C:702
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition: system.h:1593
virtual_for_inffe const std::vector< Real > & get_JxW() const
Definition: fe_abstract.h:303
virtual void reinit(const Elem *elem, const std::vector< Point > *const pts=nullptr, const std::vector< Real > *const weights=nullptr)=0
This is at the core of this class.
unsigned int n_points() const
Definition: quadrature.h:131
virtual_for_inffe const std::vector< Point > & get_xyz() const
Definition: fe_abstract.h:280
const Elem * neighbor_ptr(unsigned int i) const
Definition: elem.h:2598
void lu_solve(const DenseVector< T > &b, DenseVector< T > &x)
Solve the system Ax=b given the input vector b.
Real forcing(Real x, Real y)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual_for_inffe const std::vector< Point > & get_normals() const
Definition: fe_abstract.h:459
void resize(const unsigned int new_m, const unsigned int new_n)
Resizes the matrix to the specified size and calls zero().
Definition: dense_matrix.h:895
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:140
unsigned int mesh_dimension() const
Definition: mesh_base.C:372
void compute_qp_soln(std::vector< SolnType > &qp_vec, const unsigned int n_qps, const std::vector< std::vector< PhiType >> &phi, const LocalSolutionVector &soln)
const std::vector< std::vector< OutputShape > > & get_phi() const
Definition: fe_base.h:207

◆ compute_qp_soln()

template<typename SolnType , typename PhiType , typename LocalSolutionVector >
void compute_qp_soln ( std::vector< SolnType > &  qp_vec,
const unsigned int  n_qps,
const std::vector< std::vector< PhiType >> &  phi,
const LocalSolutionVector &  soln 
)

Definition at line 271 of file vector_fe_ex8.C.

References libMesh::index_range(), libMesh::libmesh_assert(), and libMesh::make_range().

275 {
276  libmesh_assert(cast_int<std::size_t>(soln.size()) == phi.size());
277  qp_vec.resize(n_qps);
278  for (auto & val : qp_vec)
279  val = {};
280  for (const auto i : index_range(phi))
281  {
282  const auto & qp_phis = phi[i];
283  libmesh_assert(qp_phis.size() == n_qps);
284  const auto sol = soln(i);
285  for (const auto qp : make_range(n_qps))
286  qp_vec[qp] += qp_phis[qp] * sol;
287  }
288 }
libmesh_assert(ctx)
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:140
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition: int_range.h:117

◆ compute_tau()

Real compute_tau ( const bool  internal_face,
bool &  tau_found,
const Elem *const  elem 
)

Definition at line 813 of file vector_fe_ex8.C.

References libMesh::Elem::hmin().

Referenced by alternative_fe_assembly().

814 {
815  if (!internal_face)
816  // if we're an external face then tau is 0
817  return 0;
818  else if (tau_found)
819  // if we've already applied a non-zero tau on another face, then we also return 0
820  return 0;
821  else
822  {
823  // This is our first internal face. We will apply our non-zero tau (and mark that we have)
824  tau_found = true;
825  return 1 / elem->hmin();
826  }
827 }
virtual Real hmin() const
Definition: elem.C:702

◆ fe_assembly()

void fe_assembly ( EquationSystems es,
bool  global_solve 
)

Definition at line 464 of file vector_fe_ex8.C.

References libMesh::SparseMatrix< T >::add_matrix(), libMesh::System::add_vector(), libMesh::FEGenericBase< OutputType >::build(), compute_enriched_soln(), libMesh::System::current_local_solution, libMesh::FEType::default_quadrature_order(), dim, MixedExactSolution::forcing(), libMesh::System::get_dof_map(), libMesh::EquationSystems::get_mesh(), libMesh::EquationSystems::get_system(), libMesh::libmesh_assert(), libMesh::make_range(), mesh, libMesh::MeshBase::mesh_dimension(), libMesh::Real, libMesh::LinearImplicitSystem::reinit(), libMesh::DenseVector< T >::resize(), libMesh::DenseMatrix< T >::resize(), MixedExactSolution::scalar(), libMesh::System::variable_number(), and libMesh::System::variable_type().

Referenced by assemble_hdg(), and main().

465 {
466  const MeshBase & mesh = es.get_mesh();
467  const unsigned int dim = mesh.mesh_dimension();
468 
469  // The mixed, e.g. vector-scalar system
470  auto & system = es.get_system<System>("Mixed");
471  // Our implicit Lagrange multiplier system
472  auto & lambda_system = es.get_system<LinearImplicitSystem>("Lambda");
473 
474  const auto & dof_map = system.get_dof_map();
475  const auto & lambda_dof_map = lambda_system.get_dof_map();
476 
477  const FEType vector_fe_type = dof_map.variable_type(system.variable_number("q"));
478  const FEType scalar_fe_type = dof_map.variable_type(system.variable_number("u"));
479  const FEType lambda_fe_type =
480  lambda_dof_map.variable_type(lambda_system.variable_number("lambda"));
481 
482  // Volumetric FE objects
483  std::unique_ptr<FEVectorBase> vector_fe(FEVectorBase::build(dim, vector_fe_type));
484  std::unique_ptr<FEBase> scalar_fe(FEBase::build(dim, scalar_fe_type));
485 
486  // Volumetric quadrature rule
487  QGauss qrule(dim, scalar_fe_type.default_quadrature_order());
488 
489  // Attach quadrature rules for the FE objects that we will reinit within the element "volume"
490  vector_fe->attach_quadrature_rule(&qrule);
491  scalar_fe->attach_quadrature_rule(&qrule);
492 
493  // Declare finite element objects for boundary integration
494  std::unique_ptr<FEVectorBase> vector_fe_face(FEVectorBase::build(dim, vector_fe_type));
495  std::unique_ptr<FEBase> scalar_fe_face(FEBase::build(dim, scalar_fe_type));
496  std::unique_ptr<FEBase> lambda_fe_face(FEBase::build(dim, lambda_fe_type));
497 
498  // Boundary integration requires one quadrature rule with dimensionality one
499  // less than the dimensionality of the element.
500  QGauss qface(dim - 1, scalar_fe_type.default_quadrature_order());
501 
502  // Attach quadrature rules for the FE objects that we will reinit on the element faces
503  vector_fe_face->attach_quadrature_rule(&qface);
504  scalar_fe_face->attach_quadrature_rule(&qface);
505  lambda_fe_face->attach_quadrature_rule(&qface);
506 
507  // pre-request our required volumetric data
508  const auto & JxW = vector_fe->get_JxW();
509  const auto & q_point = vector_fe->get_xyz();
510  const auto & vector_phi = vector_fe->get_phi();
511  const auto & scalar_phi = scalar_fe->get_phi();
512  const auto & grad_scalar_phi = scalar_fe->get_dphi();
513  const auto & div_vector_phi = vector_fe->get_div_phi();
514 
515  // pre-request our required element face data
516  const auto & vector_phi_face = vector_fe_face->get_phi();
517  const auto & scalar_phi_face = scalar_fe_face->get_phi();
518  const auto & lambda_phi_face = lambda_fe_face->get_phi();
519  const auto & JxW_face = scalar_fe_face->get_JxW();
520  const auto & qface_point = vector_fe_face->get_xyz();
521  const auto & normals = vector_fe_face->get_normals();
522 
523  //
524  // We will need "Eigen" versions of many of the matrices/vectors because the
525  // libMesh DenseMatrix doesn't have an inverse API
526  //
527 
528  // LM matrix and RHS after eliminating vector and scalar dofs
529  DenseMatrix<Number> K_libmesh;
530  DenseVector<Number> P_libmesh;
531  EigenMatrix K;
532  EigenVector P;
533  // (A B C)(q) = (G)
534  // (Bt D E)(u) = (F)
535  // (Ct Et H)(l) = (0)
536  // Sinv is the inverse of a Schur complement S which is given by
537  // S = Bt * A^{-1} * B.
538  EigenMatrix A, Ainv, B, Bt, C, Ct, Sinv, D, E, Et, H;
539  EigenVector G, F;
540 
541  // Lambda eigen vector for constructing vector and scalar solutions
542  EigenVector Lambda;
543 
544  // Containers for dof indices
545  std::vector<dof_id_type> vector_dof_indices;
546  std::vector<dof_id_type> scalar_dof_indices;
547  std::vector<dof_id_type> lambda_dof_indices;
548  std::vector<Number> lambda_solution_std_vec;
549 
550  // The global system matrix
551  SparseMatrix<Number> & matrix = lambda_system.get_system_matrix();
552 
553  for (const auto & elem : mesh.active_local_element_ptr_range())
554  {
555  // Retrive our dof indices for all fields
556  dof_map.dof_indices(elem, vector_dof_indices, system.variable_number("q"));
557  dof_map.dof_indices(elem, scalar_dof_indices, system.variable_number("u"));
558  lambda_dof_map.dof_indices(elem, lambda_dof_indices, lambda_system.variable_number("lambda"));
559 
560  const auto vector_n_dofs = vector_dof_indices.size();
561  const auto scalar_n_dofs = scalar_dof_indices.size();
562  const auto lambda_n_dofs = lambda_dof_indices.size();
563 
564  // Reinit our volume FE objects
565  vector_fe->reinit(elem);
566  scalar_fe->reinit(elem);
567 
568  libmesh_assert_equal_to(vector_n_dofs, vector_phi.size());
569  libmesh_assert_equal_to(scalar_n_dofs, scalar_phi.size());
570 
571  // prepare our matrix/vector data structures for the vector equation
572  A.setZero(vector_n_dofs, vector_n_dofs);
573  B.setZero(vector_n_dofs, scalar_n_dofs);
574  C.setZero(vector_n_dofs, lambda_n_dofs);
575  G.setZero(vector_n_dofs);
576  // and for the scalar equation
577  Bt.setZero(scalar_n_dofs, vector_n_dofs);
578  D.setZero(scalar_n_dofs, scalar_n_dofs);
579  E.setZero(scalar_n_dofs, lambda_n_dofs);
580  F.setZero(scalar_n_dofs);
581  // and for the LM equation
582  Ct.setZero(lambda_n_dofs, vector_n_dofs);
583  Et.setZero(lambda_n_dofs, scalar_n_dofs);
584  H.setZero(lambda_n_dofs, lambda_n_dofs);
585 
586  for (const auto qp : make_range(qrule.n_points()))
587  {
588  // Vector equation dependence on vector dofs
589  for (const auto i : make_range(vector_n_dofs))
590  for (const auto j : make_range(vector_n_dofs))
591  A(i, j) -= JxW[qp] * (vector_phi[i][qp] * vector_phi[j][qp]);
592 
593  // Vector equation dependence on scalar dofs
594  for (const auto i : make_range(vector_n_dofs))
595  for (const auto j : make_range(scalar_n_dofs))
596  B(i, j) += JxW[qp] * (div_vector_phi[i][qp] * scalar_phi[j][qp]);
597 
598  // Scalar equation dependence on vector dofs
599  for (const auto i : make_range(scalar_n_dofs))
600  for (const auto j : make_range(vector_n_dofs))
601  Bt(i, j) += JxW[qp] * (grad_scalar_phi[i][qp] * vector_phi[j][qp]);
602 
603  // This is the end of the matrix summation loop
604  // Now we build the element right-hand-side contribution.
605  // This involves a single loop in which we integrate the "forcing
606  // function" in the PDE against the scalar test functions (k).
607  {
608  const Real x = q_point[qp](0);
609  const Real y = q_point[qp](1);
610  const Real z = q_point[qp](2);
611 
612  // "f" is the forcing function for the Poisson equation, which is
613  // just the divergence of the exact solution for the vector field.
614  // This is the well-known "method of manufactured solutions".
615  Real f = 0;
616  if (dim == 2)
617  f = MixedExactSolution().forcing(x, y);
618  else if (dim == 3)
619  f = MixedExactSolution().forcing(x, y, z);
620 
621  // Scalar equation RHS
622  for (const auto i : make_range(scalar_n_dofs))
623  F(i) -= JxW[qp] * scalar_phi[i][qp] * f;
624  }
625  }
626 
627  // At the beginning of the loop, we mark that we haven't found our "Single-Face" yet
628  bool tau_found = false;
629  for (auto side : elem->side_index_range())
630  {
631  // Reinit our face FE objects
632  vector_fe_face->reinit(elem, side);
633  scalar_fe_face->reinit(elem, side);
634  lambda_fe_face->reinit(elem, side);
635 
636  if (elem->neighbor_ptr(side) == nullptr)
637  for (const auto qp : make_range(qface.n_points()))
638  {
639  const Real xf = qface_point[qp](0);
640  const Real yf = qface_point[qp](1);
641  const Real zf = qface_point[qp](2);
642 
643  // The boundary value for scalar field.
644  Real scalar_value = 0;
645  if (dim == 2)
646  scalar_value = MixedExactSolution().scalar(xf, yf);
647  else if (dim == 3)
648  scalar_value = MixedExactSolution().scalar(xf, yf, zf);
649 
650  // External boundary -> Dirichlet faces -> Vector equation RHS
651  for (const auto i : make_range(vector_n_dofs))
652  G(i) += JxW_face[qp] * (vector_phi_face[i][qp] * normals[qp]) * scalar_value;
653 
654  // Now do the external boundary term for <\hat{q} \cdot \vec{n}, \omega> ->
655  // <q + \tau (u - g), \omega> ->
656  // <q, \omega> + <\tau u, \omega> - <\tau g, \omega>
657  // BUT u = g on the boundary so we can drop those terms and just end up with
658  // <q, \omega>
659  for (const auto i : make_range(scalar_n_dofs))
660  for (const auto j : make_range(vector_n_dofs))
661  Bt(i, j) -=
662  JxW_face[qp] * scalar_phi_face[i][qp] * (vector_phi_face[j][qp] * normals[qp]);
663 
664  // Need to do something with the external boundary LM dofs to prevent
665  // the matrix from being singular. Use a minus sign because the
666  // formulation by Cockburn results in negative diagonals for the LM
667  for (const auto i : make_range(lambda_n_dofs))
668  for (const auto j : make_range(lambda_n_dofs))
669  H(i, j) += JxW_face[qp] * lambda_phi_face[i][qp] * lambda_phi_face[j][qp];
670  }
671  else
672  {
673  // If we haven't found our "Single-Face" yet, then we assign tau to
674  // something non-zero. Else we have previously designated our nonzero
675  // "Single-Face" and so we mark tau as zero
676  const Real tau = tau_found ? 0 : 1 / elem->hmin();
677  tau_found = true;
678 
679  for (const auto qp : make_range(qface.n_points()))
680  {
681  const auto normal = normals[qp];
682  const auto normal_sq = normal * normal;
683 
684  // Vector equation dependence on LM dofs
685  for (const auto i : make_range(vector_n_dofs))
686  for (const auto j : make_range(lambda_n_dofs))
687  C(i, j) -=
688  JxW_face[qp] * (vector_phi_face[i][qp] * normals[qp]) * lambda_phi_face[j][qp];
689 
690  // Now do the internal boundary term for <\hat{q} \cdot \vec{n}, \omega> ->
691  // <q + \tau (u - \lambda), \omega> ->
692  // <q, \omega> + <\tau u, \omega> - <\tau \lambda, \omega>
693  for (const auto i : make_range(scalar_n_dofs))
694  {
695  for (const auto j : make_range(vector_n_dofs))
696  Bt(i, j) -= JxW_face[qp] * scalar_phi_face[i][qp] * (vector_phi_face[j][qp] * normal);
697 
698  for (const auto j : make_range(scalar_n_dofs))
699  D(i, j) -=
700  JxW_face[qp] * scalar_phi_face[i][qp] * tau * scalar_phi_face[j][qp] * normal_sq;
701 
702  for (const auto j : make_range(lambda_n_dofs))
703  E(i, j) +=
704  JxW_face[qp] * scalar_phi_face[i][qp] * tau * lambda_phi_face[j][qp] * normal_sq;
705  }
706 
707  // Now do the internal boundary term for <\hat{q} \cdot \vec{n}, \mu> ->
708  // <q + \tau (u - \lambda), \mu> ->
709  // <q, \mu> + <\tau u, \mu> - <\tau \lambda, \mu>
710  for (const auto i : make_range(lambda_n_dofs))
711  {
712  for (const auto j : make_range(vector_n_dofs))
713  Ct(i, j) -= JxW_face[qp] * lambda_phi_face[i][qp] * (vector_phi_face[j][qp] * normal);
714 
715  for (const auto j : make_range(scalar_n_dofs))
716  Et(i, j) -=
717  JxW_face[qp] * lambda_phi_face[i][qp] * tau * scalar_phi_face[j][qp] * normal_sq;
718 
719  for (const auto j : make_range(lambda_n_dofs))
720  H(i, j) +=
721  JxW_face[qp] * lambda_phi_face[i][qp] * tau * lambda_phi_face[j][qp] * normal_sq;
722  }
723  }
724  }
725  }
726 
727  Ainv = A.inverse();
728  // Compute the Schur complement inverse
729  Sinv = (D - Bt * Ainv * B).inverse();
730  const auto BtAinvCMinusE = Bt * Ainv * C - E;
731  const auto BtAinvGMinusF = Bt * Ainv * G - F;
732  const auto CtAinv = Ct * Ainv;
733  const auto BSinv = B * Sinv;
734  const auto EtSinv = Et * Sinv;
735  K = -CtAinv * (BSinv * BtAinvCMinusE + C) + EtSinv * BtAinvCMinusE + H;
736  P = -CtAinv * (BSinv * BtAinvGMinusF + G) + EtSinv * BtAinvGMinusF;
737 
738  // Build our libMesh data structures from the Eigen ones
739  K_libmesh.resize(K.rows(), K.cols());
740  P_libmesh.resize(P.size());
741  libmesh_assert((K.rows() == K.cols()) && (K.rows() == P.size()));
742  for (const auto i : make_range(K.rows()))
743  {
744  P_libmesh(i) = P(i);
745  for (const auto j : make_range(K.cols()))
746  K_libmesh(i, j) = K(i, j);
747  }
748 
749  if (global_solve)
750  {
751  // We were performing our finite element assembly for the implicit solve step of our
752  // example. Add our local element vectors/matrices into the global system
753  dof_map.constrain_element_matrix_and_vector(K_libmesh, P_libmesh, lambda_dof_indices);
754  matrix.add_matrix(K_libmesh, lambda_dof_indices);
755  lambda_system.rhs->add_vector(P_libmesh, lambda_dof_indices);
756  }
757  else
758  {
759  //
760  // We are doing our finite element assembly for the second time. We now know the Lagrange
761  // multiplier solution. With that and the local element matrices and vectors we can compute
762  // the vector and scalar solutions
763  //
764 
765  Lambda.resize(lambda_n_dofs);
766  lambda_system.current_local_solution->get(lambda_dof_indices, lambda_solution_std_vec);
767  for (const auto i : make_range(lambda_n_dofs))
768  Lambda(i) = lambda_solution_std_vec[i];
769  const auto scalar_soln = Sinv * (BtAinvCMinusE * Lambda - BtAinvGMinusF);
770  const auto vector_soln = Ainv * (G - B * scalar_soln - C * Lambda);
771  for (const auto i : make_range(vector_n_dofs))
772  system.solution->set(vector_dof_indices[i], vector_soln(i));
773  for (const auto i : make_range(scalar_n_dofs))
774  system.solution->set(scalar_dof_indices[i], scalar_soln(i));
775 
776  // Now solve for the enriched scalar solution using our Lagrange
777  // multiplier solution, q, and our low-order u
779  dof_map,
780  system,
781  elem,
782  vector_soln,
783  scalar_soln,
784  Lambda,
785  *vector_fe,
786  *vector_fe_face,
787  *scalar_fe,
788  *scalar_fe_face,
789  *lambda_fe_face,
790  qrule,
791  qface);
792  }
793  }
794 
795  if (!global_solve)
796  {
797  system.solution->close();
798  // Scatter solution into the current_solution which is used in error computation
799  system.update();
800  }
801 }
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition: fe_type.h:196
unsigned int dim
Manages consistently variables, degrees of freedom, coefficient vectors, matrices and linear solvers ...
void resize(const unsigned int n)
Resize the vector.
Definition: dense_vector.h:396
MeshBase & mesh
Real scalar(Real x, Real y)
Order default_quadrature_order() const
Definition: fe_type.h:371
NumericVector< Number > & add_vector(std::string_view vec_name, const bool projections=true, const ParallelType type=PARALLEL)
Adds the additional vector vec_name to this system.
Definition: system.C:768
virtual void reinit() override
Reinitializes the member data fields associated with the system, so that, e.g., assemble() may be use...
VectorXcd EigenVector
const T_sys & get_system(std::string_view name) const
This is the MeshBase class.
Definition: mesh_base.h:75
unsigned int variable_number(std::string_view var) const
Definition: system.C:1609
Definition: assembly.h:38
virtual void add_matrix(const DenseMatrix< T > &dm, const std::vector< numeric_index_type > &rows, const std::vector< numeric_index_type > &cols)=0
Add the full matrix dm to the SparseMatrix.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition: system.h:96
libmesh_assert(ctx)
void compute_enriched_soln(const MeshBase &mesh, const DofMap &dof_map, System &system, const Elem *const elem, const EigenVector &vector_soln, const EigenVector &scalar_soln, const EigenVector &Lambda, FEVectorBase &vector_fe, FEVectorBase &vector_fe_face, FEBase &scalar_fe, FEBase &scalar_fe_face, FEBase &lambda_fe_face, QBase &qrule, QBase &qface)
MatrixXcd EigenMatrix
Real forcing(Real x, Real y)
const FEType & variable_type(const unsigned int i) const
Definition: system.h:2508
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const MeshBase & get_mesh() const
void resize(const unsigned int new_m, const unsigned int new_n)
Resizes the matrix to the specified size and calls zero().
Definition: dense_matrix.h:895
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:140
This class implements specific orders of Gauss quadrature.
unsigned int mesh_dimension() const
Definition: mesh_base.C:372
std::unique_ptr< NumericVector< Number > > current_local_solution
All the values I need to compute my contribution to the simulation at hand.
Definition: system.h:1605
const DofMap & get_dof_map() const
Definition: system.h:2374

◆ main() [1/2]

int main ( int  argc,
char **  argv 
)

Definition at line 93 of file vector_fe_ex8.C.

References libMesh::EquationSystems::add_system(), libMesh::System::add_variable(), alternative_assemble_hdg(), alternative_fe_assembly(), assemble_hdg(), libMesh::System::attach_assemble_function(), libMesh::ExactSolution::attach_exact_derivs(), libMesh::ExactSolution::attach_exact_values(), libMesh::MeshTools::Generation::build_cube(), libMesh::MeshTools::Generation::build_square(), libMesh::ExactSolution::compute_error(), libMesh::default_solver_package(), libMesh::ExactSolution::extra_quadrature_order(), fe_assembly(), libMesh::TriangleWrapper::init(), libMesh::EquationSystems::init(), libMesh::INVALID_SOLVER_PACKAGE, libMesh::ExactSolution::l2_error(), mesh, libMesh::out, libMesh::MeshTools::Modification::permute_elements(), libMesh::SIDE_HIERARCHIC, libMesh::System::solve(), and libMesh::ExodusII_IO::write_equation_systems().

94 {
95  // Initialize libMesh.
96  LibMeshInit init(argc, argv);
97 
98  // This example requires a linear solver package.
99  libmesh_example_requires(libMesh::default_solver_package() != INVALID_SOLVER_PACKAGE,
100  "--enable-petsc, --enable-trilinos, or --enable-eigen");
101 
102  // Parse the input file.
103  GetPot infile("vector_fe_ex8.in");
104 
105  // But allow the command line to override it.
106  infile.parse_command_line(argc, argv);
107 
108  // Read in parameters from the command line and the input file.
109  const unsigned int dimension = infile("dim", 2);
110  const unsigned int grid_size = infile("grid_size", 15);
111  const unsigned int order = infile("order", 1);
112  const std::string family_str = infile("family", "L2_LAGRANGE");
113  const FEFamily family = Utility::string_to_enum<FEFamily>(family_str);
114  const std::string vec_family_str = infile("vecfamily", "L2_LAGRANGE_VEC");
115  const FEFamily vec_family = Utility::string_to_enum<FEFamily>(vec_family_str);
116 
117  // Skip higher-dimensional examples on a lower-dimensional libMesh build.
118  libmesh_example_requires(dimension <= LIBMESH_DIM, dimension << "D support");
119 
120  // Create a mesh, with dimension to be overridden later, distributed
121  // across the default MPI communicator.
122  Mesh mesh(init.comm());
123 
124  // Use the MeshTools::Generation mesh generator to create a uniform
125  // grid on the cube [-1,1]^D. To accomodate first order side hierarchics, we must
126  // use TRI6/7 or QUAD8/9 elements in 2d, or TET14 or HEX27 in 3d.
127  const std::string elem_str = infile("element_type", std::string("TRI6"));
128 
129  libmesh_error_msg_if((dimension == 2 && elem_str != "TRI6" && elem_str != "TRI7" &&
130  elem_str != "QUAD8" && elem_str != "QUAD9") ||
131  (dimension == 3 && elem_str != "TET14" && elem_str != "HEX27"),
132  "You selected "
133  << elem_str
134  << " but this example must be run with TRI6, TRI7, QUAD8, or QUAD9 in 2d"
135  << " or with TET14, or HEX27 in 3d.");
136 
137  if (dimension == 2)
139  mesh, grid_size, grid_size, -1., 1., -1., 1., Utility::string_to_enum<ElemType>(elem_str));
140  else if (dimension == 3)
142  grid_size,
143  grid_size,
144  grid_size,
145  -1.,
146  1.,
147  -1.,
148  1.,
149  -1.,
150  1.,
151  Utility::string_to_enum<ElemType>(elem_str));
152 
153  // Make sure the code is robust against nodal reorderings.
155 
156  // Create an equation systems object.
157  EquationSystems equation_systems(mesh);
158 
159  // Declare the system "Mixed" and its variables.
160  auto & system = equation_systems.add_system<System>("Mixed");
161 
162  // Add the LM system
163  auto & lm_system = equation_systems.add_system<LinearImplicitSystem>("Lambda");
164 
165  // Adds the variable "q" and "u" to "Mixed". "q" will be our vector field
166  // whereas "u" will be the scalar field.
167  system.add_variable("q", static_cast<Order>(order), vec_family);
168  system.add_variable("u", static_cast<Order>(order), family);
169 
170  // We also add a higher order version of our 'p' variable whose solution we
171  // will postprocess using the Lagrange multiplier, 'u', and the low order 'p'
172  // solution
173  system.add_variable("u_enriched", static_cast<Order>(order+1), family);
174 
175  // Add our Lagrange multiplier to the implicit system
176  lm_system.add_variable("lambda", static_cast<Order>(order), SIDE_HIERARCHIC);
177 
178  // Give the system a pointer to the matrix assembly
179  // function. This will be called when needed by the library.
181 
182  // Initialize the data structures for the equation system.
183  equation_systems.init();
184 
185  // Solve the implicit system for the Lagrange multiplier
186  lm_system.solve();
187 
188  // Armed with our Lagrange multiplier solution, we can now compute the vector and scalar solutions
189  fe_assembly(equation_systems, /*global_solve=*/false);
190 
191  //
192  // Now we will compute our solution approximation errors
193  //
194 
195  ExactSolution exact_sol(equation_systems);
196 
197  if (dimension == 2)
198  {
199  SolutionFunction<2> soln_func;
200  SolutionGradient<2> soln_grad;
201 
202  // Build FunctionBase* containers to attach to the ExactSolution object.
203  std::vector<FunctionBase<Number> *> sols(1, &soln_func);
204  std::vector<FunctionBase<Gradient> *> grads(1, &soln_grad);
205 
206  exact_sol.attach_exact_values(sols);
207  exact_sol.attach_exact_derivs(grads);
208  }
209  else if (dimension == 3)
210  {
211  SolutionFunction<3> soln_func;
212  SolutionGradient<3> soln_grad;
213 
214  // Build FunctionBase* containers to attach to the ExactSolution object.
215  std::vector<FunctionBase<Number> *> sols(1, &soln_func);
216  std::vector<FunctionBase<Gradient> *> grads(1, &soln_grad);
217 
218  exact_sol.attach_exact_values(sols);
219  exact_sol.attach_exact_derivs(grads);
220  }
221 
222  // Use higher quadrature order for more accurate error results.
223  int extra_error_quadrature = infile("extra_error_quadrature", 2);
224  if (extra_error_quadrature)
225  exact_sol.extra_quadrature_order(extra_error_quadrature);
226 
227  // Compute the error.
228  exact_sol.compute_error("Mixed", "q");
229  exact_sol.compute_error("Mixed", "u");
230  exact_sol.compute_error("Mixed", "u_enriched");
231 
232  // Print out the error values.
233  libMesh::out << "L2 error is: " << exact_sol.l2_error("Mixed", "q") << std::endl;
234  libMesh::out << "L2 error for u is: " << exact_sol.l2_error("Mixed", "u") << std::endl;
235  libMesh::out << "L2 error u_enriched is: " << exact_sol.l2_error("Mixed", "u_enriched")
236  << std::endl;
237 
238 #ifdef LIBMESH_HAVE_EXODUS_API
239 
240  // We write the file in the ExodusII format.
241  ExodusII_IO(mesh).write_equation_systems("out.e", equation_systems);
242 
243 #endif // #ifdef LIBMESH_HAVE_EXODUS_API
244 
245  // Allright let's dry a different implementation and then ensure we get the same results
246  // auto mixed_soln = system.solution->clone();
247  // auto lm_soln = lm_system.solution->clone();
249  lm_system.solve();
250  // Armed with our Lagrange multiplier solution, we can now compute the vector and scalar solutions
251  alternative_fe_assembly(equation_systems, /*global_solve=*/false);
252 
253  // Compute the error.
254  exact_sol.compute_error("Mixed", "q");
255  exact_sol.compute_error("Mixed", "u");
256  exact_sol.compute_error("Mixed", "u_enriched");
257  // Print out the error values.
258  libMesh::out << "L2 error is: " << exact_sol.l2_error("Mixed", "q") << std::endl;
259  libMesh::out << "L2 error for u is: " << exact_sol.l2_error("Mixed", "u") << std::endl;
260  libMesh::out << "L2 error u_enriched is: " << exact_sol.l2_error("Mixed", "u_enriched")
261  << std::endl;
262 
263  // All done.
264  return 0;
265 }
This class handles the computation of the L2 and/or H1 error for the Systems in the EquationSystems o...
This is the EquationSystems class.
The ExodusII_IO class implements reading meshes in the ExodusII file format from Sandia National Labs...
Definition: exodusII_io.h:52
Manages consistently variables, degrees of freedom, coefficient vectors, matrices and linear solvers ...
void fe_assembly(EquationSystems &es, bool global_solve)
MeshBase & mesh
void build_square(UnstructuredMesh &mesh, const unsigned int nx, const unsigned int ny, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
A specialized build_cube() for 2D meshes.
The LibMeshInit class, when constructed, initializes the dependent libraries (e.g.
Definition: libmesh.h:90
void permute_elements(MeshBase &mesh)
Randomly permute the nodal ordering of each element (without twisting the element mapping)...
SolverPackage default_solver_package()
Definition: libmesh.C:1117
virtual void write_equation_systems(const std::string &fname, const EquationSystems &es, const std::set< std::string > *system_names=nullptr) override
Writes out the solution for no specific time or timestep.
Definition: exodusII_io.C:2033
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition: system.h:96
void init(triangulateio &t)
Initializes the fields of t to nullptr/0 as necessary.
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
Adds the variable var to the list of variables for this system.
Definition: system.C:1357
void attach_assemble_function(void fptr(EquationSystems &es, const std::string &name))
Register a user function to use in assembling the system matrix and RHS.
Definition: system.C:2161
virtual void solve()
Solves the system.
Definition: system.h:347
void assemble_hdg(EquationSystems &es, const std::string &system_name)
OStreamProxy out
void alternative_assemble_hdg(EquationSystems &es, const std::string &system_name)
FEFamily
defines an enum for finite element families.
void alternative_fe_assembly(EquationSystems &es, bool global_solve)
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition: mesh.h:50
void build_cube(UnstructuredMesh &mesh, const unsigned int nx=0, const unsigned int ny=0, const unsigned int nz=0, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const Real zmin=0., const Real zmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
Builds a (elements) cube.

◆ main() [2/2]

int main ( )

Definition at line 1273 of file vector_fe_ex8.C.

1274 {
1275  return 0;
1276 }