Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 10 additions & 12 deletions src/auxiliary/caches.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,16 @@ macro cached(ex)
error("cached macro can only be used on function definitions")
fcall = ex.args[1]
if Meta.isexpr(fcall, :where)
hasparams = true
params = fcall.args[2:end]
fcall = fcall.args[1]
else
hasparams = false
params = nothing
end
if Meta.isexpr(fcall, :(::))
typed = true
typeex = fcall.args[2]
fcall = fcall.args[1]
else
typed = false
typeex = nothing
end
Meta.isexpr(fcall, :call) ||
error("cached macro can only be used on function definitions")
Expand All @@ -82,14 +80,14 @@ macro cached(ex)
# actual implenetation, with underscore name
_fname = Symbol(:_, fname)
_fcall = Expr(:call, _fname, fargs...)
if hasparams
if !isnothing(params)
_fcall = Expr(:where, _fcall, params...)
end
_fex = Expr(:function, _fcall, _fbody)

# implementation that chooses the cache style
newfcall = fcall
if hasparams
if !isnothing(params)
newfcall = Expr(:where, newfcall, params...)
end
cachestylevar = gensym(:cachestyle)
Expand All @@ -103,11 +101,11 @@ macro cached(ex)

# nocache implementation
fnocachecall = Expr(:call, fname, fargs..., :(::NoCache))
if hasparams
if !isnothing(params)
fnocachecall = Expr(:where, fnocachecall, params...)
end
fnocachebody = Expr(:call, _fname, fargnames...)
if typed
if !isnothing(typeex)
T = gensym(:T)
fnocachebody = Expr(:block, Expr(:(=), T, typeex), Expr(:(::), fnocachebody, T))
end
Expand All @@ -116,7 +114,7 @@ macro cached(ex)
# tasklocal cache implementation
Dvar = gensym(:D)
flocalcachecall = Expr(:call, fname, fargs..., :(::TaskLocalCache{$Dvar}))
if hasparams
if !isnothing(params)
flocalcachecall = Expr(:where, flocalcachecall, params..., Dvar)
else
flocalcachecall = Expr(:where, flocalcachecall, Dvar)
Expand All @@ -139,7 +137,7 @@ macro cached(ex)
return $_fname($(fargnames...))
end
)
if typed
if !isnothing(typeex)
T = gensym(:T)
flocalcachebody = Expr(
:block,
Expand All @@ -160,12 +158,12 @@ macro cached(ex)

# # global cache implementation
fglobalcachecall = Expr(:call, fname, fargs..., :(::GlobalLRUCache))
if hasparams
if !isnothing(params)
fglobalcachecall = Expr(:where, fglobalcachecall, params...)
end
globalcachename = Symbol(:GLOBAL_, uppercase(string(fname)), :_CACHE)
getglobalcachex = Expr(:(=), cachevar, globalcachename)
if typed
if !isnothing(typeex)
T = gensym(:T)
fglobalcachebody = Expr(
:block,
Expand Down
2 changes: 1 addition & 1 deletion src/auxiliary/iterators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ struct OneOrNoneIterator{T}
end

function Base.iterate(it::OneOrNoneIterator, state = true)
if state && it.cond
if isone(state) && it.cond
return (it.first, false)
else
return nothing
Expand Down
2 changes: 1 addition & 1 deletion src/fusiontrees/duality_manipulations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ function planar_trace(f::FusionTree, (q₁, q₂)::Index2Tuple)
# We thus handle the total trace recursively, by first looking for and
# tracing away neighbouring pairs.
k = 1
local i, j
i = j = 0
while k <= length(q₁)
if mod1(q₁[k] + 1, length(f)) == q₂[k]
i = q₁[k]
Expand Down
3 changes: 1 addition & 2 deletions src/fusiontrees/fusiontrees.jl
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,7 @@ function fusiontensor((f₁, f₂)::FusionTreePair)
d1 = TupleTools.front(sz1)
d2 = TupleTools.front(sz2)
return reshape(
reshape(F₁, TupleTools.prod(d1), sz1[end]) *
reshape(F₂, TupleTools.prod(d2), sz2[end])', (d1..., d2...)
reshape(F₁, :, sz1[end]) * reshape(F₂, :, sz2[end])', (d1..., d2...)
)
end
fusiontensor(src::FusionTreeBlock) = sum(fusiontensor, fusiontrees(src))
Expand Down
2 changes: 1 addition & 1 deletion src/planar/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function planarparser(planarexpr, kwargs...)
if name == :order
isexpr(val, :tuple) ||
throw(ArgumentError("Invalid use of `order`, should be `order=(...,)`"))
indexorder = map(normalizeindex, val.args)
indexorder = map(TO.normalizeindex, val.args)
parser.contractiontreebuilder = network -> TO.indexordertree(
network, indexorder
)
Expand Down
10 changes: 3 additions & 7 deletions src/planar/preprocessors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -166,22 +166,18 @@ function _construct_braidingtensors!(ex, preargs, indexmap, non_braiding) # ex i
i1a, i2a, = rightind
end

foundV1, foundV2 = false, false
V1 = V2 = nothing
if haskey(indexmap, i1a)
V1 = indexmap[i1a]
foundV1 = true
elseif haskey(indexmap, i1b)
V1 = Expr(:call, :dual, indexmap[i1b])
foundV1 = true
end
if haskey(indexmap, i2a)
V2 = indexmap[i2a]
foundV2 = true
elseif haskey(indexmap, i2b)
V2 = Expr(:call, :dual, indexmap[i2b])
foundV2 = true
end
if foundV1 && foundV2
if !isnothing(V1) && !isnothing(V2)
s = gensym(:τ)
storageex = Expr(:call, GlobalRef(TensorKit, :promote_storagetype), non_braiding...)
braidingex = Expr(:call, GlobalRef(TensorKit, :braidingtensortype), V1, V2, storageex)
Expand Down Expand Up @@ -591,7 +587,7 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries)
newarg = _extract_contraction_pairs(rhs.args[3], lhs, pre, temporaries)
return Expr(:call, :\, rhs.args[2], newarg)
else
throw(ArgumentError("unknown tensor expression $ex"))
throw(ArgumentError("unknown tensor expression $rhs"))
end
end

Expand Down
6 changes: 3 additions & 3 deletions src/pullbacks/tensoroperations.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function blas_contract_pullback_ΔA!(
ΔA, ΔC, A, pA, B, pB, pAB, α, backend, allocator
ΔA, ΔC, A::AbstractTensorMap, pA, B::AbstractTensorMap, pB, pAB, α, backend, allocator
)
ipAB = invperm(linearize(pAB))
pΔC = TO.repartition(ipAB, TO.numout(pA))
Expand All @@ -23,7 +23,7 @@ function blas_contract_pullback_ΔA!(
end

function blas_contract_pullback_ΔB!(
ΔB, ΔC, A, pA, B, pB, pAB, α, backend, allocator
ΔB, ΔC, A::AbstractTensorMap, pA, B::AbstractTensorMap, pB, pAB, α, backend, allocator
)
ipAB = invperm(linearize(pAB))
pΔC = TO.repartition(ipAB, TO.numout(pA))
Expand All @@ -48,7 +48,7 @@ function blas_contract_pullback_ΔB!(
end

function trace_permute_pullback_ΔA!(
ΔA, ΔC, A, p, q, α, backend
ΔA, ΔC, A::AbstractTensorMap, p, q, α, backend
)
ip = invperm((linearize(p)..., q[1]..., q[2]...))
pdA = TO.repartition(ip, numout(A))
Expand Down
2 changes: 1 addition & 1 deletion src/spaces/productspace.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ that make up the `ProductSpace` instance.
"""
function blocksectors(P::ProductSpace{S, N}) where {S, N}
I = sectortype(S)
if I == Trivial
if I === Trivial
return OneOrNoneIterator(dim(P) != 0, Trivial())
end
bs = Vector{I}()
Expand Down
17 changes: 11 additions & 6 deletions src/tensors/abstracttensor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,11 @@ See also [`numin`](@ref) and [`numind`](@ref).
""" numout

numout(x) = numout(typeof(x))
numout(T::Type) = throw(MethodError(numout, T)) # avoid infinite recursion
numout(T::Type) = throw(MethodError(numout, (T,))) # avoid infinite recursion
# `typeintersect(Type{<:AbstractTensorMap}, Type{<:Union{FusionTreePair, FusionTreeBlock}})` is
# `Type{Union{}}`, which leaves the parametric methods mutually ambiguous. Resolve it explicitly so
# the intended `MethodError` is thrown instead of an ambiguity error.
numout(::Type{Union{}}) = throw(MethodError(numout, (Union{},)))
numout(::Type{<:AbstractTensorMap{T, S, N₁}}) where {T, S, N₁} = N₁

@doc """
Expand All @@ -251,7 +255,8 @@ See also [`numout`](@ref) and [`numind`](@ref).
""" numin

numin(x) = numin(typeof(x))
numin(T::Type) = throw(MethodError(numin, T)) # avoid infinite recursion
numin(T::Type) = throw(MethodError(numin, (T,))) # avoid infinite recursion
numin(::Type{Union{}}) = throw(MethodError(numin, (Union{},))) # see `numout(::Type{Union{}})`
numin(::Type{<:AbstractTensorMap{T, S, N₁, N₂}}) where {T, S, N₁, N₂} = N₂

"""
Expand Down Expand Up @@ -496,8 +501,8 @@ $_doc_subblock
As a result, modifying the view will modify the data in the tensor.

See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref).
""" Base.getindex(::AbstractTensorMap, ::Tuple{I, Vararg{I}}) where {I <: Sector},
Base.getindex(::AbstractTensorMap, ::FusionTree, ::FusionTree)
""" Base.getindex(t::AbstractTensorMap, sectors::Tuple{I, Vararg{I}}) where {I <: Sector},
Base.getindex(t::AbstractTensorMap, f₁::FusionTree, f₂::FusionTree)

@inline Base.getindex(t::AbstractTensorMap, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} =
subblock(t, sectors)
Expand All @@ -514,8 +519,8 @@ Copies `v` into the data slice of `t` corresponding to the splitting - fusion tr
By default, `v` can be any object that can be copied into the view associated with `t[f₁, f₂]`.

See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref).
""" Base.setindex!(::AbstractTensorMap, ::Any, ::Tuple{I, Vararg{I}}) where {I <: Sector},
Base.setindex!(::AbstractTensorMap, ::Any, ::FusionTree, ::FusionTree)
""" Base.setindex!(t::AbstractTensorMap, v, sectors::Tuple{I, Vararg{I}}) where {I <: Sector},
Base.setindex!(t::AbstractTensorMap, v, f₁::FusionTree, f₂::FusionTree)

@inline Base.setindex!(t::AbstractTensorMap, v, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} =
copy!(subblock(t, sectors), v)
Expand Down
1 change: 1 addition & 0 deletions src/tensors/adjoint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ end
Base.parent(t::AdjointTensorMap) = t.parent
parenttype(t::AdjointTensorMap) = parenttype(typeof(t))
parenttype(::Type{AdjointTensorMap{T, S, N₁, N₂, TT}}) where {T, S, N₁, N₂, TT} = TT
parenttype(::Type{Union{}}) = throw(MethodError(parenttype, (Union{},)))

# Constructor: construct from taking adjoint of a tensor
Base.adjoint(t::AdjointTensorMap) = parent(t)
Expand Down
2 changes: 1 addition & 1 deletion src/tensors/linalg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ tdst[sub_axes...] .= tsrc[sub_axes...]
absorb(tdst::AbstractTensorMap, tsrc::AbstractTensorMap) = absorb!(copy(tdst), tsrc)
function absorb!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap)
numin(tdst) == numin(tsrc) && numout(tdst) == numout(tsrc) ||
throw(DimensionError("Incompatible number of indices for source and destination"))
throw(IndexError("Incompatible number of indices for source and destination"))
S = check_spacetype(tdst, tsrc)
dom = mapreduce(infimum, ⊗, domain(tdst), domain(tsrc); init = one(S))
cod = mapreduce(infimum, ⊗, codomain(tdst), codomain(tsrc); init = one(S))
Expand Down
2 changes: 1 addition & 1 deletion src/tensors/tensor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ for randf in (:rand, :randn, :randexp, :randisometry)
function $randfun(
rng::Random.AbstractRNG, ::Type{TorA}, codomain::TensorSpace
) where {TorA}
return $randfun(rng, TorA, codomain ← one(domain))
return $randfun(rng, TorA, codomain ← one(codomain))
end

# filling in default eltype
Expand Down
10 changes: 8 additions & 2 deletions src/tensors/tensoroperations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ function trace_permute!(
return tdst
end

function _trace_permute!(::UniqueFusion, tdst, tsrc, (p₁, p₂), (q₁, q₂), α, β, backend)
function _trace_permute!(
::UniqueFusion, tdst::AbstractTensorMap, tsrc::AbstractTensorMap,
(p₁, p₂), (q₁, q₂), α, β, backend
)
scale!(tdst, β)
r₁, r₂ = (p₁..., q₁...), (p₂..., q₂...)
N₁, N₂ = length(p₁), length(p₂)
Expand All @@ -268,7 +271,10 @@ function _trace_permute!(::UniqueFusion, tdst, tsrc, (p₁, p₂), (q₁, q₂),
return tdst
end

function _trace_permute!(::FusionStyle, tdst, tsrc, (p₁, p₂), (q₁, q₂), α, β, backend)
function _trace_permute!(
::FusionStyle, tdst::AbstractTensorMap, tsrc::AbstractTensorMap,
(p₁, p₂), (q₁, q₂), α, β, backend
)
scale!(tdst, β)
r₁, r₂ = (p₁..., q₁...), (p₂..., q₂...)
N₁, N₂ = length(p₁), length(p₂)
Expand Down
3 changes: 2 additions & 1 deletion test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ AllocCheck = "0.2"
ChainRulesTestUtils = "1"
Combinatorics = "1"
cuTENSOR = "6"
JET = "0.9, 0.10, 0.11"
JET = "0.9, 0.12"
Mooncake = "0.5.45"
ParallelTestRunner = "2"
Test = "1"
TestExtras = "0.2,0.3"
Expand Down
20 changes: 14 additions & 6 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# TensorKit.jl test suite

Tests use [ParallelTestRunner.jl](https://github.com/vchuravy/ParallelTestRunner.jl) for parallel
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.
Tests use [ParallelTestRunner.jl](https://github.com/vchuravy/ParallelTestRunner.jl) for parallel 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.

## Running tests

Expand Down Expand Up @@ -37,17 +37,25 @@ julia --project=test test/runtests.jl --jobs=4
|-------|----------|
| `symmetries` | Spaces and fusion trees |
| `tensors` | Core tensor operations, factorizations, planar tensors, diagonal tensors |
| `other` | Aqua code-quality checks, bug-fix regressions |
| `other` | Aqua code-quality checks, JET static analysis, bug-fix regressions |
| `chainrules` | ChainRulesCore AD tests |
| `mooncake` | Mooncake AD tests |
| `cuda` | CUDA GPU tests (only run when a functional GPU is present) |

JET only functions on the Julia versions it supports, which it reports through `JET.JET_AVAILABLE`.
`runtests.jl` drops the JET-dependent files from the testsuite when that is `false`:
`other/jet` (whole-package analysis, which additionally requires the JET 0.12 generation)
and `mooncake/tangent` (reaches JET through Mooncake's extension).

Mooncake caps which JET versions it accepts, so the `Mooncake = "0.5.45"` floor in
`test/Project.toml` is what lets JET 0.12 be resolved at all — with an older Mooncake the
resolver settles on JET 0.11 and `other/jet` is silently dropped.

## Fast mode (`--fast`)

Skips `chainrules` and `mooncake` groups entirely, and reduces coverage in the remaining tests:

- **Sector types**: tests only `Z2Irrep`, `SU2Irrep`, `FermionParity ⊠ U1Irrep ⊠ SU2Irrep`,
and `FibonacciAnyon` (instead of the full `sectorlist`)
- **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)

Expand Down
6 changes: 5 additions & 1 deletion test/mooncake/tangent.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ rng = Random.default_rng()
spacelist = ad_spacelist(fast_tests)
eltypes = (Float64, ComplexF64)

# `test_data` reaches JET through Mooncake's extension, so it needs a functional JET; this is also
# gated in runtests.jl. JET < 0.12 does not define `JET_AVAILABLE`.
jet_available = !isdefined(JET, :JET_AVAILABLE) || JET.JET_AVAILABLE

# only run on Linux since allocation tests are broken on other versions
Sys.islinux() && @timedtestset "Mooncake - Tangent type: $(TensorKit.type_repr(sectortype(eltype(V)))) ($T)" for V in spacelist, T in eltypes
jet_available && Sys.islinux() && @timedtestset "Mooncake - Tangent type: $(TensorKit.type_repr(sectortype(eltype(V)))) ($T)" for V in spacelist, T in eltypes
A = randn(T, V[1] ⊗ V[2] ⊗ V[3] ← (V[4] ⊗ V[5])')
Mooncake.TestUtils.test_data(rng, A)

Expand Down
24 changes: 24 additions & 0 deletions test/other/jet.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using TensorKit
using Test
using JET

# Reports that are not TensorKit's to fix. Each entry needs an upstream issue.
const IGNORED = (
# `schur_full`/`schur_vals` route `Diagonal` inputs to `DiagonalAlgorithm`, which schur does
# not implement, so `schur_full(::DiagonalTensorMap)` throws a `MethodError`. This reproduces
# with a plain `LinearAlgebra.Diagonal` and no TensorKit involved:
# https://github.com/QuantumKitHub/MatrixAlgebraKit.jl/issues/276
JET.LastFrameMethod(:schur_full!),
JET.LastFrameMethod(:schur_vals!),
# `local variable kwargs may be undefined` inside `GenericTreeTransformer`, coming entirely
# from Base's `@debug` expansion (`local msg, kwargs` bound in a short-circuit guard) rather
# than from any TensorKit code: https://github.com/aviatesk/JET.jl/issues/860
JET.LastFrameMethod(:GenericTreeTransformer),
)

# also gated in runtests.jl; JET < 0.12 does not define `JET_AVAILABLE`
if isdefined(JET, :JET_AVAILABLE) && JET.JET_AVAILABLE
JET.test_package(TensorKit; target_modules = (TensorKit,), ignored_modules = IGNORED)
else
@info "Full JET functionality is unavailable on Julia $VERSION; skipping JET analysis"
end
11 changes: 11 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ CUDA.functional() || filter!(!startswith("cuda") ∘ first, testsuite)
using AMDGPU
AMDGPU.functional() || filter!(!startswith("amd") ∘ first, testsuite)

# JET tests: JET ≥ 0.12 (Julia 1.12+) signals through `JET_AVAILABLE` whether it is functional,
# and loads empty stubs when it is not. Older JET versions, still needed on Julia < 1.12, do not
# define it, hence the `isdefined` check instead of `using JET: JET_AVAILABLE`.
using JET: JET
const jet_new_generation = isdefined(JET, :JET_AVAILABLE)
const jet_available = !jet_new_generation || JET.JET_AVAILABLE
# whole-package analysis is pinned to the JET 0.12 generation, so its reports need not be curated for several JET/Julia combinations
(jet_new_generation && jet_available) || delete!(testsuite, "other/jet")
# Mooncake's tangent tests reach JET through Mooncake's extension: any functional JET works, the empty stubs do not
jet_available || delete!(testsuite, "mooncake/tangent")

# On Buildkite (GPU CI runner): only run CUDA and AMDGPU tests
if get(ENV, "BUILDKITE", "false") == "true"
f(str) = startswith(first(str), "cuda") || startswith(first(str), "amd")
Expand Down
Loading