From 90b2c354a98bebfb67a51d7f60e7b404a1201049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesus=20Mart=C3=ADnez=20Frutos?= <35522233+jmartfrut@users.noreply.github.com> Date: Wed, 11 Nov 2026 10:35:22 +0100 Subject: [PATCH 1/4] Compat: Gridap 0.20 (compatibility check) Gridap was pinned to =0.19.9 because the engine reads its internal expression tree. This branch lifts the pin to 0.20 to see what the engine tests say. The HyperFEM job cannot resolve until HyperFEM also admits Gridap 0.20. --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 251eabb..facab5d 100644 --- a/Project.toml +++ b/Project.toml @@ -14,7 +14,7 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" [compat] ForwardDiff = "1.3" -Gridap = "=0.19.9" +Gridap = "0.20" Krylov = "0.10" LinearAlgebra = "1.12" RuntimeGeneratedFunctions = "0.5" From 8f7ced36b8ddb87eddb67a587cd4cc3222d649f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesus=20Mart=C3=ADnez=20Frutos?= <35522233+jmartfrut@users.noreply.github.com> Date: Wed, 11 Nov 2026 10:49:46 +0100 Subject: [PATCH 2/4] H(div): read the Piola cell basis of Gridap 0.20 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gridap 0.20 builds the cell basis of a div-conforming space as linear_combination(M, PiolaMap(φ̂, Jt)) with the sign flips in a Diagonal change of basis M per cell, instead of PiolaMap(φ̂, Jt, |det Jt|, sign_flip). piola_basis, PiolaTable and cell_signs read both layouts (0.19 and 0.20). Spaces with scaled dofs (scale_dof) are rejected with a clear error. --- src/tables.jl | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/tables.jl b/src/tables.jl index 4a5d17e..1797f3c 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -185,15 +185,28 @@ wcols(::FieldTable{D}) where {D} = 1 + D + nhess(D) # Gridap builds the cell basis of a div-conforming space as the contravariant Piola map of the # reference shape functions with a sign flip on the dofs of the facets whose cell is the slave: # φ_j(x) = σ_j / |det Jt| · φ̂_j(ξ) ⋅ Jt, ∇φ_j = Jt⁻¹ ⋅ (σ_j / |det Jt| · ∇̂φ̂_j ⋅ Jt) -# (Gridap 0.19 takes the Jacobian as constant in the gradient; the formula above is reproduced +# (Gridap takes the Jacobian as constant in the gradient; the formula above is reproduced # exactly, also on non-affine cells, so that kernel and Gridap agree to round-off). The reference # functions are the same in every cell; the signs are a dense ±1 per (dof, cell). +# +# The lazy array that holds this basis differs between Gridap versions: +# - 0.19: `lazy_map(Broadcasting(Operation(ContraVariantPiolaMap())), φ̂, Jt, |det Jt|, sign_flip)`, the +# sign flip a Bool per cell dof; +# - 0.20: `lazy_map(linear_combination, M, lazy_map(Broadcasting(Operation(ContraVariantPiolaMap())), φ̂, Jt))`, +# the change of basis `M` a `Diagonal` of ±1 per cell (`NormalSignMap`). + +"A lazy array of contravariant Piola maps of reference fields." +is_piola_map(a) = a isa LazyArray && a.maps isa Fill && a.maps.value isa Broadcasting{<:Operation{ContraVariantPiolaMap}} -"The cell basis of a div-conforming space (the lazy Piola-map array), or `nothing`." +"The cell basis of a div-conforming space (the lazy Piola-map array, with its signs), or `nothing`." function piola_basis(space::SingleFieldFESpace) a = get_data(get_fe_basis(space)) - (a isa LazyArray && a.maps isa Fill && a.maps.value isa Broadcasting{<:Operation{ContraVariantPiolaMap}}) ? a : nothing + is_piola_map(a) && return a # Gridap 0.19 + (a isa LazyArray && a.maps isa Fill && a.maps.value === linear_combination && + length(a.args) == 2 && is_piola_map(a.args[2])) ? a : nothing # Gridap 0.20 end +"The reference shape functions (per cell) of the basis returned by `piola_basis`." +piola_reference(a) = is_piola_map(a) ? a.args[1] : a.args[2].args[1] is_piola(space::SingleFieldFESpace) = piola_basis(space) !== nothing """ @@ -214,7 +227,7 @@ function PiolaTable(space::SingleFieldFESpace, xq::AbstractVector, D::Int) a === nothing && error("HyperFEMKernels: not a Piola-mapped space") length(first(xq)) == D || error("HyperFEMKernels: H(div) spaces on a manifold triangulation (cells of lower dimension than the space) are not supported") - ref = uniform_value(a.args[1], "the reference H(div) basis") + ref = uniform_value(piola_reference(a), "the reference H(div) basis") vals = evaluate(ref, xq) # np × nd, VectorValue{D} grads = evaluate(Broadcasting(∇)(ref), xq) # np × nd, TensorValue{D,D} np, nd = size(vals) @@ -239,15 +252,24 @@ sign flips of Gridap's cell basis); a 0 × 0 matrix for other spaces (no signs). function cell_signs(space::SingleFieldFESpace, nd::Int) a = piola_basis(space) a === nothing && return Matrix{Int8}(undef, 0, 0) - flips = a.args[4].args[1] # Bool per cell dof (lazy, per cell) + # Gridap 0.19: Bool per cell dof (true = flipped); Gridap 0.20: Diagonal of ±1 per cell + old = is_piola_map(a) + flips = old ? a.args[4].args[1] : a.args[1] nc = length(flips) s = Matrix{Int8}(undef, nd, nc) cache = array_cache(flips) for cell in 1:nc f = getindex!(cache, flips, cell) - length(f) == nd || error("HyperFEMKernels: cells with different numbers of dofs are not supported yet") + old || f isa Diagonal || error("HyperFEMKernels: unexpected change of basis $(typeof(f)) in an H(div) space") + v = old ? f : f.diag + length(v) == nd || error("HyperFEMKernels: cells with different numbers of dofs are not supported yet") for j in 1:nd - s[j, cell] = f[j] ? Int8(-1) : Int8(1) + if old + s[j, cell] = v[j] ? Int8(-1) : Int8(1) + else + abs(v[j]) == 1 || error("HyperFEMKernels: H(div) spaces with scaled dofs (`scale_dof`) are not supported") + s[j, cell] = Int8(v[j]) + end end end s From 7ed6996063dc6fe6a353702a59fe4b86dadbb9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesus=20Mart=C3=ADnez=20Frutos?= <35522233+jmartfrut@users.noreply.github.com> Date: Wed, 11 Nov 2026 11:22:40 +0100 Subject: [PATCH 3/4] Gridap 0.20 only: drop the 0.19 layout of the Piola basis; README, changelog, notes piola_basis and cell_signs read only the Gridap 0.20 layout. README, CHANGELOG, the CompatHelper note and the solver benchmark environment state Gridap 0.20. --- .github/workflows/CompatHelper.yml | 3 ++- CHANGELOG.md | 3 +++ README.md | 4 +-- benchmark/solvers/Project.toml | 2 +- benchmark/solvers/setup.jl | 2 +- docs/README.md | 4 +-- src/tables.jl | 42 +++++++++++------------------- 7 files changed, 26 insertions(+), 34 deletions(-) diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index e8fda92..fc452d0 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -17,7 +17,8 @@ jobs: run: julia -e 'import Pkg; Pkg.add("CompatHelper")' - name: Run CompatHelper # Opens a pull request when a dependency releases a version outside [compat]. - # Gridap is pinned (=0.19.9) on purpose; its PRs can be closed until HyperFEM upgrades. + # Gridap is restricted to one minor series (0.20) on purpose: the engine reads Gridap's internals, + # so a PR for a new Gridap series needs the full test suite before it is merged. run: julia -e 'import CompatHelper; CompatHelper.main()' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 031e461..2755b9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ - `KernelFunctional` evaluates skeleton integrals with the facet kernels (energies of jump penalties such as `½ ∫ c ⟦∇u⟧:⟦∇u⟧ dΛ` and `½ ∫ c ⟦∇∇u⟧:⟦∇∇u⟧ dΛ`, used by the Armijo line search; `:min`/`:max` over the facet points). Before, those terms were summed by the Gridap fallback. +- **Gridap 0.20** (breaking: Gridap 0.19 is no longer supported; `[compat] Gridap = "0.20"`). H(div) + (Raviart–Thomas) spaces are read from the cell basis of Gridap 0.20, where the sign flips are a diagonal + change of basis per cell. The rest of the engine needed no change. ## 0.2.2 diff --git a/README.md b/README.md index e3f3735..c25da65 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Until the package appears in the General registry, add it from the repository in pkg> add https://github.com/MultiSimOLab/HyperFEMKernels.jl ``` -It requires Julia ≥ 1.12 and Gridap 0.19.9 (the same version as HyperFEM). +It requires Julia ≥ 1.12 and Gridap 0.20. ## Usage @@ -326,7 +326,7 @@ run with both backends as part of the test suite. are not covered yet (use `LaggedAssembled` with HyperFEM's block preconditioners). - Shared-memory parallelism (threads). Distributed runs with GridapDistributed and GPU execution are design targets: kernels are allocation-free and states use a GPU-friendly layout. -- Gridap is pinned to 0.19.9, because the engine reads Gridap's internal expression tree. +- Gridap is restricted to the 0.20 series, because the engine reads Gridap's internal expression tree. ## Testing diff --git a/benchmark/solvers/Project.toml b/benchmark/solvers/Project.toml index 79ad61a..fcf08fe 100644 --- a/benchmark/solvers/Project.toml +++ b/benchmark/solvers/Project.toml @@ -18,4 +18,4 @@ HyperFEM = {path = "../../../HyperFEM.jl"} HyperFEMKernels = {path = "../.."} [compat] -Gridap = "=0.19.9" +Gridap = "0.20" diff --git a/benchmark/solvers/setup.jl b/benchmark/solvers/setup.jl index 1e9b214..e40bb4a 100644 --- a/benchmark/solvers/setup.jl +++ b/benchmark/solvers/setup.jl @@ -9,6 +9,6 @@ root = normpath(joinpath(@__DIR__, "..", "..")) Pkg.develop([PackageSpec(path=root), PackageSpec(path=joinpath(root, "..", "HyperFEM.jl"))]) Pkg.add(["Gridap", "GridapGmsh", "GridapPETSc", "GridapSolvers", "SparseMatricesCSR", "SparseArrays", "LinearAlgebra", "Printf", "Dates", "DelimitedFiles"]) -Pkg.compat("Gridap", "=0.19.9") +Pkg.compat("Gridap", "0.20") Pkg.instantiate() Pkg.status() diff --git a/docs/README.md b/docs/README.md index e962e25..d70e563 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,5 +20,5 @@ with [Registrator](https://github.com/JuliaRegistries/Registrator.jl); GitHub re Requirements on the repository: the JuliaRegistrator and TagBot GitHub apps installed (organisation settings → GitHub Apps), the repository public, `LICENSE` in the root. `CompatHelper.yml` opens pull requests when dependencies release versions outside `[compat]`; -Gridap is pinned on purpose (`=0.19.9`, HyperFEM's version), so its pull requests are closed until -HyperFEM upgrades. +Gridap is restricted to one minor series on purpose (`0.20`: the engine reads Gridap's internals), so a pull +request for a new Gridap series needs the full test suite before it is merged. diff --git a/src/tables.jl b/src/tables.jl index 1797f3c..ca3ec42 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -189,24 +189,19 @@ wcols(::FieldTable{D}) where {D} = 1 + D + nhess(D) # exactly, also on non-affine cells, so that kernel and Gridap agree to round-off). The reference # functions are the same in every cell; the signs are a dense ±1 per (dof, cell). # -# The lazy array that holds this basis differs between Gridap versions: -# - 0.19: `lazy_map(Broadcasting(Operation(ContraVariantPiolaMap())), φ̂, Jt, |det Jt|, sign_flip)`, the -# sign flip a Bool per cell dof; -# - 0.20: `lazy_map(linear_combination, M, lazy_map(Broadcasting(Operation(ContraVariantPiolaMap())), φ̂, Jt))`, -# the change of basis `M` a `Diagonal` of ±1 per cell (`NormalSignMap`). +# The lazy array that holds this basis (Gridap ≥ 0.20) is +# lazy_map(linear_combination, M, lazy_map(Broadcasting(Operation(ContraVariantPiolaMap())), φ̂, Jt)) +# with the change of basis `M` a `Diagonal` of ±1 per cell (`NormalSignMap`). "A lazy array of contravariant Piola maps of reference fields." is_piola_map(a) = a isa LazyArray && a.maps isa Fill && a.maps.value isa Broadcasting{<:Operation{ContraVariantPiolaMap}} -"The cell basis of a div-conforming space (the lazy Piola-map array, with its signs), or `nothing`." +"The cell basis of a div-conforming space (the signed lazy Piola-map array), or `nothing`." function piola_basis(space::SingleFieldFESpace) a = get_data(get_fe_basis(space)) - is_piola_map(a) && return a # Gridap 0.19 (a isa LazyArray && a.maps isa Fill && a.maps.value === linear_combination && - length(a.args) == 2 && is_piola_map(a.args[2])) ? a : nothing # Gridap 0.20 + length(a.args) == 2 && is_piola_map(a.args[2])) ? a : nothing end -"The reference shape functions (per cell) of the basis returned by `piola_basis`." -piola_reference(a) = is_piola_map(a) ? a.args[1] : a.args[2].args[1] is_piola(space::SingleFieldFESpace) = piola_basis(space) !== nothing """ @@ -227,7 +222,7 @@ function PiolaTable(space::SingleFieldFESpace, xq::AbstractVector, D::Int) a === nothing && error("HyperFEMKernels: not a Piola-mapped space") length(first(xq)) == D || error("HyperFEMKernels: H(div) spaces on a manifold triangulation (cells of lower dimension than the space) are not supported") - ref = uniform_value(piola_reference(a), "the reference H(div) basis") + ref = uniform_value(a.args[2].args[1], "the reference H(div) basis") vals = evaluate(ref, xq) # np × nd, VectorValue{D} grads = evaluate(Broadcasting(∇)(ref), xq) # np × nd, TensorValue{D,D} np, nd = size(vals) @@ -247,29 +242,22 @@ field_table(space::SingleFieldFESpace, xq::AbstractVector, D::Int) = cell_signs(space, nd) -> Matrix{Int8} (nd × ncells) Sign of every cell dof of an H(div) space (−1 on the facet dofs of the slave cell, read from the -sign flips of Gridap's cell basis); a 0 × 0 matrix for other spaces (no signs). +sign changes of Gridap's cell basis); a 0 × 0 matrix for other spaces (no signs). """ function cell_signs(space::SingleFieldFESpace, nd::Int) a = piola_basis(space) a === nothing && return Matrix{Int8}(undef, 0, 0) - # Gridap 0.19: Bool per cell dof (true = flipped); Gridap 0.20: Diagonal of ±1 per cell - old = is_piola_map(a) - flips = old ? a.args[4].args[1] : a.args[1] - nc = length(flips) + changes = a.args[1] # Diagonal of ±1 per cell (lazy) + nc = length(changes) s = Matrix{Int8}(undef, nd, nc) - cache = array_cache(flips) + cache = array_cache(changes) for cell in 1:nc - f = getindex!(cache, flips, cell) - old || f isa Diagonal || error("HyperFEMKernels: unexpected change of basis $(typeof(f)) in an H(div) space") - v = old ? f : f.diag - length(v) == nd || error("HyperFEMKernels: cells with different numbers of dofs are not supported yet") + M = getindex!(cache, changes, cell) + M isa Diagonal || error("HyperFEMKernels: unexpected change of basis $(typeof(M)) in an H(div) space") + length(M.diag) == nd || error("HyperFEMKernels: cells with different numbers of dofs are not supported yet") for j in 1:nd - if old - s[j, cell] = v[j] ? Int8(-1) : Int8(1) - else - abs(v[j]) == 1 || error("HyperFEMKernels: H(div) spaces with scaled dofs (`scale_dof`) are not supported") - s[j, cell] = Int8(v[j]) - end + abs(M.diag[j]) == 1 || error("HyperFEMKernels: H(div) spaces with scaled dofs (`scale_dof`) are not supported") + s[j, cell] = Int8(M.diag[j]) end end s From f8d014524e22457267d670f89da68b779f247b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesus=20Mart=C3=ADnez=20Frutos?= <35522233+jmartfrut@users.noreply.github.com> Date: Wed, 11 Nov 2026 13:23:54 +0100 Subject: [PATCH 4/4] CHANGELOG: Gridap 0.20 entry under Unreleased (after the 0.2.3 release) --- CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2755b9c..56df93c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +- **Gridap 0.20** (breaking: Gridap 0.19 is no longer supported; `[compat] Gridap = "0.20"`). H(div) + (Raviart–Thomas) spaces are read from the cell basis of Gridap 0.20, where the sign flips are a diagonal + change of basis per cell. The rest of the engine needed no change. + ## 0.2.3 - Second derivatives (`∇∇`, `Δ`) on skeleton integrals: `jump(∇∇(u))`, `mean(∇∇(u))`, `mean(Δ(u))`, … of @@ -10,9 +16,6 @@ - `KernelFunctional` evaluates skeleton integrals with the facet kernels (energies of jump penalties such as `½ ∫ c ⟦∇u⟧:⟦∇u⟧ dΛ` and `½ ∫ c ⟦∇∇u⟧:⟦∇∇u⟧ dΛ`, used by the Armijo line search; `:min`/`:max` over the facet points). Before, those terms were summed by the Gridap fallback. -- **Gridap 0.20** (breaking: Gridap 0.19 is no longer supported; `[compat] Gridap = "0.20"`). H(div) - (Raviart–Thomas) spaces are read from the cell basis of Gridap 0.20, where the sign flips are a diagonal - change of basis per cell. The rest of the engine needed no change. ## 0.2.2