// 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 }
// Stiffness and RHS assembly // Equation references are from Samanta and Zabaras, 2005 void EnergySystem :: assemble(){ // GENERAL VARIABLES // Get a constant reference to the mesh object. const MeshBase& mesh = this->get_mesh(); // The dimension that we are running const unsigned int dim = this->ndim(); // FEM THERMODYNAMIC RELATIONSHIPS (ThermoEq Class) // Determine the FEM type (should be same for all ThermoEq variables) FEType fe_type_thermo = thermo->variable_type(0); // Build FE object; accessed via a pointer AutoPtr<FEBase> fe_thermo(FEBase::build(dim, fe_type_thermo)); // Setup a quadrature rule QGauss qrule_thermo(dim, fe_type_thermo.default_quadrature_order()); // Link FE and Quadrature fe_thermo->attach_quadrature_rule(&qrule_thermo); // References to shape functions and derivatives const vector<std::vector<Real> >& N_thermo = fe_thermo->get_phi(); const vector<std::vector<RealGradient> >& B_thermo = fe_thermo->get_dphi(); // Setup a DOF map const DofMap& dof_map_thermo = thermo->get_dof_map(); // FEM MOMENTUM EQUATION // Determine the FEM type FEType fe_type_momentum = momentum->variable_type(0); // Build FE object; accessed via a pointer AutoPtr<FEBase> fe_momentum(FEBase::build(dim, fe_type_momentum)); // Setup a quadrature rule QGauss qrule_momentum(dim, fe_type_momentum.default_quadrature_order()); // Link FE and Quadrature fe_momentum->attach_quadrature_rule(&qrule_momentum); // References to shape functions and derivatives const vector<std::vector<Real> >& N_momentum = fe_momentum->get_phi(); // Setup a DOF map const DofMap& dof_map_momentum = momentum->get_dof_map(); // FEM ENERGY EQ. RELATIONSHIPS // Get a constant reference to the Finite Element type // for the first (and only) variable in the system. FEType fe_type = this->variable_type(0); // Build a Finite Element object of the specified type AutoPtr<FEBase> fe (FEBase::build(dim, fe_type)); AutoPtr<FEBase> fe_face (FEBase::build(dim, fe_type)); // A Gauss quadrature rule for numerical integration. // Let the \p FEType object decide what order rule is appropriate. QGauss qrule (dim, fe_type.default_quadrature_order()); QGauss qface (dim-1, fe_type.default_quadrature_order()); // Tell the finite element object to use our quadrature rule. fe->attach_quadrature_rule(&qrule); fe_face->attach_quadrature_rule(&qface); // Here we define some references to cell-specific data that // will be used to assemble the linear system. We will start // with the element Jacobian * quadrature weight at each integration point. const vector<Real>& JxW = fe->get_JxW(); const vector<Real>& JxW_face = fe_face->get_JxW(); // The element shape functions evaluated at the quadrature points. const vector<std::vector<Real> >& N = fe->get_phi(); const vector<std::vector<Real> >& N_face = fe_face->get_phi(); // Element shape function gradients evaluated at quadrature points const vector<std::vector<RealGradient> >& B = fe->get_dphi(); // The XY locations of the quadrature points used for face integration const vector<Point>& qface_points = fe_face->get_xyz(); // A reference to the \p DofMap objects const DofMap& dof_map = this->get_dof_map(); // this system // DEFINE VECTOR AND MATRIX VARIABLES // Define data structures to contain the element matrix // and right-hand-side vector contribution (Eq. 107) DenseMatrix<Number> Me; // [\hat{M} + \hat{M}_{\delta}] DenseMatrix<Number> Ne; // [\hat{N} + \hat{N}_{\delta}] DenseMatrix<Number> Ke; // [\hat{K} + \hat{K}_{\delta}] DenseVector<Number> Fe; // [\hat{F} + \hat{F}_{\delta}] //DenseVector<Number> Fe_old; // element force vector (previous time) DenseVector<Number> h; // element enthalpy vector (previous time) DenseVector<Number> h_dot; //DenseVector<Number> delta_h_dot; DenseMatrix<Number> Mstar; // general time integration stiffness matrix (Eq. 125) DenseVector<Number> R; // general time integration force vector (Eq. 126) // Storage vectors for the degree of freedom indices std::vector<unsigned int> dof_indices; // this system (h) // std::vector<unsigned int> dof_indices_hdot; // std::vector<unsigned int> dof_indices_deltahdot; std::vector<unsigned int> dof_indices_velocity; // this system std::vector<unsigned int> dof_indices_rho; // ThermoEq density std::vector<unsigned int> dof_indices_tmp; // ThermoEq temperature std::vector<unsigned int> dof_indices_f; // ThermoEq liquid fraction std::vector<unsigned int> dof_indices_eps; // ThermoEq epsilon // Define the necessary constants const Number gamma = get_constant<Number>("gamma"); const Number dt = get_constant<Number>("dt"); // time step Real time = this->time; // current time const Number ks = thermo->get_constant<Number>("conductivity_solid"); const Number kf = thermo->get_constant<Number>("conductivity_fluid"); const Number cs = thermo->get_constant<Number>("specific_heat_solid"); const Number cf = thermo->get_constant<Number>("specific_heat_fluid"); const Number Te = thermo->get_constant<Number>("eutectic_temperature"); const Number hf = thermo->get_constant<Number>("latent_heat"); // Index of density variable in ThermoEq system const unsigned int rho_idx = thermo->variable_number("density"); const unsigned int tmp_idx = thermo->variable_number("temperature"); const unsigned int f_idx = thermo->variable_number("liquid_mass_fraction"); const unsigned int eps_idx = thermo->variable_number("epsilon"); // Loop over all the elements in the mesh that are on local processor 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){ // Pointer to the element current element const Elem* elem = *el; // Get the degree of freedom indices for the current element dof_map.dof_indices(elem, dof_indices, 0); //dof_map.dof_indices(elem, dof_indices_hdot, 1); //dof_map.dof_indices(elem, dof_indices_deltahdot, 2); dof_map_momentum.dof_indices(elem, dof_indices_velocity); dof_map_thermo.dof_indices(elem, dof_indices_rho, rho_idx); dof_map_thermo.dof_indices(elem, dof_indices_tmp, tmp_idx); dof_map_thermo.dof_indices(elem, dof_indices_f, f_idx); dof_map_thermo.dof_indices(elem, dof_indices_eps, eps_idx); // Compute the element-specific data for the current element fe->reinit (elem); fe_thermo->reinit(elem); fe_momentum->reinit(elem); // Zero the element matrices and vectors Me.resize (dof_indices.size(), dof_indices.size()); // [\hat{M} + \hat{M}_{\delta}] Ne.resize (dof_indices.size(), dof_indices.size()); // Ke.resize (dof_indices.size(), dof_indices.size()); Fe.resize (dof_indices.size()); // Extract a vector of quadrature x,y,z coordinates const vector<Point> qp_vec = fe->get_xyz(); // Compute the element length, h Number elem_length = thermo->element_length(elem); // Compute the RHS and mass and stiffness matrix for this element (Me) for (unsigned int qp = 0; qp < qrule.n_points(); qp++){ // Get the velocity vector at this point (old value) VectorValue<Number> v; for (unsigned int i = 0; i < N_momentum.size(); i++){ for (unsigned int j = 0; j < dim; j++){ v(j) += N_momentum[i][qp] * momentum->old_solution(dof_indices_velocity[2*i+j]); } } // Compute ThermoEq variables; must be mapped from node to quadrature points Number T = 0; Gradient grad_T; Number f = 0; Gradient grad_f; Number rho = 0; Number rho_old = 0; Number eps = 0; for (unsigned int i = 0; i < N_thermo.size(); i++){ T += N_thermo[i][qp] * thermo->current_solution(dof_indices_tmp[i]); grad_T.add_scaled(B_thermo[i][qp], thermo->current_solution(dof_indices_tmp[i])); f += N_thermo[i][qp] * thermo->current_solution(dof_indices_f[i]); grad_f.add_scaled(B_thermo[i][qp], thermo->current_solution(dof_indices_f[i])); rho += N_thermo[i][qp] * thermo->current_solution(dof_indices_rho[i]); rho_old += N_thermo[i][qp] * thermo->old_solution(dof_indices_rho[i]); eps += N_thermo[i][qp] * thermo->current_solution(dof_indices_eps[i]); } // Compute EnergySystem variables Gradient grad_h; for (unsigned int i = 0; i < B.size(); i++){ grad_h.add_scaled(B[i][qp], this->current_solution(dof_indices[i])); } // Compute T_{,k}^h v_k^h and f_{,k} v_k^h summation terms for F Number Tv = 0; Number fv = 0; for (unsigned int i = 0; i < dim; i++){ Tv += grad_T(i) * v(i); fv += grad_f(i) * v(i); } // Compute the time derivative of density const Number drho_dt = (rho - rho_old)/dt; // Compute alpha term of Eq. 69 const Number alpha = this->alpha(grad_T, grad_h, f); // Extract tau_1 stabilization term const Number tau_1 = thermo->tau_1(qp_vec[qp], elem_length); // Loop through the components and construct matrices for (unsigned int i = 0; i < N.size(); i++){ // Compute advective stabilization term (Eq. A, p. 1777) const Number d = tau_1 * v * B[i][qp] / f - tau_1 * 1/rho * drho_dt * (1-f)/f * N[i][qp]; // Force vector, Eq. 77 Number F1 = JxW[qp] * (N[i][qp] + d) * rho * (1 - f) * (cf - cs) * Tv; Number F2 = JxW[qp] * (N[i][qp] + d) * rho * fv * ((cf - cs) * (T - Te) + hf); Number F3 = JxW[qp] * (N[i][qp] + d) * drho_dt * (1 - f) * ((cf - cs) * (T - Te) + hf); Fe(i) += F1 + F2 + F3; // Build the stiffness matrices for (unsigned int j = 0; j < N.size(); j++){ // Mass matrix, Eq. 108 Me(i,j) += JxW[qp] * rho * ((N[i][qp] + d) * N[j][qp]); // Stiffness matrix one, Ne, Eq. 109 Ne(i,j) += JxW[qp] * rho * ((N[i][qp] + d) * (v * B[j][qp])); // Stiffness matrix two, Ke, Eq. 110 Ke(i,j) += JxW[qp]*((eps*kf + (1 - eps)*ks) * alpha * B[i][qp] * B[j][qp]); } } } printf("Me:\n"); Me.print(std::cout); printf("\nNe:\n"); Ne.print(std::cout); printf("\nKe:\n"); Ke.print(std::cout); printf("\nFe:\n"); Fe.print(std::cout); h.resize(dof_indices.size()); h_dot.resize(dof_indices.size()); // delta_h_dot.resize(dof_indices_deltahdot.size()); for (unsigned int i = 0; i < dof_indices.size(); i++){ h(i) = this->old_solution(dof_indices[i]); h_dot(i) = this->get_vector("h_dot")(dof_indices[i]); // delta_h_dot(i) = this->old_solution(dof_indices_deltahdot[i]); } this->get_matrix("M").add_matrix(Me, dof_indices); this->get_matrix("N").add_matrix(Ne, dof_indices); this->get_matrix("K").add_matrix(Ke, dof_indices); this->get_vector("F").add_vector(Fe, dof_indices); Mstar.resize(dof_indices.size(), dof_indices.size()); R.resize(dof_indices.size()); // Me + gamma*dt*(Ke + Ne); Mstar.add(1,Me); Mstar.add(gamma*dt,Ke); Mstar.add(gamma*dt,Ne); this->matrix->add_matrix(Mstar, dof_indices); R.add(1,Fe); DenseVector<Number> a(dof_indices.size()); Me.vector_mult(a, h_dot); R.add(-1, a); DenseMatrix<Number> B(dof_indices.size(), dof_indices.size()); DenseVector<Number> b(dof_indices.size()); B.add(1,Ne); B.add(1,Ke); B.vector_mult(b, h); R.add(-1,b); this->rhs->add_vector(R, dof_indices); /* // BOUNDARY CONDITIONS // Loop through each side of the element for applying boundary conditions for (unsigned int s = 0; s < elem->n_sides(); s++){ // Only consider the side if it does not have a neighbor if (elem->neighbor(s) == NULL){ // Pointer to current element side const AutoPtr<Elem> side = elem->side(s); // Boundary ID of the current side int boundary_id = (mesh.boundary_info)->boundary_id(elem, s); // Get index of the boundary class with the same id // this vector is empty if there is no match and only // contains a single value if there is a match std::vector<int> idx = get_boundary_index(boundary_id); // Continue of there is a match if(!idx.empty()){ // Compute the shape function values on the element face fe_face->reinit(elem, s); // Create a shared pointer to the boundary class boost::shared_ptr<HeatEqBoundaryBase> ptr = bc_ptrs[idx[0]]; // Determine the type of boundary considered std::string type = ptr->type; // Loop through quadrature points for (unsigned int qp = 0; qp < qface.n_points(); qp++){ // DIRICHLET (libMesh version; handled at initialization) if(type.compare("dirichlet") == 0){ // The dirichlet conditions are handled at initlization // but I don't want to throw an error if they are // encountered, so just do nothing // NEUMANN condition } else if(type.compare("neumann") == 0){ // Current and past flux values const Number q = ptr->q(qface_points[qp], time); const Number q_old = ptr->q(qface_points[qp], time - dt); // Add values to Fe for (unsigned int i = 0; i < psi.size(); i++){ Fe(i) += JxW_face[qp] * q * psi[i][qp]; Fe_old(i) += JxW_face[qp] * q_old * psi[i][qp]; } // CONVECTION boundary } else if(type.compare("convection") == 0){ // Current and past h and T_inf const Number h = ptr->h(qface_points[qp], time); const Number h_old = ptr->h(qface_points[qp], time - dt); const Number Tinf = ptr->Tinf(qface_points[qp], time); const Number Tinf_old = ptr->Tinf(qface_points[qp], time - dt); // Add values to Ke and Fe for (unsigned int i = 0; i < psi.size(); i++){ Fe(i) += (1) * JxW_face[qp] * h * Tinf * psi[i][qp]; Fe_old(i) += (1) * JxW_face[qp] * h_old * Tinf_old * psi[i][qp]; for (unsigned int j = 0; j < psi.size(); j++){ Ke(i,j) += JxW_face[qp] * psi[i][qp] * h * psi[j][qp]; } } // Un-registerd type } else { printf("WARNING! The boundary type, %s, was not understood!\n", type.c_str()); } // (end) type.compare(...) statemenst } //(end) for (int qp = 0; qp < qface.n_points(); qp++) } // (end) if(!idx.empty) } // (end) if (elem->neighbor(s) == NULL){ } // (end) for (int s = 0; s < elem->n_sides(); s++) // Zero the pervious time-step temperature vector for this element u_old.resize(dof_indices.size()); // Gather the temperatures at the nodes for (unsigned int i = 0; i < psi.size(); i++){ u_old(i) = this->old_solution(dof_indices[i]); } // Build K_hat and F_hat (appends existing) K_hat.resize(dof_indices.size(), dof_indices.size()); F_hat.resize(dof_indices.size()); build_stiffness_and_rhs(K_hat, F_hat, Me, Ke, Fe_old, Fe, u_old, dt, theta); // Applies the dirichlet constraints to K_hat and F_hat dof_map.heterogenously_constrain_element_matrix_and_vector(K_hat, F_hat, dof_indices); // Apply the local components to the global K and F this->matrix->add_matrix(K_hat, dof_indices); this->rhs->add_vector(F_hat, dof_indices); */ } // (end) for ( ; el != end_el; ++el) //update_rhs(); } // (end) assemble()