From fa17cb9fd895bb921b6579e58e8c0a3d0ca43d84 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 9 May 2025 15:39:28 +0200 Subject: [PATCH 001/104] sketch out --- cpp/dolfinx/fem/assemble_scalar_impl.h | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index 3aea777fa3e..0d5b069d768 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -9,6 +9,7 @@ #include "Constant.h" #include "Form.h" #include "FunctionSpace.h" +#include "fem/assemble_matrix_impl.h" #include "utils.h" #include #include @@ -57,6 +58,43 @@ T assemble_cells(mdspan2_t x_dofmap, return value; } +template +T assemble_vertices(mdspan2_t x_dofmap, + md::mdspan, + md::extents> + x, + std::span local_vertices, + std::span cells, FEkernel auto fn, + std::span constants, + md::mdspan> coeffs) +{ + T value(0); + if (cells.empty()) + return value; + + // Create data structures used in assembly + std::vector> cdofs(3 * x_dofmap.extent(1)); + + // Iterate over all cells + for (std::size_t index = 0; index < cells.size(); ++index) + { + std::int32_t c = cells[index]; + + // Get cell coordinates/geometry + auto x_dofs = md::submdspan(x_dofmap, c, md::full_extent); + for (std::size_t i = 0; i < x_dofs.size(); ++i) + { + std::copy_n(&x(x_dofs[i], 0), 3, std::next(cdofs.begin(), 3 * i)); + } + + std::int32_t vertex = local_vertices[index]; + fn(&value, &coeffs(index, 0), constants.data(), cdofs.data(), &vertex, + nullptr, nullptr); + } + + return value; +} + /// Execute kernel over exterior facets and accumulate result template T assemble_exterior_facets( @@ -228,6 +266,16 @@ T assemble_scalar( perms); } + for (int i : M.integral_ids(IntegralType::vertex)) + { + auto fn = M.kernel(IntegralType::vertex, i, 0); + assert(fn); + + // 1 get data + + // 2 call assemble_vertices + } + return value; } From 0a36e2d566fd126a29dd723dda943bee9f87d4c7 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 16:33:03 +0200 Subject: [PATCH 002/104] Working rank 0 assembly --- cpp/dolfinx/fem/assemble_scalar_impl.h | 91 ++++++++++--------- cpp/dolfinx/fem/utils.h | 102 +++++++++++++++++++++- python/test/unit/fem/test_point_source.py | 58 ++++++++++++ 3 files changed, 207 insertions(+), 44 deletions(-) create mode 100644 python/test/unit/fem/test_point_source.py diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index 0d5b069d768..3a5964f3c85 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025 Garth N. Wells +// Copyright (C) 2019-2025 Garth N. Wells and Paul T. Kühner // // This file is part of DOLFINx (https://www.fenicsproject.org) // @@ -9,7 +9,6 @@ #include "Constant.h" #include "Form.h" #include "FunctionSpace.h" -#include "fem/assemble_matrix_impl.h" #include "utils.h" #include #include @@ -58,43 +57,6 @@ T assemble_cells(mdspan2_t x_dofmap, return value; } -template -T assemble_vertices(mdspan2_t x_dofmap, - md::mdspan, - md::extents> - x, - std::span local_vertices, - std::span cells, FEkernel auto fn, - std::span constants, - md::mdspan> coeffs) -{ - T value(0); - if (cells.empty()) - return value; - - // Create data structures used in assembly - std::vector> cdofs(3 * x_dofmap.extent(1)); - - // Iterate over all cells - for (std::size_t index = 0; index < cells.size(); ++index) - { - std::int32_t c = cells[index]; - - // Get cell coordinates/geometry - auto x_dofs = md::submdspan(x_dofmap, c, md::full_extent); - for (std::size_t i = 0; i < x_dofs.size(); ++i) - { - std::copy_n(&x(x_dofs[i], 0), 3, std::next(cdofs.begin(), 3 * i)); - } - - std::int32_t vertex = local_vertices[index]; - fn(&value, &coeffs(index, 0), constants.data(), cdofs.data(), &vertex, - nullptr, nullptr); - } - - return value; -} - /// Execute kernel over exterior facets and accumulate result template T assemble_exterior_facets( @@ -188,6 +150,45 @@ T assemble_interior_facets( return value; } +/// Assemble functional over vertices +template +T assemble_vertices(mdspan2_t x_dofmap, + md::mdspan, + md::extents> + x, + md::mdspan> + vertices, + FEkernel auto fn, std::span constants, + md::mdspan> coeffs) +{ + T value(0); + if (vertices.empty()) + return value; + + // Create data structures used in assembly + std::vector> cdofs(3 * x_dofmap.extent(1)); + + // Iterate over all cells + for (std::size_t index = 0; index < vertices.extent(0); ++index) + { + std::int32_t cell = vertices(index, 0); + std::int32_t local_index = vertices(index, 1); + + // Get cell coordinates/geometry + auto x_dofs = md::submdspan(x_dofmap, cell, md::full_extent); + for (std::size_t i = 0; i < x_dofs.size(); ++i) + { + std::copy_n(&x(x_dofs[i], 0), 3, std::next(cdofs.begin(), 3 * i)); + } + + fn(&value, &coeffs(index, 0), constants.data(), cdofs.data(), &local_index, + nullptr, nullptr); + } + + return value; +} + /// Assemble functional into an scalar with provided mesh geometry. template T assemble_scalar( @@ -271,9 +272,17 @@ T assemble_scalar( auto fn = M.kernel(IntegralType::vertex, i, 0); assert(fn); - // 1 get data + auto& [coeffs, cstride] = coefficients.at({IntegralType::vertex, i}); - // 2 call assemble_vertices + std::span vertices + = M.domain(IntegralType::vertex, i, 0); + assert((vertices.size() / 2) * cstride == coeffs.size()); + value += impl::assemble_vertices( + x_dofmap, x, + md::mdspan>( + vertices.data(), vertices.size() / 2, 2), + fn, constants, md::mdspan(coeffs.data(), vertices.size() / 2, cstride)); } return value; diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 8ba38ef74c9..262d9bc5dd1 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -1,4 +1,5 @@ -// Copyright (C) 2013-2020 Johan Hake, Jan Blechta and Garth N. Wells +// Copyright (C) 2013-2025 Johan Hake, Jan Blechta, Garth N. Wells and Paul T. +// Kühner // // This file is part of DOLFINx (https://www.fenicsproject.org) // @@ -431,10 +432,17 @@ Form create_form_factory( // integral offsets. Since the UFL forms for each type of cell should be // the same, I think this assumption is OK. const int* integral_offsets = ufcx_forms[0].get().form_integral_offsets; - std::vector num_integrals_type(3); - for (int i = 0; i < 3; ++i) + std::array num_integrals_type; + for (int i = 0; i < num_integrals_type.size(); ++i) num_integrals_type[i] = integral_offsets[i + 1] - integral_offsets[i]; + // Create vertices, if required + if (num_integrals_type[vertex] > 0) + { + mesh->topology_mutable()->create_connectivity(0, tdim); + mesh->topology_mutable()->create_connectivity(tdim, 0); + } + // Create facets, if required if (num_integrals_type[exterior_facet] > 0 or num_integrals_type[interior_facet] > 0) @@ -761,6 +769,94 @@ Form create_form_factory( } } + // Attach vertex kernels + // TODO: create_form_factory silenty assumens ufc_forms.size() == 1? + // either generalize or make explicit for all other integral cases + { + std::vector default_vertices; + std::span ids(ufcx_forms[0].get().form_integral_ids + + integral_offsets[vertex], + num_integrals_type[vertex]); + assert(ufcx_forms.size() == 1); + + int form_idx = 0; + const ufcx_form& form = ufcx_forms[form_idx]; + + for (int i = 0; i < num_integrals_type[vertex]; ++i) + { + const int id = ids[i]; + ufcx_integral* integral + = form.form_integrals[integral_offsets[vertex] + i]; + assert(integral); + check_geometry_hash(*integral, form_idx); + + std::vector active_coeffs; + for (int j = 0; j < form.num_coefficients; ++j) + { + if (integral->enabled_coefficients[j]) + active_coeffs.push_back(j); + } + + kern_t k = nullptr; + if constexpr (std::is_same_v) + k = integral->tabulate_tensor_float32; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS + else if constexpr (std::is_same_v>) + { + k = reinterpret_cast*, const int*, + const unsigned char*, void*)>(integral->tabulate_tensor_complex64); + } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS + else if constexpr (std::is_same_v) + k = integral->tabulate_tensor_float64; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS + else if constexpr (std::is_same_v>) + { + k = reinterpret_cast*, const int*, + const unsigned char*, void*)>(integral->tabulate_tensor_complex128); + } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS + assert(k); + + // Build list of entities to assembler over + auto v_to_c = topology->connectivity(0, tdim); + assert(v_to_c); + auto c_to_v = topology->connectivity(tdim, 0); + assert(c_to_v); + if (id == -1) + { + // Default vertex kernel operates on all (owned) vertices + std::int32_t num_vertices = topology->index_map(0)->size_local(); + default_vertices.reserve(2 * num_vertices); + for (std::int32_t v = 0; v < num_vertices; v++) + { + auto cells = v_to_c->links(v); + assert(cells.size() > 0); + + // Use first cell for assembly over by default + std::int32_t cell = cells[0]; + default_vertices.push_back(cell); + + // Find local index of vertex within cell + auto cell_vertices = c_to_v->links(cell); + auto it = std::ranges::find(cell_vertices, v); + assert(it != cell_vertices.end()); + std::int32_t local_index = std::distance(cell_vertices.begin(), it); + default_vertices.push_back(local_index); + } + + integrals.insert({{IntegralType::vertex, id, form_idx}, + {k, default_vertices, active_coeffs}}); + } + else + { + throw std::runtime_error("Not implemented."); + } + } + } + return Form(spaces, std::move(integrals), mesh, coefficients, constants, needs_facet_permutations, entity_maps); } diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py new file mode 100644 index 00000000000..14d137d29d8 --- /dev/null +++ b/python/test/unit/fem/test_point_source.py @@ -0,0 +1,58 @@ +# Copyright (C) 2025 Paul T. Kühner +# +# This file is part of DOLFINx (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +from mpi4py import MPI + +import numpy as np +import pytest + +import ufl +from dolfinx import default_scalar_type, fem, mesh + + +@pytest.mark.parametrize( + "cell_type", + [ + mesh.CellType.interval, + mesh.CellType.triangle, + mesh.CellType.quadrilateral, + mesh.CellType.tetrahedron, + # mesh.CellType.pyramid, + mesh.CellType.prism, + mesh.CellType.hexahedron, + ], +) +@pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) +def test_point_source_rank_0_full_domain_1D(cell_type, ghost_mode): + comm = MPI.COMM_WORLD + + match mesh.cell_dim(cell_type): + case 1: + msh = mesh.create_unit_interval( + comm, 4, dtype=default_scalar_type, ghost_mode=ghost_mode + ) + case 2: + msh = mesh.create_unit_square( + comm, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + ) + case 3: + msh = mesh.create_unit_cube( + comm, 4, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + ) + + x = ufl.SpatialCoordinate(msh) + F = fem.form(x[0] * ufl.dP) + + expected_value_l = np.sum(msh.geometry.x[: msh.topology.index_map(0).size_local, 0]) + value_l = fem.assemble_scalar(F) + assert expected_value_l == pytest.approx(value_l) + + expected_value = comm.allreduce(expected_value_l) + value = comm.allreduce(value_l) + assert expected_value == pytest.approx(value) + + +# TODO: test partial domain, i.e. onyl some vertices From 71d2ebf7ed7109d80abaa24ac7b3a778c6060085 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 16:47:22 +0200 Subject: [PATCH 003/104] Move urls into environment file -> fork support --- .github/workflows/ccpp.yml | 36 +++++++++++++++--------------- .github/workflows/fenicsx-refs.env | 6 ++--- .github/workflows/macos.yml | 6 ++--- .github/workflows/oneapi.yml | 2 +- .github/workflows/pyvista.yml | 6 ++--- .github/workflows/sonarcloud.yml | 6 ++--- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 82657df5479..f57eeb708d1 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -111,15 +111,15 @@ jobs: - name: Install FEniCS Python components (default branches/tags) if: github.event_name != 'workflow_dispatch' run: | - pip install git+https://github.com/fenics/ufl.git@${{ env.ufl_ref }} - pip install git+https://github.com/fenics/basix.git@${{ env.basix_ref }} - pip install git+https://github.com/fenics/ffcx.git@${{ env.ffcx_ref }} + pip install git+${{ env.ufl_ref }} + pip install git+${{ env.basix_ref }} + pip install git+${{ env.ffcx_ref }} - name: Install FEniCS Python components if: github.event_name == 'workflow_dispatch' run: | - pip install git+https://github.com/FEniCS/ufl.git@${{ env.ufl_ref }} - pip install git+https://github.com/FEniCS/basix.git@${{ env.basix_ref }} - pip install git+https://github.com/FEniCS/ffcx.git@${{ env.ffcx_ref }} + pip install git+{{ env.ufl_ref }} + pip install git+${{ env.basix_ref }} + pip install git+${{ env.ffcx_ref }} - name: Configure and install C++ run: | @@ -184,16 +184,16 @@ jobs: - name: Install FEniCS Python components (default branches/tags) if: github.event_name != 'workflow_dispatch' run: | - pip install git+https://github.com/FEniCS/ufl.git@${{ env.ufl_ref }} - pip install git+https://github.com/FEniCS/basix.git@${{ env.basix_ref }} - pip install git+https://github.com/FEniCS/ffcx.git@${{ env.ffcx_ref }} + pip install git+${{ env.ufl_ref }} + pip install git+${{ env.basix_ref }} + pip install git+${{ env.ffcx_ref }} - name: Install FEniCS Python components if: github.event_name == 'workflow_dispatch' run: | - pip install git+https://github.com/FEniCS/ufl.git@${{ env.ufl_ref }} - pip install git+https://github.com/FEniCS/basix.git@${{ env.basix_ref }} - pip install git+https://github.com/FEniCS/ffcx.git@${{ env.ffcx_ref }} + pip install git+${{ env.ufl_ref }} + pip install git+${{ env.basix_ref }} + pip install git+${{ env.ffcx_ref }} - name: Configure, build and install C++ library run: | @@ -266,15 +266,15 @@ jobs: - name: Install FEniCS Python components (default branches/tags) if: github.event_name != 'workflow_dispatch' run: | - pip install git+https://github.com/FEniCS/ufl.git@${{ env.ufl_ref }} - pip install git+https://github.com/FEniCS/basix.git@${{ env.basix_ref }} - pip install git+https://github.com/FEniCS/ffcx.git@${{ env.ffcx_ref }} + pip install git+${{ env.ufl_ref }} + pip install git+${{ env.basix_ref }} + pip install git+${{ env.ffcx_ref }} - name: Install FEniCS Python components if: github.event_name == 'workflow_dispatch' run: | - pip install git+https://github.com/FEniCS/ufl.git@${{ env.ufl_ref }} - pip install git+https://github.com/FEniCS/basix.git@${{ env.basix_ref }} - pip install git+https://github.com/FEniCS/ffcx.git@${{ env.ffcx_ref }} + pip install git+${{ env.ufl_ref }} + pip install git+${{ env.basix_ref }} + pip install git+${{ env.ffcx_ref }} - name: Configure C++ run: | diff --git a/.github/workflows/fenicsx-refs.env b/.github/workflows/fenicsx-refs.env index 860d0993dc2..d5d8fc807c3 100644 --- a/.github/workflows/fenicsx-refs.env +++ b/.github/workflows/fenicsx-refs.env @@ -1,3 +1,3 @@ -basix_ref=main -ufl_ref=main -ffcx_ref=main +basix_ref=https://github.com/FEniCS/basix.git@main +ufl_ref=https://github.com/FEniCS/ufl.git@main +ffcx_ref=https://github.com/FEniCS/ffcx.git@main \ No newline at end of file diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c6daeda959d..05fb58923d2 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -73,9 +73,9 @@ jobs: - name: Install FEniCSx dependencies run: | - python -m pip install git+https://github.com/fenics/ufl.git@${{ env.ufl_ref }} - python -m pip install git+https://github.com/fenics/basix.git@${{ env.basix_ref }} - python -m pip install git+https://github.com/fenics/ffcx.git@${{ env.ffcx_ref }} + python -m pip install git+${{ env.ufl_ref }} + python -m pip install git+${{ env.basix_ref }} + python -m pip install git+${{ env.ffcx_ref }} - name: Build and install DOLFINx C++ library run: | diff --git a/.github/workflows/oneapi.yml b/.github/workflows/oneapi.yml index c964e11a791..2558f9f2e81 100644 --- a/.github/workflows/oneapi.yml +++ b/.github/workflows/oneapi.yml @@ -55,7 +55,7 @@ jobs: conda list - name: Install Basix - run: uv pip install --no-build-isolation git+https://github.com/FEniCS/basix.git@${{ env.basix_ref }} + run: uv pip install --no-build-isolation git+${{ env.basix_ref }} - name: Clone FFCx uses: actions/checkout@v4 diff --git a/.github/workflows/pyvista.yml b/.github/workflows/pyvista.yml index e9d9f36f753..396eea07473 100644 --- a/.github/workflows/pyvista.yml +++ b/.github/workflows/pyvista.yml @@ -39,9 +39,9 @@ jobs: - name: Install FEniCS Python components run: | - python -m pip install git+https://github.com/fenics/ufl.git@${{ env.ufl_ref }} - python -m pip install git+https://github.com/fenics/basix.git@${{ env.basix_ref }} - python -m pip install git+https://github.com/fenics/ffcx.git@${{ env.ffcx_ref }} + python -m pip install git+${{ env.ufl_ref }} + python -m pip install git+${{ env.basix_ref }} + python -m pip install git+${{ env.ffcx_ref }} apt-get update apt-get install -y --no-install-recommends libgl1-mesa-dev xvfb # pyvista apt-get install -y --no-install-recommends libqt5gui5t64 libgl1 # pyvistaqt diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 2a9075fa2e2..04ac9d8972f 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -62,9 +62,9 @@ jobs: echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH - name: Install FEniCS Python components run: | - python -m pip install git+https://github.com/fenics/ufl.git@${{ env.ufl_ref }} - python -m pip install git+https://github.com/fenics/basix.git@${{ env.basix_ref }} - python -m pip install git+https://github.com/fenics/ffcx.git@${{ env.ffcx_ref }} + python -m pip install git+${{ env.ufl_ref }} + python -m pip install git+${{ env.basix_ref }} + python -m pip install git+${{ env.ffcx_ref }} - name: Run build-wrapper run: | mkdir build From 78e310ceb21a39550244c15a1ef454a443bd5253 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 16:48:26 +0200 Subject: [PATCH 004/104] Switch ffcx reference branch --- .github/workflows/fenicsx-refs.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fenicsx-refs.env b/.github/workflows/fenicsx-refs.env index d5d8fc807c3..3c3c1e2e5ce 100644 --- a/.github/workflows/fenicsx-refs.env +++ b/.github/workflows/fenicsx-refs.env @@ -1,3 +1,3 @@ basix_ref=https://github.com/FEniCS/basix.git@main ufl_ref=https://github.com/FEniCS/ufl.git@main -ffcx_ref=https://github.com/FEniCS/ffcx.git@main \ No newline at end of file +ffcx_ref=https://github.com/schnellerhase/fenics-ffcx.git@point_measure \ No newline at end of file From 61242692914951c1098d6f2990c4bc0cd1b60c35 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 16:50:11 +0200 Subject: [PATCH 005/104] Missed one --- .github/workflows/redhat.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/redhat.yml b/.github/workflows/redhat.yml index 05ab9d5389a..4ee2a2151ea 100644 --- a/.github/workflows/redhat.yml +++ b/.github/workflows/redhat.yml @@ -33,9 +33,9 @@ jobs: - name: Install FEniCS Python components run: | - python3 -m pip install git+https://github.com/fenics/ufl.git@${{ env.ufl_ref }} - python3 -m pip install git+https://github.com/fenics/basix.git@${{ env.basix_ref }} - python3 -m pip install git+https://github.com/fenics/ffcx.git@${{ env.ffcx_ref }} + python3 -m pip install git+${{ env.ufl_ref }} + python3 -m pip install git+${{ env.basix_ref }} + python3 -m pip install git+${{ env.ffcx_ref }} - name: Configure C++ run: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ From 25ebace66832e81ecdd0fadf0ef011280ff3e7a5 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 17:00:30 +0200 Subject: [PATCH 006/104] Loop over forms --- cpp/dolfinx/fem/utils.h | 145 ++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 72 deletions(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 262d9bc5dd1..b4c9664d90a 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -770,89 +770,90 @@ Form create_form_factory( } // Attach vertex kernels - // TODO: create_form_factory silenty assumens ufc_forms.size() == 1? - // either generalize or make explicit for all other integral cases { - std::vector default_vertices; - std::span ids(ufcx_forms[0].get().form_integral_ids - + integral_offsets[vertex], - num_integrals_type[vertex]); - assert(ufcx_forms.size() == 1); + for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx) + { + const ufcx_form& form = ufcx_forms[form_idx]; + std::vector default_vertices; - int form_idx = 0; - const ufcx_form& form = ufcx_forms[form_idx]; + std::span ids(form.form_integral_ids + + integral_offsets[vertex], + num_integrals_type[vertex]); - for (int i = 0; i < num_integrals_type[vertex]; ++i) - { - const int id = ids[i]; - ufcx_integral* integral - = form.form_integrals[integral_offsets[vertex] + i]; - assert(integral); - check_geometry_hash(*integral, form_idx); - - std::vector active_coeffs; - for (int j = 0; j < form.num_coefficients; ++j) + for (int i = 0; i < num_integrals_type[vertex]; ++i) { - if (integral->enabled_coefficients[j]) - active_coeffs.push_back(j); - } + const int id = ids[i]; + ufcx_integral* integral + = form.form_integrals[integral_offsets[vertex] + i]; + assert(integral); + check_geometry_hash(*integral, form_idx); + + std::vector active_coeffs; + for (int j = 0; j < form.num_coefficients; ++j) + { + if (integral->enabled_coefficients[j]) + active_coeffs.push_back(j); + } - kern_t k = nullptr; - if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float32; + kern_t k = nullptr; + if constexpr (std::is_same_v) + k = integral->tabulate_tensor_float32; #ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>(integral->tabulate_tensor_complex64); - } + else if constexpr (std::is_same_v>) + { + k = reinterpret_cast*, const int*, + const unsigned char*, void*)>( + integral->tabulate_tensor_complex64); + } #endif // DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float64; + else if constexpr (std::is_same_v) + k = integral->tabulate_tensor_float64; #ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>(integral->tabulate_tensor_complex128); - } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS - assert(k); - - // Build list of entities to assembler over - auto v_to_c = topology->connectivity(0, tdim); - assert(v_to_c); - auto c_to_v = topology->connectivity(tdim, 0); - assert(c_to_v); - if (id == -1) - { - // Default vertex kernel operates on all (owned) vertices - std::int32_t num_vertices = topology->index_map(0)->size_local(); - default_vertices.reserve(2 * num_vertices); - for (std::int32_t v = 0; v < num_vertices; v++) + else if constexpr (std::is_same_v>) { - auto cells = v_to_c->links(v); - assert(cells.size() > 0); - - // Use first cell for assembly over by default - std::int32_t cell = cells[0]; - default_vertices.push_back(cell); - - // Find local index of vertex within cell - auto cell_vertices = c_to_v->links(cell); - auto it = std::ranges::find(cell_vertices, v); - assert(it != cell_vertices.end()); - std::int32_t local_index = std::distance(cell_vertices.begin(), it); - default_vertices.push_back(local_index); + k = reinterpret_cast*, const int*, + const unsigned char*, void*)>( + integral->tabulate_tensor_complex128); } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS + assert(k); - integrals.insert({{IntegralType::vertex, id, form_idx}, - {k, default_vertices, active_coeffs}}); - } - else - { - throw std::runtime_error("Not implemented."); + // Build list of entities to assembler over + auto v_to_c = topology->connectivity(0, tdim); + assert(v_to_c); + auto c_to_v = topology->connectivity(tdim, 0); + assert(c_to_v); + if (id == -1) + { + // Default vertex kernel operates on all (owned) vertices + std::int32_t num_vertices = topology->index_map(0)->size_local(); + default_vertices.reserve(2 * num_vertices); + for (std::int32_t v = 0; v < num_vertices; v++) + { + auto cells = v_to_c->links(v); + assert(cells.size() > 0); + + // Use first cell for assembly over by default + std::int32_t cell = cells[0]; + default_vertices.push_back(cell); + + // Find local index of vertex within cell + auto cell_vertices = c_to_v->links(cell); + auto it = std::ranges::find(cell_vertices, v); + assert(it != cell_vertices.end()); + std::int32_t local_index = std::distance(cell_vertices.begin(), it); + default_vertices.push_back(local_index); + } + + integrals.insert({{IntegralType::vertex, id, form_idx}, + {k, default_vertices, active_coeffs}}); + } + else + { + throw std::runtime_error("Not implemented."); + } } } } From 5d5329e7405c6549f3460d4c630853755c5a4d02 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 18:27:10 +0200 Subject: [PATCH 007/104] Domain marker support --- cpp/dolfinx/fem/utils.cpp | 3 +- cpp/dolfinx/fem/utils.h | 31 +++++++++++++++-- python/test/unit/fem/test_point_source.py | 41 ++++++++++++++++++++++- 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index d0f7c60a059..8cf08d50a0c 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -134,7 +134,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, std::span entities) { const int tdim = topology.dim(); - const int dim = integral_type == IntegralType::cell ? tdim : tdim - 1; + const int dim = integral_type == IntegralType::cell ? tdim : ((integral_type == IntegralType::vertex) ? 0 : tdim - 1); { // Create span of the owned entities (leaves off any ghosts) @@ -148,6 +148,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, switch (integral_type) { case IntegralType::cell: + case IntegralType::vertex: { entity_data.insert(entity_data.begin(), entities.begin(), entities.end()); break; diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index b4c9664d90a..c41fe1c68da 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -779,7 +779,7 @@ Form create_form_factory( std::span ids(form.form_integral_ids + integral_offsets[vertex], num_integrals_type[vertex]); - + auto sd = subdomains.find(IntegralType::vertex); for (int i = 0; i < num_integrals_type[vertex]; ++i) { const int id = ids[i]; @@ -852,7 +852,34 @@ Form create_form_factory( } else { - throw std::runtime_error("Not implemented."); + // NOTE: This requires that pairs are sorted + auto it = std::ranges::lower_bound(sd->second, id, std::less<>{}, + [](auto& a) { return a.first; }); + if (it != sd->second.end() and it->first == id) + { + // TODO: tidy up code duplication + for (std::int32_t v : it->second) + { + auto cells = v_to_c->links(v); + assert(cells.size() > 0); + + // Use first cell for assembly over by default + std::int32_t cell = cells[0]; + default_vertices.push_back(cell); + + // Find local index of vertex within cell + auto cell_vertices = c_to_v->links(cell); + auto it = std::ranges::find(cell_vertices, v); + assert(it != cell_vertices.end()); + std::int32_t local_index = std::distance(cell_vertices.begin(), it); + default_vertices.push_back(local_index); + } + + integrals.insert({{IntegralType::vertex, id, form_idx}, + {k, + default_vertices, + active_coeffs}}); + } } } } diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index 14d137d29d8..c16be197c7d 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -54,5 +54,44 @@ def test_point_source_rank_0_full_domain_1D(cell_type, ghost_mode): value = comm.allreduce(value_l) assert expected_value == pytest.approx(value) + # Split domain into first half of vertices (1) and second half of vertices (2) + vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) + tags = np.full_like(vertices, 1) + tags[tags.size // 2 :] = 2 + meshtags = mesh.meshtags(msh, 0, vertices, tags) -# TODO: test partial domain, i.e. onyl some vertices + # Test dp(1) + dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) + F = fem.form(x[0] * dP(1)) + expected_value_l = np.sum(msh.geometry.x[: msh.topology.index_map(0).size_local // 2, 0]) + value_l = fem.assemble_scalar(F) + assert expected_value_l == pytest.approx(value_l) + + expected_value = comm.allreduce(expected_value_l) + value = comm.allreduce(value_l) + assert expected_value == pytest.approx(value) + + # Test dp(2) + F = fem.form(x[0] * dP(2)) + expected_value_l = np.sum( + msh.geometry.x[ + msh.topology.index_map(0).size_local // 2 : msh.topology.index_map(0).size_local, 0 + ] + ) + value_l = fem.assemble_scalar(F) + assert expected_value_l == pytest.approx(value_l) + + expected_value = comm.allreduce(expected_value_l) + value = comm.allreduce(value_l) + assert expected_value == pytest.approx(value) + + # TODO: failing + # Test dp(1) + dp(2) + # F = fem.form(x[0] * (dP(1) + dP(2))) + # expected_value_l = np.sum(msh.geometry.x[:msh.topology.index_map(0).size_local, 0]) + # value_l = fem.assemble_scalar(F) + # assert expected_value_l == pytest.approx(value_l) + + # expected_value = comm.allreduce(expected_value_l) + # value = comm.allreduce(value_l) + # assert expected_value == pytest.approx(value) From a2d78c8a84a2ad5fc30b48f179b2636f15980381 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 18:28:42 +0200 Subject: [PATCH 008/104] Missed forms.py --- python/dolfinx/fem/forms.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/dolfinx/fem/forms.py b/python/dolfinx/fem/forms.py index 750eaacf245..05892c40cd9 100644 --- a/python/dolfinx/fem/forms.py +++ b/python/dolfinx/fem/forms.py @@ -145,6 +145,12 @@ def get_integration_domains( tdim = subdomain.topology.dim # type: ignore subdomain._cpp_object.topology.create_connectivity(tdim - 1, tdim) # type: ignore subdomain._cpp_object.topology.create_connectivity(tdim, tdim - 1) # type: ignore + + if integral_type is IntegralType.vertex: + tdim = subdomain.topology.dim # type: ignore + subdomain._cpp_object.topology.create_connectivity(0, tdim) # type: ignore + subdomain._cpp_object.topology.create_connectivity(tdim, 0) # type: ignore + # Compute integration domains only for each subdomain id in # the integrals. If a process has no integral entities, # insert an empty array. From b72fa071a45b89659e4cc948808eca2435d9a87a Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 19:58:02 +0200 Subject: [PATCH 009/104] Remove python match --- python/test/unit/fem/test_point_source.py | 25 +++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index c16be197c7d..761191fc10e 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -29,19 +29,18 @@ def test_point_source_rank_0_full_domain_1D(cell_type, ghost_mode): comm = MPI.COMM_WORLD - match mesh.cell_dim(cell_type): - case 1: - msh = mesh.create_unit_interval( - comm, 4, dtype=default_scalar_type, ghost_mode=ghost_mode - ) - case 2: - msh = mesh.create_unit_square( - comm, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode - ) - case 3: - msh = mesh.create_unit_cube( - comm, 4, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode - ) + msh = None + cell_dim = mesh.cell_dim(cell_type) + if cell_dim == 1: + msh = mesh.create_unit_interval(comm, 4, dtype=default_scalar_type, ghost_mode=ghost_mode) + elif cell_dim == 2: + msh = mesh.create_unit_square( + comm, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + ) + elif cell_dim == 3: + msh = mesh.create_unit_cube( + comm, 4, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + ) x = ufl.SpatialCoordinate(msh) F = fem.form(x[0] * ufl.dP) From dffa2027ead42e1c5bf8a5e894938061d328bd90 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 20:15:09 +0200 Subject: [PATCH 010/104] Tidy compute_integration_domains --- cpp/dolfinx/fem/utils.cpp | 131 ++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 8cf08d50a0c..61aea4c2af5 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -134,7 +135,23 @@ fem::compute_integration_domains(fem::IntegralType integral_type, std::span entities) { const int tdim = topology.dim(); - const int dim = integral_type == IntegralType::cell ? tdim : ((integral_type == IntegralType::vertex) ? 0 : tdim - 1); + + int dim = -1; + switch (integral_type) + { + case IntegralType::cell: + dim = tdim; + break; + case IntegralType::exterior_facet: + case IntegralType::interior_facet: + dim = tdim - 1; + break; + case IntegralType::vertex: + dim = 0; + break; + default: + throw std::runtime_error("Cannot compute integration domains. Integral type not supported."); + } { // Create span of the owned entities (leaves off any ghosts) @@ -144,16 +161,9 @@ fem::compute_integration_domains(fem::IntegralType integral_type, entities = entities.first(std::distance(entities.begin(), it1)); } - std::vector entity_data; - switch (integral_type) - { - case IntegralType::cell: - case IntegralType::vertex: - { - entity_data.insert(entity_data.begin(), entities.begin(), entities.end()); - break; - } - default: + auto cell_connectivity + = [&]() -> std::pair>, + std::shared_ptr>> { auto f_to_c = topology.connectivity(tdim - 1, tdim); if (!f_to_c) @@ -168,61 +178,70 @@ fem::compute_integration_domains(fem::IntegralType integral_type, throw std::runtime_error( "Topology cell-to-facet connectivity has not been computed."); } + return {f_to_c, c_to_f}; + }; - switch (integral_type) - { - case IntegralType::exterior_facet: + std::vector entity_data; + switch (integral_type) + { + case IntegralType::cell: + case IntegralType::vertex: + { + entity_data.insert(entity_data.begin(), entities.begin(), entities.end()); + break; + } + case IntegralType::exterior_facet: + { + auto [f_to_c, c_to_f] = cell_connectivity(); + // Create list of tagged boundary facets + const std::vector bfacets = mesh::exterior_facet_indices(topology); + std::vector facets; + std::ranges::set_intersection(entities, bfacets, + std::back_inserter(facets)); + for (auto f : facets) { - // Create list of tagged boundary facets - const std::vector bfacets = mesh::exterior_facet_indices(topology); - std::vector facets; - std::ranges::set_intersection(entities, bfacets, - std::back_inserter(facets)); - for (auto f : facets) - { - // Get the facet as a pair of (cell, local facet) - auto facet - = impl::get_cell_facet_pairs<1>(f, f_to_c->links(f), *c_to_f); - entity_data.insert(entity_data.end(), facet.begin(), facet.end()); - } + // Get the facet as a pair of (cell, local facet) + auto facet = impl::get_cell_facet_pairs<1>(f, f_to_c->links(f), *c_to_f); + entity_data.insert(entity_data.end(), facet.begin(), facet.end()); } break; - case IntegralType::interior_facet: + } + case IntegralType::interior_facet: + { + auto [f_to_c, c_to_f] = cell_connectivity(); + + // Create indicator for interprocess facets + assert(topology.index_map(tdim - 1)); + const std::vector& interprocess_facets + = topology.interprocess_facets(); + std::vector interprocess_marker( + topology.index_map(tdim - 1)->size_local() + + topology.index_map(tdim - 1)->num_ghosts(), + 0); + std::ranges::for_each(interprocess_facets, [&interprocess_marker](auto f) + { interprocess_marker[f] = 1; }); + for (auto f : entities) { - // Create indicator for interprocess facets - assert(topology.index_map(tdim - 1)); - const std::vector& interprocess_facets - = topology.interprocess_facets(); - std::vector interprocess_marker( - topology.index_map(tdim - 1)->size_local() - + topology.index_map(tdim - 1)->num_ghosts(), - 0); - std::ranges::for_each(interprocess_facets, [&interprocess_marker](auto f) - { interprocess_marker[f] = 1; }); - for (auto f : entities) + if (f_to_c->num_links(f) == 2) { - if (f_to_c->num_links(f) == 2) - { - // Get the facet as a pair of (cell, local facet) pairs, one - // for each cell - auto facets - = impl::get_cell_facet_pairs<2>(f, f_to_c->links(f), *c_to_f); - entity_data.insert(entity_data.end(), facets.begin(), facets.end()); - } - else if (interprocess_marker[f]) - { - throw std::runtime_error( - "Cannot compute interior facet integral over interprocess facet. " - "Use \"shared facet\" ghost mode when creating the mesh."); - } + // Get the facet as a pair of (cell, local facet) pairs, one + // for each cell + auto facets + = impl::get_cell_facet_pairs<2>(f, f_to_c->links(f), *c_to_f); + entity_data.insert(entity_data.end(), facets.begin(), facets.end()); + } + else if (interprocess_marker[f]) + { + throw std::runtime_error( + "Cannot compute interior facet integral over interprocess facet. " + "Use \"shared facet\" ghost mode when creating the mesh."); } } break; - default: - throw std::runtime_error( - "Cannot compute integration domains. Integral type not supported."); - } } + // C++ 23 + // default: + // std::unreachable(); } return entity_data; From 105fdabbd4118428db57047d48dd574e3d201bad Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 21:12:28 +0200 Subject: [PATCH 011/104] Debug partitioned domain --- cpp/dolfinx/fem/utils.h | 34 ++++++++++++----------- python/test/unit/fem/test_point_source.py | 17 ++++++------ 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index c41fe1c68da..a5cbe6829b8 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -774,7 +774,6 @@ Form create_form_factory( for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx) { const ufcx_form& form = ufcx_forms[form_idx]; - std::vector default_vertices; std::span ids(form.form_integral_ids + integral_offsets[vertex], @@ -829,6 +828,7 @@ Form create_form_factory( { // Default vertex kernel operates on all (owned) vertices std::int32_t num_vertices = topology->index_map(0)->size_local(); + std::vector default_vertices; default_vertices.reserve(2 * num_vertices); for (std::int32_t v = 0; v < num_vertices; v++) { @@ -836,6 +836,7 @@ Form create_form_factory( assert(cells.size() > 0); // Use first cell for assembly over by default + // TODO: user control in general for this? std::int32_t cell = cells[0]; default_vertices.push_back(cell); @@ -857,28 +858,29 @@ Form create_form_factory( [](auto& a) { return a.first; }); if (it != sd->second.end() and it->first == id) { + std::vector default_vertices; // TODO: tidy up code duplication for (std::int32_t v : it->second) { auto cells = v_to_c->links(v); - assert(cells.size() > 0); - - // Use first cell for assembly over by default - std::int32_t cell = cells[0]; - default_vertices.push_back(cell); - - // Find local index of vertex within cell - auto cell_vertices = c_to_v->links(cell); - auto it = std::ranges::find(cell_vertices, v); - assert(it != cell_vertices.end()); - std::int32_t local_index = std::distance(cell_vertices.begin(), it); - default_vertices.push_back(local_index); + assert(cells.size() > 0); + + // Use first cell for assembly over by default + // TODO: user control in general for this? + std::int32_t cell = cells[0]; + default_vertices.push_back(cell); + + // Find local index of vertex within cell + auto cell_vertices = c_to_v->links(cell); + auto it = std::ranges::find(cell_vertices, v); + assert(it != cell_vertices.end()); + std::int32_t local_index + = std::distance(cell_vertices.begin(), it); + default_vertices.push_back(local_index); } integrals.insert({{IntegralType::vertex, id, form_idx}, - {k, - default_vertices, - active_coeffs}}); + {k, default_vertices, active_coeffs}}); } } } diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index 761191fc10e..e7a206a854d 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -84,13 +84,12 @@ def test_point_source_rank_0_full_domain_1D(cell_type, ghost_mode): value = comm.allreduce(value_l) assert expected_value == pytest.approx(value) - # TODO: failing # Test dp(1) + dp(2) - # F = fem.form(x[0] * (dP(1) + dP(2))) - # expected_value_l = np.sum(msh.geometry.x[:msh.topology.index_map(0).size_local, 0]) - # value_l = fem.assemble_scalar(F) - # assert expected_value_l == pytest.approx(value_l) - - # expected_value = comm.allreduce(expected_value_l) - # value = comm.allreduce(value_l) - # assert expected_value == pytest.approx(value) + F = fem.form(x[0] * (dP(1) + dP(2))) + expected_value_l = np.sum(msh.geometry.x[: msh.topology.index_map(0).size_local, 0]) + value_l = fem.assemble_scalar(F) + assert expected_value_l == pytest.approx(value_l) + + expected_value = comm.allreduce(expected_value_l) + value = comm.allreduce(value_l) + assert expected_value == pytest.approx(value) From 5210a134a2e68906eda891d7b853f78686f7e77d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 21:14:00 +0200 Subject: [PATCH 012/104] Format --- cpp/dolfinx/fem/utils.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 61aea4c2af5..8cb95af13d1 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -150,7 +150,8 @@ fem::compute_integration_domains(fem::IntegralType integral_type, dim = 0; break; default: - throw std::runtime_error("Cannot compute integration domains. Integral type not supported."); + throw std::runtime_error( + "Cannot compute integration domains. Integral type not supported."); } { @@ -239,9 +240,9 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } break; } - // C++ 23 - // default: - // std::unreachable(); + // C++ 23 + // default: + // std::unreachable(); } return entity_data; From 64353e0c9e9a41b4908937e15b9e30671cd94898 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 21:22:03 +0200 Subject: [PATCH 013/104] Simplify test --- python/test/unit/fem/test_point_source.py | 56 ++++++----------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index e7a206a854d..55d81512a5c 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -42,54 +42,28 @@ def test_point_source_rank_0_full_domain_1D(cell_type, ghost_mode): comm, 4, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode ) - x = ufl.SpatialCoordinate(msh) - F = fem.form(x[0] * ufl.dP) + def check(form, coordinate_range): + expected_value_l = np.sum(msh.geometry.x[coordinate_range[0] : coordinate_range[1], 0]) + value_l = fem.assemble_scalar(form) + assert expected_value_l == pytest.approx(value_l) + + expected_value = comm.allreduce(expected_value_l) + value = comm.allreduce(value_l) + assert expected_value == pytest.approx(value) - expected_value_l = np.sum(msh.geometry.x[: msh.topology.index_map(0).size_local, 0]) - value_l = fem.assemble_scalar(F) - assert expected_value_l == pytest.approx(value_l) + num_vertices = msh.topology.index_map(0).size_local + x = ufl.SpatialCoordinate(msh) - expected_value = comm.allreduce(expected_value_l) - value = comm.allreduce(value_l) - assert expected_value == pytest.approx(value) + # Full domain + check(fem.form(x[0] * ufl.dP), (0, num_vertices)) # Split domain into first half of vertices (1) and second half of vertices (2) vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) tags = np.full_like(vertices, 1) tags[tags.size // 2 :] = 2 meshtags = mesh.meshtags(msh, 0, vertices, tags) - - # Test dp(1) dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - F = fem.form(x[0] * dP(1)) - expected_value_l = np.sum(msh.geometry.x[: msh.topology.index_map(0).size_local // 2, 0]) - value_l = fem.assemble_scalar(F) - assert expected_value_l == pytest.approx(value_l) - - expected_value = comm.allreduce(expected_value_l) - value = comm.allreduce(value_l) - assert expected_value == pytest.approx(value) - - # Test dp(2) - F = fem.form(x[0] * dP(2)) - expected_value_l = np.sum( - msh.geometry.x[ - msh.topology.index_map(0).size_local // 2 : msh.topology.index_map(0).size_local, 0 - ] - ) - value_l = fem.assemble_scalar(F) - assert expected_value_l == pytest.approx(value_l) - - expected_value = comm.allreduce(expected_value_l) - value = comm.allreduce(value_l) - assert expected_value == pytest.approx(value) - - # Test dp(1) + dp(2) - F = fem.form(x[0] * (dP(1) + dP(2))) - expected_value_l = np.sum(msh.geometry.x[: msh.topology.index_map(0).size_local, 0]) - value_l = fem.assemble_scalar(F) - assert expected_value_l == pytest.approx(value_l) - expected_value = comm.allreduce(expected_value_l) - value = comm.allreduce(value_l) - assert expected_value == pytest.approx(value) + check(fem.form(x[0] * dP(1)), (0, num_vertices // 2)) + check(fem.form(x[0] * dP(2)), (num_vertices // 2, num_vertices)) + check(fem.form(x[0] * (dP(1) + dP(2))), (0, num_vertices)) From 1e34560878993e97d7ac4edaa2877fcd90cd234f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 10 May 2025 21:25:08 +0200 Subject: [PATCH 014/104] Try with new line in .env --- .github/workflows/fenicsx-refs.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fenicsx-refs.env b/.github/workflows/fenicsx-refs.env index 3c3c1e2e5ce..3a3316e219a 100644 --- a/.github/workflows/fenicsx-refs.env +++ b/.github/workflows/fenicsx-refs.env @@ -1,3 +1,3 @@ basix_ref=https://github.com/FEniCS/basix.git@main ufl_ref=https://github.com/FEniCS/ufl.git@main -ffcx_ref=https://github.com/schnellerhase/fenics-ffcx.git@point_measure \ No newline at end of file +ffcx_ref=https://github.com/schnellerhase/fenics-ffcx.git@point_measure From 57ced5d969fd641698e8c0f7be991d1e3fbca360 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 11 May 2025 00:11:03 +0200 Subject: [PATCH 015/104] Vector assembly --- cpp/dolfinx/fem/assemble_vector_impl.h | 116 +++++++++++++++++++++- python/test/unit/fem/test_point_source.py | 61 +++++++++++- 2 files changed, 175 insertions(+), 2 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index 0e32e12fc23..2a656ed4e1d 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2021 Garth N. Wells +// Copyright (C) 2018-2025 Garth N. Wells and Paul T. Kühner // // This file is part of DOLFINx (https://www.fenicsproject.org) // @@ -907,6 +907,94 @@ void assemble_interior_facets( } } +/// @brief Execute kernel over cells and accumulate result in vector. +/// +/// @tparam T Scalar type +/// @tparam _bs Block size of the form test function dof map. If less +/// than zero the block size is determined at runtime. If `_bs` is +/// positive the block size is used as a compile-time constant, which +/// has performance benefits. +/// @param[in] P0 Function that applies transformation `P0.b` in-place +/// to `b` to transform test degrees-of-freedom. +/// @param[in,out] b Aray to accumulate into. +/// @param[in] x_dofmap Dofmap for the mesh geometry. +/// @param[in] x Mesh geometry (coordinates). +/// @param[in] vertices Vertex indices `(vertices.size(), 2)` - first entry +/// holds the cell index over which the vertex is to evaluated, and the second +/// stores the local index of the vertex within the cell. +/// @param[in] dofmap Test function (row) degree-of-freedom data holding +/// the (0) dofmap, (1) dofmap block size and (2) dofmap cell indices. +/// @param[in] kernel Kernel function to execute over each cell. +/// @param[in] constants Constant coefficient data in the kernel. +/// @param[in] coeffs Coefficient data in the kernel. It has shape +/// `(cells.size(), num_cell_coeffs)`. `coeffs(i, j)` is the `j`th +/// coefficient for cell `i`. +/// @param[in] cell_info0 Cell permutation information for the test +/// function mesh. +template +void assemble_vertices( + fem::DofTransformKernel auto P0, std::span b, mdspan2_t x_dofmap, + md::mdspan, + md::extents> + x, + md::mdspan> + vertices, + std::tuple>> + dofmap, + FEkernel auto kernel, std::span constants, + md::mdspan> coeffs, + std::span cell_info0) +{ + if (vertices.empty()) + return; + + const auto [dmap, bs, vertices0] = dofmap; + assert(_bs < 0 or _bs == bs); + + // Create data structures used in assembly + std::vector> cdofs(3 * x_dofmap.extent(1)); + std::vector be(bs * dmap.extent(1)); + std::span _be(be); + + // Iterate over active cells + for (std::size_t index = 0; index < vertices.extent(0); ++index) + { + // Integration domain cell, local index, and test function cell + std::int32_t cell = vertices(index, 0); + std::int32_t local_index = vertices(index, 1); + std::int32_t c0 = vertices0(index, 0); + + // Get cell coordinates/geometry + auto x_dofs = md::submdspan(x_dofmap, cell, md::full_extent); + for (std::size_t i = 0; i < x_dofs.size(); ++i) + std::copy_n(&x(x_dofs[i], 0), 3, std::next(cdofs.begin(), 3 * i)); + + // Tabulate vector for cell + std::ranges::fill(be, 0); + kernel(be.data(), &coeffs(index, 0), constants.data(), cdofs.data(), + &local_index, nullptr, nullptr); + P0(_be, cell_info0, c0, 1); + + // Scatter cell vector to 'global' vector array + auto dofs = md::submdspan(dmap, c0, md::full_extent); + if constexpr (_bs > 0) + { + for (std::size_t i = 0; i < dofs.size(); ++i) + for (int k = 0; k < _bs; ++k) + b[_bs * dofs[i] + k] += be[_bs * i + k]; + } + else + { + for (std::size_t i = 0; i < dofs.size(); ++i) + for (int k = 0; k < bs; ++k) + b[bs * dofs[i] + k] += be[bs * i + k]; + } + } +} + /// Modify RHS vector to account for boundary condition such that: /// /// b <- b - alpha * A.(x_bc - x0) @@ -1340,6 +1428,32 @@ void assemble_vector( cell_info0, perms); } } + + for (int i : L.integral_ids(IntegralType::vertex)) + { + auto fn = L.kernel(IntegralType::vertex, i, 0); + assert(fn); + + std::span vertices = L.domain(IntegralType::vertex, i, cell_type_idx); + std::span vertices0 + = L.domain_arg(IntegralType::vertex, 0, i, cell_type_idx); + + auto& [coeffs, cstride] = coefficients.at({IntegralType::vertex, i}); + + assert((vertices.size() / 2) * cstride == coeffs.size()); + + impl::assemble_vertices( + P0, b, x_dofmap, x, + md::mdspan>( + vertices.data(), vertices.size() / 2, 2), + {dofs, bs, + md::mdspan>( + vertices0.data(), vertices0.size() / 2, 2)}, + fn, constants, + md::mdspan(coeffs.data(), vertices.size() / 2, cstride), cell_info0); + } } } diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index 55d81512a5c..455d7a72a0a 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -26,7 +26,7 @@ ], ) @pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) -def test_point_source_rank_0_full_domain_1D(cell_type, ghost_mode): +def test_point_source_rank_0(cell_type, ghost_mode): comm = MPI.COMM_WORLD msh = None @@ -67,3 +67,62 @@ def check(form, coordinate_range): check(fem.form(x[0] * dP(1)), (0, num_vertices // 2)) check(fem.form(x[0] * dP(2)), (num_vertices // 2, num_vertices)) check(fem.form(x[0] * (dP(1) + dP(2))), (0, num_vertices)) + + +@pytest.mark.parametrize( + "cell_type", + [ + mesh.CellType.interval, + mesh.CellType.triangle, + mesh.CellType.quadrilateral, + mesh.CellType.tetrahedron, + # mesh.CellType.pyramid, + mesh.CellType.prism, + mesh.CellType.hexahedron, + ], +) +@pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) +def test_point_source_rank_1(cell_type, ghost_mode): + comm = MPI.COMM_WORLD + + msh = None + cell_dim = mesh.cell_dim(cell_type) + if cell_dim == 1: + msh = mesh.create_unit_interval(comm, 4, dtype=default_scalar_type, ghost_mode=ghost_mode) + elif cell_dim == 2: + msh = mesh.create_unit_square( + comm, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + ) + elif cell_dim == 3: + msh = mesh.create_unit_cube( + comm, 4, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + ) + + num_vertices = msh.topology.index_map(0).size_local + + def check(form, coordinate_range): + a, b = coordinate_range + expected_value_l = np.zeros(num_vertices) + expected_value_l[a:b] = msh.geometry.x[a:b, 0] + value_l = fem.assemble_vector(form) + equal_l = np.allclose(expected_value_l, value_l.array[:num_vertices]) + assert equal_l + assert comm.allreduce(equal_l, MPI.BAND) + + x = ufl.SpatialCoordinate(msh) + V = fem.functionspace(msh, ("P", 1)) + v = ufl.TestFunction(V) + + # Full domain + check(fem.form(x[0] * v * ufl.dP), (0, num_vertices)) + + # Split domain into first half of vertices (1) and second half of vertices (2) + vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) + tags = np.full_like(vertices, 1) + tags[tags.size // 2 :] = 2 + meshtags = mesh.meshtags(msh, 0, vertices, tags) + dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) + + check(fem.form(x[0] * v * dP(1)), (0, num_vertices // 2)) + check(fem.form(x[0] * v * dP(2)), (num_vertices // 2, num_vertices)) + check(fem.form(x[0] * v * (dP(1) + dP(2))), (0, num_vertices)) From cfcdecfc99bddd5295fe2084d62ddc25208c11dd Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 11 May 2025 21:34:14 +0200 Subject: [PATCH 016/104] Fix sign --- cpp/dolfinx/fem/utils.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index a5cbe6829b8..c67cc8d013d 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -433,7 +434,7 @@ Form create_form_factory( // the same, I think this assumption is OK. const int* integral_offsets = ufcx_forms[0].get().form_integral_offsets; std::array num_integrals_type; - for (int i = 0; i < num_integrals_type.size(); ++i) + for (std::size_t i = 0; i < num_integrals_type.size(); ++i) num_integrals_type[i] = integral_offsets[i + 1] - integral_offsets[i]; // Create vertices, if required From 575fbb52756c8de9e15a77eb4014d320efa9d66a Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 11 May 2025 21:38:48 +0200 Subject: [PATCH 017/104] Explicit clone in oneapi --- .github/workflows/oneapi.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/oneapi.yml b/.github/workflows/oneapi.yml index 2558f9f2e81..207524ae9d2 100644 --- a/.github/workflows/oneapi.yml +++ b/.github/workflows/oneapi.yml @@ -58,11 +58,12 @@ jobs: run: uv pip install --no-build-isolation git+${{ env.basix_ref }} - name: Clone FFCx - uses: actions/checkout@v4 - with: - path: ./ffcx - repository: FEniCS/ffcx - ref: ${{ env.ffcx_ref }} + run: git clone --depth 1 ${{ env.ffcx_ref }} ffcx + # uses: actions/checkout@v4 + # with: + # path: ./ffcx + # repository: FEniCS/ffcx + # ref: ${{ env.ffcx_ref }} - name: Install UFCx C interface run: | From 01eff1a26d450da52e28c9dfde82e67b9d2cea52 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 11 May 2025 21:54:44 +0200 Subject: [PATCH 018/104] ANother --- .github/workflows/oneapi.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/oneapi.yml b/.github/workflows/oneapi.yml index 207524ae9d2..8b5a8b3165c 100644 --- a/.github/workflows/oneapi.yml +++ b/.github/workflows/oneapi.yml @@ -54,11 +54,15 @@ jobs: conda info conda list + - name: Install ufl + run: uv pip install --no-build-isolation git+${{ env.ufl_ref }} + - name: Install Basix run: uv pip install --no-build-isolation git+${{ env.basix_ref }} - - name: Clone FFCx - run: git clone --depth 1 ${{ env.ffcx_ref }} ffcx + - name: Install FFCx + run: uv pip install --no-build-isolation ${{ env.ffcx_ref }} + # run: git clone --depth 1 ${{ env.ffcx_ref }} ffcx # uses: actions/checkout@v4 # with: # path: ./ffcx @@ -80,10 +84,9 @@ jobs: cmake --build build cmake --install build - - name: Install UFL and FFCx modules - run: | - uv pip install --no-build-isolation git+https://github.com/FEniCS/ufl.git@${{ env.ufl_ref }} - uv pip install --no-build-isolation ffcx/ + # - name: Install UFL and FFCx modules + # run: | + # uv pip install --no-build-isolation git+https://github.com/FEniCS/ufl.git@${{ env.ufl_ref }} - name: Build and run DOLFINx C++ unit tests (serial and MPI) run: | From dbc7e0ed1915bb369a1532faef3e51c5c9c18eb3 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 11 May 2025 22:08:21 +0200 Subject: [PATCH 019/104] Tidy up --- .github/workflows/oneapi.yml | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/.github/workflows/oneapi.yml b/.github/workflows/oneapi.yml index 8b5a8b3165c..008c7a87631 100644 --- a/.github/workflows/oneapi.yml +++ b/.github/workflows/oneapi.yml @@ -54,20 +54,11 @@ jobs: conda info conda list - - name: Install ufl - run: uv pip install --no-build-isolation git+${{ env.ufl_ref }} - - - name: Install Basix - run: uv pip install --no-build-isolation git+${{ env.basix_ref }} - - - name: Install FFCx - run: uv pip install --no-build-isolation ${{ env.ffcx_ref }} - # run: git clone --depth 1 ${{ env.ffcx_ref }} ffcx - # uses: actions/checkout@v4 - # with: - # path: ./ffcx - # repository: FEniCS/ffcx - # ref: ${{ env.ffcx_ref }} + - name: Install UFL/Basix/FFCx + run: | + uv pip install --no-build-isolation git+${{ env.ufl_ref }} + uv pip install --no-build-isolation git+${{ env.basix_ref }} + uv pip install --no-build-isolation git+${{ env.ffcx_ref }} - name: Install UFCx C interface run: | @@ -84,10 +75,6 @@ jobs: cmake --build build cmake --install build - # - name: Install UFL and FFCx modules - # run: | - # uv pip install --no-build-isolation git+https://github.com/FEniCS/ufl.git@${{ env.ufl_ref }} - - name: Build and run DOLFINx C++ unit tests (serial and MPI) run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S cpp/test/ From b5aa4333155ed2ab659d6eaf0139fd9b942a7a05 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 11 May 2025 22:30:55 +0200 Subject: [PATCH 020/104] Coefficient packing --- cpp/dolfinx/fem/pack.h | 33 +++++++++++++++++++++++ python/test/unit/fem/test_point_source.py | 18 +++++++++++++ 2 files changed, 51 insertions(+) diff --git a/cpp/dolfinx/fem/pack.h b/cpp/dolfinx/fem/pack.h index 7d8a30f7ff6..d9d6d6ae3e5 100644 --- a/cpp/dolfinx/fem/pack.h +++ b/cpp/dolfinx/fem/pack.h @@ -322,6 +322,39 @@ void pack_coefficients(const Form& form, } break; } + case IntegralType::vertex: + { + // Iterate over coefficients that are active in cell integrals + for (int coeff : form.active_coeffs(IntegralType::vertex, id)) + { + // Get coefficient mesh + auto mesh = coefficients[coeff]->function_space()->mesh(); + assert(mesh); + + // Other integrals in the form might have coefficients defined + // over entities of codim > 0, which don't make sense for vertex + // integrals, so don't pack them. + if (int codim + = form.mesh()->topology()->dim() - mesh->topology()->dim(); + codim > 0) + { + throw std::runtime_error("Should not be packing coefficients with " + "codim>0 in a vertex integral"); + } + + std::span vertices_b + = form.domain_coeff(IntegralType::vertex, id, coeff); + md::mdspan> + vertices(vertices_b.data(), vertices_b.size() / 2, 2); + std::span cell_info + = impl::get_cell_orientation_info(*coefficients[coeff]); + impl::pack_coefficient_entity( + std::span(c), cstride, *coefficients[coeff], cell_info, + std::submdspan(vertices, md::full_extent, 0), offsets[coeff]); + } + break; + } default: throw std::runtime_error( "Could not pack coefficient. Integral type not supported."); diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index 455d7a72a0a..d4707ba6f33 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -68,6 +68,15 @@ def check(form, coordinate_range): check(fem.form(x[0] * dP(2)), (num_vertices // 2, num_vertices)) check(fem.form(x[0] * (dP(1) + dP(2))), (0, num_vertices)) + V = fem.functionspace(msh, ("P", 1)) + u = fem.Function(V) + u.x.array[:] = 1 + + check(fem.form(u * x[0] * ufl.dP), (0, num_vertices)) + check(fem.form(u * x[0] * dP(1)), (0, num_vertices // 2)) + check(fem.form(u * x[0] * dP(2)), (num_vertices // 2, num_vertices)) + check(fem.form(u * x[0] * (dP(1) + dP(2))), (0, num_vertices)) + @pytest.mark.parametrize( "cell_type", @@ -126,3 +135,12 @@ def check(form, coordinate_range): check(fem.form(x[0] * v * dP(1)), (0, num_vertices // 2)) check(fem.form(x[0] * v * dP(2)), (num_vertices // 2, num_vertices)) check(fem.form(x[0] * v * (dP(1) + dP(2))), (0, num_vertices)) + + V = fem.functionspace(msh, ("P", 1)) + u = fem.Function(V) + u.x.array[:] = 1 + + check(fem.form(u * x[0] * v * ufl.dP), (0, num_vertices)) + check(fem.form(u * x[0] * v * dP(1)), (0, num_vertices // 2)) + check(fem.form(u * x[0] * v * dP(2)), (num_vertices // 2, num_vertices)) + check(fem.form(u * x[0] * v * (dP(1) + dP(2))), (0, num_vertices)) From 51365b51676dd11fef51fc0d0c2e08803f022c3d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 11 May 2025 22:32:23 +0200 Subject: [PATCH 021/104] deactivate c interface install --- .github/workflows/oneapi.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/oneapi.yml b/.github/workflows/oneapi.yml index 008c7a87631..17e9515654a 100644 --- a/.github/workflows/oneapi.yml +++ b/.github/workflows/oneapi.yml @@ -60,11 +60,11 @@ jobs: uv pip install --no-build-isolation git+${{ env.basix_ref }} uv pip install --no-build-isolation git+${{ env.ffcx_ref }} - - name: Install UFCx C interface - run: | - cmake -G Ninja -B ufcx-build-dir -S ffcx/cmake/ - cmake --build ufcx-build-dir - cmake --install ufcx-build-dir + # - name: Install UFCx C interface + # run: | + # cmake -G Ninja -B ufcx-build-dir -S ffcx/cmake/ + # cmake --build ufcx-build-dir + # cmake --install ufcx-build-dir - name: Configure DOLFINx C++ run: | From eca4568152dfc18d1a6327a0cc7016d617f126f1 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 11 May 2025 22:55:24 +0200 Subject: [PATCH 022/104] Debug oneapi --- .github/workflows/oneapi.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/oneapi.yml b/.github/workflows/oneapi.yml index 17e9515654a..51d3cc1e37b 100644 --- a/.github/workflows/oneapi.yml +++ b/.github/workflows/oneapi.yml @@ -60,6 +60,10 @@ jobs: uv pip install --no-build-isolation git+${{ env.basix_ref }} uv pip install --no-build-isolation git+${{ env.ffcx_ref }} + - name: Debug + run: | + uv pip list + python -c "import ffcx.codegeneration, sys; sys.stdout.write(ffcx.codegeneration.get_include_path())" # - name: Install UFCx C interface # run: | # cmake -G Ninja -B ufcx-build-dir -S ffcx/cmake/ From 6ce1f1bc1380d11cbcc4f2ba71f6ce4f5eac32bd Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 12 May 2025 09:45:32 +0200 Subject: [PATCH 023/104] Fix assertions and add non constant coefficient test --- cpp/dolfinx/fem/assemble_scalar_impl.h | 2 +- cpp/dolfinx/fem/assemble_vector_impl.h | 2 +- python/test/unit/fem/test_point_source.py | 32 +++++++++++++---------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index 3a5964f3c85..ab0cf1f0334 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -276,7 +276,7 @@ T assemble_scalar( std::span vertices = M.domain(IntegralType::vertex, i, 0); - assert((vertices.size() / 2) * cstride == coeffs.size()); + assert(vertices.size() * cstride == coeffs.size()); value += impl::assemble_vertices( x_dofmap, x, md::mdspan( P0, b, x_dofmap, x, diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index d4707ba6f33..9cda0e40b8a 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -42,8 +42,11 @@ def test_point_source_rank_0(cell_type, ghost_mode): comm, 4, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode ) - def check(form, coordinate_range): - expected_value_l = np.sum(msh.geometry.x[coordinate_range[0] : coordinate_range[1], 0]) + def check(form, coordinate_range, weighted=False): + a, b = coordinate_range + weights = np.arange(a, b) if weighted else np.ones(b - a, dtype=default_scalar_type) + + expected_value_l = np.sum(msh.geometry.x[a:b, 0] * weights) value_l = fem.assemble_scalar(form) assert expected_value_l == pytest.approx(value_l) @@ -70,12 +73,12 @@ def check(form, coordinate_range): V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V) - u.x.array[:] = 1 + u.x.array[:] = np.arange(0, u.x.array.size) - check(fem.form(u * x[0] * ufl.dP), (0, num_vertices)) - check(fem.form(u * x[0] * dP(1)), (0, num_vertices // 2)) - check(fem.form(u * x[0] * dP(2)), (num_vertices // 2, num_vertices)) - check(fem.form(u * x[0] * (dP(1) + dP(2))), (0, num_vertices)) + check(fem.form(u * x[0] * ufl.dP), (0, num_vertices), weighted=True) + check(fem.form(u * x[0] * dP(1)), (0, num_vertices // 2), weighted=True) + check(fem.form(u * x[0] * dP(2)), (num_vertices // 2, num_vertices), weighted=True) + check(fem.form(u * x[0] * (dP(1) + dP(2))), (0, num_vertices), weighted=True) @pytest.mark.parametrize( @@ -109,10 +112,11 @@ def test_point_source_rank_1(cell_type, ghost_mode): num_vertices = msh.topology.index_map(0).size_local - def check(form, coordinate_range): + def check(form, coordinate_range, weighted=False): a, b = coordinate_range + weights = np.arange(a, b) if weighted else np.ones(b - a, dtype=default_scalar_type) expected_value_l = np.zeros(num_vertices) - expected_value_l[a:b] = msh.geometry.x[a:b, 0] + expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights value_l = fem.assemble_vector(form) equal_l = np.allclose(expected_value_l, value_l.array[:num_vertices]) assert equal_l @@ -138,9 +142,9 @@ def check(form, coordinate_range): V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V) - u.x.array[:] = 1 + u.x.array[:] = np.arange(u.x.array.size) - check(fem.form(u * x[0] * v * ufl.dP), (0, num_vertices)) - check(fem.form(u * x[0] * v * dP(1)), (0, num_vertices // 2)) - check(fem.form(u * x[0] * v * dP(2)), (num_vertices // 2, num_vertices)) - check(fem.form(u * x[0] * v * (dP(1) + dP(2))), (0, num_vertices)) + check(fem.form(u * x[0] * v * ufl.dP), (0, num_vertices), weighted=True) + check(fem.form(u * x[0] * v * dP(1)), (0, num_vertices // 2), weighted=True) + check(fem.form(u * x[0] * v * dP(2)), (num_vertices // 2, num_vertices), weighted=True) + check(fem.form(u * x[0] * v * (dP(1) + dP(2))), (0, num_vertices), weighted=True) From aee26a78ecc8f2c9e47250149214423436b14cfa Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 09:04:18 +0200 Subject: [PATCH 024/104] Central extract kernel --- cpp/dolfinx/fem/utils.h | 134 ++++++++++++---------------------------- 1 file changed, 38 insertions(+), 96 deletions(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index c67cc8d013d..dd8bd695d46 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -86,6 +86,40 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, return cell_local_facet_pairs; } + +template > +using kern_t = std::function; + +template > +constexpr kern_t extract_kernel(const ufcx_integral* integral) +{ + kern_t kernel = nullptr; + + if constexpr (std::is_same_v) + kernel = integral->tabulate_tensor_float32; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS + else if constexpr (std::is_same_v>) + { + kernel = reinterpret_cast*, const int*, + const unsigned char*, void*)>(integral->tabulate_tensor_complex64); + } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS + else if constexpr (std::is_same_v) + kernel = integral->tabulate_tensor_float64; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS + else if constexpr (std::is_same_v>) + { + kernel = reinterpret_cast*, const int*, + const unsigned char*, void*)>(integral->tabulate_tensor_complex128); + } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS + + assert(k); + return kernel; +} } // namespace impl /// @brief Given an integral type and a set of entities, computes and @@ -455,8 +489,6 @@ Form create_form_factory( // Get list of integral IDs, and load tabulate tensor into memory for // each - using kern_t = std::function; std::map, integral_data> integrals; auto check_geometry_hash @@ -499,30 +531,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - kern_t k = nullptr; - if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float32; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>( - integral->tabulate_tensor_complex64); - } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float64; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>( - integral->tabulate_tensor_complex128); - } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS - + impl::kern_t k = impl::extract_kernel(integral); if (!k) { throw std::runtime_error( @@ -586,30 +595,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - kern_t k = nullptr; - if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float32; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>( - integral->tabulate_tensor_complex64); - } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float64; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>( - integral->tabulate_tensor_complex128); - } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS - assert(k); + impl::kern_t k = impl::extract_kernel(integral); // Build list of entities to assembler over const std::vector bfacets = mesh::exterior_facet_indices(*topology); @@ -694,29 +680,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - kern_t k = nullptr; - if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float32; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>( - integral->tabulate_tensor_complex64); - } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float64; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>( - integral->tabulate_tensor_complex128); - } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS + impl::kern_t k = impl::extract_kernel(integral); assert(k); // Build list of entities to assembler over @@ -795,29 +759,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - kern_t k = nullptr; - if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float32; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>( - integral->tabulate_tensor_complex64); - } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v) - k = integral->tabulate_tensor_float64; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) - { - k = reinterpret_cast*, const int*, - const unsigned char*, void*)>( - integral->tabulate_tensor_complex128); - } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS + impl::kern_t k = impl::extract_kernel(integral); assert(k); // Build list of entities to assembler over From 1507fa51e94fb357bbb19a5c9c838a629859d0ad Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 09:06:51 +0200 Subject: [PATCH 025/104] Use has_complex_ufcx_kernels instead of macro --- cpp/dolfinx/fem/utils.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index dd8bd695d46..0325a0d5323 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -98,24 +99,22 @@ constexpr kern_t extract_kernel(const ufcx_integral* integral) if constexpr (std::is_same_v) kernel = integral->tabulate_tensor_float32; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) + else if constexpr (has_complex_ufcx_kernels() + && std::is_same_v>) { kernel = reinterpret_cast*, const int*, const unsigned char*, void*)>(integral->tabulate_tensor_complex64); } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v) kernel = integral->tabulate_tensor_float64; -#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS - else if constexpr (std::is_same_v>) + else if constexpr (has_complex_ufcx_kernels() + && std::is_same_v>) { kernel = reinterpret_cast*, const int*, const unsigned char*, void*)>(integral->tabulate_tensor_complex128); } -#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS assert(k); return kernel; From ddd3004aa077c1d93ffd61c3035e64c5d0ea2ea5 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 10:02:08 +0200 Subject: [PATCH 026/104] Tidy casting --- cpp/dolfinx/fem/utils.h | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 0325a0d5323..a416002e4ac 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -88,36 +88,35 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, return cell_local_facet_pairs; } +template > +using kern_c_t = void (*)(T*, const T*, const T*, const U*, const int*, + const std::uint8_t*, void*); + template > using kern_t = std::function; template > -constexpr kern_t extract_kernel(const ufcx_integral* integral) +constexpr kern_t extract_kernel(const ufcx_integral* integral) { - kern_t kernel = nullptr; - if constexpr (std::is_same_v) - kernel = integral->tabulate_tensor_float32; - else if constexpr (has_complex_ufcx_kernels() - && std::is_same_v>) + return integral->tabulate_tensor_float32; + else if constexpr (std::is_same_v> + && has_complex_ufcx_kernels()) { - kernel = reinterpret_cast*, const int*, - const unsigned char*, void*)>(integral->tabulate_tensor_complex64); + return reinterpret_cast>( + integral->tabulate_tensor_complex64); } else if constexpr (std::is_same_v) - kernel = integral->tabulate_tensor_float64; - else if constexpr (has_complex_ufcx_kernels() - && std::is_same_v>) + return integral->tabulate_tensor_float64; + else if constexpr (std::is_same_v> + && has_complex_ufcx_kernels()) { - kernel = reinterpret_cast*, const int*, - const unsigned char*, void*)>(integral->tabulate_tensor_complex128); + return reinterpret_cast>( + integral->tabulate_tensor_complex128); } - - assert(k); - return kernel; + else + throw std::runtime_error("Could not extract kernel from ufcx integral."); } } // namespace impl From 3b93b6925fb87b49ad77d7b0f8c3bfc731e82a49 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 10:12:17 +0200 Subject: [PATCH 027/104] Env: repo + ref --- .github/workflows/ccpp.yml | 37 +++++++++++++++--------------- .github/workflows/fenicsx-refs.env | 9 +++++--- .github/workflows/macos.yml | 6 ++--- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index f57eeb708d1..a42fe744104 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -111,15 +111,15 @@ jobs: - name: Install FEniCS Python components (default branches/tags) if: github.event_name != 'workflow_dispatch' run: | - pip install git+${{ env.ufl_ref }} - pip install git+${{ env.basix_ref }} - pip install git+${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Install FEniCS Python components if: github.event_name == 'workflow_dispatch' run: | - pip install git+{{ env.ufl_ref }} - pip install git+${{ env.basix_ref }} - pip install git+${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Configure and install C++ run: | @@ -184,16 +184,15 @@ jobs: - name: Install FEniCS Python components (default branches/tags) if: github.event_name != 'workflow_dispatch' run: | - pip install git+${{ env.ufl_ref }} - pip install git+${{ env.basix_ref }} - pip install git+${{ env.ffcx_ref }} - + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Install FEniCS Python components if: github.event_name == 'workflow_dispatch' run: | - pip install git+${{ env.ufl_ref }} - pip install git+${{ env.basix_ref }} - pip install git+${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Configure, build and install C++ library run: | @@ -266,15 +265,15 @@ jobs: - name: Install FEniCS Python components (default branches/tags) if: github.event_name != 'workflow_dispatch' run: | - pip install git+${{ env.ufl_ref }} - pip install git+${{ env.basix_ref }} - pip install git+${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Install FEniCS Python components if: github.event_name == 'workflow_dispatch' run: | - pip install git+${{ env.ufl_ref }} - pip install git+${{ env.basix_ref }} - pip install git+${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Configure C++ run: | diff --git a/.github/workflows/fenicsx-refs.env b/.github/workflows/fenicsx-refs.env index 3a3316e219a..091d928d6cd 100644 --- a/.github/workflows/fenicsx-refs.env +++ b/.github/workflows/fenicsx-refs.env @@ -1,3 +1,6 @@ -basix_ref=https://github.com/FEniCS/basix.git@main -ufl_ref=https://github.com/FEniCS/ufl.git@main -ffcx_ref=https://github.com/schnellerhase/fenics-ffcx.git@point_measure +basix_repository=FEniCS/basix +basix_ref=main +ufl_repository=FEniCS/ufl +ufl_ref=main +ffcx_repository=schnellerhase/fenics-ffcx +ffcx_ref=main \ No newline at end of file diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 05fb58923d2..0f7d5244374 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -73,9 +73,9 @@ jobs: - name: Install FEniCSx dependencies run: | - python -m pip install git+${{ env.ufl_ref }} - python -m pip install git+${{ env.basix_ref }} - python -m pip install git+${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Build and install DOLFINx C++ library run: | From 263fff510579024e3a47ab9964f24bc0acbe8cc1 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 10:27:07 +0200 Subject: [PATCH 028/104] CI done? --- .github/workflows/fenicsx-refs.env | 2 +- .github/workflows/macos.yml | 6 +++--- .github/workflows/oneapi.yml | 31 +++++++++++++++++------------- .github/workflows/pyvista.yml | 6 +++--- .github/workflows/redhat.yml | 6 +++--- .github/workflows/sonarcloud.yml | 6 +++--- 6 files changed, 31 insertions(+), 26 deletions(-) diff --git a/.github/workflows/fenicsx-refs.env b/.github/workflows/fenicsx-refs.env index 091d928d6cd..9054acad9a6 100644 --- a/.github/workflows/fenicsx-refs.env +++ b/.github/workflows/fenicsx-refs.env @@ -3,4 +3,4 @@ basix_ref=main ufl_repository=FEniCS/ufl ufl_ref=main ffcx_repository=schnellerhase/fenics-ffcx -ffcx_ref=main \ No newline at end of file +ffcx_ref=point_measure \ No newline at end of file diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 0f7d5244374..776e9a83425 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -73,9 +73,9 @@ jobs: - name: Install FEniCSx dependencies run: | - pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} - pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} - pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} + python -m pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + python -m pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + python -m pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Build and install DOLFINx C++ library run: | diff --git a/.github/workflows/oneapi.yml b/.github/workflows/oneapi.yml index 92fad491065..995cb0c674f 100644 --- a/.github/workflows/oneapi.yml +++ b/.github/workflows/oneapi.yml @@ -55,21 +55,21 @@ jobs: conda info conda list - - name: Install UFL/Basix/FFCx - run: | - uv pip install --no-build-isolation git+${{ env.ufl_ref }} - uv pip install --no-build-isolation git+${{ env.basix_ref }} - uv pip install --no-build-isolation git+${{ env.ffcx_ref }} + - name: Install Basix + run: uv pip install --no-build-isolation git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + + - name: Clone FFCx + uses: actions/checkout@v4 + with: + path: ./ffcx + repository: ${{ env.ffcx_repository }} + ref: ${{ env.ffcx_ref }} - - name: Debug + - name: Install UFCx C interface run: | - uv pip list - python -c "import ffcx.codegeneration, sys; sys.stdout.write(ffcx.codegeneration.get_include_path())" - # - name: Install UFCx C interface - # run: | - # cmake -G Ninja -B ufcx-build-dir -S ffcx/cmake/ - # cmake --build ufcx-build-dir - # cmake --install ufcx-build-dir + cmake -G Ninja -B ufcx-build-dir -S ffcx/cmake/ + cmake --build ufcx-build-dir + cmake --install ufcx-build-dir - name: Configure DOLFINx C++ run: | @@ -80,6 +80,11 @@ jobs: cmake --build build cmake --install build + - name: Install UFL and FFCx modules + run: | + uv pip install --no-build-isolation git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + uv pip install --no-build-isolation ffcx/ + - name: Build and run DOLFINx C++ unit tests (serial and MPI) run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S cpp/test/ diff --git a/.github/workflows/pyvista.yml b/.github/workflows/pyvista.yml index 396eea07473..078a4969a0c 100644 --- a/.github/workflows/pyvista.yml +++ b/.github/workflows/pyvista.yml @@ -39,9 +39,9 @@ jobs: - name: Install FEniCS Python components run: | - python -m pip install git+${{ env.ufl_ref }} - python -m pip install git+${{ env.basix_ref }} - python -m pip install git+${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} apt-get update apt-get install -y --no-install-recommends libgl1-mesa-dev xvfb # pyvista apt-get install -y --no-install-recommends libqt5gui5t64 libgl1 # pyvistaqt diff --git a/.github/workflows/redhat.yml b/.github/workflows/redhat.yml index 4ee2a2151ea..38512424201 100644 --- a/.github/workflows/redhat.yml +++ b/.github/workflows/redhat.yml @@ -33,9 +33,9 @@ jobs: - name: Install FEniCS Python components run: | - python3 -m pip install git+${{ env.ufl_ref }} - python3 -m pip install git+${{ env.basix_ref }} - python3 -m pip install git+${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Configure C++ run: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 04ac9d8972f..ff06a647719 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -62,9 +62,9 @@ jobs: echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH - name: Install FEniCS Python components run: | - python -m pip install git+${{ env.ufl_ref }} - python -m pip install git+${{ env.basix_ref }} - python -m pip install git+${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Run build-wrapper run: | mkdir build From 27f02a5aacae9f2241eea4c06983acc3450c0af6 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 10:45:54 +0200 Subject: [PATCH 029/104] Add docs --- cpp/dolfinx/fem/utils.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index a416002e4ac..f517c869685 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -88,14 +88,25 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, return cell_local_facet_pairs; } +/// @brief Kernel C-pointer type. +/// @tparam T scalar type. +/// @tparam U geometry type. template > using kern_c_t = void (*)(T*, const T*, const T*, const U*, const int*, const std::uint8_t*, void*); +/// @brief Kernel callback type. +/// @tparam T scalar type. +/// @tparam U geometry type. template > using kern_t = std::function; +/// @brief Extract correct kernel by type from UFCx integral. +/// @tparam T scalar type of kernel to extract.s +/// @tparam U geometry type of kernel to extract. +/// @param integral UFCx integral to retrieve the kernel from. +/// @return Kernel callback. template > constexpr kern_t extract_kernel(const ufcx_integral* integral) { From 0cd54441239328d11b9684830c167bedb6492c02 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 10:49:03 +0200 Subject: [PATCH 030/104] Add space --- .github/workflows/fenicsx-refs.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fenicsx-refs.env b/.github/workflows/fenicsx-refs.env index 9054acad9a6..6b0e837ad75 100644 --- a/.github/workflows/fenicsx-refs.env +++ b/.github/workflows/fenicsx-refs.env @@ -3,4 +3,4 @@ basix_ref=main ufl_repository=FEniCS/ufl ufl_ref=main ffcx_repository=schnellerhase/fenics-ffcx -ffcx_ref=point_measure \ No newline at end of file +ffcx_ref=point_measure From fbb595d2b9802d81a79237fa8ad712b470fa97df Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 11:24:48 +0200 Subject: [PATCH 031/104] Tidy up packing code duplication --- cpp/dolfinx/fem/assemble_scalar_impl.h | 25 +++++------ cpp/dolfinx/fem/utils.h | 61 ++++++++++++-------------- 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index ab0cf1f0334..c6ddbb1d387 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -173,17 +173,15 @@ T assemble_vertices(mdspan2_t x_dofmap, for (std::size_t index = 0; index < vertices.extent(0); ++index) { std::int32_t cell = vertices(index, 0); - std::int32_t local_index = vertices(index, 1); + std::int32_t local_vertex_index = vertices(index, 1); // Get cell coordinates/geometry auto x_dofs = md::submdspan(x_dofmap, cell, md::full_extent); for (std::size_t i = 0; i < x_dofs.size(); ++i) - { std::copy_n(&x(x_dofs[i], 0), 3, std::next(cdofs.begin(), 3 * i)); - } - fn(&value, &coeffs(index, 0), constants.data(), cdofs.data(), &local_index, - nullptr, nullptr); + fn(&value, &coeffs(index, 0), constants.data(), cdofs.data(), + &local_vertex_index, nullptr, nullptr); } return value; @@ -274,15 +272,16 @@ T assemble_scalar( auto& [coeffs, cstride] = coefficients.at({IntegralType::vertex, i}); - std::span vertices - = M.domain(IntegralType::vertex, i, 0); - assert(vertices.size() * cstride == coeffs.size()); + std::span data = M.domain(IntegralType::vertex, i, 0); + assert(data.size() * cstride == coeffs.size()); + + md::mdspan> + cell_and_vertex(data.data(), data.size() / 2, 2); + value += impl::assemble_vertices( - x_dofmap, x, - md::mdspan>( - vertices.data(), vertices.size() / 2, 2), - fn, constants, md::mdspan(coeffs.data(), vertices.size() / 2, cstride)); + x_dofmap, x, cell_and_vertex, + fn, constants, md::mdspan(coeffs.data(), data.size() / 2, cstride)); } return value; diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index f517c869685..ee069ee75cd 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -776,32 +777,44 @@ Form create_form_factory( assert(v_to_c); auto c_to_v = topology->connectivity(tdim, 0); assert(c_to_v); - if (id == -1) + + // pack for a range of vertices a flattened list of cell index c_i and + // local vertex index l_i: + // [c_0, l_0, ..., c_n, l_n] + auto get_cells_and_vertices = [v_to_c](auto vertices_range) { - // Default vertex kernel operates on all (owned) vertices - std::int32_t num_vertices = topology->index_map(0)->size_local(); - std::vector default_vertices; - default_vertices.reserve(2 * num_vertices); - for (std::int32_t v = 0; v < num_vertices; v++) + std::vector cell_and_vertex; + cell_and_vertex.reserve(2 * vertices_range.size()); + for (std::int32_t vertex : vertices_range) { - auto cells = v_to_c->links(v); + auto cells = v_to_c->links(vertex); assert(cells.size() > 0); // Use first cell for assembly over by default - // TODO: user control in general for this? + // TODO: controllability? std::int32_t cell = cells[0]; - default_vertices.push_back(cell); + cell_and_vertex.push_back(cell); // Find local index of vertex within cell auto cell_vertices = c_to_v->links(cell); - auto it = std::ranges::find(cell_vertices, v); + auto it = std::ranges::find(cell_vertices, vertex); assert(it != cell_vertices.end()); std::int32_t local_index = std::distance(cell_vertices.begin(), it); - default_vertices.push_back(local_index); + cell_and_vertex.push_back(local_index); } + assert(cell_and_vertex.size() == 2 * vertices_range.size()); + return cell_and_vertex; + }; + + if (id == -1) + { + // Default vertex kernel operates on all (owned) vertices + std::int32_t num_vertices = topology->index_map(0)->size_local(); + std::vector cells_and_vertices = get_cells_and_vertices( + std::ranges::views::iota(0, num_vertices)); integrals.insert({{IntegralType::vertex, id, form_idx}, - {k, default_vertices, active_coeffs}}); + {k, cells_and_vertices, active_coeffs}}); } else { @@ -810,29 +823,11 @@ Form create_form_factory( [](auto& a) { return a.first; }); if (it != sd->second.end() and it->first == id) { - std::vector default_vertices; - // TODO: tidy up code duplication - for (std::int32_t v : it->second) - { - auto cells = v_to_c->links(v); - assert(cells.size() > 0); - - // Use first cell for assembly over by default - // TODO: user control in general for this? - std::int32_t cell = cells[0]; - default_vertices.push_back(cell); - - // Find local index of vertex within cell - auto cell_vertices = c_to_v->links(cell); - auto it = std::ranges::find(cell_vertices, v); - assert(it != cell_vertices.end()); - std::int32_t local_index - = std::distance(cell_vertices.begin(), it); - default_vertices.push_back(local_index); - } + std::vector cells_and_vertices + = get_cells_and_vertices(it->second); integrals.insert({{IntegralType::vertex, id, form_idx}, - {k, default_vertices, active_coeffs}}); + {k, cells_and_vertices, active_coeffs}}); } } } From 42273ef700aca838d57c3699788811b9fb1568af Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 11:25:54 +0200 Subject: [PATCH 032/104] kern_t template types --- cpp/dolfinx/fem/utils.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index ee069ee75cd..ea90a034466 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -541,7 +541,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - impl::kern_t k = impl::extract_kernel(integral); + impl::kern_t k = impl::extract_kernel(integral); if (!k) { throw std::runtime_error( @@ -605,7 +605,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - impl::kern_t k = impl::extract_kernel(integral); + impl::kern_t k = impl::extract_kernel(integral); // Build list of entities to assembler over const std::vector bfacets = mesh::exterior_facet_indices(*topology); @@ -690,7 +690,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - impl::kern_t k = impl::extract_kernel(integral); + impl::kern_t k = impl::extract_kernel(integral); assert(k); // Build list of entities to assembler over @@ -769,7 +769,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - impl::kern_t k = impl::extract_kernel(integral); + impl::kern_t k = impl::extract_kernel(integral); assert(k); // Build list of entities to assembler over From 1cd3d37e791fb5100bdf8ae9aef25a32faaab400 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 11:27:00 +0200 Subject: [PATCH 033/104] format --- cpp/dolfinx/fem/assemble_scalar_impl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index c6ddbb1d387..ff35811f5f0 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -280,8 +280,8 @@ T assemble_scalar( cell_and_vertex(data.data(), data.size() / 2, 2); value += impl::assemble_vertices( - x_dofmap, x, cell_and_vertex, - fn, constants, md::mdspan(coeffs.data(), data.size() / 2, cstride)); + x_dofmap, x, cell_and_vertex, fn, constants, + md::mdspan(coeffs.data(), data.size() / 2, cstride)); } return value; From 10ddac2b49c35f1acacdc6b837ee03384788aaa0 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 11:30:42 +0200 Subject: [PATCH 034/104] capture --- cpp/dolfinx/fem/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index ea90a034466..cb638784746 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -781,7 +781,7 @@ Form create_form_factory( // pack for a range of vertices a flattened list of cell index c_i and // local vertex index l_i: // [c_0, l_0, ..., c_n, l_n] - auto get_cells_and_vertices = [v_to_c](auto vertices_range) + auto get_cells_and_vertices = [v_to_c, c_to_v](auto vertices_range) { std::vector cell_and_vertex; cell_and_vertex.reserve(2 * vertices_range.size()); From 944b5b64d3b3b7390f4ecd42ca83a8c1b65430f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=2E=20K=C3=BChner?= <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 15:07:32 +0200 Subject: [PATCH 035/104] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jørgen Schartum Dokken --- cpp/dolfinx/fem/assemble_vector_impl.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index c342d41e21d..233aec03a46 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -907,7 +907,7 @@ void assemble_interior_facets( } } -/// @brief Execute kernel over cells and accumulate result in vector. +/// @brief Execute kernel over a set of vertices and accumulate result in vector. /// /// @tparam T Scalar type /// @tparam _bs Block size of the form test function dof map. If less @@ -916,7 +916,7 @@ void assemble_interior_facets( /// has performance benefits. /// @param[in] P0 Function that applies transformation `P0.b` in-place /// to `b` to transform test degrees-of-freedom. -/// @param[in,out] b Aray to accumulate into. +/// @param[in,out] b Array to accumulate into. /// @param[in] x_dofmap Dofmap for the mesh geometry. /// @param[in] x Mesh geometry (coordinates). /// @param[in] vertices Vertex indices `(vertices.size(), 2)` - first entry @@ -927,7 +927,7 @@ void assemble_interior_facets( /// @param[in] kernel Kernel function to execute over each cell. /// @param[in] constants Constant coefficient data in the kernel. /// @param[in] coeffs Coefficient data in the kernel. It has shape -/// `(cells.size(), num_cell_coeffs)`. `coeffs(i, j)` is the `j`th +/// `(vertices.size(), num_cell_coeffs)`. `coeffs(i, j)` is the `j`th /// coefficient for cell `i`. /// @param[in] cell_info0 Cell permutation information for the test /// function mesh. From 8c4e7be6520a297e6434658ff3ef1002ce1629bd Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 15:11:17 +0200 Subject: [PATCH 036/104] Ruff --- cpp/dolfinx/fem/assemble_vector_impl.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index 233aec03a46..ae28a06418f 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -907,7 +907,8 @@ void assemble_interior_facets( } } -/// @brief Execute kernel over a set of vertices and accumulate result in vector. +/// @brief Execute kernel over a set of vertices and accumulate result in +/// vector. /// /// @tparam T Scalar type /// @tparam _bs Block size of the form test function dof map. If less From f61acce6f18fac296336d5e6b9c42b407fe37ec4 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 16 May 2025 15:45:45 +0200 Subject: [PATCH 037/104] Parametrize over dtype --- python/test/unit/fem/test_point_source.py | 78 +++++++++++++---------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index 9cda0e40b8a..c47edffefc7 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -10,7 +10,7 @@ import pytest import ufl -from dolfinx import default_scalar_type, fem, mesh +from dolfinx import fem, mesh @pytest.mark.parametrize( @@ -26,29 +26,31 @@ ], ) @pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) -def test_point_source_rank_0(cell_type, ghost_mode): +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) +def test_point_source_rank_0(cell_type, ghost_mode, dtype): comm = MPI.COMM_WORLD + rdtype = np.real(dtype(0)).dtype msh = None cell_dim = mesh.cell_dim(cell_type) if cell_dim == 1: - msh = mesh.create_unit_interval(comm, 4, dtype=default_scalar_type, ghost_mode=ghost_mode) + msh = mesh.create_unit_interval(comm, 4, dtype=rdtype, ghost_mode=ghost_mode) elif cell_dim == 2: msh = mesh.create_unit_square( - comm, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + comm, 4, 4, cell_type=cell_type, dtype=rdtype, ghost_mode=ghost_mode ) elif cell_dim == 3: msh = mesh.create_unit_cube( - comm, 4, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + comm, 4, 4, 4, cell_type=cell_type, dtype=rdtype, ghost_mode=ghost_mode ) def check(form, coordinate_range, weighted=False): a, b = coordinate_range - weights = np.arange(a, b) if weighted else np.ones(b - a, dtype=default_scalar_type) + weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) expected_value_l = np.sum(msh.geometry.x[a:b, 0] * weights) value_l = fem.assemble_scalar(form) - assert expected_value_l == pytest.approx(value_l) + assert expected_value_l == pytest.approx(value_l, rel=1e2 * np.finfo(dtype).eps) expected_value = comm.allreduce(expected_value_l) value = comm.allreduce(value_l) @@ -58,7 +60,7 @@ def check(form, coordinate_range, weighted=False): x = ufl.SpatialCoordinate(msh) # Full domain - check(fem.form(x[0] * ufl.dP), (0, num_vertices)) + check(fem.form(x[0] * ufl.dP, dtype=dtype), (0, num_vertices)) # Split domain into first half of vertices (1) and second half of vertices (2) vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) @@ -67,18 +69,18 @@ def check(form, coordinate_range, weighted=False): meshtags = mesh.meshtags(msh, 0, vertices, tags) dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - check(fem.form(x[0] * dP(1)), (0, num_vertices // 2)) - check(fem.form(x[0] * dP(2)), (num_vertices // 2, num_vertices)) - check(fem.form(x[0] * (dP(1) + dP(2))), (0, num_vertices)) + check(fem.form(x[0] * dP(1), dtype=dtype), (0, num_vertices // 2)) + check(fem.form(x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices)) + check(fem.form(x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices)) V = fem.functionspace(msh, ("P", 1)) - u = fem.Function(V) - u.x.array[:] = np.arange(0, u.x.array.size) + u = fem.Function(V, dtype=dtype) + u.x.array[:] = np.arange(0, u.x.array.size, dtype=dtype) - check(fem.form(u * x[0] * ufl.dP), (0, num_vertices), weighted=True) - check(fem.form(u * x[0] * dP(1)), (0, num_vertices // 2), weighted=True) - check(fem.form(u * x[0] * dP(2)), (num_vertices // 2, num_vertices), weighted=True) - check(fem.form(u * x[0] * (dP(1) + dP(2))), (0, num_vertices), weighted=True) + check(fem.form(u * x[0] * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True) + check(fem.form(u * x[0] * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True) + check(fem.form(u * x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices), weighted=True) + check(fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) @pytest.mark.parametrize( @@ -94,40 +96,44 @@ def check(form, coordinate_range, weighted=False): ], ) @pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) -def test_point_source_rank_1(cell_type, ghost_mode): +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) +def test_point_source_rank_1(cell_type, ghost_mode, dtype): comm = MPI.COMM_WORLD + rdtype = np.real(dtype(0)).dtype msh = None cell_dim = mesh.cell_dim(cell_type) if cell_dim == 1: - msh = mesh.create_unit_interval(comm, 4, dtype=default_scalar_type, ghost_mode=ghost_mode) + msh = mesh.create_unit_interval(comm, 4, ghost_mode=ghost_mode, dtype=rdtype) elif cell_dim == 2: msh = mesh.create_unit_square( - comm, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + comm, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype ) elif cell_dim == 3: msh = mesh.create_unit_cube( - comm, 4, 4, 4, cell_type=cell_type, dtype=default_scalar_type, ghost_mode=ghost_mode + comm, 4, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype ) num_vertices = msh.topology.index_map(0).size_local def check(form, coordinate_range, weighted=False): a, b = coordinate_range - weights = np.arange(a, b) if weighted else np.ones(b - a, dtype=default_scalar_type) - expected_value_l = np.zeros(num_vertices) + weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) + expected_value_l = np.zeros(num_vertices, dtype=rdtype) expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights value_l = fem.assemble_vector(form) - equal_l = np.allclose(expected_value_l, value_l.array[:num_vertices]) + equal_l = np.allclose( + expected_value_l, np.real(value_l.array[:num_vertices]), atol=1e3 * np.finfo(rdtype).eps + ) assert equal_l assert comm.allreduce(equal_l, MPI.BAND) x = ufl.SpatialCoordinate(msh) V = fem.functionspace(msh, ("P", 1)) - v = ufl.TestFunction(V) + v = ufl.conj(ufl.TestFunction(V)) # Full domain - check(fem.form(x[0] * v * ufl.dP), (0, num_vertices)) + check(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) # Split domain into first half of vertices (1) and second half of vertices (2) vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) @@ -136,15 +142,19 @@ def check(form, coordinate_range, weighted=False): meshtags = mesh.meshtags(msh, 0, vertices, tags) dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - check(fem.form(x[0] * v * dP(1)), (0, num_vertices // 2)) - check(fem.form(x[0] * v * dP(2)), (num_vertices // 2, num_vertices)) - check(fem.form(x[0] * v * (dP(1) + dP(2))), (0, num_vertices)) + check(fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2)) + check(fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices)) + check(fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices)) V = fem.functionspace(msh, ("P", 1)) - u = fem.Function(V) + u = fem.Function(V, dtype=dtype) u.x.array[:] = np.arange(u.x.array.size) - check(fem.form(u * x[0] * v * ufl.dP), (0, num_vertices), weighted=True) - check(fem.form(u * x[0] * v * dP(1)), (0, num_vertices // 2), weighted=True) - check(fem.form(u * x[0] * v * dP(2)), (num_vertices // 2, num_vertices), weighted=True) - check(fem.form(u * x[0] * v * (dP(1) + dP(2))), (0, num_vertices), weighted=True) + check(fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True) + check(fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True) + check( + fem.form(u * x[0] * v * dP(2), dtype=dtype), + (num_vertices // 2, num_vertices), + weighted=True, + ) + check(fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) From a53929516f6e905e869dba4caf4a486ef8463131 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 17 May 2025 09:56:04 +0200 Subject: [PATCH 038/104] Debug: test without rank 1 --- python/test/unit/fem/test_point_source.py | 150 +++++++++++----------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index c47edffefc7..9aed1ee5723 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -83,78 +83,78 @@ def check(form, coordinate_range, weighted=False): check(fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) -@pytest.mark.parametrize( - "cell_type", - [ - mesh.CellType.interval, - mesh.CellType.triangle, - mesh.CellType.quadrilateral, - mesh.CellType.tetrahedron, - # mesh.CellType.pyramid, - mesh.CellType.prism, - mesh.CellType.hexahedron, - ], -) -@pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) -@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) -def test_point_source_rank_1(cell_type, ghost_mode, dtype): - comm = MPI.COMM_WORLD - rdtype = np.real(dtype(0)).dtype - - msh = None - cell_dim = mesh.cell_dim(cell_type) - if cell_dim == 1: - msh = mesh.create_unit_interval(comm, 4, ghost_mode=ghost_mode, dtype=rdtype) - elif cell_dim == 2: - msh = mesh.create_unit_square( - comm, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype - ) - elif cell_dim == 3: - msh = mesh.create_unit_cube( - comm, 4, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype - ) - - num_vertices = msh.topology.index_map(0).size_local - - def check(form, coordinate_range, weighted=False): - a, b = coordinate_range - weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) - expected_value_l = np.zeros(num_vertices, dtype=rdtype) - expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights - value_l = fem.assemble_vector(form) - equal_l = np.allclose( - expected_value_l, np.real(value_l.array[:num_vertices]), atol=1e3 * np.finfo(rdtype).eps - ) - assert equal_l - assert comm.allreduce(equal_l, MPI.BAND) - - x = ufl.SpatialCoordinate(msh) - V = fem.functionspace(msh, ("P", 1)) - v = ufl.conj(ufl.TestFunction(V)) - - # Full domain - check(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) - - # Split domain into first half of vertices (1) and second half of vertices (2) - vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) - tags = np.full_like(vertices, 1) - tags[tags.size // 2 :] = 2 - meshtags = mesh.meshtags(msh, 0, vertices, tags) - dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - - check(fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2)) - check(fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices)) - check(fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices)) - - V = fem.functionspace(msh, ("P", 1)) - u = fem.Function(V, dtype=dtype) - u.x.array[:] = np.arange(u.x.array.size) - - check(fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True) - check(fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True) - check( - fem.form(u * x[0] * v * dP(2), dtype=dtype), - (num_vertices // 2, num_vertices), - weighted=True, - ) - check(fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) +# @pytest.mark.parametrize( +# "cell_type", +# [ +# mesh.CellType.interval, +# mesh.CellType.triangle, +# mesh.CellType.quadrilateral, +# mesh.CellType.tetrahedron, +# # mesh.CellType.pyramid, +# mesh.CellType.prism, +# mesh.CellType.hexahedron, +# ], +# ) +# @pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) +# @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) +# def test_point_source_rank_1(cell_type, ghost_mode, dtype): +# comm = MPI.COMM_WORLD +# rdtype = np.real(dtype(0)).dtype + +# msh = None +# cell_dim = mesh.cell_dim(cell_type) +# if cell_dim == 1: +# msh = mesh.create_unit_interval(comm, 4, ghost_mode=ghost_mode, dtype=rdtype) +# elif cell_dim == 2: +# msh = mesh.create_unit_square( +# comm, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype +# ) +# elif cell_dim == 3: +# msh = mesh.create_unit_cube( +# comm, 4, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype +# ) + +# num_vertices = msh.topology.index_map(0).size_local + +# def check(form, coordinate_range, weighted=False): +# a, b = coordinate_range +# weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) +# expected_value_l = np.zeros(num_vertices, dtype=rdtype) +# expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights +# value_l = fem.assemble_vector(form) +# equal_l = np.allclose( +# expected_value_l, np.real(value_l.array[:num_vertices]), atol=1e3 * np.finfo(rdtype).eps +# ) +# assert equal_l +# assert comm.allreduce(equal_l, MPI.BAND) + +# x = ufl.SpatialCoordinate(msh) +# V = fem.functionspace(msh, ("P", 1)) +# v = ufl.conj(ufl.TestFunction(V)) + +# # Full domain +# check(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) + +# # Split domain into first half of vertices (1) and second half of vertices (2) +# vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) +# tags = np.full_like(vertices, 1) +# tags[tags.size // 2 :] = 2 +# meshtags = mesh.meshtags(msh, 0, vertices, tags) +# dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) + +# check(fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2)) +# check(fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices)) +# check(fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices)) + +# V = fem.functionspace(msh, ("P", 1)) +# u = fem.Function(V, dtype=dtype) +# u.x.array[:] = np.arange(u.x.array.size) + +# check(fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True) +# check(fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True) +# check( +# fem.form(u * x[0] * v * dP(2), dtype=dtype), +# (num_vertices // 2, num_vertices), +# weighted=True, +# ) +# check(fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) From dc3c8c7ebf1f1b6dd6034528853d3b839cdb7423 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 17 May 2025 11:46:39 +0200 Subject: [PATCH 039/104] Fix tolerances --- python/test/unit/fem/test_point_source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index 9aed1ee5723..57f40ba3722 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -50,11 +50,11 @@ def check(form, coordinate_range, weighted=False): expected_value_l = np.sum(msh.geometry.x[a:b, 0] * weights) value_l = fem.assemble_scalar(form) - assert expected_value_l == pytest.approx(value_l, rel=1e2 * np.finfo(dtype).eps) + assert expected_value_l == pytest.approx(value_l, abs=1e4 * np.finfo(rdtype).eps) expected_value = comm.allreduce(expected_value_l) value = comm.allreduce(value_l) - assert expected_value == pytest.approx(value) + assert expected_value == pytest.approx(value, abs=5e4 * np.finfo(rdtype).eps) num_vertices = msh.topology.index_map(0).size_local x = ufl.SpatialCoordinate(msh) From e91624ad07afb4c05be4cad5773604549df5dc4b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 17 May 2025 11:48:37 +0200 Subject: [PATCH 040/104] Reactivate rank1 --- python/test/unit/fem/test_point_source.py | 150 +++++++++++----------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index 57f40ba3722..53cb2612767 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -83,78 +83,78 @@ def check(form, coordinate_range, weighted=False): check(fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) -# @pytest.mark.parametrize( -# "cell_type", -# [ -# mesh.CellType.interval, -# mesh.CellType.triangle, -# mesh.CellType.quadrilateral, -# mesh.CellType.tetrahedron, -# # mesh.CellType.pyramid, -# mesh.CellType.prism, -# mesh.CellType.hexahedron, -# ], -# ) -# @pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) -# @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) -# def test_point_source_rank_1(cell_type, ghost_mode, dtype): -# comm = MPI.COMM_WORLD -# rdtype = np.real(dtype(0)).dtype - -# msh = None -# cell_dim = mesh.cell_dim(cell_type) -# if cell_dim == 1: -# msh = mesh.create_unit_interval(comm, 4, ghost_mode=ghost_mode, dtype=rdtype) -# elif cell_dim == 2: -# msh = mesh.create_unit_square( -# comm, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype -# ) -# elif cell_dim == 3: -# msh = mesh.create_unit_cube( -# comm, 4, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype -# ) - -# num_vertices = msh.topology.index_map(0).size_local - -# def check(form, coordinate_range, weighted=False): -# a, b = coordinate_range -# weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) -# expected_value_l = np.zeros(num_vertices, dtype=rdtype) -# expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights -# value_l = fem.assemble_vector(form) -# equal_l = np.allclose( -# expected_value_l, np.real(value_l.array[:num_vertices]), atol=1e3 * np.finfo(rdtype).eps -# ) -# assert equal_l -# assert comm.allreduce(equal_l, MPI.BAND) - -# x = ufl.SpatialCoordinate(msh) -# V = fem.functionspace(msh, ("P", 1)) -# v = ufl.conj(ufl.TestFunction(V)) - -# # Full domain -# check(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) - -# # Split domain into first half of vertices (1) and second half of vertices (2) -# vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) -# tags = np.full_like(vertices, 1) -# tags[tags.size // 2 :] = 2 -# meshtags = mesh.meshtags(msh, 0, vertices, tags) -# dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - -# check(fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2)) -# check(fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices)) -# check(fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices)) - -# V = fem.functionspace(msh, ("P", 1)) -# u = fem.Function(V, dtype=dtype) -# u.x.array[:] = np.arange(u.x.array.size) - -# check(fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True) -# check(fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True) -# check( -# fem.form(u * x[0] * v * dP(2), dtype=dtype), -# (num_vertices // 2, num_vertices), -# weighted=True, -# ) -# check(fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) +@pytest.mark.parametrize( + "cell_type", + [ + mesh.CellType.interval, + mesh.CellType.triangle, + mesh.CellType.quadrilateral, + mesh.CellType.tetrahedron, + # mesh.CellType.pyramid, + mesh.CellType.prism, + mesh.CellType.hexahedron, + ], +) +@pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) +def test_point_source_rank_1(cell_type, ghost_mode, dtype): + comm = MPI.COMM_WORLD + rdtype = np.real(dtype(0)).dtype + + msh = None + cell_dim = mesh.cell_dim(cell_type) + if cell_dim == 1: + msh = mesh.create_unit_interval(comm, 4, ghost_mode=ghost_mode, dtype=rdtype) + elif cell_dim == 2: + msh = mesh.create_unit_square( + comm, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype + ) + elif cell_dim == 3: + msh = mesh.create_unit_cube( + comm, 4, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype + ) + + num_vertices = msh.topology.index_map(0).size_local + + def check(form, coordinate_range, weighted=False): + a, b = coordinate_range + weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) + expected_value_l = np.zeros(num_vertices, dtype=rdtype) + expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights + value_l = fem.assemble_vector(form) + equal_l = np.allclose( + expected_value_l, np.real(value_l.array[:num_vertices]), atol=1e3 * np.finfo(rdtype).eps + ) + assert equal_l + assert comm.allreduce(equal_l, MPI.BAND) + + x = ufl.SpatialCoordinate(msh) + V = fem.functionspace(msh, ("P", 1)) + v = ufl.conj(ufl.TestFunction(V)) + + # Full domain + check(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) + + # Split domain into first half of vertices (1) and second half of vertices (2) + vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) + tags = np.full_like(vertices, 1) + tags[tags.size // 2 :] = 2 + meshtags = mesh.meshtags(msh, 0, vertices, tags) + dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) + + check(fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2)) + check(fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices)) + check(fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices)) + + V = fem.functionspace(msh, ("P", 1)) + u = fem.Function(V, dtype=dtype) + u.x.array[:] = np.arange(u.x.array.size) + + check(fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True) + check(fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True) + check( + fem.form(u * x[0] * v * dP(2), dtype=dtype), + (num_vertices // 2, num_vertices), + weighted=True, + ) + check(fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) From 98790c9100a581d1f8fe3d9d590f001a8e7bb6c6 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 26 Jul 2025 12:39:27 +0200 Subject: [PATCH 041/104] Remove workflows removed on main --- .github/workflows/oneapi.yml | 115 ---------------------------------- .github/workflows/pyvista.yml | 68 -------------------- .github/workflows/redhat.yml | 94 --------------------------- 3 files changed, 277 deletions(-) delete mode 100644 .github/workflows/oneapi.yml delete mode 100644 .github/workflows/pyvista.yml delete mode 100644 .github/workflows/redhat.yml diff --git a/.github/workflows/oneapi.yml b/.github/workflows/oneapi.yml deleted file mode 100644 index 3ea44e12c48..00000000000 --- a/.github/workflows/oneapi.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: oneAPI compilers - -on: - pull_request: - branches: - - main - push: - branches: - - "main" - tags: - - "v*" - merge_group: - branches: - - main - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - container: ubuntu:24.04 - - env: - CC: icx - CXX: icpx - MPICH_CC: icx - MPICH_CXX: icpx - I_MPI_OFI_LIBRARY_INTERNAL: 0 - OMP_NUM_THREADS: 1 - OPENBLAS_NUM_THREADS: 1 - KMP_DUPLICATE_LIB_OK: 1 # Silences error on multiple openMP installs found - - name: oneAPI build and test - - defaults: - run: - shell: bash -el {0} - - steps: - - name: Install compiler dependencies - run: | - apt-get -y update - apt-get -y install binutils libstdc++-14-dev git - apt-get install -y libglu1-mesa libgl1 libxrender1 libxcursor1 libxft2 libxinerama1 libegl1 - - - uses: actions/checkout@v4 - - - name: Load environment variables - run: cat .github/workflows/fenicsx-refs.env >> $GITHUB_ENV - - - uses: conda-incubator/setup-miniconda@v3 - with: - activate-environment: oneapi-test-env - environment-file: .github/workflows/oneapi-conda/environment.yml - auto-activate-base: false - - run: | - conda info - conda list - - - name: Install Basix - run: uv pip install --no-build-isolation git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} - - - name: Clone FFCx - uses: actions/checkout@v4 - with: - path: ./ffcx - repository: ${{ env.ffcx_repository }} - ref: ${{ env.ffcx_ref }} - - - name: Install UFCx C interface - run: | - cmake -G Ninja -B ufcx-build-dir -S ffcx/cmake/ - cmake --build ufcx-build-dir - cmake --install ufcx-build-dir - - - name: Configure DOLFINx C++ - run: | - cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -DDOLFINX_ENABLE_SCOTCH=on -DDOLFINX_ENABLE_KAHIP=on -DDOLFINX_UFCX_PYTHON=off -B build -S cpp/ - - - name: Build and install DOLFINx C++ library - run: | - cmake --build build - cmake --install build - - - name: Install UFL and FFCx modules - run: | - uv pip install --no-build-isolation git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} - uv pip install --no-build-isolation ffcx/ - - - name: Build and run DOLFINx C++ unit tests (serial and MPI) - run: | - cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S cpp/test/ - cmake --build build/test - cd build/test - ctest --output-on-failure -R unittests - mpiexec -n 2 ctest --output-on-failure -R unittests - - - name: Build and run DOLFINx C++ regression tests (serial and MPI (np=2)) - run: | - cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/demo/ -S cpp/demo/ - cmake --build build/demo - cd build/demo - ctest -R demo -R serial - ctest -R demo -R mpi_2 - - - name: Build DOLFINx Python interface - run: uv pip -v install --no-build-isolation --config-settings=cmake.build-type="Developer" python/ - - name: Run DOLFINx demos (Python, serial) - run: python -m pytest -v -n=2 -m serial --durations=10 python/demo/test.py - - name: Run DOLFINx demos (Python, MPI (np=2)) - run: pytest -m mpi --num-proc=2 python/demo/test.py - - - name: Run DOLFINx Python unit tests (serial) - run: python -m pytest -m "not adios2" -n=auto --durations=50 python/test/unit - - name: Run DOLFINx Python unit tests (MPI, np=2) - run: mpiexec -n 2 python -m pytest -m "not adios2" python/test/unit diff --git a/.github/workflows/pyvista.yml b/.github/workflows/pyvista.yml deleted file mode 100644 index 078a4969a0c..00000000000 --- a/.github/workflows/pyvista.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Test visualisation demos - -on: - # Uncomment the below to trigger tests on push - # push: - # branches: - # - "**" - schedule: - # '*' is a special character in YAML, so string must be quoted - - cron: "0 1 * * *" - workflow_dispatch: ~ - -jobs: - pyvista: - runs-on: ubuntu-latest - container: ghcr.io/fenics/test-env:current-openmpi - - env: - # For pyvista/pyvistaqt - DISPLAY: ":99.0" - PYVISTA_OFF_SCREEN: true - PYVISTA_QT_VERSION: 0.11.1 - PYVISTA_VERSION: 0.44.2 - QT_DEBUG_PLUGINS: 1 - - PETSC_ARCH: ${{ matrix.petsc_arch }} - OMPI_ALLOW_RUN_AS_ROOT: 1 - OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1 - - strategy: - matrix: - petsc_arch: [linux-gnu-real64-32, linux-gnu-complex128-32] - - steps: - - uses: actions/checkout@v4 - - - name: Load environment variables - run: cat .github/workflows/fenicsx-refs.env >> $GITHUB_ENV - - - name: Install FEniCS Python components - run: | - pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} - pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} - pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - apt-get update - apt-get install -y --no-install-recommends libgl1-mesa-dev xvfb # pyvista - apt-get install -y --no-install-recommends libqt5gui5t64 libgl1 # pyvistaqt - pip install pyvista==${PYVISTA_VERSION} - pip install pyqt5 pyvistaqt==${PYVISTA_QT_VERSION} - pip install --no-build-isolation -r python/build-requirements.txt - - - name: Configure C++ - run: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ - - name: Build and install C++ library - run: | - cmake --build build - cmake --install build - - - name: Build Python interface - run: pip -v install --check-build-dependencies --config-settings=build-dir="build" --config-settings=cmake.build-type="Debug" --no-build-isolation 'python/[test]' - - - name: Run pyvista demos (Python, serial) - run: | - pip install pytest-xdist - python3 -m pytest -v -n 2 -m serial --durations=10 python/demo/test.py - - - name: Run pyvista demos (Python, MPI (np=2)) - run: python3 -m pytest -v -m mpi --num-proc=2 python/demo/test.py diff --git a/.github/workflows/redhat.yml b/.github/workflows/redhat.yml deleted file mode 100644 index 8ee7edbc151..00000000000 --- a/.github/workflows/redhat.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Red Hat clone - -on: - pull_request: - branches: - - main - push: - branches: - - "main" - tags: - - "v*" - merge_group: - branches: - - main - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - container: fenicsproject/test-env:current-redhat - - name: Rocky build and test - - steps: - - uses: actions/checkout@v4 - - - name: Load environment variables - run: cat .github/workflows/fenicsx-refs.env >> $GITHUB_ENV - - - name: Install Python build dependencies - run: | - python3 -m pip install --no-cache-dir --upgrade pip setuptools wheel - - - name: Install FEniCS Python components - run: | - pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} - pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} - pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - - - name: Configure C++ - run: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ - - name: Build and install C++ library - run: | - cmake --build build - cmake --install build - - - name: Build C++ unit tests - run: | - cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S cpp/test/ - cmake --build build/test - - name: Run C++ unit tests (serial) - run: | - cd build/test - ctest -V --output-on-failure -R unittests - - name: Run C++ unit tests (MPI) - run: | - cd build/test - mpiexec -np 2 ctest -V --output-on-failure -R unittests - - name: Build and run C++ regression tests (serial and MPI (np=2)) - run: | - cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/demo/ -S cpp/demo/ - cmake --build build/demo - cd build/demo - ctest -V -R demo -R serial - ctest -V -R demo -R mpi_2 - - - name: Install Python dependencies - run: | - - name: Build Python interface (editable install) - run: | - python3 -m pip install --upgrade -r python/build-requirements.txt - python3 -m pip install --check-build-dependencies --no-build-isolation --config-settings=cmake.build-type=Debug --config-settings=build-dir="build" -e 'python/[test]' - - - name: Set default DOLFINx JIT options - run: | - mkdir -p ~/.config/dolfinx - echo '{ "cffi_extra_compile_args": ["-g0", "-O0" ] }' > ~/.config/dolfinx/dolfinx_jit_options.json - - - name: Install pyvista and gmsh - run: | - dnf install -y mesa-libGLU libX11 libXrender mesa-libEGL libglvnd-glx libXcursor libXft libXinerama - python3 -m pip install gmsh pyvista - - - name: Run demos (Python, serial) - run: | - python3 -m pip install pytest-xdist - python3 -m pytest -n auto -m serial --durations=10 python/demo/test.py - - name: Run demos (Python, MPI (np=2)) - run: python3 -m pytest -m mpi --num-proc=2 python/demo/test.py - - - name: Run Python unit tests (serial) - run: python3 -m pytest -n auto -m "not adios2" --durations=50 python/test/unit/ - - name: Run Python unit tests (MPI, np=2) - run: mpirun -np 2 python3 -m pytest -m "not adios2" python/test/unit/ From aafb7290f2d4dcd3de4e186e94b631a6fd98a2b3 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Sat, 26 Jul 2025 22:03:26 +0100 Subject: [PATCH 042/104] Update CI --- .github/workflows/ci-spack.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-spack.yml b/.github/workflows/ci-spack.yml index 8e00e65f94b..b7cd4127f98 100644 --- a/.github/workflows/ci-spack.yml +++ b/.github/workflows/ci-spack.yml @@ -86,9 +86,9 @@ jobs: run: | . ./spack-src/share/spack/setup-env.sh spack env activate . - pip install git+https://github.com/fenics/ufl.git@${{ env.ufl_ref }} - pip install git+https://github.com/fenics/basix.git@${{ env.basix_ref }} - pip install git+https://github.com/fenics/ffcx.git@${{ env.ffcx_ref }} + pip install git+https://github.com/fenics/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/fenics/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/fenics/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Configure and build C++ run: | From 09582e548169dcda6decb42a5a0eaeafe31569ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=2E=20K=C3=BChner?= <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:14:57 +0200 Subject: [PATCH 043/104] Update .github/workflows/ci-spack.yml --- .github/workflows/ci-spack.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-spack.yml b/.github/workflows/ci-spack.yml index b7cd4127f98..4d7a08af367 100644 --- a/.github/workflows/ci-spack.yml +++ b/.github/workflows/ci-spack.yml @@ -86,9 +86,9 @@ jobs: run: | . ./spack-src/share/spack/setup-env.sh spack env activate . - pip install git+https://github.com/fenics/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} - pip install git+https://github.com/fenics/${{ env.basix_repository }}.git@${{ env.basix_ref }} - pip install git+https://github.com/fenics/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} + pip install git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} + pip install git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} + pip install git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} - name: Configure and build C++ run: | From 171b4846531131dad240d9cf8362cf54895e0328 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:55:09 +0200 Subject: [PATCH 044/104] Remove capture everything --- cpp/dolfinx/fem/utils.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index b7474d2d00c..7f1f8ab4828 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -165,9 +165,9 @@ fem::compute_integration_domains(fem::IntegralType integral_type, entities = entities.first(std::distance(entities.begin(), it1)); } - auto cell_connectivity - = [&]() -> std::pair>, - std::shared_ptr>> + auto cell_connectivity = [tdim, &topology]() + -> std::pair>, + std::shared_ptr>> { auto f_to_c = topology.connectivity(tdim - 1, tdim); if (!f_to_c) From f2b4b0a17f456192b5ad11b3d45e8fa55a984741 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:55:35 +0200 Subject: [PATCH 045/104] Remove future comment --- cpp/dolfinx/fem/utils.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 7f1f8ab4828..268df4734ae 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -243,9 +243,6 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } break; } - // C++ 23 - // default: - // std::unreachable(); } return entity_data; From bff97b18203e30326e67d2145c65e4eae1202fc0 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:00:52 +0200 Subject: [PATCH 046/104] Change test helper function name --- python/test/unit/fem/test_point_source.py | 60 ++++++++++++++++------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_point_source.py index 53cb2612767..56b907628c9 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_point_source.py @@ -44,7 +44,7 @@ def test_point_source_rank_0(cell_type, ghost_mode, dtype): comm, 4, 4, 4, cell_type=cell_type, dtype=rdtype, ghost_mode=ghost_mode ) - def check(form, coordinate_range, weighted=False): + def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): a, b = coordinate_range weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) @@ -60,7 +60,7 @@ def check(form, coordinate_range, weighted=False): x = ufl.SpatialCoordinate(msh) # Full domain - check(fem.form(x[0] * ufl.dP, dtype=dtype), (0, num_vertices)) + check_vertex_integral_against_sum(fem.form(x[0] * ufl.dP, dtype=dtype), (0, num_vertices)) # Split domain into first half of vertices (1) and second half of vertices (2) vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) @@ -69,18 +69,30 @@ def check(form, coordinate_range, weighted=False): meshtags = mesh.meshtags(msh, 0, vertices, tags) dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - check(fem.form(x[0] * dP(1), dtype=dtype), (0, num_vertices // 2)) - check(fem.form(x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices)) - check(fem.form(x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices)) + check_vertex_integral_against_sum(fem.form(x[0] * dP(1), dtype=dtype), (0, num_vertices // 2)) + check_vertex_integral_against_sum( + fem.form(x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices) + ) + check_vertex_integral_against_sum( + fem.form(x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices) + ) V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V, dtype=dtype) u.x.array[:] = np.arange(0, u.x.array.size, dtype=dtype) - check(fem.form(u * x[0] * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True) - check(fem.form(u * x[0] * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True) - check(fem.form(u * x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices), weighted=True) - check(fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) + check_vertex_integral_against_sum( + fem.form(u * x[0] * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices), weighted=True + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True + ) @pytest.mark.parametrize( @@ -116,7 +128,7 @@ def test_point_source_rank_1(cell_type, ghost_mode, dtype): num_vertices = msh.topology.index_map(0).size_local - def check(form, coordinate_range, weighted=False): + def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): a, b = coordinate_range weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) expected_value_l = np.zeros(num_vertices, dtype=rdtype) @@ -133,7 +145,7 @@ def check(form, coordinate_range, weighted=False): v = ufl.conj(ufl.TestFunction(V)) # Full domain - check(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) + check_vertex_integral_against_sum(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) # Split domain into first half of vertices (1) and second half of vertices (2) vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) @@ -142,19 +154,31 @@ def check(form, coordinate_range, weighted=False): meshtags = mesh.meshtags(msh, 0, vertices, tags) dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - check(fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2)) - check(fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices)) - check(fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices)) + check_vertex_integral_against_sum( + fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2) + ) + check_vertex_integral_against_sum( + fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices) + ) + check_vertex_integral_against_sum( + fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices) + ) V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V, dtype=dtype) u.x.array[:] = np.arange(u.x.array.size) - check(fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True) - check(fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True) - check( + check_vertex_integral_against_sum( + fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True + ) + check_vertex_integral_against_sum( fem.form(u * x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices), weighted=True, ) - check(fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True) + check_vertex_integral_against_sum( + fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True + ) From bcfb361fe40ef024e98a59b36d508e141a16e5fc Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:02:36 +0200 Subject: [PATCH 047/104] Change to md:: --- cpp/dolfinx/fem/pack.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/pack.h b/cpp/dolfinx/fem/pack.h index d9d6d6ae3e5..b33f147dc37 100644 --- a/cpp/dolfinx/fem/pack.h +++ b/cpp/dolfinx/fem/pack.h @@ -351,7 +351,7 @@ void pack_coefficients(const Form& form, = impl::get_cell_orientation_info(*coefficients[coeff]); impl::pack_coefficient_entity( std::span(c), cstride, *coefficients[coeff], cell_info, - std::submdspan(vertices, md::full_extent, 0), offsets[coeff]); + md::submdspan(vertices, md::full_extent, 0), offsets[coeff]); } break; } From d90075d2970e022368bd5f03417cf344f81ee924 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:12:37 +0200 Subject: [PATCH 048/104] Move kernel extraction to own header --- cpp/dolfinx/fem/CMakeLists.txt | 1 + cpp/dolfinx/fem/kernel.h | 57 ++++++++++++++++++++++++++++++++++ cpp/dolfinx/fem/utils.h | 42 +------------------------ 3 files changed, 59 insertions(+), 41 deletions(-) create mode 100644 cpp/dolfinx/fem/kernel.h diff --git a/cpp/dolfinx/fem/CMakeLists.txt b/cpp/dolfinx/fem/CMakeLists.txt index 8016d431877..548df96a011 100644 --- a/cpp/dolfinx/fem/CMakeLists.txt +++ b/cpp/dolfinx/fem/CMakeLists.txt @@ -18,6 +18,7 @@ set(HEADERS_fem ${CMAKE_CURRENT_SOURCE_DIR}/dofmapbuilder.h ${CMAKE_CURRENT_SOURCE_DIR}/dolfinx_fem.h ${CMAKE_CURRENT_SOURCE_DIR}/interpolate.h + ${CMAKE_CURRENT_SOURCE_DIR}/kernel.h ${CMAKE_CURRENT_SOURCE_DIR}/petsc.h ${CMAKE_CURRENT_SOURCE_DIR}/pack.h ${CMAKE_CURRENT_SOURCE_DIR}/sparsitybuild.h diff --git a/cpp/dolfinx/fem/kernel.h b/cpp/dolfinx/fem/kernel.h new file mode 100644 index 00000000000..b67df02a3a3 --- /dev/null +++ b/cpp/dolfinx/fem/kernel.h @@ -0,0 +1,57 @@ +// Copyright (C) 2025 Paul T. Kühner +// +// This file is part of DOLFINx (https://www.fenicsproject.org) +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include + +namespace dolfinx::fem::impl +{ +/// @brief Kernel C-pointer type. +/// @tparam T scalar type. +/// @tparam U geometry type. +template > +using kern_c_t = void (*)(T*, const T*, const T*, const U*, const int*, + const std::uint8_t*, void*); + +/// @brief Kernel callback type. +/// @tparam T scalar type. +/// @tparam U geometry type. +template > +using kern_t = std::function; + +/// @brief Extract correct kernel by type from UFCx integral. +/// @tparam T scalar type of kernel to extract.s +/// @tparam U geometry type of kernel to extract. +/// @param integral UFCx integral to retrieve the kernel from. +/// @return Kernel callback. +template > +constexpr kern_t extract_kernel(const ufcx_integral* integral) +{ + if constexpr (std::is_same_v) + return integral->tabulate_tensor_float32; + else if constexpr (std::is_same_v) + return integral->tabulate_tensor_float64; + else if constexpr (std::is_same_v> + && has_complex_ufcx_kernels()) + { + return reinterpret_cast>( + integral->tabulate_tensor_complex64); + } + else if constexpr (std::is_same_v> + && has_complex_ufcx_kernels()) + { + return reinterpret_cast>( + integral->tabulate_tensor_complex128); + } + else + throw std::runtime_error("Could not extract kernel from ufcx integral."); +} + +} // namespace dolfinx::fem::impl \ No newline at end of file diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 67a107bd1a7..3390f4adec7 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -15,6 +15,7 @@ #include "Form.h" #include "Function.h" #include "FunctionSpace.h" +#include "kernel.h" #include "sparsitybuild.h" #include #include @@ -91,47 +92,6 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, return cell_local_facet_pairs; } -/// @brief Kernel C-pointer type. -/// @tparam T scalar type. -/// @tparam U geometry type. -template > -using kern_c_t = void (*)(T*, const T*, const T*, const U*, const int*, - const std::uint8_t*, void*); - -/// @brief Kernel callback type. -/// @tparam T scalar type. -/// @tparam U geometry type. -template > -using kern_t = std::function; - -/// @brief Extract correct kernel by type from UFCx integral. -/// @tparam T scalar type of kernel to extract.s -/// @tparam U geometry type of kernel to extract. -/// @param integral UFCx integral to retrieve the kernel from. -/// @return Kernel callback. -template > -constexpr kern_t extract_kernel(const ufcx_integral* integral) -{ - if constexpr (std::is_same_v) - return integral->tabulate_tensor_float32; - else if constexpr (std::is_same_v> - && has_complex_ufcx_kernels()) - { - return reinterpret_cast>( - integral->tabulate_tensor_complex64); - } - else if constexpr (std::is_same_v) - return integral->tabulate_tensor_float64; - else if constexpr (std::is_same_v> - && has_complex_ufcx_kernels()) - { - return reinterpret_cast>( - integral->tabulate_tensor_complex128); - } - else - throw std::runtime_error("Could not extract kernel from ufcx integral."); -} } // namespace impl /// @brief Given an integral type and a set of entities, computes and From 5836e552b45aadc23acb236c634d16a8265ee03e Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:18:51 +0200 Subject: [PATCH 049/104] Fix bad rebase --- .github/workflows/ccpp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 27d57d05747..99f1cd6ff58 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -271,6 +271,7 @@ jobs: - name: Install FEniCS Python components run: | + pip install -r python/build-requirements.txt pip install --no-build-isolation git+https://github.com/${{ env.ufl_repository }}.git@${{ env.ufl_ref }} pip install --no-build-isolation git+https://github.com/${{ env.basix_repository }}.git@${{ env.basix_ref }} pip install --no-build-isolation git+https://github.com/${{ env.ffcx_repository }}.git@${{ env.ffcx_ref }} From 8ab65ead983fe9a2dbe9b004c4ddd43db17cc3cd Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 1 Aug 2025 19:09:52 +0200 Subject: [PATCH 050/104] Rely on impliciy span conversion --- cpp/dolfinx/fem/assemble_vector_impl.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index 4aa2d067f2a..e3f52f5d8c1 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -681,7 +681,6 @@ void assemble_cells( // Create data structures used in assembly std::vector> cdofs(3 * x_dofmap.extent(1)); std::vector be(bs * dmap.extent(1)); - std::span _be(be); // Iterate over active cells for (std::size_t index = 0; index < cells.size(); ++index) @@ -699,7 +698,7 @@ void assemble_cells( std::ranges::fill(be, 0); kernel(be.data(), &coeffs(index, 0), constants.data(), cdofs.data(), nullptr, nullptr, nullptr); - P0(_be, cell_info0, c0, 1); + P0(be, cell_info0, c0, 1); // Scatter cell vector to 'global' vector array auto dofs = md::submdspan(dmap, c0, md::full_extent); @@ -770,7 +769,7 @@ void assemble_exterior_facets( const int num_dofs = dmap.extent(1); std::vector> cdofs(3 * x_dofmap.extent(1)); std::vector be(bs * num_dofs); - std::span _be(be); + assert(facets0.size() == facets.size()); for (std::size_t f = 0; f < facets.extent(0); ++f) { @@ -793,7 +792,7 @@ void assemble_exterior_facets( fn(be.data(), &coeffs(f, 0), constants.data(), cdofs.data(), &local_facet, &perm, nullptr); - P0(_be, cell_info0, cell0, 1); + P0(be, cell_info0, cell0, 1); // Add element vector to global vector auto dofs = md::submdspan(dmap, cell0, md::full_extent); @@ -993,7 +992,6 @@ void assemble_vertices( // Create data structures used in assembly std::vector> cdofs(3 * x_dofmap.extent(1)); std::vector be(bs * dmap.extent(1)); - std::span _be(be); // Iterate over active cells for (std::size_t index = 0; index < vertices.extent(0); ++index) @@ -1012,7 +1010,7 @@ void assemble_vertices( std::ranges::fill(be, 0); kernel(be.data(), &coeffs(index, 0), constants.data(), cdofs.data(), &local_index, nullptr, nullptr); - P0(_be, cell_info0, c0, 1); + P0(be, cell_info0, c0, 1); // Scatter cell vector to 'global' vector array auto dofs = md::submdspan(dmap, c0, md::full_extent); From d19f02ec2c12f82a59a081a8babf9358f728183b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=2E=20K=C3=BChner?= <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 11:26:37 +0200 Subject: [PATCH 051/104] Update cpp/dolfinx/fem/kernel.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jørgen Schartum Dokken --- cpp/dolfinx/fem/kernel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/kernel.h b/cpp/dolfinx/fem/kernel.h index b67df02a3a3..5055eaf62ff 100644 --- a/cpp/dolfinx/fem/kernel.h +++ b/cpp/dolfinx/fem/kernel.h @@ -27,7 +27,7 @@ using kern_t = std::function; /// @brief Extract correct kernel by type from UFCx integral. -/// @tparam T scalar type of kernel to extract.s +/// @tparam T scalar type of kernel to extract. /// @tparam U geometry type of kernel to extract. /// @param integral UFCx integral to retrieve the kernel from. /// @return Kernel callback. From 3479a5646d83556326b749eb93d1b5f5c6686e40 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:48:09 +0200 Subject: [PATCH 052/104] cell_connectivity -> get_cell_facet_connectivity --- cpp/dolfinx/fem/utils.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 268df4734ae..cbfb9374ad5 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -165,7 +165,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, entities = entities.first(std::distance(entities.begin(), it1)); } - auto cell_connectivity = [tdim, &topology]() + auto get_cell_facet_connectivity = [tdim, &topology]() -> std::pair>, std::shared_ptr>> { @@ -196,7 +196,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } case IntegralType::exterior_facet: { - auto [f_to_c, c_to_f] = cell_connectivity(); + auto [f_to_c, c_to_f] = get_cell_facet_connectivity(); // Create list of tagged boundary facets const std::vector bfacets = mesh::exterior_facet_indices(topology); std::vector facets; @@ -212,7 +212,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } case IntegralType::interior_facet: { - auto [f_to_c, c_to_f] = cell_connectivity(); + auto [f_to_c, c_to_f] = get_cell_facet_connectivity(); // Create indicator for interprocess facets assert(topology.index_map(tdim - 1)); From 4c68c1d893b346b620bf147a0ee68e5904e7482b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:55:05 +0200 Subject: [PATCH 053/104] Remove codim>0 check --- cpp/dolfinx/fem/pack.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/cpp/dolfinx/fem/pack.h b/cpp/dolfinx/fem/pack.h index b33f147dc37..6e27298e88f 100644 --- a/cpp/dolfinx/fem/pack.h +++ b/cpp/dolfinx/fem/pack.h @@ -331,17 +331,6 @@ void pack_coefficients(const Form& form, auto mesh = coefficients[coeff]->function_space()->mesh(); assert(mesh); - // Other integrals in the form might have coefficients defined - // over entities of codim > 0, which don't make sense for vertex - // integrals, so don't pack them. - if (int codim - = form.mesh()->topology()->dim() - mesh->topology()->dim(); - codim > 0) - { - throw std::runtime_error("Should not be packing coefficients with " - "codim>0 in a vertex integral"); - } - std::span vertices_b = form.domain_coeff(IntegralType::vertex, id, coeff); md::mdspan Date: Thu, 7 Aug 2025 13:09:05 +0200 Subject: [PATCH 054/104] Inline/Remove cell_and_vertex mdspan --- cpp/dolfinx/fem/assemble_scalar_impl.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index ff35811f5f0..e8c29a82b80 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -275,13 +275,12 @@ T assemble_scalar( std::span data = M.domain(IntegralType::vertex, i, 0); assert(data.size() * cstride == coeffs.size()); - md::mdspan> - cell_and_vertex(data.data(), data.size() / 2, 2); - value += impl::assemble_vertices( - x_dofmap, x, cell_and_vertex, fn, constants, - md::mdspan(coeffs.data(), data.size() / 2, cstride)); + x_dofmap, x, + md::mdspan>( + data.data(), data.size() / 2, 2), + fn, constants, md::mdspan(coeffs.data(), data.size() / 2, cstride)); } return value; From ee9b92f0f7cc54c70983b03da80e2d3f4f7e2d1f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:45:01 +0200 Subject: [PATCH 055/104] Update docstring compute_integration_domain --- cpp/dolfinx/fem/utils.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 3390f4adec7..6c01c6f843e 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -115,11 +115,16 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, /// /// @param[in] integral_type Integral type. /// @param[in] topology Mesh topology. -/// @param[in] entities List of mesh entities. For -/// `integral_type==IntegralType::cell`, `entities` should be cell -/// indices. For other `IntegralType`, `entities` should be facet -/// indices. -/// @return List of integration entity data. +/// @param[in] entities List of mesh entities. Depending on the `IntegralType` these are associated with different entities: +/// `IntegralType::cell`: cells +/// `IntegralType::exterior_facet`: facets +/// `IntegralType::interior_facet`: facets +/// `IntegralType::vertex`: vertices +/// @return List of integration entity data, depending on the `IntegralType` the data per entity has different layouts +/// `IntegralType::cell`: cell +/// `IntegralType::exterior_facet`: (cell, local_facet) +/// `IntegralType::interior_facet`: (cell, local_facet) +/// `IntegralType::vertex`: (cell, local_vertex) std::vector compute_integration_domains(IntegralType integral_type, const mesh::Topology& topology, From 42c6a845e0e6a63cd5de6e70b02742ae8e6b1283 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:18:56 +0200 Subject: [PATCH 056/104] Integration domain is (cell, local_vertex) for vertex integrals --- cpp/dolfinx/fem/utils.cpp | 41 ++++++++++++++++++++++++++++++++++++++- cpp/dolfinx/fem/utils.h | 1 - 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index cbfb9374ad5..446299c13bc 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -185,11 +185,30 @@ fem::compute_integration_domains(fem::IntegralType integral_type, return {f_to_c, c_to_f}; }; + auto get_cell_vertex_connectivity = [tdim, &topology]() + -> std::pair>, + std::shared_ptr>> + { + auto v_to_c = topology.connectivity(0, tdim); + if (!v_to_c) + { + throw std::runtime_error( + "Topology vertex-to-cell connectivity has not been computed."); + } + + auto c_to_v = topology.connectivity(tdim, tdim - 1); + if (!c_to_v) + { + throw std::runtime_error( + "Topology cell-to-vertex connectivity has not been computed."); + } + return {v_to_c, c_to_v}; + }; + std::vector entity_data; switch (integral_type) { case IntegralType::cell: - case IntegralType::vertex: { entity_data.insert(entity_data.begin(), entities.begin(), entities.end()); break; @@ -243,6 +262,26 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } break; } + case IntegralType::vertex: + { + auto [v_to_c, c_to_v] = get_cell_vertex_connectivity(); + for (auto vertex : entities) + { + auto cells = v_to_c->links(vertex); + assert(cells.size() > 0); + + // Use first cell for assembly over by default + std::int32_t cell = cells[0]; + entity_data.push_back(cell); + + // Find local index of vertex within cell + auto cell_vertices = c_to_v->links(cell); + auto it = std::ranges::find(cell_vertices, vertex); + assert(it != cell_vertices.end()); + std::int32_t local_index = std::distance(cell_vertices.begin(), it); + entity_data.push_back(local_index); + } + } } return entity_data; diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 6c01c6f843e..97000e567e4 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -775,7 +775,6 @@ Form create_form_factory( assert(cells.size() > 0); // Use first cell for assembly over by default - // TODO: controllability? std::int32_t cell = cells[0]; cell_and_vertex.push_back(cell); From 30bb8de1f9b238819fa4fe1c909809766e124121 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:20:27 +0200 Subject: [PATCH 057/104] format --- cpp/dolfinx/fem/utils.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 97000e567e4..d2ce3b3d9d4 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -115,12 +115,14 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, /// /// @param[in] integral_type Integral type. /// @param[in] topology Mesh topology. -/// @param[in] entities List of mesh entities. Depending on the `IntegralType` these are associated with different entities: -/// `IntegralType::cell`: cells +/// @param[in] entities List of mesh entities. Depending on the `IntegralType` +/// these are associated with different entities: +/// `IntegralType::cell`: cells /// `IntegralType::exterior_facet`: facets /// `IntegralType::interior_facet`: facets /// `IntegralType::vertex`: vertices -/// @return List of integration entity data, depending on the `IntegralType` the data per entity has different layouts +/// @return List of integration entity data, depending on the `IntegralType` the +/// data per entity has different layouts /// `IntegralType::cell`: cell /// `IntegralType::exterior_facet`: (cell, local_facet) /// `IntegralType::interior_facet`: (cell, local_facet) From aa6e2a4a570d89db3f43aac49f0fb38975a3dc7d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:47:14 +0200 Subject: [PATCH 058/104] Bad --- cpp/dolfinx/fem/utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 446299c13bc..2846799427b 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -196,7 +196,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, "Topology vertex-to-cell connectivity has not been computed."); } - auto c_to_v = topology.connectivity(tdim, tdim - 1); + auto c_to_v = topology.connectivity(tdim, 0); if (!c_to_v) { throw std::runtime_error( From 4ab0a49f5532ab5574ba572e4950e8cfdc0f5ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=2E=20K=C3=BChner?= <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:55:20 +0200 Subject: [PATCH 059/104] Update cpp/dolfinx/fem/pack.h Co-authored-by: Michal Habera --- cpp/dolfinx/fem/pack.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/pack.h b/cpp/dolfinx/fem/pack.h index 6e27298e88f..45cdbce1340 100644 --- a/cpp/dolfinx/fem/pack.h +++ b/cpp/dolfinx/fem/pack.h @@ -324,7 +324,7 @@ void pack_coefficients(const Form& form, } case IntegralType::vertex: { - // Iterate over coefficients that are active in cell integrals + // Iterate over coefficients that are active in vertex integrals for (int coeff : form.active_coeffs(IntegralType::vertex, id)) { // Get coefficient mesh From 8ae982de99f58cdc2040ef06e605b971a60f1254 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:16:41 +0200 Subject: [PATCH 060/104] Only copy if already packed --- cpp/dolfinx/fem/utils.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index d2ce3b3d9d4..a6eccfede55 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -801,18 +801,18 @@ Form create_form_factory( integrals.insert({{IntegralType::vertex, id, form_idx}, {k, cells_and_vertices, active_coeffs}}); } - else + else if (sd != subdomains.end()) { // NOTE: This requires that pairs are sorted auto it = std::ranges::lower_bound(sd->second, id, std::less<>{}, [](auto& a) { return a.first; }); if (it != sd->second.end() and it->first == id) { - std::vector cells_and_vertices - = get_cells_and_vertices(it->second); - integrals.insert({{IntegralType::vertex, id, form_idx}, - {k, cells_and_vertices, active_coeffs}}); + {k, + std::vector(it->second.begin(), + it->second.end()), + active_coeffs}}); } } } From 63ea524ec084d090951ff69b526c96e47f24f25c Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 7 Aug 2025 17:43:51 +0200 Subject: [PATCH 061/104] Avoid double case handling --- cpp/dolfinx/fem/utils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 2846799427b..2df405288a9 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -146,6 +146,8 @@ fem::compute_integration_domains(fem::IntegralType integral_type, dim = tdim; break; case IntegralType::exterior_facet: + dim = tdim - 1; + break; case IntegralType::interior_facet: dim = tdim - 1; break; From 2709b8c7005fa783f1c21eb7879e1f0bdd0cf073 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 15 Aug 2025 14:00:30 +0200 Subject: [PATCH 062/104] Use vertex integral consistently --- .../fem/{test_point_source.py => test_vertex_integral.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename python/test/unit/fem/{test_point_source.py => test_vertex_integral.py} (98%) diff --git a/python/test/unit/fem/test_point_source.py b/python/test/unit/fem/test_vertex_integral.py similarity index 98% rename from python/test/unit/fem/test_point_source.py rename to python/test/unit/fem/test_vertex_integral.py index 56b907628c9..3fb4f20afe9 100644 --- a/python/test/unit/fem/test_point_source.py +++ b/python/test/unit/fem/test_vertex_integral.py @@ -27,7 +27,7 @@ ) @pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) -def test_point_source_rank_0(cell_type, ghost_mode, dtype): +def test_vertex_integral_rank_0(cell_type, ghost_mode, dtype): comm = MPI.COMM_WORLD rdtype = np.real(dtype(0)).dtype @@ -109,7 +109,7 @@ def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): ) @pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) -def test_point_source_rank_1(cell_type, ghost_mode, dtype): +def test_vertex_integral_rank_1(cell_type, ghost_mode, dtype): comm = MPI.COMM_WORLD rdtype = np.real(dtype(0)).dtype From a40385a282dda0e63a176c6fd02b4d654770fd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=2E=20K=C3=BChner?= <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:48:30 +0200 Subject: [PATCH 063/104] Update cpp/dolfinx/fem/assemble_vector_impl.h Co-authored-by: Michal Habera --- cpp/dolfinx/fem/assemble_vector_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index e3f52f5d8c1..c85ee341285 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -955,7 +955,7 @@ void assemble_interior_facets( /// @param[in] x_dofmap Dofmap for the mesh geometry. /// @param[in] x Mesh geometry (coordinates). /// @param[in] vertices Vertex indices `(vertices.size(), 2)` - first entry -/// holds the cell index over which the vertex is to evaluated, and the second +/// holds the index of the cell adjacent to the vertex, and the second /// stores the local index of the vertex within the cell. /// @param[in] dofmap Test function (row) degree-of-freedom data holding /// the (0) dofmap, (1) dofmap block size and (2) dofmap cell indices. From eb1c1dfe339a367f6a54091649ab232a3428571f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:01:53 +0200 Subject: [PATCH 064/104] Rename data to vertices --- cpp/dolfinx/fem/assemble_scalar_impl.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index e8c29a82b80..057b766d9c7 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -272,15 +272,16 @@ T assemble_scalar( auto& [coeffs, cstride] = coefficients.at({IntegralType::vertex, i}); - std::span data = M.domain(IntegralType::vertex, i, 0); - assert(data.size() * cstride == coeffs.size()); + std::span vertices + = M.domain(IntegralType::vertex, i, 0); + assert(vertices.size() * cstride == coeffs.size()); value += impl::assemble_vertices( x_dofmap, x, md::mdspan>( - data.data(), data.size() / 2, 2), - fn, constants, md::mdspan(coeffs.data(), data.size() / 2, cstride)); + vertices.data(), vertices.size() / 2, 2), + fn, constants, md::mdspan(coeffs.data(), vertices.size() / 2, cstride)); } return value; From b0d95dcb5d4c88ad75b36b18cb310a6a4a367d3f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:04:22 +0200 Subject: [PATCH 065/104] kern_c_t -> kernelptr_t --- cpp/dolfinx/fem/kernel.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/dolfinx/fem/kernel.h b/cpp/dolfinx/fem/kernel.h index 5055eaf62ff..50011639906 100644 --- a/cpp/dolfinx/fem/kernel.h +++ b/cpp/dolfinx/fem/kernel.h @@ -16,8 +16,8 @@ namespace dolfinx::fem::impl /// @tparam T scalar type. /// @tparam U geometry type. template > -using kern_c_t = void (*)(T*, const T*, const T*, const U*, const int*, - const std::uint8_t*, void*); +using kernelptr_t = void (*)(T*, const T*, const T*, const U*, const int*, + const std::uint8_t*, void*); /// @brief Kernel callback type. /// @tparam T scalar type. @@ -41,13 +41,13 @@ constexpr kern_t extract_kernel(const ufcx_integral* integral) else if constexpr (std::is_same_v> && has_complex_ufcx_kernels()) { - return reinterpret_cast>( + return reinterpret_cast>( integral->tabulate_tensor_complex64); } else if constexpr (std::is_same_v> && has_complex_ufcx_kernels()) { - return reinterpret_cast>( + return reinterpret_cast>( integral->tabulate_tensor_complex128); } else From e1d76e76b6e64d04684ee9cdff2c2ec55c2a4c6d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:09:27 +0200 Subject: [PATCH 066/104] Name shape1 --- cpp/dolfinx/fem/assemble_scalar_impl.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index 057b766d9c7..8d95ec1f540 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -252,16 +252,18 @@ T assemble_scalar( auto& [coeffs, cstride] = coefficients.at({IntegralType::interior_facet, i}); std::span facets = M.domain(IntegralType::interior_facet, i, 0); - assert((facets.size() / 4) * 2 * cstride == coeffs.size()); + + constexpr std::uint8_t shape1 = 4; + assert((facets.size() / shape1) * 2 * cstride == coeffs.size()); value += impl::assemble_interior_facets( x_dofmap, x, md::mdspan>( - facets.data(), facets.size() / 4, 2, 2), + facets.data(), facets.size() / shape1, 2, 2), fn, constants, md::mdspan>( - coeffs.data(), facets.size() / 4, 2, cstride), + coeffs.data(), facets.size() / shape1, 2, cstride), perms); } @@ -276,12 +278,14 @@ T assemble_scalar( = M.domain(IntegralType::vertex, i, 0); assert(vertices.size() * cstride == coeffs.size()); + constexpr std::uint8_t shape1 = 2; value += impl::assemble_vertices( x_dofmap, x, md::mdspan>( - vertices.data(), vertices.size() / 2, 2), - fn, constants, md::mdspan(coeffs.data(), vertices.size() / 2, cstride)); + vertices.data(), vertices.size() / shape1, shape1), + fn, constants, + md::mdspan(coeffs.data(), vertices.size() / cstride, cstride)); } return value; From 457c65621d508f516c889c0838b94ee0a9c9b65e Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:27:54 +0200 Subject: [PATCH 067/104] Fix shape type --- cpp/dolfinx/fem/assemble_scalar_impl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index 8d95ec1f540..1025ea60178 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -253,7 +253,7 @@ T assemble_scalar( = coefficients.at({IntegralType::interior_facet, i}); std::span facets = M.domain(IntegralType::interior_facet, i, 0); - constexpr std::uint8_t shape1 = 4; + constexpr std::size_t shape1 = 4; assert((facets.size() / shape1) * 2 * cstride == coeffs.size()); value += impl::assemble_interior_facets( x_dofmap, x, @@ -278,7 +278,7 @@ T assemble_scalar( = M.domain(IntegralType::vertex, i, 0); assert(vertices.size() * cstride == coeffs.size()); - constexpr std::uint8_t shape1 = 2; + constexpr std::size_t shape1 = 2; value += impl::assemble_vertices( x_dofmap, x, md::mdspan Date: Wed, 20 Aug 2025 18:12:43 +0200 Subject: [PATCH 068/104] Fix --- cpp/dolfinx/fem/assemble_scalar_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index 1025ea60178..4813ae9bb54 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -285,7 +285,7 @@ T assemble_scalar( md::extents>( vertices.data(), vertices.size() / shape1, shape1), fn, constants, - md::mdspan(coeffs.data(), vertices.size() / cstride, cstride)); + md::mdspan(coeffs.data(), vertices.size() / shape1, cstride)); } return value; From 268fa5ee351a818270d5d76945978a1977b71253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=2E=20K=C3=BChner?= <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 20 Aug 2025 23:26:30 +0200 Subject: [PATCH 069/104] Update .github/workflows/fenicsx-refs.env --- .github/workflows/fenicsx-refs.env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fenicsx-refs.env b/.github/workflows/fenicsx-refs.env index 6b0e837ad75..22c7e44a7e7 100644 --- a/.github/workflows/fenicsx-refs.env +++ b/.github/workflows/fenicsx-refs.env @@ -2,5 +2,5 @@ basix_repository=FEniCS/basix basix_ref=main ufl_repository=FEniCS/ufl ufl_ref=main -ffcx_repository=schnellerhase/fenics-ffcx -ffcx_ref=point_measure +ffcx_repository=FEniCS/ffcx +ffcx_ref=main From c684b736ff78ef6f340326954b2431c10d79f7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=2E=20K=C3=BChner?= <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:07:01 +0200 Subject: [PATCH 070/104] Update cpp/dolfinx/fem/assemble_vector_impl.h Co-authored-by: Michal Habera --- cpp/dolfinx/fem/assemble_vector_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index c85ee341285..cc222ef38a1 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -993,7 +993,7 @@ void assemble_vertices( std::vector> cdofs(3 * x_dofmap.extent(1)); std::vector be(bs * dmap.extent(1)); - // Iterate over active cells + // Iterate over active vertices for (std::size_t index = 0; index < vertices.extent(0); ++index) { // Integration domain cell, local index, and test function cell From c076436b74122b30dccf931b83c0bd7a5f3a407b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:13:32 +0200 Subject: [PATCH 071/104] Switch consistently to num_adjacenet_cells + values per entity --- cpp/dolfinx/fem/assemble_scalar_impl.h | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index 4813ae9bb54..a8aab0bda74 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -235,14 +235,19 @@ T assemble_scalar( = coefficients.at({IntegralType::exterior_facet, i}); std::span facets = M.domain(IntegralType::exterior_facet, i, 0); + + constexpr std::size_t num_adjacent_cells = 1; + // Two values per each adj. cell (cell index and local facet index). + constexpr std::size_t shape1 = 2 * num_adjacent_cells; + assert((facets.size() / 2) * cstride == coeffs.size()); value += impl::assemble_exterior_facets( x_dofmap, x, md::mdspan>( - facets.data(), facets.size() / 2, 2), - fn, constants, md::mdspan(coeffs.data(), facets.size() / 2, cstride), - perms); + facets.data(), facets.size() / shape1, 2), + fn, constants, + md::mdspan(coeffs.data(), facets.size() / shape1, cstride), perms); } for (int i : M.integral_ids(IntegralType::interior_facet)) @@ -253,7 +258,10 @@ T assemble_scalar( = coefficients.at({IntegralType::interior_facet, i}); std::span facets = M.domain(IntegralType::interior_facet, i, 0); - constexpr std::size_t shape1 = 4; + constexpr std::size_t num_adjacent_cells = 2; + // Two values per each adj. cell (cell index and local facet index). + constexpr std::size_t shape1 = 2 * num_adjacent_cells; + assert((facets.size() / shape1) * 2 * cstride == coeffs.size()); value += impl::assemble_interior_facets( x_dofmap, x, @@ -278,7 +286,10 @@ T assemble_scalar( = M.domain(IntegralType::vertex, i, 0); assert(vertices.size() * cstride == coeffs.size()); - constexpr std::size_t shape1 = 2; + constexpr std::size_t num_adjacent_cells = 1; + // Two values per adj. cell (cell index and local vertex index). + constexpr std::size_t shape1 = 2 * num_adjacent_cells; + value += impl::assemble_vertices( x_dofmap, x, md::mdspan Date: Thu, 21 Aug 2025 13:14:04 +0200 Subject: [PATCH 072/104] Apply suggestions from code review Co-authored-by: Michal Habera --- cpp/dolfinx/fem/assemble_vector_impl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index cc222ef38a1..8fa73519e38 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -1006,13 +1006,13 @@ void assemble_vertices( for (std::size_t i = 0; i < x_dofs.size(); ++i) std::copy_n(&x(x_dofs[i], 0), 3, std::next(cdofs.begin(), 3 * i)); - // Tabulate vector for cell + // Tabulate vector for vertex std::ranges::fill(be, 0); kernel(be.data(), &coeffs(index, 0), constants.data(), cdofs.data(), &local_index, nullptr, nullptr); P0(be, cell_info0, c0, 1); - // Scatter cell vector to 'global' vector array + // Scatter vertex vector to 'global' vector array auto dofs = md::submdspan(dmap, c0, md::full_extent); if constexpr (_bs > 0) { From 2a740ff668148d162ed67f51f7c6a053bcd5a3c2 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:15:03 +0200 Subject: [PATCH 073/104] kern_t -> kernel_t --- cpp/dolfinx/fem/kernel.h | 4 ++-- cpp/dolfinx/fem/utils.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cpp/dolfinx/fem/kernel.h b/cpp/dolfinx/fem/kernel.h index 50011639906..496fcad49c7 100644 --- a/cpp/dolfinx/fem/kernel.h +++ b/cpp/dolfinx/fem/kernel.h @@ -23,7 +23,7 @@ using kernelptr_t = void (*)(T*, const T*, const T*, const U*, const int*, /// @tparam T scalar type. /// @tparam U geometry type. template > -using kern_t = std::function; /// @brief Extract correct kernel by type from UFCx integral. @@ -32,7 +32,7 @@ using kern_t = std::function> -constexpr kern_t extract_kernel(const ufcx_integral* integral) +constexpr kernel_t extract_kernel(const ufcx_integral* integral) { if constexpr (std::is_same_v) return integral->tabulate_tensor_float32; diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index a6eccfede55..53a85fef85a 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -527,7 +527,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - impl::kern_t k = impl::extract_kernel(integral); + impl::kernel_t k = impl::extract_kernel(integral); if (!k) { throw std::runtime_error( @@ -591,7 +591,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - impl::kern_t k = impl::extract_kernel(integral); + impl::kernel_t k = impl::extract_kernel(integral); // Build list of entities to assembler over const std::vector bfacets = mesh::exterior_facet_indices(*topology); @@ -676,7 +676,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - impl::kern_t k = impl::extract_kernel(integral); + impl::kernel_t k = impl::extract_kernel(integral); assert(k); // Build list of entities to assembler over @@ -755,7 +755,7 @@ Form create_form_factory( active_coeffs.push_back(j); } - impl::kern_t k = impl::extract_kernel(integral); + impl::kernel_t k = impl::extract_kernel(integral); assert(k); // Build list of entities to assembler over From 2bf3d1bb6293e8fc3eb4d745fdc6a90927911c67 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:21:31 +0200 Subject: [PATCH 074/104] Format --- cpp/dolfinx/fem/kernel.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/dolfinx/fem/kernel.h b/cpp/dolfinx/fem/kernel.h index 496fcad49c7..653611cb2f9 100644 --- a/cpp/dolfinx/fem/kernel.h +++ b/cpp/dolfinx/fem/kernel.h @@ -23,8 +23,8 @@ using kernelptr_t = void (*)(T*, const T*, const T*, const U*, const int*, /// @tparam T scalar type. /// @tparam U geometry type. template > -using kernel_t = std::function; +using kernel_t = std::function; /// @brief Extract correct kernel by type from UFCx integral. /// @tparam T scalar type of kernel to extract. From ec43b19d769fd15369e0cff2fd2ad97e2e7fbd6f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:45:46 +0200 Subject: [PATCH 075/104] Move test_vertex_integral into test_assembler --- python/test/unit/fem/test_assembler.py | 175 +++++++++++++++++- python/test/unit/fem/test_vertex_integral.py | 184 ------------------- 2 files changed, 173 insertions(+), 186 deletions(-) delete mode 100644 python/test/unit/fem/test_vertex_integral.py diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 894a0ebbf10..66f80a8e7b7 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2022 Garth N. Wells +# Copyright (C) 2018-2025 Garth N. Wells and Paul T. Kühner # # This file is part of DOLFINx (https://www.fenicsproject.org) # @@ -17,7 +17,7 @@ import ufl from basix.ufl import element, mixed_element from dolfinx import cpp as _cpp -from dolfinx import default_real_type, default_scalar_type, fem, graph, la +from dolfinx import default_real_type, default_scalar_type, fem, graph, la, mesh from dolfinx.fem import ( Constant, Function, @@ -1507,3 +1507,174 @@ def test_vector_types(): assert np.linalg.norm(x0.array - x1.array) == pytest.approx(0.0) assert np.linalg.norm(x0.array - x2.array) == pytest.approx(0.0, abs=1e-7) + + +@pytest.mark.parametrize( + "cell_type", + [ + mesh.CellType.interval, + mesh.CellType.triangle, + mesh.CellType.quadrilateral, + mesh.CellType.tetrahedron, + # mesh.CellType.pyramid, + mesh.CellType.prism, + mesh.CellType.hexahedron, + ], +) +@pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) +def test_vertex_integral_rank_0(cell_type, ghost_mode, dtype): + comm = MPI.COMM_WORLD + rdtype = np.real(dtype(0)).dtype + + msh = None + cell_dim = mesh.cell_dim(cell_type) + if cell_dim == 1: + msh = mesh.create_unit_interval(comm, 4, dtype=rdtype, ghost_mode=ghost_mode) + elif cell_dim == 2: + msh = mesh.create_unit_square( + comm, 4, 4, cell_type=cell_type, dtype=rdtype, ghost_mode=ghost_mode + ) + elif cell_dim == 3: + msh = mesh.create_unit_cube( + comm, 4, 4, 4, cell_type=cell_type, dtype=rdtype, ghost_mode=ghost_mode + ) + + def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): + a, b = coordinate_range + weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) + + expected_value_l = np.sum(msh.geometry.x[a:b, 0] * weights) + value_l = fem.assemble_scalar(form) + assert expected_value_l == pytest.approx(value_l, abs=1e4 * np.finfo(rdtype).eps) + + expected_value = comm.allreduce(expected_value_l) + value = comm.allreduce(value_l) + assert expected_value == pytest.approx(value, abs=5e4 * np.finfo(rdtype).eps) + + num_vertices = msh.topology.index_map(0).size_local + x = ufl.SpatialCoordinate(msh) + + # Full domain + check_vertex_integral_against_sum(fem.form(x[0] * ufl.dP, dtype=dtype), (0, num_vertices)) + + # Split domain into first half of vertices (1) and second half of vertices (2) + vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) + tags = np.full_like(vertices, 1) + tags[tags.size // 2 :] = 2 + meshtags = mesh.meshtags(msh, 0, vertices, tags) + dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) + + check_vertex_integral_against_sum(fem.form(x[0] * dP(1), dtype=dtype), (0, num_vertices // 2)) + check_vertex_integral_against_sum( + fem.form(x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices) + ) + check_vertex_integral_against_sum( + fem.form(x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices) + ) + + V = fem.functionspace(msh, ("P", 1)) + u = fem.Function(V, dtype=dtype) + u.x.array[:] = np.arange(0, u.x.array.size, dtype=dtype) + + check_vertex_integral_against_sum( + fem.form(u * x[0] * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices), weighted=True + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True + ) + + +@pytest.mark.parametrize( + "cell_type", + [ + mesh.CellType.interval, + mesh.CellType.triangle, + mesh.CellType.quadrilateral, + mesh.CellType.tetrahedron, + # mesh.CellType.pyramid, + mesh.CellType.prism, + mesh.CellType.hexahedron, + ], +) +@pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) +def test_vertex_integral_rank_1(cell_type, ghost_mode, dtype): + comm = MPI.COMM_WORLD + rdtype = np.real(dtype(0)).dtype + + msh = None + cell_dim = mesh.cell_dim(cell_type) + if cell_dim == 1: + msh = mesh.create_unit_interval(comm, 4, ghost_mode=ghost_mode, dtype=rdtype) + elif cell_dim == 2: + msh = mesh.create_unit_square( + comm, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype + ) + elif cell_dim == 3: + msh = mesh.create_unit_cube( + comm, 4, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype + ) + + num_vertices = msh.topology.index_map(0).size_local + + def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): + a, b = coordinate_range + weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) + expected_value_l = np.zeros(num_vertices, dtype=rdtype) + expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights + value_l = fem.assemble_vector(form) + equal_l = np.allclose( + expected_value_l, np.real(value_l.array[:num_vertices]), atol=1e3 * np.finfo(rdtype).eps + ) + assert equal_l + assert comm.allreduce(equal_l, MPI.BAND) + + x = ufl.SpatialCoordinate(msh) + V = fem.functionspace(msh, ("P", 1)) + v = ufl.conj(ufl.TestFunction(V)) + + # Full domain + check_vertex_integral_against_sum(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) + + # Split domain into first half of vertices (1) and second half of vertices (2) + vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) + tags = np.full_like(vertices, 1) + tags[tags.size // 2 :] = 2 + meshtags = mesh.meshtags(msh, 0, vertices, tags) + dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) + + check_vertex_integral_against_sum( + fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2) + ) + check_vertex_integral_against_sum( + fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices) + ) + check_vertex_integral_against_sum( + fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices) + ) + + V = fem.functionspace(msh, ("P", 1)) + u = fem.Function(V, dtype=dtype) + u.x.array[:] = np.arange(u.x.array.size) + + check_vertex_integral_against_sum( + fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * v * dP(2), dtype=dtype), + (num_vertices // 2, num_vertices), + weighted=True, + ) + check_vertex_integral_against_sum( + fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True + ) diff --git a/python/test/unit/fem/test_vertex_integral.py b/python/test/unit/fem/test_vertex_integral.py deleted file mode 100644 index 3fb4f20afe9..00000000000 --- a/python/test/unit/fem/test_vertex_integral.py +++ /dev/null @@ -1,184 +0,0 @@ -# Copyright (C) 2025 Paul T. Kühner -# -# This file is part of DOLFINx (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -from mpi4py import MPI - -import numpy as np -import pytest - -import ufl -from dolfinx import fem, mesh - - -@pytest.mark.parametrize( - "cell_type", - [ - mesh.CellType.interval, - mesh.CellType.triangle, - mesh.CellType.quadrilateral, - mesh.CellType.tetrahedron, - # mesh.CellType.pyramid, - mesh.CellType.prism, - mesh.CellType.hexahedron, - ], -) -@pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) -@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) -def test_vertex_integral_rank_0(cell_type, ghost_mode, dtype): - comm = MPI.COMM_WORLD - rdtype = np.real(dtype(0)).dtype - - msh = None - cell_dim = mesh.cell_dim(cell_type) - if cell_dim == 1: - msh = mesh.create_unit_interval(comm, 4, dtype=rdtype, ghost_mode=ghost_mode) - elif cell_dim == 2: - msh = mesh.create_unit_square( - comm, 4, 4, cell_type=cell_type, dtype=rdtype, ghost_mode=ghost_mode - ) - elif cell_dim == 3: - msh = mesh.create_unit_cube( - comm, 4, 4, 4, cell_type=cell_type, dtype=rdtype, ghost_mode=ghost_mode - ) - - def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): - a, b = coordinate_range - weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) - - expected_value_l = np.sum(msh.geometry.x[a:b, 0] * weights) - value_l = fem.assemble_scalar(form) - assert expected_value_l == pytest.approx(value_l, abs=1e4 * np.finfo(rdtype).eps) - - expected_value = comm.allreduce(expected_value_l) - value = comm.allreduce(value_l) - assert expected_value == pytest.approx(value, abs=5e4 * np.finfo(rdtype).eps) - - num_vertices = msh.topology.index_map(0).size_local - x = ufl.SpatialCoordinate(msh) - - # Full domain - check_vertex_integral_against_sum(fem.form(x[0] * ufl.dP, dtype=dtype), (0, num_vertices)) - - # Split domain into first half of vertices (1) and second half of vertices (2) - vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) - tags = np.full_like(vertices, 1) - tags[tags.size // 2 :] = 2 - meshtags = mesh.meshtags(msh, 0, vertices, tags) - dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - - check_vertex_integral_against_sum(fem.form(x[0] * dP(1), dtype=dtype), (0, num_vertices // 2)) - check_vertex_integral_against_sum( - fem.form(x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices) - ) - check_vertex_integral_against_sum( - fem.form(x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices) - ) - - V = fem.functionspace(msh, ("P", 1)) - u = fem.Function(V, dtype=dtype) - u.x.array[:] = np.arange(0, u.x.array.size, dtype=dtype) - - check_vertex_integral_against_sum( - fem.form(u * x[0] * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices), weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True - ) - - -@pytest.mark.parametrize( - "cell_type", - [ - mesh.CellType.interval, - mesh.CellType.triangle, - mesh.CellType.quadrilateral, - mesh.CellType.tetrahedron, - # mesh.CellType.pyramid, - mesh.CellType.prism, - mesh.CellType.hexahedron, - ], -) -@pytest.mark.parametrize("ghost_mode", [mesh.GhostMode.none, mesh.GhostMode.shared_facet]) -@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) -def test_vertex_integral_rank_1(cell_type, ghost_mode, dtype): - comm = MPI.COMM_WORLD - rdtype = np.real(dtype(0)).dtype - - msh = None - cell_dim = mesh.cell_dim(cell_type) - if cell_dim == 1: - msh = mesh.create_unit_interval(comm, 4, ghost_mode=ghost_mode, dtype=rdtype) - elif cell_dim == 2: - msh = mesh.create_unit_square( - comm, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype - ) - elif cell_dim == 3: - msh = mesh.create_unit_cube( - comm, 4, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype - ) - - num_vertices = msh.topology.index_map(0).size_local - - def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): - a, b = coordinate_range - weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) - expected_value_l = np.zeros(num_vertices, dtype=rdtype) - expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights - value_l = fem.assemble_vector(form) - equal_l = np.allclose( - expected_value_l, np.real(value_l.array[:num_vertices]), atol=1e3 * np.finfo(rdtype).eps - ) - assert equal_l - assert comm.allreduce(equal_l, MPI.BAND) - - x = ufl.SpatialCoordinate(msh) - V = fem.functionspace(msh, ("P", 1)) - v = ufl.conj(ufl.TestFunction(V)) - - # Full domain - check_vertex_integral_against_sum(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) - - # Split domain into first half of vertices (1) and second half of vertices (2) - vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) - tags = np.full_like(vertices, 1) - tags[tags.size // 2 :] = 2 - meshtags = mesh.meshtags(msh, 0, vertices, tags) - dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - - check_vertex_integral_against_sum( - fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2) - ) - check_vertex_integral_against_sum( - fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices) - ) - check_vertex_integral_against_sum( - fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices) - ) - - V = fem.functionspace(msh, ("P", 1)) - u = fem.Function(V, dtype=dtype) - u.x.array[:] = np.arange(u.x.array.size) - - check_vertex_integral_against_sum( - fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * v * dP(2), dtype=dtype), - (num_vertices // 2, num_vertices), - weighted=True, - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True - ) From 7fa9950e75934d66d729d4b1dcc02af6633b46a0 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 22 Aug 2025 12:44:49 +0200 Subject: [PATCH 076/104] Change to geometrically defined subdomains in vertex integral rank 0 test --- python/test/unit/fem/test_assembler.py | 49 +++++++++++++++----------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 66f80a8e7b7..75f15c41aab 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1540,54 +1540,63 @@ def test_vertex_integral_rank_0(cell_type, ghost_mode, dtype): comm, 4, 4, 4, cell_type=cell_type, dtype=rdtype, ghost_mode=ghost_mode ) - def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): - a, b = coordinate_range - weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) + vertex_map = msh.topology.index_map(0) - expected_value_l = np.sum(msh.geometry.x[a:b, 0] * weights) + def check_vertex_integral_against_sum(form, vertices, weighted=False): + weights = vertex_map.local_to_global(vertices) if weighted else np.ones_like(vertices) + expected_value_l = np.sum(msh.geometry.x[vertices, 0] * weights) value_l = fem.assemble_scalar(form) - assert expected_value_l == pytest.approx(value_l, abs=1e4 * np.finfo(rdtype).eps) + assert expected_value_l == pytest.approx(value_l, abs=5e4 * np.finfo(rdtype).eps) expected_value = comm.allreduce(expected_value_l) value = comm.allreduce(value_l) assert expected_value == pytest.approx(value, abs=5e4 * np.finfo(rdtype).eps) - num_vertices = msh.topology.index_map(0).size_local + num_vertices = vertex_map.size_local x = ufl.SpatialCoordinate(msh) # Full domain - check_vertex_integral_against_sum(fem.form(x[0] * ufl.dP, dtype=dtype), (0, num_vertices)) + check_vertex_integral_against_sum( + fem.form(x[0] * ufl.dP, dtype=dtype), np.arange(0, num_vertices) + ) - # Split domain into first half of vertices (1) and second half of vertices (2) - vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) - tags = np.full_like(vertices, 1) - tags[tags.size // 2 :] = 2 + # Split domain into left half of vertices (1) and right half of vertices (2) + vertices_1 = mesh.locate_entities(msh, 0, lambda x: x[0] <= 0.5) + vertices_1 = vertices_1[vertices_1 < num_vertices] + vertices_2 = mesh.locate_entities(msh, 0, lambda x: x[0] > 0.5) + vertices_2 = vertices_2[vertices_2 < num_vertices] + + tags = np.full(num_vertices, 1) + tags[vertices_2] = 2 + vertices = np.arange(0, vertex_map.size_local, dtype=np.int32) meshtags = mesh.meshtags(msh, 0, vertices, tags) + dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - check_vertex_integral_against_sum(fem.form(x[0] * dP(1), dtype=dtype), (0, num_vertices // 2)) + check_vertex_integral_against_sum(fem.form(x[0] * dP(1), dtype=dtype), vertices_1) + check_vertex_integral_against_sum(fem.form(x[0] * dP(2), dtype=dtype), vertices_2) check_vertex_integral_against_sum( - fem.form(x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices) - ) - check_vertex_integral_against_sum( - fem.form(x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices) + fem.form(x[0] * (dP(1) + dP(2)), dtype=dtype), np.arange(num_vertices) ) V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V, dtype=dtype) u.x.array[:] = np.arange(0, u.x.array.size, dtype=dtype) + u.x.array[:] = vertex_map.local_to_global( + np.arange(vertex_map.size_local + vertex_map.num_ghosts) + ) check_vertex_integral_against_sum( - fem.form(u * x[0] * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True + fem.form(u * x[0] * ufl.dP, dtype=dtype), np.arange(num_vertices), weighted=True ) check_vertex_integral_against_sum( - fem.form(u * x[0] * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True + fem.form(u * x[0] * dP(1), dtype=dtype), vertices_1, weighted=True ) check_vertex_integral_against_sum( - fem.form(u * x[0] * dP(2), dtype=dtype), (num_vertices // 2, num_vertices), weighted=True + fem.form(u * x[0] * dP(2), dtype=dtype), vertices_2, weighted=True ) check_vertex_integral_against_sum( - fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True + fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), np.arange(num_vertices), weighted=True ) From c3bb5995d09df2794f3ca2486b3784123b5ecebc Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 22 Aug 2025 12:49:54 +0200 Subject: [PATCH 077/104] Simplify --- python/test/unit/fem/test_assembler.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 75f15c41aab..78f38e2c9a1 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1545,7 +1545,7 @@ def test_vertex_integral_rank_0(cell_type, ghost_mode, dtype): def check_vertex_integral_against_sum(form, vertices, weighted=False): weights = vertex_map.local_to_global(vertices) if weighted else np.ones_like(vertices) expected_value_l = np.sum(msh.geometry.x[vertices, 0] * weights) - value_l = fem.assemble_scalar(form) + value_l = fem.assemble_scalar(fem.form(form, dtype=dtype)) assert expected_value_l == pytest.approx(value_l, abs=5e4 * np.finfo(rdtype).eps) expected_value = comm.allreduce(expected_value_l) @@ -1573,11 +1573,9 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - check_vertex_integral_against_sum(fem.form(x[0] * dP(1), dtype=dtype), vertices_1) - check_vertex_integral_against_sum(fem.form(x[0] * dP(2), dtype=dtype), vertices_2) - check_vertex_integral_against_sum( - fem.form(x[0] * (dP(1) + dP(2)), dtype=dtype), np.arange(num_vertices) - ) + check_vertex_integral_against_sum(x[0] * dP(1), vertices_1) + check_vertex_integral_against_sum(x[0] * dP(2), vertices_2) + check_vertex_integral_against_sum(x[0] * (dP(1) + dP(2)), np.arange(num_vertices)) V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V, dtype=dtype) @@ -1586,18 +1584,10 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): np.arange(vertex_map.size_local + vertex_map.num_ghosts) ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * ufl.dP, dtype=dtype), np.arange(num_vertices), weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * dP(1), dtype=dtype), vertices_1, weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * dP(2), dtype=dtype), vertices_2, weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * (dP(1) + dP(2)), dtype=dtype), np.arange(num_vertices), weighted=True - ) + check_vertex_integral_against_sum(u * x[0] * ufl.dP, np.arange(num_vertices), True) + check_vertex_integral_against_sum(u * x[0] * dP(1), vertices_1, True) + check_vertex_integral_against_sum(u * x[0] * dP(2), vertices_2, True) + check_vertex_integral_against_sum(u * x[0] * (dP(1) + dP(2)), np.arange(num_vertices), True) @pytest.mark.parametrize( From 9b4b5cc0a19243fdf8eeb05b18c91c87d1afda02 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 22 Aug 2025 12:51:39 +0200 Subject: [PATCH 078/104] More --- python/test/unit/fem/test_assembler.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 78f38e2c9a1..14cd1412e0a 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1556,9 +1556,7 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): x = ufl.SpatialCoordinate(msh) # Full domain - check_vertex_integral_against_sum( - fem.form(x[0] * ufl.dP, dtype=dtype), np.arange(0, num_vertices) - ) + check_vertex_integral_against_sum(x[0] * ufl.dP, np.arange(num_vertices)) # Split domain into left half of vertices (1) and right half of vertices (2) vertices_1 = mesh.locate_entities(msh, 0, lambda x: x[0] <= 0.5) @@ -1568,21 +1566,19 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): tags = np.full(num_vertices, 1) tags[vertices_2] = 2 - vertices = np.arange(0, vertex_map.size_local, dtype=np.int32) + vertices = np.arange(0, num_vertices, dtype=np.int32) meshtags = mesh.meshtags(msh, 0, vertices, tags) dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) + # Combinations of sub domains check_vertex_integral_against_sum(x[0] * dP(1), vertices_1) check_vertex_integral_against_sum(x[0] * dP(2), vertices_2) check_vertex_integral_against_sum(x[0] * (dP(1) + dP(2)), np.arange(num_vertices)) V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V, dtype=dtype) - u.x.array[:] = np.arange(0, u.x.array.size, dtype=dtype) - u.x.array[:] = vertex_map.local_to_global( - np.arange(vertex_map.size_local + vertex_map.num_ghosts) - ) + u.x.array[:] = vertex_map.local_to_global(np.arange(num_vertices + vertex_map.num_ghosts)) check_vertex_integral_against_sum(u * x[0] * ufl.dP, np.arange(num_vertices), True) check_vertex_integral_against_sum(u * x[0] * dP(1), vertices_1, True) From fd76955e5658b18b1d09c96acd8e34eb4060aea1 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 22 Aug 2025 12:54:41 +0200 Subject: [PATCH 079/104] Start on rank 1 --- python/test/unit/fem/test_assembler.py | 34 +++++++------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 14cd1412e0a..04c22617b44 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1624,7 +1624,7 @@ def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) expected_value_l = np.zeros(num_vertices, dtype=rdtype) expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights - value_l = fem.assemble_vector(form) + value_l = fem.assemble_vector(fem.form(form, dtype=dtype)) equal_l = np.allclose( expected_value_l, np.real(value_l.array[:num_vertices]), atol=1e3 * np.finfo(rdtype).eps ) @@ -1636,7 +1636,7 @@ def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): v = ufl.conj(ufl.TestFunction(V)) # Full domain - check_vertex_integral_against_sum(fem.form(x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices)) + check_vertex_integral_against_sum(x[0] * v * ufl.dP, (0, num_vertices)) # Split domain into first half of vertices (1) and second half of vertices (2) vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) @@ -1645,31 +1645,15 @@ def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): meshtags = mesh.meshtags(msh, 0, vertices, tags) dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - check_vertex_integral_against_sum( - fem.form(x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2) - ) - check_vertex_integral_against_sum( - fem.form(x[0] * v * dP(2), dtype=dtype), (num_vertices // 2, num_vertices) - ) - check_vertex_integral_against_sum( - fem.form(x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices) - ) + check_vertex_integral_against_sum(x[0] * v * dP(1), (0, num_vertices // 2)) + check_vertex_integral_against_sum(x[0] * v * dP(2), (num_vertices // 2, num_vertices)) + check_vertex_integral_against_sum(x[0] * v * (dP(1) + dP(2)), (0, num_vertices)) V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V, dtype=dtype) u.x.array[:] = np.arange(u.x.array.size) - check_vertex_integral_against_sum( - fem.form(u * x[0] * v * ufl.dP, dtype=dtype), (0, num_vertices), weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * v * dP(1), dtype=dtype), (0, num_vertices // 2), weighted=True - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * v * dP(2), dtype=dtype), - (num_vertices // 2, num_vertices), - weighted=True, - ) - check_vertex_integral_against_sum( - fem.form(u * x[0] * v * (dP(1) + dP(2)), dtype=dtype), (0, num_vertices), weighted=True - ) + check_vertex_integral_against_sum(u * x[0] * v * ufl.dP, (0, num_vertices), True) + check_vertex_integral_against_sum(u * x[0] * v * dP(1), (0, num_vertices // 2), True) + check_vertex_integral_against_sum(u * x[0] * v * dP(2), (num_vertices // 2, num_vertices), True) + check_vertex_integral_against_sum(u * x[0] * v * (dP(1) + dP(2)), (0, num_vertices), True) From 64bee1369e641bc2ebc662d4c7d34d25697c5434 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 22 Aug 2025 13:01:10 +0200 Subject: [PATCH 080/104] Change to geometrically defined subdomains in vertex integral rank 1 test --- python/test/unit/fem/test_assembler.py | 42 +++++++++++++++----------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 04c22617b44..ecddf6c3781 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1617,13 +1617,13 @@ def test_vertex_integral_rank_1(cell_type, ghost_mode, dtype): comm, 4, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype ) - num_vertices = msh.topology.index_map(0).size_local + vertex_map = msh.topology.index_map(0) + num_vertices = vertex_map.size_local - def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): - a, b = coordinate_range - weights = np.arange(a, b, dtype=rdtype) if weighted else np.ones(b - a, dtype=rdtype) + def check_vertex_integral_against_sum(form, vertices, weighted=False): + weights = vertex_map.local_to_global(vertices) if weighted else np.ones_like(vertices) expected_value_l = np.zeros(num_vertices, dtype=rdtype) - expected_value_l[a:b] = msh.geometry.x[a:b, 0] * weights + expected_value_l[vertices] = msh.geometry.x[vertices, 0] * weights value_l = fem.assemble_vector(fem.form(form, dtype=dtype)) equal_l = np.allclose( expected_value_l, np.real(value_l.array[:num_vertices]), atol=1e3 * np.finfo(rdtype).eps @@ -1636,24 +1636,30 @@ def check_vertex_integral_against_sum(form, coordinate_range, weighted=False): v = ufl.conj(ufl.TestFunction(V)) # Full domain - check_vertex_integral_against_sum(x[0] * v * ufl.dP, (0, num_vertices)) + check_vertex_integral_against_sum(x[0] * v * ufl.dP, np.arange(num_vertices)) - # Split domain into first half of vertices (1) and second half of vertices (2) - vertices = np.arange(0, msh.topology.index_map(0).size_local, dtype=np.int32) - tags = np.full_like(vertices, 1) - tags[tags.size // 2 :] = 2 + # Split domain into left half of vertices (1) and right half of vertices (2) + vertices_1 = mesh.locate_entities(msh, 0, lambda x: x[0] <= 0.5) + vertices_1 = vertices_1[vertices_1 < num_vertices] + vertices_2 = mesh.locate_entities(msh, 0, lambda x: x[0] > 0.5) + vertices_2 = vertices_2[vertices_2 < num_vertices] + + tags = np.full(num_vertices, 1) + tags[vertices_2] = 2 + vertices = np.arange(0, num_vertices, dtype=np.int32) meshtags = mesh.meshtags(msh, 0, vertices, tags) + dP = ufl.Measure("dP", domain=msh, subdomain_data=meshtags) - check_vertex_integral_against_sum(x[0] * v * dP(1), (0, num_vertices // 2)) - check_vertex_integral_against_sum(x[0] * v * dP(2), (num_vertices // 2, num_vertices)) - check_vertex_integral_against_sum(x[0] * v * (dP(1) + dP(2)), (0, num_vertices)) + check_vertex_integral_against_sum(x[0] * v * dP(1), vertices_1) + check_vertex_integral_against_sum(x[0] * v * dP(2), vertices_2) + check_vertex_integral_against_sum(x[0] * v * (dP(1) + dP(2)), np.arange(num_vertices)) V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V, dtype=dtype) - u.x.array[:] = np.arange(u.x.array.size) + u.x.array[:] = vertex_map.local_to_global(np.arange(num_vertices + vertex_map.num_ghosts)) - check_vertex_integral_against_sum(u * x[0] * v * ufl.dP, (0, num_vertices), True) - check_vertex_integral_against_sum(u * x[0] * v * dP(1), (0, num_vertices // 2), True) - check_vertex_integral_against_sum(u * x[0] * v * dP(2), (num_vertices // 2, num_vertices), True) - check_vertex_integral_against_sum(u * x[0] * v * (dP(1) + dP(2)), (0, num_vertices), True) + check_vertex_integral_against_sum(u * x[0] * v * ufl.dP, np.arange(num_vertices), True) + check_vertex_integral_against_sum(u * x[0] * v * dP(1), vertices_1, True) + check_vertex_integral_against_sum(u * x[0] * v * dP(2), vertices_2, True) + check_vertex_integral_against_sum(u * x[0] * v * (dP(1) + dP(2)), np.arange(num_vertices), True) From 1b22ffae5d1fa3d99af708d76e76c3858d882afb Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 26 Aug 2025 09:28:26 +0200 Subject: [PATCH 081/104] Remove merge confilt --- python/test/unit/fem/test_assembler.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index c039c93f336..ed00d81c222 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1,8 +1,4 @@ -<<<<<<< HEAD -# Copyright (C) 2018-2025 Garth N. Wells and Paul T. Kühner -======= -# Copyright (C) 2018-2025 Garth N. Wells, Jørgen S. Dokken ->>>>>>> main +# Copyright (C) 2018-2025 Garth N. Wells, Jørgen S. Dokken and Paul T. Kühner # # This file is part of DOLFINx (https://www.fenicsproject.org) # @@ -1514,6 +1510,7 @@ def test_vector_types(): assert np.linalg.norm(x0.array - x1.array) == pytest.approx(0.0) assert np.linalg.norm(x0.array - x2.array) == pytest.approx(0.0, abs=1e-7) + @dtype_parametrize @pytest.mark.parametrize("method", ["degree", "metadata"]) def test_mixed_quadrature(dtype, method): From 1f2b22116df591f6b4057c161841ffb875ba6571 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:06:10 +0200 Subject: [PATCH 082/104] Adapt to new kernel storage --- cpp/dolfinx/fem/assemble_scalar_impl.h | 2 +- cpp/dolfinx/fem/assemble_vector_impl.h | 2 +- cpp/dolfinx/fem/utils.h | 4 ++-- python/dolfinx/fem/forms.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index 45a37a92f7e..3f060e78537 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -275,7 +275,7 @@ T assemble_scalar( perms); } - for (int i : M.integral_ids(IntegralType::vertex)) + for (int i = 0; i < M.num_integrals(IntegralType::vertex, 0); ++i) { auto fn = M.kernel(IntegralType::vertex, i, 0); assert(fn); diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index e8b1aadab2b..f4ba9664138 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -1472,7 +1472,7 @@ void assemble_vector( } } - for (int i : L.integral_ids(IntegralType::vertex)) + for (int i = 0; i < L.num_integrals(IntegralType::vertex, 0); ++i) { auto fn = L.kernel(IntegralType::vertex, i, 0); assert(fn); diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index c6ff63e7544..a9cf94864be 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -797,7 +797,7 @@ Form create_form_factory( std::vector cells_and_vertices = get_cells_and_vertices( std::ranges::views::iota(0, num_vertices)); - integrals.insert({{IntegralType::vertex, id, form_idx}, + integrals.insert({{IntegralType::vertex, i, form_idx}, {k, cells_and_vertices, active_coeffs}}); } else if (sd != subdomains.end()) @@ -807,7 +807,7 @@ Form create_form_factory( [](auto& a) { return a.first; }); if (it != sd->second.end() and it->first == id) { - integrals.insert({{IntegralType::vertex, id, form_idx}, + integrals.insert({{IntegralType::vertex, i, form_idx}, {k, std::vector(it->second.begin(), it->second.end()), diff --git a/python/dolfinx/fem/forms.py b/python/dolfinx/fem/forms.py index 48883215c21..69323ec3148 100644 --- a/python/dolfinx/fem/forms.py +++ b/python/dolfinx/fem/forms.py @@ -359,8 +359,8 @@ def _form(form): # Extract subdomain ids from ufcx_form subdomain_ids = {type: [] for type in sd.get(domain).keys()} - integral_offsets = [ufcx_form.form_integral_offsets[i] for i in range(4)] - for i in range(3): + integral_offsets = [ufcx_form.form_integral_offsets[i] for i in range(5)] + for i in range(len(integral_offsets) - 1): integral_type = IntegralType(i) for j in range(integral_offsets[i], integral_offsets[i + 1]): subdomain_ids[integral_type.name].append(ufcx_form.form_integral_ids[j]) From db60ea2a2104fed69131d8699bdad5a57277e674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=2E=20K=C3=BChner?= <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 10:52:44 +0200 Subject: [PATCH 083/104] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jørgen Schartum Dokken --- cpp/dolfinx/fem/assemble_vector_impl.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index f4ba9664138..3e8c1c2a13d 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -772,7 +772,6 @@ void assemble_exterior_facets( const int num_dofs = dmap.extent(1); std::vector> cdofs(3 * x_dofmap.extent(1)); std::vector be(bs * num_dofs); - assert(facets0.size() == facets.size()); for (std::size_t f = 0; f < facets.extent(0); ++f) { @@ -794,7 +793,6 @@ void assemble_exterior_facets( std::ranges::fill(be, 0); kernel(be.data(), &coeffs(f, 0), constants.data(), cdofs.data(), &local_facet, &perm, nullptr); - P0(be, cell_info0, cell0, 1); // Add element vector to global vector From d092b758b5a71ff908e77519492a71325034c207 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:03:51 +0200 Subject: [PATCH 084/104] Change to assert for c->v --- cpp/dolfinx/fem/utils.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 2df405288a9..b6206279d51 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -199,11 +199,9 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } auto c_to_v = topology.connectivity(tdim, 0); - if (!c_to_v) - { - throw std::runtime_error( - "Topology cell-to-vertex connectivity has not been computed."); - } + // Any mesh already has c->v connectivity. + assert(c_to_v); + return {v_to_c, c_to_v}; }; From 73c6fbd2271c033323a2dfdfbe35fe93e588b231 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:17:33 +0200 Subject: [PATCH 085/104] Introduce convenience wrapper get_cell_vertex_pairs --- cpp/dolfinx/fem/utils.cpp | 15 +++---------- cpp/dolfinx/fem/utils.h | 45 ++++++++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index b6206279d51..4ab359344b1 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -267,19 +267,10 @@ fem::compute_integration_domains(fem::IntegralType integral_type, auto [v_to_c, c_to_v] = get_cell_vertex_connectivity(); for (auto vertex : entities) { - auto cells = v_to_c->links(vertex); - assert(cells.size() > 0); + std::array pair = impl::get_cell_vertex_pairs<1>( + vertex, v_to_c->links(vertex), *v_to_c); - // Use first cell for assembly over by default - std::int32_t cell = cells[0]; - entity_data.push_back(cell); - - // Find local index of vertex within cell - auto cell_vertices = c_to_v->links(cell); - auto it = std::ranges::find(cell_vertices, vertex); - assert(it != cell_vertices.end()); - std::int32_t local_index = std::distance(cell_vertices.begin(), it); - entity_data.push_back(local_index); + entity_data.insert(entity_data.end(), pair.begin(), pair.end()); } } } diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index a9cf94864be..0118eb13f78 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -92,6 +92,33 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, return cell_local_facet_pairs; } +/// Helper function to get an array of of (cell, local_vertex) pairs +/// corresponding to a given vertex index. +/// @param[in] v vertex index +/// @param[in] cells List of cells incident to the vertex +/// @param[in] c_to_f Cell to facet connectivity +/// @return Vector of (cell, local_vertex) pairs +template +std::array +get_cell_vertex_pairs(std::int32_t v, std::span cells, + const graph::AdjacencyList& c_to_v) +{ + static_assert(num_cells == 1); // Patch assembly not supported. + + assert(cells.size() > 0); + + // Use first cell for assembly over by default + std::int32_t cell = cells[0]; + + // Find local index of vertex within cell + auto cell_vertices = c_to_v.links(cell); + auto it = std::ranges::find(cell_vertices, v); + assert(it != cell_vertices.end()); + std::int32_t local_index = std::distance(cell_vertices.begin(), it); + + return {cell, local_index}; +} + } // namespace impl /// @brief Given an integral type and a set of entities, computes and @@ -772,19 +799,11 @@ Form create_form_factory( cell_and_vertex.reserve(2 * vertices_range.size()); for (std::int32_t vertex : vertices_range) { - auto cells = v_to_c->links(vertex); - assert(cells.size() > 0); - - // Use first cell for assembly over by default - std::int32_t cell = cells[0]; - cell_and_vertex.push_back(cell); - - // Find local index of vertex within cell - auto cell_vertices = c_to_v->links(cell); - auto it = std::ranges::find(cell_vertices, vertex); - assert(it != cell_vertices.end()); - std::int32_t local_index = std::distance(cell_vertices.begin(), it); - cell_and_vertex.push_back(local_index); + std::array pair = impl::get_cell_vertex_pairs<1>( + vertex, v_to_c->links(vertex), *v_to_c); + + cell_and_vertex.insert(cell_and_vertex.end(), pair.begin(), + pair.end()); } assert(cell_and_vertex.size() == 2 * vertices_range.size()); return cell_and_vertex; From 5689562f1e84e1d639b4f3afb3a575ed5f09cd5d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:20:26 +0200 Subject: [PATCH 086/104] Bad dimension check --- python/test/unit/fem/test_assembler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index ed00d81c222..26638f88578 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1592,6 +1592,8 @@ def test_vertex_integral_rank_0(cell_type, ghost_mode, dtype): msh = mesh.create_unit_cube( comm, 4, 4, 4, cell_type=cell_type, dtype=rdtype, ghost_mode=ghost_mode ) + else: + raise RuntimeError("Bad dimension") vertex_map = msh.topology.index_map(0) @@ -1669,6 +1671,8 @@ def test_vertex_integral_rank_1(cell_type, ghost_mode, dtype): msh = mesh.create_unit_cube( comm, 4, 4, 4, cell_type=cell_type, ghost_mode=ghost_mode, dtype=rdtype ) + else: + raise RuntimeError("Bad dimension") vertex_map = msh.topology.index_map(0) num_vertices = vertex_map.size_local From 8a9359a6ad80547debd86ba8d9648b769578563b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:22:28 +0200 Subject: [PATCH 087/104] Explain weighting --- python/test/unit/fem/test_assembler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 26638f88578..5dc21e0ecf3 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1598,6 +1598,8 @@ def test_vertex_integral_rank_0(cell_type, ghost_mode, dtype): vertex_map = msh.topology.index_map(0) def check_vertex_integral_against_sum(form, vertices, weighted=False): + """Weighting assumes the vertex integral to be weighted by a P1 function, each vertex value + corresponding to its global index.""" weights = vertex_map.local_to_global(vertices) if weighted else np.ones_like(vertices) expected_value_l = np.sum(msh.geometry.x[vertices, 0] * weights) value_l = fem.assemble_scalar(fem.form(form, dtype=dtype)) @@ -1678,6 +1680,8 @@ def test_vertex_integral_rank_1(cell_type, ghost_mode, dtype): num_vertices = vertex_map.size_local def check_vertex_integral_against_sum(form, vertices, weighted=False): + """Weighting assumes the vertex integral to be weighted by a P1 function, each vertex value + corresponding to its global index.""" weights = vertex_map.local_to_global(vertices) if weighted else np.ones_like(vertices) expected_value_l = np.zeros(num_vertices, dtype=rdtype) expected_value_l[vertices] = msh.geometry.x[vertices, 0] * weights From 3901400aaf3ce4b56cf56742ceded647af19d46b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:23:27 +0200 Subject: [PATCH 088/104] Fix doc --- cpp/dolfinx/fem/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 0118eb13f78..3c471726c01 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -96,7 +96,7 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, /// corresponding to a given vertex index. /// @param[in] v vertex index /// @param[in] cells List of cells incident to the vertex -/// @param[in] c_to_f Cell to facet connectivity +/// @param[in] c_to_v Cell to vertex connectivity /// @return Vector of (cell, local_vertex) pairs template std::array From f2ccd3cc9807fca3da9c0855b072aaf73c7e99c2 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 12:07:02 +0200 Subject: [PATCH 089/104] Fix --- cpp/dolfinx/fem/utils.cpp | 2 +- cpp/dolfinx/fem/utils.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 4ab359344b1..37f041120d0 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -268,7 +268,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, for (auto vertex : entities) { std::array pair = impl::get_cell_vertex_pairs<1>( - vertex, v_to_c->links(vertex), *v_to_c); + vertex, v_to_c->links(vertex), *c_to_v); entity_data.insert(entity_data.end(), pair.begin(), pair.end()); } diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 3c471726c01..93f5461c333 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -800,7 +800,7 @@ Form create_form_factory( for (std::int32_t vertex : vertices_range) { std::array pair = impl::get_cell_vertex_pairs<1>( - vertex, v_to_c->links(vertex), *v_to_c); + vertex, v_to_c->links(vertex), *c_to_v); cell_and_vertex.insert(cell_and_vertex.end(), pair.begin(), pair.end()); From 41381c5ca266e353a4655216be213dc47e952f6b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 12:33:05 +0200 Subject: [PATCH 090/104] Add custom packing tests --- python/test/unit/fem/test_assembler.py | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 5dc21e0ecf3..f72247db2f8 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1642,6 +1642,30 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): check_vertex_integral_against_sum(u * x[0] * dP(2), vertices_2, True) check_vertex_integral_against_sum(u * x[0] * (dP(1) + dP(2)), np.arange(num_vertices), True) + # Check custom packing + if cell_type is mesh.CellType.prism: + return + + msh.topology.create_entities(1) + msh.topology.create_connectivity(cell_dim - 1, cell_dim) + cell_count = msh.topology.index_map(cell_dim).size_local + vertex_entities = np.hstack([np.arange(cell_count), np.zeros(cell_count)]) + + fem.compute_integration_domains( + fem.IntegralType.exterior_facet, + msh.topology, + np.arange(msh.topology.index_map(0).size_local), + ) + subdomains = {fem.IntegralType.exterior_facet: [(0, vertex_entities)]} + + compiled_form = fem.compile_form( + comm, x[0] * ufl.dP, form_compiler_options={"scalar_type": dtype} + ) + form = fem.create_form(compiled_form, [], msh, subdomains, {}, {}, []) + expected_value_l = np.sum(msh.geometry.x[vertices, 0]) + value_l = fem.assemble_scalar(form) + assert expected_value_l == pytest.approx(value_l, abs=5e4 * np.finfo(rdtype).eps) + @pytest.mark.parametrize( "cell_type", @@ -1724,3 +1748,31 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): check_vertex_integral_against_sum(u * x[0] * v * dP(1), vertices_1, True) check_vertex_integral_against_sum(u * x[0] * v * dP(2), vertices_2, True) check_vertex_integral_against_sum(u * x[0] * v * (dP(1) + dP(2)), np.arange(num_vertices), True) + + # Check custom packing + if cell_type is mesh.CellType.prism: + return + + msh.topology.create_entities(1) + msh.topology.create_connectivity(cell_dim - 1, cell_dim) + cell_count = msh.topology.index_map(cell_dim).size_local + vertex_entities = np.hstack([np.arange(cell_count), np.zeros(cell_count)]) + + fem.compute_integration_domains( + fem.IntegralType.exterior_facet, + msh.topology, + np.arange(msh.topology.index_map(0).size_local), + ) + subdomains = {fem.IntegralType.exterior_facet: [(0, vertex_entities)]} + + compiled_form = fem.compile_form( + comm, x[0] * v * ufl.dP, form_compiler_options={"scalar_type": dtype} + ) + form = fem.create_form(compiled_form, [V], msh, subdomains, {}, {}, []) + expected_value_l = np.sum(msh.geometry.x[vertices, 0]) + expected_value_l = np.zeros(num_vertices, dtype=rdtype) + expected_value_l[vertices] = msh.geometry.x[vertices, 0] + value_l = fem.assemble_vector(form) + assert expected_value_l == pytest.approx( + value_l.array[: expected_value_l.size], abs=5e4 * np.finfo(rdtype).eps + ) From eb4092dbc1a345a8a6be7f9249763d439abbae2f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:19:22 +0200 Subject: [PATCH 091/104] Reorder with vertex->dof map --- python/test/unit/fem/test_assembler.py | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index f72247db2f8..cfbd217ca1f 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -14,6 +14,7 @@ import scipy.sparse import basix +import dolfinx.cpp import ufl from basix.ufl import element, mixed_element from dolfinx import cpp as _cpp @@ -1562,6 +1563,29 @@ def test_mixed_quadrature(dtype, method): assert np.isclose(global_contribution, global_sum, rtol=tol, atol=tol) +def vertex_to_dof_map(V): + """Create a map from the vertices of the mesh to the corresponding degree of freedom.""" + mesh = V.mesh + num_vertices_per_cell = dolfinx.cpp.mesh.cell_num_entities(mesh.topology.cell_type, 0) + + dof_layout2 = np.empty((num_vertices_per_cell,), dtype=np.int32) + for i in range(num_vertices_per_cell): + var = V.dofmap.dof_layout.entity_dofs(0, i) + assert len(var) == 1 + dof_layout2[i] = var[0] + + num_vertices = mesh.topology.index_map(0).size_local + mesh.topology.index_map(0).num_ghosts + + c_to_v = mesh.topology.connectivity(mesh.topology.dim, 0) + assert (c_to_v.offsets[1:] - c_to_v.offsets[:-1] == c_to_v.offsets[1]).all(), ( + "Single cell type supported" + ) + + vertex_to_dof_map = np.empty(num_vertices, dtype=np.int32) + vertex_to_dof_map[c_to_v.array] = V.dofmap.list[:, dof_layout2].reshape(-1) + return vertex_to_dof_map + + @pytest.mark.parametrize( "cell_type", [ @@ -1635,7 +1659,9 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V, dtype=dtype) - u.x.array[:] = vertex_map.local_to_global(np.arange(num_vertices + vertex_map.num_ghosts)) + vertex_to_dof = vertex_to_dof_map(V) + vertices = np.arange(num_vertices + vertex_map.num_ghosts) + u.x.array[vertex_to_dof[vertices]] = vertex_map.local_to_global(vertices) check_vertex_integral_against_sum(u * x[0] * ufl.dP, np.arange(num_vertices), True) check_vertex_integral_against_sum(u * x[0] * dP(1), vertices_1, True) @@ -1743,6 +1769,9 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): V = fem.functionspace(msh, ("P", 1)) u = fem.Function(V, dtype=dtype) u.x.array[:] = vertex_map.local_to_global(np.arange(num_vertices + vertex_map.num_ghosts)) + vertex_to_dof = vertex_to_dof_map(V) + vertices = np.arange(num_vertices + vertex_map.num_ghosts) + u.x.array[vertex_to_dof[vertices]] = vertex_map.local_to_global(vertices) check_vertex_integral_against_sum(u * x[0] * v * ufl.dP, np.arange(num_vertices), True) check_vertex_integral_against_sum(u * x[0] * v * dP(1), vertices_1, True) From e7ecf2bcb106b517cca4224fd8b9eaa6fc53e7dc Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:50:09 +0200 Subject: [PATCH 092/104] Add additional custom packing test through subdomain_data --- python/test/unit/fem/test_assembler.py | 41 ++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index cfbd217ca1f..2d6ee1b9f6b 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1674,15 +1674,29 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): msh.topology.create_entities(1) msh.topology.create_connectivity(cell_dim - 1, cell_dim) - cell_count = msh.topology.index_map(cell_dim).size_local - vertex_entities = np.hstack([np.arange(cell_count), np.zeros(cell_count)]) + v_to_c = msh.topology.connectivity(0, cell_dim) + c_to_v = msh.topology.connectivity(cell_dim, 0) + + cell_vertex_pairs = [] + for v in range(num_vertices): + c = v_to_c.links(v)[0] + v_l = np.where(c_to_v.links(c) == v)[0] + cell_vertex_pairs = np.append(cell_vertex_pairs, [c, *v_l]) + + # a) With subdomain_data + check_vertex_integral_against_sum( + x[0] * ufl.dP(domain=msh, subdomain_data=[(1, cell_vertex_pairs)], subdomain_id=1), + np.arange(num_vertices), + ) + + # b) With create_form fem.compute_integration_domains( fem.IntegralType.exterior_facet, msh.topology, np.arange(msh.topology.index_map(0).size_local), ) - subdomains = {fem.IntegralType.exterior_facet: [(0, vertex_entities)]} + subdomains = {fem.IntegralType.exterior_facet: [(0, cell_vertex_pairs)]} compiled_form = fem.compile_form( comm, x[0] * ufl.dP, form_compiler_options={"scalar_type": dtype} @@ -1784,15 +1798,30 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): msh.topology.create_entities(1) msh.topology.create_connectivity(cell_dim - 1, cell_dim) - cell_count = msh.topology.index_map(cell_dim).size_local - vertex_entities = np.hstack([np.arange(cell_count), np.zeros(cell_count)]) + v_to_c = msh.topology.connectivity(0, cell_dim) + c_to_v = msh.topology.connectivity(cell_dim, 0) + + cell_vertex_pairs = [] + for v in range(num_vertices): + c = v_to_c.links(v)[0] + v_l = np.where(c_to_v.links(c) == v)[0] + cell_vertex_pairs = np.append(cell_vertex_pairs, [c, *v_l]) + + # a) With subdomain_data + v = ufl.conj(ufl.TestFunction(V)) + check_vertex_integral_against_sum( + x[0] * v * ufl.dP(domain=msh, subdomain_data=[(1, cell_vertex_pairs)], subdomain_id=1), + np.arange(num_vertices), + ) + + # b) With create_form fem.compute_integration_domains( fem.IntegralType.exterior_facet, msh.topology, np.arange(msh.topology.index_map(0).size_local), ) - subdomains = {fem.IntegralType.exterior_facet: [(0, vertex_entities)]} + subdomains = {fem.IntegralType.exterior_facet: [(0, cell_vertex_pairs)]} compiled_form = fem.compile_form( comm, x[0] * v * ufl.dP, form_compiler_options={"scalar_type": dtype} From 94e0e3dd56cea964596791a02dbadc7159c20b68 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 15:31:10 +0200 Subject: [PATCH 093/104] Fix? --- python/test/unit/fem/test_assembler.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 2d6ee1b9f6b..52b505a90cf 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1691,11 +1691,8 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): ) # b) With create_form - fem.compute_integration_domains( - fem.IntegralType.exterior_facet, - msh.topology, - np.arange(msh.topology.index_map(0).size_local), - ) + vertices = np.arange(num_vertices) + fem.compute_integration_domains(fem.IntegralType.exterior_facet, msh.topology, vertices) subdomains = {fem.IntegralType.exterior_facet: [(0, cell_vertex_pairs)]} compiled_form = fem.compile_form( @@ -1816,11 +1813,8 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): ) # b) With create_form - fem.compute_integration_domains( - fem.IntegralType.exterior_facet, - msh.topology, - np.arange(msh.topology.index_map(0).size_local), - ) + vertices = np.arange(num_vertices) + fem.compute_integration_domains(fem.IntegralType.exterior_facet, msh.topology, vertices) subdomains = {fem.IntegralType.exterior_facet: [(0, cell_vertex_pairs)]} compiled_form = fem.compile_form( From 5aed6012aa3df30be46aadac15822c569ff330c6 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:44:20 +0200 Subject: [PATCH 094/104] Defend against empty mesh on process --- python/test/unit/fem/test_assembler.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index 52b505a90cf..e5a87ac4961 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -1577,9 +1577,10 @@ def vertex_to_dof_map(V): num_vertices = mesh.topology.index_map(0).size_local + mesh.topology.index_map(0).num_ghosts c_to_v = mesh.topology.connectivity(mesh.topology.dim, 0) - assert (c_to_v.offsets[1:] - c_to_v.offsets[:-1] == c_to_v.offsets[1]).all(), ( - "Single cell type supported" - ) + assert ( + c_to_v.num_nodes == 0 + or (c_to_v.offsets[1:] - c_to_v.offsets[:-1] == c_to_v.offsets[1]).all() + ), "Single cell type supported" vertex_to_dof_map = np.empty(num_vertices, dtype=np.int32) vertex_to_dof_map[c_to_v.array] = V.dofmap.list[:, dof_layout2].reshape(-1) @@ -1678,7 +1679,7 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): v_to_c = msh.topology.connectivity(0, cell_dim) c_to_v = msh.topology.connectivity(cell_dim, 0) - cell_vertex_pairs = [] + cell_vertex_pairs = np.array([], dtype=np.int32) for v in range(num_vertices): c = v_to_c.links(v)[0] v_l = np.where(c_to_v.links(c) == v)[0] @@ -1799,7 +1800,7 @@ def check_vertex_integral_against_sum(form, vertices, weighted=False): v_to_c = msh.topology.connectivity(0, cell_dim) c_to_v = msh.topology.connectivity(cell_dim, 0) - cell_vertex_pairs = [] + cell_vertex_pairs = np.array([], dtype=np.int32) for v in range(num_vertices): c = v_to_c.links(v)[0] v_l = np.where(c_to_v.links(c) == v)[0] From 341f08a17cd2104a53bdd86956a1467410ae3b5f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:12:43 +0200 Subject: [PATCH 095/104] Remove entity access code duplication --- cpp/dolfinx/fem/utils.cpp | 44 +++++++++++++-------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 37f041120d0..f5c11cd6dce 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -167,42 +167,28 @@ fem::compute_integration_domains(fem::IntegralType integral_type, entities = entities.first(std::distance(entities.begin(), it1)); } - auto get_cell_facet_connectivity = [tdim, &topology]() + auto get_connectivities = [tdim, &topology](int entity_dim) -> std::pair>, std::shared_ptr>> { - auto f_to_c = topology.connectivity(tdim - 1, tdim); - if (!f_to_c) + auto e_to_c = topology.connectivity(entity_dim, tdim); + if (!e_to_c) { throw std::runtime_error( - "Topology facet-to-cell connectivity has not been computed."); + std::format("Topology entity-to-cell connectivity has not been " + "computed for entity dim {}.", + entity_dim)); } - auto c_to_f = topology.connectivity(tdim, tdim - 1); - if (!c_to_f) + auto e_to_f = topology.connectivity(tdim, entity_dim); + if (!e_to_f) { throw std::runtime_error( - "Topology cell-to-facet connectivity has not been computed."); + std::format("Topology cell-to-entity connectivity has not been " + "computed for entity dim {}.", + entity_dim)); } - return {f_to_c, c_to_f}; - }; - - auto get_cell_vertex_connectivity = [tdim, &topology]() - -> std::pair>, - std::shared_ptr>> - { - auto v_to_c = topology.connectivity(0, tdim); - if (!v_to_c) - { - throw std::runtime_error( - "Topology vertex-to-cell connectivity has not been computed."); - } - - auto c_to_v = topology.connectivity(tdim, 0); - // Any mesh already has c->v connectivity. - assert(c_to_v); - - return {v_to_c, c_to_v}; + return {e_to_c, e_to_f}; }; std::vector entity_data; @@ -215,7 +201,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } case IntegralType::exterior_facet: { - auto [f_to_c, c_to_f] = get_cell_facet_connectivity(); + auto [f_to_c, c_to_f] = get_connectivities(tdim-1); // Create list of tagged boundary facets const std::vector bfacets = mesh::exterior_facet_indices(topology); std::vector facets; @@ -231,7 +217,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } case IntegralType::interior_facet: { - auto [f_to_c, c_to_f] = get_cell_facet_connectivity(); + auto [f_to_c, c_to_f] = get_connectivities(tdim-1); // Create indicator for interprocess facets assert(topology.index_map(tdim - 1)); @@ -264,7 +250,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } case IntegralType::vertex: { - auto [v_to_c, c_to_v] = get_cell_vertex_connectivity(); + auto [v_to_c, c_to_v] = get_connectivities(0); for (auto vertex : entities) { std::array pair = impl::get_cell_vertex_pairs<1>( From ce1f2fed113daad45adfa11ed17e7302dbddf117 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:14:51 +0200 Subject: [PATCH 096/104] Add docstring --- cpp/dolfinx/fem/utils.cpp | 4 ++-- cpp/dolfinx/fem/utils.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index f5c11cd6dce..ec7a8501ae3 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -201,7 +201,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } case IntegralType::exterior_facet: { - auto [f_to_c, c_to_f] = get_connectivities(tdim-1); + auto [f_to_c, c_to_f] = get_connectivities(tdim - 1); // Create list of tagged boundary facets const std::vector bfacets = mesh::exterior_facet_indices(topology); std::vector facets; @@ -217,7 +217,7 @@ fem::compute_integration_domains(fem::IntegralType integral_type, } case IntegralType::interior_facet: { - auto [f_to_c, c_to_f] = get_connectivities(tdim-1); + auto [f_to_c, c_to_f] = get_connectivities(tdim - 1); // Create indicator for interprocess facets assert(topology.index_map(tdim - 1)); diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 93f5461c333..22436c8e50d 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -94,6 +94,7 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, /// Helper function to get an array of of (cell, local_vertex) pairs /// corresponding to a given vertex index. +/// @note If the vertex is connected to multiple cells, the first one is picked. /// @param[in] v vertex index /// @param[in] cells List of cells incident to the vertex /// @param[in] c_to_v Cell to vertex connectivity From 7cbe01a31d8e4da1ce30b97bd508942580aa2cda Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:12:57 +0200 Subject: [PATCH 097/104] Use branch in spack ci --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb2e85119ab..71d59998122 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: . $GITHUB_WORKSPACE/spack-src/share/spack/setup-env.sh spack info fenics-dolfinx spack env create cxx dolfinx-src/.github/workflows/spack-config/gh-actions-env-test.yml - spack -e cxx develop --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main + spack -e cxx develop --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@git.${{ env.CURRENT_BRANCH }}=main spack -e cxx develop fenics-basix@git.${{ env.basix_ref }}=main spack -e cxx develop fenics-ufcx@git.${{ env.ffcx_ref }}=main spack -e cxx develop py-fenics-basix@git.${{ env.basix_ref }}=main @@ -77,7 +77,7 @@ jobs: . $GITHUB_WORKSPACE/spack-src/share/spack/setup-env.sh spack -e cxx add fenics-dolfinx@main generator=ninja build_type=Developer +petsc +slepc partitioners=parmetis \ %petsc +mpi +hypre +mumps +superlu-dist %openmpi %adios2~fortran~libcatalyst %cmake~ncurses - spack -e cxx add catch2 py-fenics-ffcx@main + spack -e cxx add catch2 py-fenics-ffcx@git.${{ env.CURRENT_BRANCH }}=main spack -e cxx concretize -j 4 spack -v -e cxx install -j 4 -p 2 --use-buildcache auto From db889243dc55b1819ef4178824d6b49e90803839 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 1 Sep 2025 11:22:53 +0200 Subject: [PATCH 098/104] Revert "Use branch in spack ci" This reverts commit 7cbe01a31d8e4da1ce30b97bd508942580aa2cda. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71d59998122..bb2e85119ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: . $GITHUB_WORKSPACE/spack-src/share/spack/setup-env.sh spack info fenics-dolfinx spack env create cxx dolfinx-src/.github/workflows/spack-config/gh-actions-env-test.yml - spack -e cxx develop --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@git.${{ env.CURRENT_BRANCH }}=main + spack -e cxx develop --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main spack -e cxx develop fenics-basix@git.${{ env.basix_ref }}=main spack -e cxx develop fenics-ufcx@git.${{ env.ffcx_ref }}=main spack -e cxx develop py-fenics-basix@git.${{ env.basix_ref }}=main @@ -77,7 +77,7 @@ jobs: . $GITHUB_WORKSPACE/spack-src/share/spack/setup-env.sh spack -e cxx add fenics-dolfinx@main generator=ninja build_type=Developer +petsc +slepc partitioners=parmetis \ %petsc +mpi +hypre +mumps +superlu-dist %openmpi %adios2~fortran~libcatalyst %cmake~ncurses - spack -e cxx add catch2 py-fenics-ffcx@git.${{ env.CURRENT_BRANCH }}=main + spack -e cxx add catch2 py-fenics-ffcx@main spack -e cxx concretize -j 4 spack -v -e cxx install -j 4 -p 2 --use-buildcache auto From 796f9f2b9044f9f1ce0e3120e2ce84cb0125d7aa Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 1 Sep 2025 11:23:45 +0200 Subject: [PATCH 099/104] Add --no-clone --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb2e85119ab..dc705ee18a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: . $GITHUB_WORKSPACE/spack-src/share/spack/setup-env.sh spack info fenics-dolfinx spack env create cxx dolfinx-src/.github/workflows/spack-config/gh-actions-env-test.yml - spack -e cxx develop --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main + spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main spack -e cxx develop fenics-basix@git.${{ env.basix_ref }}=main spack -e cxx develop fenics-ufcx@git.${{ env.ffcx_ref }}=main spack -e cxx develop py-fenics-basix@git.${{ env.basix_ref }}=main @@ -115,7 +115,7 @@ jobs: . $GITHUB_WORKSPACE/spack-src/share/spack/setup-env.sh spack info py-fenics-dolfinx spack env create py dolfinx-src/.github/workflows/spack-config/gh-actions-env-test.yml - spack -e py develop --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main spack -e py develop fenics-basix@git.${{ env.basix_ref }}=main spack -e py develop fenics-ufcx@git.${{ env.ffcx_ref }}=main spack -e py develop --path $GITHUB_WORKSPACE/dolfinx-src py-fenics-dolfinx@main From a847719531de1b163af5978897b44a49bfd37c26 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 1 Sep 2025 13:34:08 +0200 Subject: [PATCH 100/104] Clone all FEniCS dependencies --- .github/workflows/ci.yml | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc705ee18a1..e304130bee3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,24 @@ jobs: tar unzip xz-utils g++ gcc gfortran apt-get install -y llvm-15-dev + - name: Get UFL code + uses: actions/checkout@v5 + with: + repository: fenics/ufl + ref: ${{ env.ufl_ref }} + path: ufl-src + - name: Get FFCx code + uses: actions/checkout@v5 + with: + repository: fenics/ffcx + ref: ${{ env.ffcx_ref }} + path: ffcx-src + - name: Get BASIx code + uses: actions/checkout@v5 + with: + repository: fenics/basix + ref: ${{ env.basix_ref }} + path: basix-src - name: Get DOLFINx code uses: actions/checkout@v5 with: @@ -66,11 +84,11 @@ jobs: spack info fenics-dolfinx spack env create cxx dolfinx-src/.github/workflows/spack-config/gh-actions-env-test.yml spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main - spack -e cxx develop fenics-basix@git.${{ env.basix_ref }}=main - spack -e cxx develop fenics-ufcx@git.${{ env.ffcx_ref }}=main - spack -e cxx develop py-fenics-basix@git.${{ env.basix_ref }}=main - spack -e cxx develop py-fenics-ffcx@git.${{ env.ffcx_ref }}=main - spack -e cxx develop py-fenics-ufl@git.${{ env.ufl_ref }}=main + spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/basix-src fenics-basix@main + spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src fenics-ufcx@main + spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/basix-src py-fenics-basix@main + spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src py-fenics-ffcx@main + spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/ufl-src py-fenics-ufl@main - name: Build library (C++) run: | @@ -116,12 +134,12 @@ jobs: spack info py-fenics-dolfinx spack env create py dolfinx-src/.github/workflows/spack-config/gh-actions-env-test.yml spack -e py develop --no-clone --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main - spack -e py develop fenics-basix@git.${{ env.basix_ref }}=main - spack -e py develop fenics-ufcx@git.${{ env.ffcx_ref }}=main - spack -e py develop --path $GITHUB_WORKSPACE/dolfinx-src py-fenics-dolfinx@main - spack -e py develop py-fenics-basix@git.${{ env.basix_ref }}=main - spack -e py develop py-fenics-ffcx@git.${{ env.ffcx_ref }}=main - spack -e py develop py-fenics-ufl@git.${{ env.ufl_ref }}=main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/basix-src fenics-basix@main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src fenics-ufcx@main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/dolfinx-src py-fenics-dolfinx@main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/basix-src py-fenics-basix@git.${{ env.basix_ref }}=main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src py-fenics-ffcx@git.${{ env.ffcx_ref }}=main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ufl-src py-fenics-ufl@git.${{ env.ufl_ref }}=main - name: Build library (Python) run: | From d452c3eb72dcd4c44a445c1b8d23204aaaa30884 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 1 Sep 2025 13:42:48 +0200 Subject: [PATCH 101/104] On PR --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e304130bee3..e26469fe8a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,8 @@ on: push: branches: - "**" - # pull_request: - # branches: [ "main" ] + pull_request: + branches: [ "main" ] # merge_group: # branches: # - main From e98ce4a5e33a692de3d0336e29d0c5d3f9ac0784 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:32:56 +0200 Subject: [PATCH 102/104] One more --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e26469fe8a8..20ef70bf196 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,9 @@ jobs: tar unzip xz-utils g++ gcc gfortran apt-get install -y llvm-15-dev + - name: Load dev branch environment variables + run: cat dolfinx-src/.github/workflows/fenicsx-refs.env >> $GITHUB_ENV + - name: Get UFL code uses: actions/checkout@v5 with: @@ -70,9 +73,6 @@ jobs: ref: fenics-testing path: spack-packages - - name: Load dev branch environment variables - run: cat dolfinx-src/.github/workflows/fenicsx-refs.env >> $GITHUB_ENV - - name: Add repo run: | . $GITHUB_WORKSPACE/spack-src/share/spack/setup-env.sh @@ -137,9 +137,9 @@ jobs: spack -e py develop --no-clone --path $GITHUB_WORKSPACE/basix-src fenics-basix@main spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src fenics-ufcx@main spack -e py develop --no-clone --path $GITHUB_WORKSPACE/dolfinx-src py-fenics-dolfinx@main - spack -e py develop --no-clone --path $GITHUB_WORKSPACE/basix-src py-fenics-basix@git.${{ env.basix_ref }}=main - spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src py-fenics-ffcx@git.${{ env.ffcx_ref }}=main - spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ufl-src py-fenics-ufl@git.${{ env.ufl_ref }}=main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/basix-src py-fenics-basix@main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src py-fenics-ffcx@main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ufl-src py-fenics-ufl@main - name: Build library (Python) run: | From c4eeb595c4d818d79763323b4678f4484ce79446 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:34:20 +0200 Subject: [PATCH 103/104] First cehckout --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20ef70bf196..65a70d0a4d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,11 @@ jobs: tar unzip xz-utils g++ gcc gfortran apt-get install -y llvm-15-dev + - name: Get DOLFINx code + uses: actions/checkout@v5 + with: + path: dolfinx-src + - name: Load dev branch environment variables run: cat dolfinx-src/.github/workflows/fenicsx-refs.env >> $GITHUB_ENV @@ -56,10 +61,6 @@ jobs: repository: fenics/basix ref: ${{ env.basix_ref }} path: basix-src - - name: Get DOLFINx code - uses: actions/checkout@v5 - with: - path: dolfinx-src - name: Get Spack uses: actions/checkout@v5 with: From 0cdb4ed9c7428628a843cb457844e33e969edc3f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 1 Sep 2025 15:12:23 +0200 Subject: [PATCH 104/104] Same order as on branch --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65a70d0a4d5..d4dc16dee9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,19 +46,19 @@ jobs: - name: Get UFL code uses: actions/checkout@v5 with: - repository: fenics/ufl + repository: ${{ env.ufl_repository }} ref: ${{ env.ufl_ref }} path: ufl-src - name: Get FFCx code uses: actions/checkout@v5 with: - repository: fenics/ffcx + repository: ${{ env.ffcx_repository }} ref: ${{ env.ffcx_ref }} path: ffcx-src - name: Get BASIx code uses: actions/checkout@v5 with: - repository: fenics/basix + repository: ${{ env.basix_repository }} ref: ${{ env.basix_ref }} path: basix-src - name: Get Spack @@ -85,8 +85,8 @@ jobs: spack info fenics-dolfinx spack env create cxx dolfinx-src/.github/workflows/spack-config/gh-actions-env-test.yml spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main - spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/basix-src fenics-basix@main spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src fenics-ufcx@main + spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/basix-src fenics-basix@main spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/basix-src py-fenics-basix@main spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src py-fenics-ffcx@main spack -e cxx develop --no-clone --path $GITHUB_WORKSPACE/ufl-src py-fenics-ufl@main @@ -135,9 +135,9 @@ jobs: spack info py-fenics-dolfinx spack env create py dolfinx-src/.github/workflows/spack-config/gh-actions-env-test.yml spack -e py develop --no-clone --path $GITHUB_WORKSPACE/dolfinx-src fenics-dolfinx@main - spack -e py develop --no-clone --path $GITHUB_WORKSPACE/basix-src fenics-basix@main - spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src fenics-ufcx@main spack -e py develop --no-clone --path $GITHUB_WORKSPACE/dolfinx-src py-fenics-dolfinx@main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src fenics-ufcx@main + spack -e py develop --no-clone --path $GITHUB_WORKSPACE/basix-src fenics-basix@main spack -e py develop --no-clone --path $GITHUB_WORKSPACE/basix-src py-fenics-basix@main spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ffcx-src py-fenics-ffcx@main spack -e py develop --no-clone --path $GITHUB_WORKSPACE/ufl-src py-fenics-ufl@main