@@ -250,6 +250,12 @@ Return an iterator over all splitting - fusion tree pairs of a tensor.
250250"""
251251fusiontrees (t:: AbstractTensorMap ) = fusionblockstructure (t). fusiontreelist
252252
253+ fusiontreetype (t:: AbstractTensorMap ) = fusiontreetype (typeof (t))
254+ function fusiontreetype (:: Type{T} ) where {T <: AbstractTensorMap }
255+ I = sectortype (T)
256+ return Tuple{fusiontreetype (I, numout (T)), fusiontreetype (I, numin (T))}
257+ end
258+
253259# auxiliary function
254260@inline function trivial_fusiontree (t:: AbstractTensorMap )
255261 sectortype (t) === Trivial ||
@@ -295,6 +301,126 @@ function blocktype(::Type{T}) where {T <: AbstractTensorMap}
295301 return Core. Compiler. return_type (block, Tuple{T, sectortype (T)})
296302end
297303
304+ # tensor data: subblock access
305+ # ----------------------------
306+ @doc """
307+ subblocks(t::AbstractTensorMap)
308+
309+ Return an iterator over all subblocks of a tensor, i.e. all fusiontrees and their
310+ corresponding tensor subblocks.
311+
312+ See also [`subblock`](@ref), [`fusiontrees`](@ref), and [`hassubblock`](@ref).
313+ """
314+ subblocks (t:: AbstractTensorMap ) = SubblockIterator (t, fusiontrees (t))
315+
316+ const _doc_subblock = """
317+ Return a view into the data of `t` corresponding to the splitting - fusion tree pair
318+ `(f₁, f₂)`. In particular, this is an `AbstractArray{T}` with `T = scalartype(t)`, of size
319+ `(dims(codomain(t), f₁.uncoupled)..., dims(codomain(t), f₂.uncoupled)...)`.
320+
321+ Whenever `FusionStyle(sectortype(t)) isa UniqueFusion` , it is also possible to provide only
322+ the external `sectors`, in which case the fusion tree pair will be constructed automatically.
323+ """
324+
325+ @doc """
326+ subblock(t::AbstractTensorMap, (f₁, f₂)::Tuple{FusionTree,FusionTree})
327+ subblock(t::AbstractTensorMap, sectors::Tuple{Vararg{Sector}})
328+
329+ $_doc_subblock
330+
331+ In general, new tensor types should provide an implementation of this function for the
332+ fusion tree signature.
333+
334+ See also [`subblocks`](@ref) and [`fusiontrees`](@ref).
335+ """ subblock
336+
337+ Base. @propagate_inbounds function subblock (t:: AbstractTensorMap , sectors:: Tuple{I, Vararg{I}} ) where {I <: Sector }
338+ # input checking
339+ I === sectortype (t) || throw (SectorMismatch (" Not a valid sectortype for this tensor." ))
340+ FusionStyle (I) isa UniqueFusion ||
341+ throw (SectorMismatch (" Indexing with sectors is only possible for unique fusion styles." ))
342+ length (sectors) == numind (t) || throw (ArgumentError (" invalid number of sectors" ))
343+
344+ # convert to fusiontrees
345+ s₁ = TupleTools. getindices (sectors, codomainind (t))
346+ s₂ = map (dual, TupleTools. getindices (sectors, domainind (t)))
347+ c1 = length (s₁) == 0 ? unit (I) : (length (s₁) == 1 ? s₁[1 ] : first (⊗ (s₁... )))
348+ @boundscheck begin
349+ hassector (codomain (t), s₁) && hassector (domain (t), s₂) || throw (BoundsError (t, sectors))
350+ c2 = length (s₂) == 0 ? unit (I) : (length (s₂) == 1 ? s₂[1 ] : first (⊗ (s₂... )))
351+ c2 == c1 || throw (SectorMismatch (" Not a valid fusion channel for this tensor" ))
352+ end
353+ f₁ = FusionTree (s₁, c1, map (isdual, tuple (codomain (t)... )))
354+ f₂ = FusionTree (s₂, c1, map (isdual, tuple (domain (t)... )))
355+ return @inbounds subblock (t, (f₁, f₂))
356+ end
357+ Base. @propagate_inbounds function subblock (t:: AbstractTensorMap , sectors:: Tuple )
358+ return subblock (t, map (Base. Fix1 (convert, sectortype (t)), sectors))
359+ end
360+ # attempt to provide better error messages
361+ function subblock (t:: AbstractTensorMap , (f₁, f₂):: Tuple{FusionTree, FusionTree} )
362+ (sectortype (t)) == sectortype (f₁) == sectortype (f₂) ||
363+ throw (SectorMismatch (" Not a valid sectortype for this tensor." ))
364+ numout (t) == length (f₁) && numin (t) == length (f₂) ||
365+ throw (DimensionMismatch (" Invalid number of fusiontree legs for this tensor." ))
366+ throw (MethodError (subblock, (t, (f₁, f₂))))
367+ end
368+
369+ @doc """
370+ subblocktype(t)
371+ subblocktype(::Type{T})
372+
373+ Return the type of the tensor subblocks of a tensor.
374+ """ subblocktype
375+
376+ function subblocktype (:: Type{T} ) where {T <: AbstractTensorMap }
377+ return Core. Compiler. return_type (subblock, Tuple{T, fusiontreetype (T)})
378+ end
379+ subblocktype (t) = subblocktype (typeof (t))
380+ subblocktype (T:: Type ) = throw (MethodError (subblocktype, (T,)))
381+
382+ # Indexing behavior
383+ # -----------------
384+ # by default getindex returns views!
385+ @doc """
386+ Base.getindex(t::AbstractTensorMap, sectors::Tuple{Vararg{Sector}})
387+ t[sectors]
388+ Base.getindex(t::AbstractTensorMap, f₁::FusionTree, f₂::FusionTree)
389+ t[f₁, f₂]
390+
391+ $_doc_subblock
392+
393+ !!! warning
394+ Contrary to Julia's array types, the default behavior is to return a view into the tensor data.
395+ As a result, modifying the view will modify the data in the tensor.
396+
397+ See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref).
398+ """ Base. getindex (:: AbstractTensorMap , :: Tuple{I, Vararg{I}} ) where {I <: Sector },
399+ Base. getindex (:: AbstractTensorMap , :: FusionTree , :: FusionTree )
400+
401+ @inline Base. getindex (t:: AbstractTensorMap , sectors:: Tuple{I, Vararg{I}} ) where {I <: Sector } =
402+ subblock (t, sectors)
403+ @inline Base. getindex (t:: AbstractTensorMap , f₁:: FusionTree , f₂:: FusionTree ) =
404+ subblock (t, (f₁, f₂))
405+
406+ @doc """
407+ Base.setindex!(t::AbstractTensorMap, v, sectors::Tuple{Vararg{Sector}})
408+ t[sectors] = v
409+ Base.setindex!(t::AbstractTensorMap, v, f₁::FusionTree, f₂::FusionTree)
410+ t[f₁, f₂] = v
411+
412+ Copies `v` into the data slice of `t` corresponding to the splitting - fusion tree pair `(f₁, f₂)`.
413+ By default, `v` can be any object that can be copied into the view associated with `t[f₁, f₂]`.
414+
415+ See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref).
416+ """ Base. setindex! (:: AbstractTensorMap , :: Any , :: Tuple{I, Vararg{I}} ) where {I <: Sector },
417+ Base. setindex! (:: AbstractTensorMap , :: Any , :: FusionTree , :: FusionTree )
418+
419+ @inline Base. setindex! (t:: AbstractTensorMap , v, sectors:: Tuple{I, Vararg{I}} ) where {I <: Sector } =
420+ copy! (subblock (t, sectors), v)
421+ @inline Base. setindex! (t:: AbstractTensorMap , v, f₁:: FusionTree , f₂:: FusionTree ) =
422+ copy! (subblock (t, (f₁, f₂)), v)
423+
298424# Derived indexing behavior for tensors with trivial symmetry
299425# -------------------------------------------------------------
300426using TensorKit. Strided: SliceIndex
@@ -499,3 +625,38 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap)
499625 return A
500626 end
501627end
628+
629+ # Show and friends
630+ # ----------------
631+
632+ function Base. dims2string (V:: HomSpace )
633+ str_cod = numout (V) == 0 ? " ()" : join (dim .(codomain (V)), ' ×' )
634+ str_dom = numin (V) == 0 ? " ()" : join (dim .(domain (V)), ' ×' )
635+ return str_cod * " ←" * str_dom
636+ end
637+
638+ function Base. summary (io:: IO , t:: AbstractTensorMap )
639+ V = space (t)
640+ print (io, Base. dims2string (V), " " )
641+ Base. showarg (io, t, true )
642+ return nothing
643+ end
644+
645+ # Human-readable:
646+ function Base. show (io:: IO , :: MIME"text/plain" , t:: AbstractTensorMap )
647+ # 1) show summary: typically d₁×d₂×… ← d₃×d₄×… $(typeof(t)):
648+ summary (io, t)
649+ println (io, " :" )
650+
651+ # 2) show spaces
652+ # println(io, " space(t):")
653+ println (io, " codomain: " , codomain (t))
654+ println (io, " domain: " , domain (t))
655+
656+ # 3) [optional]: show data
657+ get (io, :compact , true ) && return nothing
658+ ioc = IOContext (io, :typeinfo => sectortype (t))
659+ println (io, " \n\n blocks: " )
660+ show_blocks (io, MIME " text/plain" (), blocks (t))
661+ return nothing
662+ end
0 commit comments