// Copyright (c) 2010-2025, 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 "unit_tests.hpp" #include "mfem.hpp" #include "../../config/config.hpp" namespace mfem { #ifdef MFEM_USE_MPI class DenseMatrixSolver : public Solver { private: DenseMatrix A; LUFactors LU; int * ipiv = nullptr; public: DenseMatrixSolver() : Solver() {} void SetOperator(const Operator &op) override { auto Oph = const_cast(dynamic_cast (&op)); MFEM_VERIFY(Oph, "Not a compatible matrix type"); SparseMatrix Sp; Oph->MergeDiagAndOffd(Sp); Sp.ToDenseMatrix(A); delete [] ipiv; ipiv = new int[A.Height()]; LU.data = A.Data(); LU.ipiv = ipiv; LU.Factor(A.Height()); } void Mult(const Vector &x, Vector &y) const override { y = x; LU.Solve(A.Height(), 1, y.GetData()); } ~DenseMatrixSolver() { delete [] ipiv; } }; HypreParMatrix * GetProlongationMatrix(const ParFiniteElementSpace* pfes, int element_attribute) { Array dofs; Array tdofs; for (int i = 0; i < pfes->GetNE(); i++) { if (pfes->GetAttribute(i) == element_attribute) { pfes->GetElementVDofs(i, dofs); tdofs.Append(dofs); } } tdofs.Sort(); tdofs.Unique(); HYPRE_BigInt h = tdofs.Size(); SparseMatrix St(h,pfes->GlobalTrueVSize()); for (int i = 0; iGetTrueDofOffsets()[i]; } HYPRE_BigInt glob_nrows = nrows; HYPRE_BigInt glob_ncols = pfes->GlobalTrueVSize(); HYPRE_BigInt * J; #if !(defined(HYPRE_BIGINT) || defined(HYPRE_MIXEDINT)) J = St.GetJ(); #else J = new HYPRE_BigInt[St.NumNonZeroElems()]; std::copy(St.GetJ(), St.GetJ() + St.NumNonZeroElems(), J); #endif HypreParMatrix * Pt = new HypreParMatrix(pfes->GetComm(), nrows, glob_nrows, glob_ncols, St.GetI(), J, St.GetData(), rows,cols); HypreParMatrix * P = Pt->Transpose(); delete Pt; #if (defined(HYPRE_BIGINT) || defined(HYPRE_MIXEDINT)) delete [] J; #endif return P; } TEST_CASE("FilteredSolver and AMGFSolver", "[Parallel]") { // Note: This test is restricted to a single processor for convenience, // allowing the use of a serial dense direct solver on the filtered subspace // and avoiding any dependency on external parallel sparse direct solvers. // In general, both AMGFSolver and FilteredSolver are designed to work in parallel. if (Mpi::Root()) { MPI_Comm comm = MPI_COMM_SELF; auto ref_levels = 2; auto [order, eps, iteration_bound] = GENERATE(table( { {2, 1e-3, 12}, {2, 1e-4, 12}, {3, 1e-3, 18}, {3, 1e-4, 18} })); CAPTURE(order, eps); Mesh mesh = Mesh::MakeCartesian2D(3, 3, Element::QUADRILATERAL, 1.0, 1.0); mesh.EnsureNodes(); GridFunction *nodes = mesh.GetNodes(); (*nodes)[2] = 0.5*(1-eps); (*nodes)[10] = 0.5*(1-eps); (*nodes)[18] = 0.5*(1-eps); (*nodes)[26] = 0.5*(1-eps); (*nodes)[4] = 0.5*(1+eps); (*nodes)[12] = 0.5*(1+eps); (*nodes)[20] = 0.5*(1+eps); (*nodes)[28] = 0.5*(1+eps); mesh.SetAttribute(3, 2); mesh.SetAttribute(6, 2); mesh.SetAttribute(7, 2); mesh.SetAttributes(); int dim = mesh.Dimension(); ParMesh pmesh(comm, mesh); mesh.Clear(); for (int l = 0; l < ref_levels; l++) { pmesh.UniformRefinement(); } H1_FECollection fec(order, dim); ParFiniteElementSpace fespace(&pmesh, &fec); Array ess_bdr, ess_tdof_list; ess_bdr.SetSize(pmesh.bdr_attributes.Max()); ess_bdr = 1; fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); ParGridFunction x(&fespace); x = 0.0; ParLinearForm b(&fespace); ConstantCoefficient one(1.0); b.AddDomainIntegrator(new DomainLFIntegrator(one)); b.Assemble(); ParBilinearForm a(&fespace); ConstantCoefficient eps_cf(eps);; Vector vec(pmesh.attributes.Max()); vec(0) = 1.0; vec(1) = 1/eps; PWConstCoefficient eps_cg(vec); a.AddDomainIntegrator(new DiffusionIntegrator(eps_cg)); a.Assemble(); OperatorPtr A; Vector B, X; a.FormLinearSystem(ess_tdof_list, x, b, A, X, B); HypreParMatrix *P = GetProlongationMatrix(&fespace, 2); // 1st preconditioner: AMG HypreBoomerAMG amg; // 2nd preconditioner: AMGF AMGFSolver amgf; amg.SetPrintLevel(0); DenseMatrixSolver subspacesolver; amgf.GetAMG().SetPrintLevel(0); amgf.SetFilteredSubspaceSolver(subspacesolver); amgf.SetFilteredSubspaceTransferOperator(*P); // 3rd preconditioner: FilteredSolver FilteredSolver fs; fs.SetSolver(amg); fs.SetFilteredSubspaceTransferOperator(*P); fs.SetFilteredSubspaceSolver(subspacesolver); X = 0.0; Vector Xamgf(X); Vector Xfs(X); CGSolver cg(comm); cg.SetAbsTol(1e-16); cg.SetMaxIter(5000); cg.SetPrintLevel(3); cg.SetPreconditioner(amg); cg.SetOperator(*A); cg.Mult(B, X); cg.SetPreconditioner(amgf); cg.SetOperator(*A); cg.Mult(B, Xamgf); int amgf_iter = cg.GetNumIterations(); cg.SetPreconditioner(fs); cg.SetOperator(*A); cg.Mult(B, Xfs); int fs_iter = cg.GetNumIterations(); Xamgf -= X; REQUIRE(Xamgf.Norml2() == MFEM_Approx(0.0,1e-7)); Xfs -= X; REQUIRE(Xfs.Norml2() == MFEM_Approx(0.0,1e-7)); REQUIRE(amgf_iter == fs_iter); REQUIRE(amgf_iter <= iteration_bound); REQUIRE(fs_iter <= iteration_bound); delete P; } } #endif } // namespace mfem