Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7d352d2
implement testsuite
borisdevos Jul 6, 2026
d9cc856
apply testsuite to current tests
borisdevos Jul 6, 2026
f469892
use TKS's testsuite for sector utility
borisdevos Jul 6, 2026
bad9878
homogenise eval_show
borisdevos Jul 6, 2026
5f5e313
Merge branch 'main' of https://github.com/Jutho/TensorKit.jl into bd/…
borisdevos Jul 6, 2026
91127f9
avoid testsuite being recognised as tests
borisdevos Jul 7, 2026
cbaf351
fix x ambiguity
borisdevos Jul 7, 2026
10f8a4e
move testsuite file
borisdevos Jul 7, 2026
eff5ab6
make separate entry points for single and double fusion tree tests
borisdevos Jul 7, 2026
77c2061
split up tensor tests
borisdevos Jul 8, 2026
e1d0f33
make diagonal tensor tests also an entry point
borisdevos Jul 8, 2026
cb8d067
convert braiding tensor properties testset to testsuite
borisdevos Jul 8, 2026
df01132
have single access function to run testsuites + changes related to mo…
borisdevos Jul 8, 2026
a7e4b4a
add factorizations to the testsuite
borisdevos Jul 8, 2026
622c960
Merge branches 'bd/testsuite' and 'main' of https://github.com/Quantu…
borisdevos Jul 8, 2026
3fbf44e
typos and exports
borisdevos Jul 9, 2026
845406d
Merge branch 'main' of https://github.com/QuantumKitHub/TensorKit.jl …
borisdevos Jul 9, 2026
b9eea56
more import silliness
borisdevos Jul 9, 2026
42d7490
workaround to fast_tests external injection
borisdevos Jul 9, 2026
1adfa95
scoping is hard
borisdevos Jul 10, 2026
1712178
update test readme
borisdevos Jul 10, 2026
c8cc738
Merge branch 'main' of https://github.com/QuantumKitHub/TensorKit.jl …
borisdevos Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 62 additions & 8 deletions test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ Tests use [ParallelTestRunner.jl](https://github.com/vchuravy/ParallelTestRunner
execution. Each test file runs in its own worker process. Shared helpers are loaded automatically
via `init_code` — test files do not need to include `setup.jl` themselves.

Test content which is sector-dependent (i.e. for graded spaces, fusion trees, (diagonal) tensors and factorizations) lives in the reusable, standalone `TensorKitTestSuite` module (see below). `TensorKitTestSuite` can be `include`d directly by downstream
packages to test a custom `Sector` type.

## Running tests

```julia
# Standard — works on all supported Julia versions
# Run all tests: requires Julia 1.12+ due to Mooncake
using Pkg; Pkg.test()
```

Expand All @@ -31,16 +34,54 @@ julia --project=test test/runtests.jl --fast tensors
julia --project=test test/runtests.jl --jobs=4
```

`Pkg.test()`/`Pkg.test("TensorKit"; test_args = [...])` accept the same `--fast`/group/file
arguments via `test_args` (e.g. `test_args = ["--fast", "tensors/diagonal"]`).

## Test groups

| Group | Contents |
|-------|----------|
| `symmetries` | Spaces and fusion trees |
| `tensors` | Core tensor operations, factorizations, planar tensors, diagonal tensors |
| `symmetries` | Spaces and fusion trees (call into `TensorKitTestSuite`'s `:spaces`, `:single_fusiontrees`, `:double_fusiontrees` groups) |
| `tensors` | Core tensor operations, planar tensors, diagonal tensors (call into `:tensors`, `:diagonal_tensors`) |
| `factorizations` | Eigendecomposition, QR/LQ, projections, polar, (truncated) SVD (call into `:factorizations`) |
| `other` | Aqua code-quality checks, bug-fix regressions |
| `chainrules` | ChainRulesCore AD tests |
| `mooncake` | Mooncake AD tests |
| `cuda` | CUDA GPU tests (only run when a functional GPU is present) |
| `cuda` / `amd` | GPU tests (only run when a functional GPU is present) |

## `TensorKitTestSuite`

`test/testsuite/TensorKitTestSuite.jl` is a self-contained module. It registers individually-runnable test entries via an internal `@testsuite` macro.

Run a single registered entry with:

```julia
TensorKitTestSuite.run_testsuite(group::Symbol, name::String, arg)
```

where `arg` is a `Type{<:Sector}` (for `:single_fusiontrees`, `:double_fusiontrees`, `:spaces`), a
single space (for `:diagonal_tensors`), or a 5-tuple of mutually compatible spaces (for `:tensors`
and `:factorizations`).

Downstream packages can include it standalone, without any of `TensorKit.jl`'s own test
dependencies:

```julia
import TensorKit
testsuite_path = joinpath(
dirname(dirname(pathof(TensorKit))), # TensorKit root
"test", "testsuite", "TensorKitTestSuite.jl"
)
include(testsuite_path)
using .TensorKitTestSuite

TensorKitTestSuite.run_testsuite(:single_fusiontrees, "printing", MySector)
TensorKitTestSuite.run_testsuite(:tensors, "full trace", (V1, V2, V3, V4, V5))
```

Sector-level helpers (`smallset`, `randsector`, `hasfusiontensor`, `random_fusion`, `can_fuse`, `F_unitarity_test`, `R_unitarity_test`) are reused internally from `TensorKitSectors.SectorTestSuite`, but deliberately **not** re-exported from `TensorKitTestSuite`. A downstream package can thus directly `include`s `TensorKitSectors`'s own `test/testsuite.jl` as well without export ambiguities.

More information can be found in the docstrings of the `TensorKitTestSuite` module and its exported functions.

## Fast mode (`--fast`)

Expand All @@ -49,18 +90,18 @@ Skips `chainrules` and `mooncake` groups entirely, and reduces coverage in the r
- **Sector types**: tests only `Z2Irrep`, `SU2Irrep`, `FermionParity ⊠ U1Irrep ⊠ SU2Irrep`,
and `FibonacciAnyon` (instead of the full `sectorlist`)
- **Space lists**: tests only `(Vtr, Vℤ₂, VSU₂)` (trivial, abelian, non-abelian)
- **Scalar types**: tests only `Float64` and `ComplexF64` (instead of all integer/float variants)

Scalar type coverage within an individual `TensorKitTestSuite` entry is generally hardcoded per entry and does not shrink in fast mode, except where an entry explicitly reads `TensorKitTestSuite.fast_tests[]`. This is so that entries remain fully self-contained for standalone/downstream use.

## `setup.jl`

Defines the `TestSetup` module, which is loaded into every worker sandbox automatically. It
exports:
Defines the `TestSetup` module, which is loaded into every worker sandbox automatically. It `include`s `test/testsuite/TensorKitTestSuite.jl` and re-exports:

- **Spaces**: `Vtr`, `Vℤ₂`, `Vfℤ₂`, `Vℤ₃`, `VU₁`, `VfU₁`, `VCU₁`, `VSU₂`, `VfSU₂`,
`VSU₂U₁`, `Vfib`, `VIB_diag`, `VIB_M`
- **Sector lists**: `sectorlist` (full), `fast_sectorlist` (reduced)
- **Utilities**: `randsector`, `smallset`, `hasfusiontensor`, `force_planar`, `random_fusion`,
`randindextuple`, `randcircshift`, `_repartition`, `trivtuple`, `test_dim_isapprox`, `default_tol`
`eval_show`, `randindextuple`, `randcircshift`, `_repartition`, `trivtuple`, `default_tol`

The `fast_tests::Bool` constant is also available in every test file (injected alongside
`TestSetup` via `init_code` in `runtests.jl`).
Expand All @@ -79,3 +120,16 @@ using TensorKit
# fast_tests and all TestSetup exports (Vtr, sectorlist, …) are available here
end
```

## Adding a new `TensorKitTestSuite` entry

To add a new check to the exported test suite, register it with `@testsuite` in the corresponding file under `test/testsuite/`. For example, to add a new check to the `:tensors` group, add within `test/testsuite/tensors.jl`:

```julia
@testsuite :tensors "my new check" V -> begin
V1, V2, V3, V4, V5 = V
# ... @test ...
end
```

Then call it from the relevant worker file e.g. `test/tensors/xxx.jl` via `TensorKitTestSuite.run_testsuite(:tensors, "my new check", V)`.
89 changes: 1 addition & 88 deletions test/factorizations/eig.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,102 +3,15 @@ using TensorKit
using LinearAlgebra: LinearAlgebra
using MatrixAlgebraKit: DefaultAlgorithm, diagview


spacelist = factorization_spacelist(fast_tests)

eltypes = (Float32, ComplexF64)

for V in spacelist
I = sectortype(first(V))
Istr = TensorKit.type_repr(I)
println("---------------------------------------")
println("Eigenvalue decompositions with symmetry: $Istr")
println("---------------------------------------")
@timedtestset "Eigenvalue decompositions with symmetry: $Istr" verbose = true begin
V1, V2, V3, V4, V5 = V
W = V1 ⊗ V2 ⊗ V3
Vd = fuse(V1 ⊗ V2)

@testset "Eigenvalue decomposition" begin
for T in eltypes,
t in (
rand(T, V1, V1), rand(T, W, W), rand(T, W, W)',
DiagonalTensorMap(rand(T, reduceddim(Vd)), Vd),
)

d, v = @constinferred eig_full(t)
@test t * v ≈ v * d

d, v = @constinferred eig_full(t, DefaultAlgorithm())
@test t * v ≈ v * d

d′ = @constinferred eig_vals(t)
@test d′ ≈ diagview(d)
@test d′ isa TensorKit.SectorVector

d′ = @constinferred eig_vals(t, DefaultAlgorithm())
@test d′ ≈ diagview(d)
@test d′ isa TensorKit.SectorVector

d2 = @constinferred DiagonalTensorMap(d′)
@test d2 ≈ d

vdv = project_hermitian!(v' * v)
@test @constinferred isposdef(vdv)
t isa DiagonalTensorMap || @test !isposdef(t) # unlikely for non-hermitian map

nvals = round(Int, dim(domain(t)) / 2)
d, v = @constinferred eig_trunc(t; trunc = truncrank(nvals))
@test t * v ≈ v * d
@test abs(dim(domain(d)) - nvals) ≤ maximum(c -> blockdim(domain(t), c), blocksectors(t); init = 1)

d, v = @constinferred eig_trunc(t, DefaultAlgorithm(; trunc = truncrank(nvals)))
@test t * v ≈ v * d
@test abs(dim(domain(d)) - nvals) ≤ maximum(c -> blockdim(domain(t), c), blocksectors(t); init = 1)

t2 = @constinferred project_hermitian(t)
D, V = eigen(t2)
@test isisometric(V)
D̃, Ṽ = @constinferred eigh_full(t2)
@test D ≈ D̃
@test V ≈ Ṽ

D̃, Ṽ = @constinferred eigh_full(t2, DefaultAlgorithm())
@test D ≈ D̃
@test V ≈ Ṽ

λ = minimum(real, diagview(D))
@test cond(Ṽ) ≈ one(real(T))
@test isposdef(t2) == isposdef(λ)
@test isposdef(t2 - λ * one(t2) + 0.1 * one(t2))
@test !isposdef(t2 - λ * one(t2) - 0.1 * one(t2))

d, v = @constinferred eigh_full(t2)
@test t2 * v ≈ v * d
@test isunitary(v)

d′ = @constinferred eigh_vals(t2)
@test d′ ≈ diagview(d)
@test d′ isa TensorKit.SectorVector

d′ = @constinferred eigh_vals(t2, DefaultAlgorithm())
@test d′ ≈ diagview(d)
@test d′ isa TensorKit.SectorVector

λ = minimum(real, diagview(d))
@test cond(v) ≈ one(real(T))
@test isposdef(t2) == isposdef(λ)
@test isposdef(t2 - λ * one(t) + 0.1 * one(t2))
@test !isposdef(t2 - λ * one(t) - 0.1 * one(t2))

d, v = @constinferred eigh_trunc(t2; trunc = truncrank(nvals))
@test t2 * v ≈ v * d
@test abs(dim(domain(d)) - nvals) ≤ maximum(c -> blockdim(domain(t), c), blocksectors(t); init = 1)

d, v = @constinferred eigh_trunc(t2, DefaultAlgorithm(; trunc = truncrank(nvals)))
@test t2 * v ≈ v * d
@test abs(dim(domain(d)) - nvals) ≤ maximum(c -> blockdim(domain(t), c), blocksectors(t); init = 1)
end
end
TensorKitTestSuite.run_testsuite(:factorizations, "eigenvalue decomposition", V)
end
end
Loading
Loading