Poisson Equation with MFEM + Ginkgo#

Solves the Laplace equation $-\Delta u = 1$ with homogeneous Dirichlet boundary conditions using MFEM for finite element assembly and Ginkgo for the linear solve.

Based on MFEM Example 1.

Integration Overview#

MFEM’s SparseMatrix stores CSR data directly. After calling Finalize(), the raw arrays are accessible via GetI(), GetJ(), and GetData(). These are wrapped into a gko::matrix::Csr using gko::array::view for zero-copy interoperability.

Step-by-Step#

1. Set Up Mesh and Finite Element Space#

    // Load mesh from file, or generate a default square mesh.
    Mesh* mesh;
    if (std::string(mesh_file).empty())
        mesh = new Mesh(Mesh::MakeCartesian2D(16, 16, Element::QUADRILATERAL));
    else
        mesh = new Mesh(mesh_file, 1, 1);

    int dim = mesh->Dimension();
    for (int l = 0; l < ref_levels; l++) mesh->UniformRefinement();

    std::cout << "Number of elements: " << mesh->GetNE() << std::endl;
    H1_FECollection fec(order, dim);
    FiniteElementSpace fespace(mesh, &fec);
    std::cout << "Number of unknowns: " << fespace.GetTrueVSize() << std::endl;

2. Assemble the Linear System#

    // Determine essential (Dirichlet) boundary DOFs.
    Array<int> ess_tdof_list;
    if (mesh->bdr_attributes.Size()) {
        Array<int> ess_bdr(mesh->bdr_attributes.Max());
        ess_bdr = 1;
        fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
    }

    // Right-hand side: f = 1.
    LinearForm b(&fespace);
    ConstantCoefficient one(1.0);
    b.AddDomainIntegrator(new DomainLFIntegrator(one));
    b.Assemble();

    // Solution grid function, initialized to zero.
    GridFunction x(&fespace);
    x = 0.0;

    // Stiffness matrix.
    BilinearForm a(&fespace);
    a.AddDomainIntegrator(new DiffusionIntegrator(one));
    a.Assemble();

    // Apply boundary conditions and form the final linear system.
    SparseMatrix A;
    Vector B, X;
    a.FormLinearSystem(ess_tdof_list, x, b, A, X, B);
    std::cout << "System size: " << A.Height() << " x " << A.Width()
              << ", nnz: " << A.NumNonZeroElems() << std::endl;

3. Convert to Ginkgo Objects#

MFEM exposes CSR pointers directly — GetI() returns row pointers, GetJ() returns column indices, GetData() returns values.

    // MFEM's SparseMatrix stores CSR data directly accessible via
    // GetI() (row pointers), GetJ() (column indices), GetData() (values).
    A.Finalize();
    const int n = A.Height();
    const int nnz = A.NumNonZeroElems();

    auto exec = gko::ReferenceExecutor::create();

    auto gko_A = gko::share(gko::matrix::Csr<double, int>::create(
        exec,
        gko::dim<2>(static_cast<gko::size_type>(n),
                    static_cast<gko::size_type>(n)),
        gko::array<double>::view(exec, nnz, A.GetData()),
        gko::array<int>::view(exec, nnz, A.GetJ()),
        gko::array<int>::view(exec, n + 1, A.GetI())));

    auto gko_b = gko::matrix::Dense<double>::create(
        exec, gko::dim<2>(static_cast<gko::size_type>(n), 1),
        gko::array<double>::view(exec, n, B.GetData()), 1);
    auto gko_x = gko::matrix::Dense<double>::create(
        exec, gko::dim<2>(static_cast<gko::size_type>(n), 1),
        gko::array<double>::view(exec, n, X.GetData()), 1);

4. Solve with Ginkgo#

    auto solver =
        gko::solver::Cg<double>::build()
            .with_preconditioner(gko::preconditioner::Jacobi<double>::build())
            .with_criteria(
                gko::stop::Iteration::build().with_max_iters(500u),
                gko::stop::ResidualNorm<double>::build().with_reduction_factor(
                    1e-12))
            .on(exec)
            ->generate(gko_A);

    solver->apply(gko_b, gko_x);
    std::cout << "Ginkgo solver converged." << std::endl;

5. Recover Solution#

    // Map algebraic solution back to the FE grid function.
    a.RecoverFEMSolution(X, b, x);

    std::ofstream sol_ofs("solution.gf");
    x.Save(sol_ofs);

    std::ofstream mesh_ofs("mesh.mesh");
    mesh->Print(mesh_ofs);
    std::cout << "Output written to solution.gf and mesh.mesh" << std::endl;

Building#

cmake -S . -B build --preset default
cmake --build build
./build/poisson

A mesh file can be passed with -m:

./build/poisson -m /path/to/mfem/data/star.mesh -o 2

Expected Output#

Options used:
   --mesh
   --order 1
   --refine 2
Number of elements: 4096
Number of unknowns: 4225
System size: 4225 x 4225, nnz: 37249
Ginkgo solver converged.
Output written to solution.gf and mesh.mesh