Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DiagonalTensorMap constructors and converters #212

Merged
merged 11 commits into from
Feb 5, 2025

Conversation

Yue-Zhengyuan
Copy link
Contributor

@Yue-Zhengyuan Yue-Zhengyuan commented Feb 3, 2025

Changes:

  • In README guide of transferring data from old to new TensorKit, the old data is now specified by blocks instead of fusiontrees.
  • Added the conversion from a TensorMap to a DiagonalTensorMap. It first checks if the input is indeed diagonal.

@lkdvos
Copy link
Collaborator

lkdvos commented Feb 3, 2025

I don't think that this is fully correct. We really need the data to be associated to the fusiontrees, because their order within the blocks changed between versions. As a result, simply loading in the blocks would yield permuted data, which is definitely not what you want.

src/tensors/diagonal.jl Outdated Show resolved Hide resolved
src/tensors/tensor.jl Outdated Show resolved Hide resolved
src/tensors/diagonal.jl Outdated Show resolved Hide resolved
src/tensors/tensor.jl Outdated Show resolved Hide resolved
@lkdvos
Copy link
Collaborator

lkdvos commented Feb 3, 2025

So, summarizing my thoughts a bit here:

For the functions, I would say the best approach is:

  • The DiagonalTensorMap(::AbstractTensorMap) constructor should take in arbitrary tensormaps, and simply "project onto the diagonal", ie discard off-diagonal elements. This is similar to how LinearAlgebra.Diagonal functions.
  • The convert(::Type{<:DiagonalTensorMap}, x::AbstractTensorMap) should first check if x is actually diagonal, and then either throw or do the conversion. This way, this is lossless.
  • The convert(::Type{<:DiagonalTensorMap}, x::Dict) can be defined in terms of convert(DiagonalTensorMap, convert(TensorMap, x)) (or a more efficient direct path). This way, it will throw whenever you try loading in data that is not diagonal.

For the best practice on saving and loading states, I'm actually a bit torn. It seems to me that JLD2 has actually come quite a long way, and is quite good at supporting arbitrary datatypes. Additionally, after reading through the documentation a bit more, there are several features that could make our lives a lot easier:

The combination of these two could mean that we simply define something like:

struct TensorData_v1
    # stable data format
end

and store tensors simply by using JLD2's automatic serialization. Then, since the custom serialization is stable, we can load in tensors from any version by implementing the correct deserializer.
(similarly for DiagonalTensorData, etc)

Additionally, if we would ever like to go to a different data format, we could just add

struct TensorData_v2 end

and remap the types on load.

src/tensors/diagonal.jl Outdated Show resolved Hide resolved
Copy link

codecov bot commented Feb 4, 2025

Codecov Report

Attention: Patch coverage is 64.70588% with 6 lines in your changes missing coverage. Please review.

Project coverage is 82.22%. Comparing base (450ff9b) to head (bcca586).
Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/tensors/diagonal.jl 64.70% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #212      +/-   ##
==========================================
- Coverage   82.25%   82.22%   -0.03%     
==========================================
  Files          43       43              
  Lines        5433     5447      +14     
==========================================
+ Hits         4469     4479      +10     
- Misses        964      968       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@Jutho
Copy link
Owner

Jutho commented Feb 4, 2025

I think this is good to go from my end; the failure on nightly seems unrelated. @Yue-Zhengyuan , anything that you still like to see changed?

@Yue-Zhengyuan Yue-Zhengyuan changed the title Update upgrade guide and add TensorMap to DiagonalTensorMap conversion Add TensorMap to DiagonalTensorMap conversion Feb 5, 2025
@Yue-Zhengyuan Yue-Zhengyuan changed the title Add TensorMap to DiagonalTensorMap conversion Add DiagonalTensorMap constructors and converters Feb 5, 2025
@Yue-Zhengyuan
Copy link
Contributor Author

Yue-Zhengyuan commented Feb 5, 2025

I'm OK to fix in another PR about transferring tensors from old to new TensorKit.

@Jutho
Copy link
Owner

Jutho commented Feb 5, 2025

I seem to have missed this, but what is wrong with the description of how to transfer tensors from TensorKit prior to v0.13 to newer versions as currently in the README? I've restored the README because the updated description you wrote was incorrect, as shown by the example in #211 . Is there anything else that you would like to see in this regard?

@Yue-Zhengyuan
Copy link
Contributor Author

Yue-Zhengyuan commented Feb 5, 2025

  • Currently JLD2 does not have the function jldload. I suggested to replace it with load_object.
  • The fusion trees structure in new TensorKit is defined differently from the old TensorKit, so the new TensorKit cannot reconstruct it from the old string representation.

Example: first, run the following in environment with v0.12.7

using TensorKit
using JLD2

function save_tensor_tmp(filename::AbstractString, t::AbstractTensorMap)
    t_dict = Dict(
        :space => space(t), 
        :data => Dict((f₁, f₂) => t[f₁, f₂] for (f₁, f₂) in fusiontrees(t))
    )
    jldsave(filename; t_dict)
    return nothing
end

V1, V2 =^2, ℂ^3
a = TensorMap(randn, Float64, V1  V2);
println(fusiontrees(a)) # output: ((nothing, nothing),)
save_tensor_tmp("tmp.jld2", a)

Next, run the following in environment with v0.14.3

using TensorKit
using JLD2

function load_tensor_tmp(filename::AbstractString)
    t_dict = JLD2.load_object(filename) # I replaced `jldload` with `load_object`
    T = eltype(valtype(t_dict[:data]))
    t = TensorMap{T}(undef, t_dict[:space])
    for ((f₁, f₂), val) in t_dict[:data]
        t[f₁, f₂] .= val
    end
    return t
end

a =  load_tensor_tmp("tmp.jld2");

Error message:

MethodError: no method matching getindex(::TensorMap{Float64, ComplexSpace, 1, 1, Vector{Float64}}, ::Nothing, ::Nothing)
The function `getindex` exists, but no method is defined for this combination of argument types.
...

This is because in v0.14.3, the fusiontrees of a trivial tensor is defined as

a = randn(ℂ^2 ^3)
fusiontrees(a)
#= output:
1-element Vector{Tuple{FusionTree{Trivial, 1, 0, 0}, FusionTree{Trivial, 1, 0, 0}}}:
 (FusionTree{Trivial}((Trivial(),), Trivial(), (false,), ()), FusionTree{Trivial}((Trivial(),), Trivial(), (false,), ()))
=#

There are also problems with other sector types. For example, for an SU(2) tensor, the error message is

┌ Warning: read type FusionTree has a different number of parameters from type FusionTree in workspace; reconstructing
└ @ JLD2 [/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/data/reconstructing_datatypes.jl:543](https://file+.vscode-resource.vscode-cdn.net/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/data/reconstructing_datatypes.jl:543)
┌ Warning: read type NTuple{N, T} where {N, T} has a different number of parameters from type NTuple{N, T} where {N, T} in workspace; reconstructing
└ @ JLD2 [/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/data/reconstructing_datatypes.jl:543](https://file+.vscode-resource.vscode-cdn.net/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/data/reconstructing_datatypes.jl:543)
┌ Warning: custom serialization of Dict{JLD2.ReconstructedStatic{Symbol("NTuple{N, T} where {N, T}{2,JLD2.ReconstructedStatic{Symbol(\"FusionTree{SU2Irrep,1,0,0,Nothing}\"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}"), (Symbol("1"), Symbol("2")), Tuple{JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}, JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}},StridedViews.StridedView{Float64, 2, Matrix{Float64}, typeof(identity)}} encountered, but the type does not exist in the workspace; the data will be read unconverted
└ @ JLD2 [/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/data/reconstructing_datatypes.jl:108](https://file+.vscode-resource.vscode-cdn.net/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/data/reconstructing_datatypes.jl:108)
┌ Warning: type Pair{JLD2.ReconstructedStatic{Symbol("NTuple{N, T} where {N, T}{2,JLD2.ReconstructedStatic{Symbol(\"FusionTree{SU2Irrep,1,0,0,Nothing}\"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}"), (Symbol("1"), Symbol("2")), Tuple{JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}, JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}},StridedViews.StridedView{Float64, 2, Matrix{Float64}, typeof(identity)}} does not exist in workspace; interpreting Array{Pair{JLD2.ReconstructedStatic{Symbol("NTuple{N, T} where {N, T}{2,JLD2.ReconstructedStatic{Symbol(\"FusionTree{SU2Irrep,1,0,0,Nothing}\"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}"), (Symbol("1"), Symbol("2")), Tuple{JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}, JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}},StridedViews.StridedView{Float64, 2, Matrix{Float64}, typeof(identity)}}} as Array{Any}
└ @ JLD2 [/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/datasets.jl:128](https://file+.vscode-resource.vscode-cdn.net/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/datasets.jl:128)
┌ Warning: some parameters could not be resolved for type Pair{JLD2.ReconstructedStatic{Symbol("NTuple{N, T} where {N, T}{2,JLD2.ReconstructedStatic{Symbol(\"FusionTree{SU2Irrep,1,0,0,Nothing}\"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}"), (Symbol("1"), Symbol("2")), Tuple{JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}, JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}},StridedViews.StridedView{Float64, 2, Matrix{Float64}, typeof(identity)}}; reading as Pair{JLD2.ReconstructedStatic{Symbol("NTuple{N, T} where {N, T}{2,JLD2.ReconstructedStatic{Symbol(\"FusionTree{SU2Irrep,1,0,0,Nothing}\"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}"), (Symbol("1"), Symbol("2")), Tuple{JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}, JLD2.ReconstructedStatic{Symbol("FusionTree{SU2Irrep,1,0,0,Nothing}"), (:uncoupled, :coupled, :isdual, :innerlines, :vertices), Tuple{Tuple{SU2Irrep}, SU2Irrep, Tuple{Bool}, Tuple{}, Tuple{}}}}}, StridedViews.StridedView{Float64, 2, Matrix{Float64}, typeof(identity)}}
└ @ JLD2 [/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/data/reconstructing_datatypes.jl:566](https://file+.vscode-resource.vscode-cdn.net/Users/zhengyuanyue/.julia/packages/JLD2/nPYlZ/src/data/reconstructing_datatypes.jl:566)
MethodError: no method matching valtype(::JLD2.SerializedDict)
The function `valtype` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  valtype(!Matched::Type{Union{}}, Any...)
   @ Base abstractarray.jl:195
  valtype(!Matched::Type{<:AbstractDict{<:Any, V}}) where V
   @ Base abstractdict.jl:322
  valtype(!Matched::Type{<:AbstractArray})
   @ Base abstractarray.jl:213
  ...

@Jutho
Copy link
Owner

Jutho commented Feb 5, 2025

Ok, I surely had missed/forgotten that part of the issue. My apologies and thanks for laying it out so clearly. It would indeed be good to fix this in a separate PR. I will merge this one and think about possible solutions.

@Jutho Jutho merged commit a0e3460 into Jutho:master Feb 5, 2025
8 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants