Operator example: trace
This page illustrates the "linear operator" feature of the Julia package LinearMapsAA
.
This page comes from a single Julia file: 02-trace.jl
.
You can access the source code for such Julia documentation using the 'Edit on GitHub' link in the top right. You can view the corresponding notebook in nbviewer here: 02-trace.ipynb
, or open it in binder here: 02-trace.ipynb
.
Setup
Packages needed here.
using LinearMapsAA
using LinearAlgebra: tr, I
using InteractiveUtils: versioninfo
Overview
The "operator" aspect of this package may seem unfamiliar to some readers who are used to thinking in terms of matrices and vectors, so this page describes a simple example: the matrix trace operation. The trace of a $N × N$ matrix is the sum of its $N$ diagonal elements. We tend to think of this a function, and indeed it is the tr
function in the LinearAlgebra
package. But it is a linear function so we can represent it as a linear operator $𝒜$ that maps a $N × N$ matrix into its trace. In other words, $𝒜 : \mathbb{C}^{N × N} \mapsto \mathbb{C}$ is defined by $𝒜 X = \mathrm{tr}(X)$. Note that the product $𝒜 X$ is not a "matrix vector" product; it is a linear operator acting on the matrix $X$.
(Note that we use a fancy unicode character $𝒜$ here just as a reminder that it is an operator; in practical code we usually just use A
.)
The LinearMapsAA
package can represent such an operator easily. Here is the definition for $N = 5$:
N = 5
forw(X) = [tr(X)] # forward mapping function
𝒜 = LinearMapAA(forw, (1, N*N); idim = (N,N), odim = (1,))
LinearMapAO: 1 × 25 odim=(1,) idim=(5, 5) T=Float32 Do=1 Di=2
map = 1×25 LinearMaps.FunctionMap{Float32,false}(#16; issymmetric=false, ishermitian=false, isposdef=false)
The idim
argument specifies that the input is a matrix of size N × N
and the odim
argument specifies that the output is vector of size (1,)
.
One subtlety with this particular didactic example is that the ordinary trace yields a scalar, but LinearMaps.jl
is (understandably) designed exclusively for mapping vectors to vectors, so we use [tr(X)]
above so that the output is a 1-element Vector
. This behavior is consistent with what happens when one multiplies a 1 × N
matrix with a vector in $\mathbb{C}^N$.
Here is a verification that applying this operator to a matrix produces the correct result:
X = ones(5)*(1:5)'
𝒜 * X, tr(X), (N*(N+1))÷2
([15.0], 15.0, 15)
Although $𝒜$ here is not a matrix, we can convert it to a matrix (at least when $N$ is sufficiently small) to perhaps understand it better:
A = Matrix(𝒜)
A = Int8.(A) # just for nicer display
1×25 Matrix{Int8}:
1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1
The pattern of 0 and 1 elements is more obvious when reshaped:
reshape(A, N, N)
5×5 Matrix{Int8}:
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1
Adjoint
Although this is largely a didactic example, there are optimization problems with trace constraints of the form $𝒜 X = b$. To solve such problems, often one would also need the adjoint of the operator $𝒜$.
Mathematically, and adjoint is a generalization of the (Hermitian) transpose of a matrix. For a (bounded) linear mapping $𝒜$ between inner product space $𝒳$ with inner product $⟨ \cdot, \cdot \rangle_𝒳$ and inner product space $𝒴$ with inner product $⟨ \cdot, \cdot \rangle_𝒴,$ the adjoint of $𝒜$, denoted $𝒜'$, is the unique bound linear mapping that satisfies $⟨ 𝒜 x, y \rangle_𝒴 = ⟨ x, 𝒜' y \rangle_𝒳$ for all $x ∈ 𝒳$ and $y ∈ 𝒴$.
Here, let $𝒳$ denote the vector space of $N × N$ matrices with the Frobenius inner product for matrices: $⟨ A, B \rangle_𝒳 = \mathrm{tr}(A'B)$. Let $𝒴$ simply be $\mathbb{C}^1$ with the usual inner product $⟨ x, y \rangle_𝒴 = x_1^* y_1$.
With those definitions, one can verify that the adjoint of $𝒜$ is the mapping $𝒜' c = c_1 \mathbf{I}_N$, for $c ∈ \mathbb{C}^1$, where $\mathbf{I}_N$ denotes the $N × N$ identity matrix.
Here is the LinearMapAO
that includes the adjoint:
back(y) = y[1] * I(N) # remember `y` is a 1-vector
𝒜 = LinearMapAA(forw, back, (1, N*N); idim = (N,N), odim = (1,))
LinearMapAO: 1 × 25 odim=(1,) idim=(5, 5) T=Float32 Do=1 Di=2
map = 1×25 LinearMaps.FunctionMap{Float32,false}(#6, #8; issymmetric=false, ishermitian=false, isposdef=false)
Here is a verification that the adjoint is correct (very important!):
@assert Matrix(𝒜)' == Matrix(𝒜')
Int8.(Matrix(𝒜'))
25×1 Matrix{Int8}:
1
0
0
0
0
0
1
0
0
0
⋮
0
0
1
0
0
0
0
0
1
Reproducibility
This page was generated with the following version of Julia:
using InteractiveUtils: versioninfo
io = IOBuffer(); versioninfo(io); split(String(take!(io)), '\n')
12-element Vector{SubString{String}}:
"Julia Version 1.10.3"
"Commit 0b4590a5507 (2024-04-30 10:59 UTC)"
"Build Info:"
" Official https://julialang.org/ release"
"Platform Info:"
" OS: Linux (x86_64-linux-gnu)"
" CPU: 4 × AMD EPYC 7763 64-Core Processor"
" WORD_SIZE: 64"
" LIBM: libopenlibm"
" LLVM: libLLVM-15.0.7 (ORCJIT, znver3)"
"Threads: 1 default, 0 interactive, 1 GC (on 4 virtual cores)"
""
And with the following package versions
import Pkg; Pkg.status()
Status `~/work/LinearMapsAA.jl/LinearMapsAA.jl/docs/Project.toml`
[6e4b80f9] BenchmarkTools v1.5.0
[e30172f5] Documenter v1.4.1
[7a1cc6ca] FFTW v1.8.0
[71a99df6] ImagePhantoms v0.8.0
[7031d0ef] LazyGrids v1.0.0
[599c1a8e] LinearMapsAA v0.12.0 `~/work/LinearMapsAA.jl/LinearMapsAA.jl`
[98b081ad] Literate v2.18.0
[170b2178] MIRTjim v0.24.0
[b77e0a4c] InteractiveUtils
[37e2e46d] LinearAlgebra
This page was generated using Literate.jl.