I have a problem that comes up fairly often that I do not really have a satisfying solution to at the moment.
Say I have a container type like the one below, representing many “versions” of a real number
struct Baz{T} <: Real
a::Vector{T}
end
and that I can have arrays of such containers, for instance the following complex version
R = [Complex(Baz(randn(3)), Baz(randn(3))) for i in 1:2, j in 1:2]
julia> typeof(R)
Matrix{Complex{Baz{Float64}}}
When performing some operations on R
, I would essentially like to permute the memory layout of R
by unpacking the elements of Baz.a
one by one so that I get r
illustrated by the pseudo code below, perform some operation on r
and then repackage the result back into the structure Baz
again.
function eigvals(R::Matrix{Complex{Baz{T}}}) where T
# Allocate E
for all k
r[i,j] = Complex(R[i,j].re.a[k], R[i,j].im.a[k]) # "unpack R into r"
e = eigvals(r) # operate on r
E[i].re.a[k] = e[i].re # repackage result into Baz
E[i].im.a[k] = e[i].im
end
E
end
So far, this can be done manually without too much of a problem, but there might be variations of the problem, for instance, if instead of eigvals
I call svdvals
, the result is a vector of real values rather than complex, and I would like to do something like this instead:
function svdvals(R::Matrix{Complex{Baz{T}}}) where T
for all k
r[i,j] = Complex(R[i,j].re.a[k], R[i,j].im.a[k])
s = svdvals(r)
S[i].a[k] = e[i]
end
S
end
The data layout of Baz
is chosen to fit well for scalar operations, but when Baz
appears in more complicated contexts, I lack a general solution that doesn’t require customizing the implementation for each function I want to handle, i.e., eigvals, svdvals
above. I’ve been looking towards Accessors.jl which could potentially serve as a piece of the puzzle, but I can’t really find something that does exactly what I need. For completeness, below is a manual implementation of eigvals that does what I want
using LinearAlgebra
struct Baz{T} <: Real
a::Vector{T}
end
Base.show(io::IO, baz::Complex{Baz{T}}) where T = print(io, "Complex{Baz{$T}[($(baz.re.a), $(baz.im.a))")
function LinearAlgebra.eigvals(R::Matrix{Complex{Baz{T}}}) where T
K = length(R[1].re.a)
# Allocate E
E = similar(R, size(R,1))
for i in eachindex(E)
E[i] = Complex(Baz(zeros(K)), Baz(zeros(K)))
end
r = zeros(Complex{T}, size(R)...)
for k in 1:K
for j in eachindex(R)
r[j] = Complex(R[j].re.a[k], R[j].im.a[k]) # "unpack R into r"
end
e = eigvals(r) # operate on r
for i in eachindex(e)
E[i].re.a[k] = e[i].re # repackage result into Baz
E[i].im.a[k] = e[i].im
end
end
E
end
R = [Complex(Baz(randn(3)), Baz(randn(3))) for i in 1:2, j in 1:2]
E = eigvals(R)
Generally, the array R
may have any number of dimensions and produce a result of any number of dimensions, and both R
and E
may be arrays of either complex or real versions of Baz
.
Any idea that would help in implementing functions like the above would be greatly appreciated!