diff --git a/README.md b/README.md index fee1a4f5..8f22e28d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ chi = 20 ctm_alg = SimultaneousCTMRG(; tol=1e-10, trscheme=truncdim(chi)) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, - optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=3), + optimizer_alg=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=3), gradient_alg=LinSolver(), reuse_env=true, ) diff --git a/docs/src/index.md b/docs/src/index.md index dd1d66e5..e6a6face 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -29,7 +29,7 @@ chi = 20 ctm_alg = SimultaneousCTMRG(; tol=1e-10, trscheme=truncdim(chi)) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, - optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=3), + optimizer_alg=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=3), gradient_alg=LinSolver(), reuse_env=true, ) diff --git a/examples/heisenberg.jl b/examples/heisenberg.jl index d54a7eb3..04394feb 100644 --- a/examples/heisenberg.jl +++ b/examples/heisenberg.jl @@ -14,8 +14,8 @@ H = heisenberg_XYZ(InfiniteSquare(); Jx=-1, Jy=1, Jz=-1) ctm_alg = SimultaneousCTMRG(; tol=1e-10, verbosity=2) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, - optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=3), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100)), + optimizer_alg=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=3), + gradient_alg=LinSolver(; solver_alg=GMRES(; tol=1e-6, maxiter=100)), reuse_env=true, ) diff --git a/src/Defaults.jl b/src/Defaults.jl new file mode 100644 index 00000000..d384f573 --- /dev/null +++ b/src/Defaults.jl @@ -0,0 +1,139 @@ +""" + module Defaults + +Module containing default algorithm parameter values and arguments. + +## CTMRG + +- `ctmrg_tol=$(Defaults.ctmrg_tol)` : Tolerance checking singular value and norm convergence. +- `ctmrg_maxiter=$(Defaults.ctmrg_maxiter)` : Maximal number of CTMRG iterations per run. +- `ctmrg_miniter=$(Defaults.ctmrg_miniter)` : Minimal number of CTMRG carried out. +- `ctmrg_alg=:$(Defaults.ctmrg_alg)` : Default CTMRG algorithm variant. +- `ctmrg_verbosity=$(Defaults.ctmrg_verbosity)` : CTMRG output information verbosity + +## SVD forward & reverse + +- `trscheme=:$(Defaults.trscheme)` : Truncation scheme for SVDs and other decompositions. +- `svd_fwd_alg=:$(Defaults.svd_fwd_alg)` : SVD algorithm that is used in the forward pass. +- `svd_rrule_tol=$(Defaults.svd_rrule_tol)` : Accuracy of SVD reverse-rule. +- `svd_rrule_min_krylovdim=$(Defaults.svd_rrule_min_krylovdim)` : Minimal Krylov dimension of the reverse-rule algorithm (if it is a Krylov algorithm). +- `svd_rrule_verbosity=$(Defaults.svd_rrule_verbosity)` : SVD gradient output verbosity. +- `svd_rrule_alg=:$(Defaults.svd_rrule_alg)` : Reverse-rule algorithm for the SVD gradient. + +## Projectors + +- `projector_alg=:$(Defaults.projector_alg)` : Default variant of the CTMRG projector algorithm. +- `projector_verbosity=$(Defaults.projector_verbosity)` : Projector output information verbosity. + +## Fixed-point gradient + +- `gradient_tol=$(Defaults.gradient_tol)` : Convergence tolerance for the fixed-point gradient iteration. +- `gradient_maxiter=$(Defaults.gradient_maxiter)` : Maximal number of iterations for computing the CTMRG fixed-point gradient. +- `gradient_verbosity=$(Defaults.gradient_verbosity)` : Gradient output information verbosity. +- `gradient_linsolver=:$(Defaults.gradient_linsolver)` : Default linear solver for the `LinSolver` gradient algorithm. +- `gradient_eigsolver=:$(Defaults.gradient_eigsolver)` : Default eigensolver for the `EigSolver` gradient algorithm. +- `gradient_eigsolver_eager=$(Defaults.gradient_eigsolver_eager)` : Enables `EigSolver` algorithm to finish before the full Krylov dimension is reached. +- `gradient_iterscheme=:$(Defaults.gradient_iterscheme)` : Scheme for differentiating one CTMRG iteration. +- `gradient_alg=:$(Defaults.gradient_alg)` : Algorithm variant for computing the gradient fixed-point. + +## Optimization + +- `reuse_env=$(Defaults.reuse_env)` : If `true`, the current optimization step is initialized on the previous environment, otherwise a random environment is used. +- `optimizer_tol=$(Defaults.optimizer_tol)` : Gradient norm tolerance of the optimizer. +- `optimizer_maxiter=$(Defaults.optimizer_maxiter)` : Maximal number of optimization steps. +- `optimizer_verbosity=$(Defaults.optimizer_verbosity)` : Optimizer output information verbosity. +- `optimizer_alg=:$(Defaults.optimizer_alg)` : Default `OptimKit.OptimizerAlgorithm` for PEPS optimization. +- `lbfgs_memory=$(Defaults.lbfgs_memory)` : Size of limited memory representation of BFGS Hessian matrix. + +## OhMyThreads scheduler + +- `scheduler=Ref{Scheduler}(...)` : Multi-threading scheduler which can be accessed via `set_scheduler!`. +""" +module Defaults + +export set_scheduler! + +using OhMyThreads + +# CTMRG +const ctmrg_tol = 1e-8 +const ctmrg_maxiter = 100 +const ctmrg_miniter = 4 +const ctmrg_alg = :simultaneous # ∈ {:simultaneous, :sequential} +const ctmrg_verbosity = 2 +const sparse = false # TODO: implement sparse CTMRG + +# SVD forward & reverse +const trscheme = :fixedspace # ∈ {:fixedspace, :notrunc, :truncerr, :truncspace, :truncbelow} +const svd_fwd_alg = :sdd # ∈ {:sdd, :svd, :iterative} +const svd_rrule_tol = ctmrg_tol +const svd_rrule_min_krylovdim = 48 +const svd_rrule_verbosity = -1 +const svd_rrule_alg = :arnoldi # ∈ {:gmres, :bicgstab, :arnoldi} +const krylovdim_factor = 1.4 + +# Projectors +const projector_alg = :halfinfinite # ∈ {:halfinfinite, :fullinfinite} +const projector_verbosity = 0 + +# Fixed-point gradient +const gradient_tol = 1e-6 +const gradient_maxiter = 30 +const gradient_verbosity = -1 +const gradient_linsolver = :bicgstab # ∈ {:gmres, :bicgstab} +const gradient_eigsolver = :arnoldi +const gradient_eigsolver_eager = true +const gradient_iterscheme = :fixed # ∈ {:fixed, :diffgauge} +const gradient_alg = :linsolver # ∈ {:geomsum, :manualiter, :linsolver, :eigsolver} + +# Optimization +const reuse_env = true +const optimizer_tol = 1e-4 +const optimizer_maxiter = 100 +const optimizer_verbosity = 3 +const optimizer_alg = :lbfgs +const lbfgs_memory = 20 + +# OhMyThreads scheduler defaults +const scheduler = Ref{Scheduler}() + +""" + set_scheduler!([scheduler]; kwargs...) + +Set `OhMyThreads` multi-threading scheduler parameters. + +The function either accepts a `scheduler` as an `OhMyThreads.Scheduler` or +as a symbol where the corresponding parameters are specificed as keyword arguments. +For instance, a static scheduler that uses four tasks with chunking enabled +can be set via +``` +set_scheduler!(StaticScheduler(; ntasks=4, chunking=true)) +``` +or equivalently with +``` +set_scheduler!(:static; ntasks=4, chunking=true) +``` +For a detailed description of all schedulers and their keyword arguments consult the +[`OhMyThreads` documentation](https://juliafolds2.github.io/OhMyThreads.jl/stable/refs/api/#Schedulers). + +If no `scheduler` is passed and only kwargs are provided, the `DynamicScheduler` +constructor is used with the provided kwargs. + +To reset the scheduler to its default value, one calls `set_scheduler!` without passing +arguments which then uses the default `DynamicScheduler()`. If the number of used threads is +just one it falls back to `SerialScheduler()`. +""" +function set_scheduler!(sc=OhMyThreads.Implementation.NotGiven(); kwargs...) + if isempty(kwargs) && sc isa OhMyThreads.Implementation.NotGiven + scheduler[] = Threads.nthreads() == 1 ? SerialScheduler() : DynamicScheduler() + else + scheduler[] = OhMyThreads.Implementation._scheduler_from_userinput(sc; kwargs...) + end + return nothing +end + +function __init__() + return set_scheduler!() +end + +end diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index f7be8d30..eefef3ff 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -1,18 +1,19 @@ module PEPSKit using LinearAlgebra, Statistics, Base.Threads, Base.Iterators, Printf -using Base: @kwdef using Compat using Accessors: @set, @reset using VectorInterface using TensorKit, KrylovKit, MPSKit, OptimKit, TensorOperations using ChainRulesCore, Zygote using LoggingExtras -using MPSKit: loginit!, logiter!, logfinish!, logcancel! +import MPSKit: leading_boundary, loginit!, logiter!, logfinish!, logcancel! using MPSKitModels using FiniteDifferences using OhMyThreads: tmap +include("Defaults.jl") # Include first to allow for docstring interpolation with Defaults values + include("utility/util.jl") include("utility/diffable_threads.jl") include("utility/svd.jl") @@ -65,147 +66,7 @@ include("utility/symmetrization.jl") include("algorithms/optimization/fixed_point_differentiation.jl") include("algorithms/optimization/peps_optimization.jl") -""" - module Defaults - -Module containing default algorithm parameter values and arguments. - -# CTMRG -- `ctmrg_tol=1e-8`: Tolerance checking singular value and norm convergence -- `ctmrg_maxiter=100`: Maximal number of CTMRG iterations per run -- `ctmrg_miniter=4`: Minimal number of CTMRG carried out -- `trscheme=FixedSpaceTruncation()`: Truncation scheme for SVDs and other decompositions -- `fwd_alg=TensorKit.SDD()`: SVD algorithm that is used in the forward pass -- `rrule_alg`: Reverse-rule for differentiating that SVD - - ``` - rrule_alg = Arnoldi(; tol=ctmrg_tol, krylovdim=48, verbosity=-1) - ``` - -- `svd_alg=SVDAdjoint(; fwd_alg, rrule_alg)`: Combination of `fwd_alg` and `rrule_alg` -- `projector_alg_type=HalfInfiniteProjector`: Default type of projector algorithm -- `projector_alg`: Algorithm to compute CTMRG projectors - - ``` - projector_alg = projector_alg_type(; svd_alg, trscheme, verbosity=0) - ``` - -- `ctmrg_alg`: Algorithm for performing CTMRG runs - - ``` - ctmrg_alg = SimultaneousCTMRG( - ctmrg_tol, ctmrg_maxiter, ctmrg_miniter, 2, projector_alg - ) - ``` - -# Optimization -- `fpgrad_maxiter=30`: Maximal number of iterations for computing the CTMRG fixed-point gradient -- `fpgrad_tol=1e-6`: Convergence tolerance for the fixed-point gradient iteration -- `iterscheme=:fixed`: Scheme for differentiating one CTMRG iteration -- `gradient_linsolver`: Default linear solver for the `LinSolver` gradient algorithm - - ``` - gradient_linsolver=KrylovKit.BiCGStab(; maxiter=fpgrad_maxiter, tol=fpgrad_tol) - ``` - -- `gradient_eigsolve`: Default eigsolver for the `EigSolver` gradient algorithm - - ``` - gradient_eigsolver = KrylovKit.Arnoldi(; maxiter=fpgrad_maxiter, tol=fpgrad_tol, eager=true) - ``` - -- `gradient_alg`: Algorithm to compute the gradient fixed-point - - ``` - gradient_alg = LinSolver(; solver=gradient_linsolver, iterscheme) - ``` - -- `reuse_env=true`: If `true`, the current optimization step is initialized on the previous environment -- `optimizer=LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=3)`: Default `OptimKit.OptimizerAlgorithm` for PEPS optimization - -# OhMyThreads scheduler -- `scheduler=Ref{Scheduler}(...)`: Multi-threading scheduler which can be accessed via `set_scheduler!` -""" -module Defaults - using TensorKit, KrylovKit, OptimKit, OhMyThreads - using PEPSKit: - LinSolver, - FixedSpaceTruncation, - SVDAdjoint, - HalfInfiniteProjector, - SimultaneousCTMRG - - # CTMRG - const ctmrg_tol = 1e-8 - const ctmrg_maxiter = 100 - const ctmrg_miniter = 4 - const sparse = false - const trscheme = FixedSpaceTruncation() - const fwd_alg = TensorKit.SDD() - const rrule_alg = Arnoldi(; tol=ctmrg_tol, krylovdim=48, verbosity=-1) - const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) - const projector_alg_type = HalfInfiniteProjector - const projector_alg = projector_alg_type(; svd_alg, trscheme, verbosity=0) - const ctmrg_alg = SimultaneousCTMRG( - ctmrg_tol, ctmrg_maxiter, ctmrg_miniter, 2, projector_alg - ) - - # Optimization - const fpgrad_maxiter = 30 - const fpgrad_tol = 1e-6 - const gradient_linsolver = KrylovKit.BiCGStab(; maxiter=fpgrad_maxiter, tol=fpgrad_tol) - const gradient_eigsolver = KrylovKit.Arnoldi(; - maxiter=fpgrad_maxiter, tol=fpgrad_tol, eager=true - ) - const iterscheme = :fixed - const gradient_alg = LinSolver(; solver=gradient_linsolver, iterscheme) - const reuse_env = true - const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=3) - - # OhMyThreads scheduler defaults - const scheduler = Ref{Scheduler}() - """ - set_scheduler!([scheduler]; kwargs...) - - Set `OhMyThreads` multi-threading scheduler parameters. - - The function either accepts a `scheduler` as an `OhMyThreads.Scheduler` or - as a symbol where the corresponding parameters are specificed as keyword arguments. - For instance, a static scheduler that uses four tasks with chunking enabled - can be set via - ``` - set_scheduler!(StaticScheduler(; ntasks=4, chunking=true)) - ``` - or equivalently with - ``` - set_scheduler!(:static; ntasks=4, chunking=true) - ``` - For a detailed description of all schedulers and their keyword arguments consult the - [`OhMyThreads` documentation](https://juliafolds2.github.io/OhMyThreads.jl/stable/refs/api/#Schedulers). - - If no `scheduler` is passed and only kwargs are provided, the `DynamicScheduler` - constructor is used with the provided kwargs. - - To reset the scheduler to its default value, one calls `set_scheduler!` without passing - arguments which then uses the default `DynamicScheduler()`. If the number of used threads is - just one it falls back to `SerialScheduler()`. - """ - function set_scheduler!(sc=OhMyThreads.Implementation.NotGiven(); kwargs...) - if isempty(kwargs) && sc isa OhMyThreads.Implementation.NotGiven - scheduler[] = Threads.nthreads() == 1 ? SerialScheduler() : DynamicScheduler() - else - scheduler[] = OhMyThreads.Implementation._scheduler_from_userinput( - sc; kwargs... - ) - end - return nothing - end - export set_scheduler! - - function __init__() - return set_scheduler!() - end -end +include("algorithms/select_algorithm.jl") using .Defaults: set_scheduler! export set_scheduler! diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 6ae10af9..14b6f519 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -7,38 +7,63 @@ for contracting infinite PEPS. abstract type CTMRGAlgorithm end """ - ctmrg_iteration(state, env, alg::CTMRGAlgorithm) -> env′, info + ctmrg_iteration(network, env, alg::CTMRGAlgorithm) -> env′, info Perform a single CTMRG iteration in which all directions are being grown and renormalized. """ -function ctmrg_iteration(state, env, alg::CTMRGAlgorithm) end +function ctmrg_iteration(network, env, alg::CTMRGAlgorithm) end """ - MPSKit.leading_boundary([envinit], state, alg::CTMRGAlgorithm) - -Contract `state` using CTMRG and return the CTM environment. Per default, a random -initial environment is used. - -Each CTMRG run is converged up to `alg.tol` where the singular value convergence -of the corners and edges is checked. The maximal and minimal number of CTMRG -iterations is set with `alg.maxiter` and `alg.miniter`. - -Different levels of output information are printed depending on `alg.verbosity`, where `0` -suppresses all output, `1` only prints warnings, `2` gives information at the start and -end, and `3` prints information every iteration. + leading_boundary(env₀, network; kwargs...) + # expert version: + leading_boundary(env₀, network, alg::CTMRGAlgorithm) + +Contract `network` using CTMRG and return the CTM environment. The algorithm can be +supplied via the keyword arguments or directly as an [`CTMRGAlgorithm`](@ref) struct. + +## Keyword arguments + +### CTMRG iterations + +* `tol::Real=$(Defaults.ctmrg_tol)`: Stopping criterium for the CTMRG iterations. This is the norm convergence, as well as the distance in singular values of the corners and edges. +* `miniter::Int=$(Defaults.ctmrg_miniter)`: Minimal number of CTMRG iterations. +* `maxiter::Int=$(Defaults.ctmrg_maxiter)`: Maximal number of CTMRG iterations. +* `verbosity::Int=$(Defaults.ctmrg_verbosity)`: Output verbosity level, should be one of the following: + 0. Suppress all output + 1. Only print warnings + 2. Initialization and convergence info + 3. Iteration info + 4. Debug info +* `alg::Symbol=:$(Defaults.ctmrg_alg)`: Variant of the CTMRG algorithm. See also [`CTMRGAlgorithm`](@ref). + +### Projector algorithm + +* `trscheme::Union{TruncationScheme,NamedTuple}=(; alg::Symbol=:$(Defaults.trscheme))`: Truncation scheme for the projector computation, which controls the resulting virtual spaces. Here, `alg` can be one of the following: + - `:fixedspace`: Keep virtual spaces fixed during projection + - `:notrunc`: No singular values are truncated and the performed SVDs are exact + - `:truncerr`: Additionally supply error threshold `η`; truncate to the maximal virtual dimension of `η` + - `:truncdim`: Additionally supply truncation dimension `η`; truncate such that the 2-norm of the truncated values is smaller than `η` + - `:truncspace`: Additionally supply truncation space `η`; truncate according to the supplied vector space + - `:truncbelow`: Additionally supply singular value cutoff `η`; truncate such that every retained singular value is larger than `η` +* `svd_alg::Union{<:SVDAdjoint,NamedTuple}`: SVD algorithm for computing projectors. See also [`SVDAdjoint`](@ref). By default, a reverse-rule tolerance of `tol=1e1tol` where the `krylovdim` is adapted to the `env₀` environment dimension. +* `projector_alg::Symbol=:$(Defaults.projector_alg)`: Variant of the projector algorithm. See also [`ProjectorAlgorithm`](@ref). """ -function MPSKit.leading_boundary( - envinit, network::InfiniteSquareNetwork, alg::CTMRGAlgorithm +function leading_boundary(env₀::CTMRGEnv, network::InfiniteSquareNetwork; kwargs...) + alg = select_algorithm(leading_boundary, env₀; kwargs...) + return leading_boundary(env₀, network, alg) +end +function leading_boundary( + env₀::CTMRGEnv, network::InfiniteSquareNetwork, alg::CTMRGAlgorithm ) - CS = map(x -> tsvd(x)[2], envinit.corners) - TS = map(x -> tsvd(x)[2], envinit.edges) + CS = map(x -> tsvd(x)[2], env₀.corners) + TS = map(x -> tsvd(x)[2], env₀.edges) η = one(real(scalartype(network))) - env = deepcopy(envinit) + env = deepcopy(env₀) log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) return LoggingExtras.withlevel(; alg.verbosity) do - ctmrg_loginit!(log, η, network, envinit) + ctmrg_loginit!(log, η, network, env₀) local info for iter in 1:(alg.maxiter) env, info = ctmrg_iteration(network, env, alg) # Grow and renormalize in all 4 directions @@ -57,8 +82,8 @@ function MPSKit.leading_boundary( return env, info end end -function MPSKit.leading_boundary(envinit, state, alg::CTMRGAlgorithm) - return MPSKit.leading_boundary(envinit, InfiniteSquareNetwork(state), alg) +function leading_boundary(env₀::CTMRGEnv, state, args...; kwargs...) + return leading_boundary(env₀, InfiniteSquareNetwork(state), args...; kwargs...) end # custom CTMRG logging diff --git a/src/algorithms/ctmrg/projectors.jl b/src/algorithms/ctmrg/projectors.jl index 61965f68..130748e8 100644 --- a/src/algorithms/ctmrg/projectors.jl +++ b/src/algorithms/ctmrg/projectors.jl @@ -1,5 +1,5 @@ """ - FixedSpaceTruncation <: TensorKit.TruncationScheme + struct FixedSpaceTruncation <: TensorKit.TruncationScheme CTMRG specific truncation scheme for `tsvd` which keeps the bond space on which the SVD is performed fixed. Since different environment directions and unit cell entries might @@ -33,27 +33,59 @@ function truncation_scheme(alg::ProjectorAlgorithm, edge) end """ - struct HalfInfiniteProjector{S,T}(; svd_alg=Defaults.svd_alg, - trscheme=Defaults.trscheme, verbosity=0) + struct HalfInfiniteProjector{S,T} Projector algorithm implementing projectors from SVDing the half-infinite CTMRG environment. + +## Keyword arguments + +* `svd_alg::Union{<:SVDAdjoint,NamedTuple}=SVDAdjoint()`: SVD algorithm including the reverse rule. See ['SVDAdjoint'](@ref). +* `trscheme::Union{TruncationScheme,NamedTuple}=(; alg::Symbol=:$(Defaults.trscheme))`: Truncation scheme for the projector computation, which controls the resulting virtual spaces. Here, `alg` can be one of the following: + - `:fixedspace`: Keep virtual spaces fixed during projection + - `:notrunc`: No singular values are truncated and the performed SVDs are exact + - `:truncerr`: Additionally supply error threshold `η`; truncate to the maximal virtual dimension of `η` + - `:truncdim`: Additionally supply truncation dimension `η`; truncate such that the 2-norm of the truncated values is smaller than `η` + - `:truncspace`: Additionally supply truncation space `η`; truncate according to the supplied vector space + - `:truncbelow`: Additionally supply singular value cutoff `η`; truncate such that every retained singular value is larger than `η` +* `verbosity::Int=$(Defaults.projector_verbosity)`: Projector output verbosity which can be: + 0. Suppress output information + 1. Print singular value degeneracy warnings """ -@kwdef struct HalfInfiniteProjector{S<:SVDAdjoint,T} <: ProjectorAlgorithm - svd_alg::S = Defaults.svd_alg - trscheme::T = Defaults.trscheme - verbosity::Int = 0 +struct HalfInfiniteProjector{S<:SVDAdjoint,T} <: ProjectorAlgorithm + svd_alg::S + trscheme::T + verbosity::Int +end +function HalfInfiniteProjector(; kwargs...) + return select_algorithm(ProjectorAlgorithm; alg=:halfinfinite, kwargs...) end """ - struct FullInfiniteProjector{S,T}(; svd_alg=Defaults.svd_alg, - trscheme=Defaults.trscheme, verbosity=0) + struct FullInfiniteProjector{S,T} Projector algorithm implementing projectors from SVDing the full 4x4 CTMRG environment. + +## Keyword arguments + +* `svd_alg::Union{<:SVDAdjoint,NamedTuple}=SVDAdjoint()`: SVD algorithm including the reverse rule. See ['SVDAdjoint'](@ref). +* `trscheme::Union{TruncationScheme,NamedTuple}=(; alg::Symbol=:$(Defaults.trscheme))`: Truncation scheme for the projector computation, which controls the resulting virtual spaces. Here, `alg` can be one of the following: + - `:fixedspace`: Keep virtual spaces fixed during projection + - `:notrunc`: No singular values are truncated and the performed SVDs are exact + - `:truncerr`: Additionally supply error threshold `η`; truncate to the maximal virtual dimension of `η` + - `:truncdim`: Additionally supply truncation dimension `η`; truncate such that the 2-norm of the truncated values is smaller than `η` + - `:truncspace`: Additionally supply truncation space `η`; truncate according to the supplied vector space + - `:truncbelow`: Additionally supply singular value cutoff `η`; truncate such that every retained singular value is larger than `η` +* `verbosity::Int=$(Defaults.projector_verbosity)`: Projector output verbosity which can be: + 0. Suppress output information + 1. Print singular value degeneracy warnings """ -@kwdef struct FullInfiniteProjector{S<:SVDAdjoint,T} <: ProjectorAlgorithm - svd_alg::S = Defaults.svd_alg - trscheme::T = Defaults.trscheme - verbosity::Int = 0 +struct FullInfiniteProjector{S<:SVDAdjoint,T} <: ProjectorAlgorithm + svd_alg::S + trscheme::T + verbosity::Int +end +function FullInfiniteProjector(; kwargs...) + return select_algorithm(ProjectorAlgorithm; alg=:fullinfinite, kwargs...) end # TODO: add `LinearAlgebra.cond` to TensorKit diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index d343cc42..8b02d9c2 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -1,13 +1,24 @@ """ - SequentialCTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, - miniter=Defaults.ctmrg_miniter, verbosity=0, - projector_alg=typeof(Defaults.projector_alg), - svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation()) + SequentialCTMRG(; tol=$(Defaults.ctmrg_tol), maxiter=$(Defaults.ctmrg_maxiter), + miniter=$(Defaults.ctmrg_miniter), verbosity=$(Defaults.ctmrg_verbosity), + svd_alg=TODO, trscheme=TODO, + projector_alg=TODO) CTMRG algorithm where the expansions and renormalization is performed sequentially column-wise. This is implemented as a growing and projecting step to the left, followed by -a clockwise rotation (performed four times). The projectors are computed using -`projector_alg` from `svd_alg` SVDs where the truncation scheme is set via `trscheme`. +a clockwise rotation (performed four times). + +## Keyword arguments + +For a full description, see [`leading_boundary`](@ref). The supported keywords are: + +* `tol::Real=$(Defaults.ctmrg_tol)` +* `maxiter::Int=$(Defaults.ctmrg_maxiter)` +* `miniter::Int=$(Defaults.ctmrg_miniter)` +* `verbosity::Int=$(Defaults.ctmrg_verbosity)` +* `trscheme::Union{TruncationScheme,NamedTuple}=(; alg::Symbol=:$(Defaults.trscheme))` +* `svd_alg::Union{<:SVDAdjoint,NamedTuple}` +* `projector_alg::Symbol=:$(Defaults.projector_alg)` """ struct SequentialCTMRG <: CTMRGAlgorithm tol::Float64 @@ -16,18 +27,8 @@ struct SequentialCTMRG <: CTMRGAlgorithm verbosity::Int projector_alg::ProjectorAlgorithm end -function SequentialCTMRG(; - tol=Defaults.ctmrg_tol, - maxiter=Defaults.ctmrg_maxiter, - miniter=Defaults.ctmrg_miniter, - verbosity=2, - projector_alg=Defaults.projector_alg_type, - svd_alg=Defaults.svd_alg, - trscheme=Defaults.trscheme, -) - return SequentialCTMRG( - tol, maxiter, miniter, verbosity, projector_alg(; svd_alg, trscheme, verbosity) - ) +function SequentialCTMRG(; kwargs...) + return select_algorithm(CTMRGAlgorithm; alg=:sequential, kwargs...) end """ diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index 0d9431b8..04ecf56b 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -1,13 +1,20 @@ """ - SimultaneousCTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, - miniter=Defaults.ctmrg_miniter, verbosity=0, - projector_alg=Defaults.projector_alg, - svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation()) + struct SimultaneousCTMRG CTMRG algorithm where all sides are grown and renormalized at the same time. In particular, -the projectors are applied to the corners from two sides simultaneously. The projectors are -computed using `projector_alg` from `svd_alg` SVDs where the truncation scheme is set via -`trscheme`. +the projectors are applied to the corners from two sides simultaneously. + +## Keyword arguments + +For a full description, see [`leading_boundary`](@ref). The supported keywords are: + +* `tol::Real=$(Defaults.ctmrg_tol)` +* `maxiter::Int=$(Defaults.ctmrg_maxiter)` +* `miniter::Int=$(Defaults.ctmrg_miniter)` +* `verbosity::Int=$(Defaults.ctmrg_verbosity)` +* `trscheme::Union{TruncationScheme,NamedTuple}=(; alg::Symbol=:$(Defaults.trscheme))` +* `svd_alg::Union{<:SVDAdjoint,NamedTuple}` +* `projector_alg::Symbol=:$(Defaults.projector_alg)` """ struct SimultaneousCTMRG <: CTMRGAlgorithm tol::Float64 @@ -16,18 +23,8 @@ struct SimultaneousCTMRG <: CTMRGAlgorithm verbosity::Int projector_alg::ProjectorAlgorithm end -function SimultaneousCTMRG(; - tol=Defaults.ctmrg_tol, - maxiter=Defaults.ctmrg_maxiter, - miniter=Defaults.ctmrg_miniter, - verbosity=2, - projector_alg=Defaults.projector_alg_type, - svd_alg=Defaults.svd_alg, - trscheme=Defaults.trscheme, -) - return SimultaneousCTMRG( - tol, maxiter, miniter, verbosity, projector_alg(; svd_alg, trscheme, verbosity) - ) +function SimultaneousCTMRG(; kwargs...) + return select_algorithm(CTMRGAlgorithm; alg=:simultaneous, kwargs...) end function ctmrg_iteration(network, env::CTMRGEnv, alg::SimultaneousCTMRG) diff --git a/src/algorithms/optimization/fixed_point_differentiation.jl b/src/algorithms/optimization/fixed_point_differentiation.jl index 93837725..a4856f6e 100644 --- a/src/algorithms/optimization/fixed_point_differentiation.jl +++ b/src/algorithms/optimization/fixed_point_differentiation.jl @@ -3,97 +3,97 @@ abstract type GradMode{F} end iterscheme(::GradMode{F}) where {F} = F """ - struct GeomSum(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, - verbosity=0, iterscheme=Defaults.iterscheme) <: GradMode{iterscheme} + struct GeomSum <: GradMode{iterscheme} Gradient mode for CTMRG using explicit evaluation of the geometric sum. -With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. -If set to `:fixed`, the differentiated CTMRG iteration is assumed to have a pre-computed -SVD of the environments with a fixed set of gauges. Alternatively, if set to `:diffgauge`, -the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, -such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. +## Keyword arguments + +* `tol::Real=$(Defaults.gradient_tol)`: Convergence tolerance for the difference of norms of two consecutive summands in the geometric sum. +* `maxiter::Int=$(Defaults.gradient_maxiter)`: Maximal number of gradient iterations. +* `verbosity::Int=$(Defaults.gradient_verbosity)`: Output information verbosity that can be one of the following: + 0. Suppress output information + 1. Print convergence warnings + 2. Information at each gradient iteration +* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)`: Style of CTMRG iteration which is being differentiated, which can be: + - `:fixed`: the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges + - `:diffgauge`: the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well """ struct GeomSum{F} <: GradMode{F} - maxiter::Int tol::Real + maxiter::Int verbosity::Int end -function GeomSum(; - maxiter=Defaults.fpgrad_maxiter, - tol=Defaults.fpgrad_tol, - verbosity=0, - iterscheme=Defaults.iterscheme, -) - return GeomSum{iterscheme}(maxiter, tol, verbosity) -end +GeomSum(; kwargs...) = select_algorithm(GradMode; alg=:geomsum, kwargs...) """ - struct ManualIter(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, - verbosity=0, iterscheme=Defaults.iterscheme) <: GradMode{iterscheme} + struct ManualIter <: GradMode{iterscheme} Gradient mode for CTMRG using manual iteration to solve the linear problem. -With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. -If set to `:fixed`, the differentiated CTMRG iteration is assumed to have a pre-computed -SVD of the environments with a fixed set of gauges. Alternatively, if set to `:diffgauge`, -the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, -such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. +## Keyword arguments + +* `tol::Real=$(Defaults.gradient_tol)`: Convergence tolerance for the norm difference of two consecutive `dx` contributions. +* `maxiter::Int=$(Defaults.gradient_maxiter)`: Maximal number of gradient iterations. +* `verbosity::Int=$(Defaults.gradient_verbosity)`: Output information verbosity that can be one of the following: + 0. Suppress output information + 1. Print convergence warnings + 2. Information at each gradient iteration +* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)`: Style of CTMRG iteration which is being differentiated, which can be: + - `:fixed`: the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges + - `:diffgauge`: the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well """ struct ManualIter{F} <: GradMode{F} - maxiter::Int tol::Real + maxiter::Int verbosity::Int end -function ManualIter(; - maxiter=Defaults.fpgrad_maxiter, - tol=Defaults.fpgrad_tol, - verbosity=0, - iterscheme=Defaults.iterscheme, -) - return ManualIter{iterscheme}(maxiter, tol, verbosity) -end +ManualIter(; kwargs...) = select_algorithm(GradMode; alg=:manualiter, kwargs...) """ - struct LinSolver(; solver=KrylovKit.GMRES(), iterscheme=Defaults.iterscheme) <: GradMode{iterscheme} + struct LinSolver <: GradMode{iterscheme} Gradient mode wrapper around `KrylovKit.LinearSolver` for solving the gradient linear problem using iterative solvers. -With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. -If set to `:fixed`, the differentiated CTMRG iteration is assumed to have a pre-computed -SVD of the environments with a fixed set of gauges. Alternatively, if set to `:diffgauge`, -the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, -such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. +## Keyword arguments + +* `tol::Real=$(Defaults.gradient_tol)`: Convergence tolerance of the linear solver. +* `maxiter::Int=$(Defaults.gradient_maxiter)`: Maximal number of solver iterations. +* `verbosity::Int=$(Defaults.gradient_verbosity)`: Output information verbosity of the linear solver. +* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)`: Style of CTMRG iteration which is being differentiated, which can be: + - `:fixed`: the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges + - `:diffgauge`: the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well +* `solver_alg::Union{KrylovKit.LinearSolver,NamedTuple}=(; alg::Symbol=:$(Defaults.gradient_linsolver)`: Linear solver algorithm which, if supplied directly as a `KrylovKit.LinearSolver` overrides the above specified `tol`, `maxiter` and `verbosity`. Alternatively, it can be supplied via a `NamedTuple` where `alg` can be one of the following: + - `:gmres`: GMRES iterative linear solver, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.GMRES) for details + - `:bicgstab`: BiCGStab iterative linear solver, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.BiCGStab) for details """ struct LinSolver{F} <: GradMode{F} - solver::KrylovKit.LinearSolver -end -function LinSolver(; - solver=KrylovKit.BiCGStab(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol), - iterscheme=Defaults.iterscheme, -) - return LinSolver{iterscheme}(solver) + solver_alg::KrylovKit.LinearSolver end +LinSolver(; kwargs...) = select_algorithm(GradMode; alg=:linsolver, kwargs...) """ - struct EigSolver(; solver=KrylovKit.Arnoldi(), iterscheme=Defaults.iterscheme) <: GradMode{iterscheme} + struct EigSolver <: GradMode{iterscheme} Gradient mode wrapper around `KrylovKit.KrylovAlgorithm` for solving the gradient linear problem as an eigenvalue problem. -With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. -If set to `:fixed`, the differentiated CTMRG iteration is assumed to have a pre-computed -SVD of the environments with a fixed set of gauges. Alternatively, if set to `:diffgauge`, -the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, -such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. +## Keyword arguments + +* `tol::Real=$(Defaults.gradient_tol)`: Convergence tolerance of the linear solver. +* `maxiter::Int=$(Defaults.gradient_maxiter)`: Maximal number of solver iterations. +* `verbosity::Int=$(Defaults.gradient_verbosity)`: Output information verbosity of the linear solver. +* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)`: Style of CTMRG iteration which is being differentiated, which can be: + - `:fixed`: the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges + - `:diffgauge`: the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well +* `solver_alg::Union{KrylovKit.KrylovAlgorithm,NamedTuple}=(; alg=:$(Defaults.gradient_eigsolver)`: Eigen solver algorithm which, if supplied directly as a `KrylovKit.KrylovAlgorithm` overrides the above specified `tol`, `maxiter` and `verbosity`. Alternatively, it can be supplied via a `NamedTuple` where `alg` can be one of the following: + - `:arnoldi`: Arnoldi Krylov algorithm, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.Arnoldi) for details """ struct EigSolver{F} <: GradMode{F} - solver::KrylovKit.KrylovAlgorithm -end -function EigSolver(; solver=Defauls.gradient_eigsolver, iterscheme=Defaults.iterscheme) - return EigSolver{iterscheme}(solver) + solver_alg::KrylovKit.KrylovAlgorithm end +EigSolver(; kwargs...) = select_algorithm(GradMode; alg=:eigsolver, kwargs...) #= Evaluating the gradient of the cost function for CTMRG: @@ -105,7 +105,7 @@ Evaluating the gradient of the cost function for CTMRG: function _rrule( gradmode::GradMode{:diffgauge}, config::RuleConfig, - ::typeof(MPSKit.leading_boundary), + ::typeof(leading_boundary), envinit, state, alg::CTMRGAlgorithm, @@ -242,8 +242,8 @@ function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y₀, alg::ManualIter) end function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y₀, alg::LinSolver) - y, info = reallinsolve(∂f∂x, ∂F∂x, y₀, alg.solver, 1, -1) - if alg.solver.verbosity > 0 && info.converged != 1 + y, info = reallinsolve(∂f∂x, ∂F∂x, y₀, alg.solver_alg, 1, -1) + if alg.solver_alg.verbosity > 0 && info.converged != 1 @warn("gradient fixed-point iteration reached maximal number of iterations:", info) end @@ -256,11 +256,11 @@ function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, x₀, alg::EigSolver) return (y + X[2] * ∂F∂x, X[2]) end X₀ = (x₀, one(scalartype(x₀))) - vals, vecs, info = realeigsolve(f, X₀, 1, :LM, alg.solver) - if alg.solver.verbosity > 0 && info.converged < 1 + vals, vecs, info = realeigsolve(f, X₀, 1, :LM, alg.solver_alg) + if alg.solver_alg.verbosity > 0 && info.converged < 1 @warn("gradient fixed-point iteration reached maximal number of iterations:", info) end - if norm(vecs[1][2]) < 1e-2 * alg.solver.tol + if norm(vecs[1][2]) < 1e-2 * alg.solver_alg.tol @warn "Fixed-point gradient computation using Arnoldi failed: auxiliary component should be finite but was $(vecs[1][2]). Possibly the Jacobian does not have a unique eigenvalue 1." end y = scale(vecs[1][1], 1 / vecs[1][2]) diff --git a/src/algorithms/optimization/peps_optimization.jl b/src/algorithms/optimization/peps_optimization.jl index aecb2132..c6cf63cb 100644 --- a/src/algorithms/optimization/peps_optimization.jl +++ b/src/algorithms/optimization/peps_optimization.jl @@ -1,78 +1,136 @@ """ - PEPSOptimize{G}(; boundary_alg=Defaults.ctmrg_alg, optimizer::OptimKit.OptimizationAlgorithm=Defaults.optimizer - reuse_env::Bool=true, gradient_alg::G=Defaults.gradient_alg) - -Algorithm struct that represent PEPS ground-state optimization using AD. -Set the algorithm to contract the infinite PEPS in `boundary_alg`; -currently only `CTMRGAlgorithm`s are supported. The `optimizer` computes the gradient directions -based on the CTMRG gradient and updates the PEPS parameters. In this optimization, -the CTMRG runs can be started on the converged environments of the previous optimizer -step by setting `reuse_env` to true. Otherwise a random environment is used at each -step. The CTMRG gradient itself is computed using the `gradient_alg` algorithm. + struct PEPSOptimize{G} + +Algorithm struct for PEPS ground-state optimization using AD. See [`fixedpoint`](@ref) for details. + +## Keyword arguments + +* `boundary_alg::Union{NamedTuple,<:CTMRGAlgorithm}`: Supply boundary algorithm parameters using either a `NamedTuple` of keyword arguments or a `CTMRGAlgorithm` directly. See [`leading_boundary`](@ref) for a description of all possible keyword arguments. +* `gradient_alg::Union{NamedTuple,Nothing,<:GradMode}`: Supply gradient algorithm parameters using either a `NamedTuple` of keyword arguments, `nothing`, or a `GradMode` directly. See [`fixedpoint`](@ref) for a description of all possible keyword arguments. +* `optimizer_alg::Union{NamedTuple,<:OptimKit.OptimizationAlgorithm}`: Supply optimizer algorithm parameters using either a `NamedTuple` of keyword arguments, or a `OptimKit.OptimizationAlgorithm` directly. See [`fixedpoint`](@ref) for a description of all possible keyword arguments. +* `reuse_env::Bool=$(Defaults.reuse_env)`: If `true`, the current optimization step is initialized on the previous environment, otherwise a random environment is used. +* `symmetrization::Union{Nothing,SymmetrizationStyle}=nothing`: Accepts `nothing` or a `SymmetrizationStyle`, in which case the PEPS and PEPS gradient are symmetrized after each optimization iteration. """ struct PEPSOptimize{G} boundary_alg::CTMRGAlgorithm gradient_alg::G - optimizer::OptimKit.OptimizationAlgorithm + optimizer_alg::OptimKit.OptimizationAlgorithm reuse_env::Bool symmetrization::Union{Nothing,SymmetrizationStyle} function PEPSOptimize( # Inner constructor to prohibit illegal setting combinations boundary_alg::CTMRGAlgorithm, gradient_alg::G, - optimizer, + optimizer_alg, reuse_env, symmetrization, ) where {G} if gradient_alg isa GradMode if boundary_alg isa SequentialCTMRG && iterscheme(gradient_alg) === :fixed - throw(ArgumentError(":sequential and :fixed are not compatible")) + msg = ":fixed was converted to :diffgauge since SequentialCTMRG does not \ + support :fixed differentiation mode due to sequential application of \ + SVDs; select SimultaneousCTMRG instead to use :fixed mode" + throw(ArgumentError(msg)) end end - return new{G}(boundary_alg, gradient_alg, optimizer, reuse_env, symmetrization) + return new{G}(boundary_alg, gradient_alg, optimizer_alg, reuse_env, symmetrization) end end -function PEPSOptimize(; - boundary_alg=Defaults.ctmrg_alg, - gradient_alg=Defaults.gradient_alg, - optimizer=Defaults.optimizer, - reuse_env=Defaults.reuse_env, - symmetrization=nothing, -) - return PEPSOptimize(boundary_alg, gradient_alg, optimizer, reuse_env, symmetrization) -end +PEPSOptimize(; kwargs...) = select_algorithm(PEPSOptimize; kwargs...) """ fixedpoint(operator, peps₀::InfinitePEPS, env₀::CTMRGEnv; kwargs...) + # expert version: fixedpoint(operator, peps₀::InfinitePEPS, env₀::CTMRGEnv, alg::PEPSOptimize; finalize!=OptimKit._finalize!) Find the fixed point of `operator` (i.e. the ground state) starting from `peps₀` according -to the optimization parameters supplied in `alg`. The initial environment `env₀` serves as -an initial guess for the first CTMRG run. By default, a random initial environment is used. +to the supplied optimization parameters. The initial environment `env₀` serves as an +initial guess for the first CTMRG run. By default, a random initial environment is used. + +The optimization parameters can be supplied via the keyword arguments or directly as a +`PEPSOptimize` struct. The following keyword arguments are supported: + +## Keyword arguments + +### General settings + +* `tol::Real=$(Defaults.optimizer_tol)`: Overall tolerance for gradient norm convergence of the optimizer. Sets related tolerance such as the boundary and boundary-gradient tolerances to sensible defaults unless they are explictly specified. +* `verbosity::Int=1`: Overall output information verbosity level, should be one of the following: + 0. Suppress all output + 1. Optimizer output and warnings + 2. Additionally print boundary information + 3. All information including AD debug outputs +* `reuse_env::Bool=$(Defaults.reuse_env)`: If `true`, the current optimization step is initialized on the previous environment, otherwise a random environment is used. +* `symmetrization::Union{Nothing,SymmetrizationStyle}=nothing`: Accepts `nothing` or a `SymmetrizationStyle`, in which case the PEPS and PEPS gradient are symmetrized after each optimization iteration. +* `(finalize!)=OptimKit._finalize!`: Inserts a `finalize!` function call after each optimization step by utilizing the `finalize!` kwarg of `OptimKit.optimize`. The function maps `(peps, env), f, g = finalize!((peps, env), f, g, numiter)`. + +### Boundary algorithm + +Supply boundary algorithm parameters via `boundary_alg::Union{NamedTuple,<:CTMRGAlgorithm}` +using either a `NamedTuple` of keyword arguments or a `CTMRGAlgorithm` directly. +See [`leading_boundary`](@ref) for a description of all possible keyword arguments. +By default, a CTMRG tolerance of `tol=1e-4tol` and is used. + +### Gradient algorithm + +Supply gradient algorithm parameters via `gradient_alg::Union{NamedTuple,Nothing,<:GradMode}` +using either a `NamedTuple` of keyword arguments, `nothing`, or a `GradMode` struct directly. +Pass `nothing` to fully differentiate the CTMRG run, meaning that all iterations will be +taken into account, instead of differentiating the fixed point. The supported `NamedTuple` +keyword arguments are: -The `finalize!` kwarg can be used to insert a function call after each optimization step -by utilizing the `finalize!` kwarg of `OptimKit.optimize`. -The function maps `(peps, env), f, g = finalize!((peps, env), f, g, numiter)`. -The `symmetrization` kwarg accepts `nothing` or a `SymmetrizationStyle`, in which case the -PEPS and PEPS gradient are symmetrized after each optimization iteration. Note that this -requires a symmmetric `peps₀` and `env₀` to converge properly. +* `tol::Real=1e-2tol`: Convergence tolerance for the fixed-point gradient iteration. +* `maxiter::Int=$(Defaults.gradient_maxiter)`: Maximal number of gradient problem iterations. +* `alg::Symbol=:$(Defaults.gradient_alg)`: Gradient algorithm variant, can be one of the following: + - `:geomsum`: Compute gradient directly from the geometric sum, see [`GeomSum`](@ref) + - `:manualiter`: Iterate gradient geometric sum manually, see ['ManualIter'](@ref) + - `:linsolver`: Solve fixed-point gradient linear problem using iterative solver, see ['LinSolver'](@ref) + - `:eigsolver`: Determine gradient via eigenvalue formulation of its Sylvester equation, see [`EigSolver`](@ref) +* `verbosity::Int`: Gradient output verbosity, ≤0 by default to disable too verbose printing. Should only be >0 for debug purposes. +* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)`: CTMRG iteration scheme determining mode of differentiation. This can be: + - `:fixed`: the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges + - `:diffgauge`: the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well + +### Optimizer settings + +Supply the optimizer algorithm via `optimizer_alg::Union{NamedTuple,<:OptimKit.OptimizationAlgorithm}` +using either a `NamedTuple` of keyword arguments or a `OptimKit.OptimizationAlgorithm` directly. By default, +`OptimKit.LBFGS` is used in combination with a `HagerZhangLineSearch`. The supported +keyword arguments are: + +* `alg::Symbol=:$(Defaults.optimizer_alg)`: Optimizer algorithm, can be one of the following: + - `:gradientdescent`: Gradient descent algorithm, see the [OptimKit README](https://github.com/Jutho/OptimKit.jl) + - `:conjugategradient`: Conjugate gradient algorithm, see the [OptimKit README](https://github.com/Jutho/OptimKit.jl) + - `:lbfgs`: L-BFGS algorithm, see the [OptimKit README](https://github.com/Jutho/OptimKit.jl) +* `tol::Real=tol`: Gradient norm tolerance of the optimizer. +* `maxiter::Int=$(Defaults.optimizer_maxiter)`: Maximal number of optimization steps. +* `verbosity::Int=$(Defaults.optimizer_verbosity)`: Optimizer output verbosity. +* `lbfgs_memory::Int=$(Defaults.lbfgs_memory)`: Size of limited memory representation of BFGS Hessian matrix. + +## Return values The function returns the final PEPS, CTMRG environment and cost value, as well as an information `NamedTuple` which contains the following entries: -- `last_gradient`: last gradient of the cost function -- `fg_evaluations`: number of evaluations of the cost and gradient function -- `costs`: history of cost values -- `gradnorms`: history of gradient norms -- `truncation_errors`: history of truncation errors of the boundary algorithm -- `condition_numbers`: history of condition numbers of the CTMRG environments -- `gradnorms_unitcell`: history of gradient norms for each respective unit cell entry -- `times`: history of times each optimization step took + +* `last_gradient`: Last gradient of the cost function. +* `fg_evaluations`: Number of evaluations of the cost and gradient function. +* `costs`: History of cost values. +* `gradnorms`: History of gradient norms. +* `truncation_errors`: History of maximal truncation errors of the boundary algorithm. +* `condition_numbers`: History of maximal condition numbers of the CTMRG environments. +* `gradnorms_unitcell`: History of gradient norms for each respective unit cell entry. +* `times`: History of optimization step execution times. """ -function fixedpoint(operator, peps₀::InfinitePEPS, env₀::CTMRGEnv; kwargs...) - throw(error("method not yet implemented")) - alg = fixedpoint_selector(; kwargs...) # TODO: implement fixedpoint_selector - return fixedpoint(operator, peps₀, env₀, alg) +function fixedpoint( + operator, + peps₀::InfinitePEPS, + env₀::CTMRGEnv; + (finalize!)=OptimKit._finalize!, + kwargs..., +) + alg = select_algorithm(fixedpoint, env₀; kwargs...) + return fixedpoint(operator, peps₀, env₀, alg; finalize!) end function fixedpoint( operator, @@ -90,12 +148,26 @@ function fixedpoint( finalize! = (x, f, g, numiter) -> fin!(symm_finalize!(x, f, g, numiter)..., numiter) end - # check realness compatibility - if scalartype(env₀) <: Real && iterscheme(alg.gradient_alg) == :fixed - env₀ = complex(env₀) - @warn "the provided real environment was converted to a complex environment since \ - :fixed mode generally produces complex gauges; use :diffgauge mode instead to work \ - with purely real environments" + # :fixed mode compatibility + if !isnothing(alg.gradient_alg) && iterscheme(alg.gradient_alg) == :fixed + if scalartype(env₀) <: Real # incompatible with real environments + env₀ = complex(env₀) + @warn "the provided real environment was converted to a complex environment \ + since :fixed mode generally produces complex gauges; use :diffgauge mode \ + instead by passing gradient_alg=(; iterscheme=:diffgauge) to the fixedpoint \ + keyword arguments to work with purely real environments" + end + if isnothing(alg.boundary_alg.projector_alg.svd_alg.rrule_alg) # incompatible with TensorKit SVD rrule + G = Base.typename(typeof(alg.gradient_alg)).wrapper # simple type without iterscheme parameter + gradient_alg = G{:diffgauge}( + (getproperty(alg.gradient_alg, f) for f in fieldnames(G))... + ) + @reset alg.gradient_alg = gradient_alg + @warn ":fixed was converted to :diffgauge since :fixed mode and \ + rrule_alg=nothing are incompatible - nothing uses the TensorKit \ + reverse-rule requiring access to the untruncated SVD which FixedSVD does not \ + have; select GMRES, BiCGStab or Arnoldi instead to use :fixed mode" + end end # initialize info collection vectors @@ -107,7 +179,7 @@ function fixedpoint( # optimize operator cost function (peps_final, env_final), cost, ∂cost, numfg, convergence_history = optimize( - (peps₀, env₀), alg.optimizer; retract, inner=real_inner, finalize! + (peps₀, env₀), alg.optimizer_alg; retract, inner=real_inner, finalize! ) do (peps, env) start_time = time_ns() E, gs = withgradient(peps) do ψ @@ -131,7 +203,7 @@ function fixedpoint( return E, g end - info = ( + info = (; last_gradient=∂cost, fg_evaluations=numfg, costs=convergence_history[:, 1], diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl new file mode 100644 index 00000000..d5d99393 --- /dev/null +++ b/src/algorithms/select_algorithm.jl @@ -0,0 +1,359 @@ +function _select_alg_or_namedtuple(alg, alg_type, selects...; extra_kwargs...) + if alg isa alg_type + return alg + elseif alg isa NamedTuple + return select_algorithm(selects...; extra_kwargs..., alg...) + else + throw(ArgumentError("unknown algorithm: $alg")) + end +end + +""" + select_algorithm(func_or_alg, args...; kwargs...) -> Algorithm + +Parse arguments and keyword arguments to the algorithm struct corresponding to +`func_or_alg` and return an algorithm instance. To that end, we use a general interface +where all keyword arguments that can be algorithm themselves can be specified using + +* `alg::Algorithm`: an instance of the algorithm struct or +* `(; alg::Symbol, alg_kwargs...)`: a `NamedTuple` where the algorithm is specified by a `Symbol` and the algorithm keyword arguments + +A full description of the keyword argument can be found in the respective function or +algorithm struct docstrings. +""" +function select_algorithm end + +function select_algorithm( + ::typeof(fixedpoint), + env₀::CTMRGEnv; + tol=Defaults.optimizer_tol, # top-level tolerance + verbosity=2, # top-level verbosity + boundary_alg=(;), + gradient_alg=(;), + optimizer_alg=(;), + kwargs..., +) + # top-level verbosity + if verbosity ≤ 0 # disable output + boundary_verbosity = -1 + gradient_verbosity = -1 + optimizer_verbosity = -1 + elseif verbosity == 1 # output only optimization steps and degeneracy warnings + boundary_verbosity = -1 + gradient_verbosity = 1 + optimizer_verbosity = 3 + elseif verbosity == 2 # output optimization and boundary information + boundary_verbosity = 2 + gradient_verbosity = -1 + optimizer_verbosity = 3 + elseif verbosity == 3 # verbose debug output + boundary_verbosity = 3 + gradient_verbosity = 3 + optimizer_verbosity = 3 + end + + # adjust CTMRG tols and verbosity + boundary_algorithm = _select_alg_or_namedtuple( + boundary_alg, + CTMRGAlgorithm, + leading_boundary, + env₀; + tol=1e-4tol, + verbosity=boundary_verbosity, + ) + + # adjust gradient verbosity + gradient_algorithm = _select_alg_or_namedtuple( + gradient_alg, GradMode, GradMode; tol=1e-2tol, verbosity=gradient_verbosity + ) + + # adjust optimizer tol and verbosity + optimizer_algorithm = _select_alg_or_namedtuple( + optimizer_alg, + OptimKit.OptimizationAlgorithm, + OptimKit.OptimizationAlgorithm; + tol, + verbosity=optimizer_verbosity, + ) + + return select_algorithm( + PEPSOptimize, + env₀; + boundary_alg=boundary_algorithm, + gradient_alg=gradient_algorithm, + optimizer_alg=optimizer_algorithm, + kwargs..., + ) +end + +function select_algorithm( + ::Type{PEPSOptimize}, + env₀::CTMRGEnv; + boundary_alg=(;), + gradient_alg=(;), + optimizer_alg=(;), + reuse_env=Defaults.reuse_env, + symmetrization=nothing, +) + # parse boundary algorithm + boundary_algorithm = _select_alg_or_namedtuple( + boundary_alg, CTMRGAlgorithm, leading_boundary, env₀ + ) + + # parse fixed-point gradient algorithm + gradient_algorithm = _select_alg_or_namedtuple(gradient_alg, GradMode, GradMode) + + # parse optimizer algorithm + optimizer_algorithm = _select_alg_or_namedtuple( + optimizer_alg, OptimKit.OptimizationAlgorithm, OptimKit.OptimizationAlgorithm + ) + + return PEPSOptimize( + boundary_algorithm, + gradient_algorithm, + optimizer_algorithm, + reuse_env, + symmetrization, + ) +end + +const OPTIMIZATION_SYMBOLS = IdDict{Symbol,Type{<:OptimKit.OptimizationAlgorithm}}( + :gradientdescent => GradientDescent, + :conjugategradient => ConjugateGradient, + :lbfgs => LBFGS, +) +function select_algorithm( + ::Type{OptimKit.OptimizationAlgorithm}; + alg=Defaults.optimizer_alg, + tol=Defaults.optimizer_tol, + maxiter=Defaults.optimizer_maxiter, + verbosity=Defaults.optimizer_verbosity, + lbfgs_memory=Defaults.lbfgs_memory, + # TODO: add linesearch, ... to kwargs and defaults? +) + # replace symbol with optimizer alg type + haskey(OPTIMIZATION_SYMBOLS, alg) || + throw(ArgumentError("unknown optimizer algorithm: $alg")) + alg_type = OPTIMIZATION_SYMBOLS[alg] + + # instantiate algorithm + return if alg_type <: LBFGS + alg_type(lbfgs_memory; gradtol=tol, maxiter, verbosity) + else + alg_type(; gradtol=tol, maxiter, verbosity) + end +end + +function select_algorithm( + ::typeof(leading_boundary), + env₀::CTMRGEnv; + alg=Defaults.ctmrg_alg, + tol=Defaults.ctmrg_tol, + verbosity=Defaults.ctmrg_verbosity, + svd_alg=(;), + kwargs..., +) + # adjust SVD rrule settings to CTMRG tolerance, verbosity and environment dimension + if svd_alg isa NamedTuple && + haskey(svd_alg, :rrule_alg) && + svd_alg.rrule_alg isa NamedTuple + χenv = maximum(env₀.corners) do corner + return dim(space(corner, 1)) + end + krylovdim = max( + Defaults.svd_rrule_min_krylovdim, round(Int, Defaults.krylovdim_factor * χenv) + ) + rrule_alg = (; tol=1e1tol, verbosity=verbosity - 2, krylovdim, svd_alg.rrule_alg...) + svd_alg = (; rrule_alg, svd_alg...) + end + svd_algorithm = _select_alg_or_namedtuple(svd_alg, SVDAdjoint, SVDAdjoint) + + return select_algorithm( + CTMRGAlgorithm; alg, tol, verbosity, svd_alg=svd_algorithm, kwargs... + ) +end + +const CTMRG_SYMBOLS = IdDict{Symbol,Type{<:CTMRGAlgorithm}}( + :simultaneous => SimultaneousCTMRG, :sequential => SequentialCTMRG +) +function select_algorithm( + ::Type{CTMRGAlgorithm}; + alg=Defaults.ctmrg_alg, + tol=Defaults.ctmrg_tol, + maxiter=Defaults.ctmrg_maxiter, + miniter=Defaults.ctmrg_miniter, + verbosity=Defaults.ctmrg_verbosity, + trscheme=(; alg=Defaults.trscheme), + svd_alg=(;), + projector_alg=Defaults.projector_alg, # only allows for Symbol/Type{ProjectorAlgorithm} to expose projector kwargs +) + # replace symbol with projector alg type + haskey(CTMRG_SYMBOLS, alg) || throw(ArgumentError("unknown CTMRG algorithm: $alg")) + alg_type = CTMRG_SYMBOLS[alg] + + # parse CTMRG projector algorithm + projector_algorithm = select_algorithm( + ProjectorAlgorithm; alg=projector_alg, svd_alg, trscheme, verbosity + ) + + return alg_type(tol, maxiter, miniter, verbosity, projector_algorithm) +end + +const PROJECTOR_SYMBOLS = IdDict{Symbol,Type{<:ProjectorAlgorithm}}( + :halfinfinite => HalfInfiniteProjector, :fullinfinite => FullInfiniteProjector +) +function select_algorithm( + ::Type{ProjectorAlgorithm}; + alg=Defaults.projector_alg, + svd_alg=(;), + trscheme=(;), + verbosity=Defaults.projector_verbosity, +) + # replace symbol with projector alg type + haskey(PROJECTOR_SYMBOLS, alg) || + throw(ArgumentError("unknown projector algorithm: $alg")) + alg_type = PROJECTOR_SYMBOLS[alg] + + # parse SVD forward & rrule algorithm + svd_algorithm = _select_alg_or_namedtuple(svd_alg, SVDAdjoint, SVDAdjoint) + + # parse truncation scheme + truncation_scheme = _select_alg_or_namedtuple( + trscheme, TruncationScheme, TruncationScheme + ) + + return alg_type(svd_algorithm, truncation_scheme, verbosity) +end + +const GRADIENT_MODE_SYMBOLS = IdDict{Symbol,Type{<:GradMode}}( + :geomsum => GeomSum, + :manualiter => ManualIter, + :linsolver => LinSolver, + :eigsolver => EigSolver, +) +const LINSOLVER_SOLVER_SYMBOLS = IdDict{Symbol,Type{<:KrylovKit.LinearSolver}}( + :gmres => GMRES, :bicgstab => BiCGStab +) +const EIGSOLVER_SOLVER_SYMBOLS = IdDict{Symbol,Type{<:KrylovKit.KrylovAlgorithm}}( + :arnoldi => Arnoldi +) +function select_algorithm( + ::Type{GradMode}; + alg=Defaults.gradient_alg, + tol=Defaults.gradient_tol, + maxiter=Defaults.gradient_maxiter, + verbosity=Defaults.gradient_verbosity, + iterscheme=Defaults.gradient_iterscheme, + solver_alg=(;), +) + # replace symbol with GradMode alg type + haskey(GRADIENT_MODE_SYMBOLS, alg) || + throw(ArgumentError("unknown GradMode algorithm: $alg")) + alg_type = GRADIENT_MODE_SYMBOLS[alg] + + # parse GradMode algorithm + gradient_algorithm = if alg_type <: Union{GeomSum,ManualIter} + alg_type{iterscheme}(tol, maxiter, verbosity) + elseif alg_type <: Union{<:LinSolver,<:EigSolver} + solver = if solver_alg isa NamedTuple # determine linear/eigen solver algorithm + solver_kwargs = (; tol, maxiter, verbosity, solver_alg...) + + solver_type = if alg_type <: LinSolver # replace symbol with solver alg type + solver_kwargs = (; alg=Defaults.gradient_linsolver, solver_kwargs...) + haskey(LINSOLVER_SOLVER_SYMBOLS, solver_kwargs.alg) || throw( + ArgumentError("unknown LinSolver solver: $(solver_kwargs.alg)"), + ) + LINSOLVER_SOLVER_SYMBOLS[solver_kwargs.alg] + elseif alg_type <: EigSolver + solver_kwargs = (; + alg=Defaults.gradient_eigsolver, + eager=Defaults.gradient_eigsolver_eager, + solver_kwargs..., + ) + haskey(EIGSOLVER_SOLVER_SYMBOLS, solver_kwargs.alg) || throw( + ArgumentError("unknown EigSolver solver: $(solver_kwargs.alg)"), + ) + EIGSOLVER_SOLVER_SYMBOLS[solver_kwargs.alg] + end + + solver_kwargs = Base.structdiff(solver_kwargs, (; alg=nothing)) # remove `alg` keyword argument + solver_type(; solver_kwargs...) + else + solver_alg + end + + alg_type{iterscheme}(solver) + else + throw(ArgumentError("unknown gradient algorithm: $alg")) + end + + return gradient_algorithm +end + +const TRUNCATION_SCHEME_SYMBOLS = IdDict{Symbol,Type{<:TruncationScheme}}( + :fixedspace => FixedSpaceTruncation, + :notrunc => TensorKit.NoTruncation, + :truncerr => TensorKit.TruncationError, + :truncdim => TensorKit.TruncationDimension, + :truncspace => TensorKit.TruncationSpace, + :truncbelow => TensorKit.TruncationCutoff, +) +function select_algorithm( + ::Type{TensorKit.TruncationScheme}; alg=Defaults.trscheme, η=nothing +) + # replace Symbol with TruncationScheme type + haskey(TRUNCATION_SCHEME_SYMBOLS, alg) || + throw(ArgumentError("unknown truncation scheme: $alg")) + alg_type = TRUNCATION_SCHEME_SYMBOLS[alg] + + return isnothing(η) ? alg_type() : alg_type(η) +end + +const SVD_FWD_SYMBOLS = IdDict{Symbol,Any}( + :sdd => TensorKit.SDD, + :svd => TensorKit.SVD, + :iterative => + (; tol=1e-14, krylovdim=25, kwargs...) -> + IterSVD(; alg=GKL(; tol, krylovdim), kwargs...), +) +const SVD_RRULE_SYMBOLS = IdDict{Symbol,Type{<:Any}}( + :gmres => GMRES, :bicgstab => BiCGStab, :arnoldi => Arnoldi +) +function select_algorithm( + ::Type{SVDAdjoint}; fwd_alg=(;), rrule_alg=(;), broadening=nothing +) + # parse forward SVD algorithm + fwd_algorithm = if fwd_alg isa NamedTuple + fwd_kwargs = (; alg=Defaults.svd_fwd_alg, fwd_alg...) # overwrite with specified kwargs + haskey(SVD_FWD_SYMBOLS, fwd_kwargs.alg) || + throw(ArgumentError("unknown forward algorithm: $(fwd_kwargs.alg)")) + fwd_type = SVD_FWD_SYMBOLS[fwd_kwargs.alg] + fwd_kwargs = Base.structdiff(fwd_kwargs, (; alg=nothing)) # remove `alg` keyword argument + fwd_type(; fwd_kwargs...) + else + fwd_alg + end + + # parse reverse-rule SVD algorithm + rrule_algorithm = if rrule_alg isa NamedTuple + rrule_kwargs = (; + alg=Defaults.svd_rrule_alg, + tol=Defaults.svd_rrule_tol, + krylovdim=Defaults.svd_rrule_min_krylovdim, + verbosity=Defaults.svd_rrule_verbosity, + rrule_alg..., + ) # overwrite with specified kwargs + + haskey(SVD_RRULE_SYMBOLS, rrule_kwargs.alg) || + throw(ArgumentError("unknown rrule algorithm: $(rrule_kwargs.alg)")) + rrule_type = SVD_RRULE_SYMBOLS[rrule_kwargs.alg] + rrule_kwargs = Base.structdiff(rrule_kwargs, (; alg=nothing)) # remove `alg` keyword argument + rrule_type <: BiCGStab && + (rrule_kwargs = Base.structdiff(rrule_kwargs, (; krylovdim=nothing))) # BiCGStab doens't take `krylovdim` + rrule_type(; rrule_kwargs...) + else + rrule_alg + end + + return SVDAdjoint(fwd_algorithm, rrule_algorithm, broadening) +end diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 4c5928d9..382b74b2 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -10,6 +10,7 @@ struct SimpleUpdate maxiter::Int trscheme::TensorKit.TruncationScheme end +# TODO: add kwarg constructor and SU Defaults """ _su_bondx!(row::Int, col::Int, gate::AbstractTensorMap{T,S,2,2}, diff --git a/src/operators/transfermatrix.jl b/src/operators/transfermatrix.jl index b4873977..4cabc9ba 100644 --- a/src/operators/transfermatrix.jl +++ b/src/operators/transfermatrix.jl @@ -183,13 +183,13 @@ the unit cell. """ MPSKit.expectation_value(st, op) @doc """ - MPSKit.leading_boundary( + leading_boundary( st::InfiniteMPS, op::Union{InfiniteTransferPEPS,InfiniteTransferPEPO}, alg, [env] ) - MPSKit.leading_boundary( + leading_boundary( st::MPSMulitline, op::Union{MultilineTransferPEPS,MultilineTransferPEPO}, alg, [env] ) Approximate the leading boundary MPS eigenvector for the transfer operator `op` using `st` as initial guess. -""" MPSKit.leading_boundary(st, op, alg) +""" leading_boundary(st, op, alg) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 321f31d6..40840841 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -10,19 +10,41 @@ using TensorKit: const CRCExt = Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) """ - struct SVDAdjoint(; fwd_alg=Defaults.fwd_alg, rrule_alg=Defaults.rrule_alg, - broadening=nothing) + struct SVDAdjoint Wrapper for a SVD algorithm `fwd_alg` with a defined reverse rule `rrule_alg`. If `isnothing(rrule_alg)`, Zygote differentiates the forward call automatically. In case of degenerate singular values, one might need a `broadening` scheme which removes the divergences from the adjoint. + +## Keyword arguments + +* `fwd_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=Defaults.svd_fwd_alg)`: SVD algorithm of the forward pass which can either be passed as an `Algorithm` instance or a `NamedTuple` where `alg` is one of the following: + - `:sdd`: TensorKit's wrapper for LAPACK's `_gesdd` + - `:svd`: TensorKit's wrapper for LAPACK's `_gesvd` + - `:iterative`: Iterative SVD only computing the specifed number of singular values and vectors, see ['IterSVD'](@ref) +* `rrule_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=Defaults.svd_rrule_alg)`: Reverse-rule algorithm for differentiating the SVD. Can be supplied by an `Algorithm` instance directly or as a `NamedTuple` where `alg` is one of the following: + - `:gmres`: GMRES iterative linear solver, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.GMRES) for details + - `:bicgstab`: BiCGStab iterative linear solver, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.BiCGStab) for details + - `:arnoldi`: Arnoldi Krylov algorithm, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.Arnoldi) for details +* `broadening=nothing`: Broadening of singular value differences to stabilize the SVD gradient. Currently not implemented. """ -@kwdef struct SVDAdjoint{F,R,B} - fwd_alg::F = Defaults.fwd_alg - rrule_alg::R = Defaults.rrule_alg - broadening::B = nothing +struct SVDAdjoint{F,R,B} + fwd_alg::F + rrule_alg::R + broadening::B + + # Inner constructor to prohibit illegal setting combinations + function SVDAdjoint(fwd_alg::F, rrule_alg::R, broadening::B) where {F,R,B} + if fwd_alg isa FixedSVD && isnothing(rrule_alg) + throw( + ArgumentError("FixedSVD and nothing (TensorKit rrule) are not compatible") + ) + end + return new{F,R,B}(fwd_alg, rrule_alg, broadening) + end end # Keep truncation algorithm separate to be able to specify CTMRG dependent information +SVDAdjoint(; kwargs...) = select_algorithm(SVDAdjoint; kwargs...) """ PEPSKit.tsvd(t, alg; trunc=notrunc(), p=2) @@ -56,7 +78,7 @@ function TensorKit._tsvd!(t, alg::FixedSVD, ::NoTruncation, ::Real=2) end """ - struct IterSVD(; alg=KrylovKit.GKL(), fallback_threshold = Inf) + struct IterSVD(; alg=KrylovKit.GKL(), fallback_threshold = Inf, start_vector=random_start_vector) Iterative SVD solver based on KrylovKit's GKL algorithm, adapted to (symmetric) tensors. The number of targeted singular values is set via the `TruncationSpace` in `ProjectorAlg`. diff --git a/test/boundarymps/vumps.jl b/test/boundarymps/vumps.jl index b97cf3f3..0d4ee102 100644 --- a/test/boundarymps/vumps.jl +++ b/test/boundarymps/vumps.jl @@ -17,9 +17,7 @@ const vumps_alg = VUMPS(; alg_eigsolve=MPSKit.Defaults.alg_eigsolve(; ishermitia mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(sum(expectation_value(mps, T))) - ctm, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(20)), psi, SimultaneousCTMRG(; verbosity=1) - ) + ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) N´ = abs(norm(psi, ctm)) @test N ≈ N´ atol = 1e-3 @@ -33,9 +31,7 @@ end mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(prod(expectation_value(mps, T))) - ctm, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(20)), psi, SimultaneousCTMRG(; verbosity=1) - ) + ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) N´ = abs(norm(psi, ctm)) @test N ≈ N´ rtol = 1e-2 diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 75e2e585..c7b36296 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -15,7 +15,7 @@ using PEPSKit: χbond = 2 χenv = 16 svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SDD()), SVDAdjoint(; fwd_alg=IterSVD())] -projector_algs = [HalfInfiniteProjector] #, FullInfiniteProjector] +projector_algs = [:halfinfinite] #, :fullinfinite] unitcells = [(1, 1), (3, 4)] atol = 1e-5 diff --git a/test/ctmrg/flavors.jl b/test/ctmrg/flavors.jl index b3d935a5..590f4303 100644 --- a/test/ctmrg/flavors.jl +++ b/test/ctmrg/flavors.jl @@ -7,10 +7,8 @@ using PEPSKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg_sequential = SequentialCTMRG() -ctm_alg_simultaneous = SimultaneousCTMRG() unitcells = [(1, 1), (3, 4)] -projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] +projector_algs = [:halfinfinite, :fullinfinite] @testset "$(unitcell) unit cell with $projector_alg" for (unitcell, projector_alg) in Iterators.product( @@ -20,10 +18,10 @@ projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] Random.seed!(32350283290358) psi = InfinitePEPS(2, χbond; unitcell) env_sequential, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg_sequential + CTMRGEnv(psi, ComplexSpace(χenv)), psi; alg=:sequential, projector_alg ) env_simultaneous, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg_simultaneous + CTMRGEnv(psi, ComplexSpace(χenv)), psi; alg=:simultaneous, projector_alg ) # compare norms @@ -56,19 +54,17 @@ projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] end # test fixedspace actually fixes space -@testset "Fixedspace truncation using $ctmrg_alg and $projector_alg" for ( - ctmrg_alg, projector_alg -) in Iterators.product( - [SequentialCTMRG, SimultaneousCTMRG], projector_algs +@testset "Fixedspace truncation using $alg and $projector_alg" for (alg, projector_alg) in + Iterators.product( + [:sequential, :simultaneous], projector_algs ) - ctm_alg = ctmrg_alg(; - tol=1e-6, maxiter=1, verbosity=0, trscheme=FixedSpaceTruncation(), projector_alg - ) Ds = fill(2, 3, 3) χs = [16 17 18; 15 20 21; 14 19 22] psi = InfinitePEPS(Ds, Ds, Ds) env = CTMRGEnv(psi, rand(10:20, 3, 3), rand(10:20, 3, 3)) - env2, = leading_boundary(env, psi, ctm_alg) + env2, = leading_boundary( + env, psi; alg, maxiter=1, trscheme=FixedSpaceTruncation(), projector_alg + ) # check that the space is fixed @test all(space.(env.corners) .== space.(env2.corners)) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 7acad367..775428c4 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -9,7 +9,7 @@ spacetypes = [ComplexSpace, Z2Space] scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] ctmrg_algs = [SequentialCTMRG, SimultaneousCTMRG] -projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] +projector_algs = [:halfinfinite, :fullinfinite] tol = 1e-6 # large tol due to χ=6 χ = 6 atol = 1e-4 @@ -20,7 +20,7 @@ function _pre_converge_env( Random.seed!(seed) # Seed RNG to make random environment consistent psi = InfinitePEPS(rand, T, physical_space, peps_space; unitcell) env₀ = CTMRGEnv(psi, ctm_space) - env_conv, = leading_boundary(env₀, psi, SequentialCTMRG(; tol)) + env_conv, = leading_boundary(env₀, psi; alg=:sequential, tol) return env_conv, psi end diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 3e808ea1..a14955c3 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -19,10 +19,10 @@ names = ["Heisenberg", "p-wave superconductor"] gradtol = 1e-4 ctmrg_algs = [ [ - SimultaneousCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), - SimultaneousCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), + SimultaneousCTMRG(; verbosity=0, projector_alg=:halfinfinite), + SimultaneousCTMRG(; verbosity=0, projector_alg=:fullinfinite), ], - [SequentialCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector)], + [SequentialCTMRG(; verbosity=0, projector_alg=:halfinfinite)], ] gradmodes = [ [ @@ -31,21 +31,17 @@ gradmodes = [ GeomSum(; tol=gradtol, iterscheme=:diffgauge), ManualIter(; tol=gradtol, iterscheme=:fixed), ManualIter(; tol=gradtol, iterscheme=:diffgauge), - LinSolver(; solver=KrylovKit.BiCGStab(; tol=gradtol), iterscheme=:fixed), - LinSolver(; solver=KrylovKit.BiCGStab(; tol=gradtol), iterscheme=:diffgauge), - EigSolver(; solver=KrylovKit.Arnoldi(; tol=gradtol, eager=true), iterscheme=:fixed), - EigSolver(; - solver=KrylovKit.Arnoldi(; tol=gradtol, eager=true), iterscheme=:diffgauge - ), + LinSolver(; solver_alg=BiCGStab(; tol=gradtol), iterscheme=:fixed), + LinSolver(; solver_alg=BiCGStab(; tol=gradtol), iterscheme=:diffgauge), + EigSolver(; solver_alg=Arnoldi(; tol=gradtol, eager=true), iterscheme=:fixed), + EigSolver(; solver_alg=Arnoldi(; tol=gradtol, eager=true), iterscheme=:diffgauge), ], [ # Only use :diffgauge due to high gauge-sensitivity (perhaps due to small χenv?) nothing, GeomSum(; tol=gradtol, iterscheme=:diffgauge), ManualIter(; tol=gradtol, iterscheme=:diffgauge), - LinSolver(; solver=KrylovKit.BiCGStab(; tol=gradtol), iterscheme=:diffgauge), - EigSolver(; - solver=KrylovKit.Arnoldi(; tol=gradtol, eager=true), iterscheme=:diffgauge - ), + LinSolver(; solver_alg=BiCGStab(; tol=gradtol), iterscheme=:diffgauge), + EigSolver(; solver_alg=Arnoldi(; tol=gradtol, eager=true), iterscheme=:diffgauge), ], ] steps = -0.01:0.005:0.01 diff --git a/test/ctmrg/jacobian_real_linear.jl b/test/ctmrg/jacobian_real_linear.jl index 1f51791a..68d34835 100644 --- a/test/ctmrg/jacobian_real_linear.jl +++ b/test/ctmrg/jacobian_real_linear.jl @@ -6,9 +6,9 @@ using TensorKit, KrylovKit, PEPSKit using PEPSKit: ctmrg_iteration, gauge_fix, fix_relative_phases, fix_global_phases algs = [ - (:fixed, SimultaneousCTMRG(; projector_alg=HalfInfiniteProjector)), - (:diffgauge, SequentialCTMRG(; projector_alg=HalfInfiniteProjector)), - (:diffgauge, SimultaneousCTMRG(; projector_alg=HalfInfiniteProjector)), + (:fixed, SimultaneousCTMRG(; projector_alg=:halfinfinite)), + (:diffgauge, SequentialCTMRG(; projector_alg=:halfinfinite)), + (:diffgauge, SimultaneousCTMRG(; projector_alg=:halfinfinite)), # TODO: FullInfiniteProjector errors since even real_err_∂A, real_err_∂x are finite? # (:fixed, SimultaneousCTMRG(; projector_alg=FullInfiniteProjector)), # (:diffgauge, SequentialCTMRG(; projector_alg=FullInfiniteProjector)), diff --git a/test/ctmrg/partition_function.jl b/test/ctmrg/partition_function.jl index dbb5b02a..18f70ee9 100644 --- a/test/ctmrg/partition_function.jl +++ b/test/ctmrg/partition_function.jl @@ -89,16 +89,15 @@ Random.seed!(81812781143) env0 = CTMRGEnv(Z, χenv) # cover all different flavors -ctm_styles = [SequentialCTMRG, SimultaneousCTMRG] -projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] +ctm_styles = [:sequential, :simultaneous] +projector_algs = [:halfinfinite, :fullinfinite] -@testset "Classical Ising partition function using $ctm_style with $projector_alg" for ( - ctm_style, projector_alg +@testset "Classical Ising partition function using $alg with $projector_alg" for ( + alg, projector_alg ) in Iterators.product( ctm_styles, projector_algs ) - ctm_alg = ctm_style(; maxiter=150, projector_alg) - env, = leading_boundary(env0, Z, ctm_alg) + env, = leading_boundary(env0, Z; alg, maxiter=150, projector_alg) # check observables λ = network_value(Z, env) diff --git a/test/ctmrg/unitcell.jl b/test/ctmrg/unitcell.jl index b72ded9a..3b399afa 100644 --- a/test/ctmrg/unitcell.jl +++ b/test/ctmrg/unitcell.jl @@ -8,10 +8,10 @@ using TensorKit Random.seed!(91283219347) stype = ComplexF64 ctm_algs = [ - SequentialCTMRG(; projector_alg=HalfInfiniteProjector), - SequentialCTMRG(; projector_alg=FullInfiniteProjector), - SimultaneousCTMRG(; projector_alg=HalfInfiniteProjector), - SimultaneousCTMRG(; projector_alg=FullInfiniteProjector), + SequentialCTMRG(; projector_alg=:halfinfinite), + SequentialCTMRG(; projector_alg=:fullinfinite), + SimultaneousCTMRG(; projector_alg=:halfinfinite), + SimultaneousCTMRG(; projector_alg=:fullinfinite), ] function test_unitcell( diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 24ea65ae..d9b9a97a 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -9,10 +9,7 @@ using OptimKit # initialize parameters Dbond = 2 χenv = 16 -ctm_alg = SimultaneousCTMRG() -opt_alg = PEPSOptimize(; - boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=3) -) +gradtol = 1e-3 # compare against Juraj Hasik's data: # https://github.com/jurajHasik/j1j2_ipeps_states/blob/main/single-site_pg-C4v-A1/j20.0/state_1s_A1_j20.0_D2_chi_opt48.dat E_ref = -0.6602310934799577 @@ -22,10 +19,10 @@ E_ref = -0.6602310934799577 Random.seed!(123) H = heisenberg_XYZ(InfiniteSquare()) peps₀ = InfinitePEPS(2, Dbond) - env₀, = leading_boundary(CTMRGEnv(peps₀, ComplexSpace(χenv)), peps₀, ctm_alg) + env₀, = leading_boundary(CTMRGEnv(peps₀, ComplexSpace(χenv)), peps₀) # optimize energy and compute correlation lengths - peps, env, E, = fixedpoint(H, peps₀, env₀, opt_alg) + peps, env, E, = fixedpoint(H, peps₀, env₀; tol=gradtol) ξ_h, ξ_v, = correlation_length(peps, env) @test E ≈ E_ref atol = 1e-2 @@ -38,10 +35,10 @@ end unitcell = (1, 2) H = heisenberg_XYZ(InfiniteSquare(unitcell...)) peps₀ = InfinitePEPS(2, Dbond; unitcell) - env₀, = leading_boundary(CTMRGEnv(peps₀, ComplexSpace(χenv)), peps₀, ctm_alg) + env₀, = leading_boundary(CTMRGEnv(peps₀, ComplexSpace(χenv)), peps₀) # optimize energy and compute correlation lengths - peps, env, E, = fixedpoint(H, peps₀, env₀, opt_alg) + peps, env, E, = fixedpoint(H, peps₀, env₀; tol=gradtol) ξ_h, ξ_v, = correlation_length(peps, env) @test E ≈ 2 * E_ref atol = 1e-2 @@ -50,12 +47,13 @@ end @testset "Simple update into AD optimization" begin # random initialization of 2x2 iPEPS with weights and CTMRGEnv (using real numbers) - Random.seed!(234829) + Random.seed!(100) N1, N2 = 2, 2 Pspace = ℂ^2 Vspace = ℂ^Dbond Espace = ℂ^χenv wpeps = InfiniteWeightPEPS(rand, Float64, Pspace, Vspace; unitcell=(N1, N2)) + ctmrg_tol = 1e-6 # normalize vertex tensors for ind in CartesianIndices(wpeps.vertices) @@ -67,7 +65,7 @@ end ham = LocalOperator(ham.lattice, Tuple(ind => real(op) for (ind, op) in ham.terms)...) # simple update - dts = [1e-2, 1e-3, 4e-4, 1e-4] + dts = [1e-2, 1e-3, 1e-3, 1e-4] tols = [1e-7, 1e-8, 1e-8, 1e-8] maxiter = 5000 for (n, (dt, tol)) in enumerate(zip(dts, tols)) @@ -80,8 +78,7 @@ end # absorb weight into site tensors and CTMRG peps = InfinitePEPS(wpeps) - env₀ = CTMRGEnv(rand, Float64, peps, Espace) - env, = leading_boundary(env₀, peps, SimultaneousCTMRG()) + env, = leading_boundary(CTMRGEnv(rand, Float64, peps, Espace), peps; tol=ctmrg_tol) # measure physical quantities e_site = cost_function(peps, env, ham) / (N1 * N2) @@ -90,9 +87,13 @@ end @test isapprox(e_site, -0.6594; atol=1e-3) # continue with auto differentiation - svd_alg_gmres = SVDAdjoint(; rrule_alg=GMRES(; tol=1e-5)) - opt_alg_gmres = @set opt_alg.boundary_alg.projector_alg.svd_alg = svd_alg_gmres - peps_final, env_final, E_final, = fixedpoint(ham, peps, env, opt_alg_gmres) # sensitivity warnings and degeneracies due to SU(2)? + peps_final, env_final, E_final, = fixedpoint( + ham, + peps, + env; + tol=gradtol, + boundary_alg=(; maxiter=150, svd_alg=(; rrule_alg=(; alg=:gmres, tol=1e-5))), + ) # sensitivity warnings and degeneracies due to SU(2)? ξ_h, ξ_v, = correlation_length(peps_final, env_final) e_site2 = E_final / (N1 * N2) @info "Auto diff energy = $e_site2" diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 391f81a2..bd886bb1 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -8,23 +8,23 @@ using OptimKit # initialize parameters χbond = 2 χenv = 12 -ctm_alg = SimultaneousCTMRG() -opt_alg = PEPSOptimize(; - boundary_alg=ctm_alg, - optimizer=LBFGS(4; gradtol=1e-3, verbosity=3), - gradient_alg=LinSolver(; iterscheme=:diffgauge), - symmetrization=RotateReflect(), -) # initialize states Random.seed!(91283219347) H = j1_j2(InfiniteSquare(); J2=0.25) peps₀ = product_peps(2, χbond; noise_amp=1e-1) peps₀ = symmetrize!(peps₀, RotateReflect()) -env₀, = leading_boundary(CTMRGEnv(peps₀, ComplexSpace(χenv)), peps₀, ctm_alg); +env₀, = leading_boundary(CTMRGEnv(peps₀, ComplexSpace(χenv)), peps₀) # find fixedpoint -peps, env, E, = fixedpoint(H, peps₀, env₀, opt_alg) +peps, env, E, = fixedpoint( + H, + peps₀, + env₀; + tol=1e-3, + gradient_alg=(; iterscheme=:diffgauge), + symmetrization=RotateReflect(), +) ξ_h, ξ_v, = correlation_length(peps, env) # compare against Juraj Hasik's data: diff --git a/test/pwave.jl b/test/pwave.jl index 00a65391..e0745605 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -10,10 +10,6 @@ unitcell = (2, 2) H = pwave_superconductor(InfiniteSquare(unitcell...)) Dbond = 2 χenv = 16 -ctm_alg = SimultaneousCTMRG() -opt_alg = PEPSOptimize(; - boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=3) -) # initialize states Pspace = Vect[FermionParity](0 => 1, 1 => 1) @@ -21,10 +17,10 @@ Vspace = Vect[FermionParity](0 => Dbond ÷ 2, 1 => Dbond ÷ 2) Envspace = Vect[FermionParity](0 => χenv ÷ 2, 1 => χenv ÷ 2) Random.seed!(91283219347) peps₀ = InfinitePEPS(Pspace, Vspace, Vspace; unitcell) -env₀, = leading_boundary(CTMRGEnv(peps₀, Envspace), peps₀, ctm_alg); +env₀, = leading_boundary(CTMRGEnv(peps₀, Envspace), peps₀) # find fixedpoint -_, _, E, = fixedpoint(H, peps₀, env₀, opt_alg) +_, _, E, = fixedpoint(H, peps₀, env₀; tol=1e-3, optimizer_alg=(; maxiter=10)) # comparison with Gaussian PEPS minimum at D=2 on 1000x1000 square lattice with aPBC @test E / prod(size(peps₀)) ≈ -2.6241 atol = 5e-2 diff --git a/test/tf_ising.jl b/test/tf_ising.jl index f4ad251c..fa3b730c 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -19,19 +19,16 @@ mˣ = 0.91 # initialize parameters χbond = 2 χenv = 16 -ctm_alg = SimultaneousCTMRG() -opt_alg = PEPSOptimize(; - boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=3) -) +gradtol = 1e-3 # initialize states H = transverse_field_ising(InfiniteSquare(); g) Random.seed!(2928528935) peps₀ = InfinitePEPS(2, χbond) -env₀, = leading_boundary(CTMRGEnv(peps₀, ComplexSpace(χenv)), peps₀, ctm_alg) +env₀, = leading_boundary(CTMRGEnv(peps₀, ComplexSpace(χenv)), peps₀) # find fixedpoint -peps, env, E, = fixedpoint(H, peps₀, env₀, opt_alg) +peps, env, E, = fixedpoint(H, peps₀, env₀; tol=gradtol) ξ_h, ξ_v, = correlation_length(peps, env) # compute magnetization @@ -45,7 +42,7 @@ magn = expectation_value(peps, M, env) # find fixedpoint in polarized phase and compute correlations lengths H_polar = transverse_field_ising(InfiniteSquare(); g=4.5) -peps_polar, env_polar, = fixedpoint(H_polar, peps₀, env₀, opt_alg) +peps_polar, env_polar, = fixedpoint(H_polar, peps₀, env₀; tol=gradtol) ξ_h_polar, ξ_v_polar, = correlation_length(peps_polar, env_polar) @test ξ_h_polar < ξ_h @test ξ_v_polar < ξ_v diff --git a/test/utility/svd_wrapper.jl b/test/utility/svd_wrapper.jl index 61af7940..e923a706 100644 --- a/test/utility/svd_wrapper.jl +++ b/test/utility/svd_wrapper.jl @@ -25,7 +25,7 @@ r = randn(dtype, ℂ^m, ℂ^n) R = randn(space(r)) full_alg = SVDAdjoint(; rrule_alg=nothing) -iter_alg = SVDAdjoint(; fwd_alg=IterSVD()) +iter_alg = SVDAdjoint(; fwd_alg=(; alg=:iterative)) @testset "Non-truncacted SVD" begin l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, R), r)