// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #include "../../config/config.hpp" #ifdef MFEM_USE_MPI #include "pmortarassembler.hpp" #include "transferutils.hpp" #include "cut.hpp" #include "moonolith_bounding_volume_with_span.hpp" #include "moonolith_n_tree_mutator_factory.hpp" #include "moonolith_n_tree_with_span_mutator_factory.hpp" #include "moonolith_n_tree_with_tags_mutator_factory.hpp" #include "moonolith_profiler.hpp" #include "moonolith_redistribute.hpp" #include "moonolith_sparse_matrix.hpp" #include "moonolith_tree.hpp" #include "par_moonolith.hpp" #include using namespace mfem::internal; namespace mfem { struct ParMortarAssembler::Impl { public: MPI_Comm comm; std::shared_ptr source; std::shared_ptr destination; std::vector> integrators; std::shared_ptr coupling_matrix; std::shared_ptr mass_matrix; std::shared_ptr solver; bool verbose{false}; bool assemble_mass_and_coupling_together{true}; void ensure_solver() { if(!solver) { solver = std::make_shared(comm); solver->SetMaxIter(20000); solver->SetRelTol(1e-6); } } bool is_vector_fe() const { bool is_vector_fe = false; for (auto i_ptr : integrators) { if (i_ptr->is_vector_fe()) { is_vector_fe = true; break; } } return is_vector_fe; } BilinearFormIntegrator * new_mass_integrator() const { if (is_vector_fe()) { return new VectorFEMassIntegrator(); } else { return new MassIntegrator(); } } }; ParMortarAssembler::~ParMortarAssembler() = default; void ParMortarAssembler::SetSolver(const std::shared_ptr &solver) { impl_->solver = solver; } void ParMortarAssembler::SetAssembleMassAndCouplingTogether(const bool value) { impl_->assemble_mass_and_coupling_together = value; } void ParMortarAssembler::SetMaxSolverIterations(const int max_solver_iterations) { impl_->ensure_solver(); impl_->solver->SetMaxIter(max_solver_iterations); } void ParMortarAssembler::AddMortarIntegrator( const std::shared_ptr &integrator) { impl_->integrators.push_back(integrator); } void ParMortarAssembler::SetVerbose(const bool verbose) { impl_->verbose = verbose; } template class ElementAdapter : public moonolith::Serializable { public: using Bound = moonolith::AABBWithKDOPSpan; using Point = moonolith::Vector; inline int tag() const { return tag_; } const Bound &bound() const { return bound_; } Bound &bound() { return bound_; } void applyRW(moonolith::Stream &stream) { stream &bound_; stream &element_; stream &element_handle_; } ElementAdapter(FiniteElementSpace &fe, const long element, const long element_handle, const int tag) : fe_(&fe), element_(element), element_handle_(element_handle), tag_(tag), dof_map_(nullptr) { assert(element < fe.GetNE()); DenseMatrix pts; fe_->GetMesh()->GetPointMatrix(element, pts); Point p; for (int j = 0; j < pts.Width(); ++j) { for (int i = 0; i < pts.Height(); ++i) { p[i] = pts.Elem(i, j); } bound_.static_bound() += p; bound_.dynamic_bound() += p; } } ElementAdapter() : fe_(nullptr), element_(-1), element_handle_(-1), tag_(-1), dof_map_(nullptr) {} inline long handle() const { return element_handle_; } inline long element() const { return element_; } inline const FiniteElement &get() const { assert(fe_); assert(element_ < fe_->GetNE()); return *fe_->GetFE(element_); } inline const FiniteElementSpace &space() const { assert(fe_); return *fe_; } void set_dof_map(std::vector *ptr) { dof_map_ = ptr; } void get_elements_vdofs(Array &vdofs) const { fe_->GetElementVDofs(element_, vdofs); if (dof_map_) { assert(dof_map_->size() == vdofs.Size()); for (int i = 0; i < vdofs.Size(); ++i) { vdofs[i] = dof_map_->at(i); } } else { assert(false); } } private: FiniteElementSpace *fe_; long element_; long element_handle_; int tag_; Bound bound_; std::vector *dof_map_; }; template class TreeTraits { public: enum { Dimension = _Dimension }; using Bound = moonolith::AABBWithKDOPSpan; using DataType = mfem::ElementAdapter; }; template class MFEMTree : public moonolith::Tree> { public: using Traits = mfem::TreeTraits; MFEMTree() {}; static std::shared_ptr New(const int maxElementsXNode = moonolith::DEFAULT_REFINE_MAX_ELEMENTS, const int maxDepth = moonolith::DEFAULT_REFINE_DEPTH) { using namespace moonolith; std::shared_ptr tree = std::make_shared(); std::shared_ptr> factory = std::make_shared>(); factory->set_refine_params(maxElementsXNode, maxDepth); tree->set_mutator_factory(factory); return tree; } static std::shared_ptr New(const std::shared_ptr &predicate, const int maxElementsXNode = moonolith::DEFAULT_REFINE_MAX_ELEMENTS, const int maxDepth = moonolith::DEFAULT_REFINE_DEPTH) { using namespace moonolith; if (!predicate) { return New(maxElementsXNode, maxDepth); } std::shared_ptr tree = std::make_shared(); std::shared_ptr> factory = std::make_shared>(predicate); factory->set_refine_params(maxElementsXNode, maxDepth); tree->set_mutator_factory(factory); return tree; } }; class ElementDofMap : public moonolith::Serializable { public: void read(moonolith::InputStream &is) override { int n; is >> n; global.resize(n); is.read(&global[0], n); } void write(moonolith::OutputStream &os) const override { int n = global.size(); os << n; os.write(&global[0], n); } std::vector global; }; class Spaces { public: explicit Spaces(const moonolith::Communicator &comm) : comm(comm) { must_destroy_attached[0] = false; must_destroy_attached[1] = false; } Spaces(const std::shared_ptr &source, const std::shared_ptr &destination) { spaces_.reserve(2); spaces_.push_back(source); spaces_.push_back(destination); must_destroy_attached[0] = false; must_destroy_attached[1] = false; copy_global_dofs(*source, dof_maps_[0]); copy_global_dofs(*destination, dof_maps_[1]); } ~Spaces() { Mesh *m = nullptr; FiniteElementCollection *fec = nullptr; for (int i = 0; i < spaces_.size(); ++i) { if (spaces_[i] && must_destroy_attached[0]) { m = spaces_[i]->GetMesh(); fec = const_cast(spaces_[i]->FEColl()); // make it null spaces_[i] = std::shared_ptr(); delete m; delete fec; } } } inline long n_elements() const { long ret = 0; for (auto s : spaces_) { if (s) { ret += s->GetNE(); } } return ret; } inline std::vector> &spaces() { return spaces_; } inline const std::vector> & spaces() const { return spaces_; } inline std::vector &dof_map(const int i) { assert(i < 2); assert(i >= 0); return dof_maps_[i]; } inline const std::vector &dof_map(const int i) const { assert(i < 2); assert(i >= 0); return dof_maps_[i]; } inline void set_must_destroy_attached(const int index, const bool value) { assert(index < 2); assert(index >= 0); must_destroy_attached[index] = value; } private: std::vector> spaces_; moonolith::Communicator comm; std::vector dof_maps_[2]; bool must_destroy_attached[2]; inline static void copy_global_dofs(ParFiniteElementSpace &fe, std::vector &dof_map) { dof_map.resize(fe.GetNE()); Array vdofs; for (int i = 0; i < fe.GetNE(); ++i) { fe.GetElementVDofs(i, vdofs); for (int k = 0; k < vdofs.Size(); ++k) { long g_dof = 0; if (vdofs[k] >= 0) { g_dof = fe.GetGlobalTDofNumber(vdofs[k]); } else { g_dof = -1 - fe.GetGlobalTDofNumber(-1 - vdofs[k]); } dof_map[i].global.push_back(g_dof); } } } }; template static void write_space(const Iterator &begin, const Iterator &end, FiniteElementSpace &space, const std::vector &dof_map, const int role, moonolith::OutputStream &os) { const int dim = space.GetMesh()->Dimension(); const long n_elements = std::distance(begin, end); std::set nodeIds; std::map mapping; Array verts; for (Iterator it = begin; it != end; ++it) { const int i = *it; space.GetElementVertices(i, verts); for (int j = 0; j < verts.Size(); ++j) { nodeIds.insert(verts[j]); } } long n_nodes = nodeIds.size(); // Estimate for allocation os.request_space((n_elements * 8 + n_nodes * dim) * (sizeof(double) + sizeof(long))); auto fe_coll = space.FEColl(); const char *name = fe_coll->Name(); const int name_lenght = strlen(name); // WRITE 1 os << dim << role; os << name_lenght; os.write(name, name_lenght); long index = 0; for (auto nodeId : nodeIds) { mapping[nodeId] = index++; } // WRITE 2 os << n_nodes; // WRITE 6 os << n_elements; Array vdofs; for (auto node_id : nodeIds) { double *v = space.GetMesh()->GetVertex(node_id); for (int i = 0; i < dim; ++i) { // WRITE 3 os << v[i]; } } for (Iterator it = begin; it != end; ++it) { const int k = *it; space.GetElementVertices(k, verts); const int attribute = space.GetAttribute(k); const int e_n_nodes = verts.Size(); const int type = space.GetElementType(k); const int order = space.GetOrder(k); // WRITE 7 os << type << attribute << order << e_n_nodes; for (int i = 0; i < e_n_nodes; ++i) { auto it = mapping.find(verts[i]); assert(it != mapping.end()); int index = it->second; // WRITE 8 os << index; } // WRITE 9 os << dof_map.at(k); } } template static void write_element_selection(const Iterator &begin, const Iterator &end, const Spaces &spaces, moonolith::OutputStream &os) { if (spaces.spaces().empty()) { assert(false); return; } auto m = spaces.spaces()[0]; std::shared_ptr s = nullptr; if (spaces.spaces().size() > 1) { s = spaces.spaces()[1]; } std::vector source_selection; std::vector destination_selection; bool met_destination_selection = false; for (Iterator it = begin; it != end; ++it) { long index = *it; if (m && index >= m->GetNE()) { index -= m->GetNE(); destination_selection.push_back(index); } else if (!m) { met_destination_selection = true; destination_selection.push_back(index); } else { assert(!met_destination_selection); assert(index < m->GetNE()); source_selection.push_back(index); } } const bool has_source = !source_selection.empty(); const bool has_destination = !destination_selection.empty(); os << has_source << has_destination; if (has_source) { write_space(source_selection.begin(), source_selection.end(), *m, spaces.dof_map(0), 0, os); } if (has_destination) { write_space(destination_selection.begin(), destination_selection.end(), *s, spaces.dof_map(1), 1, os); } } static FiniteElementCollection *FECollFromName(const std::string &comp_name) { return FiniteElementCollection::New(comp_name.c_str()); } static void read_space(moonolith::InputStream &is, std::shared_ptr &space, std::vector &dof_map) { using namespace std; // READ 1 int dim, role, name_lenght; is >> dim >> role; is >> name_lenght; std::string name(name_lenght, 0); is.read(&name[0], name_lenght); // READ 2 long n_nodes; is >> n_nodes; // READ 6 long n_elements; is >> n_elements; auto fe_coll = FECollFromName(name); auto mesh_ptr = new Mesh(dim, n_nodes, n_elements); for (long i = 0; i < n_nodes; ++i) { double v[3]; for (int i = 0; i < dim; ++i) { // READ 3 is >> v[i]; } mesh_ptr->AddVertex(v); } dof_map.resize(n_elements); std::vector e2v; for (long i = 0; i < n_elements; ++i) { // READ 7 int type, attribute, order, e_n_nodes; is >> type >> attribute >> order >> e_n_nodes; e2v.resize(e_n_nodes); int index; for (int i = 0; i < e_n_nodes; ++i) { // READ 8 is >> index; e2v[i] = index; } mesh_ptr->AddElement(NewElem(type, &e2v[0], attribute)); // READ 9 is >> dof_map.at(i); } // if(mesh_ptr->Dimension() == 3) { Finalize(*mesh_ptr, true); // } space = make_shared(mesh_ptr, fe_coll); } static void read_spaces(moonolith::InputStream &is, Spaces &spaces) { bool has_source, has_destination; is >> has_source >> has_destination; spaces.spaces().resize(2); if (has_source) { read_space(is, spaces.spaces()[0], spaces.dof_map(0)); spaces.set_must_destroy_attached(0, true); } else { spaces.spaces()[0] = nullptr; } if (has_destination) { read_space(is, spaces.spaces()[1], spaces.dof_map(1)); spaces.set_must_destroy_attached(1, true); } else { spaces.spaces()[1] = nullptr; } } template static bool Assemble(moonolith::Communicator &comm, std::shared_ptr &source, std::shared_ptr &destination, Fun process_fun, const moonolith::SearchSettings &settings, const bool verbose) { using namespace moonolith; typedef mfem::MFEMTree NTreeT; typedef typename NTreeT::DataContainer DataContainer; typedef typename NTreeT::DataType Adapter; long maxNElements = settings.max_elements; long maxDepth = settings.max_depth; const int n_elements_source = source->GetNE(); const int n_elements_destination = destination->GetNE(); const int n_elements = n_elements_source + n_elements_destination; auto predicate = std::make_shared(); predicate->add(0, 1); MOONOLITH_EVENT_BEGIN("create_adapters"); std::shared_ptr tree = NTreeT::New(predicate, maxNElements, maxDepth); tree->reserve(n_elements); std::shared_ptr local_spaces = std::make_shared(source, destination); int offset = 0; int space_num = 0; for (auto s : local_spaces->spaces()) { if (s) { for (int i = 0; i < s->GetNE(); ++i) { Adapter a(*s, i, offset + i, space_num); a.set_dof_map(&local_spaces->dof_map(space_num)[i].global); tree->insert(a); } offset += s->GetNE(); } ++space_num; } tree->root()->bound().static_bound().enlarge(1e-6); MOONOLITH_EVENT_END("create_adapters"); // Just to have an indexed-storage std::map> spaces; std::map>> migrated_spaces; auto read = [&spaces, &migrated_spaces, comm](const long ownerrank, const long /*senderrank*/, bool is_forwarding, DataContainer &data, InputStream &in) { CHECK_STREAM_READ_BEGIN("vol_proj", in); std::shared_ptr proc_space = std::make_shared(comm); read_spaces(in, *proc_space); if (!is_forwarding) { assert(!spaces[ownerrank]); spaces[ownerrank] = proc_space; } else { migrated_spaces[ownerrank].push_back(proc_space); } data.reserve(data.size() + proc_space->n_elements()); int space_num = 0; long offset = 0; for (auto s : proc_space->spaces()) { if (s) { for (int i = 0; i < s->GetNE(); ++i) { data.push_back(Adapter(*s, i, offset + i, space_num)); data.back().set_dof_map(&proc_space->dof_map(space_num)[i].global); } offset += s->GetNE(); } ++space_num; } CHECK_STREAM_READ_END("vol_proj", in); }; auto write = [&local_spaces, &spaces, &comm](const long ownerrank, const long /*recvrank*/, const std::vector::const_iterator &begin, const std::vector::const_iterator &end, const DataContainer &/*data*/, OutputStream &out) { CHECK_STREAM_WRITE_BEGIN("vol_proj", out); if (ownerrank == comm.rank()) { write_element_selection(begin, end, *local_spaces, out); } else { auto it = spaces.find(ownerrank); assert(it != spaces.end()); std::shared_ptr spaceptr = it->second; assert(std::distance(begin, end) > 0); write_element_selection(begin, end, *spaceptr, out); } CHECK_STREAM_WRITE_END("vol_proj", out); }; long n_false_positives = 0, n_intersections = 0; auto fun = [&n_false_positives, &n_intersections, &process_fun](Adapter &source, Adapter &destination) -> bool { bool ok = process_fun(source, destination); if (ok) { n_intersections++; return true; } else { n_false_positives++; return false; } return true; }; moonolith::search_and_compute(comm, tree, predicate, read, write, fun, settings); if (verbose) { long n_total_candidates = n_intersections + n_false_positives; long n_collection[3] = {n_intersections, n_total_candidates, n_false_positives }; comm.all_reduce(n_collection, 3, moonolith::MPISum()); if (comm.is_root()) { mfem::out << "n_intersections: " << n_collection[0] << ", n_total_candidates: " << n_collection[1] << ", n_false_positives: " << n_collection[2] << std::endl; } } return true; } static void add_matrix(const Array &destination_vdofs, const Array &source_vdofs, const DenseMatrix &elem_mat, moonolith::SparseMatrix &mat_buffer) { for (int i = 0; i < destination_vdofs.Size(); ++i) { long dof_I = destination_vdofs[i]; double sign_I = 1.0; if (dof_I < 0) { sign_I = -1.0; dof_I = -dof_I - 1; } for (int j = 0; j < source_vdofs.Size(); ++j) { long dof_J = source_vdofs[j]; double sign_J = sign_I; if (dof_J < 0) { sign_J = -sign_I; dof_J = -dof_J - 1; } mat_buffer.add(dof_I, dof_J, sign_J * elem_mat.Elem(i, j)); } } } std::shared_ptr convert_to_hypre_matrix( const std::vector &destination_ranges, HYPRE_BigInt *s_offsets, HYPRE_BigInt *m_offsets, moonolith::SparseMatrix &mat_buffer) { auto && comm = mat_buffer.comm(); moonolith::Redistribute> redist(comm.get()); redist.apply(destination_ranges, mat_buffer, moonolith::AddAssign()); std::vector I(s_offsets[1] - s_offsets[0] + 1); I[0] = 0; std::vector J; std::vector data; J.reserve(mat_buffer.n_local_entries()); data.reserve(J.size()); for (auto it = mat_buffer.iter(); it; ++it) { I[it.row() - s_offsets[0] + 1]++; J.push_back(it.col()); data.push_back(*it); } for (int i = 1; i < I.size(); ++i) { I[i] += I[i - 1]; } return std::make_shared( comm.get(), s_offsets[1] - s_offsets[0], mat_buffer.rows(), mat_buffer.cols(), &I[0], &J[0], &data[0], s_offsets, m_offsets); } int order_multiplier(const Geometry::Type type, const int dim) { return (type == Geometry::TRIANGLE || type == Geometry::TETRAHEDRON || type == Geometry::SEGMENT)? 1 : dim; } template static bool Assemble(moonolith::Communicator &comm, ParMortarAssembler::Impl &impl, const moonolith::SearchSettings &settings, const bool verbose) { auto &source = impl.source; auto &destination = impl.destination; auto &integrators = impl.integrators; auto &pmat = impl.coupling_matrix; bool lump_mass = false; int max_q_order = 0; for (auto i_ptr : integrators) { max_q_order = std::max(i_ptr->GetQuadratureOrder(), max_q_order); } const int dim = source->GetMesh()->Dimension(); std::shared_ptr cut = NewCut(dim); if (!cut) { assert(false && "NOT Supported!"); return false; } Array source_vdofs, destination_vdofs; DenseMatrix elemmat; DenseMatrix cumulative_elemmat; IntegrationRule src_ir; IntegrationRule dest_ir; double local_element_matrices_sum = 0.0; const auto m_global_n_dofs = source->GlobalVSize(); auto *m_offsets = source->GetDofOffsets(); const auto s_global_n_dofs = destination->GlobalVSize(); auto *s_offsets = destination->GetDofOffsets(); moonolith::SparseMatrix mat_buffer(comm); mat_buffer.set_size(s_global_n_dofs, m_global_n_dofs); moonolith::SparseMatrix mass_mat_buffer(comm); mass_mat_buffer.set_size(s_global_n_dofs, s_global_n_dofs); std::unique_ptr mass_integr(impl.new_mass_integrator()); auto fun = [&](const ElementAdapter &source, const ElementAdapter &destination) -> bool { const auto &src = source.space(); const auto &dest = destination.space(); const int src_index = source.element(); const int dest_index = destination.element(); auto &src_fe = *src.GetFE(src_index); auto &dest_fe = *dest.GetFE(dest_index); ElementTransformation &dest_Trans = *dest.GetElementTransformation(dest_index); // Quadrature order mangling int src_order_mult = order_multiplier(src_fe.GetGeomType(), Dimensions); int dest_order_mult = order_multiplier(dest_fe.GetGeomType(), Dimensions); const int src_order = src_order_mult * src_fe.GetOrder(); const int dest_order = dest_order_mult * dest_fe.GetOrder(); int contraction_order = src_order + dest_order; if(impl.assemble_mass_and_coupling_together) { contraction_order = std::max(contraction_order, 2 * dest_order); } const int order = contraction_order + dest_order_mult * dest_Trans.OrderW() + max_q_order; cut->SetIntegrationOrder(order); if (cut->BuildQuadrature(src, src_index, dest, dest_index, src_ir, dest_ir)) { // make reference quadratures ElementTransformation &src_Trans = *src.GetElementTransformation(src_index); source.get_elements_vdofs(source_vdofs); destination.get_elements_vdofs(destination_vdofs); bool first = true; for (auto i_ptr : integrators) { if (first) { i_ptr->AssembleElementMatrix(src_fe, src_ir, src_Trans, dest_fe, dest_ir, dest_Trans, cumulative_elemmat); first = false; } else { i_ptr->AssembleElementMatrix(src_fe, src_ir, src_Trans, dest_fe, dest_ir, dest_Trans, elemmat); cumulative_elemmat += elemmat; } } local_element_matrices_sum += Sum(cumulative_elemmat); add_matrix(destination_vdofs, source_vdofs, cumulative_elemmat, mat_buffer); if(impl.assemble_mass_and_coupling_together) { mass_integr->SetIntRule(&dest_ir); mass_integr->AssembleElementMatrix(dest_fe, dest_Trans, elemmat); if(lump_mass) { int n = destination_vdofs.Size(); for(int i = 0; i < n; ++i) { double row_sum = 0.; for(int j = 0; j < n; ++j) { row_sum += elemmat(i, j); elemmat(i, j) = 0.; } elemmat(i, i) = row_sum; } } add_matrix(destination_vdofs, destination_vdofs, elemmat, mass_mat_buffer); } return true; } else { return false; } }; if (!Assemble(comm, source, destination, fun, settings, verbose)) { return false; } if (verbose) { double volumes[2] = {local_element_matrices_sum}; comm.all_reduce(volumes, 2, moonolith::MPISum()); cut->Describe(); if (comm.is_root()) { mfem::out << "sum(B): " << volumes[0] << std::endl; } } std::vector destination_ranges(comm.size() + 1, 0); std::copy(s_offsets, s_offsets + 2, destination_ranges.begin() + comm.rank()); comm.all_reduce(&destination_ranges[0], destination_ranges.size(), moonolith::MPIMax()); pmat = convert_to_hypre_matrix( destination_ranges, s_offsets, m_offsets, mat_buffer); if(impl.assemble_mass_and_coupling_together) { impl.mass_matrix = convert_to_hypre_matrix(destination_ranges, s_offsets, s_offsets, mass_mat_buffer); } return true; } ParMortarAssembler::ParMortarAssembler( const std::shared_ptr &source, const std::shared_ptr &destination) : impl_(new Impl()) { impl_->comm = source->GetComm(); impl_->source = source; impl_->destination = destination; } bool ParMortarAssembler::Assemble(std::shared_ptr &pmat) { assert(!impl_->integrators.empty() && "it must have at least on integrator see class MortarIntegrator"); moonolith::SearchSettings settings; // settings.set("disable_redistribution", moonolith::Boolean(true)); // settings.set("disable_asynch", moonolith::Boolean(true)); bool ok = false; moonolith::Communicator comm(impl_->comm); if (impl_->source->GetMesh()->Dimension() == 2) { ok = mfem::Assemble<2>(comm, *impl_, settings, impl_->verbose); } if (impl_->source->GetMesh()->Dimension() == 3) { ok = mfem::Assemble<3>(comm, *impl_, settings, impl_->verbose); } pmat = impl_->coupling_matrix; return ok; } bool ParMortarAssembler::Transfer(const ParGridFunction &src_fun, ParGridFunction &dest_fun) { return Update() && Apply(src_fun, dest_fun); } bool ParMortarAssembler::Apply(const ParGridFunction &src_fun, ParGridFunction &dest_fun) { const bool verbose = impl_->verbose; if (!impl_->coupling_matrix) { if (!Update()) { return false; } } auto &B = *impl_->coupling_matrix; auto &D = *impl_->mass_matrix; auto &P_source = *impl_->source->Dof_TrueDof_Matrix(); auto &P_destination = *impl_->destination->Dof_TrueDof_Matrix(); impl_->ensure_solver(); // BlockILU prec(D); // impl_->solver->SetPreconditioner(prec); impl_->solver->SetOperator(D); if(verbose) { impl_->solver->SetPrintLevel(3); } Vector P_x_src_fun(B.Width()); P_source.MultTranspose(src_fun, P_x_src_fun); Vector B_x_src_fun(B.Height()); B_x_src_fun = 0.0; B.Mult(P_x_src_fun, B_x_src_fun); Vector R_x_dest_fun(D.Height()); R_x_dest_fun = 0.0; impl_->solver->Mult(B_x_src_fun, R_x_dest_fun); P_destination.Mult(R_x_dest_fun, dest_fun); dest_fun.Update(); // Sanity check! int converged = impl_->solver->GetFinalNorm() < 1e-5; MPI_Allreduce(MPI_IN_PLACE, &converged, 1, MPI_INT, MPI_MIN, impl_->comm); return converged; } bool ParMortarAssembler::Update() { using namespace std; const bool verbose = impl_->verbose; static const bool dof_transformation = true; moonolith::Communicator comm(impl_->comm); if (verbose) { moonolith::root_describe( "--------------------------------------------------------" "\nAssembly begin: ", comm, mfem::out); } if (!Assemble(impl_->coupling_matrix)) { return false; } if (verbose) { moonolith::root_describe( "\nAssembly end: " "--------------------------------------------------------", comm, mfem::out); } if (dof_transformation) { impl_->coupling_matrix.reset(RAP(impl_->destination->Dof_TrueDof_Matrix(), impl_->coupling_matrix.get(), impl_->source->Dof_TrueDof_Matrix())); } auto &B = *impl_->coupling_matrix; if(!impl_->assemble_mass_and_coupling_together) { ParBilinearForm b_form(impl_->destination.get()); b_form.AddDomainIntegrator(impl_->new_mass_integrator()); b_form.Assemble(); b_form.Finalize(); impl_->mass_matrix = shared_ptr(b_form.ParallelAssemble()); } comm.barrier(); if (verbose && comm.is_root()) { mfem::out << "P in R^(" << impl_->source->Dof_TrueDof_Matrix()->Height(); mfem::out << " x " << impl_->source->Dof_TrueDof_Matrix()->Width() << ")\n"; mfem::out << "Q in R^(" << impl_->destination->Dof_TrueDof_Matrix()->Height(); mfem::out << " x " << impl_->destination->Dof_TrueDof_Matrix()->Width() << ")\n"; } if (dof_transformation && impl_->assemble_mass_and_coupling_together) { impl_->mass_matrix.reset(RAP(impl_->destination->Dof_TrueDof_Matrix(), impl_->mass_matrix.get(), impl_->destination->Dof_TrueDof_Matrix())); } auto &D = *impl_->mass_matrix; comm.barrier(); if (verbose && comm.is_root()) { mfem::out << "--------------------------------------------------------" << std::endl; mfem::out << "B in R^(" << B.GetGlobalNumRows() << " x " << B.GetGlobalNumCols() << ")" << std::endl; mfem::out << "D in R^(" << D.GetGlobalNumRows() << " x " << D.GetGlobalNumCols() << ")" << std::endl; mfem::out << "--------------------------------------------------------" << std::endl; } comm.barrier(); if (verbose) { Vector v(D.Width()); v = 1.0; Vector Dv(D.Height()); D.Mult(v, Dv); double sum_Dv = Dv.Sum(); comm.all_reduce(&sum_Dv, 1, MPI_SUM); if (comm.is_root()) { mfem::out << "sum(D): " << sum_Dv << std::endl; } } return true; } } // namespace mfem #endif // MFEM_USE_MPI