libMesh
Loading...
Searching...
No Matches
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 &  libmesh_dbg_varsystem_name 
)

Definition at line 1264 of file vector_fe_ex8.C.

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)

References alternative_fe_assembly().

◆ alternative_assemble_hdg() [2/2]

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

Referenced by main().

◆ alternative_fe_assembly()

void alternative_fe_assembly ( EquationSystems es,
bool  global_solve 
)

Definition at line 833 of file vector_fe_ex8.C.

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}
unsigned int dim
Real scalar(Real x, Real y)
Real forcing(Real x, Real y)
Defines a dense matrix for use in Finite Element-type computations.
void resize(const unsigned int new_m, const unsigned int new_n)
Resizes the matrix to the specified size and calls zero().
Defines a dense vector for use in Finite Element-type computations.
void resize(const unsigned int n)
Resize the vector.
This is the base class from which all geometric element types are derived.
Definition elem.h:96
const MeshBase & get_mesh() const
const T_sys & get_system(std::string_view name) const
static std::unique_ptr< FEGenericBase > build(const unsigned int dim, const FEType &type)
Builds a specific finite element type.
static void dofs_on_side(const Elem *const elem, const unsigned int dim, const FEType &fe_t, unsigned int s, std::vector< unsigned int > &di, const bool add_p_level=true)
Fills the vector di with the local degree of freedom indices associated with side s of element elem A...
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition fe_type.h:197
Order default_quadrature_order() const
Definition fe_type.h:415
Manages consistently variables, degrees of freedom, coefficient vectors, matrices and linear solvers ...
This is the MeshBase class.
Definition mesh_base.h:81
unsigned int mesh_dimension() const
Definition mesh_base.C:430
This class implements specific orders of Gauss quadrature.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition system.h:100
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:998
virtual void reinit()
Reinitializes degrees of freedom and other required data on the current mesh.
Definition system.C:442
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:1667
const FEType & variable_type(const unsigned int i) const
Definition system.C:2721
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:756
unsigned int variable_number(std::string_view var) const
Definition system.C:1398
const DofMap & get_dof_map() const
Definition system.h:2417
MeshBase & mesh
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:303
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
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:176
VectorXcd EigenVector
MatrixXcd EigenMatrix
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)
Real compute_tau(const bool internal_face, bool &tau_found, const Elem *const elem)

References libMesh::System::add_matrix(), libMesh::System::add_vector(), libMesh::FEGenericBase< OutputType >::build(), compute_enriched_soln(), 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::QBase::n_points(), 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().

◆ assemble_hdg() [1/2]

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

Definition at line 805 of file vector_fe_ex8.C.

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)

References fe_assembly().

◆ assemble_hdg() [2/2]

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

Referenced by main().

◆ 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.

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}
void lu_solve(const DenseVector< T > &b, DenseVector< T > &x)
Solve the system Ax=b given the input vector b.
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
Definition dof_map.C:2201
const FEType & variable_type(const unsigned int i) const
Definition dof_map.h:2388
virtual Real hmin() const
Definition elem.C:683
const Elem * neighbor_ptr(unsigned int i) const
Definition elem.h:2615
virtual_for_inffe const std::vector< Point > & get_normals() const
virtual_for_inffe const std::vector< Real > & get_JxW() const
virtual_for_inffe const std::vector< Point > & get_xyz() const
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.
const std::vector< std::vector< OutputShape > > & get_phi() const
Definition fe_base.h:207
unsigned int n_points() const
Definition quadrature.h:131
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition system.h:1655

References libMesh::FEGenericBase< OutputType >::build(), 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().

◆ 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.

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}
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:153
libmesh_assert(ctx)

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

Referenced by alternative_fe_assembly(), and compute_enriched_soln().

◆ 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.

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}

References libMesh::Elem::hmin().

Referenced by alternative_fe_assembly().

◆ fe_assembly()

void fe_assembly ( EquationSystems es,
bool  global_solve 
)

Definition at line 464 of file vector_fe_ex8.C.

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}
virtual void reinit() override
Reinitializes the member data fields associated with the system, so that, e.g., assemble() may be use...
Generic sparse matrix.
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.
Definition assembly.h:39

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::QBase::n_points(), 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().

◆ main() [1/2]

int main ( )

Definition at line 1273 of file vector_fe_ex8.C.

1274{
1275 return 0;
1276}

◆ main() [2/2]

int main ( int  argc,
char **  argv 
)

Definition at line 93 of file vector_fe_ex8.C.

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 is the EquationSystems class.
This class handles the computation of the L2 and/or H1 error for the Systems in the EquationSystems o...
The ExodusII_IO class implements reading meshes in the ExodusII file format from Sandia National Labs...
Definition exodusII_io.h:53
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.
The LibMeshInit class, when constructed, initializes the dependent libraries (e.g.
Definition libmesh.h:92
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition mesh.h:51
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:1959
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:1344
virtual void solve()
Solves the system.
Definition system.h:349
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.
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.
void permute_elements(MeshBase &mesh)
Randomly permute the nodal ordering of each element (without twisting the element mapping).
void init(triangulateio &t)
Initializes the fields of t to nullptr/0 as necessary.
SolverPackage default_solver_package()
Definition libmesh.C:1064
OStreamProxy out
void alternative_assemble_hdg(EquationSystems &es, const std::string &system_name)
void assemble_hdg(EquationSystems &es, const std::string &system_name)

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::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().