From 3cb1e804d74413440f4483ac57f8795cc2a029a1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 29 Jan 2026 10:53:20 +0100 Subject: [PATCH 01/69] Initial steps to CUDA support yolo svd distance --- Project.toml | 8 + examples/cu_bose_hubbard/Project.toml | 18 ++ examples/cu_bose_hubbard/main.jl | 133 ++++++++++ examples/cu_boundary_mps/main.jl | 236 ++++++++++++++++++ examples/cu_fermi_hubbard/main.jl | 104 ++++++++ examples/cu_heisenberg/main.jl | 187 ++++++++++++++ examples/cu_heisenberg_su/main.jl | 139 +++++++++++ examples/cu_hubbard_su/main.jl | 111 ++++++++ examples/cu_j1j2_su/main.jl | 129 ++++++++++ examples/cu_xxz/main.jl | 101 ++++++++ ext/PEPSKitCUDAExt.jl | 39 +++ src/PEPSKit.jl | 1 + src/algorithms/ctmrg/ctmrg.jl | 4 - .../optimization/peps_optimization.jl | 3 +- src/algorithms/select_algorithm.jl | 1 + src/algorithms/toolbox.jl | 2 +- src/environments/ctmrg_environments.jl | 53 ++-- src/environments/suweight.jl | 4 +- src/networks/local_sandwich.jl | 2 + src/networks/tensors.jl | 6 +- src/operators/localoperator.jl | 1 + src/operators/transfermatrix.jl | 4 + src/states/infinitepartitionfunction.jl | 8 +- src/states/infinitepeps.jl | 7 +- 24 files changed, 1259 insertions(+), 42 deletions(-) create mode 100644 examples/cu_bose_hubbard/Project.toml create mode 100644 examples/cu_bose_hubbard/main.jl create mode 100644 examples/cu_boundary_mps/main.jl create mode 100644 examples/cu_fermi_hubbard/main.jl create mode 100644 examples/cu_heisenberg/main.jl create mode 100644 examples/cu_heisenberg_su/main.jl create mode 100644 examples/cu_hubbard_su/main.jl create mode 100644 examples/cu_j1j2_su/main.jl create mode 100644 examples/cu_xxz/main.jl create mode 100644 ext/PEPSKitCUDAExt.jl diff --git a/Project.toml b/Project.toml index a718d73fd..696c79a72 100644 --- a/Project.toml +++ b/Project.toml @@ -38,10 +38,13 @@ PEPSKitAdaptExt = "Adapt" PEPSKitGPUArraysExt = "GPUArrays" [compat] +Adapt = "4" Accessors = "0.1" Adapt = "4" ChainRulesCore = "1.0" Compat = "3.46, 4.2" +CUDA = "6" +cuTENSOR = "6" DocStringExtensions = "0.9.3" FiniteDifferences = "0.12" GPUArrays = "11, 12" @@ -62,3 +65,8 @@ TupleTools = "1.6.0" VectorInterface = "0.4, 0.5, 0.6, 0.7" Zygote = "0.6, 0.7" julia = "1.10" + +[sources] +TensorKit = {url = "https://github.com/QuantumKitHub/TensorKit.jl", rev="main"} +MPSKit = {url = "https://github.com/QuantumKitHub/MPSKit.jl", rev="ksh/cu2"} +MPSKitModels = {url = "https://github.com/QuantumKitHub/MPSKitModels.jl", rev="ksh/bump"} diff --git a/examples/cu_bose_hubbard/Project.toml b/examples/cu_bose_hubbard/Project.toml new file mode 100644 index 000000000..95b9c2d30 --- /dev/null +++ b/examples/cu_bose_hubbard/Project.toml @@ -0,0 +1,18 @@ +[deps] +Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" +MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" +MPSKitModels = "ca635005-6f8c-4cd1-b51d-8491250ef2ab" +MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" +OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" +PEPSKit = "52969e89-939e-4361-9b68-9bc7cde4bdeb" +QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" +TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" +cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" diff --git a/examples/cu_bose_hubbard/main.jl b/examples/cu_bose_hubbard/main.jl new file mode 100644 index 000000000..001ec413c --- /dev/null +++ b/examples/cu_bose_hubbard/main.jl @@ -0,0 +1,133 @@ +using Markdown #hide +md""" +# Optimizing the $U(1)$-symmetric Bose-Hubbard model + +This example demonstrates the simulation of the two-dimensional Bose-Hubbard model. In +particular, the point will be to showcase the use of internal symmetries and finite +particle densities in PEPS ground state searches. As we will see, incorporating symmetries +into the simulation consists of initializing a symmetric Hamiltonian, PEPS state and CTM +environment - made possible through TensorKit. + +But first let's seed the RNG and import the required modules: +""" + +using Random +using TensorKit, PEPSKit, Adapt, CUDA, cuTENSOR, MPSKitModels +using PEPSKit.MatrixAlgebraKit +using MPSKit: add_physical_charge +Random.seed!(2928528935); + +md""" +## Defining the model + +We will construct the Bose-Hubbard model Hamiltonian through the +[`bose_hubbard_model`](https://quantumkithub.github.io/MPSKitModels.jl/dev/man/models/#MPSKitModels.bose_hubbard_model), +function from MPSKitModels as reexported by PEPSKit. We'll simulate the model in its +Mott-insulating phase where the ratio $U/t$ is large, since in this phase we expect the +ground state to be well approximated by a PEPS with a manifest global $U(1)$ symmetry. +Furthermore, we'll impose a cutoff at 2 bosons per site, set the chemical potential to zero +and use a simple $1 \times 1$ unit cell: +""" + +t = 1.0 +U = 30.0 +cutoff = 2 +mu = 0.0 +lattice = InfiniteSquare(1, 1); + +md""" +Next, we impose an explicit global $U(1)$ symmetry as well as a fixed particle number +density in our simulations. We can do this by setting the `symmetry` argument of the +Hamiltonian constructor to `U1Irrep` and passing one as the particle number density +keyword argument `n`: +""" + +symmetry = U1Irrep +n = 1 +H = adapt(CuArray, bose_hubbard_model(ComplexF64, symmetry, lattice; cutoff, t, U, n)); + +md""" +Before we continue, it might be interesting to inspect the corresponding lattice physical +spaces (which is here just a $1 \times 1$ matrix due to the single-site unit cell): +""" + +physical_spaces = physicalspace(H) + +md""" +Note that the physical space contains $U(1)$ charges -1, 0 and +1. Indeed, imposing a +particle number density of +1 corresponds to shifting the physical charges by -1 to +'re-center' the physical charges around the desired density. When we do this with a cutoff +of two bosons per site, i.e. starting from $U(1)$ charges 0, 1 and 2 on the physical level, +we indeed get the observed charges. + +## Characterizing the virtual spaces + +When running PEPS simulations with explicit internal symmetries, specifying the structure of +the virtual spaces of the PEPS and its environment becomes a bit more involved. For the +environment, one could in principle allow the virtual space to be chosen dynamically during + +(e.g. using `alg=:truncrank` or `alg=:trunctol` to truncate to a fixed total bond dimension +or singular value cutoff respectively). For the PEPS virtual space however, the structure +has to be specified before the optimization. + +While there are a host of techniques to do this in an informed way (e.g. starting from a +simple update result), here we just specify the virtual space manually. Since we're dealing +with a model at unit filling our physical space only contains integer $U(1)$ irreps. +Therefore, we'll build our PEPS and environment spaces using integer $U(1)$ irreps centered +around the zero charge: +""" + +V_peps = U1Space(0 => 2, 1 => 1, -1 => 1) +V_env = U1Space(0 => 6, 1 => 4, -1 => 4, 2 => 2, -2 => 2); + +md""" +## Finding the ground state + +Having defined our Hamiltonian and spaces, it is just a matter of plugging this into the +optimization framework in the usual way to find the ground state. So, we first specify all +algorithms and their tolerances: +""" + +boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, decomposition_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration()), trunc = (; alg = :fixedspace)) +gradient_alg = (; tol = 1.0e-6, maxiter = 10, alg = :eigsolver, iterscheme = :diffgauge) +optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 150, ls_maxiter = 2, ls_maxfg = 2); + +md""" +!!! note + Taking CTMRG gradients and optimizing symmetric tensors tends to be more problematic + than with dense tensors. In particular, this means that one frequently needs to tweak + the `boundary_alg`, `gradient_alg` and `optimizer_alg` settings. There rarely is a + general-purpose set of settings which will always work, so instead one has to adjust + the simulation settings for each specific application. For example, it might help to + switch between the CTMRG flavors `alg=:simultaneous` and `alg=:sequential` to + improve convergence. The evaluation of the CTMRG gradient can be instable, so there it + is advised to try the different `iterscheme=:diffgauge` and `iterscheme=:fixed` schemes + as well as different `alg` keywords. Of course the tolerances of the algorithms and + their subalgorithms also have to be compatible. For more details on the available + options, see the [`fixedpoint`](@ref) docstring. + +Keep in mind that the PEPS is constructed from a unit cell of spaces, so we have to make a +matrix of `V_peps` spaces: +""" + +virtual_spaces = fill(V_peps, size(lattice)...) +peps₀ = InfinitePEPS(randn, CuMatrix{ComplexF64}, physical_spaces, virtual_spaces) +env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...); + +md""" +And at last, we optimize (which might take a bit): +""" + +peps, env, E, info = fixedpoint( + H, peps₀, env₀; boundary_alg, gradient_alg, optimizer_alg, verbosity = 3 +) +@show E; + +md""" +We can compare our PEPS result to the energy obtained using a cylinder-MPS calculation +using a cylinder circumference of $L_y = 7$ and a bond dimension of 446, which yields +$E = -0.273284888$: +""" + +E_ref = -0.273284888 +@show (E - E_ref) / E_ref; diff --git a/examples/cu_boundary_mps/main.jl b/examples/cu_boundary_mps/main.jl new file mode 100644 index 000000000..ddebaf0e9 --- /dev/null +++ b/examples/cu_boundary_mps/main.jl @@ -0,0 +1,236 @@ +using Markdown #hide +md""" +# [Boundary MPS contractions of 2D networks](@id e_boundary_mps) + +Instead of using CTMRG to contract the network encoding the norm of an infinite PEPS, one +can also use so-called [boundary MPS methods](@cite haegeman_diagonalizing_2017) to contract +this network. In this example, we will demonstrate how to use [the VUMPS algorithm](@cite +vanderstraeten_tangentspace_2019) to do so. + +Before we start, we'll fix the random seed for reproducability: +""" + +using Random +Random.seed!(29384293742893); + +md""" +Besides `TensorKit` and `PEPSKit`, here we also need to load the +[`MPSKit.jl`](https://quantumkithub.github.io/MPSKit.jl/stable/) package which implements a +host of tools for working with 1D matrix product states (MPS), including the VUMPS +algorithm: +""" + +using TensorKit, PEPSKit, MPSKit, CUDA, cuTENSOR, MatrixAlgebraKit + +md""" +## Computing a PEPS norm + +We start by initializing a random infinite PEPS. Let us use normally distributed complex +entries using `randn`: +""" + +ψ = InfinitePEPS(randn, CuMatrix{ComplexF64}, ComplexSpace(2), ComplexSpace(2)) + +md""" + +To compute its norm, we have to contract a double-layer network which encodes the bra-ket +PEPS overlap ``\langle ψ | ψ \rangle``: + +```@raw html +
+peps norm network +
+``` + +In PEPSKit.jl, this structure is represented as an [`InfiniteSquareNetwork`](@ref) object, +whose effective local rank-4 constituent tensor is given by the contraction of a pair of bra +and ket [`PEPSKit.PEPSTensor`](@ref)s across their physical legs. Until now, we have always +contracted such a network using the CTMRG algorithm. Here however, we will use another +approach. + +If we take out a single row of this infinite norm network, we can interpret it as a 1D +row-to-row transfer operator ``\mathbb{T}``, + +```@raw html +
+peps transfer operator +
+``` + +This transfer operator can be seen as an infinite chain of the effective local rank-4 +tensors that make up the PEPS norm network. Since the network we want to contract can be +interpreted as the infinite power of ``\mathbb{T}``, we can contract it by finding its +leading eigenvector as a 1D MPS ``| \psi_{\text{MPS}} \rangle``, which we call the boundary +MPS. This boundary MPS should satisfy the eigenvalue equation +``\mathbb{T} | \psi_{\text{MPS}} \rangle \approx \Lambda | \psi_{\text{MPS}} \rangle``, or +diagrammatically: + +```@raw html +
+peps transfer fixedpoint equation +
+``` + +Note that if ``\mathbb{T}`` is Hermitian, we can formulate this eigenvalue equation in terms of a +variational problem for the free energy, + +```math +\begin{align} +f &= \lim_{N \to ∞} - \frac{1}{N} \log \left( \frac{\langle \psi_{\text{MPS}} | \mathbb{T} | \psi_{\text{MPS}} \rangle}{\langle \psi_{\text{MPS}} | \psi_{\text{MPS}} \rangle} \right), +\\ +&= -\log(\lambda) +\end{align} +``` + +where ``\lambda = \Lambda^{1/N}`` is the 'eigenvalue per site' of ``\mathbb{T}``, giving +``f`` the meaning of a free energy density. + +Since the contraction of a PEPS norm network is in essence exactly the same problem as the +contraction of a 2D classical partition function, we can directly use boundary MPS +algorithms designed for 2D statistical mechanics models in this context. In particular, +we'll use the [the VUMPS algorithm](@cite vanderstraeten_tangentspace_2019) to perform the +boundary MPS contraction, and we'll call it through the [`leading_boundary`](@ref) method +from MPSKit.jl. This method precisely finds the MPS fixed point of a 1D transfer operator. + +## Boundary MPS contractions with PEPSKit.jl + +To use [`leading_boundary`](@ref), we first need to contruct the transfer operator +``\mathbb{T}`` as an [`MPSKit.InfiniteMPO`](@extref) object. In PEPSKit.jl, we can directly +construct the transfer operator corresponding to a PEPS norm network from a given infinite +PEPS as an [`InfiniteTransferPEPS`](@ref) object, which is a specific kind of +[`MPSKit.InfiniteMPO`](@extref). + +To construct a 1D transfer operator from a 2D PEPS state, we need to specify which direction +should be facing north (`dir=1` corresponding to north, counting clockwise) and which row of +the network is selected from the north - but since we have a trivial unit cell there is only +one row here: +""" + +dir = 1 ## does not rotate the partition function +row = 1 +T = InfiniteTransferPEPS(ψ, dir, row) + +md""" +Since we'll find the leading eigenvector of ``\mathbb{T}`` as a boundary MPS, we first need +to construct an initial guess to supply to our algorithm. We can do this using the +[`initialize_mps`](@ref) function, which constructs a random MPS with a specific virtual +space for a given transfer operator. Here, we'll build an initial guess for the boundary MPS +with a bond dimension of 20: +""" + +mps₀ = initialize_mps(T, [ComplexSpace(20)]; alg_orth = CUSOLVER_HouseholderQR(; positive=true)) + +md""" +Note that this will just construct a MPS with random Gaussian entries based on the physical +spaces of the supplied transfer operator. Of course, one might come up with a better initial +guess (leading to better convergence) depending on the application. To find the leading +boundary MPS fixed point, we call [`leading_boundary`](@ref) using the +[`MPSKit.VUMPS`](@extref) algorithm: +""" + +MPSKit.Defaults.alg_qr() = CUSOLVER_HouseholderQR(; positive=true) +MPSKit.Defaults.alg_svd() = CUSOLVER_QRIteration() +MPSKit.Defaults.alg_lq() = LQViaTransposedQR(CUSOLVER_HouseholderQR(; positive=true)) +mps, env, ϵ = leading_boundary(mps₀, T, VUMPS(; tol = 1.0e-6, verbosity = 2)); + +md""" +The norm of the state per unit cell is then given by the expectation value +$\langle \psi_\text{MPS} | \mathbb{T} | \psi_\text{MPS} \rangle$ per site: +""" + +norm_vumps = abs(prod(expectation_value(mps, T))) + +md""" +This can be compared to the result obtained using CTMRG, where we see that the results +match: +""" + +env_ctmrg, = leading_boundary(CTMRGEnv(ψ, ComplexSpace(20)), ψ; tol = 1.0e-6, verbosity = 2, svd_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration())) +norm_ctmrg = abs(norm(ψ, env_ctmrg)) +@show abs(norm_vumps - norm_ctmrg) / norm_vumps; + +md""" +## Working with unit cells + +For PEPS with non-trivial unit cells, the principle is exactly the same. The only difference +is that now the transfer operator of the PEPS norm partition function has multiple rows or +'lines', each of which can be represented by an [`InfiniteTransferPEPS`](@ref) object. Such +a multi-line transfer operator is represented by a [`PEPSKit.MultilineTransferPEPS`](@ref) +object. In this case, the boundary MPS is an [`MultilineMPS`](@extref) object, which should +be initialized by specifying a virtual space for each site in the partition function unit +cell. + +First, we construct a PEPS with a $2 \times 2$ unit cell using the `unitcell` keyword +argument and then define the corresponding transfer operator, where we again specify the +direction which will be facing north: +""" + +ψ_2x2 = InfinitePEPS(rand, CuMatrix{ComplexF64}, ComplexSpace(2), ComplexSpace(2); unitcell = (2, 2)) +T_2x2 = PEPSKit.MultilineTransferPEPS(ψ_2x2, dir); + +md""" +Now, the procedure is the same as before: We compute the norm once using VUMPS, once using CTMRG and then compare. +""" + +mps₀_2x2 = initialize_mps(T_2x2, fill(ComplexSpace(20), 2, 2); alg_orth = CUSOLVER_HouseholderQR(; positive=true)) +mps_2x2, = leading_boundary(mps₀_2x2, T_2x2, VUMPS(; tol = 1.0e-6, verbosity = 2)) +norm_2x2_vumps = abs(prod(expectation_value(mps_2x2, T_2x2))) + +env_ctmrg_2x2, = leading_boundary( + CTMRGEnv(ψ_2x2, ComplexSpace(20)), ψ_2x2; tol = 1.0e-6, verbosity = 2, svd_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration()) +) +norm_2x2_ctmrg = abs(norm(ψ_2x2, env_ctmrg_2x2)) + +@show abs(norm_2x2_vumps - norm_2x2_ctmrg) / norm_2x2_vumps; + +md""" +Again, the results are compatible. Note that for larger unit cells and non-Hermitian PEPS +[the VUMPS algorithm may become unstable](@cite vanderstraeten_variational_2022), in which +case the CTMRG algorithm is recommended. + +## Contracting PEPO overlaps + +Using exactly the same machinery, we can contract 2D networks which encode the expectation +value of a PEPO for a given PEPS state. As an example, we can consider the overlap of the +PEPO correponding to the partition function of [3D classical Ising model](@ref e_3d_ising) +with our random PEPS from before and evaluate the overlap $\langle \psi | +T | \psi \rangle$. + +The classical Ising PEPO is defined as follows: +""" + +function ising_pepo(β; unitcell = (1, 1, 1)) + t = ComplexF64[exp(β) exp(-β); exp(-β) exp(β)] + q = sqrt(t) + + O = zeros(2, 2, 2, 2, 2, 2) + + O[1, 1, 1, 1, 1, 1] = 1 + O[2, 2, 2, 2, 2, 2] = 1 + @tensor o[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + O = TensorMap(CuArray(o), ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)') + return InfinitePEPO(O; unitcell) +end; + +md""" +To evaluate the overlap, we instantiate the PEPO and the corresponding [`InfiniteTransferPEPO`](@ref) +in the right direction, on the right row of the partition function (trivial here): +""" + +T = ising_pepo(1) +transfer_pepo = InfiniteTransferPEPO(ψ, T, 1, 1) + +md""" +As before, we converge the boundary MPS using VUMPS and then compute the expectation value: +""" + +mps₀_pepo = initialize_mps(transfer_pepo, [ComplexSpace(20)]; alg_orth = CUSOLVER_HouseholderQR(; positive=true)) +mps_pepo, = leading_boundary(mps₀_pepo, transfer_pepo, VUMPS(; tol = 1.0e-6, verbosity = 2)) +norm_pepo = abs(prod(expectation_value(mps_pepo, transfer_pepo))); +@show norm_pepo; + +md""" +These objects and routines can be used to optimize PEPS fixed points of 3D partition +functions, see for example [Vanderstraeten et al.](@cite vanderstraeten_residual_2018) +""" diff --git a/examples/cu_fermi_hubbard/main.jl b/examples/cu_fermi_hubbard/main.jl new file mode 100644 index 000000000..f1ef40388 --- /dev/null +++ b/examples/cu_fermi_hubbard/main.jl @@ -0,0 +1,104 @@ +using Markdown #hide +md""" +# Fermi-Hubbard model with $f\mathbb{Z}_2 \boxtimes U(1)$ symmetry, at large $U$ and half-filling + +In this example, we will demonstrate how to handle fermionic PEPS tensors and how to +optimize them. To that end, we consider the two-dimensional Hubbard model + +```math +H = -t \sum_{\langle i,j \rangle} \sum_{\sigma} \left( c_{i,\sigma}^+ c_{j,\sigma}^- - +c_{i,\sigma}^- c_{j,\sigma}^+ \right) + U \sum_i n_{i,\uparrow}n_{i,\downarrow} - \mu \sum_i n_i +``` + +where $\sigma \in \{\uparrow,\downarrow\}$ and $n_{i,\sigma} = c_{i,\sigma}^+ c_{i,\sigma}^-$ +is the fermionic number operator. As in previous examples, using fermionic degrees of freedom +is a matter of creating tensors with the right symmetry sectors - the rest of the simulation +workflow remains the same. + +First though, we make the example deterministic by seeding the RNG, and we make our imports: +""" + +using Random +using TensorKit, MatrixAlgebraKit, PEPSKit, Adapt, CUDA, cuTENSOR, MPSKitModels +using MPSKit: add_physical_charge +Random.seed!(2928528937); + +md""" +## Defining the fermionic Hamiltonian + +Let us start by fixing the parameters of the Hubbard model. We're going to use a hopping of +$t=1$ and a large $U=8$ on a $2 \times 2$ unit cell: +""" + +t = 1.0 +U = 8.0 +lattice = InfiniteSquare(2, 2); + +md""" +In order to create fermionic tensors, one needs to define symmetry sectors using TensorKit's +`FermionParity`. Not only do we want use fermion parity but we also want our +particles to exploit the global $U(1)$ symmetry. The combined product sector can be obtained +using the [Deligne product](https://jutho.github.io/TensorKit.jl/stable/lib/sectors/#TensorKitSectors.deligneproduct-Tuple{Sector,%20Sector}), +called through `⊠` which is obtained by typing `\boxtimes+TAB`. We will not impose any extra +spin symmetry, so we have: +""" + +fermion = fℤ₂ +particle_symmetry = U1Irrep +spin_symmetry = Trivial +S = fermion ⊠ particle_symmetry + +md""" +The next step is defining graded virtual PEPS and environment spaces using `S`. Here we also +use the symmetry sector to impose half-filling. That is all we need to define the Hubbard +Hamiltonian: +""" + +D, χ = 1, 1 +V_peps = Vect[S]((0, 0) => 2 * D, (1, 1) => D, (1, -1) => D) +V_env = Vect[S]( + (0, 0) => 4 * χ, (1, -1) => 2 * χ, (1, 1) => 2 * χ, (0, 2) => χ, (0, -2) => χ +) +S_aux = S((1, 1)) +H₀ = hubbard_model(ComplexF64, particle_symmetry, spin_symmetry, lattice; t, U) +H = adapt(CuArray, add_physical_charge(H₀, fill(S_aux, size(H₀.lattice)...))); + +md""" +## Finding the ground state + +Again, the procedure of ground state optimization is very similar to before. First, we +define all algorithmic parameters: +""" + +boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, decomposition_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration()), trunc = (; alg = :fixedspace)) +gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge) +optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 80, ls_maxiter = 3, ls_maxfg = 3) + +md""" +Second, we initialize a PEPS state and environment (which we converge) constructed from +symmetric physical and virtual spaces: +""" + +physical_spaces = physicalspace(H) +virtual_spaces = fill(V_peps, size(lattice)...) +peps₀ = InfinitePEPS(randn, CuMatrix{ComplexF64}, physical_spaces, virtual_spaces) +env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...); + +md""" +And third, we start the ground state search (this does take quite long): +""" + +peps, env, E, info = fixedpoint( + H, peps₀, env₀; boundary_alg, gradient_alg, optimizer_alg, verbosity = 3 +) +@show E; + +md""" +Finally, let's compare the obtained energy against a reference energy from a QMC study by +[Qin et al.](@cite qin_benchmark_2016). With the parameters specified above, they obtain an +energy of $E_\text{ref} \approx 4 \times -0.5244140625 = -2.09765625$ (the factor 4 comes +from the $2 \times 2$ unit cell that we use here). Thus, we find: +""" + +E_ref = -2.09765625 +@show (E - E_ref) / E_ref; diff --git a/examples/cu_heisenberg/main.jl b/examples/cu_heisenberg/main.jl new file mode 100644 index 000000000..573e6d933 --- /dev/null +++ b/examples/cu_heisenberg/main.jl @@ -0,0 +1,187 @@ +using Markdown #hide +md""" +# [Optimizing the 2D Heisenberg model](@id examples_heisenberg) + +In this example we want to provide a basic rundown of PEPSKit's optimization workflow for +PEPS. To that end, we will consider the two-dimensional Heisenberg model on a square lattice + +```math +H = \sum_{\langle i,j \rangle} \left ( J_x S^{x}_i S^{x}_j + J_y S^{y}_i S^{y}_j + J_z S^{z}_i S^{z}_j \right ) +``` + +Here, we want to set $J_x = J_y = J_z = 1$ where the Heisenberg model is in the antiferromagnetic +regime. Due to the bipartite sublattice structure of antiferromagnetic order one needs a +PEPS ansatz with a $2 \times 2$ unit cell. This can be circumvented by performing a unitary +sublattice rotation on all B-sites resulting in a change of parameters to +$(J_x, J_y, J_z)=(-1, 1, -1)$. This gives us a unitarily equivalent Hamiltonian (with the +same spectrum) with a ground state on a single-site unit cell. + +Let us get started by fixing the random seed of this example to make it deterministic: +""" + +using Random +Random.seed!(123456789); + +md""" +We're going to need only two packages: `TensorKit`, since we use that for all the underlying +tensor operations, and `PEPSKit` itself. So let us import these: +""" + +using TensorKit, Adapt, PEPSKit, MPSKitModels, CUDA, cuTENSOR + +md""" +## Defining the Heisenberg Hamiltonian + +To create the sublattice rotated Heisenberg Hamiltonian on an infinite square lattice, we use +the `heisenberg_XYZ` method from [MPSKitModels](https://quantumkithub.github.io/MPSKitModels.jl/dev/) +which is redefined for the `InfiniteSquare` and reexported in PEPSKit: +""" + +H = adapt(CuArray, heisenberg_XYZ(ComplexF64, InfiniteSquare(); Jx = -1, Jy = 1, Jz = -1)) + +md""" +## Setting up the algorithms and initial guesses + +Next, we set the simulation parameters. During optimization, the PEPS will be contracted +using CTMRG and the PEPS gradient will be computed by differentiating through the CTMRG +routine using AD. Since the algorithmic stack that implements this is rather elaborate, +the amount of settings one can configure is also quite large. To reduce this complexity, +PEPSKit defaults to (presumably) reasonable settings which also dynamically adapts to the +user-specified parameters. + +First, we set the bond dimension `Dbond` of the virtual PEPS indices and the environment +dimension `χenv` of the virtual corner and transfer matrix indices. +""" + +Dbond = 2 +χenv = 16; + +md""" +To configure the CTMRG algorithm, we create a `NamedTuple` containing different keyword +arguments. To see a description of all arguments, see the docstring of +[`leading_boundary`](@ref). Here, we want to converge the CTMRG environments up to a +specific tolerance and during the CTMRG run keep all index dimensions fixed: +""" + +boundary_alg = (; tol = 1.0e-10, trunc = (; alg = :fixedspace)); + +md""" +Let us also configure the optimizer algorithm. We are going to optimize the PEPS using the +L-BFGS optimizer from [OptimKit](https://github.com/Jutho/OptimKit.jl). Again, we specify +the convergence tolerance (for the gradient norm) as well as the maximal number of iterations +and the BFGS memory size (which is used to approximate the Hessian): +""" + +optimizer_alg = (; alg = :lbfgs, tol = 1.0e-4, maxiter = 100, lbfgs_memory = 16); + +md""" +Additionally, during optimization, we want to reuse the previous CTMRG environment to +initialize the CTMRG run of the current optimization step using the `reuse_env` argument. +And to control the output information, we set the `verbosity`: +""" + +reuse_env = true +verbosity = 3; + +md""" +Next, we initialize a random PEPS which will be used as an initial guess for the +optimization. To get a PEPS with physical dimension 2 (since we have a spin-1/2 Hamiltonian) +with complex-valued random Gaussian entries, we set: +""" + +peps₀ = InfinitePEPS(randn, CuMatrix{ComplexF64}, ℂ^2, ℂ^Dbond) + +md""" +The last thing we need before we can start the optimization is an initial CTMRG environment. +Typically, a random environment which we converge on `peps₀` serves as a good starting point. +To contract a PEPS starting from an environment using CTMRG, we call [`leading_boundary`](@ref): +""" + +env_random = CTMRGEnv(randn, CuMatrix{ComplexF64}, peps₀, ℂ^χenv); +env₀, info_ctmrg = leading_boundary(env_random, peps₀; boundary_alg...); + +md""" +Besides the converged environment, `leading_boundary` also returns a `NamedTuple` of +informational quantities such as the last maximal truncation error - that is, the SVD +approximation error incurred in the last CTMRG iteration, maximized over all spatial +directions and unit cell entries: +""" + +@show info_ctmrg.truncation_error; + +md""" +## Ground state search + +Finally, we can start the optimization by calling [`fixedpoint`](@ref) on `H` with our +settings for the boundary (CTMRG) algorithm and the optimizer. This might take a while +(especially the precompilation of AD code in this case): +""" + +peps, env, E, info_opt = fixedpoint( + H, peps₀, env₀; boundary_alg, optimizer_alg, reuse_env, verbosity +); + +md""" +Note that `fixedpoint` returns the final optimized PEPS, the last converged environment, +the final energy estimate as well as a `NamedTuple` of diagnostics. This allows us to, e.g., +analyze the number of cost function calls or the history of gradient norms to evaluate +the convergence rate: +""" + +@show info_opt.fg_evaluations info_opt.gradnorms[1:10:end]; + +md""" +Let's now compare the optimized energy against an accurate Quantum Monte Carlo estimate by +[Sandvik](@cite sandvik_computational_2011), where the energy per site was found to be +$E_{\text{ref}}=−0.6694421$. From our simple optimization we find: +""" + +@show E; + +md""" +While this energy is in the right ballpark, there is still quite some deviation from the +accurate reference energy. This, however, can be attributed to the small bond dimension - an +optimization with larger bond dimension would approach this value much more closely. + +A more reasonable comparison would be against another finite bond dimension PEPS simulation. +For example, Juraj Hasik's data from $J_1\text{-}J_2$ +[PEPS simulations](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) +yields $E_{D=2,\chi=16}=-0.660231\dots$ which is more in line with what we find here. + +## Compute the correlation lengths and transfer matrix spectra + +In practice, in order to obtain an accurate and variational energy estimate, one would need +to compute multiple energies at different environment dimensions and extrapolate in, e.g., +the correlation length or the second gap of the transfer matrix spectrum. For that, we would +need the [`correlation_length`](@ref) function, which computes the horizontal and vertical +correlation lengths and transfer matrix spectra for all unit cell coordinates: +""" + +ξ_h, ξ_v, λ_h, λ_v = correlation_length(peps, env) +@show ξ_h ξ_v; + +md""" +## Computing observables + +As a last thing, we want to see how we can compute expectation values of observables, given +the optimized PEPS and its CTMRG environment. To compute, e.g., the magnetization, we first +need to define the observable as a `TensorMap`: +""" + +σ_z = TensorMap([1.0 0.0; 0.0 -1.0], ℂ^2, ℂ^2) + +md""" +In order to be able to contract it with the PEPS and environment, we define need to define a +`LocalOperator` and specify on which physical spaces and sites the observable acts. That way, +the PEPS-environment-operator contraction gets automatically generated (also works for +multi-site operators!). See the [`LocalOperator`](@ref) docstring for more details. +The magnetization is just a single-site observable, so we have: +""" + +M = LocalOperator(fill(ℂ^2, 1, 1), (CartesianIndex(1, 1),) => σ_z) + +md""" +Finally, to evaluate the expecation value on the `LocalOperator`, we call: +""" + +@show expectation_value(peps, M, env); diff --git a/examples/cu_heisenberg_su/main.jl b/examples/cu_heisenberg_su/main.jl new file mode 100644 index 000000000..6785b9ad1 --- /dev/null +++ b/examples/cu_heisenberg_su/main.jl @@ -0,0 +1,139 @@ +using Markdown #hide +md""" +# Simple update for the Heisenberg model + +In this example, we will use [`SimpleUpdate`](@ref) imaginary time evolution to treat +the two-dimensional Heisenberg model once again: + +```math +H = \sum_{\langle i,j \rangle} J_x S^{x}_i S^{x}_j + J_y S^{y}_i S^{y}_j + J_z S^{z}_i S^{z}_j. +``` + +In order to simulate the antiferromagnetic order of the Hamiltonian on a single-site unit +cell one typically applies a unitary sublattice rotation. Here, we will instead use a +$2 \times 2$ unit cell and set $J_x = J_y = J_z = 1$. + +Let's get started by seeding the RNG and importing all required modules: +""" + +using Random +import Statistics: mean +using TensorKit, Adapt, PEPSKit, MPSKitModels, CUDA, cuTENSOR +import MPSKitModels: S_x, S_y, S_z, S_exchange +Random.seed!(0); + +md""" +## Defining the Hamiltonian + +To construct the Heisenberg Hamiltonian as just discussed, we'll use `heisenberg_XYZ` and, +in addition, make it real (`real` and `imag` works for `LocalOperator`s) since we want to +use PEPS and environments with real entries. We can either initialize the Hamiltonian with +no internal symmetries (`symm = Trivial`) or use the global spin $U(1)$ symmetry +(`symm = U1Irrep`): +""" + +symm = Trivial ## ∈ {Trivial, U1Irrep} +Nr, Nc = 2, 2 +H = adapt(CuArray, real(heisenberg_XYZ(ComplexF64, symm, InfiniteSquare(Nr, Nc); Jx = 1, Jy = 1, Jz = 1))); + +md""" +## Simple updating + +We proceed by initializing a random PEPS that will be evolved. +The weights used for simple update are initialized as identity matrices. +First though, we need to define the appropriate (symmetric) spaces: +""" + +Dbond = 4 +χenv = 16 +if symm == Trivial + physical_space = ℂ^2 + bond_space = ℂ^Dbond + env_space = ℂ^χenv +elseif symm == U1Irrep + physical_space = ℂ[U1Irrep](1 // 2 => 1, -1 // 2 => 1) + bond_space = ℂ[U1Irrep](0 => Dbond ÷ 2, 1 // 2 => Dbond ÷ 4, -1 // 2 => Dbond ÷ 4) + env_space = ℂ[U1Irrep](0 => χenv ÷ 2, 1 // 2 => χenv ÷ 4, -1 // 2 => χenv ÷ 4) +else + error("not implemented") +end + +peps = InfinitePEPS(rand, CuMatrix{Float64}, physical_space, bond_space; unitcell = (Nr, Nc)); +wts = SUWeight(peps); + +md""" +Next, we can start the `SimpleUpdate` routine, successively decreasing the time intervals +and singular value convergence tolerances. Note that TensorKit allows to combine SVD +truncation schemes, which we use here to set a maximal bond dimension and at the same time +fix a truncation error (if that can be reached by remaining below `Dbond`): +""" + +dts = [1.0e-2, 1.0e-3, 4.0e-4] +tols = [1.0e-6, 1.0e-8, 1.0e-8] +nstep = 10000 +trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dbond) +alg = SimpleUpdate(; trunc = trunc_peps, bipartite = true) +for (dt, tol) in zip(dts, tols) + global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol, check_interval = 500) +end + +md""" +## Computing the ground-state energy and magnetizations + +In order to compute observable expectation values, we need to converge a CTMRG environment +on the evolved PEPS. Let's do so: +""" +normalize!.(peps.A, Inf) +env₀ = CTMRGEnv(rand, CuMatrix{Float64}, peps, env_space) +trunc_env = truncerror(; atol = 1.0e-10) & truncrank(χenv) +env, = leading_boundary( + env₀, + peps; + alg = :sequential, + projector_alg = :fullinfinite, + tol = 1.0e-10, + trunc = trunc_env, +); + +md""" +Finally, we'll measure the energy and different magnetizations. For the magnetizations, +the plan is to compute the expectation values unit cell entry-wise in different spin +directions: +""" + +function compute_mags(peps::InfinitePEPS, env::CTMRGEnv) + lattice = collect(space(t, 1) for t in peps.A) + + ## detect symmetry on physical axis + symm = sectortype(space(peps.A[1, 1])) + if symm == Trivial + S_ops = real.([S_x(symm), im * S_y(symm), S_z(symm)]) + elseif symm == U1Irrep + S_ops = real.([S_z(symm)]) ## only Sz preserves + end + + return map(Iterators.product(axes(peps, 1), axes(peps, 2), S_ops)) do (r, c, S) + expectation_value(peps, LocalOperator(lattice, (CartesianIndex(r, c),) => S), env) + end +end + +E = expectation_value(peps, H, env) / (Nr * Nc) +Ms = compute_mags(peps, env) +M_norms = map( + rc -> norm(Ms[rc[1], rc[2], :]), Iterators.product(axes(peps, 1), axes(peps, 2)) +) +@show E Ms M_norms; + +md""" +To assess the results, we will benchmark against data from [Corboz](@cite corboz_variational_2016), +which use manual gradients to perform a variational optimization of the Heisenberg model. +In particular, for the energy and magnetization they find $E_\text{ref} = -0.6675$ and +$M_\text{ref} = 0.3767$. Looking at the relative errors, we find general agreement, although +the accuracy is limited by the methodological limitations of the simple update algorithm as +well as finite bond dimension effects and a lacking extrapolation: +""" + +E_ref = -0.6675 +M_ref = 0.3767 +@show (E - E_ref) / abs(E_ref) +@show (mean(M_norms) - M_ref) / M_ref; diff --git a/examples/cu_hubbard_su/main.jl b/examples/cu_hubbard_su/main.jl new file mode 100644 index 000000000..4bf099c0c --- /dev/null +++ b/examples/cu_hubbard_su/main.jl @@ -0,0 +1,111 @@ +using Markdown #hide +md""" +# Simple update for the Fermi-Hubbard model at half-filling + +Once again, we consider the Hubbard model but this time we obtain the ground-state PEPS by +imaginary time evolution. In particular, we'll use the [`SimpleUpdate`](@ref) algorithm. +As a reminder, we define the Hubbard model as + +```math +H = -t \sum_{\langle i,j \rangle} \sum_{\sigma} \left( c_{i,\sigma}^+ c_{j,\sigma}^- - +c_{i,\sigma}^- c_{j,\sigma}^+ \right) + U \sum_i n_{i,\uparrow}n_{i,\downarrow} - \mu \sum_i n_i +``` + +with $\sigma \in \{\uparrow,\downarrow\}$ and $n_{i,\sigma} = c_{i,\sigma}^+ c_{i,\sigma}^-$. + +Let's get started by seeding the RNG and importing the required modules: +""" + +using Random +using TensorKit, Adapt, PEPSKit, MPSKit, CUDA, cuTENSOR, MatrixAlgebraKit +Random.seed!(12329348592498); + +MPSKit.Defaults.alg_svd() = CUSOLVER_QRIteration() + +md""" +## Defining the Hamiltonian + +First, we define the Hubbard model at $t=1$ hopping and $U=6$ using `Trivial` sectors for +the particle and spin symmetries, and set $\mu = U/2$ for half-filling. The model will be +constructed on a $2 \times 2$ unit cell, so we have: +""" + +t = 1 +U = 6 +Nr, Nc = 2, 2 +H = adapt(CuArray, hubbard_model(Float64, Trivial, Trivial, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)); +physical_space = Vect[fℤ₂](0 => 2, 1 => 2); + +md""" +## Running the simple update algorithm + +Suppose the goal is to use imaginary-time simple update to optimize a PEPS +with bond dimension D = 8, and $2 \times 2$ unit cells. +For a challenging model like the Hubbard model, a naive evolution starting from a +random PEPS at D = 8 will almost always produce a sub-optimal state. +In this example, we shall demonstrate some common practices to improve SU result. + +First, we shall use a small D for the random PEPS initialization, which is chosen as 4 here. +For convenience, here we work with real tensors with `Float64` entries. +The bond weights are still initialized as identity matrices. +""" + +virtual_space = Vect[fℤ₂](0 => 2, 1 => 2) +peps = InfinitePEPS(rand, CuMatrix{Float64}, physical_space, virtual_space; unitcell = (Nr, Nc)); +wts = SUWeight(peps); + +md""" +Starting from the random state, we first use a relatively large evolution time step +`dt = 1e-2`. After convergence at D = 4, to avoid stucking at some bad local minimum, +we first increase D to 12, and drop it back to D = 8 after a while. +Afterwards, we keep D = 8 and gradually decrease `dt` to `1e-4` to improve convergence. +""" + +dts = [1.0e-2, 1.0e-2, 1.0e-3, 4.0e-4, 1.0e-4] +tols = [1.0e-7, 1.0e-7, 1.0e-8, 1.0e-8, 1.0e-8] +Ds = [4, 12, 8, 8, 8] +maxiter = 20000 + +for (dt, tol, Dbond) in zip(dts, tols, Ds) + trunc = truncerror(; atol = 1.0e-10) & truncrank(Dbond) + alg = SimpleUpdate(; trunc, bipartite = false) + global peps, wts, = time_evolve(peps, H, dt, maxiter, alg, wts; tol, check_interval = 2000) +end + +md""" +## Computing the ground-state energy + +In order to compute the energy expectation value with evolved PEPS, we need to converge a +CTMRG environment on it. We first converge an environment with a small enviroment dimension, +which is initialized using the simple update bond weights. Next we use it to initialize +another run with bigger environment dimension. The dynamic adjustment of environment dimension +is achieved by using `trunc=truncrank(χ)` with different `χ`s in the CTMRG runs: +""" + +χenv₀, χenv = 6, 16 +env_space = Vect[fℤ₂](0 => χenv₀ / 2, 1 => χenv₀ / 2) +normalize!.(peps.A, Inf) +env = CTMRGEnv(wts) +for χ in [χenv₀, χenv] + global env, = leading_boundary( + env, peps; alg = :sequential, tol = 1.0e-8, maxiter = 50, trunc = truncrank(χ) + ) +end + +md""" +We measure the energy by computing the `H` expectation value, where we have to make sure to +normalize with respect to the unit cell to obtain the energy per site: +""" + +E = expectation_value(peps, H, env) / (Nr * Nc) +@show E; + +md""" +Finally, we can compare the obtained ground-state energy against the literature, namely the +QMC estimates from [Qin et al.](@cite qin_benchmark_2016). We find that the results generally +agree: +""" + +Es_exact = Dict(0 => -1.62, 2 => -0.176, 4 => 0.8603, 6 => -0.6567, 8 => -0.5243) +E_exact = Es_exact[U] - U / 2 +@show (E - E_exact) / abs(E_exact); diff --git a/examples/cu_j1j2_su/main.jl b/examples/cu_j1j2_su/main.jl new file mode 100644 index 000000000..a6123aba7 --- /dev/null +++ b/examples/cu_j1j2_su/main.jl @@ -0,0 +1,129 @@ +using Markdown #hide +md""" +# Three-site simple update for the $J_1$-$J_2$ model + +In this example, we will use [`SimpleUpdate`](@ref) imaginary time evolution to treat +the two-dimensional $J_1$-$J_2$ model, which contains next-nearest-neighbour interactions: + +```math +H = J_1 \sum_{\langle i,j \rangle} \mathbf{S}_i \cdot \mathbf{S}_j ++ J_2 \sum_{\langle \langle i,j \rangle \rangle} \mathbf{S}_i \cdot \mathbf{S}_j +``` + +Here we will exploit the $U(1)$ spin rotation symmetry in the $J_1$-$J_2$ model. The goal +will be to calculate the energy at $J_1 = 1$ and $J_2 = 1/2$, first using the simple update +algorithm and then, to refine the energy estimate, using AD-based variational PEPS +optimization. + +We first import all required modules and seed the RNG: +""" + +using Random +using TensorKit, Adapt, PEPSKit, Strided, CUDA, cuTENSOR, MatrixAlgebraKit +Random.seed!(29385293); + +md""" +## Simple updating a challenging phase + +Let's start by initializing an `InfinitePEPS` for which we set the required parameters +as well as physical and virtual vector spaces. +The `SUWeight` used by simple update will be initialized to identity matrices. +We use the minimal unit cell size ($2 \times 2$) required by the simple update algorithm +for Hamiltonians with next-nearest-neighbour interactions: +""" + +Dbond, symm = 4, U1Irrep +Nr, Nc, J1 = 2, 2, 1.0 + +## random initialization of 2x2 iPEPS (using real numbers) and SUWeight +Pspace = Vect[U1Irrep](1 // 2 => 1, -1 // 2 => 1) +Vspace = Vect[U1Irrep](0 => 2, 1 // 2 => 1, -1 // 2 => 1) +peps = InfinitePEPS(rand, CuMatrix{Float64}, Pspace, Vspace; unitcell = (Nr, Nc)); +wts = SUWeight(peps); + +md""" +The value $J_2 / J_1 = 0.5$ corresponds to a [possible spin liquid phase](@cite liu_gapless_2022), +which is challenging for SU to produce a relatively good state from random initialization. +Therefore, we shall gradually increase $J_2 / J_1$ from 0.1 to 0.5, each time initializing +on the previously evolved PEPS: +""" + +dt, tol, nstep = 1.0e-2, 1.0e-8, 30000 +check_interval = 4000 +trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dbond) +alg = SimpleUpdate(; trunc = trunc_peps) +for J2 in 0.1:0.1:0.5 + ## convert Hamiltonian `LocalOperator` to real floats + H = adapt(CuArray, real( + j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false), + )) + global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol, check_interval) +end + +md""" +After we reach $J_2 / J_1 = 0.5$, we gradually decrease the evolution time step to obtain +a more accurately evolved PEPS: +""" + +dts = [1.0e-3, 1.0e-4] +tols = [1.0e-9, 1.0e-9] +J2 = 0.5 +H = adapt(CuArray, real(j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false))) +for (dt, tol) in zip(dts, tols) + global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol) +end + +md""" +## Computing the simple update energy estimate + +Finally, we measure the ground-state energy by converging a CTMRG environment and computing +the expectation value, where we first normalize tensors in the PEPS: +""" + +normalize!.(peps.A, Inf) ## normalize each PEPS tensor by largest element +χenv = 32 +trunc_env = truncerror(; atol = 1.0e-10) & truncrank(χenv) +Espace = Vect[U1Irrep](0 => χenv ÷ 2, 1 // 2 => χenv ÷ 4, -1 // 2 => χenv ÷ 4) +env₀ = CTMRGEnv(rand, CuMatrix{Float64, CUDA.DeviceMemory}, peps, Espace) +env, = leading_boundary(env₀, peps; tol = 1.0e-10, alg = :sequential, trunc = trunc_env, decomposition_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration())); +E = expectation_value(peps, H, env) / (Nr * Nc) + +md""" +Let us compare that estimate with benchmark data obtained from the +[YASTN/peps-torch package](https://github.com/jurajHasik/j1j2_ipeps_states/blob/ea4140fbd7da0fc1b75fac2871f75bda125189a8/single-site_pg-C4v-A1_internal-U1/j20.5/state_1s_A1_U1B_j20.5_D4_chi_opt96.dat). +which utilizes AD-based PEPS optimization to find $E_\text{ref}=-0.49425$: +""" + +E_ref = -0.49425 +@show (E - E_ref) / abs(E_ref); + +md""" +## Variational PEPS optimization using AD + +As a last step, we will use the SU-evolved PEPS as a starting point for a [`fixedpoint`](@ref) +PEPS optimization. Note that we could have also used a sublattice-rotated version of `H` to +fit the Hamiltonian onto a single-site unit cell which would require us to optimize fewer +parameters and hence lead to a faster optimization. But here we instead take advantage of +the already evolved `peps`, thus giving us a physical initial guess for the optimization. +In order to break some of the $C_{4v}$ symmetry of the PEPS, we will add a bit of noise to it. +This is conviently done using MPSKit's `randomize!` function. +(Breaking some of the spatial symmetry can be advantageous for obtaining lower energies.) +""" + +using MPSKit: randomize! + +noise_peps = InfinitePEPS(randomize!.(deepcopy(peps.A))) +peps₀ = peps + 1.0e-1noise_peps +peps_opt, env_opt, E_opt, = fixedpoint( + H, peps₀, env; optimizer_alg = (; tol = 1.0e-4, maxiter = 80) +); + +md""" +Finally, we compare the variationally optimized energy against the reference energy. Indeed, +we find that the additional AD-based optimization improves the SU-evolved PEPS and leads to +a more accurate energy estimate. +""" + +E_opt /= (Nr * Nc) +@show E_opt +@show (E_opt - E_ref) / abs(E_ref); diff --git a/examples/cu_xxz/main.jl b/examples/cu_xxz/main.jl new file mode 100644 index 000000000..d79dfd846 --- /dev/null +++ b/examples/cu_xxz/main.jl @@ -0,0 +1,101 @@ +using Markdown #hide +md""" +# Néel order in the $U(1)$-symmetric XXZ model + +Here, we want to look at a special case of the Heisenberg model, where the $x$ and $y$ +couplings are equal, called the XXZ model + +```math +H_0 = J \big(\sum_{\langle i, j \rangle} S_i^x S_j^x + S_i^y S_j^y + \Delta S_i^z S_j^z \big) . +``` + +For appropriate $\Delta$, the model enters an antiferromagnetic phase (Néel order) which we +will force by adding staggered magnetic charges to $H_0$. Furthermore, since the XXZ +Hamiltonian obeys a $U(1)$ symmetry, we will make use of that and work with $U(1)$-symmetric +PEPS and CTMRG environments. For simplicity, we will consider spin-$1/2$ operators. + +But first, let's make this example deterministic and import the required packages: +""" + +using Random +using TensorKit, PEPSKit, CUDA, cuTENSOR, MatrixAlgebraKit +using MPSKit: add_physical_charge +Random.seed!(2928528935); + +md""" +## Constructing the model + +Let us define the $U(1)$-symmetric XXZ Hamiltonian on a $2 \times 2$ unit cell with the +parameters: +""" + +J = 1.0 +Delta = 1.0 +spin = 1 // 2 +symmetry = U1Irrep +lattice = InfiniteSquare(2, 2) +H₀ = heisenberg_XXZ(CuMatrix{ComplexF64}, symmetry, lattice; J, Delta, spin); + +md""" +This ensures that our PEPS ansatz can support the bipartite Néel order. As discussed above, +we encode the Néel order directly in the ansatz by adding staggered auxiliary physical +charges: +""" + +S_aux = [ + U1Irrep(-1 // 2) U1Irrep(1 // 2) + U1Irrep(1 // 2) U1Irrep(-1 // 2) +] +H = add_physical_charge(H₀, S_aux); + +md""" +## Specifying the symmetric virtual spaces + +Before we create an initial PEPS and CTM environment, we need to think about which +symmetric spaces we need to construct. Since we want to exploit the global $U(1)$ symmetry +of the model, we will use TensorKit's `U1Space`s where we specify dimensions for each +symmetry sector. From the virtual spaces, we will need to construct a unit cell (a matrix) +of spaces which will be supplied to the PEPS constructor. The same is true for the physical +spaces, which can be extracted directly from the Hamiltonian `LocalOperator`: +""" + +V_peps = U1Space(0 => 2, 1 => 1, -1 => 1) +V_env = U1Space(0 => 6, 1 => 4, -1 => 4, 2 => 2, -2 => 2) +virtual_spaces = fill(V_peps, size(lattice)...) +physical_spaces = physicalspace(H) + +md""" +## Ground state search + +From this point onwards it's business as usual: Create an initial PEPS and environment +(using the symmetric spaces), specify the algorithmic parameters and optimize: +""" + +boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, decomposition_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration()), trunc = (; alg = :fixedspace)) +gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge) +optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 85, ls_maxiter = 3, ls_maxfg = 3) + +peps₀ = InfinitePEPS(randn, CuMatrix{ComplexF64}, physical_spaces, virtual_spaces) +env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...); + +md""" +Finally, we can optimize the PEPS with respect to the XXZ Hamiltonian and check the +resulting ground state energy per site using our $(2 \times 2)$ unit cell. Note that the +optimization might take a while since precompilation of symmetric AD code takes longer and +because symmetric tensors do create a bit of overhead (which does pay off at larger bond and +environment dimensions): +""" + +peps, env, E, info = fixedpoint( + H, peps₀, env₀; boundary_alg, gradient_alg, optimizer_alg, verbosity = 3 +) +@show E / prod(size(lattice)); + +md""" +Note that for the specified parameters $J = \Delta = 1$, we simulated the same Hamiltonian +as in the [Heisenberg example](@ref examples_heisenberg). In that example, with a +non-symmetric $D=2$ PEPS simulation, we reached a ground-state energy per site of around +$E_\text{D=2} = -0.6625\dots$. Again comparing against [Sandvik's](@cite +sandvik_computational_2011) accurate QMC estimate ``E_{\text{ref}}=−0.6694421``, we see that +we already got closer to the reference energy. +""" diff --git a/ext/PEPSKitCUDAExt.jl b/ext/PEPSKitCUDAExt.jl new file mode 100644 index 000000000..87b91422b --- /dev/null +++ b/ext/PEPSKitCUDAExt.jl @@ -0,0 +1,39 @@ +module PEPSKitCUDAExt + +using PEPSKit, CUDA, cuTENSOR, Random +import CUDA: rand as curand, rand! as curand!, randn as curandn, randn! as curandn! + +using PEPSKit.TensorKit +import PEPSKit: PEPSTensor, _corner_tensor, _edge_tensor + +function PEPSTensor( + f::typeof(rand), + ::Type{TA}, + Pspace::S, + Nspace::S, Espace::S = Nspace, Sspace::S = Nspace', Wspace::S = Espace', + ) where {S <: ElementarySpace, TA <: CuArray} + return curand(eltype(TA), Pspace ← Nspace ⊗ Espace ⊗ Sspace ⊗ Wspace) +end + +function PEPSTensor( + f::typeof(randn), + ::Type{TA}, + Pspace::S, + Nspace::S, Espace::S = Nspace, Sspace::S = Nspace', Wspace::S = Espace', + ) where {S <: ElementarySpace, TA <: CuArray} + return curandn(eltype(TA), Pspace ← Nspace ⊗ Espace ⊗ Sspace ⊗ Wspace) +end + +function _corner_tensor( + f::typeof(rand), ::Type{TA}, left_vspace::S, right_vspace::S = left_vspace + ) where {T, TA <: CuArray{T}, S <: ElementarySpace} + return curand(T, left_vspace ← right_vspace) +end + +function _edge_tensor( + f::typeof(randn), ::Type{TA}, left_vspace::S, pspaces::P, right_vspace::S = left_vspace + ) where {T, TA <: CuArray{T}, S <: ElementarySpace, P <: ProductSpace} + return curandn(T, left_vspace ⊗ pspaces, right_vspace) +end + +end diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 4280c30af..3b1943076 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -18,6 +18,7 @@ using TensorKit using TensorKit: AdjointTensorMap, SectorDict using TensorKit: throw_invalid_innerproduct, similarstoragetype using TensorKit.Factorizations: TruncationSpace, _notrunc_ind +import TensorKit: storagetype using KrylovKit using KrylovKit: Lanczos, BlockLanczos diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 9263eb89f..d6fab844c 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -190,10 +190,6 @@ function _singular_value_distance(S₁::SV, S₂::SV) where {SV <: TensorKit.Sec for (c, b) in blocks(S₁) diff[c][1:length(b)] .= b end - for (c, b) in blocks(S₂) - diff[c][1:length(b)] .-= b - end - return norm(diff) end _singular_value_distance(S₁::DiagonalTensorMap, S₂::DiagonalTensorMap) = diff --git a/src/algorithms/optimization/peps_optimization.jl b/src/algorithms/optimization/peps_optimization.jl index 808ed04f5..6009ad8b1 100644 --- a/src/algorithms/optimization/peps_optimization.jl +++ b/src/algorithms/optimization/peps_optimization.jl @@ -306,7 +306,8 @@ function fixedpoint( alg.reuse_env && update!(env, env′) tracked_finalizer.contraction_metrics[end] = info.contraction_metrics end - return cost_function(ψ, env′, operator) + cf = cost_function(ψ, env′, operator) + return cf end g = peps_project_tangent(only(gs), peps) tracked_finalizer.gradnorms_unitcell[end] = norm.(unitcell(g)) diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index 2671a8053..5f51ac6e1 100644 --- a/src/algorithms/select_algorithm.jl +++ b/src/algorithms/select_algorithm.jl @@ -155,6 +155,7 @@ function select_algorithm( rrule_alg = (; tol = 1.0e1tol, verbosity = verbosity - 2, krylovdim, decomposition_alg.rrule_alg...) decomposition_alg = (; rrule_alg, decomposition_alg...) end + decomposition_alg = isa(decomposition_alg, SVDAdjoint) ? decomposition_alg : SVDAdjoint(; decomposition_alg...) return CTMRGAlgorithm(; alg, tol, verbosity, decomposition_alg, kwargs...) end diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index f88ba8e2b..cbbcea503 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -111,7 +111,7 @@ function product_peps(peps_args...; unitcell = (1, 1), noise_amp = 1.0e-2, state error("symmetric tensors not generically supported") if isnothing(state_vector) state_vector = map(noise_peps.A) do t - randn(scalartype(t), dim(space(t, 1))) + randn(storagetype(t), dim(space(t, 1))) end else all(dim.(space.(noise_peps.A, 1)) .== length.(state_vector)) || diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 50b394b6d..8ba5e1848 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -51,20 +51,20 @@ function check_environment_virtualspace(E::CTMRGEdgeTensor) end function _corner_tensor( - f, ::Type{T}, left_vspace::S, right_vspace::S = left_vspace - ) where {T, S <: ElementarySpace} - return f(T, left_vspace ← right_vspace) + f, ::Type{TorA}, left_vspace::S, right_vspace::S = left_vspace + ) where {TorA, S <: ElementarySpace} + return f(TorA, left_vspace ← right_vspace) end function _edge_tensor( - f, ::Type{T}, left_vspace::S, pspaces::P, right_vspace::S = left_vspace - ) where {T, S <: ElementarySpace, P <: ProductSpace} - return f(T, left_vspace ⊗ pspaces, right_vspace) + f, ::Type{TA}, left_vspace::S, pspaces::P, right_vspace::S = left_vspace + ) where {TA, S <: ElementarySpace, P <: ProductSpace} + return f(TA, left_vspace ⊗ pspaces, right_vspace) end """ CTMRGEnv( - [f=randn, T=ComplexF64], Ds_north::A, Ds_east::A, chis_north::B, [chis_east::B], [chis_south::B], [chis_west::B] + [f=randn, T=ComplexF64, TA=Matrix{ComplexF64},] Ds_north::A, Ds_east::A, chis_north::B, [chis_east::B], [chis_south::B], [chis_west::B] ) where {A<:AbstractMatrix{<:VectorSpace}, B<:AbstractMatrix{<:ElementarySpace}} Construct a CTMRG environment by specifying matrices of north and east virtual spaces of the @@ -85,10 +85,11 @@ of a partition function defined in terms of local rank-4 tensors) or a `ProductS for the case of a network representing overlaps of PEPSs and PEPOs). """ function CTMRGEnv( - f, T, Ds_north::A, Ds_east::A, chis_north::B, chis_east::B = chis_north, + f, ::Type{TorA}, Ds_north::A, Ds_east::A, chis_north::B, chis_east::B = chis_north, chis_south::B = chis_north, chis_west::B = chis_north, ) where { A <: AbstractMatrix{<:ProductSpace}, B <: AbstractMatrix{<:ElementarySpace}, + TorA, } # check all of the sizes size(Ds_north) == size(Ds_east) == size(chis_north) == size(chis_east) == @@ -101,8 +102,8 @@ function CTMRGEnv( # do the whole thing N = length(first(Ds_north)) st = spacetype(first(Ds_north)) - C_type = tensormaptype(st, 1, 1, T) - T_type = tensormaptype(st, N + 1, 1, T) + C_type = tensormaptype(st, 1, 1, TorA) + T_type = tensormaptype(st, N + 1, 1, TorA) # First index is direction corners = Array{C_type}(undef, 4, size(Ds_north)...) @@ -111,28 +112,28 @@ function CTMRGEnv( for I in CartesianIndices(Ds_north) r, c = I.I edges[NORTH, r, c] = _edge_tensor( - f, T, chis_north[r, _prev(c, end)], Ds_north[_next(r, end), c], chis_north[r, c] + f, TorA, chis_north[r, _prev(c, end)], Ds_north[_next(r, end), c], chis_north[r, c] ) edges[EAST, r, c] = _edge_tensor( - f, T, chis_east[r, c], Ds_east[r, _prev(c, end)], chis_east[_next(r, end), c] + f, TorA, chis_east[r, c], Ds_east[r, _prev(c, end)], chis_east[_next(r, end), c] ) edges[SOUTH, r, c] = _edge_tensor( - f, T, chis_south[r, c], Ds_south[_prev(r, end), c], chis_south[r, _prev(c, end)] + f, TorA, chis_south[r, c], Ds_south[_prev(r, end), c], chis_south[r, _prev(c, end)] ) edges[WEST, r, c] = _edge_tensor( - f, T, chis_west[_next(r, end), c], Ds_west[r, _next(c, end)], chis_west[r, c] + f, TorA, chis_west[_next(r, end), c], Ds_west[r, _next(c, end)], chis_west[r, c] ) corners[NORTHWEST, r, c] = _corner_tensor( - f, T, chis_west[_next(r, end), c], chis_north[r, c] + f, TorA, chis_west[_next(r, end), c], chis_north[r, c] ) corners[NORTHEAST, r, c] = _corner_tensor( - f, T, chis_north[r, _prev(c, end)], chis_east[_next(r, end), c] + f, TorA, chis_north[r, _prev(c, end)], chis_east[_next(r, end), c] ) corners[SOUTHEAST, r, c] = _corner_tensor( - f, T, chis_east[r, c], chis_south[r, _prev(c, end)] + f, TorA, chis_east[r, c], chis_south[r, _prev(c, end)] ) - corners[SOUTHWEST, r, c] = _corner_tensor(f, T, chis_south[r, c], chis_west[r, c]) + corners[SOUTHWEST, r, c] = _corner_tensor(f, TorA, chis_south[r, c], chis_west[r, c]) end corners[:, :, :] ./= norm.(corners[:, :, :]) @@ -140,7 +141,7 @@ function CTMRGEnv( return CTMRGEnv(corners, edges) end function CTMRGEnv(D_north::P, args...; kwargs...) where {P <: Union{Matrix{VectorSpace}, VectorSpace}} - return CTMRGEnv(randn, ComplexF64, D_north, args...; kwargs...) + return CTMRGEnv(randn, Matrix{ComplexF64}, D_north, args...; kwargs...) end # expand physical edge spaces to unit cell size @@ -181,11 +182,11 @@ The environment virtual spaces for each site correspond to virtual space of the corresponding edge tensor for each direction. """ function CTMRGEnv( - f, T, + f, ::Type{TorA}, D_north::S, D_east::S, virtual_spaces...; unitcell::Tuple{Int, Int} = (1, 1), - ) where {S <: VectorSpace} + ) where {S <: VectorSpace, TorA} return CTMRGEnv( - f, T, + f, TorA, _fill_edge_physical_spaces(D_north, D_east; unitcell)..., _fill_environment_virtual_spaces(virtual_spaces...; unitcell)..., ) @@ -218,7 +219,7 @@ of the corresponding edge tensor for each direction. Specifically, for a given s `chis_south[r, c]` corresponds to the east space of the south edge tensor, and `chis_west[r, c]` corresponds to the north space of the west edge tensor. """ -function CTMRGEnv(f, T, network::InfiniteSquareNetwork, virtual_spaces...) +function CTMRGEnv(f, ::Type{TorA}, network::N, virtual_spaces...) where {TorA, N <: InfiniteSquareNetwork} Ds_north = _north_edge_physical_spaces(network) Ds_east = _east_edge_physical_spaces(network) virtual_spaces = _fill_environment_virtual_spaces(virtual_spaces...; unitcell = size(network)) @@ -260,13 +261,15 @@ function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnv, name::Symbol) if name === :corners function corner_pullback(Δcorners_) Δcorners = unthunk(Δcorners_) - return NoTangent(), CTMRGEnv(Δcorners, zerovector.(e.edges)), NoTangent() + zvs = CTMRGEnv(Δcorners, zerovector.(e.edges)) + return NoTangent(), zvs, NoTangent() end return result, corner_pullback elseif name === :edges function edge_pullback(Δedges_) Δedges = unthunk(Δedges_) - return NoTangent(), CTMRGEnv(zerovector.(e.corners), Δedges), NoTangent() + zvs = CTMRGEnv(zerovector.(e.corners), Δedges) + return NoTangent(), zvs, NoTangent() end return result, edge_pullback else # this should never happen because already errored in forwards pass diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index 796c4e711..b1e18579f 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -67,7 +67,7 @@ specifies the storage type. If `T <: Number` the default storage type is `Vector function SUWeight( T, Nspaces::M, Espaces::M = Nspaces - ) where {M <: AbstractMatrix{<:ElementarySpace}} + ) where {M <: AbstractMatrix{<:ElementarySpace}, TorA} @assert size(Nspaces) == size(Espaces) Nr, Nc = size(Nspaces) weights = map(Iterators.product(1:2, 1:Nr, 1:Nc)) do (d, r, c) @@ -102,7 +102,7 @@ end Create a trivial `SUWeight` by specifying its vertical (north) and horizontal (east) as `ElementarySpace`s) and unit cell size. """ -function SUWeight( +function SUWeight(::Type{TorA}, Nspace::S, Espace::S = Nspace; unitcell::Tuple{Int, Int} = (1, 1) ) where {S <: ElementarySpace} return SUWeight(ComplexF64, fill(Nspace, unitcell), fill(Espace, unitcell)) diff --git a/src/networks/local_sandwich.jl b/src/networks/local_sandwich.jl index 33f61625b..ff485ee0f 100644 --- a/src/networks/local_sandwich.jl +++ b/src/networks/local_sandwich.jl @@ -60,6 +60,8 @@ _isapprox_localsandwich(O1::PFTensor, O2::PFTensor; kwargs...) = isapprox(O1, O2 ## PEPS const PEPSSandwich{T <: PEPSTensor} = Tuple{T, T} +TensorKit.storagetype(::Type{PEPSSandwich{T}}) where {T} = T +TensorKit.storagetype(S::PEPSSandwich{T}) where {T} = T ket(O::PEPSSandwich) = O[1] bra(O::PEPSSandwich) = O[2] diff --git a/src/networks/tensors.jl b/src/networks/tensors.jl index 347860366..2aab3f6fe 100644 --- a/src/networks/tensors.jl +++ b/src/networks/tensors.jl @@ -31,10 +31,10 @@ The tensor elements are generated based on `f` and the element type and storage specified in `T`. """ function PartitionFunctionTensor( - f, ::Type{T}, + f, ::Type{TorA}, Nspace::S, Espace::S = Nspace, Sspace::S = Nspace, Wspace::S = Espace, - ) where {T, S <: ElementarySpace} - return f(T, Wspace ⊗ Sspace ← Nspace ⊗ Espace) + ) where {TorA, S <: ElementarySpace} + return f(TorA, Wspace ⊗ Sspace ← Nspace ⊗ Espace) end Base.rotl90(t::PFTensor) = permute(t, ((3, 1), (4, 2))) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 47391f4d2..e5ae170a9 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -59,6 +59,7 @@ function _promote_term_scalars(term::AbstractVector) T = promote_type(map(scalartype, term)...) return map(t -> scalartype(t) === T ? t : VI.scale(t, one(T)), term) end +TensorKit.storagetype(lo::LocalOperator{T, S}) where {T, S} = storagetype(first(lo.terms)[2]) # horrible! """ Return the tensor factors of a dense or factored local term. diff --git a/src/operators/transfermatrix.jl b/src/operators/transfermatrix.jl index fd94825e1..7b3d9ebf0 100644 --- a/src/operators/transfermatrix.jl +++ b/src/operators/transfermatrix.jl @@ -14,6 +14,8 @@ function which corresponds to the overlap between 'ket' and 'bra' `InfinitePEPS` """ const InfiniteTransferPEPS{T <: PEPSTensor} = InfiniteMPO{PEPSSandwich{T}} +TensorKit.storagetype(::InfiniteTransferPEPS{T}) where {T} = storagetype(T) + function InfiniteTransferPEPS( top::PeriodicArray{T, 1}, bot::PeriodicArray{T, 1} ) where {T <: PEPSTensor} @@ -69,6 +71,8 @@ function which corresponds to the expectation value of an `InfinitePEPO` between """ const InfiniteTransferPEPO{H, T <: PEPSTensor, O <: PEPOTensor} = InfiniteMPO{PEPOSandwich{H, T, O}} +TensorKit.storagetype(::InfiniteTransferPEPO{H,T,O}) where {H,T,O} = storagetype(T) + function InfiniteTransferPEPO( top::PeriodicArray{T, 1}, mid::PeriodicArray{O, 2}, bot::PeriodicArray{T, 1} ) where {T, O} diff --git a/src/states/infinitepartitionfunction.jl b/src/states/infinitepartitionfunction.jl index 392668f21..888698de8 100644 --- a/src/states/infinitepartitionfunction.jl +++ b/src/states/infinitepartitionfunction.jl @@ -50,8 +50,8 @@ of the PEPS tensor at each site in the unit cell as a matrix. Each individual sp specified as either an `Int` or an `ElementarySpace`. """ function InfinitePartitionFunction( - f, T, Nspaces::M, Espaces::M = Nspaces - ) where {M <: AbstractMatrix{<:ElementarySpace}} + f, ::Type{T}, ::Type{TA}, Nspaces::M, Espaces::M = Nspaces + ) where {M <: AbstractMatrix{<:ElementarySpace}, T <: Number, TA <: AbstractArray{T}} size(Nspaces) == size(Espaces) || throw(ArgumentError("Input spaces should have equal sizes.")) @@ -59,13 +59,13 @@ function InfinitePartitionFunction( Wspaces = circshift(Espaces, (0, 1)) A = map(Nspaces, Espaces, Sspaces, Wspaces) do N, E, S, W - return PartitionFunctionTensor(f, T, N, E, S, W) + return PartitionFunctionTensor(f, T, TA, N, E, S, W) end return InfinitePartitionFunction(A) end function InfinitePartitionFunction(Nspaces::A, args...) where {A <: Union{AbstractMatrix{<:ElementarySpace}, ElementarySpace}} - return InfinitePartitionFunction(randn, ComplexF64, Nspaces, args...) + return InfinitePartitionFunction(randn, ComplexF64, Vector{ComplexF64}, Nspaces, args...) end """ diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index 7f6a38eac..f83fabc4a 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -63,9 +63,12 @@ end function InfinitePEPS( Pspaces::A, virtual_spaces...; kwargs... ) where {A <: Union{AbstractMatrix{<:ElementarySpace}, ElementarySpace}} - return InfinitePEPS(randn, ComplexF64, Pspaces, virtual_spaces...; kwargs...) + return InfinitePEPS(randn, Vector{ComplexF64}, Pspaces, virtual_spaces...; kwargs...) end +TensorKit.storagetype(peps::InfinitePEPS{T}) where {T} = storagetype(T) +TensorKit.storagetype(::Type{InfinitePEPS{T}}) where {T} = storagetype(T) + """ InfinitePEPS(A::PEPSTensor; unitcell=(1, 1)) @@ -103,7 +106,7 @@ function _fill_state_virtual_spaces( end """ - InfinitePEPS([f=randn, T=ComplexF64,] Pspace, Nspace, [Espace]; unitcell=(1,1)) + InfinitePEPS([f=randn, TorA=ComplexF64,] Pspace, Nspace, [Espace]; unitcell=(1,1)) Create an InfinitePEPS by specifying its physical, north and east spaces and unit cell. """ From 8ae872aa1209d4d195fb410685ead6bd948d74fe Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 19 May 2026 15:23:52 +0200 Subject: [PATCH 02/69] Restore singular value distance --- Project.toml | 4 +--- src/algorithms/ctmrg/ctmrg.jl | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 696c79a72..3ad45da7c 100644 --- a/Project.toml +++ b/Project.toml @@ -67,6 +67,4 @@ Zygote = "0.6, 0.7" julia = "1.10" [sources] -TensorKit = {url = "https://github.com/QuantumKitHub/TensorKit.jl", rev="main"} -MPSKit = {url = "https://github.com/QuantumKitHub/MPSKit.jl", rev="ksh/cu2"} -MPSKitModels = {url = "https://github.com/QuantumKitHub/MPSKitModels.jl", rev="ksh/bump"} +MPSKit = {url = "https://github.com/QuantumKitHub/MPSKit.jl", rev="main"} diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index d6fab844c..a4be1d21b 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -190,6 +190,9 @@ function _singular_value_distance(S₁::SV, S₂::SV) where {SV <: TensorKit.Sec for (c, b) in blocks(S₁) diff[c][1:length(b)] .= b end + for (c, b) in blocks(S₂) + diff[c][1:length(b)] .-= b + end return norm(diff) end _singular_value_distance(S₁::DiagonalTensorMap, S₂::DiagonalTensorMap) = From 1b8e0c1f8e8e370081a61503bbc1a0438a6999d3 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 29 Jul 2026 15:45:40 +0200 Subject: [PATCH 03/69] Cleanup and some actual tests --- Project.toml | 2 - examples/cu_bose_hubbard/Project.toml | 18 -- examples/cu_bose_hubbard/main.jl | 133 ------------- examples/cu_boundary_mps/main.jl | 236 ------------------------ examples/cu_fermi_hubbard/main.jl | 104 ----------- examples/cu_heisenberg/main.jl | 187 ------------------- examples/cu_heisenberg_su/main.jl | 139 -------------- examples/cu_hubbard_su/main.jl | 111 ----------- examples/cu_j1j2_su/main.jl | 129 ------------- examples/cu_xxz/main.jl | 101 ---------- ext/PEPSKitCUDAExt.jl | 39 ---- src/environments/ctmrg_environments.jl | 8 +- src/environments/suweight.jl | 3 +- src/operators/transfermatrix.jl | 2 +- src/states/infinitepartitionfunction.jl | 2 +- test/Project.toml | 1 - test/amd/boundarymps/vumps.jl | 127 +++++++++++++ test/cuda/boundarymps/vumps.jl | 127 +++++++++++++ test/runtests.jl | 12 ++ 19 files changed, 274 insertions(+), 1207 deletions(-) delete mode 100644 examples/cu_bose_hubbard/Project.toml delete mode 100644 examples/cu_bose_hubbard/main.jl delete mode 100644 examples/cu_boundary_mps/main.jl delete mode 100644 examples/cu_fermi_hubbard/main.jl delete mode 100644 examples/cu_heisenberg/main.jl delete mode 100644 examples/cu_heisenberg_su/main.jl delete mode 100644 examples/cu_hubbard_su/main.jl delete mode 100644 examples/cu_j1j2_su/main.jl delete mode 100644 examples/cu_xxz/main.jl delete mode 100644 ext/PEPSKitCUDAExt.jl create mode 100644 test/amd/boundarymps/vumps.jl create mode 100644 test/cuda/boundarymps/vumps.jl diff --git a/Project.toml b/Project.toml index 3ad45da7c..166472a54 100644 --- a/Project.toml +++ b/Project.toml @@ -43,8 +43,6 @@ Accessors = "0.1" Adapt = "4" ChainRulesCore = "1.0" Compat = "3.46, 4.2" -CUDA = "6" -cuTENSOR = "6" DocStringExtensions = "0.9.3" FiniteDifferences = "0.12" GPUArrays = "11, 12" diff --git a/examples/cu_bose_hubbard/Project.toml b/examples/cu_bose_hubbard/Project.toml deleted file mode 100644 index 95b9c2d30..000000000 --- a/examples/cu_bose_hubbard/Project.toml +++ /dev/null @@ -1,18 +0,0 @@ -[deps] -Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" -MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" -MPSKitModels = "ca635005-6f8c-4cd1-b51d-8491250ef2ab" -MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" -OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" -PEPSKit = "52969e89-939e-4361-9b68-9bc7cde4bdeb" -QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" -TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" -Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" -cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" diff --git a/examples/cu_bose_hubbard/main.jl b/examples/cu_bose_hubbard/main.jl deleted file mode 100644 index 001ec413c..000000000 --- a/examples/cu_bose_hubbard/main.jl +++ /dev/null @@ -1,133 +0,0 @@ -using Markdown #hide -md""" -# Optimizing the $U(1)$-symmetric Bose-Hubbard model - -This example demonstrates the simulation of the two-dimensional Bose-Hubbard model. In -particular, the point will be to showcase the use of internal symmetries and finite -particle densities in PEPS ground state searches. As we will see, incorporating symmetries -into the simulation consists of initializing a symmetric Hamiltonian, PEPS state and CTM -environment - made possible through TensorKit. - -But first let's seed the RNG and import the required modules: -""" - -using Random -using TensorKit, PEPSKit, Adapt, CUDA, cuTENSOR, MPSKitModels -using PEPSKit.MatrixAlgebraKit -using MPSKit: add_physical_charge -Random.seed!(2928528935); - -md""" -## Defining the model - -We will construct the Bose-Hubbard model Hamiltonian through the -[`bose_hubbard_model`](https://quantumkithub.github.io/MPSKitModels.jl/dev/man/models/#MPSKitModels.bose_hubbard_model), -function from MPSKitModels as reexported by PEPSKit. We'll simulate the model in its -Mott-insulating phase where the ratio $U/t$ is large, since in this phase we expect the -ground state to be well approximated by a PEPS with a manifest global $U(1)$ symmetry. -Furthermore, we'll impose a cutoff at 2 bosons per site, set the chemical potential to zero -and use a simple $1 \times 1$ unit cell: -""" - -t = 1.0 -U = 30.0 -cutoff = 2 -mu = 0.0 -lattice = InfiniteSquare(1, 1); - -md""" -Next, we impose an explicit global $U(1)$ symmetry as well as a fixed particle number -density in our simulations. We can do this by setting the `symmetry` argument of the -Hamiltonian constructor to `U1Irrep` and passing one as the particle number density -keyword argument `n`: -""" - -symmetry = U1Irrep -n = 1 -H = adapt(CuArray, bose_hubbard_model(ComplexF64, symmetry, lattice; cutoff, t, U, n)); - -md""" -Before we continue, it might be interesting to inspect the corresponding lattice physical -spaces (which is here just a $1 \times 1$ matrix due to the single-site unit cell): -""" - -physical_spaces = physicalspace(H) - -md""" -Note that the physical space contains $U(1)$ charges -1, 0 and +1. Indeed, imposing a -particle number density of +1 corresponds to shifting the physical charges by -1 to -'re-center' the physical charges around the desired density. When we do this with a cutoff -of two bosons per site, i.e. starting from $U(1)$ charges 0, 1 and 2 on the physical level, -we indeed get the observed charges. - -## Characterizing the virtual spaces - -When running PEPS simulations with explicit internal symmetries, specifying the structure of -the virtual spaces of the PEPS and its environment becomes a bit more involved. For the -environment, one could in principle allow the virtual space to be chosen dynamically during - -(e.g. using `alg=:truncrank` or `alg=:trunctol` to truncate to a fixed total bond dimension -or singular value cutoff respectively). For the PEPS virtual space however, the structure -has to be specified before the optimization. - -While there are a host of techniques to do this in an informed way (e.g. starting from a -simple update result), here we just specify the virtual space manually. Since we're dealing -with a model at unit filling our physical space only contains integer $U(1)$ irreps. -Therefore, we'll build our PEPS and environment spaces using integer $U(1)$ irreps centered -around the zero charge: -""" - -V_peps = U1Space(0 => 2, 1 => 1, -1 => 1) -V_env = U1Space(0 => 6, 1 => 4, -1 => 4, 2 => 2, -2 => 2); - -md""" -## Finding the ground state - -Having defined our Hamiltonian and spaces, it is just a matter of plugging this into the -optimization framework in the usual way to find the ground state. So, we first specify all -algorithms and their tolerances: -""" - -boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, decomposition_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration()), trunc = (; alg = :fixedspace)) -gradient_alg = (; tol = 1.0e-6, maxiter = 10, alg = :eigsolver, iterscheme = :diffgauge) -optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 150, ls_maxiter = 2, ls_maxfg = 2); - -md""" -!!! note - Taking CTMRG gradients and optimizing symmetric tensors tends to be more problematic - than with dense tensors. In particular, this means that one frequently needs to tweak - the `boundary_alg`, `gradient_alg` and `optimizer_alg` settings. There rarely is a - general-purpose set of settings which will always work, so instead one has to adjust - the simulation settings for each specific application. For example, it might help to - switch between the CTMRG flavors `alg=:simultaneous` and `alg=:sequential` to - improve convergence. The evaluation of the CTMRG gradient can be instable, so there it - is advised to try the different `iterscheme=:diffgauge` and `iterscheme=:fixed` schemes - as well as different `alg` keywords. Of course the tolerances of the algorithms and - their subalgorithms also have to be compatible. For more details on the available - options, see the [`fixedpoint`](@ref) docstring. - -Keep in mind that the PEPS is constructed from a unit cell of spaces, so we have to make a -matrix of `V_peps` spaces: -""" - -virtual_spaces = fill(V_peps, size(lattice)...) -peps₀ = InfinitePEPS(randn, CuMatrix{ComplexF64}, physical_spaces, virtual_spaces) -env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...); - -md""" -And at last, we optimize (which might take a bit): -""" - -peps, env, E, info = fixedpoint( - H, peps₀, env₀; boundary_alg, gradient_alg, optimizer_alg, verbosity = 3 -) -@show E; - -md""" -We can compare our PEPS result to the energy obtained using a cylinder-MPS calculation -using a cylinder circumference of $L_y = 7$ and a bond dimension of 446, which yields -$E = -0.273284888$: -""" - -E_ref = -0.273284888 -@show (E - E_ref) / E_ref; diff --git a/examples/cu_boundary_mps/main.jl b/examples/cu_boundary_mps/main.jl deleted file mode 100644 index ddebaf0e9..000000000 --- a/examples/cu_boundary_mps/main.jl +++ /dev/null @@ -1,236 +0,0 @@ -using Markdown #hide -md""" -# [Boundary MPS contractions of 2D networks](@id e_boundary_mps) - -Instead of using CTMRG to contract the network encoding the norm of an infinite PEPS, one -can also use so-called [boundary MPS methods](@cite haegeman_diagonalizing_2017) to contract -this network. In this example, we will demonstrate how to use [the VUMPS algorithm](@cite -vanderstraeten_tangentspace_2019) to do so. - -Before we start, we'll fix the random seed for reproducability: -""" - -using Random -Random.seed!(29384293742893); - -md""" -Besides `TensorKit` and `PEPSKit`, here we also need to load the -[`MPSKit.jl`](https://quantumkithub.github.io/MPSKit.jl/stable/) package which implements a -host of tools for working with 1D matrix product states (MPS), including the VUMPS -algorithm: -""" - -using TensorKit, PEPSKit, MPSKit, CUDA, cuTENSOR, MatrixAlgebraKit - -md""" -## Computing a PEPS norm - -We start by initializing a random infinite PEPS. Let us use normally distributed complex -entries using `randn`: -""" - -ψ = InfinitePEPS(randn, CuMatrix{ComplexF64}, ComplexSpace(2), ComplexSpace(2)) - -md""" - -To compute its norm, we have to contract a double-layer network which encodes the bra-ket -PEPS overlap ``\langle ψ | ψ \rangle``: - -```@raw html -
-peps norm network -
-``` - -In PEPSKit.jl, this structure is represented as an [`InfiniteSquareNetwork`](@ref) object, -whose effective local rank-4 constituent tensor is given by the contraction of a pair of bra -and ket [`PEPSKit.PEPSTensor`](@ref)s across their physical legs. Until now, we have always -contracted such a network using the CTMRG algorithm. Here however, we will use another -approach. - -If we take out a single row of this infinite norm network, we can interpret it as a 1D -row-to-row transfer operator ``\mathbb{T}``, - -```@raw html -
-peps transfer operator -
-``` - -This transfer operator can be seen as an infinite chain of the effective local rank-4 -tensors that make up the PEPS norm network. Since the network we want to contract can be -interpreted as the infinite power of ``\mathbb{T}``, we can contract it by finding its -leading eigenvector as a 1D MPS ``| \psi_{\text{MPS}} \rangle``, which we call the boundary -MPS. This boundary MPS should satisfy the eigenvalue equation -``\mathbb{T} | \psi_{\text{MPS}} \rangle \approx \Lambda | \psi_{\text{MPS}} \rangle``, or -diagrammatically: - -```@raw html -
-peps transfer fixedpoint equation -
-``` - -Note that if ``\mathbb{T}`` is Hermitian, we can formulate this eigenvalue equation in terms of a -variational problem for the free energy, - -```math -\begin{align} -f &= \lim_{N \to ∞} - \frac{1}{N} \log \left( \frac{\langle \psi_{\text{MPS}} | \mathbb{T} | \psi_{\text{MPS}} \rangle}{\langle \psi_{\text{MPS}} | \psi_{\text{MPS}} \rangle} \right), -\\ -&= -\log(\lambda) -\end{align} -``` - -where ``\lambda = \Lambda^{1/N}`` is the 'eigenvalue per site' of ``\mathbb{T}``, giving -``f`` the meaning of a free energy density. - -Since the contraction of a PEPS norm network is in essence exactly the same problem as the -contraction of a 2D classical partition function, we can directly use boundary MPS -algorithms designed for 2D statistical mechanics models in this context. In particular, -we'll use the [the VUMPS algorithm](@cite vanderstraeten_tangentspace_2019) to perform the -boundary MPS contraction, and we'll call it through the [`leading_boundary`](@ref) method -from MPSKit.jl. This method precisely finds the MPS fixed point of a 1D transfer operator. - -## Boundary MPS contractions with PEPSKit.jl - -To use [`leading_boundary`](@ref), we first need to contruct the transfer operator -``\mathbb{T}`` as an [`MPSKit.InfiniteMPO`](@extref) object. In PEPSKit.jl, we can directly -construct the transfer operator corresponding to a PEPS norm network from a given infinite -PEPS as an [`InfiniteTransferPEPS`](@ref) object, which is a specific kind of -[`MPSKit.InfiniteMPO`](@extref). - -To construct a 1D transfer operator from a 2D PEPS state, we need to specify which direction -should be facing north (`dir=1` corresponding to north, counting clockwise) and which row of -the network is selected from the north - but since we have a trivial unit cell there is only -one row here: -""" - -dir = 1 ## does not rotate the partition function -row = 1 -T = InfiniteTransferPEPS(ψ, dir, row) - -md""" -Since we'll find the leading eigenvector of ``\mathbb{T}`` as a boundary MPS, we first need -to construct an initial guess to supply to our algorithm. We can do this using the -[`initialize_mps`](@ref) function, which constructs a random MPS with a specific virtual -space for a given transfer operator. Here, we'll build an initial guess for the boundary MPS -with a bond dimension of 20: -""" - -mps₀ = initialize_mps(T, [ComplexSpace(20)]; alg_orth = CUSOLVER_HouseholderQR(; positive=true)) - -md""" -Note that this will just construct a MPS with random Gaussian entries based on the physical -spaces of the supplied transfer operator. Of course, one might come up with a better initial -guess (leading to better convergence) depending on the application. To find the leading -boundary MPS fixed point, we call [`leading_boundary`](@ref) using the -[`MPSKit.VUMPS`](@extref) algorithm: -""" - -MPSKit.Defaults.alg_qr() = CUSOLVER_HouseholderQR(; positive=true) -MPSKit.Defaults.alg_svd() = CUSOLVER_QRIteration() -MPSKit.Defaults.alg_lq() = LQViaTransposedQR(CUSOLVER_HouseholderQR(; positive=true)) -mps, env, ϵ = leading_boundary(mps₀, T, VUMPS(; tol = 1.0e-6, verbosity = 2)); - -md""" -The norm of the state per unit cell is then given by the expectation value -$\langle \psi_\text{MPS} | \mathbb{T} | \psi_\text{MPS} \rangle$ per site: -""" - -norm_vumps = abs(prod(expectation_value(mps, T))) - -md""" -This can be compared to the result obtained using CTMRG, where we see that the results -match: -""" - -env_ctmrg, = leading_boundary(CTMRGEnv(ψ, ComplexSpace(20)), ψ; tol = 1.0e-6, verbosity = 2, svd_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration())) -norm_ctmrg = abs(norm(ψ, env_ctmrg)) -@show abs(norm_vumps - norm_ctmrg) / norm_vumps; - -md""" -## Working with unit cells - -For PEPS with non-trivial unit cells, the principle is exactly the same. The only difference -is that now the transfer operator of the PEPS norm partition function has multiple rows or -'lines', each of which can be represented by an [`InfiniteTransferPEPS`](@ref) object. Such -a multi-line transfer operator is represented by a [`PEPSKit.MultilineTransferPEPS`](@ref) -object. In this case, the boundary MPS is an [`MultilineMPS`](@extref) object, which should -be initialized by specifying a virtual space for each site in the partition function unit -cell. - -First, we construct a PEPS with a $2 \times 2$ unit cell using the `unitcell` keyword -argument and then define the corresponding transfer operator, where we again specify the -direction which will be facing north: -""" - -ψ_2x2 = InfinitePEPS(rand, CuMatrix{ComplexF64}, ComplexSpace(2), ComplexSpace(2); unitcell = (2, 2)) -T_2x2 = PEPSKit.MultilineTransferPEPS(ψ_2x2, dir); - -md""" -Now, the procedure is the same as before: We compute the norm once using VUMPS, once using CTMRG and then compare. -""" - -mps₀_2x2 = initialize_mps(T_2x2, fill(ComplexSpace(20), 2, 2); alg_orth = CUSOLVER_HouseholderQR(; positive=true)) -mps_2x2, = leading_boundary(mps₀_2x2, T_2x2, VUMPS(; tol = 1.0e-6, verbosity = 2)) -norm_2x2_vumps = abs(prod(expectation_value(mps_2x2, T_2x2))) - -env_ctmrg_2x2, = leading_boundary( - CTMRGEnv(ψ_2x2, ComplexSpace(20)), ψ_2x2; tol = 1.0e-6, verbosity = 2, svd_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration()) -) -norm_2x2_ctmrg = abs(norm(ψ_2x2, env_ctmrg_2x2)) - -@show abs(norm_2x2_vumps - norm_2x2_ctmrg) / norm_2x2_vumps; - -md""" -Again, the results are compatible. Note that for larger unit cells and non-Hermitian PEPS -[the VUMPS algorithm may become unstable](@cite vanderstraeten_variational_2022), in which -case the CTMRG algorithm is recommended. - -## Contracting PEPO overlaps - -Using exactly the same machinery, we can contract 2D networks which encode the expectation -value of a PEPO for a given PEPS state. As an example, we can consider the overlap of the -PEPO correponding to the partition function of [3D classical Ising model](@ref e_3d_ising) -with our random PEPS from before and evaluate the overlap $\langle \psi | -T | \psi \rangle$. - -The classical Ising PEPO is defined as follows: -""" - -function ising_pepo(β; unitcell = (1, 1, 1)) - t = ComplexF64[exp(β) exp(-β); exp(-β) exp(β)] - q = sqrt(t) - - O = zeros(2, 2, 2, 2, 2, 2) - - O[1, 1, 1, 1, 1, 1] = 1 - O[2, 2, 2, 2, 2, 2] = 1 - @tensor o[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - O = TensorMap(CuArray(o), ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)') - return InfinitePEPO(O; unitcell) -end; - -md""" -To evaluate the overlap, we instantiate the PEPO and the corresponding [`InfiniteTransferPEPO`](@ref) -in the right direction, on the right row of the partition function (trivial here): -""" - -T = ising_pepo(1) -transfer_pepo = InfiniteTransferPEPO(ψ, T, 1, 1) - -md""" -As before, we converge the boundary MPS using VUMPS and then compute the expectation value: -""" - -mps₀_pepo = initialize_mps(transfer_pepo, [ComplexSpace(20)]; alg_orth = CUSOLVER_HouseholderQR(; positive=true)) -mps_pepo, = leading_boundary(mps₀_pepo, transfer_pepo, VUMPS(; tol = 1.0e-6, verbosity = 2)) -norm_pepo = abs(prod(expectation_value(mps_pepo, transfer_pepo))); -@show norm_pepo; - -md""" -These objects and routines can be used to optimize PEPS fixed points of 3D partition -functions, see for example [Vanderstraeten et al.](@cite vanderstraeten_residual_2018) -""" diff --git a/examples/cu_fermi_hubbard/main.jl b/examples/cu_fermi_hubbard/main.jl deleted file mode 100644 index f1ef40388..000000000 --- a/examples/cu_fermi_hubbard/main.jl +++ /dev/null @@ -1,104 +0,0 @@ -using Markdown #hide -md""" -# Fermi-Hubbard model with $f\mathbb{Z}_2 \boxtimes U(1)$ symmetry, at large $U$ and half-filling - -In this example, we will demonstrate how to handle fermionic PEPS tensors and how to -optimize them. To that end, we consider the two-dimensional Hubbard model - -```math -H = -t \sum_{\langle i,j \rangle} \sum_{\sigma} \left( c_{i,\sigma}^+ c_{j,\sigma}^- - -c_{i,\sigma}^- c_{j,\sigma}^+ \right) + U \sum_i n_{i,\uparrow}n_{i,\downarrow} - \mu \sum_i n_i -``` - -where $\sigma \in \{\uparrow,\downarrow\}$ and $n_{i,\sigma} = c_{i,\sigma}^+ c_{i,\sigma}^-$ -is the fermionic number operator. As in previous examples, using fermionic degrees of freedom -is a matter of creating tensors with the right symmetry sectors - the rest of the simulation -workflow remains the same. - -First though, we make the example deterministic by seeding the RNG, and we make our imports: -""" - -using Random -using TensorKit, MatrixAlgebraKit, PEPSKit, Adapt, CUDA, cuTENSOR, MPSKitModels -using MPSKit: add_physical_charge -Random.seed!(2928528937); - -md""" -## Defining the fermionic Hamiltonian - -Let us start by fixing the parameters of the Hubbard model. We're going to use a hopping of -$t=1$ and a large $U=8$ on a $2 \times 2$ unit cell: -""" - -t = 1.0 -U = 8.0 -lattice = InfiniteSquare(2, 2); - -md""" -In order to create fermionic tensors, one needs to define symmetry sectors using TensorKit's -`FermionParity`. Not only do we want use fermion parity but we also want our -particles to exploit the global $U(1)$ symmetry. The combined product sector can be obtained -using the [Deligne product](https://jutho.github.io/TensorKit.jl/stable/lib/sectors/#TensorKitSectors.deligneproduct-Tuple{Sector,%20Sector}), -called through `⊠` which is obtained by typing `\boxtimes+TAB`. We will not impose any extra -spin symmetry, so we have: -""" - -fermion = fℤ₂ -particle_symmetry = U1Irrep -spin_symmetry = Trivial -S = fermion ⊠ particle_symmetry - -md""" -The next step is defining graded virtual PEPS and environment spaces using `S`. Here we also -use the symmetry sector to impose half-filling. That is all we need to define the Hubbard -Hamiltonian: -""" - -D, χ = 1, 1 -V_peps = Vect[S]((0, 0) => 2 * D, (1, 1) => D, (1, -1) => D) -V_env = Vect[S]( - (0, 0) => 4 * χ, (1, -1) => 2 * χ, (1, 1) => 2 * χ, (0, 2) => χ, (0, -2) => χ -) -S_aux = S((1, 1)) -H₀ = hubbard_model(ComplexF64, particle_symmetry, spin_symmetry, lattice; t, U) -H = adapt(CuArray, add_physical_charge(H₀, fill(S_aux, size(H₀.lattice)...))); - -md""" -## Finding the ground state - -Again, the procedure of ground state optimization is very similar to before. First, we -define all algorithmic parameters: -""" - -boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, decomposition_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration()), trunc = (; alg = :fixedspace)) -gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge) -optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 80, ls_maxiter = 3, ls_maxfg = 3) - -md""" -Second, we initialize a PEPS state and environment (which we converge) constructed from -symmetric physical and virtual spaces: -""" - -physical_spaces = physicalspace(H) -virtual_spaces = fill(V_peps, size(lattice)...) -peps₀ = InfinitePEPS(randn, CuMatrix{ComplexF64}, physical_spaces, virtual_spaces) -env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...); - -md""" -And third, we start the ground state search (this does take quite long): -""" - -peps, env, E, info = fixedpoint( - H, peps₀, env₀; boundary_alg, gradient_alg, optimizer_alg, verbosity = 3 -) -@show E; - -md""" -Finally, let's compare the obtained energy against a reference energy from a QMC study by -[Qin et al.](@cite qin_benchmark_2016). With the parameters specified above, they obtain an -energy of $E_\text{ref} \approx 4 \times -0.5244140625 = -2.09765625$ (the factor 4 comes -from the $2 \times 2$ unit cell that we use here). Thus, we find: -""" - -E_ref = -2.09765625 -@show (E - E_ref) / E_ref; diff --git a/examples/cu_heisenberg/main.jl b/examples/cu_heisenberg/main.jl deleted file mode 100644 index 573e6d933..000000000 --- a/examples/cu_heisenberg/main.jl +++ /dev/null @@ -1,187 +0,0 @@ -using Markdown #hide -md""" -# [Optimizing the 2D Heisenberg model](@id examples_heisenberg) - -In this example we want to provide a basic rundown of PEPSKit's optimization workflow for -PEPS. To that end, we will consider the two-dimensional Heisenberg model on a square lattice - -```math -H = \sum_{\langle i,j \rangle} \left ( J_x S^{x}_i S^{x}_j + J_y S^{y}_i S^{y}_j + J_z S^{z}_i S^{z}_j \right ) -``` - -Here, we want to set $J_x = J_y = J_z = 1$ where the Heisenberg model is in the antiferromagnetic -regime. Due to the bipartite sublattice structure of antiferromagnetic order one needs a -PEPS ansatz with a $2 \times 2$ unit cell. This can be circumvented by performing a unitary -sublattice rotation on all B-sites resulting in a change of parameters to -$(J_x, J_y, J_z)=(-1, 1, -1)$. This gives us a unitarily equivalent Hamiltonian (with the -same spectrum) with a ground state on a single-site unit cell. - -Let us get started by fixing the random seed of this example to make it deterministic: -""" - -using Random -Random.seed!(123456789); - -md""" -We're going to need only two packages: `TensorKit`, since we use that for all the underlying -tensor operations, and `PEPSKit` itself. So let us import these: -""" - -using TensorKit, Adapt, PEPSKit, MPSKitModels, CUDA, cuTENSOR - -md""" -## Defining the Heisenberg Hamiltonian - -To create the sublattice rotated Heisenberg Hamiltonian on an infinite square lattice, we use -the `heisenberg_XYZ` method from [MPSKitModels](https://quantumkithub.github.io/MPSKitModels.jl/dev/) -which is redefined for the `InfiniteSquare` and reexported in PEPSKit: -""" - -H = adapt(CuArray, heisenberg_XYZ(ComplexF64, InfiniteSquare(); Jx = -1, Jy = 1, Jz = -1)) - -md""" -## Setting up the algorithms and initial guesses - -Next, we set the simulation parameters. During optimization, the PEPS will be contracted -using CTMRG and the PEPS gradient will be computed by differentiating through the CTMRG -routine using AD. Since the algorithmic stack that implements this is rather elaborate, -the amount of settings one can configure is also quite large. To reduce this complexity, -PEPSKit defaults to (presumably) reasonable settings which also dynamically adapts to the -user-specified parameters. - -First, we set the bond dimension `Dbond` of the virtual PEPS indices and the environment -dimension `χenv` of the virtual corner and transfer matrix indices. -""" - -Dbond = 2 -χenv = 16; - -md""" -To configure the CTMRG algorithm, we create a `NamedTuple` containing different keyword -arguments. To see a description of all arguments, see the docstring of -[`leading_boundary`](@ref). Here, we want to converge the CTMRG environments up to a -specific tolerance and during the CTMRG run keep all index dimensions fixed: -""" - -boundary_alg = (; tol = 1.0e-10, trunc = (; alg = :fixedspace)); - -md""" -Let us also configure the optimizer algorithm. We are going to optimize the PEPS using the -L-BFGS optimizer from [OptimKit](https://github.com/Jutho/OptimKit.jl). Again, we specify -the convergence tolerance (for the gradient norm) as well as the maximal number of iterations -and the BFGS memory size (which is used to approximate the Hessian): -""" - -optimizer_alg = (; alg = :lbfgs, tol = 1.0e-4, maxiter = 100, lbfgs_memory = 16); - -md""" -Additionally, during optimization, we want to reuse the previous CTMRG environment to -initialize the CTMRG run of the current optimization step using the `reuse_env` argument. -And to control the output information, we set the `verbosity`: -""" - -reuse_env = true -verbosity = 3; - -md""" -Next, we initialize a random PEPS which will be used as an initial guess for the -optimization. To get a PEPS with physical dimension 2 (since we have a spin-1/2 Hamiltonian) -with complex-valued random Gaussian entries, we set: -""" - -peps₀ = InfinitePEPS(randn, CuMatrix{ComplexF64}, ℂ^2, ℂ^Dbond) - -md""" -The last thing we need before we can start the optimization is an initial CTMRG environment. -Typically, a random environment which we converge on `peps₀` serves as a good starting point. -To contract a PEPS starting from an environment using CTMRG, we call [`leading_boundary`](@ref): -""" - -env_random = CTMRGEnv(randn, CuMatrix{ComplexF64}, peps₀, ℂ^χenv); -env₀, info_ctmrg = leading_boundary(env_random, peps₀; boundary_alg...); - -md""" -Besides the converged environment, `leading_boundary` also returns a `NamedTuple` of -informational quantities such as the last maximal truncation error - that is, the SVD -approximation error incurred in the last CTMRG iteration, maximized over all spatial -directions and unit cell entries: -""" - -@show info_ctmrg.truncation_error; - -md""" -## Ground state search - -Finally, we can start the optimization by calling [`fixedpoint`](@ref) on `H` with our -settings for the boundary (CTMRG) algorithm and the optimizer. This might take a while -(especially the precompilation of AD code in this case): -""" - -peps, env, E, info_opt = fixedpoint( - H, peps₀, env₀; boundary_alg, optimizer_alg, reuse_env, verbosity -); - -md""" -Note that `fixedpoint` returns the final optimized PEPS, the last converged environment, -the final energy estimate as well as a `NamedTuple` of diagnostics. This allows us to, e.g., -analyze the number of cost function calls or the history of gradient norms to evaluate -the convergence rate: -""" - -@show info_opt.fg_evaluations info_opt.gradnorms[1:10:end]; - -md""" -Let's now compare the optimized energy against an accurate Quantum Monte Carlo estimate by -[Sandvik](@cite sandvik_computational_2011), where the energy per site was found to be -$E_{\text{ref}}=−0.6694421$. From our simple optimization we find: -""" - -@show E; - -md""" -While this energy is in the right ballpark, there is still quite some deviation from the -accurate reference energy. This, however, can be attributed to the small bond dimension - an -optimization with larger bond dimension would approach this value much more closely. - -A more reasonable comparison would be against another finite bond dimension PEPS simulation. -For example, Juraj Hasik's data from $J_1\text{-}J_2$ -[PEPS simulations](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) -yields $E_{D=2,\chi=16}=-0.660231\dots$ which is more in line with what we find here. - -## Compute the correlation lengths and transfer matrix spectra - -In practice, in order to obtain an accurate and variational energy estimate, one would need -to compute multiple energies at different environment dimensions and extrapolate in, e.g., -the correlation length or the second gap of the transfer matrix spectrum. For that, we would -need the [`correlation_length`](@ref) function, which computes the horizontal and vertical -correlation lengths and transfer matrix spectra for all unit cell coordinates: -""" - -ξ_h, ξ_v, λ_h, λ_v = correlation_length(peps, env) -@show ξ_h ξ_v; - -md""" -## Computing observables - -As a last thing, we want to see how we can compute expectation values of observables, given -the optimized PEPS and its CTMRG environment. To compute, e.g., the magnetization, we first -need to define the observable as a `TensorMap`: -""" - -σ_z = TensorMap([1.0 0.0; 0.0 -1.0], ℂ^2, ℂ^2) - -md""" -In order to be able to contract it with the PEPS and environment, we define need to define a -`LocalOperator` and specify on which physical spaces and sites the observable acts. That way, -the PEPS-environment-operator contraction gets automatically generated (also works for -multi-site operators!). See the [`LocalOperator`](@ref) docstring for more details. -The magnetization is just a single-site observable, so we have: -""" - -M = LocalOperator(fill(ℂ^2, 1, 1), (CartesianIndex(1, 1),) => σ_z) - -md""" -Finally, to evaluate the expecation value on the `LocalOperator`, we call: -""" - -@show expectation_value(peps, M, env); diff --git a/examples/cu_heisenberg_su/main.jl b/examples/cu_heisenberg_su/main.jl deleted file mode 100644 index 6785b9ad1..000000000 --- a/examples/cu_heisenberg_su/main.jl +++ /dev/null @@ -1,139 +0,0 @@ -using Markdown #hide -md""" -# Simple update for the Heisenberg model - -In this example, we will use [`SimpleUpdate`](@ref) imaginary time evolution to treat -the two-dimensional Heisenberg model once again: - -```math -H = \sum_{\langle i,j \rangle} J_x S^{x}_i S^{x}_j + J_y S^{y}_i S^{y}_j + J_z S^{z}_i S^{z}_j. -``` - -In order to simulate the antiferromagnetic order of the Hamiltonian on a single-site unit -cell one typically applies a unitary sublattice rotation. Here, we will instead use a -$2 \times 2$ unit cell and set $J_x = J_y = J_z = 1$. - -Let's get started by seeding the RNG and importing all required modules: -""" - -using Random -import Statistics: mean -using TensorKit, Adapt, PEPSKit, MPSKitModels, CUDA, cuTENSOR -import MPSKitModels: S_x, S_y, S_z, S_exchange -Random.seed!(0); - -md""" -## Defining the Hamiltonian - -To construct the Heisenberg Hamiltonian as just discussed, we'll use `heisenberg_XYZ` and, -in addition, make it real (`real` and `imag` works for `LocalOperator`s) since we want to -use PEPS and environments with real entries. We can either initialize the Hamiltonian with -no internal symmetries (`symm = Trivial`) or use the global spin $U(1)$ symmetry -(`symm = U1Irrep`): -""" - -symm = Trivial ## ∈ {Trivial, U1Irrep} -Nr, Nc = 2, 2 -H = adapt(CuArray, real(heisenberg_XYZ(ComplexF64, symm, InfiniteSquare(Nr, Nc); Jx = 1, Jy = 1, Jz = 1))); - -md""" -## Simple updating - -We proceed by initializing a random PEPS that will be evolved. -The weights used for simple update are initialized as identity matrices. -First though, we need to define the appropriate (symmetric) spaces: -""" - -Dbond = 4 -χenv = 16 -if symm == Trivial - physical_space = ℂ^2 - bond_space = ℂ^Dbond - env_space = ℂ^χenv -elseif symm == U1Irrep - physical_space = ℂ[U1Irrep](1 // 2 => 1, -1 // 2 => 1) - bond_space = ℂ[U1Irrep](0 => Dbond ÷ 2, 1 // 2 => Dbond ÷ 4, -1 // 2 => Dbond ÷ 4) - env_space = ℂ[U1Irrep](0 => χenv ÷ 2, 1 // 2 => χenv ÷ 4, -1 // 2 => χenv ÷ 4) -else - error("not implemented") -end - -peps = InfinitePEPS(rand, CuMatrix{Float64}, physical_space, bond_space; unitcell = (Nr, Nc)); -wts = SUWeight(peps); - -md""" -Next, we can start the `SimpleUpdate` routine, successively decreasing the time intervals -and singular value convergence tolerances. Note that TensorKit allows to combine SVD -truncation schemes, which we use here to set a maximal bond dimension and at the same time -fix a truncation error (if that can be reached by remaining below `Dbond`): -""" - -dts = [1.0e-2, 1.0e-3, 4.0e-4] -tols = [1.0e-6, 1.0e-8, 1.0e-8] -nstep = 10000 -trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dbond) -alg = SimpleUpdate(; trunc = trunc_peps, bipartite = true) -for (dt, tol) in zip(dts, tols) - global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol, check_interval = 500) -end - -md""" -## Computing the ground-state energy and magnetizations - -In order to compute observable expectation values, we need to converge a CTMRG environment -on the evolved PEPS. Let's do so: -""" -normalize!.(peps.A, Inf) -env₀ = CTMRGEnv(rand, CuMatrix{Float64}, peps, env_space) -trunc_env = truncerror(; atol = 1.0e-10) & truncrank(χenv) -env, = leading_boundary( - env₀, - peps; - alg = :sequential, - projector_alg = :fullinfinite, - tol = 1.0e-10, - trunc = trunc_env, -); - -md""" -Finally, we'll measure the energy and different magnetizations. For the magnetizations, -the plan is to compute the expectation values unit cell entry-wise in different spin -directions: -""" - -function compute_mags(peps::InfinitePEPS, env::CTMRGEnv) - lattice = collect(space(t, 1) for t in peps.A) - - ## detect symmetry on physical axis - symm = sectortype(space(peps.A[1, 1])) - if symm == Trivial - S_ops = real.([S_x(symm), im * S_y(symm), S_z(symm)]) - elseif symm == U1Irrep - S_ops = real.([S_z(symm)]) ## only Sz preserves - end - - return map(Iterators.product(axes(peps, 1), axes(peps, 2), S_ops)) do (r, c, S) - expectation_value(peps, LocalOperator(lattice, (CartesianIndex(r, c),) => S), env) - end -end - -E = expectation_value(peps, H, env) / (Nr * Nc) -Ms = compute_mags(peps, env) -M_norms = map( - rc -> norm(Ms[rc[1], rc[2], :]), Iterators.product(axes(peps, 1), axes(peps, 2)) -) -@show E Ms M_norms; - -md""" -To assess the results, we will benchmark against data from [Corboz](@cite corboz_variational_2016), -which use manual gradients to perform a variational optimization of the Heisenberg model. -In particular, for the energy and magnetization they find $E_\text{ref} = -0.6675$ and -$M_\text{ref} = 0.3767$. Looking at the relative errors, we find general agreement, although -the accuracy is limited by the methodological limitations of the simple update algorithm as -well as finite bond dimension effects and a lacking extrapolation: -""" - -E_ref = -0.6675 -M_ref = 0.3767 -@show (E - E_ref) / abs(E_ref) -@show (mean(M_norms) - M_ref) / M_ref; diff --git a/examples/cu_hubbard_su/main.jl b/examples/cu_hubbard_su/main.jl deleted file mode 100644 index 4bf099c0c..000000000 --- a/examples/cu_hubbard_su/main.jl +++ /dev/null @@ -1,111 +0,0 @@ -using Markdown #hide -md""" -# Simple update for the Fermi-Hubbard model at half-filling - -Once again, we consider the Hubbard model but this time we obtain the ground-state PEPS by -imaginary time evolution. In particular, we'll use the [`SimpleUpdate`](@ref) algorithm. -As a reminder, we define the Hubbard model as - -```math -H = -t \sum_{\langle i,j \rangle} \sum_{\sigma} \left( c_{i,\sigma}^+ c_{j,\sigma}^- - -c_{i,\sigma}^- c_{j,\sigma}^+ \right) + U \sum_i n_{i,\uparrow}n_{i,\downarrow} - \mu \sum_i n_i -``` - -with $\sigma \in \{\uparrow,\downarrow\}$ and $n_{i,\sigma} = c_{i,\sigma}^+ c_{i,\sigma}^-$. - -Let's get started by seeding the RNG and importing the required modules: -""" - -using Random -using TensorKit, Adapt, PEPSKit, MPSKit, CUDA, cuTENSOR, MatrixAlgebraKit -Random.seed!(12329348592498); - -MPSKit.Defaults.alg_svd() = CUSOLVER_QRIteration() - -md""" -## Defining the Hamiltonian - -First, we define the Hubbard model at $t=1$ hopping and $U=6$ using `Trivial` sectors for -the particle and spin symmetries, and set $\mu = U/2$ for half-filling. The model will be -constructed on a $2 \times 2$ unit cell, so we have: -""" - -t = 1 -U = 6 -Nr, Nc = 2, 2 -H = adapt(CuArray, hubbard_model(Float64, Trivial, Trivial, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)); -physical_space = Vect[fℤ₂](0 => 2, 1 => 2); - -md""" -## Running the simple update algorithm - -Suppose the goal is to use imaginary-time simple update to optimize a PEPS -with bond dimension D = 8, and $2 \times 2$ unit cells. -For a challenging model like the Hubbard model, a naive evolution starting from a -random PEPS at D = 8 will almost always produce a sub-optimal state. -In this example, we shall demonstrate some common practices to improve SU result. - -First, we shall use a small D for the random PEPS initialization, which is chosen as 4 here. -For convenience, here we work with real tensors with `Float64` entries. -The bond weights are still initialized as identity matrices. -""" - -virtual_space = Vect[fℤ₂](0 => 2, 1 => 2) -peps = InfinitePEPS(rand, CuMatrix{Float64}, physical_space, virtual_space; unitcell = (Nr, Nc)); -wts = SUWeight(peps); - -md""" -Starting from the random state, we first use a relatively large evolution time step -`dt = 1e-2`. After convergence at D = 4, to avoid stucking at some bad local minimum, -we first increase D to 12, and drop it back to D = 8 after a while. -Afterwards, we keep D = 8 and gradually decrease `dt` to `1e-4` to improve convergence. -""" - -dts = [1.0e-2, 1.0e-2, 1.0e-3, 4.0e-4, 1.0e-4] -tols = [1.0e-7, 1.0e-7, 1.0e-8, 1.0e-8, 1.0e-8] -Ds = [4, 12, 8, 8, 8] -maxiter = 20000 - -for (dt, tol, Dbond) in zip(dts, tols, Ds) - trunc = truncerror(; atol = 1.0e-10) & truncrank(Dbond) - alg = SimpleUpdate(; trunc, bipartite = false) - global peps, wts, = time_evolve(peps, H, dt, maxiter, alg, wts; tol, check_interval = 2000) -end - -md""" -## Computing the ground-state energy - -In order to compute the energy expectation value with evolved PEPS, we need to converge a -CTMRG environment on it. We first converge an environment with a small enviroment dimension, -which is initialized using the simple update bond weights. Next we use it to initialize -another run with bigger environment dimension. The dynamic adjustment of environment dimension -is achieved by using `trunc=truncrank(χ)` with different `χ`s in the CTMRG runs: -""" - -χenv₀, χenv = 6, 16 -env_space = Vect[fℤ₂](0 => χenv₀ / 2, 1 => χenv₀ / 2) -normalize!.(peps.A, Inf) -env = CTMRGEnv(wts) -for χ in [χenv₀, χenv] - global env, = leading_boundary( - env, peps; alg = :sequential, tol = 1.0e-8, maxiter = 50, trunc = truncrank(χ) - ) -end - -md""" -We measure the energy by computing the `H` expectation value, where we have to make sure to -normalize with respect to the unit cell to obtain the energy per site: -""" - -E = expectation_value(peps, H, env) / (Nr * Nc) -@show E; - -md""" -Finally, we can compare the obtained ground-state energy against the literature, namely the -QMC estimates from [Qin et al.](@cite qin_benchmark_2016). We find that the results generally -agree: -""" - -Es_exact = Dict(0 => -1.62, 2 => -0.176, 4 => 0.8603, 6 => -0.6567, 8 => -0.5243) -E_exact = Es_exact[U] - U / 2 -@show (E - E_exact) / abs(E_exact); diff --git a/examples/cu_j1j2_su/main.jl b/examples/cu_j1j2_su/main.jl deleted file mode 100644 index a6123aba7..000000000 --- a/examples/cu_j1j2_su/main.jl +++ /dev/null @@ -1,129 +0,0 @@ -using Markdown #hide -md""" -# Three-site simple update for the $J_1$-$J_2$ model - -In this example, we will use [`SimpleUpdate`](@ref) imaginary time evolution to treat -the two-dimensional $J_1$-$J_2$ model, which contains next-nearest-neighbour interactions: - -```math -H = J_1 \sum_{\langle i,j \rangle} \mathbf{S}_i \cdot \mathbf{S}_j -+ J_2 \sum_{\langle \langle i,j \rangle \rangle} \mathbf{S}_i \cdot \mathbf{S}_j -``` - -Here we will exploit the $U(1)$ spin rotation symmetry in the $J_1$-$J_2$ model. The goal -will be to calculate the energy at $J_1 = 1$ and $J_2 = 1/2$, first using the simple update -algorithm and then, to refine the energy estimate, using AD-based variational PEPS -optimization. - -We first import all required modules and seed the RNG: -""" - -using Random -using TensorKit, Adapt, PEPSKit, Strided, CUDA, cuTENSOR, MatrixAlgebraKit -Random.seed!(29385293); - -md""" -## Simple updating a challenging phase - -Let's start by initializing an `InfinitePEPS` for which we set the required parameters -as well as physical and virtual vector spaces. -The `SUWeight` used by simple update will be initialized to identity matrices. -We use the minimal unit cell size ($2 \times 2$) required by the simple update algorithm -for Hamiltonians with next-nearest-neighbour interactions: -""" - -Dbond, symm = 4, U1Irrep -Nr, Nc, J1 = 2, 2, 1.0 - -## random initialization of 2x2 iPEPS (using real numbers) and SUWeight -Pspace = Vect[U1Irrep](1 // 2 => 1, -1 // 2 => 1) -Vspace = Vect[U1Irrep](0 => 2, 1 // 2 => 1, -1 // 2 => 1) -peps = InfinitePEPS(rand, CuMatrix{Float64}, Pspace, Vspace; unitcell = (Nr, Nc)); -wts = SUWeight(peps); - -md""" -The value $J_2 / J_1 = 0.5$ corresponds to a [possible spin liquid phase](@cite liu_gapless_2022), -which is challenging for SU to produce a relatively good state from random initialization. -Therefore, we shall gradually increase $J_2 / J_1$ from 0.1 to 0.5, each time initializing -on the previously evolved PEPS: -""" - -dt, tol, nstep = 1.0e-2, 1.0e-8, 30000 -check_interval = 4000 -trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dbond) -alg = SimpleUpdate(; trunc = trunc_peps) -for J2 in 0.1:0.1:0.5 - ## convert Hamiltonian `LocalOperator` to real floats - H = adapt(CuArray, real( - j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false), - )) - global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol, check_interval) -end - -md""" -After we reach $J_2 / J_1 = 0.5$, we gradually decrease the evolution time step to obtain -a more accurately evolved PEPS: -""" - -dts = [1.0e-3, 1.0e-4] -tols = [1.0e-9, 1.0e-9] -J2 = 0.5 -H = adapt(CuArray, real(j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false))) -for (dt, tol) in zip(dts, tols) - global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol) -end - -md""" -## Computing the simple update energy estimate - -Finally, we measure the ground-state energy by converging a CTMRG environment and computing -the expectation value, where we first normalize tensors in the PEPS: -""" - -normalize!.(peps.A, Inf) ## normalize each PEPS tensor by largest element -χenv = 32 -trunc_env = truncerror(; atol = 1.0e-10) & truncrank(χenv) -Espace = Vect[U1Irrep](0 => χenv ÷ 2, 1 // 2 => χenv ÷ 4, -1 // 2 => χenv ÷ 4) -env₀ = CTMRGEnv(rand, CuMatrix{Float64, CUDA.DeviceMemory}, peps, Espace) -env, = leading_boundary(env₀, peps; tol = 1.0e-10, alg = :sequential, trunc = trunc_env, decomposition_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration())); -E = expectation_value(peps, H, env) / (Nr * Nc) - -md""" -Let us compare that estimate with benchmark data obtained from the -[YASTN/peps-torch package](https://github.com/jurajHasik/j1j2_ipeps_states/blob/ea4140fbd7da0fc1b75fac2871f75bda125189a8/single-site_pg-C4v-A1_internal-U1/j20.5/state_1s_A1_U1B_j20.5_D4_chi_opt96.dat). -which utilizes AD-based PEPS optimization to find $E_\text{ref}=-0.49425$: -""" - -E_ref = -0.49425 -@show (E - E_ref) / abs(E_ref); - -md""" -## Variational PEPS optimization using AD - -As a last step, we will use the SU-evolved PEPS as a starting point for a [`fixedpoint`](@ref) -PEPS optimization. Note that we could have also used a sublattice-rotated version of `H` to -fit the Hamiltonian onto a single-site unit cell which would require us to optimize fewer -parameters and hence lead to a faster optimization. But here we instead take advantage of -the already evolved `peps`, thus giving us a physical initial guess for the optimization. -In order to break some of the $C_{4v}$ symmetry of the PEPS, we will add a bit of noise to it. -This is conviently done using MPSKit's `randomize!` function. -(Breaking some of the spatial symmetry can be advantageous for obtaining lower energies.) -""" - -using MPSKit: randomize! - -noise_peps = InfinitePEPS(randomize!.(deepcopy(peps.A))) -peps₀ = peps + 1.0e-1noise_peps -peps_opt, env_opt, E_opt, = fixedpoint( - H, peps₀, env; optimizer_alg = (; tol = 1.0e-4, maxiter = 80) -); - -md""" -Finally, we compare the variationally optimized energy against the reference energy. Indeed, -we find that the additional AD-based optimization improves the SU-evolved PEPS and leads to -a more accurate energy estimate. -""" - -E_opt /= (Nr * Nc) -@show E_opt -@show (E_opt - E_ref) / abs(E_ref); diff --git a/examples/cu_xxz/main.jl b/examples/cu_xxz/main.jl deleted file mode 100644 index d79dfd846..000000000 --- a/examples/cu_xxz/main.jl +++ /dev/null @@ -1,101 +0,0 @@ -using Markdown #hide -md""" -# Néel order in the $U(1)$-symmetric XXZ model - -Here, we want to look at a special case of the Heisenberg model, where the $x$ and $y$ -couplings are equal, called the XXZ model - -```math -H_0 = J \big(\sum_{\langle i, j \rangle} S_i^x S_j^x + S_i^y S_j^y + \Delta S_i^z S_j^z \big) . -``` - -For appropriate $\Delta$, the model enters an antiferromagnetic phase (Néel order) which we -will force by adding staggered magnetic charges to $H_0$. Furthermore, since the XXZ -Hamiltonian obeys a $U(1)$ symmetry, we will make use of that and work with $U(1)$-symmetric -PEPS and CTMRG environments. For simplicity, we will consider spin-$1/2$ operators. - -But first, let's make this example deterministic and import the required packages: -""" - -using Random -using TensorKit, PEPSKit, CUDA, cuTENSOR, MatrixAlgebraKit -using MPSKit: add_physical_charge -Random.seed!(2928528935); - -md""" -## Constructing the model - -Let us define the $U(1)$-symmetric XXZ Hamiltonian on a $2 \times 2$ unit cell with the -parameters: -""" - -J = 1.0 -Delta = 1.0 -spin = 1 // 2 -symmetry = U1Irrep -lattice = InfiniteSquare(2, 2) -H₀ = heisenberg_XXZ(CuMatrix{ComplexF64}, symmetry, lattice; J, Delta, spin); - -md""" -This ensures that our PEPS ansatz can support the bipartite Néel order. As discussed above, -we encode the Néel order directly in the ansatz by adding staggered auxiliary physical -charges: -""" - -S_aux = [ - U1Irrep(-1 // 2) U1Irrep(1 // 2) - U1Irrep(1 // 2) U1Irrep(-1 // 2) -] -H = add_physical_charge(H₀, S_aux); - -md""" -## Specifying the symmetric virtual spaces - -Before we create an initial PEPS and CTM environment, we need to think about which -symmetric spaces we need to construct. Since we want to exploit the global $U(1)$ symmetry -of the model, we will use TensorKit's `U1Space`s where we specify dimensions for each -symmetry sector. From the virtual spaces, we will need to construct a unit cell (a matrix) -of spaces which will be supplied to the PEPS constructor. The same is true for the physical -spaces, which can be extracted directly from the Hamiltonian `LocalOperator`: -""" - -V_peps = U1Space(0 => 2, 1 => 1, -1 => 1) -V_env = U1Space(0 => 6, 1 => 4, -1 => 4, 2 => 2, -2 => 2) -virtual_spaces = fill(V_peps, size(lattice)...) -physical_spaces = physicalspace(H) - -md""" -## Ground state search - -From this point onwards it's business as usual: Create an initial PEPS and environment -(using the symmetric spaces), specify the algorithmic parameters and optimize: -""" - -boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, decomposition_alg = SVDAdjoint(; fwd_alg = CUSOLVER_QRIteration()), trunc = (; alg = :fixedspace)) -gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge) -optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 85, ls_maxiter = 3, ls_maxfg = 3) - -peps₀ = InfinitePEPS(randn, CuMatrix{ComplexF64}, physical_spaces, virtual_spaces) -env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...); - -md""" -Finally, we can optimize the PEPS with respect to the XXZ Hamiltonian and check the -resulting ground state energy per site using our $(2 \times 2)$ unit cell. Note that the -optimization might take a while since precompilation of symmetric AD code takes longer and -because symmetric tensors do create a bit of overhead (which does pay off at larger bond and -environment dimensions): -""" - -peps, env, E, info = fixedpoint( - H, peps₀, env₀; boundary_alg, gradient_alg, optimizer_alg, verbosity = 3 -) -@show E / prod(size(lattice)); - -md""" -Note that for the specified parameters $J = \Delta = 1$, we simulated the same Hamiltonian -as in the [Heisenberg example](@ref examples_heisenberg). In that example, with a -non-symmetric $D=2$ PEPS simulation, we reached a ground-state energy per site of around -$E_\text{D=2} = -0.6625\dots$. Again comparing against [Sandvik's](@cite -sandvik_computational_2011) accurate QMC estimate ``E_{\text{ref}}=−0.6694421``, we see that -we already got closer to the reference energy. -""" diff --git a/ext/PEPSKitCUDAExt.jl b/ext/PEPSKitCUDAExt.jl deleted file mode 100644 index 87b91422b..000000000 --- a/ext/PEPSKitCUDAExt.jl +++ /dev/null @@ -1,39 +0,0 @@ -module PEPSKitCUDAExt - -using PEPSKit, CUDA, cuTENSOR, Random -import CUDA: rand as curand, rand! as curand!, randn as curandn, randn! as curandn! - -using PEPSKit.TensorKit -import PEPSKit: PEPSTensor, _corner_tensor, _edge_tensor - -function PEPSTensor( - f::typeof(rand), - ::Type{TA}, - Pspace::S, - Nspace::S, Espace::S = Nspace, Sspace::S = Nspace', Wspace::S = Espace', - ) where {S <: ElementarySpace, TA <: CuArray} - return curand(eltype(TA), Pspace ← Nspace ⊗ Espace ⊗ Sspace ⊗ Wspace) -end - -function PEPSTensor( - f::typeof(randn), - ::Type{TA}, - Pspace::S, - Nspace::S, Espace::S = Nspace, Sspace::S = Nspace', Wspace::S = Espace', - ) where {S <: ElementarySpace, TA <: CuArray} - return curandn(eltype(TA), Pspace ← Nspace ⊗ Espace ⊗ Sspace ⊗ Wspace) -end - -function _corner_tensor( - f::typeof(rand), ::Type{TA}, left_vspace::S, right_vspace::S = left_vspace - ) where {T, TA <: CuArray{T}, S <: ElementarySpace} - return curand(T, left_vspace ← right_vspace) -end - -function _edge_tensor( - f::typeof(randn), ::Type{TA}, left_vspace::S, pspaces::P, right_vspace::S = left_vspace - ) where {T, TA <: CuArray{T}, S <: ElementarySpace, P <: ProductSpace} - return curandn(T, left_vspace ⊗ pspaces, right_vspace) -end - -end diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 8ba5e1848..fad2550e6 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -52,13 +52,13 @@ end function _corner_tensor( f, ::Type{TorA}, left_vspace::S, right_vspace::S = left_vspace - ) where {TorA, S <: ElementarySpace} + ) where {TorA, S <: ElementarySpace} return f(TorA, left_vspace ← right_vspace) end function _edge_tensor( - f, ::Type{TA}, left_vspace::S, pspaces::P, right_vspace::S = left_vspace - ) where {TA, S <: ElementarySpace, P <: ProductSpace} + f, ::Type{TA}, left_vspace::S, pspaces::P, right_vspace::S = left_vspace + ) where {TA, S <: ElementarySpace, P <: ProductSpace} return f(TA, left_vspace ⊗ pspaces, right_vspace) end @@ -182,7 +182,7 @@ The environment virtual spaces for each site correspond to virtual space of the corresponding edge tensor for each direction. """ function CTMRGEnv( - f, ::Type{TorA}, + f, ::Type{TorA}, D_north::S, D_east::S, virtual_spaces...; unitcell::Tuple{Int, Int} = (1, 1), ) where {S <: VectorSpace, TorA} return CTMRGEnv( diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index b1e18579f..22af1d055 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -102,7 +102,8 @@ end Create a trivial `SUWeight` by specifying its vertical (north) and horizontal (east) as `ElementarySpace`s) and unit cell size. """ -function SUWeight(::Type{TorA}, +function SUWeight( + ::Type{TorA}, Nspace::S, Espace::S = Nspace; unitcell::Tuple{Int, Int} = (1, 1) ) where {S <: ElementarySpace} return SUWeight(ComplexF64, fill(Nspace, unitcell), fill(Espace, unitcell)) diff --git a/src/operators/transfermatrix.jl b/src/operators/transfermatrix.jl index 7b3d9ebf0..83959680d 100644 --- a/src/operators/transfermatrix.jl +++ b/src/operators/transfermatrix.jl @@ -71,7 +71,7 @@ function which corresponds to the expectation value of an `InfinitePEPO` between """ const InfiniteTransferPEPO{H, T <: PEPSTensor, O <: PEPOTensor} = InfiniteMPO{PEPOSandwich{H, T, O}} -TensorKit.storagetype(::InfiniteTransferPEPO{H,T,O}) where {H,T,O} = storagetype(T) +TensorKit.storagetype(::InfiniteTransferPEPO{H, T, O}) where {H, T, O} = storagetype(T) function InfiniteTransferPEPO( top::PeriodicArray{T, 1}, mid::PeriodicArray{O, 2}, bot::PeriodicArray{T, 1} diff --git a/src/states/infinitepartitionfunction.jl b/src/states/infinitepartitionfunction.jl index 888698de8..e3818a4c6 100644 --- a/src/states/infinitepartitionfunction.jl +++ b/src/states/infinitepartitionfunction.jl @@ -50,7 +50,7 @@ of the PEPS tensor at each site in the unit cell as a matrix. Each individual sp specified as either an `Int` or an `ElementarySpace`. """ function InfinitePartitionFunction( - f, ::Type{T}, ::Type{TA}, Nspaces::M, Espaces::M = Nspaces + f, ::Type{T}, ::Type{TA}, Nspaces::M, Espaces::M = Nspaces ) where {M <: AbstractMatrix{<:ElementarySpace}, T <: Number, TA <: AbstractArray{T}} size(Nspaces) == size(Espaces) || throw(ArgumentError("Input spaces should have equal sizes.")) diff --git a/test/Project.toml b/test/Project.toml index 7e019cf9c..794a83c44 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -8,7 +8,6 @@ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" diff --git a/test/amd/boundarymps/vumps.jl b/test/amd/boundarymps/vumps.jl new file mode 100644 index 000000000..6bb43e360 --- /dev/null +++ b/test/amd/boundarymps/vumps.jl @@ -0,0 +1,127 @@ +using Test +using Random +using PEPSKit +using TensorKit +using MPSKit +using LinearAlgebra +using Adapt, AMDGPU + +Random.seed!(29384293742893) + +const vumps_alg = VUMPS(; + tol = 1.0e-6, alg_eigsolve = MPSKit.Defaults.alg_eigsolve(; ishermitian = false), verbosity = 2 +) + +@testset "(1, 1) PEPS" begin + Vpeps = ComplexSpace(2) + psi = adapt(ROCArray, InfinitePEPS(Vpeps, Vpeps)) + + T = adapt(ROCArray, PEPSKit.InfiniteTransferPEPS(psi, 1, 1)) + foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) + mps = adapt(ROCArray, initialize_mps(T, [ComplexSpace(20)])) + + mps, env, ϵ = leading_boundary(mps, T, vumps_alg) + N = abs(sum(expectation_value(mps, T))) + + mps2, = changebonds(mps, T, OptimalExpand(; trscheme = truncrank(30))) # TODO: update `trscheme` to `trunc` once MPSKit does + mps2, env2, ϵ = leading_boundary(mps2, T, vumps_alg) + N2 = abs(sum(expectation_value(mps2, T))) + @test N ≈ N2 rtol = 1.0e-2 + + ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) + N´ = abs(norm(psi, ctm)) + + @test N ≈ N´ atol = 1.0e-3 +end + +@testset "(2, 2) PEPS" begin + Vpeps = ComplexSpace(2) + psi = adapt(ROCArray, InfinitePEPS(Vpeps, Vpeps; unitcell = (2, 2))) + T = adapt(ROCArray, PEPSKit.MultilineTransferPEPS(psi, 1)) + # foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) # TODO: MPSKit.physicalspace(::MultilineMPO) isn't implemented... + mps = initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2)) + mps, env, ϵ = leading_boundary(mps, T, vumps_alg) + N = abs(prod(expectation_value(mps, T))) + + ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) + N´ = abs(norm(psi, ctm)) + + @test N ≈ N´ rtol = 1.0e-2 +end + +@testset "Fermionic PEPS" begin + D = Vect[fℤ₂](0 => 1, 1 => 1) + d = Vect[fℤ₂](0 => 1, 1 => 1) + χ = Vect[fℤ₂](0 => 10, 1 => 10) + + psi = adapt(ROCArray, InfinitePEPS(D, d; unitcell = (1, 1))) + n = adapt(ROCArray, InfiniteSquareNetwork(psi)) + T = adapt(ROCArray, InfiniteTransferPEPS(psi, 1, 1)) + foreach(V -> (@test V == D ⊗ D'), physicalspace(T)) + + # compare boundary MPS contraction to CTMRG contraction + mps = adapt(ROCArray, initialize_mps(T, [χ])) + mps, env, ϵ = leading_boundary(mps, T, vumps_alg) + N_vumps = abs(prod(expectation_value(mps, T))) + + ctm, = leading_boundary(CTMRGEnv(psi, χ), psi) + N_ctm = abs(norm(psi, ctm)) + + @test N_vumps ≈ N_ctm rtol = 1.0e-2 + + # and again after blocking the local sandwiches + n´ = adapt(ROCArray, InfiniteSquareNetwork(map(PEPSKit.mpotensor, PEPSKit.unitcell(n)))) + T´ = adapt(ROCArray, InfiniteMPO(map(PEPSKit.mpotensor, T.O))) + foreach(V -> (@test V == fuse(D, D')), physicalspace(T´)) + + mps´ = adapt(ROCArray, InfiniteMPS(randn, ComplexF64, [physicalspace(T´, 1)], [χ])) + mps´, env´, ϵ = leading_boundary(mps´, T´, vumps_alg) + N_vumps´ = abs(prod(expectation_value(mps´, T´))) + + ctm´, = leading_boundary(CTMRGEnv(n´, χ), n´) + N_ctm´ = abs(network_value(n´, ctm´)) + + @show N_vumps´ + @test N_vumps´ ≈ N_vumps rtol = 1.0e-2 + @test N_vumps´ ≈ N_ctm´ rtol = 1.0e-2 +end + +@testset "PEPO runthrough" begin + function ising_pepo(beta; unitcell = (1, 1, 1)) + t = ComplexF64[exp(beta) exp(-beta); exp(-beta) exp(beta)] + q = sqrt(t) + + O = zeros(2, 2, 2, 2, 2, 2) + O[1, 1, 1, 1, 1, 1] = 1 + O[2, 2, 2, 2, 2, 2] = 1 + @tensor o[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + + O = TensorMap(o, ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)') + + return adapt(ROCArray, InfinitePEPO(O; unitcell)) + end + + Vpepo = ComplexSpace(2) + Vpeps = ComplexSpace(2) + + # single-layer PEPO + O = ising_pepo(1) + psi = adapt(ROCArray, PEPSKit.initializePEPS(O, Vpeps)) + T = adapt(ROCArray, InfiniteTransferPEPO(psi, O, 1, 1)) + foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpeps'), physicalspace(T)) + + mps = adapt(ROCArray, initialize_mps(rand, scalartype(T), T, [ComplexSpace(10)])) + mps, env, ϵ = leading_boundary(mps, T, vumps_alg) + f = abs(prod(expectation_value(mps, T))) + + # double-layer PEPO + O2 = repeat(O, 1, 1, 2) + psi2 = adapt(ROCArray, initializePEPS(O2, Vpeps)) + T2 = adapt(ROCArray, InfiniteTransferPEPO(psi, O2, 1, 1)) + foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpepo ⊗ Vpeps'), physicalspace(T2)) + + mps2 = adapt(ROCArray, initialize_mps(rand, scalartype(T2), T2, [ComplexSpace(8)])) + mps2, env2, ϵ = leading_boundary(mps2, T2, vumps_alg) + f = abs(prod(expectation_value(mps2, T2))) +end diff --git a/test/cuda/boundarymps/vumps.jl b/test/cuda/boundarymps/vumps.jl new file mode 100644 index 000000000..f5d8f2471 --- /dev/null +++ b/test/cuda/boundarymps/vumps.jl @@ -0,0 +1,127 @@ +using Test +using Random +using PEPSKit +using TensorKit +using MPSKit +using LinearAlgebra +using Adapt, CUDA + +Random.seed!(29384293742893) + +const vumps_alg = VUMPS(; + tol = 1.0e-6, alg_eigsolve = MPSKit.Defaults.alg_eigsolve(; ishermitian = false), verbosity = 2 +) + +@testset "(1, 1) PEPS" begin + Vpeps = ComplexSpace(2) + psi = adapt(CuArray, InfinitePEPS(Vpeps, Vpeps)) + + T = adapt(CuArray, PEPSKit.InfiniteTransferPEPS(psi, 1, 1)) + foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) + mps = adapt(CuArray, initialize_mps(T, [ComplexSpace(20)])) + + mps, env, ϵ = leading_boundary(mps, T, vumps_alg) + N = abs(sum(expectation_value(mps, T))) + + mps2, = changebonds(mps, T, OptimalExpand(; trscheme = truncrank(30))) # TODO: update `trscheme` to `trunc` once MPSKit does + mps2, env2, ϵ = leading_boundary(mps2, T, vumps_alg) + N2 = abs(sum(expectation_value(mps2, T))) + @test N ≈ N2 rtol = 1.0e-2 + + ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) + N´ = abs(norm(psi, ctm)) + + @test N ≈ N´ atol = 1.0e-3 +end + +@testset "(2, 2) PEPS" begin + Vpeps = ComplexSpace(2) + psi = adapt(CuArray, InfinitePEPS(Vpeps, Vpeps; unitcell = (2, 2))) + T = adapt(CuArray, PEPSKit.MultilineTransferPEPS(psi, 1)) + # foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) # TODO: MPSKit.physicalspace(::MultilineMPO) isn't implemented... + mps = initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2)) + mps, env, ϵ = leading_boundary(mps, T, vumps_alg) + N = abs(prod(expectation_value(mps, T))) + + ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) + N´ = abs(norm(psi, ctm)) + + @test N ≈ N´ rtol = 1.0e-2 +end + +@testset "Fermionic PEPS" begin + D = Vect[fℤ₂](0 => 1, 1 => 1) + d = Vect[fℤ₂](0 => 1, 1 => 1) + χ = Vect[fℤ₂](0 => 10, 1 => 10) + + psi = adapt(CuArray, InfinitePEPS(D, d; unitcell = (1, 1))) + n = adapt(CuArray, InfiniteSquareNetwork(psi)) + T = adapt(CuArray, InfiniteTransferPEPS(psi, 1, 1)) + foreach(V -> (@test V == D ⊗ D'), physicalspace(T)) + + # compare boundary MPS contraction to CTMRG contraction + mps = adapt(CuArray, initialize_mps(T, [χ])) + mps, env, ϵ = leading_boundary(mps, T, vumps_alg) + N_vumps = abs(prod(expectation_value(mps, T))) + + ctm, = leading_boundary(CTMRGEnv(psi, χ), psi) + N_ctm = abs(norm(psi, ctm)) + + @test N_vumps ≈ N_ctm rtol = 1.0e-2 + + # and again after blocking the local sandwiches + n´ = adapt(CuArray, InfiniteSquareNetwork(map(PEPSKit.mpotensor, PEPSKit.unitcell(n)))) + T´ = adapt(CuArray, InfiniteMPO(map(PEPSKit.mpotensor, T.O))) + foreach(V -> (@test V == fuse(D, D')), physicalspace(T´)) + + mps´ = adapt(CuArray, InfiniteMPS(randn, ComplexF64, [physicalspace(T´, 1)], [χ])) + mps´, env´, ϵ = leading_boundary(mps´, T´, vumps_alg) + N_vumps´ = abs(prod(expectation_value(mps´, T´))) + + ctm´, = leading_boundary(CTMRGEnv(n´, χ), n´) + N_ctm´ = abs(network_value(n´, ctm´)) + + @show N_vumps´ + @test N_vumps´ ≈ N_vumps rtol = 1.0e-2 + @test N_vumps´ ≈ N_ctm´ rtol = 1.0e-2 +end + +@testset "PEPO runthrough" begin + function ising_pepo(beta; unitcell = (1, 1, 1)) + t = ComplexF64[exp(beta) exp(-beta); exp(-beta) exp(beta)] + q = sqrt(t) + + O = zeros(2, 2, 2, 2, 2, 2) + O[1, 1, 1, 1, 1, 1] = 1 + O[2, 2, 2, 2, 2, 2] = 1 + @tensor o[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + + O = TensorMap(o, ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)') + + return adapt(CuArray, InfinitePEPO(O; unitcell)) + end + + Vpepo = ComplexSpace(2) + Vpeps = ComplexSpace(2) + + # single-layer PEPO + O = ising_pepo(1) + psi = adapt(CuArray, PEPSKit.initializePEPS(O, Vpeps)) + T = adapt(CuArray, InfiniteTransferPEPO(psi, O, 1, 1)) + foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpeps'), physicalspace(T)) + + mps = adapt(CuArray, initialize_mps(rand, scalartype(T), T, [ComplexSpace(10)])) + mps, env, ϵ = leading_boundary(mps, T, vumps_alg) + f = abs(prod(expectation_value(mps, T))) + + # double-layer PEPO + O2 = repeat(O, 1, 1, 2) + psi2 = adapt(CuArray, initializePEPS(O2, Vpeps)) + T2 = adapt(CuArray, InfiniteTransferPEPO(psi, O2, 1, 1)) + foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpepo ⊗ Vpeps'), physicalspace(T2)) + + mps2 = adapt(CuArray, initialize_mps(rand, scalartype(T2), T2, [ComplexSpace(8)])) + mps2, env2, ϵ = leading_boundary(mps2, T2, vumps_alg) + f = abs(prod(expectation_value(mps2, T2))) +end diff --git a/test/runtests.jl b/test/runtests.jl index 89981e3f7..7be097c9c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,6 +6,18 @@ testsuite = find_tests(@__DIR__) # remove testsuite filter!(!(startswith("testsuite") ∘ first), testsuite) +# CUDA tests: only run if CUDA is functional +using CUDA: CUDA +CUDA.functional() || filter!(!startswith("cuda") ∘ first, testsuite) +# AMDGPU tests: only run if AMDGPU is functional +using AMDGPU +AMDGPU.functional() || filter!(!startswith("amd") ∘ first, testsuite) + +# On Buildkite (GPU CI runner): only run CUDA and AMDGPU tests +if get(ENV, "BUILDKITE", "false") == "true" + f(str) = startswith(first(str), "cuda") || startswith(first(str), "amd") + filter!(f, testsuite) +end # --fast to indicate a smaller set of tests args = parse_args(ARGS; custom = ["fast"]) From 620f9cff69caeebd5c6f4f43b1b12e0cfd153746 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 29 Jul 2026 17:04:53 +0200 Subject: [PATCH 04/69] Try densitymatrices tests --- test/amd/toolbox/densitymatrices.jl | 79 ++++++++++++++++++++++++++++ test/cuda/toolbox/densitymatrices.jl | 79 ++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 test/amd/toolbox/densitymatrices.jl create mode 100644 test/cuda/toolbox/densitymatrices.jl diff --git a/test/amd/toolbox/densitymatrices.jl b/test/amd/toolbox/densitymatrices.jl new file mode 100644 index 000000000..4b7c28103 --- /dev/null +++ b/test/amd/toolbox/densitymatrices.jl @@ -0,0 +1,79 @@ +using TensorKit +using PEPSKit +using PEPSKit: contract_local_operator, contract_local_norm +using Test +using TestExtras +using AMDGPU, Adapt + +ds = Dict(Trivial => ℂ^2, U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), FermionParity => Vect[FermionParity](0 => 2, 1 => 1)) +Ds = Dict(Trivial => ℂ^3, U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 2, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) +χs = Dict(Trivial => ℂ^4, U1Irrep => U1Space(i => χ for (i, χ) in zip(-2:2, (1, 3, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) + +@testset "Single-layer densitymatrix contractions ($I)" for I in keys(ds) + d = ds[I] + D = Ds[I] + χ = χs[I] + ρ = adapt(ROCArray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) + + ρ_pf = @constinferred InfinitePartitionFunction(ρ) + env = CTMRGEnv(adapt(ROCArray, ρ_pf), χ) + + O = adapt(ROCArray, rand(d, d)) + @plansor O_pf[W S; N E] := O[p'; p] * ρ[1, 1, 1][p p'; N E S W] + + # Single site + O_singlesite = adapt(ROCArray, LocalOperator(physicalspace(ρ), ((1, 1),) => O)) + E1 = expectation_value(ρ, O_singlesite, env) + E2 = expectation_value(ρ_pf, CartesianIndex(1, 1) => O_pf, env) + @test E1 ≈ E2 + + # two sites + for inds in zip( + [(1, 1), (1, 1), (1, 1), (1, 2), (1, 1)], + [(2, 1), (1, 2), (2, 2), (2, 1), (3, 1)] + ) + O_twosite = adapt(ROCArray, LocalOperator(physicalspace(ρ), inds => O ⊗ O)) + E3 = expectation_value(ρ, O_twosite, env) + # TODO: not defined for partition functions... + end +end + +@testset "Double-layer densitymatrix contractions ($I)" for I in keys(ds) + d = ds[I] + D = Ds[I] + χ = χs[I] + ρ = adapt(ROCARray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) + + ρ_peps = @constinferred InfinitePEPS(ρ) + env = CTMRGEnv(adapt(ROCArray, ρ_peps), χ) + + O = adapt(ROCArray, rand(d, d)) + F = adapt(ROCArray, isomorphism(fuse(d ⊗ d'), d ⊗ d')) + @tensor O_doubled[-1; -2] := F[-1; 1 2] * O[1; 3] * twist(F', 2)[3 2; -2] + + # Single site + site = (1, 1) + O_singlesite = adapt(ROCArray, LocalOperator(physicalspace(ρ), (site,) => O)) + E1 = expectation_value(ρ, O_singlesite, ρ, env) + O_doubled_singlesite = adapt(ROCArray, LocalOperator(physicalspace(ρ_peps), (site,) => O_doubled)) + E2 = expectation_value(ρ_peps, O_doubled_singlesite, ρ_peps, env) + @test E1 ≈ E2 + val = contract_local_operator([site], O_doubled, ρ_peps, ρ_peps, env) + nrm = contract_local_norm([site], ρ_peps, ρ_peps, env) + @test E1 ≈ val / nrm + + # two sites + for inds in zip( + [(1, 1), (1, 1), (1, 1), (1, 2), (1, 1)], + [(2, 1), (1, 2), (2, 2), (2, 1), (3, 1)] + ) + O_twosite = adapt(ROCArray, LocalOperator(physicalspace(ρ), inds => O ⊗ O)) + E1 = expectation_value(ρ, O_twosite, ρ, env) + O_doubled_twosite = adapt(ROCArray, LocalOperator(physicalspace(ρ_peps), inds => O_doubled ⊗ O_doubled)) + E2 = expectation_value(ρ_peps, O_doubled_twosite, ρ_peps, env) + @test E1 ≈ E2 + val = contract_local_operator(collect(inds), O_doubled ⊗ O_doubled, ρ_peps, ρ_peps, env) + nrm = contract_local_norm(collect(inds), ρ_peps, ρ_peps, env) + @test E1 ≈ val / nrm + end +end diff --git a/test/cuda/toolbox/densitymatrices.jl b/test/cuda/toolbox/densitymatrices.jl new file mode 100644 index 000000000..ced3c66c2 --- /dev/null +++ b/test/cuda/toolbox/densitymatrices.jl @@ -0,0 +1,79 @@ +using TensorKit +using PEPSKit +using PEPSKit: contract_local_operator, contract_local_norm +using Test +using TestExtras +using CUDA, Adapt + +ds = Dict(Trivial => ℂ^2, U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), FermionParity => Vect[FermionParity](0 => 2, 1 => 1)) +Ds = Dict(Trivial => ℂ^3, U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 2, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) +χs = Dict(Trivial => ℂ^4, U1Irrep => U1Space(i => χ for (i, χ) in zip(-2:2, (1, 3, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) + +@testset "Single-layer densitymatrix contractions ($I)" for I in keys(ds) + d = ds[I] + D = Ds[I] + χ = χs[I] + ρ = adapt(CuArray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) + + ρ_pf = @constinferred InfinitePartitionFunction(ρ) + env = CTMRGEnv(adapt(CuArray, ρ_pf), χ) + + O = adapt(CuArray, rand(d, d)) + @plansor O_pf[W S; N E] := O[p'; p] * ρ[1, 1, 1][p p'; N E S W] + + # Single site + O_singlesite = adapt(CuArray, LocalOperator(physicalspace(ρ), ((1, 1),) => O)) + E1 = expectation_value(ρ, O_singlesite, env) + E2 = expectation_value(ρ_pf, CartesianIndex(1, 1) => O_pf, env) + @test E1 ≈ E2 + + # two sites + for inds in zip( + [(1, 1), (1, 1), (1, 1), (1, 2), (1, 1)], + [(2, 1), (1, 2), (2, 2), (2, 1), (3, 1)] + ) + O_twosite = adapt(CuArray, LocalOperator(physicalspace(ρ), inds => O ⊗ O)) + E3 = expectation_value(ρ, O_twosite, env) + # TODO: not defined for partition functions... + end +end + +@testset "Double-layer densitymatrix contractions ($I)" for I in keys(ds) + d = ds[I] + D = Ds[I] + χ = χs[I] + ρ = adapt(CuARray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) + + ρ_peps = @constinferred InfinitePEPS(ρ) + env = CTMRGEnv(adapt(CuArray, ρ_peps), χ) + + O = adapt(CuArray, rand(d, d)) + F = adapt(CuArray, isomorphism(fuse(d ⊗ d'), d ⊗ d')) + @tensor O_doubled[-1; -2] := F[-1; 1 2] * O[1; 3] * twist(F', 2)[3 2; -2] + + # Single site + site = (1, 1) + O_singlesite = adapt(CuArray, LocalOperator(physicalspace(ρ), (site,) => O)) + E1 = expectation_value(ρ, O_singlesite, ρ, env) + O_doubled_singlesite = adapt(CuArray, LocalOperator(physicalspace(ρ_peps), (site,) => O_doubled)) + E2 = expectation_value(ρ_peps, O_doubled_singlesite, ρ_peps, env) + @test E1 ≈ E2 + val = contract_local_operator([site], O_doubled, ρ_peps, ρ_peps, env) + nrm = contract_local_norm([site], ρ_peps, ρ_peps, env) + @test E1 ≈ val / nrm + + # two sites + for inds in zip( + [(1, 1), (1, 1), (1, 1), (1, 2), (1, 1)], + [(2, 1), (1, 2), (2, 2), (2, 1), (3, 1)] + ) + O_twosite = adapt(CuArray, LocalOperator(physicalspace(ρ), inds => O ⊗ O)) + E1 = expectation_value(ρ, O_twosite, ρ, env) + O_doubled_twosite = adapt(CuArray, LocalOperator(physicalspace(ρ_peps), inds => O_doubled ⊗ O_doubled)) + E2 = expectation_value(ρ_peps, O_doubled_twosite, ρ_peps, env) + @test E1 ≈ E2 + val = contract_local_operator(collect(inds), O_doubled ⊗ O_doubled, ρ_peps, ρ_peps, env) + nrm = contract_local_norm(collect(inds), ρ_peps, ρ_peps, env) + @test E1 ≈ val / nrm + end +end From 2dc1caed1955935b41b178427f9469d871c1771e Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 29 Jul 2026 17:44:11 +0200 Subject: [PATCH 05/69] Add a simple timestep example --- test/amd/timeevol/timestep.jl | 35 ++++++++++++++++++++++++++++++++++ test/cuda/timeevol/timestep.jl | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 test/amd/timeevol/timestep.jl create mode 100644 test/cuda/timeevol/timestep.jl diff --git a/test/amd/timeevol/timestep.jl b/test/amd/timeevol/timestep.jl new file mode 100644 index 000000000..211cccc5c --- /dev/null +++ b/test/amd/timeevol/timestep.jl @@ -0,0 +1,35 @@ +using Test +using Random +using TensorKit +using PEPSKit +using AMDGPU, Adapt + +@testset "SimpleUpdate timestep" begin + Nr, Nc = 2, 2 + H = adapt(ROCArray, real(heisenberg_XYZ(ComplexF64, Trivial, InfiniteSquare(Nr, Nc); Jx = 1, Jy = 1, Jz = 1))) + Pspace, Vspace = ℂ^2, ℂ^4 + ψ0 = adapt(ROCArray, InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc))) + @test TensorKit.storagetype(ψ0) <: ROCArray + env0 = adapt(ROCArray, SUWeight(ψ0)) + alg = SimpleUpdate(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) + dt, nstep = 1.0e-2, 50 + # manual timestep + evolver = TimeEvolver(ψ0, H, dt, nstep, alg, env0) + ψ1, env1, info1 = deepcopy(ψ0), deepcopy(env0), nothing + for iter in 0:(nstep - 1) + ψ1, env1, info1 = timestep(evolver, ψ1, env1) + end + # time_evolve + ψ2, env2, info2 = time_evolve(ψ0, H, dt, nstep, alg, env0) + # for-loop syntax + ## manually reset internal state of evolver + evolver.state = PEPSKit.SUState(0, 0.0, ψ0, env0) + ψ3, env3, info3 = nothing, nothing, nothing + for state in evolver + ψ3, env3, info3 = state + end + # results should be *exactly* the same + @test ψ1 == ψ2 == ψ3 + @test env1 == env2 == env3 + @test info1 == info2 == info3 +end diff --git a/test/cuda/timeevol/timestep.jl b/test/cuda/timeevol/timestep.jl new file mode 100644 index 000000000..c99254566 --- /dev/null +++ b/test/cuda/timeevol/timestep.jl @@ -0,0 +1,34 @@ +using Test +using Random +using TensorKit +using PEPSKit +using CUDA, Adapt + +@testset "SimpleUpdate timestep" begin + Nr, Nc = 2, 2 + H = adapt(CuArray, real(heisenberg_XYZ(ComplexF64, Trivial, InfiniteSquare(Nr, Nc); Jx = 1, Jy = 1, Jz = 1))) + Pspace, Vspace = ℂ^2, ℂ^4 + ψ0 = adapt(CuArray, InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc))) + env0 = adapt(CuArray, SUWeight(ψ0)) + alg = SimpleUpdate(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) + dt, nstep = 1.0e-2, 50 + # manual timestep + evolver = TimeEvolver(ψ0, H, dt, nstep, alg, env0) + ψ1, env1, info1 = deepcopy(ψ0), deepcopy(env0), nothing + for iter in 0:(nstep - 1) + ψ1, env1, info1 = timestep(evolver, ψ1, env1) + end + # time_evolve + ψ2, env2, info2 = time_evolve(ψ0, H, dt, nstep, alg, env0) + # for-loop syntax + ## manually reset internal state of evolver + evolver.state = PEPSKit.SUState(0, 0.0, ψ0, env0) + ψ3, env3, info3 = nothing, nothing, nothing + for state in evolver + ψ3, env3, info3 = state + end + # results should be *exactly* the same + @test ψ1 == ψ2 == ψ3 + @test env1 == env2 == env3 + @test info1 == info2 == info3 +end From e1b1d7e7a0ee09d8c630b8423aba45f9a509b114 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 31 Jul 2026 12:46:35 +0200 Subject: [PATCH 06/69] More progress --- src/environments/ctmrg_environments.jl | 46 ++-- src/networks/infinitesquarenetwork.jl | 1 + src/operators/infinitepepo.jl | 2 + src/states/infinitepartitionfunction.jl | 6 +- src/states/infinitepeps.jl | 2 - test/amd/ctmrg/contractions.jl | 314 ++++++++++++++++++++++++ 6 files changed, 343 insertions(+), 28 deletions(-) create mode 100644 test/amd/ctmrg/contractions.jl diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index fad2550e6..e0aaef5e4 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -51,15 +51,15 @@ function check_environment_virtualspace(E::CTMRGEdgeTensor) end function _corner_tensor( - f, ::Type{TorA}, left_vspace::S, right_vspace::S = left_vspace - ) where {TorA, S <: ElementarySpace} - return f(TorA, left_vspace ← right_vspace) + f, ::Type{T}, left_vspace::S, right_vspace::S = left_vspace + ) where {T, S <: ElementarySpace} + return f(T, left_vspace ← right_vspace) end function _edge_tensor( - f, ::Type{TA}, left_vspace::S, pspaces::P, right_vspace::S = left_vspace - ) where {TA, S <: ElementarySpace, P <: ProductSpace} - return f(TA, left_vspace ⊗ pspaces, right_vspace) + f, ::Type{T}, left_vspace::S, pspaces::P, right_vspace::S = left_vspace + ) where {T, S <: ElementarySpace, P <: ProductSpace} + return f(T, left_vspace ⊗ pspaces, right_vspace) end """ @@ -85,11 +85,11 @@ of a partition function defined in terms of local rank-4 tensors) or a `ProductS for the case of a network representing overlaps of PEPSs and PEPOs). """ function CTMRGEnv( - f, ::Type{TorA}, Ds_north::A, Ds_east::A, chis_north::B, chis_east::B = chis_north, + f, ::Type{T}, Ds_north::A, Ds_east::A, chis_north::B, chis_east::B = chis_north, chis_south::B = chis_north, chis_west::B = chis_north, ) where { A <: AbstractMatrix{<:ProductSpace}, B <: AbstractMatrix{<:ElementarySpace}, - TorA, + T, } # check all of the sizes size(Ds_north) == size(Ds_east) == size(chis_north) == size(chis_east) == @@ -102,8 +102,8 @@ function CTMRGEnv( # do the whole thing N = length(first(Ds_north)) st = spacetype(first(Ds_north)) - C_type = tensormaptype(st, 1, 1, TorA) - T_type = tensormaptype(st, N + 1, 1, TorA) + C_type = tensormaptype(st, 1, 1, T) + T_type = tensormaptype(st, N + 1, 1, T) # First index is direction corners = Array{C_type}(undef, 4, size(Ds_north)...) @@ -112,28 +112,28 @@ function CTMRGEnv( for I in CartesianIndices(Ds_north) r, c = I.I edges[NORTH, r, c] = _edge_tensor( - f, TorA, chis_north[r, _prev(c, end)], Ds_north[_next(r, end), c], chis_north[r, c] + f, T, chis_north[r, _prev(c, end)], Ds_north[_next(r, end), c], chis_north[r, c] ) edges[EAST, r, c] = _edge_tensor( - f, TorA, chis_east[r, c], Ds_east[r, _prev(c, end)], chis_east[_next(r, end), c] + f, T, chis_east[r, c], Ds_east[r, _prev(c, end)], chis_east[_next(r, end), c] ) edges[SOUTH, r, c] = _edge_tensor( - f, TorA, chis_south[r, c], Ds_south[_prev(r, end), c], chis_south[r, _prev(c, end)] + f, T, chis_south[r, c], Ds_south[_prev(r, end), c], chis_south[r, _prev(c, end)] ) edges[WEST, r, c] = _edge_tensor( - f, TorA, chis_west[_next(r, end), c], Ds_west[r, _next(c, end)], chis_west[r, c] + f, T, chis_west[_next(r, end), c], Ds_west[r, _next(c, end)], chis_west[r, c] ) corners[NORTHWEST, r, c] = _corner_tensor( - f, TorA, chis_west[_next(r, end), c], chis_north[r, c] + f, T, chis_west[_next(r, end), c], chis_north[r, c] ) corners[NORTHEAST, r, c] = _corner_tensor( - f, TorA, chis_north[r, _prev(c, end)], chis_east[_next(r, end), c] + f, T, chis_north[r, _prev(c, end)], chis_east[_next(r, end), c] ) corners[SOUTHEAST, r, c] = _corner_tensor( - f, TorA, chis_east[r, c], chis_south[r, _prev(c, end)] + f, T, chis_east[r, c], chis_south[r, _prev(c, end)] ) - corners[SOUTHWEST, r, c] = _corner_tensor(f, TorA, chis_south[r, c], chis_west[r, c]) + corners[SOUTHWEST, r, c] = _corner_tensor(f, T, chis_south[r, c], chis_west[r, c]) end corners[:, :, :] ./= norm.(corners[:, :, :]) @@ -141,7 +141,7 @@ function CTMRGEnv( return CTMRGEnv(corners, edges) end function CTMRGEnv(D_north::P, args...; kwargs...) where {P <: Union{Matrix{VectorSpace}, VectorSpace}} - return CTMRGEnv(randn, Matrix{ComplexF64}, D_north, args...; kwargs...) + return CTMRGEnv(randn, ComplexF64, D_north, args...; kwargs...) end # expand physical edge spaces to unit cell size @@ -182,11 +182,11 @@ The environment virtual spaces for each site correspond to virtual space of the corresponding edge tensor for each direction. """ function CTMRGEnv( - f, ::Type{TorA}, + f, ::Type{T}, D_north::S, D_east::S, virtual_spaces...; unitcell::Tuple{Int, Int} = (1, 1), - ) where {S <: VectorSpace, TorA} + ) where {S <: VectorSpace, T} return CTMRGEnv( - f, TorA, + f, T, _fill_edge_physical_spaces(D_north, D_east; unitcell)..., _fill_environment_virtual_spaces(virtual_spaces...; unitcell)..., ) @@ -219,7 +219,7 @@ of the corresponding edge tensor for each direction. Specifically, for a given s `chis_south[r, c]` corresponds to the east space of the south edge tensor, and `chis_west[r, c]` corresponds to the north space of the west edge tensor. """ -function CTMRGEnv(f, ::Type{TorA}, network::N, virtual_spaces...) where {TorA, N <: InfiniteSquareNetwork} +function CTMRGEnv(f, ::Type{T}, network::N, virtual_spaces...) where {T, N <: InfiniteSquareNetwork} Ds_north = _north_edge_physical_spaces(network) Ds_east = _east_edge_physical_spaces(network) virtual_spaces = _fill_environment_virtual_spaces(virtual_spaces...; unitcell = size(network)) diff --git a/src/networks/infinitesquarenetwork.jl b/src/networks/infinitesquarenetwork.jl index 5a52e1f17..1c9995baf 100644 --- a/src/networks/infinitesquarenetwork.jl +++ b/src/networks/infinitesquarenetwork.jl @@ -25,6 +25,7 @@ struct InfiniteSquareNetwork{O} end end InfiniteSquareNetwork(n::InfiniteSquareNetwork) = n +TensorKit.storagetype(::Type{InfiniteSquareNetwork{O}}) where {O} = storagetype(O) ## Unit cell interface diff --git a/src/operators/infinitepepo.jl b/src/operators/infinitepepo.jl index 1f68a9161..65ec5194d 100644 --- a/src/operators/infinitepepo.jl +++ b/src/operators/infinitepepo.jl @@ -169,6 +169,8 @@ function physicalspace(T::InfinitePEPO, r::Int, c::Int) return codomain_physicalspace(T, r, c) end +TensorKit.storagetype(::Type{InfinitePEPO{T}}) where {T} = storagetype(T) + ## InfiniteSquareNetwork interface function InfiniteSquareNetwork(top::InfinitePEPS, mid::InfinitePEPO, bot::InfinitePEPS = top) diff --git a/src/states/infinitepartitionfunction.jl b/src/states/infinitepartitionfunction.jl index e3818a4c6..ad011849e 100644 --- a/src/states/infinitepartitionfunction.jl +++ b/src/states/infinitepartitionfunction.jl @@ -50,8 +50,8 @@ of the PEPS tensor at each site in the unit cell as a matrix. Each individual sp specified as either an `Int` or an `ElementarySpace`. """ function InfinitePartitionFunction( - f, ::Type{T}, ::Type{TA}, Nspaces::M, Espaces::M = Nspaces - ) where {M <: AbstractMatrix{<:ElementarySpace}, T <: Number, TA <: AbstractArray{T}} + f, ::Type{T}, Nspaces::M, Espaces::M = Nspaces + ) where {M <: AbstractMatrix{<:ElementarySpace}, T <: Number} size(Nspaces) == size(Espaces) || throw(ArgumentError("Input spaces should have equal sizes.")) @@ -65,7 +65,7 @@ function InfinitePartitionFunction( return InfinitePartitionFunction(A) end function InfinitePartitionFunction(Nspaces::A, args...) where {A <: Union{AbstractMatrix{<:ElementarySpace}, ElementarySpace}} - return InfinitePartitionFunction(randn, ComplexF64, Vector{ComplexF64}, Nspaces, args...) + return InfinitePartitionFunction(randn, ComplexF64, Nspaces, args...) end """ diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index f83fabc4a..471c14253 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -65,8 +65,6 @@ function InfinitePEPS( ) where {A <: Union{AbstractMatrix{<:ElementarySpace}, ElementarySpace}} return InfinitePEPS(randn, Vector{ComplexF64}, Pspaces, virtual_spaces...; kwargs...) end - -TensorKit.storagetype(peps::InfinitePEPS{T}) where {T} = storagetype(T) TensorKit.storagetype(::Type{InfinitePEPS{T}}) where {T} = storagetype(T) """ diff --git a/test/amd/ctmrg/contractions.jl b/test/amd/ctmrg/contractions.jl new file mode 100644 index 000000000..090b0aa75 --- /dev/null +++ b/test/amd/ctmrg/contractions.jl @@ -0,0 +1,314 @@ +using Test +using Random +using PEPSKit +using TensorKit +using AMDGPU, Adapt + +using PEPSKit: eachcoordinate, _next_coordinate +using PEPSKit: EnlargedCorner, HalfInfiniteEnv, FullInfiniteEnv +using PEPSKit: half_infinite_environment, full_infinite_environment +using PEPSKit: simultaneous_projectors, contract_projectors +using PEPSKit: renormalize_northwest_corner, renormalize_northeast_corner, + renormalize_southeast_corner, renormalize_southwest_corner +using PEPSKit: random_start_vector + +# settings +Random.seed!(91283219348) +stype = ComplexF64 + +renormalize_corner_fns = ( + renormalize_northwest_corner, renormalize_northeast_corner, + renormalize_southeast_corner, renormalize_southwest_corner, +) + +function test_ctmrg_contractions( + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) + + @testset "CTMRG PEPS contractions" begin + peps = adapt(ROCArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) + env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) + + n = InfiniteSquareNetwork(peps) + + @test storagetype(peps) <: ROCArray + @test storagetype(env) <: ROCArray + @test storagetype(n) <: ROCArray + test_contractions(n, env) + end + + @testset "CTMRG PartitionFunction contractions" begin + pf = adapt(ROCArray, InfinitePartitionFunction(randn, stype, Nspaces, Espaces)) + env = CTMRGEnv(randn, stype, pf, chis_north, chis_east, chis_south, chis_west) + n = InfiniteSquareNetwork(pf) + @test storagetype(peps) <: ROCArray + @test storagetype(env) <: ROCArray + @test storagetype(n) <: ROCArray + + test_contractions(n, env) + end + + @testset "CTMRG PEPO contractions" begin + pepo = adapt(ROCArray, InfinitePEPO(randn, stype, Pspaces, Pspaces, Pspaces)) + peps = adapt(ROCArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) + n = InfiniteSquareNetwork(peps, pepo) + env = adapt(ROCArray, CTMRGEnv(randn, stype, n, chis_north, chis_east, chis_south, chis_west)) + @test storagetype(pepo) <: ROCArray + @test storagetype(env) <: ROCArray + @test storagetype(n) <: ROCArray + + test_contractions(n, env) + end + + return nothing +end + +function test_contractions(n::InfiniteSquareNetwork, env::CTMRGEnv) + dirs_and_coordinates = eachcoordinate(n, 1:4) + + # initialize dense and sparse enlarged corners + sparse_enlarged_corners = map(dirs_and_coordinates) do co + return EnlargedCorner(n, env, co) + end + dense_enlarged_corners = map(TensorMap, sparse_enlarged_corners) + + # initialize sparse and dense half-inifite environments + sparse_halfinf_envs = map(dirs_and_coordinates) do co + co´ = _next_coordinate(co, size(env)[2:3]...) + return HalfInfiniteEnv( + sparse_enlarged_corners[co...], sparse_enlarged_corners[co´...] + ) + end + dense_halfinf_envs = map(TensorMap, sparse_halfinf_envs) + # also compute directly from dense enlarged corners, for consistency with current implementation + dense_halfinf_envs_bis = map(dirs_and_coordinates) do co + co´ = _next_coordinate(co, size(env)[2:3]...) + return half_infinite_environment( + dense_enlarged_corners[co...], dense_enlarged_corners[co´...] + ) + end + + # initialize sparse and dense full-inifite environments + sparse_fullinf_envs = map(dirs_and_coordinates) do co + rowsize, colsize = size(env)[2:3] + co2 = _next_coordinate(co, rowsize, colsize) + co3 = _next_coordinate(co2, rowsize, colsize) + co4 = _next_coordinate(co3, rowsize, colsize) + return FullInfiniteEnv( + sparse_enlarged_corners[co4...], + sparse_enlarged_corners[co...], + sparse_enlarged_corners[co2...], + sparse_enlarged_corners[co3...], + ) + end + dense_fullinf_envs = map(TensorMap, sparse_fullinf_envs) + # also compute directly from dense enlarged corners, for consistency with current implementation + dense_fullinf_envs_bis = map(dirs_and_coordinates) do co + rowsize, colsize = size(env)[2:3] + co2 = _next_coordinate(co, rowsize, colsize) + co3 = _next_coordinate(co2, rowsize, colsize) + co4 = _next_coordinate(co3, rowsize, colsize) + return full_infinite_environment( + dense_enlarged_corners[co4...], + dense_enlarged_corners[co...], + dense_enlarged_corners[co2...], + dense_enlarged_corners[co3...], + ) + end + + # SVD half and full infinite environments + (P_left_half, P_right_half), info_half = simultaneous_projectors( + dense_enlarged_corners, env, HalfInfiniteProjector() + ) + U_half, S_half, V_half = info_half.U, info_half.S, info_half.V + (P_left_full, P_right_full), info_full = simultaneous_projectors( + dense_enlarged_corners, env, FullInfiniteProjector() + ) + U_full, S_full, V_full = info_full.U, info_full.S, info_full.V + + # check projector computation for both types of environments, + # comparing dense and sparse implementations + foreach(dirs_and_coordinates) do co + dir, r, c = co + + co2 = _next_coordinate(co, size(env)[2:3]...) + co3 = _next_coordinate(co2, size(env)[2:3]...) + co4 = _next_coordinate(co3, size(env)[2:3]...) + + ## HalfInfiniteEnv + + shenv = sparse_halfinf_envs[dir, r, c] + dhenv = dense_halfinf_envs[dir, r, c] + dhenv_bis = dense_halfinf_envs_bis[dir, r, c] + @test dhenv ≈ dhenv_bis + + # application + xr = random_start_vector(shenv) + xl = randn(storagetype(shenv), codomain(shenv)) + @test shenv(xr, Val(false)) ≈ dhenv * xr + @test shenv(xl, Val(true)) ≈ dhenv' * xl + + # projector computation + P_left_sparse, P_right_sparse = contract_projectors( + U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], shenv + ) + P_left_dense, P_right_dense = contract_projectors( + U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], + dense_enlarged_corners[co...], dense_enlarged_corners[co2...], + ) + @test P_left_sparse ≈ P_left_dense + @test P_right_sparse ≈ P_right_dense + @test P_left_sparse ≈ P_left_half[dir, r, c] + @test P_right_sparse ≈ P_right_half[dir, r, c] + + + ## FullInfiniteEnv + + sfenv = sparse_fullinf_envs[dir, r, c] + dfenv = dense_fullinf_envs[dir, r, c] + dfenv_bis = dense_fullinf_envs_bis[dir, r, c] + @test dfenv ≈ dfenv_bis + + # application + xl = randn(storagetype(sfenv), codomain(sfenv)) + xr = random_start_vector(sfenv) + @test sfenv(xr, Val(false)) ≈ dfenv * xr + @test sfenv(xl, Val(true)) ≈ dfenv' * xl + + # projector computation + P_left_sparse, P_right_sparse = contract_projectors( + U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], sfenv + ) + P_left_dense, P_right_dense = contract_projectors( + U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], + half_infinite_environment( + dense_enlarged_corners[co4...], dense_enlarged_corners[co...] + ), + half_infinite_environment( + dense_enlarged_corners[co2...], dense_enlarged_corners[co3...] + ), + ) + @test P_left_sparse ≈ P_left_dense + @test P_right_sparse ≈ P_right_dense + @test P_left_sparse ≈ P_left_full[dir, r, c] + @test P_right_sparse ≈ P_right_full[dir, r, c] + end + + foreach(dirs_and_coordinates) do co + dir, r, c = co + + ## Corner renormalization + + C_sparse = renormalize_corner_fns[dir]( + (r, c), sparse_enlarged_corners, P_left_half, P_right_half + ) + C_dense = renormalize_corner_fns[dir]( + (r, c), dense_enlarged_corners, P_left_half, P_right_half + ) + @test C_sparse ≈ C_dense + end + + return nothing +end + +@testset "Random Cartesian spaces" begin + unitcell = (3, 3) + + Pspaces = ComplexSpace.(rand(2:3, unitcell...)) + Nspaces = ComplexSpace.(rand(2:4, unitcell...)) + Espaces = ComplexSpace.(rand(2:4, unitcell...)) + chis_north = ComplexSpace.(rand(5:10, unitcell...)) + chis_east = ComplexSpace.(rand(5:10, unitcell...)) + chis_south = ComplexSpace.(rand(5:10, unitcell...)) + chis_west = ComplexSpace.(rand(5:10, unitcell...)) + + test_ctmrg_contractions( + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) +end + +@testset "Specific U1 spaces" begin + unitcell = (2, 2) + + PA = U1Space(-1 => 1, 0 => 1) + PB = U1Space(0 => 1, 1 => 1) + Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) + Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) + + Pspaces = [PA PB; PB PA] + Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] + chis = [Venv Venv; Venv Venv] + + test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) + + # 4x4 unit cell with all 32 inequivalent bonds + # + # 10 4 7 32 + # | | | | + # 3--A--1--B--5--C--8--D--3 + # | | | | + # 2 6 9 11 + # | | | | + # 14--E-12--F-15--G-17--H-14 + # | | | | + # 13 16 18 19 + # | | | | + # 22--I-20--J-23--K-25--L-22 + # | | | | + # 21 24 26 27 + # | | | | + # 29--M-28--N-30--O-31--P-29 + # | | | | + # 10 4 7 32 + + phys_space = Vect[U1Irrep](1 => 1, -1 => 1) + corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) + vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) + @test length(Set(vspaces)) == 32 + + Espaces = [ + vspaces[1] vspaces[5] vspaces[8] vspaces[3] + vspaces[12] vspaces[15] vspaces[17] vspaces[14] + vspaces[20] vspaces[23] vspaces[25] vspaces[22] + vspaces[28] vspaces[30] vspaces[31] vspaces[29] + ] + + Nspaces = [ + vspaces[10] vspaces[4] vspaces[7] vspaces[32] + vspaces[2] vspaces[6] vspaces[9] vspaces[11] + vspaces[13] vspaces[16] vspaces[18] vspaces[19] + vspaces[21] vspaces[24] vspaces[26] vspaces[27] + ] + Pspaces = fill(phys_space, (4, 4)) + chis = fill(corner_space, (4, 4)) + # TODO broken? + #test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) +end + +@testset "Random fermionic spaces" begin + unitcell = (3, 3) + + S = Vect[FermionParity] + pdims = rand(1:2, unitcell..., 2) + vdims = rand(2:4, unitcell..., 2) + edims = rand(3:6, unitcell..., 2) + + function _construct_space(ds::Array{<:Int, 3}) + V = map(Iterators.product(axes(ds)[1:2]...)) do (r, c) + return S(0 => ds[r, c, 1], 1 => ds[r, c, 2]) + end + return V + end + + Pspaces = _construct_space(pdims) + Nspaces = _construct_space(vdims) + Espaces = _construct_space(vdims) + chis_north = _construct_space(edims) + chis_east = _construct_space(edims) + chis_south = _construct_space(edims) + chis_west = _construct_space(edims) + + test_ctmrg_contractions( + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) +end From a31c1f72567e3842bcbfcdadb138bd10fdabc17c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 31 Jul 2026 10:31:13 -0400 Subject: [PATCH 07/69] refactor `_fliptwist_s` --- test/amd/boundarymps/vumps.jl | 2 +- test/amd/toolbox/densitymatrices.jl | 2 +- test/cuda/boundarymps/vumps.jl | 2 +- test/cuda/toolbox/densitymatrices.jl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/amd/boundarymps/vumps.jl b/test/amd/boundarymps/vumps.jl index 6bb43e360..e9b192d25 100644 --- a/test/amd/boundarymps/vumps.jl +++ b/test/amd/boundarymps/vumps.jl @@ -23,7 +23,7 @@ const vumps_alg = VUMPS(; mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(sum(expectation_value(mps, T))) - mps2, = changebonds(mps, T, OptimalExpand(; trscheme = truncrank(30))) # TODO: update `trscheme` to `trunc` once MPSKit does + mps2, = changebonds(mps, T, OptimalExpand(; trunc = truncrank(30))) mps2, env2, ϵ = leading_boundary(mps2, T, vumps_alg) N2 = abs(sum(expectation_value(mps2, T))) @test N ≈ N2 rtol = 1.0e-2 diff --git a/test/amd/toolbox/densitymatrices.jl b/test/amd/toolbox/densitymatrices.jl index 4b7c28103..71b750c4e 100644 --- a/test/amd/toolbox/densitymatrices.jl +++ b/test/amd/toolbox/densitymatrices.jl @@ -42,7 +42,7 @@ end d = ds[I] D = Ds[I] χ = χs[I] - ρ = adapt(ROCARray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) + ρ = adapt(ROCArray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) ρ_peps = @constinferred InfinitePEPS(ρ) env = CTMRGEnv(adapt(ROCArray, ρ_peps), χ) diff --git a/test/cuda/boundarymps/vumps.jl b/test/cuda/boundarymps/vumps.jl index f5d8f2471..bd070f4fc 100644 --- a/test/cuda/boundarymps/vumps.jl +++ b/test/cuda/boundarymps/vumps.jl @@ -23,7 +23,7 @@ const vumps_alg = VUMPS(; mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(sum(expectation_value(mps, T))) - mps2, = changebonds(mps, T, OptimalExpand(; trscheme = truncrank(30))) # TODO: update `trscheme` to `trunc` once MPSKit does + mps2, = changebonds(mps, T, OptimalExpand(; trunc = truncrank(30))) mps2, env2, ϵ = leading_boundary(mps2, T, vumps_alg) N2 = abs(sum(expectation_value(mps2, T))) @test N ≈ N2 rtol = 1.0e-2 diff --git a/test/cuda/toolbox/densitymatrices.jl b/test/cuda/toolbox/densitymatrices.jl index ced3c66c2..8ee47d574 100644 --- a/test/cuda/toolbox/densitymatrices.jl +++ b/test/cuda/toolbox/densitymatrices.jl @@ -42,7 +42,7 @@ end d = ds[I] D = Ds[I] χ = χs[I] - ρ = adapt(CuARray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) + ρ = adapt(CuArray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) ρ_peps = @constinferred InfinitePEPS(ρ) env = CTMRGEnv(adapt(CuArray, ρ_peps), χ) From b6135a607e64b381ee41e4ebd429eae55ebc9e17 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 3 Aug 2026 11:59:02 +0200 Subject: [PATCH 08/69] Working CTMRG contractions tests --- src/environments/ctmrg_environments.jl | 1 - src/states/infinitepartitionfunction.jl | 3 +- src/states/infinitepeps.jl | 2 +- test/amd/boundarymps/vumps.jl | 8 +- test/amd/ctmrg/contractions.jl | 3 +- test/amd/ctmrg/initialization.jl | 94 +++++++ test/cuda/boundarymps/vumps.jl | 8 +- test/cuda/ctmrg/contractions.jl | 315 ++++++++++++++++++++++++ test/cuda/ctmrg/initialization.jl | 94 +++++++ 9 files changed, 518 insertions(+), 10 deletions(-) create mode 100644 test/amd/ctmrg/initialization.jl create mode 100644 test/cuda/ctmrg/contractions.jl create mode 100644 test/cuda/ctmrg/initialization.jl diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index e0aaef5e4..e92a51a94 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -104,7 +104,6 @@ function CTMRGEnv( st = spacetype(first(Ds_north)) C_type = tensormaptype(st, 1, 1, T) T_type = tensormaptype(st, N + 1, 1, T) - # First index is direction corners = Array{C_type}(undef, 4, size(Ds_north)...) edges = Array{T_type}(undef, 4, size(Ds_north)...) diff --git a/src/states/infinitepartitionfunction.jl b/src/states/infinitepartitionfunction.jl index ad011849e..9104aa86a 100644 --- a/src/states/infinitepartitionfunction.jl +++ b/src/states/infinitepartitionfunction.jl @@ -25,6 +25,7 @@ struct InfinitePartitionFunction{T <: PartitionFunctionTensor} return new{T}(A) end end +TensorKit.storagetype(::Type{InfinitePartitionFunction{T}}) where {T} = storagetype(T) const InfinitePF{T} = InfinitePartitionFunction{T} @@ -59,7 +60,7 @@ function InfinitePartitionFunction( Wspaces = circshift(Espaces, (0, 1)) A = map(Nspaces, Espaces, Sspaces, Wspaces) do N, E, S, W - return PartitionFunctionTensor(f, T, TA, N, E, S, W) + return PartitionFunctionTensor(f, T, N, E, S, W) end return InfinitePartitionFunction(A) diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index 471c14253..c48f54f41 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -104,7 +104,7 @@ function _fill_state_virtual_spaces( end """ - InfinitePEPS([f=randn, TorA=ComplexF64,] Pspace, Nspace, [Espace]; unitcell=(1,1)) + InfinitePEPS([f=randn, T=ComplexF64,] Pspace, Nspace, [Espace]; unitcell=(1,1)) Create an InfinitePEPS by specifying its physical, north and east spaces and unit cell. """ diff --git a/test/amd/boundarymps/vumps.jl b/test/amd/boundarymps/vumps.jl index e9b192d25..2e5cebd4f 100644 --- a/test/amd/boundarymps/vumps.jl +++ b/test/amd/boundarymps/vumps.jl @@ -38,8 +38,10 @@ end Vpeps = ComplexSpace(2) psi = adapt(ROCArray, InfinitePEPS(Vpeps, Vpeps; unitcell = (2, 2))) T = adapt(ROCArray, PEPSKit.MultilineTransferPEPS(psi, 1)) + @test storagetype(T) <: ROCArray # foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) # TODO: MPSKit.physicalspace(::MultilineMPO) isn't implemented... - mps = initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2)) + mps = adapt(ROCArray, initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2))) + @test storagetype(mps) <: ROCArray mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(prod(expectation_value(mps, T))) @@ -49,7 +51,7 @@ end @test N ≈ N´ rtol = 1.0e-2 end -@testset "Fermionic PEPS" begin +#=@testset "Fermionic PEPS" begin D = Vect[fℤ₂](0 => 1, 1 => 1) d = Vect[fℤ₂](0 => 1, 1 => 1) χ = Vect[fℤ₂](0 => 10, 1 => 10) @@ -84,7 +86,7 @@ end @show N_vumps´ @test N_vumps´ ≈ N_vumps rtol = 1.0e-2 @test N_vumps´ ≈ N_ctm´ rtol = 1.0e-2 -end +end=# @testset "PEPO runthrough" begin function ising_pepo(beta; unitcell = (1, 1, 1)) diff --git a/test/amd/ctmrg/contractions.jl b/test/amd/ctmrg/contractions.jl index 090b0aa75..8060bc3ae 100644 --- a/test/amd/ctmrg/contractions.jl +++ b/test/amd/ctmrg/contractions.jl @@ -41,7 +41,7 @@ function test_ctmrg_contractions( pf = adapt(ROCArray, InfinitePartitionFunction(randn, stype, Nspaces, Espaces)) env = CTMRGEnv(randn, stype, pf, chis_north, chis_east, chis_south, chis_west) n = InfiniteSquareNetwork(pf) - @test storagetype(peps) <: ROCArray + @test storagetype(pf) <: ROCArray @test storagetype(env) <: ROCArray @test storagetype(n) <: ROCArray @@ -53,6 +53,7 @@ function test_ctmrg_contractions( peps = adapt(ROCArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) n = InfiniteSquareNetwork(peps, pepo) env = adapt(ROCArray, CTMRGEnv(randn, stype, n, chis_north, chis_east, chis_south, chis_west)) + @test storagetype(peps) <: ROCArray @test storagetype(pepo) <: ROCArray @test storagetype(env) <: ROCArray @test storagetype(n) <: ROCArray diff --git a/test/amd/ctmrg/initialization.jl b/test/amd/ctmrg/initialization.jl new file mode 100644 index 000000000..49806d781 --- /dev/null +++ b/test/amd/ctmrg/initialization.jl @@ -0,0 +1,94 @@ +using Test +using TensorKit +using PEPSKit +using Random +using Adapt, AMDGPU +using MPSKitModels: classical_ising +using PEPSKit: ProductStateEnv + +sd = 12345 + +# toggle symmetry, but same issue for both +symmetries = [Z2Irrep, Trivial] +make_space(::Type{Z2Irrep}, d::Int) = Z2Space(0 => d / 2, 1 => d / 2) +make_space(::Type{Trivial}, d::Int) = ComplexSpace(d) + +d = 2 +D = 4 +χ = 20 +tol = 1.0e-4 +maxiter = 1000 +verbosity = 2 +trunc = truncrank(χ) +boundary_alg = (; alg = :SimultaneousCTMRG, tol, verbosity, trunc, maxiter) + +@testset "CTMRG environment initialization for critical ising with $S symmetry (#255)" for S in symmetries + # initialize + Random.seed!(sd) + T = classical_ising(S) + O = T[1] + n = adapt(ROCArray, InfinitePartitionFunction([O O; O O])) + Venv = make_space(S, χ) + P = space(O, 2) + + # random, doesn't converge + env0_rand = initialize_ctmrg_environment(n, RandomInitialization(), Venv) + env_rand, info = leading_boundary(env0_rand, n; boundary_alg...) + @test_broken info.convergence_error ≤ tol + + # embedded random product state, converges + env0_prod = initialize_ctmrg_environment(n, ProductStateInitialization()) + env_prod, info = leading_boundary(env0_prod, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # grown product state, converges + env0_appl = initialize_ctmrg_environment(n, ApplicationInitialization()) + env_appl, info = leading_boundary(env0_appl, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # specific custom starting product state + p_data = ComplexF64[1; 0;;] + p = adapt(ROCArray, Tensor(p_data, P)) + prod_env0 = ProductStateEnv(reshape([p, p, flip(p, 1), flip(p, 1)], 4, 1, 1)) + env0_custom = initialize_ctmrg_environment(n, ApplicationInitialization(), prod_env0) + # or just CTMRGEnv(prod_env0) + env_custom, info = leading_boundary(env0_custom, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # PEPS-specific identity initialization; should throw when used on partition functions + @test_throws ArgumentError env0_prod_id = initialize_ctmrg_environment(n, IdentityInitialization()) +end + +@testset "CTMRG environment initialization for PEPS with $S symmetry" for S in symmetries + # initialize + Random.seed!(sd) + P = make_space(S, d) + Vpeps = make_space(S, D) + Venv = make_space(S, χ) + peps = adapt(ROCArray, InfinitePEPS(P, Vpeps; unitcell = (2, 2))) + n = InfiniteSquareNetwork(peps) + + # random, converges + env0_rand = initialize_ctmrg_environment(n, RandomInitialization(), Venv) + @test storagetype(env0_rand) <: ROCArray + env_rand, info = leading_boundary(env0_rand, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # embedded random product state, converges + env0_prod = initialize_ctmrg_environment(n, ProductStateInitialization()) + @test storagetype(env0_prod) <: ROCArray + env_prod, info = leading_boundary(env0_prod, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # embedded product state as identity from ket to bra, converges + env0_prod_id = initialize_ctmrg_environment(n, IdentityInitialization()) + @test storagetype(env0_prod_id) <: ROCArray + env_prod, info = leading_boundary(env0_prod_id, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # grown product state, converges + env0_appl = initialize_ctmrg_environment(n, ApplicationInitialization()) + @test storagetype(env0_appl) <: ROCArray + env_appl, info = leading_boundary(env0_appl, n; boundary_alg...) + @test info.convergence_error ≤ tol +end diff --git a/test/cuda/boundarymps/vumps.jl b/test/cuda/boundarymps/vumps.jl index bd070f4fc..fce047828 100644 --- a/test/cuda/boundarymps/vumps.jl +++ b/test/cuda/boundarymps/vumps.jl @@ -38,8 +38,10 @@ end Vpeps = ComplexSpace(2) psi = adapt(CuArray, InfinitePEPS(Vpeps, Vpeps; unitcell = (2, 2))) T = adapt(CuArray, PEPSKit.MultilineTransferPEPS(psi, 1)) + @test storagetype(T) <: ROCArray # foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) # TODO: MPSKit.physicalspace(::MultilineMPO) isn't implemented... - mps = initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2)) + mps = adapt(CuArray, initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2))) + @test storagetype(mps) <: ROCArray mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(prod(expectation_value(mps, T))) @@ -49,7 +51,7 @@ end @test N ≈ N´ rtol = 1.0e-2 end -@testset "Fermionic PEPS" begin +#=@testset "Fermionic PEPS" begin D = Vect[fℤ₂](0 => 1, 1 => 1) d = Vect[fℤ₂](0 => 1, 1 => 1) χ = Vect[fℤ₂](0 => 10, 1 => 10) @@ -84,7 +86,7 @@ end @show N_vumps´ @test N_vumps´ ≈ N_vumps rtol = 1.0e-2 @test N_vumps´ ≈ N_ctm´ rtol = 1.0e-2 -end +end=# @testset "PEPO runthrough" begin function ising_pepo(beta; unitcell = (1, 1, 1)) diff --git a/test/cuda/ctmrg/contractions.jl b/test/cuda/ctmrg/contractions.jl new file mode 100644 index 000000000..817255458 --- /dev/null +++ b/test/cuda/ctmrg/contractions.jl @@ -0,0 +1,315 @@ +using Test +using Random +using PEPSKit +using TensorKit +using CUDA, Adapt + +using PEPSKit: eachcoordinate, _next_coordinate +using PEPSKit: EnlargedCorner, HalfInfiniteEnv, FullInfiniteEnv +using PEPSKit: half_infinite_environment, full_infinite_environment +using PEPSKit: simultaneous_projectors, contract_projectors +using PEPSKit: renormalize_northwest_corner, renormalize_northeast_corner, + renormalize_southeast_corner, renormalize_southwest_corner +using PEPSKit: random_start_vector + +# settings +Random.seed!(91283219348) +stype = ComplexF64 + +renormalize_corner_fns = ( + renormalize_northwest_corner, renormalize_northeast_corner, + renormalize_southeast_corner, renormalize_southwest_corner, +) + +function test_ctmrg_contractions( + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) + + @testset "CTMRG PEPS contractions" begin + peps = adapt(CuArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) + env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) + + n = InfiniteSquareNetwork(peps) + + @test storagetype(peps) <: CuArray + @test storagetype(env) <: CuArray + @test storagetype(n) <: CuArray + test_contractions(n, env) + end + + @testset "CTMRG PartitionFunction contractions" begin + pf = adapt(CuArray, InfinitePartitionFunction(randn, stype, Nspaces, Espaces)) + env = CTMRGEnv(randn, stype, pf, chis_north, chis_east, chis_south, chis_west) + n = InfiniteSquareNetwork(pf) + @test storagetype(pf) <: CuArray + @test storagetype(env) <: CuArray + @test storagetype(n) <: CuArray + + test_contractions(n, env) + end + + @testset "CTMRG PEPO contractions" begin + pepo = adapt(CuArray, InfinitePEPO(randn, stype, Pspaces, Pspaces, Pspaces)) + peps = adapt(CuArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) + n = InfiniteSquareNetwork(peps, pepo) + env = adapt(CuArray, CTMRGEnv(randn, stype, n, chis_north, chis_east, chis_south, chis_west)) + @test storagetype(peps) <: CuArray + @test storagetype(pepo) <: CuArray + @test storagetype(env) <: CuArray + @test storagetype(n) <: CuArray + + test_contractions(n, env) + end + + return nothing +end + +function test_contractions(n::InfiniteSquareNetwork, env::CTMRGEnv) + dirs_and_coordinates = eachcoordinate(n, 1:4) + + # initialize dense and sparse enlarged corners + sparse_enlarged_corners = map(dirs_and_coordinates) do co + return EnlargedCorner(n, env, co) + end + dense_enlarged_corners = map(TensorMap, sparse_enlarged_corners) + + # initialize sparse and dense half-inifite environments + sparse_halfinf_envs = map(dirs_and_coordinates) do co + co´ = _next_coordinate(co, size(env)[2:3]...) + return HalfInfiniteEnv( + sparse_enlarged_corners[co...], sparse_enlarged_corners[co´...] + ) + end + dense_halfinf_envs = map(TensorMap, sparse_halfinf_envs) + # also compute directly from dense enlarged corners, for consistency with current implementation + dense_halfinf_envs_bis = map(dirs_and_coordinates) do co + co´ = _next_coordinate(co, size(env)[2:3]...) + return half_infinite_environment( + dense_enlarged_corners[co...], dense_enlarged_corners[co´...] + ) + end + + # initialize sparse and dense full-inifite environments + sparse_fullinf_envs = map(dirs_and_coordinates) do co + rowsize, colsize = size(env)[2:3] + co2 = _next_coordinate(co, rowsize, colsize) + co3 = _next_coordinate(co2, rowsize, colsize) + co4 = _next_coordinate(co3, rowsize, colsize) + return FullInfiniteEnv( + sparse_enlarged_corners[co4...], + sparse_enlarged_corners[co...], + sparse_enlarged_corners[co2...], + sparse_enlarged_corners[co3...], + ) + end + dense_fullinf_envs = map(TensorMap, sparse_fullinf_envs) + # also compute directly from dense enlarged corners, for consistency with current implementation + dense_fullinf_envs_bis = map(dirs_and_coordinates) do co + rowsize, colsize = size(env)[2:3] + co2 = _next_coordinate(co, rowsize, colsize) + co3 = _next_coordinate(co2, rowsize, colsize) + co4 = _next_coordinate(co3, rowsize, colsize) + return full_infinite_environment( + dense_enlarged_corners[co4...], + dense_enlarged_corners[co...], + dense_enlarged_corners[co2...], + dense_enlarged_corners[co3...], + ) + end + + # SVD half and full infinite environments + (P_left_half, P_right_half), info_half = simultaneous_projectors( + dense_enlarged_corners, env, HalfInfiniteProjector() + ) + U_half, S_half, V_half = info_half.U, info_half.S, info_half.V + (P_left_full, P_right_full), info_full = simultaneous_projectors( + dense_enlarged_corners, env, FullInfiniteProjector() + ) + U_full, S_full, V_full = info_full.U, info_full.S, info_full.V + + # check projector computation for both types of environments, + # comparing dense and sparse implementations + foreach(dirs_and_coordinates) do co + dir, r, c = co + + co2 = _next_coordinate(co, size(env)[2:3]...) + co3 = _next_coordinate(co2, size(env)[2:3]...) + co4 = _next_coordinate(co3, size(env)[2:3]...) + + ## HalfInfiniteEnv + + shenv = sparse_halfinf_envs[dir, r, c] + dhenv = dense_halfinf_envs[dir, r, c] + dhenv_bis = dense_halfinf_envs_bis[dir, r, c] + @test dhenv ≈ dhenv_bis + + # application + xr = random_start_vector(shenv) + xl = randn(storagetype(shenv), codomain(shenv)) + @test shenv(xr, Val(false)) ≈ dhenv * xr + @test shenv(xl, Val(true)) ≈ dhenv' * xl + + # projector computation + P_left_sparse, P_right_sparse = contract_projectors( + U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], shenv + ) + P_left_dense, P_right_dense = contract_projectors( + U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], + dense_enlarged_corners[co...], dense_enlarged_corners[co2...], + ) + @test P_left_sparse ≈ P_left_dense + @test P_right_sparse ≈ P_right_dense + @test P_left_sparse ≈ P_left_half[dir, r, c] + @test P_right_sparse ≈ P_right_half[dir, r, c] + + + ## FullInfiniteEnv + + sfenv = sparse_fullinf_envs[dir, r, c] + dfenv = dense_fullinf_envs[dir, r, c] + dfenv_bis = dense_fullinf_envs_bis[dir, r, c] + @test dfenv ≈ dfenv_bis + + # application + xl = randn(storagetype(sfenv), codomain(sfenv)) + xr = random_start_vector(sfenv) + @test sfenv(xr, Val(false)) ≈ dfenv * xr + @test sfenv(xl, Val(true)) ≈ dfenv' * xl + + # projector computation + P_left_sparse, P_right_sparse = contract_projectors( + U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], sfenv + ) + P_left_dense, P_right_dense = contract_projectors( + U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], + half_infinite_environment( + dense_enlarged_corners[co4...], dense_enlarged_corners[co...] + ), + half_infinite_environment( + dense_enlarged_corners[co2...], dense_enlarged_corners[co3...] + ), + ) + @test P_left_sparse ≈ P_left_dense + @test P_right_sparse ≈ P_right_dense + @test P_left_sparse ≈ P_left_full[dir, r, c] + @test P_right_sparse ≈ P_right_full[dir, r, c] + end + + foreach(dirs_and_coordinates) do co + dir, r, c = co + + ## Corner renormalization + + C_sparse = renormalize_corner_fns[dir]( + (r, c), sparse_enlarged_corners, P_left_half, P_right_half + ) + C_dense = renormalize_corner_fns[dir]( + (r, c), dense_enlarged_corners, P_left_half, P_right_half + ) + @test C_sparse ≈ C_dense + end + + return nothing +end + +@testset "Random Cartesian spaces" begin + unitcell = (3, 3) + + Pspaces = ComplexSpace.(rand(2:3, unitcell...)) + Nspaces = ComplexSpace.(rand(2:4, unitcell...)) + Espaces = ComplexSpace.(rand(2:4, unitcell...)) + chis_north = ComplexSpace.(rand(5:10, unitcell...)) + chis_east = ComplexSpace.(rand(5:10, unitcell...)) + chis_south = ComplexSpace.(rand(5:10, unitcell...)) + chis_west = ComplexSpace.(rand(5:10, unitcell...)) + + test_ctmrg_contractions( + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) +end + +@testset "Specific U1 spaces" begin + unitcell = (2, 2) + + PA = U1Space(-1 => 1, 0 => 1) + PB = U1Space(0 => 1, 1 => 1) + Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) + Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) + + Pspaces = [PA PB; PB PA] + Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] + chis = [Venv Venv; Venv Venv] + + test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) + + # 4x4 unit cell with all 32 inequivalent bonds + # + # 10 4 7 32 + # | | | | + # 3--A--1--B--5--C--8--D--3 + # | | | | + # 2 6 9 11 + # | | | | + # 14--E-12--F-15--G-17--H-14 + # | | | | + # 13 16 18 19 + # | | | | + # 22--I-20--J-23--K-25--L-22 + # | | | | + # 21 24 26 27 + # | | | | + # 29--M-28--N-30--O-31--P-29 + # | | | | + # 10 4 7 32 + + phys_space = Vect[U1Irrep](1 => 1, -1 => 1) + corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) + vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) + @test length(Set(vspaces)) == 32 + + Espaces = [ + vspaces[1] vspaces[5] vspaces[8] vspaces[3] + vspaces[12] vspaces[15] vspaces[17] vspaces[14] + vspaces[20] vspaces[23] vspaces[25] vspaces[22] + vspaces[28] vspaces[30] vspaces[31] vspaces[29] + ] + + Nspaces = [ + vspaces[10] vspaces[4] vspaces[7] vspaces[32] + vspaces[2] vspaces[6] vspaces[9] vspaces[11] + vspaces[13] vspaces[16] vspaces[18] vspaces[19] + vspaces[21] vspaces[24] vspaces[26] vspaces[27] + ] + Pspaces = fill(phys_space, (4, 4)) + chis = fill(corner_space, (4, 4)) + # TODO broken? + #test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) +end + +@testset "Random fermionic spaces" begin + unitcell = (3, 3) + + S = Vect[FermionParity] + pdims = rand(1:2, unitcell..., 2) + vdims = rand(2:4, unitcell..., 2) + edims = rand(3:6, unitcell..., 2) + + function _construct_space(ds::Array{<:Int, 3}) + V = map(Iterators.product(axes(ds)[1:2]...)) do (r, c) + return S(0 => ds[r, c, 1], 1 => ds[r, c, 2]) + end + return V + end + + Pspaces = _construct_space(pdims) + Nspaces = _construct_space(vdims) + Espaces = _construct_space(vdims) + chis_north = _construct_space(edims) + chis_east = _construct_space(edims) + chis_south = _construct_space(edims) + chis_west = _construct_space(edims) + + test_ctmrg_contractions( + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) +end diff --git a/test/cuda/ctmrg/initialization.jl b/test/cuda/ctmrg/initialization.jl new file mode 100644 index 000000000..375d982c6 --- /dev/null +++ b/test/cuda/ctmrg/initialization.jl @@ -0,0 +1,94 @@ +using Test +using TensorKit +using PEPSKit +using Random +using Adapt, CUDA +using MPSKitModels: classical_ising +using PEPSKit: ProductStateEnv + +sd = 12345 + +# toggle symmetry, but same issue for both +symmetries = [Z2Irrep, Trivial] +make_space(::Type{Z2Irrep}, d::Int) = Z2Space(0 => d / 2, 1 => d / 2) +make_space(::Type{Trivial}, d::Int) = ComplexSpace(d) + +d = 2 +D = 4 +χ = 20 +tol = 1.0e-4 +maxiter = 1000 +verbosity = 2 +trunc = truncrank(χ) +boundary_alg = (; alg = :SimultaneousCTMRG, tol, verbosity, trunc, maxiter) + +@testset "CTMRG environment initialization for critical ising with $S symmetry (#255)" for S in symmetries + # initialize + Random.seed!(sd) + T = classical_ising(S) + O = T[1] + n = adapt(CuArray, InfinitePartitionFunction([O O; O O])) + Venv = make_space(S, χ) + P = space(O, 2) + + # random, doesn't converge + env0_rand = initialize_ctmrg_environment(n, RandomInitialization(), Venv) + env_rand, info = leading_boundary(env0_rand, n; boundary_alg...) + @test_broken info.convergence_error ≤ tol + + # embedded random product state, converges + env0_prod = initialize_ctmrg_environment(n, ProductStateInitialization()) + env_prod, info = leading_boundary(env0_prod, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # grown product state, converges + env0_appl = initialize_ctmrg_environment(n, ApplicationInitialization()) + env_appl, info = leading_boundary(env0_appl, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # specific custom starting product state + p_data = ComplexF64[1; 0;;] + p = adapt(CuArray, Tensor(p_data, P)) + prod_env0 = ProductStateEnv(reshape([p, p, flip(p, 1), flip(p, 1)], 4, 1, 1)) + env0_custom = initialize_ctmrg_environment(n, ApplicationInitialization(), prod_env0) + # or just CTMRGEnv(prod_env0) + env_custom, info = leading_boundary(env0_custom, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # PEPS-specific identity initialization; should throw when used on partition functions + @test_throws ArgumentError env0_prod_id = initialize_ctmrg_environment(n, IdentityInitialization()) +end + +@testset "CTMRG environment initialization for PEPS with $S symmetry" for S in symmetries + # initialize + Random.seed!(sd) + P = make_space(S, d) + Vpeps = make_space(S, D) + Venv = make_space(S, χ) + peps = adapt(CuArray, InfinitePEPS(P, Vpeps; unitcell = (2, 2))) + n = InfiniteSquareNetwork(peps) + + # random, converges + env0_rand = initialize_ctmrg_environment(n, RandomInitialization(), Venv) + @test storagetype(env0_rand) <: CuArray + env_rand, info = leading_boundary(env0_rand, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # embedded random product state, converges + env0_prod = initialize_ctmrg_environment(n, ProductStateInitialization()) + @test storagetype(env0_prod) <: CuArray + env_prod, info = leading_boundary(env0_prod, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # embedded product state as identity from ket to bra, converges + env0_prod_id = initialize_ctmrg_environment(n, IdentityInitialization()) + @test storagetype(env0_prod_id) <: CuArray + env_prod, info = leading_boundary(env0_prod_id, n; boundary_alg...) + @test info.convergence_error ≤ tol + + # grown product state, converges + env0_appl = initialize_ctmrg_environment(n, ApplicationInitialization()) + @test storagetype(env0_appl) <: CuArray + env_appl, info = leading_boundary(env0_appl, n; boundary_alg...) + @test info.convergence_error ≤ tol +end From d7b6cb0f162bb908a48dedee2207b79fbfc88536 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 4 Aug 2026 12:01:37 +0200 Subject: [PATCH 09/69] Partially support cluster projectors tests --- test/amd/timeevol/cluster_projectors.jl | 258 +++++++++++++++++++++++ test/cuda/boundarymps/vumps.jl | 4 +- test/cuda/timeevol/cluster_projectors.jl | 258 +++++++++++++++++++++++ 3 files changed, 518 insertions(+), 2 deletions(-) create mode 100644 test/amd/timeevol/cluster_projectors.jl create mode 100644 test/cuda/timeevol/cluster_projectors.jl diff --git a/test/amd/timeevol/cluster_projectors.jl b/test/amd/timeevol/cluster_projectors.jl new file mode 100644 index 000000000..5ead59475 --- /dev/null +++ b/test/amd/timeevol/cluster_projectors.jl @@ -0,0 +1,258 @@ +using Test +using TensorKit +using PEPSKit +using LinearAlgebra +using Random +import MPSKitModels: hubbard_space +using PEPSKit: sdiag_pow, _cluster_truncate!, _flip_virtuals! +using MPSKit: GenericMPSTensor, MPSBondTensor +using AMDGPU, Adapt + +# Utility setup +# ------------- +function _contract_left( + M::GenericMPSTensor{S, 4}, sl::DiagonalTensorMap{T, S} + ) where {T <: Number, S <: ElementarySpace} + @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) + M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) + @tensor sl1[e1; e0] := conj(M[w1; p n s e1]) * sl[w1; w0] * M0[w0; p n s e0] + return sl1 +end +function _contract_left( + M::GenericMPSTensor{S, 4}, ::Nothing + ) where {S <: ElementarySpace} + @assert !isdual(domain(M, 1)) + M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) + @tensor sl1[e1; e0] := conj(M[w; p n s e1]) * M0[w; p n s e0] + return sl1 +end + +function _contract_right( + M::GenericMPSTensor{S, 4}, sr::DiagonalTensorMap{T, S} + ) where {T <: Number, S <: ElementarySpace} + @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) + M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) + @tensor sr1[w0; w1] := M0[w0; p n s e0] * sr[e0; e1] * conj(M[w1; p n s e1]) + return sr1 +end +function _contract_right( + M::GenericMPSTensor{S, 4}, ::Nothing + ) where {S <: ElementarySpace} + @assert !isdual(codomain(M, 1)) + M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) + @tensor sr1[w0; w1] := M0[w0; p n s e] * conj(M[w1; p n s e]) + return sr1 +end + +""" +Verify the generalized left/right orthogonal condition +""" +function verify_cluster_orth( + Ms::Vector{T1}, wts::Vector{T2} + ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 4}, T2 <: DiagonalTensorMap} + N = length(Ms) + @assert length(wts) == N - 1 + lorths = fill(false, N - 1) + rorths = fill(false, N - 1) + # left orthogonal + for i in 1:(N - 1) + M, sl0 = Ms[i], wts[i] + sl1 = _contract_left(M, i == 1 ? nothing : wts[i - 1]) + lorths[i] = (normalize(TensorMap(sl0)) ≈ normalize(sl1)) # sl0 is DiagonalTensorMap while sl1 is not + end + # right orthogonal + for i in 2:N + M, sr0 = Ms[i], wts[i - 1] + sr1 = _contract_right(M, i == N ? nothing : wts[i]) + rorths[i - 1] = (normalize(TensorMap(sr0)) ≈ normalize(sr1)) + end + return lorths, rorths +end + +function inner_prod_cluster( + Ms1::Vector{T1}, Ms2::Vector{T2} + ) where { + T1 <: GenericMPSTensor{<:ElementarySpace, 4}, + T2 <: GenericMPSTensor{<:ElementarySpace, 4}, + } + N = length(Ms1) + @assert length(Ms2) == N + # physical spaces are assumed to be non-dual + @assert all(!isdual(space(t, 2)) for t in Ms1) + @assert all(!isdual(space(t, 2)) for t in Ms2) + # not the most efficient implementation + M1, M2 = Ms1[1], deepcopy(Ms2[1]) + for ax in 1:4 + isdual(space(M2, ax)) && twist!(M2, ax) + end + @tensor res[-1 -2] := conj(M1[1 2 3 4; -1]) * M2[1 2 3 4; -2] + for i in 2:(N - 1) + M1, M2 = Ms1[i], deepcopy(Ms2[i]) + for ax in 2:4 + isdual(space(M2, ax)) && twist!(M2, ax) + end + @tensor M[-1 -2; -3 -4] := conj(M1[-1 1 2 3; -3]) * M2[-2 1 2 3; -4] + @tensor res[-1 -2] := res[1 2] * M[1 2; -1 -2] + end + M1, M2 = Ms1[N], deepcopy(Ms2[N]) + for ax in 2:5 + isdual(space(M2, ax)) && twist!(M2, ax) + end + @tensor M[-1 -2] := conj(M1[-1 1 2 3; 4]) * M2[-2 1 2 3; 4] + return @tensor res[1 2] * M[1 2] +end + +function fidelity_cluster( + Ms1::Vector{T1}, Ms2::Vector{T2} + ) where { + T1 <: GenericMPSTensor{<:ElementarySpace, 4}, + T2 <: GenericMPSTensor{<:ElementarySpace, 4}, + } + return abs2(inner_prod_cluster(Ms1, Ms2)) / + (inner_prod_cluster(Ms1, Ms1) * inner_prod_cluster(Ms2, Ms2)) +end + +function mpo_to_gate3(gs::Vector{T}) where {T <: AbstractTensorMap} + #= + -4 -5 -6 + ↓ ↓ ↓ + g1 ←- 1 ←- g2 ←- 2 ←- g3 + ↓ ↓ ↓ + -1 -2 -3 + =# + @assert length(gs) == 3 + @tensor gate[-1 -2 -3; -4 -5 -6] := gs[1][-1 -4 1] * gs[2][1 -2 -5 2] * gs[3][2 -3 -6] + return gate +end + +Vspaces = [ + ( + U1Space(0 => 1, 1 => 1, -1 => 1), + U1Space(0 => 1, 1 => 2, -1 => 1)', + U1Space(0 => 4, 1 => 5, -1 => 6)', + ), + ( + Vect[FermionParity](0 => 1, 1 => 1), + Vect[FermionParity](0 => 2, 1 => 2), + Vect[FermionParity](0 => 6, 1 => 6)', + ), +] + +@testset "Cluster bond truncation with projectors" begin + Random.seed!(0) + N, n = 5, 2 + for (Vphy, Vns, V) in Vspaces + Vvirs = fill(Vns, N + 1) + Vvirs[n + 1] = V + Ms1 = map(1:N) do i + Vw, Ve = Vvirs[i], Vvirs[i + 1] + return adapt(ROCArray, rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve)) + end + normalize!.(Ms1, Inf) + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] + # no truncation + Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) + truncs = [truncrank(dim(space(M, 1))) for M in Iterators.drop(Ms2, 1)] + wts2, ϵs, = _cluster_truncate!(Ms2, truncs) + @test all((ϵ == 0) for ϵ in ϵs) + normalize!.(Ms2, Inf) + @test fidelity_cluster(Ms1, Ms2) ≈ 1.0 + lorths, rorths = verify_cluster_orth(Ms2, wts2) + @test all(lorths) && all(rorths) + # truncation on one bond + Ms3 = _flip_virtuals!(deepcopy(Ms1), flips) + tspace = isdual(Vns) ? flip(Vns) : Vns + wts3, ϵs, = _cluster_truncate!(Ms3, fill(truncspace(tspace), N - 1)) + @test all((i == n) || (ϵ == 0) for (i, ϵ) in enumerate(ϵs)) + normalize!.(Ms3, Inf) + ϵ = ϵs[n] + wt2, wt3 = wts2[n], wts3[n] + _flip_virtuals!(Ms3, flips) + fid3, fid3_ = fidelity_cluster(Ms1, Ms3), fidelity_cluster(Ms2, Ms3) + @info "Fidelity of truncated cluster = $(fid3)" + @test fid3 ≈ fid3_ + @test fid3 ≈ (norm(wt3) / norm(wt2))^2 + @test fid3 ≈ 1.0 - (ϵ / norm(wt2))^2 + end +end +#= # TODO NEEDS REPARTITION FIX FOR DIAGONALTENSORMAP +@testset "Identity gate on 3-site cluster" begin + N, n = 3, 1 + for (Vphy, Vns, V) in Vspaces + Vvirs = fill(Vns, N + 1) + Vvirs[n + 1] = V + Ms1 = map(1:N) do i + Vw, Ve = Vvirs[i], Vvirs[i + 1] + return adapt(ROCArray, normalize(rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve), Inf)) + end + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] + unit = id(Vphy) + gate = reduce(⊗, fill(unit, 3)) + gs = PEPSKit.gate_to_mpo(gate) + @test mpo_to_gate3(gs) ≈ gate + Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) + PEPSKit._apply_gatempo!(Ms2, gs) + fid = fidelity_cluster(Ms1, Ms2) + @test fid ≈ 1.0 + end + for (Vphy, Vns, V) in Vspaces + Vvirs = fill(Vns, N + 1) + Vvirs[n + 1] = V + Ms1 = map(1:N) do i + Vw, Ve = Vvirs[i], Vvirs[i + 1] + return adapt(ROCArray, normalize(rand(Vw ⊗ Vphy ⊗ Vphy' ⊗ Vns' ⊗ Vns ← Ve), Inf)) + end + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] + unit = adapt(ROCArray, id(Vphy)) + gate = reduce(⊗, fill(unit, 3)) + gs = PEPSKit.gate_to_mpo(gate) + @test mpo_to_gate3(gs) ≈ gate + for gate_ax in 1:2 + Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) + PEPSKit._apply_gatempo!(Ms2, gs; gate_ax) + fid = fidelity_cluster( + [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms1], + [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms2] + ) + @test fid ≈ 1.0 + end + end +end + +@testset "Hubbard model SU (MPO gate)" begin + Nr, Nc = 2, 2 + ctmrg_tol = 1.0e-9 + Random.seed!(1459) + # with U(1) spin rotation symmetry + Pspace = hubbard_space(Trivial, U1Irrep) + Vspace = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) + Espace = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 8, (1, 1 // 2) => 4, (1, -1 // 2) => 4) + truncs_env = collect(truncerror(; atol = 1.0e-12) & truncrank(χ) for χ in [8, 16]) + peps0 = adapt(ROCArray, InfinitePEPS(rand, Float64, Pspace, Vspace, Vspace'; unitcell = (Nr, Nc))) + # make initial state bipartite + for r in 1:2 + peps0[r + 1, 2] = copy(peps0[r, 1]) + end + wts0 = SUWeight(peps0) + ham = adapt(ROCArray, hubbard_model(Float64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t = 1.0, U = 6.0, mu = 3.0)) + # applying 2-site gates decomposed to MPO or not, + # resulting energy should be almost the same + e_sites = map((true, false)) do force_mpo + peps, wts = deepcopy(peps0), deepcopy(wts0) + trunc = truncerror(; atol = 1.0e-10) & truncrank(4) + alg = SimpleUpdate(; trunc, force_mpo) + peps, wts, = time_evolve( + peps, ham, 0.01, 10000, alg, wts; tol = 1.0e-6, check_interval = 1000 + ) + normalize!.(peps.A, Inf) + env = CTMRGEnv(wts) + for trunc in truncs_env + env, = leading_boundary(env, peps; alg = :SequentialCTMRG, tol = ctmrg_tol, trunc) + end + e_site = cost_function(peps, env, ham) / (Nr * Nc) + @info "Energy (force_mpo = $(force_mpo)): $e_site" + return e_site + end + @test e_sites[1] ≈ e_sites[2] atol = 1.0e-4 +end +=# diff --git a/test/cuda/boundarymps/vumps.jl b/test/cuda/boundarymps/vumps.jl index fce047828..21e147da9 100644 --- a/test/cuda/boundarymps/vumps.jl +++ b/test/cuda/boundarymps/vumps.jl @@ -38,10 +38,10 @@ end Vpeps = ComplexSpace(2) psi = adapt(CuArray, InfinitePEPS(Vpeps, Vpeps; unitcell = (2, 2))) T = adapt(CuArray, PEPSKit.MultilineTransferPEPS(psi, 1)) - @test storagetype(T) <: ROCArray + @test storagetype(T) <: CuArray # foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) # TODO: MPSKit.physicalspace(::MultilineMPO) isn't implemented... mps = adapt(CuArray, initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2))) - @test storagetype(mps) <: ROCArray + @test storagetype(mps) <: CuArray mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(prod(expectation_value(mps, T))) diff --git a/test/cuda/timeevol/cluster_projectors.jl b/test/cuda/timeevol/cluster_projectors.jl new file mode 100644 index 000000000..3aba45dfe --- /dev/null +++ b/test/cuda/timeevol/cluster_projectors.jl @@ -0,0 +1,258 @@ +using Test +using TensorKit +using PEPSKit +using LinearAlgebra +using Random +import MPSKitModels: hubbard_space +using PEPSKit: sdiag_pow, _cluster_truncate!, _flip_virtuals! +using MPSKit: GenericMPSTensor, MPSBondTensor +using CUDA, Adapt + +# Utility setup +# ------------- +function _contract_left( + M::GenericMPSTensor{S, 4}, sl::DiagonalTensorMap{T, S} + ) where {T <: Number, S <: ElementarySpace} + @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) + M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) + @tensor sl1[e1; e0] := conj(M[w1; p n s e1]) * sl[w1; w0] * M0[w0; p n s e0] + return sl1 +end +function _contract_left( + M::GenericMPSTensor{S, 4}, ::Nothing + ) where {S <: ElementarySpace} + @assert !isdual(domain(M, 1)) + M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) + @tensor sl1[e1; e0] := conj(M[w; p n s e1]) * M0[w; p n s e0] + return sl1 +end + +function _contract_right( + M::GenericMPSTensor{S, 4}, sr::DiagonalTensorMap{T, S} + ) where {T <: Number, S <: ElementarySpace} + @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) + M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) + @tensor sr1[w0; w1] := M0[w0; p n s e0] * sr[e0; e1] * conj(M[w1; p n s e1]) + return sr1 +end +function _contract_right( + M::GenericMPSTensor{S, 4}, ::Nothing + ) where {S <: ElementarySpace} + @assert !isdual(codomain(M, 1)) + M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) + @tensor sr1[w0; w1] := M0[w0; p n s e] * conj(M[w1; p n s e]) + return sr1 +end + +""" +Verify the generalized left/right orthogonal condition +""" +function verify_cluster_orth( + Ms::Vector{T1}, wts::Vector{T2} + ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 4}, T2 <: DiagonalTensorMap} + N = length(Ms) + @assert length(wts) == N - 1 + lorths = fill(false, N - 1) + rorths = fill(false, N - 1) + # left orthogonal + for i in 1:(N - 1) + M, sl0 = Ms[i], wts[i] + sl1 = _contract_left(M, i == 1 ? nothing : wts[i - 1]) + lorths[i] = (normalize(TensorMap(sl0)) ≈ normalize(sl1)) # sl0 is DiagonalTensorMap while sl1 is not + end + # right orthogonal + for i in 2:N + M, sr0 = Ms[i], wts[i - 1] + sr1 = _contract_right(M, i == N ? nothing : wts[i]) + rorths[i - 1] = (normalize(TensorMap(sr0)) ≈ normalize(sr1)) + end + return lorths, rorths +end + +function inner_prod_cluster( + Ms1::Vector{T1}, Ms2::Vector{T2} + ) where { + T1 <: GenericMPSTensor{<:ElementarySpace, 4}, + T2 <: GenericMPSTensor{<:ElementarySpace, 4}, + } + N = length(Ms1) + @assert length(Ms2) == N + # physical spaces are assumed to be non-dual + @assert all(!isdual(space(t, 2)) for t in Ms1) + @assert all(!isdual(space(t, 2)) for t in Ms2) + # not the most efficient implementation + M1, M2 = Ms1[1], deepcopy(Ms2[1]) + for ax in 1:4 + isdual(space(M2, ax)) && twist!(M2, ax) + end + @tensor res[-1 -2] := conj(M1[1 2 3 4; -1]) * M2[1 2 3 4; -2] + for i in 2:(N - 1) + M1, M2 = Ms1[i], deepcopy(Ms2[i]) + for ax in 2:4 + isdual(space(M2, ax)) && twist!(M2, ax) + end + @tensor M[-1 -2; -3 -4] := conj(M1[-1 1 2 3; -3]) * M2[-2 1 2 3; -4] + @tensor res[-1 -2] := res[1 2] * M[1 2; -1 -2] + end + M1, M2 = Ms1[N], deepcopy(Ms2[N]) + for ax in 2:5 + isdual(space(M2, ax)) && twist!(M2, ax) + end + @tensor M[-1 -2] := conj(M1[-1 1 2 3; 4]) * M2[-2 1 2 3; 4] + return @tensor res[1 2] * M[1 2] +end + +function fidelity_cluster( + Ms1::Vector{T1}, Ms2::Vector{T2} + ) where { + T1 <: GenericMPSTensor{<:ElementarySpace, 4}, + T2 <: GenericMPSTensor{<:ElementarySpace, 4}, + } + return abs2(inner_prod_cluster(Ms1, Ms2)) / + (inner_prod_cluster(Ms1, Ms1) * inner_prod_cluster(Ms2, Ms2)) +end + +function mpo_to_gate3(gs::Vector{T}) where {T <: AbstractTensorMap} + #= + -4 -5 -6 + ↓ ↓ ↓ + g1 ←- 1 ←- g2 ←- 2 ←- g3 + ↓ ↓ ↓ + -1 -2 -3 + =# + @assert length(gs) == 3 + @tensor gate[-1 -2 -3; -4 -5 -6] := gs[1][-1 -4 1] * gs[2][1 -2 -5 2] * gs[3][2 -3 -6] + return gate +end + +Vspaces = [ + ( + U1Space(0 => 1, 1 => 1, -1 => 1), + U1Space(0 => 1, 1 => 2, -1 => 1)', + U1Space(0 => 4, 1 => 5, -1 => 6)', + ), + ( + Vect[FermionParity](0 => 1, 1 => 1), + Vect[FermionParity](0 => 2, 1 => 2), + Vect[FermionParity](0 => 6, 1 => 6)', + ), +] + +@testset "Cluster bond truncation with projectors" begin + Random.seed!(0) + N, n = 5, 2 + for (Vphy, Vns, V) in Vspaces + Vvirs = fill(Vns, N + 1) + Vvirs[n + 1] = V + Ms1 = map(1:N) do i + Vw, Ve = Vvirs[i], Vvirs[i + 1] + return adapt(CuArray, rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve)) + end + normalize!.(Ms1, Inf) + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] + # no truncation + Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) + truncs = [truncrank(dim(space(M, 1))) for M in Iterators.drop(Ms2, 1)] + wts2, ϵs, = _cluster_truncate!(Ms2, truncs) + @test all((ϵ == 0) for ϵ in ϵs) + normalize!.(Ms2, Inf) + @test fidelity_cluster(Ms1, Ms2) ≈ 1.0 + lorths, rorths = verify_cluster_orth(Ms2, wts2) + @test all(lorths) && all(rorths) + # truncation on one bond + Ms3 = _flip_virtuals!(deepcopy(Ms1), flips) + tspace = isdual(Vns) ? flip(Vns) : Vns + wts3, ϵs, = _cluster_truncate!(Ms3, fill(truncspace(tspace), N - 1)) + @test all((i == n) || (ϵ == 0) for (i, ϵ) in enumerate(ϵs)) + normalize!.(Ms3, Inf) + ϵ = ϵs[n] + wt2, wt3 = wts2[n], wts3[n] + _flip_virtuals!(Ms3, flips) + fid3, fid3_ = fidelity_cluster(Ms1, Ms3), fidelity_cluster(Ms2, Ms3) + @info "Fidelity of truncated cluster = $(fid3)" + @test fid3 ≈ fid3_ + @test fid3 ≈ (norm(wt3) / norm(wt2))^2 + @test fid3 ≈ 1.0 - (ϵ / norm(wt2))^2 + end +end +#= # TODO NEEDS REPARTITION FIX FOR DIAGONALTENSORMAP +@testset "Identity gate on 3-site cluster" begin + N, n = 3, 1 + for (Vphy, Vns, V) in Vspaces + Vvirs = fill(Vns, N + 1) + Vvirs[n + 1] = V + Ms1 = map(1:N) do i + Vw, Ve = Vvirs[i], Vvirs[i + 1] + return adapt(CuArray, normalize(rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve), Inf)) + end + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] + unit = id(Vphy) + gate = reduce(⊗, fill(unit, 3)) + gs = PEPSKit.gate_to_mpo(gate) + @test mpo_to_gate3(gs) ≈ gate + Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) + PEPSKit._apply_gatempo!(Ms2, gs) + fid = fidelity_cluster(Ms1, Ms2) + @test fid ≈ 1.0 + end + for (Vphy, Vns, V) in Vspaces + Vvirs = fill(Vns, N + 1) + Vvirs[n + 1] = V + Ms1 = map(1:N) do i + Vw, Ve = Vvirs[i], Vvirs[i + 1] + return adapt(CuArray, normalize(rand(Vw ⊗ Vphy ⊗ Vphy' ⊗ Vns' ⊗ Vns ← Ve), Inf)) + end + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] + unit = adapt(CuArray, id(Vphy)) + gate = reduce(⊗, fill(unit, 3)) + gs = PEPSKit.gate_to_mpo(gate) + @test mpo_to_gate3(gs) ≈ gate + for gate_ax in 1:2 + Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) + PEPSKit._apply_gatempo!(Ms2, gs; gate_ax) + fid = fidelity_cluster( + [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms1], + [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms2] + ) + @test fid ≈ 1.0 + end + end +end + +@testset "Hubbard model SU (MPO gate)" begin + Nr, Nc = 2, 2 + ctmrg_tol = 1.0e-9 + Random.seed!(1459) + # with U(1) spin rotation symmetry + Pspace = hubbard_space(Trivial, U1Irrep) + Vspace = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) + Espace = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 8, (1, 1 // 2) => 4, (1, -1 // 2) => 4) + truncs_env = collect(truncerror(; atol = 1.0e-12) & truncrank(χ) for χ in [8, 16]) + peps0 = adapt(CuArray, InfinitePEPS(rand, Float64, Pspace, Vspace, Vspace'; unitcell = (Nr, Nc))) + # make initial state bipartite + for r in 1:2 + peps0[r + 1, 2] = copy(peps0[r, 1]) + end + wts0 = SUWeight(peps0) + ham = adapt(CuArray, hubbard_model(Float64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t = 1.0, U = 6.0, mu = 3.0)) + # applying 2-site gates decomposed to MPO or not, + # resulting energy should be almost the same + e_sites = map((true, false)) do force_mpo + peps, wts = deepcopy(peps0), deepcopy(wts0) + trunc = truncerror(; atol = 1.0e-10) & truncrank(4) + alg = SimpleUpdate(; trunc, force_mpo) + peps, wts, = time_evolve( + peps, ham, 0.01, 10000, alg, wts; tol = 1.0e-6, check_interval = 1000 + ) + normalize!.(peps.A, Inf) + env = CTMRGEnv(wts) + for trunc in truncs_env + env, = leading_boundary(env, peps; alg = :SequentialCTMRG, tol = ctmrg_tol, trunc) + end + e_site = cost_function(peps, env, ham) / (Nr * Nc) + @info "Energy (force_mpo = $(force_mpo)): $e_site" + return e_site + end + @test e_sites[1] ≈ e_sites[2] atol = 1.0e-4 +end +=# From 0fea2170d1d1742cda8d80952593b6ee71bf1e53 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 4 Aug 2026 22:04:34 +0200 Subject: [PATCH 10/69] Working tf_ising_finiteT --- test/amd/timeevol/j1j2_finiteT.jl | 62 ++++++++++++++++++ test/amd/timeevol/tf_ising_finiteT.jl | 88 ++++++++++++++++++++++++++ test/cuda/timeevol/j1j2_finiteT.jl | 62 ++++++++++++++++++ test/cuda/timeevol/tf_ising_finiteT.jl | 88 ++++++++++++++++++++++++++ 4 files changed, 300 insertions(+) create mode 100644 test/amd/timeevol/j1j2_finiteT.jl create mode 100644 test/amd/timeevol/tf_ising_finiteT.jl create mode 100644 test/cuda/timeevol/j1j2_finiteT.jl create mode 100644 test/cuda/timeevol/tf_ising_finiteT.jl diff --git a/test/amd/timeevol/j1j2_finiteT.jl b/test/amd/timeevol/j1j2_finiteT.jl new file mode 100644 index 000000000..fcef728c0 --- /dev/null +++ b/test/amd/timeevol/j1j2_finiteT.jl @@ -0,0 +1,62 @@ +using Test +using LinearAlgebra +using TensorKit +import MPSKitModels: σˣ, σᶻ +using PEPSKit +using AMDGPU, Adapt + +# Benchmark energy from high-temperature expansion +# at β = 0.3, 0.6 +# Physical Review B 86, 045139 (2012) Fig. 15-16 +bm = [-0.1235, -0.213] + +function converge_env(state, χ::Int) + env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) + trunc1 = truncrank(χ) & truncerror(; atol = 1.0e-12) + env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) + return env +end + +Nr, Nc = 2, 2 +ham = adapt(ROCArray, j1_j2_model( + Float64, SU2Irrep, InfiniteSquare(Nr, Nc); + J1 = 1.0, J2 = 0.5, sublattice = false + )) +@test storagetype(ham) <: ROCArray +pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) +@test storagetype(pepo0) <: ROCArray +wts0 = SUWeight(pepo0) +# 7 = 1 (spin-0) + 2 x 3 (spin-1) +trunc_pepo = truncrank(7) & truncerror(; atol = 1.0e-12) +check_interval = 100 +dt, nstep = 1.0e-3, 600 + +# PEPO approach +alg = SimpleUpdate(; trunc = trunc_pepo, purified = false) +evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) +pepo, wts, info = time_evolve(evolver; check_interval) +env = converge_env(InfinitePartitionFunction(pepo), 16) +energy = expectation_value(pepo, ham, env) / (Nr * Nc) +@info "β = $(dt * nstep): tr(ρH) = $(energy)" +@test dt * nstep ≈ info.t +@test energy ≈ bm[2] atol = 5.0e-3 + +# PEPS (purified PEPO) approach +alg = SimpleUpdate(; trunc = trunc_pepo, purified = true) +evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) +pepo, wts, info = time_evolve(evolver; check_interval) +env = converge_env(InfinitePartitionFunction(pepo), 16) +energy = expectation_value(pepo, ham, env) / (Nr * Nc) +@info "β = $(dt * nstep) / 2: tr(ρH) = $(energy)" +@test energy ≈ bm[1] atol = 5.0e-3 + +# test BP gauge fixing for purified iPEPO +bp_alg = BeliefPropagation(; maxiter = 100, tol = 1.0e-9) +bp_env, = leading_boundary(BPEnv(ones, Float64, pepo), pepo, bp_alg) +pepo, = gauge_fix(pepo, BPGauge(), bp_env) + +env = converge_env(InfinitePEPS(pepo), 16) +energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) +@info "β = $(dt * nstep): ⟨ρ|H|ρ⟩ = $(energy)" +@test dt * nstep ≈ info.t +@test energy ≈ bm[2] atol = 5.0e-3 diff --git a/test/amd/timeevol/tf_ising_finiteT.jl b/test/amd/timeevol/tf_ising_finiteT.jl new file mode 100644 index 000000000..046c69816 --- /dev/null +++ b/test/amd/timeevol/tf_ising_finiteT.jl @@ -0,0 +1,88 @@ +using Test +using LinearAlgebra +using TensorKit +import MPSKitModels: σˣ, σᶻ +using PEPSKit, AMDGPU, Adapt + +# Benchmark data of [σx, σz] from HOTRG +# Physical Review B 86, 045139 (2012) Fig. 15-16 +bm_β = [0.5632, 0.0] +bm_2β = [0.5297, 0.8265] + +function converge_env(state, χ::Int) + trunc1 = truncrank(4) & truncerror(; atol = 1.0e-12) + env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) + env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) + trunc2 = truncrank(χ) & truncerror(; atol = 1.0e-12) + env, = leading_boundary(env, state; alg = :SequentialCTMRG, trunc = trunc2, tol = 1.0e-10) + return env +end + +function measure_mag(pepo::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) + r, c = 1, 1 + lattice = physicalspace(pepo) + Mx = adapt(ROCArray, LocalOperator(lattice, ((r, c),) => σˣ(Float64, Trivial))) + Mz = adapt(ROCArray, LocalOperator(lattice, ((r, c),) => σᶻ(Float64, Trivial))) + if purified + magx = expectation_value(pepo, Mx, pepo, env) + magz = expectation_value(pepo, Mz, pepo, env) + else + magx = expectation_value(pepo, Mx, env) + magz = expectation_value(pepo, Mz, env) + end + return [magx, magz] +end + +Nr, Nc = 2, 2 +ham = adapt(ROCArray, transverse_field_ising(Float64, Trivial, InfiniteSquare(Nr, Nc); J = 1.0, g = 2.0)) +pepo0 = adapt(ROCArray, PEPSKit.infinite_temperature_density_matrix(ham)) +@test TensorKit.storagetype(ham) <: ROCVector +@test TensorKit.storagetype(pepo0) <: ROCVector +wts0 = SUWeight(pepo0) + +trunc_pepo = truncrank(8) & truncerror(; atol = 1.0e-12) + +dt, nstep = 1.0e-3, 400 +β = dt * nstep + +# when g = 2, β = 0.4 and 2β = 0.8 belong to two phases (without and with nonzero σᶻ) +@testset "Finite-T SU (force_mpo = $(force_mpo))" for force_mpo in (false, true) + # use second order Trotter decomposition + symmetrize_gates = true + bipartite = true + + # PEPO approach: results at β, or T = 2.5 + alg = SimpleUpdate(; trunc = trunc_pepo, purified = false, bipartite, force_mpo) + pepo, wts, info = time_evolve(pepo0, ham, dt, nstep, alg, wts0; symmetrize_gates) + @test storagetype(pepo) <: ROCArray + + ## BP gauge fixing + bp_alg = BeliefPropagation(; maxiter = 100, tol = 1.0e-9, bipartite) + bp_env₀ = BPEnv(ones, Float64, pepo) + @test storagetype(bp_env₀) <: ROCArray + bp_env, = leading_boundary(bp_env₀, pepo, bp_alg) + pepo, = gauge_fix(pepo, BPGauge(), bp_env) + + env = converge_env(InfinitePartitionFunction(pepo), 16) + result_β = measure_mag(pepo, env) + @info "tr(σ(x,z)ρ) at T = $(1 / β): $(result_β)." + @test β ≈ info.t + @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) + + # use `compress` to reach 2β, or T = 1.25 + pepo2, = compress((pepo, pepo), LocalTruncation(trunc_pepo)) + normalize!.(pepo2.A) + env2 = converge_env(InfinitePartitionFunction(pepo2), 16) + result_2β = measure_mag(pepo2, env2) + @info "tr(σ(x,z)ρ) at T = $(1 / (2β)): $(result_2β)." + @test isapprox(abs.(result_2β), bm_2β, rtol = 5.0e-3) + + # Purification approach: results at 2β, or T = 1.25 + alg = SimpleUpdate(; trunc = trunc_pepo, purified = true, bipartite, force_mpo) + pepo, wts, info = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0; symmetrize_gates) + env = converge_env(InfinitePEPS(pepo), 8) + result_2β′ = measure_mag(pepo, env; purified = true) + @info "⟨ρ|σ(x,z)|ρ⟩ at T = $(1 / (2β)): $(result_2β′)." + @test 2 * β ≈ info.t + @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) +end diff --git a/test/cuda/timeevol/j1j2_finiteT.jl b/test/cuda/timeevol/j1j2_finiteT.jl new file mode 100644 index 000000000..95bca9024 --- /dev/null +++ b/test/cuda/timeevol/j1j2_finiteT.jl @@ -0,0 +1,62 @@ +using Test +using LinearAlgebra +using TensorKit +import MPSKitModels: σˣ, σᶻ +using PEPSKit +using CUDA, Adapt + +# Benchmark energy from high-temperature expansion +# at β = 0.3, 0.6 +# Physical Review B 86, 045139 (2012) Fig. 15-16 +bm = [-0.1235, -0.213] + +function converge_env(state, χ::Int) + env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) + trunc1 = truncrank(χ) & truncerror(; atol = 1.0e-12) + env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) + return env +end + +Nr, Nc = 2, 2 +ham = adapt(CuArray, j1_j2_model( + Float64, SU2Irrep, InfiniteSquare(Nr, Nc); + J1 = 1.0, J2 = 0.5, sublattice = false + )) +@test storagetype(ham) <: CuArray +pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) +@test storagetype(pepo0) <: CuArray +wts0 = SUWeight(pepo0) +# 7 = 1 (spin-0) + 2 x 3 (spin-1) +trunc_pepo = truncrank(7) & truncerror(; atol = 1.0e-12) +check_interval = 100 +dt, nstep = 1.0e-3, 600 + +# PEPO approach +alg = SimpleUpdate(; trunc = trunc_pepo, purified = false) +evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) +pepo, wts, info = time_evolve(evolver; check_interval) +env = converge_env(InfinitePartitionFunction(pepo), 16) +energy = expectation_value(pepo, ham, env) / (Nr * Nc) +@info "β = $(dt * nstep): tr(ρH) = $(energy)" +@test dt * nstep ≈ info.t +@test energy ≈ bm[2] atol = 5.0e-3 + +# PEPS (purified PEPO) approach +alg = SimpleUpdate(; trunc = trunc_pepo, purified = true) +evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) +pepo, wts, info = time_evolve(evolver; check_interval) +env = converge_env(InfinitePartitionFunction(pepo), 16) +energy = expectation_value(pepo, ham, env) / (Nr * Nc) +@info "β = $(dt * nstep) / 2: tr(ρH) = $(energy)" +@test energy ≈ bm[1] atol = 5.0e-3 + +# test BP gauge fixing for purified iPEPO +bp_alg = BeliefPropagation(; maxiter = 100, tol = 1.0e-9) +bp_env, = leading_boundary(BPEnv(ones, Float64, pepo), pepo, bp_alg) +pepo, = gauge_fix(pepo, BPGauge(), bp_env) + +env = converge_env(InfinitePEPS(pepo), 16) +energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) +@info "β = $(dt * nstep): ⟨ρ|H|ρ⟩ = $(energy)" +@test dt * nstep ≈ info.t +@test energy ≈ bm[2] atol = 5.0e-3 diff --git a/test/cuda/timeevol/tf_ising_finiteT.jl b/test/cuda/timeevol/tf_ising_finiteT.jl new file mode 100644 index 000000000..a2974a592 --- /dev/null +++ b/test/cuda/timeevol/tf_ising_finiteT.jl @@ -0,0 +1,88 @@ +using Test +using LinearAlgebra +using TensorKit +import MPSKitModels: σˣ, σᶻ +using PEPSKit, CUDA, Adapt + +# Benchmark data of [σx, σz] from HOTRG +# Physical Review B 86, 045139 (2012) Fig. 15-16 +bm_β = [0.5632, 0.0] +bm_2β = [0.5297, 0.8265] + +function converge_env(state, χ::Int) + trunc1 = truncrank(4) & truncerror(; atol = 1.0e-12) + env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) + env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) + trunc2 = truncrank(χ) & truncerror(; atol = 1.0e-12) + env, = leading_boundary(env, state; alg = :SequentialCTMRG, trunc = trunc2, tol = 1.0e-10) + return env +end + +function measure_mag(pepo::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) + r, c = 1, 1 + lattice = physicalspace(pepo) + Mx = adapt(CuArray, LocalOperator(lattice, ((r, c),) => σˣ(Float64, Trivial))) + Mz = adapt(CuArray, LocalOperator(lattice, ((r, c),) => σᶻ(Float64, Trivial))) + if purified + magx = expectation_value(pepo, Mx, pepo, env) + magz = expectation_value(pepo, Mz, pepo, env) + else + magx = expectation_value(pepo, Mx, env) + magz = expectation_value(pepo, Mz, env) + end + return [magx, magz] +end + +Nr, Nc = 2, 2 +ham = adapt(CuArray, transverse_field_ising(Float64, Trivial, InfiniteSquare(Nr, Nc); J = 1.0, g = 2.0)) +pepo0 = adapt(CuArray, PEPSKit.infinite_temperature_density_matrix(ham)) +@test TensorKit.storagetype(ham) <: CuVector +@test TensorKit.storagetype(pepo0) <: CuVector +wts0 = SUWeight(pepo0) + +trunc_pepo = truncrank(8) & truncerror(; atol = 1.0e-12) + +dt, nstep = 1.0e-3, 400 +β = dt * nstep + +# when g = 2, β = 0.4 and 2β = 0.8 belong to two phases (without and with nonzero σᶻ) +@testset "Finite-T SU (force_mpo = $(force_mpo))" for force_mpo in (false, true) + # use second order Trotter decomposition + symmetrize_gates = true + bipartite = true + + # PEPO approach: results at β, or T = 2.5 + alg = SimpleUpdate(; trunc = trunc_pepo, purified = false, bipartite, force_mpo) + pepo, wts, info = time_evolve(pepo0, ham, dt, nstep, alg, wts0; symmetrize_gates) + @test storagetype(pepo) <: CuArray + + ## BP gauge fixing + bp_alg = BeliefPropagation(; maxiter = 100, tol = 1.0e-9, bipartite) + bp_env₀ = BPEnv(ones, Float64, pepo) + @test storagetype(bp_env₀) <: CuArray + bp_env, = leading_boundary(bp_env₀, pepo, bp_alg) + pepo, = gauge_fix(pepo, BPGauge(), bp_env) + + env = converge_env(InfinitePartitionFunction(pepo), 16) + result_β = measure_mag(pepo, env) + @info "tr(σ(x,z)ρ) at T = $(1 / β): $(result_β)." + @test β ≈ info.t + @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) + + # use `compress` to reach 2β, or T = 1.25 + pepo2, = compress((pepo, pepo), LocalTruncation(trunc_pepo)) + normalize!.(pepo2.A) + env2 = converge_env(InfinitePartitionFunction(pepo2), 16) + result_2β = measure_mag(pepo2, env2) + @info "tr(σ(x,z)ρ) at T = $(1 / (2β)): $(result_2β)." + @test isapprox(abs.(result_2β), bm_2β, rtol = 5.0e-3) + + # Purification approach: results at 2β, or T = 1.25 + alg = SimpleUpdate(; trunc = trunc_pepo, purified = true, bipartite, force_mpo) + pepo, wts, info = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0; symmetrize_gates) + env = converge_env(InfinitePEPS(pepo), 8) + result_2β′ = measure_mag(pepo, env; purified = true) + @info "⟨ρ|σ(x,z)|ρ⟩ at T = $(1 / (2β)): $(result_2β′)." + @test 2 * β ≈ info.t + @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) +end From ed9a67fc5354dfbfc59748de317892b94348a91f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 11:36:58 +0200 Subject: [PATCH 11/69] Sitedep truncation also working --- test/amd/timeevol/sitedep_truncation.jl | 64 ++++++++++++++++++++++++ test/cuda/timeevol/sitedep_truncation.jl | 64 ++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 test/amd/timeevol/sitedep_truncation.jl create mode 100644 test/cuda/timeevol/sitedep_truncation.jl diff --git a/test/amd/timeevol/sitedep_truncation.jl b/test/amd/timeevol/sitedep_truncation.jl new file mode 100644 index 000000000..2f6f1af32 --- /dev/null +++ b/test/amd/timeevol/sitedep_truncation.jl @@ -0,0 +1,64 @@ +using Test +using Random +using TensorKit +using PEPSKit +using PEPSKit: _is_bipartite, _get_fixedspacetrunc +using AMDGPU, Adapt + +elt = Float64 +Nr, Nc = 2, 2 +Vps = fill(U1Space(1 / 2 => 1, -1 / 2 => 1), (Nr, Nc)) +Vns = [ + U1Space(0 => 1, 1 => 2, -1 => 1) U1Space(0 => 1, 1 => 2, -1 => 1)'; + U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 2, -1 => 1) +] +Ves1 = [ + U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); + U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' +] +Ves2 = [ + U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); + U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(0 => 1, 1 => 2, -1 => 1)' +] +Venv = U1Space(0 => 2, 1 => 1, -1 => 1) +Random.seed!(48736) +states = ( + adapt(ROCArray, InfinitePEPS(randn, elt, Vps, Vns, Ves1)), + adapt(ROCArray, InfinitePEPO(randn, elt, Vps, Vns, Ves2)), +) + +@testset "Rotation of SiteDependentTruncation" begin + state = states[1] + for f in (rotl90, rotr90, rot180) + trunc1 = f(_get_fixedspacetrunc(state)) + trunc2 = _get_fixedspacetrunc(f(state)) + @test all( + t1.space == t2.space for (t1, t2) in zip(trunc1.truncs, trunc2.truncs) + ) + end +end + +@testset "Simple update on $(typeof(state0).name.wrapper), bipartite = $(bipartite)" for + (state0, bipartite) in Iterators.product(states, (true, false)) + J2 = 0.5 + if bipartite + state0[2, 1] = copy(state0[1, 2]) + state0[2, 2] = copy(state0[1, 1]) + J2 = 0.0 + end + ham = adapt(ROCArray, j1_j2_model(elt, U1Irrep, InfiniteSquare(Nr, Nc); J1 = 1.0, J2, sublattice = false)) + # converted internally to SiteDependentTruncation + alg = SimpleUpdate(; trunc = FixedSpaceTruncation(), bipartite) + wts0 = SUWeight(state0) + state, wts, = time_evolve(state0, ham, 0.1, 1, alg, wts0) + for (t, t0) in zip(state.A, state0.A) + @test space(t) == space(t0) + end + for (wt, wt0) in zip(wts.data, wts0.data) + @test space(wt) == space(wt0) + end + if bipartite + @test _is_bipartite(state) + @test _is_bipartite(wts) + end +end diff --git a/test/cuda/timeevol/sitedep_truncation.jl b/test/cuda/timeevol/sitedep_truncation.jl new file mode 100644 index 000000000..482780780 --- /dev/null +++ b/test/cuda/timeevol/sitedep_truncation.jl @@ -0,0 +1,64 @@ +using Test +using Random +using TensorKit +using PEPSKit +using PEPSKit: _is_bipartite, _get_fixedspacetrunc +using CUDA, Adapt + +elt = Float64 +Nr, Nc = 2, 2 +Vps = fill(U1Space(1 / 2 => 1, -1 / 2 => 1), (Nr, Nc)) +Vns = [ + U1Space(0 => 1, 1 => 2, -1 => 1) U1Space(0 => 1, 1 => 2, -1 => 1)'; + U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 2, -1 => 1) +] +Ves1 = [ + U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); + U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' +] +Ves2 = [ + U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); + U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(0 => 1, 1 => 2, -1 => 1)' +] +Venv = U1Space(0 => 2, 1 => 1, -1 => 1) +Random.seed!(48736) +states = ( + adapt(CuArray, InfinitePEPS(randn, elt, Vps, Vns, Ves1)), + adapt(CuArray, InfinitePEPO(randn, elt, Vps, Vns, Ves2)), +) + +@testset "Rotation of SiteDependentTruncation" begin + state = states[1] + for f in (rotl90, rotr90, rot180) + trunc1 = f(_get_fixedspacetrunc(state)) + trunc2 = _get_fixedspacetrunc(f(state)) + @test all( + t1.space == t2.space for (t1, t2) in zip(trunc1.truncs, trunc2.truncs) + ) + end +end + +@testset "Simple update on $(typeof(state0).name.wrapper), bipartite = $(bipartite)" for + (state0, bipartite) in Iterators.product(states, (true, false)) + J2 = 0.5 + if bipartite + state0[2, 1] = copy(state0[1, 2]) + state0[2, 2] = copy(state0[1, 1]) + J2 = 0.0 + end + ham = adapt(CuArray, j1_j2_model(elt, U1Irrep, InfiniteSquare(Nr, Nc); J1 = 1.0, J2, sublattice = false)) + # converted internally to SiteDependentTruncation + alg = SimpleUpdate(; trunc = FixedSpaceTruncation(), bipartite) + wts0 = SUWeight(state0) + state, wts, = time_evolve(state0, ham, 0.1, 1, alg, wts0) + for (t, t0) in zip(state.A, state0.A) + @test space(t) == space(t0) + end + for (wt, wt0) in zip(wts.data, wts0.data) + @test space(wt) == space(wt0) + end + if bipartite + @test _is_bipartite(state) + @test _is_bipartite(wts) + end +end From 94d7de9559932ac149f670282e5b6d1cd46b81bf Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 12:05:16 +0200 Subject: [PATCH 12/69] Working ctmrg/partition_function (except C4v) --- src/algorithms/ctmrg/c4v.jl | 2 +- src/algorithms/select_algorithm.jl | 2 - src/environments/suweight.jl | 2 + test/amd/ctmrg/gaugefix.jl | 99 +++++++++++++++++ test/amd/ctmrg/partition_function.jl | 152 ++++++++++++++++++++++++++ test/cuda/ctmrg/gaugefix.jl | 99 +++++++++++++++++ test/cuda/ctmrg/partition_function.jl | 152 ++++++++++++++++++++++++++ 7 files changed, 505 insertions(+), 3 deletions(-) create mode 100644 test/amd/ctmrg/gaugefix.jl create mode 100644 test/amd/ctmrg/partition_function.jl create mode 100644 test/cuda/ctmrg/gaugefix.jl create mode 100644 test/cuda/ctmrg/partition_function.jl diff --git a/src/algorithms/ctmrg/c4v.jl b/src/algorithms/ctmrg/c4v.jl index a16d80f01..12486145c 100644 --- a/src/algorithms/ctmrg/c4v.jl +++ b/src/algorithms/ctmrg/c4v.jl @@ -248,7 +248,7 @@ function initialize_singlet_c4v_env(state::InfinitePEPS, Venv::ElementarySpace) end function initialize_singlet_c4v_env(T, state::InfinitePEPS, Venv::ElementarySpace) Vpeps = north_virtualspace(state, 1, 1)' - return initialize_singlet_c4v_env(similarstoragetype(storagetype(state), eltype(T)), Vpeps, Venv) + return initialize_singlet_c4v_env(similarstoragetype(storagetype(state), real(eltype(T))), Vpeps, Venv) end function initialize_singlet_c4v_env(T, Vpeps::ElementarySpace, Venv::ElementarySpace) realT = similarstoragetype(similarstoragetype(T), real(eltype(T))) diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index 5f51ac6e1..59ccbccfa 100644 --- a/src/algorithms/select_algorithm.jl +++ b/src/algorithms/select_algorithm.jl @@ -155,7 +155,5 @@ function select_algorithm( rrule_alg = (; tol = 1.0e1tol, verbosity = verbosity - 2, krylovdim, decomposition_alg.rrule_alg...) decomposition_alg = (; rrule_alg, decomposition_alg...) end - decomposition_alg = isa(decomposition_alg, SVDAdjoint) ? decomposition_alg : SVDAdjoint(; decomposition_alg...) - return CTMRGAlgorithm(; alg, tol, verbosity, decomposition_alg, kwargs...) end diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index 22af1d055..32e34cc04 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -164,6 +164,8 @@ TensorKit.sectortype(w::SUWeight) = sectortype(typeof(w)) TensorKit.sectortype(::Type{<:SUWeight{T}}) where {T} = sectortype(spacetype(T)) TensorKit.storagetype(::Type{SUWeight{T}}) where {T} = storagetype(T) +TensorKit.storagetype(::Type{SUWeight{T}}) where {T} = storagetype(T) + ## Bipartite check function _is_bipartite(wts::SUWeight) (size(wts, 2) == size(wts, 3) == 2) || (return false) diff --git a/test/amd/ctmrg/gaugefix.jl b/test/amd/ctmrg/gaugefix.jl new file mode 100644 index 000000000..30f3211c6 --- /dev/null +++ b/test/amd/ctmrg/gaugefix.jl @@ -0,0 +1,99 @@ +using Test +using Random +using PEPSKit +using TensorKit +using AMDGPU, Adapt +using PEPSKit: ctmrg_iteration, calc_elementwise_convergence +using PEPSKit: ScramblingEnvGauge, ScramblingEnvGaugeC4v +using PEPSKit: peps_normalize + +spacetypes = [ComplexSpace, Z2Space] +scalartypes = [Float64, ComplexF64] +unitcells = [(1, 1), (2, 2), (3, 2)] +ctmrg_algs_asymm = [SequentialCTMRG, SimultaneousCTMRG] +projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] +projector_algs_c4v = [:C4vEighProjector, :C4vQRProjector] +gauge_algs_asymm = [ScramblingEnvGauge()] +gauge_algs_c4v = [ScramblingEnvGaugeC4v()] +tol = 1.0e-6 # large tol due to χ=6 +χ = 6 +atol = 1.0e-4 + +function _pre_converge_env( + ::Type{T}, alg, physical_space, peps_space, env_space, unitcell; + seed = 985293852935829 + ) where {T} + Random.seed!(seed) # Seed RNG to make random environment consistent + psi = adapt(ROCArray, InfinitePEPS(rand, T, physical_space, peps_space; unitcell)) + @test storagetype(psi) <: ROCArray + alg == :C4vCTMRG && (psi = peps_normalize(symmetrize!(psi, RotateReflect()))) + env₀ = if alg == :C4vCTMRG + initialize_singlet_c4v_env(T, psi, env_space) + else + CTMRGEnv(psi, env_space) + end + @test storagetype(env₀) <: ROCArray + env_conv, = leading_boundary(env₀, psi; alg, tol) + return env_conv, psi +end + +# pre-converge CTMRG environments with given spacetype, scalartype and unit cell +preconv = Dict() +for (S, T, unitcell) in Iterators.product(spacetypes, scalartypes, unitcells) + if S == ComplexSpace + result = _pre_converge_env(T, :SequentialCTMRG, S(2), S(2), S(χ), unitcell) + elseif S == Z2Space + result = _pre_converge_env( + T, :SequentialCTMRG, S(0 => 1, 1 => 1), S(0 => 1, 1 => 1), + S(0 => χ ÷ 2, 1 => χ ÷ 2), unitcell + ) + end + push!(preconv, (S, T, unitcell) => result) +end +preconv_c4v = Dict() +for (S, T) in Iterators.product(spacetypes, scalartypes) + if S == ComplexSpace + result = _pre_converge_env(T, :C4vCTMRG, S(2), S(2), S(χ), (1, 1)) + elseif S == Z2Space + result = _pre_converge_env( + T, :C4vCTMRG, S(0 => 1, 1 => 1), S(0 => 1, 1 => 1), S(0 => χ ÷ 2, 1 => χ ÷ 2), (1, 1) + ) + end + push!(preconv_c4v, (S, T) => result) +end + +# asymmetric CTMRG +@testset "($S) - ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg) - ($gauge_alg)" for ( + S, T, unitcell, ctmrg_alg, projector_alg, gauge_alg, + ) in Iterators.product( + spacetypes, scalartypes, unitcells, ctmrg_algs_asymm, projector_algs_asymm, gauge_algs_asymm + ) + alg = ctmrg_alg(; tol, projector_alg) + env_pre, psi = preconv[(S, T, unitcell)] + n = InfiniteSquareNetwork(psi) + @test storagetype(n) <: ROCArray + env, = leading_boundary(env_pre, psi, alg) + env′, = ctmrg_iteration(n, env, alg) + env_fixed = gauge_fix(env′, env, gauge_alg) + env_diff = calc_elementwise_convergence(env, env_fixed) + @info "Diff between iters = $(env_diff)" + @test env_diff ≈ 0 atol = atol +end + +# C4v CTMRG +@testset "($S) - ($T) - ($projector_alg) - ($gauge_alg)" for ( + S, T, projector_alg, gauge_alg, + ) in Iterators.product( + spacetypes, scalartypes, projector_algs_c4v, gauge_algs_c4v + ) + alg = C4vCTMRG(; tol, projector_alg) + env_pre, psi = preconv_c4v[(S, T)] + n = InfiniteSquareNetwork(psi) + @test storagetype(n) <: ROCArray + env, = leading_boundary(env_pre, psi, alg) + env′, = ctmrg_iteration(n, env, alg) + env_fixed = gauge_fix(env′, env, gauge_alg) + env_diff = calc_elementwise_convergence(env, env_fixed) + @info "Diff between iters = $(env_diff)" + @test env_diff ≈ 0 atol = atol +end diff --git a/test/amd/ctmrg/partition_function.jl b/test/amd/ctmrg/partition_function.jl new file mode 100644 index 000000000..11d0d7d5a --- /dev/null +++ b/test/amd/ctmrg/partition_function.jl @@ -0,0 +1,152 @@ +using Test +using Random +using LinearAlgebra +using PEPSKit +using TensorKit +using QuadGK +using Test +using AMDGPU, Adapt + +@testset "Check spaces in partition function CTMRG" begin + zA = randn(ℂ^6 ⊗ ℂ^8 ← ℂ^4 ⊗ ℂ^2) + zB = randn(ℂ^2 ⊗ ℂ^9 ← ℂ^5 ⊗ ℂ^6) + zC = randn(ℂ^7 ⊗ ℂ^4 ← ℂ^8 ⊗ ℂ^3) + zD = randn(ℂ^3 ⊗ ℂ^5 ← ℂ^9 ⊗ ℂ^7) + + Z = adapt(ROCArray, InfinitePartitionFunction([zA zB; zC zD])) + χenv = ℂ^12 + env0 = CTMRGEnv(Z, χenv) + env, = leading_boundary(env0, Z; alg = :SimultaneousCTMRG, maxiter = 3, projector_alg = :FullInfiniteProjector) + @test env isa CTMRGEnv +end + + +## Setup + +""" + classical_ising_exact(beta, J) + +[Exact Onsager solution](https://en.wikipedia.org/wiki/Square_lattice_Ising_model#Exact_solution) +for the 2D classical Ising Model with partition function + +```math +\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j +``` +""" +function classical_ising_exact(; beta = log(1 + sqrt(2)) / 2, J = 1.0) + K = beta * J + + k = 1 / sinh(2 * K)^2 + F = quadgk( + theta -> log(cosh(2 * K)^2 + 1 / k * sqrt(1 + k^2 - 2 * k * cos(2 * theta))), 0, pi + )[1] + f = -1 / beta * (log(2) / 2 + 1 / (2 * pi) * F) + + m = 1 - (sinh(2 * K))^(-4) > 0 ? (1 - (sinh(2 * K))^(-4))^(1 / 8) : 0 + + E = quadgk(theta -> 1 / sqrt(1 - (4 * k) * (1 + k)^(-2) * sin(theta)^2), 0, pi / 2)[1] + e = -J * cosh(2 * K) / sinh(2 * K) * (1 + 2 / pi * (2 * tanh(2 * K)^2 - 1) * E) + + return f, m, e +end + +""" + classical_ising(; beta=log(1 + sqrt(2)) / 2) + +Implements the 2D classical Ising model with partition function + +```math +\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j +``` +""" +function classical_ising(; beta = log(1 + sqrt(2)) / 2, J = 1.0) + K = beta * J + + # Boltzmann weights + t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)] + r = eigen(t) + nt = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors + + # local partition function tensor + O = zeros(2, 2, 2, 2) + O[1, 1, 1, 1] = 1 + O[2, 2, 2, 2] = 1 + @tensor o[-1 -2; -3 -4] := O[3 4; 2 1] * nt[-3; 3] * nt[-4; 4] * nt[-2; 2] * nt[-1; 1] + + # magnetization tensor + M = copy(O) + M[2, 2, 2, 2] *= -1 + @tensor m[-1 -2; -3 -4] := M[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * nt[-4; 4] + + # bond interaction tensor and energy-per-site tensor + e = ComplexF64[-J J; J -J] .* nt + @tensor e_hor[-1 -2; -3 -4] := + O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * e[-4; 4] + @tensor e_vert[-1 -2; -3 -4] := + O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * e[-3; 3] * nt[-4; 4] + e = e_hor + e_vert + + # fixed tensor map space for all three + TMS = ℂ^2 ⊗ ℂ^2 ← ℂ^2 ⊗ ℂ^2 + + return TensorMap(o, TMS), TensorMap(m, TMS), TensorMap(e, TMS) +end + +## Test + +# initialize +beta = 0.6 +O, M, E = classical_ising(; beta) +O = adapt(ROCArray, O) +M = adapt(ROCArray, M) +E = adapt(ROCArray, E) +Z = InfinitePartitionFunction(O) +Venv = ℂ^12 +Random.seed!(81812781143) +env₀ = CTMRGEnv(Z, Venv) +env₀_c4v = initialize_random_c4v_env(Z, Venv) +# cover all different flavors +args = [ + (:SequentialCTMRG, :HalfInfiniteProjector), (:SequentialCTMRG, :FullInfiniteProjector), + (:SimultaneousCTMRG, :HalfInfiniteProjector), (:SimultaneousCTMRG, :FullInfiniteProjector), + # (:C4vCTMRG, :C4vEighProjector), (:C4vCTMRG, :C4vQRProjector), # TODO +] + +# Basic properties +@test storagetype(Z) <: ROCArray +@test spacetype(typeof(Z)) === ComplexSpace +@test spacetype(Z) === ComplexSpace +@test sectortype(typeof(Z)) === Trivial +@test sectortype(Z) === Trivial +@test length(Z) == 1 +@test size(Z, 1) == 1 +@test size(Z, 2) == 1 +@test eltype(similar(Z)) == eltype(Z) +@test copy(Z) == Z +@test copy(Z) ≈ Z + + +@testset "Classical Ising partition function using $alg with $projector_alg" for ( + alg, projector_alg, + ) in args + env₀₀ = alg == :C4vCTMRG ? env₀_c4v : env₀ + env, = leading_boundary(env₀₀, Z; alg, maxiter = 300, projector_alg) + + # check observables + λ = network_value(Z, env) + m = expectation_value(Z, (1, 1) => M, env) + e = expectation_value(Z, (1, 1) => E, env) + f_exact, m_exact, e_exact = classical_ising_exact(; beta) + @info "Exact energy = $(e_exact)." + + # should be real-ish + @test abs(imag(λ)) < 1.0e-4 + @test abs(imag(m)) < 1.0e-4 + @test abs(imag(e)) < 1.0e-4 + + # should match exact solution + @test -log(λ) / beta ≈ f_exact rtol = 1.0e-4 + @test abs(m) ≈ abs(m_exact) rtol = 1.0e-4 + @info "Evaluated energy = $(e)." + @test e ≈ e_exact rtol = 1.0e-1 # accuracy limited by bond dimension and maxiter +end diff --git a/test/cuda/ctmrg/gaugefix.jl b/test/cuda/ctmrg/gaugefix.jl new file mode 100644 index 000000000..c6e1b1f9d --- /dev/null +++ b/test/cuda/ctmrg/gaugefix.jl @@ -0,0 +1,99 @@ +using Test +using Random +using PEPSKit +using TensorKit +using CUDA, Adapt +using PEPSKit: ctmrg_iteration, calc_elementwise_convergence +using PEPSKit: ScramblingEnvGauge, ScramblingEnvGaugeC4v +using PEPSKit: peps_normalize + +spacetypes = [ComplexSpace, Z2Space] +scalartypes = [Float64, ComplexF64] +unitcells = [(1, 1), (2, 2), (3, 2)] +ctmrg_algs_asymm = [SequentialCTMRG, SimultaneousCTMRG] +projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] +projector_algs_c4v = [:C4vEighProjector, :C4vQRProjector] +gauge_algs_asymm = [ScramblingEnvGauge()] +gauge_algs_c4v = [ScramblingEnvGaugeC4v()] +tol = 1.0e-6 # large tol due to χ=6 +χ = 6 +atol = 1.0e-4 + +function _pre_converge_env( + ::Type{T}, alg, physical_space, peps_space, env_space, unitcell; + seed = 985293852935829 + ) where {T} + Random.seed!(seed) # Seed RNG to make random environment consistent + psi = adapt(CuArray, InfinitePEPS(rand, T, physical_space, peps_space; unitcell)) + @test storagetype(psi) <: CuArray + alg == :C4vCTMRG && (psi = peps_normalize(symmetrize!(psi, RotateReflect()))) + env₀ = if alg == :C4vCTMRG + initialize_singlet_c4v_env(T, psi, env_space) + else + CTMRGEnv(psi, env_space) + end + @test storagetype(env₀) <: CuArray + env_conv, = leading_boundary(env₀, psi; alg, tol) + return env_conv, psi +end + +# pre-converge CTMRG environments with given spacetype, scalartype and unit cell +preconv = Dict() +for (S, T, unitcell) in Iterators.product(spacetypes, scalartypes, unitcells) + if S == ComplexSpace + result = _pre_converge_env(T, :SequentialCTMRG, S(2), S(2), S(χ), unitcell) + elseif S == Z2Space + result = _pre_converge_env( + T, :SequentialCTMRG, S(0 => 1, 1 => 1), S(0 => 1, 1 => 1), + S(0 => χ ÷ 2, 1 => χ ÷ 2), unitcell + ) + end + push!(preconv, (S, T, unitcell) => result) +end +preconv_c4v = Dict() +for (S, T) in Iterators.product(spacetypes, scalartypes) + if S == ComplexSpace + result = _pre_converge_env(T, :C4vCTMRG, S(2), S(2), S(χ), (1, 1)) + elseif S == Z2Space + result = _pre_converge_env( + T, :C4vCTMRG, S(0 => 1, 1 => 1), S(0 => 1, 1 => 1), S(0 => χ ÷ 2, 1 => χ ÷ 2), (1, 1) + ) + end + push!(preconv_c4v, (S, T) => result) +end + +# asymmetric CTMRG +@testset "($S) - ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg) - ($gauge_alg)" for ( + S, T, unitcell, ctmrg_alg, projector_alg, gauge_alg, + ) in Iterators.product( + spacetypes, scalartypes, unitcells, ctmrg_algs_asymm, projector_algs_asymm, gauge_algs_asymm + ) + alg = ctmrg_alg(; tol, projector_alg) + env_pre, psi = preconv[(S, T, unitcell)] + n = InfiniteSquareNetwork(psi) + @test storagetype(n) <: CuArray + env, = leading_boundary(env_pre, psi, alg) + env′, = ctmrg_iteration(n, env, alg) + env_fixed = gauge_fix(env′, env, gauge_alg) + env_diff = calc_elementwise_convergence(env, env_fixed) + @info "Diff between iters = $(env_diff)" + @test env_diff ≈ 0 atol = atol +end + +# C4v CTMRG +@testset "($S) - ($T) - ($projector_alg) - ($gauge_alg)" for ( + S, T, projector_alg, gauge_alg, + ) in Iterators.product( + spacetypes, scalartypes, projector_algs_c4v, gauge_algs_c4v + ) + alg = C4vCTMRG(; tol, projector_alg) + env_pre, psi = preconv_c4v[(S, T)] + n = InfiniteSquareNetwork(psi) + @test storagetype(n) <: CuArray + env, = leading_boundary(env_pre, psi, alg) + env′, = ctmrg_iteration(n, env, alg) + env_fixed = gauge_fix(env′, env, gauge_alg) + env_diff = calc_elementwise_convergence(env, env_fixed) + @info "Diff between iters = $(env_diff)" + @test env_diff ≈ 0 atol = atol +end diff --git a/test/cuda/ctmrg/partition_function.jl b/test/cuda/ctmrg/partition_function.jl new file mode 100644 index 000000000..90979bd94 --- /dev/null +++ b/test/cuda/ctmrg/partition_function.jl @@ -0,0 +1,152 @@ +using Test +using Random +using LinearAlgebra +using PEPSKit +using TensorKit +using QuadGK +using Test +using CUDA, Adapt + +@testset "Check spaces in partition function CTMRG" begin + zA = randn(ℂ^6 ⊗ ℂ^8 ← ℂ^4 ⊗ ℂ^2) + zB = randn(ℂ^2 ⊗ ℂ^9 ← ℂ^5 ⊗ ℂ^6) + zC = randn(ℂ^7 ⊗ ℂ^4 ← ℂ^8 ⊗ ℂ^3) + zD = randn(ℂ^3 ⊗ ℂ^5 ← ℂ^9 ⊗ ℂ^7) + + Z = adapt(CuArray, InfinitePartitionFunction([zA zB; zC zD])) + χenv = ℂ^12 + env0 = CTMRGEnv(Z, χenv) + env, = leading_boundary(env0, Z; alg = :SimultaneousCTMRG, maxiter = 3, projector_alg = :FullInfiniteProjector) + @test env isa CTMRGEnv +end + + +## Setup + +""" + classical_ising_exact(beta, J) + +[Exact Onsager solution](https://en.wikipedia.org/wiki/Square_lattice_Ising_model#Exact_solution) +for the 2D classical Ising Model with partition function + +```math +\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j +``` +""" +function classical_ising_exact(; beta = log(1 + sqrt(2)) / 2, J = 1.0) + K = beta * J + + k = 1 / sinh(2 * K)^2 + F = quadgk( + theta -> log(cosh(2 * K)^2 + 1 / k * sqrt(1 + k^2 - 2 * k * cos(2 * theta))), 0, pi + )[1] + f = -1 / beta * (log(2) / 2 + 1 / (2 * pi) * F) + + m = 1 - (sinh(2 * K))^(-4) > 0 ? (1 - (sinh(2 * K))^(-4))^(1 / 8) : 0 + + E = quadgk(theta -> 1 / sqrt(1 - (4 * k) * (1 + k)^(-2) * sin(theta)^2), 0, pi / 2)[1] + e = -J * cosh(2 * K) / sinh(2 * K) * (1 + 2 / pi * (2 * tanh(2 * K)^2 - 1) * E) + + return f, m, e +end + +""" + classical_ising(; beta=log(1 + sqrt(2)) / 2) + +Implements the 2D classical Ising model with partition function + +```math +\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j +``` +""" +function classical_ising(; beta = log(1 + sqrt(2)) / 2, J = 1.0) + K = beta * J + + # Boltzmann weights + t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)] + r = eigen(t) + nt = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors + + # local partition function tensor + O = zeros(2, 2, 2, 2) + O[1, 1, 1, 1] = 1 + O[2, 2, 2, 2] = 1 + @tensor o[-1 -2; -3 -4] := O[3 4; 2 1] * nt[-3; 3] * nt[-4; 4] * nt[-2; 2] * nt[-1; 1] + + # magnetization tensor + M = copy(O) + M[2, 2, 2, 2] *= -1 + @tensor m[-1 -2; -3 -4] := M[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * nt[-4; 4] + + # bond interaction tensor and energy-per-site tensor + e = ComplexF64[-J J; J -J] .* nt + @tensor e_hor[-1 -2; -3 -4] := + O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * e[-4; 4] + @tensor e_vert[-1 -2; -3 -4] := + O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * e[-3; 3] * nt[-4; 4] + e = e_hor + e_vert + + # fixed tensor map space for all three + TMS = ℂ^2 ⊗ ℂ^2 ← ℂ^2 ⊗ ℂ^2 + + return TensorMap(o, TMS), TensorMap(m, TMS), TensorMap(e, TMS) +end + +## Test + +# initialize +beta = 0.6 +O, M, E = classical_ising(; beta) +O = adapt(CuArray, O) +M = adapt(CuArray, M) +E = adapt(CuArray, E) +Z = InfinitePartitionFunction(O) +Venv = ℂ^12 +Random.seed!(81812781143) +env₀ = CTMRGEnv(Z, Venv) +env₀_c4v = initialize_random_c4v_env(Z, Venv) +# cover all different flavors +args = [ + (:SequentialCTMRG, :HalfInfiniteProjector), (:SequentialCTMRG, :FullInfiniteProjector), + (:SimultaneousCTMRG, :HalfInfiniteProjector), (:SimultaneousCTMRG, :FullInfiniteProjector), + # (:C4vCTMRG, :C4vEighProjector), (:C4vCTMRG, :C4vQRProjector), # TODO +] + +# Basic properties +@test storagetype(Z) <: CuArray +@test spacetype(typeof(Z)) === ComplexSpace +@test spacetype(Z) === ComplexSpace +@test sectortype(typeof(Z)) === Trivial +@test sectortype(Z) === Trivial +@test length(Z) == 1 +@test size(Z, 1) == 1 +@test size(Z, 2) == 1 +@test eltype(similar(Z)) == eltype(Z) +@test copy(Z) == Z +@test copy(Z) ≈ Z + + +@testset "Classical Ising partition function using $alg with $projector_alg" for ( + alg, projector_alg, + ) in args + env₀₀ = alg == :C4vCTMRG ? env₀_c4v : env₀ + env, = leading_boundary(env₀₀, Z; alg, maxiter = 300, projector_alg) + + # check observables + λ = network_value(Z, env) + m = expectation_value(Z, (1, 1) => M, env) + e = expectation_value(Z, (1, 1) => E, env) + f_exact, m_exact, e_exact = classical_ising_exact(; beta) + @info "Exact energy = $(e_exact)." + + # should be real-ish + @test abs(imag(λ)) < 1.0e-4 + @test abs(imag(m)) < 1.0e-4 + @test abs(imag(e)) < 1.0e-4 + + # should match exact solution + @test -log(λ) / beta ≈ f_exact rtol = 1.0e-4 + @test abs(m) ≈ abs(m_exact) rtol = 1.0e-4 + @info "Evaluated energy = $(e)." + @test e ≈ e_exact rtol = 1.0e-1 # accuracy limited by bond dimension and maxiter +end From 03799bf51723d1ef0adc8fd0bdf7b1780a44a66a Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 15:53:05 +0200 Subject: [PATCH 13/69] Working BP tests --- test/amd/bp/rotation.jl | 46 ++++++++++++++++++ test/amd/bp/unitcell.jl | 100 +++++++++++++++++++++++++++++++++++++++ test/cuda/bp/rotation.jl | 46 ++++++++++++++++++ test/cuda/bp/unitcell.jl | 100 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 292 insertions(+) create mode 100644 test/amd/bp/rotation.jl create mode 100644 test/amd/bp/unitcell.jl create mode 100644 test/cuda/bp/rotation.jl create mode 100644 test/cuda/bp/unitcell.jl diff --git a/test/amd/bp/rotation.jl b/test/amd/bp/rotation.jl new file mode 100644 index 000000000..e4be19528 --- /dev/null +++ b/test/amd/bp/rotation.jl @@ -0,0 +1,46 @@ +using Test +using Random +using TensorKit +using PEPSKit +using PEPSKit: random_dual! +using AMDGPU, Adapt + +ds = Dict( + Trivial => ℂ^2, + U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), + FermionParity => Vect[FermionParity](0 => 2, 1 => 1) +) +Ds = Dict( + Trivial => ℂ^3, + U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 3, 2))), + FermionParity => Vect[FermionParity](0 => 3, 1 => 2) +) +Random.seed!(41973582) + +function meas_sites( + op::O, ψ::InfinitePEPS, env::Union{BPEnv, CTMRGEnv} + ) where {O <: AbstractTensorMap{<:Any, <:Any, 1, 1}} + lattice = physicalspace(ψ) + return map(eachindex(ψ)) do site1 + lo = LocalOperator(lattice, (site1,) => op) + return expectation_value(ψ, lo, env) + end +end + +@testset "Rotation of BPEnv ($S)" for S in keys(ds) + d, D, unitcell = ds[S], Ds[S], (2, 3) + ψds = fill(d, unitcell) + ψDNs = random_dual!(fill(D, unitcell)) + ψDEs = random_dual!(fill(D, unitcell)) + ψ = adapt(ROCArray, InfinitePEPS(ψds, ψDNs, ψDEs)) + env = BPEnv(ψ) + + op = adapt(ROCArray, randn(d → d)) + meas1 = meas_sites(op, ψ, env) + # rotated peps and env + for f in (rotl90, rotr90, rot180) + ψ′, env′ = f(ψ), f(env) + meas1′ = meas_sites(op, ψ′, env′) + @test meas1′ ≈ f(meas1) + end +end diff --git a/test/amd/bp/unitcell.jl b/test/amd/bp/unitcell.jl new file mode 100644 index 000000000..d0130f84e --- /dev/null +++ b/test/amd/bp/unitcell.jl @@ -0,0 +1,100 @@ +using Test +using Random +using PEPSKit +using PEPSKit: bp_iteration +using TensorKit +using AMDGPU, Adapt + +# settings +Random.seed!(91283219347) +elt = ComplexF64 + +function test_unitcell(unitcell, Pspaces, Nspaces, Espaces) + peps = adapt(ROCArray, InfinitePEPS(randn, elt, Pspaces, Nspaces, Espaces)) + env0 = BPEnv(ones, elt, peps) + alg = BeliefPropagation() + + # apply one BP iteration + network = InfiniteSquareNetwork(peps) + env1 = bp_iteration(network, env0, alg) + # another iteration to detect bond mismatches + env1 = bp_iteration(network, env1, alg) + + # compute random expecation value to test matching bonds + random_op = adapt(ROCArray, LocalOperator( + Pspaces, ( + (c,) => randn(elt, Pspaces[c], Pspaces[c]) + for c in CartesianIndices(unitcell) + )..., + )) + @test storagetype(random_op) <: ROCArray + @test expectation_value(peps, random_op, env0) isa Number + @test expectation_value(peps, random_op, env1) isa Number + return +end + +@testset "Random Cartesian spaces with BP" begin + unitcell = (3, 3) + + Pspaces = ComplexSpace.(rand(2:3, unitcell...)) + Nspaces = ComplexSpace.(rand(2:4, unitcell...)) + Espaces = ComplexSpace.(rand(2:4, unitcell...)) + + test_unitcell(unitcell, Pspaces, Nspaces, Espaces) +end + +@testset "Specific U1 spaces with BP" begin + unitcell = (2, 2) + + PA = U1Space(-1 => 1, 0 => 1) + PB = U1Space(0 => 1, 1 => 1) + Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) + Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) + + Pspaces = [PA PB; PB PA] + Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] + + test_unitcell(unitcell, Pspaces, Nspaces, Nspaces) + + # 4x4 unit cell with all 32 inequivalent bonds + # + # 10 4 7 32 + # | | | | + # 3--A--1--B--5--C--8--D--3 + # | | | | + # 2 6 9 11 + # | | | | + # 14--E-12--F-15--G-17--H-14 + # | | | | + # 13 16 18 19 + # | | | | + # 22--I-20--J-23--K-25--L-22 + # | | | | + # 21 24 26 27 + # | | | | + # 29--M-28--N-30--O-31--P-29 + # | | | | + # 10 4 7 32 + + phys_space = Vect[U1Irrep](1 => 1, -1 => 1) + corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) + vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) + @test length(Set(vspaces)) == 32 + + Espaces = [ + vspaces[1] vspaces[5] vspaces[8] vspaces[3] + vspaces[12] vspaces[15] vspaces[17] vspaces[14] + vspaces[20] vspaces[23] vspaces[25] vspaces[22] + vspaces[28] vspaces[30] vspaces[31] vspaces[29] + ] + + Nspaces = [ + vspaces[10] vspaces[4] vspaces[7] vspaces[32] + vspaces[2] vspaces[6] vspaces[9] vspaces[11] + vspaces[13] vspaces[16] vspaces[18] vspaces[19] + vspaces[21] vspaces[24] vspaces[26] vspaces[27] + ] + Pspaces = fill(phys_space, (4, 4)) + + test_unitcell(unitcell, Pspaces, Nspaces, Nspaces) +end diff --git a/test/cuda/bp/rotation.jl b/test/cuda/bp/rotation.jl new file mode 100644 index 000000000..e3aa0d39e --- /dev/null +++ b/test/cuda/bp/rotation.jl @@ -0,0 +1,46 @@ +using Test +using Random +using TensorKit +using PEPSKit +using PEPSKit: random_dual! +using CUDA, Adapt + +ds = Dict( + Trivial => ℂ^2, + U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), + FermionParity => Vect[FermionParity](0 => 2, 1 => 1) +) +Ds = Dict( + Trivial => ℂ^3, + U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 3, 2))), + FermionParity => Vect[FermionParity](0 => 3, 1 => 2) +) +Random.seed!(41973582) + +function meas_sites( + op::O, ψ::InfinitePEPS, env::Union{BPEnv, CTMRGEnv} + ) where {O <: AbstractTensorMap{<:Any, <:Any, 1, 1}} + lattice = physicalspace(ψ) + return map(eachindex(ψ)) do site1 + lo = LocalOperator(lattice, (site1,) => op) + return expectation_value(ψ, lo, env) + end +end + +@testset "Rotation of BPEnv ($S)" for S in keys(ds) + d, D, unitcell = ds[S], Ds[S], (2, 3) + ψds = fill(d, unitcell) + ψDNs = random_dual!(fill(D, unitcell)) + ψDEs = random_dual!(fill(D, unitcell)) + ψ = adapt(CuArray, InfinitePEPS(ψds, ψDNs, ψDEs)) + env = BPEnv(ψ) + + op = adapt(CuArray, randn(d → d)) + meas1 = meas_sites(op, ψ, env) + # rotated peps and env + for f in (rotl90, rotr90, rot180) + ψ′, env′ = f(ψ), f(env) + meas1′ = meas_sites(op, ψ′, env′) + @test meas1′ ≈ f(meas1) + end +end diff --git a/test/cuda/bp/unitcell.jl b/test/cuda/bp/unitcell.jl new file mode 100644 index 000000000..84a1f32e1 --- /dev/null +++ b/test/cuda/bp/unitcell.jl @@ -0,0 +1,100 @@ +using Test +using Random +using PEPSKit +using PEPSKit: bp_iteration +using TensorKit +using CUDA, Adapt + +# settings +Random.seed!(91283219347) +elt = ComplexF64 + +function test_unitcell(unitcell, Pspaces, Nspaces, Espaces) + peps = adapt(CuArray, InfinitePEPS(randn, elt, Pspaces, Nspaces, Espaces)) + env0 = BPEnv(ones, elt, peps) + alg = BeliefPropagation() + + # apply one BP iteration + network = InfiniteSquareNetwork(peps) + env1 = bp_iteration(network, env0, alg) + # another iteration to detect bond mismatches + env1 = bp_iteration(network, env1, alg) + + # compute random expecation value to test matching bonds + random_op = adapt(CuArray, LocalOperator( + Pspaces, ( + (c,) => randn(elt, Pspaces[c], Pspaces[c]) + for c in CartesianIndices(unitcell) + )..., + )) + @test storagetype(random_op) <: CuArray + @test expectation_value(peps, random_op, env0) isa Number + @test expectation_value(peps, random_op, env1) isa Number + return +end + +@testset "Random Cartesian spaces with BP" begin + unitcell = (3, 3) + + Pspaces = ComplexSpace.(rand(2:3, unitcell...)) + Nspaces = ComplexSpace.(rand(2:4, unitcell...)) + Espaces = ComplexSpace.(rand(2:4, unitcell...)) + + test_unitcell(unitcell, Pspaces, Nspaces, Espaces) +end + +@testset "Specific U1 spaces with BP" begin + unitcell = (2, 2) + + PA = U1Space(-1 => 1, 0 => 1) + PB = U1Space(0 => 1, 1 => 1) + Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) + Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) + + Pspaces = [PA PB; PB PA] + Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] + + test_unitcell(unitcell, Pspaces, Nspaces, Nspaces) + + # 4x4 unit cell with all 32 inequivalent bonds + # + # 10 4 7 32 + # | | | | + # 3--A--1--B--5--C--8--D--3 + # | | | | + # 2 6 9 11 + # | | | | + # 14--E-12--F-15--G-17--H-14 + # | | | | + # 13 16 18 19 + # | | | | + # 22--I-20--J-23--K-25--L-22 + # | | | | + # 21 24 26 27 + # | | | | + # 29--M-28--N-30--O-31--P-29 + # | | | | + # 10 4 7 32 + + phys_space = Vect[U1Irrep](1 => 1, -1 => 1) + corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) + vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) + @test length(Set(vspaces)) == 32 + + Espaces = [ + vspaces[1] vspaces[5] vspaces[8] vspaces[3] + vspaces[12] vspaces[15] vspaces[17] vspaces[14] + vspaces[20] vspaces[23] vspaces[25] vspaces[22] + vspaces[28] vspaces[30] vspaces[31] vspaces[29] + ] + + Nspaces = [ + vspaces[10] vspaces[4] vspaces[7] vspaces[32] + vspaces[2] vspaces[6] vspaces[9] vspaces[11] + vspaces[13] vspaces[16] vspaces[18] vspaces[19] + vspaces[21] vspaces[24] vspaces[26] vspaces[27] + ] + Pspaces = fill(phys_space, (4, 4)) + + test_unitcell(unitcell, Pspaces, Nspaces, Nspaces) +end From 9f68923923abcdf96cbe2777543811f1a35fa426 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 17:39:13 +0200 Subject: [PATCH 14/69] Fixed iteration appears to be working --- test/amd/ctmrg/fixed_iterscheme.jl | 108 ++++++++++++++++++++++++++++ test/cuda/ctmrg/fixed_iterscheme.jl | 108 ++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 test/amd/ctmrg/fixed_iterscheme.jl create mode 100644 test/cuda/ctmrg/fixed_iterscheme.jl diff --git a/test/amd/ctmrg/fixed_iterscheme.jl b/test/amd/ctmrg/fixed_iterscheme.jl new file mode 100644 index 000000000..59813907e --- /dev/null +++ b/test/amd/ctmrg/fixed_iterscheme.jl @@ -0,0 +1,108 @@ +using Test +using TestExtras: @constinferred +using Accessors +using Random +using LinearAlgebra +using TensorKit, KrylovKit +using PEPSKit +using AMDGPU, Adapt +using PEPSKit: + ctmrg_iteration, + compute_gauge_fix_gauge, + fix_phases, + fix_relative_phases, + calc_elementwise_convergence, + peps_normalize, + ScramblingEnvGauge, + ScramblingEnvGaugeC4v +using PEPSKit.Defaults: ctmrg_tol + +# initialize parameters +D = 2 +χ = 16 +svd_algs = [(; alg = :QRIteration), (; alg = :GKL)] +projector_algs_asymm = [:HalfInfiniteProjector] #, :FullInfiniteProjector] +unitcells = [(1, 1), (3, 4)] +atol = 1.0e-5 + +# test for element-wise convergence after application of fixed step +@testset "$unitcell unit cell with $(decomposition_alg.alg) and $projector_alg" for ( + unitcell, decomposition_alg, projector_alg, + ) in Iterators.product( + unitcells, svd_algs, projector_algs_asymm + ) + ctm_alg = SimultaneousCTMRG(; decomposition_alg, projector_alg) + + # initialize states + Random.seed!(2394823842) + psi = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) + @test storagetype(psi) <: ROCArray + n = InfiniteSquareNetwork(psi) + + env_conv1, = leading_boundary(CTMRGEnv(psi, ComplexSpace(χ)), psi, ctm_alg) + + # do extra iteration and gauge fix + env_conv2, = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) + env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGauge()) + @test calc_elementwise_convergence(env_conv1, env_fixed) ≈ 0 atol = atol + + # fix gauge of single iteration + signs, corner_phases, edge_phases = + compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGauge()) + gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( + ctmrg_iteration(n, env, ctm_alg)[1], + signs, corner_phases, edge_phases, + ) + + # do gauge-fixed iteration + env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) + @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol +end + +# test same thing for C4v CTMRG +c4v_algs = [ + (:C4vQRProjector, (; alg = :Householder)), + (:C4vEighProjector, (; alg = :QRIteration)), + (:C4vEighProjector, (; alg = :Lanczos)), +] +@testset "$(decomposition_alg.alg) and $projector_alg" for + (projector_alg, decomposition_alg) in c4v_algs + # initialize states + Random.seed!(2394823842) + ctm_alg = C4vCTMRG(; + projector_alg, decomposition_alg, maxiter = 200, + tol = (projector_alg == :C4vQRProjector ? 1.0e-12 : ctmrg_tol) + ) + symm = RotateReflect() + + psi = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D))) + @test storagetype(psi) <: ROCArray + psi = peps_normalize(symmetrize!(psi, symm)) + @test storagetype(psi) <: ROCArray + n = InfiniteSquareNetwork(psi) + @test storagetype(n) <: ROCArray + + env₀ = initialize_random_c4v_env(psi, ComplexSpace(χ)) + @test storagetype(env₀) <: ROCArray + env_conv1, info = leading_boundary(env₀, psi, ctm_alg) + + # do extra iteration to check gauge fixing + env_conv2, info = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) # CHECK + + env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) + env_diff = calc_elementwise_convergence(env_conv1, env_fixed) + @info "Diff between iters = $(env_diff)" + @test env_diff ≈ 0 atol = atol + + # fix gauge of single iteration + signs, corner_phases, edge_phases = + compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) + gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( + ctmrg_iteration(n, env, ctm_alg)[1], + signs, corner_phases, edge_phases, + ) + + # do gauge-fixed iteration + env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) + @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol +end diff --git a/test/cuda/ctmrg/fixed_iterscheme.jl b/test/cuda/ctmrg/fixed_iterscheme.jl new file mode 100644 index 000000000..5ebb4e8cd --- /dev/null +++ b/test/cuda/ctmrg/fixed_iterscheme.jl @@ -0,0 +1,108 @@ +using Test +using TestExtras: @constinferred +using Accessors +using Random +using LinearAlgebra +using TensorKit, KrylovKit +using PEPSKit +using CUDA, Adapt +using PEPSKit: + ctmrg_iteration, + compute_gauge_fix_gauge, + fix_phases, + fix_relative_phases, + calc_elementwise_convergence, + peps_normalize, + ScramblingEnvGauge, + ScramblingEnvGaugeC4v +using PEPSKit.Defaults: ctmrg_tol + +# initialize parameters +D = 2 +χ = 16 +svd_algs = [(; alg = :QRIteration), (; alg = :GKL)] +projector_algs_asymm = [:HalfInfiniteProjector] #, :FullInfiniteProjector] +unitcells = [(1, 1), (3, 4)] +atol = 1.0e-5 + +# test for element-wise convergence after application of fixed step +@testset "$unitcell unit cell with $(decomposition_alg.alg) and $projector_alg" for ( + unitcell, decomposition_alg, projector_alg, + ) in Iterators.product( + unitcells, svd_algs, projector_algs_asymm + ) + ctm_alg = SimultaneousCTMRG(; decomposition_alg, projector_alg) + + # initialize states + Random.seed!(2394823842) + psi = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) + @test storagetype(psi) <: CuArray + n = InfiniteSquareNetwork(psi) + + env_conv1, = leading_boundary(CTMRGEnv(psi, ComplexSpace(χ)), psi, ctm_alg) + + # do extra iteration and gauge fix + env_conv2, = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) + env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGauge()) + @test calc_elementwise_convergence(env_conv1, env_fixed) ≈ 0 atol = atol + + # fix gauge of single iteration + signs, corner_phases, edge_phases = + compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGauge()) + gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( + ctmrg_iteration(n, env, ctm_alg)[1], + signs, corner_phases, edge_phases, + ) + + # do gauge-fixed iteration + env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) + @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol +end + +# test same thing for C4v CTMRG +c4v_algs = [ + (:C4vQRProjector, (; alg = :Householder)), + (:C4vEighProjector, (; alg = :QRIteration)), + (:C4vEighProjector, (; alg = :Lanczos)), +] +@testset "$(decomposition_alg.alg) and $projector_alg" for + (projector_alg, decomposition_alg) in c4v_algs + # initialize states + Random.seed!(2394823842) + ctm_alg = C4vCTMRG(; + projector_alg, decomposition_alg, maxiter = 200, + tol = (projector_alg == :C4vQRProjector ? 1.0e-12 : ctmrg_tol) + ) + symm = RotateReflect() + + psi = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D))) + @test storagetype(psi) <: CuArray + psi = peps_normalize(symmetrize!(psi, symm)) + @test storagetype(psi) <: CuArray + n = InfiniteSquareNetwork(psi) + @test storagetype(n) <: CuArray + + env₀ = initialize_random_c4v_env(psi, ComplexSpace(χ)) + @test storagetype(env₀) <: CuArray + env_conv1, info = leading_boundary(env₀, psi, ctm_alg) + + # do extra iteration to check gauge fixing + env_conv2, info = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) # CHECK + + env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) + env_diff = calc_elementwise_convergence(env_conv1, env_fixed) + @info "Diff between iters = $(env_diff)" + @test env_diff ≈ 0 atol = atol + + # fix gauge of single iteration + signs, corner_phases, edge_phases = + compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) + gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( + ctmrg_iteration(n, env, ctm_alg)[1], + signs, corner_phases, edge_phases, + ) + + # do gauge-fixed iteration + env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) + @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol +end From b95ec3bdd42b044192533710477d23f9d96b9ad1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 18:37:47 +0200 Subject: [PATCH 15/69] Flavors working too --- test/amd/ctmrg/flavors.jl | 88 ++++++++++++++++++++++++++++++++++++++ test/cuda/ctmrg/flavors.jl | 88 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 test/amd/ctmrg/flavors.jl create mode 100644 test/cuda/ctmrg/flavors.jl diff --git a/test/amd/ctmrg/flavors.jl b/test/amd/ctmrg/flavors.jl new file mode 100644 index 000000000..e391bd674 --- /dev/null +++ b/test/amd/ctmrg/flavors.jl @@ -0,0 +1,88 @@ +using Test +using Random +using MatrixAlgebraKit +using TensorKit +using MPSKit +using PEPSKit +using AMDGPU, Adapt +using PEPSKit: peps_normalize + +# initialize parameters +D = 2 +χ = 16 +unitcells = [(1, 1), (3, 4)] +projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] +projector_algs_c4v = [ + (:C4vQRProjector, :Householder), + (:C4vEighProjector, :QRIteration), (:C4vEighProjector, :Lanczos), +] +Ts = [Float64, ComplexF64] + +@testset "$(unitcell) unit cell with $projector_alg" for (unitcell, projector_alg) in + Iterators.product(unitcells, projector_algs_asymm) + # compute environments + Random.seed!(32350283290358) + psi = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) + env_sequential, = leading_boundary( + CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SequentialCTMRG, projector_alg + ) + env_simultaneous, = leading_boundary( + CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SimultaneousCTMRG, projector_alg + ) + + # compare norms + @test abs(norm(psi, env_sequential)) ≈ abs(norm(psi, env_simultaneous)) rtol = 1.0e-6 + + # compare singular values + CS_sequential = map(svd_vals, env_sequential.corners) + CS_simultaneous = map(svd_vals, env_simultaneous.corners) + ΔCS = maximum(splat(PEPSKit._singular_value_distance), zip(CS_sequential, CS_simultaneous)) + @test ΔCS < 1.0e-2 + + TS_sequential = map(svd_vals, env_sequential.edges) + TS_simultaneous = map(svd_vals, env_simultaneous.edges) + ΔTS = maximum(splat(PEPSKit._singular_value_distance), zip(TS_sequential, TS_simultaneous)) + @test ΔTS < 1.0e-2 + + # compare Heisenberg energies + H = adapt(ROCArray, heisenberg_XYZ(InfiniteSquare(unitcell...))) + E_sequential = cost_function(psi, env_sequential, H) + E_simultaneous = cost_function(psi, env_simultaneous, H) + @test E_sequential ≈ E_simultaneous rtol = 1.0e-3 +end + +# test fixedspace actually fixes space +@testset "Fixedspace truncation using $alg and $projector_alg" for (alg, projector_alg) in + Iterators.product([:SequentialCTMRG, :SimultaneousCTMRG], projector_algs_asymm) + Ds = ComplexSpace.(fill(2, 3, 3)) + χs = ComplexSpace.([16 17 18; 15 20 21; 14 19 22]) + psi = adapt(ROCArray, InfinitePEPS(Ds, Ds, Ds)) + env = CTMRGEnv(psi, ComplexSpace.(rand(10:20, 3, 3)), ComplexSpace.(rand(10:20, 3, 3))) + env2, = leading_boundary( + env, psi; alg, maxiter = 1, trunc = FixedSpaceTruncation(), projector_alg + ) + + # check that the space is fixed + @test all(space.(env.corners) .== space.(env2.corners)) + @test all(space.(env.edges) .== space.(env2.edges)) +end + +@testset "C4v with ($T) - ($projector_alg, $decomp_alg)" for (T, (projector_alg, decomp_alg)) in + Iterators.product(Ts, projector_algs_c4v) + + Random.seed!(29358293829382) + symm = RotateReflect() + Vphys = ComplexSpace(2) + Vpeps = ComplexSpace(D) + Venv = ComplexSpace(χ) + + peps = adapt(ROCArray, InfinitePEPS(randn, T, Vphys, Vpeps, Vpeps)) + peps = peps_normalize(symmetrize!(peps, symm)) + + env₀ = initialize_random_c4v_env(peps, Venv) + env, = leading_boundary( + env₀, peps; alg = :C4vCTMRG, projector_alg, + decomposition_alg = (; alg = decomp_alg) + ) + @test env isa CTMRGEnv +end diff --git a/test/cuda/ctmrg/flavors.jl b/test/cuda/ctmrg/flavors.jl new file mode 100644 index 000000000..16664e1c1 --- /dev/null +++ b/test/cuda/ctmrg/flavors.jl @@ -0,0 +1,88 @@ +using Test +using Random +using MatrixAlgebraKit +using TensorKit +using MPSKit +using PEPSKit +using CUDA, Adapt +using PEPSKit: peps_normalize + +# initialize parameters +D = 2 +χ = 16 +unitcells = [(1, 1), (3, 4)] +projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] +projector_algs_c4v = [ + (:C4vQRProjector, :Householder), + (:C4vEighProjector, :QRIteration), (:C4vEighProjector, :Lanczos), +] +Ts = [Float64, ComplexF64] + +@testset "$(unitcell) unit cell with $projector_alg" for (unitcell, projector_alg) in + Iterators.product(unitcells, projector_algs_asymm) + # compute environments + Random.seed!(32350283290358) + psi = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) + env_sequential, = leading_boundary( + CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SequentialCTMRG, projector_alg + ) + env_simultaneous, = leading_boundary( + CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SimultaneousCTMRG, projector_alg + ) + + # compare norms + @test abs(norm(psi, env_sequential)) ≈ abs(norm(psi, env_simultaneous)) rtol = 1.0e-6 + + # compare singular values + CS_sequential = map(svd_vals, env_sequential.corners) + CS_simultaneous = map(svd_vals, env_simultaneous.corners) + ΔCS = maximum(splat(PEPSKit._singular_value_distance), zip(CS_sequential, CS_simultaneous)) + @test ΔCS < 1.0e-2 + + TS_sequential = map(svd_vals, env_sequential.edges) + TS_simultaneous = map(svd_vals, env_simultaneous.edges) + ΔTS = maximum(splat(PEPSKit._singular_value_distance), zip(TS_sequential, TS_simultaneous)) + @test ΔTS < 1.0e-2 + + # compare Heisenberg energies + H = adapt(CuArray, heisenberg_XYZ(InfiniteSquare(unitcell...))) + E_sequential = cost_function(psi, env_sequential, H) + E_simultaneous = cost_function(psi, env_simultaneous, H) + @test E_sequential ≈ E_simultaneous rtol = 1.0e-3 +end + +# test fixedspace actually fixes space +@testset "Fixedspace truncation using $alg and $projector_alg" for (alg, projector_alg) in + Iterators.product([:SequentialCTMRG, :SimultaneousCTMRG], projector_algs_asymm) + Ds = ComplexSpace.(fill(2, 3, 3)) + χs = ComplexSpace.([16 17 18; 15 20 21; 14 19 22]) + psi = adapt(CuArray, InfinitePEPS(Ds, Ds, Ds)) + env = CTMRGEnv(psi, ComplexSpace.(rand(10:20, 3, 3)), ComplexSpace.(rand(10:20, 3, 3))) + env2, = leading_boundary( + env, psi; alg, maxiter = 1, trunc = FixedSpaceTruncation(), projector_alg + ) + + # check that the space is fixed + @test all(space.(env.corners) .== space.(env2.corners)) + @test all(space.(env.edges) .== space.(env2.edges)) +end + +@testset "C4v with ($T) - ($projector_alg, $decomp_alg)" for (T, (projector_alg, decomp_alg)) in + Iterators.product(Ts, projector_algs_c4v) + + Random.seed!(29358293829382) + symm = RotateReflect() + Vphys = ComplexSpace(2) + Vpeps = ComplexSpace(D) + Venv = ComplexSpace(χ) + + peps = adapt(CuArray, InfinitePEPS(randn, T, Vphys, Vpeps, Vpeps)) + peps = peps_normalize(symmetrize!(peps, symm)) + + env₀ = initialize_random_c4v_env(peps, Venv) + env, = leading_boundary( + env₀, peps; alg = :C4vCTMRG, projector_alg, + decomposition_alg = (; alg = decomp_alg) + ) + @test env isa CTMRGEnv +end From 9cd731d935ab6ed54ebbc24dc8b24e41ff1180e5 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 18:50:31 +0200 Subject: [PATCH 16/69] Working ctmrg/unitcell --- test/amd/ctmrg/unitcell.jl | 123 ++++++++++++++++++++++++++++++++++++ test/cuda/ctmrg/unitcell.jl | 123 ++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 test/amd/ctmrg/unitcell.jl create mode 100644 test/cuda/ctmrg/unitcell.jl diff --git a/test/amd/ctmrg/unitcell.jl b/test/amd/ctmrg/unitcell.jl new file mode 100644 index 000000000..65a3f053f --- /dev/null +++ b/test/amd/ctmrg/unitcell.jl @@ -0,0 +1,123 @@ +using Test +using Random +using PEPSKit +using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, ScramblingEnvGauge +using TensorKit +using AMDGPU, Adapt + +# settings +Random.seed!(91283219347) +stype = ComplexF64 +ctm_algs = [ + SequentialCTMRG(; projector_alg = :HalfInfiniteProjector), + SequentialCTMRG(; projector_alg = :FullInfiniteProjector), + SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector), + SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector), +] + +function test_unitcell( + ctm_alg, unitcell, + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) + peps = adapt(ROCArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) + env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) + + # apply one CTMRG iteration with fixeds + env′, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env, ctm_alg) + env″, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env′, ctm_alg) # another iteration to fix spaces + + # compute random expecation value to test matching bonds + random_op = adapt(ROCArray, LocalOperator( + Pspaces, + [ + (c,) => randn( + scalartype(peps), + Pspaces[c], Pspaces[c], + ) for c in CartesianIndices(unitcell) + ]..., + )) + @test expectation_value(peps, random_op, env) isa Number + @test expectation_value(peps, random_op, env′) isa Number + + # test if gauge fixing routines run through + signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env″, env′, ScramblingEnvGauge()) + @test signs isa Array + @test corner_phases isa Array + @test edge_phases isa Array + return nothing +end + +@testset "Random Cartesian spaces with $ctm_alg" for ctm_alg in ctm_algs + unitcell = (3, 3) + + Pspaces = ComplexSpace.(rand(2:3, unitcell...)) + Nspaces = ComplexSpace.(rand(2:4, unitcell...)) + Espaces = ComplexSpace.(rand(2:4, unitcell...)) + chis_north = ComplexSpace.(rand(5:10, unitcell...)) + chis_east = ComplexSpace.(rand(5:10, unitcell...)) + chis_south = ComplexSpace.(rand(5:10, unitcell...)) + chis_west = ComplexSpace.(rand(5:10, unitcell...)) + + test_unitcell( + ctm_alg, unitcell, + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) +end + +@testset "Specific U1 spaces with $ctm_alg" for ctm_alg in ctm_algs + unitcell = (2, 2) + + PA = U1Space(-1 => 1, 0 => 1) + PB = U1Space(0 => 1, 1 => 1) + Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) + Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) + + Pspaces = [PA PB; PB PA] + Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] + chis = [Venv Venv; Venv Venv] + + test_unitcell(ctm_alg, unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) + + # 4x4 unit cell with all 32 inequivalent bonds + # + # 10 4 7 32 + # | | | | + # 3--A--1--B--5--C--8--D--3 + # | | | | + # 2 6 9 11 + # | | | | + # 14--E-12--F-15--G-17--H-14 + # | | | | + # 13 16 18 19 + # | | | | + # 22--I-20--J-23--K-25--L-22 + # | | | | + # 21 24 26 27 + # | | | | + # 29--M-28--N-30--O-31--P-29 + # | | | | + # 10 4 7 32 + + phys_space = Vect[U1Irrep](1 => 1, -1 => 1) + corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) + vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) + @test length(Set(vspaces)) == 32 + + Espaces = [ + vspaces[1] vspaces[5] vspaces[8] vspaces[3] + vspaces[12] vspaces[15] vspaces[17] vspaces[14] + vspaces[20] vspaces[23] vspaces[25] vspaces[22] + vspaces[28] vspaces[30] vspaces[31] vspaces[29] + ] + + Nspaces = [ + vspaces[10] vspaces[4] vspaces[7] vspaces[32] + vspaces[2] vspaces[6] vspaces[9] vspaces[11] + vspaces[13] vspaces[16] vspaces[18] vspaces[19] + vspaces[21] vspaces[24] vspaces[26] vspaces[27] + ] + Pspaces = fill(phys_space, (4, 4)) + chis = fill(corner_space, (4, 4)) + + test_unitcell(ctm_alg, unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) +end diff --git a/test/cuda/ctmrg/unitcell.jl b/test/cuda/ctmrg/unitcell.jl new file mode 100644 index 000000000..0b5947d71 --- /dev/null +++ b/test/cuda/ctmrg/unitcell.jl @@ -0,0 +1,123 @@ +using Test +using Random +using PEPSKit +using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, ScramblingEnvGauge +using TensorKit +using CUDA, Adapt + +# settings +Random.seed!(91283219347) +stype = ComplexF64 +ctm_algs = [ + SequentialCTMRG(; projector_alg = :HalfInfiniteProjector), + SequentialCTMRG(; projector_alg = :FullInfiniteProjector), + SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector), + SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector), +] + +function test_unitcell( + ctm_alg, unitcell, + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) + peps = adapt(CuArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) + env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) + + # apply one CTMRG iteration with fixeds + env′, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env, ctm_alg) + env″, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env′, ctm_alg) # another iteration to fix spaces + + # compute random expecation value to test matching bonds + random_op = adapt(CuArray, LocalOperator( + Pspaces, + [ + (c,) => randn( + scalartype(peps), + Pspaces[c], Pspaces[c], + ) for c in CartesianIndices(unitcell) + ]..., + )) + @test expectation_value(peps, random_op, env) isa Number + @test expectation_value(peps, random_op, env′) isa Number + + # test if gauge fixing routines run through + signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env″, env′, ScramblingEnvGauge()) + @test signs isa Array + @test corner_phases isa Array + @test edge_phases isa Array + return nothing +end + +@testset "Random Cartesian spaces with $ctm_alg" for ctm_alg in ctm_algs + unitcell = (3, 3) + + Pspaces = ComplexSpace.(rand(2:3, unitcell...)) + Nspaces = ComplexSpace.(rand(2:4, unitcell...)) + Espaces = ComplexSpace.(rand(2:4, unitcell...)) + chis_north = ComplexSpace.(rand(5:10, unitcell...)) + chis_east = ComplexSpace.(rand(5:10, unitcell...)) + chis_south = ComplexSpace.(rand(5:10, unitcell...)) + chis_west = ComplexSpace.(rand(5:10, unitcell...)) + + test_unitcell( + ctm_alg, unitcell, + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) +end + +@testset "Specific U1 spaces with $ctm_alg" for ctm_alg in ctm_algs + unitcell = (2, 2) + + PA = U1Space(-1 => 1, 0 => 1) + PB = U1Space(0 => 1, 1 => 1) + Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) + Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) + + Pspaces = [PA PB; PB PA] + Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] + chis = [Venv Venv; Venv Venv] + + test_unitcell(ctm_alg, unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) + + # 4x4 unit cell with all 32 inequivalent bonds + # + # 10 4 7 32 + # | | | | + # 3--A--1--B--5--C--8--D--3 + # | | | | + # 2 6 9 11 + # | | | | + # 14--E-12--F-15--G-17--H-14 + # | | | | + # 13 16 18 19 + # | | | | + # 22--I-20--J-23--K-25--L-22 + # | | | | + # 21 24 26 27 + # | | | | + # 29--M-28--N-30--O-31--P-29 + # | | | | + # 10 4 7 32 + + phys_space = Vect[U1Irrep](1 => 1, -1 => 1) + corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) + vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) + @test length(Set(vspaces)) == 32 + + Espaces = [ + vspaces[1] vspaces[5] vspaces[8] vspaces[3] + vspaces[12] vspaces[15] vspaces[17] vspaces[14] + vspaces[20] vspaces[23] vspaces[25] vspaces[22] + vspaces[28] vspaces[30] vspaces[31] vspaces[29] + ] + + Nspaces = [ + vspaces[10] vspaces[4] vspaces[7] vspaces[32] + vspaces[2] vspaces[6] vspaces[9] vspaces[11] + vspaces[13] vspaces[16] vspaces[18] vspaces[19] + vspaces[21] vspaces[24] vspaces[26] vspaces[27] + ] + Pspaces = fill(phys_space, (4, 4)) + chis = fill(corner_space, (4, 4)) + + test_unitcell(ctm_alg, unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) +end From f059449d4f06deccd1565bfcba2a5fffda5b0f00 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 20:10:56 +0200 Subject: [PATCH 17/69] Working CTMRG/pepo --- test/amd/bp/unitcell.jl | 14 ++- test/amd/ctmrg/partition_function.jl | 2 +- test/amd/ctmrg/pepo.jl | 148 +++++++++++++++++++++++ test/amd/ctmrg/unitcell.jl | 20 +-- test/amd/timeevol/j1j2_finiteT.jl | 10 +- test/amd/timeevol/sitedep_truncation.jl | 4 +- test/cuda/bp/unitcell.jl | 14 ++- test/cuda/ctmrg/partition_function.jl | 2 +- test/cuda/ctmrg/pepo.jl | 148 +++++++++++++++++++++++ test/cuda/ctmrg/unitcell.jl | 20 +-- test/cuda/timeevol/j1j2_finiteT.jl | 10 +- test/cuda/timeevol/sitedep_truncation.jl | 4 +- 12 files changed, 352 insertions(+), 44 deletions(-) create mode 100644 test/amd/ctmrg/pepo.jl create mode 100644 test/cuda/ctmrg/pepo.jl diff --git a/test/amd/bp/unitcell.jl b/test/amd/bp/unitcell.jl index d0130f84e..8214d2f86 100644 --- a/test/amd/bp/unitcell.jl +++ b/test/amd/bp/unitcell.jl @@ -21,12 +21,14 @@ function test_unitcell(unitcell, Pspaces, Nspaces, Espaces) env1 = bp_iteration(network, env1, alg) # compute random expecation value to test matching bonds - random_op = adapt(ROCArray, LocalOperator( - Pspaces, ( - (c,) => randn(elt, Pspaces[c], Pspaces[c]) - for c in CartesianIndices(unitcell) - )..., - )) + random_op = adapt( + ROCArray, LocalOperator( + Pspaces, ( + (c,) => randn(elt, Pspaces[c], Pspaces[c]) + for c in CartesianIndices(unitcell) + )..., + ) + ) @test storagetype(random_op) <: ROCArray @test expectation_value(peps, random_op, env0) isa Number @test expectation_value(peps, random_op, env1) isa Number diff --git a/test/amd/ctmrg/partition_function.jl b/test/amd/ctmrg/partition_function.jl index 11d0d7d5a..dbc3586d7 100644 --- a/test/amd/ctmrg/partition_function.jl +++ b/test/amd/ctmrg/partition_function.jl @@ -113,7 +113,7 @@ args = [ ] # Basic properties -@test storagetype(Z) <: ROCArray +@test storagetype(Z) <: ROCArray @test spacetype(typeof(Z)) === ComplexSpace @test spacetype(Z) === ComplexSpace @test sectortype(typeof(Z)) === Trivial diff --git a/test/amd/ctmrg/pepo.jl b/test/amd/ctmrg/pepo.jl new file mode 100644 index 000000000..e687d757a --- /dev/null +++ b/test/amd/ctmrg/pepo.jl @@ -0,0 +1,148 @@ +using Test +using Random +using LinearAlgebra +using PEPSKit +using TensorKit +using KrylovKit +using OptimKit +using Zygote +using AMDGPU, Adapt +## Setup + +function three_dimensional_classical_ising(; beta, J = 1.0) + K = beta * J + + # Boltzmann weights + t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)] + r = eigen(t) + q = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors + + # local partition function tensor + O = zeros(2, 2, 2, 2, 2, 2) + O[1, 1, 1, 1, 1, 1] = 1 + O[2, 2, 2, 2, 2, 2] = 1 + @tensor o[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + + # magnetization tensor + M = copy(O) + M[2, 2, 2, 2, 2, 2] *= -1 + @tensor m[-1 -2; -3 -4 -5 -6] := + M[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + + # bond interaction tensor and energy-per-site tensor + e = ComplexF64[-J J; J -J] .* q + @tensor e_x[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * e[-4; 4] * q[-5; 5] * q[-6; 6] + @tensor e_y[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * e[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + @tensor e_z[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * e[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + e = e_x + e_y + e_z + + # fixed tensor map space for all three + TMS = ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)' + + return adapt(ROCArray, TensorMap(o, TMS)), adapt(ROCArray, TensorMap(m, TMS)), adapt(ROCArray, TensorMap(e, TMS)) +end + +## Test + +# initialize +beta = 0.2391 # slightly lower temperature than βc ≈ 0.2216544 +O, M, E = three_dimensional_classical_ising(; beta) +χpeps = ℂ^2 +χenv = ℂ^12 + +# cover all different flavors +ctm_styles = [:SequentialCTMRG, :SimultaneousCTMRG] +projector_algs = [:HalfInfiniteProjector, :FullInfiniteProjector] + +@testset "PEPO CTMRG runthroughs for unitcell=$(unitcell)" for unitcell in + [(1, 1, 1), (1, 1, 2)] + Random.seed!(81812781144) + + # contract + T = InfinitePEPO(O; unitcell = unitcell) + psi0 = initializePEPS(T, χpeps) + n = InfiniteSquareNetwork(psi0, T) + env0 = CTMRGEnv(n, χenv) + + @test spacetype(typeof(T)) === ComplexSpace + @test spacetype(T) === ComplexSpace + @test sectortype(typeof(T)) === Trivial + @test sectortype(T) === Trivial + @test storagetype(T) <: ROCArray + + @testset "PEPO CTMRG contraction using $alg with $projector_alg" for ( + alg, projector_alg, + ) in Iterators.product(ctm_styles, projector_algs) + env, = leading_boundary(env0, n; alg, maxiter = 150, projector_alg) + end +end + +@testset "Fixed-point computation for 3D classical ising model" begin + Random.seed!(81812781144) + + # prep + ctm_alg = SimultaneousCTMRG(; maxiter = 150, tol = 1.0e-8, verbosity = 2) + gradient_alg = FixedPointGradient(; + solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), + ) + opt_alg = LBFGS(32; maxiter = 50, gradtol = 1.0e-5, verbosity = 3) + function pepo_retract(x, η, α) + x´_partial, ξ = PEPSKit.peps_retract(x[1:2], η, α) + x´ = (x´_partial..., deepcopy(x[3])) + return x´, ξ + end + function pepo_transport!(ξ, x, η, α, x´) + return PEPSKit.peps_transport!(ξ, x[1:2], η, α, x´[1:2]) + end + + # contract + T = adapt(ROCArray, InfinitePEPO(O; unitcell = (1, 1, 1))) + psi0 = initializePEPS(T, χpeps) + n2 = InfiniteSquareNetwork(psi0) + @test storagetype(n2) <: ROCArray + env2_0 = CTMRGEnv(n2, χenv) + n3 = InfiniteSquareNetwork(psi0, T) + @test storagetype(n3) <: ROCArray + env3_0 = CTMRGEnv(n3, χenv) + + # optimize free energy per site + (psi_final, env2_final, env3_final), f, = optimize( + (psi0, env2_0, env3_0), + opt_alg; + inner = PEPSKit.real_inner, + retract = pepo_retract, + (transport!) = (pepo_transport!), + ) do (psi, env2, env3) + E, gs = withgradient(psi) do ψ + n2 = InfiniteSquareNetwork(ψ) + env2′, info = PEPSKit.hook_pullback( + leading_boundary, env2, n2, ctm_alg; alg_rrule = gradient_alg + ) + n3 = InfiniteSquareNetwork(ψ, T) + env3′, info = PEPSKit.hook_pullback( + leading_boundary, env3, n3, ctm_alg; alg_rrule = gradient_alg + ) + PEPSKit.ignore_derivatives() do + PEPSKit.update!(env2, env2′) + PEPSKit.update!(env3, env3′) + end + λ3 = network_value(n3, env3) + λ2 = network_value(n2, env2) + return -log(real(λ3 / λ2)) + end + g = only(gs) + return E, g + end + + # check energy + n3_final = InfiniteSquareNetwork(psi_final, T) + m = PEPSKit.contract_local_tensor((1, 1, 1), M, n3_final, env3_final) + nrm3 = PEPSKit._contract_site((1, 1), n3_final, env3_final) + + # compare to Monte-Carlo result from https://www.worldscientific.com/doi/abs/10.1142/S0129183101002383 + @test abs(m / nrm3) ≈ 0.667162 rtol = 1.0e-2 +end diff --git a/test/amd/ctmrg/unitcell.jl b/test/amd/ctmrg/unitcell.jl index 65a3f053f..2880aae82 100644 --- a/test/amd/ctmrg/unitcell.jl +++ b/test/amd/ctmrg/unitcell.jl @@ -27,15 +27,17 @@ function test_unitcell( env″, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env′, ctm_alg) # another iteration to fix spaces # compute random expecation value to test matching bonds - random_op = adapt(ROCArray, LocalOperator( - Pspaces, - [ - (c,) => randn( - scalartype(peps), - Pspaces[c], Pspaces[c], - ) for c in CartesianIndices(unitcell) - ]..., - )) + random_op = adapt( + ROCArray, LocalOperator( + Pspaces, + [ + (c,) => randn( + scalartype(peps), + Pspaces[c], Pspaces[c], + ) for c in CartesianIndices(unitcell) + ]..., + ) + ) @test expectation_value(peps, random_op, env) isa Number @test expectation_value(peps, random_op, env′) isa Number diff --git a/test/amd/timeevol/j1j2_finiteT.jl b/test/amd/timeevol/j1j2_finiteT.jl index fcef728c0..ea724b6ef 100644 --- a/test/amd/timeevol/j1j2_finiteT.jl +++ b/test/amd/timeevol/j1j2_finiteT.jl @@ -18,10 +18,12 @@ function converge_env(state, χ::Int) end Nr, Nc = 2, 2 -ham = adapt(ROCArray, j1_j2_model( - Float64, SU2Irrep, InfiniteSquare(Nr, Nc); - J1 = 1.0, J2 = 0.5, sublattice = false - )) +ham = adapt( + ROCArray, j1_j2_model( + Float64, SU2Irrep, InfiniteSquare(Nr, Nc); + J1 = 1.0, J2 = 0.5, sublattice = false + ) +) @test storagetype(ham) <: ROCArray pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) @test storagetype(pepo0) <: ROCArray diff --git a/test/amd/timeevol/sitedep_truncation.jl b/test/amd/timeevol/sitedep_truncation.jl index 2f6f1af32..a78d9bd21 100644 --- a/test/amd/timeevol/sitedep_truncation.jl +++ b/test/amd/timeevol/sitedep_truncation.jl @@ -23,8 +23,8 @@ Ves2 = [ Venv = U1Space(0 => 2, 1 => 1, -1 => 1) Random.seed!(48736) states = ( - adapt(ROCArray, InfinitePEPS(randn, elt, Vps, Vns, Ves1)), - adapt(ROCArray, InfinitePEPO(randn, elt, Vps, Vns, Ves2)), + adapt(ROCArray, InfinitePEPS(randn, elt, Vps, Vns, Ves1)), + adapt(ROCArray, InfinitePEPO(randn, elt, Vps, Vns, Ves2)), ) @testset "Rotation of SiteDependentTruncation" begin diff --git a/test/cuda/bp/unitcell.jl b/test/cuda/bp/unitcell.jl index 84a1f32e1..cb5105adf 100644 --- a/test/cuda/bp/unitcell.jl +++ b/test/cuda/bp/unitcell.jl @@ -21,12 +21,14 @@ function test_unitcell(unitcell, Pspaces, Nspaces, Espaces) env1 = bp_iteration(network, env1, alg) # compute random expecation value to test matching bonds - random_op = adapt(CuArray, LocalOperator( - Pspaces, ( - (c,) => randn(elt, Pspaces[c], Pspaces[c]) - for c in CartesianIndices(unitcell) - )..., - )) + random_op = adapt( + CuArray, LocalOperator( + Pspaces, ( + (c,) => randn(elt, Pspaces[c], Pspaces[c]) + for c in CartesianIndices(unitcell) + )..., + ) + ) @test storagetype(random_op) <: CuArray @test expectation_value(peps, random_op, env0) isa Number @test expectation_value(peps, random_op, env1) isa Number diff --git a/test/cuda/ctmrg/partition_function.jl b/test/cuda/ctmrg/partition_function.jl index 90979bd94..4ce4f515f 100644 --- a/test/cuda/ctmrg/partition_function.jl +++ b/test/cuda/ctmrg/partition_function.jl @@ -113,7 +113,7 @@ args = [ ] # Basic properties -@test storagetype(Z) <: CuArray +@test storagetype(Z) <: CuArray @test spacetype(typeof(Z)) === ComplexSpace @test spacetype(Z) === ComplexSpace @test sectortype(typeof(Z)) === Trivial diff --git a/test/cuda/ctmrg/pepo.jl b/test/cuda/ctmrg/pepo.jl new file mode 100644 index 000000000..462f9a990 --- /dev/null +++ b/test/cuda/ctmrg/pepo.jl @@ -0,0 +1,148 @@ +using Test +using Random +using LinearAlgebra +using PEPSKit +using TensorKit +using KrylovKit +using OptimKit +using Zygote +using CUDA, Adapt +## Setup + +function three_dimensional_classical_ising(; beta, J = 1.0) + K = beta * J + + # Boltzmann weights + t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)] + r = eigen(t) + q = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors + + # local partition function tensor + O = zeros(2, 2, 2, 2, 2, 2) + O[1, 1, 1, 1, 1, 1] = 1 + O[2, 2, 2, 2, 2, 2] = 1 + @tensor o[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + + # magnetization tensor + M = copy(O) + M[2, 2, 2, 2, 2, 2] *= -1 + @tensor m[-1 -2; -3 -4 -5 -6] := + M[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + + # bond interaction tensor and energy-per-site tensor + e = ComplexF64[-J J; J -J] .* q + @tensor e_x[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * e[-4; 4] * q[-5; 5] * q[-6; 6] + @tensor e_y[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * e[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + @tensor e_z[-1 -2; -3 -4 -5 -6] := + O[1 2; 3 4 5 6] * e[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] + e = e_x + e_y + e_z + + # fixed tensor map space for all three + TMS = ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)' + + return adapt(CuArray, TensorMap(o, TMS)), adapt(CuArray, TensorMap(m, TMS)), adapt(CuArray, TensorMap(e, TMS)) +end + +## Test + +# initialize +beta = 0.2391 # slightly lower temperature than βc ≈ 0.2216544 +O, M, E = three_dimensional_classical_ising(; beta) +χpeps = ℂ^2 +χenv = ℂ^12 + +# cover all different flavors +ctm_styles = [:SequentialCTMRG, :SimultaneousCTMRG] +projector_algs = [:HalfInfiniteProjector, :FullInfiniteProjector] + +@testset "PEPO CTMRG runthroughs for unitcell=$(unitcell)" for unitcell in + [(1, 1, 1), (1, 1, 2)] + Random.seed!(81812781144) + + # contract + T = InfinitePEPO(O; unitcell = unitcell) + psi0 = initializePEPS(T, χpeps) + n = InfiniteSquareNetwork(psi0, T) + env0 = CTMRGEnv(n, χenv) + + @test spacetype(typeof(T)) === ComplexSpace + @test spacetype(T) === ComplexSpace + @test sectortype(typeof(T)) === Trivial + @test sectortype(T) === Trivial + @test storagetype(T) <: CuArray + + @testset "PEPO CTMRG contraction using $alg with $projector_alg" for ( + alg, projector_alg, + ) in Iterators.product(ctm_styles, projector_algs) + env, = leading_boundary(env0, n; alg, maxiter = 150, projector_alg) + end +end + +@testset "Fixed-point computation for 3D classical ising model" begin + Random.seed!(81812781144) + + # prep + ctm_alg = SimultaneousCTMRG(; maxiter = 150, tol = 1.0e-8, verbosity = 2) + gradient_alg = FixedPointGradient(; + solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), + ) + opt_alg = LBFGS(32; maxiter = 50, gradtol = 1.0e-5, verbosity = 3) + function pepo_retract(x, η, α) + x´_partial, ξ = PEPSKit.peps_retract(x[1:2], η, α) + x´ = (x´_partial..., deepcopy(x[3])) + return x´, ξ + end + function pepo_transport!(ξ, x, η, α, x´) + return PEPSKit.peps_transport!(ξ, x[1:2], η, α, x´[1:2]) + end + + # contract + T = adapt(CuArray, InfinitePEPO(O; unitcell = (1, 1, 1))) + psi0 = initializePEPS(T, χpeps) + n2 = InfiniteSquareNetwork(psi0) + @test storagetype(n2) <: CuArray + env2_0 = CTMRGEnv(n2, χenv) + n3 = InfiniteSquareNetwork(psi0, T) + @test storagetype(n3) <: CuArray + env3_0 = CTMRGEnv(n3, χenv) + + # optimize free energy per site + (psi_final, env2_final, env3_final), f, = optimize( + (psi0, env2_0, env3_0), + opt_alg; + inner = PEPSKit.real_inner, + retract = pepo_retract, + (transport!) = (pepo_transport!), + ) do (psi, env2, env3) + E, gs = withgradient(psi) do ψ + n2 = InfiniteSquareNetwork(ψ) + env2′, info = PEPSKit.hook_pullback( + leading_boundary, env2, n2, ctm_alg; alg_rrule = gradient_alg + ) + n3 = InfiniteSquareNetwork(ψ, T) + env3′, info = PEPSKit.hook_pullback( + leading_boundary, env3, n3, ctm_alg; alg_rrule = gradient_alg + ) + PEPSKit.ignore_derivatives() do + PEPSKit.update!(env2, env2′) + PEPSKit.update!(env3, env3′) + end + λ3 = network_value(n3, env3) + λ2 = network_value(n2, env2) + return -log(real(λ3 / λ2)) + end + g = only(gs) + return E, g + end + + # check energy + n3_final = InfiniteSquareNetwork(psi_final, T) + m = PEPSKit.contract_local_tensor((1, 1, 1), M, n3_final, env3_final) + nrm3 = PEPSKit._contract_site((1, 1), n3_final, env3_final) + + # compare to Monte-Carlo result from https://www.worldscientific.com/doi/abs/10.1142/S0129183101002383 + @test abs(m / nrm3) ≈ 0.667162 rtol = 1.0e-2 +end diff --git a/test/cuda/ctmrg/unitcell.jl b/test/cuda/ctmrg/unitcell.jl index 0b5947d71..69dbeee6b 100644 --- a/test/cuda/ctmrg/unitcell.jl +++ b/test/cuda/ctmrg/unitcell.jl @@ -27,15 +27,17 @@ function test_unitcell( env″, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env′, ctm_alg) # another iteration to fix spaces # compute random expecation value to test matching bonds - random_op = adapt(CuArray, LocalOperator( - Pspaces, - [ - (c,) => randn( - scalartype(peps), - Pspaces[c], Pspaces[c], - ) for c in CartesianIndices(unitcell) - ]..., - )) + random_op = adapt( + CuArray, LocalOperator( + Pspaces, + [ + (c,) => randn( + scalartype(peps), + Pspaces[c], Pspaces[c], + ) for c in CartesianIndices(unitcell) + ]..., + ) + ) @test expectation_value(peps, random_op, env) isa Number @test expectation_value(peps, random_op, env′) isa Number diff --git a/test/cuda/timeevol/j1j2_finiteT.jl b/test/cuda/timeevol/j1j2_finiteT.jl index 95bca9024..bda0b4346 100644 --- a/test/cuda/timeevol/j1j2_finiteT.jl +++ b/test/cuda/timeevol/j1j2_finiteT.jl @@ -18,10 +18,12 @@ function converge_env(state, χ::Int) end Nr, Nc = 2, 2 -ham = adapt(CuArray, j1_j2_model( - Float64, SU2Irrep, InfiniteSquare(Nr, Nc); - J1 = 1.0, J2 = 0.5, sublattice = false - )) +ham = adapt( + CuArray, j1_j2_model( + Float64, SU2Irrep, InfiniteSquare(Nr, Nc); + J1 = 1.0, J2 = 0.5, sublattice = false + ) +) @test storagetype(ham) <: CuArray pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) @test storagetype(pepo0) <: CuArray diff --git a/test/cuda/timeevol/sitedep_truncation.jl b/test/cuda/timeevol/sitedep_truncation.jl index 482780780..b3adc8851 100644 --- a/test/cuda/timeevol/sitedep_truncation.jl +++ b/test/cuda/timeevol/sitedep_truncation.jl @@ -23,8 +23,8 @@ Ves2 = [ Venv = U1Space(0 => 2, 1 => 1, -1 => 1) Random.seed!(48736) states = ( - adapt(CuArray, InfinitePEPS(randn, elt, Vps, Vns, Ves1)), - adapt(CuArray, InfinitePEPO(randn, elt, Vps, Vns, Ves2)), + adapt(CuArray, InfinitePEPS(randn, elt, Vps, Vns, Ves1)), + adapt(CuArray, InfinitePEPO(randn, elt, Vps, Vns, Ves2)), ) @testset "Rotation of SiteDependentTruncation" begin From 12368e539bc7834f1dfdaa6e3dba53223ee8afb9 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 10:25:20 +0200 Subject: [PATCH 18/69] Try simplifying the test script --- .buildkite/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index e6cbf14eb..04b807804 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -23,7 +23,7 @@ steps: - "1.13" queue: - "cuda" - #- "rocm" # disable for now as these are timing out + - "rocm" group: - "boundarymps" - "bondenv" From 17ec8943ec284cefb01cc225c33211deea9a4377 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 10:40:17 +0200 Subject: [PATCH 19/69] Adjust paths --- .buildkite/pipeline.yml | 2 +- test/{amd => rocm}/boundarymps/vumps.jl | 0 test/{amd => rocm}/bp/rotation.jl | 0 test/{amd => rocm}/bp/unitcell.jl | 0 test/{amd => rocm}/ctmrg/contractions.jl | 0 test/{amd => rocm}/ctmrg/fixed_iterscheme.jl | 0 test/{amd => rocm}/ctmrg/flavors.jl | 0 test/{amd => rocm}/ctmrg/gaugefix.jl | 0 test/{amd => rocm}/ctmrg/initialization.jl | 0 test/{amd => rocm}/ctmrg/partition_function.jl | 0 test/{amd => rocm}/ctmrg/pepo.jl | 0 test/{amd => rocm}/ctmrg/unitcell.jl | 0 test/{amd => rocm}/timeevol/cluster_projectors.jl | 0 test/{amd => rocm}/timeevol/j1j2_finiteT.jl | 0 test/{amd => rocm}/timeevol/sitedep_truncation.jl | 0 test/{amd => rocm}/timeevol/tf_ising_finiteT.jl | 0 test/{amd => rocm}/timeevol/timestep.jl | 0 test/{amd => rocm}/toolbox/densitymatrices.jl | 0 18 files changed, 1 insertion(+), 1 deletion(-) rename test/{amd => rocm}/boundarymps/vumps.jl (100%) rename test/{amd => rocm}/bp/rotation.jl (100%) rename test/{amd => rocm}/bp/unitcell.jl (100%) rename test/{amd => rocm}/ctmrg/contractions.jl (100%) rename test/{amd => rocm}/ctmrg/fixed_iterscheme.jl (100%) rename test/{amd => rocm}/ctmrg/flavors.jl (100%) rename test/{amd => rocm}/ctmrg/gaugefix.jl (100%) rename test/{amd => rocm}/ctmrg/initialization.jl (100%) rename test/{amd => rocm}/ctmrg/partition_function.jl (100%) rename test/{amd => rocm}/ctmrg/pepo.jl (100%) rename test/{amd => rocm}/ctmrg/unitcell.jl (100%) rename test/{amd => rocm}/timeevol/cluster_projectors.jl (100%) rename test/{amd => rocm}/timeevol/j1j2_finiteT.jl (100%) rename test/{amd => rocm}/timeevol/sitedep_truncation.jl (100%) rename test/{amd => rocm}/timeevol/tf_ising_finiteT.jl (100%) rename test/{amd => rocm}/timeevol/timestep.jl (100%) rename test/{amd => rocm}/toolbox/densitymatrices.jl (100%) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 04b807804..9323d280d 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -7,7 +7,7 @@ steps: - JuliaCI/julia#v1: version: "{{matrix.julia}}" - JuliaCI/julia-test#v1: - test_args: "{{matrix.group}}" + test_args: "{{matrix.queue}}/{{matrix.group}}" - JuliaCI/julia-coverage#v1: dirs: - src diff --git a/test/amd/boundarymps/vumps.jl b/test/rocm/boundarymps/vumps.jl similarity index 100% rename from test/amd/boundarymps/vumps.jl rename to test/rocm/boundarymps/vumps.jl diff --git a/test/amd/bp/rotation.jl b/test/rocm/bp/rotation.jl similarity index 100% rename from test/amd/bp/rotation.jl rename to test/rocm/bp/rotation.jl diff --git a/test/amd/bp/unitcell.jl b/test/rocm/bp/unitcell.jl similarity index 100% rename from test/amd/bp/unitcell.jl rename to test/rocm/bp/unitcell.jl diff --git a/test/amd/ctmrg/contractions.jl b/test/rocm/ctmrg/contractions.jl similarity index 100% rename from test/amd/ctmrg/contractions.jl rename to test/rocm/ctmrg/contractions.jl diff --git a/test/amd/ctmrg/fixed_iterscheme.jl b/test/rocm/ctmrg/fixed_iterscheme.jl similarity index 100% rename from test/amd/ctmrg/fixed_iterscheme.jl rename to test/rocm/ctmrg/fixed_iterscheme.jl diff --git a/test/amd/ctmrg/flavors.jl b/test/rocm/ctmrg/flavors.jl similarity index 100% rename from test/amd/ctmrg/flavors.jl rename to test/rocm/ctmrg/flavors.jl diff --git a/test/amd/ctmrg/gaugefix.jl b/test/rocm/ctmrg/gaugefix.jl similarity index 100% rename from test/amd/ctmrg/gaugefix.jl rename to test/rocm/ctmrg/gaugefix.jl diff --git a/test/amd/ctmrg/initialization.jl b/test/rocm/ctmrg/initialization.jl similarity index 100% rename from test/amd/ctmrg/initialization.jl rename to test/rocm/ctmrg/initialization.jl diff --git a/test/amd/ctmrg/partition_function.jl b/test/rocm/ctmrg/partition_function.jl similarity index 100% rename from test/amd/ctmrg/partition_function.jl rename to test/rocm/ctmrg/partition_function.jl diff --git a/test/amd/ctmrg/pepo.jl b/test/rocm/ctmrg/pepo.jl similarity index 100% rename from test/amd/ctmrg/pepo.jl rename to test/rocm/ctmrg/pepo.jl diff --git a/test/amd/ctmrg/unitcell.jl b/test/rocm/ctmrg/unitcell.jl similarity index 100% rename from test/amd/ctmrg/unitcell.jl rename to test/rocm/ctmrg/unitcell.jl diff --git a/test/amd/timeevol/cluster_projectors.jl b/test/rocm/timeevol/cluster_projectors.jl similarity index 100% rename from test/amd/timeevol/cluster_projectors.jl rename to test/rocm/timeevol/cluster_projectors.jl diff --git a/test/amd/timeevol/j1j2_finiteT.jl b/test/rocm/timeevol/j1j2_finiteT.jl similarity index 100% rename from test/amd/timeevol/j1j2_finiteT.jl rename to test/rocm/timeevol/j1j2_finiteT.jl diff --git a/test/amd/timeevol/sitedep_truncation.jl b/test/rocm/timeevol/sitedep_truncation.jl similarity index 100% rename from test/amd/timeevol/sitedep_truncation.jl rename to test/rocm/timeevol/sitedep_truncation.jl diff --git a/test/amd/timeevol/tf_ising_finiteT.jl b/test/rocm/timeevol/tf_ising_finiteT.jl similarity index 100% rename from test/amd/timeevol/tf_ising_finiteT.jl rename to test/rocm/timeevol/tf_ising_finiteT.jl diff --git a/test/amd/timeevol/timestep.jl b/test/rocm/timeevol/timestep.jl similarity index 100% rename from test/amd/timeevol/timestep.jl rename to test/rocm/timeevol/timestep.jl diff --git a/test/amd/toolbox/densitymatrices.jl b/test/rocm/toolbox/densitymatrices.jl similarity index 100% rename from test/amd/toolbox/densitymatrices.jl rename to test/rocm/toolbox/densitymatrices.jl From 7811018b151837f0f03342a73a9672a401486939 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 11:01:06 +0200 Subject: [PATCH 20/69] Fix runtests.jl --- test/cuda/compress/local.jl | 62 +++++++++++++++++++++++++++++++++++++ test/rocm/compress/local.jl | 62 +++++++++++++++++++++++++++++++++++++ test/runtests.jl | 4 +-- 3 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 test/cuda/compress/local.jl create mode 100644 test/rocm/compress/local.jl diff --git a/test/cuda/compress/local.jl b/test/cuda/compress/local.jl new file mode 100644 index 000000000..8a877f1bb --- /dev/null +++ b/test/cuda/compress/local.jl @@ -0,0 +1,62 @@ +using Test +using Random +using LinearAlgebra +using TensorKit +using PEPSKit +using PEPSKit: virtual_projector +using CUDA, Adapt + +""" +Cost function of LocalTruncation. +For test convenience, open virtual indices are made trivial and removed. +""" +function localcompress_cost(A1, A2, B1, B2, P1, P2) + @tensor net1[pa1 pb1; pa2′ pb2′] := + A1[pa1 pa; D1] * A2[pa pa2′; D2] * B1[pb1 pb; D1] * B2[pb pb2′; D2] + @tensor net2[pa1 pb1; pa2′ pb2′] := P1[Da1 Da2; D] * P2[D; Db1 Db2] * + A1[pa1 pa; Da1] * A2[pa pa2′; Da2] * B1[pb1 pb; Db1] * B2[pb pb2′; Db2] + return norm(net1 - net2) +end + +@testset "Fermionic twists" begin + Vphy = Vect[FermionParity](0 => 2, 1 => 2) + Vvir = Vect[FermionParity](0 => 2, 1 => 2) + for _ in 1:4 # multiple trials without setting seed + Aspace = (Vphy ⊗ Vphy' ← Vvir ⊗ Vvir ⊗ Vvir' ⊗ Vvir') + A1 = adapt(CuArray, randn(ComplexF64, Aspace)) + A2 = adapt(CuArray, randn(ComplexF64, Aspace)) + for MM in [PEPSKit._get_MMdag(A1, A2), PEPSKit._get_MdagM(A1, A2)] + @test isposdef(MM) + end + end +end + +@testset "Cost function of LocalTruncation" begin + Random.seed!(0) + Vaux, Vphy, V = ℂ^1, ℂ^10, ℂ^4 + A1 = adapt(CuArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ V ⊗ Vaux' ⊗ Vaux'), Inf)) + A2 = adapt(CuArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ V ⊗ Vaux' ⊗ Vaux'), Inf)) + B1 = adapt(CuArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ Vaux ⊗ Vaux' ⊗ V'), Inf)) + B2 = adapt(CuArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ Vaux ⊗ Vaux' ⊗ V'), Inf)) + + P1, P2, info = virtual_projector(A1, A2, B1, B2; trunc = notrunc()) + @test P1 * P2 ≈ adapt(CuArray, TensorKit.id(domain(P2))) + + P1, P2, info = virtual_projector(A1, A2, B1, B2; trunc = truncrank(8)) + A1 = removeunit(removeunit(removeunit(A1, 6), 5), 3) + A2 = removeunit(removeunit(removeunit(A2, 6), 5), 3) + B1 = removeunit(removeunit(removeunit(B1, 5), 4), 3) + B2 = removeunit(removeunit(removeunit(B2, 5), 4), 3) + @info "Truncation error = $(info.ϵ)." + @test info.ϵ ≈ localcompress_cost(A1, A2, B1, B2, P1, P2) +end + +@testset "Virtual space matching" begin + Vps = ComplexSpace.([2 2; 2 2]) + Vns = ComplexSpace.([2 4; 5 3]) + Ves = ComplexSpace.([3 5; 4 2]) + ρ = adapt(CuArray, InfinitePEPO(randn, ComplexF64, Vps, Vns, Ves)) + alg = LocalTruncation(truncrank(2)) + ρ2, = compress((ρ, ρ), alg) + @test ρ2 isa InfinitePEPO +end diff --git a/test/rocm/compress/local.jl b/test/rocm/compress/local.jl new file mode 100644 index 000000000..5a97c8d85 --- /dev/null +++ b/test/rocm/compress/local.jl @@ -0,0 +1,62 @@ +using Test +using Random +using LinearAlgebra +using TensorKit +using PEPSKit +using PEPSKit: virtual_projector +using AMDGPU, Adapt + +""" +Cost function of LocalTruncation. +For test convenience, open virtual indices are made trivial and removed. +""" +function localcompress_cost(A1, A2, B1, B2, P1, P2) + @tensor net1[pa1 pb1; pa2′ pb2′] := + A1[pa1 pa; D1] * A2[pa pa2′; D2] * B1[pb1 pb; D1] * B2[pb pb2′; D2] + @tensor net2[pa1 pb1; pa2′ pb2′] := P1[Da1 Da2; D] * P2[D; Db1 Db2] * + A1[pa1 pa; Da1] * A2[pa pa2′; Da2] * B1[pb1 pb; Db1] * B2[pb pb2′; Db2] + return norm(net1 - net2) +end + +@testset "Fermionic twists" begin + Vphy = Vect[FermionParity](0 => 2, 1 => 2) + Vvir = Vect[FermionParity](0 => 2, 1 => 2) + for _ in 1:4 # multiple trials without setting seed + Aspace = (Vphy ⊗ Vphy' ← Vvir ⊗ Vvir ⊗ Vvir' ⊗ Vvir') + A1 = adapt(ROCArray, randn(ComplexF64, Aspace)) + A2 = adapt(ROCArray, randn(ComplexF64, Aspace)) + for MM in [PEPSKit._get_MMdag(A1, A2), PEPSKit._get_MdagM(A1, A2)] + @test isposdef(MM) + end + end +end + +@testset "Cost function of LocalTruncation" begin + Random.seed!(0) + Vaux, Vphy, V = ℂ^1, ℂ^10, ℂ^4 + A1 = adapt(ROCArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ V ⊗ Vaux' ⊗ Vaux'), Inf)) + A2 = adapt(ROCArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ V ⊗ Vaux' ⊗ Vaux'), Inf)) + B1 = adapt(ROCArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ Vaux ⊗ Vaux' ⊗ V'), Inf)) + B2 = adapt(ROCArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ Vaux ⊗ Vaux' ⊗ V'), Inf)) + + P1, P2, info = virtual_projector(A1, A2, B1, B2; trunc = notrunc()) + @test P1 * P2 ≈ adapt(ROCArray, TensorKit.id(domain(P2))) + + P1, P2, info = virtual_projector(A1, A2, B1, B2; trunc = truncrank(8)) + A1 = removeunit(removeunit(removeunit(A1, 6), 5), 3) + A2 = removeunit(removeunit(removeunit(A2, 6), 5), 3) + B1 = removeunit(removeunit(removeunit(B1, 5), 4), 3) + B2 = removeunit(removeunit(removeunit(B2, 5), 4), 3) + @info "Truncation error = $(info.ϵ)." + @test info.ϵ ≈ localcompress_cost(A1, A2, B1, B2, P1, P2) +end + +@testset "Virtual space matching" begin + Vps = ComplexSpace.([2 2; 2 2]) + Vns = ComplexSpace.([2 4; 5 3]) + Ves = ComplexSpace.([3 5; 4 2]) + ρ = adapt(ROCArray, InfinitePEPO(randn, ComplexF64, Vps, Vns, Ves)) + alg = LocalTruncation(truncrank(2)) + ρ2, = compress((ρ, ρ), alg) + @test ρ2 isa InfinitePEPO +end diff --git a/test/runtests.jl b/test/runtests.jl index 7be097c9c..1176447fe 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,11 +11,11 @@ using CUDA: CUDA CUDA.functional() || filter!(!startswith("cuda") ∘ first, testsuite) # AMDGPU tests: only run if AMDGPU is functional using AMDGPU -AMDGPU.functional() || filter!(!startswith("amd") ∘ first, testsuite) +AMDGPU.functional() || filter!(!startswith("rocm") ∘ first, testsuite) # On Buildkite (GPU CI runner): only run CUDA and AMDGPU tests if get(ENV, "BUILDKITE", "false") == "true" - f(str) = startswith(first(str), "cuda") || startswith(first(str), "amd") + f(str) = startswith(first(str), "cuda") || startswith(first(str), "rocm") filter!(f, testsuite) end From 6d6c878786743ba69d6311fb038a596ae11efc74 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 11:52:45 +0200 Subject: [PATCH 21/69] Soothe VUMPS on 1.12 --- src/PEPSKit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 3b1943076..25d3f200b 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -24,7 +24,7 @@ using KrylovKit using KrylovKit: Lanczos, BlockLanczos using TensorOperations, OptimKit -using TensorOperations: DefaultAllocator, DefaultBackend, tensoralloc_add +using TensorOperations: AbstractBackend, DefaultBackend, DefaultAllocator, tensoralloc_add using TensorKit.Strided: StridedView, sreshape import OptimKit: fixedpoint using ChainRulesCore, Zygote From 89c6b17d993d5d721d638dfefcd9f83e1e74e9b1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 11:40:22 +0200 Subject: [PATCH 22/69] Bondenv tests --- test/cuda/bondenv/benv_ctm.jl | 68 ++++++++++++++++++++++++++++++ test/cuda/bondenv/benv_gaugefix.jl | 44 +++++++++++++++++++ test/cuda/bondenv/bond_truncate.jl | 54 ++++++++++++++++++++++++ test/rocm/bondenv/benv_ctm.jl | 68 ++++++++++++++++++++++++++++++ test/rocm/bondenv/benv_gaugefix.jl | 44 +++++++++++++++++++ test/rocm/bondenv/bond_truncate.jl | 54 ++++++++++++++++++++++++ 6 files changed, 332 insertions(+) create mode 100644 test/cuda/bondenv/benv_ctm.jl create mode 100644 test/cuda/bondenv/benv_gaugefix.jl create mode 100644 test/cuda/bondenv/bond_truncate.jl create mode 100644 test/rocm/bondenv/benv_ctm.jl create mode 100644 test/rocm/bondenv/benv_gaugefix.jl create mode 100644 test/rocm/bondenv/bond_truncate.jl diff --git a/test/cuda/bondenv/benv_ctm.jl b/test/cuda/bondenv/benv_ctm.jl new file mode 100644 index 000000000..969f7c6d0 --- /dev/null +++ b/test/cuda/bondenv/benv_ctm.jl @@ -0,0 +1,68 @@ +using Test +using TensorKit +using PEPSKit +using LinearAlgebra +using Random +using CUDA, Adapt + +Random.seed!(100) +Nr, Nc = 2, 2 +Envspace = Vect[FermionParity ⊠ U1Irrep]( + (0, 0) => 4, (1, 1 // 2) => 1, (1, -1 // 2) => 1, (0, 1) => 1, (0, -1) => 1 +) +trunc_state = truncerror(; atol = 1.0e-10) & truncrank(4) +ctm_alg = SequentialCTMRG(; tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(8)) +# create Hubbard iPEPS using simple update +function get_hubbard_peps(t::Float64 = 1.0, U::Float64 = 8.0) + H = adapt(CuArray, hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)) + Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) + peps = adapt(CuArray, InfinitePEPS(rand, ComplexF64, Vphy, Vphy; unitcell = (Nr, Nc))) + wts = SUWeight(peps) + alg = SimpleUpdate(; trunc = trunc_state) + evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts) + peps, = time_evolve(evolver, H; tol = 1.0e-8, verbosity = 1, check_interval = 2000) + normalize!.(peps.A, Inf) + return peps +end + +function get_hubbard_pepo(t::Float64 = 1.0, U::Float64 = 8.0) + H = adapt(CuArray, hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)) + pepo = PEPSKit.infinite_temperature_density_matrix(H) + wts = SUWeight(pepo) + alg = SimpleUpdate(; trunc = trunc_state, bipartite = false) + pepo, = time_evolve(pepo, H, 2.0e-3, 500, alg, wts; verbosity = 1, check_interval = 100) + normalize!.(pepo.A, Inf) + return pepo +end + +function test_benv_ctm(state::Union{InfinitePEPS, InfinitePEPO}) + network = isa(state, InfinitePEPS) ? state : InfinitePEPS(state) + env, = leading_boundary(CTMRGEnv(rand, ComplexF64, network, Envspace), network, ctm_alg) + for row in 1:Nr, col in 1:Nc + cp1 = col + 1 + A, B = state[row, col], state[row, cp1] + a, X = PEPSKit.bond_tensor_first(A) + b, Y = PEPSKit.bond_tensor_last(B) + benv = PEPSKit.bondenv_ctm(row, col, X, Y, env) + Z = PEPSKit.positive_approx(benv) + # verify that gauge fixing can greatly reduce + # condition number for physical state bond envs + cond1 = cond(Z' * Z) + Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) + benv2 = Z2' * Z2 + cond2 = cond(benv2) + @test 1 <= cond2 < cond1 + @info "benv cond number: (gauge-fixed) $(cond2) ≤ $(cond1) (initial)" + # verify gauge fixing is done correctly + @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] + @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] + @test half ≈ half2 + end + return +end + +peps = get_hubbard_peps() +pepo = get_hubbard_pepo() +for state in (peps, pepo) + test_benv_ctm(state) +end diff --git a/test/cuda/bondenv/benv_gaugefix.jl b/test/cuda/bondenv/benv_gaugefix.jl new file mode 100644 index 000000000..bb78584be --- /dev/null +++ b/test/cuda/bondenv/benv_gaugefix.jl @@ -0,0 +1,44 @@ +using Test +using TensorKit +using PEPSKit +using LinearAlgebra +using Random +using CUDA, Adapt + +Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 1, (1, -1) => 2) +Vin = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 3, (1, -1) => 2) +V = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 2, (1, -1) => 3) +Vs = (V, V') +for V1 in Vs, V2 in Vs, V3 in Vs + #= + ┌---┬---------------┬---┐ + | | | | ┌--------------┐ + ├---X--- -2 -3 ---Y---┤ = | | + | | | | └--Z-- -2 -3 -┘ + └---┴-------Z0------┴---┘ ↓ + ↓ -1 + -1 + =# + X = adapt(CuArray, rand(ComplexF64, Vin ⊗ V1' ⊗ Vin' ⊗ Vin)) + Y = adapt(CuArray, rand(ComplexF64, Vin ⊗ Vin ⊗ Vin' ⊗ V3)) + Z0 = adapt(CuArray, randn(ComplexF64, Vphy ← Vin ⊗ Vin' ⊗ Vin ⊗ Vin ⊗ Vin ⊗ Vin')) + @tensor Z[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X[Xn Xe Xs Xw] * Y[Yn Ye Ys Yw] + #= + ┌---------------------------┐ + | | + └---Z-- 1 --a-- 2 --b-- 3 --┘ + ↓ ↓ ↓ + -1 -2 -3 + =# + a = adapt(CuArray, randn(ComplexF64, V1 ⊗ Vphy ← V2)) + b = adapt(CuArray, randn(ComplexF64, V2 ⊗ Vphy ← V3)) + @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] + Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) + @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] + @test half ≈ half2 + # test gauge transformation of X, Y + X2 = PEPSKit._fixgauge_benvX(X, Rinv) + Y2 = PEPSKit._fixgauge_benvY(Y, Linv) + @tensor Z2_[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X2[Xn Xe Xs Xw] * Y2[Yn Ye Ys Yw] + @test Z2 ≈ Z2_ +end diff --git a/test/cuda/bondenv/bond_truncate.jl b/test/cuda/bondenv/bond_truncate.jl new file mode 100644 index 000000000..9a78d44fe --- /dev/null +++ b/test/cuda/bondenv/bond_truncate.jl @@ -0,0 +1,54 @@ +using Random +using Test +using TensorKit +using PEPSKit +using LinearAlgebra +using PEPSKit: bond_truncate, cost_function_als +using PEPSKit: _combine_ket, _combine_ket_for_svd +using CUDA, Adapt + +Random.seed!(0) +maxiter = 600 +check_interval = 30 +elt = ComplexF64 +# simulating the situation of applying a 2-site gate +# to a bond with virtual dimension D, physical dimension d. +d, D = 2, 4 +trunc = truncerror(; atol = 1.0e-10) & truncrank(D) +Vphy = Vect[FermionParity](0 => div(d, 2), 1 => div(d, 2)) +Vqro = Vect[FermionParity](0 => div(d * D, 2), 1 => div(d * D, 2)) +# virtual dimension of gate MPO is d^2 +Vint = Vect[FermionParity](0 => div(d^2 * D, 2), 1 => div(d^2 * D, 2)) +for Vl in (Vqro, Vqro'), Vr in (Vqro, Vqro') + # random positive-definite environment + Vbond = Vl ⊗ Vr + Dext = dim(Vbond) + Vext = Vect[FermionParity](0 => div(Dext, 2) + 1, 1 => div(Dext, 2) + 1) + Z = adapt(CuArray, randn(elt, Vext ← Vbond)) + normalize!(Z, Inf) + benv = Z' * Z + @info "Dimension of benv = $(Dext)" + # untruncated bond tensors + a2 = adapt(CuArray, randn(elt, Vl ⊗ Vphy ← Vint)) + b2 = adapt(CuArray, randn(elt, Vint ⊗ Vphy ← Vr')) + # bond tensor (truncated SVD initialization) + a2b2 = _combine_ket(a2, b2) + a0, s, b0 = svd_trunc(permute(a2b2, ((1, 3), (4, 2))); trunc = trunc) + a0, b0 = PEPSKit.absorb_s(a0, s, b0) + b0 = permute(b0, ((1, 2), (3,))) + fid0 = cost_function_als(benv, _combine_ket(a0, b0), a2b2)[2] + @info "Fidelity of simple SVD truncation = $fid0.\n" + ss = Dict{String, DiagonalTensorMap}() + # FET is slower when d is large + for (label, alg) in ( + ("ALS", ALSTruncation(; trunc, maxiter, check_interval)), + ("FET", FullEnvTruncation(; trunc, maxiter, check_interval, trunc_init = false)), + ) + a1, ss[label], b1, info = bond_truncate(a2, b2, benv, alg) + @info "$label improved fidelity = $(info.fid)." + # display(ss[label]) + @test info.fid ≈ cost_function_als(benv, _combine_ket(a1, b1), a2b2)[2] + @test info.fid > fid0 + end + @test isapprox(ss["ALS"], ss["FET"], atol = 1.0e-3) +end diff --git a/test/rocm/bondenv/benv_ctm.jl b/test/rocm/bondenv/benv_ctm.jl new file mode 100644 index 000000000..786dd6f7c --- /dev/null +++ b/test/rocm/bondenv/benv_ctm.jl @@ -0,0 +1,68 @@ +using Test +using TensorKit +using PEPSKit +using LinearAlgebra +using Random +using AMDGPU, Adapt + +Random.seed!(100) +Nr, Nc = 2, 2 +Envspace = Vect[FermionParity ⊠ U1Irrep]( + (0, 0) => 4, (1, 1 // 2) => 1, (1, -1 // 2) => 1, (0, 1) => 1, (0, -1) => 1 +) +trunc_state = truncerror(; atol = 1.0e-10) & truncrank(4) +ctm_alg = SequentialCTMRG(; tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(8)) +# create Hubbard iPEPS using simple update +function get_hubbard_peps(t::Float64 = 1.0, U::Float64 = 8.0) + H = adapt(ROCArray, hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)) + Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) + peps = adapt(ROCArray, InfinitePEPS(rand, ComplexF64, Vphy, Vphy; unitcell = (Nr, Nc))) + wts = SUWeight(peps) + alg = SimpleUpdate(; trunc = trunc_state) + evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts) + peps, = time_evolve(evolver, H; tol = 1.0e-8, verbosity = 1, check_interval = 2000) + normalize!.(peps.A, Inf) + return peps +end + +function get_hubbard_pepo(t::Float64 = 1.0, U::Float64 = 8.0) + H = adapt(ROCArray, hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)) + pepo = PEPSKit.infinite_temperature_density_matrix(H) + wts = SUWeight(pepo) + alg = SimpleUpdate(; trunc = trunc_state, bipartite = false) + pepo, = time_evolve(pepo, H, 2.0e-3, 500, alg, wts; verbosity = 1, check_interval = 100) + normalize!.(pepo.A, Inf) + return pepo +end + +function test_benv_ctm(state::Union{InfinitePEPS, InfinitePEPO}) + network = isa(state, InfinitePEPS) ? state : InfinitePEPS(state) + env, = leading_boundary(CTMRGEnv(rand, ComplexF64, network, Envspace), network, ctm_alg) + for row in 1:Nr, col in 1:Nc + cp1 = col + 1 + A, B = state[row, col], state[row, cp1] + a, X = PEPSKit.bond_tensor_first(A) + b, Y = PEPSKit.bond_tensor_last(B) + benv = PEPSKit.bondenv_ctm(row, col, X, Y, env) + Z = PEPSKit.positive_approx(benv) + # verify that gauge fixing can greatly reduce + # condition number for physical state bond envs + cond1 = cond(Z' * Z) + Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) + benv2 = Z2' * Z2 + cond2 = cond(benv2) + @test 1 <= cond2 < cond1 + @info "benv cond number: (gauge-fixed) $(cond2) ≤ $(cond1) (initial)" + # verify gauge fixing is done correctly + @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] + @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] + @test half ≈ half2 + end + return +end + +peps = get_hubbard_peps() +pepo = get_hubbard_pepo() +for state in (peps, pepo) + test_benv_ctm(state) +end diff --git a/test/rocm/bondenv/benv_gaugefix.jl b/test/rocm/bondenv/benv_gaugefix.jl new file mode 100644 index 000000000..818a79e80 --- /dev/null +++ b/test/rocm/bondenv/benv_gaugefix.jl @@ -0,0 +1,44 @@ +using Test +using TensorKit +using PEPSKit +using LinearAlgebra +using Random +using AMDGPU, Adapt + +Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 1, (1, -1) => 2) +Vin = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 3, (1, -1) => 2) +V = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 2, (1, -1) => 3) +Vs = (V, V') +for V1 in Vs, V2 in Vs, V3 in Vs + #= + ┌---┬---------------┬---┐ + | | | | ┌--------------┐ + ├---X--- -2 -3 ---Y---┤ = | | + | | | | └--Z-- -2 -3 -┘ + └---┴-------Z0------┴---┘ ↓ + ↓ -1 + -1 + =# + X = adapt(ROCArray, rand(ComplexF64, Vin ⊗ V1' ⊗ Vin' ⊗ Vin)) + Y = adapt(ROCArray, rand(ComplexF64, Vin ⊗ Vin ⊗ Vin' ⊗ V3)) + Z0 = adapt(ROCArray, randn(ComplexF64, Vphy ← Vin ⊗ Vin' ⊗ Vin ⊗ Vin ⊗ Vin ⊗ Vin')) + @tensor Z[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X[Xn Xe Xs Xw] * Y[Yn Ye Ys Yw] + #= + ┌---------------------------┐ + | | + └---Z-- 1 --a-- 2 --b-- 3 --┘ + ↓ ↓ ↓ + -1 -2 -3 + =# + a = adapt(ROCArray, randn(ComplexF64, V1 ⊗ Vphy ← V2)) + b = adapt(ROCArray, randn(ComplexF64, V2 ⊗ Vphy ← V3)) + @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] + Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) + @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] + @test half ≈ half2 + # test gauge transformation of X, Y + X2 = PEPSKit._fixgauge_benvX(X, Rinv) + Y2 = PEPSKit._fixgauge_benvY(Y, Linv) + @tensor Z2_[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X2[Xn Xe Xs Xw] * Y2[Yn Ye Ys Yw] + @test Z2 ≈ Z2_ +end diff --git a/test/rocm/bondenv/bond_truncate.jl b/test/rocm/bondenv/bond_truncate.jl new file mode 100644 index 000000000..c49ce8eb6 --- /dev/null +++ b/test/rocm/bondenv/bond_truncate.jl @@ -0,0 +1,54 @@ +using Random +using Test +using TensorKit +using PEPSKit +using LinearAlgebra +using PEPSKit: bond_truncate, cost_function_als +using PEPSKit: _combine_ket, _combine_ket_for_svd +using AMDGPU, Adapt + +Random.seed!(0) +maxiter = 600 +check_interval = 30 +elt = ComplexF64 +# simulating the situation of applying a 2-site gate +# to a bond with virtual dimension D, physical dimension d. +d, D = 2, 4 +trunc = truncerror(; atol = 1.0e-10) & truncrank(D) +Vphy = Vect[FermionParity](0 => div(d, 2), 1 => div(d, 2)) +Vqro = Vect[FermionParity](0 => div(d * D, 2), 1 => div(d * D, 2)) +# virtual dimension of gate MPO is d^2 +Vint = Vect[FermionParity](0 => div(d^2 * D, 2), 1 => div(d^2 * D, 2)) +for Vl in (Vqro, Vqro'), Vr in (Vqro, Vqro') + # random positive-definite environment + Vbond = Vl ⊗ Vr + Dext = dim(Vbond) + Vext = Vect[FermionParity](0 => div(Dext, 2) + 1, 1 => div(Dext, 2) + 1) + Z = adapt(ROCArray, randn(elt, Vext ← Vbond)) + normalize!(Z, Inf) + benv = Z' * Z + @info "Dimension of benv = $(Dext)" + # untruncated bond tensors + a2 = adapt(ROCArray, randn(elt, Vl ⊗ Vphy ← Vint)) + b2 = adapt(ROCArray, randn(elt, Vint ⊗ Vphy ← Vr')) + # bond tensor (truncated SVD initialization) + a2b2 = _combine_ket(a2, b2) + a0, s, b0 = svd_trunc(permute(a2b2, ((1, 3), (4, 2))); trunc = trunc) + a0, b0 = PEPSKit.absorb_s(a0, s, b0) + b0 = permute(b0, ((1, 2), (3,))) + fid0 = cost_function_als(benv, _combine_ket(a0, b0), a2b2)[2] + @info "Fidelity of simple SVD truncation = $fid0.\n" + ss = Dict{String, DiagonalTensorMap}() + # FET is slower when d is large + for (label, alg) in ( + ("ALS", ALSTruncation(; trunc, maxiter, check_interval)), + ("FET", FullEnvTruncation(; trunc, maxiter, check_interval, trunc_init = false)), + ) + a1, ss[label], b1, info = bond_truncate(a2, b2, benv, alg) + @info "$label improved fidelity = $(info.fid)." + # display(ss[label]) + @test info.fid ≈ cost_function_als(benv, _combine_ket(a1, b1), a2b2)[2] + @test info.fid > fid0 + end + @test isapprox(ss["ALS"], ss["FET"], atol = 1.0e-3) +end From 24f5fb1bf2c083db50cf26c5ddcaaa40e0617af7 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 15:15:30 +0200 Subject: [PATCH 23/69] Fix product_peps --- src/algorithms/toolbox.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index cbbcea503..f88ba8e2b 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -111,7 +111,7 @@ function product_peps(peps_args...; unitcell = (1, 1), noise_amp = 1.0e-2, state error("symmetric tensors not generically supported") if isnothing(state_vector) state_vector = map(noise_peps.A) do t - randn(storagetype(t), dim(space(t, 1))) + randn(scalartype(t), dim(space(t, 1))) end else all(dim.(space.(noise_peps.A, 1)) .== length.(state_vector)) || From f961f5bdffa5d56d010cf5b61433f618c230ce8c Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sun, 9 Aug 2026 21:08:59 +0200 Subject: [PATCH 24/69] BP-Gaugefix test for CUDA --- test/cuda/bp/gaugefix.jl | 92 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 test/cuda/bp/gaugefix.jl diff --git a/test/cuda/bp/gaugefix.jl b/test/cuda/bp/gaugefix.jl new file mode 100644 index 000000000..13de9ea6b --- /dev/null +++ b/test/cuda/bp/gaugefix.jl @@ -0,0 +1,92 @@ +using Test, TestExtras +using Random +using TensorKit +using PEPSKit +using PEPSKit: compare_weights, random_dual!, twistdual +using PEPSKit: _next, _is_bipartite +using CUDA, Adapt + +@testset "BP vs SU ($S, bipartite = $(bipartite), posdef msgs = $h)" for + (S, bipartite, h) in Iterators.product( + [U1Irrep, FermionParity], [true, false], [true, false] + ) + unitcell = bipartite ? (2, 2) : (2, 3) + elt = ComplexF64 + maxiter, tol = 100, 1.0e-9 + Random.seed!(52840679) + Pspaces, Nspaces, Espaces = if S == U1Irrep + map(rand(1:2, unitcell), rand(1:2, unitcell), rand(1:2, unitcell)) do d0, d1, d2 + Vect[S](0 => d0, 1 => d1, -1 => d2) + end, + map(rand(2:4, unitcell), rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1, d2 + Vect[S](0 => d0, 1 => d1, -1 => d2) + end, + map(rand(2:4, unitcell), rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1, d2 + Vect[S](0 => d0, 1 => d1, -1 => d2) + end + else + map(rand(2:3, unitcell), rand(2:3, unitcell)) do d0, d1 + Vect[S](0 => d0, 1 => d1) + end, + map(rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1 + Vect[S](0 => d0, 1 => d1) + end, + map(rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1 + Vect[S](0 => d0, 1 => d1) + end + end + Nspaces, Espaces = random_dual!(Nspaces), random_dual!(Espaces) + if bipartite + for c in 1:2 + cp1 = _next(c, 2) + Pspaces[2, c] = Pspaces[1, cp1] + Nspaces[2, c] = Nspaces[1, cp1] + Espaces[2, c] = Espaces[1, cp1] + end + end + peps0 = adapt(CuArray, InfinitePEPS(randn, elt, Pspaces, Nspaces, Espaces)) + if bipartite + for c in 1:2 + peps0[2, c] = copy(peps0[1, c + 1]) + end + end + + # start by gauging with SU + peps1, wts1 = gauge_fix(peps0, SUGauge(; maxiter, tol)) + for (a0, a1) in zip(peps0.A, peps1.A) + @test space(a0) == space(a1) + end + if bipartite + @test _is_bipartite(peps1) + @test _is_bipartite(wts1) + end + normalize!.(wts1.data) + + # find BP fixed point and SUWeight + bp_alg = BeliefPropagation(; maxiter, tol, bipartite, project_hermitian = h) + env = BPEnv(randn, elt, peps1; posdef = h) + @test storagetype(env) <: CuArray + env, err = leading_boundary(env, peps1, bp_alg) + if bipartite + @test _is_bipartite(env) + end + wts2 = SUWeight(env) + normalize!.(wts2.data) + @test compare_weights(wts1, wts2) < 1.0e-9 + + bpg_alg = BPGauge() + peps2, XXinv = @constinferred gauge_fix(peps1, bpg_alg, env) + if bipartite + @test _is_bipartite(peps2) + end + for (a1, a2) in zip(peps1.A, peps2.A) + @test space(a1) == space(a2) + end + for (X, Xinv) in XXinv + # X, Xinv should contract to identity + @tensor tmp[-1; -2] := X[-1; 1] * Xinv[1; -2] + @test tmp ≈ twistdual(TensorKit.id(storagetype(peps1), space(X, 1)), 1) + # BP should differ from SU only by a unitary gauge transformation + @test inv(X) ≈ adjoint(X) ≈ Xinv + end +end From da91ec13d3c69714ed798d141a4230742951c094 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 11 Aug 2026 16:41:30 +0200 Subject: [PATCH 25/69] More working tests with downstream fixes --- Project.toml | 11 +-- test/cuda/ctmrg/jacobian_real_linear.jl | 50 +++++++++++++ test/cuda/ctmrg/suweight.jl | 56 +++++++++++++++ test/cuda/utility/correlator.jl | 93 +++++++++++++++++++++++++ test/rocm/ctmrg/jacobian_real_linear.jl | 50 +++++++++++++ test/rocm/ctmrg/suweight.jl | 56 +++++++++++++++ test/rocm/utility/correlator.jl | 93 +++++++++++++++++++++++++ 7 files changed, 405 insertions(+), 4 deletions(-) create mode 100644 test/cuda/ctmrg/jacobian_real_linear.jl create mode 100644 test/cuda/ctmrg/suweight.jl create mode 100644 test/cuda/utility/correlator.jl create mode 100644 test/rocm/ctmrg/jacobian_real_linear.jl create mode 100644 test/rocm/ctmrg/suweight.jl create mode 100644 test/rocm/utility/correlator.jl diff --git a/Project.toml b/Project.toml index 166472a54..f92e372a2 100644 --- a/Project.toml +++ b/Project.toml @@ -22,6 +22,8 @@ OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +Strided = "5e0ebb24-38b0-5f93-81fe-25c709ecae67" +StridedViews = "4db3bf67-4bd7-4b4e-b153-31dc3fb37143" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitTensors = "41b62e7d-e9d1-4e23-942c-79a97adf954b" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" @@ -33,12 +35,16 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" +[sources] +MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} +MatrixAlgebraKit = {rev = "main", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} +TensorOperations = {rev = "main", url = "https://github.com/QuantumKitHub/TensorOperations.jl"} + [extensions] PEPSKitAdaptExt = "Adapt" PEPSKitGPUArraysExt = "GPUArrays" [compat] -Adapt = "4" Accessors = "0.1" Adapt = "4" ChainRulesCore = "1.0" @@ -63,6 +69,3 @@ TupleTools = "1.6.0" VectorInterface = "0.4, 0.5, 0.6, 0.7" Zygote = "0.6, 0.7" julia = "1.10" - -[sources] -MPSKit = {url = "https://github.com/QuantumKitHub/MPSKit.jl", rev="main"} diff --git a/test/cuda/ctmrg/jacobian_real_linear.jl b/test/cuda/ctmrg/jacobian_real_linear.jl new file mode 100644 index 000000000..5e9bfa8ac --- /dev/null +++ b/test/cuda/ctmrg/jacobian_real_linear.jl @@ -0,0 +1,50 @@ +using Test +using Random +using Accessors +using Zygote +using TensorKit, KrylovKit, PEPSKit +using CUDA, Adapt +using PEPSKit: + ctmrg_iteration, compute_gauge_fix_gauge, fix_phases, ScramblingEnvGauge + +algs = [ + SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector), + SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector), # TODO: why are the errors quite a bit larger for :FullInfiniteProjector? +] +Dbond, χenv = 2, 16 +alg_gauge = ScramblingEnvGauge() +errtol = 1.0e-3 + +@testset "$ctm_alg" for ctm_alg in algs + Random.seed!(123521938519) + state = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(Dbond))) + env, = leading_boundary(CTMRGEnv(state, ComplexSpace(χenv)), state, ctm_alg) + + # follow code of _rrule + env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, ctm_alg) + signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env_conv, env, alg_gauge) + + _, env_vjp = pullback(state, env_conv) do A, x + e, = ctmrg_iteration(InfiniteSquareNetwork(A), x, ctm_alg) + return fix_phases(e, signs, corner_phases, edge_phases) + end + + # get Jacobians of single iteration + ∂f∂A(x)::typeof(state) = env_vjp(x)[1] + ∂f∂x(x)::typeof(env) = env_vjp(x)[2] + + # compute real and complex errors + env_in = CTMRGEnv(state, ComplexSpace(16)) + α_real = randn(Float64) + α_complex = randn(ComplexF64) + + real_err_∂A = norm(scale(∂f∂A(env_in), α_real) - ∂f∂A(scale(env_in, α_real))) + real_err_∂x = norm(scale(∂f∂x(env_in), α_real) - ∂f∂x(scale(env_in, α_real))) + complex_err_∂A = norm(scale(∂f∂A(env_in), α_complex) - ∂f∂A(scale(env_in, α_complex))) + complex_err_∂x = norm(scale(∂f∂x(env_in), α_complex) - ∂f∂x(scale(env_in, α_complex))) + + @test real_err_∂A < errtol + @test real_err_∂x < errtol + @test complex_err_∂A > 1.0e-3 + @test complex_err_∂x > 1.0e-3 +end diff --git a/test/cuda/ctmrg/suweight.jl b/test/cuda/ctmrg/suweight.jl new file mode 100644 index 000000000..b115c54eb --- /dev/null +++ b/test/cuda/ctmrg/suweight.jl @@ -0,0 +1,56 @@ +using Test +using Random +using TensorKit +using CUDA, Adapt +using PEPSKit +using PEPSKit: str, twistdual, unitcell + +Vps = Dict( + Z2Irrep => Vect[Z2Irrep](0 => 1, 1 => 2), + U1Irrep => Vect[U1Irrep](0 => 2, 1 => 2, -1 => 1), + FermionParity => Vect[FermionParity](0 => 1, 1 => 2), +) +Vvs = Dict( + Z2Irrep => Vect[Z2Irrep](0 => 2, 1 => 2), + U1Irrep => Vect[U1Irrep](0 => 3, 1 => 1, -1 => 2), + FermionParity => Vect[FermionParity](0 => 2, 1 => 2), +) + +function su_rdm_1x1( + row::Int, col::Int, peps::InfinitePEPS, wts::Union{Nothing, SUWeight} = nothing + ) + Nr, Nc = size(peps) + @assert 1 <= row <= Nr && 1 <= col <= Nc + t = peps.A[row, col] + if !(wts === nothing) + t = absorb_weight(t, wts, row, col, Tuple(1:4)) + end + # contract local ⟨t|t⟩ without virtual twists + @tensor ρ[k; b] := conj(t[b; n e s w]) * twistdual(t, 2:5)[k; n e s w] + return ρ / str(ρ) +end + +@testset "SUWeight ($(init) init, $(sect))" for (init, sect) in + Iterators.product([:trivial, :random], keys(Vps)) + + Vp, Vv = Vps[sect], Vvs[sect] + Nspaces = [Vv Vv' Vv; Vv' Vv Vv'] + Espaces = [Vv Vv Vv'; Vv Vv' Vv'] + Pspaces = fill(Vp, size(Nspaces)) + peps = adapt(CuArray, InfinitePEPS(randn, ComplexF64, Pspaces, Nspaces, Espaces)) + wts = SUWeight(peps) + if init != :trivial + rand!(wts) + normalize!.(wts.data, Inf) + end + env = CTMRGEnv(wts) + for idx in CartesianIndices(unitcell(peps)) + r, c = Tuple(idx) + ρ1 = su_rdm_1x1(r, c, peps, wts) + if init == :trivial + @test ρ1 ≈ su_rdm_1x1(r, c, peps, nothing) + end + ρ2 = reduced_densitymatrix([idx], peps, env) + @test ρ1 ≈ ρ2 + end +end diff --git a/test/cuda/utility/correlator.jl b/test/cuda/utility/correlator.jl new file mode 100644 index 000000000..28d252ae9 --- /dev/null +++ b/test/cuda/utility/correlator.jl @@ -0,0 +1,93 @@ +using Test +using Random +using TensorKit +using PEPSKit +using CUDA, Adapt + +const syms = (Z2Irrep, FermionParity) + +function get_spaces(sym::Type{<:Sector}) + @assert sym in syms + Nr, Nc = 2, 2 + Vphy = Vect[sym](0 => 1, 1 => 1) + V = Vect[sym](0 => 1, 1 => 2) + Venv = Vect[sym](0 => 2, 1 => 2) + Nspaces = [V' V; V V'] + Espaces = [V V'; V' V] + return Vphy, Venv, Nspaces, Espaces +end + +site0 = CartesianIndex(1, 1) +site1xs = collect(site0 + CartesianIndex(0, i) for i in [1, -1, 3, -2]) +site1ys = collect(site0 + CartesianIndex(i, 0) for i in [1, -1, 3, -2]) + +@testset "Correlator in InfinitePEPS ($(sym))" for sym in syms + Random.seed!(100) + Vphy, Venv, Nspaces, Espaces = get_spaces(sym) + # TODO: test dual physical space + for Vp in [Vphy] + op = adapt(CuArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) + Pspaces = fill(Vp, size(Nspaces)) + peps = adapt(CuArray, InfinitePEPS(randn, ComplexF64, Pspaces, Nspaces, Espaces)) + env = CTMRGEnv(randn, ComplexF64, peps, Venv) + for site1s in (site1xs, site1ys) + vals1 = correlator(peps, op, site0, site1s, env) + vals2 = map(site1s) do site1 + O = LocalOperator(Pspaces, (site0, site1) => op) + return expectation_value(peps, O, env) + end + @info vals1 + @info vals2 + @test vals1 ≈ vals2 + end + @test_throws ArgumentError correlator(peps, op, site0, site0, env) + end +end + +@testset "Correlator in purified InfinitePEPO ($(sym))" for sym in syms + Random.seed!(100) + Vphy, Venv, Nspaces, Espaces = get_spaces(sym) + # TODO: test dual physical space + for Vp in [Vphy] + op = adapt(CuArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) + Pspaces = fill(Vp, size(Nspaces)) + pepo = adapt(CuArray, InfinitePEPO(randn, ComplexF64, Pspaces, Nspaces, Espaces)) + peps = InfinitePEPS(pepo) + env = CTMRGEnv(randn, ComplexF64, peps, Venv) + for site1s in (site1xs, site1ys) + vals1 = correlator(pepo, op, site0, site1s, pepo, env) + vals2 = map(site1s) do site1 + O = LocalOperator(Pspaces, (site0, site1) => op) + return expectation_value(pepo, O, pepo, env) + end + @info vals1 + @info vals2 + @test vals1 ≈ vals2 + end + @test_throws ArgumentError correlator(pepo, op, site0, site0, pepo, env) + end +end + +@testset "Correlator in 1-layer InfinitePEPO ($(sym))" for sym in syms + Random.seed!(100) + Vphy, Venv, Nspaces, Espaces = get_spaces(sym) + # TODO: test dual physical space + for Vp in [Vphy] + op = adapt(CuArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) + Pspaces = fill(Vp, size(Nspaces)) + pepo = adapt(CuArray, InfinitePEPO(randn, ComplexF64, Pspaces, Nspaces, Espaces)) + pf = InfinitePartitionFunction(pepo) + env = CTMRGEnv(randn, ComplexF64, pf, Venv) + for site1s in (site1xs, site1ys) + vals1 = correlator(pepo, op, site0, site1s, env) + vals2 = map(site1s) do site1 + O = LocalOperator(Pspaces, (site0, site1) => op) + return expectation_value(pepo, O, env) + end + @info vals1 + @info vals2 + @test vals1 ≈ vals2 + end + @test_throws ArgumentError correlator(pepo, op, site0, site0, env) + end +end diff --git a/test/rocm/ctmrg/jacobian_real_linear.jl b/test/rocm/ctmrg/jacobian_real_linear.jl new file mode 100644 index 000000000..78a6e91bf --- /dev/null +++ b/test/rocm/ctmrg/jacobian_real_linear.jl @@ -0,0 +1,50 @@ +using Test +using Random +using Accessors +using Zygote +using TensorKit, KrylovKit, PEPSKit +using AMDGPU, Adapt +using PEPSKit: + ctmrg_iteration, compute_gauge_fix_gauge, fix_phases, ScramblingEnvGauge + +algs = [ + SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector), + SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector), # TODO: why are the errors quite a bit larger for :FullInfiniteProjector? +] +Dbond, χenv = 2, 16 +alg_gauge = ScramblingEnvGauge() +errtol = 1.0e-3 + +@testset "$ctm_alg" for ctm_alg in algs + Random.seed!(123521938519) + state = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(Dbond))) + env, = leading_boundary(CTMRGEnv(state, ComplexSpace(χenv)), state, ctm_alg) + + # follow code of _rrule + env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, ctm_alg) + signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env_conv, env, alg_gauge) + + _, env_vjp = pullback(state, env_conv) do A, x + e, = ctmrg_iteration(InfiniteSquareNetwork(A), x, ctm_alg) + return fix_phases(e, signs, corner_phases, edge_phases) + end + + # get Jacobians of single iteration + ∂f∂A(x)::typeof(state) = env_vjp(x)[1] + ∂f∂x(x)::typeof(env) = env_vjp(x)[2] + + # compute real and complex errors + env_in = CTMRGEnv(state, ComplexSpace(16)) + α_real = randn(Float64) + α_complex = randn(ComplexF64) + + real_err_∂A = norm(scale(∂f∂A(env_in), α_real) - ∂f∂A(scale(env_in, α_real))) + real_err_∂x = norm(scale(∂f∂x(env_in), α_real) - ∂f∂x(scale(env_in, α_real))) + complex_err_∂A = norm(scale(∂f∂A(env_in), α_complex) - ∂f∂A(scale(env_in, α_complex))) + complex_err_∂x = norm(scale(∂f∂x(env_in), α_complex) - ∂f∂x(scale(env_in, α_complex))) + + @test real_err_∂A < errtol + @test real_err_∂x < errtol + @test complex_err_∂A > 1.0e-3 + @test complex_err_∂x > 1.0e-3 +end diff --git a/test/rocm/ctmrg/suweight.jl b/test/rocm/ctmrg/suweight.jl new file mode 100644 index 000000000..dab8df1c1 --- /dev/null +++ b/test/rocm/ctmrg/suweight.jl @@ -0,0 +1,56 @@ +using Test +using Random +using TensorKit +using AMDGPU, Adapt +using PEPSKit +using PEPSKit: str, twistdual, unitcell + +Vps = Dict( + Z2Irrep => Vect[Z2Irrep](0 => 1, 1 => 2), + U1Irrep => Vect[U1Irrep](0 => 2, 1 => 2, -1 => 1), + FermionParity => Vect[FermionParity](0 => 1, 1 => 2), +) +Vvs = Dict( + Z2Irrep => Vect[Z2Irrep](0 => 2, 1 => 2), + U1Irrep => Vect[U1Irrep](0 => 3, 1 => 1, -1 => 2), + FermionParity => Vect[FermionParity](0 => 2, 1 => 2), +) + +function su_rdm_1x1( + row::Int, col::Int, peps::InfinitePEPS, wts::Union{Nothing, SUWeight} = nothing + ) + Nr, Nc = size(peps) + @assert 1 <= row <= Nr && 1 <= col <= Nc + t = peps.A[row, col] + if !(wts === nothing) + t = absorb_weight(t, wts, row, col, Tuple(1:4)) + end + # contract local ⟨t|t⟩ without virtual twists + @tensor ρ[k; b] := conj(t[b; n e s w]) * twistdual(t, 2:5)[k; n e s w] + return ρ / str(ρ) +end + +@testset "SUWeight ($(init) init, $(sect))" for (init, sect) in + Iterators.product([:trivial, :random], keys(Vps)) + + Vp, Vv = Vps[sect], Vvs[sect] + Nspaces = [Vv Vv' Vv; Vv' Vv Vv'] + Espaces = [Vv Vv Vv'; Vv Vv' Vv'] + Pspaces = fill(Vp, size(Nspaces)) + peps = adapt(ROCArray, InfinitePEPS(randn, ComplexF64, Pspaces, Nspaces, Espaces)) + wts = SUWeight(peps) + if init != :trivial + rand!(wts) + normalize!.(wts.data, Inf) + end + env = CTMRGEnv(wts) + for idx in CartesianIndices(unitcell(peps)) + r, c = Tuple(idx) + ρ1 = su_rdm_1x1(r, c, peps, wts) + if init == :trivial + @test ρ1 ≈ su_rdm_1x1(r, c, peps, nothing) + end + ρ2 = reduced_densitymatrix([idx], peps, env) + @test ρ1 ≈ ρ2 + end +end diff --git a/test/rocm/utility/correlator.jl b/test/rocm/utility/correlator.jl new file mode 100644 index 000000000..3e2e611fc --- /dev/null +++ b/test/rocm/utility/correlator.jl @@ -0,0 +1,93 @@ +using Test +using Random +using TensorKit +using PEPSKit +using AMDGPU, Adapt + +const syms = (Z2Irrep, FermionParity) + +function get_spaces(sym::Type{<:Sector}) + @assert sym in syms + Nr, Nc = 2, 2 + Vphy = Vect[sym](0 => 1, 1 => 1) + V = Vect[sym](0 => 1, 1 => 2) + Venv = Vect[sym](0 => 2, 1 => 2) + Nspaces = [V' V; V V'] + Espaces = [V V'; V' V] + return Vphy, Venv, Nspaces, Espaces +end + +site0 = CartesianIndex(1, 1) +site1xs = collect(site0 + CartesianIndex(0, i) for i in [1, -1, 3, -2]) +site1ys = collect(site0 + CartesianIndex(i, 0) for i in [1, -1, 3, -2]) + +@testset "Correlator in InfinitePEPS ($(sym))" for sym in syms + Random.seed!(100) + Vphy, Venv, Nspaces, Espaces = get_spaces(sym) + # TODO: test dual physical space + for Vp in [Vphy] + op = adapt(ROCArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) + Pspaces = fill(Vp, size(Nspaces)) + peps = adapt(ROCArray, InfinitePEPS(randn, ComplexF64, Pspaces, Nspaces, Espaces)) + env = CTMRGEnv(randn, ComplexF64, peps, Venv) + for site1s in (site1xs, site1ys) + vals1 = correlator(peps, op, site0, site1s, env) + vals2 = map(site1s) do site1 + O = LocalOperator(Pspaces, (site0, site1) => op) + return expectation_value(peps, O, env) + end + @info vals1 + @info vals2 + @test vals1 ≈ vals2 + end + @test_throws ArgumentError correlator(peps, op, site0, site0, env) + end +end + +@testset "Correlator in purified InfinitePEPO ($(sym))" for sym in syms + Random.seed!(100) + Vphy, Venv, Nspaces, Espaces = get_spaces(sym) + # TODO: test dual physical space + for Vp in [Vphy] + op = adapt(ROCArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) + Pspaces = fill(Vp, size(Nspaces)) + pepo = adapt(ROCArray, InfinitePEPO(randn, ComplexF64, Pspaces, Nspaces, Espaces)) + peps = InfinitePEPS(pepo) + env = CTMRGEnv(randn, ComplexF64, peps, Venv) + for site1s in (site1xs, site1ys) + vals1 = correlator(pepo, op, site0, site1s, pepo, env) + vals2 = map(site1s) do site1 + O = LocalOperator(Pspaces, (site0, site1) => op) + return expectation_value(pepo, O, pepo, env) + end + @info vals1 + @info vals2 + @test vals1 ≈ vals2 + end + @test_throws ArgumentError correlator(pepo, op, site0, site0, pepo, env) + end +end + +@testset "Correlator in 1-layer InfinitePEPO ($(sym))" for sym in syms + Random.seed!(100) + Vphy, Venv, Nspaces, Espaces = get_spaces(sym) + # TODO: test dual physical space + for Vp in [Vphy] + op = adapt(ROCArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) + Pspaces = fill(Vp, size(Nspaces)) + pepo = adapt(ROCArray, InfinitePEPO(randn, ComplexF64, Pspaces, Nspaces, Espaces)) + pf = InfinitePartitionFunction(pepo) + env = CTMRGEnv(randn, ComplexF64, pf, Venv) + for site1s in (site1xs, site1ys) + vals1 = correlator(pepo, op, site0, site1s, env) + vals2 = map(site1s) do site1 + O = LocalOperator(Pspaces, (site0, site1) => op) + return expectation_value(pepo, O, env) + end + @info vals1 + @info vals2 + @test vals1 ≈ vals2 + end + @test_throws ArgumentError correlator(pepo, op, site0, site0, env) + end +end From 3d3f12012bb7943bb64f23ad3252835ca652b829 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 12 Aug 2026 06:30:44 -0400 Subject: [PATCH 26/69] Don't use QRIteration for C4V + CUDA --- test/cuda/ctmrg/fixed_iterscheme.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cuda/ctmrg/fixed_iterscheme.jl b/test/cuda/ctmrg/fixed_iterscheme.jl index 5ebb4e8cd..b525ff087 100644 --- a/test/cuda/ctmrg/fixed_iterscheme.jl +++ b/test/cuda/ctmrg/fixed_iterscheme.jl @@ -62,7 +62,7 @@ end # test same thing for C4v CTMRG c4v_algs = [ (:C4vQRProjector, (; alg = :Householder)), - (:C4vEighProjector, (; alg = :QRIteration)), + (:C4vEighProjector, (; alg = :DivideAndConquer)), (:C4vEighProjector, (; alg = :Lanczos)), ] @testset "$(decomposition_alg.alg) and $projector_alg" for From 7f3e7a3cb215002dd3bf5ede76df7cd2945f743b Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 12 Aug 2026 17:23:00 -0400 Subject: [PATCH 27/69] Get cuda flavors working --- test/Project.toml | 2 ++ test/cuda/ctmrg/flavors.jl | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index 794a83c44..05b7e3c66 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -8,6 +8,7 @@ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" @@ -29,6 +30,7 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [sources] PEPSKit = {path = ".."} +GPUArrays = {url = "https://github.com/JuliaGPU/GPUArrays.jl", rev = "ksh/more_diag"} [compat] AMDGPU = "2" diff --git a/test/cuda/ctmrg/flavors.jl b/test/cuda/ctmrg/flavors.jl index 16664e1c1..0bebb4eb4 100644 --- a/test/cuda/ctmrg/flavors.jl +++ b/test/cuda/ctmrg/flavors.jl @@ -14,7 +14,7 @@ unitcells = [(1, 1), (3, 4)] projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] projector_algs_c4v = [ (:C4vQRProjector, :Householder), - (:C4vEighProjector, :QRIteration), (:C4vEighProjector, :Lanczos), + (:C4vEighProjector, :DivideAndConquer), (:C4vEighProjector, :Lanczos), ] Ts = [Float64, ComplexF64] From 1f33bce170115966ee3fb9cbba6e4b8c394ab963 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 13 Aug 2026 18:49:58 +0200 Subject: [PATCH 28/69] Make the gaugefix loop GPU-friendly --- test/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index 05b7e3c66..3dc295f54 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -30,7 +30,7 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [sources] PEPSKit = {path = ".."} -GPUArrays = {url = "https://github.com/JuliaGPU/GPUArrays.jl", rev = "ksh/more_diag"} +GPUArrays = {url = "https://github.com/JuliaGPU/GPUArrays.jl", rev = "main"} [compat] AMDGPU = "2" From 94989bcd8e1a8fe0a8d8c9b4044ae5a24921b5d7 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 14 Aug 2026 09:32:15 -0400 Subject: [PATCH 29/69] CUDA expvals test works --- test/cuda/bp/expvals.jl | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 test/cuda/bp/expvals.jl diff --git a/test/cuda/bp/expvals.jl b/test/cuda/bp/expvals.jl new file mode 100644 index 000000000..bed8da987 --- /dev/null +++ b/test/cuda/bp/expvals.jl @@ -0,0 +1,54 @@ +using Test +using Random +using TensorKit +using PEPSKit +using PEPSKit: random_dual! + +ds = Dict( + U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), + FermionParity => Vect[FermionParity](0 => 2, 1 => 1) +) +Ds = Dict( + U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 3, 2))), + FermionParity => Vect[FermionParity](0 => 3, 1 => 2) +) +Random.seed!(41973582) + +@testset "Expectation values of BPEnv ($S)" for S in keys(ds) + d, D, uc = ds[S], Ds[S], (2, 3) + ψds = fill(d, uc) + ψDNs = random_dual!(fill(D, uc)) + ψDEs = random_dual!(fill(D, uc)) + ψ0 = InfinitePEPS(ψds, ψDNs, ψDEs) + + ψ, wts, _ = gauge_fix(ψ0, SUGauge(; maxiter = 100, tol = 1.0e-10)) + for (a0, a) in zip(ψ0.A, ψ.A) + @test space(a0) == space(a) + end + bp_env = BPEnv(wts) + ctm_env = CTMRGEnv(wts) + @test ctm_env ≈ CTMRGEnv(bp_env) + + # SU fixed point wts should already be a BP fixed point of ψ + bp_alg = BeliefPropagation(; miniter = 1, maxiter = 1, tol = 1.0e-7) + _, err = leading_boundary(bp_env, ψ, bp_alg) + @test err < 1.0e-9 + + op = randn(d → d) + for site in CartesianIndices(size(ψ)) + lo = LocalOperator(ψds, (site,) => op) + val1 = expectation_value(ψ, lo, bp_env) + val2 = expectation_value(ψ, lo, ctm_env) + @test val1 ≈ val2 + end + + op = randn(d ⊗ d → d ⊗ d) + vs = [CartesianIndex(1, 0), CartesianIndex(0, 1)] + for site1 in CartesianIndices(size(ψ)), v in vs + site2 = site1 + v + lo = LocalOperator(ψds, (site1, site2) => op) + val1 = expectation_value(ψ, lo, bp_env) + val2 = expectation_value(ψ, lo, ctm_env) + @test val1 ≈ val2 + end +end From 17957120a00fddccb2e45b3beac2bb77c8213bcb Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 14 Aug 2026 12:17:58 +0200 Subject: [PATCH 30/69] bp/expvals tests working --- test/cuda/bp/expvals.jl | 7 +++--- test/rocm/bp/expvals.jl | 55 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 test/rocm/bp/expvals.jl diff --git a/test/cuda/bp/expvals.jl b/test/cuda/bp/expvals.jl index bed8da987..fe30cb409 100644 --- a/test/cuda/bp/expvals.jl +++ b/test/cuda/bp/expvals.jl @@ -3,6 +3,7 @@ using Random using TensorKit using PEPSKit using PEPSKit: random_dual! +using CUDA, Adapt ds = Dict( U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), @@ -19,7 +20,7 @@ Random.seed!(41973582) ψds = fill(d, uc) ψDNs = random_dual!(fill(D, uc)) ψDEs = random_dual!(fill(D, uc)) - ψ0 = InfinitePEPS(ψds, ψDNs, ψDEs) + ψ0 = adapt(CuArray, InfinitePEPS(ψds, ψDNs, ψDEs)) ψ, wts, _ = gauge_fix(ψ0, SUGauge(; maxiter = 100, tol = 1.0e-10)) for (a0, a) in zip(ψ0.A, ψ.A) @@ -36,7 +37,7 @@ Random.seed!(41973582) op = randn(d → d) for site in CartesianIndices(size(ψ)) - lo = LocalOperator(ψds, (site,) => op) + lo = adapt(CuArray, LocalOperator(ψds, (site,) => op)) val1 = expectation_value(ψ, lo, bp_env) val2 = expectation_value(ψ, lo, ctm_env) @test val1 ≈ val2 @@ -46,7 +47,7 @@ Random.seed!(41973582) vs = [CartesianIndex(1, 0), CartesianIndex(0, 1)] for site1 in CartesianIndices(size(ψ)), v in vs site2 = site1 + v - lo = LocalOperator(ψds, (site1, site2) => op) + lo = adapt(CuArray, LocalOperator(ψds, (site1, site2) => op)) val1 = expectation_value(ψ, lo, bp_env) val2 = expectation_value(ψ, lo, ctm_env) @test val1 ≈ val2 diff --git a/test/rocm/bp/expvals.jl b/test/rocm/bp/expvals.jl new file mode 100644 index 000000000..b732f46d3 --- /dev/null +++ b/test/rocm/bp/expvals.jl @@ -0,0 +1,55 @@ +using Test +using Random +using TensorKit +using PEPSKit +using PEPSKit: random_dual! +using AMDGPU, Adapt + +ds = Dict( + U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), + FermionParity => Vect[FermionParity](0 => 2, 1 => 1) +) +Ds = Dict( + U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 3, 2))), + FermionParity => Vect[FermionParity](0 => 3, 1 => 2) +) +Random.seed!(41973582) + +@testset "Expectation values of BPEnv ($S)" for S in keys(ds) + d, D, uc = ds[S], Ds[S], (2, 3) + ψds = fill(d, uc) + ψDNs = random_dual!(fill(D, uc)) + ψDEs = random_dual!(fill(D, uc)) + ψ0 = adapt(ROCArray, InfinitePEPS(ψds, ψDNs, ψDEs)) + + ψ, wts, _ = gauge_fix(ψ0, SUGauge(; maxiter = 100, tol = 1.0e-10)) + for (a0, a) in zip(ψ0.A, ψ.A) + @test space(a0) == space(a) + end + bp_env = BPEnv(wts) + ctm_env = CTMRGEnv(wts) + @test ctm_env ≈ CTMRGEnv(bp_env) + + # SU fixed point wts should already be a BP fixed point of ψ + bp_alg = BeliefPropagation(; miniter = 1, maxiter = 1, tol = 1.0e-7) + _, err = leading_boundary(bp_env, ψ, bp_alg) + @test err < 1.0e-9 + + op = randn(d → d) + for site in CartesianIndices(size(ψ)) + lo = adapt(ROCArray, LocalOperator(ψds, (site,) => op)) + val1 = expectation_value(ψ, lo, bp_env) + val2 = expectation_value(ψ, lo, ctm_env) + @test val1 ≈ val2 + end + + op = randn(d ⊗ d → d ⊗ d) + vs = [CartesianIndex(1, 0), CartesianIndex(0, 1)] + for site1 in CartesianIndices(size(ψ)), v in vs + site2 = site1 + v + lo = adapt(ROCArray, LocalOperator(ψds, (site1, site2) => op)) + val1 = expectation_value(ψ, lo, bp_env) + val2 = expectation_value(ψ, lo, ctm_env) + @test val1 ≈ val2 + end +end From f2cc88474d9997966fa31c4359d6e3712517b424 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 14 Aug 2026 15:00:39 +0200 Subject: [PATCH 31/69] GPUified SVD and passing tests --- src/utility/svd.jl | 6 + test/cuda/utility/svd_wrapper.jl | 189 +++++++++++++++++++++++++++++++ test/rocm/utility/svd_wrapper.jl | 189 +++++++++++++++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 test/cuda/utility/svd_wrapper.jl create mode 100644 test/rocm/utility/svd_wrapper.jl diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 8a39ebf1d..0b10ed24a 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -399,15 +399,21 @@ end # `block(::AdjointTensorMap, c)` hands back a `LinearAlgebra.Adjoint` # Some backends will need to materialize this to avoid scalar indexing _materialize_block(m::AbstractMatrix) = m +#_materialize_block(m::Union{Adjoint, Transpose}) = copyto!(similar(m, size(m)), m) # Columns of `m` as a vector of `A`-typed vectors function _column_vectors(::Type{A}, m::AbstractMatrix) where {A} return Vector{A}(eachcol(_materialize_block(m))) end +#function _column_vectors(::Type{A}, m::AbstractMatrix) where {A} +# m̃ = _materialize_block(m) +# return A[m̃[:, j] for j in axes(m̃, 2)] +#end # Diagonal of `m` for `compute_svdsolve_pullback_data`, which reads it element-wise # Some backends will need to copy this to the host to avoid scalar indexing _indexable_diagview(m::AbstractMatrix) = diagview(m) +#_indexable_diagview(m::AbstractMatrix) = collect(diagview(m)) # KrylovKit rrule compatible with TensorMaps & function handles function ChainRulesCore.rrule( diff --git a/test/cuda/utility/svd_wrapper.jl b/test/cuda/utility/svd_wrapper.jl new file mode 100644 index 000000000..c813f7d3c --- /dev/null +++ b/test/cuda/utility/svd_wrapper.jl @@ -0,0 +1,189 @@ +using Test +using Random +using LinearAlgebra +using TensorKit +using ChainRulesCore, Zygote +using Accessors +using PEPSKit +using Adapt, CUDA +using MatrixAlgebraKit: TruncatedAlgorithm, diagview, svd_trunc_no_error + +# Gauge-invariant loss function +function lossfun(svd_trunc_f, A, alg, R = randn(space(A)), trunc = notrunc()) + alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) + USV = svd_trunc_f(A, alg) + U, S, V = USV[1:3] # avoid looking at ϵ if present + return real(dot(R, U * V)) + dot(S, S) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n +end + +dtype = ComplexF64 +m, n = 20, 30 +χ = 12 +trunc = truncspace(ℂ^χ) +rtol = 1.0e-9 +Random.seed!(12345678) +r = adapt(CuArray, randn(dtype, ℂ^m, ℂ^n)) +R = adapt(CuArray, randn(space(r))) + +full_alg = SVDAdjoint(; rrule_alg = (; alg = :FullPullback, degeneracy_atol = 1.0e-13)) +trunc_alg = SVDAdjoint(; rrule_alg = (; alg = :TruncPullback, degeneracy_atol = 1.0e-13)) +iter_alg = SVDAdjoint(; fwd_alg = (; alg = :GKL)) + +@testset "Non-truncated SVD $f" for f in (svd_trunc, svd_trunc_no_error) + l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, R), r) + l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, R), r) + l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, R), r) + + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol +end + +@testset "Truncated SVD $f with χ=$χ" for f in (svd_trunc, svd_trunc_no_error) + l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, R, trunc), r) + l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, R, trunc), r) + l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, R, trunc), r) + + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol +end + +@testset "Truncated SVD broadening for $f, $(alg.rrule_alg)" for f in (svd_trunc, svd_trunc_no_error), alg in [full_alg, trunc_alg] + u, s, v, = svd_compact(r) + s.data[1:2:m] .= s.data[2:2:m] # make every singular value two-fold degenerate + r_degen = u * s * v + + no_broadening_no_cutoff_alg = @set full_alg.rrule_alg.degeneracy_atol = 1.0e-30 + small_broadening_alg = @set full_alg.rrule_alg.degeneracy_atol = 1.0e-13 + + l_only_cutoff, g_only_cutoff = withgradient( + A -> lossfun(f, A, full_alg, R, trunc), r_degen + ) # cutoff sets degenerate difference to zero + l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions + A -> lossfun(f, A, no_broadening_no_cutoff_alg, R, trunc), r_degen, + ) + l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions + A -> lossfun(f, A, small_broadening_alg, R, trunc), r_degen, + ) + + @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening + @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient + @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect +end + +symm_m, symm_n = 18, 24 +symm_space = Z2Space(0 => symm_m, 1 => symm_n) +symm_trspace = truncspace(Z2Space(0 => symm_m ÷ 2, 1 => symm_n ÷ 3)) +symm_r = adapt(CuArray, randn(dtype, symm_space, symm_space)) +symm_R = adapt(CuArray, randn(dtype, space(symm_r))) + +@testset "IterSVD of symmetric tensors $f" for f in (svd_trunc, svd_trunc_no_error) + l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, symm_R), symm_r) + l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, symm_R), symm_r) + l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, symm_R), symm_r) + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol + + l_full_tr, g_full_tr = withgradient( + A -> lossfun(f, A, full_alg, symm_R, symm_trspace), symm_r + ) + l_trunc_tr, g_trunc_tr = withgradient( + A -> lossfun(f, A, trunc_alg, symm_R, symm_trspace), symm_r + ) + l_iter_tr, g_iter_tr = withgradient( + A -> lossfun(f, A, iter_alg, symm_R, symm_trspace), symm_r + ) + @test l_full_tr ≈ l_trunc_tr ≈ l_iter_tr + @test g_full_tr[1] ≈ g_trunc_tr[1] rtol = rtol + @test g_full_tr[1] ≈ g_iter_tr[1] rtol = rtol + @test g_trunc_tr[1] ≈ g_iter_tr[1] rtol = rtol + + iter_alg_fallback = @set iter_alg.fwd_alg.fallback_threshold = 0.4 # Do dense decomposition in one block, sparse one in the other + l_iter_fb, g_iter_fb = withgradient( + A -> lossfun(f, A, iter_alg_fallback, symm_R, symm_trspace), symm_r + ) + @test l_iter_fb ≈ l_trunc_tr ≈ l_full_tr + @test g_full_tr[1] ≈ g_iter_fb[1] rtol = rtol + @test g_trunc_tr[1] ≈ g_iter_fb[1] rtol = rtol +end + +@testset "Truncated symmetric SVD broadening for $f, $(alg.rrule_alg)" for f in (svd_trunc, svd_trunc_no_error), alg in [full_alg, trunc_alg] + u, s, v, = svd_compact(symm_r) + # make every singular value in the 0-sector three-fold degenerate + b0 = diagview(block(s, Z2Irrep(0))) + b0[1:3:symm_m] .= b0[3:3:symm_m] + b0[2:3:symm_m] .= b0[3:3:symm_m] + # make every singular value in the 1-sector two-fold degenerate + b1 = diagview(block(s, Z2Irrep(1))) + b1[1:2:symm_n] .= b1[2:2:symm_n] + symm_r_degen = u * s * v + + no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 + small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 + + l_only_cutoff, g_only_cutoff = withgradient( + A -> lossfun(f, A, alg, symm_R, symm_trspace), symm_r_degen + ) # cutoff sets degenerate difference to zero + l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions + A -> lossfun(f, A, no_broadening_no_cutoff_alg, symm_R, symm_trspace), + symm_r_degen, + ) + l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions + A -> lossfun(f, A, small_broadening_alg, symm_R, symm_trspace), + symm_r_degen, + ) + + @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening + @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient + @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect +end + +# TODO: Add when IterSVD is implemented for HalfInfiniteEnv +# χbond = 2 +# χenv = 6 +# ctm_alg = CTMRG(; tol=1e-10, verbosity=2, svd_alg=SVDAdjoint()) +# Random.seed!(91283219347) +# H = heisenberg_XYZ(InfiniteSquare()) +# psi = InfinitePEPS(ComplexSpace(2), ComplexSpace(χbond)) +# env = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); +# hienv = HalfInfiniteEnv( +# env.corners[1], +# env.corners[2], +# env.edges[4], +# env.edges[1], +# env.edges[1], +# env.edges[2], +# psi[1], +# psi[1], +# psi[1], +# psi[1], +# ) +# hienv_dense = hienv() +# env_R = randn(space(hienv)) + +# svd_trunc!(hienv, iter_alg) + +# @testset "IterSVD with HalfInfiniteEnv function handle" begin +# # Equivalence of dense and sparse contractions +# x₀ = PEPSKit.random_start_vector(hienv) +# x′ = hienv(x₀, Val(false)) +# x″ = hienv(x′, Val(true)) +# x‴ = hienv(x″, Val(false)) + +# a = hienv_dense * x₀ +# b = hienv_dense' * a +# c = hienv_dense * b +# @test a ≈ x′ +# @test b ≈ x″ +# @test c ≈ x‴ + +# # l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, env_R), hienv_dense) +# # l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, env_R), hienv) +# # @test l_itersvd ≈ l_fullsvd +# # @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol +# end diff --git a/test/rocm/utility/svd_wrapper.jl b/test/rocm/utility/svd_wrapper.jl new file mode 100644 index 000000000..55ee835aa --- /dev/null +++ b/test/rocm/utility/svd_wrapper.jl @@ -0,0 +1,189 @@ +using Test +using Random +using LinearAlgebra +using TensorKit +using ChainRulesCore, Zygote +using Accessors +using PEPSKit +using Adapt, AMDGPU +using MatrixAlgebraKit: TruncatedAlgorithm, diagview, svd_trunc_no_error + +# Gauge-invariant loss function +function lossfun(svd_trunc_f, A, alg, R = randn(space(A)), trunc = notrunc()) + alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) + USV = svd_trunc_f(A, alg) + U, S, V = USV[1:3] # avoid looking at ϵ if present + return real(dot(R, U * V)) + dot(S, S) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n +end + +dtype = ComplexF64 +m, n = 20, 30 +χ = 12 +trunc = truncspace(ℂ^χ) +rtol = 1.0e-9 +Random.seed!(12345678) +r = adapt(ROCArray, randn(dtype, ℂ^m, ℂ^n)) +R = adapt(ROCArray, randn(space(r))) + +full_alg = SVDAdjoint(; rrule_alg = (; alg = :FullPullback, degeneracy_atol = 1.0e-13)) +trunc_alg = SVDAdjoint(; rrule_alg = (; alg = :TruncPullback, degeneracy_atol = 1.0e-13)) +iter_alg = SVDAdjoint(; fwd_alg = (; alg = :GKL)) + +@testset "Non-truncated SVD $f" for f in (svd_trunc, svd_trunc_no_error) + l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, R), r) + l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, R), r) + l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, R), r) + + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol +end + +@testset "Truncated SVD $f with χ=$χ" for f in (svd_trunc, svd_trunc_no_error) + l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, R, trunc), r) + l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, R, trunc), r) + l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, R, trunc), r) + + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol +end + +@testset "Truncated SVD broadening for $f, $(alg.rrule_alg)" for f in (svd_trunc, svd_trunc_no_error), alg in [full_alg, trunc_alg] + u, s, v, = svd_compact(r) + s.data[1:2:m] .= s.data[2:2:m] # make every singular value two-fold degenerate + r_degen = u * s * v + + no_broadening_no_cutoff_alg = @set full_alg.rrule_alg.degeneracy_atol = 1.0e-30 + small_broadening_alg = @set full_alg.rrule_alg.degeneracy_atol = 1.0e-13 + + l_only_cutoff, g_only_cutoff = withgradient( + A -> lossfun(f, A, full_alg, R, trunc), r_degen + ) # cutoff sets degenerate difference to zero + l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions + A -> lossfun(f, A, no_broadening_no_cutoff_alg, R, trunc), r_degen, + ) + l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions + A -> lossfun(f, A, small_broadening_alg, R, trunc), r_degen, + ) + + @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening + @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient + @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect +end + +symm_m, symm_n = 18, 24 +symm_space = Z2Space(0 => symm_m, 1 => symm_n) +symm_trspace = truncspace(Z2Space(0 => symm_m ÷ 2, 1 => symm_n ÷ 3)) +symm_r = adapt(ROCArray, randn(dtype, symm_space, symm_space)) +symm_R = adapt(ROCArray, randn(dtype, space(symm_r))) + +@testset "IterSVD of symmetric tensors $f" for f in (svd_trunc, svd_trunc_no_error) + l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, symm_R), symm_r) + l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, symm_R), symm_r) + l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, symm_R), symm_r) + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol + + l_full_tr, g_full_tr = withgradient( + A -> lossfun(f, A, full_alg, symm_R, symm_trspace), symm_r + ) + l_trunc_tr, g_trunc_tr = withgradient( + A -> lossfun(f, A, trunc_alg, symm_R, symm_trspace), symm_r + ) + l_iter_tr, g_iter_tr = withgradient( + A -> lossfun(f, A, iter_alg, symm_R, symm_trspace), symm_r + ) + @test l_full_tr ≈ l_trunc_tr ≈ l_iter_tr + @test g_full_tr[1] ≈ g_trunc_tr[1] rtol = rtol + @test g_full_tr[1] ≈ g_iter_tr[1] rtol = rtol + @test g_trunc_tr[1] ≈ g_iter_tr[1] rtol = rtol + + iter_alg_fallback = @set iter_alg.fwd_alg.fallback_threshold = 0.4 # Do dense decomposition in one block, sparse one in the other + l_iter_fb, g_iter_fb = withgradient( + A -> lossfun(f, A, iter_alg_fallback, symm_R, symm_trspace), symm_r + ) + @test l_iter_fb ≈ l_trunc_tr ≈ l_full_tr + @test g_full_tr[1] ≈ g_iter_fb[1] rtol = rtol + @test g_trunc_tr[1] ≈ g_iter_fb[1] rtol = rtol +end + +@testset "Truncated symmetric SVD broadening for $f, $(alg.rrule_alg)" for f in (svd_trunc, svd_trunc_no_error), alg in [full_alg, trunc_alg] + u, s, v, = svd_compact(symm_r) + # make every singular value in the 0-sector three-fold degenerate + b0 = diagview(block(s, Z2Irrep(0))) + b0[1:3:symm_m] .= b0[3:3:symm_m] + b0[2:3:symm_m] .= b0[3:3:symm_m] + # make every singular value in the 1-sector two-fold degenerate + b1 = diagview(block(s, Z2Irrep(1))) + b1[1:2:symm_n] .= b1[2:2:symm_n] + symm_r_degen = u * s * v + + no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 + small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 + + l_only_cutoff, g_only_cutoff = withgradient( + A -> lossfun(f, A, alg, symm_R, symm_trspace), symm_r_degen + ) # cutoff sets degenerate difference to zero + l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions + A -> lossfun(f, A, no_broadening_no_cutoff_alg, symm_R, symm_trspace), + symm_r_degen, + ) + l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions + A -> lossfun(f, A, small_broadening_alg, symm_R, symm_trspace), + symm_r_degen, + ) + + @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening + @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient + @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect +end + +# TODO: Add when IterSVD is implemented for HalfInfiniteEnv +# χbond = 2 +# χenv = 6 +# ctm_alg = CTMRG(; tol=1e-10, verbosity=2, svd_alg=SVDAdjoint()) +# Random.seed!(91283219347) +# H = heisenberg_XYZ(InfiniteSquare()) +# psi = InfinitePEPS(ComplexSpace(2), ComplexSpace(χbond)) +# env = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); +# hienv = HalfInfiniteEnv( +# env.corners[1], +# env.corners[2], +# env.edges[4], +# env.edges[1], +# env.edges[1], +# env.edges[2], +# psi[1], +# psi[1], +# psi[1], +# psi[1], +# ) +# hienv_dense = hienv() +# env_R = randn(space(hienv)) + +# svd_trunc!(hienv, iter_alg) + +# @testset "IterSVD with HalfInfiniteEnv function handle" begin +# # Equivalence of dense and sparse contractions +# x₀ = PEPSKit.random_start_vector(hienv) +# x′ = hienv(x₀, Val(false)) +# x″ = hienv(x′, Val(true)) +# x‴ = hienv(x″, Val(false)) + +# a = hienv_dense * x₀ +# b = hienv_dense' * a +# c = hienv_dense * b +# @test a ≈ x′ +# @test b ≈ x″ +# @test c ≈ x‴ + +# # l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, env_R), hienv_dense) +# # l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, env_R), hienv) +# # @test l_itersvd ≈ l_fullsvd +# # @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol +# end From a1df2fee1a09a8b97fe80007170676919e07f521 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 14 Aug 2026 18:27:47 +0200 Subject: [PATCH 32/69] Working eigh --- test/cuda/utility/eigh_wrapper.jl | 147 ++++++++++++++++++++++++++++++ test/rocm/utility/eigh_wrapper.jl | 147 ++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 test/cuda/utility/eigh_wrapper.jl create mode 100644 test/rocm/utility/eigh_wrapper.jl diff --git a/test/cuda/utility/eigh_wrapper.jl b/test/cuda/utility/eigh_wrapper.jl new file mode 100644 index 000000000..9984a2bd3 --- /dev/null +++ b/test/cuda/utility/eigh_wrapper.jl @@ -0,0 +1,147 @@ +using Test +using Random +using LinearAlgebra +using TensorKit +using ChainRulesCore, Zygote +using Accessors +using PEPSKit +using CUDA, Adapt +using MatrixAlgebraKit: TruncatedAlgorithm, diagview + +# Gauge-invariant loss function +function lossfun(A, alg, R = randn(space(A)), trunc = notrunc()) + alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) + D, V, = eigh_trunc(A, alg) + return real(dot(R, V * V')) + dot(D, D) # Overlap with random tensor R is gauge-invariant and differentiable +end + +dtype = ComplexF64 +n = 20 +χ = 10 +trunc = truncspace(ℂ^χ) +rtol = 1.0e-9 +Random.seed!(123456789) +r = adapt(CuArray, randn(dtype, ℂ^n, ℂ^n)) +r = 0.5 * (r + r') # make r Hermitian +R = adapt(CuArray, randn(space(r))) +R = 0.5 * (R + R') + +full_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :FullPullback)) +trunc_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :TruncPullback)) +iter_alg = EighAdjoint(; fwd_alg = (; alg = :Lanczos), rrule_alg = (; alg = :TruncPullback)) + +@testset "Non-truncated eigh" begin + l_full, g_full = withgradient(A -> lossfun(A, full_alg, R), r) + l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, R), r) + l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, R), r) + + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol +end + +@testset "Truncated eigh with χ=$χ" begin + l_full, g_full = withgradient(A -> lossfun(A, full_alg, R, trunc), r) + l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, R, trunc), r) + l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, R, trunc), r) + + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol +end + +@testset "Truncated eigh broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] + d, v = eigh_full(r) + d.data[1:2:n] .= d.data[2:2:n] # make every eigenvalue two-fold degenerate + r_degen = v * d * v' + + no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 + small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 + + l_only_cutoff, g_only_cutoff = withgradient( + A -> lossfun(A, alg, R, trunc), r_degen + ) # cutoff sets degenerate difference to zero + l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions + A -> lossfun(A, no_broadening_no_cutoff_alg, R, trunc), r_degen, + ) + l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions + A -> lossfun(A, small_broadening_alg, R, trunc), r_degen, + ) + + @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening + @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient + @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect +end + +symm_m, symm_n = 18, 24 +symm_space = Z2Space(0 => symm_m, 1 => symm_n) +symm_trspace = truncspace(Z2Space(0 => symm_m ÷ 2, 1 => symm_n ÷ 3)) +symm_r = adapt(CuArray, randn(dtype, symm_space, symm_space)) +symm_r = 0.5 * (symm_r + symm_r') +symm_R = adapt(CuArray, randn(dtype, space(symm_r))) +symm_R = 0.5 * (symm_R + symm_R') + +@testset "IterEig of symmetric tensors" begin + l_full, g_full = withgradient(A -> lossfun(A, full_alg, symm_R), symm_r) + l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, symm_R), symm_r) + l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, symm_R), symm_r) + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol + + l_full_tr, g_full_tr = withgradient( + A -> lossfun(A, full_alg, symm_R, symm_trspace), symm_r + ) + l_trunc_tr, g_trunc_tr = withgradient( + A -> lossfun(A, trunc_alg, symm_R, symm_trspace), symm_r + ) + l_iter_tr, g_iter_tr = withgradient( + A -> lossfun(A, iter_alg, symm_R, symm_trspace), symm_r + ) + @test l_full_tr ≈ l_trunc_tr ≈ l_iter_tr + @test g_full_tr[1] ≈ g_trunc_tr[1] rtol = rtol + @test g_full_tr[1] ≈ g_iter_tr[1] rtol = rtol + @test g_trunc_tr[1] ≈ g_iter_tr[1] rtol = rtol + + iter_alg_fallback = @set iter_alg.fwd_alg.fallback_threshold = 0.4 # Do dense decomposition in one block, sparse one in the other + l_iter_fb, g_iter_fb = withgradient( + A -> lossfun(A, iter_alg_fallback, symm_R, symm_trspace), symm_r + ) + @test l_iter_fb ≈ l_trunc_tr ≈ l_full_tr + @test g_full_tr[1] ≈ g_iter_fb[1] rtol = rtol + @test g_trunc_tr[1] ≈ g_iter_fb[1] rtol = rtol +end + +@testset "Truncated symmetric eigh broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] + d, v = eigh_full(symm_r) + # make every singular value in the 0-sector three-fold degenerate + b0 = diagview(block(d, Z2Irrep(0))) + b0[1:3:symm_m] .= b0[3:3:symm_m] + b0[2:3:symm_m] .= b0[3:3:symm_m] + # make every singular value in the 1-sector two-fold degenerate + b1 = diagview(block(d, Z2Irrep(1))) + b1[1:2:symm_n] .= b1[2:2:symm_n] + symm_r_degen = v * d * v' + + no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 + small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 + + l_only_cutoff, g_only_cutoff = withgradient( + A -> lossfun(A, alg, symm_R, symm_trspace), symm_r_degen + ) # cutoff sets degenerate difference to zero + l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions + A -> lossfun(A, no_broadening_no_cutoff_alg, symm_R, symm_trspace), + symm_r_degen, + ) + l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions + A -> lossfun(A, small_broadening_alg, symm_R, symm_trspace), + symm_r_degen, + ) + + @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening + @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient + @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect +end diff --git a/test/rocm/utility/eigh_wrapper.jl b/test/rocm/utility/eigh_wrapper.jl new file mode 100644 index 000000000..3eb750522 --- /dev/null +++ b/test/rocm/utility/eigh_wrapper.jl @@ -0,0 +1,147 @@ +using Test +using Random +using LinearAlgebra +using TensorKit +using ChainRulesCore, Zygote +using Accessors +using PEPSKit +using AMDGPU, Adapt +using MatrixAlgebraKit: TruncatedAlgorithm, diagview + +# Gauge-invariant loss function +function lossfun(A, alg, R = randn(space(A)), trunc = notrunc()) + alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) + D, V, = eigh_trunc(A, alg) + return real(dot(R, V * V')) + dot(D, D) # Overlap with random tensor R is gauge-invariant and differentiable +end + +dtype = ComplexF64 +n = 20 +χ = 10 +trunc = truncspace(ℂ^χ) +rtol = 1.0e-9 +Random.seed!(123456789) +r = adapt(ROCArray, randn(dtype, ℂ^n, ℂ^n)) +r = 0.5 * (r + r') # make r Hermitian +R = adapt(ROCArray, randn(space(r))) +R = 0.5 * (R + R') + +full_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :FullPullback)) +trunc_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :TruncPullback)) +iter_alg = EighAdjoint(; fwd_alg = (; alg = :Lanczos), rrule_alg = (; alg = :TruncPullback)) + +@testset "Non-truncated eigh" begin + l_full, g_full = withgradient(A -> lossfun(A, full_alg, R), r) + l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, R), r) + l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, R), r) + + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol +end + +@testset "Truncated eigh with χ=$χ" begin + l_full, g_full = withgradient(A -> lossfun(A, full_alg, R, trunc), r) + l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, R, trunc), r) + l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, R, trunc), r) + + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol +end + +@testset "Truncated eigh broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] + d, v = eigh_full(r) + d.data[1:2:n] .= d.data[2:2:n] # make every eigenvalue two-fold degenerate + r_degen = v * d * v' + + no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 + small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 + + l_only_cutoff, g_only_cutoff = withgradient( + A -> lossfun(A, alg, R, trunc), r_degen + ) # cutoff sets degenerate difference to zero + l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions + A -> lossfun(A, no_broadening_no_cutoff_alg, R, trunc), r_degen, + ) + l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions + A -> lossfun(A, small_broadening_alg, R, trunc), r_degen, + ) + + @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening + @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient + @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect +end + +symm_m, symm_n = 18, 24 +symm_space = Z2Space(0 => symm_m, 1 => symm_n) +symm_trspace = truncspace(Z2Space(0 => symm_m ÷ 2, 1 => symm_n ÷ 3)) +symm_r = adapt(ROCArray, randn(dtype, symm_space, symm_space)) +symm_r = 0.5 * (symm_r + symm_r') +symm_R = adapt(ROCArray, randn(dtype, space(symm_r))) +symm_R = 0.5 * (symm_R + symm_R') + +@testset "IterEig of symmetric tensors" begin + l_full, g_full = withgradient(A -> lossfun(A, full_alg, symm_R), symm_r) + l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, symm_R), symm_r) + l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, symm_R), symm_r) + @test l_full ≈ l_trunc ≈ l_iter + @test g_full[1] ≈ g_trunc[1] rtol = rtol + @test g_full[1] ≈ g_iter[1] rtol = rtol + @test g_trunc[1] ≈ g_iter[1] rtol = rtol + + l_full_tr, g_full_tr = withgradient( + A -> lossfun(A, full_alg, symm_R, symm_trspace), symm_r + ) + l_trunc_tr, g_trunc_tr = withgradient( + A -> lossfun(A, trunc_alg, symm_R, symm_trspace), symm_r + ) + l_iter_tr, g_iter_tr = withgradient( + A -> lossfun(A, iter_alg, symm_R, symm_trspace), symm_r + ) + @test l_full_tr ≈ l_trunc_tr ≈ l_iter_tr + @test g_full_tr[1] ≈ g_trunc_tr[1] rtol = rtol + @test g_full_tr[1] ≈ g_iter_tr[1] rtol = rtol + @test g_trunc_tr[1] ≈ g_iter_tr[1] rtol = rtol + + iter_alg_fallback = @set iter_alg.fwd_alg.fallback_threshold = 0.4 # Do dense decomposition in one block, sparse one in the other + l_iter_fb, g_iter_fb = withgradient( + A -> lossfun(A, iter_alg_fallback, symm_R, symm_trspace), symm_r + ) + @test l_iter_fb ≈ l_trunc_tr ≈ l_full_tr + @test g_full_tr[1] ≈ g_iter_fb[1] rtol = rtol + @test g_trunc_tr[1] ≈ g_iter_fb[1] rtol = rtol +end + +@testset "Truncated symmetric eigh broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] + d, v = eigh_full(symm_r) + # make every singular value in the 0-sector three-fold degenerate + b0 = diagview(block(d, Z2Irrep(0))) + b0[1:3:symm_m] .= b0[3:3:symm_m] + b0[2:3:symm_m] .= b0[3:3:symm_m] + # make every singular value in the 1-sector two-fold degenerate + b1 = diagview(block(d, Z2Irrep(1))) + b1[1:2:symm_n] .= b1[2:2:symm_n] + symm_r_degen = v * d * v' + + no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 + small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 + + l_only_cutoff, g_only_cutoff = withgradient( + A -> lossfun(A, alg, symm_R, symm_trspace), symm_r_degen + ) # cutoff sets degenerate difference to zero + l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions + A -> lossfun(A, no_broadening_no_cutoff_alg, symm_R, symm_trspace), + symm_r_degen, + ) + l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions + A -> lossfun(A, small_broadening_alg, symm_R, symm_trspace), + symm_r_degen, + ) + + @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening + @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient + @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect +end From b73ac9e0e9a1c418687e9bbb8192019dd61a020c Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 15 Aug 2026 11:18:29 +0200 Subject: [PATCH 33/69] Switch to CUDACore and more tests working --- Project.toml | 2 +- test/Project.toml | 21 ++++++++++ test/cuda/bondenv/benv_ctm.jl | 2 +- test/cuda/bondenv/benv_gaugefix.jl | 2 +- test/cuda/bondenv/bond_truncate.jl | 2 +- test/cuda/boundarymps/vumps.jl | 2 +- test/cuda/bp/expvals.jl | 2 +- test/cuda/bp/gaugefix.jl | 2 +- test/cuda/bp/rotation.jl | 2 +- test/cuda/bp/unitcell.jl | 2 +- test/cuda/compress/local.jl | 2 +- test/cuda/ctmrg/contractions.jl | 2 +- test/cuda/ctmrg/fixed_iterscheme.jl | 2 +- test/cuda/ctmrg/flavors.jl | 2 +- test/cuda/ctmrg/gaugefix.jl | 2 +- test/cuda/ctmrg/initialization.jl | 2 +- test/cuda/ctmrg/jacobian_real_linear.jl | 2 +- test/cuda/ctmrg/partition_function.jl | 2 +- test/cuda/ctmrg/pepo.jl | 2 +- test/cuda/ctmrg/suweight.jl | 2 +- test/cuda/ctmrg/unitcell.jl | 2 +- test/cuda/timeevol/cluster_projectors.jl | 2 +- test/cuda/timeevol/j1j2_finiteT.jl | 2 +- test/cuda/timeevol/sitedep_truncation.jl | 2 +- test/cuda/timeevol/tf_ising_finiteT.jl | 2 +- test/cuda/timeevol/timestep.jl | 2 +- test/cuda/toolbox/densitymatrices.jl | 2 +- test/cuda/utility/correlator.jl | 2 +- test/cuda/utility/eigh_wrapper.jl | 6 +-- test/cuda/utility/retractions.jl | 34 +++++++++++++++ test/cuda/utility/svd_wrapper.jl | 2 +- test/cuda/utility/symmetrization.jl | 53 ++++++++++++++++++++++++ test/rocm/utility/retractions.jl | 34 +++++++++++++++ test/rocm/utility/symmetrization.jl | 53 ++++++++++++++++++++++++ test/runtests.jl | 2 +- 35 files changed, 227 insertions(+), 32 deletions(-) create mode 100644 test/cuda/utility/retractions.jl create mode 100644 test/cuda/utility/symmetrization.jl create mode 100644 test/rocm/utility/retractions.jl create mode 100644 test/rocm/utility/symmetrization.jl diff --git a/Project.toml b/Project.toml index f92e372a2..198405b8f 100644 --- a/Project.toml +++ b/Project.toml @@ -37,7 +37,7 @@ GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" [sources] MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} -MatrixAlgebraKit = {rev = "main", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} +MatrixAlgebraKit = {rev = "ksh/svd_trunc_gpu", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} TensorOperations = {rev = "main", url = "https://github.com/QuantumKitHub/TensorOperations.jl"} [extensions] diff --git a/test/Project.toml b/test/Project.toml index 3dc295f54..b4d761a57 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -8,6 +8,9 @@ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDACore = "bd0ed864-bdfe-4181-a5ed-ce625a5fdea2" +CUPTI = "9e67e8f6-ba02-4b6c-a7db-3b11ae1e7ab7" +CUDATools = "9ec180c6-1c07-47c7-9e6e-ebefa4d1f6d0" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -27,15 +30,33 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" +cuBLAS = "182d3088-87b7-4494-8cad-fc6afaa545bc" +cuFFT = "533571aa-0936-420e-b4be-9c66f5f626ca" +cuRAND = "20fd9a0b-12d5-4c2f-a8af-7c34e9e60431" +cuSOLVER = "887afef0-6a32-4de5-add4-7827692ba8fc" +cuSPARSE = "b26da814-b3bc-49ef-b0ee-c816305aa060" [sources] PEPSKit = {path = ".."} GPUArrays = {url = "https://github.com/JuliaGPU/GPUArrays.jl", rev = "main"} +AMDGPU = {url = "https://github.com/JuliaGPU/AMDGPU.jl", rev = "main"} +CUDA = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main"} +CUDACore = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "CUDACore"} +CUDATools ={url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "CUDATools"} +CUPTI = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cupti"} +NVML = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/nvml"} +cuBLAS = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cublas"} +cuFFT = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cufft"} +cuRAND = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/curand"} +cuSOLVER = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cusolver"} +cuSPARSE = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cusparse"} [compat] AMDGPU = "2" ChainRulesTestUtils = "1.13" CUDA = "6" +CUDACore = "6" +CUDATools = "6" ParallelTestRunner = "2.6.0" JLArrays = "0.3, 0.4" QuadGK = "2.11.1" diff --git a/test/cuda/bondenv/benv_ctm.jl b/test/cuda/bondenv/benv_ctm.jl index 969f7c6d0..a91e40d8a 100644 --- a/test/cuda/bondenv/benv_ctm.jl +++ b/test/cuda/bondenv/benv_ctm.jl @@ -3,7 +3,7 @@ using TensorKit using PEPSKit using LinearAlgebra using Random -using CUDA, Adapt +using CUDACore, Adapt Random.seed!(100) Nr, Nc = 2, 2 diff --git a/test/cuda/bondenv/benv_gaugefix.jl b/test/cuda/bondenv/benv_gaugefix.jl index bb78584be..9b1b91bd0 100644 --- a/test/cuda/bondenv/benv_gaugefix.jl +++ b/test/cuda/bondenv/benv_gaugefix.jl @@ -3,7 +3,7 @@ using TensorKit using PEPSKit using LinearAlgebra using Random -using CUDA, Adapt +using CUDACore, Adapt Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 1, (1, -1) => 2) Vin = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 3, (1, -1) => 2) diff --git a/test/cuda/bondenv/bond_truncate.jl b/test/cuda/bondenv/bond_truncate.jl index 9a78d44fe..67a0c1a3f 100644 --- a/test/cuda/bondenv/bond_truncate.jl +++ b/test/cuda/bondenv/bond_truncate.jl @@ -5,7 +5,7 @@ using PEPSKit using LinearAlgebra using PEPSKit: bond_truncate, cost_function_als using PEPSKit: _combine_ket, _combine_ket_for_svd -using CUDA, Adapt +using CUDACore, Adapt Random.seed!(0) maxiter = 600 diff --git a/test/cuda/boundarymps/vumps.jl b/test/cuda/boundarymps/vumps.jl index 21e147da9..0262d28d8 100644 --- a/test/cuda/boundarymps/vumps.jl +++ b/test/cuda/boundarymps/vumps.jl @@ -4,7 +4,7 @@ using PEPSKit using TensorKit using MPSKit using LinearAlgebra -using Adapt, CUDA +using Adapt, CUDACore Random.seed!(29384293742893) diff --git a/test/cuda/bp/expvals.jl b/test/cuda/bp/expvals.jl index fe30cb409..e9139db64 100644 --- a/test/cuda/bp/expvals.jl +++ b/test/cuda/bp/expvals.jl @@ -3,7 +3,7 @@ using Random using TensorKit using PEPSKit using PEPSKit: random_dual! -using CUDA, Adapt +using CUDACore, Adapt ds = Dict( U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), diff --git a/test/cuda/bp/gaugefix.jl b/test/cuda/bp/gaugefix.jl index 13de9ea6b..a182b222b 100644 --- a/test/cuda/bp/gaugefix.jl +++ b/test/cuda/bp/gaugefix.jl @@ -4,7 +4,7 @@ using TensorKit using PEPSKit using PEPSKit: compare_weights, random_dual!, twistdual using PEPSKit: _next, _is_bipartite -using CUDA, Adapt +using CUDACore, Adapt @testset "BP vs SU ($S, bipartite = $(bipartite), posdef msgs = $h)" for (S, bipartite, h) in Iterators.product( diff --git a/test/cuda/bp/rotation.jl b/test/cuda/bp/rotation.jl index e3aa0d39e..6946b3c68 100644 --- a/test/cuda/bp/rotation.jl +++ b/test/cuda/bp/rotation.jl @@ -3,7 +3,7 @@ using Random using TensorKit using PEPSKit using PEPSKit: random_dual! -using CUDA, Adapt +using CUDACore, Adapt ds = Dict( Trivial => ℂ^2, diff --git a/test/cuda/bp/unitcell.jl b/test/cuda/bp/unitcell.jl index cb5105adf..0a92e9ad5 100644 --- a/test/cuda/bp/unitcell.jl +++ b/test/cuda/bp/unitcell.jl @@ -3,7 +3,7 @@ using Random using PEPSKit using PEPSKit: bp_iteration using TensorKit -using CUDA, Adapt +using CUDACore, Adapt # settings Random.seed!(91283219347) diff --git a/test/cuda/compress/local.jl b/test/cuda/compress/local.jl index 8a877f1bb..3eda9e39b 100644 --- a/test/cuda/compress/local.jl +++ b/test/cuda/compress/local.jl @@ -4,7 +4,7 @@ using LinearAlgebra using TensorKit using PEPSKit using PEPSKit: virtual_projector -using CUDA, Adapt +using CUDACore, Adapt """ Cost function of LocalTruncation. diff --git a/test/cuda/ctmrg/contractions.jl b/test/cuda/ctmrg/contractions.jl index 817255458..d7f9cdd38 100644 --- a/test/cuda/ctmrg/contractions.jl +++ b/test/cuda/ctmrg/contractions.jl @@ -2,7 +2,7 @@ using Test using Random using PEPSKit using TensorKit -using CUDA, Adapt +using CUDACore, Adapt using PEPSKit: eachcoordinate, _next_coordinate using PEPSKit: EnlargedCorner, HalfInfiniteEnv, FullInfiniteEnv diff --git a/test/cuda/ctmrg/fixed_iterscheme.jl b/test/cuda/ctmrg/fixed_iterscheme.jl index b525ff087..01abc9470 100644 --- a/test/cuda/ctmrg/fixed_iterscheme.jl +++ b/test/cuda/ctmrg/fixed_iterscheme.jl @@ -5,7 +5,7 @@ using Random using LinearAlgebra using TensorKit, KrylovKit using PEPSKit -using CUDA, Adapt +using CUDACore, Adapt using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, diff --git a/test/cuda/ctmrg/flavors.jl b/test/cuda/ctmrg/flavors.jl index 0bebb4eb4..e76e488d1 100644 --- a/test/cuda/ctmrg/flavors.jl +++ b/test/cuda/ctmrg/flavors.jl @@ -4,7 +4,7 @@ using MatrixAlgebraKit using TensorKit using MPSKit using PEPSKit -using CUDA, Adapt +using CUDACore, Adapt using PEPSKit: peps_normalize # initialize parameters diff --git a/test/cuda/ctmrg/gaugefix.jl b/test/cuda/ctmrg/gaugefix.jl index c6e1b1f9d..169135ec9 100644 --- a/test/cuda/ctmrg/gaugefix.jl +++ b/test/cuda/ctmrg/gaugefix.jl @@ -2,7 +2,7 @@ using Test using Random using PEPSKit using TensorKit -using CUDA, Adapt +using CUDACore, Adapt using PEPSKit: ctmrg_iteration, calc_elementwise_convergence using PEPSKit: ScramblingEnvGauge, ScramblingEnvGaugeC4v using PEPSKit: peps_normalize diff --git a/test/cuda/ctmrg/initialization.jl b/test/cuda/ctmrg/initialization.jl index 375d982c6..6bf3ffd24 100644 --- a/test/cuda/ctmrg/initialization.jl +++ b/test/cuda/ctmrg/initialization.jl @@ -2,7 +2,7 @@ using Test using TensorKit using PEPSKit using Random -using Adapt, CUDA +using Adapt, CUDACore using MPSKitModels: classical_ising using PEPSKit: ProductStateEnv diff --git a/test/cuda/ctmrg/jacobian_real_linear.jl b/test/cuda/ctmrg/jacobian_real_linear.jl index 5e9bfa8ac..43d1ed9aa 100644 --- a/test/cuda/ctmrg/jacobian_real_linear.jl +++ b/test/cuda/ctmrg/jacobian_real_linear.jl @@ -3,7 +3,7 @@ using Random using Accessors using Zygote using TensorKit, KrylovKit, PEPSKit -using CUDA, Adapt +using CUDACore, Adapt using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, fix_phases, ScramblingEnvGauge diff --git a/test/cuda/ctmrg/partition_function.jl b/test/cuda/ctmrg/partition_function.jl index 4ce4f515f..0eb5a8069 100644 --- a/test/cuda/ctmrg/partition_function.jl +++ b/test/cuda/ctmrg/partition_function.jl @@ -5,7 +5,7 @@ using PEPSKit using TensorKit using QuadGK using Test -using CUDA, Adapt +using CUDACore, Adapt @testset "Check spaces in partition function CTMRG" begin zA = randn(ℂ^6 ⊗ ℂ^8 ← ℂ^4 ⊗ ℂ^2) diff --git a/test/cuda/ctmrg/pepo.jl b/test/cuda/ctmrg/pepo.jl index 462f9a990..a03321ef2 100644 --- a/test/cuda/ctmrg/pepo.jl +++ b/test/cuda/ctmrg/pepo.jl @@ -6,7 +6,7 @@ using TensorKit using KrylovKit using OptimKit using Zygote -using CUDA, Adapt +using CUDACore, Adapt ## Setup function three_dimensional_classical_ising(; beta, J = 1.0) diff --git a/test/cuda/ctmrg/suweight.jl b/test/cuda/ctmrg/suweight.jl index b115c54eb..8b9ace1ae 100644 --- a/test/cuda/ctmrg/suweight.jl +++ b/test/cuda/ctmrg/suweight.jl @@ -1,7 +1,7 @@ using Test using Random using TensorKit -using CUDA, Adapt +using CUDACore, Adapt using PEPSKit using PEPSKit: str, twistdual, unitcell diff --git a/test/cuda/ctmrg/unitcell.jl b/test/cuda/ctmrg/unitcell.jl index 69dbeee6b..64cd405d1 100644 --- a/test/cuda/ctmrg/unitcell.jl +++ b/test/cuda/ctmrg/unitcell.jl @@ -3,7 +3,7 @@ using Random using PEPSKit using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, ScramblingEnvGauge using TensorKit -using CUDA, Adapt +using CUDACore, Adapt # settings Random.seed!(91283219347) diff --git a/test/cuda/timeevol/cluster_projectors.jl b/test/cuda/timeevol/cluster_projectors.jl index 3aba45dfe..3ffb8d8ea 100644 --- a/test/cuda/timeevol/cluster_projectors.jl +++ b/test/cuda/timeevol/cluster_projectors.jl @@ -6,7 +6,7 @@ using Random import MPSKitModels: hubbard_space using PEPSKit: sdiag_pow, _cluster_truncate!, _flip_virtuals! using MPSKit: GenericMPSTensor, MPSBondTensor -using CUDA, Adapt +using CUDACore, Adapt # Utility setup # ------------- diff --git a/test/cuda/timeevol/j1j2_finiteT.jl b/test/cuda/timeevol/j1j2_finiteT.jl index bda0b4346..002ab7573 100644 --- a/test/cuda/timeevol/j1j2_finiteT.jl +++ b/test/cuda/timeevol/j1j2_finiteT.jl @@ -3,7 +3,7 @@ using LinearAlgebra using TensorKit import MPSKitModels: σˣ, σᶻ using PEPSKit -using CUDA, Adapt +using CUDACore, Adapt # Benchmark energy from high-temperature expansion # at β = 0.3, 0.6 diff --git a/test/cuda/timeevol/sitedep_truncation.jl b/test/cuda/timeevol/sitedep_truncation.jl index b3adc8851..80549b4c7 100644 --- a/test/cuda/timeevol/sitedep_truncation.jl +++ b/test/cuda/timeevol/sitedep_truncation.jl @@ -3,7 +3,7 @@ using Random using TensorKit using PEPSKit using PEPSKit: _is_bipartite, _get_fixedspacetrunc -using CUDA, Adapt +using CUDACore, Adapt elt = Float64 Nr, Nc = 2, 2 diff --git a/test/cuda/timeevol/tf_ising_finiteT.jl b/test/cuda/timeevol/tf_ising_finiteT.jl index a2974a592..9813ec9bf 100644 --- a/test/cuda/timeevol/tf_ising_finiteT.jl +++ b/test/cuda/timeevol/tf_ising_finiteT.jl @@ -2,7 +2,7 @@ using Test using LinearAlgebra using TensorKit import MPSKitModels: σˣ, σᶻ -using PEPSKit, CUDA, Adapt +using PEPSKit, CUDACore, Adapt # Benchmark data of [σx, σz] from HOTRG # Physical Review B 86, 045139 (2012) Fig. 15-16 diff --git a/test/cuda/timeevol/timestep.jl b/test/cuda/timeevol/timestep.jl index c99254566..6e1934f8d 100644 --- a/test/cuda/timeevol/timestep.jl +++ b/test/cuda/timeevol/timestep.jl @@ -2,7 +2,7 @@ using Test using Random using TensorKit using PEPSKit -using CUDA, Adapt +using CUDACore, Adapt @testset "SimpleUpdate timestep" begin Nr, Nc = 2, 2 diff --git a/test/cuda/toolbox/densitymatrices.jl b/test/cuda/toolbox/densitymatrices.jl index 8ee47d574..c04b34b04 100644 --- a/test/cuda/toolbox/densitymatrices.jl +++ b/test/cuda/toolbox/densitymatrices.jl @@ -3,7 +3,7 @@ using PEPSKit using PEPSKit: contract_local_operator, contract_local_norm using Test using TestExtras -using CUDA, Adapt +using CUDACore, Adapt ds = Dict(Trivial => ℂ^2, U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), FermionParity => Vect[FermionParity](0 => 2, 1 => 1)) Ds = Dict(Trivial => ℂ^3, U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 2, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) diff --git a/test/cuda/utility/correlator.jl b/test/cuda/utility/correlator.jl index 28d252ae9..47fb4dade 100644 --- a/test/cuda/utility/correlator.jl +++ b/test/cuda/utility/correlator.jl @@ -2,7 +2,7 @@ using Test using Random using TensorKit using PEPSKit -using CUDA, Adapt +using CUDACore, Adapt const syms = (Z2Irrep, FermionParity) diff --git a/test/cuda/utility/eigh_wrapper.jl b/test/cuda/utility/eigh_wrapper.jl index 9984a2bd3..319b5f713 100644 --- a/test/cuda/utility/eigh_wrapper.jl +++ b/test/cuda/utility/eigh_wrapper.jl @@ -5,7 +5,7 @@ using TensorKit using ChainRulesCore, Zygote using Accessors using PEPSKit -using CUDA, Adapt +using CUDACore, Adapt using MatrixAlgebraKit: TruncatedAlgorithm, diagview # Gauge-invariant loss function @@ -26,8 +26,8 @@ r = 0.5 * (r + r') # make r Hermitian R = adapt(CuArray, randn(space(r))) R = 0.5 * (R + R') -full_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :FullPullback)) -trunc_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :TruncPullback)) +full_alg = EighAdjoint(; fwd_alg = (; alg = :DivideAndConquer), rrule_alg = (; alg = :FullPullback)) +trunc_alg = EighAdjoint(; fwd_alg = (; alg = :DivideAndConquer), rrule_alg = (; alg = :TruncPullback)) iter_alg = EighAdjoint(; fwd_alg = (; alg = :Lanczos), rrule_alg = (; alg = :TruncPullback)) @testset "Non-truncated eigh" begin diff --git a/test/cuda/utility/retractions.jl b/test/cuda/utility/retractions.jl new file mode 100644 index 000000000..a196ae15c --- /dev/null +++ b/test/cuda/utility/retractions.jl @@ -0,0 +1,34 @@ +using Test +using Random +using LinearAlgebra +using TensorKit +using VectorInterface +using PEPSKit +using Adapt, CUDA + +dtype = ComplexF64 +Vphyss = [ℂ^2, U1Space(0 => 1, -1 => 1, 1 => 1)] +Vpepss = [ℂ^4, U1Space(0 => 2, -1 => 1, 1 => 1)] + +@testset "Norm-preserving tensor retractions for sectortype $(sectortype(Vphyss[i]))" for i in + eachindex( + Vphyss + ) + Vphys = Vphyss[i] + Vpeps = Vpepss[i] + peps_space = Vphys ← Vpeps ⊗ Vpeps ⊗ Vpeps' ⊗ Vpeps' + + α = 1.0e-1 * randn(Float64) + A = adapt(CuArray, randn(dtype, peps_space)) + normalized_A = scale(A, inv(norm(A))) + η = adapt(CuArray, randn(dtype, peps_space)) + ζ = adapt(CuArray, randn(dtype, peps_space)) + add!(η, normalized_A, -inner(normalized_A, η)) + add!(ζ, normalized_A, -inner(normalized_A, ζ)) + + A´, ξ = PEPSKit.norm_preserving_retract(A, η, α) + @test norm(A´) ≈ norm(A) rtol = 1.0e-12 + + PEPSKit.norm_preserving_transport!(ζ, A, η, α, A´) + @test inner(ζ, A´) ≈ 0 atol = 1.0e-12 +end diff --git a/test/cuda/utility/svd_wrapper.jl b/test/cuda/utility/svd_wrapper.jl index c813f7d3c..a95c3700e 100644 --- a/test/cuda/utility/svd_wrapper.jl +++ b/test/cuda/utility/svd_wrapper.jl @@ -5,7 +5,7 @@ using TensorKit using ChainRulesCore, Zygote using Accessors using PEPSKit -using Adapt, CUDA +using Adapt, CUDACore using MatrixAlgebraKit: TruncatedAlgorithm, diagview, svd_trunc_no_error # Gauge-invariant loss function diff --git a/test/cuda/utility/symmetrization.jl b/test/cuda/utility/symmetrization.jl new file mode 100644 index 000000000..be424230a --- /dev/null +++ b/test/cuda/utility/symmetrization.jl @@ -0,0 +1,53 @@ +using Test +using PEPSKit +using PEPSKit: herm_depth, herm_width, _fit_spaces +using TensorKit +using Adapt, CUDA + +@testset "ReflectDepth" for unitcell in [(1, 1), (2, 2), (3, 3), (3, 2)] + peps = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) + + peps_depth = symmetrize!(deepcopy(peps), ReflectDepth()) + peps_reflect = _fit_spaces( + InfinitePEPS(reverse(map(herm_depth, peps_depth.A); dims = 1)), peps_depth + ) + @test peps_depth ≈ peps_reflect +end + +@testset "ReflectWidth" for unitcell in [(1, 1), (2, 2), (3, 3), (3, 2)] + peps = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) + + peps_width = symmetrize!(deepcopy(peps), ReflectWidth()) + peps_reflect = _fit_spaces( + InfinitePEPS(reverse(map(herm_width, peps_width.A); dims = 2)), peps_width + ) + @test peps_width ≈ peps_reflect +end + +@testset "Rotate" for unitcell in [(1, 1), (2, 2), (3, 3)] + peps = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) + + peps_rot = symmetrize!(deepcopy(peps), Rotate()) + @test peps_rot ≈ _fit_spaces(rotl90(peps_rot), peps_rot) + @test peps_rot ≈ _fit_spaces(rot180(peps_rot), peps_rot) + @test peps_rot ≈ _fit_spaces(rotr90(peps_rot), peps_rot) +end + +@testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] + peps = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) + + peps_full = symmetrize!(deepcopy(peps), RotateReflect()) + @test peps_full ≈ _fit_spaces(rotl90(peps_full), peps_full) + @test peps_full ≈ _fit_spaces(rot180(peps_full), peps_full) + @test peps_full ≈ _fit_spaces(rotr90(peps_full), peps_full) + + peps_reflect_depth = _fit_spaces( + InfinitePEPS(reverse(map(herm_depth, peps_full.A); dims = 1)), peps_full + ) + @test peps_full ≈ peps_reflect_depth + + peps_reflect_width = _fit_spaces( + InfinitePEPS(reverse(map(herm_width, peps_full.A); dims = 2)), peps_full + ) + @test peps_full ≈ peps_reflect_width +end diff --git a/test/rocm/utility/retractions.jl b/test/rocm/utility/retractions.jl new file mode 100644 index 000000000..3cbb68b75 --- /dev/null +++ b/test/rocm/utility/retractions.jl @@ -0,0 +1,34 @@ +using Test +using Random +using LinearAlgebra +using TensorKit +using VectorInterface +using PEPSKit +using Adapt, AMDGPU + +dtype = ComplexF64 +Vphyss = [ℂ^2, U1Space(0 => 1, -1 => 1, 1 => 1)] +Vpepss = [ℂ^4, U1Space(0 => 2, -1 => 1, 1 => 1)] + +@testset "Norm-preserving tensor retractions for sectortype $(sectortype(Vphyss[i]))" for i in + eachindex( + Vphyss + ) + Vphys = Vphyss[i] + Vpeps = Vpepss[i] + peps_space = Vphys ← Vpeps ⊗ Vpeps ⊗ Vpeps' ⊗ Vpeps' + + α = 1.0e-1 * randn(Float64) + A = adapt(ROCArray, randn(dtype, peps_space)) + normalized_A = scale(A, inv(norm(A))) + η = adapt(ROCArray, randn(dtype, peps_space)) + ζ = adapt(ROCArray, randn(dtype, peps_space)) + add!(η, normalized_A, -inner(normalized_A, η)) + add!(ζ, normalized_A, -inner(normalized_A, ζ)) + + A´, ξ = PEPSKit.norm_preserving_retract(A, η, α) + @test norm(A´) ≈ norm(A) rtol = 1.0e-12 + + PEPSKit.norm_preserving_transport!(ζ, A, η, α, A´) + @test inner(ζ, A´) ≈ 0 atol = 1.0e-12 +end diff --git a/test/rocm/utility/symmetrization.jl b/test/rocm/utility/symmetrization.jl new file mode 100644 index 000000000..0a5cd4073 --- /dev/null +++ b/test/rocm/utility/symmetrization.jl @@ -0,0 +1,53 @@ +using Test +using PEPSKit +using PEPSKit: herm_depth, herm_width, _fit_spaces +using TensorKit +using Adapt, AMDGPU + +@testset "ReflectDepth" for unitcell in [(1, 1), (2, 2), (3, 3), (3, 2)] + peps = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) + + peps_depth = symmetrize!(deepcopy(peps), ReflectDepth()) + peps_reflect = _fit_spaces( + InfinitePEPS(reverse(map(herm_depth, peps_depth.A); dims = 1)), peps_depth + ) + @test peps_depth ≈ peps_reflect +end + +@testset "ReflectWidth" for unitcell in [(1, 1), (2, 2), (3, 3), (3, 2)] + peps = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) + + peps_width = symmetrize!(deepcopy(peps), ReflectWidth()) + peps_reflect = _fit_spaces( + InfinitePEPS(reverse(map(herm_width, peps_width.A); dims = 2)), peps_width + ) + @test peps_width ≈ peps_reflect +end + +@testset "Rotate" for unitcell in [(1, 1), (2, 2), (3, 3)] + peps = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) + + peps_rot = symmetrize!(deepcopy(peps), Rotate()) + @test peps_rot ≈ _fit_spaces(rotl90(peps_rot), peps_rot) + @test peps_rot ≈ _fit_spaces(rot180(peps_rot), peps_rot) + @test peps_rot ≈ _fit_spaces(rotr90(peps_rot), peps_rot) +end + +@testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] + peps = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) + + peps_full = symmetrize!(deepcopy(peps), RotateReflect()) + @test peps_full ≈ _fit_spaces(rotl90(peps_full), peps_full) + @test peps_full ≈ _fit_spaces(rot180(peps_full), peps_full) + @test peps_full ≈ _fit_spaces(rotr90(peps_full), peps_full) + + peps_reflect_depth = _fit_spaces( + InfinitePEPS(reverse(map(herm_depth, peps_full.A); dims = 1)), peps_full + ) + @test peps_full ≈ peps_reflect_depth + + peps_reflect_width = _fit_spaces( + InfinitePEPS(reverse(map(herm_width, peps_full.A); dims = 2)), peps_full + ) + @test peps_full ≈ peps_reflect_width +end diff --git a/test/runtests.jl b/test/runtests.jl index 1176447fe..4e5adf2f6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,7 +7,7 @@ testsuite = find_tests(@__DIR__) # remove testsuite filter!(!(startswith("testsuite") ∘ first), testsuite) # CUDA tests: only run if CUDA is functional -using CUDA: CUDA +using CUDA CUDA.functional() || filter!(!startswith("cuda") ∘ first, testsuite) # AMDGPU tests: only run if AMDGPU is functional using AMDGPU From 7a6182e14807a85ffbf1b997e798fa26fc43d70d Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 15 Aug 2026 21:21:09 +0200 Subject: [PATCH 34/69] Gradient tests --- test/cuda/gradients/c4v_ctmrg_gradients.jl | 131 +++++++++++++++ test/cuda/gradients/ctmrg_gradients.jl | 175 +++++++++++++++++++++ test/rocm/gradients/c4v_ctmrg_gradients.jl | 131 +++++++++++++++ test/rocm/gradients/ctmrg_gradients.jl | 175 +++++++++++++++++++++ 4 files changed, 612 insertions(+) create mode 100644 test/cuda/gradients/c4v_ctmrg_gradients.jl create mode 100644 test/cuda/gradients/ctmrg_gradients.jl create mode 100644 test/rocm/gradients/c4v_ctmrg_gradients.jl create mode 100644 test/rocm/gradients/ctmrg_gradients.jl diff --git a/test/cuda/gradients/c4v_ctmrg_gradients.jl b/test/cuda/gradients/c4v_ctmrg_gradients.jl new file mode 100644 index 000000000..46e515e19 --- /dev/null +++ b/test/cuda/gradients/c4v_ctmrg_gradients.jl @@ -0,0 +1,131 @@ +using Test +using Random +using PEPSKit +using TensorKit +using Zygote +using OptimKit +using KrylovKit +using CUDA, Adapt + +sd = 42039482052 + +## Test C4v CTMRG gradients +# ------------------------------------------- +χbond = 2 +χenv = 6 +symmetry = RotateReflect() +Pspaces = [ComplexSpace(2)] +Vspaces = [ComplexSpace(χbond)] +Espaces = [ComplexSpace(χenv)] +models = [adapt(CuArray, heisenberg_XYZ(InfiniteSquare()))] +names = ["Heisenberg"] + +gradtol = 1.0e-4 +ctmrg_verbosity = 1 +ctmrg_algs = [[:C4vCTMRG]] +projector_algs = [[:C4vEighProjector, :C4vQRProjector]] +decomposition_rrule_algs = [[:FullPullback, :TruncPullback]] +gradient_algs = [[nothing, :FixedPointGradient]] +# the gradient solvers are device-independent and are covered exhaustively by the CPU test, +# so only keep the two KrylovKit code paths here, since GPU gradients are slow +gradient_solver_algs = [[:Arnoldi, :GMRES]] +steps = -0.01:0.005:0.01 + +# record which rrule alg is compatible with which projector alg +allowed_rrule_algs = Dict( + :C4vEighProjector => keys(PEPSKit.EIGH_RRULE_SYMBOLS), + :C4vQRProjector => keys(PEPSKit.QR_RRULE_SYMBOLS), +) + +# be selective on which configurations to test the naive gradient for +naive_gradient_combinations = [(:C4vCTMRG, :C4vEighProjector, :FullPullback), (:C4vCTMRG, :C4vQRProjector, :FullPullback)] +naive_gradient_done = Set() + +## Tests +# ------ +@testset "AD C4v CTMRG energy gradients for $(names[i]) model" verbose = true for i in + eachindex( + models + ) + Pspace = Pspaces[i] + Vspace = Vspaces[i] + Espace = Espaces[i] + calgs = ctmrg_algs[i] + palgs = projector_algs[i] + dalgs = decomposition_rrule_algs[i] + galgs = gradient_algs[i] + gsalgs = gradient_solver_algs[i] + @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=(alg = :$gradient_alg, solver_alg = :$gradient_solver_alg)" for ( + ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, gradient_solver_alg, + ) in Iterators.product( + calgs, palgs, dalgs, galgs, gsalgs + ) + + # check for allowed algorithm combinations when testing naive gradient + if isnothing(gradient_alg) + combo = (ctmrg_alg, projector_alg, decomposition_rrule_alg) + combo in naive_gradient_combinations || continue + combo in naive_gradient_done && continue + push!(naive_gradient_done, combo) + gradient_solver_alg = nothing # unused in naive gradient, so set to nothing to avoid confusion + end + + # check for allowed combinations of projector alg and decomposition rrule alg + decomposition_rrule_alg in allowed_rrule_algs[projector_alg] || continue + + # construct appropriate decomposition struct to pass custom rrule alg + decomposition_alg = if projector_alg == :C4vEighProjector + EighAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) + elseif projector_alg == :C4vQRProjector + QRAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) + else + error("unknown projector alg: $projector_alg") + end + + @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg)) on $(names[i])" + Random.seed!(sd) + dir = adapt(CuArray, InfinitePEPS(Pspace, Vspace)) + psi = adapt(CuArray, InfinitePEPS(Pspace, Vspace)) + symmetrize!(psi, symmetry) + symmetrize!(dir, symmetry) + # instantiate to avoid having to type this twice... + contrete_ctmrg_alg = PEPSKit.CTMRGAlgorithm(; + alg = ctmrg_alg, + verbosity = ctmrg_verbosity, + projector_alg = projector_alg, + decomposition_alg, + ) + # instantiate because hook_pullback doesn't go through the keyword selector... + concrete_gradient_alg = if isnothing(gradient_alg) + nothing # TODO: add this to the PEPSKit.GradientAlgorithm selector? + else + PEPSKit.GradientAlgorithm(; + alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol) + ) + end + env0 = PEPSKit.initialize_random_c4v_env(psi, Espace) + env, = leading_boundary(env0, psi, contrete_ctmrg_alg) + alphas, fs, dfs1, dfs2 = OptimKit.optimtest( + (psi, env), + dir; + alpha = steps, + retract = PEPSKit.peps_retract, + inner = PEPSKit.real_inner, + ) do (peps, env) + E, g = Zygote.withgradient(peps) do psi + env2, = PEPSKit.hook_pullback( + leading_boundary, + env, + psi, + contrete_ctmrg_alg; + alg_rrule = concrete_gradient_alg, + ) + return cost_function(psi, env2, models[i]) + end + g = only(g) + symmetrize!(g, symmetry) + return E, g + end + @test dfs1 ≈ dfs2 atol = 1.0e-2 + end +end diff --git a/test/cuda/gradients/ctmrg_gradients.jl b/test/cuda/gradients/ctmrg_gradients.jl new file mode 100644 index 000000000..3cdab9245 --- /dev/null +++ b/test/cuda/gradients/ctmrg_gradients.jl @@ -0,0 +1,175 @@ +using Test +using Random +using PEPSKit +using TensorKit +using Zygote +using OptimKit +using KrylovKit +using CUDA, Adapt + +## Test models, gradmodes and CTMRG algorithm +# ------------------------------------------- +χbond = 2 +χenv = 6 +Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] +Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] +Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] +models = [ + adapt(CuArray, heisenberg_XYZ(InfiniteSquare())), + adapt(CuArray, pwave_superconductor(InfiniteSquare())), +] +names = ["Heisenberg", "p-wave superconductor"] + +gradtol = 1.0e-4 +ctmrg_verbosity = 0 +ctmrg_algs = [[:SequentialCTMRG, :SimultaneousCTMRG], [:SequentialCTMRG, :SimultaneousCTMRG]] +projector_algs = [[:HalfInfiniteProjector, :FullInfiniteProjector], [:HalfInfiniteProjector, :FullInfiniteProjector]] +svd_rrule_algs = [[:FullPullback, :TruncPullback, :Arnoldi], [:FullPullback, :Arnoldi]] +gradient_algs = [[nothing, :FixedPointGradient], [:FixedPointGradient]] +# the gradient solvers are device-independent and are covered exhaustively by the CPU test, +# so only keep the two KrylovKit code paths here, since GPU gradients are slow +gradient_solver_algs = [[:Arnoldi, :GMRES], [:Arnoldi, :GMRES]] +steps = -0.01:0.005:0.01 + +# don't check naive AD gradients for all algorithm combinations, since it's slow +naive_gradient_combinations = [ + (:SimultaneousCTMRG, :HalfInfiniteProjector, :FullPullback), + (:SimultaneousCTMRG, :FullInfiniteProjector, :FullPullback), + (:SequentialCTMRG, :HalfInfiniteProjector, :FullPullback), +] +naive_gradient_done = Set() + +# fixed-point differentiation is incompatible with sequential CTMRG +function _check_disallowed_combination( + ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg + ) + ctmrg_alg == :SequentialCTMRG && !isnothing(gradient_alg) && return true + return false +end + + +## Tests +# ------ +@testset "AD CTMRG energy gradients for $(names[i]) model" verbose = true for i in + eachindex( + models + ) + Pspace = Pspaces[i] + Vspace = Vspaces[i] + Espace = Espaces[i] + calgs = ctmrg_algs[i] + palgs = projector_algs[i] + salgs = svd_rrule_algs[i] + galgs = gradient_algs[i] + gsalgs = gradient_solver_algs[i] + @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg, gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg))" for ( + ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg, gradient_solver_alg, + ) in Iterators.product( + calgs, palgs, salgs, galgs, gsalgs + ) + + # filter disallowed algorithm combinations + if _check_disallowed_combination( + ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg + ) + # but verify that its use would throw an error + @test_throws ArgumentError PEPSOptimize(; + boundary_alg = (; alg = ctmrg_alg, projector_alg, decomposition_alg = (; rrule_alg = (; alg = svd_rrule_alg))), + gradient_alg = (; alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol)), + ) + continue + end + + # check for allowed algorithm combinations when testing naive gradient + if isnothing(gradient_alg) + combo = (ctmrg_alg, projector_alg, svd_rrule_alg) + combo in naive_gradient_combinations || continue + combo in naive_gradient_done && continue + push!(naive_gradient_done, combo) + gradient_solver_alg = nothing # unused in naive gradient, so set to nothing to avoid confusion + end + + @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg and gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg)) on $(names[i])" + Random.seed!(42039482030) + dir = adapt(CuArray, InfinitePEPS(Pspace, Vspace)) + psi = adapt(CuArray, InfinitePEPS(Pspace, Vspace)) + @test storagetype(psi) <: CuArray + # instantiate to avoid having to type this twice... + concrete_ctmrg_alg = PEPSKit.CTMRGAlgorithm(; + alg = ctmrg_alg, + verbosity = ctmrg_verbosity, + projector_alg = projector_alg, + decomposition_alg = SVDAdjoint(; rrule_alg = (; alg = svd_rrule_alg)), + ) + # instantiate because hook_pullback doesn't go through the keyword selector... + concrete_gradient_alg = if isnothing(gradient_alg) + nothing # TODO: add this to the PEPSKit.GradientAlgorithm selector? + else + PEPSKit.GradientAlgorithm(; + alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol) + ) + end + env, = leading_boundary(CTMRGEnv(psi, Espace), psi, concrete_ctmrg_alg) + @test storagetype(env) <: CuArray + alphas, fs, dfs1, dfs2 = OptimKit.optimtest( + (psi, env), + dir; + alpha = steps, + retract = PEPSKit.peps_retract, + inner = PEPSKit.real_inner, + ) do (peps, env) + E, g = Zygote.withgradient(peps) do psi + env2, = PEPSKit.hook_pullback( + leading_boundary, + env, + psi, + concrete_ctmrg_alg; + alg_rrule = concrete_gradient_alg, + ) + return cost_function(psi, env2, models[i]) + end + + return E, only(g) + end + @test dfs1 ≈ dfs2 atol = 1.0e-2 + end +end + +## Regression test for gradient accuracy (https://github.com/QuantumKitHub/PEPSKit.jl/pull/276) +@testset "AD CTMRG energy gradient accuracy regression test (#276)" begin + Random.seed!(1234) + + boundary_alg = PEPSKit.CTMRGAlgorithm(; tol = 1.0e-10) + gradient_alg = PEPSKit.GradientAlgorithm(; tol = 5.0e-8) + + function fg((peps, env)) + E, g = Zygote.withgradient(peps) do ψ + env2, = PEPSKit.hook_pullback( + leading_boundary, + env, + ψ, + boundary_alg; + alg_rrule = gradient_alg, + ) + return cost_function(ψ, env2, H) + end + return E, only(g) + end + + # initialize randomly + H = adapt(CuArray, heisenberg_XYZ(InfiniteSquare(1, 1))) + peps = adapt(CuArray, PEPSKit.peps_normalize(InfinitePEPS(randn, ComplexF64, physicalspace(H)[1], ComplexSpace(3)))) + env0 = CTMRGEnv(randn, ComplexF64, peps, ComplexSpace(20)) + + # test gradient against finite-difference + Δx = 1.0e-5 + _, _, dfs1, dfs2 = OptimKit.optimtest( + fg, (peps, env0); + alpha = LinRange(-Δx, Δx, 2), + retract = PEPSKit.peps_retract, + inner = PEPSKit.real_inner, + ) + + # verify high gradient accuracy for small finite-difference step size + @test dfs1 ≈ dfs2 rtol = 1.0e-2 * Δx +end diff --git a/test/rocm/gradients/c4v_ctmrg_gradients.jl b/test/rocm/gradients/c4v_ctmrg_gradients.jl new file mode 100644 index 000000000..bb238b68f --- /dev/null +++ b/test/rocm/gradients/c4v_ctmrg_gradients.jl @@ -0,0 +1,131 @@ +using Test +using Random +using PEPSKit +using TensorKit +using Zygote +using OptimKit +using KrylovKit +using AMDGPU, Adapt + +sd = 42039482052 + +## Test C4v CTMRG gradients +# ------------------------------------------- +χbond = 2 +χenv = 6 +symmetry = RotateReflect() +Pspaces = [ComplexSpace(2)] +Vspaces = [ComplexSpace(χbond)] +Espaces = [ComplexSpace(χenv)] +models = [adapt(ROCArray, heisenberg_XYZ(InfiniteSquare()))] +names = ["Heisenberg"] + +gradtol = 1.0e-4 +ctmrg_verbosity = 1 +ctmrg_algs = [[:C4vCTMRG]] +projector_algs = [[:C4vEighProjector, :C4vQRProjector]] +decomposition_rrule_algs = [[:FullPullback, :TruncPullback]] +gradient_algs = [[nothing, :FixedPointGradient]] +# the gradient solvers are device-independent and are covered exhaustively by the CPU test, +# so only keep the two KrylovKit code paths here, since GPU gradients are slow +gradient_solver_algs = [[:Arnoldi, :GMRES]] +steps = -0.01:0.005:0.01 + +# record which rrule alg is compatible with which projector alg +allowed_rrule_algs = Dict( + :C4vEighProjector => keys(PEPSKit.EIGH_RRULE_SYMBOLS), + :C4vQRProjector => keys(PEPSKit.QR_RRULE_SYMBOLS), +) + +# be selective on which configurations to test the naive gradient for +naive_gradient_combinations = [(:C4vCTMRG, :C4vEighProjector, :FullPullback), (:C4vCTMRG, :C4vQRProjector, :FullPullback)] +naive_gradient_done = Set() + +## Tests +# ------ +@testset "AD C4v CTMRG energy gradients for $(names[i]) model" verbose = true for i in + eachindex( + models + ) + Pspace = Pspaces[i] + Vspace = Vspaces[i] + Espace = Espaces[i] + calgs = ctmrg_algs[i] + palgs = projector_algs[i] + dalgs = decomposition_rrule_algs[i] + galgs = gradient_algs[i] + gsalgs = gradient_solver_algs[i] + @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=(alg = :$gradient_alg, solver_alg = :$gradient_solver_alg)" for ( + ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, gradient_solver_alg, + ) in Iterators.product( + calgs, palgs, dalgs, galgs, gsalgs + ) + + # check for allowed algorithm combinations when testing naive gradient + if isnothing(gradient_alg) + combo = (ctmrg_alg, projector_alg, decomposition_rrule_alg) + combo in naive_gradient_combinations || continue + combo in naive_gradient_done && continue + push!(naive_gradient_done, combo) + gradient_solver_alg = nothing # unused in naive gradient, so set to nothing to avoid confusion + end + + # check for allowed combinations of projector alg and decomposition rrule alg + decomposition_rrule_alg in allowed_rrule_algs[projector_alg] || continue + + # construct appropriate decomposition struct to pass custom rrule alg + decomposition_alg = if projector_alg == :C4vEighProjector + EighAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) + elseif projector_alg == :C4vQRProjector + QRAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) + else + error("unknown projector alg: $projector_alg") + end + + @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg)) on $(names[i])" + Random.seed!(sd) + dir = adapt(ROCArray, InfinitePEPS(Pspace, Vspace)) + psi = adapt(ROCArray, InfinitePEPS(Pspace, Vspace)) + symmetrize!(psi, symmetry) + symmetrize!(dir, symmetry) + # instantiate to avoid having to type this twice... + contrete_ctmrg_alg = PEPSKit.CTMRGAlgorithm(; + alg = ctmrg_alg, + verbosity = ctmrg_verbosity, + projector_alg = projector_alg, + decomposition_alg, + ) + # instantiate because hook_pullback doesn't go through the keyword selector... + concrete_gradient_alg = if isnothing(gradient_alg) + nothing # TODO: add this to the PEPSKit.GradientAlgorithm selector? + else + PEPSKit.GradientAlgorithm(; + alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol) + ) + end + env0 = PEPSKit.initialize_random_c4v_env(psi, Espace) + env, = leading_boundary(env0, psi, contrete_ctmrg_alg) + alphas, fs, dfs1, dfs2 = OptimKit.optimtest( + (psi, env), + dir; + alpha = steps, + retract = PEPSKit.peps_retract, + inner = PEPSKit.real_inner, + ) do (peps, env) + E, g = Zygote.withgradient(peps) do psi + env2, = PEPSKit.hook_pullback( + leading_boundary, + env, + psi, + contrete_ctmrg_alg; + alg_rrule = concrete_gradient_alg, + ) + return cost_function(psi, env2, models[i]) + end + g = only(g) + symmetrize!(g, symmetry) + return E, g + end + @test dfs1 ≈ dfs2 atol = 1.0e-2 + end +end diff --git a/test/rocm/gradients/ctmrg_gradients.jl b/test/rocm/gradients/ctmrg_gradients.jl new file mode 100644 index 000000000..3e1744ae1 --- /dev/null +++ b/test/rocm/gradients/ctmrg_gradients.jl @@ -0,0 +1,175 @@ +using Test +using Random +using PEPSKit +using TensorKit +using Zygote +using OptimKit +using KrylovKit +using AMDGPU, Adapt + +## Test models, gradmodes and CTMRG algorithm +# ------------------------------------------- +χbond = 2 +χenv = 6 +Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] +Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] +Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] +models = [ + adapt(ROCArray, heisenberg_XYZ(InfiniteSquare())), + adapt(ROCArray, pwave_superconductor(InfiniteSquare())), +] +names = ["Heisenberg", "p-wave superconductor"] + +gradtol = 1.0e-4 +ctmrg_verbosity = 0 +ctmrg_algs = [[:SequentialCTMRG, :SimultaneousCTMRG], [:SequentialCTMRG, :SimultaneousCTMRG]] +projector_algs = [[:HalfInfiniteProjector, :FullInfiniteProjector], [:HalfInfiniteProjector, :FullInfiniteProjector]] +svd_rrule_algs = [[:FullPullback, :TruncPullback, :Arnoldi], [:FullPullback, :Arnoldi]] +gradient_algs = [[nothing, :FixedPointGradient], [:FixedPointGradient]] +# the gradient solvers are device-independent and are covered exhaustively by the CPU test, +# so only keep the two KrylovKit code paths here, since GPU gradients are slow +gradient_solver_algs = [[:Arnoldi, :GMRES], [:Arnoldi, :GMRES]] +steps = -0.01:0.005:0.01 + +# don't check naive AD gradients for all algorithm combinations, since it's slow +naive_gradient_combinations = [ + (:SimultaneousCTMRG, :HalfInfiniteProjector, :FullPullback), + (:SimultaneousCTMRG, :FullInfiniteProjector, :FullPullback), + (:SequentialCTMRG, :HalfInfiniteProjector, :FullPullback), +] +naive_gradient_done = Set() + +# fixed-point differentiation is incompatible with sequential CTMRG +function _check_disallowed_combination( + ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg + ) + ctmrg_alg == :SequentialCTMRG && !isnothing(gradient_alg) && return true + return false +end + + +## Tests +# ------ +@testset "AD CTMRG energy gradients for $(names[i]) model" verbose = true for i in + eachindex( + models + ) + Pspace = Pspaces[i] + Vspace = Vspaces[i] + Espace = Espaces[i] + calgs = ctmrg_algs[i] + palgs = projector_algs[i] + salgs = svd_rrule_algs[i] + galgs = gradient_algs[i] + gsalgs = gradient_solver_algs[i] + @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg, gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg))" for ( + ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg, gradient_solver_alg, + ) in Iterators.product( + calgs, palgs, salgs, galgs, gsalgs + ) + + # filter disallowed algorithm combinations + if _check_disallowed_combination( + ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg + ) + # but verify that its use would throw an error + @test_throws ArgumentError PEPSOptimize(; + boundary_alg = (; alg = ctmrg_alg, projector_alg, decomposition_alg = (; rrule_alg = (; alg = svd_rrule_alg))), + gradient_alg = (; alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol)), + ) + continue + end + + # check for allowed algorithm combinations when testing naive gradient + if isnothing(gradient_alg) + combo = (ctmrg_alg, projector_alg, svd_rrule_alg) + combo in naive_gradient_combinations || continue + combo in naive_gradient_done && continue + push!(naive_gradient_done, combo) + gradient_solver_alg = nothing # unused in naive gradient, so set to nothing to avoid confusion + end + + @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg and gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg)) on $(names[i])" + Random.seed!(42039482030) + dir = adapt(ROCArray, InfinitePEPS(Pspace, Vspace)) + psi = adapt(ROCArray, InfinitePEPS(Pspace, Vspace)) + @test storagetype(psi) <: ROCArray + # instantiate to avoid having to type this twice... + concrete_ctmrg_alg = PEPSKit.CTMRGAlgorithm(; + alg = ctmrg_alg, + verbosity = ctmrg_verbosity, + projector_alg = projector_alg, + decomposition_alg = SVDAdjoint(; rrule_alg = (; alg = svd_rrule_alg)), + ) + # instantiate because hook_pullback doesn't go through the keyword selector... + concrete_gradient_alg = if isnothing(gradient_alg) + nothing # TODO: add this to the PEPSKit.GradientAlgorithm selector? + else + PEPSKit.GradientAlgorithm(; + alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol) + ) + end + env, = leading_boundary(CTMRGEnv(psi, Espace), psi, concrete_ctmrg_alg) + @test storagetype(env) <: ROCArray + alphas, fs, dfs1, dfs2 = OptimKit.optimtest( + (psi, env), + dir; + alpha = steps, + retract = PEPSKit.peps_retract, + inner = PEPSKit.real_inner, + ) do (peps, env) + E, g = Zygote.withgradient(peps) do psi + env2, = PEPSKit.hook_pullback( + leading_boundary, + env, + psi, + concrete_ctmrg_alg; + alg_rrule = concrete_gradient_alg, + ) + return cost_function(psi, env2, models[i]) + end + + return E, only(g) + end + @test dfs1 ≈ dfs2 atol = 1.0e-2 + end +end + +## Regression test for gradient accuracy (https://github.com/QuantumKitHub/PEPSKit.jl/pull/276) +@testset "AD CTMRG energy gradient accuracy regression test (#276)" begin + Random.seed!(1234) + + boundary_alg = PEPSKit.CTMRGAlgorithm(; tol = 1.0e-10) + gradient_alg = PEPSKit.GradientAlgorithm(; tol = 5.0e-8) + + function fg((peps, env)) + E, g = Zygote.withgradient(peps) do ψ + env2, = PEPSKit.hook_pullback( + leading_boundary, + env, + ψ, + boundary_alg; + alg_rrule = gradient_alg, + ) + return cost_function(ψ, env2, H) + end + return E, only(g) + end + + # initialize randomly + H = adapt(ROCArray, heisenberg_XYZ(InfiniteSquare(1, 1))) + peps = adapt(ROCArray, PEPSKit.peps_normalize(InfinitePEPS(randn, ComplexF64, physicalspace(H)[1], ComplexSpace(3)))) + env0 = CTMRGEnv(randn, ComplexF64, peps, ComplexSpace(20)) + + # test gradient against finite-difference + Δx = 1.0e-5 + _, _, dfs1, dfs2 = OptimKit.optimtest( + fg, (peps, env0); + alpha = LinRange(-Δx, Δx, 2), + retract = PEPSKit.peps_retract, + inner = PEPSKit.real_inner, + ) + + # verify high gradient accuracy for small finite-difference step size + @test dfs1 ≈ dfs2 rtol = 1.0e-2 * Δx +end From 66818c582687e13114cef62330262d24ee8679c2 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 18 Aug 2026 12:44:33 +0200 Subject: [PATCH 35/69] Fix for c4v --- test/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index b4d761a57..2bad34f8d 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -39,7 +39,7 @@ cuSPARSE = "b26da814-b3bc-49ef-b0ee-c816305aa060" [sources] PEPSKit = {path = ".."} GPUArrays = {url = "https://github.com/JuliaGPU/GPUArrays.jl", rev = "main"} -AMDGPU = {url = "https://github.com/JuliaGPU/AMDGPU.jl", rev = "main"} +AMDGPU = {url = "https://github.com/JuliaGPU/AMDGPU.jl", rev = "ksh/zerodim"} CUDA = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main"} CUDACore = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "CUDACore"} CUDATools ={url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "CUDATools"} From a6a0bdc54a31981657457a61e474639939ef2bd1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 09:01:49 +0200 Subject: [PATCH 36/69] Use the new braiding branch over at TK --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index 198405b8f..ca2d57a6f 100644 --- a/Project.toml +++ b/Project.toml @@ -39,6 +39,7 @@ GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} MatrixAlgebraKit = {rev = "ksh/svd_trunc_gpu", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} TensorOperations = {rev = "main", url = "https://github.com/QuantumKitHub/TensorOperations.jl"} +TensorKit = {rev = "ksh/add_transform", url = "https://github.com/QuantumKitHub/TensorKit.jl"} [extensions] PEPSKitAdaptExt = "Adapt" From 90e397aff6fd931b391538cde05861d464414ad5 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 12:26:15 +0200 Subject: [PATCH 37/69] Use alloc_caches from GPUArrays --- Project.toml | 1 + ext/PEPSKitGPUArraysExt.jl | 30 ++++++++++++++ src/PEPSKit.jl | 1 + src/algorithms/bp/beliefpropagation.jl | 1 + src/algorithms/ctmrg/ctmrg.jl | 4 +- src/algorithms/time_evolution/simpleupdate.jl | 4 +- src/utility/alloc_cache.jl | 40 +++++++++++++++++++ test/Project.toml | 10 ----- 8 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 src/utility/alloc_cache.jl diff --git a/Project.toml b/Project.toml index ca2d57a6f..c4b7a0e5f 100644 --- a/Project.toml +++ b/Project.toml @@ -48,6 +48,7 @@ PEPSKitGPUArraysExt = "GPUArrays" [compat] Accessors = "0.1" Adapt = "4" +GPUArrays = "11" ChainRulesCore = "1.0" Compat = "3.46, 4.2" DocStringExtensions = "0.9.3" diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 13510fd5c..db8cc1cb6 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -1,6 +1,7 @@ module PEPSKitGPUArraysExt using GPUArrays +using GPUArrays: AnyGPUArray, AllocCache using PEPSKit, MatrixAlgebraKit using LinearAlgebra import PEPSKit: _column_vectors, _materialize_block, _indexable_diagview @@ -12,4 +13,33 @@ function PEPSKit._column_vectors(::Type{A}, m::AbstractMatrix) where {A <: AnyGP end PEPSKit._indexable_diagview(m::AnyGPUArray) = collect(MatrixAlgebraKit.diagview(m)) +# Each caller (such as `su_iter`) gets a pair of caches. This makes sense to do on a per-caller basis +# because what is being cached varies between algorithms. +# For each caller we also store *two* caches, one for even iterations and one for odd. +# This has to be done because we can't reuse a cache from iteration `i` +# until iteration `i+1` is completely finished and its result handed off to iteration `i+2`. +const ALLOC_CACHES = Dict{Symbol, NTuple{2, AllocCache}}() +const ALLOC_CACHES_LOCK = ReentrantLock() + +function _caches(site::Symbol) + return Base.@lock ALLOC_CACHES_LOCK begin + get!(() -> (AllocCache(), AllocCache()), ALLOC_CACHES, site) + end +end + +function PEPSKit._with_alloc_cache(f, ::Type{<:AnyGPUArray}, site::Symbol, iter::Int) + cache = @inbounds _caches(site)[mod1(iter + 1, 2)] + return GPUArrays.@cached cache f() +end + +function PEPSKit.free_alloc_caches!(::Type{<:AnyGPUArray}) + Base.@lock ALLOC_CACHES_LOCK begin + for caches in values(ALLOC_CACHES), cache in caches + GPUArrays.unsafe_free!(cache) + end + empty!(ALLOC_CACHES) + end + return nothing +end + end diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 25d3f200b..7baf6ff56 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -61,6 +61,7 @@ include("Defaults.jl") # Include first to allow for docstring interpolation wit include("utility/util.jl") include("utility/contraction_labels.jl") include("utility/tensor_traces.jl") +include("utility/alloc_cache.jl") include("utility/indexing.jl") include("utility/diffable_threads.jl") include("utility/twistdual.jl") diff --git a/src/algorithms/bp/beliefpropagation.jl b/src/algorithms/bp/beliefpropagation.jl index 6a274289f..f1e6dadda 100644 --- a/src/algorithms/bp/beliefpropagation.jl +++ b/src/algorithms/bp/beliefpropagation.jl @@ -39,6 +39,7 @@ function leading_boundary(env₀::BPEnv, network::InfiniteSquareNetwork, alg::Be ϵ = Inf @log_initialization loginit!(log, ϵ) for iter in 1:(alg.maxiter) + # TODO investigate why caching doesn't help here and actually makes things worse env′ = bp_iteration(network, env, alg) ϵ = oftype(ϵ, tr_distance(env, env′)) env = env′ diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index a4be1d21b..66f7cbd3f 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -120,7 +120,9 @@ function leading_boundary( local info_iter converged = false for iter in 1:(alg.maxiter) - env, info_iter = ctmrg_iteration(network, env, alg) + env, info_iter = with_alloc_cache(storagetype(env), :ctmrg, iter) do + ctmrg_iteration(network, env, alg) + end η, CS, TS = calc_convergence(env, CS, TS, alg) if η ≤ alg.tol && iter ≥ alg.miniter diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 38a3614ea..a40843fb7 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -162,7 +162,9 @@ end function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) iter, t = state.iter, state.t (iter == it.nstep) && return nothing - psi, env, ϵ = su_iter(state.psi, it.circuit, it.alg, state.env) + psi, env, ϵ = with_alloc_cache(storagetype(state.psi), :su, iter) do + su_iter(state.psi, it.circuit, it.alg, state.env) + end # update internal state iter += 1 t += it.dt diff --git a/src/utility/alloc_cache.jl b/src/utility/alloc_cache.jl new file mode 100644 index 000000000..288264c05 --- /dev/null +++ b/src/utility/alloc_cache.jl @@ -0,0 +1,40 @@ +""" + with_alloc_cache(f, storage, caller::Symbol, iter::Int) -> f() + +Run one iteration `f()` of an iterative algorithm using memory cache. + +**This is a no-op unless `GPUArrays` is loaded _and_ `storage` is a GPU array.** + +There seems to be no benefit to caching for CPU-side memory, but for the device, +using a warm memory pool avoids everything being blocked while the GPU device driver +allocates. + +`caller` ids the calling algo (`:su`, `:ctmrg`) so that algorithms allocating different +buffer sizes avoid stepping on each others' caches. Not every algorithm needs an allocation cache, +only the ones with repeated iterations. + +Belief propagation seems to not benefit from the caching as much, so it's currently unused there. + +`iter` selects between two alternating caches per `caller`. A buffer allocated during iteration +`i` can't become reusable until iteration `i+2`, by which point the output of iteration `i` has +been used by iteration `i+1`. With only one cache, it would be possible to overwrite the state +at `i+1` while it's still being used. + +Caching is skipped while Zygote is differentiating, because the reverse-mode tape holds references +to intermediates, and recycling those could silently corrupt gradients. +""" +function with_alloc_cache(f, storage::Type, caller::Symbol, iter::Int) + Zygote.isderiving() && return f() + return _with_alloc_cache(f, storage, caller, iter) +end +_with_alloc_cache(f, ::Type, ::Symbol, ::Int) = f() + +""" + free_alloc_caches!(storage) + +Release memory held by the allocation caches for storage type `storage`. Does nothing +unless the `PEPSKitGPUArraysExt` extension is loaded and `storage` is a GPU array type. +It's worth calling this when the bond dimension changes, because the cache keys depend on +buffer size. +""" +free_alloc_caches!(::Type) = nothing diff --git a/test/Project.toml b/test/Project.toml index 2bad34f8d..987b2977c 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -40,16 +40,6 @@ cuSPARSE = "b26da814-b3bc-49ef-b0ee-c816305aa060" PEPSKit = {path = ".."} GPUArrays = {url = "https://github.com/JuliaGPU/GPUArrays.jl", rev = "main"} AMDGPU = {url = "https://github.com/JuliaGPU/AMDGPU.jl", rev = "ksh/zerodim"} -CUDA = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main"} -CUDACore = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "CUDACore"} -CUDATools ={url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "CUDATools"} -CUPTI = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cupti"} -NVML = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/nvml"} -cuBLAS = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cublas"} -cuFFT = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cufft"} -cuRAND = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/curand"} -cuSOLVER = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cusolver"} -cuSPARSE = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main", subdir = "lib/cusparse"} [compat] AMDGPU = "2" From 6024f7b9b937d521ff76e908d70a65daca01eb65 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 15:53:16 +0200 Subject: [PATCH 38/69] Leave a note about which GPU algo to use --- src/Defaults.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults.jl b/src/Defaults.jl index 78fc88ab2..ce39011b3 100644 --- a/src/Defaults.jl +++ b/src/Defaults.jl @@ -52,7 +52,7 @@ Module containing default algorithm parameter values and arguments. ## `eigh` forward & reverse -* `eigh_fwd_alg=:$(Defaults.eigh_fwd_alg)` : `eigh` algorithm that is used in the forward pass. +* `eigh_fwd_alg=:$(Defaults.eigh_fwd_alg)` : `eigh` algorithm that is used in the forward pass. **Note** that on GPU, `DivideAndConquer` is much more performant than `QRIteration`. - `:DefaultAlgorithm` : MatrixAlgebraKit's default Eigh algorithm for a given matrix type. - `:DivideAndConquer` : MatrixAlgebraKit's [`DivideAndConquer`](@extref MatrixAlgebraKit.DivideAndConquer) - `:QRIteration` : MatrixAlgebraKit's [`QRIteration`](@extref MatrixAlgebraKit.QRIteration) From 9cfdf5c38508e2b0c05bc1f029ed3bb51fb5a75e Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 16:05:39 +0200 Subject: [PATCH 39/69] Use DivideAndConquer for ROCm tests --- test/rocm/ctmrg/fixed_iterscheme.jl | 2 +- test/rocm/ctmrg/flavors.jl | 2 +- test/rocm/utility/eigh_wrapper.jl | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/rocm/ctmrg/fixed_iterscheme.jl b/test/rocm/ctmrg/fixed_iterscheme.jl index 59813907e..92fa4ab7e 100644 --- a/test/rocm/ctmrg/fixed_iterscheme.jl +++ b/test/rocm/ctmrg/fixed_iterscheme.jl @@ -62,7 +62,7 @@ end # test same thing for C4v CTMRG c4v_algs = [ (:C4vQRProjector, (; alg = :Householder)), - (:C4vEighProjector, (; alg = :QRIteration)), + (:C4vEighProjector, (; alg = :DivideAndConquer)), (:C4vEighProjector, (; alg = :Lanczos)), ] @testset "$(decomposition_alg.alg) and $projector_alg" for diff --git a/test/rocm/ctmrg/flavors.jl b/test/rocm/ctmrg/flavors.jl index e391bd674..07a5b465d 100644 --- a/test/rocm/ctmrg/flavors.jl +++ b/test/rocm/ctmrg/flavors.jl @@ -14,7 +14,7 @@ unitcells = [(1, 1), (3, 4)] projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] projector_algs_c4v = [ (:C4vQRProjector, :Householder), - (:C4vEighProjector, :QRIteration), (:C4vEighProjector, :Lanczos), + (:C4vEighProjector, :DivideAndConquer), (:C4vEighProjector, :Lanczos), ] Ts = [Float64, ComplexF64] diff --git a/test/rocm/utility/eigh_wrapper.jl b/test/rocm/utility/eigh_wrapper.jl index 3eb750522..cdf124812 100644 --- a/test/rocm/utility/eigh_wrapper.jl +++ b/test/rocm/utility/eigh_wrapper.jl @@ -26,8 +26,8 @@ r = 0.5 * (r + r') # make r Hermitian R = adapt(ROCArray, randn(space(r))) R = 0.5 * (R + R') -full_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :FullPullback)) -trunc_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :TruncPullback)) +full_alg = EighAdjoint(; fwd_alg = (; alg = :DivideAndConquer), rrule_alg = (; alg = :FullPullback)) +trunc_alg = EighAdjoint(; fwd_alg = (; alg = :DivideAndConquer), rrule_alg = (; alg = :TruncPullback)) iter_alg = EighAdjoint(; fwd_alg = (; alg = :Lanczos), rrule_alg = (; alg = :TruncPullback)) @testset "Non-truncated eigh" begin From 66abf4b919556e23955e0278260ae39fd1c6bad2 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 22:12:30 +0200 Subject: [PATCH 40/69] Let the cache depth vary by algo --- ext/PEPSKitGPUArraysExt.jl | 17 +++++++++-------- src/algorithms/ctmrg/ctmrg.jl | 2 +- src/algorithms/ctmrg/sequential.jl | 6 ++++++ src/utility/alloc_cache.jl | 14 +++++++++++--- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index db8cc1cb6..6ed449183 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -13,22 +13,23 @@ function PEPSKit._column_vectors(::Type{A}, m::AbstractMatrix) where {A <: AnyGP end PEPSKit._indexable_diagview(m::AnyGPUArray) = collect(MatrixAlgebraKit.diagview(m)) -# Each caller (such as `su_iter`) gets a pair of caches. This makes sense to do on a per-caller basis +# Each caller (such as `su_iter`) gets a pair of caches. This makes sense to do on a per-caller basis # because what is being cached varies between algorithms. -# For each caller we also store *two* caches, one for even iterations and one for odd. +# For each caller we also store several caches, for SimultaneousCTMRG and SU, +# one for even iterations and one for odd, and for SequentialCTMRG, 5 (one "round" plus one extra) # This has to be done because we can't reuse a cache from iteration `i` -# until iteration `i+1` is completely finished and its result handed off to iteration `i+2`. -const ALLOC_CACHES = Dict{Symbol, NTuple{2, AllocCache}}() +# until iteration `i+n` is completely finished and its result handed off. +const ALLOC_CACHES = Dict{Tuple{Symbol, Int}, Vector{AllocCache}}() const ALLOC_CACHES_LOCK = ReentrantLock() -function _caches(site::Symbol) +function _caches(site::Symbol, depth::Int) return Base.@lock ALLOC_CACHES_LOCK begin - get!(() -> (AllocCache(), AllocCache()), ALLOC_CACHES, site) + get!(() -> [AllocCache() for _ in 1:depth], ALLOC_CACHES, (site, depth)) end end -function PEPSKit._with_alloc_cache(f, ::Type{<:AnyGPUArray}, site::Symbol, iter::Int) - cache = @inbounds _caches(site)[mod1(iter + 1, 2)] +function PEPSKit._with_alloc_cache(f, ::Type{<:AnyGPUArray}, site::Symbol, iter::Int, depth::Int) + cache = @inbounds _caches(site, depth)[mod1(iter + 1, depth)] return GPUArrays.@cached cache f() end diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 66f7cbd3f..7805853fc 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -120,7 +120,7 @@ function leading_boundary( local info_iter converged = false for iter in 1:(alg.maxiter) - env, info_iter = with_alloc_cache(storagetype(env), :ctmrg, iter) do + env, info_iter = with_alloc_cache(storagetype(env), :ctmrg, iter, alloc_cache_depth(alg)) do ctmrg_iteration(network, env, alg) end η, CS, TS = calc_convergence(env, CS, TS, alg) diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index 3ce75c4c1..f42101ce7 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -37,6 +37,12 @@ end CTMRG_SYMBOLS[:SequentialCTMRG] = SequentialCTMRG +# A sequential sweep updates *one( direction at a time, then hands off the not-yet-updated +# directions, so those tensors can stay live for a full cycle of the +# *four* directions. This means we need 5 total cache elements to avoid overwriting something +# still in use. +alloc_cache_depth(::SequentialCTMRG) = 5 + """ ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) diff --git a/src/utility/alloc_cache.jl b/src/utility/alloc_cache.jl index 288264c05..a8ed31e5d 100644 --- a/src/utility/alloc_cache.jl +++ b/src/utility/alloc_cache.jl @@ -23,11 +23,19 @@ at `i+1` while it's still being used. Caching is skipped while Zygote is differentiating, because the reverse-mode tape holds references to intermediates, and recycling those could silently corrupt gradients. """ -function with_alloc_cache(f, storage::Type, caller::Symbol, iter::Int) +function with_alloc_cache(f, storage::Type, caller::Symbol, iter::Int, depth::Int = 2) Zygote.isderiving() && return f() - return _with_alloc_cache(f, storage, caller, iter) + return _with_alloc_cache(f, storage, caller, iter, depth) end -_with_alloc_cache(f, ::Type, ::Symbol, ::Int) = f() + +""" + alloc_cache_depth(alg) + +How many iterations a buffer must go unused before it may be recycled. +For SimultaneousCTMRG and SU, 2 is enough, but not necessarily for other algorithms. +""" +alloc_cache_depth(alg) = 2 +_with_alloc_cache(f, ::Type, ::Symbol, ::Int, ::Int) = f() """ free_alloc_caches!(storage) From f427b7e24f178314bcdb4f5bcd27a8d7cbd87aff Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 07:05:10 +0200 Subject: [PATCH 41/69] Fix AMDGPU rev --- test/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index 987b2977c..5576e3190 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -39,7 +39,7 @@ cuSPARSE = "b26da814-b3bc-49ef-b0ee-c816305aa060" [sources] PEPSKit = {path = ".."} GPUArrays = {url = "https://github.com/JuliaGPU/GPUArrays.jl", rev = "main"} -AMDGPU = {url = "https://github.com/JuliaGPU/AMDGPU.jl", rev = "ksh/zerodim"} +AMDGPU = {url = "https://github.com/JuliaGPU/AMDGPU.jl", rev = "main"} [compat] AMDGPU = "2" From ef005046b883d9fcc9a9eebe6250d5790cfcdbb3 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 02:40:20 -0400 Subject: [PATCH 42/69] CUDA/CUDACore badness STRIKES AGAIN --- test/Project.toml | 2 -- test/cuda/bondenv/benv_ctm.jl | 7 +++++-- test/cuda/bondenv/benv_gaugefix.jl | 2 +- test/cuda/bondenv/bond_truncate.jl | 2 +- test/cuda/boundarymps/vumps.jl | 18 +++++++++++++----- test/cuda/bp/expvals.jl | 2 +- test/cuda/bp/gaugefix.jl | 2 +- test/cuda/bp/rotation.jl | 2 +- test/cuda/bp/unitcell.jl | 2 +- test/cuda/compress/local.jl | 2 +- test/cuda/ctmrg/contractions.jl | 2 +- test/cuda/ctmrg/fixed_iterscheme.jl | 4 ++-- test/cuda/ctmrg/flavors.jl | 11 +++++++---- test/cuda/ctmrg/gaugefix.jl | 8 +++++--- test/cuda/ctmrg/initialization.jl | 7 +++++-- test/cuda/ctmrg/jacobian_real_linear.jl | 6 +++--- test/cuda/ctmrg/partition_function.jl | 12 +++++++++--- test/cuda/ctmrg/pepo.jl | 12 +++++++++--- test/cuda/ctmrg/suweight.jl | 2 +- test/cuda/ctmrg/unitcell.jl | 10 +++++----- test/cuda/gradients/ctmrg_gradients.jl | 8 ++++++-- test/cuda/timeevol/cluster_projectors.jl | 7 +++++-- test/cuda/timeevol/j1j2_finiteT.jl | 7 +++++-- test/cuda/timeevol/sitedep_truncation.jl | 2 +- test/cuda/timeevol/tf_ising_finiteT.jl | 12 +++++++++--- test/cuda/timeevol/timestep.jl | 2 +- test/cuda/toolbox/densitymatrices.jl | 2 +- test/cuda/utility/correlator.jl | 2 +- test/cuda/utility/eigh_wrapper.jl | 2 +- test/cuda/utility/svd_wrapper.jl | 2 +- 30 files changed, 103 insertions(+), 58 deletions(-) diff --git a/test/Project.toml b/test/Project.toml index 5576e3190..3f624d9c1 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -8,7 +8,6 @@ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -CUDACore = "bd0ed864-bdfe-4181-a5ed-ce625a5fdea2" CUPTI = "9e67e8f6-ba02-4b6c-a7db-3b11ae1e7ab7" CUDATools = "9ec180c6-1c07-47c7-9e6e-ebefa4d1f6d0" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" @@ -45,7 +44,6 @@ AMDGPU = {url = "https://github.com/JuliaGPU/AMDGPU.jl", rev = "main"} AMDGPU = "2" ChainRulesTestUtils = "1.13" CUDA = "6" -CUDACore = "6" CUDATools = "6" ParallelTestRunner = "2.6.0" JLArrays = "0.3, 0.4" diff --git a/test/cuda/bondenv/benv_ctm.jl b/test/cuda/bondenv/benv_ctm.jl index a91e40d8a..b9c22b169 100644 --- a/test/cuda/bondenv/benv_ctm.jl +++ b/test/cuda/bondenv/benv_ctm.jl @@ -3,7 +3,7 @@ using TensorKit using PEPSKit using LinearAlgebra using Random -using CUDACore, Adapt +using CUDA, Adapt Random.seed!(100) Nr, Nc = 2, 2 @@ -11,7 +11,10 @@ Envspace = Vect[FermionParity ⊠ U1Irrep]( (0, 0) => 4, (1, 1 // 2) => 1, (1, -1 // 2) => 1, (0, 1) => 1, (0, -1) => 1 ) trunc_state = truncerror(; atol = 1.0e-10) & truncrank(4) -ctm_alg = SequentialCTMRG(; tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(8)) +ctm_alg = SequentialCTMRG(; + tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(8), + decomposition_alg = (; alg = :SVDViaPolar), +) # create Hubbard iPEPS using simple update function get_hubbard_peps(t::Float64 = 1.0, U::Float64 = 8.0) H = adapt(CuArray, hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)) diff --git a/test/cuda/bondenv/benv_gaugefix.jl b/test/cuda/bondenv/benv_gaugefix.jl index 9b1b91bd0..bb78584be 100644 --- a/test/cuda/bondenv/benv_gaugefix.jl +++ b/test/cuda/bondenv/benv_gaugefix.jl @@ -3,7 +3,7 @@ using TensorKit using PEPSKit using LinearAlgebra using Random -using CUDACore, Adapt +using CUDA, Adapt Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 1, (1, -1) => 2) Vin = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 3, (1, -1) => 2) diff --git a/test/cuda/bondenv/bond_truncate.jl b/test/cuda/bondenv/bond_truncate.jl index 67a0c1a3f..9a78d44fe 100644 --- a/test/cuda/bondenv/bond_truncate.jl +++ b/test/cuda/bondenv/bond_truncate.jl @@ -5,7 +5,7 @@ using PEPSKit using LinearAlgebra using PEPSKit: bond_truncate, cost_function_als using PEPSKit: _combine_ket, _combine_ket_for_svd -using CUDACore, Adapt +using CUDA, Adapt Random.seed!(0) maxiter = 600 diff --git a/test/cuda/boundarymps/vumps.jl b/test/cuda/boundarymps/vumps.jl index 0262d28d8..5503fec89 100644 --- a/test/cuda/boundarymps/vumps.jl +++ b/test/cuda/boundarymps/vumps.jl @@ -4,7 +4,7 @@ using PEPSKit using TensorKit using MPSKit using LinearAlgebra -using Adapt, CUDACore +using Adapt, CUDA Random.seed!(29384293742893) @@ -28,7 +28,9 @@ const vumps_alg = VUMPS(; N2 = abs(sum(expectation_value(mps2, T))) @test N ≈ N2 rtol = 1.0e-2 - ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) + ctm, = leading_boundary( + CTMRGEnv(psi, ComplexSpace(20)), psi; decomposition_alg = (; alg = :SVDViaPolar) + ) N´ = abs(norm(psi, ctm)) @test N ≈ N´ atol = 1.0e-3 @@ -45,7 +47,9 @@ end mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(prod(expectation_value(mps, T))) - ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) + ctm, = leading_boundary( + CTMRGEnv(psi, ComplexSpace(20)), psi; decomposition_alg = (; alg = :SVDViaPolar) + ) N´ = abs(norm(psi, ctm)) @test N ≈ N´ rtol = 1.0e-2 @@ -66,7 +70,9 @@ end mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N_vumps = abs(prod(expectation_value(mps, T))) - ctm, = leading_boundary(CTMRGEnv(psi, χ), psi) + ctm, = leading_boundary( + CTMRGEnv(psi, χ), psi; decomposition_alg = (; alg = :SVDViaPolar) + ) N_ctm = abs(norm(psi, ctm)) @test N_vumps ≈ N_ctm rtol = 1.0e-2 @@ -80,7 +86,9 @@ end mps´, env´, ϵ = leading_boundary(mps´, T´, vumps_alg) N_vumps´ = abs(prod(expectation_value(mps´, T´))) - ctm´, = leading_boundary(CTMRGEnv(n´, χ), n´) + ctm´, = leading_boundary( + CTMRGEnv(n´, χ), n´; decomposition_alg = (; alg = :SVDViaPolar) + ) N_ctm´ = abs(network_value(n´, ctm´)) @show N_vumps´ diff --git a/test/cuda/bp/expvals.jl b/test/cuda/bp/expvals.jl index e9139db64..fe30cb409 100644 --- a/test/cuda/bp/expvals.jl +++ b/test/cuda/bp/expvals.jl @@ -3,7 +3,7 @@ using Random using TensorKit using PEPSKit using PEPSKit: random_dual! -using CUDACore, Adapt +using CUDA, Adapt ds = Dict( U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), diff --git a/test/cuda/bp/gaugefix.jl b/test/cuda/bp/gaugefix.jl index a182b222b..13de9ea6b 100644 --- a/test/cuda/bp/gaugefix.jl +++ b/test/cuda/bp/gaugefix.jl @@ -4,7 +4,7 @@ using TensorKit using PEPSKit using PEPSKit: compare_weights, random_dual!, twistdual using PEPSKit: _next, _is_bipartite -using CUDACore, Adapt +using CUDA, Adapt @testset "BP vs SU ($S, bipartite = $(bipartite), posdef msgs = $h)" for (S, bipartite, h) in Iterators.product( diff --git a/test/cuda/bp/rotation.jl b/test/cuda/bp/rotation.jl index 6946b3c68..e3aa0d39e 100644 --- a/test/cuda/bp/rotation.jl +++ b/test/cuda/bp/rotation.jl @@ -3,7 +3,7 @@ using Random using TensorKit using PEPSKit using PEPSKit: random_dual! -using CUDACore, Adapt +using CUDA, Adapt ds = Dict( Trivial => ℂ^2, diff --git a/test/cuda/bp/unitcell.jl b/test/cuda/bp/unitcell.jl index 0a92e9ad5..cb5105adf 100644 --- a/test/cuda/bp/unitcell.jl +++ b/test/cuda/bp/unitcell.jl @@ -3,7 +3,7 @@ using Random using PEPSKit using PEPSKit: bp_iteration using TensorKit -using CUDACore, Adapt +using CUDA, Adapt # settings Random.seed!(91283219347) diff --git a/test/cuda/compress/local.jl b/test/cuda/compress/local.jl index 3eda9e39b..8a877f1bb 100644 --- a/test/cuda/compress/local.jl +++ b/test/cuda/compress/local.jl @@ -4,7 +4,7 @@ using LinearAlgebra using TensorKit using PEPSKit using PEPSKit: virtual_projector -using CUDACore, Adapt +using CUDA, Adapt """ Cost function of LocalTruncation. diff --git a/test/cuda/ctmrg/contractions.jl b/test/cuda/ctmrg/contractions.jl index d7f9cdd38..817255458 100644 --- a/test/cuda/ctmrg/contractions.jl +++ b/test/cuda/ctmrg/contractions.jl @@ -2,7 +2,7 @@ using Test using Random using PEPSKit using TensorKit -using CUDACore, Adapt +using CUDA, Adapt using PEPSKit: eachcoordinate, _next_coordinate using PEPSKit: EnlargedCorner, HalfInfiniteEnv, FullInfiniteEnv diff --git a/test/cuda/ctmrg/fixed_iterscheme.jl b/test/cuda/ctmrg/fixed_iterscheme.jl index 01abc9470..ba01efb0f 100644 --- a/test/cuda/ctmrg/fixed_iterscheme.jl +++ b/test/cuda/ctmrg/fixed_iterscheme.jl @@ -5,7 +5,7 @@ using Random using LinearAlgebra using TensorKit, KrylovKit using PEPSKit -using CUDACore, Adapt +using CUDA, Adapt using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, @@ -20,7 +20,7 @@ using PEPSKit.Defaults: ctmrg_tol # initialize parameters D = 2 χ = 16 -svd_algs = [(; alg = :QRIteration), (; alg = :GKL)] +svd_algs = [(; alg = :SVDViaPolar), (; alg = :GKL)] projector_algs_asymm = [:HalfInfiniteProjector] #, :FullInfiniteProjector] unitcells = [(1, 1), (3, 4)] atol = 1.0e-5 diff --git a/test/cuda/ctmrg/flavors.jl b/test/cuda/ctmrg/flavors.jl index e76e488d1..55fbfeaba 100644 --- a/test/cuda/ctmrg/flavors.jl +++ b/test/cuda/ctmrg/flavors.jl @@ -4,7 +4,7 @@ using MatrixAlgebraKit using TensorKit using MPSKit using PEPSKit -using CUDACore, Adapt +using CUDA, Adapt using PEPSKit: peps_normalize # initialize parameters @@ -24,10 +24,12 @@ Ts = [Float64, ComplexF64] Random.seed!(32350283290358) psi = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) env_sequential, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SequentialCTMRG, projector_alg + CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SequentialCTMRG, projector_alg, + decomposition_alg = (; alg = :SVDViaPolar) ) env_simultaneous, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SimultaneousCTMRG, projector_alg + CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SimultaneousCTMRG, projector_alg, + decomposition_alg = (; alg = :SVDViaPolar) ) # compare norms @@ -59,7 +61,8 @@ end psi = adapt(CuArray, InfinitePEPS(Ds, Ds, Ds)) env = CTMRGEnv(psi, ComplexSpace.(rand(10:20, 3, 3)), ComplexSpace.(rand(10:20, 3, 3))) env2, = leading_boundary( - env, psi; alg, maxiter = 1, trunc = FixedSpaceTruncation(), projector_alg + env, psi; alg, maxiter = 1, trunc = FixedSpaceTruncation(), projector_alg, + decomposition_alg = (; alg = :SVDViaPolar) ) # check that the space is fixed diff --git a/test/cuda/ctmrg/gaugefix.jl b/test/cuda/ctmrg/gaugefix.jl index 169135ec9..e5277530b 100644 --- a/test/cuda/ctmrg/gaugefix.jl +++ b/test/cuda/ctmrg/gaugefix.jl @@ -2,7 +2,7 @@ using Test using Random using PEPSKit using TensorKit -using CUDACore, Adapt +using CUDA, Adapt using PEPSKit: ctmrg_iteration, calc_elementwise_convergence using PEPSKit: ScramblingEnvGauge, ScramblingEnvGaugeC4v using PEPSKit: peps_normalize @@ -33,7 +33,9 @@ function _pre_converge_env( CTMRGEnv(psi, env_space) end @test storagetype(env₀) <: CuArray - env_conv, = leading_boundary(env₀, psi; alg, tol) + # C4v projectors decompose with `eigh`/`qr`, so only the SVD-based flavors take `:SVDViaPolar` + svd_kwargs = alg == :C4vCTMRG ? (;) : (; decomposition_alg = (; alg = :SVDViaPolar)) + env_conv, = leading_boundary(env₀, psi; alg, tol, svd_kwargs...) return env_conv, psi end @@ -68,7 +70,7 @@ end ) in Iterators.product( spacetypes, scalartypes, unitcells, ctmrg_algs_asymm, projector_algs_asymm, gauge_algs_asymm ) - alg = ctmrg_alg(; tol, projector_alg) + alg = ctmrg_alg(; tol, projector_alg, decomposition_alg = (; alg = :SVDViaPolar)) env_pre, psi = preconv[(S, T, unitcell)] n = InfiniteSquareNetwork(psi) @test storagetype(n) <: CuArray diff --git a/test/cuda/ctmrg/initialization.jl b/test/cuda/ctmrg/initialization.jl index 6bf3ffd24..5bbe99788 100644 --- a/test/cuda/ctmrg/initialization.jl +++ b/test/cuda/ctmrg/initialization.jl @@ -2,7 +2,7 @@ using Test using TensorKit using PEPSKit using Random -using Adapt, CUDACore +using Adapt, CUDA using MPSKitModels: classical_ising using PEPSKit: ProductStateEnv @@ -20,7 +20,10 @@ tol = 1.0e-4 maxiter = 1000 verbosity = 2 trunc = truncrank(χ) -boundary_alg = (; alg = :SimultaneousCTMRG, tol, verbosity, trunc, maxiter) +boundary_alg = (; + alg = :SimultaneousCTMRG, tol, verbosity, trunc, maxiter, + decomposition_alg = (; alg = :SVDViaPolar), +) @testset "CTMRG environment initialization for critical ising with $S symmetry (#255)" for S in symmetries # initialize diff --git a/test/cuda/ctmrg/jacobian_real_linear.jl b/test/cuda/ctmrg/jacobian_real_linear.jl index 43d1ed9aa..ae1bad3b5 100644 --- a/test/cuda/ctmrg/jacobian_real_linear.jl +++ b/test/cuda/ctmrg/jacobian_real_linear.jl @@ -3,13 +3,13 @@ using Random using Accessors using Zygote using TensorKit, KrylovKit, PEPSKit -using CUDACore, Adapt +using CUDA, Adapt using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, fix_phases, ScramblingEnvGauge algs = [ - SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector), - SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector), # TODO: why are the errors quite a bit larger for :FullInfiniteProjector? + SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), + SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), # TODO: why are the errors quite a bit larger for :FullInfiniteProjector? ] Dbond, χenv = 2, 16 alg_gauge = ScramblingEnvGauge() diff --git a/test/cuda/ctmrg/partition_function.jl b/test/cuda/ctmrg/partition_function.jl index 0eb5a8069..d58caf915 100644 --- a/test/cuda/ctmrg/partition_function.jl +++ b/test/cuda/ctmrg/partition_function.jl @@ -5,7 +5,7 @@ using PEPSKit using TensorKit using QuadGK using Test -using CUDACore, Adapt +using CUDA, Adapt @testset "Check spaces in partition function CTMRG" begin zA = randn(ℂ^6 ⊗ ℂ^8 ← ℂ^4 ⊗ ℂ^2) @@ -16,7 +16,10 @@ using CUDACore, Adapt Z = adapt(CuArray, InfinitePartitionFunction([zA zB; zC zD])) χenv = ℂ^12 env0 = CTMRGEnv(Z, χenv) - env, = leading_boundary(env0, Z; alg = :SimultaneousCTMRG, maxiter = 3, projector_alg = :FullInfiniteProjector) + env, = leading_boundary( + env0, Z; alg = :SimultaneousCTMRG, maxiter = 3, + projector_alg = :FullInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar) + ) @test env isa CTMRGEnv end @@ -130,7 +133,10 @@ args = [ alg, projector_alg, ) in args env₀₀ = alg == :C4vCTMRG ? env₀_c4v : env₀ - env, = leading_boundary(env₀₀, Z; alg, maxiter = 300, projector_alg) + env, = leading_boundary( + env₀₀, Z; alg, maxiter = 300, projector_alg, + decomposition_alg = (; alg = :SVDViaPolar) + ) # check observables λ = network_value(Z, env) diff --git a/test/cuda/ctmrg/pepo.jl b/test/cuda/ctmrg/pepo.jl index a03321ef2..c6c76f081 100644 --- a/test/cuda/ctmrg/pepo.jl +++ b/test/cuda/ctmrg/pepo.jl @@ -6,7 +6,7 @@ using TensorKit using KrylovKit using OptimKit using Zygote -using CUDACore, Adapt +using CUDA, Adapt ## Setup function three_dimensional_classical_ising(; beta, J = 1.0) @@ -77,7 +77,10 @@ projector_algs = [:HalfInfiniteProjector, :FullInfiniteProjector] @testset "PEPO CTMRG contraction using $alg with $projector_alg" for ( alg, projector_alg, ) in Iterators.product(ctm_styles, projector_algs) - env, = leading_boundary(env0, n; alg, maxiter = 150, projector_alg) + env, = leading_boundary( + env0, n; alg, maxiter = 150, projector_alg, + decomposition_alg = (; alg = :SVDViaPolar) + ) end end @@ -85,7 +88,10 @@ end Random.seed!(81812781144) # prep - ctm_alg = SimultaneousCTMRG(; maxiter = 150, tol = 1.0e-8, verbosity = 2) + ctm_alg = SimultaneousCTMRG(; + maxiter = 150, tol = 1.0e-8, verbosity = 2, + decomposition_alg = (; alg = :SVDViaPolar), + ) gradient_alg = FixedPointGradient(; solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), ) diff --git a/test/cuda/ctmrg/suweight.jl b/test/cuda/ctmrg/suweight.jl index 8b9ace1ae..b115c54eb 100644 --- a/test/cuda/ctmrg/suweight.jl +++ b/test/cuda/ctmrg/suweight.jl @@ -1,7 +1,7 @@ using Test using Random using TensorKit -using CUDACore, Adapt +using CUDA, Adapt using PEPSKit using PEPSKit: str, twistdual, unitcell diff --git a/test/cuda/ctmrg/unitcell.jl b/test/cuda/ctmrg/unitcell.jl index 64cd405d1..f7da0eec3 100644 --- a/test/cuda/ctmrg/unitcell.jl +++ b/test/cuda/ctmrg/unitcell.jl @@ -3,16 +3,16 @@ using Random using PEPSKit using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, ScramblingEnvGauge using TensorKit -using CUDACore, Adapt +using CUDA, Adapt # settings 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 = :HalfInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), + SequentialCTMRG(; projector_alg = :FullInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), + SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), + SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), ] function test_unitcell( diff --git a/test/cuda/gradients/ctmrg_gradients.jl b/test/cuda/gradients/ctmrg_gradients.jl index 3cdab9245..abad25f34 100644 --- a/test/cuda/gradients/ctmrg_gradients.jl +++ b/test/cuda/gradients/ctmrg_gradients.jl @@ -99,7 +99,9 @@ end alg = ctmrg_alg, verbosity = ctmrg_verbosity, projector_alg = projector_alg, - decomposition_alg = SVDAdjoint(; rrule_alg = (; alg = svd_rrule_alg)), + decomposition_alg = SVDAdjoint(; + fwd_alg = (; alg = :SVDViaPolar), rrule_alg = (; alg = svd_rrule_alg) + ), ) # instantiate because hook_pullback doesn't go through the keyword selector... concrete_gradient_alg = if isnothing(gradient_alg) @@ -139,7 +141,9 @@ end @testset "AD CTMRG energy gradient accuracy regression test (#276)" begin Random.seed!(1234) - boundary_alg = PEPSKit.CTMRGAlgorithm(; tol = 1.0e-10) + boundary_alg = PEPSKit.CTMRGAlgorithm(; + tol = 1.0e-10, decomposition_alg = (; alg = :SVDViaPolar) + ) gradient_alg = PEPSKit.GradientAlgorithm(; tol = 5.0e-8) function fg((peps, env)) diff --git a/test/cuda/timeevol/cluster_projectors.jl b/test/cuda/timeevol/cluster_projectors.jl index 3ffb8d8ea..d31341c5f 100644 --- a/test/cuda/timeevol/cluster_projectors.jl +++ b/test/cuda/timeevol/cluster_projectors.jl @@ -6,7 +6,7 @@ using Random import MPSKitModels: hubbard_space using PEPSKit: sdiag_pow, _cluster_truncate!, _flip_virtuals! using MPSKit: GenericMPSTensor, MPSBondTensor -using CUDACore, Adapt +using CUDA, Adapt # Utility setup # ------------- @@ -247,7 +247,10 @@ end normalize!.(peps.A, Inf) env = CTMRGEnv(wts) for trunc in truncs_env - env, = leading_boundary(env, peps; alg = :SequentialCTMRG, tol = ctmrg_tol, trunc) + env, = leading_boundary( + env, peps; alg = :SequentialCTMRG, tol = ctmrg_tol, trunc, + decomposition_alg = (; alg = :SVDViaPolar) + ) end e_site = cost_function(peps, env, ham) / (Nr * Nc) @info "Energy (force_mpo = $(force_mpo)): $e_site" diff --git a/test/cuda/timeevol/j1j2_finiteT.jl b/test/cuda/timeevol/j1j2_finiteT.jl index 002ab7573..e1331a5a3 100644 --- a/test/cuda/timeevol/j1j2_finiteT.jl +++ b/test/cuda/timeevol/j1j2_finiteT.jl @@ -3,7 +3,7 @@ using LinearAlgebra using TensorKit import MPSKitModels: σˣ, σᶻ using PEPSKit -using CUDACore, Adapt +using CUDA, Adapt # Benchmark energy from high-temperature expansion # at β = 0.3, 0.6 @@ -13,7 +13,10 @@ bm = [-0.1235, -0.213] function converge_env(state, χ::Int) env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) trunc1 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) + env, = leading_boundary( + env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10, + decomposition_alg = (; alg = :SVDViaPolar) + ) return env end diff --git a/test/cuda/timeevol/sitedep_truncation.jl b/test/cuda/timeevol/sitedep_truncation.jl index 80549b4c7..b3adc8851 100644 --- a/test/cuda/timeevol/sitedep_truncation.jl +++ b/test/cuda/timeevol/sitedep_truncation.jl @@ -3,7 +3,7 @@ using Random using TensorKit using PEPSKit using PEPSKit: _is_bipartite, _get_fixedspacetrunc -using CUDACore, Adapt +using CUDA, Adapt elt = Float64 Nr, Nc = 2, 2 diff --git a/test/cuda/timeevol/tf_ising_finiteT.jl b/test/cuda/timeevol/tf_ising_finiteT.jl index 9813ec9bf..928f038d7 100644 --- a/test/cuda/timeevol/tf_ising_finiteT.jl +++ b/test/cuda/timeevol/tf_ising_finiteT.jl @@ -2,7 +2,7 @@ using Test using LinearAlgebra using TensorKit import MPSKitModels: σˣ, σᶻ -using PEPSKit, CUDACore, Adapt +using PEPSKit, CUDA, Adapt # Benchmark data of [σx, σz] from HOTRG # Physical Review B 86, 045139 (2012) Fig. 15-16 @@ -12,9 +12,15 @@ bm_2β = [0.5297, 0.8265] function converge_env(state, χ::Int) trunc1 = truncrank(4) & truncerror(; atol = 1.0e-12) env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) - env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) + env, = leading_boundary( + env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10, + decomposition_alg = (; alg = :SVDViaPolar) + ) trunc2 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env, = leading_boundary(env, state; alg = :SequentialCTMRG, trunc = trunc2, tol = 1.0e-10) + env, = leading_boundary( + env, state; alg = :SequentialCTMRG, trunc = trunc2, tol = 1.0e-10, + decomposition_alg = (; alg = :SVDViaPolar) + ) return env end diff --git a/test/cuda/timeevol/timestep.jl b/test/cuda/timeevol/timestep.jl index 6e1934f8d..c99254566 100644 --- a/test/cuda/timeevol/timestep.jl +++ b/test/cuda/timeevol/timestep.jl @@ -2,7 +2,7 @@ using Test using Random using TensorKit using PEPSKit -using CUDACore, Adapt +using CUDA, Adapt @testset "SimpleUpdate timestep" begin Nr, Nc = 2, 2 diff --git a/test/cuda/toolbox/densitymatrices.jl b/test/cuda/toolbox/densitymatrices.jl index c04b34b04..8ee47d574 100644 --- a/test/cuda/toolbox/densitymatrices.jl +++ b/test/cuda/toolbox/densitymatrices.jl @@ -3,7 +3,7 @@ using PEPSKit using PEPSKit: contract_local_operator, contract_local_norm using Test using TestExtras -using CUDACore, Adapt +using CUDA, Adapt ds = Dict(Trivial => ℂ^2, U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), FermionParity => Vect[FermionParity](0 => 2, 1 => 1)) Ds = Dict(Trivial => ℂ^3, U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 2, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) diff --git a/test/cuda/utility/correlator.jl b/test/cuda/utility/correlator.jl index 47fb4dade..28d252ae9 100644 --- a/test/cuda/utility/correlator.jl +++ b/test/cuda/utility/correlator.jl @@ -2,7 +2,7 @@ using Test using Random using TensorKit using PEPSKit -using CUDACore, Adapt +using CUDA, Adapt const syms = (Z2Irrep, FermionParity) diff --git a/test/cuda/utility/eigh_wrapper.jl b/test/cuda/utility/eigh_wrapper.jl index 319b5f713..12d604586 100644 --- a/test/cuda/utility/eigh_wrapper.jl +++ b/test/cuda/utility/eigh_wrapper.jl @@ -5,7 +5,7 @@ using TensorKit using ChainRulesCore, Zygote using Accessors using PEPSKit -using CUDACore, Adapt +using CUDA, Adapt using MatrixAlgebraKit: TruncatedAlgorithm, diagview # Gauge-invariant loss function diff --git a/test/cuda/utility/svd_wrapper.jl b/test/cuda/utility/svd_wrapper.jl index a95c3700e..c813f7d3c 100644 --- a/test/cuda/utility/svd_wrapper.jl +++ b/test/cuda/utility/svd_wrapper.jl @@ -5,7 +5,7 @@ using TensorKit using ChainRulesCore, Zygote using Accessors using PEPSKit -using Adapt, CUDACore +using Adapt, CUDA using MatrixAlgebraKit: TruncatedAlgorithm, diagview, svd_trunc_no_error # Gauge-invariant loss function From 6c1666c5cdfee0678fa6e55794268c70c57afe4b Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 06:10:59 -0400 Subject: [PATCH 43/69] Make sure buffers aren't overwritten in rrule_via_ad --- ext/PEPSKitGPUArraysExt.jl | 2 ++ src/algorithms/ctmrg/ctmrg.jl | 1 + src/algorithms/time_evolution/simpleupdate.jl | 4 ++- src/utility/alloc_cache.jl | 36 +++++++++++++++---- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 6ed449183..6887e5566 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -33,6 +33,8 @@ function PEPSKit._with_alloc_cache(f, ::Type{<:AnyGPUArray}, site::Symbol, iter: return GPUArrays.@cached cache f() end +PEPSKit._uncache(x, ::Type{<:AnyGPUArray}) = deepcopy(x) + function PEPSKit.free_alloc_caches!(::Type{<:AnyGPUArray}) Base.@lock ALLOC_CACHES_LOCK begin for caches in values(ALLOC_CACHES), cache in caches diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 7805853fc..e7ae72f36 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -136,6 +136,7 @@ function leading_boundary( ctmrg_logiter!(log, iter, η, network, env) end end + env = uncache(env, storagetype(env)) info = (; converged, convergence_error = η, diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index a40843fb7..d1a00b1f4 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -162,9 +162,11 @@ end function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) iter, t = state.iter, state.t (iter == it.nstep) && return nothing - psi, env, ϵ = with_alloc_cache(storagetype(state.psi), :su, iter) do + storage = storagetype(state.psi) + psi, env, ϵ = with_alloc_cache(storage, :su, iter) do su_iter(state.psi, it.circuit, it.alg, state.env) end + psi, env = uncache(psi, storage), uncache(env, storage) # update internal state iter += 1 t += it.dt diff --git a/src/utility/alloc_cache.jl b/src/utility/alloc_cache.jl index a8ed31e5d..7c28ef80f 100644 --- a/src/utility/alloc_cache.jl +++ b/src/utility/alloc_cache.jl @@ -1,13 +1,13 @@ """ with_alloc_cache(f, storage, caller::Symbol, iter::Int) -> f() -Run one iteration `f()` of an iterative algorithm using memory cache. +Run one iteration `f()` of an iterative algorithm using a memory cache. **This is a no-op unless `GPUArrays` is loaded _and_ `storage` is a GPU array.** There seems to be no benefit to caching for CPU-side memory, but for the device, -using a warm memory pool avoids everything being blocked while the GPU device driver -allocates. +using a warm memory pool which recycles memory avoids everything being blocked +while the GPU device driver allocates. `caller` ids the calling algo (`:su`, `:ctmrg`) so that algorithms allocating different buffer sizes avoid stepping on each others' caches. Not every algorithm needs an allocation cache, @@ -16,11 +16,12 @@ only the ones with repeated iterations. Belief propagation seems to not benefit from the caching as much, so it's currently unused there. `iter` selects between two alternating caches per `caller`. A buffer allocated during iteration -`i` can't become reusable until iteration `i+2`, by which point the output of iteration `i` has -been used by iteration `i+1`. With only one cache, it would be possible to overwrite the state -at `i+1` while it's still being used. +`i` can't become reusable until later iterations have completely used and discarded its output. +For `:SimultaneousCTMRG` and simple update, that occurs after 2 iterations, while for `:SequentialCTMRG`, +it occurs after 5. With too few caches, it would be possible to overwrite the state +at later iterations while it's still being used. -Caching is skipped while Zygote is differentiating, because the reverse-mode tape holds references +Caching is skipped while `Zygote.jl` is differentiating, because the reverse-mode tape holds references to intermediates, and recycling those could silently corrupt gradients. """ function with_alloc_cache(f, storage::Type, caller::Symbol, iter::Int, depth::Int = 2) @@ -28,6 +29,27 @@ function with_alloc_cache(f, storage::Type, caller::Symbol, iter::Int, depth::In return _with_alloc_cache(f, storage, caller, iter, depth) end +""" + uncache(x, storage::Type) -> x + +Copy `x` out of any allocation-cache region, so that it stays live and is not overwriten +once the enclosing [`with_alloc_cache`](@ref) block has exited. This is necessary to +ensure `leading_boundary` and other functions which call back into AD handle cached +memory correctly. + +Buffers allocated inside a cache block are handed back to the pool when the block exits, +and the next cached call may hand them out again, which *silently overwrites* a result the +caller is still holding. Anything that escapes such a block must be copied out. + +**This is a no-op unless `GPUArrays` is loaded _and_ `storage` is a GPU array**, and also +while Zygote is differentiating, since caching is skipped in both of those cases. +""" +function uncache(x, storage::Type) + Zygote.isderiving() && return x + return _uncache(x, storage) +end +_uncache(x, ::Type) = x + """ alloc_cache_depth(alg) From a495111628946a4888f1d6e4849724a8c94bdddd Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 12:18:20 +0200 Subject: [PATCH 44/69] use Jacobi for the fixed_iterscheme tests for speeeeeeeed --- test/rocm/ctmrg/fixed_iterscheme.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rocm/ctmrg/fixed_iterscheme.jl b/test/rocm/ctmrg/fixed_iterscheme.jl index 92fa4ab7e..1b90c9850 100644 --- a/test/rocm/ctmrg/fixed_iterscheme.jl +++ b/test/rocm/ctmrg/fixed_iterscheme.jl @@ -20,7 +20,7 @@ using PEPSKit.Defaults: ctmrg_tol # initialize parameters D = 2 χ = 16 -svd_algs = [(; alg = :QRIteration), (; alg = :GKL)] +svd_algs = [(; alg = :Jacobi), (; alg = :GKL)] projector_algs_asymm = [:HalfInfiniteProjector] #, :FullInfiniteProjector] unitcells = [(1, 1), (3, 4)] atol = 1.0e-5 From 0a4e8cbe61a3c6eceb71ec5234a2946cd62b9f57 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 09:20:11 -0400 Subject: [PATCH 45/69] Fixes for holding cache memory for too long --- Project.toml | 2 +- ext/PEPSKitGPUArraysExt.jl | 14 ++++++ src/algorithms/ctmrg/ctmrg.jl | 19 +++++++++ src/utility/alloc_cache.jl | 54 ++++++++++++++++++++++-- test/cuda/timeevol/cluster_projectors.jl | 4 ++ test/cuda/timeevol/j1j2_finiteT.jl | 2 + test/cuda/timeevol/sitedep_truncation.jl | 1 + test/cuda/timeevol/tf_ising_finiteT.jl | 1 + 8 files changed, 92 insertions(+), 5 deletions(-) diff --git a/Project.toml b/Project.toml index c4b7a0e5f..80a3e29ca 100644 --- a/Project.toml +++ b/Project.toml @@ -37,7 +37,7 @@ GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" [sources] MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} -MatrixAlgebraKit = {rev = "ksh/svd_trunc_gpu", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} +MatrixAlgebraKit = {rev = "main", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} TensorOperations = {rev = "main", url = "https://github.com/QuantumKitHub/TensorOperations.jl"} TensorKit = {rev = "ksh/add_transform", url = "https://github.com/QuantumKitHub/TensorKit.jl"} diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 6887e5566..d69306e34 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -35,6 +35,20 @@ end PEPSKit._uncache(x, ::Type{<:AnyGPUArray}) = deepcopy(x) +function PEPSKit.free_alloc_caches!(::Type{<:AnyGPUArray}, caller::Symbol) + Base.@lock ALLOC_CACHES_LOCK begin + # collect first: freeing mutates ALLOC_CACHES + stale = [key for key in keys(ALLOC_CACHES) if first(key) === caller] + for key in stale + for cache in ALLOC_CACHES[key] + GPUArrays.unsafe_free!(cache) + end + delete!(ALLOC_CACHES, key) + end + end + return nothing +end + function PEPSKit.free_alloc_caches!(::Type{<:AnyGPUArray}) Base.@lock ALLOC_CACHES_LOCK begin for caches in values(ALLOC_CACHES), cache in caches diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index e7ae72f36..bac4b3193 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -49,6 +49,12 @@ Perform a single CTMRG iteration in which all directions are being grown and ren """ function ctmrg_iteration(network, env, alg::CTMRGAlgorithm) end +# Signature of the buffer sizes a CTMRG run allocates: the corner and edge spaces fix them, +# and the edges carry the network bond dimension as well as the environment dimension. +function _ctmrg_cache_signature(env::CTMRGEnv) + return hash((alloc_cache_signature(env.corners), alloc_cache_signature(env.edges))) +end + """ leading_boundary(env₀, network; kwargs...) -> env, info # expert version: @@ -109,6 +115,11 @@ function leading_boundary( env₀::AbstractCTMRGEnv, network::InfiniteSquareNetwork, alg::CTMRGAlgorithm ) check_input(leading_boundary, network, env₀, alg) + # cached buffer sizes are set by the corner/edge spaces, and the edges carry the network + # bond dimension too, so a change here means every pooled buffer has gone stale + ignore_derivatives() do + free_stale_alloc_caches!(storagetype(env₀), :ctmrg, _ctmrg_cache_signature(env₀)) + end log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) return with_verbosity(; alg.verbosity) do env = deepcopy(env₀) @@ -137,6 +148,14 @@ function leading_boundary( end end env = uncache(env, storagetype(env)) + # a truncation that is not fixed-space grows the corner/edge spaces as the loop runs, + # leaving one pooled buffer set per intermediate shape. `env` is copied out by now, so + # nothing live is backed by the cache and it is safe to drop those here. Re-checking + # the signature keeps the pool warm for the repeated fixed-space calls of an + # optimization loop, where the spaces do not move. + ignore_derivatives() do + free_stale_alloc_caches!(storagetype(env), :ctmrg, _ctmrg_cache_signature(env)) + end info = (; converged, convergence_error = η, diff --git a/src/utility/alloc_cache.jl b/src/utility/alloc_cache.jl index 7c28ef80f..62d0cda50 100644 --- a/src/utility/alloc_cache.jl +++ b/src/utility/alloc_cache.jl @@ -61,10 +61,56 @@ _with_alloc_cache(f, ::Type, ::Symbol, ::Int, ::Int) = f() """ free_alloc_caches!(storage) + free_alloc_caches!(storage, caller::Symbol) -Release memory held by the allocation caches for storage type `storage`. Does nothing -unless the `PEPSKitGPUArraysExt` extension is loaded and `storage` is a GPU array type. -It's worth calling this when the bond dimension changes, because the cache keys depend on -buffer size. +Release memory held by the allocation caches for storage type `storage`, either for every +`caller` (if none is provided) or only for the given one. Does nothing unless the +`PEPSKitGPUArraysExt` extension is loaded and `storage` is a GPU array type. +This should be called when the bond dimension changes, because the cache keys depend on +buffer size and the caches can't be reused when the bond dimension has changed. + +!!! warning + This releases the underlying device memory, so it is only safe to call when nothing + still references a buffer that was allocated inside a [`with_alloc_cache`](@ref) block. + Results that escape such a block must have been copied out with [`uncache`](@ref) + first, otherwise freeing the cache leaves them undefined. """ free_alloc_caches!(::Type) = nothing +free_alloc_caches!(::Type, ::Symbol) = nothing + +# last buffer-shape signature seen per caller, used to detect when cached buffer sizes +# have gone stale because a bond dimension changed +const ALLOC_CACHE_SIGNATURES = Dict{Symbol, UInt}() +const ALLOC_CACHE_SIGNATURES_LOCK = ReentrantLock() + +""" + free_stale_alloc_caches!(storage, caller::Symbol, signature::UInt) + +Release `caller`'s allocation caches when `signature` differs from the one seen on the +previous call, and record `signature` as the current one. + +The caches are keyed by buffer size, so that when the size changes the old, unusable caches +can be freed and new ones allocated, corresponding to the new size. This avoids the cache +size growing unboundedly. + +Like [`free_alloc_caches!`](@ref) this is a no-op without a GPU storage type, and it is +skipped while `Zygote.jl` is differentiating, since caching is disabled there anyway. +""" +function free_stale_alloc_caches!(storage::Type, caller::Symbol, signature::UInt) + Zygote.isderiving() && return nothing + stale = Base.@lock ALLOC_CACHE_SIGNATURES_LOCK begin + previous = get(ALLOC_CACHE_SIGNATURES, caller, nothing) + ALLOC_CACHE_SIGNATURES[caller] = signature + !isnothing(previous) && previous != signature + end + stale && free_alloc_caches!(storage, caller) + return nothing +end + +""" + alloc_cache_signature(tensors) -> UInt + +Hash the spaces of `tensors`, for use as a [`free_stale_alloc_caches!`](@ref) signature. +Two states whose tensors live in the same spaces allocate the same buffer sizes. +""" +alloc_cache_signature(tensors) = hash(map(space, tensors)) diff --git a/test/cuda/timeevol/cluster_projectors.jl b/test/cuda/timeevol/cluster_projectors.jl index d31341c5f..3db8d1942 100644 --- a/test/cuda/timeevol/cluster_projectors.jl +++ b/test/cuda/timeevol/cluster_projectors.jl @@ -142,6 +142,7 @@ Vspaces = [ Random.seed!(0) N, n = 5, 2 for (Vphy, Vns, V) in Vspaces + GC.gc(); CUDA.reclaim() # release the previous iteration's device pool Vvirs = fill(Vns, N + 1) Vvirs[n + 1] = V Ms1 = map(1:N) do i @@ -179,6 +180,7 @@ end @testset "Identity gate on 3-site cluster" begin N, n = 3, 1 for (Vphy, Vns, V) in Vspaces + GC.gc(); CUDA.reclaim() # release the previous iteration's device pool Vvirs = fill(Vns, N + 1) Vvirs[n + 1] = V Ms1 = map(1:N) do i @@ -196,6 +198,7 @@ end @test fid ≈ 1.0 end for (Vphy, Vns, V) in Vspaces + GC.gc(); CUDA.reclaim() # release the previous iteration's device pool Vvirs = fill(Vns, N + 1) Vvirs[n + 1] = V Ms1 = map(1:N) do i @@ -238,6 +241,7 @@ end # applying 2-site gates decomposed to MPO or not, # resulting energy should be almost the same e_sites = map((true, false)) do force_mpo + GC.gc(); CUDA.reclaim() # release the previous iteration's device pool peps, wts = deepcopy(peps0), deepcopy(wts0) trunc = truncerror(; atol = 1.0e-10) & truncrank(4) alg = SimpleUpdate(; trunc, force_mpo) diff --git a/test/cuda/timeevol/j1j2_finiteT.jl b/test/cuda/timeevol/j1j2_finiteT.jl index e1331a5a3..d96b4c126 100644 --- a/test/cuda/timeevol/j1j2_finiteT.jl +++ b/test/cuda/timeevol/j1j2_finiteT.jl @@ -38,6 +38,7 @@ dt, nstep = 1.0e-3, 600 # PEPO approach alg = SimpleUpdate(; trunc = trunc_pepo, purified = false) +GC.gc(); CUDA.reclaim() # release the previous phase's device pool evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) pepo, wts, info = time_evolve(evolver; check_interval) env = converge_env(InfinitePartitionFunction(pepo), 16) @@ -48,6 +49,7 @@ energy = expectation_value(pepo, ham, env) / (Nr * Nc) # PEPS (purified PEPO) approach alg = SimpleUpdate(; trunc = trunc_pepo, purified = true) +GC.gc(); CUDA.reclaim() # release the previous phase's device pool evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) pepo, wts, info = time_evolve(evolver; check_interval) env = converge_env(InfinitePartitionFunction(pepo), 16) diff --git a/test/cuda/timeevol/sitedep_truncation.jl b/test/cuda/timeevol/sitedep_truncation.jl index b3adc8851..5a26f345d 100644 --- a/test/cuda/timeevol/sitedep_truncation.jl +++ b/test/cuda/timeevol/sitedep_truncation.jl @@ -40,6 +40,7 @@ end @testset "Simple update on $(typeof(state0).name.wrapper), bipartite = $(bipartite)" for (state0, bipartite) in Iterators.product(states, (true, false)) + GC.gc(); CUDA.reclaim() # release the previous iteration's device pool J2 = 0.5 if bipartite state0[2, 1] = copy(state0[1, 2]) diff --git a/test/cuda/timeevol/tf_ising_finiteT.jl b/test/cuda/timeevol/tf_ising_finiteT.jl index 928f038d7..664934ce0 100644 --- a/test/cuda/timeevol/tf_ising_finiteT.jl +++ b/test/cuda/timeevol/tf_ising_finiteT.jl @@ -53,6 +53,7 @@ dt, nstep = 1.0e-3, 400 # when g = 2, β = 0.4 and 2β = 0.8 belong to two phases (without and with nonzero σᶻ) @testset "Finite-T SU (force_mpo = $(force_mpo))" for force_mpo in (false, true) + GC.gc(); CUDA.reclaim() # release the previous iteration's device pool # use second order Trotter decomposition symmetrize_gates = true bipartite = true From 1943a405f8f2cf84415177f4e1732b2982349649 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 21 Aug 2026 03:47:47 -0400 Subject: [PATCH 46/69] Try turning off the most memory intensive part --- test/cuda/timeevol/tf_ising_finiteT.jl | 35 ++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/test/cuda/timeevol/tf_ising_finiteT.jl b/test/cuda/timeevol/tf_ising_finiteT.jl index 664934ce0..c70b143c3 100644 --- a/test/cuda/timeevol/tf_ising_finiteT.jl +++ b/test/cuda/timeevol/tf_ising_finiteT.jl @@ -46,6 +46,11 @@ pepo0 = adapt(CuArray, PEPSKit.infinite_temperature_density_matrix(ham)) @test TensorKit.storagetype(pepo0) <: CuVector wts0 = SUWeight(pepo0) +# Buildkite's GPU has less memory (~4GB) than most workstations, +# so the most memory-hungry block below # is skipped there; +# see the comment on the purification block for the details. +const CI_GPU = get(ENV, "BUILDKITE", "false") == "true" + trunc_pepo = truncrank(8) & truncerror(; atol = 1.0e-12) dt, nstep = 1.0e-3, 400 @@ -75,6 +80,7 @@ dt, nstep = 1.0e-3, 400 @info "tr(σ(x,z)ρ) at T = $(1 / β): $(result_β)." @test β ≈ info.t @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) + GC.gc(); CUDA.reclaim() # release the previous block's device pool # use `compress` to reach 2β, or T = 1.25 pepo2, = compress((pepo, pepo), LocalTruncation(trunc_pepo)) @@ -83,13 +89,26 @@ dt, nstep = 1.0e-3, 400 result_2β = measure_mag(pepo2, env2) @info "tr(σ(x,z)ρ) at T = $(1 / (2β)): $(result_2β)." @test isapprox(abs.(result_2β), bm_2β, rtol = 5.0e-3) + GC.gc(); CUDA.reclaim() # release the previous block's device pool - # Purification approach: results at 2β, or T = 1.25 - alg = SimpleUpdate(; trunc = trunc_pepo, purified = true, bipartite, force_mpo) - pepo, wts, info = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0; symmetrize_gates) - env = converge_env(InfinitePEPS(pepo), 8) - result_2β′ = measure_mag(pepo, env; purified = true) - @info "⟨ρ|σ(x,z)|ρ⟩ at T = $(1 / (2β)): $(result_2β′)." - @test 2 * β ≈ info.t - @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) + # Purification approach: results at 2β, or T = 1.25. + # + # Skipped on Buildkite + CUDA: `converge_env` here contracts an `InfinitePEPS`, which is a + # *double-layer* network carrying both a ket and a bra virtual index per leg. With the + # PEPO bond dimension at `trunc_pepo` = 8 that makes the enlarged corners 512x512 + # (2.0 MiB) rather than the 128x128 (0.12 MiB) of the `InfinitePartitionFunction` + # contractions above, so one CTMRG iteration allocates ~1.5 GiB against ~160 MiB, and + # the block peaks around 13 GiB — more than the 4GiB CI GPU has! Lowering χ here + # wouldn't help much: it is already the smallest χ in this file, and the cost is + # dominated by the D² network legs rather than by χ. + if !CI_GPU + alg = SimpleUpdate(; trunc = trunc_pepo, purified = true, bipartite, force_mpo) + pepo, wts, info = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0; symmetrize_gates) + env = converge_env(InfinitePEPS(pepo), 8) + result_2β′ = measure_mag(pepo, env; purified = true) + @info "⟨ρ|σ(x,z)|ρ⟩ at T = $(1 / (2β)): $(result_2β′)." + @test 2 * β ≈ info.t + @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) + GC.gc(); CUDA.reclaim() # release the previous block's device pool + end end From 4e9916ebb7201e5c3afc13e9aa6db5074f52573d Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 21 Aug 2026 08:44:07 +0200 Subject: [PATCH 47/69] Add a quick check to make sure no empty spaces were created --- src/algorithms/time_evolution/simpleupdate.jl | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index d1a00b1f4..778190ff8 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -159,6 +159,29 @@ function su_iter( return state2, env2, ϵ end +""" + check_su_state(psi, iter) + +Check that a simple-update step did not produce a degenerate state. + +Without this check, later CTMRG can "converge" immediately because a `NaN` +objective compares equal to itself, reports `converged = true`, and the run finishes with +a `NaN` energy and a suspiciously fast wall time. + +Only vector-space dimensions are inspected, never tensor data, so this is quick and inexpensive. +""" +function check_su_state(psi, iter) + for (idx, t) in pairs(unitcell(psi)) + dim(space(t)) > 0 || throw( + ErrorException( + "simple update produced a degenerate state at iteration $iter: tensor $idx \ + has an empty space ($(space(t)))." + ) + ) + end + return nothing +end + function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) iter, t = state.iter, state.t (iter == it.nstep) && return nothing @@ -167,6 +190,7 @@ function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) su_iter(state.psi, it.circuit, it.alg, state.env) end psi, env = uncache(psi, storage), uncache(env, storage) + check_su_state(psi, iter + 1) # update internal state iter += 1 t += it.dt From 8ae77e63375e56628f30d28390e306a06dd11da9 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 22 Aug 2026 21:21:14 +0200 Subject: [PATCH 48/69] Batched SVD support and more caching improvements --- Project.toml | 4 +- ext/PEPSKitGPUArraysExt.jl | 250 ++++++++++++++++++++- src/algorithms/ctmrg/ctmrg.jl | 5 +- src/algorithms/time_evolution/apply_mpo.jl | 33 ++- src/utility/alloc_cache.jl | 26 ++- src/utility/util.jl | 17 ++ 6 files changed, 318 insertions(+), 17 deletions(-) diff --git a/Project.toml b/Project.toml index 80a3e29ca..16f339a0d 100644 --- a/Project.toml +++ b/Project.toml @@ -37,9 +37,9 @@ GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" [sources] MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} -MatrixAlgebraKit = {rev = "main", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} +MatrixAlgebraKit = {rev = "ksh/batched_svd", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} TensorOperations = {rev = "main", url = "https://github.com/QuantumKitHub/TensorOperations.jl"} -TensorKit = {rev = "ksh/add_transform", url = "https://github.com/QuantumKitHub/TensorKit.jl"} +TensorKit = {rev = "ksh/batched_svd", url = "https://github.com/QuantumKitHub/TensorKit.jl"} [extensions] PEPSKitAdaptExt = "Adapt" diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index d69306e34..09daece81 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -2,8 +2,10 @@ module PEPSKitGPUArraysExt using GPUArrays using GPUArrays: AnyGPUArray, AllocCache -using PEPSKit, MatrixAlgebraKit +using PEPSKit using LinearAlgebra +using TensorKit +using TensorKit: MatrixAlgebraKit as MAK import PEPSKit: _column_vectors, _materialize_block, _indexable_diagview PEPSKit._materialize_block(m::Union{Adjoint{T, A}, Transpose{T, A}}) where {T, A <: AnyGPUArray} = copyto!(similar(m, size(m)), m) @@ -11,7 +13,7 @@ function PEPSKit._column_vectors(::Type{A}, m::AbstractMatrix) where {A <: AnyGP m̃ = PEPSKit._materialize_block(m) return A[m̃[:, j] for j in axes(m̃, 2)] end -PEPSKit._indexable_diagview(m::AnyGPUArray) = collect(MatrixAlgebraKit.diagview(m)) +PEPSKit._indexable_diagview(m::AnyGPUArray) = collect(MAK.diagview(m)) # Each caller (such as `su_iter`) gets a pair of caches. This makes sense to do on a per-caller basis # because what is being cached varies between algorithms. @@ -33,6 +35,17 @@ function PEPSKit._with_alloc_cache(f, ::Type{<:AnyGPUArray}, site::Symbol, iter: return GPUArrays.@cached cache f() end +# Reduce into a 0-dimensional device array instead of returning a host scalar. `sdiag_pow` only +# feeds this into a broadcast, and a 0-dim array broadcasts as a scalar, so the value never has to +# come back to the host. Returning a number here would force a device sync on every call, and +# `sdiag_pow` runs once per bond per weight absorption in simple update. +function PEPSKit._maxabs(data::AnyGPUArray) + acc = similar(data, ()) + fill!(acc, zero(eltype(data))) + Base.mapreducedim!(abs, max, acc, data) + return acc +end + PEPSKit._uncache(x, ::Type{<:AnyGPUArray}) = deepcopy(x) function PEPSKit.free_alloc_caches!(::Type{<:AnyGPUArray}, caller::Symbol) @@ -59,4 +72,237 @@ function PEPSKit.free_alloc_caches!(::Type{<:AnyGPUArray}) return nothing end + +const Factorizations = TensorKit.Factorizations + +# Batched truncated SVD of a whole cluster's internal bonds to avoid multiple small kernel launches. +function PEPSKit.bond_svds( + ::Type{<:AnyGPUArray}, rls::AbstractVector, truncs::AbstractVector + ) + isempty(rls) && return map(_ -> nothing, rls) + # The different GPU libaries offer different batching algos, + # make sure we have one that actually works. + alg_t = MAK.default_algorithm(MAK.svd_compact!, eltype(rls)) + alg_b = alg_t isa Factorizations.BatchedSVDAlgorithm ? + Factorizations.unbatched(alg_t) : alg_t + Fs = map(rl -> MAK.initialize_output(MAK.svd_compact!, rl, alg_b), rls) + balg = _cluster_batched_alg(rls) + if isnothing(balg) + for (rl, F) in zip(rls, Fs) + MAK.svd_compact!(rl, F, alg_b) + end + else + _cluster_svd_compact!(rls, Fs, balg, alg_b) + end + return map(Fs, truncs) do F, trunc + (U, S, Vᴴ) = F + USVᴴtrunc, ind = MAK.truncate(MAK.svd_trunc!, (U, S, Vᴴ), trunc) + ϵ = MAK.truncation_error!(TensorKit.diagview(S), ind) + return (USVᴴtrunc..., ϵ) + end +end + +# Pool every (bond, sector) block, group by block size, and hand each group to the batched +# driver. Blocks of equal size batch together even across different bonds, since the +# decomposition does not care which bond a block came from. +""" + CLUSTER_BATCHED_SVD[] + +Whether simple update batches the SVDs of a cluster's internal bonds into one call. +Off by default. +""" +const CLUSTER_BATCHED_SVD = Ref(false) + +# Which batched algorithm the backend offers for the cluster's blocks, or `nothing`. +function _cluster_batched_alg(rls::AbstractVector) + CLUSTER_BATCHED_SVD[] || return nothing + for i in eachindex(rls), c in blocksectors(rls[i]) + return _batched_spectra_alg(block(rls[i], c)) + end + return nothing +end + +function _cluster_svd_compact!(rls::AbstractVector, Fs, alg, alg_b) + I = eltype(eachindex(rls)) + C = sectortype(eltype(rls)) + groups = Dict{Tuple{Int, Int}, Vector{Tuple{I, C}}}() + for i in eachindex(rls), c in blocksectors(rls[i]) + push!(get!(() -> Tuple{I, C}[], groups, size(block(rls[i], c))), (i, c)) + end + lim = Factorizations.max_batched_blocksize(alg, TensorKit.storagetype(eltype(rls))) + tall = Factorizations.batched_requires_tall(alg) + + small = Tuple{I, C}[] + for ((m, n), items) in groups + if length(items) >= BATCH_THRESHOLD && (!tall || m >= n) && max(m, n) <= lim + _batch_svd_compact!(rls, Fs, items, (m, n), false, alg) + else + append!(small, items) + end + end + + # Everything left over goes into one zero-padded batch. A compact decomposition only reads + # back the leading `min(m, n)` columns of each block, and zero padding leaves those + # untouched, so padding is safe here (unlike a full decomposition). + mm = maximum(((i, c),) -> size(block(rls[i], c), 1), small; init = 0) + nn = maximum(((i, c),) -> size(block(rls[i], c), 2), small; init = 0) + padded = tall ? (max(mm, nn), max(mm, nn)) : (mm, nn) + if length(small) >= BATCH_THRESHOLD && maximum(padded) <= lim + _batch_svd_compact!(rls, Fs, small, padded, true, alg) + else + for (i, c) in small + U, S, Vᴴ = Fs[i] + MAK.svd_compact!( + block(rls[i], c), (block(U, c), block(S, c), block(Vᴴ, c)), alg_b + ) + end + end + return Fs +end + +function _batch_svd_compact!(rls, Fs, items, (m, n), pad::Bool, alg) + i1, c1 = first(items) + nb, minmn = length(items), min(m, n) + A = similar(block(rls[i1], c1), m, n, nb) + pad && fill!(A, zero(eltype(A))) + for (j, (i, c)) in enumerate(items) + b = block(rls[i], c) + copyto!(view(A, axes(b, 1), axes(b, 2), j), b) + end + Ub = similar(A, m, minmn, nb) + Vb = similar(A, minmn, n, nb) + Sb = similar(TensorKit.diagview(block(Fs[i1][2], c1)), minmn, nb) + MAK.svd_compact!(A, (Ub, Sb, Vb), alg) + for (j, (i, c)) in enumerate(items) + U, S, Vᴴ = Fs[i] + u, sv, v = block(U, c), block(S, c), block(Vᴴ, c) + copyto!(u, view(Ub, axes(u, 1), axes(u, 2), j)) + dv = TensorKit.diagview(sv) + copyto!(dv, view(Sb, axes(dv, 1), j)) + copyto!(v, view(Vb, axes(v, 1), axes(v, 2), j)) + end + return nothing +end + +""" + _batched_spectra_alg(proto) -> alg or nothing + +Default batched SVD algorithm this backend offers, or `nothing` if it has none. +""" +function _batched_spectra_alg(proto) + # TODO BAD MAKE THIS A MAK CALL + alg = try + MAK.default_svd_algorithm(typeof(similar(proto, 0, 0, 0))) + catch + return nothing + end + return alg isa Factorizations.BatchedSVDAlgorithm ? alg : nothing +end + +# `calc_convergence` decomposes every corner and every edge of the environment +# for regular CTMRG, which is expensive, at *least* 8 separate `svd_vals` calls. +# Across *multiple tensors* the situation is much better than within *one*, +# because the corners have to all share a space, +# so for a given sector their blocks have identical sizes and can be batched with no +# padding at all. +function _batch_svd_vals!(ts, Ss, items, (m, n), pad::Bool, alg) + b1 = block(ts[first(items)[1]], first(items)[2]) + A = similar(b1, m, n, length(items)) + pad && fill!(A, zero(eltype(A))) + for (j, (i, c)) in enumerate(items) + b = block(ts[i], c) + copyto!(view(A, axes(b, 1), axes(b, 2), j), b) + end + o1 = block(Ss[first(items)[1]], first(items)[2]) + Sb = similar(o1, min(m, n), length(items)) + MAK.svd_vals!(A, Sb, alg) + for (j, (i, c)) in enumerate(items) + o = block(Ss[i], c) + copyto!(o, view(Sb, axes(o, 1), j)) + end + return nothing +end + +# Hook into the collection-level convergence API. Deliberately restricted to the generic +# CTMRG algorithms: `C4vCTMRG` overrides `corner_spectrum` to `eigh_vals` (its corners are +# diagonal), so a blanket override here would silently switch it back to `svd_vals`. +function PEPSKit.corner_spectra( + Cs::AbstractArray{<:AbstractTensorMap}, + ::Union{PEPSKit.SequentialCTMRG, PEPSKit.SimultaneousCTMRG}, + ) + return _batched_spectra(Cs) +end +function PEPSKit.edge_spectra( + Ts::AbstractArray{<:AbstractTensorMap}, + ::Union{PEPSKit.SequentialCTMRG, PEPSKit.SimultaneousCTMRG}, + ) + return _batched_spectra(Ts) +end + +function _batched_spectra(ts::AbstractArray{T}) where {T <: AbstractTensorMap} + # TODO BAD FIND A BETTER DISPATCH HERE + (isempty(ts) || !(TensorKit.storagetype(T) <: AnyGPUArray)) && return map(svd_vals, ts) + proto = nothing + for i in eachindex(ts), c in blocksectors(ts[i]) + proto = block(ts[i], c) + break + end + isnothing(proto) && return map(svd_vals, ts) + alg = _batched_spectra_alg(proto) + isnothing(alg) && return map(svd_vals, ts) + tall = Factorizations.batched_requires_tall(alg) + lim = Factorizations.max_batched_blocksize(alg, TensorKit.storagetype(T)) + + Ss = map( + t -> MAK.initialize_output( + MAK.svd_vals!, t, + MAK.default_algorithm( + MAK.svd_vals!, typeof(t) + ) + ), ts + ) + + # Group by block size because the decomposition doesn't care which sector a block came + # from, so blocks of equal size can batch together even *across* sectors and tensors. + # Each entry records the (tensor index, sector) it came from so the spectrum can be written back. + I = eltype(eachindex(ts)) + C = sectortype(eltype(ts)) + groups = Dict{Tuple{Int, Int}, Vector{Tuple{I, C}}}() + for i in eachindex(ts), c in blocksectors(ts[i]) + push!(get!(() -> Tuple{I, C}[], groups, size(block(ts[i], c))), (i, c)) + end + + # Groups that the solver can't work with, due to too few blocks, wider than + # tall for an algo that needs m >= n, or larger than the solver's block limit, + # join the padded batch below. + small = Tuple{I, C}[] + for ((m, n), items) in groups + if length(items) >= BATCH_THRESHOLD && (!tall || m >= n) && max(m, n) <= lim + _batch_svd_vals!(ts, Ss, items, (m, n), false, alg) + else + append!(small, items) + end + end + + # Groups too small to batch on their own are merged into one padded batch: zero padding + # leaves a block's leading min(m, n) singular values untouched. + mm = maximum(((i, c),) -> size(block(ts[i], c), 1), small; init = 0) + nn = maximum(((i, c),) -> size(block(ts[i], c), 2), small; init = 0) + # pad to a square only when the algo demands m >= n + padded = tall ? (max(mm, nn), max(mm, nn)) : (mm, nn) + if length(small) >= BATCH_THRESHOLD && maximum(padded) <= lim + _batch_svd_vals!(ts, Ss, small, padded, true, alg) + else + for (i, c) in small + # `svd_vals!` destroys its input, and `block(ts[i], c)` is a view into the live + # environment tensor -- computing the convergence spectra must not damage the + # environment it is measuring, so hand the driver a copy. (The batched branch is + # already safe: `_batch_svd_vals!` packs the blocks into a fresh array.) + b = copy(block(ts[i], c)) + MAK.svd_vals!(b, block(Ss[i], c), MAK.default_svd_algorithm(typeof(b))) + end + end + return Ss +end + end diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index bac4b3193..47e171ca4 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -118,7 +118,7 @@ function leading_boundary( # cached buffer sizes are set by the corner/edge spaces, and the edges carry the network # bond dimension too, so a change here means every pooled buffer has gone stale ignore_derivatives() do - free_stale_alloc_caches!(storagetype(env₀), :ctmrg, _ctmrg_cache_signature(env₀)) + free_stale_alloc_caches!(storagetype(env₀), :ctmrg, :enter, _ctmrg_cache_signature(env₀)) end log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) return with_verbosity(; alg.verbosity) do @@ -154,7 +154,7 @@ function leading_boundary( # the signature keeps the pool warm for the repeated fixed-space calls of an # optimization loop, where the spaces do not move. ignore_derivatives() do - free_stale_alloc_caches!(storagetype(env), :ctmrg, _ctmrg_cache_signature(env)) + free_stale_alloc_caches!(storagetype(env), :ctmrg, :exit, _ctmrg_cache_signature(env)) end info = (; converged, @@ -263,6 +263,7 @@ edge_spectra(Ts, alg) = map(T -> edge_spectrum(T, alg), Ts) The spectrum of a corner, used to measure CTMRG convergence. """ corner_spectrum(C, alg) = svd_vals(C) + """ edge_spectrum(T, alg) diff --git a/src/algorithms/time_evolution/apply_mpo.jl b/src/algorithms/time_evolution/apply_mpo.jl index 511efb47d..7c3c287aa 100644 --- a/src/algorithms/time_evolution/apply_mpo.jl +++ b/src/algorithms/time_evolution/apply_mpo.jl @@ -214,11 +214,34 @@ function _proj_from_RL( @assert isdual(domain(l, 1)) == isdual(codomain(l, 1)) == false rl = r * l u, s, vh, ϵ = svd_trunc!(rl; trunc) + return _proj_from_svd(r, l, u, s, vh, ϵ) +end + +# Second half of `_proj_from_RL`, split off so that the decomposition can be +# done for the whole cluster at once (see `bond_svds`). +function _proj_from_svd(r::MPSBondTensor, l::MPSBondTensor, u, s, vh, ϵ) sinv = sdiag_pow(s, -1 / 2) Pa, Pb = l * vh' * sinv, sinv * u' * r return Pa, s, Pb, ϵ end +""" + bond_svds(rls, truncs) + +Truncated SVD of every internal bond of a cluster, returning `(u, s, vh, ϵ)` per bond. + +Separate from [`_proj_from_RL`](@ref) so that *all* bonds of the cluster can be decomposed +in a single batched call. +""" +function bond_svds(rls::AbstractVector, truncs::AbstractVector) + return bond_svds(storagetype(eltype(rls)), rls, truncs) +end +function bond_svds(::Type, rls::AbstractVector, truncs::AbstractVector) + return map(rls, truncs) do rl, trunc + return svd_trunc!(rl; trunc) + end +end + """ Given a cluster `Ms`, find all projectors `Pa`, `Pb` and Schmidt weights `wts` on internal bonds. @@ -229,8 +252,14 @@ function _get_allprojs( N = length(Ms) Rs, Ls = _get_allRLs(Ms) @assert length(truncs) == N - 1 - projs_errs = map(Rs, Ls, truncs) do R, L, trunc - return _proj_from_RL(R, L; trunc) + for (R, L) in zip(Rs, Ls) + @assert isdual(domain(R, 1)) == isdual(codomain(R, 1)) == false + @assert isdual(domain(L, 1)) == isdual(codomain(L, 1)) == false + end + # decompose every bond of the cluster in one go, then finish each projector locally + svds = bond_svds(map(*, Rs, Ls), truncs) + projs_errs = map(Rs, Ls, svds) do R, L, (u, s, vh, ϵ) + return _proj_from_svd(R, L, u, s, vh, ϵ) end Pas = map(Base.Fix2(getindex, 1), projs_errs) wts = map(Base.Fix2(getindex, 2), projs_errs) diff --git a/src/utility/alloc_cache.jl b/src/utility/alloc_cache.jl index 62d0cda50..64feb3fce 100644 --- a/src/utility/alloc_cache.jl +++ b/src/utility/alloc_cache.jl @@ -80,27 +80,35 @@ free_alloc_caches!(::Type, ::Symbol) = nothing # last buffer-shape signature seen per caller, used to detect when cached buffer sizes # have gone stale because a bond dimension changed -const ALLOC_CACHE_SIGNATURES = Dict{Symbol, UInt}() +const ALLOC_CACHE_SIGNATURES = Dict{Tuple{Symbol, Symbol}, UInt}() const ALLOC_CACHE_SIGNATURES_LOCK = ReentrantLock() """ - free_stale_alloc_caches!(storage, caller::Symbol, signature::UInt) + free_stale_alloc_caches!(storage, caller::Symbol, phase::Symbol, signature::UInt) -Release `caller`'s allocation caches when `signature` differs from the one seen on the -previous call, and record `signature` as the current one. +Release `caller`'s allocation caches when `signature` differs from the one seen at the same +`phase` of the previous call, and record `signature` as the current one for that phase. The caches are keyed by buffer size, so that when the size changes the old, unusable caches can be freed and new ones allocated, corresponding to the new size. This avoids the cache -size growing unboundedly. +size growing without bound. -Like [`free_alloc_caches!`](@ref) this is a no-op without a GPU storage type, and it is +`phase` distinguishes the points a caller checks from. For example, CTMRG grows the +corner and edge spaces as it converges, so its incoming and outgoing environments differ +whenever the truncation is *not* fixed-space. Recording both under one key makes the stored +signature alternate between them, so every check reports stale and the pool is freed on every +call. Comparing each phase only against itself keeps the pool across repeated calls while +still invalidating it when the spaces genuinely change. + +Like [`free_alloc_caches!`](@ref) this is a no-op without a GPU storage type, and it's skipped while `Zygote.jl` is differentiating, since caching is disabled there anyway. """ -function free_stale_alloc_caches!(storage::Type, caller::Symbol, signature::UInt) +function free_stale_alloc_caches!(storage::Type, caller::Symbol, phase::Symbol, signature::UInt) Zygote.isderiving() && return nothing stale = Base.@lock ALLOC_CACHE_SIGNATURES_LOCK begin - previous = get(ALLOC_CACHE_SIGNATURES, caller, nothing) - ALLOC_CACHE_SIGNATURES[caller] = signature + key = (caller, phase) + previous = get(ALLOC_CACHE_SIGNATURES, key, nothing) + ALLOC_CACHE_SIGNATURES[key] = signature !isnothing(previous) && previous != signature end stale && free_alloc_caches!(storage, caller) diff --git a/src/utility/util.jl b/src/utility/util.jl index df25d2ae5..51297dfd3 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -8,6 +8,23 @@ function _elementwise_mult(a₁::AbstractTensorMap, a₂::AbstractTensorMap) end _safe_pow(a::Number, pow::Real, tol::Real) = (pow < 0 && abs(a) < tol) ? zero(a) : a^pow +# Same cutoff, but with the relative tolerance and the scale kept as separate arguments so +# that the scale doesn't need to be copied back to the CPU memory. +# `mx` is either a plain number or a 0-dimensional array, which broadcasts as a scalar. +_safe_pow(a::Number, pow::Real, tol::Real, mx::Number) = _safe_pow(a, pow, tol * mx) + +""" + _maxabs(data) + +Largest absolute value in `data`, equal to `norm(_, Inf)` for the diagonal storage of a +`DiagonalTensorMap`. + +Returns a scalar by default. +""" +function _maxabs(data::AbstractArray) + isempty(data) && return zero(real(eltype(data))) + return norm(data, Inf) +end """ _maxabs(data) From 35c2cd147cbe932bba5cc128b774dcaf45dfab4e Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 24 Aug 2026 01:51:59 -0400 Subject: [PATCH 49/69] Force real-valued accumulator --- ext/PEPSKitGPUArraysExt.jl | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 09daece81..517193017 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -40,8 +40,9 @@ end # come back to the host. Returning a number here would force a device sync on every call, and # `sdiag_pow` runs once per bond per weight absorption in simple update. function PEPSKit._maxabs(data::AnyGPUArray) - acc = similar(data, ()) - fill!(acc, zero(eltype(data))) + T = real(eltype(data)) + acc = similar(data, T, ()) + fill!(acc, zero(T)) Base.mapreducedim!(abs, max, acc, data) return acc end @@ -82,17 +83,15 @@ function PEPSKit.bond_svds( isempty(rls) && return map(_ -> nothing, rls) # The different GPU libaries offer different batching algos, # make sure we have one that actually works. - alg_t = MAK.default_algorithm(MAK.svd_compact!, eltype(rls)) - alg_b = alg_t isa Factorizations.BatchedSVDAlgorithm ? - Factorizations.unbatched(alg_t) : alg_t - Fs = map(rl -> MAK.initialize_output(MAK.svd_compact!, rl, alg_b), rls) + alg = MAK.default_algorithm(MAK.batched_svd_compact!, eltype(rls)) + Fs = map(rl -> MAK.initialize_output(MAK.svd_compact!, rl, alg), rls) balg = _cluster_batched_alg(rls) if isnothing(balg) for (rl, F) in zip(rls, Fs) - MAK.svd_compact!(rl, F, alg_b) + MAK.svd_compact!(rl, F, alg) end else - _cluster_svd_compact!(rls, Fs, balg, alg_b) + _cluster_svd_compact!(rls, Fs, balg, alg) end return map(Fs, truncs) do F, trunc (U, S, Vᴴ) = F @@ -122,7 +121,7 @@ function _cluster_batched_alg(rls::AbstractVector) return nothing end -function _cluster_svd_compact!(rls::AbstractVector, Fs, alg, alg_b) +function _cluster_svd_compact!(rls::AbstractVector, Fs, alg) I = eltype(eachindex(rls)) C = sectortype(eltype(rls)) groups = Dict{Tuple{Int, Int}, Vector{Tuple{I, C}}}() @@ -153,7 +152,7 @@ function _cluster_svd_compact!(rls::AbstractVector, Fs, alg, alg_b) for (i, c) in small U, S, Vᴴ = Fs[i] MAK.svd_compact!( - block(rls[i], c), (block(U, c), block(S, c), block(Vᴴ, c)), alg_b + block(rls[i], c), (block(U, c), block(S, c), block(Vᴴ, c)), alg ) end end @@ -196,7 +195,7 @@ function _batched_spectra_alg(proto) catch return nothing end - return alg isa Factorizations.BatchedSVDAlgorithm ? alg : nothing + return alg isa Factorizations.AbstractAlgorithm ? alg : nothing end # `calc_convergence` decomposes every corner and every edge of the environment @@ -215,7 +214,7 @@ function _batch_svd_vals!(ts, Ss, items, (m, n), pad::Bool, alg) end o1 = block(Ss[first(items)[1]], first(items)[2]) Sb = similar(o1, min(m, n), length(items)) - MAK.svd_vals!(A, Sb, alg) + MAK.batched_svd_vals!(A, Sb, alg) for (j, (i, c)) in enumerate(items) o = block(Ss[i], c) copyto!(o, view(Sb, axes(o, 1), j)) @@ -295,8 +294,8 @@ function _batched_spectra(ts::AbstractArray{T}) where {T <: AbstractTensorMap} else for (i, c) in small # `svd_vals!` destroys its input, and `block(ts[i], c)` is a view into the live - # environment tensor -- computing the convergence spectra must not damage the - # environment it is measuring, so hand the driver a copy. (The batched branch is + # environment tensor. Computing the convergence spectra must not damage the + # environment it is measuring, so let's hand the driver a copy. (The batched branch is # already safe: `_batch_svd_vals!` packs the blocks into a fresh array.) b = copy(block(ts[i], c)) MAK.svd_vals!(b, block(Ss[i], c), MAK.default_svd_algorithm(typeof(b))) From 5b28bac639050c584c8c819d0ae5d8c5978ee8e1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 15 Sep 2026 07:18:58 -0400 Subject: [PATCH 50/69] Move batching logic into MAK as much as possible --- ext/PEPSKitGPUArraysExt.jl | 167 ++++--------------------- test/cuda/ctmrg/unitcell.jl | 6 +- test/cuda/gradients/ctmrg_gradients.jl | 18 +-- test/rocm/ctmrg/unitcell.jl | 6 +- test/rocm/gradients/ctmrg_gradients.jl | 18 +-- 5 files changed, 40 insertions(+), 175 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 517193017..f1be4f8d2 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -74,8 +74,6 @@ function PEPSKit.free_alloc_caches!(::Type{<:AnyGPUArray}) end -const Factorizations = TensorKit.Factorizations - # Batched truncated SVD of a whole cluster's internal bonds to avoid multiple small kernel launches. function PEPSKit.bond_svds( ::Type{<:AnyGPUArray}, rls::AbstractVector, truncs::AbstractVector @@ -91,7 +89,15 @@ function PEPSKit.bond_svds( MAK.svd_compact!(rl, F, alg) end else - _cluster_svd_compact!(rls, Fs, balg, alg) + # Pool every (bond, sector) block into one ragged batch. MatrixAlgebraKit batches + # blocks of equal size together even across different bonds, since the decomposition + # does not care which bond a block came from, and zero-pads the leftovers. + items = [(i, c) for i in eachindex(rls) for c in blocksectors(rls[i])] + As = [block(rls[i], c) for (i, c) in items] + Us = [block(Fs[i][1], c) for (i, c) in items] + Ss = [TensorKit.diagview(block(Fs[i][2], c)) for (i, c) in items] + Vᴴs = [block(Fs[i][3], c) for (i, c) in items] + MAK.batched_svd_compact!(As, (Us, Ss, Vᴴs), balg) end return map(Fs, truncs) do F, trunc (U, S, Vᴴ) = F @@ -101,9 +107,6 @@ function PEPSKit.bond_svds( end end -# Pool every (bond, sector) block, group by block size, and hand each group to the batched -# driver. Blocks of equal size batch together even across different bonds, since the -# decomposition does not care which bond a block came from. """ CLUSTER_BATCHED_SVD[] @@ -121,68 +124,6 @@ function _cluster_batched_alg(rls::AbstractVector) return nothing end -function _cluster_svd_compact!(rls::AbstractVector, Fs, alg) - I = eltype(eachindex(rls)) - C = sectortype(eltype(rls)) - groups = Dict{Tuple{Int, Int}, Vector{Tuple{I, C}}}() - for i in eachindex(rls), c in blocksectors(rls[i]) - push!(get!(() -> Tuple{I, C}[], groups, size(block(rls[i], c))), (i, c)) - end - lim = Factorizations.max_batched_blocksize(alg, TensorKit.storagetype(eltype(rls))) - tall = Factorizations.batched_requires_tall(alg) - - small = Tuple{I, C}[] - for ((m, n), items) in groups - if length(items) >= BATCH_THRESHOLD && (!tall || m >= n) && max(m, n) <= lim - _batch_svd_compact!(rls, Fs, items, (m, n), false, alg) - else - append!(small, items) - end - end - - # Everything left over goes into one zero-padded batch. A compact decomposition only reads - # back the leading `min(m, n)` columns of each block, and zero padding leaves those - # untouched, so padding is safe here (unlike a full decomposition). - mm = maximum(((i, c),) -> size(block(rls[i], c), 1), small; init = 0) - nn = maximum(((i, c),) -> size(block(rls[i], c), 2), small; init = 0) - padded = tall ? (max(mm, nn), max(mm, nn)) : (mm, nn) - if length(small) >= BATCH_THRESHOLD && maximum(padded) <= lim - _batch_svd_compact!(rls, Fs, small, padded, true, alg) - else - for (i, c) in small - U, S, Vᴴ = Fs[i] - MAK.svd_compact!( - block(rls[i], c), (block(U, c), block(S, c), block(Vᴴ, c)), alg - ) - end - end - return Fs -end - -function _batch_svd_compact!(rls, Fs, items, (m, n), pad::Bool, alg) - i1, c1 = first(items) - nb, minmn = length(items), min(m, n) - A = similar(block(rls[i1], c1), m, n, nb) - pad && fill!(A, zero(eltype(A))) - for (j, (i, c)) in enumerate(items) - b = block(rls[i], c) - copyto!(view(A, axes(b, 1), axes(b, 2), j), b) - end - Ub = similar(A, m, minmn, nb) - Vb = similar(A, minmn, n, nb) - Sb = similar(TensorKit.diagview(block(Fs[i1][2], c1)), minmn, nb) - MAK.svd_compact!(A, (Ub, Sb, Vb), alg) - for (j, (i, c)) in enumerate(items) - U, S, Vᴴ = Fs[i] - u, sv, v = block(U, c), block(S, c), block(Vᴴ, c) - copyto!(u, view(Ub, axes(u, 1), axes(u, 2), j)) - dv = TensorKit.diagview(sv) - copyto!(dv, view(Sb, axes(dv, 1), j)) - copyto!(v, view(Vb, axes(v, 1), axes(v, 2), j)) - end - return nothing -end - """ _batched_spectra_alg(proto) -> alg or nothing @@ -195,31 +136,7 @@ function _batched_spectra_alg(proto) catch return nothing end - return alg isa Factorizations.AbstractAlgorithm ? alg : nothing -end - -# `calc_convergence` decomposes every corner and every edge of the environment -# for regular CTMRG, which is expensive, at *least* 8 separate `svd_vals` calls. -# Across *multiple tensors* the situation is much better than within *one*, -# because the corners have to all share a space, -# so for a given sector their blocks have identical sizes and can be batched with no -# padding at all. -function _batch_svd_vals!(ts, Ss, items, (m, n), pad::Bool, alg) - b1 = block(ts[first(items)[1]], first(items)[2]) - A = similar(b1, m, n, length(items)) - pad && fill!(A, zero(eltype(A))) - for (j, (i, c)) in enumerate(items) - b = block(ts[i], c) - copyto!(view(A, axes(b, 1), axes(b, 2), j), b) - end - o1 = block(Ss[first(items)[1]], first(items)[2]) - Sb = similar(o1, min(m, n), length(items)) - MAK.batched_svd_vals!(A, Sb, alg) - for (j, (i, c)) in enumerate(items) - o = block(Ss[i], c) - copyto!(o, view(Sb, axes(o, 1), j)) - end - return nothing + return alg isa MAK.AbstractAlgorithm ? alg : nothing end # Hook into the collection-level convergence API. Deliberately restricted to the generic @@ -238,19 +155,18 @@ function PEPSKit.edge_spectra( return _batched_spectra(Ts) end +# `calc_convergence` decomposes every corner and every edge of the environment +# for regular CTMRG, which is expensive, at *least* 8 separate `svd_vals` calls. +# Across *multiple tensors* the situation is much better than within *one*, +# because the corners have to all share a space, +# so for a given sector their blocks have identical sizes and batch with no padding at all. function _batched_spectra(ts::AbstractArray{T}) where {T <: AbstractTensorMap} # TODO BAD FIND A BETTER DISPATCH HERE (isempty(ts) || !(TensorKit.storagetype(T) <: AnyGPUArray)) && return map(svd_vals, ts) - proto = nothing - for i in eachindex(ts), c in blocksectors(ts[i]) - proto = block(ts[i], c) - break - end - isnothing(proto) && return map(svd_vals, ts) - alg = _batched_spectra_alg(proto) + items = [(i, c) for i in eachindex(ts) for c in blocksectors(ts[i])] + isempty(items) && return map(svd_vals, ts) + alg = _batched_spectra_alg(block(ts[first(items)[1]], first(items)[2])) isnothing(alg) && return map(svd_vals, ts) - tall = Factorizations.batched_requires_tall(alg) - lim = Factorizations.max_batched_blocksize(alg, TensorKit.storagetype(T)) Ss = map( t -> MAK.initialize_output( @@ -260,47 +176,12 @@ function _batched_spectra(ts::AbstractArray{T}) where {T <: AbstractTensorMap} ) ), ts ) - - # Group by block size because the decomposition doesn't care which sector a block came - # from, so blocks of equal size can batch together even *across* sectors and tensors. - # Each entry records the (tensor index, sector) it came from so the spectrum can be written back. - I = eltype(eachindex(ts)) - C = sectortype(eltype(ts)) - groups = Dict{Tuple{Int, Int}, Vector{Tuple{I, C}}}() - for i in eachindex(ts), c in blocksectors(ts[i]) - push!(get!(() -> Tuple{I, C}[], groups, size(block(ts[i], c))), (i, c)) - end - - # Groups that the solver can't work with, due to too few blocks, wider than - # tall for an algo that needs m >= n, or larger than the solver's block limit, - # join the padded batch below. - small = Tuple{I, C}[] - for ((m, n), items) in groups - if length(items) >= BATCH_THRESHOLD && (!tall || m >= n) && max(m, n) <= lim - _batch_svd_vals!(ts, Ss, items, (m, n), false, alg) - else - append!(small, items) - end - end - - # Groups too small to batch on their own are merged into one padded batch: zero padding - # leaves a block's leading min(m, n) singular values untouched. - mm = maximum(((i, c),) -> size(block(ts[i], c), 1), small; init = 0) - nn = maximum(((i, c),) -> size(block(ts[i], c), 2), small; init = 0) - # pad to a square only when the algo demands m >= n - padded = tall ? (max(mm, nn), max(mm, nn)) : (mm, nn) - if length(small) >= BATCH_THRESHOLD && maximum(padded) <= lim - _batch_svd_vals!(ts, Ss, small, padded, true, alg) - else - for (i, c) in small - # `svd_vals!` destroys its input, and `block(ts[i], c)` is a view into the live - # environment tensor. Computing the convergence spectra must not damage the - # environment it is measuring, so let's hand the driver a copy. (The batched branch is - # already safe: `_batch_svd_vals!` packs the blocks into a fresh array.) - b = copy(block(ts[i], c)) - MAK.svd_vals!(b, block(Ss[i], c), MAK.default_svd_algorithm(typeof(b))) - end - end + # `batched_svd_vals!` destroys the blocks it has to decompose one at a time, but `ts` is the + # live environment, and computing the convergence spectra must not damage the environment + # it is measuring. Copying whole tensors costs one copy per tensor instead of one per block. + ts′ = map(copy, ts) + As = [block(ts′[i], c) for (i, c) in items] + MAK.batched_svd_vals!(As, [block(Ss[i], c) for (i, c) in items], alg) return Ss end diff --git a/test/cuda/ctmrg/unitcell.jl b/test/cuda/ctmrg/unitcell.jl index f7da0eec3..b6004f2a6 100644 --- a/test/cuda/ctmrg/unitcell.jl +++ b/test/cuda/ctmrg/unitcell.jl @@ -32,9 +32,9 @@ function test_unitcell( Pspaces, [ (c,) => randn( - scalartype(peps), - Pspaces[c], Pspaces[c], - ) for c in CartesianIndices(unitcell) + scalartype(peps), + Pspaces[c], Pspaces[c], + ) for c in CartesianIndices(unitcell) ]..., ) ) diff --git a/test/cuda/gradients/ctmrg_gradients.jl b/test/cuda/gradients/ctmrg_gradients.jl index abad25f34..1ce8f2f28 100644 --- a/test/cuda/gradients/ctmrg_gradients.jl +++ b/test/cuda/gradients/ctmrg_gradients.jl @@ -39,8 +39,9 @@ naive_gradient_combinations = [ ] naive_gradient_done = Set() -# fixed-point differentiation is incompatible with sequential CTMRG -function _check_disallowed_combination( +# fixed-point gradients with sequential CTMRG are covered by the CPU test, +# so skip them here since GPU gradients are slow +function _skip_combination( ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg ) ctmrg_alg == :SequentialCTMRG && !isnothing(gradient_alg) && return true @@ -68,17 +69,8 @@ end calgs, palgs, salgs, galgs, gsalgs ) - # filter disallowed algorithm combinations - if _check_disallowed_combination( - ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg - ) - # but verify that its use would throw an error - @test_throws ArgumentError PEPSOptimize(; - boundary_alg = (; alg = ctmrg_alg, projector_alg, decomposition_alg = (; rrule_alg = (; alg = svd_rrule_alg))), - gradient_alg = (; alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol)), - ) - continue - end + # skip slow combinations that the CPU test already covers + _skip_combination(ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg) && continue # check for allowed algorithm combinations when testing naive gradient if isnothing(gradient_alg) diff --git a/test/rocm/ctmrg/unitcell.jl b/test/rocm/ctmrg/unitcell.jl index 2880aae82..9ea614f1e 100644 --- a/test/rocm/ctmrg/unitcell.jl +++ b/test/rocm/ctmrg/unitcell.jl @@ -32,9 +32,9 @@ function test_unitcell( Pspaces, [ (c,) => randn( - scalartype(peps), - Pspaces[c], Pspaces[c], - ) for c in CartesianIndices(unitcell) + scalartype(peps), + Pspaces[c], Pspaces[c], + ) for c in CartesianIndices(unitcell) ]..., ) ) diff --git a/test/rocm/gradients/ctmrg_gradients.jl b/test/rocm/gradients/ctmrg_gradients.jl index 3e1744ae1..d7add28b1 100644 --- a/test/rocm/gradients/ctmrg_gradients.jl +++ b/test/rocm/gradients/ctmrg_gradients.jl @@ -39,8 +39,9 @@ naive_gradient_combinations = [ ] naive_gradient_done = Set() -# fixed-point differentiation is incompatible with sequential CTMRG -function _check_disallowed_combination( +# fixed-point gradients with sequential CTMRG are covered by the CPU test, +# so skip them here since GPU gradients are slow +function _skip_combination( ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg ) ctmrg_alg == :SequentialCTMRG && !isnothing(gradient_alg) && return true @@ -68,17 +69,8 @@ end calgs, palgs, salgs, galgs, gsalgs ) - # filter disallowed algorithm combinations - if _check_disallowed_combination( - ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg - ) - # but verify that its use would throw an error - @test_throws ArgumentError PEPSOptimize(; - boundary_alg = (; alg = ctmrg_alg, projector_alg, decomposition_alg = (; rrule_alg = (; alg = svd_rrule_alg))), - gradient_alg = (; alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol)), - ) - continue - end + # skip slow combinations that the CPU test already covers + _skip_combination(ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg) && continue # check for allowed algorithm combinations when testing naive gradient if isnothing(gradient_alg) From 18843392a1fc0453d753167a18efd0a3f262a279 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 14 Sep 2026 16:25:40 +0200 Subject: [PATCH 51/69] Incremental trimming of duplicated tests --- .buildkite/pipeline.yml | 2 +- test/cuda/bondenv/benv_ctm.jl | 71 ----- test/cuda/bondenv/benv_gaugefix.jl | 44 --- test/cuda/bondenv/bond_truncate.jl | 54 ---- test/cuda/boundarymps/vumps.jl | 137 --------- test/cuda/bp/expvals.jl | 55 ---- test/cuda/bp/gaugefix.jl | 92 ------ test/cuda/bp/rotation.jl | 46 --- test/cuda/bp/unitcell.jl | 102 ------- test/cuda/compress/local.jl | 62 ---- test/cuda/ctmrg/contractions.jl | 315 --------------------- test/cuda/ctmrg/fixed_iterscheme.jl | 108 ------- test/cuda/ctmrg/flavors.jl | 91 ------ test/cuda/ctmrg/gaugefix.jl | 101 ------- test/cuda/ctmrg/initialization.jl | 97 ------- test/cuda/ctmrg/jacobian_real_linear.jl | 50 ---- test/cuda/ctmrg/partition_function.jl | 158 ----------- test/cuda/ctmrg/pepo.jl | 154 ---------- test/cuda/ctmrg/suweight.jl | 56 ---- test/cuda/ctmrg/unitcell.jl | 125 -------- test/cuda/gradients/c4v_ctmrg_gradients.jl | 131 --------- test/cuda/gradients/ctmrg_gradients.jl | 171 ----------- test/cuda/timeevol/cluster_projectors.jl | 265 ----------------- test/cuda/timeevol/j1j2_finiteT.jl | 69 ----- test/cuda/timeevol/sitedep_truncation.jl | 65 ----- test/cuda/timeevol/tf_ising_finiteT.jl | 114 -------- test/cuda/timeevol/timestep.jl | 34 --- test/cuda/toolbox/densitymatrices.jl | 79 ------ test/cuda/utility/correlator.jl | 93 ------ test/cuda/utility/eigh_wrapper.jl | 147 ---------- test/cuda/utility/retractions.jl | 34 --- test/cuda/utility/svd_wrapper.jl | 189 ------------- test/cuda/utility/symmetrization.jl | 53 ---- test/rocm/bondenv/benv_ctm.jl | 68 ----- test/rocm/bondenv/benv_gaugefix.jl | 44 --- test/rocm/bondenv/bond_truncate.jl | 54 ---- test/rocm/boundarymps/vumps.jl | 129 --------- test/rocm/bp/expvals.jl | 55 ---- test/rocm/bp/rotation.jl | 46 --- test/rocm/bp/unitcell.jl | 102 ------- test/rocm/compress/local.jl | 62 ---- test/rocm/ctmrg/contractions.jl | 315 --------------------- test/rocm/ctmrg/fixed_iterscheme.jl | 108 ------- test/rocm/ctmrg/flavors.jl | 88 ------ test/rocm/ctmrg/gaugefix.jl | 99 ------- test/rocm/ctmrg/initialization.jl | 94 ------ test/rocm/ctmrg/jacobian_real_linear.jl | 50 ---- test/rocm/ctmrg/partition_function.jl | 152 ---------- test/rocm/ctmrg/pepo.jl | 148 ---------- test/rocm/ctmrg/suweight.jl | 56 ---- test/rocm/ctmrg/unitcell.jl | 125 -------- test/rocm/gradients/c4v_ctmrg_gradients.jl | 131 --------- test/rocm/gradients/ctmrg_gradients.jl | 167 ----------- test/rocm/timeevol/cluster_projectors.jl | 258 ----------------- test/rocm/timeevol/j1j2_finiteT.jl | 64 ----- test/rocm/timeevol/sitedep_truncation.jl | 64 ----- test/rocm/timeevol/tf_ising_finiteT.jl | 88 ------ test/rocm/timeevol/timestep.jl | 35 --- test/rocm/toolbox/densitymatrices.jl | 79 ------ test/rocm/utility/correlator.jl | 93 ------ test/rocm/utility/eigh_wrapper.jl | 147 ---------- test/rocm/utility/retractions.jl | 34 --- test/rocm/utility/svd_wrapper.jl | 189 ------------- test/rocm/utility/symmetrization.jl | 53 ---- test/runtests.jl | 12 - test/testsuite/ctmrg/unitcell.jl | 10 +- 66 files changed, 6 insertions(+), 6577 deletions(-) delete mode 100644 test/cuda/bondenv/benv_ctm.jl delete mode 100644 test/cuda/bondenv/benv_gaugefix.jl delete mode 100644 test/cuda/bondenv/bond_truncate.jl delete mode 100644 test/cuda/boundarymps/vumps.jl delete mode 100644 test/cuda/bp/expvals.jl delete mode 100644 test/cuda/bp/gaugefix.jl delete mode 100644 test/cuda/bp/rotation.jl delete mode 100644 test/cuda/bp/unitcell.jl delete mode 100644 test/cuda/compress/local.jl delete mode 100644 test/cuda/ctmrg/contractions.jl delete mode 100644 test/cuda/ctmrg/fixed_iterscheme.jl delete mode 100644 test/cuda/ctmrg/flavors.jl delete mode 100644 test/cuda/ctmrg/gaugefix.jl delete mode 100644 test/cuda/ctmrg/initialization.jl delete mode 100644 test/cuda/ctmrg/jacobian_real_linear.jl delete mode 100644 test/cuda/ctmrg/partition_function.jl delete mode 100644 test/cuda/ctmrg/pepo.jl delete mode 100644 test/cuda/ctmrg/suweight.jl delete mode 100644 test/cuda/ctmrg/unitcell.jl delete mode 100644 test/cuda/gradients/c4v_ctmrg_gradients.jl delete mode 100644 test/cuda/gradients/ctmrg_gradients.jl delete mode 100644 test/cuda/timeevol/cluster_projectors.jl delete mode 100644 test/cuda/timeevol/j1j2_finiteT.jl delete mode 100644 test/cuda/timeevol/sitedep_truncation.jl delete mode 100644 test/cuda/timeevol/tf_ising_finiteT.jl delete mode 100644 test/cuda/timeevol/timestep.jl delete mode 100644 test/cuda/toolbox/densitymatrices.jl delete mode 100644 test/cuda/utility/correlator.jl delete mode 100644 test/cuda/utility/eigh_wrapper.jl delete mode 100644 test/cuda/utility/retractions.jl delete mode 100644 test/cuda/utility/svd_wrapper.jl delete mode 100644 test/cuda/utility/symmetrization.jl delete mode 100644 test/rocm/bondenv/benv_ctm.jl delete mode 100644 test/rocm/bondenv/benv_gaugefix.jl delete mode 100644 test/rocm/bondenv/bond_truncate.jl delete mode 100644 test/rocm/boundarymps/vumps.jl delete mode 100644 test/rocm/bp/expvals.jl delete mode 100644 test/rocm/bp/rotation.jl delete mode 100644 test/rocm/bp/unitcell.jl delete mode 100644 test/rocm/compress/local.jl delete mode 100644 test/rocm/ctmrg/contractions.jl delete mode 100644 test/rocm/ctmrg/fixed_iterscheme.jl delete mode 100644 test/rocm/ctmrg/flavors.jl delete mode 100644 test/rocm/ctmrg/gaugefix.jl delete mode 100644 test/rocm/ctmrg/initialization.jl delete mode 100644 test/rocm/ctmrg/jacobian_real_linear.jl delete mode 100644 test/rocm/ctmrg/partition_function.jl delete mode 100644 test/rocm/ctmrg/pepo.jl delete mode 100644 test/rocm/ctmrg/suweight.jl delete mode 100644 test/rocm/ctmrg/unitcell.jl delete mode 100644 test/rocm/gradients/c4v_ctmrg_gradients.jl delete mode 100644 test/rocm/gradients/ctmrg_gradients.jl delete mode 100644 test/rocm/timeevol/cluster_projectors.jl delete mode 100644 test/rocm/timeevol/j1j2_finiteT.jl delete mode 100644 test/rocm/timeevol/sitedep_truncation.jl delete mode 100644 test/rocm/timeevol/tf_ising_finiteT.jl delete mode 100644 test/rocm/timeevol/timestep.jl delete mode 100644 test/rocm/toolbox/densitymatrices.jl delete mode 100644 test/rocm/utility/correlator.jl delete mode 100644 test/rocm/utility/eigh_wrapper.jl delete mode 100644 test/rocm/utility/retractions.jl delete mode 100644 test/rocm/utility/svd_wrapper.jl delete mode 100644 test/rocm/utility/symmetrization.jl diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 9323d280d..04b807804 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -7,7 +7,7 @@ steps: - JuliaCI/julia#v1: version: "{{matrix.julia}}" - JuliaCI/julia-test#v1: - test_args: "{{matrix.queue}}/{{matrix.group}}" + test_args: "{{matrix.group}}" - JuliaCI/julia-coverage#v1: dirs: - src diff --git a/test/cuda/bondenv/benv_ctm.jl b/test/cuda/bondenv/benv_ctm.jl deleted file mode 100644 index b9c22b169..000000000 --- a/test/cuda/bondenv/benv_ctm.jl +++ /dev/null @@ -1,71 +0,0 @@ -using Test -using TensorKit -using PEPSKit -using LinearAlgebra -using Random -using CUDA, Adapt - -Random.seed!(100) -Nr, Nc = 2, 2 -Envspace = Vect[FermionParity ⊠ U1Irrep]( - (0, 0) => 4, (1, 1 // 2) => 1, (1, -1 // 2) => 1, (0, 1) => 1, (0, -1) => 1 -) -trunc_state = truncerror(; atol = 1.0e-10) & truncrank(4) -ctm_alg = SequentialCTMRG(; - tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(8), - decomposition_alg = (; alg = :SVDViaPolar), -) -# create Hubbard iPEPS using simple update -function get_hubbard_peps(t::Float64 = 1.0, U::Float64 = 8.0) - H = adapt(CuArray, hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)) - Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) - peps = adapt(CuArray, InfinitePEPS(rand, ComplexF64, Vphy, Vphy; unitcell = (Nr, Nc))) - wts = SUWeight(peps) - alg = SimpleUpdate(; trunc = trunc_state) - evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts) - peps, = time_evolve(evolver, H; tol = 1.0e-8, verbosity = 1, check_interval = 2000) - normalize!.(peps.A, Inf) - return peps -end - -function get_hubbard_pepo(t::Float64 = 1.0, U::Float64 = 8.0) - H = adapt(CuArray, hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)) - pepo = PEPSKit.infinite_temperature_density_matrix(H) - wts = SUWeight(pepo) - alg = SimpleUpdate(; trunc = trunc_state, bipartite = false) - pepo, = time_evolve(pepo, H, 2.0e-3, 500, alg, wts; verbosity = 1, check_interval = 100) - normalize!.(pepo.A, Inf) - return pepo -end - -function test_benv_ctm(state::Union{InfinitePEPS, InfinitePEPO}) - network = isa(state, InfinitePEPS) ? state : InfinitePEPS(state) - env, = leading_boundary(CTMRGEnv(rand, ComplexF64, network, Envspace), network, ctm_alg) - for row in 1:Nr, col in 1:Nc - cp1 = col + 1 - A, B = state[row, col], state[row, cp1] - a, X = PEPSKit.bond_tensor_first(A) - b, Y = PEPSKit.bond_tensor_last(B) - benv = PEPSKit.bondenv_ctm(row, col, X, Y, env) - Z = PEPSKit.positive_approx(benv) - # verify that gauge fixing can greatly reduce - # condition number for physical state bond envs - cond1 = cond(Z' * Z) - Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) - benv2 = Z2' * Z2 - cond2 = cond(benv2) - @test 1 <= cond2 < cond1 - @info "benv cond number: (gauge-fixed) $(cond2) ≤ $(cond1) (initial)" - # verify gauge fixing is done correctly - @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] - @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] - @test half ≈ half2 - end - return -end - -peps = get_hubbard_peps() -pepo = get_hubbard_pepo() -for state in (peps, pepo) - test_benv_ctm(state) -end diff --git a/test/cuda/bondenv/benv_gaugefix.jl b/test/cuda/bondenv/benv_gaugefix.jl deleted file mode 100644 index bb78584be..000000000 --- a/test/cuda/bondenv/benv_gaugefix.jl +++ /dev/null @@ -1,44 +0,0 @@ -using Test -using TensorKit -using PEPSKit -using LinearAlgebra -using Random -using CUDA, Adapt - -Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 1, (1, -1) => 2) -Vin = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 3, (1, -1) => 2) -V = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 2, (1, -1) => 3) -Vs = (V, V') -for V1 in Vs, V2 in Vs, V3 in Vs - #= - ┌---┬---------------┬---┐ - | | | | ┌--------------┐ - ├---X--- -2 -3 ---Y---┤ = | | - | | | | └--Z-- -2 -3 -┘ - └---┴-------Z0------┴---┘ ↓ - ↓ -1 - -1 - =# - X = adapt(CuArray, rand(ComplexF64, Vin ⊗ V1' ⊗ Vin' ⊗ Vin)) - Y = adapt(CuArray, rand(ComplexF64, Vin ⊗ Vin ⊗ Vin' ⊗ V3)) - Z0 = adapt(CuArray, randn(ComplexF64, Vphy ← Vin ⊗ Vin' ⊗ Vin ⊗ Vin ⊗ Vin ⊗ Vin')) - @tensor Z[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X[Xn Xe Xs Xw] * Y[Yn Ye Ys Yw] - #= - ┌---------------------------┐ - | | - └---Z-- 1 --a-- 2 --b-- 3 --┘ - ↓ ↓ ↓ - -1 -2 -3 - =# - a = adapt(CuArray, randn(ComplexF64, V1 ⊗ Vphy ← V2)) - b = adapt(CuArray, randn(ComplexF64, V2 ⊗ Vphy ← V3)) - @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] - Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) - @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] - @test half ≈ half2 - # test gauge transformation of X, Y - X2 = PEPSKit._fixgauge_benvX(X, Rinv) - Y2 = PEPSKit._fixgauge_benvY(Y, Linv) - @tensor Z2_[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X2[Xn Xe Xs Xw] * Y2[Yn Ye Ys Yw] - @test Z2 ≈ Z2_ -end diff --git a/test/cuda/bondenv/bond_truncate.jl b/test/cuda/bondenv/bond_truncate.jl deleted file mode 100644 index 9a78d44fe..000000000 --- a/test/cuda/bondenv/bond_truncate.jl +++ /dev/null @@ -1,54 +0,0 @@ -using Random -using Test -using TensorKit -using PEPSKit -using LinearAlgebra -using PEPSKit: bond_truncate, cost_function_als -using PEPSKit: _combine_ket, _combine_ket_for_svd -using CUDA, Adapt - -Random.seed!(0) -maxiter = 600 -check_interval = 30 -elt = ComplexF64 -# simulating the situation of applying a 2-site gate -# to a bond with virtual dimension D, physical dimension d. -d, D = 2, 4 -trunc = truncerror(; atol = 1.0e-10) & truncrank(D) -Vphy = Vect[FermionParity](0 => div(d, 2), 1 => div(d, 2)) -Vqro = Vect[FermionParity](0 => div(d * D, 2), 1 => div(d * D, 2)) -# virtual dimension of gate MPO is d^2 -Vint = Vect[FermionParity](0 => div(d^2 * D, 2), 1 => div(d^2 * D, 2)) -for Vl in (Vqro, Vqro'), Vr in (Vqro, Vqro') - # random positive-definite environment - Vbond = Vl ⊗ Vr - Dext = dim(Vbond) - Vext = Vect[FermionParity](0 => div(Dext, 2) + 1, 1 => div(Dext, 2) + 1) - Z = adapt(CuArray, randn(elt, Vext ← Vbond)) - normalize!(Z, Inf) - benv = Z' * Z - @info "Dimension of benv = $(Dext)" - # untruncated bond tensors - a2 = adapt(CuArray, randn(elt, Vl ⊗ Vphy ← Vint)) - b2 = adapt(CuArray, randn(elt, Vint ⊗ Vphy ← Vr')) - # bond tensor (truncated SVD initialization) - a2b2 = _combine_ket(a2, b2) - a0, s, b0 = svd_trunc(permute(a2b2, ((1, 3), (4, 2))); trunc = trunc) - a0, b0 = PEPSKit.absorb_s(a0, s, b0) - b0 = permute(b0, ((1, 2), (3,))) - fid0 = cost_function_als(benv, _combine_ket(a0, b0), a2b2)[2] - @info "Fidelity of simple SVD truncation = $fid0.\n" - ss = Dict{String, DiagonalTensorMap}() - # FET is slower when d is large - for (label, alg) in ( - ("ALS", ALSTruncation(; trunc, maxiter, check_interval)), - ("FET", FullEnvTruncation(; trunc, maxiter, check_interval, trunc_init = false)), - ) - a1, ss[label], b1, info = bond_truncate(a2, b2, benv, alg) - @info "$label improved fidelity = $(info.fid)." - # display(ss[label]) - @test info.fid ≈ cost_function_als(benv, _combine_ket(a1, b1), a2b2)[2] - @test info.fid > fid0 - end - @test isapprox(ss["ALS"], ss["FET"], atol = 1.0e-3) -end diff --git a/test/cuda/boundarymps/vumps.jl b/test/cuda/boundarymps/vumps.jl deleted file mode 100644 index 5503fec89..000000000 --- a/test/cuda/boundarymps/vumps.jl +++ /dev/null @@ -1,137 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using MPSKit -using LinearAlgebra -using Adapt, CUDA - -Random.seed!(29384293742893) - -const vumps_alg = VUMPS(; - tol = 1.0e-6, alg_eigsolve = MPSKit.Defaults.alg_eigsolve(; ishermitian = false), verbosity = 2 -) - -@testset "(1, 1) PEPS" begin - Vpeps = ComplexSpace(2) - psi = adapt(CuArray, InfinitePEPS(Vpeps, Vpeps)) - - T = adapt(CuArray, PEPSKit.InfiniteTransferPEPS(psi, 1, 1)) - foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) - mps = adapt(CuArray, initialize_mps(T, [ComplexSpace(20)])) - - mps, env, ϵ = leading_boundary(mps, T, vumps_alg) - N = abs(sum(expectation_value(mps, T))) - - mps2, = changebonds(mps, T, OptimalExpand(; trunc = truncrank(30))) - mps2, env2, ϵ = leading_boundary(mps2, T, vumps_alg) - N2 = abs(sum(expectation_value(mps2, T))) - @test N ≈ N2 rtol = 1.0e-2 - - ctm, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(20)), psi; decomposition_alg = (; alg = :SVDViaPolar) - ) - N´ = abs(norm(psi, ctm)) - - @test N ≈ N´ atol = 1.0e-3 -end - -@testset "(2, 2) PEPS" begin - Vpeps = ComplexSpace(2) - psi = adapt(CuArray, InfinitePEPS(Vpeps, Vpeps; unitcell = (2, 2))) - T = adapt(CuArray, PEPSKit.MultilineTransferPEPS(psi, 1)) - @test storagetype(T) <: CuArray - # foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) # TODO: MPSKit.physicalspace(::MultilineMPO) isn't implemented... - mps = adapt(CuArray, initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2))) - @test storagetype(mps) <: CuArray - mps, env, ϵ = leading_boundary(mps, T, vumps_alg) - N = abs(prod(expectation_value(mps, T))) - - ctm, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(20)), psi; decomposition_alg = (; alg = :SVDViaPolar) - ) - N´ = abs(norm(psi, ctm)) - - @test N ≈ N´ rtol = 1.0e-2 -end - -#=@testset "Fermionic PEPS" begin - D = Vect[fℤ₂](0 => 1, 1 => 1) - d = Vect[fℤ₂](0 => 1, 1 => 1) - χ = Vect[fℤ₂](0 => 10, 1 => 10) - - psi = adapt(CuArray, InfinitePEPS(D, d; unitcell = (1, 1))) - n = adapt(CuArray, InfiniteSquareNetwork(psi)) - T = adapt(CuArray, InfiniteTransferPEPS(psi, 1, 1)) - foreach(V -> (@test V == D ⊗ D'), physicalspace(T)) - - # compare boundary MPS contraction to CTMRG contraction - mps = adapt(CuArray, initialize_mps(T, [χ])) - mps, env, ϵ = leading_boundary(mps, T, vumps_alg) - N_vumps = abs(prod(expectation_value(mps, T))) - - ctm, = leading_boundary( - CTMRGEnv(psi, χ), psi; decomposition_alg = (; alg = :SVDViaPolar) - ) - N_ctm = abs(norm(psi, ctm)) - - @test N_vumps ≈ N_ctm rtol = 1.0e-2 - - # and again after blocking the local sandwiches - n´ = adapt(CuArray, InfiniteSquareNetwork(map(PEPSKit.mpotensor, PEPSKit.unitcell(n)))) - T´ = adapt(CuArray, InfiniteMPO(map(PEPSKit.mpotensor, T.O))) - foreach(V -> (@test V == fuse(D, D')), physicalspace(T´)) - - mps´ = adapt(CuArray, InfiniteMPS(randn, ComplexF64, [physicalspace(T´, 1)], [χ])) - mps´, env´, ϵ = leading_boundary(mps´, T´, vumps_alg) - N_vumps´ = abs(prod(expectation_value(mps´, T´))) - - ctm´, = leading_boundary( - CTMRGEnv(n´, χ), n´; decomposition_alg = (; alg = :SVDViaPolar) - ) - N_ctm´ = abs(network_value(n´, ctm´)) - - @show N_vumps´ - @test N_vumps´ ≈ N_vumps rtol = 1.0e-2 - @test N_vumps´ ≈ N_ctm´ rtol = 1.0e-2 -end=# - -@testset "PEPO runthrough" begin - function ising_pepo(beta; unitcell = (1, 1, 1)) - t = ComplexF64[exp(beta) exp(-beta); exp(-beta) exp(beta)] - q = sqrt(t) - - O = zeros(2, 2, 2, 2, 2, 2) - O[1, 1, 1, 1, 1, 1] = 1 - O[2, 2, 2, 2, 2, 2] = 1 - @tensor o[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - - O = TensorMap(o, ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)') - - return adapt(CuArray, InfinitePEPO(O; unitcell)) - end - - Vpepo = ComplexSpace(2) - Vpeps = ComplexSpace(2) - - # single-layer PEPO - O = ising_pepo(1) - psi = adapt(CuArray, PEPSKit.initializePEPS(O, Vpeps)) - T = adapt(CuArray, InfiniteTransferPEPO(psi, O, 1, 1)) - foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpeps'), physicalspace(T)) - - mps = adapt(CuArray, initialize_mps(rand, scalartype(T), T, [ComplexSpace(10)])) - mps, env, ϵ = leading_boundary(mps, T, vumps_alg) - f = abs(prod(expectation_value(mps, T))) - - # double-layer PEPO - O2 = repeat(O, 1, 1, 2) - psi2 = adapt(CuArray, initializePEPS(O2, Vpeps)) - T2 = adapt(CuArray, InfiniteTransferPEPO(psi, O2, 1, 1)) - foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpepo ⊗ Vpeps'), physicalspace(T2)) - - mps2 = adapt(CuArray, initialize_mps(rand, scalartype(T2), T2, [ComplexSpace(8)])) - mps2, env2, ϵ = leading_boundary(mps2, T2, vumps_alg) - f = abs(prod(expectation_value(mps2, T2))) -end diff --git a/test/cuda/bp/expvals.jl b/test/cuda/bp/expvals.jl deleted file mode 100644 index fe30cb409..000000000 --- a/test/cuda/bp/expvals.jl +++ /dev/null @@ -1,55 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using PEPSKit: random_dual! -using CUDA, Adapt - -ds = Dict( - U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), - FermionParity => Vect[FermionParity](0 => 2, 1 => 1) -) -Ds = Dict( - U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 3, 2))), - FermionParity => Vect[FermionParity](0 => 3, 1 => 2) -) -Random.seed!(41973582) - -@testset "Expectation values of BPEnv ($S)" for S in keys(ds) - d, D, uc = ds[S], Ds[S], (2, 3) - ψds = fill(d, uc) - ψDNs = random_dual!(fill(D, uc)) - ψDEs = random_dual!(fill(D, uc)) - ψ0 = adapt(CuArray, InfinitePEPS(ψds, ψDNs, ψDEs)) - - ψ, wts, _ = gauge_fix(ψ0, SUGauge(; maxiter = 100, tol = 1.0e-10)) - for (a0, a) in zip(ψ0.A, ψ.A) - @test space(a0) == space(a) - end - bp_env = BPEnv(wts) - ctm_env = CTMRGEnv(wts) - @test ctm_env ≈ CTMRGEnv(bp_env) - - # SU fixed point wts should already be a BP fixed point of ψ - bp_alg = BeliefPropagation(; miniter = 1, maxiter = 1, tol = 1.0e-7) - _, err = leading_boundary(bp_env, ψ, bp_alg) - @test err < 1.0e-9 - - op = randn(d → d) - for site in CartesianIndices(size(ψ)) - lo = adapt(CuArray, LocalOperator(ψds, (site,) => op)) - val1 = expectation_value(ψ, lo, bp_env) - val2 = expectation_value(ψ, lo, ctm_env) - @test val1 ≈ val2 - end - - op = randn(d ⊗ d → d ⊗ d) - vs = [CartesianIndex(1, 0), CartesianIndex(0, 1)] - for site1 in CartesianIndices(size(ψ)), v in vs - site2 = site1 + v - lo = adapt(CuArray, LocalOperator(ψds, (site1, site2) => op)) - val1 = expectation_value(ψ, lo, bp_env) - val2 = expectation_value(ψ, lo, ctm_env) - @test val1 ≈ val2 - end -end diff --git a/test/cuda/bp/gaugefix.jl b/test/cuda/bp/gaugefix.jl deleted file mode 100644 index 13de9ea6b..000000000 --- a/test/cuda/bp/gaugefix.jl +++ /dev/null @@ -1,92 +0,0 @@ -using Test, TestExtras -using Random -using TensorKit -using PEPSKit -using PEPSKit: compare_weights, random_dual!, twistdual -using PEPSKit: _next, _is_bipartite -using CUDA, Adapt - -@testset "BP vs SU ($S, bipartite = $(bipartite), posdef msgs = $h)" for - (S, bipartite, h) in Iterators.product( - [U1Irrep, FermionParity], [true, false], [true, false] - ) - unitcell = bipartite ? (2, 2) : (2, 3) - elt = ComplexF64 - maxiter, tol = 100, 1.0e-9 - Random.seed!(52840679) - Pspaces, Nspaces, Espaces = if S == U1Irrep - map(rand(1:2, unitcell), rand(1:2, unitcell), rand(1:2, unitcell)) do d0, d1, d2 - Vect[S](0 => d0, 1 => d1, -1 => d2) - end, - map(rand(2:4, unitcell), rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1, d2 - Vect[S](0 => d0, 1 => d1, -1 => d2) - end, - map(rand(2:4, unitcell), rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1, d2 - Vect[S](0 => d0, 1 => d1, -1 => d2) - end - else - map(rand(2:3, unitcell), rand(2:3, unitcell)) do d0, d1 - Vect[S](0 => d0, 1 => d1) - end, - map(rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1 - Vect[S](0 => d0, 1 => d1) - end, - map(rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1 - Vect[S](0 => d0, 1 => d1) - end - end - Nspaces, Espaces = random_dual!(Nspaces), random_dual!(Espaces) - if bipartite - for c in 1:2 - cp1 = _next(c, 2) - Pspaces[2, c] = Pspaces[1, cp1] - Nspaces[2, c] = Nspaces[1, cp1] - Espaces[2, c] = Espaces[1, cp1] - end - end - peps0 = adapt(CuArray, InfinitePEPS(randn, elt, Pspaces, Nspaces, Espaces)) - if bipartite - for c in 1:2 - peps0[2, c] = copy(peps0[1, c + 1]) - end - end - - # start by gauging with SU - peps1, wts1 = gauge_fix(peps0, SUGauge(; maxiter, tol)) - for (a0, a1) in zip(peps0.A, peps1.A) - @test space(a0) == space(a1) - end - if bipartite - @test _is_bipartite(peps1) - @test _is_bipartite(wts1) - end - normalize!.(wts1.data) - - # find BP fixed point and SUWeight - bp_alg = BeliefPropagation(; maxiter, tol, bipartite, project_hermitian = h) - env = BPEnv(randn, elt, peps1; posdef = h) - @test storagetype(env) <: CuArray - env, err = leading_boundary(env, peps1, bp_alg) - if bipartite - @test _is_bipartite(env) - end - wts2 = SUWeight(env) - normalize!.(wts2.data) - @test compare_weights(wts1, wts2) < 1.0e-9 - - bpg_alg = BPGauge() - peps2, XXinv = @constinferred gauge_fix(peps1, bpg_alg, env) - if bipartite - @test _is_bipartite(peps2) - end - for (a1, a2) in zip(peps1.A, peps2.A) - @test space(a1) == space(a2) - end - for (X, Xinv) in XXinv - # X, Xinv should contract to identity - @tensor tmp[-1; -2] := X[-1; 1] * Xinv[1; -2] - @test tmp ≈ twistdual(TensorKit.id(storagetype(peps1), space(X, 1)), 1) - # BP should differ from SU only by a unitary gauge transformation - @test inv(X) ≈ adjoint(X) ≈ Xinv - end -end diff --git a/test/cuda/bp/rotation.jl b/test/cuda/bp/rotation.jl deleted file mode 100644 index e3aa0d39e..000000000 --- a/test/cuda/bp/rotation.jl +++ /dev/null @@ -1,46 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using PEPSKit: random_dual! -using CUDA, Adapt - -ds = Dict( - Trivial => ℂ^2, - U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), - FermionParity => Vect[FermionParity](0 => 2, 1 => 1) -) -Ds = Dict( - Trivial => ℂ^3, - U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 3, 2))), - FermionParity => Vect[FermionParity](0 => 3, 1 => 2) -) -Random.seed!(41973582) - -function meas_sites( - op::O, ψ::InfinitePEPS, env::Union{BPEnv, CTMRGEnv} - ) where {O <: AbstractTensorMap{<:Any, <:Any, 1, 1}} - lattice = physicalspace(ψ) - return map(eachindex(ψ)) do site1 - lo = LocalOperator(lattice, (site1,) => op) - return expectation_value(ψ, lo, env) - end -end - -@testset "Rotation of BPEnv ($S)" for S in keys(ds) - d, D, unitcell = ds[S], Ds[S], (2, 3) - ψds = fill(d, unitcell) - ψDNs = random_dual!(fill(D, unitcell)) - ψDEs = random_dual!(fill(D, unitcell)) - ψ = adapt(CuArray, InfinitePEPS(ψds, ψDNs, ψDEs)) - env = BPEnv(ψ) - - op = adapt(CuArray, randn(d → d)) - meas1 = meas_sites(op, ψ, env) - # rotated peps and env - for f in (rotl90, rotr90, rot180) - ψ′, env′ = f(ψ), f(env) - meas1′ = meas_sites(op, ψ′, env′) - @test meas1′ ≈ f(meas1) - end -end diff --git a/test/cuda/bp/unitcell.jl b/test/cuda/bp/unitcell.jl deleted file mode 100644 index cb5105adf..000000000 --- a/test/cuda/bp/unitcell.jl +++ /dev/null @@ -1,102 +0,0 @@ -using Test -using Random -using PEPSKit -using PEPSKit: bp_iteration -using TensorKit -using CUDA, Adapt - -# settings -Random.seed!(91283219347) -elt = ComplexF64 - -function test_unitcell(unitcell, Pspaces, Nspaces, Espaces) - peps = adapt(CuArray, InfinitePEPS(randn, elt, Pspaces, Nspaces, Espaces)) - env0 = BPEnv(ones, elt, peps) - alg = BeliefPropagation() - - # apply one BP iteration - network = InfiniteSquareNetwork(peps) - env1 = bp_iteration(network, env0, alg) - # another iteration to detect bond mismatches - env1 = bp_iteration(network, env1, alg) - - # compute random expecation value to test matching bonds - random_op = adapt( - CuArray, LocalOperator( - Pspaces, ( - (c,) => randn(elt, Pspaces[c], Pspaces[c]) - for c in CartesianIndices(unitcell) - )..., - ) - ) - @test storagetype(random_op) <: CuArray - @test expectation_value(peps, random_op, env0) isa Number - @test expectation_value(peps, random_op, env1) isa Number - return -end - -@testset "Random Cartesian spaces with BP" begin - unitcell = (3, 3) - - Pspaces = ComplexSpace.(rand(2:3, unitcell...)) - Nspaces = ComplexSpace.(rand(2:4, unitcell...)) - Espaces = ComplexSpace.(rand(2:4, unitcell...)) - - test_unitcell(unitcell, Pspaces, Nspaces, Espaces) -end - -@testset "Specific U1 spaces with BP" begin - unitcell = (2, 2) - - PA = U1Space(-1 => 1, 0 => 1) - PB = U1Space(0 => 1, 1 => 1) - Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) - Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) - - Pspaces = [PA PB; PB PA] - Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] - - test_unitcell(unitcell, Pspaces, Nspaces, Nspaces) - - # 4x4 unit cell with all 32 inequivalent bonds - # - # 10 4 7 32 - # | | | | - # 3--A--1--B--5--C--8--D--3 - # | | | | - # 2 6 9 11 - # | | | | - # 14--E-12--F-15--G-17--H-14 - # | | | | - # 13 16 18 19 - # | | | | - # 22--I-20--J-23--K-25--L-22 - # | | | | - # 21 24 26 27 - # | | | | - # 29--M-28--N-30--O-31--P-29 - # | | | | - # 10 4 7 32 - - phys_space = Vect[U1Irrep](1 => 1, -1 => 1) - corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) - vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) - @test length(Set(vspaces)) == 32 - - Espaces = [ - vspaces[1] vspaces[5] vspaces[8] vspaces[3] - vspaces[12] vspaces[15] vspaces[17] vspaces[14] - vspaces[20] vspaces[23] vspaces[25] vspaces[22] - vspaces[28] vspaces[30] vspaces[31] vspaces[29] - ] - - Nspaces = [ - vspaces[10] vspaces[4] vspaces[7] vspaces[32] - vspaces[2] vspaces[6] vspaces[9] vspaces[11] - vspaces[13] vspaces[16] vspaces[18] vspaces[19] - vspaces[21] vspaces[24] vspaces[26] vspaces[27] - ] - Pspaces = fill(phys_space, (4, 4)) - - test_unitcell(unitcell, Pspaces, Nspaces, Nspaces) -end diff --git a/test/cuda/compress/local.jl b/test/cuda/compress/local.jl deleted file mode 100644 index 8a877f1bb..000000000 --- a/test/cuda/compress/local.jl +++ /dev/null @@ -1,62 +0,0 @@ -using Test -using Random -using LinearAlgebra -using TensorKit -using PEPSKit -using PEPSKit: virtual_projector -using CUDA, Adapt - -""" -Cost function of LocalTruncation. -For test convenience, open virtual indices are made trivial and removed. -""" -function localcompress_cost(A1, A2, B1, B2, P1, P2) - @tensor net1[pa1 pb1; pa2′ pb2′] := - A1[pa1 pa; D1] * A2[pa pa2′; D2] * B1[pb1 pb; D1] * B2[pb pb2′; D2] - @tensor net2[pa1 pb1; pa2′ pb2′] := P1[Da1 Da2; D] * P2[D; Db1 Db2] * - A1[pa1 pa; Da1] * A2[pa pa2′; Da2] * B1[pb1 pb; Db1] * B2[pb pb2′; Db2] - return norm(net1 - net2) -end - -@testset "Fermionic twists" begin - Vphy = Vect[FermionParity](0 => 2, 1 => 2) - Vvir = Vect[FermionParity](0 => 2, 1 => 2) - for _ in 1:4 # multiple trials without setting seed - Aspace = (Vphy ⊗ Vphy' ← Vvir ⊗ Vvir ⊗ Vvir' ⊗ Vvir') - A1 = adapt(CuArray, randn(ComplexF64, Aspace)) - A2 = adapt(CuArray, randn(ComplexF64, Aspace)) - for MM in [PEPSKit._get_MMdag(A1, A2), PEPSKit._get_MdagM(A1, A2)] - @test isposdef(MM) - end - end -end - -@testset "Cost function of LocalTruncation" begin - Random.seed!(0) - Vaux, Vphy, V = ℂ^1, ℂ^10, ℂ^4 - A1 = adapt(CuArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ V ⊗ Vaux' ⊗ Vaux'), Inf)) - A2 = adapt(CuArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ V ⊗ Vaux' ⊗ Vaux'), Inf)) - B1 = adapt(CuArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ Vaux ⊗ Vaux' ⊗ V'), Inf)) - B2 = adapt(CuArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ Vaux ⊗ Vaux' ⊗ V'), Inf)) - - P1, P2, info = virtual_projector(A1, A2, B1, B2; trunc = notrunc()) - @test P1 * P2 ≈ adapt(CuArray, TensorKit.id(domain(P2))) - - P1, P2, info = virtual_projector(A1, A2, B1, B2; trunc = truncrank(8)) - A1 = removeunit(removeunit(removeunit(A1, 6), 5), 3) - A2 = removeunit(removeunit(removeunit(A2, 6), 5), 3) - B1 = removeunit(removeunit(removeunit(B1, 5), 4), 3) - B2 = removeunit(removeunit(removeunit(B2, 5), 4), 3) - @info "Truncation error = $(info.ϵ)." - @test info.ϵ ≈ localcompress_cost(A1, A2, B1, B2, P1, P2) -end - -@testset "Virtual space matching" begin - Vps = ComplexSpace.([2 2; 2 2]) - Vns = ComplexSpace.([2 4; 5 3]) - Ves = ComplexSpace.([3 5; 4 2]) - ρ = adapt(CuArray, InfinitePEPO(randn, ComplexF64, Vps, Vns, Ves)) - alg = LocalTruncation(truncrank(2)) - ρ2, = compress((ρ, ρ), alg) - @test ρ2 isa InfinitePEPO -end diff --git a/test/cuda/ctmrg/contractions.jl b/test/cuda/ctmrg/contractions.jl deleted file mode 100644 index 817255458..000000000 --- a/test/cuda/ctmrg/contractions.jl +++ /dev/null @@ -1,315 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using CUDA, Adapt - -using PEPSKit: eachcoordinate, _next_coordinate -using PEPSKit: EnlargedCorner, HalfInfiniteEnv, FullInfiniteEnv -using PEPSKit: half_infinite_environment, full_infinite_environment -using PEPSKit: simultaneous_projectors, contract_projectors -using PEPSKit: renormalize_northwest_corner, renormalize_northeast_corner, - renormalize_southeast_corner, renormalize_southwest_corner -using PEPSKit: random_start_vector - -# settings -Random.seed!(91283219348) -stype = ComplexF64 - -renormalize_corner_fns = ( - renormalize_northwest_corner, renormalize_northeast_corner, - renormalize_southeast_corner, renormalize_southwest_corner, -) - -function test_ctmrg_contractions( - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) - - @testset "CTMRG PEPS contractions" begin - peps = adapt(CuArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) - env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) - - n = InfiniteSquareNetwork(peps) - - @test storagetype(peps) <: CuArray - @test storagetype(env) <: CuArray - @test storagetype(n) <: CuArray - test_contractions(n, env) - end - - @testset "CTMRG PartitionFunction contractions" begin - pf = adapt(CuArray, InfinitePartitionFunction(randn, stype, Nspaces, Espaces)) - env = CTMRGEnv(randn, stype, pf, chis_north, chis_east, chis_south, chis_west) - n = InfiniteSquareNetwork(pf) - @test storagetype(pf) <: CuArray - @test storagetype(env) <: CuArray - @test storagetype(n) <: CuArray - - test_contractions(n, env) - end - - @testset "CTMRG PEPO contractions" begin - pepo = adapt(CuArray, InfinitePEPO(randn, stype, Pspaces, Pspaces, Pspaces)) - peps = adapt(CuArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) - n = InfiniteSquareNetwork(peps, pepo) - env = adapt(CuArray, CTMRGEnv(randn, stype, n, chis_north, chis_east, chis_south, chis_west)) - @test storagetype(peps) <: CuArray - @test storagetype(pepo) <: CuArray - @test storagetype(env) <: CuArray - @test storagetype(n) <: CuArray - - test_contractions(n, env) - end - - return nothing -end - -function test_contractions(n::InfiniteSquareNetwork, env::CTMRGEnv) - dirs_and_coordinates = eachcoordinate(n, 1:4) - - # initialize dense and sparse enlarged corners - sparse_enlarged_corners = map(dirs_and_coordinates) do co - return EnlargedCorner(n, env, co) - end - dense_enlarged_corners = map(TensorMap, sparse_enlarged_corners) - - # initialize sparse and dense half-inifite environments - sparse_halfinf_envs = map(dirs_and_coordinates) do co - co´ = _next_coordinate(co, size(env)[2:3]...) - return HalfInfiniteEnv( - sparse_enlarged_corners[co...], sparse_enlarged_corners[co´...] - ) - end - dense_halfinf_envs = map(TensorMap, sparse_halfinf_envs) - # also compute directly from dense enlarged corners, for consistency with current implementation - dense_halfinf_envs_bis = map(dirs_and_coordinates) do co - co´ = _next_coordinate(co, size(env)[2:3]...) - return half_infinite_environment( - dense_enlarged_corners[co...], dense_enlarged_corners[co´...] - ) - end - - # initialize sparse and dense full-inifite environments - sparse_fullinf_envs = map(dirs_and_coordinates) do co - rowsize, colsize = size(env)[2:3] - co2 = _next_coordinate(co, rowsize, colsize) - co3 = _next_coordinate(co2, rowsize, colsize) - co4 = _next_coordinate(co3, rowsize, colsize) - return FullInfiniteEnv( - sparse_enlarged_corners[co4...], - sparse_enlarged_corners[co...], - sparse_enlarged_corners[co2...], - sparse_enlarged_corners[co3...], - ) - end - dense_fullinf_envs = map(TensorMap, sparse_fullinf_envs) - # also compute directly from dense enlarged corners, for consistency with current implementation - dense_fullinf_envs_bis = map(dirs_and_coordinates) do co - rowsize, colsize = size(env)[2:3] - co2 = _next_coordinate(co, rowsize, colsize) - co3 = _next_coordinate(co2, rowsize, colsize) - co4 = _next_coordinate(co3, rowsize, colsize) - return full_infinite_environment( - dense_enlarged_corners[co4...], - dense_enlarged_corners[co...], - dense_enlarged_corners[co2...], - dense_enlarged_corners[co3...], - ) - end - - # SVD half and full infinite environments - (P_left_half, P_right_half), info_half = simultaneous_projectors( - dense_enlarged_corners, env, HalfInfiniteProjector() - ) - U_half, S_half, V_half = info_half.U, info_half.S, info_half.V - (P_left_full, P_right_full), info_full = simultaneous_projectors( - dense_enlarged_corners, env, FullInfiniteProjector() - ) - U_full, S_full, V_full = info_full.U, info_full.S, info_full.V - - # check projector computation for both types of environments, - # comparing dense and sparse implementations - foreach(dirs_and_coordinates) do co - dir, r, c = co - - co2 = _next_coordinate(co, size(env)[2:3]...) - co3 = _next_coordinate(co2, size(env)[2:3]...) - co4 = _next_coordinate(co3, size(env)[2:3]...) - - ## HalfInfiniteEnv - - shenv = sparse_halfinf_envs[dir, r, c] - dhenv = dense_halfinf_envs[dir, r, c] - dhenv_bis = dense_halfinf_envs_bis[dir, r, c] - @test dhenv ≈ dhenv_bis - - # application - xr = random_start_vector(shenv) - xl = randn(storagetype(shenv), codomain(shenv)) - @test shenv(xr, Val(false)) ≈ dhenv * xr - @test shenv(xl, Val(true)) ≈ dhenv' * xl - - # projector computation - P_left_sparse, P_right_sparse = contract_projectors( - U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], shenv - ) - P_left_dense, P_right_dense = contract_projectors( - U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], - dense_enlarged_corners[co...], dense_enlarged_corners[co2...], - ) - @test P_left_sparse ≈ P_left_dense - @test P_right_sparse ≈ P_right_dense - @test P_left_sparse ≈ P_left_half[dir, r, c] - @test P_right_sparse ≈ P_right_half[dir, r, c] - - - ## FullInfiniteEnv - - sfenv = sparse_fullinf_envs[dir, r, c] - dfenv = dense_fullinf_envs[dir, r, c] - dfenv_bis = dense_fullinf_envs_bis[dir, r, c] - @test dfenv ≈ dfenv_bis - - # application - xl = randn(storagetype(sfenv), codomain(sfenv)) - xr = random_start_vector(sfenv) - @test sfenv(xr, Val(false)) ≈ dfenv * xr - @test sfenv(xl, Val(true)) ≈ dfenv' * xl - - # projector computation - P_left_sparse, P_right_sparse = contract_projectors( - U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], sfenv - ) - P_left_dense, P_right_dense = contract_projectors( - U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], - half_infinite_environment( - dense_enlarged_corners[co4...], dense_enlarged_corners[co...] - ), - half_infinite_environment( - dense_enlarged_corners[co2...], dense_enlarged_corners[co3...] - ), - ) - @test P_left_sparse ≈ P_left_dense - @test P_right_sparse ≈ P_right_dense - @test P_left_sparse ≈ P_left_full[dir, r, c] - @test P_right_sparse ≈ P_right_full[dir, r, c] - end - - foreach(dirs_and_coordinates) do co - dir, r, c = co - - ## Corner renormalization - - C_sparse = renormalize_corner_fns[dir]( - (r, c), sparse_enlarged_corners, P_left_half, P_right_half - ) - C_dense = renormalize_corner_fns[dir]( - (r, c), dense_enlarged_corners, P_left_half, P_right_half - ) - @test C_sparse ≈ C_dense - end - - return nothing -end - -@testset "Random Cartesian spaces" begin - unitcell = (3, 3) - - Pspaces = ComplexSpace.(rand(2:3, unitcell...)) - Nspaces = ComplexSpace.(rand(2:4, unitcell...)) - Espaces = ComplexSpace.(rand(2:4, unitcell...)) - chis_north = ComplexSpace.(rand(5:10, unitcell...)) - chis_east = ComplexSpace.(rand(5:10, unitcell...)) - chis_south = ComplexSpace.(rand(5:10, unitcell...)) - chis_west = ComplexSpace.(rand(5:10, unitcell...)) - - test_ctmrg_contractions( - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) -end - -@testset "Specific U1 spaces" begin - unitcell = (2, 2) - - PA = U1Space(-1 => 1, 0 => 1) - PB = U1Space(0 => 1, 1 => 1) - Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) - Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) - - Pspaces = [PA PB; PB PA] - Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] - chis = [Venv Venv; Venv Venv] - - test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) - - # 4x4 unit cell with all 32 inequivalent bonds - # - # 10 4 7 32 - # | | | | - # 3--A--1--B--5--C--8--D--3 - # | | | | - # 2 6 9 11 - # | | | | - # 14--E-12--F-15--G-17--H-14 - # | | | | - # 13 16 18 19 - # | | | | - # 22--I-20--J-23--K-25--L-22 - # | | | | - # 21 24 26 27 - # | | | | - # 29--M-28--N-30--O-31--P-29 - # | | | | - # 10 4 7 32 - - phys_space = Vect[U1Irrep](1 => 1, -1 => 1) - corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) - vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) - @test length(Set(vspaces)) == 32 - - Espaces = [ - vspaces[1] vspaces[5] vspaces[8] vspaces[3] - vspaces[12] vspaces[15] vspaces[17] vspaces[14] - vspaces[20] vspaces[23] vspaces[25] vspaces[22] - vspaces[28] vspaces[30] vspaces[31] vspaces[29] - ] - - Nspaces = [ - vspaces[10] vspaces[4] vspaces[7] vspaces[32] - vspaces[2] vspaces[6] vspaces[9] vspaces[11] - vspaces[13] vspaces[16] vspaces[18] vspaces[19] - vspaces[21] vspaces[24] vspaces[26] vspaces[27] - ] - Pspaces = fill(phys_space, (4, 4)) - chis = fill(corner_space, (4, 4)) - # TODO broken? - #test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) -end - -@testset "Random fermionic spaces" begin - unitcell = (3, 3) - - S = Vect[FermionParity] - pdims = rand(1:2, unitcell..., 2) - vdims = rand(2:4, unitcell..., 2) - edims = rand(3:6, unitcell..., 2) - - function _construct_space(ds::Array{<:Int, 3}) - V = map(Iterators.product(axes(ds)[1:2]...)) do (r, c) - return S(0 => ds[r, c, 1], 1 => ds[r, c, 2]) - end - return V - end - - Pspaces = _construct_space(pdims) - Nspaces = _construct_space(vdims) - Espaces = _construct_space(vdims) - chis_north = _construct_space(edims) - chis_east = _construct_space(edims) - chis_south = _construct_space(edims) - chis_west = _construct_space(edims) - - test_ctmrg_contractions( - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) -end diff --git a/test/cuda/ctmrg/fixed_iterscheme.jl b/test/cuda/ctmrg/fixed_iterscheme.jl deleted file mode 100644 index ba01efb0f..000000000 --- a/test/cuda/ctmrg/fixed_iterscheme.jl +++ /dev/null @@ -1,108 +0,0 @@ -using Test -using TestExtras: @constinferred -using Accessors -using Random -using LinearAlgebra -using TensorKit, KrylovKit -using PEPSKit -using CUDA, Adapt -using PEPSKit: - ctmrg_iteration, - compute_gauge_fix_gauge, - fix_phases, - fix_relative_phases, - calc_elementwise_convergence, - peps_normalize, - ScramblingEnvGauge, - ScramblingEnvGaugeC4v -using PEPSKit.Defaults: ctmrg_tol - -# initialize parameters -D = 2 -χ = 16 -svd_algs = [(; alg = :SVDViaPolar), (; alg = :GKL)] -projector_algs_asymm = [:HalfInfiniteProjector] #, :FullInfiniteProjector] -unitcells = [(1, 1), (3, 4)] -atol = 1.0e-5 - -# test for element-wise convergence after application of fixed step -@testset "$unitcell unit cell with $(decomposition_alg.alg) and $projector_alg" for ( - unitcell, decomposition_alg, projector_alg, - ) in Iterators.product( - unitcells, svd_algs, projector_algs_asymm - ) - ctm_alg = SimultaneousCTMRG(; decomposition_alg, projector_alg) - - # initialize states - Random.seed!(2394823842) - psi = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) - @test storagetype(psi) <: CuArray - n = InfiniteSquareNetwork(psi) - - env_conv1, = leading_boundary(CTMRGEnv(psi, ComplexSpace(χ)), psi, ctm_alg) - - # do extra iteration and gauge fix - env_conv2, = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) - env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGauge()) - @test calc_elementwise_convergence(env_conv1, env_fixed) ≈ 0 atol = atol - - # fix gauge of single iteration - signs, corner_phases, edge_phases = - compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGauge()) - gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( - ctmrg_iteration(n, env, ctm_alg)[1], - signs, corner_phases, edge_phases, - ) - - # do gauge-fixed iteration - env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) - @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol -end - -# test same thing for C4v CTMRG -c4v_algs = [ - (:C4vQRProjector, (; alg = :Householder)), - (:C4vEighProjector, (; alg = :DivideAndConquer)), - (:C4vEighProjector, (; alg = :Lanczos)), -] -@testset "$(decomposition_alg.alg) and $projector_alg" for - (projector_alg, decomposition_alg) in c4v_algs - # initialize states - Random.seed!(2394823842) - ctm_alg = C4vCTMRG(; - projector_alg, decomposition_alg, maxiter = 200, - tol = (projector_alg == :C4vQRProjector ? 1.0e-12 : ctmrg_tol) - ) - symm = RotateReflect() - - psi = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D))) - @test storagetype(psi) <: CuArray - psi = peps_normalize(symmetrize!(psi, symm)) - @test storagetype(psi) <: CuArray - n = InfiniteSquareNetwork(psi) - @test storagetype(n) <: CuArray - - env₀ = initialize_random_c4v_env(psi, ComplexSpace(χ)) - @test storagetype(env₀) <: CuArray - env_conv1, info = leading_boundary(env₀, psi, ctm_alg) - - # do extra iteration to check gauge fixing - env_conv2, info = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) # CHECK - - env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) - env_diff = calc_elementwise_convergence(env_conv1, env_fixed) - @info "Diff between iters = $(env_diff)" - @test env_diff ≈ 0 atol = atol - - # fix gauge of single iteration - signs, corner_phases, edge_phases = - compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) - gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( - ctmrg_iteration(n, env, ctm_alg)[1], - signs, corner_phases, edge_phases, - ) - - # do gauge-fixed iteration - env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) - @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol -end diff --git a/test/cuda/ctmrg/flavors.jl b/test/cuda/ctmrg/flavors.jl deleted file mode 100644 index 55fbfeaba..000000000 --- a/test/cuda/ctmrg/flavors.jl +++ /dev/null @@ -1,91 +0,0 @@ -using Test -using Random -using MatrixAlgebraKit -using TensorKit -using MPSKit -using PEPSKit -using CUDA, Adapt -using PEPSKit: peps_normalize - -# initialize parameters -D = 2 -χ = 16 -unitcells = [(1, 1), (3, 4)] -projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] -projector_algs_c4v = [ - (:C4vQRProjector, :Householder), - (:C4vEighProjector, :DivideAndConquer), (:C4vEighProjector, :Lanczos), -] -Ts = [Float64, ComplexF64] - -@testset "$(unitcell) unit cell with $projector_alg" for (unitcell, projector_alg) in - Iterators.product(unitcells, projector_algs_asymm) - # compute environments - Random.seed!(32350283290358) - psi = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) - env_sequential, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SequentialCTMRG, projector_alg, - decomposition_alg = (; alg = :SVDViaPolar) - ) - env_simultaneous, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SimultaneousCTMRG, projector_alg, - decomposition_alg = (; alg = :SVDViaPolar) - ) - - # compare norms - @test abs(norm(psi, env_sequential)) ≈ abs(norm(psi, env_simultaneous)) rtol = 1.0e-6 - - # compare singular values - CS_sequential = map(svd_vals, env_sequential.corners) - CS_simultaneous = map(svd_vals, env_simultaneous.corners) - ΔCS = maximum(splat(PEPSKit._singular_value_distance), zip(CS_sequential, CS_simultaneous)) - @test ΔCS < 1.0e-2 - - TS_sequential = map(svd_vals, env_sequential.edges) - TS_simultaneous = map(svd_vals, env_simultaneous.edges) - ΔTS = maximum(splat(PEPSKit._singular_value_distance), zip(TS_sequential, TS_simultaneous)) - @test ΔTS < 1.0e-2 - - # compare Heisenberg energies - H = adapt(CuArray, heisenberg_XYZ(InfiniteSquare(unitcell...))) - E_sequential = cost_function(psi, env_sequential, H) - E_simultaneous = cost_function(psi, env_simultaneous, H) - @test E_sequential ≈ E_simultaneous rtol = 1.0e-3 -end - -# test fixedspace actually fixes space -@testset "Fixedspace truncation using $alg and $projector_alg" for (alg, projector_alg) in - Iterators.product([:SequentialCTMRG, :SimultaneousCTMRG], projector_algs_asymm) - Ds = ComplexSpace.(fill(2, 3, 3)) - χs = ComplexSpace.([16 17 18; 15 20 21; 14 19 22]) - psi = adapt(CuArray, InfinitePEPS(Ds, Ds, Ds)) - env = CTMRGEnv(psi, ComplexSpace.(rand(10:20, 3, 3)), ComplexSpace.(rand(10:20, 3, 3))) - env2, = leading_boundary( - env, psi; alg, maxiter = 1, trunc = FixedSpaceTruncation(), projector_alg, - decomposition_alg = (; alg = :SVDViaPolar) - ) - - # check that the space is fixed - @test all(space.(env.corners) .== space.(env2.corners)) - @test all(space.(env.edges) .== space.(env2.edges)) -end - -@testset "C4v with ($T) - ($projector_alg, $decomp_alg)" for (T, (projector_alg, decomp_alg)) in - Iterators.product(Ts, projector_algs_c4v) - - Random.seed!(29358293829382) - symm = RotateReflect() - Vphys = ComplexSpace(2) - Vpeps = ComplexSpace(D) - Venv = ComplexSpace(χ) - - peps = adapt(CuArray, InfinitePEPS(randn, T, Vphys, Vpeps, Vpeps)) - peps = peps_normalize(symmetrize!(peps, symm)) - - env₀ = initialize_random_c4v_env(peps, Venv) - env, = leading_boundary( - env₀, peps; alg = :C4vCTMRG, projector_alg, - decomposition_alg = (; alg = decomp_alg) - ) - @test env isa CTMRGEnv -end diff --git a/test/cuda/ctmrg/gaugefix.jl b/test/cuda/ctmrg/gaugefix.jl deleted file mode 100644 index e5277530b..000000000 --- a/test/cuda/ctmrg/gaugefix.jl +++ /dev/null @@ -1,101 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using CUDA, Adapt -using PEPSKit: ctmrg_iteration, calc_elementwise_convergence -using PEPSKit: ScramblingEnvGauge, ScramblingEnvGaugeC4v -using PEPSKit: peps_normalize - -spacetypes = [ComplexSpace, Z2Space] -scalartypes = [Float64, ComplexF64] -unitcells = [(1, 1), (2, 2), (3, 2)] -ctmrg_algs_asymm = [SequentialCTMRG, SimultaneousCTMRG] -projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] -projector_algs_c4v = [:C4vEighProjector, :C4vQRProjector] -gauge_algs_asymm = [ScramblingEnvGauge()] -gauge_algs_c4v = [ScramblingEnvGaugeC4v()] -tol = 1.0e-6 # large tol due to χ=6 -χ = 6 -atol = 1.0e-4 - -function _pre_converge_env( - ::Type{T}, alg, physical_space, peps_space, env_space, unitcell; - seed = 985293852935829 - ) where {T} - Random.seed!(seed) # Seed RNG to make random environment consistent - psi = adapt(CuArray, InfinitePEPS(rand, T, physical_space, peps_space; unitcell)) - @test storagetype(psi) <: CuArray - alg == :C4vCTMRG && (psi = peps_normalize(symmetrize!(psi, RotateReflect()))) - env₀ = if alg == :C4vCTMRG - initialize_singlet_c4v_env(T, psi, env_space) - else - CTMRGEnv(psi, env_space) - end - @test storagetype(env₀) <: CuArray - # C4v projectors decompose with `eigh`/`qr`, so only the SVD-based flavors take `:SVDViaPolar` - svd_kwargs = alg == :C4vCTMRG ? (;) : (; decomposition_alg = (; alg = :SVDViaPolar)) - env_conv, = leading_boundary(env₀, psi; alg, tol, svd_kwargs...) - return env_conv, psi -end - -# pre-converge CTMRG environments with given spacetype, scalartype and unit cell -preconv = Dict() -for (S, T, unitcell) in Iterators.product(spacetypes, scalartypes, unitcells) - if S == ComplexSpace - result = _pre_converge_env(T, :SequentialCTMRG, S(2), S(2), S(χ), unitcell) - elseif S == Z2Space - result = _pre_converge_env( - T, :SequentialCTMRG, S(0 => 1, 1 => 1), S(0 => 1, 1 => 1), - S(0 => χ ÷ 2, 1 => χ ÷ 2), unitcell - ) - end - push!(preconv, (S, T, unitcell) => result) -end -preconv_c4v = Dict() -for (S, T) in Iterators.product(spacetypes, scalartypes) - if S == ComplexSpace - result = _pre_converge_env(T, :C4vCTMRG, S(2), S(2), S(χ), (1, 1)) - elseif S == Z2Space - result = _pre_converge_env( - T, :C4vCTMRG, S(0 => 1, 1 => 1), S(0 => 1, 1 => 1), S(0 => χ ÷ 2, 1 => χ ÷ 2), (1, 1) - ) - end - push!(preconv_c4v, (S, T) => result) -end - -# asymmetric CTMRG -@testset "($S) - ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg) - ($gauge_alg)" for ( - S, T, unitcell, ctmrg_alg, projector_alg, gauge_alg, - ) in Iterators.product( - spacetypes, scalartypes, unitcells, ctmrg_algs_asymm, projector_algs_asymm, gauge_algs_asymm - ) - alg = ctmrg_alg(; tol, projector_alg, decomposition_alg = (; alg = :SVDViaPolar)) - env_pre, psi = preconv[(S, T, unitcell)] - n = InfiniteSquareNetwork(psi) - @test storagetype(n) <: CuArray - env, = leading_boundary(env_pre, psi, alg) - env′, = ctmrg_iteration(n, env, alg) - env_fixed = gauge_fix(env′, env, gauge_alg) - env_diff = calc_elementwise_convergence(env, env_fixed) - @info "Diff between iters = $(env_diff)" - @test env_diff ≈ 0 atol = atol -end - -# C4v CTMRG -@testset "($S) - ($T) - ($projector_alg) - ($gauge_alg)" for ( - S, T, projector_alg, gauge_alg, - ) in Iterators.product( - spacetypes, scalartypes, projector_algs_c4v, gauge_algs_c4v - ) - alg = C4vCTMRG(; tol, projector_alg) - env_pre, psi = preconv_c4v[(S, T)] - n = InfiniteSquareNetwork(psi) - @test storagetype(n) <: CuArray - env, = leading_boundary(env_pre, psi, alg) - env′, = ctmrg_iteration(n, env, alg) - env_fixed = gauge_fix(env′, env, gauge_alg) - env_diff = calc_elementwise_convergence(env, env_fixed) - @info "Diff between iters = $(env_diff)" - @test env_diff ≈ 0 atol = atol -end diff --git a/test/cuda/ctmrg/initialization.jl b/test/cuda/ctmrg/initialization.jl deleted file mode 100644 index 5bbe99788..000000000 --- a/test/cuda/ctmrg/initialization.jl +++ /dev/null @@ -1,97 +0,0 @@ -using Test -using TensorKit -using PEPSKit -using Random -using Adapt, CUDA -using MPSKitModels: classical_ising -using PEPSKit: ProductStateEnv - -sd = 12345 - -# toggle symmetry, but same issue for both -symmetries = [Z2Irrep, Trivial] -make_space(::Type{Z2Irrep}, d::Int) = Z2Space(0 => d / 2, 1 => d / 2) -make_space(::Type{Trivial}, d::Int) = ComplexSpace(d) - -d = 2 -D = 4 -χ = 20 -tol = 1.0e-4 -maxiter = 1000 -verbosity = 2 -trunc = truncrank(χ) -boundary_alg = (; - alg = :SimultaneousCTMRG, tol, verbosity, trunc, maxiter, - decomposition_alg = (; alg = :SVDViaPolar), -) - -@testset "CTMRG environment initialization for critical ising with $S symmetry (#255)" for S in symmetries - # initialize - Random.seed!(sd) - T = classical_ising(S) - O = T[1] - n = adapt(CuArray, InfinitePartitionFunction([O O; O O])) - Venv = make_space(S, χ) - P = space(O, 2) - - # random, doesn't converge - env0_rand = initialize_ctmrg_environment(n, RandomInitialization(), Venv) - env_rand, info = leading_boundary(env0_rand, n; boundary_alg...) - @test_broken info.convergence_error ≤ tol - - # embedded random product state, converges - env0_prod = initialize_ctmrg_environment(n, ProductStateInitialization()) - env_prod, info = leading_boundary(env0_prod, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # grown product state, converges - env0_appl = initialize_ctmrg_environment(n, ApplicationInitialization()) - env_appl, info = leading_boundary(env0_appl, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # specific custom starting product state - p_data = ComplexF64[1; 0;;] - p = adapt(CuArray, Tensor(p_data, P)) - prod_env0 = ProductStateEnv(reshape([p, p, flip(p, 1), flip(p, 1)], 4, 1, 1)) - env0_custom = initialize_ctmrg_environment(n, ApplicationInitialization(), prod_env0) - # or just CTMRGEnv(prod_env0) - env_custom, info = leading_boundary(env0_custom, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # PEPS-specific identity initialization; should throw when used on partition functions - @test_throws ArgumentError env0_prod_id = initialize_ctmrg_environment(n, IdentityInitialization()) -end - -@testset "CTMRG environment initialization for PEPS with $S symmetry" for S in symmetries - # initialize - Random.seed!(sd) - P = make_space(S, d) - Vpeps = make_space(S, D) - Venv = make_space(S, χ) - peps = adapt(CuArray, InfinitePEPS(P, Vpeps; unitcell = (2, 2))) - n = InfiniteSquareNetwork(peps) - - # random, converges - env0_rand = initialize_ctmrg_environment(n, RandomInitialization(), Venv) - @test storagetype(env0_rand) <: CuArray - env_rand, info = leading_boundary(env0_rand, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # embedded random product state, converges - env0_prod = initialize_ctmrg_environment(n, ProductStateInitialization()) - @test storagetype(env0_prod) <: CuArray - env_prod, info = leading_boundary(env0_prod, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # embedded product state as identity from ket to bra, converges - env0_prod_id = initialize_ctmrg_environment(n, IdentityInitialization()) - @test storagetype(env0_prod_id) <: CuArray - env_prod, info = leading_boundary(env0_prod_id, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # grown product state, converges - env0_appl = initialize_ctmrg_environment(n, ApplicationInitialization()) - @test storagetype(env0_appl) <: CuArray - env_appl, info = leading_boundary(env0_appl, n; boundary_alg...) - @test info.convergence_error ≤ tol -end diff --git a/test/cuda/ctmrg/jacobian_real_linear.jl b/test/cuda/ctmrg/jacobian_real_linear.jl deleted file mode 100644 index ae1bad3b5..000000000 --- a/test/cuda/ctmrg/jacobian_real_linear.jl +++ /dev/null @@ -1,50 +0,0 @@ -using Test -using Random -using Accessors -using Zygote -using TensorKit, KrylovKit, PEPSKit -using CUDA, Adapt -using PEPSKit: - ctmrg_iteration, compute_gauge_fix_gauge, fix_phases, ScramblingEnvGauge - -algs = [ - SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), - SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), # TODO: why are the errors quite a bit larger for :FullInfiniteProjector? -] -Dbond, χenv = 2, 16 -alg_gauge = ScramblingEnvGauge() -errtol = 1.0e-3 - -@testset "$ctm_alg" for ctm_alg in algs - Random.seed!(123521938519) - state = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(Dbond))) - env, = leading_boundary(CTMRGEnv(state, ComplexSpace(χenv)), state, ctm_alg) - - # follow code of _rrule - env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, ctm_alg) - signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env_conv, env, alg_gauge) - - _, env_vjp = pullback(state, env_conv) do A, x - e, = ctmrg_iteration(InfiniteSquareNetwork(A), x, ctm_alg) - return fix_phases(e, signs, corner_phases, edge_phases) - end - - # get Jacobians of single iteration - ∂f∂A(x)::typeof(state) = env_vjp(x)[1] - ∂f∂x(x)::typeof(env) = env_vjp(x)[2] - - # compute real and complex errors - env_in = CTMRGEnv(state, ComplexSpace(16)) - α_real = randn(Float64) - α_complex = randn(ComplexF64) - - real_err_∂A = norm(scale(∂f∂A(env_in), α_real) - ∂f∂A(scale(env_in, α_real))) - real_err_∂x = norm(scale(∂f∂x(env_in), α_real) - ∂f∂x(scale(env_in, α_real))) - complex_err_∂A = norm(scale(∂f∂A(env_in), α_complex) - ∂f∂A(scale(env_in, α_complex))) - complex_err_∂x = norm(scale(∂f∂x(env_in), α_complex) - ∂f∂x(scale(env_in, α_complex))) - - @test real_err_∂A < errtol - @test real_err_∂x < errtol - @test complex_err_∂A > 1.0e-3 - @test complex_err_∂x > 1.0e-3 -end diff --git a/test/cuda/ctmrg/partition_function.jl b/test/cuda/ctmrg/partition_function.jl deleted file mode 100644 index d58caf915..000000000 --- a/test/cuda/ctmrg/partition_function.jl +++ /dev/null @@ -1,158 +0,0 @@ -using Test -using Random -using LinearAlgebra -using PEPSKit -using TensorKit -using QuadGK -using Test -using CUDA, Adapt - -@testset "Check spaces in partition function CTMRG" begin - zA = randn(ℂ^6 ⊗ ℂ^8 ← ℂ^4 ⊗ ℂ^2) - zB = randn(ℂ^2 ⊗ ℂ^9 ← ℂ^5 ⊗ ℂ^6) - zC = randn(ℂ^7 ⊗ ℂ^4 ← ℂ^8 ⊗ ℂ^3) - zD = randn(ℂ^3 ⊗ ℂ^5 ← ℂ^9 ⊗ ℂ^7) - - Z = adapt(CuArray, InfinitePartitionFunction([zA zB; zC zD])) - χenv = ℂ^12 - env0 = CTMRGEnv(Z, χenv) - env, = leading_boundary( - env0, Z; alg = :SimultaneousCTMRG, maxiter = 3, - projector_alg = :FullInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar) - ) - @test env isa CTMRGEnv -end - - -## Setup - -""" - classical_ising_exact(beta, J) - -[Exact Onsager solution](https://en.wikipedia.org/wiki/Square_lattice_Ising_model#Exact_solution) -for the 2D classical Ising Model with partition function - -```math -\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j -``` -""" -function classical_ising_exact(; beta = log(1 + sqrt(2)) / 2, J = 1.0) - K = beta * J - - k = 1 / sinh(2 * K)^2 - F = quadgk( - theta -> log(cosh(2 * K)^2 + 1 / k * sqrt(1 + k^2 - 2 * k * cos(2 * theta))), 0, pi - )[1] - f = -1 / beta * (log(2) / 2 + 1 / (2 * pi) * F) - - m = 1 - (sinh(2 * K))^(-4) > 0 ? (1 - (sinh(2 * K))^(-4))^(1 / 8) : 0 - - E = quadgk(theta -> 1 / sqrt(1 - (4 * k) * (1 + k)^(-2) * sin(theta)^2), 0, pi / 2)[1] - e = -J * cosh(2 * K) / sinh(2 * K) * (1 + 2 / pi * (2 * tanh(2 * K)^2 - 1) * E) - - return f, m, e -end - -""" - classical_ising(; beta=log(1 + sqrt(2)) / 2) - -Implements the 2D classical Ising model with partition function - -```math -\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j -``` -""" -function classical_ising(; beta = log(1 + sqrt(2)) / 2, J = 1.0) - K = beta * J - - # Boltzmann weights - t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)] - r = eigen(t) - nt = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors - - # local partition function tensor - O = zeros(2, 2, 2, 2) - O[1, 1, 1, 1] = 1 - O[2, 2, 2, 2] = 1 - @tensor o[-1 -2; -3 -4] := O[3 4; 2 1] * nt[-3; 3] * nt[-4; 4] * nt[-2; 2] * nt[-1; 1] - - # magnetization tensor - M = copy(O) - M[2, 2, 2, 2] *= -1 - @tensor m[-1 -2; -3 -4] := M[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * nt[-4; 4] - - # bond interaction tensor and energy-per-site tensor - e = ComplexF64[-J J; J -J] .* nt - @tensor e_hor[-1 -2; -3 -4] := - O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * e[-4; 4] - @tensor e_vert[-1 -2; -3 -4] := - O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * e[-3; 3] * nt[-4; 4] - e = e_hor + e_vert - - # fixed tensor map space for all three - TMS = ℂ^2 ⊗ ℂ^2 ← ℂ^2 ⊗ ℂ^2 - - return TensorMap(o, TMS), TensorMap(m, TMS), TensorMap(e, TMS) -end - -## Test - -# initialize -beta = 0.6 -O, M, E = classical_ising(; beta) -O = adapt(CuArray, O) -M = adapt(CuArray, M) -E = adapt(CuArray, E) -Z = InfinitePartitionFunction(O) -Venv = ℂ^12 -Random.seed!(81812781143) -env₀ = CTMRGEnv(Z, Venv) -env₀_c4v = initialize_random_c4v_env(Z, Venv) -# cover all different flavors -args = [ - (:SequentialCTMRG, :HalfInfiniteProjector), (:SequentialCTMRG, :FullInfiniteProjector), - (:SimultaneousCTMRG, :HalfInfiniteProjector), (:SimultaneousCTMRG, :FullInfiniteProjector), - # (:C4vCTMRG, :C4vEighProjector), (:C4vCTMRG, :C4vQRProjector), # TODO -] - -# Basic properties -@test storagetype(Z) <: CuArray -@test spacetype(typeof(Z)) === ComplexSpace -@test spacetype(Z) === ComplexSpace -@test sectortype(typeof(Z)) === Trivial -@test sectortype(Z) === Trivial -@test length(Z) == 1 -@test size(Z, 1) == 1 -@test size(Z, 2) == 1 -@test eltype(similar(Z)) == eltype(Z) -@test copy(Z) == Z -@test copy(Z) ≈ Z - - -@testset "Classical Ising partition function using $alg with $projector_alg" for ( - alg, projector_alg, - ) in args - env₀₀ = alg == :C4vCTMRG ? env₀_c4v : env₀ - env, = leading_boundary( - env₀₀, Z; alg, maxiter = 300, projector_alg, - decomposition_alg = (; alg = :SVDViaPolar) - ) - - # check observables - λ = network_value(Z, env) - m = expectation_value(Z, (1, 1) => M, env) - e = expectation_value(Z, (1, 1) => E, env) - f_exact, m_exact, e_exact = classical_ising_exact(; beta) - @info "Exact energy = $(e_exact)." - - # should be real-ish - @test abs(imag(λ)) < 1.0e-4 - @test abs(imag(m)) < 1.0e-4 - @test abs(imag(e)) < 1.0e-4 - - # should match exact solution - @test -log(λ) / beta ≈ f_exact rtol = 1.0e-4 - @test abs(m) ≈ abs(m_exact) rtol = 1.0e-4 - @info "Evaluated energy = $(e)." - @test e ≈ e_exact rtol = 1.0e-1 # accuracy limited by bond dimension and maxiter -end diff --git a/test/cuda/ctmrg/pepo.jl b/test/cuda/ctmrg/pepo.jl deleted file mode 100644 index c6c76f081..000000000 --- a/test/cuda/ctmrg/pepo.jl +++ /dev/null @@ -1,154 +0,0 @@ -using Test -using Random -using LinearAlgebra -using PEPSKit -using TensorKit -using KrylovKit -using OptimKit -using Zygote -using CUDA, Adapt -## Setup - -function three_dimensional_classical_ising(; beta, J = 1.0) - K = beta * J - - # Boltzmann weights - t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)] - r = eigen(t) - q = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors - - # local partition function tensor - O = zeros(2, 2, 2, 2, 2, 2) - O[1, 1, 1, 1, 1, 1] = 1 - O[2, 2, 2, 2, 2, 2] = 1 - @tensor o[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - - # magnetization tensor - M = copy(O) - M[2, 2, 2, 2, 2, 2] *= -1 - @tensor m[-1 -2; -3 -4 -5 -6] := - M[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - - # bond interaction tensor and energy-per-site tensor - e = ComplexF64[-J J; J -J] .* q - @tensor e_x[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * e[-4; 4] * q[-5; 5] * q[-6; 6] - @tensor e_y[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * e[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - @tensor e_z[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * e[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - e = e_x + e_y + e_z - - # fixed tensor map space for all three - TMS = ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)' - - return adapt(CuArray, TensorMap(o, TMS)), adapt(CuArray, TensorMap(m, TMS)), adapt(CuArray, TensorMap(e, TMS)) -end - -## Test - -# initialize -beta = 0.2391 # slightly lower temperature than βc ≈ 0.2216544 -O, M, E = three_dimensional_classical_ising(; beta) -χpeps = ℂ^2 -χenv = ℂ^12 - -# cover all different flavors -ctm_styles = [:SequentialCTMRG, :SimultaneousCTMRG] -projector_algs = [:HalfInfiniteProjector, :FullInfiniteProjector] - -@testset "PEPO CTMRG runthroughs for unitcell=$(unitcell)" for unitcell in - [(1, 1, 1), (1, 1, 2)] - Random.seed!(81812781144) - - # contract - T = InfinitePEPO(O; unitcell = unitcell) - psi0 = initializePEPS(T, χpeps) - n = InfiniteSquareNetwork(psi0, T) - env0 = CTMRGEnv(n, χenv) - - @test spacetype(typeof(T)) === ComplexSpace - @test spacetype(T) === ComplexSpace - @test sectortype(typeof(T)) === Trivial - @test sectortype(T) === Trivial - @test storagetype(T) <: CuArray - - @testset "PEPO CTMRG contraction using $alg with $projector_alg" for ( - alg, projector_alg, - ) in Iterators.product(ctm_styles, projector_algs) - env, = leading_boundary( - env0, n; alg, maxiter = 150, projector_alg, - decomposition_alg = (; alg = :SVDViaPolar) - ) - end -end - -@testset "Fixed-point computation for 3D classical ising model" begin - Random.seed!(81812781144) - - # prep - ctm_alg = SimultaneousCTMRG(; - maxiter = 150, tol = 1.0e-8, verbosity = 2, - decomposition_alg = (; alg = :SVDViaPolar), - ) - gradient_alg = FixedPointGradient(; - solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), - ) - opt_alg = LBFGS(32; maxiter = 50, gradtol = 1.0e-5, verbosity = 3) - function pepo_retract(x, η, α) - x´_partial, ξ = PEPSKit.peps_retract(x[1:2], η, α) - x´ = (x´_partial..., deepcopy(x[3])) - return x´, ξ - end - function pepo_transport!(ξ, x, η, α, x´) - return PEPSKit.peps_transport!(ξ, x[1:2], η, α, x´[1:2]) - end - - # contract - T = adapt(CuArray, InfinitePEPO(O; unitcell = (1, 1, 1))) - psi0 = initializePEPS(T, χpeps) - n2 = InfiniteSquareNetwork(psi0) - @test storagetype(n2) <: CuArray - env2_0 = CTMRGEnv(n2, χenv) - n3 = InfiniteSquareNetwork(psi0, T) - @test storagetype(n3) <: CuArray - env3_0 = CTMRGEnv(n3, χenv) - - # optimize free energy per site - (psi_final, env2_final, env3_final), f, = optimize( - (psi0, env2_0, env3_0), - opt_alg; - inner = PEPSKit.real_inner, - retract = pepo_retract, - (transport!) = (pepo_transport!), - ) do (psi, env2, env3) - E, gs = withgradient(psi) do ψ - n2 = InfiniteSquareNetwork(ψ) - env2′, info = PEPSKit.hook_pullback( - leading_boundary, env2, n2, ctm_alg; alg_rrule = gradient_alg - ) - n3 = InfiniteSquareNetwork(ψ, T) - env3′, info = PEPSKit.hook_pullback( - leading_boundary, env3, n3, ctm_alg; alg_rrule = gradient_alg - ) - PEPSKit.ignore_derivatives() do - PEPSKit.update!(env2, env2′) - PEPSKit.update!(env3, env3′) - end - λ3 = network_value(n3, env3) - λ2 = network_value(n2, env2) - return -log(real(λ3 / λ2)) - end - g = only(gs) - return E, g - end - - # check energy - n3_final = InfiniteSquareNetwork(psi_final, T) - m = PEPSKit.contract_local_tensor((1, 1, 1), M, n3_final, env3_final) - nrm3 = PEPSKit._contract_site((1, 1), n3_final, env3_final) - - # compare to Monte-Carlo result from https://www.worldscientific.com/doi/abs/10.1142/S0129183101002383 - @test abs(m / nrm3) ≈ 0.667162 rtol = 1.0e-2 -end diff --git a/test/cuda/ctmrg/suweight.jl b/test/cuda/ctmrg/suweight.jl deleted file mode 100644 index b115c54eb..000000000 --- a/test/cuda/ctmrg/suweight.jl +++ /dev/null @@ -1,56 +0,0 @@ -using Test -using Random -using TensorKit -using CUDA, Adapt -using PEPSKit -using PEPSKit: str, twistdual, unitcell - -Vps = Dict( - Z2Irrep => Vect[Z2Irrep](0 => 1, 1 => 2), - U1Irrep => Vect[U1Irrep](0 => 2, 1 => 2, -1 => 1), - FermionParity => Vect[FermionParity](0 => 1, 1 => 2), -) -Vvs = Dict( - Z2Irrep => Vect[Z2Irrep](0 => 2, 1 => 2), - U1Irrep => Vect[U1Irrep](0 => 3, 1 => 1, -1 => 2), - FermionParity => Vect[FermionParity](0 => 2, 1 => 2), -) - -function su_rdm_1x1( - row::Int, col::Int, peps::InfinitePEPS, wts::Union{Nothing, SUWeight} = nothing - ) - Nr, Nc = size(peps) - @assert 1 <= row <= Nr && 1 <= col <= Nc - t = peps.A[row, col] - if !(wts === nothing) - t = absorb_weight(t, wts, row, col, Tuple(1:4)) - end - # contract local ⟨t|t⟩ without virtual twists - @tensor ρ[k; b] := conj(t[b; n e s w]) * twistdual(t, 2:5)[k; n e s w] - return ρ / str(ρ) -end - -@testset "SUWeight ($(init) init, $(sect))" for (init, sect) in - Iterators.product([:trivial, :random], keys(Vps)) - - Vp, Vv = Vps[sect], Vvs[sect] - Nspaces = [Vv Vv' Vv; Vv' Vv Vv'] - Espaces = [Vv Vv Vv'; Vv Vv' Vv'] - Pspaces = fill(Vp, size(Nspaces)) - peps = adapt(CuArray, InfinitePEPS(randn, ComplexF64, Pspaces, Nspaces, Espaces)) - wts = SUWeight(peps) - if init != :trivial - rand!(wts) - normalize!.(wts.data, Inf) - end - env = CTMRGEnv(wts) - for idx in CartesianIndices(unitcell(peps)) - r, c = Tuple(idx) - ρ1 = su_rdm_1x1(r, c, peps, wts) - if init == :trivial - @test ρ1 ≈ su_rdm_1x1(r, c, peps, nothing) - end - ρ2 = reduced_densitymatrix([idx], peps, env) - @test ρ1 ≈ ρ2 - end -end diff --git a/test/cuda/ctmrg/unitcell.jl b/test/cuda/ctmrg/unitcell.jl deleted file mode 100644 index b6004f2a6..000000000 --- a/test/cuda/ctmrg/unitcell.jl +++ /dev/null @@ -1,125 +0,0 @@ -using Test -using Random -using PEPSKit -using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, ScramblingEnvGauge -using TensorKit -using CUDA, Adapt - -# settings -Random.seed!(91283219347) -stype = ComplexF64 -ctm_algs = [ - SequentialCTMRG(; projector_alg = :HalfInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), - SequentialCTMRG(; projector_alg = :FullInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), - SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), - SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector, decomposition_alg = (; alg = :SVDViaPolar)), -] - -function test_unitcell( - ctm_alg, unitcell, - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) - peps = adapt(CuArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) - env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) - - # apply one CTMRG iteration with fixeds - env′, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env, ctm_alg) - env″, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env′, ctm_alg) # another iteration to fix spaces - - # compute random expecation value to test matching bonds - random_op = adapt( - CuArray, LocalOperator( - Pspaces, - [ - (c,) => randn( - scalartype(peps), - Pspaces[c], Pspaces[c], - ) for c in CartesianIndices(unitcell) - ]..., - ) - ) - @test expectation_value(peps, random_op, env) isa Number - @test expectation_value(peps, random_op, env′) isa Number - - # test if gauge fixing routines run through - signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env″, env′, ScramblingEnvGauge()) - @test signs isa Array - @test corner_phases isa Array - @test edge_phases isa Array - return nothing -end - -@testset "Random Cartesian spaces with $ctm_alg" for ctm_alg in ctm_algs - unitcell = (3, 3) - - Pspaces = ComplexSpace.(rand(2:3, unitcell...)) - Nspaces = ComplexSpace.(rand(2:4, unitcell...)) - Espaces = ComplexSpace.(rand(2:4, unitcell...)) - chis_north = ComplexSpace.(rand(5:10, unitcell...)) - chis_east = ComplexSpace.(rand(5:10, unitcell...)) - chis_south = ComplexSpace.(rand(5:10, unitcell...)) - chis_west = ComplexSpace.(rand(5:10, unitcell...)) - - test_unitcell( - ctm_alg, unitcell, - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) -end - -@testset "Specific U1 spaces with $ctm_alg" for ctm_alg in ctm_algs - unitcell = (2, 2) - - PA = U1Space(-1 => 1, 0 => 1) - PB = U1Space(0 => 1, 1 => 1) - Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) - Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) - - Pspaces = [PA PB; PB PA] - Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] - chis = [Venv Venv; Venv Venv] - - test_unitcell(ctm_alg, unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) - - # 4x4 unit cell with all 32 inequivalent bonds - # - # 10 4 7 32 - # | | | | - # 3--A--1--B--5--C--8--D--3 - # | | | | - # 2 6 9 11 - # | | | | - # 14--E-12--F-15--G-17--H-14 - # | | | | - # 13 16 18 19 - # | | | | - # 22--I-20--J-23--K-25--L-22 - # | | | | - # 21 24 26 27 - # | | | | - # 29--M-28--N-30--O-31--P-29 - # | | | | - # 10 4 7 32 - - phys_space = Vect[U1Irrep](1 => 1, -1 => 1) - corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) - vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) - @test length(Set(vspaces)) == 32 - - Espaces = [ - vspaces[1] vspaces[5] vspaces[8] vspaces[3] - vspaces[12] vspaces[15] vspaces[17] vspaces[14] - vspaces[20] vspaces[23] vspaces[25] vspaces[22] - vspaces[28] vspaces[30] vspaces[31] vspaces[29] - ] - - Nspaces = [ - vspaces[10] vspaces[4] vspaces[7] vspaces[32] - vspaces[2] vspaces[6] vspaces[9] vspaces[11] - vspaces[13] vspaces[16] vspaces[18] vspaces[19] - vspaces[21] vspaces[24] vspaces[26] vspaces[27] - ] - Pspaces = fill(phys_space, (4, 4)) - chis = fill(corner_space, (4, 4)) - - test_unitcell(ctm_alg, unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) -end diff --git a/test/cuda/gradients/c4v_ctmrg_gradients.jl b/test/cuda/gradients/c4v_ctmrg_gradients.jl deleted file mode 100644 index 46e515e19..000000000 --- a/test/cuda/gradients/c4v_ctmrg_gradients.jl +++ /dev/null @@ -1,131 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using Zygote -using OptimKit -using KrylovKit -using CUDA, Adapt - -sd = 42039482052 - -## Test C4v CTMRG gradients -# ------------------------------------------- -χbond = 2 -χenv = 6 -symmetry = RotateReflect() -Pspaces = [ComplexSpace(2)] -Vspaces = [ComplexSpace(χbond)] -Espaces = [ComplexSpace(χenv)] -models = [adapt(CuArray, heisenberg_XYZ(InfiniteSquare()))] -names = ["Heisenberg"] - -gradtol = 1.0e-4 -ctmrg_verbosity = 1 -ctmrg_algs = [[:C4vCTMRG]] -projector_algs = [[:C4vEighProjector, :C4vQRProjector]] -decomposition_rrule_algs = [[:FullPullback, :TruncPullback]] -gradient_algs = [[nothing, :FixedPointGradient]] -# the gradient solvers are device-independent and are covered exhaustively by the CPU test, -# so only keep the two KrylovKit code paths here, since GPU gradients are slow -gradient_solver_algs = [[:Arnoldi, :GMRES]] -steps = -0.01:0.005:0.01 - -# record which rrule alg is compatible with which projector alg -allowed_rrule_algs = Dict( - :C4vEighProjector => keys(PEPSKit.EIGH_RRULE_SYMBOLS), - :C4vQRProjector => keys(PEPSKit.QR_RRULE_SYMBOLS), -) - -# be selective on which configurations to test the naive gradient for -naive_gradient_combinations = [(:C4vCTMRG, :C4vEighProjector, :FullPullback), (:C4vCTMRG, :C4vQRProjector, :FullPullback)] -naive_gradient_done = Set() - -## Tests -# ------ -@testset "AD C4v CTMRG energy gradients for $(names[i]) model" verbose = true for i in - eachindex( - models - ) - Pspace = Pspaces[i] - Vspace = Vspaces[i] - Espace = Espaces[i] - calgs = ctmrg_algs[i] - palgs = projector_algs[i] - dalgs = decomposition_rrule_algs[i] - galgs = gradient_algs[i] - gsalgs = gradient_solver_algs[i] - @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=(alg = :$gradient_alg, solver_alg = :$gradient_solver_alg)" for ( - ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, gradient_solver_alg, - ) in Iterators.product( - calgs, palgs, dalgs, galgs, gsalgs - ) - - # check for allowed algorithm combinations when testing naive gradient - if isnothing(gradient_alg) - combo = (ctmrg_alg, projector_alg, decomposition_rrule_alg) - combo in naive_gradient_combinations || continue - combo in naive_gradient_done && continue - push!(naive_gradient_done, combo) - gradient_solver_alg = nothing # unused in naive gradient, so set to nothing to avoid confusion - end - - # check for allowed combinations of projector alg and decomposition rrule alg - decomposition_rrule_alg in allowed_rrule_algs[projector_alg] || continue - - # construct appropriate decomposition struct to pass custom rrule alg - decomposition_alg = if projector_alg == :C4vEighProjector - EighAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) - elseif projector_alg == :C4vQRProjector - QRAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) - else - error("unknown projector alg: $projector_alg") - end - - @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg)) on $(names[i])" - Random.seed!(sd) - dir = adapt(CuArray, InfinitePEPS(Pspace, Vspace)) - psi = adapt(CuArray, InfinitePEPS(Pspace, Vspace)) - symmetrize!(psi, symmetry) - symmetrize!(dir, symmetry) - # instantiate to avoid having to type this twice... - contrete_ctmrg_alg = PEPSKit.CTMRGAlgorithm(; - alg = ctmrg_alg, - verbosity = ctmrg_verbosity, - projector_alg = projector_alg, - decomposition_alg, - ) - # instantiate because hook_pullback doesn't go through the keyword selector... - concrete_gradient_alg = if isnothing(gradient_alg) - nothing # TODO: add this to the PEPSKit.GradientAlgorithm selector? - else - PEPSKit.GradientAlgorithm(; - alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol) - ) - end - env0 = PEPSKit.initialize_random_c4v_env(psi, Espace) - env, = leading_boundary(env0, psi, contrete_ctmrg_alg) - alphas, fs, dfs1, dfs2 = OptimKit.optimtest( - (psi, env), - dir; - alpha = steps, - retract = PEPSKit.peps_retract, - inner = PEPSKit.real_inner, - ) do (peps, env) - E, g = Zygote.withgradient(peps) do psi - env2, = PEPSKit.hook_pullback( - leading_boundary, - env, - psi, - contrete_ctmrg_alg; - alg_rrule = concrete_gradient_alg, - ) - return cost_function(psi, env2, models[i]) - end - g = only(g) - symmetrize!(g, symmetry) - return E, g - end - @test dfs1 ≈ dfs2 atol = 1.0e-2 - end -end diff --git a/test/cuda/gradients/ctmrg_gradients.jl b/test/cuda/gradients/ctmrg_gradients.jl deleted file mode 100644 index 1ce8f2f28..000000000 --- a/test/cuda/gradients/ctmrg_gradients.jl +++ /dev/null @@ -1,171 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using Zygote -using OptimKit -using KrylovKit -using CUDA, Adapt - -## Test models, gradmodes and CTMRG algorithm -# ------------------------------------------- -χbond = 2 -χenv = 6 -Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] -Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] -Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] -models = [ - adapt(CuArray, heisenberg_XYZ(InfiniteSquare())), - adapt(CuArray, pwave_superconductor(InfiniteSquare())), -] -names = ["Heisenberg", "p-wave superconductor"] - -gradtol = 1.0e-4 -ctmrg_verbosity = 0 -ctmrg_algs = [[:SequentialCTMRG, :SimultaneousCTMRG], [:SequentialCTMRG, :SimultaneousCTMRG]] -projector_algs = [[:HalfInfiniteProjector, :FullInfiniteProjector], [:HalfInfiniteProjector, :FullInfiniteProjector]] -svd_rrule_algs = [[:FullPullback, :TruncPullback, :Arnoldi], [:FullPullback, :Arnoldi]] -gradient_algs = [[nothing, :FixedPointGradient], [:FixedPointGradient]] -# the gradient solvers are device-independent and are covered exhaustively by the CPU test, -# so only keep the two KrylovKit code paths here, since GPU gradients are slow -gradient_solver_algs = [[:Arnoldi, :GMRES], [:Arnoldi, :GMRES]] -steps = -0.01:0.005:0.01 - -# don't check naive AD gradients for all algorithm combinations, since it's slow -naive_gradient_combinations = [ - (:SimultaneousCTMRG, :HalfInfiniteProjector, :FullPullback), - (:SimultaneousCTMRG, :FullInfiniteProjector, :FullPullback), - (:SequentialCTMRG, :HalfInfiniteProjector, :FullPullback), -] -naive_gradient_done = Set() - -# fixed-point gradients with sequential CTMRG are covered by the CPU test, -# so skip them here since GPU gradients are slow -function _skip_combination( - ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg - ) - ctmrg_alg == :SequentialCTMRG && !isnothing(gradient_alg) && return true - return false -end - - -## Tests -# ------ -@testset "AD CTMRG energy gradients for $(names[i]) model" verbose = true for i in - eachindex( - models - ) - Pspace = Pspaces[i] - Vspace = Vspaces[i] - Espace = Espaces[i] - calgs = ctmrg_algs[i] - palgs = projector_algs[i] - salgs = svd_rrule_algs[i] - galgs = gradient_algs[i] - gsalgs = gradient_solver_algs[i] - @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg, gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg))" for ( - ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg, gradient_solver_alg, - ) in Iterators.product( - calgs, palgs, salgs, galgs, gsalgs - ) - - # skip slow combinations that the CPU test already covers - _skip_combination(ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg) && continue - - # check for allowed algorithm combinations when testing naive gradient - if isnothing(gradient_alg) - combo = (ctmrg_alg, projector_alg, svd_rrule_alg) - combo in naive_gradient_combinations || continue - combo in naive_gradient_done && continue - push!(naive_gradient_done, combo) - gradient_solver_alg = nothing # unused in naive gradient, so set to nothing to avoid confusion - end - - @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg and gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg)) on $(names[i])" - Random.seed!(42039482030) - dir = adapt(CuArray, InfinitePEPS(Pspace, Vspace)) - psi = adapt(CuArray, InfinitePEPS(Pspace, Vspace)) - @test storagetype(psi) <: CuArray - # instantiate to avoid having to type this twice... - concrete_ctmrg_alg = PEPSKit.CTMRGAlgorithm(; - alg = ctmrg_alg, - verbosity = ctmrg_verbosity, - projector_alg = projector_alg, - decomposition_alg = SVDAdjoint(; - fwd_alg = (; alg = :SVDViaPolar), rrule_alg = (; alg = svd_rrule_alg) - ), - ) - # instantiate because hook_pullback doesn't go through the keyword selector... - concrete_gradient_alg = if isnothing(gradient_alg) - nothing # TODO: add this to the PEPSKit.GradientAlgorithm selector? - else - PEPSKit.GradientAlgorithm(; - alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol) - ) - end - env, = leading_boundary(CTMRGEnv(psi, Espace), psi, concrete_ctmrg_alg) - @test storagetype(env) <: CuArray - alphas, fs, dfs1, dfs2 = OptimKit.optimtest( - (psi, env), - dir; - alpha = steps, - retract = PEPSKit.peps_retract, - inner = PEPSKit.real_inner, - ) do (peps, env) - E, g = Zygote.withgradient(peps) do psi - env2, = PEPSKit.hook_pullback( - leading_boundary, - env, - psi, - concrete_ctmrg_alg; - alg_rrule = concrete_gradient_alg, - ) - return cost_function(psi, env2, models[i]) - end - - return E, only(g) - end - @test dfs1 ≈ dfs2 atol = 1.0e-2 - end -end - -## Regression test for gradient accuracy (https://github.com/QuantumKitHub/PEPSKit.jl/pull/276) -@testset "AD CTMRG energy gradient accuracy regression test (#276)" begin - Random.seed!(1234) - - boundary_alg = PEPSKit.CTMRGAlgorithm(; - tol = 1.0e-10, decomposition_alg = (; alg = :SVDViaPolar) - ) - gradient_alg = PEPSKit.GradientAlgorithm(; tol = 5.0e-8) - - function fg((peps, env)) - E, g = Zygote.withgradient(peps) do ψ - env2, = PEPSKit.hook_pullback( - leading_boundary, - env, - ψ, - boundary_alg; - alg_rrule = gradient_alg, - ) - return cost_function(ψ, env2, H) - end - return E, only(g) - end - - # initialize randomly - H = adapt(CuArray, heisenberg_XYZ(InfiniteSquare(1, 1))) - peps = adapt(CuArray, PEPSKit.peps_normalize(InfinitePEPS(randn, ComplexF64, physicalspace(H)[1], ComplexSpace(3)))) - env0 = CTMRGEnv(randn, ComplexF64, peps, ComplexSpace(20)) - - # test gradient against finite-difference - Δx = 1.0e-5 - _, _, dfs1, dfs2 = OptimKit.optimtest( - fg, (peps, env0); - alpha = LinRange(-Δx, Δx, 2), - retract = PEPSKit.peps_retract, - inner = PEPSKit.real_inner, - ) - - # verify high gradient accuracy for small finite-difference step size - @test dfs1 ≈ dfs2 rtol = 1.0e-2 * Δx -end diff --git a/test/cuda/timeevol/cluster_projectors.jl b/test/cuda/timeevol/cluster_projectors.jl deleted file mode 100644 index 3db8d1942..000000000 --- a/test/cuda/timeevol/cluster_projectors.jl +++ /dev/null @@ -1,265 +0,0 @@ -using Test -using TensorKit -using PEPSKit -using LinearAlgebra -using Random -import MPSKitModels: hubbard_space -using PEPSKit: sdiag_pow, _cluster_truncate!, _flip_virtuals! -using MPSKit: GenericMPSTensor, MPSBondTensor -using CUDA, Adapt - -# Utility setup -# ------------- -function _contract_left( - M::GenericMPSTensor{S, 4}, sl::DiagonalTensorMap{T, S} - ) where {T <: Number, S <: ElementarySpace} - @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) - M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) - @tensor sl1[e1; e0] := conj(M[w1; p n s e1]) * sl[w1; w0] * M0[w0; p n s e0] - return sl1 -end -function _contract_left( - M::GenericMPSTensor{S, 4}, ::Nothing - ) where {S <: ElementarySpace} - @assert !isdual(domain(M, 1)) - M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) - @tensor sl1[e1; e0] := conj(M[w; p n s e1]) * M0[w; p n s e0] - return sl1 -end - -function _contract_right( - M::GenericMPSTensor{S, 4}, sr::DiagonalTensorMap{T, S} - ) where {T <: Number, S <: ElementarySpace} - @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) - M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) - @tensor sr1[w0; w1] := M0[w0; p n s e0] * sr[e0; e1] * conj(M[w1; p n s e1]) - return sr1 -end -function _contract_right( - M::GenericMPSTensor{S, 4}, ::Nothing - ) where {S <: ElementarySpace} - @assert !isdual(codomain(M, 1)) - M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) - @tensor sr1[w0; w1] := M0[w0; p n s e] * conj(M[w1; p n s e]) - return sr1 -end - -""" -Verify the generalized left/right orthogonal condition -""" -function verify_cluster_orth( - Ms::Vector{T1}, wts::Vector{T2} - ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 4}, T2 <: DiagonalTensorMap} - N = length(Ms) - @assert length(wts) == N - 1 - lorths = fill(false, N - 1) - rorths = fill(false, N - 1) - # left orthogonal - for i in 1:(N - 1) - M, sl0 = Ms[i], wts[i] - sl1 = _contract_left(M, i == 1 ? nothing : wts[i - 1]) - lorths[i] = (normalize(TensorMap(sl0)) ≈ normalize(sl1)) # sl0 is DiagonalTensorMap while sl1 is not - end - # right orthogonal - for i in 2:N - M, sr0 = Ms[i], wts[i - 1] - sr1 = _contract_right(M, i == N ? nothing : wts[i]) - rorths[i - 1] = (normalize(TensorMap(sr0)) ≈ normalize(sr1)) - end - return lorths, rorths -end - -function inner_prod_cluster( - Ms1::Vector{T1}, Ms2::Vector{T2} - ) where { - T1 <: GenericMPSTensor{<:ElementarySpace, 4}, - T2 <: GenericMPSTensor{<:ElementarySpace, 4}, - } - N = length(Ms1) - @assert length(Ms2) == N - # physical spaces are assumed to be non-dual - @assert all(!isdual(space(t, 2)) for t in Ms1) - @assert all(!isdual(space(t, 2)) for t in Ms2) - # not the most efficient implementation - M1, M2 = Ms1[1], deepcopy(Ms2[1]) - for ax in 1:4 - isdual(space(M2, ax)) && twist!(M2, ax) - end - @tensor res[-1 -2] := conj(M1[1 2 3 4; -1]) * M2[1 2 3 4; -2] - for i in 2:(N - 1) - M1, M2 = Ms1[i], deepcopy(Ms2[i]) - for ax in 2:4 - isdual(space(M2, ax)) && twist!(M2, ax) - end - @tensor M[-1 -2; -3 -4] := conj(M1[-1 1 2 3; -3]) * M2[-2 1 2 3; -4] - @tensor res[-1 -2] := res[1 2] * M[1 2; -1 -2] - end - M1, M2 = Ms1[N], deepcopy(Ms2[N]) - for ax in 2:5 - isdual(space(M2, ax)) && twist!(M2, ax) - end - @tensor M[-1 -2] := conj(M1[-1 1 2 3; 4]) * M2[-2 1 2 3; 4] - return @tensor res[1 2] * M[1 2] -end - -function fidelity_cluster( - Ms1::Vector{T1}, Ms2::Vector{T2} - ) where { - T1 <: GenericMPSTensor{<:ElementarySpace, 4}, - T2 <: GenericMPSTensor{<:ElementarySpace, 4}, - } - return abs2(inner_prod_cluster(Ms1, Ms2)) / - (inner_prod_cluster(Ms1, Ms1) * inner_prod_cluster(Ms2, Ms2)) -end - -function mpo_to_gate3(gs::Vector{T}) where {T <: AbstractTensorMap} - #= - -4 -5 -6 - ↓ ↓ ↓ - g1 ←- 1 ←- g2 ←- 2 ←- g3 - ↓ ↓ ↓ - -1 -2 -3 - =# - @assert length(gs) == 3 - @tensor gate[-1 -2 -3; -4 -5 -6] := gs[1][-1 -4 1] * gs[2][1 -2 -5 2] * gs[3][2 -3 -6] - return gate -end - -Vspaces = [ - ( - U1Space(0 => 1, 1 => 1, -1 => 1), - U1Space(0 => 1, 1 => 2, -1 => 1)', - U1Space(0 => 4, 1 => 5, -1 => 6)', - ), - ( - Vect[FermionParity](0 => 1, 1 => 1), - Vect[FermionParity](0 => 2, 1 => 2), - Vect[FermionParity](0 => 6, 1 => 6)', - ), -] - -@testset "Cluster bond truncation with projectors" begin - Random.seed!(0) - N, n = 5, 2 - for (Vphy, Vns, V) in Vspaces - GC.gc(); CUDA.reclaim() # release the previous iteration's device pool - Vvirs = fill(Vns, N + 1) - Vvirs[n + 1] = V - Ms1 = map(1:N) do i - Vw, Ve = Vvirs[i], Vvirs[i + 1] - return adapt(CuArray, rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve)) - end - normalize!.(Ms1, Inf) - flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] - # no truncation - Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) - truncs = [truncrank(dim(space(M, 1))) for M in Iterators.drop(Ms2, 1)] - wts2, ϵs, = _cluster_truncate!(Ms2, truncs) - @test all((ϵ == 0) for ϵ in ϵs) - normalize!.(Ms2, Inf) - @test fidelity_cluster(Ms1, Ms2) ≈ 1.0 - lorths, rorths = verify_cluster_orth(Ms2, wts2) - @test all(lorths) && all(rorths) - # truncation on one bond - Ms3 = _flip_virtuals!(deepcopy(Ms1), flips) - tspace = isdual(Vns) ? flip(Vns) : Vns - wts3, ϵs, = _cluster_truncate!(Ms3, fill(truncspace(tspace), N - 1)) - @test all((i == n) || (ϵ == 0) for (i, ϵ) in enumerate(ϵs)) - normalize!.(Ms3, Inf) - ϵ = ϵs[n] - wt2, wt3 = wts2[n], wts3[n] - _flip_virtuals!(Ms3, flips) - fid3, fid3_ = fidelity_cluster(Ms1, Ms3), fidelity_cluster(Ms2, Ms3) - @info "Fidelity of truncated cluster = $(fid3)" - @test fid3 ≈ fid3_ - @test fid3 ≈ (norm(wt3) / norm(wt2))^2 - @test fid3 ≈ 1.0 - (ϵ / norm(wt2))^2 - end -end -#= # TODO NEEDS REPARTITION FIX FOR DIAGONALTENSORMAP -@testset "Identity gate on 3-site cluster" begin - N, n = 3, 1 - for (Vphy, Vns, V) in Vspaces - GC.gc(); CUDA.reclaim() # release the previous iteration's device pool - Vvirs = fill(Vns, N + 1) - Vvirs[n + 1] = V - Ms1 = map(1:N) do i - Vw, Ve = Vvirs[i], Vvirs[i + 1] - return adapt(CuArray, normalize(rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve), Inf)) - end - flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] - unit = id(Vphy) - gate = reduce(⊗, fill(unit, 3)) - gs = PEPSKit.gate_to_mpo(gate) - @test mpo_to_gate3(gs) ≈ gate - Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) - PEPSKit._apply_gatempo!(Ms2, gs) - fid = fidelity_cluster(Ms1, Ms2) - @test fid ≈ 1.0 - end - for (Vphy, Vns, V) in Vspaces - GC.gc(); CUDA.reclaim() # release the previous iteration's device pool - Vvirs = fill(Vns, N + 1) - Vvirs[n + 1] = V - Ms1 = map(1:N) do i - Vw, Ve = Vvirs[i], Vvirs[i + 1] - return adapt(CuArray, normalize(rand(Vw ⊗ Vphy ⊗ Vphy' ⊗ Vns' ⊗ Vns ← Ve), Inf)) - end - flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] - unit = adapt(CuArray, id(Vphy)) - gate = reduce(⊗, fill(unit, 3)) - gs = PEPSKit.gate_to_mpo(gate) - @test mpo_to_gate3(gs) ≈ gate - for gate_ax in 1:2 - Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) - PEPSKit._apply_gatempo!(Ms2, gs; gate_ax) - fid = fidelity_cluster( - [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms1], - [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms2] - ) - @test fid ≈ 1.0 - end - end -end - -@testset "Hubbard model SU (MPO gate)" begin - Nr, Nc = 2, 2 - ctmrg_tol = 1.0e-9 - Random.seed!(1459) - # with U(1) spin rotation symmetry - Pspace = hubbard_space(Trivial, U1Irrep) - Vspace = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) - Espace = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 8, (1, 1 // 2) => 4, (1, -1 // 2) => 4) - truncs_env = collect(truncerror(; atol = 1.0e-12) & truncrank(χ) for χ in [8, 16]) - peps0 = adapt(CuArray, InfinitePEPS(rand, Float64, Pspace, Vspace, Vspace'; unitcell = (Nr, Nc))) - # make initial state bipartite - for r in 1:2 - peps0[r + 1, 2] = copy(peps0[r, 1]) - end - wts0 = SUWeight(peps0) - ham = adapt(CuArray, hubbard_model(Float64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t = 1.0, U = 6.0, mu = 3.0)) - # applying 2-site gates decomposed to MPO or not, - # resulting energy should be almost the same - e_sites = map((true, false)) do force_mpo - GC.gc(); CUDA.reclaim() # release the previous iteration's device pool - peps, wts = deepcopy(peps0), deepcopy(wts0) - trunc = truncerror(; atol = 1.0e-10) & truncrank(4) - alg = SimpleUpdate(; trunc, force_mpo) - peps, wts, = time_evolve( - peps, ham, 0.01, 10000, alg, wts; tol = 1.0e-6, check_interval = 1000 - ) - normalize!.(peps.A, Inf) - env = CTMRGEnv(wts) - for trunc in truncs_env - env, = leading_boundary( - env, peps; alg = :SequentialCTMRG, tol = ctmrg_tol, trunc, - decomposition_alg = (; alg = :SVDViaPolar) - ) - end - e_site = cost_function(peps, env, ham) / (Nr * Nc) - @info "Energy (force_mpo = $(force_mpo)): $e_site" - return e_site - end - @test e_sites[1] ≈ e_sites[2] atol = 1.0e-4 -end -=# diff --git a/test/cuda/timeevol/j1j2_finiteT.jl b/test/cuda/timeevol/j1j2_finiteT.jl deleted file mode 100644 index d96b4c126..000000000 --- a/test/cuda/timeevol/j1j2_finiteT.jl +++ /dev/null @@ -1,69 +0,0 @@ -using Test -using LinearAlgebra -using TensorKit -import MPSKitModels: σˣ, σᶻ -using PEPSKit -using CUDA, Adapt - -# Benchmark energy from high-temperature expansion -# at β = 0.3, 0.6 -# Physical Review B 86, 045139 (2012) Fig. 15-16 -bm = [-0.1235, -0.213] - -function converge_env(state, χ::Int) - env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) - trunc1 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env, = leading_boundary( - env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10, - decomposition_alg = (; alg = :SVDViaPolar) - ) - return env -end - -Nr, Nc = 2, 2 -ham = adapt( - CuArray, j1_j2_model( - Float64, SU2Irrep, InfiniteSquare(Nr, Nc); - J1 = 1.0, J2 = 0.5, sublattice = false - ) -) -@test storagetype(ham) <: CuArray -pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) -@test storagetype(pepo0) <: CuArray -wts0 = SUWeight(pepo0) -# 7 = 1 (spin-0) + 2 x 3 (spin-1) -trunc_pepo = truncrank(7) & truncerror(; atol = 1.0e-12) -check_interval = 100 -dt, nstep = 1.0e-3, 600 - -# PEPO approach -alg = SimpleUpdate(; trunc = trunc_pepo, purified = false) -GC.gc(); CUDA.reclaim() # release the previous phase's device pool -evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) -pepo, wts, info = time_evolve(evolver; check_interval) -env = converge_env(InfinitePartitionFunction(pepo), 16) -energy = expectation_value(pepo, ham, env) / (Nr * Nc) -@info "β = $(dt * nstep): tr(ρH) = $(energy)" -@test dt * nstep ≈ info.t -@test energy ≈ bm[2] atol = 5.0e-3 - -# PEPS (purified PEPO) approach -alg = SimpleUpdate(; trunc = trunc_pepo, purified = true) -GC.gc(); CUDA.reclaim() # release the previous phase's device pool -evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) -pepo, wts, info = time_evolve(evolver; check_interval) -env = converge_env(InfinitePartitionFunction(pepo), 16) -energy = expectation_value(pepo, ham, env) / (Nr * Nc) -@info "β = $(dt * nstep) / 2: tr(ρH) = $(energy)" -@test energy ≈ bm[1] atol = 5.0e-3 - -# test BP gauge fixing for purified iPEPO -bp_alg = BeliefPropagation(; maxiter = 100, tol = 1.0e-9) -bp_env, = leading_boundary(BPEnv(ones, Float64, pepo), pepo, bp_alg) -pepo, = gauge_fix(pepo, BPGauge(), bp_env) - -env = converge_env(InfinitePEPS(pepo), 16) -energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) -@info "β = $(dt * nstep): ⟨ρ|H|ρ⟩ = $(energy)" -@test dt * nstep ≈ info.t -@test energy ≈ bm[2] atol = 5.0e-3 diff --git a/test/cuda/timeevol/sitedep_truncation.jl b/test/cuda/timeevol/sitedep_truncation.jl deleted file mode 100644 index 5a26f345d..000000000 --- a/test/cuda/timeevol/sitedep_truncation.jl +++ /dev/null @@ -1,65 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using PEPSKit: _is_bipartite, _get_fixedspacetrunc -using CUDA, Adapt - -elt = Float64 -Nr, Nc = 2, 2 -Vps = fill(U1Space(1 / 2 => 1, -1 / 2 => 1), (Nr, Nc)) -Vns = [ - U1Space(0 => 1, 1 => 2, -1 => 1) U1Space(0 => 1, 1 => 2, -1 => 1)'; - U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 2, -1 => 1) -] -Ves1 = [ - U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); - U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' -] -Ves2 = [ - U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); - U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(0 => 1, 1 => 2, -1 => 1)' -] -Venv = U1Space(0 => 2, 1 => 1, -1 => 1) -Random.seed!(48736) -states = ( - adapt(CuArray, InfinitePEPS(randn, elt, Vps, Vns, Ves1)), - adapt(CuArray, InfinitePEPO(randn, elt, Vps, Vns, Ves2)), -) - -@testset "Rotation of SiteDependentTruncation" begin - state = states[1] - for f in (rotl90, rotr90, rot180) - trunc1 = f(_get_fixedspacetrunc(state)) - trunc2 = _get_fixedspacetrunc(f(state)) - @test all( - t1.space == t2.space for (t1, t2) in zip(trunc1.truncs, trunc2.truncs) - ) - end -end - -@testset "Simple update on $(typeof(state0).name.wrapper), bipartite = $(bipartite)" for - (state0, bipartite) in Iterators.product(states, (true, false)) - GC.gc(); CUDA.reclaim() # release the previous iteration's device pool - J2 = 0.5 - if bipartite - state0[2, 1] = copy(state0[1, 2]) - state0[2, 2] = copy(state0[1, 1]) - J2 = 0.0 - end - ham = adapt(CuArray, j1_j2_model(elt, U1Irrep, InfiniteSquare(Nr, Nc); J1 = 1.0, J2, sublattice = false)) - # converted internally to SiteDependentTruncation - alg = SimpleUpdate(; trunc = FixedSpaceTruncation(), bipartite) - wts0 = SUWeight(state0) - state, wts, = time_evolve(state0, ham, 0.1, 1, alg, wts0) - for (t, t0) in zip(state.A, state0.A) - @test space(t) == space(t0) - end - for (wt, wt0) in zip(wts.data, wts0.data) - @test space(wt) == space(wt0) - end - if bipartite - @test _is_bipartite(state) - @test _is_bipartite(wts) - end -end diff --git a/test/cuda/timeevol/tf_ising_finiteT.jl b/test/cuda/timeevol/tf_ising_finiteT.jl deleted file mode 100644 index c70b143c3..000000000 --- a/test/cuda/timeevol/tf_ising_finiteT.jl +++ /dev/null @@ -1,114 +0,0 @@ -using Test -using LinearAlgebra -using TensorKit -import MPSKitModels: σˣ, σᶻ -using PEPSKit, CUDA, Adapt - -# Benchmark data of [σx, σz] from HOTRG -# Physical Review B 86, 045139 (2012) Fig. 15-16 -bm_β = [0.5632, 0.0] -bm_2β = [0.5297, 0.8265] - -function converge_env(state, χ::Int) - trunc1 = truncrank(4) & truncerror(; atol = 1.0e-12) - env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) - env, = leading_boundary( - env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10, - decomposition_alg = (; alg = :SVDViaPolar) - ) - trunc2 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env, = leading_boundary( - env, state; alg = :SequentialCTMRG, trunc = trunc2, tol = 1.0e-10, - decomposition_alg = (; alg = :SVDViaPolar) - ) - return env -end - -function measure_mag(pepo::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) - r, c = 1, 1 - lattice = physicalspace(pepo) - Mx = adapt(CuArray, LocalOperator(lattice, ((r, c),) => σˣ(Float64, Trivial))) - Mz = adapt(CuArray, LocalOperator(lattice, ((r, c),) => σᶻ(Float64, Trivial))) - if purified - magx = expectation_value(pepo, Mx, pepo, env) - magz = expectation_value(pepo, Mz, pepo, env) - else - magx = expectation_value(pepo, Mx, env) - magz = expectation_value(pepo, Mz, env) - end - return [magx, magz] -end - -Nr, Nc = 2, 2 -ham = adapt(CuArray, transverse_field_ising(Float64, Trivial, InfiniteSquare(Nr, Nc); J = 1.0, g = 2.0)) -pepo0 = adapt(CuArray, PEPSKit.infinite_temperature_density_matrix(ham)) -@test TensorKit.storagetype(ham) <: CuVector -@test TensorKit.storagetype(pepo0) <: CuVector -wts0 = SUWeight(pepo0) - -# Buildkite's GPU has less memory (~4GB) than most workstations, -# so the most memory-hungry block below # is skipped there; -# see the comment on the purification block for the details. -const CI_GPU = get(ENV, "BUILDKITE", "false") == "true" - -trunc_pepo = truncrank(8) & truncerror(; atol = 1.0e-12) - -dt, nstep = 1.0e-3, 400 -β = dt * nstep - -# when g = 2, β = 0.4 and 2β = 0.8 belong to two phases (without and with nonzero σᶻ) -@testset "Finite-T SU (force_mpo = $(force_mpo))" for force_mpo in (false, true) - GC.gc(); CUDA.reclaim() # release the previous iteration's device pool - # use second order Trotter decomposition - symmetrize_gates = true - bipartite = true - - # PEPO approach: results at β, or T = 2.5 - alg = SimpleUpdate(; trunc = trunc_pepo, purified = false, bipartite, force_mpo) - pepo, wts, info = time_evolve(pepo0, ham, dt, nstep, alg, wts0; symmetrize_gates) - @test storagetype(pepo) <: CuArray - - ## BP gauge fixing - bp_alg = BeliefPropagation(; maxiter = 100, tol = 1.0e-9, bipartite) - bp_env₀ = BPEnv(ones, Float64, pepo) - @test storagetype(bp_env₀) <: CuArray - bp_env, = leading_boundary(bp_env₀, pepo, bp_alg) - pepo, = gauge_fix(pepo, BPGauge(), bp_env) - - env = converge_env(InfinitePartitionFunction(pepo), 16) - result_β = measure_mag(pepo, env) - @info "tr(σ(x,z)ρ) at T = $(1 / β): $(result_β)." - @test β ≈ info.t - @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) - GC.gc(); CUDA.reclaim() # release the previous block's device pool - - # use `compress` to reach 2β, or T = 1.25 - pepo2, = compress((pepo, pepo), LocalTruncation(trunc_pepo)) - normalize!.(pepo2.A) - env2 = converge_env(InfinitePartitionFunction(pepo2), 16) - result_2β = measure_mag(pepo2, env2) - @info "tr(σ(x,z)ρ) at T = $(1 / (2β)): $(result_2β)." - @test isapprox(abs.(result_2β), bm_2β, rtol = 5.0e-3) - GC.gc(); CUDA.reclaim() # release the previous block's device pool - - # Purification approach: results at 2β, or T = 1.25. - # - # Skipped on Buildkite + CUDA: `converge_env` here contracts an `InfinitePEPS`, which is a - # *double-layer* network carrying both a ket and a bra virtual index per leg. With the - # PEPO bond dimension at `trunc_pepo` = 8 that makes the enlarged corners 512x512 - # (2.0 MiB) rather than the 128x128 (0.12 MiB) of the `InfinitePartitionFunction` - # contractions above, so one CTMRG iteration allocates ~1.5 GiB against ~160 MiB, and - # the block peaks around 13 GiB — more than the 4GiB CI GPU has! Lowering χ here - # wouldn't help much: it is already the smallest χ in this file, and the cost is - # dominated by the D² network legs rather than by χ. - if !CI_GPU - alg = SimpleUpdate(; trunc = trunc_pepo, purified = true, bipartite, force_mpo) - pepo, wts, info = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0; symmetrize_gates) - env = converge_env(InfinitePEPS(pepo), 8) - result_2β′ = measure_mag(pepo, env; purified = true) - @info "⟨ρ|σ(x,z)|ρ⟩ at T = $(1 / (2β)): $(result_2β′)." - @test 2 * β ≈ info.t - @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) - GC.gc(); CUDA.reclaim() # release the previous block's device pool - end -end diff --git a/test/cuda/timeevol/timestep.jl b/test/cuda/timeevol/timestep.jl deleted file mode 100644 index c99254566..000000000 --- a/test/cuda/timeevol/timestep.jl +++ /dev/null @@ -1,34 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using CUDA, Adapt - -@testset "SimpleUpdate timestep" begin - Nr, Nc = 2, 2 - H = adapt(CuArray, real(heisenberg_XYZ(ComplexF64, Trivial, InfiniteSquare(Nr, Nc); Jx = 1, Jy = 1, Jz = 1))) - Pspace, Vspace = ℂ^2, ℂ^4 - ψ0 = adapt(CuArray, InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc))) - env0 = adapt(CuArray, SUWeight(ψ0)) - alg = SimpleUpdate(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) - dt, nstep = 1.0e-2, 50 - # manual timestep - evolver = TimeEvolver(ψ0, H, dt, nstep, alg, env0) - ψ1, env1, info1 = deepcopy(ψ0), deepcopy(env0), nothing - for iter in 0:(nstep - 1) - ψ1, env1, info1 = timestep(evolver, ψ1, env1) - end - # time_evolve - ψ2, env2, info2 = time_evolve(ψ0, H, dt, nstep, alg, env0) - # for-loop syntax - ## manually reset internal state of evolver - evolver.state = PEPSKit.SUState(0, 0.0, ψ0, env0) - ψ3, env3, info3 = nothing, nothing, nothing - for state in evolver - ψ3, env3, info3 = state - end - # results should be *exactly* the same - @test ψ1 == ψ2 == ψ3 - @test env1 == env2 == env3 - @test info1 == info2 == info3 -end diff --git a/test/cuda/toolbox/densitymatrices.jl b/test/cuda/toolbox/densitymatrices.jl deleted file mode 100644 index 8ee47d574..000000000 --- a/test/cuda/toolbox/densitymatrices.jl +++ /dev/null @@ -1,79 +0,0 @@ -using TensorKit -using PEPSKit -using PEPSKit: contract_local_operator, contract_local_norm -using Test -using TestExtras -using CUDA, Adapt - -ds = Dict(Trivial => ℂ^2, U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), FermionParity => Vect[FermionParity](0 => 2, 1 => 1)) -Ds = Dict(Trivial => ℂ^3, U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 2, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) -χs = Dict(Trivial => ℂ^4, U1Irrep => U1Space(i => χ for (i, χ) in zip(-2:2, (1, 3, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) - -@testset "Single-layer densitymatrix contractions ($I)" for I in keys(ds) - d = ds[I] - D = Ds[I] - χ = χs[I] - ρ = adapt(CuArray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) - - ρ_pf = @constinferred InfinitePartitionFunction(ρ) - env = CTMRGEnv(adapt(CuArray, ρ_pf), χ) - - O = adapt(CuArray, rand(d, d)) - @plansor O_pf[W S; N E] := O[p'; p] * ρ[1, 1, 1][p p'; N E S W] - - # Single site - O_singlesite = adapt(CuArray, LocalOperator(physicalspace(ρ), ((1, 1),) => O)) - E1 = expectation_value(ρ, O_singlesite, env) - E2 = expectation_value(ρ_pf, CartesianIndex(1, 1) => O_pf, env) - @test E1 ≈ E2 - - # two sites - for inds in zip( - [(1, 1), (1, 1), (1, 1), (1, 2), (1, 1)], - [(2, 1), (1, 2), (2, 2), (2, 1), (3, 1)] - ) - O_twosite = adapt(CuArray, LocalOperator(physicalspace(ρ), inds => O ⊗ O)) - E3 = expectation_value(ρ, O_twosite, env) - # TODO: not defined for partition functions... - end -end - -@testset "Double-layer densitymatrix contractions ($I)" for I in keys(ds) - d = ds[I] - D = Ds[I] - χ = χs[I] - ρ = adapt(CuArray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) - - ρ_peps = @constinferred InfinitePEPS(ρ) - env = CTMRGEnv(adapt(CuArray, ρ_peps), χ) - - O = adapt(CuArray, rand(d, d)) - F = adapt(CuArray, isomorphism(fuse(d ⊗ d'), d ⊗ d')) - @tensor O_doubled[-1; -2] := F[-1; 1 2] * O[1; 3] * twist(F', 2)[3 2; -2] - - # Single site - site = (1, 1) - O_singlesite = adapt(CuArray, LocalOperator(physicalspace(ρ), (site,) => O)) - E1 = expectation_value(ρ, O_singlesite, ρ, env) - O_doubled_singlesite = adapt(CuArray, LocalOperator(physicalspace(ρ_peps), (site,) => O_doubled)) - E2 = expectation_value(ρ_peps, O_doubled_singlesite, ρ_peps, env) - @test E1 ≈ E2 - val = contract_local_operator([site], O_doubled, ρ_peps, ρ_peps, env) - nrm = contract_local_norm([site], ρ_peps, ρ_peps, env) - @test E1 ≈ val / nrm - - # two sites - for inds in zip( - [(1, 1), (1, 1), (1, 1), (1, 2), (1, 1)], - [(2, 1), (1, 2), (2, 2), (2, 1), (3, 1)] - ) - O_twosite = adapt(CuArray, LocalOperator(physicalspace(ρ), inds => O ⊗ O)) - E1 = expectation_value(ρ, O_twosite, ρ, env) - O_doubled_twosite = adapt(CuArray, LocalOperator(physicalspace(ρ_peps), inds => O_doubled ⊗ O_doubled)) - E2 = expectation_value(ρ_peps, O_doubled_twosite, ρ_peps, env) - @test E1 ≈ E2 - val = contract_local_operator(collect(inds), O_doubled ⊗ O_doubled, ρ_peps, ρ_peps, env) - nrm = contract_local_norm(collect(inds), ρ_peps, ρ_peps, env) - @test E1 ≈ val / nrm - end -end diff --git a/test/cuda/utility/correlator.jl b/test/cuda/utility/correlator.jl deleted file mode 100644 index 28d252ae9..000000000 --- a/test/cuda/utility/correlator.jl +++ /dev/null @@ -1,93 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using CUDA, Adapt - -const syms = (Z2Irrep, FermionParity) - -function get_spaces(sym::Type{<:Sector}) - @assert sym in syms - Nr, Nc = 2, 2 - Vphy = Vect[sym](0 => 1, 1 => 1) - V = Vect[sym](0 => 1, 1 => 2) - Venv = Vect[sym](0 => 2, 1 => 2) - Nspaces = [V' V; V V'] - Espaces = [V V'; V' V] - return Vphy, Venv, Nspaces, Espaces -end - -site0 = CartesianIndex(1, 1) -site1xs = collect(site0 + CartesianIndex(0, i) for i in [1, -1, 3, -2]) -site1ys = collect(site0 + CartesianIndex(i, 0) for i in [1, -1, 3, -2]) - -@testset "Correlator in InfinitePEPS ($(sym))" for sym in syms - Random.seed!(100) - Vphy, Venv, Nspaces, Espaces = get_spaces(sym) - # TODO: test dual physical space - for Vp in [Vphy] - op = adapt(CuArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) - Pspaces = fill(Vp, size(Nspaces)) - peps = adapt(CuArray, InfinitePEPS(randn, ComplexF64, Pspaces, Nspaces, Espaces)) - env = CTMRGEnv(randn, ComplexF64, peps, Venv) - for site1s in (site1xs, site1ys) - vals1 = correlator(peps, op, site0, site1s, env) - vals2 = map(site1s) do site1 - O = LocalOperator(Pspaces, (site0, site1) => op) - return expectation_value(peps, O, env) - end - @info vals1 - @info vals2 - @test vals1 ≈ vals2 - end - @test_throws ArgumentError correlator(peps, op, site0, site0, env) - end -end - -@testset "Correlator in purified InfinitePEPO ($(sym))" for sym in syms - Random.seed!(100) - Vphy, Venv, Nspaces, Espaces = get_spaces(sym) - # TODO: test dual physical space - for Vp in [Vphy] - op = adapt(CuArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) - Pspaces = fill(Vp, size(Nspaces)) - pepo = adapt(CuArray, InfinitePEPO(randn, ComplexF64, Pspaces, Nspaces, Espaces)) - peps = InfinitePEPS(pepo) - env = CTMRGEnv(randn, ComplexF64, peps, Venv) - for site1s in (site1xs, site1ys) - vals1 = correlator(pepo, op, site0, site1s, pepo, env) - vals2 = map(site1s) do site1 - O = LocalOperator(Pspaces, (site0, site1) => op) - return expectation_value(pepo, O, pepo, env) - end - @info vals1 - @info vals2 - @test vals1 ≈ vals2 - end - @test_throws ArgumentError correlator(pepo, op, site0, site0, pepo, env) - end -end - -@testset "Correlator in 1-layer InfinitePEPO ($(sym))" for sym in syms - Random.seed!(100) - Vphy, Venv, Nspaces, Espaces = get_spaces(sym) - # TODO: test dual physical space - for Vp in [Vphy] - op = adapt(CuArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) - Pspaces = fill(Vp, size(Nspaces)) - pepo = adapt(CuArray, InfinitePEPO(randn, ComplexF64, Pspaces, Nspaces, Espaces)) - pf = InfinitePartitionFunction(pepo) - env = CTMRGEnv(randn, ComplexF64, pf, Venv) - for site1s in (site1xs, site1ys) - vals1 = correlator(pepo, op, site0, site1s, env) - vals2 = map(site1s) do site1 - O = LocalOperator(Pspaces, (site0, site1) => op) - return expectation_value(pepo, O, env) - end - @info vals1 - @info vals2 - @test vals1 ≈ vals2 - end - @test_throws ArgumentError correlator(pepo, op, site0, site0, env) - end -end diff --git a/test/cuda/utility/eigh_wrapper.jl b/test/cuda/utility/eigh_wrapper.jl deleted file mode 100644 index 12d604586..000000000 --- a/test/cuda/utility/eigh_wrapper.jl +++ /dev/null @@ -1,147 +0,0 @@ -using Test -using Random -using LinearAlgebra -using TensorKit -using ChainRulesCore, Zygote -using Accessors -using PEPSKit -using CUDA, Adapt -using MatrixAlgebraKit: TruncatedAlgorithm, diagview - -# Gauge-invariant loss function -function lossfun(A, alg, R = randn(space(A)), trunc = notrunc()) - alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) - D, V, = eigh_trunc(A, alg) - return real(dot(R, V * V')) + dot(D, D) # Overlap with random tensor R is gauge-invariant and differentiable -end - -dtype = ComplexF64 -n = 20 -χ = 10 -trunc = truncspace(ℂ^χ) -rtol = 1.0e-9 -Random.seed!(123456789) -r = adapt(CuArray, randn(dtype, ℂ^n, ℂ^n)) -r = 0.5 * (r + r') # make r Hermitian -R = adapt(CuArray, randn(space(r))) -R = 0.5 * (R + R') - -full_alg = EighAdjoint(; fwd_alg = (; alg = :DivideAndConquer), rrule_alg = (; alg = :FullPullback)) -trunc_alg = EighAdjoint(; fwd_alg = (; alg = :DivideAndConquer), rrule_alg = (; alg = :TruncPullback)) -iter_alg = EighAdjoint(; fwd_alg = (; alg = :Lanczos), rrule_alg = (; alg = :TruncPullback)) - -@testset "Non-truncated eigh" begin - l_full, g_full = withgradient(A -> lossfun(A, full_alg, R), r) - l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, R), r) - l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, R), r) - - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol -end - -@testset "Truncated eigh with χ=$χ" begin - l_full, g_full = withgradient(A -> lossfun(A, full_alg, R, trunc), r) - l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, R, trunc), r) - l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, R, trunc), r) - - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol -end - -@testset "Truncated eigh broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] - d, v = eigh_full(r) - d.data[1:2:n] .= d.data[2:2:n] # make every eigenvalue two-fold degenerate - r_degen = v * d * v' - - no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 - small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 - - l_only_cutoff, g_only_cutoff = withgradient( - A -> lossfun(A, alg, R, trunc), r_degen - ) # cutoff sets degenerate difference to zero - l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions - A -> lossfun(A, no_broadening_no_cutoff_alg, R, trunc), r_degen, - ) - l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions - A -> lossfun(A, small_broadening_alg, R, trunc), r_degen, - ) - - @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening - @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient - @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect -end - -symm_m, symm_n = 18, 24 -symm_space = Z2Space(0 => symm_m, 1 => symm_n) -symm_trspace = truncspace(Z2Space(0 => symm_m ÷ 2, 1 => symm_n ÷ 3)) -symm_r = adapt(CuArray, randn(dtype, symm_space, symm_space)) -symm_r = 0.5 * (symm_r + symm_r') -symm_R = adapt(CuArray, randn(dtype, space(symm_r))) -symm_R = 0.5 * (symm_R + symm_R') - -@testset "IterEig of symmetric tensors" begin - l_full, g_full = withgradient(A -> lossfun(A, full_alg, symm_R), symm_r) - l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, symm_R), symm_r) - l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, symm_R), symm_r) - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol - - l_full_tr, g_full_tr = withgradient( - A -> lossfun(A, full_alg, symm_R, symm_trspace), symm_r - ) - l_trunc_tr, g_trunc_tr = withgradient( - A -> lossfun(A, trunc_alg, symm_R, symm_trspace), symm_r - ) - l_iter_tr, g_iter_tr = withgradient( - A -> lossfun(A, iter_alg, symm_R, symm_trspace), symm_r - ) - @test l_full_tr ≈ l_trunc_tr ≈ l_iter_tr - @test g_full_tr[1] ≈ g_trunc_tr[1] rtol = rtol - @test g_full_tr[1] ≈ g_iter_tr[1] rtol = rtol - @test g_trunc_tr[1] ≈ g_iter_tr[1] rtol = rtol - - iter_alg_fallback = @set iter_alg.fwd_alg.fallback_threshold = 0.4 # Do dense decomposition in one block, sparse one in the other - l_iter_fb, g_iter_fb = withgradient( - A -> lossfun(A, iter_alg_fallback, symm_R, symm_trspace), symm_r - ) - @test l_iter_fb ≈ l_trunc_tr ≈ l_full_tr - @test g_full_tr[1] ≈ g_iter_fb[1] rtol = rtol - @test g_trunc_tr[1] ≈ g_iter_fb[1] rtol = rtol -end - -@testset "Truncated symmetric eigh broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] - d, v = eigh_full(symm_r) - # make every singular value in the 0-sector three-fold degenerate - b0 = diagview(block(d, Z2Irrep(0))) - b0[1:3:symm_m] .= b0[3:3:symm_m] - b0[2:3:symm_m] .= b0[3:3:symm_m] - # make every singular value in the 1-sector two-fold degenerate - b1 = diagview(block(d, Z2Irrep(1))) - b1[1:2:symm_n] .= b1[2:2:symm_n] - symm_r_degen = v * d * v' - - no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 - small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 - - l_only_cutoff, g_only_cutoff = withgradient( - A -> lossfun(A, alg, symm_R, symm_trspace), symm_r_degen - ) # cutoff sets degenerate difference to zero - l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions - A -> lossfun(A, no_broadening_no_cutoff_alg, symm_R, symm_trspace), - symm_r_degen, - ) - l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions - A -> lossfun(A, small_broadening_alg, symm_R, symm_trspace), - symm_r_degen, - ) - - @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening - @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient - @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect -end diff --git a/test/cuda/utility/retractions.jl b/test/cuda/utility/retractions.jl deleted file mode 100644 index a196ae15c..000000000 --- a/test/cuda/utility/retractions.jl +++ /dev/null @@ -1,34 +0,0 @@ -using Test -using Random -using LinearAlgebra -using TensorKit -using VectorInterface -using PEPSKit -using Adapt, CUDA - -dtype = ComplexF64 -Vphyss = [ℂ^2, U1Space(0 => 1, -1 => 1, 1 => 1)] -Vpepss = [ℂ^4, U1Space(0 => 2, -1 => 1, 1 => 1)] - -@testset "Norm-preserving tensor retractions for sectortype $(sectortype(Vphyss[i]))" for i in - eachindex( - Vphyss - ) - Vphys = Vphyss[i] - Vpeps = Vpepss[i] - peps_space = Vphys ← Vpeps ⊗ Vpeps ⊗ Vpeps' ⊗ Vpeps' - - α = 1.0e-1 * randn(Float64) - A = adapt(CuArray, randn(dtype, peps_space)) - normalized_A = scale(A, inv(norm(A))) - η = adapt(CuArray, randn(dtype, peps_space)) - ζ = adapt(CuArray, randn(dtype, peps_space)) - add!(η, normalized_A, -inner(normalized_A, η)) - add!(ζ, normalized_A, -inner(normalized_A, ζ)) - - A´, ξ = PEPSKit.norm_preserving_retract(A, η, α) - @test norm(A´) ≈ norm(A) rtol = 1.0e-12 - - PEPSKit.norm_preserving_transport!(ζ, A, η, α, A´) - @test inner(ζ, A´) ≈ 0 atol = 1.0e-12 -end diff --git a/test/cuda/utility/svd_wrapper.jl b/test/cuda/utility/svd_wrapper.jl deleted file mode 100644 index c813f7d3c..000000000 --- a/test/cuda/utility/svd_wrapper.jl +++ /dev/null @@ -1,189 +0,0 @@ -using Test -using Random -using LinearAlgebra -using TensorKit -using ChainRulesCore, Zygote -using Accessors -using PEPSKit -using Adapt, CUDA -using MatrixAlgebraKit: TruncatedAlgorithm, diagview, svd_trunc_no_error - -# Gauge-invariant loss function -function lossfun(svd_trunc_f, A, alg, R = randn(space(A)), trunc = notrunc()) - alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) - USV = svd_trunc_f(A, alg) - U, S, V = USV[1:3] # avoid looking at ϵ if present - return real(dot(R, U * V)) + dot(S, S) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n -end - -dtype = ComplexF64 -m, n = 20, 30 -χ = 12 -trunc = truncspace(ℂ^χ) -rtol = 1.0e-9 -Random.seed!(12345678) -r = adapt(CuArray, randn(dtype, ℂ^m, ℂ^n)) -R = adapt(CuArray, randn(space(r))) - -full_alg = SVDAdjoint(; rrule_alg = (; alg = :FullPullback, degeneracy_atol = 1.0e-13)) -trunc_alg = SVDAdjoint(; rrule_alg = (; alg = :TruncPullback, degeneracy_atol = 1.0e-13)) -iter_alg = SVDAdjoint(; fwd_alg = (; alg = :GKL)) - -@testset "Non-truncated SVD $f" for f in (svd_trunc, svd_trunc_no_error) - l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, R), r) - l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, R), r) - l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, R), r) - - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol -end - -@testset "Truncated SVD $f with χ=$χ" for f in (svd_trunc, svd_trunc_no_error) - l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, R, trunc), r) - l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, R, trunc), r) - l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, R, trunc), r) - - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol -end - -@testset "Truncated SVD broadening for $f, $(alg.rrule_alg)" for f in (svd_trunc, svd_trunc_no_error), alg in [full_alg, trunc_alg] - u, s, v, = svd_compact(r) - s.data[1:2:m] .= s.data[2:2:m] # make every singular value two-fold degenerate - r_degen = u * s * v - - no_broadening_no_cutoff_alg = @set full_alg.rrule_alg.degeneracy_atol = 1.0e-30 - small_broadening_alg = @set full_alg.rrule_alg.degeneracy_atol = 1.0e-13 - - l_only_cutoff, g_only_cutoff = withgradient( - A -> lossfun(f, A, full_alg, R, trunc), r_degen - ) # cutoff sets degenerate difference to zero - l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions - A -> lossfun(f, A, no_broadening_no_cutoff_alg, R, trunc), r_degen, - ) - l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions - A -> lossfun(f, A, small_broadening_alg, R, trunc), r_degen, - ) - - @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening - @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient - @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect -end - -symm_m, symm_n = 18, 24 -symm_space = Z2Space(0 => symm_m, 1 => symm_n) -symm_trspace = truncspace(Z2Space(0 => symm_m ÷ 2, 1 => symm_n ÷ 3)) -symm_r = adapt(CuArray, randn(dtype, symm_space, symm_space)) -symm_R = adapt(CuArray, randn(dtype, space(symm_r))) - -@testset "IterSVD of symmetric tensors $f" for f in (svd_trunc, svd_trunc_no_error) - l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, symm_R), symm_r) - l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, symm_R), symm_r) - l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, symm_R), symm_r) - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol - - l_full_tr, g_full_tr = withgradient( - A -> lossfun(f, A, full_alg, symm_R, symm_trspace), symm_r - ) - l_trunc_tr, g_trunc_tr = withgradient( - A -> lossfun(f, A, trunc_alg, symm_R, symm_trspace), symm_r - ) - l_iter_tr, g_iter_tr = withgradient( - A -> lossfun(f, A, iter_alg, symm_R, symm_trspace), symm_r - ) - @test l_full_tr ≈ l_trunc_tr ≈ l_iter_tr - @test g_full_tr[1] ≈ g_trunc_tr[1] rtol = rtol - @test g_full_tr[1] ≈ g_iter_tr[1] rtol = rtol - @test g_trunc_tr[1] ≈ g_iter_tr[1] rtol = rtol - - iter_alg_fallback = @set iter_alg.fwd_alg.fallback_threshold = 0.4 # Do dense decomposition in one block, sparse one in the other - l_iter_fb, g_iter_fb = withgradient( - A -> lossfun(f, A, iter_alg_fallback, symm_R, symm_trspace), symm_r - ) - @test l_iter_fb ≈ l_trunc_tr ≈ l_full_tr - @test g_full_tr[1] ≈ g_iter_fb[1] rtol = rtol - @test g_trunc_tr[1] ≈ g_iter_fb[1] rtol = rtol -end - -@testset "Truncated symmetric SVD broadening for $f, $(alg.rrule_alg)" for f in (svd_trunc, svd_trunc_no_error), alg in [full_alg, trunc_alg] - u, s, v, = svd_compact(symm_r) - # make every singular value in the 0-sector three-fold degenerate - b0 = diagview(block(s, Z2Irrep(0))) - b0[1:3:symm_m] .= b0[3:3:symm_m] - b0[2:3:symm_m] .= b0[3:3:symm_m] - # make every singular value in the 1-sector two-fold degenerate - b1 = diagview(block(s, Z2Irrep(1))) - b1[1:2:symm_n] .= b1[2:2:symm_n] - symm_r_degen = u * s * v - - no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 - small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 - - l_only_cutoff, g_only_cutoff = withgradient( - A -> lossfun(f, A, alg, symm_R, symm_trspace), symm_r_degen - ) # cutoff sets degenerate difference to zero - l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions - A -> lossfun(f, A, no_broadening_no_cutoff_alg, symm_R, symm_trspace), - symm_r_degen, - ) - l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions - A -> lossfun(f, A, small_broadening_alg, symm_R, symm_trspace), - symm_r_degen, - ) - - @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening - @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient - @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect -end - -# TODO: Add when IterSVD is implemented for HalfInfiniteEnv -# χbond = 2 -# χenv = 6 -# ctm_alg = CTMRG(; tol=1e-10, verbosity=2, svd_alg=SVDAdjoint()) -# Random.seed!(91283219347) -# H = heisenberg_XYZ(InfiniteSquare()) -# psi = InfinitePEPS(ComplexSpace(2), ComplexSpace(χbond)) -# env = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); -# hienv = HalfInfiniteEnv( -# env.corners[1], -# env.corners[2], -# env.edges[4], -# env.edges[1], -# env.edges[1], -# env.edges[2], -# psi[1], -# psi[1], -# psi[1], -# psi[1], -# ) -# hienv_dense = hienv() -# env_R = randn(space(hienv)) - -# svd_trunc!(hienv, iter_alg) - -# @testset "IterSVD with HalfInfiniteEnv function handle" begin -# # Equivalence of dense and sparse contractions -# x₀ = PEPSKit.random_start_vector(hienv) -# x′ = hienv(x₀, Val(false)) -# x″ = hienv(x′, Val(true)) -# x‴ = hienv(x″, Val(false)) - -# a = hienv_dense * x₀ -# b = hienv_dense' * a -# c = hienv_dense * b -# @test a ≈ x′ -# @test b ≈ x″ -# @test c ≈ x‴ - -# # l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, env_R), hienv_dense) -# # l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, env_R), hienv) -# # @test l_itersvd ≈ l_fullsvd -# # @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol -# end diff --git a/test/cuda/utility/symmetrization.jl b/test/cuda/utility/symmetrization.jl deleted file mode 100644 index be424230a..000000000 --- a/test/cuda/utility/symmetrization.jl +++ /dev/null @@ -1,53 +0,0 @@ -using Test -using PEPSKit -using PEPSKit: herm_depth, herm_width, _fit_spaces -using TensorKit -using Adapt, CUDA - -@testset "ReflectDepth" for unitcell in [(1, 1), (2, 2), (3, 3), (3, 2)] - peps = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) - - peps_depth = symmetrize!(deepcopy(peps), ReflectDepth()) - peps_reflect = _fit_spaces( - InfinitePEPS(reverse(map(herm_depth, peps_depth.A); dims = 1)), peps_depth - ) - @test peps_depth ≈ peps_reflect -end - -@testset "ReflectWidth" for unitcell in [(1, 1), (2, 2), (3, 3), (3, 2)] - peps = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) - - peps_width = symmetrize!(deepcopy(peps), ReflectWidth()) - peps_reflect = _fit_spaces( - InfinitePEPS(reverse(map(herm_width, peps_width.A); dims = 2)), peps_width - ) - @test peps_width ≈ peps_reflect -end - -@testset "Rotate" for unitcell in [(1, 1), (2, 2), (3, 3)] - peps = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) - - peps_rot = symmetrize!(deepcopy(peps), Rotate()) - @test peps_rot ≈ _fit_spaces(rotl90(peps_rot), peps_rot) - @test peps_rot ≈ _fit_spaces(rot180(peps_rot), peps_rot) - @test peps_rot ≈ _fit_spaces(rotr90(peps_rot), peps_rot) -end - -@testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] - peps = adapt(CuArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) - - peps_full = symmetrize!(deepcopy(peps), RotateReflect()) - @test peps_full ≈ _fit_spaces(rotl90(peps_full), peps_full) - @test peps_full ≈ _fit_spaces(rot180(peps_full), peps_full) - @test peps_full ≈ _fit_spaces(rotr90(peps_full), peps_full) - - peps_reflect_depth = _fit_spaces( - InfinitePEPS(reverse(map(herm_depth, peps_full.A); dims = 1)), peps_full - ) - @test peps_full ≈ peps_reflect_depth - - peps_reflect_width = _fit_spaces( - InfinitePEPS(reverse(map(herm_width, peps_full.A); dims = 2)), peps_full - ) - @test peps_full ≈ peps_reflect_width -end diff --git a/test/rocm/bondenv/benv_ctm.jl b/test/rocm/bondenv/benv_ctm.jl deleted file mode 100644 index 786dd6f7c..000000000 --- a/test/rocm/bondenv/benv_ctm.jl +++ /dev/null @@ -1,68 +0,0 @@ -using Test -using TensorKit -using PEPSKit -using LinearAlgebra -using Random -using AMDGPU, Adapt - -Random.seed!(100) -Nr, Nc = 2, 2 -Envspace = Vect[FermionParity ⊠ U1Irrep]( - (0, 0) => 4, (1, 1 // 2) => 1, (1, -1 // 2) => 1, (0, 1) => 1, (0, -1) => 1 -) -trunc_state = truncerror(; atol = 1.0e-10) & truncrank(4) -ctm_alg = SequentialCTMRG(; tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(8)) -# create Hubbard iPEPS using simple update -function get_hubbard_peps(t::Float64 = 1.0, U::Float64 = 8.0) - H = adapt(ROCArray, hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)) - Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) - peps = adapt(ROCArray, InfinitePEPS(rand, ComplexF64, Vphy, Vphy; unitcell = (Nr, Nc))) - wts = SUWeight(peps) - alg = SimpleUpdate(; trunc = trunc_state) - evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts) - peps, = time_evolve(evolver, H; tol = 1.0e-8, verbosity = 1, check_interval = 2000) - normalize!.(peps.A, Inf) - return peps -end - -function get_hubbard_pepo(t::Float64 = 1.0, U::Float64 = 8.0) - H = adapt(ROCArray, hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2)) - pepo = PEPSKit.infinite_temperature_density_matrix(H) - wts = SUWeight(pepo) - alg = SimpleUpdate(; trunc = trunc_state, bipartite = false) - pepo, = time_evolve(pepo, H, 2.0e-3, 500, alg, wts; verbosity = 1, check_interval = 100) - normalize!.(pepo.A, Inf) - return pepo -end - -function test_benv_ctm(state::Union{InfinitePEPS, InfinitePEPO}) - network = isa(state, InfinitePEPS) ? state : InfinitePEPS(state) - env, = leading_boundary(CTMRGEnv(rand, ComplexF64, network, Envspace), network, ctm_alg) - for row in 1:Nr, col in 1:Nc - cp1 = col + 1 - A, B = state[row, col], state[row, cp1] - a, X = PEPSKit.bond_tensor_first(A) - b, Y = PEPSKit.bond_tensor_last(B) - benv = PEPSKit.bondenv_ctm(row, col, X, Y, env) - Z = PEPSKit.positive_approx(benv) - # verify that gauge fixing can greatly reduce - # condition number for physical state bond envs - cond1 = cond(Z' * Z) - Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) - benv2 = Z2' * Z2 - cond2 = cond(benv2) - @test 1 <= cond2 < cond1 - @info "benv cond number: (gauge-fixed) $(cond2) ≤ $(cond1) (initial)" - # verify gauge fixing is done correctly - @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] - @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] - @test half ≈ half2 - end - return -end - -peps = get_hubbard_peps() -pepo = get_hubbard_pepo() -for state in (peps, pepo) - test_benv_ctm(state) -end diff --git a/test/rocm/bondenv/benv_gaugefix.jl b/test/rocm/bondenv/benv_gaugefix.jl deleted file mode 100644 index 818a79e80..000000000 --- a/test/rocm/bondenv/benv_gaugefix.jl +++ /dev/null @@ -1,44 +0,0 @@ -using Test -using TensorKit -using PEPSKit -using LinearAlgebra -using Random -using AMDGPU, Adapt - -Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 1, (1, -1) => 2) -Vin = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 3, (1, -1) => 2) -V = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 2, (1, -1) => 3) -Vs = (V, V') -for V1 in Vs, V2 in Vs, V3 in Vs - #= - ┌---┬---------------┬---┐ - | | | | ┌--------------┐ - ├---X--- -2 -3 ---Y---┤ = | | - | | | | └--Z-- -2 -3 -┘ - └---┴-------Z0------┴---┘ ↓ - ↓ -1 - -1 - =# - X = adapt(ROCArray, rand(ComplexF64, Vin ⊗ V1' ⊗ Vin' ⊗ Vin)) - Y = adapt(ROCArray, rand(ComplexF64, Vin ⊗ Vin ⊗ Vin' ⊗ V3)) - Z0 = adapt(ROCArray, randn(ComplexF64, Vphy ← Vin ⊗ Vin' ⊗ Vin ⊗ Vin ⊗ Vin ⊗ Vin')) - @tensor Z[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X[Xn Xe Xs Xw] * Y[Yn Ye Ys Yw] - #= - ┌---------------------------┐ - | | - └---Z-- 1 --a-- 2 --b-- 3 --┘ - ↓ ↓ ↓ - -1 -2 -3 - =# - a = adapt(ROCArray, randn(ComplexF64, V1 ⊗ Vphy ← V2)) - b = adapt(ROCArray, randn(ComplexF64, V2 ⊗ Vphy ← V3)) - @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] - Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) - @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] - @test half ≈ half2 - # test gauge transformation of X, Y - X2 = PEPSKit._fixgauge_benvX(X, Rinv) - Y2 = PEPSKit._fixgauge_benvY(Y, Linv) - @tensor Z2_[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X2[Xn Xe Xs Xw] * Y2[Yn Ye Ys Yw] - @test Z2 ≈ Z2_ -end diff --git a/test/rocm/bondenv/bond_truncate.jl b/test/rocm/bondenv/bond_truncate.jl deleted file mode 100644 index c49ce8eb6..000000000 --- a/test/rocm/bondenv/bond_truncate.jl +++ /dev/null @@ -1,54 +0,0 @@ -using Random -using Test -using TensorKit -using PEPSKit -using LinearAlgebra -using PEPSKit: bond_truncate, cost_function_als -using PEPSKit: _combine_ket, _combine_ket_for_svd -using AMDGPU, Adapt - -Random.seed!(0) -maxiter = 600 -check_interval = 30 -elt = ComplexF64 -# simulating the situation of applying a 2-site gate -# to a bond with virtual dimension D, physical dimension d. -d, D = 2, 4 -trunc = truncerror(; atol = 1.0e-10) & truncrank(D) -Vphy = Vect[FermionParity](0 => div(d, 2), 1 => div(d, 2)) -Vqro = Vect[FermionParity](0 => div(d * D, 2), 1 => div(d * D, 2)) -# virtual dimension of gate MPO is d^2 -Vint = Vect[FermionParity](0 => div(d^2 * D, 2), 1 => div(d^2 * D, 2)) -for Vl in (Vqro, Vqro'), Vr in (Vqro, Vqro') - # random positive-definite environment - Vbond = Vl ⊗ Vr - Dext = dim(Vbond) - Vext = Vect[FermionParity](0 => div(Dext, 2) + 1, 1 => div(Dext, 2) + 1) - Z = adapt(ROCArray, randn(elt, Vext ← Vbond)) - normalize!(Z, Inf) - benv = Z' * Z - @info "Dimension of benv = $(Dext)" - # untruncated bond tensors - a2 = adapt(ROCArray, randn(elt, Vl ⊗ Vphy ← Vint)) - b2 = adapt(ROCArray, randn(elt, Vint ⊗ Vphy ← Vr')) - # bond tensor (truncated SVD initialization) - a2b2 = _combine_ket(a2, b2) - a0, s, b0 = svd_trunc(permute(a2b2, ((1, 3), (4, 2))); trunc = trunc) - a0, b0 = PEPSKit.absorb_s(a0, s, b0) - b0 = permute(b0, ((1, 2), (3,))) - fid0 = cost_function_als(benv, _combine_ket(a0, b0), a2b2)[2] - @info "Fidelity of simple SVD truncation = $fid0.\n" - ss = Dict{String, DiagonalTensorMap}() - # FET is slower when d is large - for (label, alg) in ( - ("ALS", ALSTruncation(; trunc, maxiter, check_interval)), - ("FET", FullEnvTruncation(; trunc, maxiter, check_interval, trunc_init = false)), - ) - a1, ss[label], b1, info = bond_truncate(a2, b2, benv, alg) - @info "$label improved fidelity = $(info.fid)." - # display(ss[label]) - @test info.fid ≈ cost_function_als(benv, _combine_ket(a1, b1), a2b2)[2] - @test info.fid > fid0 - end - @test isapprox(ss["ALS"], ss["FET"], atol = 1.0e-3) -end diff --git a/test/rocm/boundarymps/vumps.jl b/test/rocm/boundarymps/vumps.jl deleted file mode 100644 index 2e5cebd4f..000000000 --- a/test/rocm/boundarymps/vumps.jl +++ /dev/null @@ -1,129 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using MPSKit -using LinearAlgebra -using Adapt, AMDGPU - -Random.seed!(29384293742893) - -const vumps_alg = VUMPS(; - tol = 1.0e-6, alg_eigsolve = MPSKit.Defaults.alg_eigsolve(; ishermitian = false), verbosity = 2 -) - -@testset "(1, 1) PEPS" begin - Vpeps = ComplexSpace(2) - psi = adapt(ROCArray, InfinitePEPS(Vpeps, Vpeps)) - - T = adapt(ROCArray, PEPSKit.InfiniteTransferPEPS(psi, 1, 1)) - foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) - mps = adapt(ROCArray, initialize_mps(T, [ComplexSpace(20)])) - - mps, env, ϵ = leading_boundary(mps, T, vumps_alg) - N = abs(sum(expectation_value(mps, T))) - - mps2, = changebonds(mps, T, OptimalExpand(; trunc = truncrank(30))) - mps2, env2, ϵ = leading_boundary(mps2, T, vumps_alg) - N2 = abs(sum(expectation_value(mps2, T))) - @test N ≈ N2 rtol = 1.0e-2 - - ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) - N´ = abs(norm(psi, ctm)) - - @test N ≈ N´ atol = 1.0e-3 -end - -@testset "(2, 2) PEPS" begin - Vpeps = ComplexSpace(2) - psi = adapt(ROCArray, InfinitePEPS(Vpeps, Vpeps; unitcell = (2, 2))) - T = adapt(ROCArray, PEPSKit.MultilineTransferPEPS(psi, 1)) - @test storagetype(T) <: ROCArray - # foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) # TODO: MPSKit.physicalspace(::MultilineMPO) isn't implemented... - mps = adapt(ROCArray, initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2))) - @test storagetype(mps) <: ROCArray - mps, env, ϵ = leading_boundary(mps, T, vumps_alg) - N = abs(prod(expectation_value(mps, T))) - - ctm, = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi) - N´ = abs(norm(psi, ctm)) - - @test N ≈ N´ rtol = 1.0e-2 -end - -#=@testset "Fermionic PEPS" begin - D = Vect[fℤ₂](0 => 1, 1 => 1) - d = Vect[fℤ₂](0 => 1, 1 => 1) - χ = Vect[fℤ₂](0 => 10, 1 => 10) - - psi = adapt(ROCArray, InfinitePEPS(D, d; unitcell = (1, 1))) - n = adapt(ROCArray, InfiniteSquareNetwork(psi)) - T = adapt(ROCArray, InfiniteTransferPEPS(psi, 1, 1)) - foreach(V -> (@test V == D ⊗ D'), physicalspace(T)) - - # compare boundary MPS contraction to CTMRG contraction - mps = adapt(ROCArray, initialize_mps(T, [χ])) - mps, env, ϵ = leading_boundary(mps, T, vumps_alg) - N_vumps = abs(prod(expectation_value(mps, T))) - - ctm, = leading_boundary(CTMRGEnv(psi, χ), psi) - N_ctm = abs(norm(psi, ctm)) - - @test N_vumps ≈ N_ctm rtol = 1.0e-2 - - # and again after blocking the local sandwiches - n´ = adapt(ROCArray, InfiniteSquareNetwork(map(PEPSKit.mpotensor, PEPSKit.unitcell(n)))) - T´ = adapt(ROCArray, InfiniteMPO(map(PEPSKit.mpotensor, T.O))) - foreach(V -> (@test V == fuse(D, D')), physicalspace(T´)) - - mps´ = adapt(ROCArray, InfiniteMPS(randn, ComplexF64, [physicalspace(T´, 1)], [χ])) - mps´, env´, ϵ = leading_boundary(mps´, T´, vumps_alg) - N_vumps´ = abs(prod(expectation_value(mps´, T´))) - - ctm´, = leading_boundary(CTMRGEnv(n´, χ), n´) - N_ctm´ = abs(network_value(n´, ctm´)) - - @show N_vumps´ - @test N_vumps´ ≈ N_vumps rtol = 1.0e-2 - @test N_vumps´ ≈ N_ctm´ rtol = 1.0e-2 -end=# - -@testset "PEPO runthrough" begin - function ising_pepo(beta; unitcell = (1, 1, 1)) - t = ComplexF64[exp(beta) exp(-beta); exp(-beta) exp(beta)] - q = sqrt(t) - - O = zeros(2, 2, 2, 2, 2, 2) - O[1, 1, 1, 1, 1, 1] = 1 - O[2, 2, 2, 2, 2, 2] = 1 - @tensor o[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - - O = TensorMap(o, ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)') - - return adapt(ROCArray, InfinitePEPO(O; unitcell)) - end - - Vpepo = ComplexSpace(2) - Vpeps = ComplexSpace(2) - - # single-layer PEPO - O = ising_pepo(1) - psi = adapt(ROCArray, PEPSKit.initializePEPS(O, Vpeps)) - T = adapt(ROCArray, InfiniteTransferPEPO(psi, O, 1, 1)) - foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpeps'), physicalspace(T)) - - mps = adapt(ROCArray, initialize_mps(rand, scalartype(T), T, [ComplexSpace(10)])) - mps, env, ϵ = leading_boundary(mps, T, vumps_alg) - f = abs(prod(expectation_value(mps, T))) - - # double-layer PEPO - O2 = repeat(O, 1, 1, 2) - psi2 = adapt(ROCArray, initializePEPS(O2, Vpeps)) - T2 = adapt(ROCArray, InfiniteTransferPEPO(psi, O2, 1, 1)) - foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpepo ⊗ Vpeps'), physicalspace(T2)) - - mps2 = adapt(ROCArray, initialize_mps(rand, scalartype(T2), T2, [ComplexSpace(8)])) - mps2, env2, ϵ = leading_boundary(mps2, T2, vumps_alg) - f = abs(prod(expectation_value(mps2, T2))) -end diff --git a/test/rocm/bp/expvals.jl b/test/rocm/bp/expvals.jl deleted file mode 100644 index b732f46d3..000000000 --- a/test/rocm/bp/expvals.jl +++ /dev/null @@ -1,55 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using PEPSKit: random_dual! -using AMDGPU, Adapt - -ds = Dict( - U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), - FermionParity => Vect[FermionParity](0 => 2, 1 => 1) -) -Ds = Dict( - U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 3, 2))), - FermionParity => Vect[FermionParity](0 => 3, 1 => 2) -) -Random.seed!(41973582) - -@testset "Expectation values of BPEnv ($S)" for S in keys(ds) - d, D, uc = ds[S], Ds[S], (2, 3) - ψds = fill(d, uc) - ψDNs = random_dual!(fill(D, uc)) - ψDEs = random_dual!(fill(D, uc)) - ψ0 = adapt(ROCArray, InfinitePEPS(ψds, ψDNs, ψDEs)) - - ψ, wts, _ = gauge_fix(ψ0, SUGauge(; maxiter = 100, tol = 1.0e-10)) - for (a0, a) in zip(ψ0.A, ψ.A) - @test space(a0) == space(a) - end - bp_env = BPEnv(wts) - ctm_env = CTMRGEnv(wts) - @test ctm_env ≈ CTMRGEnv(bp_env) - - # SU fixed point wts should already be a BP fixed point of ψ - bp_alg = BeliefPropagation(; miniter = 1, maxiter = 1, tol = 1.0e-7) - _, err = leading_boundary(bp_env, ψ, bp_alg) - @test err < 1.0e-9 - - op = randn(d → d) - for site in CartesianIndices(size(ψ)) - lo = adapt(ROCArray, LocalOperator(ψds, (site,) => op)) - val1 = expectation_value(ψ, lo, bp_env) - val2 = expectation_value(ψ, lo, ctm_env) - @test val1 ≈ val2 - end - - op = randn(d ⊗ d → d ⊗ d) - vs = [CartesianIndex(1, 0), CartesianIndex(0, 1)] - for site1 in CartesianIndices(size(ψ)), v in vs - site2 = site1 + v - lo = adapt(ROCArray, LocalOperator(ψds, (site1, site2) => op)) - val1 = expectation_value(ψ, lo, bp_env) - val2 = expectation_value(ψ, lo, ctm_env) - @test val1 ≈ val2 - end -end diff --git a/test/rocm/bp/rotation.jl b/test/rocm/bp/rotation.jl deleted file mode 100644 index e4be19528..000000000 --- a/test/rocm/bp/rotation.jl +++ /dev/null @@ -1,46 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using PEPSKit: random_dual! -using AMDGPU, Adapt - -ds = Dict( - Trivial => ℂ^2, - U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), - FermionParity => Vect[FermionParity](0 => 2, 1 => 1) -) -Ds = Dict( - Trivial => ℂ^3, - U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 3, 2))), - FermionParity => Vect[FermionParity](0 => 3, 1 => 2) -) -Random.seed!(41973582) - -function meas_sites( - op::O, ψ::InfinitePEPS, env::Union{BPEnv, CTMRGEnv} - ) where {O <: AbstractTensorMap{<:Any, <:Any, 1, 1}} - lattice = physicalspace(ψ) - return map(eachindex(ψ)) do site1 - lo = LocalOperator(lattice, (site1,) => op) - return expectation_value(ψ, lo, env) - end -end - -@testset "Rotation of BPEnv ($S)" for S in keys(ds) - d, D, unitcell = ds[S], Ds[S], (2, 3) - ψds = fill(d, unitcell) - ψDNs = random_dual!(fill(D, unitcell)) - ψDEs = random_dual!(fill(D, unitcell)) - ψ = adapt(ROCArray, InfinitePEPS(ψds, ψDNs, ψDEs)) - env = BPEnv(ψ) - - op = adapt(ROCArray, randn(d → d)) - meas1 = meas_sites(op, ψ, env) - # rotated peps and env - for f in (rotl90, rotr90, rot180) - ψ′, env′ = f(ψ), f(env) - meas1′ = meas_sites(op, ψ′, env′) - @test meas1′ ≈ f(meas1) - end -end diff --git a/test/rocm/bp/unitcell.jl b/test/rocm/bp/unitcell.jl deleted file mode 100644 index 8214d2f86..000000000 --- a/test/rocm/bp/unitcell.jl +++ /dev/null @@ -1,102 +0,0 @@ -using Test -using Random -using PEPSKit -using PEPSKit: bp_iteration -using TensorKit -using AMDGPU, Adapt - -# settings -Random.seed!(91283219347) -elt = ComplexF64 - -function test_unitcell(unitcell, Pspaces, Nspaces, Espaces) - peps = adapt(ROCArray, InfinitePEPS(randn, elt, Pspaces, Nspaces, Espaces)) - env0 = BPEnv(ones, elt, peps) - alg = BeliefPropagation() - - # apply one BP iteration - network = InfiniteSquareNetwork(peps) - env1 = bp_iteration(network, env0, alg) - # another iteration to detect bond mismatches - env1 = bp_iteration(network, env1, alg) - - # compute random expecation value to test matching bonds - random_op = adapt( - ROCArray, LocalOperator( - Pspaces, ( - (c,) => randn(elt, Pspaces[c], Pspaces[c]) - for c in CartesianIndices(unitcell) - )..., - ) - ) - @test storagetype(random_op) <: ROCArray - @test expectation_value(peps, random_op, env0) isa Number - @test expectation_value(peps, random_op, env1) isa Number - return -end - -@testset "Random Cartesian spaces with BP" begin - unitcell = (3, 3) - - Pspaces = ComplexSpace.(rand(2:3, unitcell...)) - Nspaces = ComplexSpace.(rand(2:4, unitcell...)) - Espaces = ComplexSpace.(rand(2:4, unitcell...)) - - test_unitcell(unitcell, Pspaces, Nspaces, Espaces) -end - -@testset "Specific U1 spaces with BP" begin - unitcell = (2, 2) - - PA = U1Space(-1 => 1, 0 => 1) - PB = U1Space(0 => 1, 1 => 1) - Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) - Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) - - Pspaces = [PA PB; PB PA] - Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] - - test_unitcell(unitcell, Pspaces, Nspaces, Nspaces) - - # 4x4 unit cell with all 32 inequivalent bonds - # - # 10 4 7 32 - # | | | | - # 3--A--1--B--5--C--8--D--3 - # | | | | - # 2 6 9 11 - # | | | | - # 14--E-12--F-15--G-17--H-14 - # | | | | - # 13 16 18 19 - # | | | | - # 22--I-20--J-23--K-25--L-22 - # | | | | - # 21 24 26 27 - # | | | | - # 29--M-28--N-30--O-31--P-29 - # | | | | - # 10 4 7 32 - - phys_space = Vect[U1Irrep](1 => 1, -1 => 1) - corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) - vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) - @test length(Set(vspaces)) == 32 - - Espaces = [ - vspaces[1] vspaces[5] vspaces[8] vspaces[3] - vspaces[12] vspaces[15] vspaces[17] vspaces[14] - vspaces[20] vspaces[23] vspaces[25] vspaces[22] - vspaces[28] vspaces[30] vspaces[31] vspaces[29] - ] - - Nspaces = [ - vspaces[10] vspaces[4] vspaces[7] vspaces[32] - vspaces[2] vspaces[6] vspaces[9] vspaces[11] - vspaces[13] vspaces[16] vspaces[18] vspaces[19] - vspaces[21] vspaces[24] vspaces[26] vspaces[27] - ] - Pspaces = fill(phys_space, (4, 4)) - - test_unitcell(unitcell, Pspaces, Nspaces, Nspaces) -end diff --git a/test/rocm/compress/local.jl b/test/rocm/compress/local.jl deleted file mode 100644 index 5a97c8d85..000000000 --- a/test/rocm/compress/local.jl +++ /dev/null @@ -1,62 +0,0 @@ -using Test -using Random -using LinearAlgebra -using TensorKit -using PEPSKit -using PEPSKit: virtual_projector -using AMDGPU, Adapt - -""" -Cost function of LocalTruncation. -For test convenience, open virtual indices are made trivial and removed. -""" -function localcompress_cost(A1, A2, B1, B2, P1, P2) - @tensor net1[pa1 pb1; pa2′ pb2′] := - A1[pa1 pa; D1] * A2[pa pa2′; D2] * B1[pb1 pb; D1] * B2[pb pb2′; D2] - @tensor net2[pa1 pb1; pa2′ pb2′] := P1[Da1 Da2; D] * P2[D; Db1 Db2] * - A1[pa1 pa; Da1] * A2[pa pa2′; Da2] * B1[pb1 pb; Db1] * B2[pb pb2′; Db2] - return norm(net1 - net2) -end - -@testset "Fermionic twists" begin - Vphy = Vect[FermionParity](0 => 2, 1 => 2) - Vvir = Vect[FermionParity](0 => 2, 1 => 2) - for _ in 1:4 # multiple trials without setting seed - Aspace = (Vphy ⊗ Vphy' ← Vvir ⊗ Vvir ⊗ Vvir' ⊗ Vvir') - A1 = adapt(ROCArray, randn(ComplexF64, Aspace)) - A2 = adapt(ROCArray, randn(ComplexF64, Aspace)) - for MM in [PEPSKit._get_MMdag(A1, A2), PEPSKit._get_MdagM(A1, A2)] - @test isposdef(MM) - end - end -end - -@testset "Cost function of LocalTruncation" begin - Random.seed!(0) - Vaux, Vphy, V = ℂ^1, ℂ^10, ℂ^4 - A1 = adapt(ROCArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ V ⊗ Vaux' ⊗ Vaux'), Inf)) - A2 = adapt(ROCArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ V ⊗ Vaux' ⊗ Vaux'), Inf)) - B1 = adapt(ROCArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ Vaux ⊗ Vaux' ⊗ V'), Inf)) - B2 = adapt(ROCArray, normalize(randn(Vphy ⊗ Vphy' ← Vaux ⊗ Vaux ⊗ Vaux' ⊗ V'), Inf)) - - P1, P2, info = virtual_projector(A1, A2, B1, B2; trunc = notrunc()) - @test P1 * P2 ≈ adapt(ROCArray, TensorKit.id(domain(P2))) - - P1, P2, info = virtual_projector(A1, A2, B1, B2; trunc = truncrank(8)) - A1 = removeunit(removeunit(removeunit(A1, 6), 5), 3) - A2 = removeunit(removeunit(removeunit(A2, 6), 5), 3) - B1 = removeunit(removeunit(removeunit(B1, 5), 4), 3) - B2 = removeunit(removeunit(removeunit(B2, 5), 4), 3) - @info "Truncation error = $(info.ϵ)." - @test info.ϵ ≈ localcompress_cost(A1, A2, B1, B2, P1, P2) -end - -@testset "Virtual space matching" begin - Vps = ComplexSpace.([2 2; 2 2]) - Vns = ComplexSpace.([2 4; 5 3]) - Ves = ComplexSpace.([3 5; 4 2]) - ρ = adapt(ROCArray, InfinitePEPO(randn, ComplexF64, Vps, Vns, Ves)) - alg = LocalTruncation(truncrank(2)) - ρ2, = compress((ρ, ρ), alg) - @test ρ2 isa InfinitePEPO -end diff --git a/test/rocm/ctmrg/contractions.jl b/test/rocm/ctmrg/contractions.jl deleted file mode 100644 index 8060bc3ae..000000000 --- a/test/rocm/ctmrg/contractions.jl +++ /dev/null @@ -1,315 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using AMDGPU, Adapt - -using PEPSKit: eachcoordinate, _next_coordinate -using PEPSKit: EnlargedCorner, HalfInfiniteEnv, FullInfiniteEnv -using PEPSKit: half_infinite_environment, full_infinite_environment -using PEPSKit: simultaneous_projectors, contract_projectors -using PEPSKit: renormalize_northwest_corner, renormalize_northeast_corner, - renormalize_southeast_corner, renormalize_southwest_corner -using PEPSKit: random_start_vector - -# settings -Random.seed!(91283219348) -stype = ComplexF64 - -renormalize_corner_fns = ( - renormalize_northwest_corner, renormalize_northeast_corner, - renormalize_southeast_corner, renormalize_southwest_corner, -) - -function test_ctmrg_contractions( - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) - - @testset "CTMRG PEPS contractions" begin - peps = adapt(ROCArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) - env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) - - n = InfiniteSquareNetwork(peps) - - @test storagetype(peps) <: ROCArray - @test storagetype(env) <: ROCArray - @test storagetype(n) <: ROCArray - test_contractions(n, env) - end - - @testset "CTMRG PartitionFunction contractions" begin - pf = adapt(ROCArray, InfinitePartitionFunction(randn, stype, Nspaces, Espaces)) - env = CTMRGEnv(randn, stype, pf, chis_north, chis_east, chis_south, chis_west) - n = InfiniteSquareNetwork(pf) - @test storagetype(pf) <: ROCArray - @test storagetype(env) <: ROCArray - @test storagetype(n) <: ROCArray - - test_contractions(n, env) - end - - @testset "CTMRG PEPO contractions" begin - pepo = adapt(ROCArray, InfinitePEPO(randn, stype, Pspaces, Pspaces, Pspaces)) - peps = adapt(ROCArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) - n = InfiniteSquareNetwork(peps, pepo) - env = adapt(ROCArray, CTMRGEnv(randn, stype, n, chis_north, chis_east, chis_south, chis_west)) - @test storagetype(peps) <: ROCArray - @test storagetype(pepo) <: ROCArray - @test storagetype(env) <: ROCArray - @test storagetype(n) <: ROCArray - - test_contractions(n, env) - end - - return nothing -end - -function test_contractions(n::InfiniteSquareNetwork, env::CTMRGEnv) - dirs_and_coordinates = eachcoordinate(n, 1:4) - - # initialize dense and sparse enlarged corners - sparse_enlarged_corners = map(dirs_and_coordinates) do co - return EnlargedCorner(n, env, co) - end - dense_enlarged_corners = map(TensorMap, sparse_enlarged_corners) - - # initialize sparse and dense half-inifite environments - sparse_halfinf_envs = map(dirs_and_coordinates) do co - co´ = _next_coordinate(co, size(env)[2:3]...) - return HalfInfiniteEnv( - sparse_enlarged_corners[co...], sparse_enlarged_corners[co´...] - ) - end - dense_halfinf_envs = map(TensorMap, sparse_halfinf_envs) - # also compute directly from dense enlarged corners, for consistency with current implementation - dense_halfinf_envs_bis = map(dirs_and_coordinates) do co - co´ = _next_coordinate(co, size(env)[2:3]...) - return half_infinite_environment( - dense_enlarged_corners[co...], dense_enlarged_corners[co´...] - ) - end - - # initialize sparse and dense full-inifite environments - sparse_fullinf_envs = map(dirs_and_coordinates) do co - rowsize, colsize = size(env)[2:3] - co2 = _next_coordinate(co, rowsize, colsize) - co3 = _next_coordinate(co2, rowsize, colsize) - co4 = _next_coordinate(co3, rowsize, colsize) - return FullInfiniteEnv( - sparse_enlarged_corners[co4...], - sparse_enlarged_corners[co...], - sparse_enlarged_corners[co2...], - sparse_enlarged_corners[co3...], - ) - end - dense_fullinf_envs = map(TensorMap, sparse_fullinf_envs) - # also compute directly from dense enlarged corners, for consistency with current implementation - dense_fullinf_envs_bis = map(dirs_and_coordinates) do co - rowsize, colsize = size(env)[2:3] - co2 = _next_coordinate(co, rowsize, colsize) - co3 = _next_coordinate(co2, rowsize, colsize) - co4 = _next_coordinate(co3, rowsize, colsize) - return full_infinite_environment( - dense_enlarged_corners[co4...], - dense_enlarged_corners[co...], - dense_enlarged_corners[co2...], - dense_enlarged_corners[co3...], - ) - end - - # SVD half and full infinite environments - (P_left_half, P_right_half), info_half = simultaneous_projectors( - dense_enlarged_corners, env, HalfInfiniteProjector() - ) - U_half, S_half, V_half = info_half.U, info_half.S, info_half.V - (P_left_full, P_right_full), info_full = simultaneous_projectors( - dense_enlarged_corners, env, FullInfiniteProjector() - ) - U_full, S_full, V_full = info_full.U, info_full.S, info_full.V - - # check projector computation for both types of environments, - # comparing dense and sparse implementations - foreach(dirs_and_coordinates) do co - dir, r, c = co - - co2 = _next_coordinate(co, size(env)[2:3]...) - co3 = _next_coordinate(co2, size(env)[2:3]...) - co4 = _next_coordinate(co3, size(env)[2:3]...) - - ## HalfInfiniteEnv - - shenv = sparse_halfinf_envs[dir, r, c] - dhenv = dense_halfinf_envs[dir, r, c] - dhenv_bis = dense_halfinf_envs_bis[dir, r, c] - @test dhenv ≈ dhenv_bis - - # application - xr = random_start_vector(shenv) - xl = randn(storagetype(shenv), codomain(shenv)) - @test shenv(xr, Val(false)) ≈ dhenv * xr - @test shenv(xl, Val(true)) ≈ dhenv' * xl - - # projector computation - P_left_sparse, P_right_sparse = contract_projectors( - U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], shenv - ) - P_left_dense, P_right_dense = contract_projectors( - U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], - dense_enlarged_corners[co...], dense_enlarged_corners[co2...], - ) - @test P_left_sparse ≈ P_left_dense - @test P_right_sparse ≈ P_right_dense - @test P_left_sparse ≈ P_left_half[dir, r, c] - @test P_right_sparse ≈ P_right_half[dir, r, c] - - - ## FullInfiniteEnv - - sfenv = sparse_fullinf_envs[dir, r, c] - dfenv = dense_fullinf_envs[dir, r, c] - dfenv_bis = dense_fullinf_envs_bis[dir, r, c] - @test dfenv ≈ dfenv_bis - - # application - xl = randn(storagetype(sfenv), codomain(sfenv)) - xr = random_start_vector(sfenv) - @test sfenv(xr, Val(false)) ≈ dfenv * xr - @test sfenv(xl, Val(true)) ≈ dfenv' * xl - - # projector computation - P_left_sparse, P_right_sparse = contract_projectors( - U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], sfenv - ) - P_left_dense, P_right_dense = contract_projectors( - U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], - half_infinite_environment( - dense_enlarged_corners[co4...], dense_enlarged_corners[co...] - ), - half_infinite_environment( - dense_enlarged_corners[co2...], dense_enlarged_corners[co3...] - ), - ) - @test P_left_sparse ≈ P_left_dense - @test P_right_sparse ≈ P_right_dense - @test P_left_sparse ≈ P_left_full[dir, r, c] - @test P_right_sparse ≈ P_right_full[dir, r, c] - end - - foreach(dirs_and_coordinates) do co - dir, r, c = co - - ## Corner renormalization - - C_sparse = renormalize_corner_fns[dir]( - (r, c), sparse_enlarged_corners, P_left_half, P_right_half - ) - C_dense = renormalize_corner_fns[dir]( - (r, c), dense_enlarged_corners, P_left_half, P_right_half - ) - @test C_sparse ≈ C_dense - end - - return nothing -end - -@testset "Random Cartesian spaces" begin - unitcell = (3, 3) - - Pspaces = ComplexSpace.(rand(2:3, unitcell...)) - Nspaces = ComplexSpace.(rand(2:4, unitcell...)) - Espaces = ComplexSpace.(rand(2:4, unitcell...)) - chis_north = ComplexSpace.(rand(5:10, unitcell...)) - chis_east = ComplexSpace.(rand(5:10, unitcell...)) - chis_south = ComplexSpace.(rand(5:10, unitcell...)) - chis_west = ComplexSpace.(rand(5:10, unitcell...)) - - test_ctmrg_contractions( - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) -end - -@testset "Specific U1 spaces" begin - unitcell = (2, 2) - - PA = U1Space(-1 => 1, 0 => 1) - PB = U1Space(0 => 1, 1 => 1) - Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) - Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) - - Pspaces = [PA PB; PB PA] - Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] - chis = [Venv Venv; Venv Venv] - - test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) - - # 4x4 unit cell with all 32 inequivalent bonds - # - # 10 4 7 32 - # | | | | - # 3--A--1--B--5--C--8--D--3 - # | | | | - # 2 6 9 11 - # | | | | - # 14--E-12--F-15--G-17--H-14 - # | | | | - # 13 16 18 19 - # | | | | - # 22--I-20--J-23--K-25--L-22 - # | | | | - # 21 24 26 27 - # | | | | - # 29--M-28--N-30--O-31--P-29 - # | | | | - # 10 4 7 32 - - phys_space = Vect[U1Irrep](1 => 1, -1 => 1) - corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) - vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) - @test length(Set(vspaces)) == 32 - - Espaces = [ - vspaces[1] vspaces[5] vspaces[8] vspaces[3] - vspaces[12] vspaces[15] vspaces[17] vspaces[14] - vspaces[20] vspaces[23] vspaces[25] vspaces[22] - vspaces[28] vspaces[30] vspaces[31] vspaces[29] - ] - - Nspaces = [ - vspaces[10] vspaces[4] vspaces[7] vspaces[32] - vspaces[2] vspaces[6] vspaces[9] vspaces[11] - vspaces[13] vspaces[16] vspaces[18] vspaces[19] - vspaces[21] vspaces[24] vspaces[26] vspaces[27] - ] - Pspaces = fill(phys_space, (4, 4)) - chis = fill(corner_space, (4, 4)) - # TODO broken? - #test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) -end - -@testset "Random fermionic spaces" begin - unitcell = (3, 3) - - S = Vect[FermionParity] - pdims = rand(1:2, unitcell..., 2) - vdims = rand(2:4, unitcell..., 2) - edims = rand(3:6, unitcell..., 2) - - function _construct_space(ds::Array{<:Int, 3}) - V = map(Iterators.product(axes(ds)[1:2]...)) do (r, c) - return S(0 => ds[r, c, 1], 1 => ds[r, c, 2]) - end - return V - end - - Pspaces = _construct_space(pdims) - Nspaces = _construct_space(vdims) - Espaces = _construct_space(vdims) - chis_north = _construct_space(edims) - chis_east = _construct_space(edims) - chis_south = _construct_space(edims) - chis_west = _construct_space(edims) - - test_ctmrg_contractions( - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) -end diff --git a/test/rocm/ctmrg/fixed_iterscheme.jl b/test/rocm/ctmrg/fixed_iterscheme.jl deleted file mode 100644 index 1b90c9850..000000000 --- a/test/rocm/ctmrg/fixed_iterscheme.jl +++ /dev/null @@ -1,108 +0,0 @@ -using Test -using TestExtras: @constinferred -using Accessors -using Random -using LinearAlgebra -using TensorKit, KrylovKit -using PEPSKit -using AMDGPU, Adapt -using PEPSKit: - ctmrg_iteration, - compute_gauge_fix_gauge, - fix_phases, - fix_relative_phases, - calc_elementwise_convergence, - peps_normalize, - ScramblingEnvGauge, - ScramblingEnvGaugeC4v -using PEPSKit.Defaults: ctmrg_tol - -# initialize parameters -D = 2 -χ = 16 -svd_algs = [(; alg = :Jacobi), (; alg = :GKL)] -projector_algs_asymm = [:HalfInfiniteProjector] #, :FullInfiniteProjector] -unitcells = [(1, 1), (3, 4)] -atol = 1.0e-5 - -# test for element-wise convergence after application of fixed step -@testset "$unitcell unit cell with $(decomposition_alg.alg) and $projector_alg" for ( - unitcell, decomposition_alg, projector_alg, - ) in Iterators.product( - unitcells, svd_algs, projector_algs_asymm - ) - ctm_alg = SimultaneousCTMRG(; decomposition_alg, projector_alg) - - # initialize states - Random.seed!(2394823842) - psi = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) - @test storagetype(psi) <: ROCArray - n = InfiniteSquareNetwork(psi) - - env_conv1, = leading_boundary(CTMRGEnv(psi, ComplexSpace(χ)), psi, ctm_alg) - - # do extra iteration and gauge fix - env_conv2, = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) - env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGauge()) - @test calc_elementwise_convergence(env_conv1, env_fixed) ≈ 0 atol = atol - - # fix gauge of single iteration - signs, corner_phases, edge_phases = - compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGauge()) - gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( - ctmrg_iteration(n, env, ctm_alg)[1], - signs, corner_phases, edge_phases, - ) - - # do gauge-fixed iteration - env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) - @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol -end - -# test same thing for C4v CTMRG -c4v_algs = [ - (:C4vQRProjector, (; alg = :Householder)), - (:C4vEighProjector, (; alg = :DivideAndConquer)), - (:C4vEighProjector, (; alg = :Lanczos)), -] -@testset "$(decomposition_alg.alg) and $projector_alg" for - (projector_alg, decomposition_alg) in c4v_algs - # initialize states - Random.seed!(2394823842) - ctm_alg = C4vCTMRG(; - projector_alg, decomposition_alg, maxiter = 200, - tol = (projector_alg == :C4vQRProjector ? 1.0e-12 : ctmrg_tol) - ) - symm = RotateReflect() - - psi = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D))) - @test storagetype(psi) <: ROCArray - psi = peps_normalize(symmetrize!(psi, symm)) - @test storagetype(psi) <: ROCArray - n = InfiniteSquareNetwork(psi) - @test storagetype(n) <: ROCArray - - env₀ = initialize_random_c4v_env(psi, ComplexSpace(χ)) - @test storagetype(env₀) <: ROCArray - env_conv1, info = leading_boundary(env₀, psi, ctm_alg) - - # do extra iteration to check gauge fixing - env_conv2, info = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) # CHECK - - env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) - env_diff = calc_elementwise_convergence(env_conv1, env_fixed) - @info "Diff between iters = $(env_diff)" - @test env_diff ≈ 0 atol = atol - - # fix gauge of single iteration - signs, corner_phases, edge_phases = - compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) - gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( - ctmrg_iteration(n, env, ctm_alg)[1], - signs, corner_phases, edge_phases, - ) - - # do gauge-fixed iteration - env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) - @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol -end diff --git a/test/rocm/ctmrg/flavors.jl b/test/rocm/ctmrg/flavors.jl deleted file mode 100644 index 07a5b465d..000000000 --- a/test/rocm/ctmrg/flavors.jl +++ /dev/null @@ -1,88 +0,0 @@ -using Test -using Random -using MatrixAlgebraKit -using TensorKit -using MPSKit -using PEPSKit -using AMDGPU, Adapt -using PEPSKit: peps_normalize - -# initialize parameters -D = 2 -χ = 16 -unitcells = [(1, 1), (3, 4)] -projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] -projector_algs_c4v = [ - (:C4vQRProjector, :Householder), - (:C4vEighProjector, :DivideAndConquer), (:C4vEighProjector, :Lanczos), -] -Ts = [Float64, ComplexF64] - -@testset "$(unitcell) unit cell with $projector_alg" for (unitcell, projector_alg) in - Iterators.product(unitcells, projector_algs_asymm) - # compute environments - Random.seed!(32350283290358) - psi = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) - env_sequential, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SequentialCTMRG, projector_alg - ) - env_simultaneous, = leading_boundary( - CTMRGEnv(psi, ComplexSpace(χ)), psi; alg = :SimultaneousCTMRG, projector_alg - ) - - # compare norms - @test abs(norm(psi, env_sequential)) ≈ abs(norm(psi, env_simultaneous)) rtol = 1.0e-6 - - # compare singular values - CS_sequential = map(svd_vals, env_sequential.corners) - CS_simultaneous = map(svd_vals, env_simultaneous.corners) - ΔCS = maximum(splat(PEPSKit._singular_value_distance), zip(CS_sequential, CS_simultaneous)) - @test ΔCS < 1.0e-2 - - TS_sequential = map(svd_vals, env_sequential.edges) - TS_simultaneous = map(svd_vals, env_simultaneous.edges) - ΔTS = maximum(splat(PEPSKit._singular_value_distance), zip(TS_sequential, TS_simultaneous)) - @test ΔTS < 1.0e-2 - - # compare Heisenberg energies - H = adapt(ROCArray, heisenberg_XYZ(InfiniteSquare(unitcell...))) - E_sequential = cost_function(psi, env_sequential, H) - E_simultaneous = cost_function(psi, env_simultaneous, H) - @test E_sequential ≈ E_simultaneous rtol = 1.0e-3 -end - -# test fixedspace actually fixes space -@testset "Fixedspace truncation using $alg and $projector_alg" for (alg, projector_alg) in - Iterators.product([:SequentialCTMRG, :SimultaneousCTMRG], projector_algs_asymm) - Ds = ComplexSpace.(fill(2, 3, 3)) - χs = ComplexSpace.([16 17 18; 15 20 21; 14 19 22]) - psi = adapt(ROCArray, InfinitePEPS(Ds, Ds, Ds)) - env = CTMRGEnv(psi, ComplexSpace.(rand(10:20, 3, 3)), ComplexSpace.(rand(10:20, 3, 3))) - env2, = leading_boundary( - env, psi; alg, maxiter = 1, trunc = FixedSpaceTruncation(), projector_alg - ) - - # check that the space is fixed - @test all(space.(env.corners) .== space.(env2.corners)) - @test all(space.(env.edges) .== space.(env2.edges)) -end - -@testset "C4v with ($T) - ($projector_alg, $decomp_alg)" for (T, (projector_alg, decomp_alg)) in - Iterators.product(Ts, projector_algs_c4v) - - Random.seed!(29358293829382) - symm = RotateReflect() - Vphys = ComplexSpace(2) - Vpeps = ComplexSpace(D) - Venv = ComplexSpace(χ) - - peps = adapt(ROCArray, InfinitePEPS(randn, T, Vphys, Vpeps, Vpeps)) - peps = peps_normalize(symmetrize!(peps, symm)) - - env₀ = initialize_random_c4v_env(peps, Venv) - env, = leading_boundary( - env₀, peps; alg = :C4vCTMRG, projector_alg, - decomposition_alg = (; alg = decomp_alg) - ) - @test env isa CTMRGEnv -end diff --git a/test/rocm/ctmrg/gaugefix.jl b/test/rocm/ctmrg/gaugefix.jl deleted file mode 100644 index 30f3211c6..000000000 --- a/test/rocm/ctmrg/gaugefix.jl +++ /dev/null @@ -1,99 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using AMDGPU, Adapt -using PEPSKit: ctmrg_iteration, calc_elementwise_convergence -using PEPSKit: ScramblingEnvGauge, ScramblingEnvGaugeC4v -using PEPSKit: peps_normalize - -spacetypes = [ComplexSpace, Z2Space] -scalartypes = [Float64, ComplexF64] -unitcells = [(1, 1), (2, 2), (3, 2)] -ctmrg_algs_asymm = [SequentialCTMRG, SimultaneousCTMRG] -projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] -projector_algs_c4v = [:C4vEighProjector, :C4vQRProjector] -gauge_algs_asymm = [ScramblingEnvGauge()] -gauge_algs_c4v = [ScramblingEnvGaugeC4v()] -tol = 1.0e-6 # large tol due to χ=6 -χ = 6 -atol = 1.0e-4 - -function _pre_converge_env( - ::Type{T}, alg, physical_space, peps_space, env_space, unitcell; - seed = 985293852935829 - ) where {T} - Random.seed!(seed) # Seed RNG to make random environment consistent - psi = adapt(ROCArray, InfinitePEPS(rand, T, physical_space, peps_space; unitcell)) - @test storagetype(psi) <: ROCArray - alg == :C4vCTMRG && (psi = peps_normalize(symmetrize!(psi, RotateReflect()))) - env₀ = if alg == :C4vCTMRG - initialize_singlet_c4v_env(T, psi, env_space) - else - CTMRGEnv(psi, env_space) - end - @test storagetype(env₀) <: ROCArray - env_conv, = leading_boundary(env₀, psi; alg, tol) - return env_conv, psi -end - -# pre-converge CTMRG environments with given spacetype, scalartype and unit cell -preconv = Dict() -for (S, T, unitcell) in Iterators.product(spacetypes, scalartypes, unitcells) - if S == ComplexSpace - result = _pre_converge_env(T, :SequentialCTMRG, S(2), S(2), S(χ), unitcell) - elseif S == Z2Space - result = _pre_converge_env( - T, :SequentialCTMRG, S(0 => 1, 1 => 1), S(0 => 1, 1 => 1), - S(0 => χ ÷ 2, 1 => χ ÷ 2), unitcell - ) - end - push!(preconv, (S, T, unitcell) => result) -end -preconv_c4v = Dict() -for (S, T) in Iterators.product(spacetypes, scalartypes) - if S == ComplexSpace - result = _pre_converge_env(T, :C4vCTMRG, S(2), S(2), S(χ), (1, 1)) - elseif S == Z2Space - result = _pre_converge_env( - T, :C4vCTMRG, S(0 => 1, 1 => 1), S(0 => 1, 1 => 1), S(0 => χ ÷ 2, 1 => χ ÷ 2), (1, 1) - ) - end - push!(preconv_c4v, (S, T) => result) -end - -# asymmetric CTMRG -@testset "($S) - ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg) - ($gauge_alg)" for ( - S, T, unitcell, ctmrg_alg, projector_alg, gauge_alg, - ) in Iterators.product( - spacetypes, scalartypes, unitcells, ctmrg_algs_asymm, projector_algs_asymm, gauge_algs_asymm - ) - alg = ctmrg_alg(; tol, projector_alg) - env_pre, psi = preconv[(S, T, unitcell)] - n = InfiniteSquareNetwork(psi) - @test storagetype(n) <: ROCArray - env, = leading_boundary(env_pre, psi, alg) - env′, = ctmrg_iteration(n, env, alg) - env_fixed = gauge_fix(env′, env, gauge_alg) - env_diff = calc_elementwise_convergence(env, env_fixed) - @info "Diff between iters = $(env_diff)" - @test env_diff ≈ 0 atol = atol -end - -# C4v CTMRG -@testset "($S) - ($T) - ($projector_alg) - ($gauge_alg)" for ( - S, T, projector_alg, gauge_alg, - ) in Iterators.product( - spacetypes, scalartypes, projector_algs_c4v, gauge_algs_c4v - ) - alg = C4vCTMRG(; tol, projector_alg) - env_pre, psi = preconv_c4v[(S, T)] - n = InfiniteSquareNetwork(psi) - @test storagetype(n) <: ROCArray - env, = leading_boundary(env_pre, psi, alg) - env′, = ctmrg_iteration(n, env, alg) - env_fixed = gauge_fix(env′, env, gauge_alg) - env_diff = calc_elementwise_convergence(env, env_fixed) - @info "Diff between iters = $(env_diff)" - @test env_diff ≈ 0 atol = atol -end diff --git a/test/rocm/ctmrg/initialization.jl b/test/rocm/ctmrg/initialization.jl deleted file mode 100644 index 49806d781..000000000 --- a/test/rocm/ctmrg/initialization.jl +++ /dev/null @@ -1,94 +0,0 @@ -using Test -using TensorKit -using PEPSKit -using Random -using Adapt, AMDGPU -using MPSKitModels: classical_ising -using PEPSKit: ProductStateEnv - -sd = 12345 - -# toggle symmetry, but same issue for both -symmetries = [Z2Irrep, Trivial] -make_space(::Type{Z2Irrep}, d::Int) = Z2Space(0 => d / 2, 1 => d / 2) -make_space(::Type{Trivial}, d::Int) = ComplexSpace(d) - -d = 2 -D = 4 -χ = 20 -tol = 1.0e-4 -maxiter = 1000 -verbosity = 2 -trunc = truncrank(χ) -boundary_alg = (; alg = :SimultaneousCTMRG, tol, verbosity, trunc, maxiter) - -@testset "CTMRG environment initialization for critical ising with $S symmetry (#255)" for S in symmetries - # initialize - Random.seed!(sd) - T = classical_ising(S) - O = T[1] - n = adapt(ROCArray, InfinitePartitionFunction([O O; O O])) - Venv = make_space(S, χ) - P = space(O, 2) - - # random, doesn't converge - env0_rand = initialize_ctmrg_environment(n, RandomInitialization(), Venv) - env_rand, info = leading_boundary(env0_rand, n; boundary_alg...) - @test_broken info.convergence_error ≤ tol - - # embedded random product state, converges - env0_prod = initialize_ctmrg_environment(n, ProductStateInitialization()) - env_prod, info = leading_boundary(env0_prod, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # grown product state, converges - env0_appl = initialize_ctmrg_environment(n, ApplicationInitialization()) - env_appl, info = leading_boundary(env0_appl, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # specific custom starting product state - p_data = ComplexF64[1; 0;;] - p = adapt(ROCArray, Tensor(p_data, P)) - prod_env0 = ProductStateEnv(reshape([p, p, flip(p, 1), flip(p, 1)], 4, 1, 1)) - env0_custom = initialize_ctmrg_environment(n, ApplicationInitialization(), prod_env0) - # or just CTMRGEnv(prod_env0) - env_custom, info = leading_boundary(env0_custom, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # PEPS-specific identity initialization; should throw when used on partition functions - @test_throws ArgumentError env0_prod_id = initialize_ctmrg_environment(n, IdentityInitialization()) -end - -@testset "CTMRG environment initialization for PEPS with $S symmetry" for S in symmetries - # initialize - Random.seed!(sd) - P = make_space(S, d) - Vpeps = make_space(S, D) - Venv = make_space(S, χ) - peps = adapt(ROCArray, InfinitePEPS(P, Vpeps; unitcell = (2, 2))) - n = InfiniteSquareNetwork(peps) - - # random, converges - env0_rand = initialize_ctmrg_environment(n, RandomInitialization(), Venv) - @test storagetype(env0_rand) <: ROCArray - env_rand, info = leading_boundary(env0_rand, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # embedded random product state, converges - env0_prod = initialize_ctmrg_environment(n, ProductStateInitialization()) - @test storagetype(env0_prod) <: ROCArray - env_prod, info = leading_boundary(env0_prod, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # embedded product state as identity from ket to bra, converges - env0_prod_id = initialize_ctmrg_environment(n, IdentityInitialization()) - @test storagetype(env0_prod_id) <: ROCArray - env_prod, info = leading_boundary(env0_prod_id, n; boundary_alg...) - @test info.convergence_error ≤ tol - - # grown product state, converges - env0_appl = initialize_ctmrg_environment(n, ApplicationInitialization()) - @test storagetype(env0_appl) <: ROCArray - env_appl, info = leading_boundary(env0_appl, n; boundary_alg...) - @test info.convergence_error ≤ tol -end diff --git a/test/rocm/ctmrg/jacobian_real_linear.jl b/test/rocm/ctmrg/jacobian_real_linear.jl deleted file mode 100644 index 78a6e91bf..000000000 --- a/test/rocm/ctmrg/jacobian_real_linear.jl +++ /dev/null @@ -1,50 +0,0 @@ -using Test -using Random -using Accessors -using Zygote -using TensorKit, KrylovKit, PEPSKit -using AMDGPU, Adapt -using PEPSKit: - ctmrg_iteration, compute_gauge_fix_gauge, fix_phases, ScramblingEnvGauge - -algs = [ - SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector), - SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector), # TODO: why are the errors quite a bit larger for :FullInfiniteProjector? -] -Dbond, χenv = 2, 16 -alg_gauge = ScramblingEnvGauge() -errtol = 1.0e-3 - -@testset "$ctm_alg" for ctm_alg in algs - Random.seed!(123521938519) - state = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(Dbond))) - env, = leading_boundary(CTMRGEnv(state, ComplexSpace(χenv)), state, ctm_alg) - - # follow code of _rrule - env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, ctm_alg) - signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env_conv, env, alg_gauge) - - _, env_vjp = pullback(state, env_conv) do A, x - e, = ctmrg_iteration(InfiniteSquareNetwork(A), x, ctm_alg) - return fix_phases(e, signs, corner_phases, edge_phases) - end - - # get Jacobians of single iteration - ∂f∂A(x)::typeof(state) = env_vjp(x)[1] - ∂f∂x(x)::typeof(env) = env_vjp(x)[2] - - # compute real and complex errors - env_in = CTMRGEnv(state, ComplexSpace(16)) - α_real = randn(Float64) - α_complex = randn(ComplexF64) - - real_err_∂A = norm(scale(∂f∂A(env_in), α_real) - ∂f∂A(scale(env_in, α_real))) - real_err_∂x = norm(scale(∂f∂x(env_in), α_real) - ∂f∂x(scale(env_in, α_real))) - complex_err_∂A = norm(scale(∂f∂A(env_in), α_complex) - ∂f∂A(scale(env_in, α_complex))) - complex_err_∂x = norm(scale(∂f∂x(env_in), α_complex) - ∂f∂x(scale(env_in, α_complex))) - - @test real_err_∂A < errtol - @test real_err_∂x < errtol - @test complex_err_∂A > 1.0e-3 - @test complex_err_∂x > 1.0e-3 -end diff --git a/test/rocm/ctmrg/partition_function.jl b/test/rocm/ctmrg/partition_function.jl deleted file mode 100644 index dbc3586d7..000000000 --- a/test/rocm/ctmrg/partition_function.jl +++ /dev/null @@ -1,152 +0,0 @@ -using Test -using Random -using LinearAlgebra -using PEPSKit -using TensorKit -using QuadGK -using Test -using AMDGPU, Adapt - -@testset "Check spaces in partition function CTMRG" begin - zA = randn(ℂ^6 ⊗ ℂ^8 ← ℂ^4 ⊗ ℂ^2) - zB = randn(ℂ^2 ⊗ ℂ^9 ← ℂ^5 ⊗ ℂ^6) - zC = randn(ℂ^7 ⊗ ℂ^4 ← ℂ^8 ⊗ ℂ^3) - zD = randn(ℂ^3 ⊗ ℂ^5 ← ℂ^9 ⊗ ℂ^7) - - Z = adapt(ROCArray, InfinitePartitionFunction([zA zB; zC zD])) - χenv = ℂ^12 - env0 = CTMRGEnv(Z, χenv) - env, = leading_boundary(env0, Z; alg = :SimultaneousCTMRG, maxiter = 3, projector_alg = :FullInfiniteProjector) - @test env isa CTMRGEnv -end - - -## Setup - -""" - classical_ising_exact(beta, J) - -[Exact Onsager solution](https://en.wikipedia.org/wiki/Square_lattice_Ising_model#Exact_solution) -for the 2D classical Ising Model with partition function - -```math -\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j -``` -""" -function classical_ising_exact(; beta = log(1 + sqrt(2)) / 2, J = 1.0) - K = beta * J - - k = 1 / sinh(2 * K)^2 - F = quadgk( - theta -> log(cosh(2 * K)^2 + 1 / k * sqrt(1 + k^2 - 2 * k * cos(2 * theta))), 0, pi - )[1] - f = -1 / beta * (log(2) / 2 + 1 / (2 * pi) * F) - - m = 1 - (sinh(2 * K))^(-4) > 0 ? (1 - (sinh(2 * K))^(-4))^(1 / 8) : 0 - - E = quadgk(theta -> 1 / sqrt(1 - (4 * k) * (1 + k)^(-2) * sin(theta)^2), 0, pi / 2)[1] - e = -J * cosh(2 * K) / sinh(2 * K) * (1 + 2 / pi * (2 * tanh(2 * K)^2 - 1) * E) - - return f, m, e -end - -""" - classical_ising(; beta=log(1 + sqrt(2)) / 2) - -Implements the 2D classical Ising model with partition function - -```math -\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j -``` -""" -function classical_ising(; beta = log(1 + sqrt(2)) / 2, J = 1.0) - K = beta * J - - # Boltzmann weights - t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)] - r = eigen(t) - nt = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors - - # local partition function tensor - O = zeros(2, 2, 2, 2) - O[1, 1, 1, 1] = 1 - O[2, 2, 2, 2] = 1 - @tensor o[-1 -2; -3 -4] := O[3 4; 2 1] * nt[-3; 3] * nt[-4; 4] * nt[-2; 2] * nt[-1; 1] - - # magnetization tensor - M = copy(O) - M[2, 2, 2, 2] *= -1 - @tensor m[-1 -2; -3 -4] := M[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * nt[-4; 4] - - # bond interaction tensor and energy-per-site tensor - e = ComplexF64[-J J; J -J] .* nt - @tensor e_hor[-1 -2; -3 -4] := - O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * e[-4; 4] - @tensor e_vert[-1 -2; -3 -4] := - O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * e[-3; 3] * nt[-4; 4] - e = e_hor + e_vert - - # fixed tensor map space for all three - TMS = ℂ^2 ⊗ ℂ^2 ← ℂ^2 ⊗ ℂ^2 - - return TensorMap(o, TMS), TensorMap(m, TMS), TensorMap(e, TMS) -end - -## Test - -# initialize -beta = 0.6 -O, M, E = classical_ising(; beta) -O = adapt(ROCArray, O) -M = adapt(ROCArray, M) -E = adapt(ROCArray, E) -Z = InfinitePartitionFunction(O) -Venv = ℂ^12 -Random.seed!(81812781143) -env₀ = CTMRGEnv(Z, Venv) -env₀_c4v = initialize_random_c4v_env(Z, Venv) -# cover all different flavors -args = [ - (:SequentialCTMRG, :HalfInfiniteProjector), (:SequentialCTMRG, :FullInfiniteProjector), - (:SimultaneousCTMRG, :HalfInfiniteProjector), (:SimultaneousCTMRG, :FullInfiniteProjector), - # (:C4vCTMRG, :C4vEighProjector), (:C4vCTMRG, :C4vQRProjector), # TODO -] - -# Basic properties -@test storagetype(Z) <: ROCArray -@test spacetype(typeof(Z)) === ComplexSpace -@test spacetype(Z) === ComplexSpace -@test sectortype(typeof(Z)) === Trivial -@test sectortype(Z) === Trivial -@test length(Z) == 1 -@test size(Z, 1) == 1 -@test size(Z, 2) == 1 -@test eltype(similar(Z)) == eltype(Z) -@test copy(Z) == Z -@test copy(Z) ≈ Z - - -@testset "Classical Ising partition function using $alg with $projector_alg" for ( - alg, projector_alg, - ) in args - env₀₀ = alg == :C4vCTMRG ? env₀_c4v : env₀ - env, = leading_boundary(env₀₀, Z; alg, maxiter = 300, projector_alg) - - # check observables - λ = network_value(Z, env) - m = expectation_value(Z, (1, 1) => M, env) - e = expectation_value(Z, (1, 1) => E, env) - f_exact, m_exact, e_exact = classical_ising_exact(; beta) - @info "Exact energy = $(e_exact)." - - # should be real-ish - @test abs(imag(λ)) < 1.0e-4 - @test abs(imag(m)) < 1.0e-4 - @test abs(imag(e)) < 1.0e-4 - - # should match exact solution - @test -log(λ) / beta ≈ f_exact rtol = 1.0e-4 - @test abs(m) ≈ abs(m_exact) rtol = 1.0e-4 - @info "Evaluated energy = $(e)." - @test e ≈ e_exact rtol = 1.0e-1 # accuracy limited by bond dimension and maxiter -end diff --git a/test/rocm/ctmrg/pepo.jl b/test/rocm/ctmrg/pepo.jl deleted file mode 100644 index e687d757a..000000000 --- a/test/rocm/ctmrg/pepo.jl +++ /dev/null @@ -1,148 +0,0 @@ -using Test -using Random -using LinearAlgebra -using PEPSKit -using TensorKit -using KrylovKit -using OptimKit -using Zygote -using AMDGPU, Adapt -## Setup - -function three_dimensional_classical_ising(; beta, J = 1.0) - K = beta * J - - # Boltzmann weights - t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)] - r = eigen(t) - q = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors - - # local partition function tensor - O = zeros(2, 2, 2, 2, 2, 2) - O[1, 1, 1, 1, 1, 1] = 1 - O[2, 2, 2, 2, 2, 2] = 1 - @tensor o[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - - # magnetization tensor - M = copy(O) - M[2, 2, 2, 2, 2, 2] *= -1 - @tensor m[-1 -2; -3 -4 -5 -6] := - M[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - - # bond interaction tensor and energy-per-site tensor - e = ComplexF64[-J J; J -J] .* q - @tensor e_x[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * e[-4; 4] * q[-5; 5] * q[-6; 6] - @tensor e_y[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * e[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - @tensor e_z[-1 -2; -3 -4 -5 -6] := - O[1 2; 3 4 5 6] * e[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6] - e = e_x + e_y + e_z - - # fixed tensor map space for all three - TMS = ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)' - - return adapt(ROCArray, TensorMap(o, TMS)), adapt(ROCArray, TensorMap(m, TMS)), adapt(ROCArray, TensorMap(e, TMS)) -end - -## Test - -# initialize -beta = 0.2391 # slightly lower temperature than βc ≈ 0.2216544 -O, M, E = three_dimensional_classical_ising(; beta) -χpeps = ℂ^2 -χenv = ℂ^12 - -# cover all different flavors -ctm_styles = [:SequentialCTMRG, :SimultaneousCTMRG] -projector_algs = [:HalfInfiniteProjector, :FullInfiniteProjector] - -@testset "PEPO CTMRG runthroughs for unitcell=$(unitcell)" for unitcell in - [(1, 1, 1), (1, 1, 2)] - Random.seed!(81812781144) - - # contract - T = InfinitePEPO(O; unitcell = unitcell) - psi0 = initializePEPS(T, χpeps) - n = InfiniteSquareNetwork(psi0, T) - env0 = CTMRGEnv(n, χenv) - - @test spacetype(typeof(T)) === ComplexSpace - @test spacetype(T) === ComplexSpace - @test sectortype(typeof(T)) === Trivial - @test sectortype(T) === Trivial - @test storagetype(T) <: ROCArray - - @testset "PEPO CTMRG contraction using $alg with $projector_alg" for ( - alg, projector_alg, - ) in Iterators.product(ctm_styles, projector_algs) - env, = leading_boundary(env0, n; alg, maxiter = 150, projector_alg) - end -end - -@testset "Fixed-point computation for 3D classical ising model" begin - Random.seed!(81812781144) - - # prep - ctm_alg = SimultaneousCTMRG(; maxiter = 150, tol = 1.0e-8, verbosity = 2) - gradient_alg = FixedPointGradient(; - solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), - ) - opt_alg = LBFGS(32; maxiter = 50, gradtol = 1.0e-5, verbosity = 3) - function pepo_retract(x, η, α) - x´_partial, ξ = PEPSKit.peps_retract(x[1:2], η, α) - x´ = (x´_partial..., deepcopy(x[3])) - return x´, ξ - end - function pepo_transport!(ξ, x, η, α, x´) - return PEPSKit.peps_transport!(ξ, x[1:2], η, α, x´[1:2]) - end - - # contract - T = adapt(ROCArray, InfinitePEPO(O; unitcell = (1, 1, 1))) - psi0 = initializePEPS(T, χpeps) - n2 = InfiniteSquareNetwork(psi0) - @test storagetype(n2) <: ROCArray - env2_0 = CTMRGEnv(n2, χenv) - n3 = InfiniteSquareNetwork(psi0, T) - @test storagetype(n3) <: ROCArray - env3_0 = CTMRGEnv(n3, χenv) - - # optimize free energy per site - (psi_final, env2_final, env3_final), f, = optimize( - (psi0, env2_0, env3_0), - opt_alg; - inner = PEPSKit.real_inner, - retract = pepo_retract, - (transport!) = (pepo_transport!), - ) do (psi, env2, env3) - E, gs = withgradient(psi) do ψ - n2 = InfiniteSquareNetwork(ψ) - env2′, info = PEPSKit.hook_pullback( - leading_boundary, env2, n2, ctm_alg; alg_rrule = gradient_alg - ) - n3 = InfiniteSquareNetwork(ψ, T) - env3′, info = PEPSKit.hook_pullback( - leading_boundary, env3, n3, ctm_alg; alg_rrule = gradient_alg - ) - PEPSKit.ignore_derivatives() do - PEPSKit.update!(env2, env2′) - PEPSKit.update!(env3, env3′) - end - λ3 = network_value(n3, env3) - λ2 = network_value(n2, env2) - return -log(real(λ3 / λ2)) - end - g = only(gs) - return E, g - end - - # check energy - n3_final = InfiniteSquareNetwork(psi_final, T) - m = PEPSKit.contract_local_tensor((1, 1, 1), M, n3_final, env3_final) - nrm3 = PEPSKit._contract_site((1, 1), n3_final, env3_final) - - # compare to Monte-Carlo result from https://www.worldscientific.com/doi/abs/10.1142/S0129183101002383 - @test abs(m / nrm3) ≈ 0.667162 rtol = 1.0e-2 -end diff --git a/test/rocm/ctmrg/suweight.jl b/test/rocm/ctmrg/suweight.jl deleted file mode 100644 index dab8df1c1..000000000 --- a/test/rocm/ctmrg/suweight.jl +++ /dev/null @@ -1,56 +0,0 @@ -using Test -using Random -using TensorKit -using AMDGPU, Adapt -using PEPSKit -using PEPSKit: str, twistdual, unitcell - -Vps = Dict( - Z2Irrep => Vect[Z2Irrep](0 => 1, 1 => 2), - U1Irrep => Vect[U1Irrep](0 => 2, 1 => 2, -1 => 1), - FermionParity => Vect[FermionParity](0 => 1, 1 => 2), -) -Vvs = Dict( - Z2Irrep => Vect[Z2Irrep](0 => 2, 1 => 2), - U1Irrep => Vect[U1Irrep](0 => 3, 1 => 1, -1 => 2), - FermionParity => Vect[FermionParity](0 => 2, 1 => 2), -) - -function su_rdm_1x1( - row::Int, col::Int, peps::InfinitePEPS, wts::Union{Nothing, SUWeight} = nothing - ) - Nr, Nc = size(peps) - @assert 1 <= row <= Nr && 1 <= col <= Nc - t = peps.A[row, col] - if !(wts === nothing) - t = absorb_weight(t, wts, row, col, Tuple(1:4)) - end - # contract local ⟨t|t⟩ without virtual twists - @tensor ρ[k; b] := conj(t[b; n e s w]) * twistdual(t, 2:5)[k; n e s w] - return ρ / str(ρ) -end - -@testset "SUWeight ($(init) init, $(sect))" for (init, sect) in - Iterators.product([:trivial, :random], keys(Vps)) - - Vp, Vv = Vps[sect], Vvs[sect] - Nspaces = [Vv Vv' Vv; Vv' Vv Vv'] - Espaces = [Vv Vv Vv'; Vv Vv' Vv'] - Pspaces = fill(Vp, size(Nspaces)) - peps = adapt(ROCArray, InfinitePEPS(randn, ComplexF64, Pspaces, Nspaces, Espaces)) - wts = SUWeight(peps) - if init != :trivial - rand!(wts) - normalize!.(wts.data, Inf) - end - env = CTMRGEnv(wts) - for idx in CartesianIndices(unitcell(peps)) - r, c = Tuple(idx) - ρ1 = su_rdm_1x1(r, c, peps, wts) - if init == :trivial - @test ρ1 ≈ su_rdm_1x1(r, c, peps, nothing) - end - ρ2 = reduced_densitymatrix([idx], peps, env) - @test ρ1 ≈ ρ2 - end -end diff --git a/test/rocm/ctmrg/unitcell.jl b/test/rocm/ctmrg/unitcell.jl deleted file mode 100644 index 9ea614f1e..000000000 --- a/test/rocm/ctmrg/unitcell.jl +++ /dev/null @@ -1,125 +0,0 @@ -using Test -using Random -using PEPSKit -using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, ScramblingEnvGauge -using TensorKit -using AMDGPU, Adapt - -# settings -Random.seed!(91283219347) -stype = ComplexF64 -ctm_algs = [ - SequentialCTMRG(; projector_alg = :HalfInfiniteProjector), - SequentialCTMRG(; projector_alg = :FullInfiniteProjector), - SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector), - SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector), -] - -function test_unitcell( - ctm_alg, unitcell, - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) - peps = adapt(ROCArray, InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces)) - env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) - - # apply one CTMRG iteration with fixeds - env′, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env, ctm_alg) - env″, info = ctmrg_iteration(InfiniteSquareNetwork(peps), env′, ctm_alg) # another iteration to fix spaces - - # compute random expecation value to test matching bonds - random_op = adapt( - ROCArray, LocalOperator( - Pspaces, - [ - (c,) => randn( - scalartype(peps), - Pspaces[c], Pspaces[c], - ) for c in CartesianIndices(unitcell) - ]..., - ) - ) - @test expectation_value(peps, random_op, env) isa Number - @test expectation_value(peps, random_op, env′) isa Number - - # test if gauge fixing routines run through - signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env″, env′, ScramblingEnvGauge()) - @test signs isa Array - @test corner_phases isa Array - @test edge_phases isa Array - return nothing -end - -@testset "Random Cartesian spaces with $ctm_alg" for ctm_alg in ctm_algs - unitcell = (3, 3) - - Pspaces = ComplexSpace.(rand(2:3, unitcell...)) - Nspaces = ComplexSpace.(rand(2:4, unitcell...)) - Espaces = ComplexSpace.(rand(2:4, unitcell...)) - chis_north = ComplexSpace.(rand(5:10, unitcell...)) - chis_east = ComplexSpace.(rand(5:10, unitcell...)) - chis_south = ComplexSpace.(rand(5:10, unitcell...)) - chis_west = ComplexSpace.(rand(5:10, unitcell...)) - - test_unitcell( - ctm_alg, unitcell, - Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, - ) -end - -@testset "Specific U1 spaces with $ctm_alg" for ctm_alg in ctm_algs - unitcell = (2, 2) - - PA = U1Space(-1 => 1, 0 => 1) - PB = U1Space(0 => 1, 1 => 1) - Vpeps = U1Space(-1 => 2, 0 => 1, 1 => 2) - Venv = U1Space(-2 => 2, -1 => 3, 0 => 4, 1 => 3, 2 => 2) - - Pspaces = [PA PB; PB PA] - Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] - chis = [Venv Venv; Venv Venv] - - test_unitcell(ctm_alg, unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) - - # 4x4 unit cell with all 32 inequivalent bonds - # - # 10 4 7 32 - # | | | | - # 3--A--1--B--5--C--8--D--3 - # | | | | - # 2 6 9 11 - # | | | | - # 14--E-12--F-15--G-17--H-14 - # | | | | - # 13 16 18 19 - # | | | | - # 22--I-20--J-23--K-25--L-22 - # | | | | - # 21 24 26 27 - # | | | | - # 29--M-28--N-30--O-31--P-29 - # | | | | - # 10 4 7 32 - - phys_space = Vect[U1Irrep](1 => 1, -1 => 1) - corner_space = Vect[U1Irrep](0 => 1, 1 => 1, -1 => 1) - vspaces = map(i -> Vect[U1Irrep](0 => 1 + i % 4, 1 => i ÷ 4 % 4, -2 => i ÷ 16), 1:32) - @test length(Set(vspaces)) == 32 - - Espaces = [ - vspaces[1] vspaces[5] vspaces[8] vspaces[3] - vspaces[12] vspaces[15] vspaces[17] vspaces[14] - vspaces[20] vspaces[23] vspaces[25] vspaces[22] - vspaces[28] vspaces[30] vspaces[31] vspaces[29] - ] - - Nspaces = [ - vspaces[10] vspaces[4] vspaces[7] vspaces[32] - vspaces[2] vspaces[6] vspaces[9] vspaces[11] - vspaces[13] vspaces[16] vspaces[18] vspaces[19] - vspaces[21] vspaces[24] vspaces[26] vspaces[27] - ] - Pspaces = fill(phys_space, (4, 4)) - chis = fill(corner_space, (4, 4)) - - test_unitcell(ctm_alg, unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) -end diff --git a/test/rocm/gradients/c4v_ctmrg_gradients.jl b/test/rocm/gradients/c4v_ctmrg_gradients.jl deleted file mode 100644 index bb238b68f..000000000 --- a/test/rocm/gradients/c4v_ctmrg_gradients.jl +++ /dev/null @@ -1,131 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using Zygote -using OptimKit -using KrylovKit -using AMDGPU, Adapt - -sd = 42039482052 - -## Test C4v CTMRG gradients -# ------------------------------------------- -χbond = 2 -χenv = 6 -symmetry = RotateReflect() -Pspaces = [ComplexSpace(2)] -Vspaces = [ComplexSpace(χbond)] -Espaces = [ComplexSpace(χenv)] -models = [adapt(ROCArray, heisenberg_XYZ(InfiniteSquare()))] -names = ["Heisenberg"] - -gradtol = 1.0e-4 -ctmrg_verbosity = 1 -ctmrg_algs = [[:C4vCTMRG]] -projector_algs = [[:C4vEighProjector, :C4vQRProjector]] -decomposition_rrule_algs = [[:FullPullback, :TruncPullback]] -gradient_algs = [[nothing, :FixedPointGradient]] -# the gradient solvers are device-independent and are covered exhaustively by the CPU test, -# so only keep the two KrylovKit code paths here, since GPU gradients are slow -gradient_solver_algs = [[:Arnoldi, :GMRES]] -steps = -0.01:0.005:0.01 - -# record which rrule alg is compatible with which projector alg -allowed_rrule_algs = Dict( - :C4vEighProjector => keys(PEPSKit.EIGH_RRULE_SYMBOLS), - :C4vQRProjector => keys(PEPSKit.QR_RRULE_SYMBOLS), -) - -# be selective on which configurations to test the naive gradient for -naive_gradient_combinations = [(:C4vCTMRG, :C4vEighProjector, :FullPullback), (:C4vCTMRG, :C4vQRProjector, :FullPullback)] -naive_gradient_done = Set() - -## Tests -# ------ -@testset "AD C4v CTMRG energy gradients for $(names[i]) model" verbose = true for i in - eachindex( - models - ) - Pspace = Pspaces[i] - Vspace = Vspaces[i] - Espace = Espaces[i] - calgs = ctmrg_algs[i] - palgs = projector_algs[i] - dalgs = decomposition_rrule_algs[i] - galgs = gradient_algs[i] - gsalgs = gradient_solver_algs[i] - @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=(alg = :$gradient_alg, solver_alg = :$gradient_solver_alg)" for ( - ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, gradient_solver_alg, - ) in Iterators.product( - calgs, palgs, dalgs, galgs, gsalgs - ) - - # check for allowed algorithm combinations when testing naive gradient - if isnothing(gradient_alg) - combo = (ctmrg_alg, projector_alg, decomposition_rrule_alg) - combo in naive_gradient_combinations || continue - combo in naive_gradient_done && continue - push!(naive_gradient_done, combo) - gradient_solver_alg = nothing # unused in naive gradient, so set to nothing to avoid confusion - end - - # check for allowed combinations of projector alg and decomposition rrule alg - decomposition_rrule_alg in allowed_rrule_algs[projector_alg] || continue - - # construct appropriate decomposition struct to pass custom rrule alg - decomposition_alg = if projector_alg == :C4vEighProjector - EighAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) - elseif projector_alg == :C4vQRProjector - QRAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) - else - error("unknown projector alg: $projector_alg") - end - - @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg)) on $(names[i])" - Random.seed!(sd) - dir = adapt(ROCArray, InfinitePEPS(Pspace, Vspace)) - psi = adapt(ROCArray, InfinitePEPS(Pspace, Vspace)) - symmetrize!(psi, symmetry) - symmetrize!(dir, symmetry) - # instantiate to avoid having to type this twice... - contrete_ctmrg_alg = PEPSKit.CTMRGAlgorithm(; - alg = ctmrg_alg, - verbosity = ctmrg_verbosity, - projector_alg = projector_alg, - decomposition_alg, - ) - # instantiate because hook_pullback doesn't go through the keyword selector... - concrete_gradient_alg = if isnothing(gradient_alg) - nothing # TODO: add this to the PEPSKit.GradientAlgorithm selector? - else - PEPSKit.GradientAlgorithm(; - alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol) - ) - end - env0 = PEPSKit.initialize_random_c4v_env(psi, Espace) - env, = leading_boundary(env0, psi, contrete_ctmrg_alg) - alphas, fs, dfs1, dfs2 = OptimKit.optimtest( - (psi, env), - dir; - alpha = steps, - retract = PEPSKit.peps_retract, - inner = PEPSKit.real_inner, - ) do (peps, env) - E, g = Zygote.withgradient(peps) do psi - env2, = PEPSKit.hook_pullback( - leading_boundary, - env, - psi, - contrete_ctmrg_alg; - alg_rrule = concrete_gradient_alg, - ) - return cost_function(psi, env2, models[i]) - end - g = only(g) - symmetrize!(g, symmetry) - return E, g - end - @test dfs1 ≈ dfs2 atol = 1.0e-2 - end -end diff --git a/test/rocm/gradients/ctmrg_gradients.jl b/test/rocm/gradients/ctmrg_gradients.jl deleted file mode 100644 index d7add28b1..000000000 --- a/test/rocm/gradients/ctmrg_gradients.jl +++ /dev/null @@ -1,167 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using Zygote -using OptimKit -using KrylovKit -using AMDGPU, Adapt - -## Test models, gradmodes and CTMRG algorithm -# ------------------------------------------- -χbond = 2 -χenv = 6 -Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] -Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] -Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] -models = [ - adapt(ROCArray, heisenberg_XYZ(InfiniteSquare())), - adapt(ROCArray, pwave_superconductor(InfiniteSquare())), -] -names = ["Heisenberg", "p-wave superconductor"] - -gradtol = 1.0e-4 -ctmrg_verbosity = 0 -ctmrg_algs = [[:SequentialCTMRG, :SimultaneousCTMRG], [:SequentialCTMRG, :SimultaneousCTMRG]] -projector_algs = [[:HalfInfiniteProjector, :FullInfiniteProjector], [:HalfInfiniteProjector, :FullInfiniteProjector]] -svd_rrule_algs = [[:FullPullback, :TruncPullback, :Arnoldi], [:FullPullback, :Arnoldi]] -gradient_algs = [[nothing, :FixedPointGradient], [:FixedPointGradient]] -# the gradient solvers are device-independent and are covered exhaustively by the CPU test, -# so only keep the two KrylovKit code paths here, since GPU gradients are slow -gradient_solver_algs = [[:Arnoldi, :GMRES], [:Arnoldi, :GMRES]] -steps = -0.01:0.005:0.01 - -# don't check naive AD gradients for all algorithm combinations, since it's slow -naive_gradient_combinations = [ - (:SimultaneousCTMRG, :HalfInfiniteProjector, :FullPullback), - (:SimultaneousCTMRG, :FullInfiniteProjector, :FullPullback), - (:SequentialCTMRG, :HalfInfiniteProjector, :FullPullback), -] -naive_gradient_done = Set() - -# fixed-point gradients with sequential CTMRG are covered by the CPU test, -# so skip them here since GPU gradients are slow -function _skip_combination( - ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg - ) - ctmrg_alg == :SequentialCTMRG && !isnothing(gradient_alg) && return true - return false -end - - -## Tests -# ------ -@testset "AD CTMRG energy gradients for $(names[i]) model" verbose = true for i in - eachindex( - models - ) - Pspace = Pspaces[i] - Vspace = Vspaces[i] - Espace = Espaces[i] - calgs = ctmrg_algs[i] - palgs = projector_algs[i] - salgs = svd_rrule_algs[i] - galgs = gradient_algs[i] - gsalgs = gradient_solver_algs[i] - @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg, gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg))" for ( - ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg, gradient_solver_alg, - ) in Iterators.product( - calgs, palgs, salgs, galgs, gsalgs - ) - - # skip slow combinations that the CPU test already covers - _skip_combination(ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg) && continue - - # check for allowed algorithm combinations when testing naive gradient - if isnothing(gradient_alg) - combo = (ctmrg_alg, projector_alg, svd_rrule_alg) - combo in naive_gradient_combinations || continue - combo in naive_gradient_done && continue - push!(naive_gradient_done, combo) - gradient_solver_alg = nothing # unused in naive gradient, so set to nothing to avoid confusion - end - - @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg and gradient_alg=(; alg = :$gradient_alg, solver_alg = (; alg = :$gradient_solver_alg)) on $(names[i])" - Random.seed!(42039482030) - dir = adapt(ROCArray, InfinitePEPS(Pspace, Vspace)) - psi = adapt(ROCArray, InfinitePEPS(Pspace, Vspace)) - @test storagetype(psi) <: ROCArray - # instantiate to avoid having to type this twice... - concrete_ctmrg_alg = PEPSKit.CTMRGAlgorithm(; - alg = ctmrg_alg, - verbosity = ctmrg_verbosity, - projector_alg = projector_alg, - decomposition_alg = SVDAdjoint(; rrule_alg = (; alg = svd_rrule_alg)), - ) - # instantiate because hook_pullback doesn't go through the keyword selector... - concrete_gradient_alg = if isnothing(gradient_alg) - nothing # TODO: add this to the PEPSKit.GradientAlgorithm selector? - else - PEPSKit.GradientAlgorithm(; - alg = gradient_alg, solver_alg = (; alg = gradient_solver_alg, tol = gradtol) - ) - end - env, = leading_boundary(CTMRGEnv(psi, Espace), psi, concrete_ctmrg_alg) - @test storagetype(env) <: ROCArray - alphas, fs, dfs1, dfs2 = OptimKit.optimtest( - (psi, env), - dir; - alpha = steps, - retract = PEPSKit.peps_retract, - inner = PEPSKit.real_inner, - ) do (peps, env) - E, g = Zygote.withgradient(peps) do psi - env2, = PEPSKit.hook_pullback( - leading_boundary, - env, - psi, - concrete_ctmrg_alg; - alg_rrule = concrete_gradient_alg, - ) - return cost_function(psi, env2, models[i]) - end - - return E, only(g) - end - @test dfs1 ≈ dfs2 atol = 1.0e-2 - end -end - -## Regression test for gradient accuracy (https://github.com/QuantumKitHub/PEPSKit.jl/pull/276) -@testset "AD CTMRG energy gradient accuracy regression test (#276)" begin - Random.seed!(1234) - - boundary_alg = PEPSKit.CTMRGAlgorithm(; tol = 1.0e-10) - gradient_alg = PEPSKit.GradientAlgorithm(; tol = 5.0e-8) - - function fg((peps, env)) - E, g = Zygote.withgradient(peps) do ψ - env2, = PEPSKit.hook_pullback( - leading_boundary, - env, - ψ, - boundary_alg; - alg_rrule = gradient_alg, - ) - return cost_function(ψ, env2, H) - end - return E, only(g) - end - - # initialize randomly - H = adapt(ROCArray, heisenberg_XYZ(InfiniteSquare(1, 1))) - peps = adapt(ROCArray, PEPSKit.peps_normalize(InfinitePEPS(randn, ComplexF64, physicalspace(H)[1], ComplexSpace(3)))) - env0 = CTMRGEnv(randn, ComplexF64, peps, ComplexSpace(20)) - - # test gradient against finite-difference - Δx = 1.0e-5 - _, _, dfs1, dfs2 = OptimKit.optimtest( - fg, (peps, env0); - alpha = LinRange(-Δx, Δx, 2), - retract = PEPSKit.peps_retract, - inner = PEPSKit.real_inner, - ) - - # verify high gradient accuracy for small finite-difference step size - @test dfs1 ≈ dfs2 rtol = 1.0e-2 * Δx -end diff --git a/test/rocm/timeevol/cluster_projectors.jl b/test/rocm/timeevol/cluster_projectors.jl deleted file mode 100644 index 5ead59475..000000000 --- a/test/rocm/timeevol/cluster_projectors.jl +++ /dev/null @@ -1,258 +0,0 @@ -using Test -using TensorKit -using PEPSKit -using LinearAlgebra -using Random -import MPSKitModels: hubbard_space -using PEPSKit: sdiag_pow, _cluster_truncate!, _flip_virtuals! -using MPSKit: GenericMPSTensor, MPSBondTensor -using AMDGPU, Adapt - -# Utility setup -# ------------- -function _contract_left( - M::GenericMPSTensor{S, 4}, sl::DiagonalTensorMap{T, S} - ) where {T <: Number, S <: ElementarySpace} - @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) - M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) - @tensor sl1[e1; e0] := conj(M[w1; p n s e1]) * sl[w1; w0] * M0[w0; p n s e0] - return sl1 -end -function _contract_left( - M::GenericMPSTensor{S, 4}, ::Nothing - ) where {S <: ElementarySpace} - @assert !isdual(domain(M, 1)) - M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) - @tensor sl1[e1; e0] := conj(M[w; p n s e1]) * M0[w; p n s e0] - return sl1 -end - -function _contract_right( - M::GenericMPSTensor{S, 4}, sr::DiagonalTensorMap{T, S} - ) where {T <: Number, S <: ElementarySpace} - @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) - M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) - @tensor sr1[w0; w1] := M0[w0; p n s e0] * sr[e0; e1] * conj(M[w1; p n s e1]) - return sr1 -end -function _contract_right( - M::GenericMPSTensor{S, 4}, ::Nothing - ) where {S <: ElementarySpace} - @assert !isdual(codomain(M, 1)) - M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) - @tensor sr1[w0; w1] := M0[w0; p n s e] * conj(M[w1; p n s e]) - return sr1 -end - -""" -Verify the generalized left/right orthogonal condition -""" -function verify_cluster_orth( - Ms::Vector{T1}, wts::Vector{T2} - ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 4}, T2 <: DiagonalTensorMap} - N = length(Ms) - @assert length(wts) == N - 1 - lorths = fill(false, N - 1) - rorths = fill(false, N - 1) - # left orthogonal - for i in 1:(N - 1) - M, sl0 = Ms[i], wts[i] - sl1 = _contract_left(M, i == 1 ? nothing : wts[i - 1]) - lorths[i] = (normalize(TensorMap(sl0)) ≈ normalize(sl1)) # sl0 is DiagonalTensorMap while sl1 is not - end - # right orthogonal - for i in 2:N - M, sr0 = Ms[i], wts[i - 1] - sr1 = _contract_right(M, i == N ? nothing : wts[i]) - rorths[i - 1] = (normalize(TensorMap(sr0)) ≈ normalize(sr1)) - end - return lorths, rorths -end - -function inner_prod_cluster( - Ms1::Vector{T1}, Ms2::Vector{T2} - ) where { - T1 <: GenericMPSTensor{<:ElementarySpace, 4}, - T2 <: GenericMPSTensor{<:ElementarySpace, 4}, - } - N = length(Ms1) - @assert length(Ms2) == N - # physical spaces are assumed to be non-dual - @assert all(!isdual(space(t, 2)) for t in Ms1) - @assert all(!isdual(space(t, 2)) for t in Ms2) - # not the most efficient implementation - M1, M2 = Ms1[1], deepcopy(Ms2[1]) - for ax in 1:4 - isdual(space(M2, ax)) && twist!(M2, ax) - end - @tensor res[-1 -2] := conj(M1[1 2 3 4; -1]) * M2[1 2 3 4; -2] - for i in 2:(N - 1) - M1, M2 = Ms1[i], deepcopy(Ms2[i]) - for ax in 2:4 - isdual(space(M2, ax)) && twist!(M2, ax) - end - @tensor M[-1 -2; -3 -4] := conj(M1[-1 1 2 3; -3]) * M2[-2 1 2 3; -4] - @tensor res[-1 -2] := res[1 2] * M[1 2; -1 -2] - end - M1, M2 = Ms1[N], deepcopy(Ms2[N]) - for ax in 2:5 - isdual(space(M2, ax)) && twist!(M2, ax) - end - @tensor M[-1 -2] := conj(M1[-1 1 2 3; 4]) * M2[-2 1 2 3; 4] - return @tensor res[1 2] * M[1 2] -end - -function fidelity_cluster( - Ms1::Vector{T1}, Ms2::Vector{T2} - ) where { - T1 <: GenericMPSTensor{<:ElementarySpace, 4}, - T2 <: GenericMPSTensor{<:ElementarySpace, 4}, - } - return abs2(inner_prod_cluster(Ms1, Ms2)) / - (inner_prod_cluster(Ms1, Ms1) * inner_prod_cluster(Ms2, Ms2)) -end - -function mpo_to_gate3(gs::Vector{T}) where {T <: AbstractTensorMap} - #= - -4 -5 -6 - ↓ ↓ ↓ - g1 ←- 1 ←- g2 ←- 2 ←- g3 - ↓ ↓ ↓ - -1 -2 -3 - =# - @assert length(gs) == 3 - @tensor gate[-1 -2 -3; -4 -5 -6] := gs[1][-1 -4 1] * gs[2][1 -2 -5 2] * gs[3][2 -3 -6] - return gate -end - -Vspaces = [ - ( - U1Space(0 => 1, 1 => 1, -1 => 1), - U1Space(0 => 1, 1 => 2, -1 => 1)', - U1Space(0 => 4, 1 => 5, -1 => 6)', - ), - ( - Vect[FermionParity](0 => 1, 1 => 1), - Vect[FermionParity](0 => 2, 1 => 2), - Vect[FermionParity](0 => 6, 1 => 6)', - ), -] - -@testset "Cluster bond truncation with projectors" begin - Random.seed!(0) - N, n = 5, 2 - for (Vphy, Vns, V) in Vspaces - Vvirs = fill(Vns, N + 1) - Vvirs[n + 1] = V - Ms1 = map(1:N) do i - Vw, Ve = Vvirs[i], Vvirs[i + 1] - return adapt(ROCArray, rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve)) - end - normalize!.(Ms1, Inf) - flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] - # no truncation - Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) - truncs = [truncrank(dim(space(M, 1))) for M in Iterators.drop(Ms2, 1)] - wts2, ϵs, = _cluster_truncate!(Ms2, truncs) - @test all((ϵ == 0) for ϵ in ϵs) - normalize!.(Ms2, Inf) - @test fidelity_cluster(Ms1, Ms2) ≈ 1.0 - lorths, rorths = verify_cluster_orth(Ms2, wts2) - @test all(lorths) && all(rorths) - # truncation on one bond - Ms3 = _flip_virtuals!(deepcopy(Ms1), flips) - tspace = isdual(Vns) ? flip(Vns) : Vns - wts3, ϵs, = _cluster_truncate!(Ms3, fill(truncspace(tspace), N - 1)) - @test all((i == n) || (ϵ == 0) for (i, ϵ) in enumerate(ϵs)) - normalize!.(Ms3, Inf) - ϵ = ϵs[n] - wt2, wt3 = wts2[n], wts3[n] - _flip_virtuals!(Ms3, flips) - fid3, fid3_ = fidelity_cluster(Ms1, Ms3), fidelity_cluster(Ms2, Ms3) - @info "Fidelity of truncated cluster = $(fid3)" - @test fid3 ≈ fid3_ - @test fid3 ≈ (norm(wt3) / norm(wt2))^2 - @test fid3 ≈ 1.0 - (ϵ / norm(wt2))^2 - end -end -#= # TODO NEEDS REPARTITION FIX FOR DIAGONALTENSORMAP -@testset "Identity gate on 3-site cluster" begin - N, n = 3, 1 - for (Vphy, Vns, V) in Vspaces - Vvirs = fill(Vns, N + 1) - Vvirs[n + 1] = V - Ms1 = map(1:N) do i - Vw, Ve = Vvirs[i], Vvirs[i + 1] - return adapt(ROCArray, normalize(rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve), Inf)) - end - flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] - unit = id(Vphy) - gate = reduce(⊗, fill(unit, 3)) - gs = PEPSKit.gate_to_mpo(gate) - @test mpo_to_gate3(gs) ≈ gate - Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) - PEPSKit._apply_gatempo!(Ms2, gs) - fid = fidelity_cluster(Ms1, Ms2) - @test fid ≈ 1.0 - end - for (Vphy, Vns, V) in Vspaces - Vvirs = fill(Vns, N + 1) - Vvirs[n + 1] = V - Ms1 = map(1:N) do i - Vw, Ve = Vvirs[i], Vvirs[i + 1] - return adapt(ROCArray, normalize(rand(Vw ⊗ Vphy ⊗ Vphy' ⊗ Vns' ⊗ Vns ← Ve), Inf)) - end - flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] - unit = adapt(ROCArray, id(Vphy)) - gate = reduce(⊗, fill(unit, 3)) - gs = PEPSKit.gate_to_mpo(gate) - @test mpo_to_gate3(gs) ≈ gate - for gate_ax in 1:2 - Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) - PEPSKit._apply_gatempo!(Ms2, gs; gate_ax) - fid = fidelity_cluster( - [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms1], - [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms2] - ) - @test fid ≈ 1.0 - end - end -end - -@testset "Hubbard model SU (MPO gate)" begin - Nr, Nc = 2, 2 - ctmrg_tol = 1.0e-9 - Random.seed!(1459) - # with U(1) spin rotation symmetry - Pspace = hubbard_space(Trivial, U1Irrep) - Vspace = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) - Espace = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 8, (1, 1 // 2) => 4, (1, -1 // 2) => 4) - truncs_env = collect(truncerror(; atol = 1.0e-12) & truncrank(χ) for χ in [8, 16]) - peps0 = adapt(ROCArray, InfinitePEPS(rand, Float64, Pspace, Vspace, Vspace'; unitcell = (Nr, Nc))) - # make initial state bipartite - for r in 1:2 - peps0[r + 1, 2] = copy(peps0[r, 1]) - end - wts0 = SUWeight(peps0) - ham = adapt(ROCArray, hubbard_model(Float64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t = 1.0, U = 6.0, mu = 3.0)) - # applying 2-site gates decomposed to MPO or not, - # resulting energy should be almost the same - e_sites = map((true, false)) do force_mpo - peps, wts = deepcopy(peps0), deepcopy(wts0) - trunc = truncerror(; atol = 1.0e-10) & truncrank(4) - alg = SimpleUpdate(; trunc, force_mpo) - peps, wts, = time_evolve( - peps, ham, 0.01, 10000, alg, wts; tol = 1.0e-6, check_interval = 1000 - ) - normalize!.(peps.A, Inf) - env = CTMRGEnv(wts) - for trunc in truncs_env - env, = leading_boundary(env, peps; alg = :SequentialCTMRG, tol = ctmrg_tol, trunc) - end - e_site = cost_function(peps, env, ham) / (Nr * Nc) - @info "Energy (force_mpo = $(force_mpo)): $e_site" - return e_site - end - @test e_sites[1] ≈ e_sites[2] atol = 1.0e-4 -end -=# diff --git a/test/rocm/timeevol/j1j2_finiteT.jl b/test/rocm/timeevol/j1j2_finiteT.jl deleted file mode 100644 index ea724b6ef..000000000 --- a/test/rocm/timeevol/j1j2_finiteT.jl +++ /dev/null @@ -1,64 +0,0 @@ -using Test -using LinearAlgebra -using TensorKit -import MPSKitModels: σˣ, σᶻ -using PEPSKit -using AMDGPU, Adapt - -# Benchmark energy from high-temperature expansion -# at β = 0.3, 0.6 -# Physical Review B 86, 045139 (2012) Fig. 15-16 -bm = [-0.1235, -0.213] - -function converge_env(state, χ::Int) - env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) - trunc1 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) - return env -end - -Nr, Nc = 2, 2 -ham = adapt( - ROCArray, j1_j2_model( - Float64, SU2Irrep, InfiniteSquare(Nr, Nc); - J1 = 1.0, J2 = 0.5, sublattice = false - ) -) -@test storagetype(ham) <: ROCArray -pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) -@test storagetype(pepo0) <: ROCArray -wts0 = SUWeight(pepo0) -# 7 = 1 (spin-0) + 2 x 3 (spin-1) -trunc_pepo = truncrank(7) & truncerror(; atol = 1.0e-12) -check_interval = 100 -dt, nstep = 1.0e-3, 600 - -# PEPO approach -alg = SimpleUpdate(; trunc = trunc_pepo, purified = false) -evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) -pepo, wts, info = time_evolve(evolver; check_interval) -env = converge_env(InfinitePartitionFunction(pepo), 16) -energy = expectation_value(pepo, ham, env) / (Nr * Nc) -@info "β = $(dt * nstep): tr(ρH) = $(energy)" -@test dt * nstep ≈ info.t -@test energy ≈ bm[2] atol = 5.0e-3 - -# PEPS (purified PEPO) approach -alg = SimpleUpdate(; trunc = trunc_pepo, purified = true) -evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) -pepo, wts, info = time_evolve(evolver; check_interval) -env = converge_env(InfinitePartitionFunction(pepo), 16) -energy = expectation_value(pepo, ham, env) / (Nr * Nc) -@info "β = $(dt * nstep) / 2: tr(ρH) = $(energy)" -@test energy ≈ bm[1] atol = 5.0e-3 - -# test BP gauge fixing for purified iPEPO -bp_alg = BeliefPropagation(; maxiter = 100, tol = 1.0e-9) -bp_env, = leading_boundary(BPEnv(ones, Float64, pepo), pepo, bp_alg) -pepo, = gauge_fix(pepo, BPGauge(), bp_env) - -env = converge_env(InfinitePEPS(pepo), 16) -energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) -@info "β = $(dt * nstep): ⟨ρ|H|ρ⟩ = $(energy)" -@test dt * nstep ≈ info.t -@test energy ≈ bm[2] atol = 5.0e-3 diff --git a/test/rocm/timeevol/sitedep_truncation.jl b/test/rocm/timeevol/sitedep_truncation.jl deleted file mode 100644 index a78d9bd21..000000000 --- a/test/rocm/timeevol/sitedep_truncation.jl +++ /dev/null @@ -1,64 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using PEPSKit: _is_bipartite, _get_fixedspacetrunc -using AMDGPU, Adapt - -elt = Float64 -Nr, Nc = 2, 2 -Vps = fill(U1Space(1 / 2 => 1, -1 / 2 => 1), (Nr, Nc)) -Vns = [ - U1Space(0 => 1, 1 => 2, -1 => 1) U1Space(0 => 1, 1 => 2, -1 => 1)'; - U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 2, -1 => 1) -] -Ves1 = [ - U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); - U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' -] -Ves2 = [ - U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); - U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(0 => 1, 1 => 2, -1 => 1)' -] -Venv = U1Space(0 => 2, 1 => 1, -1 => 1) -Random.seed!(48736) -states = ( - adapt(ROCArray, InfinitePEPS(randn, elt, Vps, Vns, Ves1)), - adapt(ROCArray, InfinitePEPO(randn, elt, Vps, Vns, Ves2)), -) - -@testset "Rotation of SiteDependentTruncation" begin - state = states[1] - for f in (rotl90, rotr90, rot180) - trunc1 = f(_get_fixedspacetrunc(state)) - trunc2 = _get_fixedspacetrunc(f(state)) - @test all( - t1.space == t2.space for (t1, t2) in zip(trunc1.truncs, trunc2.truncs) - ) - end -end - -@testset "Simple update on $(typeof(state0).name.wrapper), bipartite = $(bipartite)" for - (state0, bipartite) in Iterators.product(states, (true, false)) - J2 = 0.5 - if bipartite - state0[2, 1] = copy(state0[1, 2]) - state0[2, 2] = copy(state0[1, 1]) - J2 = 0.0 - end - ham = adapt(ROCArray, j1_j2_model(elt, U1Irrep, InfiniteSquare(Nr, Nc); J1 = 1.0, J2, sublattice = false)) - # converted internally to SiteDependentTruncation - alg = SimpleUpdate(; trunc = FixedSpaceTruncation(), bipartite) - wts0 = SUWeight(state0) - state, wts, = time_evolve(state0, ham, 0.1, 1, alg, wts0) - for (t, t0) in zip(state.A, state0.A) - @test space(t) == space(t0) - end - for (wt, wt0) in zip(wts.data, wts0.data) - @test space(wt) == space(wt0) - end - if bipartite - @test _is_bipartite(state) - @test _is_bipartite(wts) - end -end diff --git a/test/rocm/timeevol/tf_ising_finiteT.jl b/test/rocm/timeevol/tf_ising_finiteT.jl deleted file mode 100644 index 046c69816..000000000 --- a/test/rocm/timeevol/tf_ising_finiteT.jl +++ /dev/null @@ -1,88 +0,0 @@ -using Test -using LinearAlgebra -using TensorKit -import MPSKitModels: σˣ, σᶻ -using PEPSKit, AMDGPU, Adapt - -# Benchmark data of [σx, σz] from HOTRG -# Physical Review B 86, 045139 (2012) Fig. 15-16 -bm_β = [0.5632, 0.0] -bm_2β = [0.5297, 0.8265] - -function converge_env(state, χ::Int) - trunc1 = truncrank(4) & truncerror(; atol = 1.0e-12) - env0 = initialize_ctmrg_environment(state, ProductStateInitialization()) - env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) - trunc2 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env, = leading_boundary(env, state; alg = :SequentialCTMRG, trunc = trunc2, tol = 1.0e-10) - return env -end - -function measure_mag(pepo::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) - r, c = 1, 1 - lattice = physicalspace(pepo) - Mx = adapt(ROCArray, LocalOperator(lattice, ((r, c),) => σˣ(Float64, Trivial))) - Mz = adapt(ROCArray, LocalOperator(lattice, ((r, c),) => σᶻ(Float64, Trivial))) - if purified - magx = expectation_value(pepo, Mx, pepo, env) - magz = expectation_value(pepo, Mz, pepo, env) - else - magx = expectation_value(pepo, Mx, env) - magz = expectation_value(pepo, Mz, env) - end - return [magx, magz] -end - -Nr, Nc = 2, 2 -ham = adapt(ROCArray, transverse_field_ising(Float64, Trivial, InfiniteSquare(Nr, Nc); J = 1.0, g = 2.0)) -pepo0 = adapt(ROCArray, PEPSKit.infinite_temperature_density_matrix(ham)) -@test TensorKit.storagetype(ham) <: ROCVector -@test TensorKit.storagetype(pepo0) <: ROCVector -wts0 = SUWeight(pepo0) - -trunc_pepo = truncrank(8) & truncerror(; atol = 1.0e-12) - -dt, nstep = 1.0e-3, 400 -β = dt * nstep - -# when g = 2, β = 0.4 and 2β = 0.8 belong to two phases (without and with nonzero σᶻ) -@testset "Finite-T SU (force_mpo = $(force_mpo))" for force_mpo in (false, true) - # use second order Trotter decomposition - symmetrize_gates = true - bipartite = true - - # PEPO approach: results at β, or T = 2.5 - alg = SimpleUpdate(; trunc = trunc_pepo, purified = false, bipartite, force_mpo) - pepo, wts, info = time_evolve(pepo0, ham, dt, nstep, alg, wts0; symmetrize_gates) - @test storagetype(pepo) <: ROCArray - - ## BP gauge fixing - bp_alg = BeliefPropagation(; maxiter = 100, tol = 1.0e-9, bipartite) - bp_env₀ = BPEnv(ones, Float64, pepo) - @test storagetype(bp_env₀) <: ROCArray - bp_env, = leading_boundary(bp_env₀, pepo, bp_alg) - pepo, = gauge_fix(pepo, BPGauge(), bp_env) - - env = converge_env(InfinitePartitionFunction(pepo), 16) - result_β = measure_mag(pepo, env) - @info "tr(σ(x,z)ρ) at T = $(1 / β): $(result_β)." - @test β ≈ info.t - @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) - - # use `compress` to reach 2β, or T = 1.25 - pepo2, = compress((pepo, pepo), LocalTruncation(trunc_pepo)) - normalize!.(pepo2.A) - env2 = converge_env(InfinitePartitionFunction(pepo2), 16) - result_2β = measure_mag(pepo2, env2) - @info "tr(σ(x,z)ρ) at T = $(1 / (2β)): $(result_2β)." - @test isapprox(abs.(result_2β), bm_2β, rtol = 5.0e-3) - - # Purification approach: results at 2β, or T = 1.25 - alg = SimpleUpdate(; trunc = trunc_pepo, purified = true, bipartite, force_mpo) - pepo, wts, info = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0; symmetrize_gates) - env = converge_env(InfinitePEPS(pepo), 8) - result_2β′ = measure_mag(pepo, env; purified = true) - @info "⟨ρ|σ(x,z)|ρ⟩ at T = $(1 / (2β)): $(result_2β′)." - @test 2 * β ≈ info.t - @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) -end diff --git a/test/rocm/timeevol/timestep.jl b/test/rocm/timeevol/timestep.jl deleted file mode 100644 index 211cccc5c..000000000 --- a/test/rocm/timeevol/timestep.jl +++ /dev/null @@ -1,35 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using AMDGPU, Adapt - -@testset "SimpleUpdate timestep" begin - Nr, Nc = 2, 2 - H = adapt(ROCArray, real(heisenberg_XYZ(ComplexF64, Trivial, InfiniteSquare(Nr, Nc); Jx = 1, Jy = 1, Jz = 1))) - Pspace, Vspace = ℂ^2, ℂ^4 - ψ0 = adapt(ROCArray, InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc))) - @test TensorKit.storagetype(ψ0) <: ROCArray - env0 = adapt(ROCArray, SUWeight(ψ0)) - alg = SimpleUpdate(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) - dt, nstep = 1.0e-2, 50 - # manual timestep - evolver = TimeEvolver(ψ0, H, dt, nstep, alg, env0) - ψ1, env1, info1 = deepcopy(ψ0), deepcopy(env0), nothing - for iter in 0:(nstep - 1) - ψ1, env1, info1 = timestep(evolver, ψ1, env1) - end - # time_evolve - ψ2, env2, info2 = time_evolve(ψ0, H, dt, nstep, alg, env0) - # for-loop syntax - ## manually reset internal state of evolver - evolver.state = PEPSKit.SUState(0, 0.0, ψ0, env0) - ψ3, env3, info3 = nothing, nothing, nothing - for state in evolver - ψ3, env3, info3 = state - end - # results should be *exactly* the same - @test ψ1 == ψ2 == ψ3 - @test env1 == env2 == env3 - @test info1 == info2 == info3 -end diff --git a/test/rocm/toolbox/densitymatrices.jl b/test/rocm/toolbox/densitymatrices.jl deleted file mode 100644 index 71b750c4e..000000000 --- a/test/rocm/toolbox/densitymatrices.jl +++ /dev/null @@ -1,79 +0,0 @@ -using TensorKit -using PEPSKit -using PEPSKit: contract_local_operator, contract_local_norm -using Test -using TestExtras -using AMDGPU, Adapt - -ds = Dict(Trivial => ℂ^2, U1Irrep => U1Space(i => d for (i, d) in zip(-1:1, (1, 1, 2))), FermionParity => Vect[FermionParity](0 => 2, 1 => 1)) -Ds = Dict(Trivial => ℂ^3, U1Irrep => U1Space(i => D for (i, D) in zip(-1:1, (1, 2, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) -χs = Dict(Trivial => ℂ^4, U1Irrep => U1Space(i => χ for (i, χ) in zip(-2:2, (1, 3, 2))), FermionParity => Vect[FermionParity](0 => 3, 1 => 2)) - -@testset "Single-layer densitymatrix contractions ($I)" for I in keys(ds) - d = ds[I] - D = Ds[I] - χ = χs[I] - ρ = adapt(ROCArray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) - - ρ_pf = @constinferred InfinitePartitionFunction(ρ) - env = CTMRGEnv(adapt(ROCArray, ρ_pf), χ) - - O = adapt(ROCArray, rand(d, d)) - @plansor O_pf[W S; N E] := O[p'; p] * ρ[1, 1, 1][p p'; N E S W] - - # Single site - O_singlesite = adapt(ROCArray, LocalOperator(physicalspace(ρ), ((1, 1),) => O)) - E1 = expectation_value(ρ, O_singlesite, env) - E2 = expectation_value(ρ_pf, CartesianIndex(1, 1) => O_pf, env) - @test E1 ≈ E2 - - # two sites - for inds in zip( - [(1, 1), (1, 1), (1, 1), (1, 2), (1, 1)], - [(2, 1), (1, 2), (2, 2), (2, 1), (3, 1)] - ) - O_twosite = adapt(ROCArray, LocalOperator(physicalspace(ρ), inds => O ⊗ O)) - E3 = expectation_value(ρ, O_twosite, env) - # TODO: not defined for partition functions... - end -end - -@testset "Double-layer densitymatrix contractions ($I)" for I in keys(ds) - d = ds[I] - D = Ds[I] - χ = χs[I] - ρ = adapt(ROCArray, InfinitePEPO(d, D; unitcell = (2, 2, 1))) - - ρ_peps = @constinferred InfinitePEPS(ρ) - env = CTMRGEnv(adapt(ROCArray, ρ_peps), χ) - - O = adapt(ROCArray, rand(d, d)) - F = adapt(ROCArray, isomorphism(fuse(d ⊗ d'), d ⊗ d')) - @tensor O_doubled[-1; -2] := F[-1; 1 2] * O[1; 3] * twist(F', 2)[3 2; -2] - - # Single site - site = (1, 1) - O_singlesite = adapt(ROCArray, LocalOperator(physicalspace(ρ), (site,) => O)) - E1 = expectation_value(ρ, O_singlesite, ρ, env) - O_doubled_singlesite = adapt(ROCArray, LocalOperator(physicalspace(ρ_peps), (site,) => O_doubled)) - E2 = expectation_value(ρ_peps, O_doubled_singlesite, ρ_peps, env) - @test E1 ≈ E2 - val = contract_local_operator([site], O_doubled, ρ_peps, ρ_peps, env) - nrm = contract_local_norm([site], ρ_peps, ρ_peps, env) - @test E1 ≈ val / nrm - - # two sites - for inds in zip( - [(1, 1), (1, 1), (1, 1), (1, 2), (1, 1)], - [(2, 1), (1, 2), (2, 2), (2, 1), (3, 1)] - ) - O_twosite = adapt(ROCArray, LocalOperator(physicalspace(ρ), inds => O ⊗ O)) - E1 = expectation_value(ρ, O_twosite, ρ, env) - O_doubled_twosite = adapt(ROCArray, LocalOperator(physicalspace(ρ_peps), inds => O_doubled ⊗ O_doubled)) - E2 = expectation_value(ρ_peps, O_doubled_twosite, ρ_peps, env) - @test E1 ≈ E2 - val = contract_local_operator(collect(inds), O_doubled ⊗ O_doubled, ρ_peps, ρ_peps, env) - nrm = contract_local_norm(collect(inds), ρ_peps, ρ_peps, env) - @test E1 ≈ val / nrm - end -end diff --git a/test/rocm/utility/correlator.jl b/test/rocm/utility/correlator.jl deleted file mode 100644 index 3e2e611fc..000000000 --- a/test/rocm/utility/correlator.jl +++ /dev/null @@ -1,93 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using AMDGPU, Adapt - -const syms = (Z2Irrep, FermionParity) - -function get_spaces(sym::Type{<:Sector}) - @assert sym in syms - Nr, Nc = 2, 2 - Vphy = Vect[sym](0 => 1, 1 => 1) - V = Vect[sym](0 => 1, 1 => 2) - Venv = Vect[sym](0 => 2, 1 => 2) - Nspaces = [V' V; V V'] - Espaces = [V V'; V' V] - return Vphy, Venv, Nspaces, Espaces -end - -site0 = CartesianIndex(1, 1) -site1xs = collect(site0 + CartesianIndex(0, i) for i in [1, -1, 3, -2]) -site1ys = collect(site0 + CartesianIndex(i, 0) for i in [1, -1, 3, -2]) - -@testset "Correlator in InfinitePEPS ($(sym))" for sym in syms - Random.seed!(100) - Vphy, Venv, Nspaces, Espaces = get_spaces(sym) - # TODO: test dual physical space - for Vp in [Vphy] - op = adapt(ROCArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) - Pspaces = fill(Vp, size(Nspaces)) - peps = adapt(ROCArray, InfinitePEPS(randn, ComplexF64, Pspaces, Nspaces, Espaces)) - env = CTMRGEnv(randn, ComplexF64, peps, Venv) - for site1s in (site1xs, site1ys) - vals1 = correlator(peps, op, site0, site1s, env) - vals2 = map(site1s) do site1 - O = LocalOperator(Pspaces, (site0, site1) => op) - return expectation_value(peps, O, env) - end - @info vals1 - @info vals2 - @test vals1 ≈ vals2 - end - @test_throws ArgumentError correlator(peps, op, site0, site0, env) - end -end - -@testset "Correlator in purified InfinitePEPO ($(sym))" for sym in syms - Random.seed!(100) - Vphy, Venv, Nspaces, Espaces = get_spaces(sym) - # TODO: test dual physical space - for Vp in [Vphy] - op = adapt(ROCArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) - Pspaces = fill(Vp, size(Nspaces)) - pepo = adapt(ROCArray, InfinitePEPO(randn, ComplexF64, Pspaces, Nspaces, Espaces)) - peps = InfinitePEPS(pepo) - env = CTMRGEnv(randn, ComplexF64, peps, Venv) - for site1s in (site1xs, site1ys) - vals1 = correlator(pepo, op, site0, site1s, pepo, env) - vals2 = map(site1s) do site1 - O = LocalOperator(Pspaces, (site0, site1) => op) - return expectation_value(pepo, O, pepo, env) - end - @info vals1 - @info vals2 - @test vals1 ≈ vals2 - end - @test_throws ArgumentError correlator(pepo, op, site0, site0, pepo, env) - end -end - -@testset "Correlator in 1-layer InfinitePEPO ($(sym))" for sym in syms - Random.seed!(100) - Vphy, Venv, Nspaces, Espaces = get_spaces(sym) - # TODO: test dual physical space - for Vp in [Vphy] - op = adapt(ROCArray, randn(ComplexF64, Vp ⊗ Vp → Vp ⊗ Vp)) - Pspaces = fill(Vp, size(Nspaces)) - pepo = adapt(ROCArray, InfinitePEPO(randn, ComplexF64, Pspaces, Nspaces, Espaces)) - pf = InfinitePartitionFunction(pepo) - env = CTMRGEnv(randn, ComplexF64, pf, Venv) - for site1s in (site1xs, site1ys) - vals1 = correlator(pepo, op, site0, site1s, env) - vals2 = map(site1s) do site1 - O = LocalOperator(Pspaces, (site0, site1) => op) - return expectation_value(pepo, O, env) - end - @info vals1 - @info vals2 - @test vals1 ≈ vals2 - end - @test_throws ArgumentError correlator(pepo, op, site0, site0, env) - end -end diff --git a/test/rocm/utility/eigh_wrapper.jl b/test/rocm/utility/eigh_wrapper.jl deleted file mode 100644 index cdf124812..000000000 --- a/test/rocm/utility/eigh_wrapper.jl +++ /dev/null @@ -1,147 +0,0 @@ -using Test -using Random -using LinearAlgebra -using TensorKit -using ChainRulesCore, Zygote -using Accessors -using PEPSKit -using AMDGPU, Adapt -using MatrixAlgebraKit: TruncatedAlgorithm, diagview - -# Gauge-invariant loss function -function lossfun(A, alg, R = randn(space(A)), trunc = notrunc()) - alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) - D, V, = eigh_trunc(A, alg) - return real(dot(R, V * V')) + dot(D, D) # Overlap with random tensor R is gauge-invariant and differentiable -end - -dtype = ComplexF64 -n = 20 -χ = 10 -trunc = truncspace(ℂ^χ) -rtol = 1.0e-9 -Random.seed!(123456789) -r = adapt(ROCArray, randn(dtype, ℂ^n, ℂ^n)) -r = 0.5 * (r + r') # make r Hermitian -R = adapt(ROCArray, randn(space(r))) -R = 0.5 * (R + R') - -full_alg = EighAdjoint(; fwd_alg = (; alg = :DivideAndConquer), rrule_alg = (; alg = :FullPullback)) -trunc_alg = EighAdjoint(; fwd_alg = (; alg = :DivideAndConquer), rrule_alg = (; alg = :TruncPullback)) -iter_alg = EighAdjoint(; fwd_alg = (; alg = :Lanczos), rrule_alg = (; alg = :TruncPullback)) - -@testset "Non-truncated eigh" begin - l_full, g_full = withgradient(A -> lossfun(A, full_alg, R), r) - l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, R), r) - l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, R), r) - - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol -end - -@testset "Truncated eigh with χ=$χ" begin - l_full, g_full = withgradient(A -> lossfun(A, full_alg, R, trunc), r) - l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, R, trunc), r) - l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, R, trunc), r) - - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol -end - -@testset "Truncated eigh broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] - d, v = eigh_full(r) - d.data[1:2:n] .= d.data[2:2:n] # make every eigenvalue two-fold degenerate - r_degen = v * d * v' - - no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 - small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 - - l_only_cutoff, g_only_cutoff = withgradient( - A -> lossfun(A, alg, R, trunc), r_degen - ) # cutoff sets degenerate difference to zero - l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions - A -> lossfun(A, no_broadening_no_cutoff_alg, R, trunc), r_degen, - ) - l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions - A -> lossfun(A, small_broadening_alg, R, trunc), r_degen, - ) - - @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening - @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient - @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect -end - -symm_m, symm_n = 18, 24 -symm_space = Z2Space(0 => symm_m, 1 => symm_n) -symm_trspace = truncspace(Z2Space(0 => symm_m ÷ 2, 1 => symm_n ÷ 3)) -symm_r = adapt(ROCArray, randn(dtype, symm_space, symm_space)) -symm_r = 0.5 * (symm_r + symm_r') -symm_R = adapt(ROCArray, randn(dtype, space(symm_r))) -symm_R = 0.5 * (symm_R + symm_R') - -@testset "IterEig of symmetric tensors" begin - l_full, g_full = withgradient(A -> lossfun(A, full_alg, symm_R), symm_r) - l_trunc, g_trunc = withgradient(A -> lossfun(A, trunc_alg, symm_R), symm_r) - l_iter, g_iter = withgradient(A -> lossfun(A, iter_alg, symm_R), symm_r) - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol - - l_full_tr, g_full_tr = withgradient( - A -> lossfun(A, full_alg, symm_R, symm_trspace), symm_r - ) - l_trunc_tr, g_trunc_tr = withgradient( - A -> lossfun(A, trunc_alg, symm_R, symm_trspace), symm_r - ) - l_iter_tr, g_iter_tr = withgradient( - A -> lossfun(A, iter_alg, symm_R, symm_trspace), symm_r - ) - @test l_full_tr ≈ l_trunc_tr ≈ l_iter_tr - @test g_full_tr[1] ≈ g_trunc_tr[1] rtol = rtol - @test g_full_tr[1] ≈ g_iter_tr[1] rtol = rtol - @test g_trunc_tr[1] ≈ g_iter_tr[1] rtol = rtol - - iter_alg_fallback = @set iter_alg.fwd_alg.fallback_threshold = 0.4 # Do dense decomposition in one block, sparse one in the other - l_iter_fb, g_iter_fb = withgradient( - A -> lossfun(A, iter_alg_fallback, symm_R, symm_trspace), symm_r - ) - @test l_iter_fb ≈ l_trunc_tr ≈ l_full_tr - @test g_full_tr[1] ≈ g_iter_fb[1] rtol = rtol - @test g_trunc_tr[1] ≈ g_iter_fb[1] rtol = rtol -end - -@testset "Truncated symmetric eigh broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] - d, v = eigh_full(symm_r) - # make every singular value in the 0-sector three-fold degenerate - b0 = diagview(block(d, Z2Irrep(0))) - b0[1:3:symm_m] .= b0[3:3:symm_m] - b0[2:3:symm_m] .= b0[3:3:symm_m] - # make every singular value in the 1-sector two-fold degenerate - b1 = diagview(block(d, Z2Irrep(1))) - b1[1:2:symm_n] .= b1[2:2:symm_n] - symm_r_degen = v * d * v' - - no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 - small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 - - l_only_cutoff, g_only_cutoff = withgradient( - A -> lossfun(A, alg, symm_R, symm_trspace), symm_r_degen - ) # cutoff sets degenerate difference to zero - l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions - A -> lossfun(A, no_broadening_no_cutoff_alg, symm_R, symm_trspace), - symm_r_degen, - ) - l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions - A -> lossfun(A, small_broadening_alg, symm_R, symm_trspace), - symm_r_degen, - ) - - @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening - @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient - @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect -end diff --git a/test/rocm/utility/retractions.jl b/test/rocm/utility/retractions.jl deleted file mode 100644 index 3cbb68b75..000000000 --- a/test/rocm/utility/retractions.jl +++ /dev/null @@ -1,34 +0,0 @@ -using Test -using Random -using LinearAlgebra -using TensorKit -using VectorInterface -using PEPSKit -using Adapt, AMDGPU - -dtype = ComplexF64 -Vphyss = [ℂ^2, U1Space(0 => 1, -1 => 1, 1 => 1)] -Vpepss = [ℂ^4, U1Space(0 => 2, -1 => 1, 1 => 1)] - -@testset "Norm-preserving tensor retractions for sectortype $(sectortype(Vphyss[i]))" for i in - eachindex( - Vphyss - ) - Vphys = Vphyss[i] - Vpeps = Vpepss[i] - peps_space = Vphys ← Vpeps ⊗ Vpeps ⊗ Vpeps' ⊗ Vpeps' - - α = 1.0e-1 * randn(Float64) - A = adapt(ROCArray, randn(dtype, peps_space)) - normalized_A = scale(A, inv(norm(A))) - η = adapt(ROCArray, randn(dtype, peps_space)) - ζ = adapt(ROCArray, randn(dtype, peps_space)) - add!(η, normalized_A, -inner(normalized_A, η)) - add!(ζ, normalized_A, -inner(normalized_A, ζ)) - - A´, ξ = PEPSKit.norm_preserving_retract(A, η, α) - @test norm(A´) ≈ norm(A) rtol = 1.0e-12 - - PEPSKit.norm_preserving_transport!(ζ, A, η, α, A´) - @test inner(ζ, A´) ≈ 0 atol = 1.0e-12 -end diff --git a/test/rocm/utility/svd_wrapper.jl b/test/rocm/utility/svd_wrapper.jl deleted file mode 100644 index 55ee835aa..000000000 --- a/test/rocm/utility/svd_wrapper.jl +++ /dev/null @@ -1,189 +0,0 @@ -using Test -using Random -using LinearAlgebra -using TensorKit -using ChainRulesCore, Zygote -using Accessors -using PEPSKit -using Adapt, AMDGPU -using MatrixAlgebraKit: TruncatedAlgorithm, diagview, svd_trunc_no_error - -# Gauge-invariant loss function -function lossfun(svd_trunc_f, A, alg, R = randn(space(A)), trunc = notrunc()) - alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) - USV = svd_trunc_f(A, alg) - U, S, V = USV[1:3] # avoid looking at ϵ if present - return real(dot(R, U * V)) + dot(S, S) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n -end - -dtype = ComplexF64 -m, n = 20, 30 -χ = 12 -trunc = truncspace(ℂ^χ) -rtol = 1.0e-9 -Random.seed!(12345678) -r = adapt(ROCArray, randn(dtype, ℂ^m, ℂ^n)) -R = adapt(ROCArray, randn(space(r))) - -full_alg = SVDAdjoint(; rrule_alg = (; alg = :FullPullback, degeneracy_atol = 1.0e-13)) -trunc_alg = SVDAdjoint(; rrule_alg = (; alg = :TruncPullback, degeneracy_atol = 1.0e-13)) -iter_alg = SVDAdjoint(; fwd_alg = (; alg = :GKL)) - -@testset "Non-truncated SVD $f" for f in (svd_trunc, svd_trunc_no_error) - l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, R), r) - l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, R), r) - l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, R), r) - - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol -end - -@testset "Truncated SVD $f with χ=$χ" for f in (svd_trunc, svd_trunc_no_error) - l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, R, trunc), r) - l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, R, trunc), r) - l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, R, trunc), r) - - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol -end - -@testset "Truncated SVD broadening for $f, $(alg.rrule_alg)" for f in (svd_trunc, svd_trunc_no_error), alg in [full_alg, trunc_alg] - u, s, v, = svd_compact(r) - s.data[1:2:m] .= s.data[2:2:m] # make every singular value two-fold degenerate - r_degen = u * s * v - - no_broadening_no_cutoff_alg = @set full_alg.rrule_alg.degeneracy_atol = 1.0e-30 - small_broadening_alg = @set full_alg.rrule_alg.degeneracy_atol = 1.0e-13 - - l_only_cutoff, g_only_cutoff = withgradient( - A -> lossfun(f, A, full_alg, R, trunc), r_degen - ) # cutoff sets degenerate difference to zero - l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions - A -> lossfun(f, A, no_broadening_no_cutoff_alg, R, trunc), r_degen, - ) - l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions - A -> lossfun(f, A, small_broadening_alg, R, trunc), r_degen, - ) - - @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening - @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient - @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect -end - -symm_m, symm_n = 18, 24 -symm_space = Z2Space(0 => symm_m, 1 => symm_n) -symm_trspace = truncspace(Z2Space(0 => symm_m ÷ 2, 1 => symm_n ÷ 3)) -symm_r = adapt(ROCArray, randn(dtype, symm_space, symm_space)) -symm_R = adapt(ROCArray, randn(dtype, space(symm_r))) - -@testset "IterSVD of symmetric tensors $f" for f in (svd_trunc, svd_trunc_no_error) - l_full, g_full = withgradient(A -> lossfun(f, A, full_alg, symm_R), symm_r) - l_trunc, g_trunc = withgradient(A -> lossfun(f, A, trunc_alg, symm_R), symm_r) - l_iter, g_iter = withgradient(A -> lossfun(f, A, iter_alg, symm_R), symm_r) - @test l_full ≈ l_trunc ≈ l_iter - @test g_full[1] ≈ g_trunc[1] rtol = rtol - @test g_full[1] ≈ g_iter[1] rtol = rtol - @test g_trunc[1] ≈ g_iter[1] rtol = rtol - - l_full_tr, g_full_tr = withgradient( - A -> lossfun(f, A, full_alg, symm_R, symm_trspace), symm_r - ) - l_trunc_tr, g_trunc_tr = withgradient( - A -> lossfun(f, A, trunc_alg, symm_R, symm_trspace), symm_r - ) - l_iter_tr, g_iter_tr = withgradient( - A -> lossfun(f, A, iter_alg, symm_R, symm_trspace), symm_r - ) - @test l_full_tr ≈ l_trunc_tr ≈ l_iter_tr - @test g_full_tr[1] ≈ g_trunc_tr[1] rtol = rtol - @test g_full_tr[1] ≈ g_iter_tr[1] rtol = rtol - @test g_trunc_tr[1] ≈ g_iter_tr[1] rtol = rtol - - iter_alg_fallback = @set iter_alg.fwd_alg.fallback_threshold = 0.4 # Do dense decomposition in one block, sparse one in the other - l_iter_fb, g_iter_fb = withgradient( - A -> lossfun(f, A, iter_alg_fallback, symm_R, symm_trspace), symm_r - ) - @test l_iter_fb ≈ l_trunc_tr ≈ l_full_tr - @test g_full_tr[1] ≈ g_iter_fb[1] rtol = rtol - @test g_trunc_tr[1] ≈ g_iter_fb[1] rtol = rtol -end - -@testset "Truncated symmetric SVD broadening for $f, $(alg.rrule_alg)" for f in (svd_trunc, svd_trunc_no_error), alg in [full_alg, trunc_alg] - u, s, v, = svd_compact(symm_r) - # make every singular value in the 0-sector three-fold degenerate - b0 = diagview(block(s, Z2Irrep(0))) - b0[1:3:symm_m] .= b0[3:3:symm_m] - b0[2:3:symm_m] .= b0[3:3:symm_m] - # make every singular value in the 1-sector two-fold degenerate - b1 = diagview(block(s, Z2Irrep(1))) - b1[1:2:symm_n] .= b1[2:2:symm_n] - symm_r_degen = u * s * v - - no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 - small_broadening_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-13 - - l_only_cutoff, g_only_cutoff = withgradient( - A -> lossfun(f, A, alg, symm_R, symm_trspace), symm_r_degen - ) # cutoff sets degenerate difference to zero - l_no_broadening_no_cutoff, g_no_broadening_no_cutoff = withgradient( # degenerate singular value differences lead to divergent contributions - A -> lossfun(f, A, no_broadening_no_cutoff_alg, symm_R, symm_trspace), - symm_r_degen, - ) - l_small_broadening, g_small_broadening = withgradient( # broadening smoothens divergent contributions - A -> lossfun(f, A, small_broadening_alg, symm_R, symm_trspace), - symm_r_degen, - ) - - @test l_only_cutoff ≈ l_no_broadening_no_cutoff ≈ l_small_broadening - @test norm(g_no_broadening_no_cutoff[1] - g_small_broadening[1]) > 1.0e-2 # divergences mess up the gradient - @test g_only_cutoff[1] ≈ g_small_broadening[1] rtol = rtol # cutoff and broadening have similar effect -end - -# TODO: Add when IterSVD is implemented for HalfInfiniteEnv -# χbond = 2 -# χenv = 6 -# ctm_alg = CTMRG(; tol=1e-10, verbosity=2, svd_alg=SVDAdjoint()) -# Random.seed!(91283219347) -# H = heisenberg_XYZ(InfiniteSquare()) -# psi = InfinitePEPS(ComplexSpace(2), ComplexSpace(χbond)) -# env = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); -# hienv = HalfInfiniteEnv( -# env.corners[1], -# env.corners[2], -# env.edges[4], -# env.edges[1], -# env.edges[1], -# env.edges[2], -# psi[1], -# psi[1], -# psi[1], -# psi[1], -# ) -# hienv_dense = hienv() -# env_R = randn(space(hienv)) - -# svd_trunc!(hienv, iter_alg) - -# @testset "IterSVD with HalfInfiniteEnv function handle" begin -# # Equivalence of dense and sparse contractions -# x₀ = PEPSKit.random_start_vector(hienv) -# x′ = hienv(x₀, Val(false)) -# x″ = hienv(x′, Val(true)) -# x‴ = hienv(x″, Val(false)) - -# a = hienv_dense * x₀ -# b = hienv_dense' * a -# c = hienv_dense * b -# @test a ≈ x′ -# @test b ≈ x″ -# @test c ≈ x‴ - -# # l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, env_R), hienv_dense) -# # l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, env_R), hienv) -# # @test l_itersvd ≈ l_fullsvd -# # @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol -# end diff --git a/test/rocm/utility/symmetrization.jl b/test/rocm/utility/symmetrization.jl deleted file mode 100644 index 0a5cd4073..000000000 --- a/test/rocm/utility/symmetrization.jl +++ /dev/null @@ -1,53 +0,0 @@ -using Test -using PEPSKit -using PEPSKit: herm_depth, herm_width, _fit_spaces -using TensorKit -using Adapt, AMDGPU - -@testset "ReflectDepth" for unitcell in [(1, 1), (2, 2), (3, 3), (3, 2)] - peps = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) - - peps_depth = symmetrize!(deepcopy(peps), ReflectDepth()) - peps_reflect = _fit_spaces( - InfinitePEPS(reverse(map(herm_depth, peps_depth.A); dims = 1)), peps_depth - ) - @test peps_depth ≈ peps_reflect -end - -@testset "ReflectWidth" for unitcell in [(1, 1), (2, 2), (3, 3), (3, 2)] - peps = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) - - peps_width = symmetrize!(deepcopy(peps), ReflectWidth()) - peps_reflect = _fit_spaces( - InfinitePEPS(reverse(map(herm_width, peps_width.A); dims = 2)), peps_width - ) - @test peps_width ≈ peps_reflect -end - -@testset "Rotate" for unitcell in [(1, 1), (2, 2), (3, 3)] - peps = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) - - peps_rot = symmetrize!(deepcopy(peps), Rotate()) - @test peps_rot ≈ _fit_spaces(rotl90(peps_rot), peps_rot) - @test peps_rot ≈ _fit_spaces(rot180(peps_rot), peps_rot) - @test peps_rot ≈ _fit_spaces(rotr90(peps_rot), peps_rot) -end - -@testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] - peps = adapt(ROCArray, InfinitePEPS(ComplexSpace(2), ComplexSpace(2); unitcell)) - - peps_full = symmetrize!(deepcopy(peps), RotateReflect()) - @test peps_full ≈ _fit_spaces(rotl90(peps_full), peps_full) - @test peps_full ≈ _fit_spaces(rot180(peps_full), peps_full) - @test peps_full ≈ _fit_spaces(rotr90(peps_full), peps_full) - - peps_reflect_depth = _fit_spaces( - InfinitePEPS(reverse(map(herm_depth, peps_full.A); dims = 1)), peps_full - ) - @test peps_full ≈ peps_reflect_depth - - peps_reflect_width = _fit_spaces( - InfinitePEPS(reverse(map(herm_width, peps_full.A); dims = 2)), peps_full - ) - @test peps_full ≈ peps_reflect_width -end diff --git a/test/runtests.jl b/test/runtests.jl index 4e5adf2f6..89981e3f7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,18 +6,6 @@ testsuite = find_tests(@__DIR__) # remove testsuite filter!(!(startswith("testsuite") ∘ first), testsuite) -# CUDA tests: only run if CUDA is functional -using CUDA -CUDA.functional() || filter!(!startswith("cuda") ∘ first, testsuite) -# AMDGPU tests: only run if AMDGPU is functional -using AMDGPU -AMDGPU.functional() || filter!(!startswith("rocm") ∘ first, testsuite) - -# On Buildkite (GPU CI runner): only run CUDA and AMDGPU tests -if get(ENV, "BUILDKITE", "false") == "true" - f(str) = startswith(first(str), "cuda") || startswith(first(str), "rocm") - filter!(f, testsuite) -end # --fast to indicate a smaller set of tests args = parse_args(ARGS; custom = ["fast"]) diff --git a/test/testsuite/ctmrg/unitcell.jl b/test/testsuite/ctmrg/unitcell.jl index fb7b24e70..abcd74ebe 100644 --- a/test/testsuite/ctmrg/unitcell.jl +++ b/test/testsuite/ctmrg/unitcell.jl @@ -31,11 +31,11 @@ function test_unitcell( Pspaces, [ (c,) => adapt( - AT, randn( - scalartype(peps), - Pspaces[c], Pspaces[c], - ) - ) for c in CartesianIndices(unitcell) + AT, randn( + scalartype(peps), + Pspaces[c], Pspaces[c], + ) + ) for c in CartesianIndices(unitcell) ]..., ) @test expectation_value(peps, random_op, env) isa Number From 9a343dae8ab9c6fd610a1ef5afe642945bd4ae4b Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 21 Sep 2026 07:11:20 -0400 Subject: [PATCH 52/69] Use new TO version --- Project.toml | 1 - test/testsuite/ctmrg/unitcell.jl | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Project.toml b/Project.toml index 16f339a0d..0fc65f5e6 100644 --- a/Project.toml +++ b/Project.toml @@ -38,7 +38,6 @@ GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" [sources] MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} MatrixAlgebraKit = {rev = "ksh/batched_svd", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} -TensorOperations = {rev = "main", url = "https://github.com/QuantumKitHub/TensorOperations.jl"} TensorKit = {rev = "ksh/batched_svd", url = "https://github.com/QuantumKitHub/TensorKit.jl"} [extensions] diff --git a/test/testsuite/ctmrg/unitcell.jl b/test/testsuite/ctmrg/unitcell.jl index abcd74ebe..fb7b24e70 100644 --- a/test/testsuite/ctmrg/unitcell.jl +++ b/test/testsuite/ctmrg/unitcell.jl @@ -31,11 +31,11 @@ function test_unitcell( Pspaces, [ (c,) => adapt( - AT, randn( - scalartype(peps), - Pspaces[c], Pspaces[c], - ) - ) for c in CartesianIndices(unitcell) + AT, randn( + scalartype(peps), + Pspaces[c], Pspaces[c], + ) + ) for c in CartesianIndices(unitcell) ]..., ) @test expectation_value(peps, random_op, env) isa Number From 3c3d9eebbc6030c422209b39145c226bbce2d1eb Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 22 Sep 2026 10:16:47 -0400 Subject: [PATCH 53/69] Fix the c4v gradients --- .../optimization/implicit_differentiation.jl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/algorithms/optimization/implicit_differentiation.jl b/src/algorithms/optimization/implicit_differentiation.jl index 37ae322b4..8c8d84402 100644 --- a/src/algorithms/optimization/implicit_differentiation.jl +++ b/src/algorithms/optimization/implicit_differentiation.jl @@ -648,8 +648,17 @@ function PEPSKit._rrule( # variables of the characteristic equations, living in the full projector spaces and # constrained to the orthogonal complements of the isometries inside the characteristic # equations - u = zerovector.(U) - v = zerovector.(V) + # get the projector nullspaces + UL = left_null.(U) + VR = right_null.(V) + + # instantiate the variables used in the characteristic equations + u = map(zip(U, UL)) do (Uc, ULc) + return zeros(storagetype(Uc), space(ULc, numind(ULc))' ← space(Uc, numind(Uc))') + end + v = map(zip(V, VR)) do (Vc, VRc) + return zeros(storagetype(Vc), space(Vc, 1) ← space(VRc, 1)) + end is = sdiag_pow.(s, -1) # also treat them as general complex tensors # Two tapes: the linear solve only uses the environment cotangents, so reading the From 34b293af0610c0ef9b381150ab5e6cd129fed6e4 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 23 Sep 2026 04:35:48 -0400 Subject: [PATCH 54/69] Update caching strategy --- src/algorithms/ctmrg/ctmrg.jl | 1 + src/algorithms/ctmrg/sequential.jl | 6 ------ src/utility/alloc_cache.jl | 23 +++++++++++++++-------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 47e171ca4..dcccfd1bd 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -134,6 +134,7 @@ function leading_boundary( env, info_iter = with_alloc_cache(storagetype(env), :ctmrg, iter, alloc_cache_depth(alg)) do ctmrg_iteration(network, env, alg) end + env = uncache(env, storagetype(env)) η, CS, TS = calc_convergence(env, CS, TS, alg) if η ≤ alg.tol && iter ≥ alg.miniter diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index f42101ce7..3ce75c4c1 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -37,12 +37,6 @@ end CTMRG_SYMBOLS[:SequentialCTMRG] = SequentialCTMRG -# A sequential sweep updates *one( direction at a time, then hands off the not-yet-updated -# directions, so those tensors can stay live for a full cycle of the -# *four* directions. This means we need 5 total cache elements to avoid overwriting something -# still in use. -alloc_cache_depth(::SequentialCTMRG) = 5 - """ ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) diff --git a/src/utility/alloc_cache.jl b/src/utility/alloc_cache.jl index 64feb3fce..d17b45abe 100644 --- a/src/utility/alloc_cache.jl +++ b/src/utility/alloc_cache.jl @@ -15,11 +15,19 @@ only the ones with repeated iterations. Belief propagation seems to not benefit from the caching as much, so it's currently unused there. -`iter` selects between two alternating caches per `caller`. A buffer allocated during iteration -`i` can't become reusable until later iterations have completely used and discarded its output. -For `:SimultaneousCTMRG` and simple update, that occurs after 2 iterations, while for `:SequentialCTMRG`, -it occurs after 5. With too few caches, it would be possible to overwrite the state -at later iterations while it's still being used. +`iter` selects between `depth` alternating caches per `caller`. A buffer allocated during +iteration `i` can't become reusable until later iterations have completely used and discarded +its output, so with too few caches it would be possible to overwrite state that is still in +use. A caller that copies its result out with [`uncache`](@ref) before the next iteration +begins needs only a single cache; simple update keeps 2, since it hands its result off after +the following step. + +!!! note + Buffers are only returned to the cache when the enclosing block exits, so a single + block retains *everything* it allocated rather than just its maximum live block. + Keeping more caches in the rotation multiplies this effect, which matters for + algorithms like (esp. sequential) CTMRG whose iterations allocate many + short-lived temporaries. Caching is skipped while `Zygote.jl` is differentiating, because the reverse-mode tape holds references to intermediates, and recycling those could silently corrupt gradients. @@ -53,10 +61,9 @@ _uncache(x, ::Type) = x """ alloc_cache_depth(alg) -How many iterations a buffer must go unused before it may be recycled. -For SimultaneousCTMRG and SU, 2 is enough, but not necessarily for other algorithms. +How many iterations a buffer must go unused for before it may be recycled. """ -alloc_cache_depth(alg) = 2 +alloc_cache_depth(alg) = 1 _with_alloc_cache(f, ::Type, ::Symbol, ::Int, ::Int) = f() """ From 1ccf58c56fff958cc1c8da5108f3e996ec27664f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 24 Sep 2026 05:08:24 -0400 Subject: [PATCH 55/69] Soothe CI a bit more --- Project.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 0fc65f5e6..ad86c3471 100644 --- a/Project.toml +++ b/Project.toml @@ -36,8 +36,7 @@ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" [sources] -MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} -MatrixAlgebraKit = {rev = "ksh/batched_svd", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} +MatrixAlgebraKit = {rev = "main", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} TensorKit = {rev = "ksh/batched_svd", url = "https://github.com/QuantumKitHub/TensorKit.jl"} [extensions] From be04518f6daa42817b7f4cb5b50c35fe8981c44a Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 7 Oct 2026 10:20:06 -0400 Subject: [PATCH 56/69] Try GPUArrays 12 --- Project.toml | 2 +- test/Project.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index ad86c3471..145c68208 100644 --- a/Project.toml +++ b/Project.toml @@ -46,7 +46,7 @@ PEPSKitGPUArraysExt = "GPUArrays" [compat] Accessors = "0.1" Adapt = "4" -GPUArrays = "11" +GPUArrays = "12" ChainRulesCore = "1.0" Compat = "3.46, 4.2" DocStringExtensions = "0.9.3" diff --git a/test/Project.toml b/test/Project.toml index 3f624d9c1..9570b702a 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -37,7 +37,6 @@ cuSPARSE = "b26da814-b3bc-49ef-b0ee-c816305aa060" [sources] PEPSKit = {path = ".."} -GPUArrays = {url = "https://github.com/JuliaGPU/GPUArrays.jl", rev = "main"} AMDGPU = {url = "https://github.com/JuliaGPU/AMDGPU.jl", rev = "main"} [compat] From 0e32d384eb1d937bc89e2e3a3cca7740465c1e31 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 9 Oct 2026 13:18:35 +0200 Subject: [PATCH 57/69] Fixups for Project files --- Project.toml | 3 +-- test/Project.toml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 145c68208..bddd6fb53 100644 --- a/Project.toml +++ b/Project.toml @@ -36,7 +36,6 @@ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" [sources] -MatrixAlgebraKit = {rev = "main", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} TensorKit = {rev = "ksh/batched_svd", url = "https://github.com/QuantumKitHub/TensorKit.jl"} [extensions] @@ -46,7 +45,7 @@ PEPSKitGPUArraysExt = "GPUArrays" [compat] Accessors = "0.1" Adapt = "4" -GPUArrays = "12" +GPUArrays = "11" ChainRulesCore = "1.0" Compat = "3.46, 4.2" DocStringExtensions = "0.9.3" diff --git a/test/Project.toml b/test/Project.toml index 9570b702a..7e310e298 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -37,7 +37,6 @@ cuSPARSE = "b26da814-b3bc-49ef-b0ee-c816305aa060" [sources] PEPSKit = {path = ".."} -AMDGPU = {url = "https://github.com/JuliaGPU/AMDGPU.jl", rev = "main"} [compat] AMDGPU = "2" From 538ab256d6339896e4ab0dc30006e36e1d7557ce Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 12 Oct 2026 05:18:06 -0400 Subject: [PATCH 58/69] Drop some useless changes --- src/algorithms/optimization/peps_optimization.jl | 3 +-- src/algorithms/select_algorithm.jl | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/optimization/peps_optimization.jl b/src/algorithms/optimization/peps_optimization.jl index 6009ad8b1..808ed04f5 100644 --- a/src/algorithms/optimization/peps_optimization.jl +++ b/src/algorithms/optimization/peps_optimization.jl @@ -306,8 +306,7 @@ function fixedpoint( alg.reuse_env && update!(env, env′) tracked_finalizer.contraction_metrics[end] = info.contraction_metrics end - cf = cost_function(ψ, env′, operator) - return cf + return cost_function(ψ, env′, operator) end g = peps_project_tangent(only(gs), peps) tracked_finalizer.gradnorms_unitcell[end] = norm.(unitcell(g)) diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index 59ccbccfa..2671a8053 100644 --- a/src/algorithms/select_algorithm.jl +++ b/src/algorithms/select_algorithm.jl @@ -155,5 +155,6 @@ function select_algorithm( rrule_alg = (; tol = 1.0e1tol, verbosity = verbosity - 2, krylovdim, decomposition_alg.rrule_alg...) decomposition_alg = (; rrule_alg, decomposition_alg...) end + return CTMRGAlgorithm(; alg, tol, verbosity, decomposition_alg, kwargs...) end From 904b1cc9b593ca0fa548304019f0a7897e30f09c Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 12 Oct 2026 05:31:41 -0400 Subject: [PATCH 59/69] Remove duplicated storage type methods --- src/networks/infinitesquarenetwork.jl | 1 - src/networks/local_sandwich.jl | 2 -- src/operators/infinitepepo.jl | 2 -- src/states/infinitepartitionfunction.jl | 1 - src/states/infinitepeps.jl | 1 - 5 files changed, 7 deletions(-) diff --git a/src/networks/infinitesquarenetwork.jl b/src/networks/infinitesquarenetwork.jl index 1c9995baf..5a52e1f17 100644 --- a/src/networks/infinitesquarenetwork.jl +++ b/src/networks/infinitesquarenetwork.jl @@ -25,7 +25,6 @@ struct InfiniteSquareNetwork{O} end end InfiniteSquareNetwork(n::InfiniteSquareNetwork) = n -TensorKit.storagetype(::Type{InfiniteSquareNetwork{O}}) where {O} = storagetype(O) ## Unit cell interface diff --git a/src/networks/local_sandwich.jl b/src/networks/local_sandwich.jl index ff485ee0f..33f61625b 100644 --- a/src/networks/local_sandwich.jl +++ b/src/networks/local_sandwich.jl @@ -60,8 +60,6 @@ _isapprox_localsandwich(O1::PFTensor, O2::PFTensor; kwargs...) = isapprox(O1, O2 ## PEPS const PEPSSandwich{T <: PEPSTensor} = Tuple{T, T} -TensorKit.storagetype(::Type{PEPSSandwich{T}}) where {T} = T -TensorKit.storagetype(S::PEPSSandwich{T}) where {T} = T ket(O::PEPSSandwich) = O[1] bra(O::PEPSSandwich) = O[2] diff --git a/src/operators/infinitepepo.jl b/src/operators/infinitepepo.jl index 65ec5194d..1f68a9161 100644 --- a/src/operators/infinitepepo.jl +++ b/src/operators/infinitepepo.jl @@ -169,8 +169,6 @@ function physicalspace(T::InfinitePEPO, r::Int, c::Int) return codomain_physicalspace(T, r, c) end -TensorKit.storagetype(::Type{InfinitePEPO{T}}) where {T} = storagetype(T) - ## InfiniteSquareNetwork interface function InfiniteSquareNetwork(top::InfinitePEPS, mid::InfinitePEPO, bot::InfinitePEPS = top) diff --git a/src/states/infinitepartitionfunction.jl b/src/states/infinitepartitionfunction.jl index 9104aa86a..a39af7fbe 100644 --- a/src/states/infinitepartitionfunction.jl +++ b/src/states/infinitepartitionfunction.jl @@ -25,7 +25,6 @@ struct InfinitePartitionFunction{T <: PartitionFunctionTensor} return new{T}(A) end end -TensorKit.storagetype(::Type{InfinitePartitionFunction{T}}) where {T} = storagetype(T) const InfinitePF{T} = InfinitePartitionFunction{T} diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index c48f54f41..9ad3aef2b 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -65,7 +65,6 @@ function InfinitePEPS( ) where {A <: Union{AbstractMatrix{<:ElementarySpace}, ElementarySpace}} return InfinitePEPS(randn, Vector{ComplexF64}, Pspaces, virtual_spaces...; kwargs...) end -TensorKit.storagetype(::Type{InfinitePEPS{T}}) where {T} = storagetype(T) """ InfinitePEPS(A::PEPSTensor; unitcell=(1, 1)) From 42ebea2dcaae6aacda3506e377ec6790f87c3ef5 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 12 Oct 2026 10:22:11 -0400 Subject: [PATCH 60/69] Try the handle handler fix branch --- test/Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Project.toml b/test/Project.toml index 7e310e298..408c63e26 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -37,6 +37,7 @@ cuSPARSE = "b26da814-b3bc-49ef-b0ee-c816305aa060" [sources] PEPSKit = {path = ".."} +CUDA = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "ksh/uncached-handles"} [compat] AMDGPU = "2" From ea04608a96777c839d4067444aef1ec5f8bbb546 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 14 Oct 2026 10:20:07 +0200 Subject: [PATCH 61/69] Update Project.toml --- test/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index 408c63e26..114bfd1dd 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -37,7 +37,7 @@ cuSPARSE = "b26da814-b3bc-49ef-b0ee-c816305aa060" [sources] PEPSKit = {path = ".."} -CUDA = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "ksh/uncached-handles"} +CUDA = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main"} [compat] AMDGPU = "2" From 14194fdb42047dc91420f78ab0636d9150500f52 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 14 Oct 2026 04:55:08 -0400 Subject: [PATCH 62/69] Cleanup some rebase dupes --- src/environments/suweight.jl | 2 -- src/operators/localoperator.jl | 1 - src/operators/transfermatrix.jl | 4 ---- src/states/infinitepartitionfunction.jl | 4 ++-- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index 32e34cc04..22af1d055 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -164,8 +164,6 @@ TensorKit.sectortype(w::SUWeight) = sectortype(typeof(w)) TensorKit.sectortype(::Type{<:SUWeight{T}}) where {T} = sectortype(spacetype(T)) TensorKit.storagetype(::Type{SUWeight{T}}) where {T} = storagetype(T) -TensorKit.storagetype(::Type{SUWeight{T}}) where {T} = storagetype(T) - ## Bipartite check function _is_bipartite(wts::SUWeight) (size(wts, 2) == size(wts, 3) == 2) || (return false) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index e5ae170a9..47391f4d2 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -59,7 +59,6 @@ function _promote_term_scalars(term::AbstractVector) T = promote_type(map(scalartype, term)...) return map(t -> scalartype(t) === T ? t : VI.scale(t, one(T)), term) end -TensorKit.storagetype(lo::LocalOperator{T, S}) where {T, S} = storagetype(first(lo.terms)[2]) # horrible! """ Return the tensor factors of a dense or factored local term. diff --git a/src/operators/transfermatrix.jl b/src/operators/transfermatrix.jl index 83959680d..fd94825e1 100644 --- a/src/operators/transfermatrix.jl +++ b/src/operators/transfermatrix.jl @@ -14,8 +14,6 @@ function which corresponds to the overlap between 'ket' and 'bra' `InfinitePEPS` """ const InfiniteTransferPEPS{T <: PEPSTensor} = InfiniteMPO{PEPSSandwich{T}} -TensorKit.storagetype(::InfiniteTransferPEPS{T}) where {T} = storagetype(T) - function InfiniteTransferPEPS( top::PeriodicArray{T, 1}, bot::PeriodicArray{T, 1} ) where {T <: PEPSTensor} @@ -71,8 +69,6 @@ function which corresponds to the expectation value of an `InfinitePEPO` between """ const InfiniteTransferPEPO{H, T <: PEPSTensor, O <: PEPOTensor} = InfiniteMPO{PEPOSandwich{H, T, O}} -TensorKit.storagetype(::InfiniteTransferPEPO{H, T, O}) where {H, T, O} = storagetype(T) - function InfiniteTransferPEPO( top::PeriodicArray{T, 1}, mid::PeriodicArray{O, 2}, bot::PeriodicArray{T, 1} ) where {T, O} diff --git a/src/states/infinitepartitionfunction.jl b/src/states/infinitepartitionfunction.jl index a39af7fbe..392668f21 100644 --- a/src/states/infinitepartitionfunction.jl +++ b/src/states/infinitepartitionfunction.jl @@ -50,8 +50,8 @@ of the PEPS tensor at each site in the unit cell as a matrix. Each individual sp specified as either an `Int` or an `ElementarySpace`. """ function InfinitePartitionFunction( - f, ::Type{T}, Nspaces::M, Espaces::M = Nspaces - ) where {M <: AbstractMatrix{<:ElementarySpace}, T <: Number} + f, T, Nspaces::M, Espaces::M = Nspaces + ) where {M <: AbstractMatrix{<:ElementarySpace}} size(Nspaces) == size(Espaces) || throw(ArgumentError("Input spaces should have equal sizes.")) From a8b267c07cc21f41434bff68ff796e2aeb5461f0 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 16 Oct 2026 02:13:40 -0400 Subject: [PATCH 63/69] Rebase issues and other fixes --- Project.toml | 2 +- src/Defaults.jl | 2 +- src/algorithms/bp/beliefpropagation.jl | 1 - src/algorithms/time_evolution/simpleupdate.jl | 24 ------------------- src/environments/ctmrg_environments.jl | 18 +++++++------- src/environments/suweight.jl | 3 +-- src/networks/tensors.jl | 6 ++--- src/states/infinitepeps.jl | 2 +- 8 files changed, 15 insertions(+), 43 deletions(-) diff --git a/Project.toml b/Project.toml index bddd6fb53..8ccf0ac38 100644 --- a/Project.toml +++ b/Project.toml @@ -45,7 +45,7 @@ PEPSKitGPUArraysExt = "GPUArrays" [compat] Accessors = "0.1" Adapt = "4" -GPUArrays = "11" +GPUArrays = "11, 12" ChainRulesCore = "1.0" Compat = "3.46, 4.2" DocStringExtensions = "0.9.3" diff --git a/src/Defaults.jl b/src/Defaults.jl index ce39011b3..78fc88ab2 100644 --- a/src/Defaults.jl +++ b/src/Defaults.jl @@ -52,7 +52,7 @@ Module containing default algorithm parameter values and arguments. ## `eigh` forward & reverse -* `eigh_fwd_alg=:$(Defaults.eigh_fwd_alg)` : `eigh` algorithm that is used in the forward pass. **Note** that on GPU, `DivideAndConquer` is much more performant than `QRIteration`. +* `eigh_fwd_alg=:$(Defaults.eigh_fwd_alg)` : `eigh` algorithm that is used in the forward pass. - `:DefaultAlgorithm` : MatrixAlgebraKit's default Eigh algorithm for a given matrix type. - `:DivideAndConquer` : MatrixAlgebraKit's [`DivideAndConquer`](@extref MatrixAlgebraKit.DivideAndConquer) - `:QRIteration` : MatrixAlgebraKit's [`QRIteration`](@extref MatrixAlgebraKit.QRIteration) diff --git a/src/algorithms/bp/beliefpropagation.jl b/src/algorithms/bp/beliefpropagation.jl index f1e6dadda..6a274289f 100644 --- a/src/algorithms/bp/beliefpropagation.jl +++ b/src/algorithms/bp/beliefpropagation.jl @@ -39,7 +39,6 @@ function leading_boundary(env₀::BPEnv, network::InfiniteSquareNetwork, alg::Be ϵ = Inf @log_initialization loginit!(log, ϵ) for iter in 1:(alg.maxiter) - # TODO investigate why caching doesn't help here and actually makes things worse env′ = bp_iteration(network, env, alg) ϵ = oftype(ϵ, tr_distance(env, env′)) env = env′ diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 778190ff8..d1a00b1f4 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -159,29 +159,6 @@ function su_iter( return state2, env2, ϵ end -""" - check_su_state(psi, iter) - -Check that a simple-update step did not produce a degenerate state. - -Without this check, later CTMRG can "converge" immediately because a `NaN` -objective compares equal to itself, reports `converged = true`, and the run finishes with -a `NaN` energy and a suspiciously fast wall time. - -Only vector-space dimensions are inspected, never tensor data, so this is quick and inexpensive. -""" -function check_su_state(psi, iter) - for (idx, t) in pairs(unitcell(psi)) - dim(space(t)) > 0 || throw( - ErrorException( - "simple update produced a degenerate state at iteration $iter: tensor $idx \ - has an empty space ($(space(t)))." - ) - ) - end - return nothing -end - function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) iter, t = state.iter, state.t (iter == it.nstep) && return nothing @@ -190,7 +167,6 @@ function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) su_iter(state.psi, it.circuit, it.alg, state.env) end psi, env = uncache(psi, storage), uncache(env, storage) - check_su_state(psi, iter + 1) # update internal state iter += 1 t += it.dt diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index e92a51a94..50b394b6d 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -64,7 +64,7 @@ end """ CTMRGEnv( - [f=randn, T=ComplexF64, TA=Matrix{ComplexF64},] Ds_north::A, Ds_east::A, chis_north::B, [chis_east::B], [chis_south::B], [chis_west::B] + [f=randn, T=ComplexF64], Ds_north::A, Ds_east::A, chis_north::B, [chis_east::B], [chis_south::B], [chis_west::B] ) where {A<:AbstractMatrix{<:VectorSpace}, B<:AbstractMatrix{<:ElementarySpace}} Construct a CTMRG environment by specifying matrices of north and east virtual spaces of the @@ -85,11 +85,10 @@ of a partition function defined in terms of local rank-4 tensors) or a `ProductS for the case of a network representing overlaps of PEPSs and PEPOs). """ function CTMRGEnv( - f, ::Type{T}, Ds_north::A, Ds_east::A, chis_north::B, chis_east::B = chis_north, + f, T, Ds_north::A, Ds_east::A, chis_north::B, chis_east::B = chis_north, chis_south::B = chis_north, chis_west::B = chis_north, ) where { A <: AbstractMatrix{<:ProductSpace}, B <: AbstractMatrix{<:ElementarySpace}, - T, } # check all of the sizes size(Ds_north) == size(Ds_east) == size(chis_north) == size(chis_east) == @@ -104,6 +103,7 @@ function CTMRGEnv( st = spacetype(first(Ds_north)) C_type = tensormaptype(st, 1, 1, T) T_type = tensormaptype(st, N + 1, 1, T) + # First index is direction corners = Array{C_type}(undef, 4, size(Ds_north)...) edges = Array{T_type}(undef, 4, size(Ds_north)...) @@ -181,9 +181,9 @@ The environment virtual spaces for each site correspond to virtual space of the corresponding edge tensor for each direction. """ function CTMRGEnv( - f, ::Type{T}, + f, T, D_north::S, D_east::S, virtual_spaces...; unitcell::Tuple{Int, Int} = (1, 1), - ) where {S <: VectorSpace, T} + ) where {S <: VectorSpace} return CTMRGEnv( f, T, _fill_edge_physical_spaces(D_north, D_east; unitcell)..., @@ -218,7 +218,7 @@ of the corresponding edge tensor for each direction. Specifically, for a given s `chis_south[r, c]` corresponds to the east space of the south edge tensor, and `chis_west[r, c]` corresponds to the north space of the west edge tensor. """ -function CTMRGEnv(f, ::Type{T}, network::N, virtual_spaces...) where {T, N <: InfiniteSquareNetwork} +function CTMRGEnv(f, T, network::InfiniteSquareNetwork, virtual_spaces...) Ds_north = _north_edge_physical_spaces(network) Ds_east = _east_edge_physical_spaces(network) virtual_spaces = _fill_environment_virtual_spaces(virtual_spaces...; unitcell = size(network)) @@ -260,15 +260,13 @@ function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnv, name::Symbol) if name === :corners function corner_pullback(Δcorners_) Δcorners = unthunk(Δcorners_) - zvs = CTMRGEnv(Δcorners, zerovector.(e.edges)) - return NoTangent(), zvs, NoTangent() + return NoTangent(), CTMRGEnv(Δcorners, zerovector.(e.edges)), NoTangent() end return result, corner_pullback elseif name === :edges function edge_pullback(Δedges_) Δedges = unthunk(Δedges_) - zvs = CTMRGEnv(zerovector.(e.corners), Δedges) - return NoTangent(), zvs, NoTangent() + return NoTangent(), CTMRGEnv(zerovector.(e.corners), Δedges), NoTangent() end return result, edge_pullback else # this should never happen because already errored in forwards pass diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index 22af1d055..796c4e711 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -67,7 +67,7 @@ specifies the storage type. If `T <: Number` the default storage type is `Vector function SUWeight( T, Nspaces::M, Espaces::M = Nspaces - ) where {M <: AbstractMatrix{<:ElementarySpace}, TorA} + ) where {M <: AbstractMatrix{<:ElementarySpace}} @assert size(Nspaces) == size(Espaces) Nr, Nc = size(Nspaces) weights = map(Iterators.product(1:2, 1:Nr, 1:Nc)) do (d, r, c) @@ -103,7 +103,6 @@ Create a trivial `SUWeight` by specifying its vertical (north) and horizontal (e as `ElementarySpace`s) and unit cell size. """ function SUWeight( - ::Type{TorA}, Nspace::S, Espace::S = Nspace; unitcell::Tuple{Int, Int} = (1, 1) ) where {S <: ElementarySpace} return SUWeight(ComplexF64, fill(Nspace, unitcell), fill(Espace, unitcell)) diff --git a/src/networks/tensors.jl b/src/networks/tensors.jl index 2aab3f6fe..347860366 100644 --- a/src/networks/tensors.jl +++ b/src/networks/tensors.jl @@ -31,10 +31,10 @@ The tensor elements are generated based on `f` and the element type and storage specified in `T`. """ function PartitionFunctionTensor( - f, ::Type{TorA}, + f, ::Type{T}, Nspace::S, Espace::S = Nspace, Sspace::S = Nspace, Wspace::S = Espace, - ) where {TorA, S <: ElementarySpace} - return f(TorA, Wspace ⊗ Sspace ← Nspace ⊗ Espace) + ) where {T, S <: ElementarySpace} + return f(T, Wspace ⊗ Sspace ← Nspace ⊗ Espace) end Base.rotl90(t::PFTensor) = permute(t, ((3, 1), (4, 2))) diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index 9ad3aef2b..7f6a38eac 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -63,7 +63,7 @@ end function InfinitePEPS( Pspaces::A, virtual_spaces...; kwargs... ) where {A <: Union{AbstractMatrix{<:ElementarySpace}, ElementarySpace}} - return InfinitePEPS(randn, Vector{ComplexF64}, Pspaces, virtual_spaces...; kwargs...) + return InfinitePEPS(randn, ComplexF64, Pspaces, virtual_spaces...; kwargs...) end """ From d7ccbcf9f0a980a5457f07346147bafa8597e59e Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 19 Oct 2026 08:27:17 -0400 Subject: [PATCH 64/69] Cleanup some dead lines --- src/PEPSKit.jl | 1 - src/algorithms/ctmrg/ctmrg.jl | 1 + src/utility/util.jl | 4 ---- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 7baf6ff56..e90b022fb 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -18,7 +18,6 @@ using TensorKit using TensorKit: AdjointTensorMap, SectorDict using TensorKit: throw_invalid_innerproduct, similarstoragetype using TensorKit.Factorizations: TruncationSpace, _notrunc_ind -import TensorKit: storagetype using KrylovKit using KrylovKit: Lanczos, BlockLanczos diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index dcccfd1bd..9c61f47c1 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -216,6 +216,7 @@ function _singular_value_distance(S₁::SV, S₂::SV) where {SV <: TensorKit.Sec for (c, b) in blocks(S₂) diff[c][1:length(b)] .-= b end + return norm(diff) end _singular_value_distance(S₁::DiagonalTensorMap, S₂::DiagonalTensorMap) = diff --git a/src/utility/util.jl b/src/utility/util.jl index 51297dfd3..f9420cf0d 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -8,10 +8,6 @@ function _elementwise_mult(a₁::AbstractTensorMap, a₂::AbstractTensorMap) end _safe_pow(a::Number, pow::Real, tol::Real) = (pow < 0 && abs(a) < tol) ? zero(a) : a^pow -# Same cutoff, but with the relative tolerance and the scale kept as separate arguments so -# that the scale doesn't need to be copied back to the CPU memory. -# `mx` is either a plain number or a 0-dimensional array, which broadcasts as a scalar. -_safe_pow(a::Number, pow::Real, tol::Real, mx::Number) = _safe_pow(a, pow, tol * mx) """ _maxabs(data) From 20b93d3fd2b06ec265c9776308a17d294e1cd9d0 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 19 Oct 2026 08:32:36 -0400 Subject: [PATCH 65/69] Remove more dead lines --- .../optimization/implicit_differentiation.jl | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/algorithms/optimization/implicit_differentiation.jl b/src/algorithms/optimization/implicit_differentiation.jl index 8c8d84402..37ae322b4 100644 --- a/src/algorithms/optimization/implicit_differentiation.jl +++ b/src/algorithms/optimization/implicit_differentiation.jl @@ -648,17 +648,8 @@ function PEPSKit._rrule( # variables of the characteristic equations, living in the full projector spaces and # constrained to the orthogonal complements of the isometries inside the characteristic # equations - # get the projector nullspaces - UL = left_null.(U) - VR = right_null.(V) - - # instantiate the variables used in the characteristic equations - u = map(zip(U, UL)) do (Uc, ULc) - return zeros(storagetype(Uc), space(ULc, numind(ULc))' ← space(Uc, numind(Uc))') - end - v = map(zip(V, VR)) do (Vc, VRc) - return zeros(storagetype(Vc), space(Vc, 1) ← space(VRc, 1)) - end + u = zerovector.(U) + v = zerovector.(V) is = sdiag_pow.(s, -1) # also treat them as general complex tensors # Two tapes: the linear solve only uses the environment cotangents, so reading the From 663b8ac0492809c63bee3286730a1bb3efd0bde5 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 19 Oct 2026 08:40:32 -0400 Subject: [PATCH 66/69] More dead stuff removal --- ext/PEPSKitGPUArraysExt.jl | 51 ---------------------- src/algorithms/time_evolution/apply_mpo.jl | 33 +------------- 2 files changed, 2 insertions(+), 82 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index f1be4f8d2..608033885 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -73,57 +73,6 @@ function PEPSKit.free_alloc_caches!(::Type{<:AnyGPUArray}) return nothing end - -# Batched truncated SVD of a whole cluster's internal bonds to avoid multiple small kernel launches. -function PEPSKit.bond_svds( - ::Type{<:AnyGPUArray}, rls::AbstractVector, truncs::AbstractVector - ) - isempty(rls) && return map(_ -> nothing, rls) - # The different GPU libaries offer different batching algos, - # make sure we have one that actually works. - alg = MAK.default_algorithm(MAK.batched_svd_compact!, eltype(rls)) - Fs = map(rl -> MAK.initialize_output(MAK.svd_compact!, rl, alg), rls) - balg = _cluster_batched_alg(rls) - if isnothing(balg) - for (rl, F) in zip(rls, Fs) - MAK.svd_compact!(rl, F, alg) - end - else - # Pool every (bond, sector) block into one ragged batch. MatrixAlgebraKit batches - # blocks of equal size together even across different bonds, since the decomposition - # does not care which bond a block came from, and zero-pads the leftovers. - items = [(i, c) for i in eachindex(rls) for c in blocksectors(rls[i])] - As = [block(rls[i], c) for (i, c) in items] - Us = [block(Fs[i][1], c) for (i, c) in items] - Ss = [TensorKit.diagview(block(Fs[i][2], c)) for (i, c) in items] - Vᴴs = [block(Fs[i][3], c) for (i, c) in items] - MAK.batched_svd_compact!(As, (Us, Ss, Vᴴs), balg) - end - return map(Fs, truncs) do F, trunc - (U, S, Vᴴ) = F - USVᴴtrunc, ind = MAK.truncate(MAK.svd_trunc!, (U, S, Vᴴ), trunc) - ϵ = MAK.truncation_error!(TensorKit.diagview(S), ind) - return (USVᴴtrunc..., ϵ) - end -end - -""" - CLUSTER_BATCHED_SVD[] - -Whether simple update batches the SVDs of a cluster's internal bonds into one call. -Off by default. -""" -const CLUSTER_BATCHED_SVD = Ref(false) - -# Which batched algorithm the backend offers for the cluster's blocks, or `nothing`. -function _cluster_batched_alg(rls::AbstractVector) - CLUSTER_BATCHED_SVD[] || return nothing - for i in eachindex(rls), c in blocksectors(rls[i]) - return _batched_spectra_alg(block(rls[i], c)) - end - return nothing -end - """ _batched_spectra_alg(proto) -> alg or nothing diff --git a/src/algorithms/time_evolution/apply_mpo.jl b/src/algorithms/time_evolution/apply_mpo.jl index 7c3c287aa..511efb47d 100644 --- a/src/algorithms/time_evolution/apply_mpo.jl +++ b/src/algorithms/time_evolution/apply_mpo.jl @@ -214,34 +214,11 @@ function _proj_from_RL( @assert isdual(domain(l, 1)) == isdual(codomain(l, 1)) == false rl = r * l u, s, vh, ϵ = svd_trunc!(rl; trunc) - return _proj_from_svd(r, l, u, s, vh, ϵ) -end - -# Second half of `_proj_from_RL`, split off so that the decomposition can be -# done for the whole cluster at once (see `bond_svds`). -function _proj_from_svd(r::MPSBondTensor, l::MPSBondTensor, u, s, vh, ϵ) sinv = sdiag_pow(s, -1 / 2) Pa, Pb = l * vh' * sinv, sinv * u' * r return Pa, s, Pb, ϵ end -""" - bond_svds(rls, truncs) - -Truncated SVD of every internal bond of a cluster, returning `(u, s, vh, ϵ)` per bond. - -Separate from [`_proj_from_RL`](@ref) so that *all* bonds of the cluster can be decomposed -in a single batched call. -""" -function bond_svds(rls::AbstractVector, truncs::AbstractVector) - return bond_svds(storagetype(eltype(rls)), rls, truncs) -end -function bond_svds(::Type, rls::AbstractVector, truncs::AbstractVector) - return map(rls, truncs) do rl, trunc - return svd_trunc!(rl; trunc) - end -end - """ Given a cluster `Ms`, find all projectors `Pa`, `Pb` and Schmidt weights `wts` on internal bonds. @@ -252,14 +229,8 @@ function _get_allprojs( N = length(Ms) Rs, Ls = _get_allRLs(Ms) @assert length(truncs) == N - 1 - for (R, L) in zip(Rs, Ls) - @assert isdual(domain(R, 1)) == isdual(codomain(R, 1)) == false - @assert isdual(domain(L, 1)) == isdual(codomain(L, 1)) == false - end - # decompose every bond of the cluster in one go, then finish each projector locally - svds = bond_svds(map(*, Rs, Ls), truncs) - projs_errs = map(Rs, Ls, svds) do R, L, (u, s, vh, ϵ) - return _proj_from_svd(R, L, u, s, vh, ϵ) + projs_errs = map(Rs, Ls, truncs) do R, L, trunc + return _proj_from_RL(R, L; trunc) end Pas = map(Base.Fix2(getindex, 1), projs_errs) wts = map(Base.Fix2(getindex, 2), projs_errs) From d8757ed9152dfb7312712de9face12d144db4f64 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 20 Oct 2026 03:15:18 -0400 Subject: [PATCH 67/69] GPU specific extensions for SVD stuff --- ext/PEPSKitGPUArraysExt.jl | 7 +++++++ src/utility/svd.jl | 6 ------ test/testsuite/ctmrg/fixed_iterscheme.jl | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 608033885..3cd402d86 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -88,6 +88,13 @@ function _batched_spectra_alg(proto) return alg isa MAK.AbstractAlgorithm ? alg : nothing end +PEPSKit._materialize_block(m::Union{Adjoint{T, A}, Transpose{T, A}}) where {T, A <: AnyGPUArray} = copyto!(similar(m, size(m)), m) +function PEPSKit._column_vectors(::Type{A}, m::AbstractMatrix) where {A <: AnyGPUArray} + m̃ = PEPSKit._materialize_block(m) + return A[m̃[:, j] for j in axes(m̃, 2)] +end +PEPSKit._indexable_diagview(m::AnyGPUArray) = collect(diagview(m)) + # Hook into the collection-level convergence API. Deliberately restricted to the generic # CTMRG algorithms: `C4vCTMRG` overrides `corner_spectrum` to `eigh_vals` (its corners are # diagonal), so a blanket override here would silently switch it back to `svd_vals`. diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 0b10ed24a..8a39ebf1d 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -399,21 +399,15 @@ end # `block(::AdjointTensorMap, c)` hands back a `LinearAlgebra.Adjoint` # Some backends will need to materialize this to avoid scalar indexing _materialize_block(m::AbstractMatrix) = m -#_materialize_block(m::Union{Adjoint, Transpose}) = copyto!(similar(m, size(m)), m) # Columns of `m` as a vector of `A`-typed vectors function _column_vectors(::Type{A}, m::AbstractMatrix) where {A} return Vector{A}(eachcol(_materialize_block(m))) end -#function _column_vectors(::Type{A}, m::AbstractMatrix) where {A} -# m̃ = _materialize_block(m) -# return A[m̃[:, j] for j in axes(m̃, 2)] -#end # Diagonal of `m` for `compute_svdsolve_pullback_data`, which reads it element-wise # Some backends will need to copy this to the host to avoid scalar indexing _indexable_diagview(m::AbstractMatrix) = diagview(m) -#_indexable_diagview(m::AbstractMatrix) = collect(diagview(m)) # KrylovKit rrule compatible with TensorMaps & function handles function ChainRulesCore.rrule( diff --git a/test/testsuite/ctmrg/fixed_iterscheme.jl b/test/testsuite/ctmrg/fixed_iterscheme.jl index 835b8cfb5..37d112731 100644 --- a/test/testsuite/ctmrg/fixed_iterscheme.jl +++ b/test/testsuite/ctmrg/fixed_iterscheme.jl @@ -21,7 +21,7 @@ D = 2 χ = 16 projector_algs_asymm = [:HalfInfiniteProjector] #, :FullInfiniteProjector] unitcells = [(1, 1), (3, 4)] -atol = 1.0e-5 +const atol = 1.0e-5 function ctmrg_fixed_iterscheme_asymmetric(AT; svd_alg = :DivideAndConquer) svd_algs = [(; alg = svd_alg), (; alg = :GKL)] From 358c78fa604c95a2eb1386782991dd132cfe87d2 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 27 Oct 2026 02:08:10 -0400 Subject: [PATCH 68/69] Clean up project files a bit --- Project.toml | 5 +---- ext/PEPSKitGPUArraysExt.jl | 7 ------- test/Project.toml | 9 --------- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Project.toml b/Project.toml index 8ccf0ac38..a1559b05f 100644 --- a/Project.toml +++ b/Project.toml @@ -22,8 +22,6 @@ OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -Strided = "5e0ebb24-38b0-5f93-81fe-25c709ecae67" -StridedViews = "4db3bf67-4bd7-4b4e-b153-31dc3fb37143" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitTensors = "41b62e7d-e9d1-4e23-942c-79a97adf954b" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" @@ -36,7 +34,7 @@ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" [sources] -TensorKit = {rev = "ksh/batched_svd", url = "https://github.com/QuantumKitHub/TensorKit.jl"} +TensorKit = {rev = "main", url = "https://github.com/QuantumKitHub/TensorKit.jl"} [extensions] PEPSKitAdaptExt = "Adapt" @@ -50,7 +48,6 @@ ChainRulesCore = "1.0" Compat = "3.46, 4.2" DocStringExtensions = "0.9.3" FiniteDifferences = "0.12" -GPUArrays = "11, 12" KrylovKit = "0.9.5, 0.10" LinearAlgebra = "1" MPSKit = "0.14" diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 3cd402d86..608033885 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -88,13 +88,6 @@ function _batched_spectra_alg(proto) return alg isa MAK.AbstractAlgorithm ? alg : nothing end -PEPSKit._materialize_block(m::Union{Adjoint{T, A}, Transpose{T, A}}) where {T, A <: AnyGPUArray} = copyto!(similar(m, size(m)), m) -function PEPSKit._column_vectors(::Type{A}, m::AbstractMatrix) where {A <: AnyGPUArray} - m̃ = PEPSKit._materialize_block(m) - return A[m̃[:, j] for j in axes(m̃, 2)] -end -PEPSKit._indexable_diagview(m::AnyGPUArray) = collect(diagview(m)) - # Hook into the collection-level convergence API. Deliberately restricted to the generic # CTMRG algorithms: `C4vCTMRG` overrides `corner_spectrum` to `eigh_vals` (its corners are # diagonal), so a blanket override here would silently switch it back to `svd_vals`. diff --git a/test/Project.toml b/test/Project.toml index 114bfd1dd..7e019cf9c 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -8,8 +8,6 @@ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -CUPTI = "9e67e8f6-ba02-4b6c-a7db-3b11ae1e7ab7" -CUDATools = "9ec180c6-1c07-47c7-9e6e-ebefa4d1f6d0" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -29,21 +27,14 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" -cuBLAS = "182d3088-87b7-4494-8cad-fc6afaa545bc" -cuFFT = "533571aa-0936-420e-b4be-9c66f5f626ca" -cuRAND = "20fd9a0b-12d5-4c2f-a8af-7c34e9e60431" -cuSOLVER = "887afef0-6a32-4de5-add4-7827692ba8fc" -cuSPARSE = "b26da814-b3bc-49ef-b0ee-c816305aa060" [sources] PEPSKit = {path = ".."} -CUDA = {url = "https://github.com/JuliaGPU/CUDA.jl", rev = "main"} [compat] AMDGPU = "2" ChainRulesTestUtils = "1.13" CUDA = "6" -CUDATools = "6" ParallelTestRunner = "2.6.0" JLArrays = "0.3, 0.4" QuadGK = "2.11.1" From adc0d3cdb4283dedc19efd8fc57c345342fff5e0 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 29 Oct 2026 05:55:24 -0400 Subject: [PATCH 69/69] Fix duplicated method --- src/utility/util.jl | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/utility/util.jl b/src/utility/util.jl index f9420cf0d..df25d2ae5 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -15,19 +15,6 @@ _safe_pow(a::Number, pow::Real, tol::Real) = (pow < 0 && abs(a) < tol) ? zero(a) Largest absolute value in `data`, equal to `norm(_, Inf)` for the diagonal storage of a `DiagonalTensorMap`. -Returns a scalar by default. -""" -function _maxabs(data::AbstractArray) - isempty(data) && return zero(real(eltype(data))) - return norm(data, Inf) -end - -""" - _maxabs(data) - -Largest absolute value in `data`, equal to `norm(_, Inf)` for the diagonal storage of a -`DiagonalTensorMap`. - Returns a scalar by default. """ function _maxabs(data::AbstractArray)