Skip to content

Commit 820f092

Browse files
authored
Merge pull request #72 from lkdvos/spacetypes
Refactor: abstract spacetypes to traits - Removes `InnerProductSpace` and `EuclideanSpace` as abstract types. - Adds `InnerProductStyle` as a trait based mechanism to query if a space has an inner product. - Implements `NoInnerProduct`, `HasInnerProduct` and `EuclideanInnerProduct` as traits
2 parents c5aa8cb + 61df30f commit 820f092

19 files changed

+328
-249
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
*-old
66
__pycache__
77
.ipynb*
8+
Manifest.toml

docs/src/lib/spaces.md

-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ Field
1111
VectorSpace
1212
ElementarySpace
1313
GeneralSpace
14-
InnerProductSpace
15-
EuclideanSpace
1614
CartesianSpace
1715
ComplexSpace
1816
GradedSpace

docs/src/man/sectors.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -647,9 +647,9 @@ as illustrated below.
647647
We have introduced `Sector` subtypes as a way to label the irreps or sectors in the
648648
decomposition ``V = ⨁_a ℂ^{n_a} ⊗ R_{a}``. To actually represent such spaces, we now also
649649
introduce a corresponding type `GradedSpace`, which is a subtype of
650-
`EuclideanSpace{ℂ}`, i.e.
650+
`ElementarySpace{ℂ}`, i.e.
651651
```julia
652-
struct GradedSpace{I<:Sector, D} <: EuclideanSpace{ℂ}
652+
struct GradedSpace{I<:Sector, D} <: ElementarySpace{ℂ}
653653
dims::D
654654
dual::Bool
655655
end

docs/src/man/spaces.md

+23-25
Original file line numberDiff line numberDiff line change
@@ -103,40 +103,35 @@ struct GeneralSpace{𝕜} <: ElementarySpace{𝕜}
103103
end
104104
```
105105

106-
We furthermore define the abstract type
106+
We furthermore define the trait types
107107
```julia
108-
abstract type InnerProductSpace{𝕜} <: ElementarySpace{𝕜} end
108+
abstract type InnerProductStyle end
109+
struct NoInnerProduct <: InnerProductStyle end
110+
abstract type HasInnerProduct <: InnerProductStyle end
111+
struct EuclideanProduct <: HasInnerProduct end
109112
```
110-
to contain all vector spaces `V` which have an inner product and thus a canonical mapping
111-
from `dual(V)` to `V` (for `𝕜 ⊆ ℝ`) or from `dual(V)` to `conj(V)` (otherwise). This
112-
mapping is provided by the metric, but no further support for working with metrics is
113+
to denote for a vector space `V` whether it has an inner product and thus a canonical
114+
mapping from `dual(V)` to `V` (for `𝕜 ⊆ ℝ`) or from `dual(V)` to `conj(V)` (otherwise).
115+
This mapping is provided by the metric, but no further support for working with metrics is
113116
currently implemented.
114117

115-
Finally there is
116-
```julia
117-
abstract type EuclideanSpace{𝕜} <: InnerProductSpace{𝕜} end
118-
```
119-
to contain all spaces `V` with a standard Euclidean inner product (i.e. where the metric is
120-
the identity). These spaces have the natural isomorphisms `dual(V) == V` (for `𝕜 == ℝ`)
121-
or `dual(V) == conj(V)` (for ` 𝕜 == ℂ`). In the language of the previous section on
122-
[categories](@ref s_categories), this subtype represents
123-
[dagger or unitary categories](@ref ss_adjoints), and support an `adjoint` operation. In
124-
particular, we have two concrete types
118+
The `EuclideanProduct` has the natural isomorphisms `dual(V) == V` (for `𝕜 == ℝ`)
119+
or `dual(V) == conj(V)` (for ` 𝕜 == ℂ`).
120+
In the language of the previous section on [categories](@ref s_categories), this trait represents [dagger or unitary categories](@ref ss_adjoints), and these vector spaces support an `adjoint` operation.
121+
122+
In particular, the two concrete types
125123
```julia
126-
struct CartesianSpace <: EuclideanSpace{ℝ}
124+
struct CartesianSpace <: ElementarySpace{ℝ}
127125
d::Int
128126
end
129-
struct ComplexSpace <: EuclideanSpace{ℂ}
127+
struct ComplexSpace <: ElementarySpace{ℂ}
130128
d::Int
131129
dual::Bool
132130
end
133131
```
134-
to represent the Euclidean spaces $ℝ^d$ or $ℂ^d$ without further inner structure. They can
135-
be created using the syntax `CartesianSpace(d) == ℝ^d == ℝ[d]` and
136-
`ComplexSpace(d) == ℂ^d == ℂ[d]`, or
137-
`ComplexSpace(d, true) == ComplexSpace(d; dual = true) == (ℂ^d)' == ℂ[d]'` for the
138-
dual space of the latter. Note that the brackets are required because of the precedence
139-
rules, since `d' == d` for `d::Integer`.
132+
represent the Euclidean spaces $ℝ^d$ or $ℂ^d$ without further inner structure.
133+
They can be created using the syntax `CartesianSpace(d) == ℝ^d == ℝ[d]` and `ComplexSpace(d) == ℂ^d == ℂ[d]`, or `ComplexSpace(d, true) == ComplexSpace(d; dual = true) == (ℂ^d)' == ℂ[d]'` for the dual space of the latter.
134+
Note that the brackets are required because of the precedence rules, since `d' == d` for `d::Integer`.
140135

141136
Some examples:
142137
```@repl tensorkit
@@ -149,12 +144,15 @@ dual(ℂ^5) == (ℂ^5)' == conj(ℂ^5) == ComplexSpace(5; dual = true)
149144
typeof(ℝ^3)
150145
spacetype(ℝ^3)
151146
spacetype(ℝ[])
147+
InnerProductStyle(ℝ^3)
148+
InnerProductStyle(ℂ^5)
152149
```
150+
153151
Note that `ℝ[]` and `ℂ[]` are synonyms for `CartesianSpace` and `ComplexSpace` respectively,
154152
such that yet another syntax is e.g. `ℂ[](d)`. This is not very useful in itself, and is
155153
motivated by its generalization to `GradedSpace`. We refer to the subsection on
156154
[graded spaces](@ref s_rep) on the [next page](@ref s_sectorsrepfusion) for further
157-
information about `GradedSpace`, which is another subtype of `EuclideanSpace{ℂ}`
155+
information about `GradedSpace`, which is another subtype of `ElementarySpace{ℂ}`
158156
with an inner structure corresponding to the irreducible representations of a group, or more
159157
generally, the simple objects of a fusion category.
160158

@@ -279,7 +277,7 @@ For completeness, we also export the strict comparison operators `≺` and `≻`
279277
```
280278
However, as we expect these to be less commonly used, no ASCII alternative is provided.
281279

282-
In the context of `spacetype(V) <: EuclideanSpace`, `V1 ≾ V2` implies that there exists
280+
In the context of `InnerProductStyle(V) <: EuclideanProduct`, `V1 ≾ V2` implies that there exists
283281
isometries ``W:V1 → V2`` such that ``W^† ∘ W = \mathrm{id}_{V1}``, while `V1 ≅ V2` implies
284282
that there exist unitaries ``U:V1→V2`` such that ``U^† ∘ U = \mathrm{id}_{V1}`` and
285283
``U ∘ U^† = \mathrm{id}_{V2}``.

docs/src/man/tensors.md

+37-37
Original file line numberDiff line numberDiff line change
@@ -391,17 +391,18 @@ can use the method `u = isomorphism([A::Type{<:DenseMatrix}, ] codomain, domain)
391391
will explicitly check that the domain and codomain are isomorphic, and return an error
392392
otherwise. Again, an optional first argument can be given to specify the specific type of
393393
`DenseMatrix` that is currently used to store the rather trivial data of this tensor. If
394-
`spacetype(u) <: EuclideanSpace`, the same result can be obtained with the method `u =
395-
unitary([A::Type{<:DenseMatrix}, ] codomain, domain)`. Note that reversing the domain and
396-
codomain yields the inverse morphism, which in the case of `EuclideanSpace` coincides with
397-
the adjoint morphism, i.e. `isomorphism(A, domain, codomain) == adjoint(u) == inv(u)`, where
398-
`inv` and `adjoint` will be further discussed [below](@ref ss_tensor_linalg). Finally, if
399-
two spaces `V1` and `V2` are such that `V2` can be embedded in `V1`, i.e. there exists an
400-
inclusion with a left inverse, and furthermore they represent tensor products of some
401-
`EuclideanSpace`, the function `w = isometry([A::Type{<:DenseMatrix}, ], V1, V2)` creates
402-
one specific isometric embedding, such that `adjoint(w)*w == id(V2)` and `w*adjoint(w)` is
403-
some hermitian idempotent (a.k.a. orthogonal projector) acting on `V1`. An error will be
404-
thrown if such a map cannot be constructed for the given domain and codomain.
394+
`InnerProductStyle(u) <: EuclideanProduct`, the same result can be obtained with the method
395+
`u = unitary([A::Type{<:DenseMatrix}, ] codomain, domain)`. Note that reversing the domain
396+
and codomain yields the inverse morphism, which in the case of `EuclideanProduct` coincides
397+
with the adjoint morphism, i.e. `isomorphism(A, domain, codomain) == adjoint(u) == inv(u)`,
398+
where `inv` and `adjoint` will be further discussed [below](@ref ss_tensor_linalg).
399+
Finally, if two spaces `V1` and `V2` are such that `V2` can be embedded in `V1`, i.e. there
400+
exists an inclusion with a left inverse, and furthermore they represent tensor products of
401+
some `ElementarySpace` with `EuclideanProduct`, the function
402+
`w = isometry([A::Type{<:DenseMatrix}, ], V1, V2)` creates one specific isometric
403+
embedding, such that `adjoint(w) * w == id(V2)` and `w * adjoint(w)` is some hermitian
404+
idempotent (a.k.a. orthogonal projector) acting on `V1`. An error will be thrown if such a
405+
map cannot be constructed for the given domain and codomain.
405406

406407
Let's conclude this section with some examples with `GradedSpace`.
407408
```@repl tensors
@@ -575,7 +576,7 @@ can only exist if the domain and codomain are isomorphic, which can e.g. be chec
575576
`t2`, we can use the syntax `t1\t2` or `t2/t1`. However, this syntax also accepts instances
576577
`t1` whose domain and codomain are not isomorphic, and then amounts to `pinv(t1)`, the
577578
Moore-Penrose pseudoinverse. This, however, is only really justified as minimizing the
578-
least squares problem if `spacetype(t) <: EuclideanSpace`.
579+
least squares problem if `InnerProductStyle(t) <: EuclideanProduct`.
579580

580581
`AbstractTensorMap` instances behave themselves as vectors (i.e. they are `𝕜`-linear) and
581582
so they can be multiplied by scalars and, if they live in the same space, i.e. have the same
@@ -588,30 +589,29 @@ operations, TensorKit.jl reexports a number of efficient in-place methods from
588589
`lmul!` and `rmul!` (for `y ← α*y` and `y ← y*α`, which is typically the same) and `mul!`,
589590
which can also be used for out-of-place scalar multiplication `y ← α*x`.
590591

591-
For `t::AbstractTensorMap{S}` where `S<:EuclideanSpace`, henceforth referred to as
592-
a `(Abstract)EuclideanTensorMap`, we can compute `norm(t)`, and for two such instances, the
593-
inner product `dot(t1, t2)`, provided `t1` and `t2` have the same domain and codomain.
594-
Furthermore, there is `normalize(t)` and `normalize!(t)` to return a scaled version of `t`
595-
with unit norm. These operations should also exist for `S<:InnerProductSpace`, but requires
596-
an interface for defining a custom inner product in these spaces. Currently, there is no
597-
concrete subtype of `InnerProductSpace` that is not a subtype of `EuclideanSpace`. In
598-
particular, `CartesianSpace`, `ComplexSpace` and `GradedSpace` are all subtypes
599-
of `EuclideanSpace`.
600-
601-
With instances `t::AbstractEuclideanTensorMap` there is associated an adjoint operation,
602-
given by `adjoint(t)` or simply `t'`, such that `domain(t') == codomain(t)` and
603-
`codomain(t') == domain(t)`. Note that for an instance `t::TensorMap{S,N₁,N₂}`, `t'` is
604-
simply stored in a wrapper called `AdjointTensorMap{S,N₂,N₁}`, which is another subtype of
605-
`AbstractTensorMap`. This should be mostly unvisible to the user, as all methods should work
606-
for this type as well. It can be hard to reason about the index order of `t'`, i.e. index
607-
`i` of `t` appears in `t'` at index position `j = TensorKit.adjointtensorindex(t, i)`,
608-
where the latter method is typically not necessary and hence unexported. There is also a
609-
plural `TensorKit.adjointtensorindices` to convert multiple indices at once. Note that,
610-
because the adjoint interchanges domain and codomain, we have
611-
`space(t', j) == space(t, i)'`.
592+
For `t::AbstractTensorMap{S}` where `InnerProductStyle(S) <: EuclideanProduct`, we can
593+
compute `norm(t)`, and for two such instances, the inner product `dot(t1, t2)`, provided
594+
`t1` and `t2` have the same domain and codomain. Furthermore, there is `normalize(t)` and
595+
`normalize!(t)` to return a scaled version of `t` with unit norm. These operations should
596+
also exist for `InnerProductStyle(S) <: HasInnerProduct`, but require an interface for
597+
defining a custom inner product in these spaces. Currently, there is no concrete subtype of
598+
`HasInnerProduct` that is not an `EuclideanProduct`. In particular, `CartesianSpace`,
599+
`ComplexSpace` and `GradedSpace` all have `InnerProductStyle(V) <: EuclideanProduct`.
600+
601+
With tensors that have `InnerProductStyle(t) <: EuclideanProduct` there is associated an
602+
adjoint operation, given by `adjoint(t)` or simply `t'`, such that
603+
`domain(t') == codomain(t)` and `codomain(t') == domain(t)`. Note that for an instance
604+
`t::TensorMap{S,N₁,N₂}`, `t'` is simply stored in a wrapper called
605+
`AdjointTensorMap{S,N₂,N₁}`, which is another subtype of `AbstractTensorMap`. This should
606+
be mostly unvisible to the user, as all methods should work for this type as well. It can
607+
be hard to reason about the index order of `t'`, i.e. index `i` of `t` appears in `t'` at
608+
index position `j = TensorKit.adjointtensorindex(t, i)`, where the latter method is
609+
typically not necessary and hence unexported. There is also a plural
610+
`TensorKit.adjointtensorindices` to convert multiple indices at once. Note that, because
611+
the adjoint interchanges domain and codomain, we have `space(t', j) == space(t, i)'`.
612612

613613
`AbstractTensorMap` instances can furthermore be tested for exact (`t1 == t2`) or
614-
approximate (`t1 ≈ t2`) equality, though the latter requires `norm` can be computed.
614+
approximate (`t1 ≈ t2`) equality, though the latter requires that `norm` can be computed.
615615

616616
When tensor map instances are endomorphisms, i.e. they have the same domain and codomain,
617617
there is a multiplicative identity which can be obtained as `one(t)` or `one!(t)`, where the
@@ -798,8 +798,8 @@ inverse, to split a given index into two or more indices. For a plain tensor (i.
798798
data. However, this represents only one possibility, as there is no canonically unique way
799799
to embed the tensor product of two spaces `V₁ ⊗ V₂` in a new space `V = fuse(V₁⊗V₂)`. Such a
800800
mapping can always be accompagnied by a basis transform. However, one particular choice is
801-
created by the function `isomorphism`, or for `EuclideanSpace` spaces, `unitary`. Hence, we
802-
can join or fuse two indices of a tensor by first constructing
801+
created by the function `isomorphism`, or for `EuclideanProduct` spaces, `unitary`.
802+
Hence, we can join or fuse two indices of a tensor by first constructing
803803
`u = unitary(fuse(space(t, i) ⊗ space(t, j)), space(t, i) ⊗ space(t, j))` and then
804804
contracting this map with indices `i` and `j` of `t`, as explained in the section on
805805
[contracting tensors](@ref ss_tensor_contraction). Note, however, that a typical algorithm
@@ -851,7 +851,7 @@ U, Σ, Vʰ, ϵ = tsvd(t; trunc = notrunc(), p::Real = 2,
851851
```
852852

853853
This computes a (possibly truncated) singular value decomposition of
854-
`t::TensorMap{S,N₁,N₂}` (with `S<:EuclideanSpace`), such that
854+
`t::TensorMap{S,N₁,N₂}` (with `InnerProductStyle(t)<:EuclideanProduct`), such that
855855
`norm(t - U*Σ*Vʰ) ≈ ϵ`, where `U::TensorMap{S,N₁,1}`, `S::TensorMap{S,1,1}`,
856856
`Vʰ::TensorMap{S,1,N₂}` and `ϵ::Real`. `U` is an isometry, i.e. `U'*U` approximates the
857857
identity, whereas `U*U'` is an idempotent (squares to itself). The same holds for

docs/src/man/tutorial.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,10 @@ V = ℝ^3
2828
typeof(V)
2929
V == CartesianSpace(3)
3030
supertype(CartesianSpace)
31-
supertype(EuclideanSpace)
32-
supertype(InnerProductSpace)
3331
supertype(ElementarySpace)
3432
```
3533
i.e. `ℝ^n` can also be created without Unicode using the longer syntax `CartesianSpace(n)`.
36-
It is subtype of `EuclideanSpace{ℝ}`, a space with a standard (Euclidean) inner product
34+
It is subtype of `ElementarySpace{ℝ}`, with a standard (Euclidean) inner product
3735
over the real numbers. Furthermore,
3836
```@repl tutorial
3937
W = ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4

src/TensorKit.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export Fermion, FermionParity, FermionNumber, FermionSpin
1717
export FibonacciAnyon
1818
export IsingAnyon
1919

20-
export VectorSpace, Field, ElementarySpace, InnerProductSpace, EuclideanSpace # abstract vector spaces
20+
export VectorSpace, Field, ElementarySpace # abstract vector spaces
21+
export InnerProductStyle, NoInnerProduct, HasInnerProduct, EuclideanProduct
2122
export ComplexSpace, CartesianSpace, GeneralSpace, GradedSpace # concrete spaces
2223
export ZNSpace, Z2Space, Z3Space, Z4Space, U1Space, CU1Space, SU2Space
2324
export Vect, Rep # space constructors

src/spaces/cartesianspace.jl

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
"""
2-
struct CartesianSpace <: EuclideanSpace{ℝ}
2+
struct CartesianSpace <: ElementarySpace{ℝ}
33
44
A real Euclidean space `ℝ^d`, which is therefore self-dual. `CartesianSpace` has no
55
additonal structure and is completely characterised by its dimension `d`. This is the
66
vector space that is implicitly assumed in most of matrix algebra.
77
"""
8-
struct CartesianSpace <: EuclideanSpace{ℝ}
8+
struct CartesianSpace <: ElementarySpace{ℝ}
99
d::Int
1010
end
1111
CartesianSpace(d::Integer = 0; dual = false) = CartesianSpace(Int(d))
@@ -28,6 +28,10 @@ function CartesianSpace(dims::AbstractDict; kwargs...)
2828
end
2929
end
3030

31+
InnerProductStyle(::Type{CartesianSpace}) = EuclideanProduct()
32+
33+
isdual(V::CartesianSpace) = false
34+
3135
# convenience constructor
3236
Base.getindex(::RealNumbers) = CartesianSpace
3337
Base.:^(::RealNumbers, d::Int) = CartesianSpace(d)

src/spaces/complexspace.jl

+22-14
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"""
2-
struct ComplexSpace <: EuclideanSpace{ℂ}
2+
struct ComplexSpace <: ElementarySpace{ℂ}
33
44
A standard complex vector space ℂ^d with Euclidean inner product and no additional
55
structure. It is completely characterised by its dimension and whether its the normal space
66
or its dual (which is canonically isomorphic to the conjugate space).
77
"""
8-
struct ComplexSpace <: EuclideanSpace{ℂ}
9-
d::Int
10-
dual::Bool
8+
struct ComplexSpace <: ElementarySpace{ℂ}
9+
d::Int
10+
dual::Bool
1111
end
1212
ComplexSpace(d::Integer = 0; dual = false) = ComplexSpace(Int(d), dual)
1313
function ComplexSpace(dim::Pair; dual = false)
@@ -29,6 +29,8 @@ function ComplexSpace(dims::AbstractDict; kwargs...)
2929
end
3030
end
3131

32+
InnerProductStyle(::Type{ComplexSpace}) = EuclideanProduct()
33+
3234
# convenience constructor
3335
Base.getindex(::ComplexNumbers) = ComplexSpace
3436
Base.:^(::ComplexNumbers, d::Int) = ComplexSpace(d)
@@ -42,17 +44,23 @@ Base.axes(V::ComplexSpace) = Base.OneTo(dim(V))
4244
Base.conj(V::ComplexSpace) = ComplexSpace(dim(V), !isdual(V))
4345

4446
Base.oneunit(::Type{ComplexSpace}) = ComplexSpace(1)
45-
(V1::ComplexSpace, V2::ComplexSpace) = isdual(V1) == isdual(V2) ?
46-
ComplexSpace(dim(V1)+dim(V2), isdual(V1)) :
47-
throw(SpaceMismatch("Direct sum of a vector space and its dual does not exist"))
48-
fuse(V1::ComplexSpace, V2::ComplexSpace) = ComplexSpace(V1.d*V2.d)
47+
function (V1::ComplexSpace, V2::ComplexSpace)
48+
return isdual(V1) == isdual(V2) ?
49+
ComplexSpace(dim(V1) + dim(V2), isdual(V1)) :
50+
throw(SpaceMismatch("Direct sum of a vector space and its dual does not exist"))
51+
end
52+
fuse(V1::ComplexSpace, V2::ComplexSpace) = ComplexSpace(V1.d * V2.d)
4953
flip(V::ComplexSpace) = dual(V)
5054

51-
infimum(V1::ComplexSpace, V2::ComplexSpace) = isdual(V1) == isdual(V2) ?
52-
ComplexSpace(min(dim(V1), dim(V2)), isdual(V1)) :
53-
throw(SpaceMismatch("Infimum of space and dual space does not exist"))
54-
supremum(V1::ComplexSpace, V2::ComplexSpace) = isdual(V1) == isdual(V2) ?
55-
ComplexSpace(max(dim(V1), dim(V2)), isdual(V1)) :
56-
throw(SpaceMismatch("Supremum of space and dual space does not exist"))
55+
function infimum(V1::ComplexSpace, V2::ComplexSpace)
56+
return isdual(V1) == isdual(V2) ?
57+
ComplexSpace(min(dim(V1), dim(V2)), isdual(V1)) :
58+
throw(SpaceMismatch("Infimum of space and dual space does not exist"))
59+
end
60+
function supremum(V1::ComplexSpace, V2::ComplexSpace)
61+
return isdual(V1) == isdual(V2) ?
62+
ComplexSpace(max(dim(V1), dim(V2)), isdual(V1)) :
63+
throw(SpaceMismatch("Supremum of space and dual space does not exist"))
64+
end
5765

5866
Base.show(io::IO, V::ComplexSpace) = print(io, isdual(V) ? "(ℂ^$(V.d))'" : "ℂ^$(V.d)")

0 commit comments

Comments
 (0)