|
| 1 | +# For unparametrized destination types |
| 2 | +generate_copyto!_signature(dest, dest_type::Symbol, Msig) = |
| 3 | + :(Base.copyto!($(dest)::$(dest_type), applied_obj::$(Msig))) |
| 4 | + |
| 5 | +# For parametrized destination types |
| 6 | +function generate_copyto!_signature(dest, dest_type::Expr, Msig) |
| 7 | + dest_type.head == :curly || |
| 8 | + throw(ArgumentError("Invalid destination specification $(dest)::$(dest_type)")) |
| 9 | + :(Base.copyto!($(dest)::$(dest_type), applied_obj::$(Msig)) where {$(dest_type.args[2:end]...)}) |
| 10 | +end |
| 11 | + |
| 12 | +function generate_copyto!(body, factor_names, Msig) |
| 13 | + body.head == :(->) || |
| 14 | + throw(ArgumentError("Invalid copyto! specification")) |
| 15 | + body.args[1].head == :(::) || |
| 16 | + throw(ArgumentError("Invalid destination specification $(body.args[1])")) |
| 17 | + (dest,dest_type) = body.args[1].args |
| 18 | + copyto!_signature = generate_copyto!_signature(dest, dest_type, Msig) |
| 19 | + f_body = quote |
| 20 | + axes($dest) == axes(applied_obj) || throw(DimensionMismatch("axes must be same")) |
| 21 | + $(factor_names) = applied_obj.args |
| 22 | + $(body.args[2].args...) |
| 23 | + $(dest) |
| 24 | + end |
| 25 | + Expr(:function, copyto!_signature, f_body) |
| 26 | +end |
| 27 | + |
| 28 | +""" |
| 29 | + @materialize function op(args...) |
| 30 | +
|
| 31 | +This macro simplifies the setup of a few functions necessary for the |
| 32 | +materialization of [`Applied`](@ref) objects: |
| 33 | +
|
| 34 | +- `ApplyStyle`, used to ensure dispatch of the applied object to the |
| 35 | + routines below |
| 36 | +
|
| 37 | +- `copyto!(dest::DestType, applied_obj::Applied{...,op})` performs the |
| 38 | + actual materialization of `applied_obj` into the destination object |
| 39 | + which has been generated by |
| 40 | +
|
| 41 | +- `similar` which usually returns a suitable matrix |
| 42 | +
|
| 43 | +- `materialize` which makes use of the above functions |
| 44 | +
|
| 45 | +# Example |
| 46 | +
|
| 47 | +```julia |
| 48 | +@materialize function *(Ac::MyAdjointBasis, |
| 49 | + O::MyOperator, |
| 50 | + B::MyBasis) |
| 51 | + MyApplyStyle # An instance of this type will be returned by ApplyStyle |
| 52 | + T -> begin # generates similar |
| 53 | + A = parent(Ac) |
| 54 | + parent(A) == parent(B) || |
| 55 | + throw(ArgumentError("Incompatible bases")) |
| 56 | +
|
| 57 | + # There may be different matrices best representing different |
| 58 | + # situations: |
| 59 | + if ... |
| 60 | + Diagonal(Vector{T}(undef, size(B,1))) |
| 61 | + else |
| 62 | + Tridiagonal(Vector{T}(undef, size(B,1)-1), |
| 63 | + Vector{T}(undef, size(B,1)), |
| 64 | + Vector{T}(undef, size(B,1)-1)) |
| 65 | + end |
| 66 | + end |
| 67 | + dest::Diagonal{T} -> begin # generate copyto!(dest::Diagonal{T}, ...) where T |
| 68 | + dest.diag .= 1 |
| 69 | + end |
| 70 | + dest::Tridiagonal{T} -> begin # generate copyto!(dest::Tridiagonal{T}, ...) where T |
| 71 | + dest.dl .= -2 |
| 72 | + dest.ev .= 1 |
| 73 | + dest.du .= 3 |
| 74 | + end |
| 75 | +end |
| 76 | +``` |
| 77 | +""" |
| 78 | +macro materialize(expr) |
| 79 | + expr.head == :function || expr.head == :(=) || error("Must start with a function") |
| 80 | + @assert expr.args[1].head == :call |
| 81 | + op = expr.args[1].args[1] |
| 82 | + |
| 83 | + bodies = filter(e -> !(e isa LineNumberNode), expr.args[2].args) |
| 84 | + length(bodies) < 3 && |
| 85 | + throw(ArgumentError("At least three blocks required (ApplyStyle, similar, and at least one copyto!)")) |
| 86 | + |
| 87 | + factor_types = :(<:Tuple{}) |
| 88 | + factor_names = :(()) |
| 89 | + apply_style = first(bodies) |
| 90 | + apply_style_fun = :(LazyArrays.ApplyStyle(::typeof($op)) = $(apply_style)()) |
| 91 | + |
| 92 | + # Generate Applied signature |
| 93 | + for arg in expr.args[1].args[2:end] |
| 94 | + arg isa Expr && arg.head == :(::) || |
| 95 | + throw(ArgumentError("Invalid argument specification $(arg)")) |
| 96 | + arg_name, arg_typ = arg.args |
| 97 | + push!(factor_types.args[1].args, :(<:$(arg_typ))) |
| 98 | + push!(factor_names.args, arg_name) |
| 99 | + push!(apply_style_fun.args[1].args, :(::Type{<:$(arg_typ)})) |
| 100 | + end |
| 101 | + Msig = :(LazyArrays.Applied{$(apply_style), typeof($op), $(factor_types)}) |
| 102 | + |
| 103 | + sim_body = bodies[2] |
| 104 | + sim_body.head == :(->) || |
| 105 | + throw(ArgumentError("Invalid similar specification")) |
| 106 | + T = first(sim_body.args) |
| 107 | + |
| 108 | + copytos! = map(body -> generate_copyto!(body, factor_names, Msig), bodies[3:end]) |
| 109 | + |
| 110 | + f = quote |
| 111 | + $(apply_style_fun) |
| 112 | + |
| 113 | + function Base.similar(applied_obj::$Msig, ::Type{$T}=eltype(applied_obj)) where $T |
| 114 | + $(factor_names) = applied_obj.args |
| 115 | + $(sim_body.args[2]) |
| 116 | + end |
| 117 | + |
| 118 | + $(copytos!...) |
| 119 | + |
| 120 | + LazyArrays.materialize(applied_obj::$Msig) = |
| 121 | + copyto!(similar(applied_obj, eltype(applied_obj)), applied_obj) |
| 122 | + end |
| 123 | + esc(f) |
| 124 | +end |
0 commit comments