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 transform_mesh_and_plot(EquationSystems & es, Real curvature, const std::string & filename) { // Loop over the mesh nodes and move them! MeshBase & mesh = es.get_mesh(); MeshBase::node_iterator node_it = mesh.nodes_begin(); const MeshBase::node_iterator node_end = mesh.nodes_end(); for ( ; node_it != node_end; node_it++) { Node * node = *node_it; Real x = (*node)(0); Real z = (*node)(2); (*node)(0) = -1./curvature + (1./curvature + x)*cos(curvature*z); (*node)(2) = (1./curvature + x)*sin(curvature*z); } #ifdef LIBMESH_HAVE_EXODUS_API ExodusII_IO(mesh).write_equation_systems(filename, es); #endif }
void write_output_solvedata(EquationSystems & es, unsigned int a_step, unsigned int newton_steps, unsigned int krylov_steps, unsigned int tv_sec, unsigned int tv_usec) { MeshBase & mesh = es.get_mesh(); unsigned int n_active_elem = mesh.n_active_elem(); unsigned int n_active_dofs = es.n_active_dofs(); if (mesh.processor_id() == 0) { // Write out the number of elements/dofs used std::ofstream activemesh ("out_activemesh.m", std::ios_base::app | std::ios_base::out); activemesh.precision(17); activemesh << (a_step + 1) << ' ' << n_active_elem << ' ' << n_active_dofs << std::endl; // Write out the number of solver steps used std::ofstream solvesteps ("out_solvesteps.m", std::ios_base::app | std::ios_base::out); solvesteps.precision(17); solvesteps << newton_steps << ' ' << krylov_steps << std::endl; // Write out the clock time std::ofstream clocktime ("out_clocktime.m", std::ios_base::app | std::ios_base::out); clocktime.precision(17); clocktime << tv_sec << '.' << tv_usec << std::endl; } }
void MeshOutput<MT>::write_equation_systems (const std::string & fname, const EquationSystems & es, const std::set<std::string> * system_names) { START_LOG("write_equation_systems()", "MeshOutput"); // We may need to gather and/or renumber a ParallelMesh to output // it, making that const qualifier in our constructor a dirty lie MT & my_mesh = const_cast<MT &>(*_obj); // If we're asked to write data that's associated with a different // mesh, output files full of garbage are the result. libmesh_assert_equal_to(&es.get_mesh(), _obj); // A non-renumbered mesh may not have a contiguous numbering, and // that needs to be fixed before we can build a solution vector. if (my_mesh.max_elem_id() != my_mesh.n_elem() || my_mesh.max_node_id() != my_mesh.n_nodes()) { // If we were allowed to renumber then we should have already // been properly renumbered... libmesh_assert(!my_mesh.allow_renumbering()); libmesh_do_once(libMesh::out << "Warning: This MeshOutput subclass only supports meshes which are contiguously renumbered!" << std::endl;);
void scale_mesh_and_plot(EquationSystems & es, const RBParameters & mu, const std::string & filename) { // Loop over the mesh nodes and move them! MeshBase & mesh = es.get_mesh(); MeshBase::node_iterator node_it = mesh.nodes_begin(); const MeshBase::node_iterator node_end = mesh.nodes_end(); for ( ; node_it != node_end; node_it++) { Node * node = *node_it; (*node)(0) *= mu.get_value("x_scaling"); } // Post-process the solution to compute the stresses compute_stresses(es); #ifdef LIBMESH_HAVE_EXODUS_API ExodusII_IO (mesh).write_equation_systems (filename, es); #endif // Loop over the mesh nodes and move them! node_it = mesh.nodes_begin(); for ( ; node_it != node_end; node_it++) { Node * node = *node_it; (*node)(0) /= mu.get_value("x_scaling"); } }
void run_timestepping(EquationSystems& systems, GetPot& args) { TransientExplicitSystem& aux_system = systems.get_system<TransientExplicitSystem>("auxiliary"); SolidSystem& solid_system = systems.get_system<SolidSystem>("solid"); AutoPtr<VTKIO> io = AutoPtr<VTKIO>(new VTKIO(systems.get_mesh())); Real duration = args("duration", 1.0); for (unsigned int t_step = 0; t_step < duration/solid_system.deltat; t_step++) { // Progress in current phase [0..1] Real progress = t_step * solid_system.deltat / duration; systems.parameters.set<Real>("progress") = progress; systems.parameters.set<unsigned int>("step") = t_step; // Update message out << "===== Time Step " << std::setw(4) << t_step; out << " (" << std::fixed << std::setprecision(2) << std::setw(6) << (progress*100.) << "%)"; out << ", time = " << std::setw(7) << solid_system.time; out << " =====" << std::endl; // Advance timestep in auxiliary system aux_system.current_local_solution->close(); aux_system.old_local_solution->close(); *aux_system.older_local_solution = *aux_system.old_local_solution; *aux_system.old_local_solution = *aux_system.current_local_solution; out << "Solving Solid" << std::endl; solid_system.solve(); aux_system.reinit(); // Carry out the adaptive mesh refinement/coarsening out << "Doing a reinit of the equation systems" << std::endl; systems.reinit(); if (t_step % args("output/frequency", 1) == 0) { std::stringstream file_name; file_name << args("results_directory", "./") << "fem_"; file_name << std::setw(6) << std::setfill('0') << t_step; file_name << ".pvtu"; io->write_equation_systems(file_name.str(), systems); } // Advance to the next timestep in a transient problem out << "Advancing to next step" << std::endl; solid_system.time_solver->advance_timestep(); } }
/* * FIXME: This is known to write nonsense on AMR meshes * and it strips the imaginary parts of complex Numbers */ void VTKIO::system_vectors_to_vtk(const EquationSystems& es, vtkUnstructuredGrid*& grid) { if (MeshOutput<MeshBase>::mesh().processor_id() == 0) { std::map<std::string, std::vector<Number> > vecs; for (unsigned int i=0; i<es.n_systems(); ++i) { const System& sys = es.get_system(i); System::const_vectors_iterator v_end = sys.vectors_end(); System::const_vectors_iterator it = sys.vectors_begin(); for (; it!= v_end; ++it) { // for all vectors on this system std::vector<Number> values; // libMesh::out<<"it "<<it->first<<std::endl; it->second->localize_to_one(values, 0); // libMesh::out<<"finish localize"<<std::endl; vecs[it->first] = values; } } std::map<std::string, std::vector<Number> >::iterator it = vecs.begin(); for (; it!=vecs.end(); ++it) { vtkDoubleArray *data = vtkDoubleArray::New(); data->SetName(it->first.c_str()); libmesh_assert_equal_to (it->second.size(), es.get_mesh().n_nodes()); data->SetNumberOfValues(it->second.size()); for (unsigned int i=0; i<it->second.size(); ++i) { #ifdef LIBMESH_USE_COMPLEX_NUMBERS libmesh_do_once (libMesh::err << "Only writing the real part for complex numbers!\n" << "if you need this support contact " << LIBMESH_PACKAGE_BUGREPORT << std::endl); data->SetValue(i, it->second[i].real()); #else data->SetValue(i, it->second[i]); #endif } grid->GetPointData()->AddArray(data); data->Delete(); } } }
void write_output(EquationSystems &es, unsigned int a_step, // The adaptive step count std::string solution_type) // primal or adjoint solve { MeshBase &mesh = es.get_mesh(); #ifdef LIBMESH_HAVE_GMV OStringStream file_name_gmv; file_name_gmv << solution_type << ".out.gmv."; OSSRealzeroright(file_name_gmv,2,0,a_step); GMVIO(mesh).write_equation_systems (file_name_gmv.str(), es); #endif }
void write_output(EquationSystems &es, unsigned int a_step, // The adaptive step count std::string solution_type) // primal or adjoint solve { #ifdef LIBMESH_HAVE_GMV MeshBase &mesh = es.get_mesh(); std::ostringstream file_name_gmv; file_name_gmv << solution_type << ".out.gmv." << std::setw(2) << std::setfill('0') << std::right << a_step; GMVIO(mesh).write_equation_systems (file_name_gmv.str(), es); #endif }
void assemble_stokes (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, "Stokes"); // 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> ("Stokes"); // 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 p_var = system.variable_number ("p"); // Get the Finite Element type for "u". Note this will be // the same as the type for "v". FEType fe_vel_type = system.variable_type(u_var); // Get the Finite Element type for "p". FEType fe_pres_type = system.variable_type(p_var); // Build a Finite Element object of the specified type for // the velocity variables. UniquePtr<FEBase> fe_vel (FEBase::build(dim, fe_vel_type)); // Build a Finite Element object of the specified type for // the pressure variables. UniquePtr<FEBase> fe_pres (FEBase::build(dim, fe_pres_type)); // A Gauss quadrature rule for numerical integration. // Let the \p FEType object decide what order rule is appropriate. QGauss qrule (dim, fe_vel_type.default_quadrature_order()); // Tell the finite element objects to use our quadrature rule. fe_vel->attach_quadrature_rule (&qrule); fe_pres->attach_quadrature_rule (&qrule); // 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_vel->get_JxW(); // The element shape function gradients for the velocity // variables evaluated at the quadrature points. const std::vector<std::vector<RealGradient> >& dphi = fe_vel->get_dphi(); // The element shape functions for the pressure variable // evaluated at the quadrature points. const std::vector<std::vector<Real> >& psi = fe_pres->get_phi(); // 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; DenseSubMatrix<Number> Kuu(Ke), Kuv(Ke), Kup(Ke), Kvu(Ke), Kvv(Ke), Kvp(Ke), Kpu(Ke), Kpv(Ke), Kpp(Ke); DenseSubVector<Number> Fu(Fe), Fv(Fe), Fp(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_p; // 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 \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); 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_p, p_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(); const unsigned int n_p_dofs = dof_indices_p.size(); // 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_vel->reinit (elem); fe_pres->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 (n_dofs, n_dofs); Fe.resize (n_dofs); // Reposition the submatrices... The idea is this: // // - - - - // | Kuu Kuv Kup | | Fu | // Ke = | Kvu Kvv Kvp |; Fe = | Fv | // | Kpu Kpv Kpp | | Fp | // - - - - // // 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); Kup.reposition (u_var*n_u_dofs, p_var*n_u_dofs, n_u_dofs, n_p_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); Kvp.reposition (v_var*n_v_dofs, p_var*n_v_dofs, n_v_dofs, n_p_dofs); Kpu.reposition (p_var*n_u_dofs, u_var*n_u_dofs, n_p_dofs, n_u_dofs); Kpv.reposition (p_var*n_u_dofs, v_var*n_u_dofs, n_p_dofs, n_v_dofs); Kpp.reposition (p_var*n_u_dofs, p_var*n_u_dofs, n_p_dofs, n_p_dofs); Fu.reposition (u_var*n_u_dofs, n_u_dofs); Fv.reposition (v_var*n_u_dofs, n_v_dofs); Fp.reposition (p_var*n_u_dofs, n_p_dofs); // Now we will build the element matrix. for (unsigned int qp=0; qp<qrule.n_points(); qp++) { // Assemble the u-velocity row // uu coupling 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]); // up coupling for (unsigned int i=0; i<n_u_dofs; i++) for (unsigned int j=0; j<n_p_dofs; j++) Kup(i,j) += -JxW[qp]*psi[j][qp]*dphi[i][qp](0); // Assemble the v-velocity row // vv coupling 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]); // vp coupling for (unsigned int i=0; i<n_v_dofs; i++) for (unsigned int j=0; j<n_p_dofs; j++) Kvp(i,j) += -JxW[qp]*psi[j][qp]*dphi[i][qp](1); // Assemble the pressure row // pu coupling for (unsigned int i=0; i<n_p_dofs; i++) for (unsigned int j=0; j<n_u_dofs; j++) Kpu(i,j) += -JxW[qp]*psi[i][qp]*dphi[j][qp](0); // pv coupling for (unsigned int i=0; i<n_p_dofs; i++) for (unsigned int j=0; j<n_v_dofs; j++) Kpv(i,j) += -JxW[qp]*psi[i][qp]*dphi[j][qp](1); } // end of the quadrature point qp-loop // 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 penalty method used here // is equivalent (for Lagrange basis functions) to lumping // the matrix resulting from the L2 projection penalty // approach introduced in example 3. { // 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) { UniquePtr<Elem> side (elem->build_side(s)); // Loop over the nodes on the side. for (unsigned int ns=0; ns<side->n_nodes(); ns++) { // The location on the boundary of the current // node. // const Real xf = side->point(ns)(0); const Real yf = side->point(ns)(1); // The penalty value. \f$ \frac{1}{\epsilon \f$ const Real penalty = 1.e10; // The boundary values. // Set u = 1 on the top boundary, 0 everywhere else const Real u_value = (yf > .99) ? 1. : 0.; // Set v = 0 everywhere const Real v_value = 0.; // Find the node on the element matching this node on // the side. That defined where in the element matrix // the boundary condition will be applied. for (unsigned int n=0; n<elem->n_nodes(); n++) if (elem->node(n) == side->node(ns)) { // Matrix contribution. Kuu(n,n) += penalty; Kvv(n,n) += penalty; // Right-hand-side contribution. Fu(n) += penalty*u_value; Fv(n) += penalty*v_value; } } // end face node loop } // end if (elem->neighbor(side) == NULL) } // 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. 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 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 element loop // That's it. return; }
void MultiAppProjectionTransfer::assembleL2From(EquationSystems & es, const std::string & system_name) { unsigned int n_apps = _multi_app->numGlobalApps(); std::vector<NumericVector<Number> *> from_slns(n_apps, NULL); std::vector<MeshFunction *> from_fns(n_apps, NULL); std::vector<MeshTools::BoundingBox *> from_bbs(n_apps, NULL); // get bounding box, mesh function and solution for each subapp for (unsigned int i = 0; i < n_apps; i++) { if (!_multi_app->hasLocalApp(i)) continue; MPI_Comm swapped = Moose::swapLibMeshComm(_multi_app->comm()); FEProblem & from_problem = *_multi_app->appProblem(i); EquationSystems & from_es = from_problem.es(); MeshBase & from_mesh = from_es.get_mesh(); MeshTools::BoundingBox * app_box = new MeshTools::BoundingBox(MeshTools::processor_bounding_box(from_mesh, from_mesh.processor_id())); from_bbs[i] = app_box; MooseVariable & from_var = from_problem.getVariable(0, _from_var_name); System & from_sys = from_var.sys().system(); unsigned int from_var_num = from_sys.variable_number(from_var.name()); NumericVector<Number> * serialized_from_solution = NumericVector<Number>::build(from_sys.comm()).release(); serialized_from_solution->init(from_sys.n_dofs(), false, SERIAL); // Need to pull down a full copy of this vector on every processor so we can get values in parallel from_sys.solution->localize(*serialized_from_solution); from_slns[i] = serialized_from_solution; MeshFunction * from_func = new MeshFunction(from_es, *serialized_from_solution, from_sys.get_dof_map(), from_var_num); from_func->init(Trees::ELEMENTS); from_func->enable_out_of_mesh_mode(NOTFOUND); from_fns[i] = from_func; Moose::swapLibMeshComm(swapped); } const MeshBase& mesh = es.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); LinearImplicitSystem & system = es.get_system<LinearImplicitSystem>(system_name); FEType fe_type = system.variable_type(0); AutoPtr<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<Point> & xyz = fe->get_xyz(); const DofMap& dof_map = system.get_dof_map(); 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; fe->reinit (elem); dof_map.dof_indices (elem, dof_indices); Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); for (unsigned int qp = 0; qp < qrule.n_points(); qp++) { Point qpt = xyz[qp]; Real f = 0.; for (unsigned int app = 0; app < n_apps; app++) { Point pt = qpt - _multi_app->position(app); if (from_bbs[app] != NULL && from_bbs[app]->contains_point(pt)) { MPI_Comm swapped = Moose::swapLibMeshComm(_multi_app->comm()); f = (*from_fns[app])(pt); Moose::swapLibMeshComm(swapped); break; } } // Now compute the element matrix and RHS contributions. for (unsigned int i=0; i<phi.size(); i++) { // RHS Fe(i) += JxW[qp] * (f * phi[i][qp]); if (_compute_matrix) for (unsigned int j = 0; j < phi.size(); j++) { // The matrix contribution Ke(i,j) += JxW[qp] * (phi[i][qp] * phi[j][qp]); } } dof_map.constrain_element_matrix_and_vector(Ke, Fe, dof_indices); if (_compute_matrix) system.matrix->add_matrix(Ke, dof_indices); system.rhs->add_vector(Fe, dof_indices); } } for (unsigned int i = 0; i < n_apps; i++) { delete from_fns[i]; delete from_bbs[i]; delete from_slns[i]; } }
void MultiAppProjectionTransfer::assembleL2To(EquationSystems & es, const std::string & system_name) { unsigned int app = es.parameters.get<unsigned int>("app"); FEProblem & from_problem = *_multi_app->problem(); EquationSystems & from_es = from_problem.es(); MooseVariable & from_var = from_problem.getVariable(0, _from_var_name); System & from_sys = from_var.sys().system(); unsigned int from_var_num = from_sys.variable_number(from_var.name()); NumericVector<Number> * serialized_from_solution = NumericVector<Number>::build(from_sys.comm()).release(); serialized_from_solution->init(from_sys.n_dofs(), false, SERIAL); // Need to pull down a full copy of this vector on every processor so we can get values in parallel from_sys.solution->localize(*serialized_from_solution); MeshFunction from_func(from_es, *serialized_from_solution, from_sys.get_dof_map(), from_var_num); from_func.init(Trees::ELEMENTS); from_func.enable_out_of_mesh_mode(0.); const MeshBase& mesh = es.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); LinearImplicitSystem & system = es.get_system<LinearImplicitSystem>(system_name); FEType fe_type = system.variable_type(0); AutoPtr<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<Point> & xyz = fe->get_xyz(); const DofMap& dof_map = system.get_dof_map(); 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; fe->reinit (elem); dof_map.dof_indices (elem, dof_indices); Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); for (unsigned int qp = 0; qp < qrule.n_points(); qp++) { Point qpt = xyz[qp]; Point pt = qpt + _multi_app->position(app); Real f = from_func(pt); // Now compute the element matrix and RHS contributions. for (unsigned int i=0; i<phi.size(); i++) { // RHS Fe(i) += JxW[qp] * (f * phi[i][qp]); if (_compute_matrix) for (unsigned int j = 0; j < phi.size(); j++) { // The matrix contribution Ke(i,j) += JxW[qp] * (phi[i][qp] * phi[j][qp]); } } dof_map.constrain_element_matrix_and_vector(Ke, Fe, dof_indices); if (_compute_matrix) 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. 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 MultiAppProjectionTransfer::assembleL2(EquationSystems & es, const std::string & system_name) { // Get the system and mesh from the input arguments. LinearImplicitSystem & system = es.get_system<LinearImplicitSystem>(system_name); MeshBase & to_mesh = es.get_mesh(); // Get the meshfunction evaluations and the map that was stashed in the es. std::vector<Real> & final_evals = * es.parameters.get<std::vector<Real>*>("final_evals"); std::map<unsigned int, unsigned int> & element_map = * es.parameters.get<std::map<unsigned int, unsigned int>*>("element_map"); // Setup system vectors and matrices. FEType fe_type = system.variable_type(0); std::unique_ptr<FEBase> fe(FEBase::build(to_mesh.mesh_dimension(), fe_type)); QGauss qrule(to_mesh.mesh_dimension(), fe_type.default_quadrature_order()); fe->attach_quadrature_rule(&qrule); const DofMap& dof_map = system.get_dof_map(); DenseMatrix<Number> Ke; DenseVector<Number> Fe; std::vector<dof_id_type> dof_indices; const std::vector<Real> & JxW = fe->get_JxW(); const std::vector<std::vector<Real> > & phi = fe->get_phi(); const MeshBase::const_element_iterator end_el = to_mesh.active_local_elements_end(); for (MeshBase::const_element_iterator el = to_mesh.active_local_elements_begin(); el != end_el; ++el) { const Elem* elem = *el; fe->reinit (elem); dof_map.dof_indices (elem, dof_indices); Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); for (unsigned int qp = 0; qp < qrule.n_points(); qp++) { Real meshfun_eval = 0.; if (element_map.find(elem->id()) != element_map.end()) { // We have evaluations for this element. meshfun_eval = final_evals[element_map[elem->id()] + qp]; } // Now compute the element matrix and RHS contributions. for (unsigned int i=0; i<phi.size(); i++) { // RHS Fe(i) += JxW[qp] * (meshfun_eval * phi[i][qp]); if (_compute_matrix) for (unsigned int j = 0; j < phi.size(); j++) { // The matrix contribution Ke(i,j) += JxW[qp] * (phi[i][qp] * phi[j][qp]); } } dof_map.constrain_element_matrix_and_vector(Ke, Fe, dof_indices); if (_compute_matrix) system.matrix->add_matrix(Ke, dof_indices); system.rhs->add_vector(Fe, dof_indices); } } }
void assemble_matrices(EquationSystems & es, const std::string & libmesh_dbg_var(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> ("Eigensystem"); // 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 std::unique_ptr<FEBase>. This can be thought // of as a pointer that will clean up after itself. std::unique_ptr<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. for (const auto & elem : mesh.active_local_element_ptr_range()) { // 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). const unsigned int n_dofs = cast_int<unsigned int>(dof_indices.size()); Ke.resize (n_dofs, n_dofs); Me.resize (n_dofs, n_dofs); // 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 functions (i) against // the trial functions (j). 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++) { Me(i,j) += JxW[qp]*phi[i][qp]*phi[j][qp]; Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); } // The calls to constrain_element_matrix below have no effect in // the current example. However, if users modify this example to // include hanging nodes due to mesh refinement, for example, // then it is essential to call constrain_element_matrix. // As a result we include constrain_element_matrix here to // ensure this example is ready to be used with hanging nodes. // (Note that constrained rows/cols will be eliminated from // the eigenproblem by the CondensedEigenSystem.) 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 }
DTKInterpolationAdapter::DTKInterpolationAdapter(Teuchos::RCP<const Teuchos::MpiComm<int> > in_comm, EquationSystems & in_es, const Point & offset, unsigned int from_dim): comm(in_comm), es(in_es), _offset(offset), mesh(in_es.get_mesh()), dim(mesh.mesh_dimension()) { MPI_Comm old_comm = Moose::swapLibMeshComm(*comm->getRawMpiComm()); std::set<GlobalOrdinal> semi_local_nodes; get_semi_local_nodes(semi_local_nodes); num_local_nodes = semi_local_nodes.size(); vertices.resize(num_local_nodes); Teuchos::ArrayRCP<double> coordinates(num_local_nodes * dim); Teuchos::ArrayRCP<double> target_coordinates(num_local_nodes * from_dim); // Fill in the vertices and coordinates { GlobalOrdinal i = 0; for (std::set<GlobalOrdinal>::iterator it = semi_local_nodes.begin(); it != semi_local_nodes.end(); ++it) { const Node & node = mesh.node(*it); vertices[i] = node.id(); for (GlobalOrdinal j=0; j<dim; j++) coordinates[(j*num_local_nodes) + i] = node(j) + offset(j); for (GlobalOrdinal j=0; j<from_dim; j++) target_coordinates[(j*num_local_nodes) + i] = node(j) + offset(j); i++; } } // Currently assuming all elements are the same! DataTransferKit::DTK_ElementTopology element_topology = get_element_topology(mesh.elem(0)); GlobalOrdinal n_nodes_per_elem = mesh.elem(0)->n_nodes(); GlobalOrdinal n_local_elem = mesh.n_local_elem(); elements.resize(n_local_elem); Teuchos::ArrayRCP<GlobalOrdinal> connectivity(n_nodes_per_elem*n_local_elem); Teuchos::ArrayRCP<double> elem_centroid_coordinates(n_local_elem*from_dim); // Fill in the elements and connectivity { GlobalOrdinal i = 0; MeshBase::const_element_iterator end = mesh.local_elements_end(); for (MeshBase::const_element_iterator it = mesh.local_elements_begin(); it != end; ++it) { const Elem & elem = *(*it); elements[i] = elem.id(); for (GlobalOrdinal j=0; j<n_nodes_per_elem; j++) connectivity[(j*n_local_elem)+i] = elem.node(j); { Point centroid = elem.centroid(); for (GlobalOrdinal j=0; j<from_dim; j++) elem_centroid_coordinates[(j*n_local_elem) + i] = centroid(j) + offset(j); } i++; } } Teuchos::ArrayRCP<int> permutation_list(n_nodes_per_elem); for (GlobalOrdinal i = 0; i < n_nodes_per_elem; ++i ) permutation_list[i] = i; /* Moose::out<<"n_nodes_per_elem: "<<n_nodes_per_elem<<std::endl; Moose::out<<"Dim: "<<dim<<std::endl; Moose::err<<"Vertices size: "<<vertices.size()<<std::endl; { Moose::err<<libMesh::processor_id()<<" Vertices: "; for (unsigned int i=0; i<vertices.size(); i++) Moose::err<<vertices[i]<<" "; Moose::err<<std::endl; } Moose::err<<"Coordinates size: "<<coordinates.size()<<std::endl; { Moose::err<<libMesh::processor_id()<<" Coordinates: "; for (unsigned int i=0; i<coordinates.size(); i++) Moose::err<<coordinates[i]<<" "; Moose::err<<std::endl; } Moose::err<<"Connectivity size: "<<connectivity.size()<<std::endl; { Moose::err<<libMesh::processor_id()<<" Connectivity: "; for (unsigned int i=0; i<connectivity.size(); i++) Moose::err<<connectivity[i]<<" "; Moose::err<<std::endl; } Moose::err<<"Permutation_List size: "<<permutation_list.size()<<std::endl; { Moose::err<<libMesh::processor_id()<<" Permutation_List: "; for (unsigned int i=0; i<permutation_list.size(); i++) Moose::err<<permutation_list[i]<<" "; Moose::err<<std::endl; } */ Teuchos::RCP<MeshContainerType> mesh_container = Teuchos::rcp( new MeshContainerType(dim, vertices, coordinates, element_topology, n_nodes_per_elem, elements, connectivity, permutation_list) ); // We only have 1 element topology in this grid so we make just one mesh block Teuchos::ArrayRCP<Teuchos::RCP<MeshContainerType> > mesh_blocks(1); mesh_blocks[0] = mesh_container; // Create the MeshManager mesh_manager = Teuchos::rcp(new DataTransferKit::MeshManager<MeshContainerType>(mesh_blocks, comm, dim) ); // Pack the coordinates into a field, this will be the positions we'll ask for other systems fields at if (from_dim == dim) target_coords = Teuchos::rcp(new DataTransferKit::FieldManager<MeshContainerType>(mesh_container, comm)); else { Teuchos::ArrayRCP<GlobalOrdinal> empty_elements(0); Teuchos::ArrayRCP<GlobalOrdinal> empty_connectivity(0); Teuchos::RCP<MeshContainerType> coords_only_mesh_container = Teuchos::rcp( new MeshContainerType(from_dim, vertices, target_coordinates, element_topology, n_nodes_per_elem, empty_elements, empty_connectivity, permutation_list) ); target_coords = Teuchos::rcp(new DataTransferKit::FieldManager<MeshContainerType>(coords_only_mesh_container, comm)); } { Teuchos::ArrayRCP<GlobalOrdinal> empty_elements(0); Teuchos::ArrayRCP<GlobalOrdinal> empty_connectivity(0); Teuchos::RCP<MeshContainerType> centroid_coords_only_mesh_container = Teuchos::rcp( new MeshContainerType(from_dim, elements, elem_centroid_coordinates, element_topology, n_nodes_per_elem, empty_elements, empty_connectivity, permutation_list) ); elem_centroid_coords = Teuchos::rcp(new DataTransferKit::FieldManager<MeshContainerType>(centroid_coords_only_mesh_container, comm)); } // Swap back Moose::swapLibMeshComm(old_comm); }
void assemble_matrices(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 // \p FEBase::build() member dynamically creates memory we will // store the object as an \p AutoPtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. AutoPtr<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 \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 = 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<unsigned int> 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 \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 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 #endif // LIBMESH_HAVE_SLEPC /** * All done! */ return; }
void assemble_elasticity(EquationSystems & es, const std::string & libmesh_dbg_var(system_name)) { libmesh_assert_equal_to (system_name, "Elasticity"); 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 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<RealGradient> > & dphi = fe->get_dphi(); 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); for (unsigned int qp=0; qp<qrule.n_points(); qp++) { for (unsigned int i=0; i<n_u_dofs; i++) for (unsigned int j=0; j<n_u_dofs; j++) { // Tensor indices unsigned int C_i, C_j, C_k, C_l; C_i=0, C_k=0; C_j=0, C_l=0; Kuu(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=1, C_l=0; Kuu(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=0, C_l=1; Kuu(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=1, C_l=1; Kuu(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); } for (unsigned int i=0; i<n_u_dofs; i++) for (unsigned int j=0; j<n_v_dofs; j++) { // Tensor indices unsigned int C_i, C_j, C_k, C_l; C_i=0, C_k=1; C_j=0, C_l=0; Kuv(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=1, C_l=0; Kuv(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=0, C_l=1; Kuv(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=1, C_l=1; Kuv(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); } for (unsigned int i=0; i<n_v_dofs; i++) for (unsigned int j=0; j<n_u_dofs; j++) { // Tensor indices unsigned int C_i, C_j, C_k, C_l; C_i=1, C_k=0; C_j=0, C_l=0; Kvu(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=1, C_l=0; Kvu(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=0, C_l=1; Kvu(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=1, C_l=1; Kvu(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); } for (unsigned int i=0; i<n_v_dofs; i++) for (unsigned int j=0; j<n_v_dofs; j++) { // Tensor indices unsigned int C_i, C_j, C_k, C_l; C_i=1, C_k=1; C_j=0, C_l=0; Kvv(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=1, C_l=0; Kvv(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=0, C_l=1; Kvv(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); C_j=1, C_l=1; Kvv(i,j) += JxW[qp]*(eval_elasticity_tensor(C_i, C_j, C_k, C_l) * dphi[i][qp](C_j)*dphi[j][qp](C_l)); } } { for (unsigned int side=0; side<elem->n_sides(); side++) if (elem->neighbor_ptr(side) == libmesh_nullptr) { 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); if (mesh.get_boundary_info().has_boundary_id (elem, side, 1)) // Apply a traction on the right side { for (unsigned int qp=0; qp<qface.n_points(); qp++) for (unsigned int i=0; i<n_v_dofs; i++) Fv(i) += JxW_face[qp] * (-1.) * phi_face[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); } }
void assemble_structure (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, "Structure"); // Get a constant reference to the mesh object. const MeshBase& mesh = es.get_mesh(); // The dimension that we are running const uint dim = mesh.mesh_dimension(); // Get a reference to the Convection-Diffusion system object. LinearImplicitSystem & system = es.get_system<LinearImplicitSystem> ("Structure"); // Numeric ids corresponding to each variable in the system const uint dx_var = system.variable_number ("dx"); const uint dy_var = system.variable_number ("dy"); const uint dz_var = system.variable_number ("dz"); // Get the Finite Element type for "u". Note this will be // the same as the type for "v". FEType fe_d_type = system.variable_type(dx_var); // Build a Finite Element object of the specified type for // the velocity variables. AutoPtr<FEBase> fe_d (FEBase::build(dim, fe_d_type)); // A Gauss quadrature rule for numerical integration. // Let the \p FEType object decide what order rule is appropriate. QGauss qrule (dim, fe_d_type.default_quadrature_order()); // Tell the finite element objects to use our quadrature rule. fe_d->attach_quadrature_rule (&qrule); // 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_d->get_JxW(); // The element shape function gradients for the velocity // variables evaluated at the quadrature points. const std::vector<std::vector<Real> >& phi = fe_d->get_phi(); const std::vector<std::vector<RealGradient> >& dphi = fe_d->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. 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; DenseSubMatrix<Number> Kdxdx(Ke), Kdxdy(Ke), Kdxdz(Ke), Kdydx(Ke), Kdydy(Ke), Kdydz(Ke), Kdzdx(Ke), Kdzdy(Ke), Kdzdz(Ke); DenseSubVector<Number> Fdx(Fe), Fdy(Fe), Fdz(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_dx; std::vector<dof_id_type> dof_indices_dy; std::vector<dof_id_type> dof_indices_dz; // 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 \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); dof_map.dof_indices (elem, dof_indices_dx, dx_var); dof_map.dof_indices (elem, dof_indices_dy, dy_var); dof_map.dof_indices (elem, dof_indices_dz, dz_var); const uint n_dofs = dof_indices.size(); const uint n_dx_dofs = dof_indices_dx.size(); const uint n_dy_dofs = dof_indices_dy.size(); const uint n_dz_dofs = dof_indices_dz.size(); // 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_d->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 (n_dofs, n_dofs); Fe.resize (n_dofs); // Reposition the submatrices... The idea is this: // // - - - - // | Kuu Kuv Kup | | Fu | // Ke = | Kvu Kvv Kvp |; Fe = | Fv | // | Kpu Kpv Kpp | | Fp | // - - - - // // 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) Kdxdx.reposition (dx_var*n_dx_dofs, dx_var*n_dx_dofs, n_dx_dofs, n_dx_dofs); Kdxdy.reposition (dx_var*n_dx_dofs, dy_var*n_dx_dofs, n_dx_dofs, n_dy_dofs); Kdxdz.reposition (dx_var*n_dx_dofs, dz_var*n_dx_dofs, n_dx_dofs, n_dz_dofs); Kdydx.reposition (dy_var*n_dy_dofs, dx_var*n_dy_dofs, n_dy_dofs, n_dx_dofs); Kdydy.reposition (dy_var*n_dy_dofs, dy_var*n_dy_dofs, n_dy_dofs, n_dy_dofs); Kdydz.reposition (dy_var*n_dy_dofs, dz_var*n_dy_dofs, n_dy_dofs, n_dz_dofs); Kdzdx.reposition (dz_var*n_dy_dofs, dx_var*n_dz_dofs, n_dz_dofs, n_dx_dofs); Kdzdy.reposition (dz_var*n_dy_dofs, dy_var*n_dz_dofs, n_dz_dofs, n_dy_dofs); Kdzdz.reposition (dz_var*n_dy_dofs, dz_var*n_dz_dofs, n_dz_dofs, n_dz_dofs); Fdx.reposition (dx_var*n_dx_dofs, n_dx_dofs); Fdy.reposition (dy_var*n_dx_dofs, n_dy_dofs); Fdz.reposition (dz_var*n_dx_dofs, n_dz_dofs); // Now we will build the element matrix. for (uint qp=0; qp<qrule.n_points(); qp++) { for (uint i=0; i<n_dx_dofs; i++) { Fdx(i) += JxW[qp]*f_x*phi[i][qp]; for (uint j=0; j<n_dx_dofs; j++) { Kdxdx(i,j) += JxW[qp]*( mu*( 2.*dphi[i][qp](0)*dphi[j][qp](0) + dphi[i][qp](1)*dphi[j][qp](1) + dphi[i][qp](2)*dphi[j][qp](2) ) + lmbda*dphi[i][qp](0)*dphi[j][qp](0) ); } for (uint j=0; j<n_dy_dofs; j++) { Kdxdy(i,j) += JxW[qp]*( mu*dphi[i][qp](1)*dphi[j][qp](0) + lmbda*dphi[i][qp](0)*dphi[j][qp](1) ); } for (uint j=0; j<n_dz_dofs; j++) { Kdxdz(i,j) += JxW[qp]*( mu*dphi[i][qp](2)*dphi[j][qp](0) + lmbda*dphi[i][qp](0)*dphi[j][qp](2) ); } } for (uint i=0; i<n_dy_dofs; i++) { Fdy(i) += JxW[qp]*f_y*phi[i][qp]; for (uint j=0; j<n_dx_dofs; j++) { Kdydx(i,j) += JxW[qp]*( mu*dphi[i][qp](0)*dphi[j][qp](1) + lmbda*dphi[i][qp](1)*dphi[j][qp](0) ); } for (uint j=0; j<n_dy_dofs; j++) { Kdydy(i,j) += JxW[qp]*( mu*( dphi[i][qp](0)*dphi[j][qp](0) + 2.*dphi[i][qp](1)*dphi[j][qp](1) + dphi[i][qp](2)*dphi[j][qp](2) ) + lmbda*dphi[i][qp](1)*dphi[j][qp](1) ); } for (uint j=0; j<n_dz_dofs; j++) { Kdydz(i,j) += JxW[qp]*( mu*dphi[i][qp](2)*dphi[j][qp](1) + lmbda*dphi[i][qp](1)*dphi[j][qp](2) ); } } for (uint i=0; i<n_dz_dofs; i++) { Fdz(i) += JxW[qp]*f_z*phi[i][qp]; for (uint j=0; j<n_dx_dofs; j++) { Kdzdx(i,j) += JxW[qp]*( mu*dphi[i][qp](0)*dphi[j][qp](2) + lmbda*dphi[i][qp](2)*dphi[j][qp](0) ); } for (uint j=0; j<n_dy_dofs; j++) { Kdzdy(i,j) += JxW[qp]*( mu*dphi[i][qp](1)*dphi[j][qp](2) + lmbda*dphi[i][qp](2)*dphi[j][qp](1) ); } for (uint j=0; j<n_dz_dofs; j++) { Kdzdz(i,j) += JxW[qp]*( mu*( dphi[i][qp](0)*dphi[j][qp](0) + dphi[i][qp](1)*dphi[j][qp](1) + 2.*dphi[i][qp](2)*dphi[j][qp](2) ) + lmbda*dphi[i][qp](2)*dphi[j][qp](2) ); } } } // end of the quadrature point qp-loop // 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 \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 element loop // system.matrix->close(); // system.matrix->print(); // system.rhs->close(); // system.rhs->print(); // That's it. return; }
// 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 & libmesh_dbg_var(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 K and the bending // rigidity 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. std::unique_ptr<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; std::unique_ptr<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 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 = 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. for (const auto & elem : mesh.active_local_element_ptr_range()) { // 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 DenseSubMatrix.reposition () member takes the // (row_offset, column_offset, row_size, column_size). // // Similarly, the 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.norm(); // 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 // covariant 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 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 NumericMatrix::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); } // 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. for (const auto & elem : mesh.active_local_element_ptr_range()) { // 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 (auto s : elem->side_index_range()) { const Tri3Subdivision * nb_elem = static_cast<const Tri3Subdivision *> (elem->neighbor_ptr(s)); if (nb_elem == libmesh_nullptr || nb_elem->is_ghost()) continue; /* * Determine the four nodes involved in the boundary * condition treatment of this side. The MeshTools::Subdiv * namespace provides lookup tables next and prev * for an efficient determination of the next and previous * nodes of an element, respectively. * * n4 * / \ * / gh \ * n2 ---- n3 * \ nb / * \ / * n1 */ const Node * nodes [4]; // n1, n2, n3, n4 nodes[1] = gh_elem->node_ptr(s); // n2 nodes[2] = gh_elem->node_ptr(MeshTools::Subdivision::next[s]); // n3 nodes[3] = gh_elem->node_ptr(MeshTools::Subdivision::prev[s]); // n4 // The node in the interior of the domain, n1, is the // hardest to find. Walk along the edges of element nb until // we have identified it. unsigned int n_int = 0; nodes[0] = nb_elem->node_ptr(0); while (nodes[0]->id() == nodes[1]->id() || nodes[0]->id() == nodes[2]->id()) nodes[0] = nb_elem->node_ptr(++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 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 }
void assemble(EquationSystems& es, const std::string& libmesh_dbg_var(system_name)) { libmesh_assert_equal_to (system_name, "primary"); const MeshBase& mesh = es.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); // Also use a 3x3x3 quadrature rule (3D). Then tell the FE // about the geometry of the problem and the quadrature rule FEType fe_type (FIRST); 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); LinearImplicitSystem& system = es.get_system<LinearImplicitSystem>("primary"); // These are references to cell-specific data const std::vector<Real>& JxW_face = fe_face->get_JxW(); const std::vector<Real>& JxW = fe->get_JxW(); const std::vector<Point>& q_point = fe->get_xyz(); const std::vector<std::vector<Real> >& phi = fe->get_phi(); const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); std::vector<unsigned int> dof_indices_U; std::vector<unsigned int> dof_indices_V; const DofMap& dof_map = system.get_dof_map(); DenseMatrix<Number> Kuu; DenseMatrix<Number> Kvv; DenseVector<Number> Fu; DenseVector<Number> Fv; Real vol=0., area=0.; 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; // recompute the element-specific data for the current element fe->reinit (elem); //fe->print_info(); dof_map.dof_indices(elem, dof_indices_U, 0); dof_map.dof_indices(elem, dof_indices_V, 1); // zero the element matrix and vector Kuu.resize (phi.size(), phi.size()); Kvv.resize (phi.size(), phi.size()); Fu.resize (phi.size()); Fv.resize (phi.size()); // standard stuff... like in code 1. for (unsigned int gp=0; gp<qrule.n_points(); gp++) { for (unsigned int i=0; i<phi.size(); ++i) { // this is tricky. ig is the _global_ dof index corresponding // to the _global_ vertex number elem->node(i). Note that // in general these numbers will not be the same (except for // the case of one unknown per node on one subdomain) so // we need to go through the dof_map const Real f = q_point[gp]*q_point[gp]; // const Real f = (q_point[gp](0) + // q_point[gp](1) + // q_point[gp](2)); // add jac*weight*f*phi to the RHS in position ig Fu(i) += JxW[gp]*f*phi[i][gp]; Fv(i) += JxW[gp]*f*phi[i][gp]; for (unsigned int j=0; j<phi.size(); ++j) { Kuu(i,j) += JxW[gp]*((phi[i][gp])*(phi[j][gp])); Kvv(i,j) += JxW[gp]*((phi[i][gp])*(phi[j][gp]) + 1.*((dphi[i][gp])*(dphi[j][gp]))); }; }; vol += JxW[gp]; }; // You can't compute "area" (perimeter) if you are in 2D if (dim == 3) { for (unsigned int side=0; side<elem->n_sides(); side++) if (elem->neighbor(side) == NULL) { fe_face->reinit (elem, side); //fe_face->print_info(); for (unsigned int gp=0; gp<JxW_face.size(); gp++) area += JxW_face[gp]; } } // Constrain the DOF indices. dof_map.constrain_element_matrix_and_vector(Kuu, Fu, dof_indices_U); dof_map.constrain_element_matrix_and_vector(Kvv, Fv, dof_indices_V); system.rhs->add_vector(Fu, dof_indices_U); system.rhs->add_vector(Fv, dof_indices_V); system.matrix->add_matrix(Kuu, dof_indices_U); system.matrix->add_matrix(Kvv, dof_indices_V); } libMesh::out << "Vol=" << vol << std::endl; if (dim == 3) libMesh::out << "Area=" << area << std::endl; }
// This function assembles the system matrix and right-hand-side // for the discrete form of 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"); #ifdef LIBMESH_ENABLE_INFINITE_ELEMENTS // Get a constant reference to the mesh object. const MeshBase & mesh = es.get_mesh(); // Get a reference to the system we are solving. LinearImplicitSystem & system = es.get_system<LinearImplicitSystem>("Wave"); // 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(); // The dimension that we are running. const unsigned int dim = mesh.mesh_dimension(); // Copy the speed of sound to a local variable. const Real speed = es.parameters.get<Real>("speed"); // 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); // 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>. Check ex5 for details. UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); // Do the same for an infinite element. UniquePtr<FEBase> inf_fe (FEBase::build_InfFE(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); // Due to its internal structure, the infinite element handles // quadrature rules differently. It takes the quadrature // rule which has been initialized for the FE object, but // creates suitable quadrature rules by @e itself. The user // need not worry about this. inf_fe->attach_quadrature_rule (&qrule); // Define data structures to contain the element matrix // and right-hand-side vector contribution. Following // basic finite element terminology we will denote these // "Ke", "Ce", "Me", and "Fe" for the stiffness, damping // and mass matrices, and the load vector. Note that in // Acoustics, these descriptors though do @e not match the // true physical meaning of the projectors. The final // overall system, however, resembles the conventional // notation again. DenseMatrix<Number> Ke; DenseMatrix<Number> Ce; DenseMatrix<Number> 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); // The mesh contains both finite and infinite elements. These // elements are handled through different classes, namely // \p FE and \p InfFE, respectively. However, since both // are derived from \p FEBase, they share the same interface, // and overall burden of coding is @e greatly reduced through // using a pointer, which is adjusted appropriately to the // current element type. FEBase * cfe = libmesh_nullptr; // This here is almost the only place where we need to // distinguish between finite and infinite elements. // For faster computation, however, different approaches // may be feasible. // // Up to now, we do not know what kind of element we // have. Aske the element of what type it is: if (elem->infinite()) { // We have an infinite element. Let \p cfe point // to our \p InfFE object. This is handled through // an UniquePtr. Through the \p UniquePtr::get() we "borrow" // the pointer, while the \p UniquePtr \p inf_fe is // still in charge of memory management. cfe = inf_fe.get(); } else { // This is a conventional finite element. Let \p fe handle it. cfe = fe.get(); // Boundary conditions. // Here we just zero the rhs-vector. For natural boundary // conditions check e.g. previous examples. { // Zero the RHS for this element. Fe.resize (dof_indices.size()); system.rhs->add_vector (Fe, dof_indices); } // end boundary condition section } // else if (elem->infinite()) // This is slightly different from the Poisson solver: // Since the finite element object may change, we have to // initialize the constant references to the data fields // each time again, when a new element is processed. // // The element Jacobian * quadrature weight at each integration point. const std::vector<Real> & JxW = cfe->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> > & phi = cfe->get_phi(); // The element shape function gradients evaluated at the quadrature // points. const std::vector<std::vector<RealGradient> > & dphi = cfe->get_dphi(); // The infinite elements need more data fields than conventional FE. // These are the gradients of the phase term \p dphase, an additional // radial weight for the test functions \p Sobolev_weight, and its // gradient. // // Note that these data fields are also initialized appropriately by // the \p FE method, so that the weak form (below) is valid for @e both // finite and infinite elements. const std::vector<RealGradient> & dphase = cfe->get_dphase(); const std::vector<Real> & weight = cfe->get_Sobolev_weight(); const std::vector<RealGradient> & dweight = cfe->get_Sobolev_dweight(); // Now this is all independent of whether we use an \p FE // or an \p InfFE. Nice, hm? ;-) // // Compute the element-specific data, as described // in previous examples. cfe->reinit (elem); // Zero the element matrices. Boundary conditions were already // processed in the \p FE-only section, see above. Ke.resize (dof_indices.size(), dof_indices.size()); Ce.resize (dof_indices.size(), dof_indices.size()); Me.resize (dof_indices.size(), dof_indices.size()); // The total number of quadrature points for infinite elements // @e has to be determined in a different way, compared to // conventional finite elements. This type of access is also // valid for finite elements, so this can safely be used // anytime, instead of asking the quadrature rule, as // seen in previous examples. unsigned int max_qp = cfe->n_quadrature_points(); // Loop over the quadrature points. for (unsigned int qp=0; qp<max_qp; qp++) { // Similar to the modified access to the number of quadrature // points, the number of shape functions may also be obtained // in a different manner. This offers the great advantage // of being valid for both finite and infinite elements. const unsigned int n_sf = cfe->n_shape_functions(); // Now we will build the element matrices. Since the infinite // elements are based on a Petrov-Galerkin scheme, the // resulting system matrices are non-symmetric. The additional // weight, described before, is part of the trial space. // // For the finite elements, though, these matrices are symmetric // just as we know them, since the additional fields \p dphase, // \p weight, and \p dweight are initialized appropriately. // // test functions: weight[qp]*phi[i][qp] // trial functions: phi[j][qp] // phase term: phase[qp] // // derivatives are similar, but note that these are of type // Point, not of type Real. for (unsigned int i=0; i<n_sf; i++) for (unsigned int j=0; j<n_sf; j++) { // (ndt*Ht + nHt*d) * nH Ke(i,j) += ( (dweight[qp] * phi[i][qp] // Point * Real = Point + // + dphi[i][qp] * weight[qp] // Point * Real = Point ) * dphi[j][qp] ) * JxW[qp]; // (d*Ht*nmut*nH - ndt*nmu*Ht*H - d*nHt*nmu*H) Ce(i,j) += ( (dphase[qp] * dphi[j][qp]) // (Point * Point) = Real * weight[qp] * phi[i][qp] // * Real * Real = Real - // - (dweight[qp] * dphase[qp]) // (Point * Point) = Real * phi[i][qp] * phi[j][qp] // * Real * Real = Real - // - (dphi[i][qp] * dphase[qp]) // (Point * Point) = Real * weight[qp] * phi[j][qp] // * Real * Real = Real ) * JxW[qp]; // (d*Ht*H * (1 - nmut*nmu)) Me(i,j) += ( (1. - (dphase[qp] * dphase[qp])) // (Real - (Point * Point)) = Real * phi[i][qp] * phi[j][qp] * weight[qp] // * Real * Real * Real = Real ) * JxW[qp]; } // end of the matrix summation loop } // end of quadrature point loop // The element matrices are now built for this element. // Collect them in Ke, and then add them to the global matrix. // The \p SparseMatrix::add_matrix() member does this for us. Ke.add(1./speed , Ce); Ke.add(1./(speed*speed), Me); // 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(Ke, dof_indices); system.matrix->add_matrix (Ke, dof_indices); } // end of element loop // Note that we have not applied any boundary conditions so far. // Here we apply a unit load at the node located at (0,0,0). { // Iterate over local nodes MeshBase::const_node_iterator nd = mesh.local_nodes_begin(); const MeshBase::const_node_iterator nd_end = mesh.local_nodes_end(); for (; nd != nd_end; ++nd) { // Get a reference to the current node. const Node & node = **nd; // Check the location of the current node. if (std::abs(node(0)) < TOLERANCE && std::abs(node(1)) < TOLERANCE && std::abs(node(2)) < TOLERANCE) { // The global number of the respective degree of freedom. unsigned int dn = node.dof_number(0,0,0); system.rhs->add (dn, 1.); } } } #else // dummy assert libmesh_assert_not_equal_to (es.get_mesh().mesh_dimension(), 1); #endif //ifdef LIBMESH_ENABLE_INFINITE_ELEMENTS }
// 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! }
void compute_stresses(EquationSystems & es) { LOG_SCOPE("compute_stresses()", "main"); const MeshBase & mesh = es.get_mesh(); const unsigned int dim = mesh.mesh_dimension(); ElasticityRBConstruction & system = es.get_system<ElasticityRBConstruction>("RBElasticity"); unsigned int displacement_vars[3]; displacement_vars[0] = system.variable_number ("u"); displacement_vars[1] = system.variable_number ("v"); displacement_vars[2] = system.variable_number ("w"); 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); const std::vector<Real> & JxW = fe->get_JxW(); const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi(); // Also, get a reference to the ExplicitSystem ExplicitSystem & stress_system = es.get_system<ExplicitSystem>("StressSystem"); const DofMap & stress_dof_map = stress_system.get_dof_map(); unsigned int sigma_vars[3][3]; sigma_vars[0][0] = stress_system.variable_number ("sigma_00"); sigma_vars[0][1] = stress_system.variable_number ("sigma_01"); sigma_vars[0][2] = stress_system.variable_number ("sigma_02"); sigma_vars[1][0] = stress_system.variable_number ("sigma_10"); sigma_vars[1][1] = stress_system.variable_number ("sigma_11"); sigma_vars[1][2] = stress_system.variable_number ("sigma_12"); sigma_vars[2][0] = stress_system.variable_number ("sigma_20"); sigma_vars[2][1] = stress_system.variable_number ("sigma_21"); sigma_vars[2][2] = stress_system.variable_number ("sigma_22"); unsigned int vonMises_var = stress_system.variable_number ("vonMises"); // Storage for the stress dof indices on each element std::vector<std::vector<dof_id_type> > dof_indices_var(system.n_vars()); std::vector<dof_id_type> stress_dof_indices_var; // To store the stress tensor on each element DenseMatrix<Number> elem_sigma; 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; for (unsigned int var=0; var<3; var++) dof_map.dof_indices (elem, dof_indices_var[var], displacement_vars[var]); fe->reinit (elem); elem_sigma.resize(3, 3); for (unsigned int qp=0; qp<qrule.n_points(); qp++) for (unsigned int C_i=0; C_i<3; C_i++) for (unsigned int C_j=0; C_j<3; C_j++) for (unsigned int C_k=0; C_k<3; C_k++) { const unsigned int n_var_dofs = dof_indices_var[C_k].size(); // Get the gradient at this quadrature point Gradient displacement_gradient; for (unsigned int l=0; l<n_var_dofs; l++) displacement_gradient.add_scaled(dphi[l][qp], system.current_solution(dof_indices_var[C_k][l])); for (unsigned int C_l=0; C_l<3; C_l++) elem_sigma(C_i,C_j) += JxW[qp] * (elasticity_tensor(C_i, C_j, C_k, C_l) * displacement_gradient(C_l)); } // Get the average stresses by dividing by the element volume elem_sigma.scale(1./elem->volume()); // load elem_sigma data into stress_system for (unsigned int i=0; i<3; i++) for (unsigned int j=0; j<3; j++) { stress_dof_map.dof_indices (elem, stress_dof_indices_var, sigma_vars[i][j]); // We are using CONSTANT MONOMIAL basis functions, hence we only need to get // one dof index per variable dof_id_type dof_index = stress_dof_indices_var[0]; if ((stress_system.solution->first_local_index() <= dof_index) && (dof_index < stress_system.solution->last_local_index())) { stress_system.solution->set(dof_index, elem_sigma(i,j)); } } // Also, the von Mises stress Number vonMises_value = std::sqrt(0.5*(pow(elem_sigma(0,0) - elem_sigma(1,1),2.) + pow(elem_sigma(1,1) - elem_sigma(2,2),2.) + pow(elem_sigma(2,2) - elem_sigma(0,0),2.) + 6.*(pow(elem_sigma(0,1),2.) + pow(elem_sigma(1,2),2.) + pow(elem_sigma(2,0),2.)) )); stress_dof_map.dof_indices (elem, stress_dof_indices_var, vonMises_var); dof_id_type dof_index = stress_dof_indices_var[0]; if ((stress_system.solution->first_local_index() <= dof_index) && (dof_index < stress_system.solution->last_local_index())) { stress_system.solution->set(dof_index, vonMises_value); } } // Should call close and update when we set vector entries directly stress_system.solution->close(); stress_system.update(); }
void get_dirichlet_dofs(EquationSystems& es, const std::string& system_name, std::set<unsigned int>& dirichlet_dof_ids) { #ifdef LIBMESH_HAVE_SLEPC dirichlet_dof_ids.clear(); // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "Eigensystem"); // 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); const DofMap& dof_map = eigen_system.get_dof_map(); // 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<unsigned int> 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 \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); { // All boundary dofs are Dirichlet dofs in this case for (unsigned int s=0; s<elem->n_sides(); s++) if (elem->neighbor(s) == NULL) { std::vector<unsigned int> side_dofs; FEInterface::dofs_on_side(elem, dim, fe_type, s, side_dofs); for(unsigned int ii=0; ii<side_dofs.size(); ii++) dirichlet_dof_ids.insert(dof_indices[side_dofs[ii]]); } } } // end of element loop #endif // LIBMESH_HAVE_SLEPC /** * All done! */ return; }
DTKAdapter::DTKAdapter(Teuchos::RCP<const Teuchos::Comm<int> > in_comm, EquationSystems & in_es): comm(in_comm), es(in_es), mesh(in_es.get_mesh()), dim(mesh.mesh_dimension()) { std::set<unsigned int> semi_local_nodes; get_semi_local_nodes(semi_local_nodes); num_local_nodes = semi_local_nodes.size(); vertices.resize(num_local_nodes); Teuchos::ArrayRCP<double> coordinates(num_local_nodes * dim); // Fill in the vertices and coordinates { unsigned int i = 0; for(std::set<unsigned int>::iterator it = semi_local_nodes.begin(); it != semi_local_nodes.end(); ++it) { const Node & node = mesh.node(*it); vertices[i] = node.id(); for(unsigned int j=0; j<dim; j++) coordinates[(j*num_local_nodes) + i] = node(j); i++; } } // Currently assuming all elements are the same! DataTransferKit::DTK_ElementTopology element_topology = get_element_topology(mesh.elem(0)); unsigned int n_nodes_per_elem = mesh.elem(0)->n_nodes(); unsigned int n_local_elem = mesh.n_local_elem(); Teuchos::ArrayRCP<int> elements(n_local_elem); Teuchos::ArrayRCP<int> connectivity(n_nodes_per_elem*n_local_elem); // Fill in the elements and connectivity { unsigned int i = 0; MeshBase::const_element_iterator end = mesh.local_elements_end(); for(MeshBase::const_element_iterator it = mesh.local_elements_begin(); it != end; ++it) { const Elem & elem = *(*it); elements[i] = elem.id(); for(unsigned int j=0; j<n_nodes_per_elem; j++) connectivity[(j*n_local_elem)+i] = elem.node(j); i++; } } Teuchos::ArrayRCP<int> permutation_list(n_nodes_per_elem); for (unsigned int i = 0; i < n_nodes_per_elem; ++i ) permutation_list[i] = i; /* if(this->processor_id() == 1) sleep(1); libMesh::out<<"n_nodes_per_elem: "<<n_nodes_per_elem<<std::endl; libMesh::out<<"Dim: "<<dim<<std::endl; libMesh::err<<"Vertices size: "<<vertices.size()<<std::endl; { libMesh::err<<this->processor_id()<<" Vertices: "; for(unsigned int i=0; i<vertices.size(); i++) libMesh::err<<vertices[i]<<" "; libMesh::err<<std::endl; } libMesh::err<<"Coordinates size: "<<coordinates.size()<<std::endl; { libMesh::err<<this->processor_id()<<" Coordinates: "; for(unsigned int i=0; i<coordinates.size(); i++) libMesh::err<<coordinates[i]<<" "; libMesh::err<<std::endl; } libMesh::err<<"Connectivity size: "<<connectivity.size()<<std::endl; { libMesh::err<<this->processor_id()<<" Connectivity: "; for(unsigned int i=0; i<connectivity.size(); i++) libMesh::err<<connectivity[i]<<" "; libMesh::err<<std::endl; } libMesh::err<<"Permutation_List size: "<<permutation_list.size()<<std::endl; { libMesh::err<<this->processor_id()<<" Permutation_List: "; for(unsigned int i=0; i<permutation_list.size(); i++) libMesh::err<<permutation_list[i]<<" "; libMesh::err<<std::endl; } */ Teuchos::RCP<MeshContainerType> mesh_container = Teuchos::rcp( new MeshContainerType(dim, vertices, coordinates, element_topology, n_nodes_per_elem, elements, connectivity, permutation_list) ); // We only have 1 element topology in this grid so we make just one mesh block Teuchos::ArrayRCP<Teuchos::RCP<MeshContainerType> > mesh_blocks(1); mesh_blocks[0] = mesh_container; // Create the MeshManager mesh_manager = Teuchos::rcp(new DataTransferKit::MeshManager<MeshContainerType>(mesh_blocks, comm, dim) ); // Pack the coordinates into a field, this will be the positions we'll ask for other systems fields at target_coords = Teuchos::rcp(new DataTransferKit::FieldManager<MeshContainerType>(mesh_container, comm)); }
// Define the matrix assembly function for the 1D PDE we are solving void assemble_1D(EquationSystems& es, const std::string& system_name) { #ifdef LIBMESH_ENABLE_AMR // It is a good idea to check we are solving the correct system libmesh_assert_equal_to (system_name, "1D"); // Get a reference to the mesh object const MeshBase& mesh = es.get_mesh(); // The dimension we are using, i.e. dim==1 const unsigned int dim = mesh.mesh_dimension(); // Get a reference to the system we are solving LinearImplicitSystem& system = es.get_system<LinearImplicitSystem>("1D"); // Get 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. DofMap's are discussed in more detail 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. The build // function dynamically allocates memory so we use an UniquePtr in this case. // An UniquePtr is a pointer that cleans up after itself. See examples 3 and 4 // for more details on UniquePtr. UniquePtr<FEBase> fe(FEBase::build(dim, fe_type)); // Tell the finite element object to use fifth order Gaussian quadrature QGauss qrule(dim,FIFTH); fe->attach_quadrature_rule(&qrule); // 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 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(); // Declare a dense matrix and dense vector to hold the element matrix // and right-hand-side contribution 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; // We now loop over all the active elements in the mesh in order to calculate // the matrix and right-hand-side contribution from each element. Use a // const_element_iterator to loop over the elements. We make // el_end const as it is used only for the stopping condition of the loop. MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator el_end = mesh.active_local_elements_end(); // Note that ++el is preferred to el++ when using loops with iterators for( ; el != el_end; ++el) { // It is convenient to store a pointer to the current element 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); // Store the number of local degrees of freedom contained in this element const int n_dofs = dof_indices.size(); // We resize and zero out Ke and Fe (resize() also clears the matrix and // vector). In this example, all elements in the mesh are EDGE3's, so // Ke will always be 3x3, and Fe will always be 3x1. If the mesh contained // different element types, then the size of Ke and Fe would change. Ke.resize(n_dofs, n_dofs); Fe.resize(n_dofs); // Now loop over quadrature points to handle numerical integration for(unsigned int qp=0; qp<qrule.n_points(); qp++) { // Now build the element matrix and right-hand-side using loops to // integrate the test functions (i) against the trial functions (j). 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]*(1.e-3*dphi[i][qp]*dphi[j][qp] + phi[i][qp]*phi[j][qp]); } } } // At this point we have completed the matrix and RHS summation. The // final step is to apply boundary conditions, which in this case are // simple Dirichlet conditions with u(0) = u(1) = 0. // Define the penalty parameter used to enforce the BC's double penalty = 1.e10; // Loop over the sides of this element. For a 1D element, the "sides" // are defined as the nodes on each edge of the element, i.e. 1D elements // have 2 sides. for(unsigned int s=0; s<elem->n_sides(); s++) { // If this element has a NULL neighbor, then it is on the edge of the // mesh and we need to enforce a boundary condition using the penalty // method. if(elem->neighbor(s) == NULL) { Ke(s,s) += penalty; Fe(s) += 0*penalty; } } // This is a function call that is necessary when using adaptive // mesh refinement. See Adaptivity Example 2 for more details. dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); // Add Ke and Fe to the global matrix and right-hand-side. system.matrix->add_matrix(Ke, dof_indices); system.rhs->add_vector(Fe, dof_indices); } #endif // #ifdef LIBMESH_ENABLE_AMR }
// 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 (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 an AutoPtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. Example 4 // describes some advantages of AutoPtr's in the context of // quadrature rules. AutoPtr<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. AutoPtr<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<unsigned int> 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 \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(); // 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(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(); // 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! }
// The matrix assembly function to be called at each time step to // prepare for the linear solve. void assemble_stokes (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, "Navier-Stokes"); // 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 Stokes system object. TransientLinearImplicitSystem & navier_stokes_system = es.get_system<TransientLinearImplicitSystem> ("Navier-Stokes"); // Numeric ids corresponding to each variable in the system const unsigned int u_var = navier_stokes_system.variable_number ("u"); const unsigned int v_var = navier_stokes_system.variable_number ("v"); const unsigned int p_var = navier_stokes_system.variable_number ("p"); const unsigned int alpha_var = navier_stokes_system.variable_number ("alpha"); // Get the Finite Element type for "u". Note this will be // the same as the type for "v". FEType fe_vel_type = navier_stokes_system.variable_type(u_var); // Get the Finite Element type for "p". FEType fe_pres_type = navier_stokes_system.variable_type(p_var); // Build a Finite Element object of the specified type for // the velocity variables. UniquePtr<FEBase> fe_vel (FEBase::build(dim, fe_vel_type)); // Build a Finite Element object of the specified type for // the pressure variables. UniquePtr<FEBase> fe_pres (FEBase::build(dim, fe_pres_type)); // A Gauss quadrature rule for numerical integration. // Let the FEType object decide what order rule is appropriate. QGauss qrule (dim, fe_vel_type.default_quadrature_order()); // Tell the finite element objects to use our quadrature rule. fe_vel->attach_quadrature_rule (&qrule); fe_pres->attach_quadrature_rule (&qrule); // 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_vel->get_JxW(); // The element shape functions evaluated at the quadrature points. const std::vector<std::vector<Real> > & phi = fe_vel->get_phi(); // The element shape function gradients for the velocity // variables evaluated at the quadrature points. const std::vector<std::vector<RealGradient> > & dphi = fe_vel->get_dphi(); // The element shape functions for the pressure variable // evaluated at the quadrature points. const std::vector<std::vector<Real> > & psi = fe_pres->get_phi(); // The value of the linear shape function gradients at the quadrature points // const std::vector<std::vector<RealGradient> > & dpsi = fe_pres->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. We will talk more about the DofMap // in future examples. const DofMap & dof_map = navier_stokes_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; DenseSubMatrix<Number> Kuu(Ke), Kuv(Ke), Kup(Ke), Kvu(Ke), Kvv(Ke), Kvp(Ke), Kpu(Ke), Kpv(Ke), Kpp(Ke); DenseSubMatrix<Number> Kalpha_p(Ke), Kp_alpha(Ke); DenseSubVector<Number> Fu(Fe), Fv(Fe), Fp(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_p; std::vector<dof_id_type> dof_indices_alpha; // Find out what the timestep size parameter is from the system, and // the value of theta for the theta method. We use implicit Euler (theta=1) // for this simulation even though it is only first-order accurate in time. // The reason for this decision is that the second-order Crank-Nicolson // method is notoriously oscillatory for problems with discontinuous // initial data such as the lid-driven cavity. Therefore, // we sacrifice accuracy in time for stability, but since the solution // reaches steady state relatively quickly we can afford to take small // timesteps. If you monitor the initial nonlinear residual for this // simulation, you should see that it is monotonically decreasing in time. const Real dt = es.parameters.get<Real>("dt"); // const Real time = es.parameters.get<Real>("time"); const Real theta = 1.; // 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); 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_p, p_var); dof_map.dof_indices (elem, dof_indices_alpha, alpha_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(); const unsigned int n_p_dofs = dof_indices_p.size(); // 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_vel->reinit (elem); fe_pres->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 (n_dofs, n_dofs); Fe.resize (n_dofs); // Reposition the submatrices... The idea is this: // // - - - - // | Kuu Kuv Kup | | Fu | // Ke = | Kvu Kvv Kvp |; Fe = | Fv | // | Kpu Kpv Kpp | | Fp | // - - - - // // The DenseSubMatrix.repostition () member takes the // (row_offset, column_offset, row_size, column_size). // // Similarly, the 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); Kup.reposition (u_var*n_u_dofs, p_var*n_u_dofs, n_u_dofs, n_p_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); Kvp.reposition (v_var*n_v_dofs, p_var*n_v_dofs, n_v_dofs, n_p_dofs); Kpu.reposition (p_var*n_u_dofs, u_var*n_u_dofs, n_p_dofs, n_u_dofs); Kpv.reposition (p_var*n_u_dofs, v_var*n_u_dofs, n_p_dofs, n_v_dofs); Kpp.reposition (p_var*n_u_dofs, p_var*n_u_dofs, n_p_dofs, n_p_dofs); // Also, add a row and a column to constrain the pressure Kp_alpha.reposition (p_var*n_u_dofs, p_var*n_u_dofs+n_p_dofs, n_p_dofs, 1); Kalpha_p.reposition (p_var*n_u_dofs+n_p_dofs, p_var*n_u_dofs, 1, n_p_dofs); Fu.reposition (u_var*n_u_dofs, n_u_dofs); Fv.reposition (v_var*n_u_dofs, n_v_dofs); Fp.reposition (p_var*n_u_dofs, n_p_dofs); // 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 must 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 solution & its gradient at the previous timestep. Number u = 0., u_old = 0.; Number v = 0., v_old = 0.; Number p_old = 0.; Gradient grad_u, grad_u_old; Gradient grad_v, grad_v_old; // Compute the velocity & its gradient from the previous timestep // and the old Newton iterate. for (unsigned int l=0; l<n_u_dofs; l++) { // From the old timestep: u_old += phi[l][qp]*navier_stokes_system.old_solution (dof_indices_u[l]); v_old += phi[l][qp]*navier_stokes_system.old_solution (dof_indices_v[l]); grad_u_old.add_scaled (dphi[l][qp], navier_stokes_system.old_solution (dof_indices_u[l])); grad_v_old.add_scaled (dphi[l][qp], navier_stokes_system.old_solution (dof_indices_v[l])); // From the previous Newton iterate: u += phi[l][qp]*navier_stokes_system.current_solution (dof_indices_u[l]); v += phi[l][qp]*navier_stokes_system.current_solution (dof_indices_v[l]); grad_u.add_scaled (dphi[l][qp], navier_stokes_system.current_solution (dof_indices_u[l])); grad_v.add_scaled (dphi[l][qp], navier_stokes_system.current_solution (dof_indices_v[l])); } // Compute the old pressure value at this quadrature point. for (unsigned int l=0; l<n_p_dofs; l++) p_old += psi[l][qp]*navier_stokes_system.old_solution (dof_indices_p[l]); // Definitions for convenience. It is sometimes simpler to do a // dot product if you have the full vector at your disposal. const NumberVectorValue U_old (u_old, v_old); const NumberVectorValue U (u, v); const Number u_x = grad_u(0); const Number u_y = grad_u(1); const Number v_x = grad_v(0); const Number v_y = grad_v(1); // First, an i-loop over the velocity degrees of freedom. // We know that n_u_dofs == n_v_dofs so we can compute contributions // for both at the same time. for (unsigned int i=0; i<n_u_dofs; i++) { Fu(i) += JxW[qp]*(u_old*phi[i][qp] - // mass-matrix term (1.-theta)*dt*(U_old*grad_u_old)*phi[i][qp] + // convection term (1.-theta)*dt*p_old*dphi[i][qp](0) - // pressure term on rhs (1.-theta)*dt*(grad_u_old*dphi[i][qp]) + // diffusion term on rhs theta*dt*(U*grad_u)*phi[i][qp]); // Newton term Fv(i) += JxW[qp]*(v_old*phi[i][qp] - // mass-matrix term (1.-theta)*dt*(U_old*grad_v_old)*phi[i][qp] + // convection term (1.-theta)*dt*p_old*dphi[i][qp](1) - // pressure term on rhs (1.-theta)*dt*(grad_v_old*dphi[i][qp]) + // diffusion term on rhs theta*dt*(U*grad_v)*phi[i][qp]); // Newton term // Note that the Fp block is identically zero unless we are using // some kind of artificial compressibility scheme... // Matrix contributions for the uu and vv couplings. for (unsigned int j=0; j<n_u_dofs; j++) { Kuu(i,j) += JxW[qp]*(phi[i][qp]*phi[j][qp] + // mass matrix term theta*dt*(dphi[i][qp]*dphi[j][qp]) + // diffusion term theta*dt*(U*dphi[j][qp])*phi[i][qp] + // convection term theta*dt*u_x*phi[i][qp]*phi[j][qp]); // Newton term Kuv(i,j) += JxW[qp]*theta*dt*u_y*phi[i][qp]*phi[j][qp]; // Newton term Kvv(i,j) += JxW[qp]*(phi[i][qp]*phi[j][qp] + // mass matrix term theta*dt*(dphi[i][qp]*dphi[j][qp]) + // diffusion term theta*dt*(U*dphi[j][qp])*phi[i][qp] + // convection term theta*dt*v_y*phi[i][qp]*phi[j][qp]); // Newton term Kvu(i,j) += JxW[qp]*theta*dt*v_x*phi[i][qp]*phi[j][qp]; // Newton term } // Matrix contributions for the up and vp couplings. for (unsigned int j=0; j<n_p_dofs; j++) { Kup(i,j) += JxW[qp]*(-theta*dt*psi[j][qp]*dphi[i][qp](0)); Kvp(i,j) += JxW[qp]*(-theta*dt*psi[j][qp]*dphi[i][qp](1)); } } // Now an i-loop over the pressure degrees of freedom. This code computes // the matrix entries due to the continuity equation. Note: To maintain a // symmetric matrix, we may (or may not) multiply the continuity equation by // negative one. Here we do not. for (unsigned int i=0; i<n_p_dofs; i++) { Kp_alpha(i,0) += JxW[qp]*psi[i][qp]; Kalpha_p(0,i) += JxW[qp]*psi[i][qp]; for (unsigned int j=0; j<n_u_dofs; j++) { Kpu(i,j) += JxW[qp]*psi[i][qp]*dphi[j][qp](0); Kpv(i,j) += JxW[qp]*psi[i][qp]*dphi[j][qp](1); } } } // end of the quadrature point qp-loop // 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 penalty method used here // is equivalent (for Lagrange basis functions) to lumping // the matrix resulting from the L2 projection penalty // approach introduced in example 3. { // The penalty value. \f$ \frac{1}{\epsilon} \f$ 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) { UniquePtr<Elem> side (elem->build_side(s)); // Loop over the nodes on the side. for (unsigned int ns=0; ns<side->n_nodes(); ns++) { // Boundary ids are set internally by // build_square(). // 0=bottom // 1=right // 2=top // 3=left // Set u = 1 on the top boundary, 0 everywhere else const Real u_value = (mesh.get_boundary_info().has_boundary_id(elem, s, 2)) ? 1. : 0.; // Set v = 0 everywhere const Real v_value = 0.; // Find the node on the element matching this node on // the side. That defined where in the element matrix // the boundary condition will be applied. for (unsigned int n=0; n<elem->n_nodes(); n++) if (elem->node_id(n) == side->node_id(ns)) { // Matrix contribution. Kuu(n,n) += penalty; Kvv(n,n) += penalty; // Right-hand-side contribution. Fu(n) += penalty*u_value; Fv(n) += penalty*v_value; } } // end face node loop } // end if (elem->neighbor(side) == libmesh_nullptr) } // 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 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. navier_stokes_system.matrix->add_matrix (Ke, dof_indices); navier_stokes_system.rhs->add_vector (Fe, dof_indices); } // end of element loop // We can set the mean of the pressure by setting Falpha navier_stokes_system.rhs->add(navier_stokes_system.rhs->size()-1, 10.); }
// We now define the matrix assembly function for the // Laplace system. We need to first compute element volume // matrices, and then take into account the boundary // conditions and the flux integrals, which will be handled // via an interior penalty method. void assemble_ellipticdg(EquationSystems& es, const std::string& system_name) { std::cout<<" assembling elliptic dg system... "; std::cout.flush(); // It is a good idea to make sure we are assembling // the proper system. libmesh_assert_equal_to (system_name, "EllipticDG"); // 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 & ellipticdg_system = es.get_system<LinearImplicitSystem> ("EllipticDG"); // Get some parameters that we need during assembly const Real penalty = es.parameters.get<Real> ("penalty"); std::string refinement_type = es.parameters.get<std::string> ("refinement"); // 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 const DofMap & dof_map = ellipticdg_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 = ellipticdg_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 AutoPtr<FEBase>. This can be thought // of as a pointer that will clean up after itself. AutoPtr<FEBase> fe (FEBase::build(dim, fe_type)); AutoPtr<FEBase> fe_elem_face(FEBase::build(dim, fe_type)); AutoPtr<FEBase> fe_neighbor_face(FEBase::build(dim, fe_type)); // Quadrature rules for numerical integration. #ifdef QORDER QGauss qrule (dim, QORDER); #else QGauss qrule (dim, fe_type.default_quadrature_order()); #endif fe->attach_quadrature_rule (&qrule); #ifdef QORDER QGauss qface(dim-1, QORDER); #else QGauss qface(dim-1, fe_type.default_quadrature_order()); #endif // Tell the finite element object to use our quadrature rule. fe_elem_face->attach_quadrature_rule(&qface); fe_neighbor_face->attach_quadrature_rule(&qface); // Here we define some references to cell-specific data that // will be used to assemble the linear system. // Data for interior volume integrals const std::vector<Real>& JxW = fe->get_JxW(); const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); // Data for surface integrals on the element boundary const std::vector<std::vector<Real> >& phi_face = fe_elem_face->get_phi(); const std::vector<std::vector<RealGradient> >& dphi_face = fe_elem_face->get_dphi(); const std::vector<Real>& JxW_face = fe_elem_face->get_JxW(); const std::vector<Point>& qface_normals = fe_elem_face->get_normals(); const std::vector<Point>& qface_points = fe_elem_face->get_xyz(); // Data for surface integrals on the neighbor boundary const std::vector<std::vector<Real> >& phi_neighbor_face = fe_neighbor_face->get_phi(); const std::vector<std::vector<RealGradient> >& dphi_neighbor_face = fe_neighbor_face->get_dphi(); // Define data structures to contain the element interior 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; // Data structures to contain the element and neighbor boundary matrix // contribution. This matrices will do the coupling beetwen the dofs of // the element and those of his neighbors. // Ken: matrix coupling elem and neighbor dofs DenseMatrix<Number> Kne; DenseMatrix<Number> Ken; DenseMatrix<Number> Kee; DenseMatrix<Number> Knn; // 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 first the element interior matrix and right-hand-side contribution // and then the element and neighbors boundary matrix contributions. 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); const unsigned int n_dofs = dof_indices.size(); // 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. Ke.resize (n_dofs, n_dofs); Fe.resize (n_dofs); // Now we will build the element interior 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<n_dofs; i++) { for (unsigned int j=0; j<n_dofs; j++) { Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); } } } // Now we adress boundary conditions. // We consider Dirichlet bc imposed via the interior 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. for (unsigned int side=0; side<elem->n_sides(); side++) { if (elem->neighbor(side) == NULL) { // Pointer to the element face fe_elem_face->reinit(elem, side); AutoPtr<Elem> elem_side (elem->build_side(side)); // h elemet dimension to compute the interior penalty penalty parameter const unsigned int elem_b_order = static_cast<unsigned int> (fe_elem_face->get_order()); const double h_elem = elem->volume()/elem_side->volume() * 1./pow(elem_b_order, 2.); for (unsigned int qp=0; qp<qface.n_points(); qp++) { Number bc_value = exact_solution(qface_points[qp], es.parameters,"null","void"); for (unsigned int i=0; i<n_dofs; i++) { // Matrix contribution for (unsigned int j=0; j<n_dofs; j++) { Ke(i,j) += JxW_face[qp] * penalty/h_elem * phi_face[i][qp] * phi_face[j][qp]; // stability Ke(i,j) -= JxW_face[qp] * (phi_face[i][qp] * (dphi_face[j][qp]*qface_normals[qp]) + phi_face[j][qp] * (dphi_face[i][qp]*qface_normals[qp])); // consistency } // RHS contribution Fe(i) += JxW_face[qp] * bc_value * penalty/h_elem * phi_face[i][qp]; // stability Fe(i) -= JxW_face[qp] * dphi_face[i][qp] * (bc_value*qface_normals[qp]); // consistency } } } // If the element is not on a boundary of the domain // we loop over his neighbors to compute the element // and neighbor boundary matrix contributions else { // Store a pointer to the neighbor we are currently // working on. const Elem* neighbor = elem->neighbor(side); // Get the global id of the element and the neighbor const unsigned int elem_id = elem->id(); const unsigned int neighbor_id = neighbor->id(); // If the neighbor has the same h level and is active // perform integration only if our global id is bigger than our neighbor id. // We don't want to compute twice the same contributions. // If the neighbor has a different h level perform integration // only if the neighbor is at a lower level. if ((neighbor->active() && (neighbor->level() == elem->level()) && (elem_id < neighbor_id)) || (neighbor->level() < elem->level())) { // Pointer to the element side AutoPtr<Elem> elem_side (elem->build_side(side)); // h dimension to compute the interior penalty penalty parameter const unsigned int elem_b_order = static_cast<unsigned int>(fe_elem_face->get_order()); const unsigned int neighbor_b_order = static_cast<unsigned int>(fe_neighbor_face->get_order()); const double side_order = (elem_b_order + neighbor_b_order)/2.; const double h_elem = (elem->volume()/elem_side->volume()) * 1./pow(side_order,2.); // The quadrature point locations on the neighbor side std::vector<Point> qface_neighbor_point; // The quadrature point locations on the element side std::vector<Point > qface_point; // Reinitialize shape functions on the element side fe_elem_face->reinit(elem, side); // Get the physical locations of the element quadrature points qface_point = fe_elem_face->get_xyz(); // Find their locations on the neighbor unsigned int side_neighbor = neighbor->which_neighbor_am_i(elem); if (refinement_type == "p") fe_neighbor_face->side_map (neighbor, elem_side.get(), side_neighbor, qface.get_points(), qface_neighbor_point); else FEInterface::inverse_map (elem->dim(), fe->get_fe_type(), neighbor, qface_point, qface_neighbor_point); // Calculate the neighbor element shape functions at those locations fe_neighbor_face->reinit(neighbor, &qface_neighbor_point); // Get the degree of freedom indices for the // neighbor. These define where in the global // matrix this neighbor will contribute to. 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(); // Zero the element and neighbor side matrix before // summing them. We use the resize member here because // the number of degrees of freedom might have changed from // the last element or neighbor. // Note that Kne and Ken are not square matrices if neighbor // and element have a different p level 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); // Now we will build the element and neighbor // boundary matrices. This involves // a double loop to integrate the test funcions // (i) against the trial functions (j). for (unsigned int qp=0; qp<qface.n_points(); qp++) { // Kee Matrix. Integrate the element test function i // against the element test function j for (unsigned int i=0; i<n_dofs; i++) { for (unsigned int j=0; j<n_dofs; j++) { Kee(i,j) -= 0.5 * JxW_face[qp] * (phi_face[j][qp]*(qface_normals[qp]*dphi_face[i][qp]) + phi_face[i][qp]*(qface_normals[qp]*dphi_face[j][qp])); // consistency Kee(i,j) += JxW_face[qp] * penalty/h_elem * phi_face[j][qp]*phi_face[i][qp]; // stability } } // Knn Matrix. Integrate the neighbor test function i // against the neighbor test function j for (unsigned int i=0; i<n_neighbor_dofs; i++) { for (unsigned int j=0; j<n_neighbor_dofs; j++) { Knn(i,j) += 0.5 * JxW_face[qp] * (phi_neighbor_face[j][qp]*(qface_normals[qp]*dphi_neighbor_face[i][qp]) + phi_neighbor_face[i][qp]*(qface_normals[qp]*dphi_neighbor_face[j][qp])); // consistency Knn(i,j) += JxW_face[qp] * penalty/h_elem * phi_neighbor_face[j][qp]*phi_neighbor_face[i][qp]; // stability } } // Kne Matrix. Integrate the neighbor test function i // against the element test function j for (unsigned int i=0; i<n_neighbor_dofs; i++) { for (unsigned int j=0; j<n_dofs; j++) { Kne(i,j) += 0.5 * JxW_face[qp] * (phi_neighbor_face[i][qp]*(qface_normals[qp]*dphi_face[j][qp]) - phi_face[j][qp]*(qface_normals[qp]*dphi_neighbor_face[i][qp])); // consistency Kne(i,j) -= JxW_face[qp] * penalty/h_elem * phi_face[j][qp]*phi_neighbor_face[i][qp]; // stability } } // Ken Matrix. Integrate the element test function i // against the neighbor test function j for (unsigned int i=0; i<n_dofs; i++) { for (unsigned int j=0; j<n_neighbor_dofs; j++) { Ken(i,j) += 0.5 * JxW_face[qp] * (phi_neighbor_face[j][qp]*(qface_normals[qp]*dphi_face[i][qp]) - phi_face[i][qp]*(qface_normals[qp]*dphi_neighbor_face[j][qp])); // consistency Ken(i,j) -= JxW_face[qp] * penalty/h_elem * phi_face[i][qp]*phi_neighbor_face[j][qp]; // stability } } } // The element and neighbor boundary matrix are now built // for this side. Add them to the global matrix // The \p SparseMatrix::add_matrix() members do this for us. ellipticdg_system.matrix->add_matrix(Kne,neighbor_dof_indices,dof_indices); ellipticdg_system.matrix->add_matrix(Ken,dof_indices,neighbor_dof_indices); ellipticdg_system.matrix->add_matrix(Kee,dof_indices); ellipticdg_system.matrix->add_matrix(Knn,neighbor_dof_indices); } } } // The element interior 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. ellipticdg_system.matrix->add_matrix(Ke, dof_indices); ellipticdg_system.rhs->add_vector(Fe, dof_indices); } std::cout << "done" << std::endl; }