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

Correct adjoint for truncated SVD #15

Merged
merged 29 commits into from
Jul 10, 2024
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c390bec
Add truncated SVD adjoint with wrapper for KrylovKit iterative SVD, a…
pbrehmer Mar 4, 2024
51507ce
Use KrylovKit.linsolve for truncation linear problem, make loss funct…
pbrehmer Mar 5, 2024
d3a31fb
Improve loss function, compare SVD gradient with TensorKit.tsvd gradient
pbrehmer Mar 5, 2024
8ac307e
Update SVD adjoint linear problem to use Tuple and remove reshapes
pbrehmer Mar 26, 2024
80db1c0
Fix ZeroTangent case for linear problem
pbrehmer Apr 11, 2024
ae96298
Add SVD wrapper structs and function, utilize tsvd machinery, convert…
pbrehmer Jun 20, 2024
78ab152
Copy ctmrg.jl from master, add svdalg field to CTMRG, use svdwrap in …
pbrehmer Jun 20, 2024
dc6ad2b
Merge branch 'master' into svd_adjoint
lkdvos Jun 28, 2024
fc6600e
Use KrylovKit implementation of eigsolve instead of eigsolve.jl, dele…
pbrehmer Jul 4, 2024
823f52e
Add IterSVD _tsvd! method and adjoint using KrylovKit.svdsolve adjoint
pbrehmer Jul 5, 2024
a615b4a
Add PEPSKit.tsvd wrapper, fix IterSVD adjoint
pbrehmer Jul 5, 2024
a815e41
Add TensorKit compat entry for softened tsvd type restrictions
pbrehmer Jul 5, 2024
447bfd8
Add ProjectorAlg and refactor all tests and examples
pbrehmer Jul 5, 2024
bbd132d
Update MPSKit compat
lkdvos Jul 5, 2024
0c03cd4
Replace tsvd with tsvd!, add views to IterSVD adjoint
pbrehmer Jul 8, 2024
14f0065
Improve IterSVD allocation, implement CTMRG convenience constructor, …
pbrehmer Jul 8, 2024
9b2c4b7
Fix tests
pbrehmer Jul 8, 2024
0c13d47
Add block-wise dense fallback option
pbrehmer Jul 8, 2024
538652d
Add SVDrrule wrapper, add separate adjoint structs and rrules, update…
pbrehmer Jul 8, 2024
7032ed0
Add IterSVD test for symmetric tensor with fallback
pbrehmer Jul 9, 2024
66a827e
Merge branch 'master' into svd_adjoint
pbrehmer Jul 9, 2024
d09561f
Formatting
pbrehmer Jul 9, 2024
b3a0726
Fix missing cnext in ctmrg, update README example
pbrehmer Jul 9, 2024
89ae0a4
Rename DenseSVDAdjoint, update svd_wrapper test
pbrehmer Jul 9, 2024
25d198c
Make CRCExt extension backwards compatible with v1.8
pbrehmer Jul 9, 2024
6b818e7
Replace SVDrrule with SVDAdjoint, clean up adjoint algorithms
pbrehmer Jul 9, 2024
bb96664
Small cleanup
lkdvos Jul 9, 2024
fa7a56a
Update minimal julia version 1.9
lkdvos Jul 10, 2024
6df8efc
Remove duplicate line in left_move
pbrehmer Jul 10, 2024
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
131 changes: 92 additions & 39 deletions src/utility/svd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,106 @@ using TensorKit: SectorDict
end

# Compute SVD data block-wise using KrylovKit algorithm
function TensorKit._tsvd!(t, alg::IterSVD, trunc::TruncationScheme, p::Real=2)
# TODO
end
function _tsvd!(
t,
alg::IterSVD,
trunc::Union{TensorKit.NoTruncation,TensorKit.TruncationSpace},
p::Real=2,
)
# early return
if isempty(blocksectors(t))
truncerr = zero(real(scalartype(t)))
return _empty_svdtensors(t)..., truncerr
end

Udata, Σdata, Vdata, dims = _compute_svddata!(t, alg, trunc)
U, S, V = _create_svdtensors(t, Udata, Σdata, Vdata, spacetype(t)(dims))
truncerr = trunc isa NoTruncation ? abs(zero(scalartype(t))) : norm(U * S * V - t, p)

# function TensorKit._compute_svddata!(t::TensorMap, alg::IterSVD)
# InnerProductStyle(t) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!)
# I = sectortype(t)
# A = storagetype(t)
# Udata = SectorDict{I,A}()
# Vdata = SectorDict{I,A}()
# dims = SectorDict{I,Int}()
# local Σdata
# for (c, b) in blocks(t)
# x₀ = randn(eltype(b), size(b, 1))
# Σ, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, alg.howmany, :LR, alg.alg)
# if info.converged < alg.howmany # Fall back to dense SVD if not properly converged
# U, Σ, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD())
# Udata[c] = U
# Vdata[c] = V
# else
# Udata[c] = stack(lvecs)
# Vdata[c] = stack(rvecs)'
# end
# if @isdefined Σdata # cannot easily infer the type of Σ, so use this construction
# Σdata[c] = Σ
# else
# Σdata = SectorDict(c => Σ)
# end
# dims[c] = length(Σ)
# end
# return Udata, Σdata, Vdata, dims
# end
return U, S, V, truncerr
end
function TensorKit._compute_svddata!(
t::TensorMap,
alg::IterSVD,
trunc::Union{TensorKit.NoTruncation,TensorKit.TruncationSpace},
)
InnerProductStyle(t) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!)
I = sectortype(t)
A = storagetype(t)
Udata = SectorDict{I,A}()
Vdata = SectorDict{I,A}()
dims = SectorDict{I,Int}()
local Σdata
for (c, b) in blocks(t)
x₀ = randn(eltype(b), size(b, 1))
howmany = trunc isa NoTruncation ? minimum(size(b)) : blockdim(trunc.space, c)
Σ, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg)
if info.converged < howmany # Fall back to dense SVD if not properly converged
pbrehmer marked this conversation as resolved.
Show resolved Hide resolved
U, Σ, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD())
Udata[c] = U
Vdata[c] = V
else
Udata[c] = stack(lvecs)
Vdata[c] = stack(rvecs)'
pbrehmer marked this conversation as resolved.
Show resolved Hide resolved
end
if @isdefined Σdata # cannot easily infer the type of Σ, so use this construction
Σdata[c] = Σ
else
Σdata = SectorDict(c => Σ)
end
dims[c] = length(Σ)
end
return Udata, Σdata, Vdata, dims
end

# IterSVD adjoint for tsvd! using KrylovKit.svdsolve adjoint machinery for each block
function ChainRulesCore.rrule(
::typeof(TensorKit.tsvd),
::typeof(TensorKit.tsvd!),
t::AbstractTensorMap;
trunc::TruncationScheme=notrunc(),
p::Real=2,
alg::IterSVD=IterSVD(),
pbrehmer marked this conversation as resolved.
Show resolved Hide resolved
)
# TODO: IterSVD adjoint utilizing KryloVKit svdsolve adjoint
end
U, S, V, ϵ = tsvd(t; trunc, p, alg)

function tsvd!_itersvd_pullback((ΔU, ΔS, ΔV, Δϵ))
Δt = similar(t)
for (c, b) in blocks(Δt)
Uc, Sc, Vc = block(U, c), block(S, c), block(V, c)
ΔUc, ΔSc, ΔVc = block(ΔU, c), block(ΔS, c), block(ΔV, c)
Sdc = view(Sc, diagind(Sc))
ΔSdc = (ΔSc isa AbstractZero) ? ΔSc : view(ΔSc, diagind(ΔSc))

lvecs = eachcol(Uc)
rvecs = eachrow(Vc)
minimal_info = KrylovKit.ConvergenceInfo(length(Sdc), nothing, nothing, -1, -1) # Just supply converged to SVD pullback
xs, ys = compute_svdsolve_pullback_data(
ΔSdc,
eachcol(ΔUc),
eachrow(ΔVc),
Sdc,
lvecs,
rvecs,
minimal_info,
b,
:LR,
alg.alg,
alg.alg_rrule,
)
copyto!(b, construct∂f_svd(config, b, lvecs, rvecs, xs, ys))
end
return NoTangent(), Δt
end
function tsvd!_itersvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent})
return NoTangent(), ZeroTangent()
end

return (U, S, V, ϵ), tsvd!_itersvd_pullback
end

# Full SVD with old adjoint that doesn't account for truncation properly
@kwdef struct OldSVD{A<:Union{FullSVD,IterSVD}}
alg::A = FullSVD()
@kwdef struct OldSVD{A<:Union{TensorKit.SDD,TensorKit.SVD,IterSVD}}
alg::A = TensorKit.SVD()
lorentz_broad::Float64 = 0.0
end

Expand All @@ -65,15 +118,15 @@ end

# Use outdated adjoint in reverse pass (not taking truncated part into account for testing purposes)
function ChainRulesCore.rrule(
::typeof(TensorKit.tsvd),
::typeof(TensorKit.tsvd!),
t::AbstractTensorMap;
trunc::TruncationScheme=notrunc(),
p::Real=2,
alg::OldSVD=OldSVD(),
)
U, S, V, ϵ = tsvd(t; trunc, p, alg)

function tsvd_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ))
function tsvd!_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ))
∂t = similar(t)
for (c, b) in blocks(∂t)
copyto!(
Expand All @@ -92,7 +145,7 @@ function ChainRulesCore.rrule(
return NoTangent(), ∂t, NoTangent()
end

return (U, S, V, ϵ), tsvd_oldsvd_pullback
return (U, S, V, ϵ), tsvd!_oldsvd_pullback
end

function oldsvd_rev(
Expand Down
Loading