-
Notifications
You must be signed in to change notification settings - Fork 6
Generalized @simplify to arbitrary amount of arguments #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov Report
@@ Coverage Diff @@
## master #37 +/- ##
==========================================
- Coverage 75.21% 74.14% -1.07%
==========================================
Files 4 4
Lines 347 352 +5
==========================================
Hits 261 261
- Misses 86 91 +5 Continue to review full report at Codecov.
|
Actually, in light of #36, I think I'd prefer an alternative interface where you'd deal with the restrictions explicitly; if you're anyway only going to work on a subinterval, it's a waste of effort to calculate the full materialization and then crop the matrix. |
I feel like the |
Sure, I was thinking about writing a Happy New Year! |
I came up with this small # For unparametrized destination types
generate_copyto!_signature(dest, dest_type::Symbol, Msig) =
:(Base.copyto!($(dest)::$(dest_type), applied_obj::$(Msig)))
# For parametrized destination types
function generate_copyto!_signature(dest, dest_type::Expr, Msig)
dest_type.head == :curly ||
throw(ArgumentError("Invalid destination specification $(dest)::$(dest_type)"))
:(Base.copyto!($(dest)::$(dest_type), applied_obj::$(Msig)) where {$(dest_type.args[2:end]...)})
end
function generate_copyto!(body, factor_names, Msig)
body.head == :(->) ||
throw(ArgumentError("Invalid copyto! specification"))
body.args[1].head == :(::) ||
throw(ArgumentError("Invalid destination specification $(body.args[1])"))
(dest,dest_type) = body.args[1].args
copyto!_signature = generate_copyto!_signature(dest, dest_type, Msig)
f_body = quote
axes($dest) == axes(applied_obj) || throw(DimensionMismatch("axes must be same"))
$(factor_names) = applied_obj.args
$(body.args[2].args...)
$(dest)
end
Expr(:function, copyto!_signature, f_body)
end
# Generate three functions: similar, copyto!, and materialize for a
# LazyArrays.Applied object.
macro materialize(expr)
expr.head == :function || expr.head == :(=) || error("Must start with a function")
@assert expr.args[1].head == :call
op = expr.args[1].args[1]
factor_types = :(<:Tuple{})
factor_names = :(())
bodies = filter(e -> !(e isa LineNumberNode), expr.args[2].args)
length(bodies) < 2 && throw(ArgumentError("At least two blocks required (one similar and at least one copyto!)"))
# Generate Applied signature
for arg in expr.args[1].args[2:end]
arg isa Expr && arg.head == :(::) ||
throw(ArgumentError("Invalid argument specification $(arg)"))
arg_name, arg_typ = arg.args
push!(factor_types.args[1].args, :(<:$(arg_typ)))
push!(factor_names.args, arg_name)
end
Msig = :(LazyArrays.Applied{<:Any, typeof($op), $(factor_types)})
sim_body = first(bodies)
sim_body.head == :(->) ||
throw(ArgumentError("Invalid similar specification"))
T = first(sim_body.args)
copytos! = map(body -> generate_copyto!(body, factor_names, Msig), bodies[2:end])
f = quote
function Base.similar(applied_obj::$Msig, ::Type{$T}=eltype(applied_obj)) where $T
$(factor_names) = applied_obj.args
$(first(bodies).args[2])
end
$(copytos!...)
materialize(applied_obj::$Msig) = copyto!(similar(applied_obj, eltype(applied_obj)), applied_obj)
end
esc(f)
end Perhaps you find a bit too "magical", but with it, I can do # These type-aliases I would like to see in ContinuumArrays.jl
const RestrictionMatrix = BandedMatrix{<:Int, <:FillArrays.Ones}
const RestrictedQuasiArray{T,N,B<:Basis} = SubQuasiArray{T,N,B}
const AdjointRestrictedQuasiArray{T,N,B<:Basis} = QuasiAdjoint{T,<:RestrictedQuasiArray{T,N,B}}
const BasisOrRestricted{B<:Basis} = Union{B,<:RestrictedQuasiArray{<:Any,<:Any,<:B}}
const AdjointBasisOrRestricted{B<:Basis} = Union{<:QuasiAdjoint{<:Any,B},<:AdjointRestrictedQuasiArray{<:Any,<:Any,<:B}}
indices(B::AbstractFiniteDifferences) = axes(B)
indices(B::RestrictedQuasiArray{<:Any,2,<:AbstractFiniteDifferences}) = B.indices
@materialize function *(Ac::AdjointBasisOrRestricted{<:AbstractFiniteDifferences},
D::QuasiDiagonal,
B::BasisOrRestricted{<:AbstractFiniteDifferences})
T -> begin # generates similar
A = parent(Ac)
parent(A) == parent(B) ||
throw(ArgumentError("Cannot multiply functions on different grids"))
Ai,Bi = last(indices(A)), last(indices(B))
if Ai == Bi # "diagonal" restriction
Diagonal(Vector{T}(undef, length(Ai)))
else
m,n = length(Ai),length(Bi)
offset = Ai[1]-Bi[1]
BandedMatrix{T}(undef, (m,n), (-offset,offset))
end
end
dest::Diagonal{T} -> begin # generate copyto!(dest::Diagonal{T}, ...) where T
dest.diag .= 1
end
dest::BandedMatrix{T} -> begin # generate copyto!(dest::BandedMatrix{T}, ...) where T
dest.data .= 3
end
end Using this, we get N = 10
ρ = 0.25
R = FiniteDifferences(N, ρ)
R̃ = R[:, 3:8]
r = axes(R, 1)
x = QuasiDiagonal(r)
For some reason, it does not play well with infix or
|
I think this is out of date |
Probably, yes. |
With this, I can also write the simplified version of the weak Laplacian:
I noticed however, in restricted bases, i.e. with
SubQuasiArray{T,N,<:Basis}
, I get a dense matrix back, instead ofBlockSkylineMatrix
, since thesimplify
framework calculate the derivative in the unrestricted basis and then indexes it afterwards. I think there are two ways to fix this:Base.copy
generated by@simplify
and let the user worry about handling the restrictions correctly (which is what I've done before; hence theFEDVROrRestricted
in the signature above)BlockSkylineMatrix
returns aBlockSkylineMatrix
and not a dense one. I guess that is only well-defined if one is "shaving" off from the outside, i.e. no non-contiguous slicing.To illustrate what's going on: