void JumpErrorEstimator::estimate_error (const System& system, ErrorVector& error_per_cell, const NumericVector<Number>* solution_vector, bool estimate_parent_error) { START_LOG("estimate_error()", "JumpErrorEstimator"); /* Conventions for assigning the direction of the normal: - e & f are global element ids Case (1.) Elements are at the same level, e<f Compute the flux jump on the face and add it as a contribution to error_per_cell[e] and error_per_cell[f] ---------------------- | | | | | f | | | | | e |---> n | | | | | | | ---------------------- Case (2.) The neighbor is at a higher level. Compute the flux jump on e's face and add it as a contribution to error_per_cell[e] and error_per_cell[f] ---------------------- | | | | | | e |---> n | | | | | |-----------| f | | | | | | | | | | | | | ---------------------- */ // The current mesh const MeshBase& mesh = system.get_mesh(); // The number of variables in the system const unsigned int n_vars = system.n_vars(); // The DofMap for this system const DofMap& dof_map = system.get_dof_map(); // Resize the error_per_cell vector to be // the number of elements, initialize it to 0. error_per_cell.resize (mesh.max_elem_id()); std::fill (error_per_cell.begin(), error_per_cell.end(), 0.); // Declare a vector of floats which is as long as // error_per_cell above, and fill with zeros. This vector will be // used to keep track of the number of edges (faces) on each active // element which are either: // 1) an internal edge // 2) an edge on a Neumann boundary for which a boundary condition // function has been specified. // The error estimator can be scaled by the number of flux edges (faces) // which the element actually has to obtain a more uniform measure // of the error. Use floats instead of ints since in case 2 (above) // f gets 1/2 of a flux face contribution from each of his // neighbors std::vector<float> n_flux_faces; if (scale_by_n_flux_faces) n_flux_faces.resize(error_per_cell.size(), 0); // Prepare current_local_solution to localize a non-standard // solution vector if necessary if (solution_vector && solution_vector != system.solution.get()) { NumericVector<Number>* newsol = const_cast<NumericVector<Number>*>(solution_vector); System &sys = const_cast<System&>(system); newsol->swap(*sys.solution); sys.update(); } fine_context.reset(new FEMContext(system)); coarse_context.reset(new FEMContext(system)); // Loop over all the variables we've been requested to find jumps in, to // pre-request for (var=0; var<n_vars; var++) { // Possibly skip this variable if (error_norm.weight(var) == 0.0) continue; // FIXME: Need to generalize this to vector-valued elements. [PB] FEBase* side_fe = NULL; const std::set<unsigned char>& elem_dims = fine_context->elem_dimensions(); for (std::set<unsigned char>::const_iterator dim_it = elem_dims.begin(); dim_it != elem_dims.end(); ++dim_it) { const unsigned char dim = *dim_it; fine_context->get_side_fe( var, side_fe, dim ); libmesh_assert_not_equal_to(side_fe->get_fe_type().family, SCALAR); side_fe->get_xyz(); } } this->init_context(*fine_context); this->init_context(*coarse_context); // Iterate over all the active elements in the mesh // that live on this processor. MeshBase::const_element_iterator elem_it = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator elem_end = mesh.active_local_elements_end(); for (; elem_it != elem_end; ++elem_it) { // e is necessarily an active element on the local processor const Elem* e = *elem_it; const dof_id_type e_id = e->id(); #ifdef LIBMESH_ENABLE_AMR // See if the parent of element e has been examined yet; // if not, we may want to compute the estimator on it const Elem* parent = e->parent(); // We only can compute and only need to compute on // parents with all active children bool compute_on_parent = true; if (!parent || !estimate_parent_error) compute_on_parent = false; else for (unsigned int c=0; c != parent->n_children(); ++c) if (!parent->child(c)->active()) compute_on_parent = false; if (compute_on_parent && !error_per_cell[parent->id()]) { // Compute a projection onto the parent DenseVector<Number> Uparent; FEBase::coarsened_dof_values (*(system.solution), dof_map, parent, Uparent, false); // Loop over the neighbors of the parent for (unsigned int n_p=0; n_p<parent->n_neighbors(); n_p++) { if (parent->neighbor(n_p) != NULL) // parent has a neighbor here { // Find the active neighbors in this direction std::vector<const Elem*> active_neighbors; parent->neighbor(n_p)-> active_family_tree_by_neighbor(active_neighbors, parent); // Compute the flux to each active neighbor for (unsigned int a=0; a != active_neighbors.size(); ++a) { const Elem *f = active_neighbors[a]; // FIXME - what about when f->level < // parent->level()?? if (f->level() >= parent->level()) { fine_context->pre_fe_reinit(system, f); coarse_context->pre_fe_reinit(system, parent); libmesh_assert_equal_to (coarse_context->get_elem_solution().size(), Uparent.size()); coarse_context->get_elem_solution() = Uparent; this->reinit_sides(); // Loop over all significant variables in the system for (var=0; var<n_vars; var++) if (error_norm.weight(var) != 0.0) { this->internal_side_integration(); error_per_cell[fine_context->get_elem().id()] += static_cast<ErrorVectorReal>(fine_error); error_per_cell[coarse_context->get_elem().id()] += static_cast<ErrorVectorReal>(coarse_error); } // Keep track of the number of internal flux // sides found on each element if (scale_by_n_flux_faces) { n_flux_faces[fine_context->get_elem().id()]++; n_flux_faces[coarse_context->get_elem().id()] += this->coarse_n_flux_faces_increment(); } } } } else if (integrate_boundary_sides) { fine_context->pre_fe_reinit(system, parent); libmesh_assert_equal_to (fine_context->get_elem_solution().size(), Uparent.size()); fine_context->get_elem_solution() = Uparent; fine_context->side = n_p; fine_context->side_fe_reinit(); // If we find a boundary flux for any variable, // let's just count it as a flux face for all // variables. Otherwise we'd need to keep track of // a separate n_flux_faces and error_per_cell for // every single var. bool found_boundary_flux = false; for (var=0; var<n_vars; var++) if (error_norm.weight(var) != 0.0) { if (this->boundary_side_integration()) { error_per_cell[fine_context->get_elem().id()] += static_cast<ErrorVectorReal>(fine_error); found_boundary_flux = true; } } if (scale_by_n_flux_faces && found_boundary_flux) n_flux_faces[fine_context->get_elem().id()]++; } } } #endif // #ifdef LIBMESH_ENABLE_AMR // If we do any more flux integration, e will be the fine element fine_context->pre_fe_reinit(system, e); // Loop over the neighbors of element e for (unsigned int n_e=0; n_e<e->n_neighbors(); n_e++) { if ((e->neighbor(n_e) != NULL) || integrate_boundary_sides) { fine_context->side = n_e; fine_context->side_fe_reinit(); } if (e->neighbor(n_e) != NULL) // e is not on the boundary { const Elem* f = e->neighbor(n_e); const dof_id_type f_id = f->id(); // Compute flux jumps if we are in case 1 or case 2. if ((f->active() && (f->level() == e->level()) && (e_id < f_id)) || (f->level() < e->level())) { // f is now the coarse element coarse_context->pre_fe_reinit(system, f); this->reinit_sides(); // Loop over all significant variables in the system for (var=0; var<n_vars; var++) if (error_norm.weight(var) != 0.0) { this->internal_side_integration(); error_per_cell[fine_context->get_elem().id()] += static_cast<ErrorVectorReal>(fine_error); error_per_cell[coarse_context->get_elem().id()] += static_cast<ErrorVectorReal>(coarse_error); } // Keep track of the number of internal flux // sides found on each element if (scale_by_n_flux_faces) { n_flux_faces[fine_context->get_elem().id()]++; n_flux_faces[coarse_context->get_elem().id()] += this->coarse_n_flux_faces_increment(); } } // end if (case1 || case2) } // if (e->neigbor(n_e) != NULL) // Otherwise, e is on the boundary. If it happens to // be on a Dirichlet boundary, we need not do anything. // On the other hand, if e is on a Neumann (flux) boundary // with grad(u).n = g, we need to compute the additional residual // (h * \int |g - grad(u_h).n|^2 dS)^(1/2). // We can only do this with some knowledge of the boundary // conditions, i.e. the user must have attached an appropriate // BC function. else if (integrate_boundary_sides) { bool found_boundary_flux = false; for (var=0; var<n_vars; var++) if (error_norm.weight(var) != 0.0) if (this->boundary_side_integration()) { error_per_cell[fine_context->get_elem().id()] += static_cast<ErrorVectorReal>(fine_error); found_boundary_flux = true; } if (scale_by_n_flux_faces && found_boundary_flux) n_flux_faces[fine_context->get_elem().id()]++; } // end if (e->neighbor(n_e) == NULL) } // end loop over neighbors } // End loop over active local elements // Each processor has now computed the error contribuions // for its local elements. We need to sum the vector // and then take the square-root of each component. Note // that we only need to sum if we are running on multiple // processors, and we only need to take the square-root // if the value is nonzero. There will in general be many // zeros for the inactive elements. // First sum the vector of estimated error values this->reduce_error(error_per_cell, system.comm()); // Compute the square-root of each component. for (std::size_t i=0; i<error_per_cell.size(); i++) if (error_per_cell[i] != 0.) error_per_cell[i] = std::sqrt(error_per_cell[i]); if (this->scale_by_n_flux_faces) { // Sum the vector of flux face counts this->reduce_error(n_flux_faces, system.comm()); // Sanity check: Make sure the number of flux faces is // always an integer value #ifdef DEBUG for (unsigned int i=0; i<n_flux_faces.size(); ++i) libmesh_assert_equal_to (n_flux_faces[i], static_cast<float>(static_cast<unsigned int>(n_flux_faces[i])) ); #endif // Scale the error by the number of flux faces for each element for (unsigned int i=0; i<n_flux_faces.size(); ++i) { if (n_flux_faces[i] == 0.0) // inactive or non-local element continue; //libMesh::out << "Element " << i << " has " << n_flux_faces[i] << " flux faces." << std::endl; error_per_cell[i] /= static_cast<ErrorVectorReal>(n_flux_faces[i]); } } // If we used a non-standard solution before, now is the time to fix // the current_local_solution if (solution_vector && solution_vector != system.solution.get()) { NumericVector<Number>* newsol = const_cast<NumericVector<Number>*>(solution_vector); System &sys = const_cast<System&>(system); newsol->swap(*sys.solution); sys.update(); } STOP_LOG("estimate_error()", "JumpErrorEstimator"); }
void JumpErrorEstimator::estimate_error (const System& system, ErrorVector& error_per_cell, const NumericVector<Number>* solution_vector, bool estimate_parent_error) { START_LOG("estimate_error()", "JumpErrorEstimator"); /* Conventions for assigning the direction of the normal: - e & f are global element ids Case (1.) Elements are at the same level, e<f Compute the flux jump on the face and add it as a contribution to error_per_cell[e] and error_per_cell[f] ---------------------- | | | | | f | | | | | e |---> n | | | | | | | ---------------------- Case (2.) The neighbor is at a higher level. Compute the flux jump on e's face and add it as a contribution to error_per_cell[e] and error_per_cell[f] ---------------------- | | | | | | e |---> n | | | | | |-----------| f | | | | | | | | | | | | | ---------------------- */ // The current mesh const MeshBase& mesh = system.get_mesh(); // The dimensionality of the mesh const unsigned int dim = mesh.mesh_dimension(); // The number of variables in the system const unsigned int n_vars = system.n_vars(); // The DofMap for this system const DofMap& dof_map = system.get_dof_map(); // Resize the error_per_cell vector to be // the number of elements, initialize it to 0. error_per_cell.resize (mesh.max_elem_id()); std::fill (error_per_cell.begin(), error_per_cell.end(), 0.); // Declare a vector of floats which is as long as // error_per_cell above, and fill with zeros. This vector will be // used to keep track of the number of edges (faces) on each active // element which are either: // 1) an internal edge // 2) an edge on a Neumann boundary for which a boundary condition // function has been specified. // The error estimator can be scaled by the number of flux edges (faces) // which the element actually has to obtain a more uniform measure // of the error. Use floats instead of ints since in case 2 (above) // f gets 1/2 of a flux face contribution from each of his // neighbors std::vector<float> n_flux_faces (error_per_cell.size()); // Prepare current_local_solution to localize a non-standard // solution vector if necessary if (solution_vector && solution_vector != system.solution.get()) { NumericVector<Number>* newsol = const_cast<NumericVector<Number>*>(solution_vector); System &sys = const_cast<System&>(system); newsol->swap(*sys.solution); sys.update(); } // Loop over all the variables in the system for (var=0; var<n_vars; var++) { // Possibly skip this variable if (error_norm.weight(var) == 0.0) continue; // The type of finite element to use for this variable const FEType& fe_type = dof_map.variable_type (var); // Finite element objects for the same face from // different sides fe_fine = FEBase::build (dim, fe_type); fe_coarse = FEBase::build (dim, fe_type); // Build an appropriate Gaussian quadrature rule QGauss qrule (dim-1, fe_type.default_quadrature_order()); // Tell the finite element for the fine element about the quadrature // rule. The finite element for the coarse element need not know about it fe_fine->attach_quadrature_rule (&qrule); // By convention we will always do the integration // on the face of element e. We'll need its Jacobian values and // physical point locations, at least fe_fine->get_JxW(); fe_fine->get_xyz(); // Our derived classes may want to do some initialization here this->initialize(system, error_per_cell, estimate_parent_error); // The global DOF indices for elements e & f std::vector<dof_id_type> dof_indices_fine; std::vector<dof_id_type> dof_indices_coarse; // Iterate over all the active elements in the mesh // that live on this processor. MeshBase::const_element_iterator elem_it = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator elem_end = mesh.active_local_elements_end(); for (; elem_it != elem_end; ++elem_it) { // e is necessarily an active element on the local processor const Elem* e = *elem_it; const dof_id_type e_id = e->id(); #ifdef LIBMESH_ENABLE_AMR // See if the parent of element e has been examined yet; // if not, we may want to compute the estimator on it const Elem* parent = e->parent(); // We only can compute and only need to compute on // parents with all active children bool compute_on_parent = true; if (!parent || !estimate_parent_error) compute_on_parent = false; else for (unsigned int c=0; c != parent->n_children(); ++c) if (!parent->child(c)->active()) compute_on_parent = false; if (compute_on_parent && !error_per_cell[parent->id()]) { // Compute a projection onto the parent DenseVector<Number> Uparent; FEBase::coarsened_dof_values(*(system.solution), dof_map, parent, Uparent, var, false); // Loop over the neighbors of the parent for (unsigned int n_p=0; n_p<parent->n_neighbors(); n_p++) { if (parent->neighbor(n_p) != NULL) // parent has a neighbor here { // Find the active neighbors in this direction std::vector<const Elem*> active_neighbors; parent->neighbor(n_p)-> active_family_tree_by_neighbor(active_neighbors, parent); // Compute the flux to each active neighbor for (unsigned int a=0; a != active_neighbors.size(); ++a) { const Elem *f = active_neighbors[a]; // FIXME - what about when f->level < // parent->level()?? if (f->level() >= parent->level()) { fine_elem = f; coarse_elem = parent; Ucoarse = Uparent; dof_map.dof_indices (fine_elem, dof_indices_fine, var); const unsigned int n_dofs_fine = libmesh_cast_int<unsigned int>(dof_indices_fine.size()); Ufine.resize(n_dofs_fine); for (unsigned int i=0; i<n_dofs_fine; i++) Ufine(i) = system.current_solution(dof_indices_fine[i]); this->reinit_sides(); this->internal_side_integration(); error_per_cell[fine_elem->id()] += static_cast<ErrorVectorReal>(fine_error); error_per_cell[coarse_elem->id()] += static_cast<ErrorVectorReal>(coarse_error); // Keep track of the number of internal flux // sides found on each element n_flux_faces[fine_elem->id()]++; n_flux_faces[coarse_elem->id()] += this->coarse_n_flux_faces_increment(); } } } else if (integrate_boundary_sides) { fine_elem = parent; Ufine = Uparent; // Reinitialize shape functions on the fine element side fe_fine->reinit (fine_elem, fine_side); if (this->boundary_side_integration()) { error_per_cell[fine_elem->id()] += static_cast<ErrorVectorReal>(fine_error); n_flux_faces[fine_elem->id()]++; } } } } #endif // #ifdef LIBMESH_ENABLE_AMR // If we do any more flux integration, e will be the fine element fine_elem = e; // Loop over the neighbors of element e for (unsigned int n_e=0; n_e<e->n_neighbors(); n_e++) { fine_side = n_e; if (e->neighbor(n_e) != NULL) // e is not on the boundary { const Elem* f = e->neighbor(n_e); const dof_id_type f_id = f->id(); // Compute flux jumps if we are in case 1 or case 2. if ((f->active() && (f->level() == e->level()) && (e_id < f_id)) || (f->level() < e->level())) { // f is now the coarse element coarse_elem = f; // Get the DOF indices for the two elements dof_map.dof_indices (fine_elem, dof_indices_fine, var); dof_map.dof_indices (coarse_elem, dof_indices_coarse, var); // The number of DOFS on each element const unsigned int n_dofs_fine = libmesh_cast_int<unsigned int>(dof_indices_fine.size()); const unsigned int n_dofs_coarse = libmesh_cast_int<unsigned int>(dof_indices_coarse.size()); Ufine.resize(n_dofs_fine); Ucoarse.resize(n_dofs_coarse); // The local solutions on each element for (unsigned int i=0; i<n_dofs_fine; i++) Ufine(i) = system.current_solution(dof_indices_fine[i]); for (unsigned int i=0; i<n_dofs_coarse; i++) Ucoarse(i) = system.current_solution(dof_indices_coarse[i]); this->reinit_sides(); this->internal_side_integration(); error_per_cell[fine_elem->id()] += static_cast<ErrorVectorReal>(fine_error); error_per_cell[coarse_elem->id()] += static_cast<ErrorVectorReal>(coarse_error); // Keep track of the number of internal flux // sides found on each element n_flux_faces[fine_elem->id()]++; n_flux_faces[coarse_elem->id()] += this->coarse_n_flux_faces_increment(); } // end if (case1 || case2) } // if (e->neigbor(n_e) != NULL) // Otherwise, e is on the boundary. If it happens to // be on a Dirichlet boundary, we need not do anything. // On the other hand, if e is on a Neumann (flux) boundary // with grad(u).n = g, we need to compute the additional residual // (h * \int |g - grad(u_h).n|^2 dS)^(1/2). // We can only do this with some knowledge of the boundary // conditions, i.e. the user must have attached an appropriate // BC function. else { if (integrate_boundary_sides) { // Reinitialize shape functions on the fine element side fe_fine->reinit (fine_elem, fine_side); // Get the DOF indices dof_map.dof_indices (fine_elem, dof_indices_fine, var); // The number of DOFS on each element const unsigned int n_dofs_fine = libmesh_cast_int<unsigned int>(dof_indices_fine.size()); Ufine.resize(n_dofs_fine); for (unsigned int i=0; i<n_dofs_fine; i++) Ufine(i) = system.current_solution(dof_indices_fine[i]); if (this->boundary_side_integration()) { error_per_cell[fine_elem->id()] += static_cast<ErrorVectorReal>(fine_error); n_flux_faces[fine_elem->id()]++; } } // end if _bc_function != NULL } // end if (e->neighbor(n_e) == NULL) } // end loop over neighbors } // End loop over active local elements } // End loop over variables // Each processor has now computed the error contribuions // for its local elements. We need to sum the vector // and then take the square-root of each component. Note // that we only need to sum if we are running on multiple // processors, and we only need to take the square-root // if the value is nonzero. There will in general be many // zeros for the inactive elements. // First sum the vector of estimated error values this->reduce_error(error_per_cell); // Compute the square-root of each component. for (std::size_t i=0; i<error_per_cell.size(); i++) if (error_per_cell[i] != 0.) error_per_cell[i] = std::sqrt(error_per_cell[i]); if (this->scale_by_n_flux_faces) { // Sum the vector of flux face counts this->reduce_error(n_flux_faces); // Sanity check: Make sure the number of flux faces is // always an integer value #ifdef DEBUG for (unsigned int i=0; i<n_flux_faces.size(); ++i) libmesh_assert_equal_to (n_flux_faces[i], static_cast<float>(static_cast<unsigned int>(n_flux_faces[i])) ); #endif // Scale the error by the number of flux faces for each element for (unsigned int i=0; i<n_flux_faces.size(); ++i) { if (n_flux_faces[i] == 0.0) // inactive or non-local element continue; //libMesh::out << "Element " << i << " has " << n_flux_faces[i] << " flux faces." << std::endl; error_per_cell[i] /= static_cast<ErrorVectorReal>(n_flux_faces[i]); } } // If we used a non-standard solution before, now is the time to fix // the current_local_solution if (solution_vector && solution_vector != system.solution.get()) { NumericVector<Number>* newsol = const_cast<NumericVector<Number>*>(solution_vector); System &sys = const_cast<System&>(system); newsol->swap(*sys.solution); sys.update(); } STOP_LOG("estimate_error()", "JumpErrorEstimator"); }
void ExactErrorEstimator::estimate_error (const System & system, ErrorVector & error_per_cell, const NumericVector<Number> * solution_vector, bool estimate_parent_error) { // Ignore the fact that this variable is unused when !LIBMESH_ENABLE_AMR libmesh_ignore(estimate_parent_error); // The current mesh const MeshBase & mesh = system.get_mesh(); // The dimensionality of the mesh const unsigned int dim = mesh.mesh_dimension(); // The number of variables in the system const unsigned int n_vars = system.n_vars(); // The DofMap for this system const DofMap & dof_map = system.get_dof_map(); // Resize the error_per_cell vector to be // the number of elements, initialize it to 0. error_per_cell.resize (mesh.max_elem_id()); std::fill (error_per_cell.begin(), error_per_cell.end(), 0.); // Prepare current_local_solution to localize a non-standard // solution vector if necessary if (solution_vector && solution_vector != system.solution.get()) { NumericVector<Number> * newsol = const_cast<NumericVector<Number> *>(solution_vector); System & sys = const_cast<System &>(system); newsol->swap(*sys.solution); sys.update(); } // Loop over all the variables in the system for (unsigned int var=0; var<n_vars; var++) { // Possibly skip this variable if (error_norm.weight(var) == 0.0) continue; // The (string) name of this variable const std::string & var_name = system.variable_name(var); // The type of finite element to use for this variable const FEType & fe_type = dof_map.variable_type (var); UniquePtr<FEBase> fe (FEBase::build (dim, fe_type)); // Build an appropriate Gaussian quadrature rule UniquePtr<QBase> qrule = fe_type.default_quadrature_rule (dim, _extra_order); fe->attach_quadrature_rule (qrule.get()); // Prepare a global solution and a MeshFunction of the fine system if we need one UniquePtr<MeshFunction> fine_values; UniquePtr<NumericVector<Number> > fine_soln = NumericVector<Number>::build(system.comm()); if (_equation_systems_fine) { const System & fine_system = _equation_systems_fine->get_system(system.name()); std::vector<Number> global_soln; // FIXME - we're assuming that the fine system solution gets // used even when a different vector is used for the coarse // system fine_system.update_global_solution(global_soln); fine_soln->init (cast_int<numeric_index_type>(global_soln.size()), true, SERIAL); (*fine_soln) = global_soln; fine_values = UniquePtr<MeshFunction> (new MeshFunction(*_equation_systems_fine, *fine_soln, fine_system.get_dof_map(), fine_system.variable_number(var_name))); fine_values->init(); } else { // Initialize functors if we're using them for (unsigned int i=0; i != _exact_values.size(); ++i) if (_exact_values[i]) _exact_values[i]->init(); for (unsigned int i=0; i != _exact_derivs.size(); ++i) if (_exact_derivs[i]) _exact_derivs[i]->init(); for (unsigned int i=0; i != _exact_hessians.size(); ++i) if (_exact_hessians[i]) _exact_hessians[i]->init(); } // Request the data we'll need to compute with fe->get_JxW(); fe->get_phi(); fe->get_dphi(); #ifdef LIBMESH_ENABLE_SECOND_DERIVATIVES fe->get_d2phi(); #endif fe->get_xyz(); #ifdef LIBMESH_ENABLE_AMR // If we compute on parent elements, we'll want to do so only // once on each, so we need to keep track of which we've done. std::vector<bool> computed_var_on_parent; if (estimate_parent_error) computed_var_on_parent.resize(error_per_cell.size(), false); #endif // TODO: this ought to be threaded (and using subordinate // MeshFunction objects in each thread rather than a single // master) // Iterate over all the active elements in the mesh // that live on this processor. MeshBase::const_element_iterator elem_it = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator elem_end = mesh.active_local_elements_end(); for (;elem_it != elem_end; ++elem_it) { // e is necessarily an active element on the local processor const Elem * elem = *elem_it; const dof_id_type e_id = elem->id(); #ifdef LIBMESH_ENABLE_AMR // See if the parent of element e has been examined yet; // if not, we may want to compute the estimator on it const Elem * parent = elem->parent(); // We only can compute and only need to compute on // parents with all active children bool compute_on_parent = true; if (!parent || !estimate_parent_error) compute_on_parent = false; else for (unsigned int c=0; c != parent->n_children(); ++c) if (!parent->child_ptr(c)->active()) compute_on_parent = false; if (compute_on_parent && !computed_var_on_parent[parent->id()]) { computed_var_on_parent[parent->id()] = true; // Compute a projection onto the parent DenseVector<Number> Uparent; FEBase::coarsened_dof_values(*(system.current_local_solution), dof_map, parent, Uparent, var, false); error_per_cell[parent->id()] += static_cast<ErrorVectorReal> (find_squared_element_error(system, var_name, parent, Uparent, fe.get(), fine_values.get())); } #endif // Get the local to global degree of freedom maps std::vector<dof_id_type> dof_indices; dof_map.dof_indices (elem, dof_indices, var); const unsigned int n_dofs = cast_int<unsigned int>(dof_indices.size()); DenseVector<Number> Uelem(n_dofs); for (unsigned int i=0; i != n_dofs; ++i) Uelem(i) = system.current_solution(dof_indices[i]); error_per_cell[e_id] += static_cast<ErrorVectorReal> (find_squared_element_error(system, var_name, elem, Uelem, fe.get(), fine_values.get())); } // End loop over active local elements } // End loop over variables // Each processor has now computed the error contribuions // for its local elements. We need to sum the vector // and then take the square-root of each component. Note // that we only need to sum if we are running on multiple // processors, and we only need to take the square-root // if the value is nonzero. There will in general be many // zeros for the inactive elements. // First sum the vector of estimated error values this->reduce_error(error_per_cell, system.comm()); // Compute the square-root of each component. { LOG_SCOPE("std::sqrt()", "ExactErrorEstimator"); for (dof_id_type i=0; i<error_per_cell.size(); i++) if (error_per_cell[i] != 0.) { libmesh_assert_greater (error_per_cell[i], 0.); error_per_cell[i] = std::sqrt(error_per_cell[i]); } } // If we used a non-standard solution before, now is the time to fix // the current_local_solution if (solution_vector && solution_vector != system.solution.get()) { NumericVector<Number> * newsol = const_cast<NumericVector<Number> *>(solution_vector); System & sys = const_cast<System &>(system); newsol->swap(*sys.solution); sys.update(); } }
void AdjointRefinementEstimator::estimate_error (const System & _system, ErrorVector & error_per_cell, const NumericVector<Number> * solution_vector, bool /*estimate_parent_error*/) { // We have to break the rules here, because we can't refine a const System System & system = const_cast<System &>(_system); // An EquationSystems reference will be convenient. EquationSystems & es = system.get_equation_systems(); // The current mesh MeshBase & mesh = es.get_mesh(); // Get coarse grid adjoint solutions. This should be a relatively // quick (especially with preconditioner reuse) way to get a good // initial guess for the fine grid adjoint solutions. More // importantly, subtracting off a coarse adjoint approximation gives // us better local error indication, and subtracting off *some* lift // function is necessary for correctness if we have heterogeneous // adjoint Dirichlet conditions. // Solve the adjoint problem(s) on the coarse FE space // Only if the user didn't already solve it for us if (!system.is_adjoint_already_solved()) system.adjoint_solve(_qoi_set); // Loop over all the adjoint problems and, if any have heterogenous // Dirichlet conditions, get the corresponding coarse lift // function(s) for (unsigned int j=0; j != system.qoi.size(); j++) { // Skip this QoI if it is not in the QoI Set or if there are no // heterogeneous Dirichlet boundaries for it if (_qoi_set.has_index(j) && system.get_dof_map().has_adjoint_dirichlet_boundaries(j)) { std::ostringstream liftfunc_name; liftfunc_name << "adjoint_lift_function" << j; NumericVector<Number> & liftvec = system.add_vector(liftfunc_name.str()); system.get_dof_map().enforce_constraints_exactly (system, &liftvec, true); } } // We'll want to back up all coarse grid vectors std::map<std::string, NumericVector<Number> *> coarse_vectors; for (System::vectors_iterator vec = system.vectors_begin(); vec != system.vectors_end(); ++vec) { // The (string) name of this vector const std::string & var_name = vec->first; coarse_vectors[var_name] = vec->second->clone().release(); } // Back up the coarse solution and coarse local solution NumericVector<Number> * coarse_solution = system.solution->clone().release(); NumericVector<Number> * coarse_local_solution = system.current_local_solution->clone().release(); // And we'll need to temporarily change solution projection settings bool old_projection_setting; old_projection_setting = system.project_solution_on_reinit(); // Make sure the solution is projected when we refine the mesh system.project_solution_on_reinit() = true; // And it'll be best to avoid any repartitioning UniquePtr<Partitioner> old_partitioner(mesh.partitioner().release()); // And we can't allow any renumbering const bool old_renumbering_setting = mesh.allow_renumbering(); mesh.allow_renumbering(false); // Use a non-standard solution vector if necessary if (solution_vector && solution_vector != system.solution.get()) { NumericVector<Number> * newsol = const_cast<NumericVector<Number> *> (solution_vector); newsol->swap(*system.solution); system.update(); } // Resize the error_per_cell vector to be // the number of elements, initialized to 0. error_per_cell.clear(); error_per_cell.resize (mesh.max_elem_id(), 0.); #ifndef NDEBUG // n_coarse_elem is only used in an assertion later so // avoid declaring it unless asserts are active. const dof_id_type n_coarse_elem = mesh.n_elem(); #endif // Uniformly refine the mesh MeshRefinement mesh_refinement(mesh); libmesh_assert (number_h_refinements > 0 || number_p_refinements > 0); // FIXME: this may break if there is more than one System // on this mesh but estimate_error was still called instead of // estimate_errors for (unsigned int i = 0; i != number_h_refinements; ++i) { mesh_refinement.uniformly_refine(1); es.reinit(); } for (unsigned int i = 0; i != number_p_refinements; ++i) { mesh_refinement.uniformly_p_refine(1); es.reinit(); } // Copy the projected coarse grid solutions, which will be // overwritten by solve() std::vector<NumericVector<Number> *> coarse_adjoints; for (unsigned int j=0; j != system.qoi.size(); j++) { if (_qoi_set.has_index(j)) { NumericVector<Number> * coarse_adjoint = NumericVector<Number>::build(mesh.comm()).release(); // Can do "fast" init since we're overwriting this in a sec coarse_adjoint->init(system.get_adjoint_solution(j), /* fast = */ true); *coarse_adjoint = system.get_adjoint_solution(j); coarse_adjoints.push_back(coarse_adjoint); } else coarse_adjoints.push_back(static_cast<NumericVector<Number> *>(libmesh_nullptr)); } // Rebuild the rhs with the projected primal solution (dynamic_cast<ImplicitSystem &>(system)).assembly(true, false); NumericVector<Number> & projected_residual = (dynamic_cast<ExplicitSystem &>(system)).get_vector("RHS Vector"); projected_residual.close(); // Solve the adjoint problem(s) on the refined FE space system.adjoint_solve(_qoi_set); // Now that we have the refined adjoint solution and the projected primal solution, // we first compute the global QoI error estimate // Resize the computed_global_QoI_errors vector to hold the error estimates for each QoI computed_global_QoI_errors.resize(system.qoi.size()); // Loop over all the adjoint solutions and get the QoI error // contributions from all of them. While we're looping anyway we'll // pull off the coarse adjoints for (unsigned int j=0; j != system.qoi.size(); j++) { // Skip this QoI if not in the QoI Set if (_qoi_set.has_index(j)) { // If the adjoint solution has heterogeneous dirichlet // values, then to get a proper error estimate here we need // to subtract off a coarse grid lift function. In any case // we can get a better error estimate by separating off a // coarse representation of the adjoint solution, so we'll // use that for our lift function. system.get_adjoint_solution(j) -= *coarse_adjoints[j]; computed_global_QoI_errors[j] = projected_residual.dot(system.get_adjoint_solution(j)); } } // Done with the global error estimates, now construct the element wise error indicators // We ought to account for 'spill-over' effects while computing the // element error indicators This happens because the same dof is // shared by multiple elements, one way of mitigating this is to // scale the contribution from each dof by the number of elements it // belongs to We first obtain the number of elements each node // belongs to // A map that relates a node id to an int that will tell us how many elements it is a node of LIBMESH_BEST_UNORDERED_MAP<dof_id_type, unsigned int>shared_element_count; // To fill this map, we will loop over elements, and then in each element, we will loop // over the nodes each element contains, and then query it for the number of coarse // grid elements it was a node of // Keep track of which nodes we have already dealt with LIBMESH_BEST_UNORDERED_SET<dof_id_type> processed_node_ids; // We will be iterating over all the active elements in the fine mesh that live on // this processor { MeshBase::const_element_iterator elem_it = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator elem_end = mesh.active_local_elements_end(); // Start loop over elems for(; elem_it != elem_end; ++elem_it) { // Pointer to this element const Elem * elem = *elem_it; // Loop over the nodes in the element for(unsigned int n=0; n != elem->n_nodes(); ++n) { // Get a reference to the current node const Node & node = elem->node_ref(n); // Get the id of this node dof_id_type node_id = node.id(); // If we havent already processed this node, do so now if(processed_node_ids.find(node_id) == processed_node_ids.end()) { // Declare a neighbor_set to be filled by the find_point_neighbors std::set<const Elem *> fine_grid_neighbor_set; // Call find_point_neighbors to fill the neighbor_set elem->find_point_neighbors(node, fine_grid_neighbor_set); // A vector to hold the coarse grid parents neighbors std::vector<dof_id_type> coarse_grid_neighbors; // Iterators over the fine grid neighbors set std::set<const Elem *>::iterator fine_neighbor_it = fine_grid_neighbor_set.begin(); const std::set<const Elem *>::iterator fine_neighbor_end = fine_grid_neighbor_set.end(); // Loop over all the fine neighbors of this node for(; fine_neighbor_it != fine_neighbor_end ; ++fine_neighbor_it) { // Pointer to the current fine neighbor element const Elem * fine_elem = *fine_neighbor_it; // Find the element id for the corresponding coarse grid element const Elem * coarse_elem = fine_elem; for (unsigned int j = 0; j != number_h_refinements; ++j) { libmesh_assert (coarse_elem->parent()); coarse_elem = coarse_elem->parent(); } // Loop over the existing coarse neighbors and check if this one is // already in there const dof_id_type coarse_id = coarse_elem->id(); std::size_t j = 0; for (; j != coarse_grid_neighbors.size(); j++) { // If the set already contains this element break out of the loop if(coarse_grid_neighbors[j] == coarse_id) { break; } } // If we didn't leave the loop even at the last element, // this is a new neighbour, put in the coarse_grid_neighbor_set if(j == coarse_grid_neighbors.size()) { coarse_grid_neighbors.push_back(coarse_id); } } // End loop over fine neighbors // Set the shared_neighbour index for this node to the // size of the coarse grid neighbor set shared_element_count[node_id] = cast_int<unsigned int>(coarse_grid_neighbors.size()); // Add this node to processed_node_ids vector processed_node_ids.insert(node_id); } // End if not processed node } // End loop over nodes } // End loop over elems } // Get a DoF map, will be used to get the nodal dof_indices for each element DofMap & dof_map = system.get_dof_map(); // The global DOF indices, we will use these later on when we compute the element wise indicators std::vector<dof_id_type> dof_indices; // Localize the global rhs and adjoint solution vectors (which might be shared on multiple processsors) onto a // local ghosted vector, this ensures each processor has all the dof_indices to compute an error indicator for // an element it owns UniquePtr<NumericVector<Number> > localized_projected_residual = NumericVector<Number>::build(system.comm()); localized_projected_residual->init(system.n_dofs(), system.n_local_dofs(), system.get_dof_map().get_send_list(), false, GHOSTED); projected_residual.localize(*localized_projected_residual, system.get_dof_map().get_send_list()); // Each adjoint solution will also require ghosting; for efficiency we'll reuse the same memory UniquePtr<NumericVector<Number> > localized_adjoint_solution = NumericVector<Number>::build(system.comm()); localized_adjoint_solution->init(system.n_dofs(), system.n_local_dofs(), system.get_dof_map().get_send_list(), false, GHOSTED); // We will loop over each adjoint solution, localize that adjoint // solution and then loop over local elements for (unsigned int i=0; i != system.qoi.size(); i++) { // Skip this QoI if not in the QoI Set if (_qoi_set.has_index(i)) { // Get the weight for the current QoI Real error_weight = _qoi_set.weight(i); (system.get_adjoint_solution(i)).localize(*localized_adjoint_solution, system.get_dof_map().get_send_list()); // Loop over elements MeshBase::const_element_iterator elem_it = mesh.active_local_elements_begin(); const MeshBase::const_element_iterator elem_end = mesh.active_local_elements_end(); for(; elem_it != elem_end; ++elem_it) { // Pointer to the element const Elem * elem = *elem_it; // Go up number_h_refinements levels up to find the coarse parent const Elem * coarse = elem; for (unsigned int j = 0; j != number_h_refinements; ++j) { libmesh_assert (coarse->parent()); coarse = coarse->parent(); } const dof_id_type e_id = coarse->id(); // Get the local to global degree of freedom maps for this element dof_map.dof_indices (elem, dof_indices); // We will have to manually do the dot products. Number local_contribution = 0.; for (unsigned int j=0; j != dof_indices.size(); j++) { // The contribution to the error indicator for this element from the current QoI local_contribution += (*localized_projected_residual)(dof_indices[j]) * (*localized_adjoint_solution)(dof_indices[j]); } // Multiply by the error weight for this QoI local_contribution *= error_weight; // FIXME: we're throwing away information in the // --enable-complex case error_per_cell[e_id] += static_cast<ErrorVectorReal> (std::abs(local_contribution)); } // End loop over elements } // End if belong to QoI set } // End loop over QoIs for (unsigned int j=0; j != system.qoi.size(); j++) { if (_qoi_set.has_index(j)) { delete coarse_adjoints[j]; } } // Don't bother projecting the solution; we'll restore from backup // after coarsening system.project_solution_on_reinit() = false; // Uniformly coarsen the mesh, without projecting the solution libmesh_assert (number_h_refinements > 0 || number_p_refinements > 0); for (unsigned int i = 0; i != number_h_refinements; ++i) { mesh_refinement.uniformly_coarsen(1); // FIXME - should the reinits here be necessary? - RHS es.reinit(); } for (unsigned int i = 0; i != number_p_refinements; ++i) { mesh_refinement.uniformly_p_coarsen(1); es.reinit(); } // We should be back where we started libmesh_assert_equal_to (n_coarse_elem, mesh.n_elem()); // Restore old solutions and clean up the heap system.project_solution_on_reinit() = old_projection_setting; // Restore the coarse solution vectors and delete their copies *system.solution = *coarse_solution; delete coarse_solution; *system.current_local_solution = *coarse_local_solution; delete coarse_local_solution; for (System::vectors_iterator vec = system.vectors_begin(); vec != system.vectors_end(); ++vec) { // The (string) name of this vector const std::string & var_name = vec->first; // If it's a vector we already had (and not a newly created // vector like an adjoint rhs), we need to restore it. std::map<std::string, NumericVector<Number> *>::iterator it = coarse_vectors.find(var_name); if (it != coarse_vectors.end()) { NumericVector<Number> * coarsevec = it->second; system.get_vector(var_name) = *coarsevec; coarsevec->clear(); delete coarsevec; } } // Restore old partitioner and renumbering settings mesh.partitioner().reset(old_partitioner.release()); mesh.allow_renumbering(old_renumbering_setting); // Fiinally sum the vector of estimated error values. this->reduce_error(error_per_cell, system.comm()); // We don't take a square root here; this is a goal-oriented // estimate not a Hilbert norm estimate. } // end estimate_error function
void AdjointResidualErrorEstimator::estimate_error (const System & _system, ErrorVector & error_per_cell, const NumericVector<Number> * solution_vector, bool estimate_parent_error) { LOG_SCOPE("estimate_error()", "AdjointResidualErrorEstimator"); // The current mesh const MeshBase & mesh = _system.get_mesh(); // Resize the error_per_cell vector to be // the number of elements, initialize it to 0. error_per_cell.resize (mesh.max_elem_id()); std::fill (error_per_cell.begin(), error_per_cell.end(), 0.); // Get the number of variables in the system unsigned int n_vars = _system.n_vars(); // We need to make a map of the pointer to the solution vector std::map<const System *, const NumericVector<Number> *>solutionvecs; solutionvecs[&_system] = _system.solution.get(); // Solve the dual problem if we have to if (!_system.is_adjoint_already_solved()) { // FIXME - we'll need to change a lot of APIs to make this trick // work with a const System... System & system = const_cast<System &>(_system); system.adjoint_solve(_qoi_set); } // Flag to check whether we have not been asked to weight the variable error contributions in any specific manner bool error_norm_is_identity = error_norm.is_identity(); // Create an ErrorMap/ErrorVector to store the primal, dual and total_dual variable errors ErrorMap primal_errors_per_cell; ErrorMap dual_errors_per_cell; ErrorMap total_dual_errors_per_cell; // Allocate ErrorVectors to this map if we're going to use it if (!error_norm_is_identity) for(unsigned int v = 0; v < n_vars; v++) { primal_errors_per_cell[std::make_pair(&_system, v)] = new ErrorVector; dual_errors_per_cell[std::make_pair(&_system, v)] = new ErrorVector; total_dual_errors_per_cell[std::make_pair(&_system, v)] = new ErrorVector; } ErrorVector primal_error_per_cell; ErrorVector dual_error_per_cell; ErrorVector total_dual_error_per_cell; // Have we been asked to weight the variable error contributions in any specific manner if(!error_norm_is_identity) // If we do { // Estimate the primal problem error for each variable _primal_error_estimator->estimate_errors (_system.get_equation_systems(), primal_errors_per_cell, &solutionvecs, estimate_parent_error); } else // If not { // Just get the combined error estimate _primal_error_estimator->estimate_error (_system, primal_error_per_cell, solution_vector, estimate_parent_error); } // Sum and weight the dual error estimate based on our QoISet for (unsigned int i = 0; i != _system.qoi.size(); ++i) { if (_qoi_set.has_index(i)) { // Get the weight for the current QoI Real error_weight = _qoi_set.weight(i); // We need to make a map of the pointer to the adjoint solution vector std::map<const System *, const NumericVector<Number> *>adjointsolutionvecs; adjointsolutionvecs[&_system] = &_system.get_adjoint_solution(i); // Have we been asked to weight the variable error contributions in any specific manner if(!error_norm_is_identity) // If we have { _dual_error_estimator->estimate_errors (_system.get_equation_systems(), dual_errors_per_cell, &adjointsolutionvecs, estimate_parent_error); } else // If not { // Just get the combined error estimate _dual_error_estimator->estimate_error (_system, dual_error_per_cell, &(_system.get_adjoint_solution(i)), estimate_parent_error); } std::size_t error_size; // Get the size of the first ErrorMap vector; this will give us the number of elements if(!error_norm_is_identity) // If in non default weights case { error_size = dual_errors_per_cell[std::make_pair(&_system, 0)]->size(); } else // If in the standard default weights case { error_size = dual_error_per_cell.size(); } // Resize the ErrorVector(s) if(!error_norm_is_identity) { // Loop over variables for(unsigned int v = 0; v < n_vars; v++) { libmesh_assert(!total_dual_errors_per_cell[std::make_pair(&_system, v)]->size() || total_dual_errors_per_cell[std::make_pair(&_system, v)]->size() == error_size) ; total_dual_errors_per_cell[std::make_pair(&_system, v)]->resize(error_size); } } else { libmesh_assert(!total_dual_error_per_cell.size() || total_dual_error_per_cell.size() == error_size); total_dual_error_per_cell.resize(error_size); } for (std::size_t e = 0; e != error_size; ++e) { // Have we been asked to weight the variable error contributions in any specific manner if(!error_norm_is_identity) // If we have { // Loop over variables for(unsigned int v = 0; v < n_vars; v++) { // Now fill in total_dual_error ErrorMap with the weight (*total_dual_errors_per_cell[std::make_pair(&_system, v)])[e] += static_cast<ErrorVectorReal> (error_weight * (*dual_errors_per_cell[std::make_pair(&_system, v)])[e]); } } else // If not { total_dual_error_per_cell[e] += static_cast<ErrorVectorReal>(error_weight * dual_error_per_cell[e]); } } } } // Do some debugging plots if requested if (!error_plot_suffix.empty()) { if(!error_norm_is_identity) // If we have { // Loop over variables for(unsigned int v = 0; v < n_vars; v++) { std::ostringstream primal_out; std::ostringstream dual_out; primal_out << "primal_" << error_plot_suffix << "."; dual_out << "dual_" << error_plot_suffix << "."; primal_out << std::setw(1) << std::setprecision(0) << std::setfill('0') << std::right << v; dual_out << std::setw(1) << std::setprecision(0) << std::setfill('0') << std::right << v; (*primal_errors_per_cell[std::make_pair(&_system, v)]).plot_error(primal_out.str(), _system.get_mesh()); (*total_dual_errors_per_cell[std::make_pair(&_system, v)]).plot_error(dual_out.str(), _system.get_mesh()); primal_out.clear(); dual_out.clear(); } } else // If not { std::ostringstream primal_out; std::ostringstream dual_out; primal_out << "primal_" << error_plot_suffix ; dual_out << "dual_" << error_plot_suffix ; primal_error_per_cell.plot_error(primal_out.str(), _system.get_mesh()); total_dual_error_per_cell.plot_error(dual_out.str(), _system.get_mesh()); primal_out.clear(); dual_out.clear(); } } // Weight the primal error by the dual error using the system norm object // FIXME: we ought to thread this for (unsigned int i=0; i != error_per_cell.size(); ++i) { // Have we been asked to weight the variable error contributions in any specific manner if(!error_norm_is_identity) // If we do { // Create Error Vectors to pass to calculate_norm std::vector<Real> cell_primal_error; std::vector<Real> cell_dual_error; for(unsigned int v = 0; v < n_vars; v++) { cell_primal_error.push_back((*primal_errors_per_cell[std::make_pair(&_system, v)])[i]); cell_dual_error.push_back((*total_dual_errors_per_cell[std::make_pair(&_system, v)])[i]); } error_per_cell[i] = static_cast<ErrorVectorReal> (error_norm.calculate_norm(cell_primal_error, cell_dual_error)); } else // If not { error_per_cell[i] = primal_error_per_cell[i]*total_dual_error_per_cell[i]; } } // Deallocate the ErrorMap contents if we allocated them earlier if (!error_norm_is_identity) for(unsigned int v = 0; v < n_vars; v++) { delete primal_errors_per_cell[std::make_pair(&_system, v)]; delete dual_errors_per_cell[std::make_pair(&_system, v)]; delete total_dual_errors_per_cell[std::make_pair(&_system, v)]; } }