Description
Would it make sense to have a way to invalidate the cache for indexing a LazyArray
? So that essentially this check in getindex!(...)
Gridap.jl/src/Arrays/LazyArrays.jl
Line 199 in 55a399a
is skipped and the
getindex!(...)
call always reevaluates the cache entries (even if the same item was indexed before).
As a MWE I would have liked something like this to work on the first getindex!(...)
call:
using Gridap
model = CartesianDiscreteModel((0, 1), 10)
V = TestFESpace(model, ReferenceFE(lagrangian, Float64, 1), conformity=:H1)
Ω = Triangulation(model)
dx = Measure(Ω, 4)
α = Ref(1.0) # I would like to change this value and reevaluate the quadrature without having to build the whole lazy expression again.
g(x) = exp(-α[] * x[1])
b(v) = ∫(g * v)dx
v = get_fe_basis(V)
cell_vec_lazy = b(v)[Ω]
cell_vec_cache = Gridap.array_cache(cell_vec_lazy)
getindex!(cell_vec_cache, cell_vec_lazy, 1)[1]
Gridap.assemble_vector(b, V)[1] # this should return the same value as the getindex!(...) before
# now change α
α[] = 2.0
getindex!(cell_vec_cache, cell_vec_lazy, 1)[1] # returns the same value as before (where α still was 1)
getindex!(cell_vec_cache, cell_vec_lazy, 2)[1]; # this is a workaround to invalidate the cache (simply index into another cell)
getindex!(cell_vec_cache, cell_vec_lazy, 1)[1] # now this returns the value where α = 2
The cache invalidation (here by indexing another cell) could be more explicit and skip the reevaluation of the cache entries. For me implementing something along the lines of this was enough (but I only understand the type-structure of the cache to some extent):
invalidate_cache!(::Union{Nothing, Gridap.Arrays.CachedVector, Gridap.Arrays.CachedMatrix}) = nothing
function invalidate_cache!(cache::Tuple{T1}) where T1
invalidate_cache!(cache[1])
end
function invalidate_cache!(cache::Tuple{T1, T2}) where {T1, T2}
invalidate_cache!(cache[1])
invalidate_cache!(cache[2])
end
function invalidate_cache!(cache::Tuple{T1, T2, T3}) where {T1, T2, T3}
invalidate_cache!(cache[1])
invalidate_cache!(cache[2])
invalidate_cache!(cache[3])
end
function invalidate_cache!(cache::Tuple{T, <:Gridap.Arrays.IndexItemPair}) where T
invalidate_cache!(cache[1])
cache[2].index = -1
end
Alternatively, getindex!()
could have an optional argument always_reevaluate=false
that switches off the index check.
My use case
I stumbled upon this while I was experimenting with a probably rather uncommon use case for a FE-library. I wanted to assemble a matrixa(u, v) = ∫( ∫( k(x₁, x₂) * u(x₁) )dx₁ * v(x₂) ) dx₂
However I thought that I could reuse the quadrature rules, basis function definitions and grid-definitions in Gridap.jl.
Using only high-level interfaces, I could implement the following:
k(x1, x2) = exp(-50.0*(x1-x2)^2)
model = CartesianDiscreteModel((0, 1), 10)
V = TestFESpace(model, ReferenceFE(lagrangian, Float64, 1), conformity=:H1)
Ω = Triangulation(model)
dx = Measure(Ω, 4)
K = zeros(num_free_dofs(V), num_free_dofs(V))
u = FEFunction(V, zeros(num_free_dofs(V)))
for i in 1:num_free_dofs(V)
u.free_values[i] = 1.0
K[i, :] = Gridap.assemble_vector(
v -> ∫(
(y -> sum(∫((x -> k(y[1], x[1])) * u)dx))
* v)dx
, V)
u.free_values[i] = 0.0
end
However, the inner quadrature always evaluates the full domain whereas only the cells in the support of the basis u[i] should be integrated. For larger N
that quickly becomes prohibitively slow.
Leveraging the low-level APIs of Gridap, I found the getindex!
to be essentially the function I needed for cellwise evaluation of the quadrature rules, but I have to change the variable of the outer integration without having to rebuild the inner lazy evaluation tree.