void assemble_poisson(EquationSystems & es, const std::string & system_name) { libmesh_assert_equal_to (system_name, "Poisson"); const MeshBase & mesh = es.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); LinearImplicitSystem & system = es.get_system<LinearImplicitSystem>("Poisson"); const DofMap & dof_map = system.get_dof_map(); FEType fe_type = dof_map.variable_type(0); UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); QGauss qrule (dim, FIFTH); fe->attach_quadrature_rule (&qrule); UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); QGauss qface(dim-1, FIFTH); fe_face->attach_quadrature_rule (&qface); const std::vector<Real> & JxW = fe->get_JxW(); const std::vector<std::vector<Real> > & phi = fe->get_phi(); const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); DenseMatrix<Number> Ke; DenseVector<Number> Fe; std::vector<dof_id_type> dof_indices; MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { const Elem * elem = *el; dof_map.dof_indices (elem, dof_indices); fe->reinit (elem); Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); for (unsigned int qp=0; qp<qrule.n_points(); qp++) { for (unsigned int i=0; i<phi.size(); i++) { Fe(i) += JxW[qp]*phi[i][qp]; for (unsigned int j=0; j<phi.size(); j++) Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); } } dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); } }
void AssembleOptimization::assemble_A_and_F() { A_matrix->zero(); F_vector->zero(); const MeshBase & mesh = _sys.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); const unsigned int u_var = _sys.variable_number ("u"); const DofMap & dof_map = _sys.get_dof_map(); FEType fe_type = dof_map.variable_type(u_var); UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); QGauss qrule (dim, fe_type.default_quadrature_order()); fe->attach_quadrature_rule (&qrule); const std::vector<Real> & JxW = fe->get_JxW(); const std::vector<std::vector<Real> > & phi = fe->get_phi(); const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); std::vector<dof_id_type> dof_indices; DenseMatrix<Number> Ke; DenseVector<Number> Fe; MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { const Elem * elem = *el; dof_map.dof_indices (elem, dof_indices); const unsigned int n_dofs = dof_indices.size(); fe->reinit (elem); Ke.resize (n_dofs, n_dofs); Fe.resize (n_dofs); for (unsigned int qp=0; qp<qrule.n_points(); qp++) { for (unsigned int dof_i=0; dof_i<n_dofs; dof_i++) { for (unsigned int dof_j=0; dof_j<n_dofs; dof_j++) { Ke(dof_i, dof_j) += JxW[qp] * (dphi[dof_j][qp]* dphi[dof_i][qp]); } Fe(dof_i) += JxW[qp] * phi[dof_i][qp]; } } A_matrix->add_matrix (Ke, dof_indices); F_vector->add_vector (Fe, dof_indices); } A_matrix->close(); F_vector->close(); }
void AssembleOptimization::assemble_A_and_F() { A_matrix->zero(); F_vector->zero(); const MeshBase & mesh = _sys.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); const unsigned int u_var = _sys.variable_number ("u"); const DofMap & dof_map = _sys.get_dof_map(); FEType fe_type = dof_map.variable_type(u_var); UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); QGauss qrule (dim, fe_type.default_quadrature_order()); fe->attach_quadrature_rule (&qrule); const std::vector<Real> & JxW = fe->get_JxW(); const std::vector<std::vector<Real> > & phi = fe->get_phi(); const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); std::vector<dof_id_type> dof_indices; DenseMatrix<Number> Ke; DenseVector<Number> Fe; MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { const Elem * elem = *el; dof_map.dof_indices (elem, dof_indices); const unsigned int n_dofs = dof_indices.size(); fe->reinit (elem); Ke.resize (n_dofs, n_dofs); Fe.resize (n_dofs); for (unsigned int qp=0; qp<qrule.n_points(); qp++) { for (unsigned int dof_i=0; dof_i<n_dofs; dof_i++) { for (unsigned int dof_j=0; dof_j<n_dofs; dof_j++) { Ke(dof_i, dof_j) += JxW[qp] * (dphi[dof_j][qp]* dphi[dof_i][qp]); } Fe(dof_i) += JxW[qp] * phi[dof_i][qp]; } } // This will zero off-diagonal entries of Ke corresponding to // Dirichlet dofs. dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); // We want the diagonal of constrained dofs to be zero too for (unsigned int local_dof_index=0; local_dof_index<n_dofs; local_dof_index++) { dof_id_type global_dof_index = dof_indices[local_dof_index]; if (dof_map.is_constrained_dof(global_dof_index)) { Ke(local_dof_index, local_dof_index) = 0.; } } A_matrix->add_matrix (Ke, dof_indices); F_vector->add_vector (Fe, dof_indices); } A_matrix->close(); F_vector->close(); }
void Biharmonic::JR::bounds(NumericVector<Number> & XL, NumericVector<Number> & XU, NonlinearImplicitSystem &) { // sys is actually ignored, since it should be the same as *this. // Declare a performance log. Give it a descriptive // string to identify what part of the code we are // logging, since there may be many PerfLogs in an // application. PerfLog perf_log ("Biharmonic bounds", false); // A reference to the DofMap object for this system. The DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the DofMap // in future examples. const DofMap & dof_map = get_dof_map(); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = dof_map.variable_type(0); // Build a Finite Element object of the specified type. Since the // FEBase::build() member dynamically creates memory we will // store the object as a UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(_biharmonic._dim, fe_type)); // Define data structures to contain the bound vectors contributions. DenseVector<Number> XLe, XUe; // These vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; MeshBase::const_element_iterator el = _biharmonic._mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = _biharmonic._mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Extract the shape function to be evaluated at the nodes const std::vector<std::vector<Real> > & phi = fe->get_phi(); // Get the degree of freedom indices for the current element. // They are in 1-1 correspondence with shape functions phi // and define where in the global vector this element will. dof_map.dof_indices (*el, dof_indices); // Resize the local bounds vectors (zeroing them out in the process). XLe.resize(dof_indices.size()); XUe.resize(dof_indices.size()); // Extract the element node coordinates in the reference frame std::vector<Point> nodes; fe->get_refspace_nodes((*el)->type(), nodes); // Evaluate the shape functions at the nodes fe->reinit(*el, &nodes); // Construct the bounds based on the value of the i-th phi at the nodes. // Observe that this doesn't really work in general: we rely on the fact // that for Hermite elements each shape function is nonzero at most at a // single node. // More generally the bounds must be constructed by inspecting a "mass-like" // matrix (m_{ij}) of the shape functions (i) evaluated at their corresponding nodes (j). // The constraints imposed on the dofs (d_i) are then are -1 \leq \sum_i d_i m_{ij} \leq 1, // since \sum_i d_i m_{ij} is the value of the solution at the j-th node. // Auxiliary variables will need to be introduced to reduce this to a "box" constraint. // Additional complications will arise since m might be singular (as is the case for Hermite, // which, however, is easily handled by inspection). for (unsigned int i=0; i<phi.size(); ++i) { // FIXME: should be able to define INF and pass it to the solve Real infinity = 1.0e20; Real bound = infinity; for (unsigned int j = 0; j < nodes.size(); ++j) { if (phi[i][j]) { bound = 1.0/std::abs(phi[i][j]); break; } } // The value of the solution at this node must be between 1.0 and -1.0. // Based on the value of phi(i)(i) the nodal coordinate must be between 1.0/phi(i)(i) and its negative. XLe(i) = -bound; XUe(i) = bound; } // The element bound vectors are now built for this element. // Insert them into the global vectors, potentially overwriting // the same dof contributions from other elements: no matter -- // the bounds are always -1.0 and 1.0. XL.insert(XLe, dof_indices); XU.insert(XUe, dof_indices); } }
void Biharmonic::JR::residual_and_jacobian(const NumericVector<Number> & u, NumericVector<Number> * R, SparseMatrix<Number> * J, NonlinearImplicitSystem &) { #ifdef LIBMESH_ENABLE_SECOND_DERIVATIVES if (!R && !J) return; // Declare a performance log. Give it a descriptive // string to identify what part of the code we are // logging, since there may be many PerfLogs in an // application. PerfLog perf_log ("Biharmonic Residual and Jacobian", false); // A reference to the DofMap object for this system. The DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the DofMap // in future examples. const DofMap & dof_map = get_dof_map(); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = dof_map.variable_type(0); // Build a Finite Element object of the specified type. Since the // FEBase::build() member dynamically creates memory we will // store the object as a UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(_biharmonic._dim, fe_type)); // Quadrature rule for numerical integration. // With 2D triangles, the Clough quadrature rule puts a Gaussian // quadrature rule on each of the 3 subelements UniquePtr<QBase> qrule(fe_type.default_quadrature_rule(_biharmonic._dim)); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (qrule.get()); // Here we define some references to element-specific data that // will be used to assemble the linear system. // We begin with the element Jacobian * quadrature weight at each // integration point. const std::vector<Real> & JxW = fe->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> > & phi = fe->get_phi(); // The element shape functions' derivatives evaluated at the quadrature points. const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); // The element shape functions' second derivatives evaluated at the quadrature points. const std::vector<std::vector<RealTensor> > & d2phi = fe->get_d2phi(); // For efficiency we will compute shape function laplacians n times, // not n^2 std::vector<Real> Laplacian_phi_qp; // Define data structures to contain the element matrix // and right-hand-side vector contribution. Following // basic finite element terminology we will denote these // "Je" and "Re". More detail is in example 3. DenseMatrix<Number> Je; DenseVector<Number> Re; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Old solution const NumericVector<Number> & u_old = *old_local_solution; // Now we will loop over all the elements in the mesh. We will // compute the element matrix and right-hand-side contribution. See // example 3 for a discussion of the element iterators. MeshBase::const_element_iterator el = _biharmonic._mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = _biharmonic._mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem * elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape function // values/derivatives (phi, dphi, d2phi) for the current element. fe->reinit (elem); // Zero the element matrix, the right-hand side and the Laplacian matrix // before summing them. if (J) Je.resize(dof_indices.size(), dof_indices.size()); if (R) Re.resize(dof_indices.size()); Laplacian_phi_qp.resize(dof_indices.size()); for (unsigned int qp=0; qp<qrule->n_points(); qp++) { // AUXILIARY QUANTITIES: // Residual and Jacobian share a few calculations: // at the very least, in the case of interfacial energy only with a constant mobility, // both calculations use Laplacian_phi_qp; more is shared the case of a concentration-dependent // mobility and bulk potentials. Number u_qp = 0.0, u_old_qp = 0.0, Laplacian_u_qp = 0.0, Laplacian_u_old_qp = 0.0; Gradient grad_u_qp(0.0, 0.0, 0.0), grad_u_old_qp(0.0, 0.0, 0.0); Number M_qp = 1.0, M_old_qp = 1.0, M_prime_qp = 0.0, M_prime_old_qp = 0.0; for (unsigned int i=0; i<phi.size(); i++) { Laplacian_phi_qp[i] = d2phi[i][qp](0, 0); grad_u_qp(0) += u(dof_indices[i])*dphi[i][qp](0); grad_u_old_qp(0) += u_old(dof_indices[i])*dphi[i][qp](0); if (_biharmonic._dim > 1) { Laplacian_phi_qp[i] += d2phi[i][qp](1, 1); grad_u_qp(1) += u(dof_indices[i])*dphi[i][qp](1); grad_u_old_qp(1) += u_old(dof_indices[i])*dphi[i][qp](1); } if (_biharmonic._dim > 2) { Laplacian_phi_qp[i] += d2phi[i][qp](2, 2); grad_u_qp(2) += u(dof_indices[i])*dphi[i][qp](2); grad_u_old_qp(2) += u_old(dof_indices[i])*dphi[i][qp](2); } u_qp += phi[i][qp]*u(dof_indices[i]); u_old_qp += phi[i][qp]*u_old(dof_indices[i]); Laplacian_u_qp += Laplacian_phi_qp[i]*u(dof_indices[i]); Laplacian_u_old_qp += Laplacian_phi_qp[i]*u_old(dof_indices[i]); } // for i if (_biharmonic._degenerate) { M_qp = 1.0 - u_qp*u_qp; M_old_qp = 1.0 - u_old_qp*u_old_qp; M_prime_qp = -2.0*u_qp; M_prime_old_qp = -2.0*u_old_qp; } // ELEMENT RESIDUAL AND JACOBIAN for (unsigned int i=0; i<phi.size(); i++) { // RESIDUAL if (R) { Number ri = 0.0, ri_old = 0.0; ri -= Laplacian_phi_qp[i]*M_qp*_biharmonic._kappa*Laplacian_u_qp; ri_old -= Laplacian_phi_qp[i]*M_old_qp*_biharmonic._kappa*Laplacian_u_old_qp; if (_biharmonic._degenerate) { ri -= (dphi[i][qp]*grad_u_qp)*M_prime_qp*(_biharmonic._kappa*Laplacian_u_qp); ri_old -= (dphi[i][qp]*grad_u_old_qp)*M_prime_old_qp*(_biharmonic._kappa*Laplacian_u_old_qp); } if (_biharmonic._cahn_hillard) { if (_biharmonic._energy == DOUBLE_WELL || _biharmonic._energy == LOG_DOUBLE_WELL) { ri += Laplacian_phi_qp[i]*M_qp*_biharmonic._theta_c*(u_qp*u_qp - 1.0)*u_qp; ri_old += Laplacian_phi_qp[i]*M_old_qp*_biharmonic._theta_c*(u_old_qp*u_old_qp - 1.0)*u_old_qp; if (_biharmonic._degenerate) { ri += (dphi[i][qp]*grad_u_qp)*M_prime_qp*_biharmonic._theta_c*(u_qp*u_qp - 1.0)*u_qp; ri_old += (dphi[i][qp]*grad_u_old_qp)*M_prime_old_qp*_biharmonic._theta_c*(u_old_qp*u_old_qp - 1.0)*u_old_qp; } }// if (_biharmonic._energy == DOUBLE_WELL || _biharmonic._energy == LOG_DOUBLE_WELL) if (_biharmonic._energy == DOUBLE_OBSTACLE || _biharmonic._energy == LOG_DOUBLE_OBSTACLE) { ri -= Laplacian_phi_qp[i]*M_qp*_biharmonic._theta_c*u_qp; ri_old -= Laplacian_phi_qp[i]*M_old_qp*_biharmonic._theta_c*u_old_qp; if (_biharmonic._degenerate) { ri -= (dphi[i][qp]*grad_u_qp)*M_prime_qp*_biharmonic._theta_c*u_qp; ri_old -= (dphi[i][qp]*grad_u_old_qp)*M_prime_old_qp*_biharmonic._theta_c*u_old_qp; } } // if (_biharmonic._energy == DOUBLE_OBSTACLE || _biharmonic._energy == LOG_DOUBLE_OBSTACLE) if (_biharmonic._energy == LOG_DOUBLE_WELL || _biharmonic._energy == LOG_DOUBLE_OBSTACLE) { switch(_biharmonic._log_truncation) { case 2: break; case 3: break; default: break; }// switch(_biharmonic._log_truncation) }// if (_biharmonic._energy == LOG_DOUBLE_WELL || _biharmonic._energy == LOG_DOUBLE_OBSTACLE) }// if (_biharmonic._cahn_hillard) Re(i) += JxW[qp]*((u_qp-u_old_qp)*phi[i][qp]-_biharmonic._dt*0.5*((2.0-_biharmonic._cnWeight)*ri + _biharmonic._cnWeight*ri_old)); } // if (R) // JACOBIAN if (J) { Number M_prime_prime_qp = 0.0; if (_biharmonic._degenerate) M_prime_prime_qp = -2.0; for (unsigned int j=0; j<phi.size(); j++) { Number ri_j = 0.0; ri_j -= Laplacian_phi_qp[i]*M_qp*_biharmonic._kappa*Laplacian_phi_qp[j]; if (_biharmonic._degenerate) { ri_j -= Laplacian_phi_qp[i]*M_prime_qp*phi[j][qp]*_biharmonic._kappa*Laplacian_u_qp + (dphi[i][qp]*dphi[j][qp])*M_prime_qp*(_biharmonic._kappa*Laplacian_u_qp) + (dphi[i][qp]*grad_u_qp)*(M_prime_prime_qp*phi[j][qp])*(_biharmonic._kappa*Laplacian_u_qp) + (dphi[i][qp]*grad_u_qp)*(M_prime_qp)*(_biharmonic._kappa*Laplacian_phi_qp[j]); } if (_biharmonic._cahn_hillard) { if (_biharmonic._energy == DOUBLE_WELL || _biharmonic._energy == LOG_DOUBLE_WELL) { ri_j += Laplacian_phi_qp[i]*M_prime_qp*phi[j][qp]*_biharmonic._theta_c*(u_qp*u_qp - 1.0)*u_qp + Laplacian_phi_qp[i]*M_qp*_biharmonic._theta_c*(3.0*u_qp*u_qp - 1.0)*phi[j][qp] + (dphi[i][qp]*dphi[j][qp])*M_prime_qp*_biharmonic._theta_c*(u_qp*u_qp - 1.0)*u_qp + (dphi[i][qp]*grad_u_qp)*M_prime_prime_qp*_biharmonic._theta_c*(u_qp*u_qp - 1.0)*u_qp + (dphi[i][qp]*grad_u_qp)*M_prime_qp*_biharmonic._theta_c*(3.0*u_qp*u_qp - 1.0)*phi[j][qp]; }// if (_biharmonic._energy == DOUBLE_WELL || _biharmonic._energy == LOG_DOUBLE_WELL) if (_biharmonic._energy == DOUBLE_OBSTACLE || _biharmonic._energy == LOG_DOUBLE_OBSTACLE) { ri_j -= Laplacian_phi_qp[i]*M_prime_qp*phi[j][qp]*_biharmonic._theta_c*u_qp + Laplacian_phi_qp[i]*M_qp*_biharmonic._theta_c*phi[j][qp] + (dphi[i][qp]*dphi[j][qp])*M_prime_qp*_biharmonic._theta_c*u_qp + (dphi[i][qp]*grad_u_qp)*M_prime_prime_qp*_biharmonic._theta_c*u_qp + (dphi[i][qp]*grad_u_qp)*M_prime_qp*_biharmonic._theta_c*phi[j][qp]; } // if (_biharmonic._energy == DOUBLE_OBSTACLE || _biharmonic._energy == LOG_DOUBLE_OBSTACLE) if (_biharmonic._energy == LOG_DOUBLE_WELL || _biharmonic._energy == LOG_DOUBLE_OBSTACLE) { switch(_biharmonic._log_truncation) { case 2: break; case 3: break; default: break; }// switch(_biharmonic._log_truncation) }// if (_biharmonic._energy == LOG_DOUBLE_WELL || _biharmonic._energy == LOG_DOUBLE_OBSTACLE) }// if (_biharmonic._cahn_hillard) Je(i,j) += JxW[qp]*(phi[i][qp]*phi[j][qp] - 0.5*_biharmonic._dt*(2.0-_biharmonic._cnWeight)*ri_j); } // for j } // if (J) } // for i } // for qp // The element matrix and right-hand-side are now built // for this element. Add them to the global matrix and // right-hand-side vector. The SparseMatrix::add_matrix() // and NumericVector::add_vector() members do this for us. // Start logging the insertion of the local (element) // matrix and vector into the global matrix and vector if (R) { // If the mesh has hanging nodes (e.g., as a result of refinement), those need to be constrained. dof_map.constrain_element_vector(Re, dof_indices); R->add_vector(Re, dof_indices); } if (J) { // If the mesh has hanging nodes (e.g., as a result of refinement), those need to be constrained. dof_map.constrain_element_matrix(Je, dof_indices); J->add_matrix(Je, dof_indices); } } // for el #endif // LIBMESH_ENABLE_SECOND_DERIVATIVES }
// We now define the matrix and rhs vector assembly function // for the shell system. This function implements the // linear Kirchhoff-Love theory for thin shells. At the // end we also take into account the boundary conditions // here, using the penalty method. void assemble_shell (EquationSystems& es, const std::string& system_name) { // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Shell"); // Get a constant reference to the mesh object. const MeshBase& mesh = es.get_mesh(); // Get a reference to the shell system object. LinearImplicitSystem & system = es.get_system<LinearImplicitSystem> ("Shell"); // Get the shell parameters that we need during assembly. const Real h = es.parameters.get<Real> ("thickness"); const Real E = es.parameters.get<Real> ("young's modulus"); const Real nu = es.parameters.get<Real> ("poisson ratio"); const Real q = es.parameters.get<Real> ("uniform load"); // Compute the membrane stiffness \p K and the bending // rigidity \p D from these parameters. const Real K = E * h / (1-nu*nu); const Real D = E * h*h*h / (12*(1-nu*nu)); // Numeric ids corresponding to each variable in the system. const unsigned int u_var = system.variable_number ("u"); const unsigned int v_var = system.variable_number ("v"); const unsigned int w_var = system.variable_number ("w"); // Get the Finite Element type for "u". Note this will be // the same as the type for "v" and "w". FEType fe_type = system.variable_type (u_var); // Build a Finite Element object of the specified type. UniquePtr<FEBase> fe (FEBase::build(2, fe_type)); // A Gauss quadrature rule for numerical integration. // For subdivision shell elements, a single Gauss point per // element is sufficient, hence we use extraorder = 0. const int extraorder = 0; UniquePtr<QBase> qrule (fe_type.default_quadrature_rule (2, extraorder)); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (qrule.get()); // The element Jacobian * quadrature weight at each integration point. const std::vector<Real>& JxW = fe->get_JxW(); // The surface tangents in both directions at the quadrature points. const std::vector<RealGradient>& dxyzdxi = fe->get_dxyzdxi(); const std::vector<RealGradient>& dxyzdeta = fe->get_dxyzdeta(); // The second partial derivatives at the quadrature points. const std::vector<RealGradient>& d2xyzdxi2 = fe->get_d2xyzdxi2(); const std::vector<RealGradient>& d2xyzdeta2 = fe->get_d2xyzdeta2(); const std::vector<RealGradient>& d2xyzdxideta = fe->get_d2xyzdxideta(); // The element shape function and its derivatives evaluated at the // quadrature points. const std::vector<std::vector<Real> >& phi = fe->get_phi(); const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); const std::vector<std::vector<RealTensor> >& d2phi = fe->get_d2phi(); // A reference to the \p DofMap object for this system. The \p DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. const DofMap & dof_map = system.get_dof_map(); // Define data structures to contain the element stiffness matrix // and right-hand-side vector contribution. Following // basic finite element terminology we will denote these // "Ke" and "Fe". DenseMatrix<Number> Ke; DenseVector<Number> Fe; DenseSubMatrix<Number> Kuu(Ke), Kuv(Ke), Kuw(Ke), Kvu(Ke), Kvv(Ke), Kvw(Ke), Kwu(Ke), Kwv(Ke), Kww(Ke); DenseSubVector<Number> Fu(Fe), Fv(Fe), Fw(Fe); // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; std::vector<dof_id_type> dof_indices_u; std::vector<dof_id_type> dof_indices_v; std::vector<dof_id_type> dof_indices_w; // Now we will loop over all the elements in the mesh. We will // compute the element matrix and right-hand-side contribution. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for (; el != end_el; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem* elem = *el; // The ghost elements at the boundaries need to be excluded // here, as they don't belong to the physical shell, // but serve for a proper boundary treatment only. libmesh_assert_equal_to (elem->type(), TRI3SUBDIVISION); const Tri3Subdivision* sd_elem = static_cast<const Tri3Subdivision*> (elem); if (sd_elem->is_ghost()) continue; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); dof_map.dof_indices (elem, dof_indices_u, u_var); dof_map.dof_indices (elem, dof_indices_v, v_var); dof_map.dof_indices (elem, dof_indices_w, w_var); const std::size_t n_dofs = dof_indices.size(); const std::size_t n_u_dofs = dof_indices_u.size(); const std::size_t n_v_dofs = dof_indices_v.size(); const std::size_t n_w_dofs = dof_indices_w.size(); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points and the shape functions // (phi, dphi, d2phi) for the current element. fe->reinit (elem); // Zero the element matrix and right-hand side before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Ke.resize (n_dofs, n_dofs); Fe.resize (n_dofs); // Reposition the submatrices... The idea is this: // // - - - - // | Kuu Kuv Kuw | | Fu | // Ke = | Kvu Kvv Kvw |; Fe = | Fv | // | Kwu Kwv Kww | | Fw | // - - - - // // The \p DenseSubMatrix.repostition () member takes the // (row_offset, column_offset, row_size, column_size). // // Similarly, the \p DenseSubVector.reposition () member // takes the (row_offset, row_size) Kuu.reposition (u_var*n_u_dofs, u_var*n_u_dofs, n_u_dofs, n_u_dofs); Kuv.reposition (u_var*n_u_dofs, v_var*n_u_dofs, n_u_dofs, n_v_dofs); Kuw.reposition (u_var*n_u_dofs, w_var*n_u_dofs, n_u_dofs, n_w_dofs); Kvu.reposition (v_var*n_v_dofs, u_var*n_v_dofs, n_v_dofs, n_u_dofs); Kvv.reposition (v_var*n_v_dofs, v_var*n_v_dofs, n_v_dofs, n_v_dofs); Kvw.reposition (v_var*n_v_dofs, w_var*n_v_dofs, n_v_dofs, n_w_dofs); Kwu.reposition (w_var*n_w_dofs, u_var*n_w_dofs, n_w_dofs, n_u_dofs); Kwv.reposition (w_var*n_w_dofs, v_var*n_w_dofs, n_w_dofs, n_v_dofs); Kww.reposition (w_var*n_w_dofs, w_var*n_w_dofs, n_w_dofs, n_w_dofs); Fu.reposition (u_var*n_u_dofs, n_u_dofs); Fv.reposition (v_var*n_u_dofs, n_v_dofs); Fw.reposition (w_var*n_u_dofs, n_w_dofs); // Now we will build the element matrix and right-hand-side. for (unsigned int qp=0; qp<qrule->n_points(); ++qp) { // First, we compute the external force resulting // from a load q distributed uniformly across the plate. // Since the load is supposed to be transverse to the plate, // it affects the z-direction, i.e. the "w" variable. for (unsigned int i=0; i<n_u_dofs; ++i) Fw(i) += JxW[qp] * phi[i][qp] * q; // Next, we assemble the stiffness matrix. This is only valid // for the linear theory, i.e., for small deformations, where // reference and deformed surface metrics are indistinguishable. // Get the three surface basis vectors. const RealVectorValue & a1 = dxyzdxi[qp]; const RealVectorValue & a2 = dxyzdeta[qp]; RealVectorValue a3 = a1.cross(a2); const Real jac = a3.size(); // the surface Jacobian libmesh_assert_greater (jac, 0); a3 /= jac; // the shell director a3 is normalized to unit length // Get the derivatives of the surface tangents. const RealVectorValue & a11 = d2xyzdxi2[qp]; const RealVectorValue & a22 = d2xyzdeta2[qp]; const RealVectorValue & a12 = d2xyzdxideta[qp]; // Compute the three covariant components of the first // fundamental form of the surface. const RealVectorValue a(a1*a1, a2*a2, a1*a2); // The elastic H matrix in Voigt's notation, computed from the // covariant components of the first fundamental form rather // than the contravariant components, exploiting that the // contravariant first fundamental form is the inverse of the // covatiant first fundamental form (hence the determinant etc.). RealTensorValue H; H(0,0) = a(1) * a(1); H(0,1) = H(1,0) = nu * a(1) * a(0) + (1-nu) * a(2) * a(2); H(0,2) = H(2,0) = -a(1) * a(2); H(1,1) = a(0) * a(0); H(1,2) = H(2,1) = -a(0) * a(2); H(2,2) = 0.5 * ((1-nu) * a(1) * a(0) + (1+nu) * a(2) * a(2)); const Real det = a(0) * a(1) - a(2) * a(2); libmesh_assert_not_equal_to (det * det, 0); H /= det * det; // Precompute come cross products for the bending part below. const RealVectorValue a11xa2 = a11.cross(a2); const RealVectorValue a22xa2 = a22.cross(a2); const RealVectorValue a12xa2 = a12.cross(a2); const RealVectorValue a1xa11 = a1.cross(a11); const RealVectorValue a1xa22 = a1.cross(a22); const RealVectorValue a1xa12 = a1.cross(a12); const RealVectorValue a2xa3 = a2.cross(a3); const RealVectorValue a3xa1 = a3.cross(a1); // Loop over all pairs of nodes I,J. for (unsigned int i=0; i<n_u_dofs; ++i) { for (unsigned int j=0; j<n_u_dofs; ++j) { // The membrane strain matrices in Voigt's notation. RealTensorValue MI, MJ; for (unsigned int k=0; k<3; ++k) { MI(0,k) = dphi[i][qp](0) * a1(k); MI(1,k) = dphi[i][qp](1) * a2(k); MI(2,k) = dphi[i][qp](1) * a1(k) + dphi[i][qp](0) * a2(k); MJ(0,k) = dphi[j][qp](0) * a1(k); MJ(1,k) = dphi[j][qp](1) * a2(k); MJ(2,k) = dphi[j][qp](1) * a1(k) + dphi[j][qp](0) * a2(k); } // The bending strain matrices in Voigt's notation. RealTensorValue BI, BJ; for (unsigned int k=0; k<3; ++k) { const Real term_ik = dphi[i][qp](0) * a2xa3(k) + dphi[i][qp](1) * a3xa1(k); BI(0,k) = -d2phi[i][qp](0,0) * a3(k) +(dphi[i][qp](0) * a11xa2(k) + dphi[i][qp](1) * a1xa11(k) + (a3*a11) * term_ik) / jac; BI(1,k) = -d2phi[i][qp](1,1) * a3(k) +(dphi[i][qp](0) * a22xa2(k) + dphi[i][qp](1) * a1xa22(k) + (a3*a22) * term_ik) / jac; BI(2,k) = 2 * (-d2phi[i][qp](0,1) * a3(k) +(dphi[i][qp](0) * a12xa2(k) + dphi[i][qp](1) * a1xa12(k) + (a3*a12) * term_ik) / jac); const Real term_jk = dphi[j][qp](0) * a2xa3(k) + dphi[j][qp](1) * a3xa1(k); BJ(0,k) = -d2phi[j][qp](0,0) * a3(k) +(dphi[j][qp](0) * a11xa2(k) + dphi[j][qp](1) * a1xa11(k) + (a3*a11) * term_jk) / jac; BJ(1,k) = -d2phi[j][qp](1,1) * a3(k) +(dphi[j][qp](0) * a22xa2(k) + dphi[j][qp](1) * a1xa22(k) + (a3*a22) * term_jk) / jac; BJ(2,k) = 2 * (-d2phi[j][qp](0,1) * a3(k) +(dphi[j][qp](0) * a12xa2(k) + dphi[j][qp](1) * a1xa12(k) + (a3*a12) * term_jk) / jac); } // The total stiffness matrix coupling the nodes // I and J is a sum of membrane and bending // contributions according to the following formula. const RealTensorValue KIJ = JxW[qp] * K * MI.transpose() * H * MJ + JxW[qp] * D * BI.transpose() * H * BJ; // Insert the components of the coupling stiffness // matrix \p KIJ into the corresponding directional // submatrices. Kuu(i,j) += KIJ(0,0); Kuv(i,j) += KIJ(0,1); Kuw(i,j) += KIJ(0,2); Kvu(i,j) += KIJ(1,0); Kvv(i,j) += KIJ(1,1); Kvw(i,j) += KIJ(1,2); Kwu(i,j) += KIJ(2,0); Kwv(i,j) += KIJ(2,1); Kww(i,j) += KIJ(2,2); } } } // end of the quadrature point qp-loop // The element matrix and right-hand-side are now built // for this element. Add them to the global matrix and // right-hand-side vector. The \p NumericMatrix::add_matrix() // and \p NumericVector::add_vector() members do this for us. system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); } // end of non-ghost element loop // Next, we apply the boundary conditions. In this case, // all boundaries are clamped by the penalty method, using // the special "ghost" nodes along the boundaries. Note // that there are better ways to implement boundary conditions // for subdivision shells. We use the simplest way here, // which is known to be overly restrictive and will lead to // a slightly too small deformation of the plate. el = mesh.active_local_elements_begin(); for (; el != end_el; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem* elem = *el; // For the boundary conditions, we only need to loop over // the ghost elements. libmesh_assert_equal_to (elem->type(), TRI3SUBDIVISION); const Tri3Subdivision* gh_elem = static_cast<const Tri3Subdivision*> (elem); if (!gh_elem->is_ghost()) continue; // Find the side which is part of the physical plate boundary, // that is, the boundary of the original mesh without ghosts. for (unsigned int s=0; s<elem->n_sides(); ++s) { const Tri3Subdivision* nb_elem = static_cast<const Tri3Subdivision*> (elem->neighbor(s)); if (nb_elem == NULL || nb_elem->is_ghost()) continue; /* * Determine the four nodes involved in the boundary * condition treatment of this side. The \p MeshTools::Subdiv * namespace provides lookup tables \p next and \p prev * for an efficient determination of the next and previous * nodes of an element, respectively. * * n4 * / \ * / gh \ * n2 ---- n3 * \ nb / * \ / * n1 */ Node* nodes [4]; // n1, n2, n3, n4 nodes[1] = gh_elem->get_node(s); // n2 nodes[2] = gh_elem->get_node(MeshTools::Subdivision::next[s]); // n3 nodes[3] = gh_elem->get_node(MeshTools::Subdivision::prev[s]); // n4 // The node in the interior of the domain, \p n1, is the // hardest to find. Walk along the edges of element \p nb until // we have identified it. unsigned int n_int = 0; nodes[0] = nb_elem->get_node(0); while (nodes[0]->id() == nodes[1]->id() || nodes[0]->id() == nodes[2]->id()) nodes[0] = nb_elem->get_node(++n_int); // The penalty value. \f$ \frac{1}{\epsilon} \f$ const Real penalty = 1.e10; // With this simple method, clamped boundary conditions are // obtained by penalizing the displacements of all four nodes. // This ensures that the displacement field vanishes on the // boundary side \p s. for (unsigned int n=0; n<4; ++n) { const dof_id_type u_dof = nodes[n]->dof_number (system.number(), u_var, 0); const dof_id_type v_dof = nodes[n]->dof_number (system.number(), v_var, 0); const dof_id_type w_dof = nodes[n]->dof_number (system.number(), w_var, 0); system.matrix->add (u_dof, u_dof, penalty); system.matrix->add (v_dof, v_dof, penalty); system.matrix->add (w_dof, w_dof, penalty); } } } // end of ghost element loop }
// We now define the matrix assembly function for the // Poisson system. We need to first compute element // matrices and right-hand sides, and then take into // account the boundary conditions. void assemble_poisson(EquationSystems& es, const std::string& system_name) { // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Poisson"); // Declare a performance log. Give it a descriptive // string to identify what part of the code we are // logging, since there may be many PerfLogs in an // application. PerfLog perf_log ("Matrix Assembly"); // Get a constant reference to the mesh object. const MeshBase& mesh = es.get_mesh(); // The dimension that we are running const unsigned int dim = mesh.mesh_dimension(); // Get a reference to the LinearImplicitSystem we are solving LinearImplicitSystem& system = es.get_system<LinearImplicitSystem>("Poisson"); // A reference to the \p DofMap object for this system. The \p DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the \p DofMap // in future examples. const DofMap& dof_map = system.get_dof_map(); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = dof_map.variable_type(0); // Build a Finite Element object of the specified type. Since the // \p FEBase::build() member dynamically creates memory we will // store the object as an \p UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // A 5th order Gauss quadrature rule for numerical integration. QGauss qrule (dim, FIFTH); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (&qrule); // Declare a special finite element object for // boundary integration. UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); // Boundary integration requires one quadraure rule, // with dimensionality one less than the dimensionality // of the element. QGauss qface(dim-1, FIFTH); // Tell the finte element object to use our // quadrature rule. fe_face->attach_quadrature_rule (&qface); // Here we define some references to cell-specific data that // will be used to assemble the linear system. // We begin with the element Jacobian * quadrature weight at each // integration point. const std::vector<Real>& JxW = fe->get_JxW(); // The physical XY locations of the quadrature points on the element. // These might be useful for evaluating spatially varying material // properties at the quadrature points. const std::vector<Point>& q_point = fe->get_xyz(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> >& phi = fe->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); // Define data structures to contain the element matrix // and right-hand-side vector contribution. Following // basic finite element terminology we will denote these // "Ke" and "Fe". More detail is in example 3. DenseMatrix<Number> Ke; DenseVector<Number> Fe; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the elements in the mesh. // We will compute the element matrix and right-hand-side // contribution. See example 3 for a discussion of the // element iterators. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Start logging the shape function initialization. // This is done through a simple function call with // the name of the event to log. perf_log.push("elem init"); // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem* elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero the element matrix and right-hand side before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Note that this will be the case if the // element type is different (i.e. the last element was a // triangle, now we are on a quadrilateral). Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); // Stop logging the shape function initialization. // If you forget to stop logging an event the PerfLog // object will probably catch the error and abort. perf_log.pop("elem init"); // Now we will build the element matrix. This involves // a double loop to integrate the test funcions (i) against // the trial functions (j). // // We have split the numeric integration into two loops // so that we can log the matrix and right-hand-side // computation seperately. // // Now start logging the element matrix computation perf_log.push ("Ke"); for (unsigned int qp=0; qp<qrule.n_points(); qp++) for (unsigned int i=0; i<phi.size(); i++) for (unsigned int j=0; j<phi.size(); j++) Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); // Stop logging the matrix computation perf_log.pop ("Ke"); // Now we build the element right-hand-side contribution. // This involves a single loop in which we integrate the // "forcing function" in the PDE against the test functions. // // Start logging the right-hand-side computation perf_log.push ("Fe"); for (unsigned int qp=0; qp<qrule.n_points(); qp++) { // fxy is the forcing function for the Poisson equation. // In this case we set fxy to be a finite difference // Laplacian approximation to the (known) exact solution. // // We will use the second-order accurate FD Laplacian // approximation, which in 2D on a structured grid is // // u_xx + u_yy = (u(i-1,j) + u(i+1,j) + // u(i,j-1) + u(i,j+1) + // -4*u(i,j))/h^2 // // Since the value of the forcing function depends only // on the location of the quadrature point (q_point[qp]) // we will compute it here, outside of the i-loop const Real x = q_point[qp](0); #if LIBMESH_DIM > 1 const Real y = q_point[qp](1); #else const Real y = 0.; #endif #if LIBMESH_DIM > 2 const Real z = q_point[qp](2); #else const Real z = 0.; #endif const Real eps = 1.e-3; const Real uxx = (exact_solution(x-eps,y,z) + exact_solution(x+eps,y,z) + -2.*exact_solution(x,y,z))/eps/eps; const Real uyy = (exact_solution(x,y-eps,z) + exact_solution(x,y+eps,z) + -2.*exact_solution(x,y,z))/eps/eps; const Real uzz = (exact_solution(x,y,z-eps) + exact_solution(x,y,z+eps) + -2.*exact_solution(x,y,z))/eps/eps; Real fxy; if(dim==1) { // In 1D, compute the rhs by differentiating the // exact solution twice. const Real pi = libMesh::pi; fxy = (0.25*pi*pi)*sin(.5*pi*x); } else { fxy = - (uxx + uyy + ((dim==2) ? 0. : uzz)); } // Add the RHS contribution for (unsigned int i=0; i<phi.size(); i++) Fe(i) += JxW[qp]*fxy*phi[i][qp]; } // Stop logging the right-hand-side computation perf_log.pop ("Fe"); // If this assembly program were to be used on an adaptive mesh, // we would have to apply any hanging node constraint equations // Also, note that here we call heterogenously_constrain_element_matrix_and_vector // to impose a inhomogeneous Dirichlet boundary conditions. dof_map.heterogenously_constrain_element_matrix_and_vector (Ke, Fe, dof_indices); // The element matrix and right-hand-side are now built // for this element. Add them to the global matrix and // right-hand-side vector. The \p SparseMatrix::add_matrix() // and \p NumericVector::add_vector() members do this for us. // Start logging the insertion of the local (element) // matrix and vector into the global matrix and vector perf_log.push ("matrix insertion"); system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); // Start logging the insertion of the local (element) // matrix and vector into the global matrix and vector perf_log.pop ("matrix insertion"); } // That's it. We don't need to do anything else to the // PerfLog. When it goes out of scope (at this function return) // it will print its log to the screen. Pretty easy, huh? }
void assemble_mass(EquationSystems & es, const std::string & system_name) { // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Eigensystem"); #ifdef LIBMESH_HAVE_SLEPC // Get a constant reference to the mesh object. const MeshBase & mesh = es.get_mesh(); // The dimension that we are running. const unsigned int dim = mesh.mesh_dimension(); // Get a reference to our system. EigenSystem & eigen_system = es.get_system<EigenSystem> (system_name); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = eigen_system.get_dof_map().variable_type(0); // A reference to the two system matrices SparseMatrix<Number> & matrix_A = *eigen_system.matrix_A; SparseMatrix<Number> & matrix_B = *eigen_system.matrix_B; // Build a Finite Element object of the specified type. Since the // FEBase::build() member dynamically creates memory we will // store the object as a UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // A Gauss quadrature rule for numerical integration. // Use the default quadrature order. QGauss qrule (dim, fe_type.default_quadrature_order()); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (&qrule); // The element Jacobian * quadrature weight at each integration point. const std::vector<Real> & JxW = fe->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> > & phi = fe->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); // A reference to the DofMap object for this system. The DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. const DofMap & dof_map = eigen_system.get_dof_map(); // The element mass and stiffness matrices. DenseMatrix<Number> Me; DenseMatrix<Number> Ke; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the elements in the mesh that // live on the local processor. We will compute the element // matrix and right-hand-side contribution. In case users // later modify this program to include refinement, we will // be safe and will only consider the active elements; // hence we use a variant of the active_elem_iterator. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem * elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero the element matrices before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Note that this will be the case if the // element type is different (i.e. the last element was a // triangle, now we are on a quadrilateral). Ke.resize (dof_indices.size(), dof_indices.size()); Me.resize (dof_indices.size(), dof_indices.size()); // Now loop over the quadrature points. This handles // the numeric integration. // // We will build the element matrix. This involves // a double loop to integrate the test funcions (i) against // the trial functions (j). for (unsigned int qp=0; qp<qrule.n_points(); qp++) for (unsigned int i=0; i<phi.size(); i++) for (unsigned int j=0; j<phi.size(); j++) { Me(i,j) += JxW[qp]*phi[i][qp]*phi[j][qp]; Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); } // On an unrefined mesh, constrain_element_matrix does // nothing. If this assembly function is ever repurposed to // run on a refined mesh, getting the hanging node constraints // right will be important. Note that, even with // asymmetric_constraint_rows = false, the constrained dof // diagonals still exist in the matrix, with diagonal entries // that are there to ensure non-singular matrices for linear // solves but which would generate positive non-physical // eigenvalues for eigensolves. // dof_map.constrain_element_matrix(Ke, dof_indices, false); // dof_map.constrain_element_matrix(Me, dof_indices, false); // Finally, simply add the element contribution to the // overall matrices A and B. matrix_A.add_matrix (Ke, dof_indices); matrix_B.add_matrix (Me, dof_indices); } // end of element loop #else // Avoid compiler warnings libmesh_ignore(es); #endif // LIBMESH_HAVE_SLEPC }
// We now define the matrix assembly function for the // Biharmonic system. We need to first compute element // matrices and right-hand sides, and then take into // account the boundary conditions, which will be handled // via a penalty method. void assemble_biharmonic(EquationSystems& es, const std::string& system_name) { #ifdef LIBMESH_ENABLE_AMR #ifdef LIBMESH_ENABLE_SECOND_DERIVATIVES // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Biharmonic"); // Declare a performance log. Give it a descriptive // string to identify what part of the code we are // logging, since there may be many PerfLogs in an // application. PerfLog perf_log ("Matrix Assembly",false); // Get a constant reference to the mesh object. const MeshBase& mesh = es.get_mesh(); // The dimension that we are running const unsigned int dim = mesh.mesh_dimension(); // Get a reference to the LinearImplicitSystem we are solving LinearImplicitSystem& system = es.get_system<LinearImplicitSystem>("Biharmonic"); // A reference to the \p DofMap object for this system. The \p DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the \p DofMap // in future examples. const DofMap& dof_map = system.get_dof_map(); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = dof_map.variable_type(0); // Build a Finite Element object of the specified type. Since the // \p FEBase::build() member dynamically creates memory we will // store the object as an \p UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // Quadrature rule for numerical integration. // With 2D triangles, the Clough quadrature rule puts a Gaussian // quadrature rule on each of the 3 subelements UniquePtr<QBase> qrule(fe_type.default_quadrature_rule(dim)); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (qrule.get()); // Declare a special finite element object for // boundary integration. UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); // Boundary integration requires another quadraure rule, // with dimensionality one less than the dimensionality // of the element. // In 1D, the Clough and Gauss quadrature rules are identical. UniquePtr<QBase> qface(fe_type.default_quadrature_rule(dim-1)); // Tell the finte element object to use our // quadrature rule. fe_face->attach_quadrature_rule (qface.get()); // Here we define some references to cell-specific data that // will be used to assemble the linear system. // We begin with the element Jacobian * quadrature weight at each // integration point. const std::vector<Real>& JxW = fe->get_JxW(); // The physical XY locations of the quadrature points on the element. // These might be useful for evaluating spatially varying material // properties at the quadrature points. const std::vector<Point>& q_point = fe->get_xyz(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> >& phi = fe->get_phi(); // The element shape function second derivatives evaluated at the // quadrature points. Note that for the simple biharmonic, shape // function first derivatives are unnecessary. const std::vector<std::vector<RealTensor> >& d2phi = fe->get_d2phi(); // For efficiency we will compute shape function laplacians n times, // not n^2 std::vector<Real> shape_laplacian; // Define data structures to contain the element matrix // and right-hand-side vector contribution. Following // basic finite element terminology we will denote these // "Ke" and "Fe". More detail is in example 3. DenseMatrix<Number> Ke; DenseVector<Number> Fe; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the elements in the mesh. We will // compute the element matrix and right-hand-side contribution. See // example 3 for a discussion of the element iterators. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Start logging the shape function initialization. // This is done through a simple function call with // the name of the event to log. perf_log.push("elem init"); // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem* elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero the element matrix and right-hand side before // summing them. Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); // Make sure there is enough room in this cache shape_laplacian.resize(dof_indices.size()); // Stop logging the shape function initialization. // If you forget to stop logging an event the PerfLog // object will probably catch the error and abort. perf_log.pop("elem init"); // Now we will build the element matrix. This involves // a double loop to integrate laplacians of the test funcions // (i) against laplacians of the trial functions (j). // // This step is why we need the Clough-Tocher elements - // these C1 differentiable elements have square-integrable // second derivatives. // // Now start logging the element matrix computation perf_log.push ("Ke"); for (unsigned int qp=0; qp<qrule->n_points(); qp++) { for (unsigned int i=0; i<phi.size(); i++) { shape_laplacian[i] = d2phi[i][qp](0,0)+d2phi[i][qp](1,1); if (dim == 3) shape_laplacian[i] += d2phi[i][qp](2,2); } for (unsigned int i=0; i<phi.size(); i++) for (unsigned int j=0; j<phi.size(); j++) Ke(i,j) += JxW[qp]* shape_laplacian[i]*shape_laplacian[j]; } // Stop logging the matrix computation perf_log.pop ("Ke"); // At this point the interior element integration has // been completed. However, we have not yet addressed // boundary conditions. For this example we will only // consider simple Dirichlet boundary conditions imposed // via the penalty method. Note that this is a fourth-order // problem: Dirichlet boundary conditions include *both* // boundary values and boundary normal fluxes. { // Start logging the boundary condition computation perf_log.push ("BCs"); // The penalty values, for solution boundary trace and flux. const Real penalty = 1e10; const Real penalty2 = 1e10; // The following loops over the sides of the element. // If the element has no neighbor on a side then that // side MUST live on a boundary of the domain. for (unsigned int s=0; s<elem->n_sides(); s++) if (elem->neighbor(s) == NULL) { // The value of the shape functions at the quadrature // points. const std::vector<std::vector<Real> >& phi_face = fe_face->get_phi(); // The value of the shape function derivatives at the // quadrature points. const std::vector<std::vector<RealGradient> >& dphi_face = fe_face->get_dphi(); // The Jacobian * Quadrature Weight at the quadrature // points on the face. const std::vector<Real>& JxW_face = fe_face->get_JxW(); // The XYZ locations (in physical space) of the // quadrature points on the face. This is where // we will interpolate the boundary value function. const std::vector<Point>& qface_point = fe_face->get_xyz(); const std::vector<Point>& face_normals = fe_face->get_normals(); // Compute the shape function values on the element // face. fe_face->reinit(elem, s); // Loop over the face quagrature points for integration. for (unsigned int qp=0; qp<qface->n_points(); qp++) { // The boundary value. Number value = exact_solution(qface_point[qp], es.parameters, "null", "void"); Gradient flux = exact_2D_derivative(qface_point[qp], es.parameters, "null", "void"); // Matrix contribution of the L2 projection. // Note that the basis function values are // integrated against test function values while // basis fluxes are integrated against test function // fluxes. for (unsigned int i=0; i<phi_face.size(); i++) for (unsigned int j=0; j<phi_face.size(); j++) Ke(i,j) += JxW_face[qp] * (penalty * phi_face[i][qp] * phi_face[j][qp] + penalty2 * (dphi_face[i][qp] * face_normals[qp]) * (dphi_face[j][qp] * face_normals[qp])); // Right-hand-side contribution of the L2 // projection. for (unsigned int i=0; i<phi_face.size(); i++) Fe(i) += JxW_face[qp] * (penalty * value * phi_face[i][qp] + penalty2 * (flux * face_normals[qp]) * (dphi_face[i][qp] * face_normals[qp])); } } // Stop logging the boundary condition computation perf_log.pop ("BCs"); } for (unsigned int qp=0; qp<qrule->n_points(); qp++) for (unsigned int i=0; i<phi.size(); i++) Fe(i) += JxW[qp]*phi[i][qp]*forcing_function(q_point[qp]); // The element matrix and right-hand-side are now built // for this element. Add them to the global matrix and // right-hand-side vector. The \p SparseMatrix::add_matrix() // and \p NumericVector::add_vector() members do this for us. // Start logging the insertion of the local (element) // matrix and vector into the global matrix and vector perf_log.push ("matrix insertion"); dof_map.constrain_element_matrix_and_vector(Ke, Fe, dof_indices); system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); // Stop logging the insertion of the local (element) // matrix and vector into the global matrix and vector perf_log.pop ("matrix insertion"); } // That's it. We don't need to do anything else to the // PerfLog. When it goes out of scope (at this function return) // it will print its log to the screen. Pretty easy, huh? #else #endif // #ifdef LIBMESH_ENABLE_SECOND_DERIVATIVES #endif // #ifdef LIBMESH_ENABLE_AMR }
void InitialCondition::compute() { // -- NOTE ---- // The following code is a copy from libMesh project_vector.C plus it adds some features, so we can couple variable values // and we also do not call any callbacks, but we use our initial condition system directly. // ------------ // The element matrix and RHS for projections. // Note that Ke is always real-valued, whereas Fe may be complex valued if complex number support is enabled DenseMatrix<Real> Ke; DenseVector<Number> Fe; // The new element coefficients DenseVector<Number> Ue; const FEType & fe_type = _var.feType(); // The dimension of the current element const unsigned int dim = _current_elem->dim(); // The element type const ElemType elem_type = _current_elem->type(); // The number of nodes on the new element const unsigned int n_nodes = _current_elem->n_nodes(); // The global DOF indices std::vector<dof_id_type> dof_indices; // Side/edge DOF indices std::vector<unsigned int> side_dofs; // Get FE objects of the appropriate type // We cannot use the FE object in Assembly, since the following code is messing with the quadrature rules // for projections and would screw it up. However, if we implement projections from one mesh to another, // this code should use that implementation. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // Prepare variables for projection UniquePtr<QBase> qrule (fe_type.default_quadrature_rule(dim)); UniquePtr<QBase> qedgerule (fe_type.default_quadrature_rule(1)); UniquePtr<QBase> qsiderule (fe_type.default_quadrature_rule(dim-1)); // The values of the shape functions at the quadrature points const std::vector<std::vector<Real> > & phi = fe->get_phi(); // The gradients of the shape functions at the quadrature points on the child element. const std::vector<std::vector<RealGradient> > * dphi = NULL; const FEContinuity cont = fe->get_continuity(); if (cont == C_ONE) { const std::vector<std::vector<RealGradient> > & ref_dphi = fe->get_dphi(); dphi = &ref_dphi; } // The Jacobian * quadrature weight at the quadrature points const std::vector<Real> & JxW = fe->get_JxW(); // The XYZ locations of the quadrature points const std::vector<Point>& xyz_values = fe->get_xyz(); // Update the DOF indices for this element based on the current mesh _var.prepareIC(); dof_indices = _var.dofIndices(); // The number of DOFs on the element const unsigned int n_dofs = dof_indices.size(); if (n_dofs == 0) return; // Fixed vs. free DoFs on edge/face projections std::vector<char> dof_is_fixed(n_dofs, false); // bools std::vector<int> free_dof(n_dofs, 0); // Zero the interpolated values Ue.resize (n_dofs); Ue.zero(); // In general, we need a series of // projections to ensure a unique and continuous // solution. We start by interpolating nodes, then // hold those fixed and project edges, then // hold those fixed and project faces, then // hold those fixed and project interiors _fe_problem.sizeZeroes(n_nodes, _tid); // Interpolate node values first unsigned int current_dof = 0; for (unsigned int n = 0; n != n_nodes; ++n) { // FIXME: this should go through the DofMap, // not duplicate dof_indices code badly! const unsigned int nc = FEInterface::n_dofs_at_node (dim, fe_type, elem_type, n); if (!_current_elem->is_vertex(n)) { current_dof += nc; continue; } if (cont == DISCONTINUOUS) { libmesh_assert(nc == 0); } // Assume that C_ZERO elements have a single nodal // value shape function else if (cont == C_ZERO) { libmesh_assert(nc == 1); _qp = n; _current_node = _current_elem->get_node(n); Ue(current_dof) = value(*_current_node); dof_is_fixed[current_dof] = true; current_dof++; } // The hermite element vertex shape functions are weird else if (fe_type.family == HERMITE) { _qp = n; _current_node = _current_elem->get_node(n); Ue(current_dof) = value(*_current_node); dof_is_fixed[current_dof] = true; current_dof++; Gradient grad = gradient(*_current_node); // x derivative Ue(current_dof) = grad(0); dof_is_fixed[current_dof] = true; current_dof++; if (dim > 1) { // We'll finite difference mixed derivatives Point nxminus = _current_elem->point(n), nxplus = _current_elem->point(n); nxminus(0) -= TOLERANCE; nxplus(0) += TOLERANCE; Gradient gxminus = gradient(nxminus); Gradient gxplus = gradient(nxplus); // y derivative Ue(current_dof) = grad(1); dof_is_fixed[current_dof] = true; current_dof++; // xy derivative Ue(current_dof) = (gxplus(1) - gxminus(1)) / 2. / TOLERANCE; dof_is_fixed[current_dof] = true; current_dof++; if (dim > 2) { // z derivative Ue(current_dof) = grad(2); dof_is_fixed[current_dof] = true; current_dof++; // xz derivative Ue(current_dof) = (gxplus(2) - gxminus(2)) / 2. / TOLERANCE; dof_is_fixed[current_dof] = true; current_dof++; // We need new points for yz Point nyminus = _current_elem->point(n), nyplus = _current_elem->point(n); nyminus(1) -= TOLERANCE; nyplus(1) += TOLERANCE; Gradient gyminus = gradient(nyminus); Gradient gyplus = gradient(nyplus); // xz derivative Ue(current_dof) = (gyplus(2) - gyminus(2)) / 2. / TOLERANCE; dof_is_fixed[current_dof] = true; current_dof++; // Getting a 2nd order xyz is more tedious Point nxmym = _current_elem->point(n), nxmyp = _current_elem->point(n), nxpym = _current_elem->point(n), nxpyp = _current_elem->point(n); nxmym(0) -= TOLERANCE; nxmym(1) -= TOLERANCE; nxmyp(0) -= TOLERANCE; nxmyp(1) += TOLERANCE; nxpym(0) += TOLERANCE; nxpym(1) -= TOLERANCE; nxpyp(0) += TOLERANCE; nxpyp(1) += TOLERANCE; Gradient gxmym = gradient(nxmym); Gradient gxmyp = gradient(nxmyp); Gradient gxpym = gradient(nxpym); Gradient gxpyp = gradient(nxpyp); Number gxzplus = (gxpyp(2) - gxmyp(2)) / 2. / TOLERANCE; Number gxzminus = (gxpym(2) - gxmym(2)) / 2. / TOLERANCE; // xyz derivative Ue(current_dof) = (gxzplus - gxzminus) / 2. / TOLERANCE; dof_is_fixed[current_dof] = true; current_dof++; } } } // Assume that other C_ONE elements have a single nodal // value shape function and nodal gradient component // shape functions else if (cont == C_ONE) { libmesh_assert(nc == 1 + dim); _current_node = _current_elem->get_node(n); Ue(current_dof) = value(*_current_node); dof_is_fixed[current_dof] = true; current_dof++; Gradient grad = gradient(*_current_node); for (unsigned int i=0; i != dim; ++i) { Ue(current_dof) = grad(i); dof_is_fixed[current_dof] = true; current_dof++; } } else libmesh_error(); } // loop over nodes // From here on out we won't be sampling at nodes anymore _current_node = NULL; // In 3D, project any edge values next if (dim > 2 && cont != DISCONTINUOUS) for (unsigned int e=0; e != _current_elem->n_edges(); ++e) { FEInterface::dofs_on_edge(_current_elem, dim, fe_type, e, side_dofs); // Some edge dofs are on nodes and already // fixed, others are free to calculate unsigned int free_dofs = 0; for (unsigned int i=0; i != side_dofs.size(); ++i) if (!dof_is_fixed[side_dofs[i]]) free_dof[free_dofs++] = i; // There may be nothing to project if (!free_dofs) continue; Ke.resize (free_dofs, free_dofs); Ke.zero(); Fe.resize (free_dofs); Fe.zero(); // The new edge coefficients DenseVector<Number> Uedge(free_dofs); // Initialize FE data on the edge fe->attach_quadrature_rule (qedgerule.get()); fe->edge_reinit (_current_elem, e); const unsigned int n_qp = qedgerule->n_points(); _fe_problem.sizeZeroes(n_qp, _tid); // Loop over the quadrature points for (unsigned int qp = 0; qp < n_qp; qp++) { // solution at the quadrature point Number fineval = value(xyz_values[qp]); // solution grad at the quadrature point Gradient finegrad; if (cont == C_ONE) finegrad = gradient(xyz_values[qp]); // Form edge projection matrix for (unsigned int sidei = 0, freei = 0; sidei != side_dofs.size(); ++sidei) { unsigned int i = side_dofs[sidei]; // fixed DoFs aren't test functions if (dof_is_fixed[i]) continue; for (unsigned int sidej = 0, freej = 0; sidej != side_dofs.size(); ++sidej) { unsigned int j = side_dofs[sidej]; if (dof_is_fixed[j]) Fe(freei) -= phi[i][qp] * phi[j][qp] * JxW[qp] * Ue(j); else Ke(freei,freej) += phi[i][qp] * phi[j][qp] * JxW[qp]; if (cont == C_ONE) { if (dof_is_fixed[j]) Fe(freei) -= ((*dphi)[i][qp] * (*dphi)[j][qp]) * JxW[qp] * Ue(j); else Ke(freei,freej) += ((*dphi)[i][qp] * (*dphi)[j][qp]) * JxW[qp]; } if (!dof_is_fixed[j]) freej++; } Fe(freei) += phi[i][qp] * fineval * JxW[qp]; if (cont == C_ONE) Fe(freei) += (finegrad * (*dphi)[i][qp]) * JxW[qp]; freei++; } } Ke.cholesky_solve(Fe, Uedge); // Transfer new edge solutions to element for (unsigned int i=0; i != free_dofs; ++i) { Number &ui = Ue(side_dofs[free_dof[i]]); libmesh_assert(std::abs(ui) < TOLERANCE || std::abs(ui - Uedge(i)) < TOLERANCE); ui = Uedge(i); dof_is_fixed[side_dofs[free_dof[i]]] = true; } } // Project any side values (edges in 2D, faces in 3D) if (dim > 1 && cont != DISCONTINUOUS) for (unsigned int s=0; s != _current_elem->n_sides(); ++s) { FEInterface::dofs_on_side(_current_elem, dim, fe_type, s, side_dofs); // Some side dofs are on nodes/edges and already // fixed, others are free to calculate unsigned int free_dofs = 0; for (unsigned int i=0; i != side_dofs.size(); ++i) if (!dof_is_fixed[side_dofs[i]]) free_dof[free_dofs++] = i; // There may be nothing to project if (!free_dofs) continue; Ke.resize (free_dofs, free_dofs); Ke.zero(); Fe.resize (free_dofs); Fe.zero(); // The new side coefficients DenseVector<Number> Uside(free_dofs); // Initialize FE data on the side fe->attach_quadrature_rule (qsiderule.get()); fe->reinit (_current_elem, s); const unsigned int n_qp = qsiderule->n_points(); _fe_problem.sizeZeroes(n_qp, _tid); // Loop over the quadrature points for (unsigned int qp = 0; qp < n_qp; qp++) { // solution at the quadrature point Number fineval = value(xyz_values[qp]); // solution grad at the quadrature point Gradient finegrad; if (cont == C_ONE) finegrad = gradient(xyz_values[qp]); // Form side projection matrix for (unsigned int sidei = 0, freei = 0; sidei != side_dofs.size(); ++sidei) { unsigned int i = side_dofs[sidei]; // fixed DoFs aren't test functions if (dof_is_fixed[i]) continue; for (unsigned int sidej = 0, freej = 0; sidej != side_dofs.size(); ++sidej) { unsigned int j = side_dofs[sidej]; if (dof_is_fixed[j]) Fe(freei) -= phi[i][qp] * phi[j][qp] * JxW[qp] * Ue(j); else Ke(freei,freej) += phi[i][qp] * phi[j][qp] * JxW[qp]; if (cont == C_ONE) { if (dof_is_fixed[j]) Fe(freei) -= ((*dphi)[i][qp] * (*dphi)[j][qp]) * JxW[qp] * Ue(j); else Ke(freei,freej) += ((*dphi)[i][qp] * (*dphi)[j][qp]) * JxW[qp]; } if (!dof_is_fixed[j]) freej++; } Fe(freei) += (fineval * phi[i][qp]) * JxW[qp]; if (cont == C_ONE) Fe(freei) += (finegrad * (*dphi)[i][qp]) * JxW[qp]; freei++; } } Ke.cholesky_solve(Fe, Uside); // Transfer new side solutions to element for (unsigned int i=0; i != free_dofs; ++i) { Number &ui = Ue(side_dofs[free_dof[i]]); libmesh_assert(std::abs(ui) < TOLERANCE || std::abs(ui - Uside(i)) < TOLERANCE); ui = Uside(i); dof_is_fixed[side_dofs[free_dof[i]]] = true; } } // Project the interior values, finally // Some interior dofs are on nodes/edges/sides and // already fixed, others are free to calculate unsigned int free_dofs = 0; for (unsigned int i=0; i != n_dofs; ++i) if (!dof_is_fixed[i]) free_dof[free_dofs++] = i; // There may be nothing to project if (free_dofs) { Ke.resize (free_dofs, free_dofs); Ke.zero(); Fe.resize (free_dofs); Fe.zero(); // The new interior coefficients DenseVector<Number> Uint(free_dofs); // Initialize FE data fe->attach_quadrature_rule (qrule.get()); fe->reinit (_current_elem); const unsigned int n_qp = qrule->n_points(); _fe_problem.sizeZeroes(n_qp, _tid); // Loop over the quadrature points for (unsigned int qp=0; qp<n_qp; qp++) { // solution at the quadrature point Number fineval = value(xyz_values[qp]); // solution grad at the quadrature point Gradient finegrad; if (cont == C_ONE) finegrad = gradient(xyz_values[qp]); // Form interior projection matrix for (unsigned int i=0, freei=0; i != n_dofs; ++i) { // fixed DoFs aren't test functions if (dof_is_fixed[i]) continue; for (unsigned int j=0, freej=0; j != n_dofs; ++j) { if (dof_is_fixed[j]) Fe(freei) -= phi[i][qp] * phi[j][qp] * JxW[qp] * Ue(j); else Ke(freei,freej) += phi[i][qp] * phi[j][qp] * JxW[qp]; if (cont == C_ONE) { if (dof_is_fixed[j]) Fe(freei) -= ((*dphi)[i][qp] * (*dphi)[j][qp]) * JxW[qp] * Ue(j); else Ke(freei,freej) += ((*dphi)[i][qp] * (*dphi)[j][qp]) * JxW[qp]; } if (!dof_is_fixed[j]) freej++; } Fe(freei) += phi[i][qp] * fineval * JxW[qp]; if (cont == C_ONE) Fe(freei) += (finegrad * (*dphi)[i][qp]) * JxW[qp]; freei++; } } Ke.cholesky_solve(Fe, Uint); // Transfer new interior solutions to element for (unsigned int i=0; i != free_dofs; ++i) { Number &ui = Ue(free_dof[i]); libmesh_assert(std::abs(ui) < TOLERANCE || std::abs(ui - Uint(i)) < TOLERANCE); ui = Uint(i); dof_is_fixed[free_dof[i]] = true; } } // if there are free interior dofs // Make sure every DoF got reached! for (unsigned int i=0; i != n_dofs; ++i) libmesh_assert(dof_is_fixed[i]); NumericVector<Number> & solution = _var.sys().solution(); // 'first' and 'last' are no longer used, see note about subdomain-restricted variables below // const dof_id_type // first = solution.first_local_index(), // last = solution.last_local_index(); // Lock the new_vector since it is shared among threads. { Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx); for (unsigned int i = 0; i < n_dofs; i++) // We may be projecting a new zero value onto // an old nonzero approximation - RHS // if (Ue(i) != 0.) // This is commented out because of subdomain restricted variables. // It can be the case that if a subdomain restricted variable's boundary // aligns perfectly with a processor boundary that the variable will get // no value. To counteract this we're going to let every processor set a // value at every node and then let PETSc figure it out. // Later we can choose to do something different / better. // if ((dof_indices[i] >= first) && (dof_indices[i] < last)) { solution.set(dof_indices[i], Ue(i)); if (cont == C_ZERO) _var.setNodalValue(Ue(i), i); } } }
// We now define the matrix assembly function for the // Laplace system. We need to first compute element // matrices and right-hand sides, and then take into // account the boundary conditions, which will be handled // via a penalty method. void assemble_laplace(EquationSystems& es, const std::string& system_name) { #ifdef LIBMESH_ENABLE_AMR // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Laplace"); // Declare a performance log. Give it a descriptive // string to identify what part of the code we are // logging, since there may be many PerfLogs in an // application. PerfLog perf_log ("Matrix Assembly",false); // Get a constant reference to the mesh object. const MeshBase& mesh = es.get_mesh(); // The dimension that we are running const unsigned int mesh_dim = mesh.mesh_dimension(); // Get a reference to the LinearImplicitSystem we are solving LinearImplicitSystem& system = es.get_system<LinearImplicitSystem>("Laplace"); // A reference to the \p DofMap object for this system. The \p DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the \p DofMap // in future examples. const DofMap& dof_map = system.get_dof_map(); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = dof_map.variable_type(0); // Build a Finite Element object of the specified type. Since the // \p FEBase::build() member dynamically creates memory we will // store the object as an \p UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(mesh_dim, fe_type)); UniquePtr<FEBase> fe_face (FEBase::build(mesh_dim, fe_type)); // Quadrature rules for numerical integration. UniquePtr<QBase> qrule(fe_type.default_quadrature_rule(mesh_dim)); UniquePtr<QBase> qface(fe_type.default_quadrature_rule(mesh_dim-1)); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (qrule.get()); fe_face->attach_quadrature_rule (qface.get()); // Here we define some references to cell-specific data that // will be used to assemble the linear system. // We begin with the element Jacobian * quadrature weight at each // integration point. const std::vector<Real>& JxW = fe->get_JxW(); const std::vector<Real>& JxW_face = fe_face->get_JxW(); // The physical XY locations of the quadrature points on the element. // These might be useful for evaluating spatially varying material // properties or forcing functions at the quadrature points. const std::vector<Point>& q_point = fe->get_xyz(); // The element shape functions evaluated at the quadrature points. // For this simple problem we usually only need them on element // boundaries. const std::vector<std::vector<Real> >& phi = fe->get_phi(); const std::vector<std::vector<Real> >& psi = fe_face->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); // The XY locations of the quadrature points used for face integration const std::vector<Point>& qface_points = fe_face->get_xyz(); // Define data structures to contain the element matrix // and right-hand-side vector contribution. Following // basic finite element terminology we will denote these // "Ke" and "Fe". More detail is in example 3. DenseMatrix<Number> Ke; DenseVector<Number> Fe; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the elements in the mesh. We will // compute the element matrix and right-hand-side contribution. See // example 3 for a discussion of the element iterators. Here we use // the \p const_active_local_elem_iterator to indicate we only want // to loop over elements that are assigned to the local processor // which are "active" in the sense of AMR. This allows each // processor to compute its components of the global matrix for // active elements while ignoring parent elements which have been // refined. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Start logging the shape function initialization. // This is done through a simple function call with // the name of the event to log. perf_log.push("elem init"); // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem* elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero the element matrix and right-hand side before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Note that this will be the case if the // element type is different (i.e. the last element was a // triangle, now we are on a quadrilateral). Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); // Stop logging the shape function initialization. // If you forget to stop logging an event the PerfLog // object will probably catch the error and abort. perf_log.pop("elem init"); // Now we will build the element matrix. This involves // a double loop to integrate the test funcions (i) against // the trial functions (j). // // Now start logging the element matrix computation perf_log.push ("Ke"); for (unsigned int qp=0; qp<qrule->n_points(); qp++) for (unsigned int i=0; i<dphi.size(); i++) for (unsigned int j=0; j<dphi.size(); j++) Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); // We need a forcing function to make the 1D case interesting if (mesh_dim == 1) for (unsigned int qp=0; qp<qrule->n_points(); qp++) { Real x = q_point[qp](0); Real f = singularity ? sqrt(3.)/9.*pow(-x, -4./3.) : cos(x); for (unsigned int i=0; i<dphi.size(); ++i) Fe(i) += JxW[qp]*phi[i][qp]*f; } // Stop logging the matrix computation perf_log.pop ("Ke"); // At this point the interior element integration has // been completed. However, we have not yet addressed // boundary conditions. For this example we will only // consider simple Dirichlet boundary conditions imposed // via the penalty method. // // This approach adds the L2 projection of the boundary // data in penalty form to the weak statement. This is // a more generic approach for applying Dirichlet BCs // which is applicable to non-Lagrange finite element // discretizations. { // Start logging the boundary condition computation perf_log.push ("BCs"); // The penalty value. const Real penalty = 1.e10; // The following loops over the sides of the element. // If the element has no neighbor on a side then that // side MUST live on a boundary of the domain. for (unsigned int s=0; s<elem->n_sides(); s++) if (elem->neighbor(s) == NULL) { fe_face->reinit(elem,s); for (unsigned int qp=0; qp<qface->n_points(); qp++) { const Number value = exact_solution (qface_points[qp], es.parameters, "null", "void"); // RHS contribution for (unsigned int i=0; i<psi.size(); i++) Fe(i) += penalty*JxW_face[qp]*value*psi[i][qp]; // Matrix contribution for (unsigned int i=0; i<psi.size(); i++) for (unsigned int j=0; j<psi.size(); j++) Ke(i,j) += penalty*JxW_face[qp]*psi[i][qp]*psi[j][qp]; } } // Stop logging the boundary condition computation perf_log.pop ("BCs"); } // The element matrix and right-hand-side are now built // for this element. Add them to the global matrix and // right-hand-side vector. The \p SparseMatrix::add_matrix() // and \p NumericVector::add_vector() members do this for us. // Start logging the insertion of the local (element) // matrix and vector into the global matrix and vector perf_log.push ("matrix insertion"); dof_map.constrain_element_matrix_and_vector(Ke, Fe, dof_indices); system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); // Start logging the insertion of the local (element) // matrix and vector into the global matrix and vector perf_log.pop ("matrix insertion"); } // That's it. We don't need to do anything else to the // PerfLog. When it goes out of scope (at this function return) // it will print its log to the screen. Pretty easy, huh? #endif // #ifdef LIBMESH_ENABLE_AMR }
// Jacobian assembly function for the Laplace-Young system void LaplaceYoung::jacobian (const NumericVector<Number>& soln, SparseMatrix<Number>& jacobian, NonlinearImplicitSystem& sys) { // Get a reference to the equation system. EquationSystems &es = sys.get_equation_systems(); // Get a constant reference to the mesh object. const MeshBase& mesh = es.get_mesh(); // The dimension that we are running const unsigned int dim = mesh.mesh_dimension(); // Get a reference to the NonlinearImplicitSystem we are solving NonlinearImplicitSystem& system = es.get_system<NonlinearImplicitSystem>("Laplace-Young"); // A reference to the \p DofMap object for this system. The \p DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the \p DofMap // in future examples. const DofMap& dof_map = system.get_dof_map(); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = dof_map.variable_type(0); // Build a Finite Element object of the specified type. Since the // \p FEBase::build() member dynamically creates memory we will // store the object as an \p UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // A 5th order Gauss quadrature rule for numerical integration. QGauss qrule (dim, FIFTH); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (&qrule); // Here we define some references to cell-specific data that // will be used to assemble the linear system. // We begin with the element Jacobian * quadrature weight at each // integration point. const std::vector<Real>& JxW = fe->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> >& phi = fe->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); // Define data structures to contain the Jacobian element matrix. // Following basic finite element terminology we will denote these // "Ke". More detail is in example 3. DenseMatrix<Number> Ke; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the active elements in the mesh which // are local to this processor. // We will compute the element Jacobian contribution. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem* elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero the element Jacobian before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Note that this will be the case if the // element type is different (i.e. the last element was a // triangle, now we are on a quadrilateral). Ke.resize (dof_indices.size(), dof_indices.size()); // Now we will build the element Jacobian. This involves // a double loop to integrate the test funcions (i) against // the trial functions (j). Note that the Jacobian depends // on the current solution x, which we access using the soln // vector. // for (unsigned int qp=0; qp<qrule.n_points(); qp++) { Gradient grad_u; for (unsigned int i=0; i<phi.size(); i++) grad_u += dphi[i][qp]*soln(dof_indices[i]); const Number sa = 1. + grad_u*grad_u, K = 1. / std::sqrt(sa), dK = -K / sa; for (unsigned int i=0; i<phi.size(); i++) for (unsigned int j=0; j<phi.size(); j++) Ke(i,j) += JxW[qp]*( K * (dphi[i][qp]*dphi[j][qp]) + dK * (grad_u*dphi[j][qp]) * (grad_u*dphi[i][qp]) + _kappa * phi[i][qp] * phi[j][qp] ); } dof_map.constrain_element_matrix (Ke, dof_indices); // Add the element matrix to the system Jacobian. jacobian.add_matrix (Ke, dof_indices); } // That's it. }
// Residual assembly function for the Laplace-Young system void LaplaceYoung::residual (const NumericVector<Number>& soln, NumericVector<Number>& residual, NonlinearImplicitSystem& sys) { EquationSystems &es = sys.get_equation_systems(); // Get a constant reference to the mesh object. const MeshBase& mesh = es.get_mesh(); // The dimension that we are running const unsigned int dim = mesh.mesh_dimension(); libmesh_assert_equal_to (dim, 2); // Get a reference to the NonlinearImplicitSystem we are solving NonlinearImplicitSystem& system = es.get_system<NonlinearImplicitSystem>("Laplace-Young"); // A reference to the \p DofMap object for this system. The \p DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the \p DofMap // in future examples. const DofMap& dof_map = system.get_dof_map(); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = dof_map.variable_type(0); // Build a Finite Element object of the specified type. Since the // \p FEBase::build() member dynamically creates memory we will // store the object as an \p UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // A 5th order Gauss quadrature rule for numerical integration. QGauss qrule (dim, FIFTH); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (&qrule); // Declare a special finite element object for // boundary integration. UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); // Boundary integration requires one quadraure rule, // with dimensionality one less than the dimensionality // of the element. QGauss qface(dim-1, FIFTH); // Tell the finte element object to use our // quadrature rule. fe_face->attach_quadrature_rule (&qface); // Here we define some references to cell-specific data that // will be used to assemble the linear system. // We begin with the element Jacobian * quadrature weight at each // integration point. const std::vector<Real>& JxW = fe->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> >& phi = fe->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); // Define data structures to contain the resdual contributions DenseVector<Number> Re; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the active elements in the mesh which // are local to this processor. // We will compute the element residual. residual.zero(); MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem* elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Note that this will be the case if the // element type is different (i.e. the last element was a // triangle, now we are on a quadrilateral). Re.resize (dof_indices.size()); // Now we will build the residual. This involves // the construction of the matrix K and multiplication of it // with the current solution x. We rearrange this into two loops: // In the first, we calculate only the contribution of // K_ij*x_j which is independent of the row i. In the second loops, // we multiply with the row-dependent part and add it to the element // residual. for (unsigned int qp=0; qp<qrule.n_points(); qp++) { Number u = 0; Gradient grad_u; for (unsigned int j=0; j<phi.size(); j++) { u += phi[j][qp]*soln(dof_indices[j]); grad_u += dphi[j][qp]*soln(dof_indices[j]); } const Number K = 1./std::sqrt(1. + grad_u*grad_u); for (unsigned int i=0; i<phi.size(); i++) Re(i) += JxW[qp]*( K*(dphi[i][qp]*grad_u) + _kappa*phi[i][qp]*u ); } // At this point the interior element integration has // been completed. However, we have not yet addressed // boundary conditions. // The following loops over the sides of the element. // If the element has no neighbor on a side then that // side MUST live on a boundary of the domain. for (unsigned int side=0; side<elem->n_sides(); side++) if (elem->neighbor(side) == NULL) { // The value of the shape functions at the quadrature // points. const std::vector<std::vector<Real> >& phi_face = fe_face->get_phi(); // The Jacobian * Quadrature Weight at the quadrature // points on the face. const std::vector<Real>& JxW_face = fe_face->get_JxW(); // Compute the shape function values on the element face. fe_face->reinit(elem, side); // Loop over the face quadrature points for integration. for (unsigned int qp=0; qp<qface.n_points(); qp++) { // This is the right-hand-side contribution (f), // which has to be subtracted from the current residual for (unsigned int i=0; i<phi_face.size(); i++) Re(i) -= JxW_face[qp]*_sigma*phi_face[i][qp]; } } dof_map.constrain_element_vector (Re, dof_indices); residual.add_vector (Re, dof_indices); } // That's it. }
/** * Assemble the system matrix and right-hand side vector. */ void assemble() { const MeshBase& mesh = es.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); LinearImplicitSystem& system = es.get_system<LinearImplicitSystem>("Elasticity"); const unsigned int u_var = system.variable_number ("u"); const DofMap& dof_map = system.get_dof_map(); FEType fe_type = dof_map.variable_type(u_var); UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); QGauss qrule (dim, fe_type.default_quadrature_order()); fe->attach_quadrature_rule (&qrule); UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); QGauss qface(dim-1, fe_type.default_quadrature_order()); fe_face->attach_quadrature_rule (&qface); const std::vector<Real>& JxW = fe->get_JxW(); const std::vector<std::vector<Real> >& phi = fe->get_phi(); const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); DenseMatrix<Number> Ke; DenseSubMatrix<Number> Ke_var[3][3] = { {DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke)}, {DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke)}, {DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke)} }; DenseVector<Number> Fe; DenseSubVector<Number> Fe_var[3] = {DenseSubVector<Number>(Fe), DenseSubVector<Number>(Fe), DenseSubVector<Number>(Fe)}; std::vector<dof_id_type> dof_indices; std::vector< std::vector<dof_id_type> > dof_indices_var(3); MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { const Elem* elem = *el; dof_map.dof_indices (elem, dof_indices); for(unsigned int var=0; var<3; var++) { dof_map.dof_indices (elem, dof_indices_var[var], var); } const unsigned int n_dofs = dof_indices.size(); const unsigned int n_var_dofs = dof_indices_var[0].size(); fe->reinit (elem); Ke.resize (n_dofs,n_dofs); for(unsigned int var_i=0; var_i<3; var_i++) for(unsigned int var_j=0; var_j<3; var_j++) { Ke_var[var_i][var_j].reposition (var_i*n_var_dofs, var_j*n_var_dofs, n_var_dofs, n_var_dofs); } Fe.resize (n_dofs); for(unsigned int var=0; var<3; var++) { Fe_var[var].reposition (var*n_var_dofs, n_var_dofs); } for (unsigned int qp=0; qp<qrule.n_points(); qp++) { // assemble \int_Omega C_ijkl u_k,l v_i,j \dx for (unsigned int dof_i=0; dof_i<n_var_dofs; dof_i++) for (unsigned int dof_j=0; dof_j<n_var_dofs; dof_j++) { for(unsigned int i=0; i<3; i++) for(unsigned int j=0; j<3; j++) for(unsigned int k=0; k<3; k++) for(unsigned int l=0; l<3; l++) { Ke_var[i][k](dof_i,dof_j) += JxW[qp] * elasticity_tensor(i,j,k,l) * dphi[dof_j][qp](l) * dphi[dof_i][qp](j); } } // assemble \int_Omega f_i v_i \dx DenseVector<Number> f_vec(3); f_vec(0) = 0.; f_vec(1) = 0.; f_vec(2) = -1.; for (unsigned int dof_i=0; dof_i<n_var_dofs; dof_i++) { for(unsigned int i=0; i<3; i++) { Fe_var[i](dof_i) += JxW[qp] * ( f_vec(i) * phi[dof_i][qp] ); } } } // assemble \int_\Gamma g_i v_i \ds DenseVector<Number> g_vec(3); g_vec(0) = 0.; g_vec(1) = 0.; g_vec(2) = -1.; { for (unsigned int side=0; side<elem->n_sides(); side++) if (elem->neighbor(side) == NULL) { const std::vector<std::vector<Real> >& phi_face = fe_face->get_phi(); const std::vector<Real>& JxW_face = fe_face->get_JxW(); fe_face->reinit(elem, side); for (unsigned int qp=0; qp<qface.n_points(); qp++) { // Apply a traction if( mesh.get_boundary_info().has_boundary_id (elem, side, BOUNDARY_ID_MAX_X) ) { for (unsigned int dof_i=0; dof_i<n_var_dofs; dof_i++) { for(unsigned int i=0; i<3; i++) { Fe_var[i](dof_i) += JxW_face[qp] * ( g_vec(i) * phi_face[dof_i][qp] ); } } } } } } dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); } }
// We now define the matrix assembly function for the // Poisson system. We need to first compute element // matrices and right-hand sides, and then take into // account the boundary conditions, which will be handled // via a penalty method. void assemble_poisson(EquationSystems & es, const std::string & system_name) { // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Poisson"); // Get a constant reference to the mesh object. const MeshBase & mesh = es.get_mesh(); // The dimension that we are running const unsigned int dim = mesh.mesh_dimension(); // Get a reference to the LinearImplicitSystem we are solving LinearImplicitSystem & system = es.get_system<LinearImplicitSystem> ("Poisson"); // A reference to the DofMap object for this system. The DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the DofMap // in future examples. const DofMap & dof_map = system.get_dof_map(); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = dof_map.variable_type(0); // Build a Finite Element object of the specified type. Since the // FEBase::build() member dynamically creates memory we will // store the object as a UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. Introduction Example 4 // describes some advantages of UniquePtr's in the context of // quadrature rules. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // A 5th order Gauss quadrature rule for numerical integration. QGauss qrule (dim, FIFTH); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (&qrule); // Declare a special finite element object for // boundary integration. UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); // Boundary integration requires one quadraure rule, // with dimensionality one less than the dimensionality // of the element. QGauss qface(dim-1, FIFTH); // Tell the finite element object to use our // quadrature rule. fe_face->attach_quadrature_rule (&qface); // Here we define some references to cell-specific data that // will be used to assemble the linear system. // // The element Jacobian * quadrature weight at each integration point. const std::vector<Real> & JxW = fe->get_JxW(); // The physical XY locations of the quadrature points on the element. // These might be useful for evaluating spatially varying material // properties at the quadrature points. const std::vector<Point> & q_point = fe->get_xyz(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> > & phi = fe->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); // Define data structures to contain the element matrix // and right-hand-side vector contribution. Following // basic finite element terminology we will denote these // "Ke" and "Fe". These datatypes are templated on // Number, which allows the same code to work for real // or complex numbers. DenseMatrix<Number> Ke; DenseVector<Number> Fe; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the elements in the mesh. // We will compute the element matrix and right-hand-side // contribution. // // Element iterators are a nice way to iterate through all the // elements, or all the elements that have some property. The // iterator el will iterate from the first to the last element on // the local processor. The iterator end_el tells us when to stop. // It is smart to make this one const so that we don't accidentally // mess it up! In case users later modify this program to include // refinement, we will be safe and will only consider the active // elements; hence we use a variant of the active_elem_iterator. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); // Loop over the elements. Note that ++el is preferred to // el++ since the latter requires an unnecessary temporary // object. for ( ; el != end_el ; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem * elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero the element matrix and right-hand side before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Note that this will be the case if the // element type is different (i.e. the last element was a // triangle, now we are on a quadrilateral). // The DenseMatrix::resize() and the DenseVector::resize() // members will automatically zero out the matrix and vector. Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); // Now loop over the quadrature points. This handles // the numeric integration. for (unsigned int qp=0; qp<qrule.n_points(); qp++) { // Now we will build the element matrix. This involves // a double loop to integrate the test funcions (i) against // the trial functions (j). for (unsigned int i=0; i<phi.size(); i++) for (unsigned int j=0; j<phi.size(); j++) { Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); } // This is the end of the matrix summation loop // Now we build the element right-hand-side contribution. // This involves a single loop in which we integrate the // "forcing function" in the PDE against the test functions. { const Real x = q_point[qp](0); const Real y = q_point[qp](1); const Real eps = 1.e-3; // "fxy" is the forcing function for the Poisson equation. // In this case we set fxy to be a finite difference // Laplacian approximation to the (known) exact solution. // // We will use the second-order accurate FD Laplacian // approximation, which in 2D is // // u_xx + u_yy = (u(i,j-1) + u(i,j+1) + // u(i-1,j) + u(i+1,j) + // -4*u(i,j))/h^2 // // Since the value of the forcing function depends only // on the location of the quadrature point (q_point[qp]) // we will compute it here, outside of the i-loop const Real fxy = -(exact_solution(x, y-eps) + exact_solution(x, y+eps) + exact_solution(x-eps, y) + exact_solution(x+eps, y) - 4.*exact_solution(x, y))/eps/eps; for (unsigned int i=0; i<phi.size(); i++) Fe(i) += JxW[qp]*fxy*phi[i][qp]; } } // We have now reached the end of the RHS summation, // and the end of quadrature point loop, so // the interior element integration has // been completed. However, we have not yet addressed // boundary conditions. For this example we will only // consider simple Dirichlet boundary conditions. // // There are several ways Dirichlet boundary conditions // can be imposed. A simple approach, which works for // interpolary bases like the standard Lagrange polynomials, // is to assign function values to the // degrees of freedom living on the domain boundary. This // works well for interpolary bases, but is more difficult // when non-interpolary (e.g Legendre or Hierarchic) bases // are used. // // Dirichlet boundary conditions can also be imposed with a // "penalty" method. In this case essentially the L2 projection // of the boundary values are added to the matrix. The // projection is multiplied by some large factor so that, in // floating point arithmetic, the existing (smaller) entries // in the matrix and right-hand-side are effectively ignored. // // This amounts to adding a term of the form (in latex notation) // // \frac{1}{\epsilon} \int_{\delta \Omega} \phi_i \phi_j = \frac{1}{\epsilon} \int_{\delta \Omega} u \phi_i // // where // // \frac{1}{\epsilon} is the penalty parameter, defined such that \epsilon << 1 { // The following loop is over the sides of the element. // If the element has no neighbor on a side then that // side MUST live on a boundary of the domain. for (unsigned int side=0; side<elem->n_sides(); side++) if (elem->neighbor_ptr(side) == libmesh_nullptr) { // The value of the shape functions at the quadrature // points. const std::vector<std::vector<Real> > & phi_face = fe_face->get_phi(); // The Jacobian * Quadrature Weight at the quadrature // points on the face. const std::vector<Real> & JxW_face = fe_face->get_JxW(); // The XYZ locations (in physical space) of the // quadrature points on the face. This is where // we will interpolate the boundary value function. const std::vector<Point> & qface_point = fe_face->get_xyz(); // Compute the shape function values on the element // face. fe_face->reinit(elem, side); // Loop over the face quadrature points for integration. for (unsigned int qp=0; qp<qface.n_points(); qp++) { // The location on the boundary of the current // face quadrature point. const Real xf = qface_point[qp](0); const Real yf = qface_point[qp](1); // The penalty value. \frac{1}{\epsilon} // in the discussion above. const Real penalty = 1.e10; // The boundary value. const Real value = exact_solution(xf, yf); // Matrix contribution of the L2 projection. for (unsigned int i=0; i<phi_face.size(); i++) for (unsigned int j=0; j<phi_face.size(); j++) Ke(i,j) += JxW_face[qp]*penalty*phi_face[i][qp]*phi_face[j][qp]; // Right-hand-side contribution of the L2 // projection. for (unsigned int i=0; i<phi_face.size(); i++) Fe(i) += JxW_face[qp]*penalty*value*phi_face[i][qp]; } } } // We have now finished the quadrature point loop, // and have therefore applied all the boundary conditions. // If this assembly program were to be used on an adaptive mesh, // we would have to apply any hanging node constraint equations dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); // The element matrix and right-hand-side are now built // for this element. Add them to the global matrix and // right-hand-side vector. The SparseMatrix::add_matrix() // and NumericVector::add_vector() members do this for us. system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); } // All done! }
void ExactErrorEstimator::estimate_error (const System & system, ErrorVector & error_per_cell, const NumericVector<Number> * solution_vector, bool estimate_parent_error) { // Ignore the fact that this variable is unused when !LIBMESH_ENABLE_AMR libmesh_ignore(estimate_parent_error); // The current mesh const MeshBase & mesh = system.get_mesh(); // The dimensionality of the mesh const unsigned int dim = mesh.mesh_dimension(); // The number of variables in the system const unsigned int n_vars = system.n_vars(); // The DofMap for this system const DofMap & dof_map = system.get_dof_map(); // Resize the error_per_cell vector to be // the number of elements, initialize it to 0. error_per_cell.resize (mesh.max_elem_id()); std::fill (error_per_cell.begin(), error_per_cell.end(), 0.); // Prepare current_local_solution to localize a non-standard // solution vector if necessary if (solution_vector && solution_vector != system.solution.get()) { NumericVector<Number> * newsol = const_cast<NumericVector<Number> *>(solution_vector); System & sys = const_cast<System &>(system); newsol->swap(*sys.solution); sys.update(); } // Loop over all the variables in the system for (unsigned int var=0; var<n_vars; var++) { // Possibly skip this variable if (error_norm.weight(var) == 0.0) continue; // The (string) name of this variable const std::string & var_name = system.variable_name(var); // The type of finite element to use for this variable const FEType & fe_type = dof_map.variable_type (var); UniquePtr<FEBase> fe (FEBase::build (dim, fe_type)); // Build an appropriate Gaussian quadrature rule UniquePtr<QBase> qrule = fe_type.default_quadrature_rule (dim, _extra_order); fe->attach_quadrature_rule (qrule.get()); // Prepare a global solution and a MeshFunction of the fine system if we need one UniquePtr<MeshFunction> fine_values; UniquePtr<NumericVector<Number> > fine_soln = NumericVector<Number>::build(system.comm()); if (_equation_systems_fine) { const System & fine_system = _equation_systems_fine->get_system(system.name()); std::vector<Number> global_soln; // FIXME - we're assuming that the fine system solution gets // used even when a different vector is used for the coarse // system fine_system.update_global_solution(global_soln); fine_soln->init (cast_int<numeric_index_type>(global_soln.size()), true, SERIAL); (*fine_soln) = global_soln; fine_values = UniquePtr<MeshFunction> (new MeshFunction(*_equation_systems_fine, *fine_soln, fine_system.get_dof_map(), fine_system.variable_number(var_name))); fine_values->init(); } else { // Initialize functors if we're using them for (unsigned int i=0; i != _exact_values.size(); ++i) if (_exact_values[i]) _exact_values[i]->init(); for (unsigned int i=0; i != _exact_derivs.size(); ++i) if (_exact_derivs[i]) _exact_derivs[i]->init(); for (unsigned int i=0; i != _exact_hessians.size(); ++i) if (_exact_hessians[i]) _exact_hessians[i]->init(); } // Request the data we'll need to compute with fe->get_JxW(); fe->get_phi(); fe->get_dphi(); #ifdef LIBMESH_ENABLE_SECOND_DERIVATIVES fe->get_d2phi(); #endif fe->get_xyz(); #ifdef LIBMESH_ENABLE_AMR // If we compute on parent elements, we'll want to do so only // once on each, so we need to keep track of which we've done. std::vector<bool> computed_var_on_parent; if (estimate_parent_error) computed_var_on_parent.resize(error_per_cell.size(), false); #endif // TODO: this ought to be threaded (and using subordinate // MeshFunction objects in each thread rather than a single // master) // Iterate over all the active elements in the mesh // that live on this processor. MeshBase::const_element_iterator elem_it = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator elem_end = mesh.active_local_elements_end(); for (;elem_it != elem_end; ++elem_it) { // e is necessarily an active element on the local processor const Elem * elem = *elem_it; const dof_id_type e_id = elem->id(); #ifdef LIBMESH_ENABLE_AMR // See if the parent of element e has been examined yet; // if not, we may want to compute the estimator on it const Elem * parent = elem->parent(); // We only can compute and only need to compute on // parents with all active children bool compute_on_parent = true; if (!parent || !estimate_parent_error) compute_on_parent = false; else for (unsigned int c=0; c != parent->n_children(); ++c) if (!parent->child_ptr(c)->active()) compute_on_parent = false; if (compute_on_parent && !computed_var_on_parent[parent->id()]) { computed_var_on_parent[parent->id()] = true; // Compute a projection onto the parent DenseVector<Number> Uparent; FEBase::coarsened_dof_values(*(system.current_local_solution), dof_map, parent, Uparent, var, false); error_per_cell[parent->id()] += static_cast<ErrorVectorReal> (find_squared_element_error(system, var_name, parent, Uparent, fe.get(), fine_values.get())); } #endif // Get the local to global degree of freedom maps std::vector<dof_id_type> dof_indices; dof_map.dof_indices (elem, dof_indices, var); const unsigned int n_dofs = cast_int<unsigned int>(dof_indices.size()); DenseVector<Number> Uelem(n_dofs); for (unsigned int i=0; i != n_dofs; ++i) Uelem(i) = system.current_solution(dof_indices[i]); error_per_cell[e_id] += static_cast<ErrorVectorReal> (find_squared_element_error(system, var_name, elem, Uelem, fe.get(), fine_values.get())); } // End loop over active local elements } // End loop over variables // Each processor has now computed the error contribuions // for its local elements. We need to sum the vector // and then take the square-root of each component. Note // that we only need to sum if we are running on multiple // processors, and we only need to take the square-root // if the value is nonzero. There will in general be many // zeros for the inactive elements. // First sum the vector of estimated error values this->reduce_error(error_per_cell, system.comm()); // Compute the square-root of each component. { LOG_SCOPE("std::sqrt()", "ExactErrorEstimator"); for (dof_id_type i=0; i<error_per_cell.size(); i++) if (error_per_cell[i] != 0.) { libmesh_assert_greater (error_per_cell[i], 0.); error_per_cell[i] = std::sqrt(error_per_cell[i]); } } // If we used a non-standard solution before, now is the time to fix // the current_local_solution if (solution_vector && solution_vector != system.solution.get()) { NumericVector<Number> * newsol = const_cast<NumericVector<Number> *>(solution_vector); System & sys = const_cast<System &>(system); newsol->swap(*sys.solution); sys.update(); } }
void assemble_elasticity(EquationSystems & es, const std::string & system_name) { libmesh_assert_equal_to (system_name, "Elasticity"); const MeshBase & mesh = es.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); Real pival = libMesh::pi; Real sigma = 1000.0; Real omega = 2.0*pival*27e6; Real Mu0 = 4e-7*pival; char *filename = "coordinates_custom.dat"; LinearImplicitSystem & system = es.get_system<LinearImplicitSystem>("Elasticity"); const unsigned int u_var = system.variable_number ("u"); const unsigned int v_var = system.variable_number ("v"); const DofMap & dof_map = system.get_dof_map(); FEType fe_type = dof_map.variable_type(0); UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); QGauss qrule (dim, fe_type.default_quadrature_order()); fe->attach_quadrature_rule (&qrule); UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); QGauss qface(dim-1, fe_type.default_quadrature_order()); fe_face->attach_quadrature_rule (&qface); const std::vector<Real> & JxW = fe->get_JxW(); const std::vector<std::vector<Real>> & phi = fe->get_phi(); const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); const std::vector<Point> & q_point = fe->get_xyz(); DenseMatrix<Number> Ke; DenseVector<Number> Fe; DenseSubMatrix<Number> Kuu(Ke), Kuv(Ke), Kvu(Ke), Kvv(Ke); DenseSubVector<Number> Fu(Fe), Fv(Fe); std::vector<dof_id_type> dof_indices; std::vector<dof_id_type> dof_indices_u; std::vector<dof_id_type> dof_indices_v; MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { const Elem * elem = *el; dof_map.dof_indices (elem, dof_indices); dof_map.dof_indices (elem, dof_indices_u, u_var); dof_map.dof_indices (elem, dof_indices_v, v_var); const unsigned int n_dofs = dof_indices.size(); const unsigned int n_u_dofs = dof_indices_u.size(); const unsigned int n_v_dofs = dof_indices_v.size(); fe->reinit (elem); Ke.resize (n_dofs, n_dofs); Fe.resize (n_dofs); Kuu.reposition (u_var*n_u_dofs, u_var*n_u_dofs, n_u_dofs, n_u_dofs); Kuv.reposition (u_var*n_u_dofs, v_var*n_u_dofs, n_u_dofs, n_v_dofs); Kvu.reposition (v_var*n_v_dofs, u_var*n_v_dofs, n_v_dofs, n_u_dofs); Kvv.reposition (v_var*n_v_dofs, v_var*n_v_dofs, n_v_dofs, n_v_dofs); Fu.reposition (u_var*n_u_dofs, n_u_dofs); Fv.reposition (v_var*n_u_dofs, n_v_dofs); const Real eps = 1.e-3; for (unsigned int qp=0; qp<qrule.n_points(); qp++) { const Real x = q_point[qp](0); const Real y = q_point[qp](1); Real fxyz = 0.0; Real fxy = 0.0; for (unsigned int i=0; i<n_u_dofs; i++) for (unsigned int j=0; j<n_u_dofs; j++) { Kuu(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp] + phi[i][qp]*phi[j][qp]/x/x)*x ; } if ( x<=0.3 && y>=0.0 && y<=1.0 ) { Real Kfactor = Mu0*sigma*omega; for (unsigned int i=0; i<n_u_dofs; i++) for (unsigned int j=0; j<n_v_dofs; j++) { Kuv(i,j) += (-1.0)*(JxW[qp]*(phi[i][qp]*phi[j][qp])*Kfactor*x); } for (unsigned int i=0; i<n_v_dofs; i++) for (unsigned int j=0; j<n_u_dofs; j++) { Kvu(i,j) += JxW[qp]*(phi[i][qp]*phi[j][qp])*Kfactor*x ; } fxyz = -Kfactor*solev(x,y,filename)*Mu0*omega/2.0/pival; } for (unsigned int i=0; i<n_v_dofs; i++) for (unsigned int j=0; j<n_v_dofs; j++) { Kvv(i,j) +=JxW[qp]*(dphi[i][qp]*dphi[j][qp] + phi[i][qp]*phi[j][qp]/x/x )*x ; } // Real fxyz = exact_solution(x,y)*pival*pival/2.0 + pival*0.5*sin(0.5*pival*x)*sin(0.5*pival*y)/x + exact_solution(x,y)/x/x + Kfactor*exact_solution2(x,y); // Real fxy = exact_solution2(x,y)*pival*pival/2.0 - pival*0.5*cos(0.5*pival*x)*cos(0.5*pival*y)/x + exact_solution2(x,y)/x/x + Kfactor*exact_solution(x,y); for (unsigned int i=0; i<n_u_dofs; i++) Fu(i) += JxW[qp]*fxyz*phi[i][qp]*x; for (unsigned int i = 0;i<n_v_dofs;i++) Fv(i) += JxW[qp]*fxy*phi[i][qp]*x; } for (unsigned int s=0; s<elem->n_sides(); s++) if (elem->neighbor(s) == libmesh_nullptr) { const std::vector<std::vector<Real> > & phi_face = fe_face->get_phi(); const std::vector<Real> & JxW_face = fe_face->get_JxW(); const std::vector<Point> & qface_point = fe_face->get_xyz(); const std::vector<Point>& qface_normals = fe_face->get_normals(); fe_face->reinit(elem, s); UniquePtr<Elem> side (elem->build_side(s)); /* for (unsigned int qp=0; qp<qface.n_points(); qp++) { // The location on the boundary of the current // face quadrature point. const Real xf = qface_point[qp](0); const Real yf = qface_point[qp](1); // The penalty value. \frac{1}{\epsilon} // in the discussion above. const Real penalty = 1.e10; // The boundary value. const Real value = exact_solution(xf, yf); // Matrix contribution of the L2 projection. for (unsigned int i=0; i<phi_face.size(); i++) for (unsigned int j=0; j<phi_face.size(); j++) Ke(i,j) += JxW_face[qp]*penalty*phi_face[i][qp]*phi_face[j][qp]*xf; // Right-hand-side contribution of the L2 // projection. for (unsigned int i=0; i<phi_face.size(); i++) Fe(i) += JxW_face[qp]*penalty*value*phi_face[i][qp]*xf; }*/ double check = 0; { for (unsigned int ns=0; ns<side->n_nodes(); ns++) { const Real penalty = 1.e10; for (unsigned int n=0; n<elem->n_nodes(); n++) if (elem->node(n) == side->node(ns)) { Node *node = elem->get_node(n); Point poi = *node; const Real xf = poi(0); const Real yf = poi(1); for(unsigned int j=0; j<n_u_dofs; ++j) Kuu(n,j) = 0.; for(unsigned int j=0; j<n_v_dofs; ++j) Kvv(n,j) = 0.; Kuu(n,n) = 1.; Kvv(n,n) = 1.; Fu(n) = 0.0; Fv(n) = 0.0; // Fu(n) = exact_solution(xf,yf); // Fv(n) = exact_solution2(xf,yf); } } } } dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); } }
void assemble_poisson(EquationSystems & es, const ElementIdMap & lower_to_upper) { const MeshBase & mesh = es.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); Real R = es.parameters.get<Real>("R"); LinearImplicitSystem & system = es.get_system<LinearImplicitSystem>("Poisson"); const DofMap & dof_map = system.get_dof_map(); FEType fe_type = dof_map.variable_type(0); UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); UniquePtr<FEBase> fe_elem_face (FEBase::build(dim, fe_type)); UniquePtr<FEBase> fe_neighbor_face (FEBase::build(dim, fe_type)); QGauss qrule (dim, fe_type.default_quadrature_order()); QGauss qface(dim-1, fe_type.default_quadrature_order()); fe->attach_quadrature_rule (&qrule); fe_elem_face->attach_quadrature_rule (&qface); fe_neighbor_face->attach_quadrature_rule (&qface); const std::vector<Real> & JxW = fe->get_JxW(); const std::vector<std::vector<Real> > & phi = fe->get_phi(); const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); const std::vector<Real> & JxW_face = fe_elem_face->get_JxW(); const std::vector<Point> & qface_points = fe_elem_face->get_xyz(); const std::vector<std::vector<Real> > & phi_face = fe_elem_face->get_phi(); const std::vector<std::vector<Real> > & phi_neighbor_face = fe_neighbor_face->get_phi(); DenseMatrix<Number> Ke; DenseVector<Number> Fe; DenseMatrix<Number> Kne; DenseMatrix<Number> Ken; DenseMatrix<Number> Kee; DenseMatrix<Number> Knn; std::vector<dof_id_type> dof_indices; MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { const Elem * elem = *el; dof_map.dof_indices (elem, dof_indices); const unsigned int n_dofs = dof_indices.size(); fe->reinit (elem); Ke.resize (n_dofs, n_dofs); Fe.resize (n_dofs); // Assemble element interior terms for the matrix for (unsigned int qp=0; qp<qrule.n_points(); qp++) for (unsigned int i=0; i<n_dofs; i++) for (unsigned int j=0; j<n_dofs; j++) Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); // Boundary flux provides forcing in this example { for (unsigned int side=0; side<elem->n_sides(); side++) if (elem->neighbor(side) == NULL) { if (mesh.get_boundary_info().has_boundary_id (elem, side, MIN_Z_BOUNDARY)) { fe_elem_face->reinit(elem, side); for (unsigned int qp=0; qp<qface.n_points(); qp++) for (unsigned int i=0; i<phi.size(); i++) Fe(i) += JxW_face[qp] * phi_face[i][qp]; } } } // Add boundary terms on the crack { for (unsigned int side=0; side<elem->n_sides(); side++) if (elem->neighbor(side) == NULL) { // Found the lower side of the crack. Assemble terms due to lower and upper in here. if (mesh.get_boundary_info().has_boundary_id (elem, side, CRACK_BOUNDARY_LOWER)) { fe_elem_face->reinit(elem, side); ElementIdMap::const_iterator ltu_it = lower_to_upper.find(std::make_pair(elem->id(), side)); dof_id_type upper_elem_id = ltu_it->second; const Elem * neighbor = mesh.elem(upper_elem_id); std::vector<Point> qface_neighbor_points; FEInterface::inverse_map (elem->dim(), fe->get_fe_type(), neighbor, qface_points, qface_neighbor_points); fe_neighbor_face->reinit(neighbor, &qface_neighbor_points); std::vector<dof_id_type> neighbor_dof_indices; dof_map.dof_indices (neighbor, neighbor_dof_indices); const unsigned int n_neighbor_dofs = neighbor_dof_indices.size(); Kne.resize (n_neighbor_dofs, n_dofs); Ken.resize (n_dofs, n_neighbor_dofs); Kee.resize (n_dofs, n_dofs); Knn.resize (n_neighbor_dofs, n_neighbor_dofs); // Lower-to-lower coupling term for (unsigned int qp=0; qp<qface.n_points(); qp++) for (unsigned int i=0; i<n_dofs; i++) for (unsigned int j=0; j<n_dofs; j++) Kee(i,j) -= JxW_face[qp] * (1./R)*(phi_face[i][qp] * phi_face[j][qp]); // Lower-to-upper coupling term for (unsigned int qp=0; qp<qface.n_points(); qp++) for (unsigned int i=0; i<n_dofs; i++) for (unsigned int j=0; j<n_neighbor_dofs; j++) Ken(i,j) += JxW_face[qp] * (1./R)*(phi_face[i][qp] * phi_neighbor_face[j][qp]); // Upper-to-upper coupling term for (unsigned int qp=0; qp<qface.n_points(); qp++) for (unsigned int i=0; i<n_neighbor_dofs; i++) for (unsigned int j=0; j<n_neighbor_dofs; j++) Knn(i,j) -= JxW_face[qp] * (1./R)*(phi_neighbor_face[i][qp] * phi_neighbor_face[j][qp]); // Upper-to-lower coupling term for (unsigned int qp=0; qp<qface.n_points(); qp++) for (unsigned int i=0; i<n_neighbor_dofs; i++) for (unsigned int j=0; j<n_dofs; j++) Kne(i,j) += JxW_face[qp] * (1./R)*(phi_neighbor_face[i][qp] * phi_face[j][qp]); system.matrix->add_matrix(Kne, neighbor_dof_indices, dof_indices); system.matrix->add_matrix(Ken, dof_indices, neighbor_dof_indices); system.matrix->add_matrix(Kee, dof_indices); system.matrix->add_matrix(Knn, neighbor_dof_indices); } } } dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); } }
void LinearElasticityWithContact::residual_and_jacobian ( const NumericVector<Number>& soln, NumericVector<Number>* residual, SparseMatrix<Number>* jacobian, NonlinearImplicitSystem& /*sys*/) { EquationSystems& es = _sys.get_equation_systems(); const Real young_modulus = es.parameters.get<Real>("young_modulus"); const Real poisson_ratio = es.parameters.get<Real>("poisson_ratio"); const Real forcing_magnitude = es.parameters.get<Real>("forcing_magnitude"); const MeshBase& mesh = _sys.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); const unsigned int u_var = _sys.variable_number ("u"); DofMap& dof_map = _sys.get_dof_map(); FEType fe_type = dof_map.variable_type(u_var); UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); QGauss qrule (dim, fe_type.default_quadrature_order()); fe->attach_quadrature_rule (&qrule); UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); QGauss qface (dim-1, fe_type.default_quadrature_order()); fe_face->attach_quadrature_rule (&qface); UniquePtr<FEBase> fe_neighbor_face (FEBase::build(dim, fe_type)); fe_neighbor_face->attach_quadrature_rule (&qface); const std::vector<Real>& JxW = fe->get_JxW(); const std::vector<std::vector<Real> >& phi = fe->get_phi(); const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); const std::vector<Real>& JxW_face = fe_face->get_JxW(); const std::vector<std::vector<Real> >& phi_face = fe_face->get_phi(); const std::vector<Point>& face_normals = fe_face->get_normals(); const std::vector<Point>& face_xyz = fe_face->get_xyz(); const std::vector<std::vector<Real> >& phi_neighbor_face = fe_neighbor_face->get_phi(); // 1. Move mesh_clone based on soln. // 2. Compute and store all contact forces. // 3. Augment the sparsity pattern. { UniquePtr<MeshBase> mesh_clone = mesh.clone(); move_mesh(*mesh_clone, soln); _augment_sparsity.clear_contact_element_map(); clear_contact_data(); MeshBase::const_element_iterator el = mesh_clone->active_elements_begin(); const MeshBase::const_element_iterator end_el = mesh_clone->active_elements_end(); for ( ; el != end_el; ++el) { const Elem* elem = *el; for (unsigned int side=0; side<elem->n_sides(); side++) { if (elem->neighbor(side) == NULL) { bool on_lower_contact_surface = mesh_clone->get_boundary_info().has_boundary_id (elem, side, CONTACT_BOUNDARY_LOWER); bool on_upper_contact_surface = mesh_clone->get_boundary_info().has_boundary_id (elem, side, CONTACT_BOUNDARY_UPPER); if( on_lower_contact_surface && on_upper_contact_surface ) { libmesh_error_msg("Should not be on both surfaces at the same time"); } if( on_lower_contact_surface || on_upper_contact_surface ) { fe_face->reinit(elem, side); // Let's stash xyz and normals because we reinit on other_elem below std::vector<Point> face_normals_stashed = fe_face->get_normals(); std::vector<Point> face_xyz_stashed = fe_face->get_xyz(); for (unsigned int qp=0; qp<qface.n_points(); qp++) { Point line_point = face_xyz_stashed[qp]; Point line_direction = face_normals_stashed[qp]; // find an element which the line intersects, based on the plane // defined by the normal at the centroid of the other element bool found_other_elem = false; // Note that here we loop over all elements (not just local elements) // to be sure we find the appropriate other element. MeshBase::const_element_iterator other_el = mesh_clone->active_elements_begin(); const MeshBase::const_element_iterator other_end_el = mesh_clone->active_elements_end(); for ( ; other_el != other_end_el; ++other_el) { const Elem* other_elem = *other_el; if( other_elem->close_to_point(line_point, _contact_proximity_tol) ) { for (unsigned int other_side=0; other_side<other_elem->n_sides(); other_side++) if (other_elem->neighbor(other_side) == NULL) { boundary_id_type other_surface_id = on_lower_contact_surface ? CONTACT_BOUNDARY_UPPER : CONTACT_BOUNDARY_LOWER; if( mesh_clone->get_boundary_info().has_boundary_id (other_elem, other_side, other_surface_id) ) { UniquePtr<Elem> other_side_elem = other_elem->build_side(other_side); // Define a plane based on the normal at the centroid of other_side_elem // and check where line_point + s * line_direction (s \in R) intersects // this plane. const Point reference_centroid ( FEInterface::inverse_map ( other_elem->dim(), _sys.get_dof_map().variable_type(0), other_elem, other_side_elem->centroid())); std::vector<Point> reference_centroid_vector; reference_centroid_vector.push_back(reference_centroid); fe_face->reinit( other_elem, other_side, TOLERANCE, &reference_centroid_vector); Point plane_normal = face_normals[0]; Point plane_point = face_xyz[0]; // line_direction.dot(plane_normal) == 0.0 if the line and plane are // parallel. Ignore this case since it should give zero contact force. if( (line_direction * plane_normal) != 0. ) { // The signed distance between the line and the plane Real signed_distance = ( (plane_point - line_point) * plane_normal) / (line_direction * plane_normal); Point intersection_point = line_point + signed_distance * line_direction; // Note that signed_distance = (intersection_point - line_point) dot line_direction // since line_direction is a unit vector. if(other_side_elem->close_to_point(intersection_point, _contains_point_tol)) { // If the signed distance is negative then we have overlapping elements // i.e. we have detected contact. if(signed_distance < 0.0) { // We need to store the intersection point, and the element/side // that it belongs to. We can use this to calculate the contact // force later on. std::vector<Point> intersection_point_vec; intersection_point_vec.push_back(intersection_point); std::vector<Point> inverse_intersection_point_vec; FEInterface::inverse_map( other_elem->dim(), fe->get_fe_type(), other_elem, intersection_point_vec, inverse_intersection_point_vec); IntersectionPointData contact_intersection_pt_data( other_elem->id(), other_side, intersection_point, inverse_intersection_point_vec[0], line_point, line_direction); set_contact_data( elem->id(), side, qp, contact_intersection_pt_data); // We also need to keep track of which elements // are coupled in order to augment the sparsity pattern // appropriately later on. _augment_sparsity.add_contact_element( elem->id(), other_elem->id()); } // If we've found an element that contains // intersection_point then we're done for // the current quadrature point hence break // out to the next qp. found_other_elem = true; break; } } } } } if(found_other_elem) { break; } } // end for other_el } // end for qp } // end if on_contact_surface } // end if nieghbor(side_) != NULL } // end for side } // end for el } // Clear the Jacobian matrix and reinitialize it so that // we get the updated sparsity pattern if(jacobian) { dof_map.clear_sparsity(); dof_map.compute_sparsity(mesh); #ifdef LIBMESH_HAVE_PETSC PetscMatrix<Number>* petsc_jacobian = cast_ptr<PetscMatrix<Number>*>(jacobian); petsc_jacobian->update_preallocation_and_zero(); #else libmesh_error(); #endif } if(residual) { residual->zero(); } // Do jacobian and residual assembly, including contact forces DenseVector<Number> Re; DenseSubVector<Number> Re_var[3] = {DenseSubVector<Number>(Re), DenseSubVector<Number>(Re), DenseSubVector<Number>(Re)}; DenseMatrix<Number> Ke; DenseSubMatrix<Number> Ke_var[3][3] = { {DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke)}, {DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke)}, {DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke), DenseSubMatrix<Number>(Ke)} }; DenseMatrix<Number> Ke_en; DenseSubMatrix<Number> Ke_var_en[3][3] = { {DenseSubMatrix<Number>(Ke_en), DenseSubMatrix<Number>(Ke_en), DenseSubMatrix<Number>(Ke_en)}, {DenseSubMatrix<Number>(Ke_en), DenseSubMatrix<Number>(Ke_en), DenseSubMatrix<Number>(Ke_en)}, {DenseSubMatrix<Number>(Ke_en), DenseSubMatrix<Number>(Ke_en), DenseSubMatrix<Number>(Ke_en)} }; std::vector<dof_id_type> dof_indices; std::vector< std::vector<dof_id_type> > dof_indices_var(3); MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { const Elem* elem = *el; dof_map.dof_indices (elem, dof_indices); for(unsigned int var=0; var<3; var++) { dof_map.dof_indices (elem, dof_indices_var[var], var); } const unsigned int n_dofs = dof_indices.size(); const unsigned int n_var_dofs = dof_indices_var[0].size(); fe->reinit (elem); Re.resize (n_dofs); for(unsigned int var=0; var<3; var++) { Re_var[var].reposition (var*n_var_dofs, n_var_dofs); } Ke.resize (n_dofs,n_dofs); for(unsigned int var_i=0; var_i<3; var_i++) for(unsigned int var_j=0; var_j<3; var_j++) { Ke_var[var_i][var_j].reposition (var_i*n_var_dofs, var_j*n_var_dofs, n_var_dofs, n_var_dofs); } for (unsigned int qp=0; qp<qrule.n_points(); qp++) { DenseMatrix<Number> grad_u(3,3); for(unsigned int var_i=0; var_i<3; var_i++) { for(unsigned int var_j=0; var_j<3; var_j++) { for (unsigned int j=0; j<n_var_dofs; j++) { // Row is variable u, v, or w column is x, y, or z grad_u(var_i,var_j) += dphi[j][qp](var_j)*soln(dof_indices_var[var_i][j]); } } } // - C_ijkl u_k,l v_i,j for (unsigned int dof_i=0; dof_i<n_var_dofs; dof_i++) { for(unsigned int i=0; i<3; i++) for(unsigned int j=0; j<3; j++) for(unsigned int k=0; k<3; k++) for(unsigned int l=0; l<3; l++) { Re_var[i](dof_i) -= JxW[qp] * elasticity_tensor(young_modulus,poisson_ratio,i,j,k,l) * grad_u(k,l) * dphi[dof_i][qp](j); } } if( (elem->subdomain_id() == TOP_SUBDOMAIN) ) { // assemble \int_Omega f_i v_i \dx DenseVector<Number> f_vec(3); f_vec(0) = forcing_magnitude/10.; f_vec(1) = 0.0; f_vec(2) = -forcing_magnitude; for (unsigned int dof_i=0; dof_i<n_var_dofs; dof_i++) { for(unsigned int i=0; i<3; i++) { Re_var[i](dof_i) += JxW[qp] * ( f_vec(i) * phi[dof_i][qp] ); } } } // assemble \int_Omega C_ijkl u_k,l v_i,j \dx for (unsigned int dof_i=0; dof_i<n_var_dofs; dof_i++) for (unsigned int dof_j=0; dof_j<n_var_dofs; dof_j++) { for(unsigned int i=0; i<3; i++) for(unsigned int j=0; j<3; j++) for(unsigned int k=0; k<3; k++) for(unsigned int l=0; l<3; l++) { Ke_var[i][k](dof_i,dof_j) -= JxW[qp] * elasticity_tensor(young_modulus,poisson_ratio,i,j,k,l) * dphi[dof_j][qp](l) * dphi[dof_i][qp](j); } } } // Add contribution due to contact penalty forces for (unsigned int side=0; side<elem->n_sides(); side++) if (elem->neighbor(side) == NULL) { bool on_lower_contact_surface = mesh.get_boundary_info().has_boundary_id (elem, side, CONTACT_BOUNDARY_LOWER); bool on_upper_contact_surface = mesh.get_boundary_info().has_boundary_id (elem, side, CONTACT_BOUNDARY_UPPER); if( on_lower_contact_surface && on_upper_contact_surface ) { libmesh_error_msg("Should not be on both surfaces at the same time"); } if( on_lower_contact_surface || on_upper_contact_surface ) { fe_face->reinit(elem, side); for (unsigned int qp=0; qp<qface.n_points(); qp++) { bool contact_detected = is_contact_detected( elem->id(), side, qp); if(contact_detected) { IntersectionPointData intersection_pt_data = get_contact_data( elem->id(), side, qp); Point intersection_point = intersection_pt_data._intersection_point; Point line_point = intersection_pt_data._line_point; Point line_direction = intersection_pt_data._line_direction; // signed_distance = (intersection_point - line_point) dot line_direction, // hence we use this to get the contact force Real contact_force = get_contact_penalty() * ( (intersection_point - line_point) * line_direction ); for (unsigned int dof_i=0; dof_i<n_var_dofs; dof_i++) { for(unsigned int i=0; i<3; i++) { Re_var[i](dof_i) += JxW_face[qp] * ( contact_force * face_normals[qp](i) * phi_face[dof_i][qp] ); } } // Differentiate contact_force wrt solution coefficients to get // the Jacobian entries. // // Note that intersection_point and line_point // are linear functions of the solution. // // Also, line_direction is a function of the solution, but let's // neglect that for now to make it easier to get the Jacobian. // This is probably fine anyway, since this approximation will // have negligible effect as we approach convergence. // dofs local to this element, due to differentiating line_point for (unsigned int dof_i=0; dof_i<n_var_dofs; dof_i++) for (unsigned int dof_j=0; dof_j<n_var_dofs; dof_j++) { for(unsigned int i=0; i<3; i++) for(unsigned int j=0; j<3; j++) { Ke_var[i][j](dof_i,dof_j) += JxW_face[qp] * ( get_contact_penalty() * (-phi_face[dof_j][qp] * line_direction(j)) * face_normals[qp](i) * phi_face[dof_i][qp] ); } } // contribution due to dofs on the remote element, due to // differentiating intersection_point { dof_id_type neighbor_elem_id = intersection_pt_data._neighbor_element_id; const Elem* contact_neighbor = mesh.elem(neighbor_elem_id); unsigned char neighbor_side_index = intersection_pt_data._neighbor_side_index; std::vector<Point> inverse_intersection_point_vec; inverse_intersection_point_vec.push_back( intersection_pt_data._inverse_mapped_intersection_point); fe_neighbor_face->reinit( contact_neighbor, neighbor_side_index, TOLERANCE, &inverse_intersection_point_vec); std::vector<dof_id_type> neighbor_dof_indices; std::vector< std::vector<unsigned int> > neighbor_dof_indices_var(3); dof_map.dof_indices (contact_neighbor, neighbor_dof_indices); for(unsigned int var=0; var<3; var++) { dof_map.dof_indices (contact_neighbor, neighbor_dof_indices_var[var], var); } const unsigned int n_neighbor_dofs = neighbor_dof_indices.size(); const unsigned int n_neighbor_var_dofs = neighbor_dof_indices_var[0].size(); Ke_en.resize (n_dofs,n_neighbor_dofs); for(unsigned int var_i=0; var_i<3; var_i++) for(unsigned int var_j=0; var_j<3; var_j++) { Ke_var_en[var_i][var_j].reposition( var_i*n_var_dofs, var_j*n_neighbor_var_dofs, n_var_dofs, n_neighbor_var_dofs); } for (unsigned int dof_i=0; dof_i<n_var_dofs; dof_i++) for (unsigned int dof_j=0; dof_j<n_neighbor_var_dofs; dof_j++) { for(unsigned int i=0; i<3; i++) for(unsigned int j=0; j<3; j++) { Ke_var_en[i][j](dof_i,dof_j) += JxW_face[qp] * ( get_contact_penalty() * (phi_neighbor_face[dof_j][0] * line_direction(j)) * face_normals[qp](i) * phi_face[dof_i][qp] ); } } if(jacobian) { jacobian->add_matrix(Ke_en,dof_indices,neighbor_dof_indices); } } } } } } dof_map.constrain_element_matrix_and_vector (Ke, Re, dof_indices); if(jacobian) { jacobian->add_matrix (Ke, dof_indices); } if(residual) { residual->add_vector (Re, dof_indices); } } }
// This function assembles the system matrix and right-hand-side // for our wave equation. void assemble_wave(EquationSystems & es, const std::string & system_name) { // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Wave"); // Get a constant reference to the mesh object. const MeshBase & mesh = es.get_mesh(); // The dimension that we are running. const unsigned int dim = mesh.mesh_dimension(); // Copy the speed of sound and fluid density // to a local variable. const Real speed = es.parameters.get<Real>("speed"); const Real rho = es.parameters.get<Real>("fluid density"); // Get a reference to our system, as before. NewmarkSystem & t_system = es.get_system<NewmarkSystem> (system_name); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = t_system.get_dof_map().variable_type(0); // In here, we will add the element matrices to the // @e additional matrices "stiffness_mass" and "damping" // and the additional vector "force", not to the members // "matrix" and "rhs". Therefore, get writable // references to them. SparseMatrix<Number> & stiffness = t_system.get_matrix("stiffness"); SparseMatrix<Number> & damping = t_system.get_matrix("damping"); SparseMatrix<Number> & mass = t_system.get_matrix("mass"); NumericVector<Number> & force = t_system.get_vector("force"); // Some solver packages (PETSc) are especially picky about // allocating sparsity structure and truly assigning values // to this structure. Namely, matrix additions, as performed // later, exhibit acceptable performance only for identical // sparsity structures. Therefore, explicitly zero the // values in the collective matrix, so that matrix additions // encounter identical sparsity structures. SparseMatrix<Number> & matrix = *t_system.matrix; DenseMatrix<Number> zero_matrix; // Build a Finite Element object of the specified type. Since the // \p FEBase::build() member dynamically creates memory we will // store the object as an \p UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // A 2nd order Gauss quadrature rule for numerical integration. QGauss qrule (dim, SECOND); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (&qrule); // The element Jacobian * quadrature weight at each integration point. const std::vector<Real> & JxW = fe->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> > & phi = fe->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); // A reference to the \p DofMap object for this system. The \p DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. const DofMap & dof_map = t_system.get_dof_map(); // The element mass, damping and stiffness matrices // and the element contribution to the rhs. DenseMatrix<Number> Ke, Ce, Me; DenseVector<Number> Fe; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the elements in the mesh. // We will compute the element matrix and right-hand-side // contribution. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem * elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero the element matrices and rhs before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Note that this will be the case if the // element type is different (i.e. the last element was HEX8 // and now have a PRISM6). { const unsigned int n_dof_indices = dof_indices.size(); Ke.resize (n_dof_indices, n_dof_indices); Ce.resize (n_dof_indices, n_dof_indices); Me.resize (n_dof_indices, n_dof_indices); zero_matrix.resize (n_dof_indices, n_dof_indices); Fe.resize (n_dof_indices); } // Now loop over the quadrature points. This handles // the numeric integration. for (unsigned int qp=0; qp<qrule.n_points(); qp++) { // Now we will build the element matrix. This involves // a double loop to integrate the test funcions (i) against // the trial functions (j). for (unsigned int i=0; i<phi.size(); i++) for (unsigned int j=0; j<phi.size(); j++) { Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); Me(i,j) += JxW[qp]*phi[i][qp]*phi[j][qp] *1./(speed*speed); } // end of the matrix summation loop } // end of quadrature point loop // Now compute the contribution to the element matrix and the // right-hand-side vector if the current element lies on the // boundary. { // In this example no natural boundary conditions will // be considered. The code is left here so it can easily // be extended. // // don't do this for any side for (unsigned int side=0; side<elem->n_sides(); side++) if (!true) // if (elem->neighbor(side) == libmesh_nullptr) { // Declare a special finite element object for // boundary integration. UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); // Boundary integration requires one quadraure rule, // with dimensionality one less than the dimensionality // of the element. QGauss qface(dim-1, SECOND); // Tell the finte element object to use our // quadrature rule. fe_face->attach_quadrature_rule (&qface); // The value of the shape functions at the quadrature // points. const std::vector<std::vector<Real> > & phi_face = fe_face->get_phi(); // The Jacobian * Quadrature Weight at the quadrature // points on the face. const std::vector<Real> & JxW_face = fe_face->get_JxW(); // Compute the shape function values on the element // face. fe_face->reinit(elem, side); // Here we consider a normal acceleration acc_n=1 applied to // the whole boundary of our mesh. const Real acc_n_value = 1.0; // Loop over the face quadrature points for integration. for (unsigned int qp=0; qp<qface.n_points(); qp++) { // Right-hand-side contribution due to prescribed // normal acceleration. for (unsigned int i=0; i<phi_face.size(); i++) { Fe(i) += acc_n_value*rho *phi_face[i][qp]*JxW_face[qp]; } } // end face quadrature point loop } // end if (elem->neighbor(side) == libmesh_nullptr) // In this example the Dirichlet boundary conditions will be // imposed via panalty method after the // system is assembled. } // end boundary condition section // If this assembly program were to be used on an adaptive mesh, // we would have to apply any hanging node constraint equations // by uncommenting the following lines: // std::vector<unsigned int> dof_indicesC = dof_indices; // std::vector<unsigned int> dof_indicesM = dof_indices; // dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); // dof_map.constrain_element_matrix (Ce, dof_indicesC); // dof_map.constrain_element_matrix (Me, dof_indicesM); // Finally, simply add the contributions to the additional // matrices and vector. stiffness.add_matrix (Ke, dof_indices); damping.add_matrix (Ce, dof_indices); mass.add_matrix (Me, dof_indices); force.add_vector (Fe, dof_indices); // For the overall matrix, explicitly zero the entries where // we added values in the other ones, so that we have // identical sparsity footprints. matrix.add_matrix(zero_matrix, dof_indices); } // end of element loop }
// This function defines the assembly routine which // will be called at each time step. It is responsible // for computing the proper matrix entries for the // element stiffness matrices and right-hand sides. void assemble_cd (EquationSystems & es, const std::string & system_name) { #ifdef LIBMESH_ENABLE_AMR // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Convection-Diffusion"); // Get a constant reference to the mesh object. const MeshBase & mesh = es.get_mesh(); // The dimension that we are running const unsigned int dim = mesh.mesh_dimension(); // Get a reference to the Convection-Diffusion system object. TransientLinearImplicitSystem & system = es.get_system<TransientLinearImplicitSystem> ("Convection-Diffusion"); // Get the Finite Element type for the first (and only) // variable in the system. FEType fe_type = system.variable_type(0); // Build a Finite Element object of the specified type. Since the // \p FEBase::build() member dynamically creates memory we will // store the object as an \p UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); // A Gauss quadrature rule for numerical integration. // Let the \p FEType object decide what order rule is appropriate. QGauss qrule (dim, fe_type.default_quadrature_order()); QGauss qface (dim-1, fe_type.default_quadrature_order()); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (&qrule); fe_face->attach_quadrature_rule (&qface); // Here we define some references to cell-specific data that // will be used to assemble the linear system. We will start // with the element Jacobian * quadrature weight at each integration point. const std::vector<Real> & JxW = fe->get_JxW(); const std::vector<Real> & JxW_face = fe_face->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> > & phi = fe->get_phi(); const std::vector<std::vector<Real> > & psi = fe_face->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); // The XY locations of the quadrature points used for face integration const std::vector<Point> & qface_points = fe_face->get_xyz(); // A reference to the \p DofMap object for this system. The \p DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the \p DofMap // in future examples. const DofMap & dof_map = system.get_dof_map(); // Define data structures to contain the element matrix // and right-hand-side vector contribution. Following // basic finite element terminology we will denote these // "Ke" and "Fe". DenseMatrix<Number> Ke; DenseVector<Number> Fe; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Here we extract the velocity & parameters that we put in the // EquationSystems object. const RealVectorValue velocity = es.parameters.get<RealVectorValue> ("velocity"); const Real diffusivity = es.parameters.get<Real> ("diffusivity"); const Real dt = es.parameters.get<Real> ("dt"); // Now we will loop over all the elements in the mesh that // live on the local processor. We will compute the element // matrix and right-hand-side contribution. Since the mesh // will be refined we want to only consider the ACTIVE elements, // hence we use a variant of the \p active_elem_iterator. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem * elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero the element matrix and right-hand side before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Note that this will be the case if the // element type is different (i.e. the last element was a // triangle, now we are on a quadrilateral). Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); // Now we will build the element matrix and right-hand-side. // Constructing the RHS requires the solution and its // gradient from the previous timestep. This myst be // calculated at each quadrature point by summing the // solution degree-of-freedom values by the appropriate // weight functions. for (unsigned int qp=0; qp<qrule.n_points(); qp++) { // Values to hold the old solution & its gradient. Number u_old = 0.; Gradient grad_u_old; // Compute the old solution & its gradient. for (unsigned int l=0; l<phi.size(); l++) { u_old += phi[l][qp]*system.old_solution (dof_indices[l]); // This will work, // grad_u_old += dphi[l][qp]*system.old_solution (dof_indices[l]); // but we can do it without creating a temporary like this: grad_u_old.add_scaled (dphi[l][qp], system.old_solution (dof_indices[l])); } // Now compute the element matrix and RHS contributions. for (unsigned int i=0; i<phi.size(); i++) { // The RHS contribution Fe(i) += JxW[qp]*( // Mass matrix term u_old*phi[i][qp] + -.5*dt*( // Convection term // (grad_u_old may be complex, so the // order here is important!) (grad_u_old*velocity)*phi[i][qp] + // Diffusion term diffusivity*(grad_u_old*dphi[i][qp])) ); for (unsigned int j=0; j<phi.size(); j++) { // The matrix contribution Ke(i,j) += JxW[qp]*( // Mass-matrix phi[i][qp]*phi[j][qp] + .5*dt*( // Convection term (velocity*dphi[j][qp])*phi[i][qp] + // Diffusion term diffusivity*(dphi[i][qp]*dphi[j][qp])) ); } } } // At this point the interior element integration has // been completed. However, we have not yet addressed // boundary conditions. For this example we will only // consider simple Dirichlet boundary conditions imposed // via the penalty method. // // The following loops over the sides of the element. // If the element has no neighbor on a side then that // side MUST live on a boundary of the domain. { // The penalty value. const Real penalty = 1.e10; // The following loops over the sides of the element. // If the element has no neighbor on a side then that // side MUST live on a boundary of the domain. for (unsigned int s=0; s<elem->n_sides(); s++) if (elem->neighbor(s) == NULL) { fe_face->reinit(elem, s); for (unsigned int qp=0; qp<qface.n_points(); qp++) { const Number value = exact_solution (qface_points[qp](0), qface_points[qp](1), system.time); // RHS contribution for (unsigned int i=0; i<psi.size(); i++) Fe(i) += penalty*JxW_face[qp]*value*psi[i][qp]; // Matrix contribution for (unsigned int i=0; i<psi.size(); i++) for (unsigned int j=0; j<psi.size(); j++) Ke(i,j) += penalty*JxW_face[qp]*psi[i][qp]*psi[j][qp]; } } } // We have now built the element matrix and RHS vector in terms // of the element degrees of freedom. However, it is possible // that some of the element DOFs are constrained to enforce // solution continuity, i.e. they are not really "free". We need // to constrain those DOFs in terms of non-constrained DOFs to // ensure a continuous solution. The // \p DofMap::constrain_element_matrix_and_vector() method does // just that. dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); // The element matrix and right-hand-side are now built // for this element. Add them to the global matrix and // right-hand-side vector. The \p SparseMatrix::add_matrix() // and \p NumericVector::add_vector() members do this for us. system.matrix->add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); } // Finished computing the sytem matrix and right-hand side. #endif // #ifdef LIBMESH_ENABLE_AMR }
// Here we define the matrix assembly routine for // the Helmholtz system. This function will be // called to form the stiffness matrix and right-hand side. void assemble_helmholtz(EquationSystems & es, const std::string & system_name) { // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Helmholtz"); // Get a constant reference to the mesh object. const MeshBase & mesh = es.get_mesh(); // The dimension that we are in const unsigned int dim = mesh.mesh_dimension(); // Get a reference to our system, as before FrequencySystem & f_system = es.get_system<FrequencySystem> (system_name); // A const reference to the DofMap object for this system. The DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. const DofMap & dof_map = f_system.get_dof_map(); // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. const FEType & fe_type = dof_map.variable_type(0); // For the admittance boundary condition, // get the fluid density const Real rho = es.parameters.get<Real>("rho"); // In here, we will add the element matrices to the // <i>additional</i> matrices "stiffness_mass", "damping", // and the additional vector "rhs", not to the members // "matrix" and "rhs". Therefore, get writable // references to them SparseMatrix<Number> & stiffness = f_system.get_matrix("stiffness"); SparseMatrix<Number> & damping = f_system.get_matrix("damping"); SparseMatrix<Number> & mass = f_system.get_matrix("mass"); NumericVector<Number> & freq_indep_rhs = f_system.get_vector("rhs"); // Some solver packages (PETSc) are especially picky about // allocating sparsity structure and truly assigning values // to this structure. Namely, matrix additions, as performed // later, exhibit acceptable performance only for identical // sparsity structures. Therefore, explicitly zero the // values in the collective matrix, so that matrix additions // encounter identical sparsity structures. SparseMatrix<Number> & matrix = *f_system.matrix; // ------------------------------------------------------------------ // Finite Element related stuff // // Build a Finite Element object of the specified type. Since the // FEBase::build() member dynamically creates memory we will // store the object as a UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // A 5th order Gauss quadrature rule for numerical integration. QGauss qrule (dim, FIFTH); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (&qrule); // The element Jacobian// quadrature weight at each integration point. const std::vector<Real> & JxW = fe->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> > & phi = fe->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); // Now this is slightly different from example 4. // We will not add directly to the global matrix, // but to the additional matrices "stiffness_mass" and "damping". // The same holds for the right-hand-side vector Fe, which we will // later on store in the additional vector "rhs". // The zero_matrix is used to explicitly induce the same sparsity // structure in the overall matrix. // see later on. (At least) the mass, and stiffness matrices, however, // are inherently real. Therefore, store these as one complex // matrix. This will definitely save memory. DenseMatrix<Number> Ke, Ce, Me, zero_matrix; DenseVector<Number> Fe; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the elements in the mesh. // We will compute the element matrix and right-hand-side // contribution. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Start logging the element initialization. START_LOG("elem init", "assemble_helmholtz"); // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem * elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero & resize the element matrix and right-hand side before // summing them, with different element types in the mesh this // is quite necessary. { const unsigned int n_dof_indices = dof_indices.size(); Ke.resize (n_dof_indices, n_dof_indices); Ce.resize (n_dof_indices, n_dof_indices); Me.resize (n_dof_indices, n_dof_indices); zero_matrix.resize (n_dof_indices, n_dof_indices); Fe.resize (n_dof_indices); } // Stop logging the element initialization. STOP_LOG("elem init", "assemble_helmholtz"); // Now loop over the quadrature points. This handles // the numeric integration. START_LOG("stiffness & mass", "assemble_helmholtz"); for (unsigned int qp=0; qp<qrule.n_points(); qp++) { // Now we will build the element matrix. This involves // a double loop to integrate the test funcions (i) against // the trial functions (j). Note the braces on the rhs // of Ke(i,j): these are quite necessary to finally compute // Real*(Point*Point) = Real, and not something else... for (unsigned int i=0; i<phi.size(); i++) for (unsigned int j=0; j<phi.size(); j++) { Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); Me(i,j) += JxW[qp]*(phi[i][qp]*phi[j][qp]); } } STOP_LOG("stiffness & mass", "assemble_helmholtz"); // Now compute the contribution to the element matrix // (due to mixed boundary conditions) if the current // element lies on the boundary. // // The following loops over the sides of the element. // If the element has no neighbor on a side then that // side MUST live on a boundary of the domain. for (unsigned int side=0; side<elem->n_sides(); side++) if (elem->neighbor(side) == libmesh_nullptr) { LOG_SCOPE("damping", "assemble_helmholtz"); // Declare a special finite element object for // boundary integration. UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); // Boundary integration requires one quadraure rule, // with dimensionality one less than the dimensionality // of the element. QGauss qface(dim-1, SECOND); // Tell the finte element object to use our // quadrature rule. fe_face->attach_quadrature_rule (&qface); // The value of the shape functions at the quadrature // points. const std::vector<std::vector<Real> > & phi_face = fe_face->get_phi(); // The Jacobian// Quadrature Weight at the quadrature // points on the face. const std::vector<Real> & JxW_face = fe_face->get_JxW(); // Compute the shape function values on the element // face. fe_face->reinit(elem, side); // For the Robin BCs consider a normal admittance an=1 // at some parts of the bounfdary const Real an_value = 1.; // Loop over the face quadrature points for integration. for (unsigned int qp=0; qp<qface.n_points(); qp++) { // Element matrix contributrion due to precribed // admittance boundary conditions. for (unsigned int i=0; i<phi_face.size(); i++) for (unsigned int j=0; j<phi_face.size(); j++) Ce(i,j) += rho*an_value*JxW_face[qp]*phi_face[i][qp]*phi_face[j][qp]; } } // If this assembly program were to be used on an adaptive mesh, // we would have to apply any hanging node constraint equations // by uncommenting the following lines: // std::vector<unsigned int> dof_indicesC = dof_indices; // std::vector<unsigned int> dof_indicesM = dof_indices; // dof_map.constrain_element_matrix (Ke, dof_indices); // dof_map.constrain_element_matrix (Ce, dof_indicesC); // dof_map.constrain_element_matrix (Me, dof_indicesM); // Finally, simply add the contributions to the additional // matrices and vector. stiffness.add_matrix (Ke, dof_indices); damping.add_matrix (Ce, dof_indices); mass.add_matrix (Me, dof_indices); // For the overall matrix, explicitly zero the entries where // we added values in the other ones, so that we have // identical sparsity footprints. matrix.add_matrix(zero_matrix, dof_indices); } // loop el // It now remains to compute the rhs. Here, we simply // get the normal velocities values on the boundary from the // mesh data. { LOG_SCOPE("rhs", "assemble_helmholtz"); // get a reference to the mesh data. const MeshData & mesh_data = es.get_mesh_data(); // We will now loop over all nodes. In case nodal data // for a certain node is available in the MeshData, we simply // adopt the corresponding value for the rhs vector. // Note that normal data was given in the mesh data file, // i.e. one value per node libmesh_assert_equal_to (mesh_data.n_val_per_node(), 1); MeshBase::const_node_iterator node_it = mesh.nodes_begin(); const MeshBase::const_node_iterator node_end = mesh.nodes_end(); for ( ; node_it != node_end; ++node_it) { // the current node pointer const Node * node = *node_it; // check if the mesh data has data for the current node // and do for all components if (mesh_data.has_data(node)) for (unsigned int comp=0; comp<node->n_comp(0, 0); comp++) { // the dof number unsigned int dn = node->dof_number(0, 0, comp); // set the nodal value freq_indep_rhs.set(dn, mesh_data.get_data(node)[0]); } } } // All done! }
// This function defines the assembly routine. It is responsible for // computing the proper matrix entries for the element stiffness // matrices and right-hand sides. void assemble (EquationSystems & es, const std::string & system_name) { #ifdef LIBMESH_ENABLE_AMR // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "System"); // Get a constant reference to the mesh object. const MeshBase & mesh = es.get_mesh(); // The dimension that we are running const unsigned int dim = mesh.mesh_dimension(); // Get a reference to the Convection-Diffusion system object. LinearImplicitSystem & system = es.get_system<LinearImplicitSystem> ("System"); // Get the Finite Element type for the first (and only) // variable in the system. FEType fe_type = system.variable_type(0); // Build a Finite Element object of the specified type. Since the // FEBase::build() member dynamically creates memory we will // store the object as a UniquePtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); // A Gauss quadrature rule for numerical integration. // Let the FEType object decide what order rule is appropriate. QGauss qrule (dim, fe_type.default_quadrature_order()); QGauss qface (dim-1, fe_type.default_quadrature_order()); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule (&qrule); fe_face->attach_quadrature_rule (&qface); // Here we define some references to cell-specific data that // will be used to assemble the linear system. We will start // with the element Jacobian * quadrature weight at each integration point. const std::vector<Real> & JxW = fe->get_JxW(); const std::vector<Real> & JxW_face = fe_face->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> > & phi = fe->get_phi(); const std::vector<std::vector<Real> > & psi = fe_face->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); // The XY locations of the quadrature points used for face integration //const std::vector<Point>& qface_points = fe_face->get_xyz(); // A reference to the DofMap object for this system. The DofMap // object handles the index translation from node and element numbers // to degree of freedom numbers. We will talk more about the DofMap // in future examples. const DofMap & dof_map = system.get_dof_map(); // Define data structures to contain the element matrix // and right-hand-side vector contribution. Following // basic finite element terminology we will denote these // "Ke" and "Fe". DenseMatrix<Number> Ke; DenseVector<Number> Fe; // Analogous data structures for thw two vectors v and w that form // the tensor shell matrix. DenseVector<Number> Ve; DenseVector<Number> We; // This vector will hold the degree of freedom indices for // the element. These define where in the global system // the element degrees of freedom get mapped. std::vector<dof_id_type> dof_indices; // Now we will loop over all the elements in the mesh that // live on the local processor. We will compute the element // matrix and right-hand-side contribution. Since the mesh // will be refined we want to only consider the ACTIVE elements, // hence we use a variant of the active_elem_iterator. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); for ( ; el != end_el; ++el) { // Store a pointer to the element we are currently // working on. This allows for nicer syntax later. const Elem * elem = *el; // Get the degree of freedom indices for the // current element. These define where in the global // matrix and right-hand-side this element will // contribute to. dof_map.dof_indices (elem, dof_indices); // Compute the element-specific data for the current // element. This involves computing the location of the // quadrature points (q_point) and the shape functions // (phi, dphi) for the current element. fe->reinit (elem); // Zero the element matrix and right-hand side before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element. Note that this will be the case if the // element type is different (i.e. the last element was a // triangle, now we are on a quadrilateral). Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); Ve.resize (dof_indices.size()); We.resize (dof_indices.size()); // Now we will build the element matrix and right-hand-side. // Constructing the RHS requires the solution and its // gradient from the previous timestep. This myst be // calculated at each quadrature point by summing the // solution degree-of-freedom values by the appropriate // weight functions. for (unsigned int qp=0; qp<qrule.n_points(); qp++) { // Now compute the element matrix and RHS contributions. for (unsigned int i=0; i<phi.size(); i++) { // The RHS contribution Fe(i) += JxW[qp]*phi[i][qp]; for (unsigned int j=0; j<phi.size(); j++) { // The matrix contribution Ke(i,j) += JxW[qp]*( // Stiffness matrix (dphi[i][qp]*dphi[j][qp]) ); } // V and W are the same for this example. Ve(i) += JxW[qp]*phi[i][qp]; We(i) += JxW[qp]*phi[i][qp]; } } // At this point the interior element integration has // been completed. However, we have not yet addressed // boundary conditions. For this example we will only // consider simple Dirichlet boundary conditions imposed // via the penalty method. // // The following loops over the sides of the element. // If the element has no neighbor on a side then that // side MUST live on a boundary of the domain. { // The penalty value. const Real penalty = 1.e10; // The following loops over the sides of the element. // If the element has no neighbor on a side then that // side MUST live on a boundary of the domain. for (unsigned int s=0; s<elem->n_sides(); s++) if (elem->neighbor(s) == libmesh_nullptr) { fe_face->reinit(elem, s); for (unsigned int qp=0; qp<qface.n_points(); qp++) { // Matrix contribution for (unsigned int i=0; i<psi.size(); i++) for (unsigned int j=0; j<psi.size(); j++) Ke(i,j) += penalty*JxW_face[qp]*psi[i][qp]*psi[j][qp]; } } } // We have now built the element matrix and RHS vector in terms // of the element degrees of freedom. However, it is possible // that some of the element DOFs are constrained to enforce // solution continuity, i.e. they are not really "free". We need // to constrain those DOFs in terms of non-constrained DOFs to // ensure a continuous solution. The // DofMap::constrain_element_matrix_and_vector() method does // just that. // However, constraining both the sparse matrix (and right hand // side) plus the rank 1 matrix is tricky. The dof_indices // vector has to be backuped for that because the constraining // functions modify it. std::vector<dof_id_type> dof_indices_backup(dof_indices); dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); dof_indices = dof_indices_backup; dof_map.constrain_element_dyad_matrix(Ve, We, dof_indices); // The element matrix and right-hand-side are now built // for this element. Add them to the global matrix and // right-hand-side vector. The SparseMatrix::add_matrix() // and NumericVector::add_vector() members do this for us. system.matrix->add_matrix (Ke, dof_indices); system.get_matrix("Preconditioner").add_matrix (Ke, dof_indices); system.rhs->add_vector (Fe, dof_indices); system.get_vector("v").add_vector(Ve, dof_indices); system.get_vector("w").add_vector(We, dof_indices); } // Finished computing the sytem matrix and right-hand side. // Matrices and vectors must be closed manually. This is necessary // because the matrix is not directly used as the system matrix (in // which case the solver closes it) but as a part of a shell matrix. system.matrix->close(); system.get_matrix("Preconditioner").close(); system.rhs->close(); system.get_vector("v").close(); system.get_vector("w").close(); #endif // #ifdef LIBMESH_ENABLE_AMR }