// 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 "mortarassembler.hpp" #include "../../general/tic_toc.hpp" #include "cut.hpp" #include "transferutils.hpp" #include // Moonolith includes #include "moonolith_aabb.hpp" #include "moonolith_serial_hash_grid.hpp" #include "moonolith_stream_utils.hpp" #include "par_moonolith_config.hpp" using namespace mfem::internal; namespace mfem { struct MortarAssembler::Impl { public: std::shared_ptr source; std::shared_ptr destination; std::vector> integrators; std::shared_ptr coupling_matrix; std::shared_ptr mass_matrix; bool verbose{false}; bool assemble_mass_and_coupling_together{true}; int max_solver_iterations{400}; 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(); } } }; MortarAssembler::~MortarAssembler() = default; void MortarAssembler::SetAssembleMassAndCouplingTogether(const bool value) { impl_->assemble_mass_and_coupling_together = value; } void MortarAssembler::SetMaxSolverIterations(const int max_solver_iterations) { impl_->max_solver_iterations = max_solver_iterations; } void MortarAssembler::AddMortarIntegrator( const std::shared_ptr &integrator) { impl_->integrators.push_back(integrator); } void MortarAssembler::SetVerbose(const bool verbose) { impl_->verbose = verbose; } template void BuildBoxes(const Mesh &mesh, std::vector<::moonolith::AABB> &element_boxes) { #ifndef NDEBUG const int dim = mesh.Dimension(); assert(dim == Dim); #endif element_boxes.resize(mesh.GetNE()); DenseMatrix pts; for (int i = 0; i < mesh.GetNE(); ++i) { mesh.GetPointMatrix(i, pts); MinCol(pts, &element_boxes[i].min_[0], false); MaxCol(pts, &element_boxes[i].max_[0], false); } } bool HashGridDetectIntersections(const Mesh &src, const Mesh &dest, std::vector &pairs) { const int dim = dest.Dimension(); switch (dim) { case 1: { std::vector<::moonolith::AABB<1, double>> src_boxes, dest_boxes; BuildBoxes(src, src_boxes); BuildBoxes(dest, dest_boxes); ::moonolith::SerialHashGrid<1, double> grid; return grid.detect(src_boxes, dest_boxes, pairs); } case 2: { std::vector<::moonolith::AABB<2, double>> src_boxes, dest_boxes; BuildBoxes(src, src_boxes); BuildBoxes(dest, dest_boxes); ::moonolith::SerialHashGrid<2, double> grid; return grid.detect(src_boxes, dest_boxes, pairs); } case 3: { std::vector<::moonolith::AABB<3, double>> src_boxes, dest_boxes; BuildBoxes(src, src_boxes); BuildBoxes(dest, dest_boxes); ::moonolith::SerialHashGrid<3, double> grid; return grid.detect(src_boxes, dest_boxes, pairs); } default: { assert(false); return false; } } } MortarAssembler::MortarAssembler( const std::shared_ptr &source, const std::shared_ptr &destination) : impl_(new Impl()) { impl_->source = source; impl_->destination = destination; } int order_multiplier(const Geometry::Type type, const int dim) { return (type == Geometry::TRIANGLE || type == Geometry::TETRAHEDRON || type == Geometry::SEGMENT)? 1 : dim; } bool MortarAssembler::Assemble(std::shared_ptr &B) { using namespace std; const bool verbose = impl_->verbose; const auto &source_mesh = *impl_->source->GetMesh(); const auto &destination_mesh = *impl_->destination->GetMesh(); int dim = source_mesh.Dimension(); std::vector<::moonolith::Integer> pairs; if (!HashGridDetectIntersections(source_mesh, destination_mesh, pairs)) { return false; } std::shared_ptr cut = NewCut(dim); if (!cut) { assert(false && "NOT Supported!"); return false; } IntegrationRule source_ir; IntegrationRule destination_ir; int skip_zeros = 1; B = make_shared(impl_->destination->GetNDofs(), impl_->source->GetNDofs()); std::unique_ptr mass_integr(impl_->new_mass_integrator()); if(impl_->assemble_mass_and_coupling_together) { impl_->mass_matrix = make_shared(impl_->destination->GetNDofs(), impl_->destination->GetNDofs()); } Array source_vdofs, destination_vdofs; DenseMatrix elemmat; DenseMatrix cumulative_elemmat; double local_element_matrices_sum = 0.0; long n_intersections = 0; long n_candidates = 0; int max_q_order = 0; for (auto i_ptr : impl_->integrators) { max_q_order = std::max(i_ptr->GetQuadratureOrder(), max_q_order); } bool intersected = false; for (auto it = begin(pairs); it != end(pairs); /* inside */) { const int source_index = *it++; const int destination_index = *it++; auto &source_fe = *impl_->source->GetFE(source_index); auto &destination_fe = *impl_->destination->GetFE(destination_index); ElementTransformation &destination_Trans = *impl_->destination->GetElementTransformation(destination_index); // Quadrature order mangling int src_order_mult = order_multiplier(source_fe.GetGeomType(), dim); int dest_order_mult = order_multiplier(destination_fe.GetGeomType(), dim); const int src_order = src_order_mult * source_fe.GetOrder(); const int dest_order = dest_order_mult * destination_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 * destination_Trans.OrderW() + max_q_order; // Update the quadrature rule in case it changed the order cut->SetIntegrationOrder(order); n_candidates++; if (cut->BuildQuadrature(*impl_->source, source_index, *impl_->destination, destination_index, source_ir, destination_ir)) { impl_->source->GetElementVDofs(source_index, source_vdofs); impl_->destination->GetElementVDofs(destination_index, destination_vdofs); ElementTransformation &source_Trans = *impl_->source->GetElementTransformation(source_index); bool first = true; for (auto i_ptr : impl_->integrators) { if (first) { i_ptr->AssembleElementMatrix(source_fe, source_ir, source_Trans, destination_fe, destination_ir, destination_Trans, cumulative_elemmat); first = false; } else { i_ptr->AssembleElementMatrix(source_fe, source_ir, source_Trans, destination_fe, destination_ir, destination_Trans, elemmat); cumulative_elemmat += elemmat; } } local_element_matrices_sum += Sum(cumulative_elemmat); B->AddSubMatrix(destination_vdofs, source_vdofs, cumulative_elemmat, skip_zeros); if(impl_->assemble_mass_and_coupling_together) { mass_integr->SetIntRule(&destination_ir); mass_integr->AssembleElementMatrix(destination_fe, destination_Trans, elemmat); impl_->mass_matrix->AddSubMatrix(destination_vdofs, destination_vdofs, elemmat, skip_zeros); } intersected = true; ++n_intersections; } } if (!intersected) { return false; } B->Finalize(); if(impl_->assemble_mass_and_coupling_together) { impl_->mass_matrix->Finalize(); } if (verbose) { mfem::out << "local_element_matrices_sum: " << local_element_matrices_sum << std::endl; mfem::out << "B in R^(" << B->Height() << " x " << B->Width() << ")" << std::endl; mfem::out << "n_intersections: " << n_intersections << ", n_candidates: " << n_candidates << '\n'; cut->Describe(); } return true; } bool MortarAssembler::Transfer(const GridFunction &src_fun, GridFunction &dest_fun) { return Update() && Apply(src_fun, dest_fun); } bool MortarAssembler::Apply(const GridFunction &src_fun, GridFunction &dest_fun) { if (!impl_->coupling_matrix) { if (!Update()) { return false; } } Vector temp(impl_->coupling_matrix->Height()); impl_->coupling_matrix->Mult(src_fun, temp); CGSolver Dinv; Dinv.SetMaxIter(impl_->max_solver_iterations); if(impl_->verbose) { Dinv.SetPrintLevel(3); } Dinv.SetOperator(*impl_->mass_matrix); Dinv.SetRelTol(1e-6); Dinv.SetMaxIter(80); Dinv.Mult(temp, dest_fun); return true; } bool MortarAssembler::Update() { using namespace std; const bool verbose = impl_->verbose; StopWatch chrono; if (verbose) { mfem::out << "\nAssembling coupling operator..." << endl; } chrono.Start(); if (!Assemble(impl_->coupling_matrix)) { return false; } chrono.Stop(); if (verbose) { mfem::out << "Done. time: "; mfem::out << chrono.RealTime() << " seconds" << endl; } if(!impl_->assemble_mass_and_coupling_together) { BilinearForm b_form(impl_->destination.get()); b_form.AddDomainIntegrator(impl_->new_mass_integrator()); b_form.Assemble(); b_form.Finalize(); impl_->mass_matrix = std::shared_ptr(b_form.LoseMat()); } if (verbose) { Vector brs(impl_->coupling_matrix->Height()); impl_->coupling_matrix->GetRowSums(brs); Vector drs(impl_->mass_matrix->Height()); impl_->mass_matrix->GetRowSums(drs); mfem::out << "sum(B): " << brs.Sum() << std::endl; mfem::out << "sum(D): " << drs.Sum() << std::endl; } return true; } } // namespace mfem