From f9048a62bbf6defd9e3cf5093b2e4c9adc9153be Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 29 Jan 2026 10:53:20 +0100 Subject: [PATCH 001/102] Initial steps to CUDA support yolo svd distance --- Project.toml | 16 ++ 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/PEPSKitAdaptExt.jl | 19 ++ ext/PEPSKitCUDAExt.jl | 39 +++ src/PEPSKit.jl | 1 + src/algorithms/ctmrg/ctmrg.jl | 4 - src/algorithms/ctmrg/gaugefix.jl | 2 +- .../optimization/peps_optimization.jl | 3 +- src/algorithms/select_algorithm.jl | 1 + src/algorithms/toolbox.jl | 4 +- src/environments/ctmrg_environments.jl | 66 ++--- src/environments/suweight.jl | 22 +- src/environments/vumps_environments.jl | 6 +- src/networks/local_sandwich.jl | 2 + src/networks/tensors.jl | 23 +- src/operators/localoperator.jl | 1 + src/operators/transfermatrix.jl | 36 +-- src/states/infinitepartitionfunction.jl | 8 +- src/states/infinitepeps.jl | 19 +- 27 files changed, 1343 insertions(+), 87 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/PEPSKitAdaptExt.jl create mode 100644 ext/PEPSKitCUDAExt.jl diff --git a/Project.toml b/Project.toml index 89027f73f..c4508301a 100644 --- a/Project.toml +++ b/Project.toml @@ -30,10 +30,22 @@ TupleTools = "9d95972d-f1c8-5527-a6e0-b4b365fa01f6" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" +[weakdeps] +Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" + +[extensions] +PEPSKitAdaptExt = "Adapt" +PEPSKitCUDAExt = ["CUDA", "cuTENSOR"] + [compat] +Adapt = "4" Accessors = "0.1" ChainRulesCore = "1.0" Compat = "3.46, 4.2" +CUDA = "5" +cuTENSOR = "2" DocStringExtensions = "0.9.3" FiniteDifferences = "0.12" KrylovKit = "0.9.5, 0.10" @@ -54,3 +66,7 @@ TupleTools = "1.6.0" VectorInterface = "0.4, 0.5, 0.6" Zygote = "0.6, 0.7" julia = "1.10" + +[sources] +MPSKit = {url = "https://github.com/QuantumKitHub/MPSKit.jl", rev="main"} +MPSKitModels = {url = "https://github.com/QuantumKitHub/MPSKitModels.jl", rev="main"} 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/PEPSKitAdaptExt.jl b/ext/PEPSKitAdaptExt.jl new file mode 100644 index 000000000..dac6432cd --- /dev/null +++ b/ext/PEPSKitAdaptExt.jl @@ -0,0 +1,19 @@ +module PEPSKitAdaptExt + +using PEPSKit +using Adapt + +function Adapt.adapt_structure(to, x::PEPSKit.LocalOperator{T, S}) where {T, S} + terms′ = map(t->(t[1]=>adapt(to, t[2])), x.terms) + return PEPSKit.LocalOperator{typeof(terms′), S}(x.lattice, terms′) +end + +#=function Adapt.adapt_structure(to, x::AdjointTensorMap) + return adjoint(adapt(to, parent(x))) +end +function Adapt.adapt_structure(to, x::DiagonalTensorMap) + data′ = adapt(to, x.data) + return DiagonalTensorMap(data′, x.domain) +end=# + +end 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 827a16689..2d7ebb798 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 f8cc6e53f..f2bf1012e 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -192,10 +192,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/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 6ff44e554..206be935b 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -82,7 +82,7 @@ function compute_relative_phases( # Random MPS of same bond dimension M = map(Tsfinal) do t - randn(scalartype(t), codomain(t) ← domain(t)) + randn(storagetype(T), codomain(t) ← domain(t)) end # Find right fixed points of mixed transfer matrices diff --git a/src/algorithms/optimization/peps_optimization.jl b/src/algorithms/optimization/peps_optimization.jl index 634292d3e..9043d05e6 100644 --- a/src/algorithms/optimization/peps_optimization.jl +++ b/src/algorithms/optimization/peps_optimization.jl @@ -299,7 +299,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 = only(gs) # `withgradient` returns tuple of gradients `gs` tracked_finalizer.gradnorms_unitcell[end] = norm.(unitcell(g)) diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index c121fb3fd..feabc936b 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 533fac679..ea2ff485c 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -13,7 +13,7 @@ function edge_transfer_spectrum( sector = one(sectortype(E)) ) where {E <: CTMRGEdgeTensor} init = randn( - scalartype(E), + storagetype(E), space(first(bot), numind(first(bot)))' ← ℂ[typeof(sector)](sector => 1)' ⊗ space(first(top), 1), ) @@ -110,7 +110,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 d6f39aa7d..f5f62c8b2 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -48,20 +48,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 @@ -82,10 +82,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) == @@ -98,8 +99,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)...) @@ -108,28 +109,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[:, :, :]) @@ -137,7 +138,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 @@ -178,11 +179,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)..., ) @@ -215,19 +216,22 @@ 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)) - return CTMRGEnv(f, T, Ds_north, Ds_east, virtual_spaces...) + return CTMRGEnv(f, TorA, Ds_north, Ds_east, virtual_spaces...) +end +function CTMRGEnv(network::InfiniteSquareNetwork{O}, virtual_spaces...) where {O} + return CTMRGEnv(randn, storagetype(O), network, virtual_spaces...) end -function CTMRGEnv(network::Union{InfiniteSquareNetwork, InfinitePartitionFunction, InfinitePEPS}, virtual_spaces...) - return CTMRGEnv(randn, scalartype(network), network, virtual_spaces...) +function CTMRGEnv(network::Union{<:InfinitePartitionFunction{T}, <:InfinitePEPS{T}}, virtual_spaces...) where {T} + return CTMRGEnv(randn, storagetype(T), network, virtual_spaces...) end # allow constructing environments for implicitly defined contractible networks -function CTMRGEnv(f, T, state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) - return CTMRGEnv(f, T, InfiniteSquareNetwork(state), args...) +function CTMRGEnv(f, ::Type{TA}, state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) where {TA} + return CTMRGEnv(f, TA, InfiniteSquareNetwork(state), args...) end # copy-like constructor @@ -249,13 +253,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 diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index dbf7b0059..c58ea1053 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -62,14 +62,20 @@ end Create a trivial `SUWeight` by specifying the vertical (north) or horizontal (east) virtual bond spaces. """ -function SUWeight( +function SUWeight(::Type{TorA}, 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) V = (d == 1 ? Espaces[r, c] : Nspaces[r, c]) - DiagonalTensorMap(ones(reduceddim(V)), V) + if TorA <: AbstractArray + diag = TorA(undef, reduceddim(V)) + fill!(diag, 1) + else + diag = ones(TorA, reduceddim(V)) + end + DiagonalTensorMap(diag, V) end return SUWeight(weights) end @@ -80,10 +86,10 @@ 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(fill(Nspace, unitcell), fill(Espace, unitcell)) + ) where {S <: ElementarySpace, TorA} + return SUWeight(TorA, fill(Nspace, unitcell), fill(Espace, unitcell)) end """ @@ -94,7 +100,7 @@ Create a trivial `SUWeight` for a given InfinitePEPS. function SUWeight(peps::InfinitePEPS) Nspaces = map(Base.Fix2(domain, NORTH), unitcell(peps)) Espaces = map(Base.Fix2(domain, EAST), unitcell(peps)) - return SUWeight(Nspaces, Espaces) + return SUWeight(storagetype(peps), Nspaces, Espaces) end """ @@ -106,7 +112,7 @@ function SUWeight(pepo::InfinitePEPO) @assert size(pepo, 3) == 1 Nspaces = map(Base.Fix2(domain, NORTH), @view(unitcell(pepo)[:, :, 1])) Espaces = map(Base.Fix2(domain, EAST), @view(unitcell(pepo)[:, :, 1])) - return SUWeight(Nspaces, Espaces) + return SUWeight(storagetype(pepo), Nspaces, Espaces) end Random.rand!(wts::SUWeight) = rand!(Random.default_rng(), wts) diff --git a/src/environments/vumps_environments.jl b/src/environments/vumps_environments.jl index 48605f2f1..6ef0375d0 100644 --- a/src/environments/vumps_environments.jl +++ b/src/environments/vumps_environments.jl @@ -25,10 +25,11 @@ function MPSKit.allocate_GL( bra::InfiniteMPS, mpo::InfiniteTransferMatrix, ket::InfiniteMPS, i::Int ) T = Base.promote_type(scalartype(bra), scalartype(mpo), scalartype(ket)) + TA = similarstoragetype(storagetype(mpo), T) V = left_virtualspace(bra, i) ⊗ _elementwise_dual(left_virtualspace(mpo, i)) ← left_virtualspace(ket, i) - TT = TensorMap{T} + TT = TensorKit.TensorMapWithStorage{T, TA} return TT(undef, V) end @@ -36,7 +37,8 @@ function MPSKit.allocate_GR( bra::InfiniteMPS, mpo::InfiniteTransferMatrix, ket::InfiniteMPS, i::Int ) T = Base.promote_type(scalartype(bra), scalartype(mpo), scalartype(ket)) + TA = similarstoragetype(storagetype(mpo), T) V = right_virtualspace(ket, i) ⊗ right_virtualspace(mpo, i) ← right_virtualspace(bra, i) - TT = TensorMap{T} + TT = TensorKit.TensorMapWithStorage{T, TA} return TT(undef, V) end diff --git a/src/networks/local_sandwich.jl b/src/networks/local_sandwich.jl index d29f54ea1..4ce94e850 100644 --- a/src/networks/local_sandwich.jl +++ b/src/networks/local_sandwich.jl @@ -54,6 +54,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 6ac6411da..c16c3af63 100644 --- a/src/networks/tensors.jl +++ b/src/networks/tensors.jl @@ -23,17 +23,17 @@ const PartitionFunctionTensor{S <: ElementarySpace} = AbstractTensorMap{<:Any, S const PFTensor = PartitionFunctionTensor """ - PartitionFunctionTensor(f, ::Type{T}, Pspace::S, Nspace::S, - [Espace::S], [Sspace::S], [Wspace::S]) where {T,S<:Union{Int,ElementarySpace}} + PartitionFunctionTensor(f, ::Type{TorA}, Pspace::S, Nspace::S, + [Espace::S], [Sspace::S], [Wspace::S]) where {TorA, S<:Union{Int,ElementarySpace}} -Construct a PartitionFunctionTensor tensor based on the north, east, west and south spaces. +Construct a `PartitionFunctionTensor` tensor based on the north, east, west and south spaces. The tensor elements are generated based on `f` and the element type is 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))) @@ -79,18 +79,19 @@ respectively. const PEPSTensor{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1, 4} """ - PEPSTensor(f, ::Type{T}, Pspace::S, Nspace::S, - [Espace::S], [Sspace::S], [Wspace::S]) where {T,S<:Union{Int,ElementarySpace}} + PEPSTensor(f, ::Type{TorA}, Pspace::S, Nspace::S, + [Espace::S], [Sspace::S], [Wspace::S]) where {TorA, S<:Union{Int,ElementarySpace}} Construct a PEPS tensor based on the physical, north, east, south and west spaces. The tensor elements are generated based on `f` and the element type is specified in `T`. """ function PEPSTensor( - f, ::Type{T}, + f, + ::Type{TorA}, Pspace::S, Nspace::S, Espace::S = Nspace, Sspace::S = Nspace', Wspace::S = Espace', - ) where {T, S <: ElementarySpace} - return f(T, Pspace ← Nspace ⊗ Espace ⊗ Sspace ⊗ Wspace) + ) where {TorA, S <: ElementarySpace} + return f(TorA, Pspace ← Nspace ⊗ Espace ⊗ Sspace ⊗ Wspace) end Base.rotl90(t::PEPSTensor) = permute(t, ((1,), (3, 4, 5, 2))) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index c35e5ddb9..b38ee1dc0 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -86,6 +86,7 @@ function add_term!( return operator end +TensorKit.storagetype(lo::LocalOperator{T, S}) where {T, S} = storagetype(first(lo.terms)[2]) # horrible! """ diff --git a/src/operators/transfermatrix.jl b/src/operators/transfermatrix.jl index 76cfc54e3..83a638bd7 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} @@ -127,13 +131,13 @@ virtualspace(O::InfiniteTransferMatrix, i, dir) = virtualspace(O[i], dir) """ initialize_mps( f=randn, - T=scalartype(O), + T=storagetype(O), O::Union{InfiniteTransferPEPS,InfiniteTransferPEPO}, virtualspaces::AbstractArray{<:ElementarySpace,1} ) initialize_mps( f=randn, - T=scalartype(O), + T=storagetype(O), O::Union{MultilineTransferPEPS,MultilineTransferPEPO}, virtualspaces::AbstractArray{<:ElementarySpace,2} ) @@ -141,37 +145,37 @@ virtualspace(O::InfiniteTransferMatrix, i, dir) = virtualspace(O[i], dir) Inialize a boundary MPS for the transfer operator `O` by specifying an array of virtual spaces consistent with the unit cell. """ -function initialize_mps(O::Union{InfiniteTransferMatrix, MultilineTransferMatrix}, arg) # initialize(f=randn, T=scalartype(O), O, ...) - return initialize_mps(randn, scalartype(O), O, arg) +function initialize_mps(O::Union{InfiniteTransferMatrix, MultilineTransferMatrix}, arg; kwargs...) # initialize(f=randn, T=scalartype(O), O, ...) + return initialize_mps(randn, storagetype(O), O, arg; kwargs...) end function initialize_mps( - f, T, O::InfiniteTransferMatrix, virtualspaces::AbstractArray{S, 1} - ) where {S} + f, ::Type{TorA}, O::InfiniteTransferMatrix, virtualspaces::AbstractArray{S, 1}; kwargs... + ) where {S, TorA} return InfiniteMPS( [ f( - T, + TorA, virtualspaces[_prev(i, end)] * _elementwise_dual(north_virtualspace(O, i)), virtualspaces[mod1(i, end)], ) for i in 1:length(O) - ] + ]; kwargs... ) end function initialize_mps( - f, T, O::MultilineTransferMatrix, virtualspaces::AbstractArray{S, 2} - ) where {S} + f, ::Type{TorA}, O::MultilineTransferMatrix, virtualspaces::AbstractArray{S, 2}; kwargs... + ) where {S, TorA} mpss = map(1:size(O, 1)) do r - return initialize_mps(f, T, O[r], virtualspaces[r, :]) + return initialize_mps(f, TorA, O[r], virtualspaces[r, :]; kwargs...) end return MPSKit.Multiline(mpss) end function initialize_mps( - f, T, O::MultilineTransferMatrix, virtualspaces::AbstractArray{S, 1} - ) where {S} - return initialize_mps(f, T, O, repeat(virtualspaces, length(O), 1)) + f, ::Type{TorA}, O::MultilineTransferMatrix, virtualspaces::AbstractArray{S, 1}; kwargs... + ) where {S, TorA} + return initialize_mps(f, TorA, O, repeat(virtualspaces, length(O), 1); kwargs...) end -function initialize_mps(f, T, O::MultilineTransferMatrix, V::ElementarySpace) - return initialize_mps(f, T, O, repeat([V], length(O), length(O[1]))) +function initialize_mps(f, ::Type{TorA}, O::MultilineTransferMatrix, V::ElementarySpace; kwargs...) where {TorA} + return initialize_mps(f, TorA, O, repeat([V], length(O), length(O[1])); kwargs...) end @doc """ 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 3d3a82f0f..166599b6b 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -46,8 +46,8 @@ Create an `InfinitePEPS` by specifying the physical, north virtual and east virt of the PEPS tensor at each site in the unit cell as a matrix. """ function InfinitePEPS( - f, T::Type{<:Number}, Pspaces::M, Nspaces::M, Espaces::M = Nspaces - ) where {M <: AbstractMatrix{<:ElementarySpace}} + f, ::Type{TorA}, Pspaces::M, Nspaces::M, Espaces::M = Nspaces + ) where {M <: AbstractMatrix{<:ElementarySpace}, TorA} size(Pspaces) == size(Nspaces) == size(Espaces) || throw(ArgumentError("Input spaces should have equal sizes.")) @@ -55,7 +55,7 @@ function InfinitePEPS( Wspaces = adjoint.(circshift(Espaces, (0, 1))) A = map(Pspaces, Nspaces, Espaces, Sspaces, Wspaces) do P, N, E, S, W - return PEPSTensor(f, T, P, N, E, S, W) + return PEPSTensor(f, TorA, P, N, E, S, W) end return InfinitePEPS(A) @@ -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,15 +106,15 @@ 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. """ function InfinitePEPS( - f, T::Type{<:Number}, Pspace::S, vspaces...; unitcell::Tuple{Int, Int} = (1, 1) - ) where {S <: ElementarySpace} + f, ::Type{TorA}, Pspace::S, vspaces...; unitcell::Tuple{Int, Int} = (1, 1) + ) where {S <: ElementarySpace, TorA} return InfinitePEPS( - f, T, + f, TorA, _fill_state_physical_spaces(Pspace; unitcell), _fill_state_virtual_spaces(vspaces...; unitcell)..., ) From 3f8e47230ee7e3dc00de34c47b69398d01649da1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 12 May 2026 05:13:11 -0400 Subject: [PATCH 002/102] Update Project.toml --- Project.toml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index c4508301a..33c7d47d4 100644 --- a/Project.toml +++ b/Project.toml @@ -44,8 +44,8 @@ Adapt = "4" Accessors = "0.1" ChainRulesCore = "1.0" Compat = "3.46, 4.2" -CUDA = "5" -cuTENSOR = "2" +CUDA = "6" +cuTENSOR = "6" DocStringExtensions = "0.9.3" FiniteDifferences = "0.12" KrylovKit = "0.9.5, 0.10" @@ -68,5 +68,6 @@ Zygote = "0.6, 0.7" julia = "1.10" [sources] -MPSKit = {url = "https://github.com/QuantumKitHub/MPSKit.jl", rev="main"} -MPSKitModels = {url = "https://github.com/QuantumKitHub/MPSKitModels.jl", rev="main"} +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"} From f972bb249cbf94c9a4463ecd68901447c303c410 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 14 May 2026 06:13:26 -0400 Subject: [PATCH 003/102] A few more small fixes --- ext/PEPSKitAdaptExt.jl | 12 ++---------- src/algorithms/ctmrg/gaugefix.jl | 2 +- src/utility/util.jl | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/ext/PEPSKitAdaptExt.jl b/ext/PEPSKitAdaptExt.jl index dac6432cd..9f0a6402e 100644 --- a/ext/PEPSKitAdaptExt.jl +++ b/ext/PEPSKitAdaptExt.jl @@ -4,16 +4,8 @@ using PEPSKit using Adapt function Adapt.adapt_structure(to, x::PEPSKit.LocalOperator{T, S}) where {T, S} - terms′ = map(t->(t[1]=>adapt(to, t[2])), x.terms) - return PEPSKit.LocalOperator{typeof(terms′), S}(x.lattice, terms′) + terms′ = Dict(k=>adapt(to, v) for (k, v) in x.terms) + return PEPSKit.LocalOperator{valtype(terms′)}(x.lattice, terms′) end -#=function Adapt.adapt_structure(to, x::AdjointTensorMap) - return adjoint(adapt(to, parent(x))) -end -function Adapt.adapt_structure(to, x::DiagonalTensorMap) - data′ = adapt(to, x.data) - return DiagonalTensorMap(data′, x.domain) -end=# - end diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 206be935b..7fbd603d5 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -145,7 +145,7 @@ end function initialize_right_fixedpoint(tops, bottoms) ρ0 = randn( - scalartype(tops), space(tops[end], numind(tops[end]))' ← space(bottoms[end], numind(bottoms[end]))' + TensorKit.promote_storagetype(tops...), space(tops[end], numind(tops[end]))' ← space(bottoms[end], numind(bottoms[end]))' ) return ρ0 end diff --git a/src/utility/util.jl b/src/utility/util.jl index 603aec675..6d1c13a85 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -69,7 +69,7 @@ function is_degenerate_spectrum( S; atol::Real = 0, rtol::Real = atol > 0 ? 0 : sqrt(eps(scalartype(S))) ) for (_, b) in blocks(S) - s = real(diag(b)) + s = real(collect(diag(b))) for i in 1:(length(s) - 1) isapprox(s[i], s[i + 1]; atol, rtol) && return true end From bff7b7068f1b4563ce50c688a876387c3ed9043a Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 19 May 2026 05:16:05 -0400 Subject: [PATCH 004/102] Use proper storagetype for delta_t --- src/utility/svd.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 8e17913fe..222456fd0 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -303,7 +303,7 @@ function ChainRulesCore.rrule( function svd_trunc!_full_pullback(ΔUSV′) ΔUSV = unthunk.(ΔUSV′) Δt = svd_pullback!( - zeros(scalartype(t), space(t)), t, (U, S, V⁺), ΔUSV, inds; + zeros(storagetype(t), space(t)), t, (U, S, V⁺), ΔUSV, inds; gauge_atol = gtol(ΔUSV), degeneracy_atol = alg.rrule_alg.degeneracy_atol, ) return NoTangent(), Δt, NoTangent() From b2ac9d4b41b978a0fdc5367c43785ba8305acf38 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 19 May 2026 15:23:52 +0200 Subject: [PATCH 005/102] Restore singular value distance --- src/algorithms/ctmrg/ctmrg.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index f2bf1012e..7556076f6 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -192,6 +192,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 6e44a59cf96ffe6c44dc352df3261c1c419691a4 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 10 Jun 2026 07:25:00 +0200 Subject: [PATCH 006/102] Update Project.toml --- Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Project.toml b/Project.toml index 33c7d47d4..dfcc40876 100644 --- a/Project.toml +++ b/Project.toml @@ -68,6 +68,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"} From 9d2f14508b4b1217403ac0fb7f3180ee1071a8be Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 15 Jun 2026 09:16:31 +0200 Subject: [PATCH 007/102] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index dfcc40876..10e520464 100644 --- a/Project.toml +++ b/Project.toml @@ -68,4 +68,4 @@ Zygote = "0.6, 0.7" julia = "1.10" [sources] -MPSKit = {url = "https://github.com/QuantumKitHub/MPSKit.jl", rev="ksh/cu2"} +MPSKit = {url = "https://github.com/QuantumKitHub/MPSKit.jl", rev="main"} From 9781943e725dd65e522df1e71f1eafe3a9b3d6ba Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 29 Jul 2026 15:45:40 +0200 Subject: [PATCH 008/102] Cleanup and some actual tests --- Project.toml | 5 -- ext/PEPSKitAdaptExt.jl | 2 +- ext/PEPSKitCUDAExt.jl | 39 ---------- test/Project.toml | 4 ++ test/amd/boundarymps/vumps.jl | 127 +++++++++++++++++++++++++++++++++ test/cuda/boundarymps/vumps.jl | 127 +++++++++++++++++++++++++++++++++ test/runtests.jl | 12 ++++ 7 files changed, 271 insertions(+), 45 deletions(-) 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 10e520464..3651295ad 100644 --- a/Project.toml +++ b/Project.toml @@ -32,20 +32,15 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [weakdeps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" [extensions] PEPSKitAdaptExt = "Adapt" -PEPSKitCUDAExt = ["CUDA", "cuTENSOR"] [compat] Adapt = "4" Accessors = "0.1" ChainRulesCore = "1.0" Compat = "3.46, 4.2" -CUDA = "6" -cuTENSOR = "6" DocStringExtensions = "0.9.3" FiniteDifferences = "0.12" KrylovKit = "0.9.5, 0.10" diff --git a/ext/PEPSKitAdaptExt.jl b/ext/PEPSKitAdaptExt.jl index 9f0a6402e..500880fde 100644 --- a/ext/PEPSKitAdaptExt.jl +++ b/ext/PEPSKitAdaptExt.jl @@ -4,7 +4,7 @@ using PEPSKit using Adapt function Adapt.adapt_structure(to, x::PEPSKit.LocalOperator{T, S}) where {T, S} - terms′ = Dict(k=>adapt(to, v) for (k, v) in x.terms) + terms′ = Dict(k => adapt(to, v) for (k, v) in x.terms) return PEPSKit.LocalOperator{valtype(terms′)}(x.lattice, terms′) end 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/test/Project.toml b/test/Project.toml index 86bdd9467..cff25a22b 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -3,8 +3,10 @@ name = "PEPSKitTests" [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" @@ -27,7 +29,9 @@ PEPSKit = {path = ".."} [compat] Adapt = "4" +AMDGPU = "2" ChainRulesTestUtils = "1.13" +CUDA = "6" ParallelTestRunner = "2.6.0" QuadGK = "2.11.1" Test = "1" 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 7c831cae4552852f9175b804f20316737c000aad Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 29 Jul 2026 16:59:27 +0200 Subject: [PATCH 009/102] Get rid of duped examples and fix formatting --- 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 ---------- src/environments/ctmrg_environments.jl | 8 +- src/environments/suweight.jl | 6 +- src/operators/transfermatrix.jl | 2 +- src/states/infinitepartitionfunction.jl | 2 +- src/states/infinitepeps.jl | 4 +- 14 files changed, 12 insertions(+), 1168 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 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/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index f5f62c8b2..b868e4ab3 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -49,13 +49,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 @@ -179,7 +179,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 c58ea1053..77c6f1567 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -62,7 +62,8 @@ end Create a trivial `SUWeight` by specifying the vertical (north) or horizontal (east) virtual bond spaces. """ -function SUWeight(::Type{TorA}, +function SUWeight( + ::Type{TorA}, Nspaces::M, Espaces::M = Nspaces ) where {M <: AbstractMatrix{<:ElementarySpace}, TorA} @assert size(Nspaces) == size(Espaces) @@ -86,7 +87,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, TorA} return SUWeight(TorA, fill(Nspace, unitcell), fill(Espace, unitcell)) diff --git a/src/operators/transfermatrix.jl b/src/operators/transfermatrix.jl index 83a638bd7..e2724b25b 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/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index 166599b6b..9697bc15f 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -46,8 +46,8 @@ Create an `InfinitePEPS` by specifying the physical, north virtual and east virt of the PEPS tensor at each site in the unit cell as a matrix. """ function InfinitePEPS( - f, ::Type{TorA}, Pspaces::M, Nspaces::M, Espaces::M = Nspaces - ) where {M <: AbstractMatrix{<:ElementarySpace}, TorA} + f, ::Type{TorA}, Pspaces::M, Nspaces::M, Espaces::M = Nspaces + ) where {M <: AbstractMatrix{<:ElementarySpace}, TorA} size(Pspaces) == size(Nspaces) == size(Espaces) || throw(ArgumentError("Input spaces should have equal sizes.")) From ac5de8fead7cc2391f86165cf7489ce3880c2fcf Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 29 Jul 2026 17:04:53 +0200 Subject: [PATCH 010/102] 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 6ab850aee9749ab4ce464bed7f49a55ba6c3ffad Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 29 Jul 2026 17:44:11 +0200 Subject: [PATCH 011/102] Add a simple timestep example --- ext/PEPSKitAdaptExt.jl | 5 +++++ test/amd/timeevol/timestep.jl | 35 ++++++++++++++++++++++++++++++++++ test/cuda/timeevol/timestep.jl | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 test/amd/timeevol/timestep.jl create mode 100644 test/cuda/timeevol/timestep.jl diff --git a/ext/PEPSKitAdaptExt.jl b/ext/PEPSKitAdaptExt.jl index 500880fde..c33ce4f38 100644 --- a/ext/PEPSKitAdaptExt.jl +++ b/ext/PEPSKitAdaptExt.jl @@ -8,4 +8,9 @@ function Adapt.adapt_structure(to, x::PEPSKit.LocalOperator{T, S}) where {T, S} return PEPSKit.LocalOperator{valtype(terms′)}(x.lattice, terms′) end +function Adapt.adapt_structure(to, x::PEPSKit.InfinitePEPS{T}) where {T} + A′ = map(a -> adapt(to, a), x.A) + return InfinitePEPS{eltype(A′)}(A′) +end + end 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 b868ae488e81df6ef7e4f50a3dc5604a40b9e3fb Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 31 Jul 2026 12:46:35 +0200 Subject: [PATCH 012/102] More progress --- ext/PEPSKitAdaptExt.jl | 11 + src/algorithms/time_evolution/apply_mpo.jl | 2 +- src/environments/ctmrg_environments.jl | 54 ++-- 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 +++++++++++++++++++++ 8 files changed, 360 insertions(+), 32 deletions(-) create mode 100644 test/amd/ctmrg/contractions.jl diff --git a/ext/PEPSKitAdaptExt.jl b/ext/PEPSKitAdaptExt.jl index c33ce4f38..07c150105 100644 --- a/ext/PEPSKitAdaptExt.jl +++ b/ext/PEPSKitAdaptExt.jl @@ -13,4 +13,15 @@ function Adapt.adapt_structure(to, x::PEPSKit.InfinitePEPS{T}) where {T} return InfinitePEPS{eltype(A′)}(A′) end +function Adapt.adapt_structure(to, x::PEPSKit.InfinitePEPO{T}) where {T} + A′ = map(a -> adapt(to, a), x.A) + return InfinitePEPO{eltype(A′)}(A′) +end + +function Adapt.adapt_structure(to, x::PEPSKit.CTMRGEnv{C, T}) where {C, T} + C′ = map(c -> adapt(to, c), x.corners) + T′ = map(t -> adapt(to, t), x.edges) + return CTMRGEnv{eltype(C′), eltype(T′)}(C′, T′) +end + end diff --git a/src/algorithms/time_evolution/apply_mpo.jl b/src/algorithms/time_evolution/apply_mpo.jl index 25fd47974..85c220133 100644 --- a/src/algorithms/time_evolution/apply_mpo.jl +++ b/src/algorithms/time_evolution/apply_mpo.jl @@ -336,7 +336,7 @@ function _apply_gatempo!( fusers = map(Iterators.drop(Ms, 1), Iterators.drop(gs, 1)) do M, g V1, V2 = space(M, 1), space(g, 1) @assert !isdual(V1) && !isdual(V2) - return isomorphism(fuse(V1, V2) ← V1 ⊗ V2) + return isomorphism(storagetype(M), fuse(V1, V2) ← V1 ⊗ V2) end #= gate on codomain of PEPO (gate_ax = 1) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index b868e4ab3..730ca7eb1 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -48,15 +48,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 """ @@ -82,11 +82,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) == @@ -99,8 +99,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)...) @@ -109,28 +109,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[:, :, :]) @@ -138,7 +138,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 @@ -179,11 +179,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)..., ) @@ -216,11 +216,11 @@ 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)) - return CTMRGEnv(f, TorA, Ds_north, Ds_east, virtual_spaces...) + return CTMRGEnv(f, T, Ds_north, Ds_east, virtual_spaces...) end function CTMRGEnv(network::InfiniteSquareNetwork{O}, virtual_spaces...) where {O} return CTMRGEnv(randn, storagetype(O), network, virtual_spaces...) @@ -230,8 +230,8 @@ function CTMRGEnv(network::Union{<:InfinitePartitionFunction{T}, <:InfinitePEPS{ end # allow constructing environments for implicitly defined contractible networks -function CTMRGEnv(f, ::Type{TA}, state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) where {TA} - return CTMRGEnv(f, TA, InfiniteSquareNetwork(state), args...) +function CTMRGEnv(f, ::Type{T}, state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) where {T} + return CTMRGEnv(f, T, InfiniteSquareNetwork(state), args...) end # copy-like constructor @@ -239,6 +239,8 @@ CTMRGEnv(env::CTMRGEnv) = CTMRGEnv(env.corners, env.edges) @non_differentiable CTMRGEnv(state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) +TensorKit.storagetype(::Type{CTMRGEnv{C, E}}) where {C, E} = storagetype(C) == storagetype(E) ? storagetype(C) : error("mismatched storage types for CTMRGEnvironment") + # Custom adjoint for CTMRGEnv constructor, needed for fixed-point differentiation function ChainRulesCore.rrule( ::Type{CTMRGEnv}, corners::Array{C, 3}, edges::Array{T, 3} diff --git a/src/networks/infinitesquarenetwork.jl b/src/networks/infinitesquarenetwork.jl index fe62377b3..46161327b 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 f902a3874..22695b2e6 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 9697bc15f..010022c3b 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 834b09061fab20e2ef95af4adf796f4309cd304f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 31 Jul 2026 10:31:13 -0400 Subject: [PATCH 013/102] refactor `_fliptwist_s` --- src/algorithms/bp/gaugefix.jl | 2 +- src/algorithms/truncation/bond_truncation.jl | 4 ++-- src/utility/util.jl | 11 ++++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/algorithms/bp/gaugefix.jl b/src/algorithms/bp/gaugefix.jl index 15693557a..a86599e04 100644 --- a/src/algorithms/bp/gaugefix.jl +++ b/src/algorithms/bp/gaugefix.jl @@ -129,7 +129,7 @@ function SUWeight(env::BPEnv) I = CartesianIndex(mod1(dir′ + 1, 2), row, col) sqrtM12, _, sqrtM21, _ = _sqrt_bp_messages(I, env) Λ = DiagonalTensorMap(svd_vals!(sqrtM12 * sqrtM21)) - return isdual(space(sqrtM12, 1)) ? _fliptwist_s(Λ) : Λ + return isdual(space(sqrtM12, 1)) ? _fliptwist_s!(Λ) : Λ end return SUWeight(wts) end diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index 8fbb9d1ca..81983dfd0 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -141,7 +141,7 @@ function bond_truncate(a::MPSTensor, b::MPSTensor, benv::BondEnv, alg::ALSTrunca a, b = absorb_s(a, s, b) b = permute(b, ((1, 2), (3,))) if need_flip - a, s, b = flip(a, numind(a)), _fliptwist_s(s), flip(b, 1) + a, s, b = flip(a, numind(a)), _fliptwist_s!(s), flip(b, 1) end return a, s, b, (; fid, Δfid, Δs) end @@ -182,7 +182,7 @@ function bond_truncate(a::MPSTensor, b::MPSTensor, benv::BondEnv, alg::FullEnvTr @tensor a[-1 -2; -3] := Qa[-1 -2 3] * u[3 -3] @tensor b[-1 -2; -3] := vh[-1 1] * Qb[1 -2 -3] if need_flip - a, s, b = flip(a, numind(a)), _fliptwist_s(s), flip(b, 1) + a, s, b = flip(a, numind(a)), _fliptwist_s!(s), flip(b, 1) end return a, s, b, info end diff --git a/src/utility/util.jl b/src/utility/util.jl index 6d1c13a85..9f6c82f6f 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -62,7 +62,16 @@ function absorb_s(U::AbstractTensorMap, S::DiagonalTensorMap, V::AbstractTensorM return U * sqrt_S, sqrt_S * V end -_fliptwist_s(s::DiagonalTensorMap) = twist!(DiagonalTensorMap(flip(s, 1:2)), 1) +# returns new but destroys old S +function _fliptwist_s!(s::DiagonalTensorMap) + for (f₁, f₂) in fusiontrees(s) + data = s[f₁, f₂] + _, θ = only(flip((f₁, f₂), (1, 2))) + θ *= twist(f₁.uncoupled[1]) + scale!(data, θ) + end + return DiagonalTensorMap(s.data, flip(s.domain)) +end # Check whether diagonals contain degenerate values up to absolute or relative tolerance function is_degenerate_spectrum( From 21c927b5c7f89417dd7e0e69fd252f0222654e18 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 3 Aug 2026 10:03:38 +0200 Subject: [PATCH 014/102] Fixes --- src/algorithms/time_evolution/apply_gate.jl | 2 +- test/amd/boundarymps/vumps.jl | 2 +- test/amd/toolbox/densitymatrices.jl | 2 +- test/cuda/boundarymps/vumps.jl | 2 +- test/cuda/toolbox/densitymatrices.jl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/algorithms/time_evolution/apply_gate.jl b/src/algorithms/time_evolution/apply_gate.jl index 63f6e50b7..9b6f811aa 100644 --- a/src/algorithms/time_evolution/apply_gate.jl +++ b/src/algorithms/time_evolution/apply_gate.jl @@ -45,7 +45,7 @@ function _apply_gate(a::MPSTensor, b::MPSTensor, gate::NNGate, trunc::Truncation a, s, b, ϵ = svd_trunc!(a2b2; trunc) a, b = absorb_s(a, s, b) if need_flip - a, s, b = flip(a, numind(a)), _fliptwist_s(s), flip(b, 1) + a, s, b = flip(a, numind(a)), _fliptwist_s!(s), flip(b, 1) end b = permute(b, ((1, 2), (3,))) return a, s, b, ϵ 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 1f1067a245a341667f5e92cbd239bd8c629b57da Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 3 Aug 2026 11:59:02 +0200 Subject: [PATCH 015/102] Working CTMRG contractions tests --- ext/PEPSKitAdaptExt.jl | 5 + src/environments/ctmrg_environments.jl | 3 +- src/networks/tensors.jl | 2 +- src/states/infinitepartitionfunction.jl | 3 +- test/amd/ctmrg/contractions.jl | 3 +- test/cuda/ctmrg/contractions.jl | 315 ++++++++++++++++++++++++ 6 files changed, 326 insertions(+), 5 deletions(-) create mode 100644 test/cuda/ctmrg/contractions.jl diff --git a/ext/PEPSKitAdaptExt.jl b/ext/PEPSKitAdaptExt.jl index 07c150105..23df36a83 100644 --- a/ext/PEPSKitAdaptExt.jl +++ b/ext/PEPSKitAdaptExt.jl @@ -18,6 +18,11 @@ function Adapt.adapt_structure(to, x::PEPSKit.InfinitePEPO{T}) where {T} return InfinitePEPO{eltype(A′)}(A′) end +function Adapt.adapt_structure(to, x::PEPSKit.InfinitePartitionFunction{T}) where {T} + A′ = map(a -> adapt(to, a), x.A) + return InfinitePartitionFunction{eltype(A′)}(A′) +end + function Adapt.adapt_structure(to, x::PEPSKit.CTMRGEnv{C, T}) where {C, T} C′ = map(c -> adapt(to, c), x.corners) T′ = map(t -> adapt(to, t), x.edges) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 730ca7eb1..e06d69a19 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -101,7 +101,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)...) @@ -231,7 +230,7 @@ end # allow constructing environments for implicitly defined contractible networks function CTMRGEnv(f, ::Type{T}, state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) where {T} - return CTMRGEnv(f, T, InfiniteSquareNetwork(state), args...) + return CTMRGEnv(f, similarstoragetype(eltype(state), T), InfiniteSquareNetwork(state), args...) end # copy-like constructor diff --git a/src/networks/tensors.jl b/src/networks/tensors.jl index c16c3af63..d014150b4 100644 --- a/src/networks/tensors.jl +++ b/src/networks/tensors.jl @@ -156,7 +156,7 @@ herm_depth(t::PEPOTensor) = permute(t', ((5, 6), (3, 2, 1, 4))) Fuse the physical indices of a PEPO tensor, obtaining a PEPS tensor. """ function fuse_physicalspaces(O::PEPOTensor) - F = isomorphism(Int, fuse(codomain(O)), codomain(O)) + F = isomorphism(TensorKit.similarstoragetype(O, Int), fuse(codomain(O)), codomain(O)) return F * O, F end 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/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/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 From 3db00631e72cd4c01b9c6f3e549f717fee29019b Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 3 Aug 2026 17:06:40 +0200 Subject: [PATCH 016/102] Further updates --- src/algorithms/ctmrg/initialization.jl | 12 ++++++------ src/environments/ctmrg_environments.jl | 2 +- src/states/infinitepeps.jl | 10 +++++----- src/utility/svd.jl | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/algorithms/ctmrg/initialization.jl b/src/algorithms/ctmrg/initialization.jl index 235611e0b..6136d9377 100644 --- a/src/algorithms/ctmrg/initialization.jl +++ b/src/algorithms/ctmrg/initialization.jl @@ -5,7 +5,7 @@ Initialize a fully random `CTMRGEnv` using the given environment virtual spaces. [`CTMRGEnv`](@ref) for details on the expected format of the virtual spaces. """ function initialize_ctmrg_environment( - elt::Type{<:Number}, + elt::Type, n::InfiniteSquareNetwork, alg::RandomInitialization, virtual_spaces... = oneunit(spacetype(n)), @@ -20,7 +20,7 @@ Initialize a `CTMRGEnv` corresponding to a product state with trivial virtual sp corners. The product state edge tensors are initialized as `alg.f(elt, V::ProductSpace)`. """ function initialize_ctmrg_environment( - elt::Type{<:Number}, + elt::Type, n::InfiniteSquareNetwork, alg::ProductStateInitialization, ) @@ -36,7 +36,7 @@ Initialize a `CTMRGEnv` by applying a single untruncated iteration of environment is chosen as a random product state. """ function initialize_ctmrg_environment( - elt::Type{<:Number}, + elt::Type, n::InfiniteSquareNetwork, alg::ApplicationInitialization, env0 = ProductStateEnv(alg.f, elt, n) @@ -65,7 +65,7 @@ virtual spaces of a two-layer network, for example ``` """ function initialize_ctmrg_environment( - elt::Type{<:Number}, + elt::Type, n::InfiniteSquareNetwork, ::IdentityInitialization, ) @@ -80,11 +80,11 @@ function initialize_ctmrg_environment( A::Union{InfiniteSquareNetwork, InfinitePEPS, InfinitePartitionFunction}, args...; kwargs... ) - return initialize_ctmrg_environment(scalartype(A), A, args...; kwargs...) + return initialize_ctmrg_environment(storagetype(A), A, args...; kwargs...) end function initialize_ctmrg_environment( elt::Type{<:Number}, A::Union{InfinitePEPS, InfinitePartitionFunction}, args...; kwargs... ) - return initialize_ctmrg_environment(elt, InfiniteSquareNetwork(A), args...; kwargs...) + return initialize_ctmrg_environment(similarstoragetype(eltype(A), elt), InfiniteSquareNetwork(A), args...; kwargs...) end diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index e06d69a19..dc942398d 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -230,7 +230,7 @@ end # allow constructing environments for implicitly defined contractible networks function CTMRGEnv(f, ::Type{T}, state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) where {T} - return CTMRGEnv(f, similarstoragetype(eltype(state), T), InfiniteSquareNetwork(state), args...) + return CTMRGEnv(f, similarstoragetype(eltype(state), eltype(T)), InfiniteSquareNetwork(state), args...) end # copy-like constructor diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index 010022c3b..1fe16e5e8 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -104,15 +104,15 @@ 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. """ function InfinitePEPS( - f, ::Type{TorA}, Pspace::S, vspaces...; unitcell::Tuple{Int, Int} = (1, 1) - ) where {S <: ElementarySpace, TorA} + f, ::Type{T}, Pspace::S, vspaces...; unitcell::Tuple{Int, Int} = (1, 1) + ) where {S <: ElementarySpace, T} return InfinitePEPS( - f, TorA, + f, T, _fill_state_physical_spaces(Pspace; unitcell), _fill_state_virtual_spaces(vspaces...; unitcell)..., ) @@ -127,7 +127,7 @@ Base.eltype(::Type{InfinitePEPS{T}}) where {T} = T Base.eltype(A::InfinitePEPS) = eltype(typeof(A)) Base.copy(A::InfinitePEPS) = InfinitePEPS(copy(unitcell(A))) -function Base.similar(A::InfinitePEPS, T::Type{TorA} = scalartype(A)) where {TorA} +function Base.similar(A::InfinitePEPS, T::Type = scalartype(A)) return InfinitePEPS(map(t -> similar(t, T), unitcell(A))) end Base.repeat(A::InfinitePEPS, counts...) = InfinitePEPS(repeat(unitcell(A), counts...)) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 222456fd0..5d96d58fe 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -186,7 +186,7 @@ end _default_svd_rrule_alg(::IterSVD) = :TruncPullback random_start_vector(t::AbstractMatrix) = randn(scalartype(t), size(t, 1)) -deterministic_start_vector(t::AbstractMatrix) = ones(scalartype(t), size(t, 1)) +deterministic_start_vector(t::AbstractMatrix) = fill!(similar(t, size(t, 1)), one(scalartype(t))) # Compute SVD data block-wise using KrylovKit algorithm # TODO: redefine _empty_svdtensors, _create_svdtensors From 2b6628b12350f864c155ee4d16f5426ca58c7f5e Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 4 Aug 2026 10:45:19 +0200 Subject: [PATCH 017/102] VUMPS test fix --- src/utility/symmetrization.jl | 2 +- test/amd/boundarymps/vumps.jl | 8 +++++--- test/cuda/boundarymps/vumps.jl | 8 +++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 1d98defc3..d5694ce21 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -52,7 +52,7 @@ function _fit_spaces( ) where {T, S <: IndexSpace, N₁, N₂} for i in 1:(N₁ + N₂) if space(x, i) ≠ space(y, i) - f = unitary(space(x, i) ← space(y, i)) + f = unitary(TensorKit.promote_storagetype(y, x), space(x, i) ← space(y, i)) y = permute( ncon([f, y], [[-i, 1], [-(1:(i - 1))..., 1, -((i + 1):(N₁ + N₂))...]]), (Tuple(1:N₁), Tuple((N₁ + 1):(N₁ + N₂))), 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/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)) From f0a03e767e1891e65c89ef876755cf76a2366d70 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 4 Aug 2026 11:22:42 +0200 Subject: [PATCH 018/102] More init tests --- src/algorithms/ctmrg/c4v.jl | 6 +- src/algorithms/ctmrg/initialization.jl | 2 +- src/environments/bp_environments.jl | 4 +- test/amd/ctmrg/initialization.jl | 94 ++++++++++++++++++++++++++ test/cuda/ctmrg/initialization.jl | 94 ++++++++++++++++++++++++++ 5 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 test/amd/ctmrg/initialization.jl create mode 100644 test/cuda/ctmrg/initialization.jl diff --git a/src/algorithms/ctmrg/c4v.jl b/src/algorithms/ctmrg/c4v.jl index a4d0da08c..79b118cad 100644 --- a/src/algorithms/ctmrg/c4v.jl +++ b/src/algorithms/ctmrg/c4v.jl @@ -212,7 +212,7 @@ Initialize a C₄ᵥ-symmetric `CTMRGEnv` on virtual spaces `Venv` with random e by `f` and scalartype `T`. """ function initialize_random_c4v_env(state, Venv::ElementarySpace) - return initialize_random_c4v_env(randn, scalartype(state), state, Venv) + return initialize_random_c4v_env(randn, storagetype(state), state, Venv) end function initialize_random_c4v_env(f, T, state::InfinitePEPS, Venv::ElementarySpace) Vpeps = north_virtualspace(state, 1, 1)' @@ -223,7 +223,7 @@ function initialize_random_c4v_env(f, T, state::InfinitePartitionFunction, Venv: return initialize_random_c4v_env(f, T, Vpf, Venv) end function initialize_random_c4v_env(f, T, Vstate::VectorSpace, Venv::ElementarySpace) - corner₀ = DiagonalTensorMap(randn(real(T), Venv ← Venv)) + corner₀ = DiagonalTensorMap(randn(similarstoragetype(T, real(eltype(T))), Venv ← Venv)) edge₀ = f(T, Venv ⊗ Vstate ← Venv) edge₀ = _project_hermitian(edge₀) return CTMRGEnv(corner₀, edge₀) @@ -236,7 +236,7 @@ Initialize a C₄ᵥ-symmetric `CTMRGEnv` with a singlet corner of dimension `di identity edge from `id(T, Venv ⊗ Vpeps)`. """ function initialize_singlet_c4v_env(state::InfinitePEPS, Venv::ElementarySpace) - return initialize_singlet_c4v_env(scalartype(state), state, Venv) + return initialize_singlet_c4v_env(storagetype(state), state, Venv) end function initialize_singlet_c4v_env(T, state::InfinitePEPS, Venv::ElementarySpace) Vpeps = north_virtualspace(state, 1, 1)' diff --git a/src/algorithms/ctmrg/initialization.jl b/src/algorithms/ctmrg/initialization.jl index 6136d9377..9f179d940 100644 --- a/src/algorithms/ctmrg/initialization.jl +++ b/src/algorithms/ctmrg/initialization.jl @@ -80,7 +80,7 @@ function initialize_ctmrg_environment( A::Union{InfiniteSquareNetwork, InfinitePEPS, InfinitePartitionFunction}, args...; kwargs... ) - return initialize_ctmrg_environment(storagetype(A), A, args...; kwargs...) + return initialize_ctmrg_environment(scalartype(A), A, args...; kwargs...) end function initialize_ctmrg_environment( elt::Type{<:Number}, A::Union{InfinitePEPS, InfinitePartitionFunction}, args...; diff --git a/src/environments/bp_environments.jl b/src/environments/bp_environments.jl index bc0429e0a..def955d4e 100644 --- a/src/environments/bp_environments.jl +++ b/src/environments/bp_environments.jl @@ -27,6 +27,8 @@ struct BPEnv{T} "4 x rows x cols array of message tensors, where the first dimension specifies the spatial direction" messages::Array{T, 3} end +TensorKit.storagetype(::Type{BPEnv{T}}) where {T} = storagetype(T) + """ Construct a message tensor on a certain bond of a network, @@ -121,7 +123,7 @@ function BPEnv(f, T, network::InfiniteSquareNetwork; posdef::Bool = true) return BPEnv(f, T, Ds_north, Ds_east; posdef) end function BPEnv(network::Union{InfiniteSquareNetwork, InfinitePartitionFunction, InfinitePEPS, InfinitePEPO}, args...; kwargs...) - return BPEnv(isomorphism, scalartype(network), network, args...; kwargs...) + return BPEnv(isomorphism, storagetype(network), network, args...; kwargs...) end function BPEnv(f, T, state::Union{InfinitePartitionFunction, InfinitePEPS, InfinitePEPO}, args...; kwargs...) return BPEnv(f, T, InfiniteSquareNetwork(state), args...; kwargs...) 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/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 5b02e385ae7962e4ed273cfcc9d02f15f3f4477d Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 4 Aug 2026 12:01:37 +0200 Subject: [PATCH 019/102] Partially support cluster projectors tests --- src/algorithms/time_evolution/apply_mpo.jl | 2 +- .../time_evolution/simpleupdate3site.jl | 2 +- src/environments/suweight.jl | 2 +- test/amd/timeevol/cluster_projectors.jl | 258 ++++++++++++++++++ test/cuda/timeevol/cluster_projectors.jl | 258 ++++++++++++++++++ 5 files changed, 519 insertions(+), 3 deletions(-) create mode 100644 test/amd/timeevol/cluster_projectors.jl create mode 100644 test/cuda/timeevol/cluster_projectors.jl diff --git a/src/algorithms/time_evolution/apply_mpo.jl b/src/algorithms/time_evolution/apply_mpo.jl index 85c220133..75e0f304c 100644 --- a/src/algorithms/time_evolution/apply_mpo.jl +++ b/src/algorithms/time_evolution/apply_mpo.jl @@ -299,7 +299,7 @@ function _apply_gatempo!( fusers = map(Iterators.drop(Ms, 1), Iterators.drop(gs, 1)) do M, g V1, V2 = space(M, 1), space(g, 1) @assert !isdual(V1) && !isdual(V2) - return isomorphism(fuse(V1, V2) ← V1 ⊗ V2) + return isomorphism(storagetype(T1), fuse(V1, V2) ← V1 ⊗ V2) end #= gate on codomain of PEPS -3 -3 -3 diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 80947b876..da79055e2 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -51,7 +51,7 @@ function _su_iter!( _nn_bondrev(site1, site2) end for (wt, (bond, rev), flip) in zip(wts, bond_revs, flips) - wt_new = flip ? _fliptwist_s(wt) : wt + wt_new = flip ? _fliptwist_s!(wt) : wt wt_new = rev ? transpose(wt_new) : wt_new env[CartesianIndex(bond)] = normalize!(wt_new, Inf) end diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index 77c6f1567..c1363fe2a 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -336,7 +336,7 @@ which has the same real scalartype as ``wts`. """ function CTMRGEnv(wts::SUWeight) _, Nr, Nc = size(wts) - elt = scalartype(wts) + elt = storagetype(wts) V_env = oneunit(spacetype(wts)) edges = map(Iterators.product(1:4, 1:Nr, 1:Nc)) do (d, r, c) wt_idx = if d == NORTH 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/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 bf31e3af6e1e288270a30cf0c458fa997bda7865 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 4 Aug 2026 17:18:05 +0200 Subject: [PATCH 020/102] Fix dumb typo --- test/cuda/boundarymps/vumps.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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))) From 28fd1274bc0fbf83b804fb4966cfb064f7e30379 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 4 Aug 2026 22:04:34 +0200 Subject: [PATCH 021/102] Working tf_ising_finiteT --- src/environments/bp_environments.jl | 2 +- src/environments/ctmrg_environments.jl | 2 +- test/amd/timeevol/tf_ising_finiteT.jl | 88 ++++++++++++++++++++++++++ test/cuda/timeevol/tf_ising_finiteT.jl | 88 ++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 test/amd/timeevol/tf_ising_finiteT.jl create mode 100644 test/cuda/timeevol/tf_ising_finiteT.jl diff --git a/src/environments/bp_environments.jl b/src/environments/bp_environments.jl index def955d4e..fe15789ac 100644 --- a/src/environments/bp_environments.jl +++ b/src/environments/bp_environments.jl @@ -126,7 +126,7 @@ function BPEnv(network::Union{InfiniteSquareNetwork, InfinitePartitionFunction, return BPEnv(isomorphism, storagetype(network), network, args...; kwargs...) end function BPEnv(f, T, state::Union{InfinitePartitionFunction, InfinitePEPS, InfinitePEPO}, args...; kwargs...) - return BPEnv(f, T, InfiniteSquareNetwork(state), args...; kwargs...) + return BPEnv(f, similarstoragetype(eltype(state), eltype(T)), InfiniteSquareNetwork(state), args...; kwargs...) end Base.eltype(::Type{BPEnv{T}}) where {T} = T diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index dc942398d..9f9ca8d66 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -219,7 +219,7 @@ function CTMRGEnv(f, ::Type{T}, network::N, virtual_spaces...) where {T, N <: In 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)) - return CTMRGEnv(f, T, Ds_north, Ds_east, virtual_spaces...) + return CTMRGEnv(f, promote_storagetype(storagetype(network), T), Ds_north, Ds_east, virtual_spaces...) end function CTMRGEnv(network::InfiniteSquareNetwork{O}, virtual_spaces...) where {O} return CTMRGEnv(randn, storagetype(O), network, virtual_spaces...) 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/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 685734635b753382cdd8de32fe672d1b1b02ebce Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 09:35:42 +0200 Subject: [PATCH 022/102] Increase timeouts and add j1j2 test --- .buildkite/pipeline.yml | 8 +-- src/algorithms/time_evolution/time_evolve.jl | 2 +- src/algorithms/time_evolution/trotter_gate.jl | 4 +- test/amd/timeevol/j1j2_finiteT.jl | 62 +++++++++++++++++++ test/cuda/timeevol/j1j2_finiteT.jl | 62 +++++++++++++++++++ 5 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 test/amd/timeevol/j1j2_finiteT.jl create mode 100644 test/cuda/timeevol/j1j2_finiteT.jl diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 35f7547c7..d9c621389 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -14,7 +14,7 @@ steps: agents: queue: "cuda" if: build.message !~ /\[skip tests\]/ - timeout_in_minutes: 60 + timeout_in_minutes: 90 - label: "Julia LTS -- CUDA" plugins: @@ -28,7 +28,7 @@ steps: agents: queue: "cuda" if: build.message !~ /\[skip tests\]/ - timeout_in_minutes: 60 + timeout_in_minutes: 90 - label: "Julia v1 -- AMDGPU" plugins: @@ -42,7 +42,7 @@ steps: agents: queue: "rocm" if: build.message !~ /\[skip tests\]/ - timeout_in_minutes: 60 + timeout_in_minutes: 90 - label: "Julia LTS -- AMDGPU" plugins: @@ -56,4 +56,4 @@ steps: agents: queue: "rocm" if: build.message !~ /\[skip tests\]/ - timeout_in_minutes: 60 + timeout_in_minutes: 90 diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index c1dc32233..d6065d20a 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -69,7 +69,7 @@ function _timeevol_sanity_check( end function MPSKit.infinite_temperature_density_matrix(H::LocalOperator) - T = scalartype(H) + T = storagetype(H) A = map(physicalspace(H)) do Vp ψ = permute(TensorKit.id(T, Vp), (1, 2)) Vv = oneunit(Vp) # trivial (1D) virtual space diff --git a/src/algorithms/time_evolution/trotter_gate.jl b/src/algorithms/time_evolution/trotter_gate.jl index c4a52bfd5..7d20f40ab 100644 --- a/src/algorithms/time_evolution/trotter_gate.jl +++ b/src/algorithms/time_evolution/trotter_gate.jl @@ -157,7 +157,9 @@ function _trotterize_nnn2site!(gates::Vector, H::LocalOperator, dt::Number) term = permute(term, ((2, 1), (4, 3))) end gate = gate_to_mpo(exp(term * -dt / 2)) - b = TensorKit.BraidingTensor{T}(physicalspace(H, x2), left_virtualspace(gate[2])) + A = similarstoragetype(term, T) + S = spacetype(TensorKit.promote(physicalspace(H, x2), left_virtualspace(gate[2]))[1]) + b = TensorKit.BraidingTensor{T, S, A}(physicalspace(H, x2), left_virtualspace(gate[2])) insert!(gate, 2, TensorMap(b)) push!(gates, [x1, x2, x3] => gate) end 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/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 From d3b37854076291bf83d5c7a2a1009247e7400e2e Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 05:14:09 -0400 Subject: [PATCH 023/102] Fix CTMRG env init --- src/environments/ctmrg_environments.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 9f9ca8d66..101bd6805 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -219,7 +219,7 @@ function CTMRGEnv(f, ::Type{T}, network::N, virtual_spaces...) where {T, N <: In 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)) - return CTMRGEnv(f, promote_storagetype(storagetype(network), T), Ds_north, Ds_east, virtual_spaces...) + return CTMRGEnv(f, similarstoragetype(storagetype(network), eltype(T)), Ds_north, Ds_east, virtual_spaces...) end function CTMRGEnv(network::InfiniteSquareNetwork{O}, virtual_spaces...) where {O} return CTMRGEnv(randn, storagetype(O), network, virtual_spaces...) From 2b16ace57cef77019ad02d51c02d4977179259bf Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 07:13:47 -0400 Subject: [PATCH 024/102] More init fixes --- src/algorithms/ctmrg/initialization.jl | 2 +- src/environments/bp_environments.jl | 4 ++-- src/environments/product_state_environments.jl | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/algorithms/ctmrg/initialization.jl b/src/algorithms/ctmrg/initialization.jl index 9f179d940..c44f04826 100644 --- a/src/algorithms/ctmrg/initialization.jl +++ b/src/algorithms/ctmrg/initialization.jl @@ -86,5 +86,5 @@ function initialize_ctmrg_environment( elt::Type{<:Number}, A::Union{InfinitePEPS, InfinitePartitionFunction}, args...; kwargs... ) - return initialize_ctmrg_environment(similarstoragetype(eltype(A), elt), InfiniteSquareNetwork(A), args...; kwargs...) + return initialize_ctmrg_environment(similarstoragetype(storagetype(A), elt), InfiniteSquareNetwork(A), args...; kwargs...) end diff --git a/src/environments/bp_environments.jl b/src/environments/bp_environments.jl index fe15789ac..53785faf4 100644 --- a/src/environments/bp_environments.jl +++ b/src/environments/bp_environments.jl @@ -120,7 +120,7 @@ Construct a BP environment by specifying a corresponding [`InfiniteSquareNetwork function BPEnv(f, T, network::InfiniteSquareNetwork; posdef::Bool = true) Ds_north = _north_edge_physical_spaces(network) Ds_east = _east_edge_physical_spaces(network) - return BPEnv(f, T, Ds_north, Ds_east; posdef) + return BPEnv(f, similarstoragetype(storagetype(network), eltype(T)), Ds_north, Ds_east; posdef) end function BPEnv(network::Union{InfiniteSquareNetwork, InfinitePartitionFunction, InfinitePEPS, InfinitePEPO}, args...; kwargs...) return BPEnv(isomorphism, storagetype(network), network, args...; kwargs...) @@ -174,7 +174,7 @@ function CTMRGEnv(bp_env::BPEnv) return insertleftunit(insertleftunit(M), 1) end corners = map(CartesianIndices(edges)) do _ - return TensorKit.id(scalartype(bp_env), oneunit(spacetype(bp_env))) + return TensorKit.id(storagetype(bp_env), oneunit(spacetype(bp_env))) end return CTMRGEnv(corners, edges) end diff --git a/src/environments/product_state_environments.jl b/src/environments/product_state_environments.jl index 394bc5751..93a1577b9 100644 --- a/src/environments/product_state_environments.jl +++ b/src/environments/product_state_environments.jl @@ -82,13 +82,13 @@ Construct a product state environment by specifying a corresponding [`InfiniteSq function ProductStateEnv(f, T, network::InfiniteSquareNetwork) Ds_north = _north_edge_physical_spaces(network) Ds_east = _east_edge_physical_spaces(network) - return ProductStateEnv(f, T, Ds_north, Ds_east) + return ProductStateEnv(f, similarstoragetype(storagetype(network), eltype(T)), Ds_north, Ds_east) end function ProductStateEnv(network::Union{InfiniteSquareNetwork, InfinitePartitionFunction, InfinitePEPS}) - return ProductStateEnv(randn, scalartype(network), network) + return ProductStateEnv(randn, storagetype(network), network) end function ProductStateEnv(f, T, state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) - return ProductStateEnv(f, T, InfiniteSquareNetwork(state), args...) + return ProductStateEnv(f, similarstoragetype(eltype(state), eltype(T)), InfiniteSquareNetwork(state), args...) end Base.eltype(::Type{ProductStateEnv{T}}) where {T} = T From 10a90cba122ea822db1a60b7998578853ef551a1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 11:36:58 +0200 Subject: [PATCH 025/102] 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 d982416a7c2c9f566f3c86b7310799e6e14b0bef Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 12:05:16 +0200 Subject: [PATCH 026/102] Working ctmrg/partition_function (except C4v) --- test/amd/ctmrg/partition_function.jl | 152 ++++++++++++++++++++++++++ test/cuda/ctmrg/partition_function.jl | 152 ++++++++++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 test/amd/ctmrg/partition_function.jl create mode 100644 test/cuda/ctmrg/partition_function.jl 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/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 a56ce75b99a5084db6eadded952942da79971a9f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 12:21:17 +0200 Subject: [PATCH 027/102] Fix SUWeight --- src/environments/suweight.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index c1363fe2a..3a91e4098 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -71,10 +71,11 @@ function SUWeight( weights = map(Iterators.product(1:2, 1:Nr, 1:Nc)) do (d, r, c) V = (d == 1 ? Espaces[r, c] : Nspaces[r, c]) if TorA <: AbstractArray - diag = TorA(undef, reduceddim(V)) + realTorA = similarstoragetype(TorA, real(eltype(TorA))) + diag = realTorA(undef, reduceddim(V)) fill!(diag, 1) else - diag = ones(TorA, reduceddim(V)) + diag = ones(real(TorA), reduceddim(V)) end DiagonalTensorMap(diag, V) end @@ -148,6 +149,8 @@ TensorKit.spacetype(::Type{T}) where {E, T <: SUWeight{E}} = spacetype(E) 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) + ## Bipartite check function _is_bipartite(wts::SUWeight) (size(wts, 2) == size(wts, 3) == 2) || (return false) From 6ac0f2c084a587e77b9e5307cdd5e0024949ce3f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 13:15:21 +0200 Subject: [PATCH 028/102] Fix select algo --- src/algorithms/select_algorithm.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index feabc936b..dfb30792e 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 From acd5b65688acc453d804c193be20b1256cdb8d64 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 14:38:26 +0200 Subject: [PATCH 029/102] Gaugefix working? --- src/algorithms/ctmrg/c4v.jl | 19 +++++-- test/amd/ctmrg/gaugefix.jl | 99 +++++++++++++++++++++++++++++++++++++ test/cuda/ctmrg/gaugefix.jl | 99 +++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 test/amd/ctmrg/gaugefix.jl create mode 100644 test/cuda/ctmrg/gaugefix.jl diff --git a/src/algorithms/ctmrg/c4v.jl b/src/algorithms/ctmrg/c4v.jl index 79b118cad..b30c00c3e 100644 --- a/src/algorithms/ctmrg/c4v.jl +++ b/src/algorithms/ctmrg/c4v.jl @@ -230,7 +230,7 @@ function initialize_random_c4v_env(f, T, Vstate::VectorSpace, Venv::ElementarySp end """ - initialize_singlet_c4v_env([T=scalartype(state)], state::InfinitePEPS, Venv::ElementarySpace) + initialize_singlet_c4v_env([T=storagetype(state)], state::InfinitePEPS, Venv::ElementarySpace) Initialize a C₄ᵥ-symmetric `CTMRGEnv` with a singlet corner of dimension `dim(Venv)` and an identity edge from `id(T, Venv ⊗ Vpeps)`. @@ -240,11 +240,22 @@ 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(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) - corner₀ = DiagonalTensorMap(zeros(real(T), Venv ← Venv)) +function initialize_singlet_c4v_env(T::Type{<:Number}, Vpeps::ElementarySpace, Venv::ElementarySpace) + realT = real(T) + diag = zeros(realT, dim(Venv)) + corner₀ = DiagonalTensorMap(diag, Venv) corner₀.data[1] = one(real(T)) edge₀ = permute(id(T, Venv ⊗ Vpeps), ((1, 2, 4), (3,))) return CTMRGEnv(corner₀, edge₀) end +function initialize_singlet_c4v_env(T::Type{<:AbstractArray}, Vpeps::ElementarySpace, Venv::ElementarySpace) + realT = similarstoragetype(T, real(eltype(T))) + diag = realT(undef, dim(Venv)) + fill!(diag, 1) + corner₀ = DiagonalTensorMap(diag, Venv) + corner₀.data[2:end] .= zero(real(eltype(T))) + edge₀ = permute(id(T, Venv ⊗ Vpeps), ((1, 2, 4), (3,))) + return CTMRGEnv(corner₀, edge₀) +end 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/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 From ea491af4854d067003871580c1754bf920ae5190 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 15:53:05 +0200 Subject: [PATCH 030/102] 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 72b0d91e1c13075e50d31018f665a55e79da2945 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 17:39:13 +0200 Subject: [PATCH 031/102] Fixed iteration appears to be working --- src/algorithms/ctmrg/gaugefix.jl | 2 +- src/environments/ctmrg_environments.jl | 6 ++---- src/utility/eigh.jl | 4 ++-- src/utility/svd.jl | 3 ++- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 7fbd603d5..9c5c73d78 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -107,7 +107,7 @@ function compute_relative_phases(envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, # Random Hermitian MPS of same bond dimension # (make Hermitian such that T-M transfer matrix has real eigenvalues) - M = _project_hermitian(randn(scalartype(Tfinal), space(Tfinal))) + M = _project_hermitian(randn(storagetype(Tfinal), space(Tfinal))) # Find right fixed points of mixed transfer matrices eigsolve_alg = Lanczos() # real eigenvalues diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 101bd6805..40e3764bf 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -238,7 +238,7 @@ CTMRGEnv(env::CTMRGEnv) = CTMRGEnv(env.corners, env.edges) @non_differentiable CTMRGEnv(state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) -TensorKit.storagetype(::Type{CTMRGEnv{C, E}}) where {C, E} = storagetype(C) == storagetype(E) ? storagetype(C) : error("mismatched storage types for CTMRGEnvironment") +TensorKit.storagetype(::Type{CTMRGEnv{C, E}}) where {C, E} = storagetype(C) == storagetype(E) ? storagetype(C) : promote_type(storagetype(C), storagetype(E)) # Custom adjoint for CTMRGEnv constructor, needed for fixed-point differentiation function ChainRulesCore.rrule( @@ -264,9 +264,7 @@ function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnv, name::Symbol) 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 + return result, edge_pullback else # this should never happen because already errored in forwards pass throw(ArgumentError("No rrule for getproperty of $name")) end end diff --git a/src/utility/eigh.jl b/src/utility/eigh.jl index 0db3bb7ef..cb8c98621 100644 --- a/src/utility/eigh.jl +++ b/src/utility/eigh.jl @@ -224,6 +224,7 @@ function _compute_eighdata!( I = sectortype(f) dims = SectorDict{I, Int}() + Dtype = similarstoragetype(f, real(scalartype(f))) sectors = trunc isa NoTruncation ? blocksectors(f) : blocksectors(trunc.space) generator = Base.Iterators.map(sectors) do c b = block(f, c) @@ -251,13 +252,12 @@ function _compute_eighdata!( V = stack(view(lvecs, 1:howmany)) end end - # make it deterministic-ish MatrixAlgebraKit.gaugefix!(eigh_full!, V) resize!(D, howmany) dims[c] = length(D) - return c => (D, V) + return c => (Dtype(D), V) end eigdata = SectorDict(generator) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 5d96d58fe..7d6300b0a 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -239,6 +239,7 @@ function _compute_svddata!( I = sectortype(f) dims = SectorDict{I, Int}() + Stype = similarstoragetype(f, real(scalartype(f))) sectors = trunc isa NoTruncation ? blocksectors(f) : blocksectors(trunc.space) generator = Base.Iterators.map(sectors) do c b = block(f, c) @@ -273,7 +274,7 @@ function _compute_svddata!( resize!(S, howmany) dims[c] = length(S) - return c => (U, S, V) + return c => (U, Stype(S), V) end SVDdata = SectorDict(generator) From e1d5e251c978c611ee982d6b71f688449cc72187 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 17:39:51 +0200 Subject: [PATCH 032/102] Actually add the test files whoops --- 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 29be9a4465c3bef27c7181d77be8fcbbfe71a455 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 18:37:47 +0200 Subject: [PATCH 033/102] 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 b50560de530db2b0ae8245f084c439c63cad3260 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 18:50:31 +0200 Subject: [PATCH 034/102] 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 67a3084b6c63214cc945f3017e49095e944bb706 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 5 Aug 2026 20:10:56 +0200 Subject: [PATCH 035/102] Working CTMRG/pepo --- src/operators/infinitepepo.jl | 2 +- test/amd/ctmrg/pepo.jl | 148 ++++++++++++++++++++++++++++++++++ test/cuda/ctmrg/pepo.jl | 148 ++++++++++++++++++++++++++++++++++ 3 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 test/amd/ctmrg/pepo.jl create mode 100644 test/cuda/ctmrg/pepo.jl diff --git a/src/operators/infinitepepo.jl b/src/operators/infinitepepo.jl index 22695b2e6..65ec5194d 100644 --- a/src/operators/infinitepepo.jl +++ b/src/operators/infinitepepo.jl @@ -121,7 +121,7 @@ function initializePEPS( end Nspaces = repeat([vspace], size(T, 1), size(T, 2)) Espaces = repeat([vspace], size(T, 1), size(T, 2)) - return InfinitePEPS(Pspaces, Nspaces, Espaces) + return InfinitePEPS(randn, storagetype(T), Pspaces, Nspaces, Espaces) end ## Unit cell interface 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/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 From 71664e34ccb3a5692cc165a756d93cf9b6efbdbf Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 10:11:42 +0200 Subject: [PATCH 036/102] Formatter --- src/environments/ctmrg_environments.jl | 5 +++-- test/amd/bp/unitcell.jl | 14 ++++++++------ test/amd/ctmrg/partition_function.jl | 2 +- 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/unitcell.jl | 20 +++++++++++--------- test/cuda/timeevol/j1j2_finiteT.jl | 10 ++++++---- test/cuda/timeevol/sitedep_truncation.jl | 4 ++-- 11 files changed, 59 insertions(+), 46 deletions(-) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 40e3764bf..35f080fbe 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -238,7 +238,7 @@ CTMRGEnv(env::CTMRGEnv) = CTMRGEnv(env.corners, env.edges) @non_differentiable CTMRGEnv(state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) -TensorKit.storagetype(::Type{CTMRGEnv{C, E}}) where {C, E} = storagetype(C) == storagetype(E) ? storagetype(C) : promote_type(storagetype(C), storagetype(E)) +TensorKit.storagetype(::Type{CTMRGEnv{C, E}}) where {C, E} = storagetype(C) == storagetype(E) ? storagetype(C) : promote_type(storagetype(C), storagetype(E)) # Custom adjoint for CTMRGEnv constructor, needed for fixed-point differentiation function ChainRulesCore.rrule( @@ -264,7 +264,8 @@ function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnv, name::Symbol) 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 + return result, edge_pullback + else # this should never happen because already errored in forwards pass throw(ArgumentError("No rrule for getproperty of $name")) end end 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/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/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 b2a857179e4d830e152b9a41fd6779a3f6113ec5 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 10:25:20 +0200 Subject: [PATCH 037/102] Try simplifying the test script --- .buildkite/pipeline.yml | 56 ++++++++--------------------------------- 1 file changed, 11 insertions(+), 45 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index d9c621389..67ec614e5 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -2,58 +2,24 @@ env: SECRET_CODECOV_TOKEN: "MH6hHjQi7vG2V1Yfotv5/z5Dkx1k5SdyGYlGTFXiQr22XksJgsXaBuvFKUrjC7JwcpBsOVU8103LuMKl3m7VJ35WzHZrOssYycVbdGcb2kloc6xvUOsN2R5BrhCQ4Pii0l6ZeVRjCnZVkcmb0Rf4glGFyfibCrqniry8RLhblsuFKFsijRK4OxiWYEs1IvUulN+ER8tEsEtw4+ZqC5nbLGMSnUG/saPkDQOVIBscvikbKEnBcCXBheGPktF+Y/cy/1Xa+FiBPoZcypwTeAjKG1g0MqyHXjaYekb/7fekaj+hukGaeJSCXxY8KEb2IZCh+Y36Tp6y6qsIp/AdtEnCpQ==;U2FsdGVkX18WQxvGLspPwzC4aDe+U7TXU+itebTbgh8LUkE6GukxxReHYiDZ6IrBiVvSGTVJMquW0c8KsOI1pw==" steps: - - label: "Julia v1 -- CUDA" + - label: "Julia {{matrix.julia}} -- {{matrix.queue}}" plugins: - JuliaCI/julia#v1: - version: "1" + version: "{{matrix.julia}}" - JuliaCI/julia-test#v1: ~ - JuliaCI/julia-coverage#v1: dirs: - src - ext agents: - queue: "cuda" - if: build.message !~ /\[skip tests\]/ - timeout_in_minutes: 90 - - - label: "Julia LTS -- CUDA" - plugins: - - JuliaCI/julia#v1: - version: "1.10" # "lts" isn't valid - - JuliaCI/julia-test#v1: ~ - - JuliaCI/julia-coverage#v1: - dirs: - - src - - ext - agents: - queue: "cuda" - if: build.message !~ /\[skip tests\]/ - timeout_in_minutes: 90 - - - label: "Julia v1 -- AMDGPU" - plugins: - - JuliaCI/julia#v1: - version: "1" - - JuliaCI/julia-test#v1: ~ - - JuliaCI/julia-coverage#v1: - dirs: - - src - - ext - agents: - queue: "rocm" - if: build.message !~ /\[skip tests\]/ - timeout_in_minutes: 90 - - - label: "Julia LTS -- AMDGPU" - plugins: - - JuliaCI/julia#v1: - version: "1.10" # "lts" isn't valid - - JuliaCI/julia-test#v1: ~ - - JuliaCI/julia-coverage#v1: - dirs: - - src - - ext - agents: - queue: "rocm" + queue: "{{matrix.queue}}" if: build.message !~ /\[skip tests\]/ timeout_in_minutes: 90 + matrix: + setup: + julia: + - "1.10" + - "1.12" + queue: + - "cuda" + - "rocm" From b665ca542658e72ef1a47862ebf1a2239ad35bed Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 10:36:07 +0200 Subject: [PATCH 038/102] Try splitting groups --- .buildkite/pipeline.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 67ec614e5..46b677439 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -6,7 +6,8 @@ steps: plugins: - JuliaCI/julia#v1: version: "{{matrix.julia}}" - - JuliaCI/julia-test#v1: ~ + - JuliaCI/julia-test#v1: + test_args: "{{matrix.group}}" - JuliaCI/julia-coverage#v1: dirs: - src @@ -23,3 +24,10 @@ steps: queue: - "cuda" - "rocm" + group: + - "boundarymps" + - "bp" + - "ctmrg" + - "timeevol" + - "toolbox" + From 021b5f976d41866702d38bf19f3c71dade0377ae Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 10:36:57 +0200 Subject: [PATCH 039/102] Include in title --- .buildkite/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 46b677439..04fd05a74 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -2,7 +2,7 @@ env: SECRET_CODECOV_TOKEN: "MH6hHjQi7vG2V1Yfotv5/z5Dkx1k5SdyGYlGTFXiQr22XksJgsXaBuvFKUrjC7JwcpBsOVU8103LuMKl3m7VJ35WzHZrOssYycVbdGcb2kloc6xvUOsN2R5BrhCQ4Pii0l6ZeVRjCnZVkcmb0Rf4glGFyfibCrqniry8RLhblsuFKFsijRK4OxiWYEs1IvUulN+ER8tEsEtw4+ZqC5nbLGMSnUG/saPkDQOVIBscvikbKEnBcCXBheGPktF+Y/cy/1Xa+FiBPoZcypwTeAjKG1g0MqyHXjaYekb/7fekaj+hukGaeJSCXxY8KEb2IZCh+Y36Tp6y6qsIp/AdtEnCpQ==;U2FsdGVkX18WQxvGLspPwzC4aDe+U7TXU+itebTbgh8LUkE6GukxxReHYiDZ6IrBiVvSGTVJMquW0c8KsOI1pw==" steps: - - label: "Julia {{matrix.julia}} -- {{matrix.queue}}" + - label: "Julia {{matrix.julia}} -- Queue {{matrix.queue}} -- Group {{matrix.group}}" plugins: - JuliaCI/julia#v1: version: "{{matrix.julia}}" From 9fbd7f2b96fb5cc291fdf9bbaf9626866534fb8e Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 10:40:17 +0200 Subject: [PATCH 040/102] 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 04fd05a74..39fbdb3c5 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 d24483edce5ce3e7696da5f4870bcff9f0dd2161 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 11:01:06 +0200 Subject: [PATCH 041/102] Fix runtests.jl --- .buildkite/pipeline.yml | 3 +- test/cuda/compress/local.jl | 62 +++++++++++++++++++++++++++++++++++++ test/rocm/compress/local.jl | 62 +++++++++++++++++++++++++++++++++++++ test/runtests.jl | 4 +-- 4 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 test/cuda/compress/local.jl create mode 100644 test/rocm/compress/local.jl diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 39fbdb3c5..b0639b983 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -2,7 +2,7 @@ env: SECRET_CODECOV_TOKEN: "MH6hHjQi7vG2V1Yfotv5/z5Dkx1k5SdyGYlGTFXiQr22XksJgsXaBuvFKUrjC7JwcpBsOVU8103LuMKl3m7VJ35WzHZrOssYycVbdGcb2kloc6xvUOsN2R5BrhCQ4Pii0l6ZeVRjCnZVkcmb0Rf4glGFyfibCrqniry8RLhblsuFKFsijRK4OxiWYEs1IvUulN+ER8tEsEtw4+ZqC5nbLGMSnUG/saPkDQOVIBscvikbKEnBcCXBheGPktF+Y/cy/1Xa+FiBPoZcypwTeAjKG1g0MqyHXjaYekb/7fekaj+hukGaeJSCXxY8KEb2IZCh+Y36Tp6y6qsIp/AdtEnCpQ==;U2FsdGVkX18WQxvGLspPwzC4aDe+U7TXU+itebTbgh8LUkE6GukxxReHYiDZ6IrBiVvSGTVJMquW0c8KsOI1pw==" steps: - - label: "Julia {{matrix.julia}} -- Queue {{matrix.queue}} -- Group {{matrix.group}}" + - label: "Julia {{matrix.julia}} -- {{matrix.queue}} / {{matrix.group}}" plugins: - JuliaCI/julia#v1: version: "{{matrix.julia}}" @@ -27,6 +27,7 @@ steps: group: - "boundarymps" - "bp" + - "compress" - "ctmrg" - "timeevol" - "toolbox" 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 97db82c8e5635c4d3fb29d15af6891880f5d7952 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 11:52:45 +0200 Subject: [PATCH 042/102] Soothe VUMPS on 1.12 --- src/PEPSKit.jl | 4 +++- .../contractions/vumps_contractions.jl | 24 +++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 2d7ebb798..f6e561573 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -23,7 +23,9 @@ import TensorKit: storagetype using KrylovKit using KrylovKit: Lanczos, BlockLanczos -using TensorOperations, OptimKit +using TensorOperations +using TensorOperations: AbstractBackend, DefaultBackend, DefaultAllocator +using OptimKit using ChainRulesCore, Zygote using LoggingExtras import TupleTools diff --git a/src/algorithms/contractions/vumps_contractions.jl b/src/algorithms/contractions/vumps_contractions.jl index 44d10e498..4d58c12de 100644 --- a/src/algorithms/contractions/vumps_contractions.jl +++ b/src/algorithms/contractions/vumps_contractions.jl @@ -120,46 +120,46 @@ end const PEPS_C_Hamiltonian{S, N} = MPSKit.MPO_C_Hamiltonian{ <:GenericMPSTensor{S, N}, <:GenericMPSTensor{S, N}, } # this one is technically type-piracy -PEPS_C_Hamiltonian(GL, GR) = MPSKit.MPODerivativeOperator(GL, (), GR) +PEPS_C_Hamiltonian(GL, GR, backend::AbstractBackend = DefaultBackend(), allocator = DefaultAllocator()) = MPSKit.MPODerivativeOperator(GL, (), GR, backend, allocator) const PEPS_AC_Hamiltonian{S, N} = MPSKit.MPO_AC_Hamiltonian{ <:GenericMPSTensor{S, N}, <:PEPSSandwich, <:GenericMPSTensor{S, N}, } -PEPS_AC_Hamiltonian(GL, O, GR) = MPSKit.MPODerivativeOperator(GL, (O,), GR) +PEPS_AC_Hamiltonian(GL, O, GR, backend::AbstractBackend = DefaultBackend(), allocator = DefaultAllocator()) = MPSKit.MPODerivativeOperator(GL, (O,), GR, backend, allocator) const PEPS_AC2_Hamiltonian{S, N} = MPSKit.MPO_AC2_Hamiltonian{ <:GenericMPSTensor{S, N}, <:PEPSSandwich, <:PEPSSandwich, <:GenericMPSTensor{S, N}, } -PEPS_AC2_Hamiltonian(GL, O1, O2, GR) = MPSKit.MPODerivativeOperator(GL, (O1, O2), GR) +PEPS_AC2_Hamiltonian(GL, O1, O2, GR, backend::AbstractBackend = DefaultBackend(), allocator = DefaultAllocator()) = MPSKit.MPODerivativeOperator(GL, (O1, O2), GR, backend, allocator) # Constructors # -function MPSKit.C_hamiltonian(site::Int, below, ::InfiniteTransferMatrix, above, envs; kwargs...) +function MPSKit.C_hamiltonian(site::Int, below, ::InfiniteTransferMatrix, above, envs; backend::AbstractBackend = DefaultBackend(), allocator = DefaultAllocator(), kwargs...) GL = leftenv(envs, site + 1, below) GL = twistdual(GL, 1) GR = rightenv(envs, site, below) GR = twistdual(GR, numind(GR)) - return PEPS_C_Hamiltonian(GL, GR) + return PEPS_C_Hamiltonian(GL, GR, backend, allocator) end function MPSKit.AC_hamiltonian( - site::Int, below, operator::InfiniteTransferPEPS, above, envs; kwargs... + site::Int, below, operator::InfiniteTransferPEPS, above, envs; backend::AbstractBackend = DefaultBackend(), allocator = DefaultAllocator(), kwargs... ) GL = leftenv(envs, site, below) GL = twistdual(GL, 1) GR = rightenv(envs, site, below) GR = twistdual(GR, numind(GR)) - return PEPS_AC_Hamiltonian(GL, operator[site], GR) + return PEPS_AC_Hamiltonian(GL, operator[site], GR, backend, allocator) end function MPSKit.AC2_hamiltonian( - site::Int, below, operator::InfiniteTransferPEPS, above, envs; kwargs... + site::Int, below, operator::InfiniteTransferPEPS, above, envs; backend::AbstractBackend = DefaultBackend(), allocator = DefaultAllocator(), kwargs... ) GL = leftenv(envs, site, below) GL = twistdual(GL, 1) GR = rightenv(envs, site + 1, below) GR = twistdual(GR, numind(GR)) - return PEPS_AC2_Hamiltonian(GL, operator[site], operator[site + 1], GR) + return PEPS_AC2_Hamiltonian(GL, operator[site], operator[site + 1], GR, backend, allocator) end # Actions @@ -213,16 +213,16 @@ end const PEPO_AC_Hamiltonian{S, N, H} = MPSKit.MPO_AC_Hamiltonian{ <:GenericMPSTensor{S, N}, <:PEPOSandwich{H}, <:GenericMPSTensor{S, N}, } -PEPO_AC_Hamiltonian(GL, O, GR) = MPSKit.MPODerivativeOperator(GL, (O,), GR) +PEPO_AC_Hamiltonian(GL, O, GR, backend::AbstractBackend = DefaultBackend(), allocator = DefaultAllocator()) = MPSKit.MPODerivativeOperator(GL, (O,), GR, backend, allocator) function MPSKit.AC_hamiltonian( - site::Int, below, operator::InfiniteTransferPEPO, above, envs; kwargs... + site::Int, below, operator::InfiniteTransferPEPO, above, envs; backend::AbstractBackend = DefaultBackend(), allocator = DefaultAllocator(), kwargs... ) GL = leftenv(envs, site, below) GL = twistdual(GL, 1) GR = rightenv(envs, site, below) GR = twistdual(GR, numind(GR)) - return PEPO_AC_Hamiltonian(GL, operator[site], GR) + return PEPO_AC_Hamiltonian(GL, operator[site], GR, backend, allocator) end @generated function (h::PEPO_AC_Hamiltonian{S, N, H})(AC::GenericMPSTensor{S, N}) where {S, N, H} From b66e44b0a0ac34c807f720bb8827bb767fae0c20 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 11:40:22 +0200 Subject: [PATCH 043/102] Bondenv tests --- .buildkite/pipeline.yml | 1 + 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 ++++++++++++++++++++++++ 7 files changed, 333 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/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index b0639b983..a2c04d751 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -26,6 +26,7 @@ steps: - "rocm" group: - "boundarymps" + - "bondenv" - "bp" - "compress" - "ctmrg" 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 2cabe919a7d4e85b14c86f0fafc3765d30598ca7 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 15:15:30 +0200 Subject: [PATCH 044/102] 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 ea2ff485c..b825b2f80 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -110,7 +110,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 61cb698fc38b79c3de883b86fd6323638aea4e72 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sun, 9 Aug 2026 21:08:59 +0200 Subject: [PATCH 045/102] BP-Gaugefix test for CUDA --- src/algorithms/time_evolution/gaugefix_su.jl | 4 +- test/cuda/bp/gaugefix.jl | 92 ++++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 test/cuda/bp/gaugefix.jl diff --git a/src/algorithms/time_evolution/gaugefix_su.jl b/src/algorithms/time_evolution/gaugefix_su.jl index 13a2782d6..a373d773d 100644 --- a/src/algorithms/time_evolution/gaugefix_su.jl +++ b/src/algorithms/time_evolution/gaugefix_su.jl @@ -17,7 +17,7 @@ $(TYPEDFIELDS) maxiter::Int = 100 end -function _trivial_gates(elt::Type{<:Number}, lattice::Matrix{S}) where {S <: ElementarySpace} +function _trivial_gates(elt::Type, lattice::Matrix{S}) where {S <: ElementarySpace} Nr, Nc = size(lattice) gates = map(Iterators.product(1:2, 1:Nc, 1:Nr)) do (d, c, r) site1 = CartesianIndex(r, c) @@ -38,7 +38,7 @@ Fix the gauge of `psi` using trivial simple update. """ function gauge_fix(psi::InfiniteState, alg::SUGauge) time0 = time() - gates = _trivial_gates(scalartype(psi), physicalspace(psi)) + gates = _trivial_gates(storagetype(psi), physicalspace(psi)) trunc = _get_fixedspacetrunc(psi) su_alg = SimpleUpdate(; trunc, bipartite = _is_bipartite(psi)) wts0 = SUWeight(psi) 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 b6dbd39cb7787dc4bd88301ba3fbd6857762cf27 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 11 Aug 2026 16:41:30 +0200 Subject: [PATCH 046/102] More working tests with downstream fixes --- .buildkite/pipeline.yml | 1 + Project.toml | 12 ++-- 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 +++++++++++++++++++++++++ 8 files changed, 407 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/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index a2c04d751..08a7aa6ba 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -32,4 +32,5 @@ steps: - "ctmrg" - "timeevol" - "toolbox" + - "utility" diff --git a/Project.toml b/Project.toml index 3651295ad..2a947e950 100644 --- a/Project.toml +++ b/Project.toml @@ -23,6 +23,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,17 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [weakdeps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +[sources] +MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} +MatrixAlgebraKit = {rev = "main", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} +TensorOperations = {rev = "ksh/diag", url = "https://github.com/QuantumKitHub/TensorOperations.jl"} + [extensions] PEPSKitAdaptExt = "Adapt" [compat] -Adapt = "4" Accessors = "0.1" +Adapt = "4" ChainRulesCore = "1.0" Compat = "3.46, 4.2" DocStringExtensions = "0.9.3" @@ -61,6 +68,3 @@ TupleTools = "1.6.0" VectorInterface = "0.4, 0.5, 0.6" 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 33c92a98fba30e44c7a378d9fa8d8ec730630e81 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 11 Aug 2026 16:43:20 +0200 Subject: [PATCH 047/102] Bad branch --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 2a947e950..f0ccf7758 100644 --- a/Project.toml +++ b/Project.toml @@ -38,7 +38,7 @@ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" [sources] MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} MatrixAlgebraKit = {rev = "main", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} -TensorOperations = {rev = "ksh/diag", url = "https://github.com/QuantumKitHub/TensorOperations.jl"} +TensorOperations = {rev = "main", url = "https://github.com/QuantumKitHub/TensorOperations.jl"} [extensions] PEPSKitAdaptExt = "Adapt" From 5e9139e7ef0b0ef0102a5f4cad192e20fb7f85ab Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 12 Aug 2026 06:30:44 -0400 Subject: [PATCH 048/102] 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 ee60dbf4afc943cc0f30aec5e3ed4bb2d28029d5 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 12 Aug 2026 17:23:00 -0400 Subject: [PATCH 049/102] 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 cff25a22b..503e626b4 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -7,6 +7,7 @@ AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" 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" @@ -26,6 +27,7 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [sources] PEPSKit = {path = ".."} +GPUArrays = {url = "https://github.com/JuliaGPU/GPUArrays.jl", rev = "ksh/more_diag"} [compat] Adapt = "4" 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 affe1a7f9422a042eb919771c5ba98e87747fe5d Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 13 Aug 2026 18:49:58 +0200 Subject: [PATCH 050/102] Make the gaugefix loop GPU-friendly --- src/algorithms/contractions/bondenv/gaugefix.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/algorithms/contractions/bondenv/gaugefix.jl b/src/algorithms/contractions/bondenv/gaugefix.jl index 3e6643325..19c982b35 100644 --- a/src/algorithms/contractions/bondenv/gaugefix.jl +++ b/src/algorithms/contractions/bondenv/gaugefix.jl @@ -21,9 +21,9 @@ function positive_approx(benv::AbstractTensorMap{T, S, N, N}) where {T, S, N} # If `benv` is negative (e.g. obtained approximately from CTMRG), # we can multiply it by (-1). data = D.data - @inbounds for i in eachindex(data) - d = (sgn == -1) ? -data[i] : data[i] - data[i] = (d > 0) ? sqrt(d) : zero(d) + map!(data, data) do d + d2 = (sgn < 0) ? -d : d + return (d2 > 0) ? sqrt(d2) : zero(d2) end Z = D * U' return Z From 43d749d4d6040d222b979e750c4ba63bebfaabe6 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 14 Aug 2026 01:49:34 -0400 Subject: [PATCH 051/102] Branch --- test/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index 503e626b4..fe5dad793 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -27,7 +27,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] Adapt = "4" From 8121dd6fab467f2d5eba8dadd90ddb82179eba55 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 14 Aug 2026 09:32:15 -0400 Subject: [PATCH 052/102] 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 9f20acb37e7ae1d3178b67affc1df2399a5e0fe1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 14 Aug 2026 12:17:58 +0200 Subject: [PATCH 053/102] 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 1261547ce4b4cac25c4e2621afceadffd534ff41 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 14 Aug 2026 15:00:39 +0200 Subject: [PATCH 054/102] GPUified SVD and passing tests --- src/utility/svd.jl | 72 ++++++++---- test/cuda/utility/svd_wrapper.jl | 189 +++++++++++++++++++++++++++++++ test/rocm/utility/svd_wrapper.jl | 189 +++++++++++++++++++++++++++++++ 3 files changed, 428 insertions(+), 22 deletions(-) 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 7d6300b0a..9dd630cd8 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -206,7 +206,7 @@ end function MatrixAlgebraKit.svd_trunc!(f, alg::TruncatedAlgorithm{<:IterSVD}) U, S, Vᴴ = svd_trunc_no_error!(f, alg) truncation_error = - (trunc isa NoTruncation || isempty(blocksectors(f))) ? abs(zero(scalartype(f))) : norm(U * S * Vᴴ - f) + (alg.trunc isa NoTruncation || isempty(blocksectors(f))) ? abs(zero(scalartype(f))) : norm(U * S * Vᴴ - f) return U, S, Vᴴ, truncation_error end @@ -334,7 +334,7 @@ function ChainRulesCore.rrule( function svd_trunc!_full_pullback(ΔUSV′) ΔUSV = unthunk.(ΔUSV′) Δt = svd_pullback!( - zeros(scalartype(t), space(t)), t, (U, S, V⁺), ΔUSV, inds; + zeros(storagetype(t), space(t)), t, (U, S, V⁺), ΔUSV, inds; gauge_atol = gtol(ΔUSV), degeneracy_atol = alg.rrule_alg.degeneracy_atol, ) return NoTangent(), Δt, NoTangent() @@ -359,7 +359,7 @@ function ChainRulesCore.rrule( function svd_trunc!_trunc_pullback(ΔUSV′) ΔUSV = unthunk.(ΔUSV′) Δf = svd_trunc_pullback!( - zeros(scalartype(t), space(t)), t, (U, S, V⁺), ΔUSV; + zeros(storagetype(t), space(t)), t, (U, S, V⁺), ΔUSV; gauge_atol = gtol(ΔUSV), degeneracy_atol = alg.rrule_alg.degeneracy_atol, ) return NoTangent(), Δf, NoTangent() @@ -384,7 +384,7 @@ function ChainRulesCore.rrule( function svd_trunc!_trunc_pullback(ΔUSV′) ΔUSV = unthunk.(ΔUSV′) Δf = svd_trunc_pullback!( - zeros(scalartype(t), space(t)), t, (U, S, V⁺), ΔUSV; + zeros(storagetype(t), space(t)), t, (U, S, V⁺), ΔUSV; gauge_atol = gtol(ΔUSV), degeneracy_atol = alg.rrule_alg.degeneracy_atol, ) return NoTangent(), Δf, NoTangent() @@ -396,6 +396,20 @@ function ChainRulesCore.rrule( return (U, S, V⁺), svd_trunc!_trunc_pullback end +# `block(::AdjointTensorMap, c)` hands back a `LinearAlgebra.Adjoint`, and GPU array +# packages only recognize a single layer of array wrappers (see the `WrappedArray` union in +# Adapt). Slicing a doubly-wrapped array -- as `eachcol(V')` does -- therefore escapes the +# device fast paths and falls back to scalar indexing on the host, so materialize the +# wrapper before slicing. +_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, keeping the data on its original device. +function _column_vectors(::Type{A}, m::AbstractMatrix) where {A} + m̃ = _materialize_block(m) + return A[m̃[:, j] for j in axes(m̃, 2)] +end + # KrylovKit rrule compatible with TensorMaps & function handles function ChainRulesCore.rrule( ::typeof(svd_trunc!), @@ -417,12 +431,15 @@ function ChainRulesCore.rrule( for (c, b) in blocks(Δf) Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) ΔUc, ΔSc, ΔVc = block(ΔU, c), block(ΔS, c), block(ΔV, c) - Sdc = view(Sc, diagind(Sc)) - ΔSdc = ΔSc isa AbstractZero ? ΔSc : view(ΔSc, diagind(ΔSc)) + # `compute_svdsolve_pullback_data` reads the singular values element-wise and + # combines them with dense `n_vals × n_vals` host matrices, so keep these on + # the CPU; the bulk data (`lvecs`, `rvecs`, `block(f, c)`) stays on device. + Sdc = collect(diagview(Sc)) + ΔSdc = ΔSc isa AbstractZero ? zero(Sdc) : collect(diagview(ΔSc)) n_vals = length(Sdc) - lvecs = Vector{Vector{scalartype(f)}}(eachcol(Uc)) - rvecs = Vector{Vector{scalartype(f)}}(eachcol(Vc')) + lvecs = _column_vectors(storagetype(f), Uc) + rvecs = _column_vectors(storagetype(f), Vc') # Dummy objects only used for warnings minimal_info = KrylovKit.ConvergenceInfo(n_vals, nothing, nothing, -1, -1) # Only num. converged is used @@ -432,12 +449,12 @@ function ChainRulesCore.rrule( Δlvecs = fill(ZeroTangent(), n_vals) Δrvecs = fill(ZeroTangent(), n_vals) else - Δlvecs = Vector{Vector{scalartype(f)}}(eachcol(ΔUc)) - Δrvecs = Vector{Vector{scalartype(f)}}(eachcol(ΔVc')) + Δlvecs = _column_vectors(storagetype(f), ΔUc) + Δrvecs = _column_vectors(storagetype(f), ΔVc') end xs, ys = KrylovKitCRCExt.compute_svdsolve_pullback_data( - ΔSc isa AbstractZero ? fill(zero(Sc[1]), n_vals) : ΔSdc, + ΔSdc, Δlvecs, Δrvecs, Sdc, @@ -449,10 +466,14 @@ function ChainRulesCore.rrule( minimal_alg, rrule_alg, ) + # `construct∂f_svd` hands back an `InplaceableThunk`; copying from it directly + # would fall back to iterating it element-wise, so unthunk it first. copyto!( b, - KrylovKitCRCExt.construct∂f_svd( - HasReverseMode(), block(f, c), lvecs, rvecs, xs, ys + unthunk( + KrylovKitCRCExt.construct∂f_svd( + HasReverseMode(), block(f, c), lvecs, rvecs, xs, ys + ) ), ) end @@ -486,12 +507,15 @@ function ChainRulesCore.rrule( for (c, b) in blocks(Δf) Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) ΔUc, ΔSc, ΔVc = block(ΔU, c), block(ΔS, c), block(ΔV, c) - Sdc = view(Sc, diagind(Sc)) - ΔSdc = ΔSc isa AbstractZero ? ΔSc : view(ΔSc, diagind(ΔSc)) + # `compute_svdsolve_pullback_data` reads the singular values element-wise and + # combines them with dense `n_vals × n_vals` host matrices, so keep these on + # the CPU; the bulk data (`lvecs`, `rvecs`, `block(f, c)`) stays on device. + Sdc = collect(diagview(Sc)) + ΔSdc = ΔSc isa AbstractZero ? zero(Sdc) : collect(diagview(ΔSc)) n_vals = length(Sdc) - lvecs = Vector{Vector{scalartype(f)}}(eachcol(Uc)) - rvecs = Vector{Vector{scalartype(f)}}(eachcol(Vc')) + lvecs = _column_vectors(storagetype(f), Uc) + rvecs = _column_vectors(storagetype(f), Vc') # Dummy objects only used for warnings minimal_info = KrylovKit.ConvergenceInfo(n_vals, nothing, nothing, -1, -1) # Only num. converged is used @@ -501,12 +525,12 @@ function ChainRulesCore.rrule( Δlvecs = fill(ZeroTangent(), n_vals) Δrvecs = fill(ZeroTangent(), n_vals) else - Δlvecs = Vector{Vector{scalartype(f)}}(eachcol(ΔUc)) - Δrvecs = Vector{Vector{scalartype(f)}}(eachcol(ΔVc')) + Δlvecs = _column_vectors(storagetype(f), ΔUc) + Δrvecs = _column_vectors(storagetype(f), ΔVc') end xs, ys = KrylovKitCRCExt.compute_svdsolve_pullback_data( - ΔSc isa AbstractZero ? fill(zero(Sc[1]), n_vals) : ΔSdc, + ΔSdc, Δlvecs, Δrvecs, Sdc, @@ -518,10 +542,14 @@ function ChainRulesCore.rrule( minimal_alg, rrule_alg, ) + # `construct∂f_svd` hands back an `InplaceableThunk`; copying from it directly + # would fall back to iterating it element-wise, so unthunk it first. copyto!( b, - KrylovKitCRCExt.construct∂f_svd( - HasReverseMode(), block(f, c), lvecs, rvecs, xs, ys + unthunk( + KrylovKitCRCExt.construct∂f_svd( + HasReverseMode(), block(f, c), lvecs, rvecs, xs, ys + ) ), ) end 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 72609633b2f7e91c56520124d970f614eb337007 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 14 Aug 2026 18:27:47 +0200 Subject: [PATCH 055/102] Working eigh --- src/utility/eigh.jl | 8 +- test/cuda/utility/eigh_wrapper.jl | 147 ++++++++++++++++++++++++++++++ test/rocm/utility/eigh_wrapper.jl | 147 ++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 test/cuda/utility/eigh_wrapper.jl create mode 100644 test/rocm/utility/eigh_wrapper.jl diff --git a/src/utility/eigh.jl b/src/utility/eigh.jl index cb8c98621..e4eba3a61 100644 --- a/src/utility/eigh.jl +++ b/src/utility/eigh.jl @@ -234,7 +234,7 @@ function _compute_eighdata!( D, V = eigh_full!(b) lm_ordering = sortperm(abs.(D.diag); rev = true) # order values and vectors consistently with eigsolve D = D.diag[lm_ordering] # extracts diagonal as Vector instead of Diagonal to make compatible with D of svdsolve - V = stack(eachcol(V)[lm_ordering])[:, 1:howmany] + V = V[:, view(lm_ordering, 1:howmany)] else x₀ = alg.start_vector(b) eig_alg = alg.alg @@ -247,7 +247,7 @@ function _compute_eighdata!( D, V = eigh_full!(b) lm_ordering = sortperm(abs.(D.diag); rev = true) D = D.diag[lm_ordering] - V = stack(eachcol(V)[lm_ordering])[:, 1:howmany] + V = V[:, view(lm_ordering, 1:howmany)] else # Slice in case more values were converged than requested V = stack(view(lvecs, 1:howmany)) end @@ -314,7 +314,7 @@ function ChainRulesCore.rrule( function eigh_trunc!_full_pullback(ΔDV) Δt = eigh_pullback!( - zeros(scalartype(t), space(t)), t, (D, V), ΔDV, inds; + zeros(storagetype(t), space(t)), t, (D, V), ΔDV, inds; gauge_atol = gtol(ΔDV), degeneracy_atol = alg.rrule_alg.degeneracy_atol, ) return NoTangent(), Δt, NoTangent() @@ -338,7 +338,7 @@ function ChainRulesCore.rrule( function eigh_trunc!_trunc_pullback(ΔDV) Δf = eigh_trunc_pullback!( - zeros(scalartype(t), space(t)), t, (D, V), ΔDV; + zeros(storagetype(t), space(t)), t, (D, V), ΔDV; gauge_atol = gtol(ΔDV), degeneracy_atol = alg.rrule_alg.degeneracy_atol, ) return NoTangent(), Δf, NoTangent() 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 678ef000a1388f4ac2a16fa2eaa092d5f9e83113 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 15 Aug 2026 11:18:29 +0200 Subject: [PATCH 056/102] Switch to CUDACore and more tests working --- test/Project.toml | 22 ++++++++++++ test/cuda/utility/retractions.jl | 34 ++++++++++++++++++ test/cuda/utility/symmetrization.jl | 53 +++++++++++++++++++++++++++++ test/rocm/utility/retractions.jl | 34 ++++++++++++++++++ test/rocm/utility/symmetrization.jl | 53 +++++++++++++++++++++++++++++ test/runtests.jl | 2 +- 6 files changed, 197 insertions(+), 1 deletion(-) 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/test/Project.toml b/test/Project.toml index fe5dad793..47135655c 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -7,12 +7,16 @@ AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" 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" MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" MPSKitModels = "ca635005-6f8c-4cd1-b51d-8491250ef2ab" MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" +NVML = "611af6d1-644e-4c5d-bd58-854d7d1254b9" OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" PEPSKit = "52969e89-939e-4361-9b68-9bc7cde4bdeb" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" @@ -24,16 +28,34 @@ 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] Adapt = "4" AMDGPU = "2" ChainRulesTestUtils = "1.13" CUDA = "6" +CUDACore = "6" +CUDATools = "6" ParallelTestRunner = "2.6.0" QuadGK = "2.11.1" Test = "1" 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/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 35b2996c01abdfc7cbb8933d2e986ad13c600872 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 15 Aug 2026 15:23:59 -0400 Subject: [PATCH 057/102] CUSOLVER doesn't have a QRIteration heev --- test/cuda/utility/eigh_wrapper.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cuda/utility/eigh_wrapper.jl b/test/cuda/utility/eigh_wrapper.jl index 9984a2bd3..12d604586 100644 --- a/test/cuda/utility/eigh_wrapper.jl +++ b/test/cuda/utility/eigh_wrapper.jl @@ -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 From c9cb1ad189a8184213a0ef12df665b8a1df19166 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 17 Aug 2026 09:20:39 -0400 Subject: [PATCH 058/102] Point at branch for now --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index f0ccf7758..b745c0383 100644 --- a/Project.toml +++ b/Project.toml @@ -37,7 +37,7 @@ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" [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] From 7e31399761e11475aba82a567147cbfb5a329b58 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 15 Aug 2026 11:18:29 +0200 Subject: [PATCH 059/102] Switch to CUDACore and more tests working --- 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 | 2 +- test/cuda/utility/svd_wrapper.jl | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) 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 12d604586..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 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 From 6283e3c3fc1fc577f71322b4273e1a72233df939 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 15 Aug 2026 21:21:09 +0200 Subject: [PATCH 060/102] Gradient tests --- .buildkite/pipeline.yml | 1 + 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 +++++++++++++++++++++ 5 files changed, 613 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/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 08a7aa6ba..e8f443ba0 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -30,6 +30,7 @@ steps: - "bp" - "compress" - "ctmrg" + - "gradients" - "timeevol" - "toolbox" - "utility" 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 af651ee97e37ed13d0fdc3a0a3a43a7e169d9511 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 18 Aug 2026 12:44:33 +0200 Subject: [PATCH 061/102] Fix for c4v --- src/utility/qr.jl | 2 +- test/Project.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utility/qr.jl b/src/utility/qr.jl index a512821c8..bb67b43f8 100644 --- a/src/utility/qr.jl +++ b/src/utility/qr.jl @@ -96,7 +96,7 @@ function ChainRulesCore.rrule( gtol = _get_pullback_gauge_tol(alg.rrule_alg.verbosity) function left_orth!_pullback(ΔQR) - Δt = zeros(scalartype(t), space(t)) + Δt = zeros(storagetype(t), space(t)) MatrixAlgebraKit.qr_pullback!(Δt, t, QR, unthunk.(ΔQR); gauge_atol = gtol(ΔQR)) return NoTangent(), Δt, NoTangent() end diff --git a/test/Project.toml b/test/Project.toml index 47135655c..6f0dbbc2a 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -37,7 +37,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 f4e1540cdf8a790dbe11016a25dd9df0dc670c61 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 09:01:49 +0200 Subject: [PATCH 062/102] 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 b745c0383..48b801afa 100644 --- a/Project.toml +++ b/Project.toml @@ -39,6 +39,7 @@ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" 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 7aa97a1dd5305323df790bcb7dc6a1cf5878112d Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 12:26:15 +0200 Subject: [PATCH 063/102] Use alloc_caches from GPUArrays --- Project.toml | 3 ++ ext/PEPSKitGPUArraysExt.jl | 36 +++++++++++++++++ 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, 87 insertions(+), 12 deletions(-) create mode 100644 ext/PEPSKitGPUArraysExt.jl create mode 100644 src/utility/alloc_cache.jl diff --git a/Project.toml b/Project.toml index 48b801afa..ceb054cc5 100644 --- a/Project.toml +++ b/Project.toml @@ -34,6 +34,7 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [weakdeps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" [sources] MPSKit = {rev = "main", url = "https://github.com/QuantumKitHub/MPSKit.jl"} @@ -43,10 +44,12 @@ TensorKit = {rev = "ksh/add_transform", url = "https://github.com/QuantumKitHub/ [extensions] PEPSKitAdaptExt = "Adapt" +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 new file mode 100644 index 000000000..0ee19bc48 --- /dev/null +++ b/ext/PEPSKitGPUArraysExt.jl @@ -0,0 +1,36 @@ +module PEPSKitGPUArraysExt + +using GPUArrays +using GPUArrays: AnyGPUArray, AllocCache +using PEPSKit + +# 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 f6e561573..f3a5c08b2 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -57,6 +57,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 5f64ed0b7..57c7e97b3 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 @infov 1 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 7556076f6..dbdf0aa90 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -122,7 +122,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 cd9516239..adaa7f51d 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -158,7 +158,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 6f0dbbc2a..2a77f4eb6 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -38,16 +38,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] Adapt = "4" From 987539c902710bcd23cff2f9910c0648a2982ce8 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 15:53:16 +0200 Subject: [PATCH 064/102] 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 17c22c72d..088ca1c54 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 650af378b5fac2198f9bfd0e688a5911cd408b4f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 16:05:39 +0200 Subject: [PATCH 065/102] 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 179051558b2c266079cfa1671132c29e7ec03985 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 19 Aug 2026 22:12:30 +0200 Subject: [PATCH 066/102] 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 0ee19bc48..945c43956 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -4,22 +4,23 @@ using GPUArrays using GPUArrays: AnyGPUArray, AllocCache using PEPSKit -# 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 dbdf0aa90..8f90f41b4 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -122,7 +122,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 f7d4b820939ecfb9b3ec95512fa481d7e9088b6f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 07:05:10 +0200 Subject: [PATCH 067/102] 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 2a77f4eb6..890f3b31a 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -37,7 +37,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] Adapt = "4" From 05457d7eafa17f8c6a29791d3982e172b2889a6a Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 02:40:20 -0400 Subject: [PATCH 068/102] 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 890f3b31a..d4d8e0ec4 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -7,7 +7,6 @@ AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" 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" @@ -44,7 +43,6 @@ Adapt = "4" AMDGPU = "2" ChainRulesTestUtils = "1.13" CUDA = "6" -CUDACore = "6" CUDATools = "6" ParallelTestRunner = "2.6.0" QuadGK = "2.11.1" 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 b14704822344f25b690e47258f3ee33dcdd2506e Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 06:10:59 -0400 Subject: [PATCH 069/102] 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 945c43956..ae31d6f02 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -24,6 +24,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 8f90f41b4..03b461802 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -138,6 +138,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 adaa7f51d..86f121acb 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -158,9 +158,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 7043ba66ef855b9969c0744c8d636ce4a1cba64c Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 12:18:20 +0200 Subject: [PATCH 070/102] 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 655042948f86efd2904d00bc1e3e6940ae7e9e33 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 09:20:11 -0400 Subject: [PATCH 071/102] Fixes for holding cache memory for too long --- ext/PEPSKitGPUArraysExt.jl | 14 +++++++++ src/algorithms/ctmrg/ctmrg.jl | 19 ++++++++++++ src/utility/alloc_cache.jl | 54 ++++++++++++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index ae31d6f02..d65f90507 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -26,6 +26,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 03b461802..fa72fd16d 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: @@ -111,6 +117,11 @@ function leading_boundary( env₀::CTMRGEnv, 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 LoggingExtras.withlevel(; alg.verbosity) do env = deepcopy(env₀) @@ -139,6 +150,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)) From 70a950155e6b47a97f5a1a9b869843d34676dd86 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 20 Aug 2026 09:25:04 -0400 Subject: [PATCH 072/102] Fix sources AGAIN --- Project.toml | 2 +- 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 + 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ceb054cc5..0fac078c7 100644 --- a/Project.toml +++ b/Project.toml @@ -38,7 +38,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/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 b1674035b76fa09496a8ea1fd3b47f43f4e4dad2 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 21 Aug 2026 03:47:47 -0400 Subject: [PATCH 073/102] 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 e347f1645b9f4e16e639aeb3743e7c0b32cf1b3a Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Fri, 21 Aug 2026 08:44:07 +0200 Subject: [PATCH 074/102] 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 86f121acb..046cce397 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -155,6 +155,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 @@ -163,6 +186,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 43b6c589ec418add1bd76d00b0442c68783aca94 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 22 Aug 2026 21:21:14 +0200 Subject: [PATCH 075/102] Batched SVD support and more caching improvements --- Project.toml | 2 +- ext/PEPSKitGPUArraysExt.jl | 124 ++++++++++++++++++++++++++++++++++ src/algorithms/ctmrg/ctmrg.jl | 24 ++++++- src/utility/alloc_cache.jl | 26 ++++--- 4 files changed, 163 insertions(+), 13 deletions(-) diff --git a/Project.toml b/Project.toml index 0fac078c7..4c109d55f 100644 --- a/Project.toml +++ b/Project.toml @@ -40,7 +40,7 @@ GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" 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"} -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 d65f90507..ce1bcccfe 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -3,6 +3,10 @@ module PEPSKitGPUArraysExt using GPUArrays using GPUArrays: AnyGPUArray, AllocCache using PEPSKit +using TensorKit +using TensorKit: MatrixAlgebraKit as MAK + +const BATCH_THRESHOLD = 4 # 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. @@ -50,4 +54,124 @@ function PEPSKit.free_alloc_caches!(::Type{<:AnyGPUArray}) return nothing end + +const Factorizations = TensorKit.Factorizations + +""" + _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 + b = 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 fa72fd16d..4d452e970 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -120,7 +120,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 LoggingExtras.withlevel(; alg.verbosity) do @@ -156,7 +156,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, @@ -238,9 +238,27 @@ Spectra of the tensors that [`convergence_tensors`](@ref) selects. """ function convergence_spectra(env::CTMRGEnv, alg) corners, edges = convergence_tensors(env, alg) - return map(C -> corner_spectrum(C, alg), corners), map(T -> edge_spectrum(T, alg), edges) + return corner_spectra(corners, alg), edge_spectra(edges, alg) end +""" + corner_spectra(Cs, alg) + edge_spectra(Ts, alg) + +Spectra of a whole collection of corners or edges. + +Separate from [`corner_spectrum`](@ref) so that a backend can decompose the entire +collection in one go rather than one tensor at a time. That matters on GPU: each tensor +here carries only a handful of sector blocks, so decomposing them individually is dominated +by per-call overhead, while a corner or edge array holds tens of tensors whose blocks share +sizes and can be batched. + +The defaults just map [`corner_spectrum`](@ref) / [`edge_spectrum`](@ref) over the +collection, so any algorithm that overrides those keeps its behaviour. +""" +corner_spectra(Cs, alg) = map(C -> corner_spectrum(C, alg), Cs) +edge_spectra(Ts, alg) = map(T -> edge_spectrum(T, alg), Ts) + """ corner_spectrum(C, alg) edge_spectrum(T, alg) 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) From 237c2682bfa03023909d70deca1f814e61e8f437 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sun, 23 Aug 2026 14:43:11 -0400 Subject: [PATCH 076/102] More batching --- Project.toml | 2 +- ext/PEPSKitGPUArraysExt.jl | 126 ++++++++++++++++++++- src/algorithms/time_evolution/apply_mpo.jl | 33 +++++- src/utility/util.jl | 23 +++- 4 files changed, 176 insertions(+), 8 deletions(-) diff --git a/Project.toml b/Project.toml index 4c109d55f..467850ff7 100644 --- a/Project.toml +++ b/Project.toml @@ -38,7 +38,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/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"} diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index ce1bcccfe..952e272f5 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -28,6 +28,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) @@ -57,6 +68,115 @@ 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 @@ -167,7 +287,11 @@ function _batched_spectra(ts::AbstractArray{T}) where {T <: AbstractTensorMap} _batch_svd_vals!(ts, Ss, small, padded, true, alg) else for (i, c) in small - b = block(ts[i], c) + # `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 diff --git a/src/algorithms/time_evolution/apply_mpo.jl b/src/algorithms/time_evolution/apply_mpo.jl index 75e0f304c..05e6e6dba 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/util.jl b/src/utility/util.jl index 9f6c82f6f..f2fc3620b 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -8,6 +8,24 @@ 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. GPU backends override this to return a 0-dimensional +array, to avoid a copy back to the CPU memory. +""" +function _maxabs(data::AbstractArray) + isempty(data) && return zero(real(eltype(data))) + return LinearAlgebra.normInf(data) +end """ sdiag_pow(s, pow::Real; tol::Real=eps(real(scalartype(s)))^(3 / 4)) @@ -15,10 +33,7 @@ _safe_pow(a::Number, pow::Real, tol::Real) = (pow < 0 && abs(a) < tol) ? zero(a) Compute `s^pow` for a diagonal matrix `s`. """ function sdiag_pow(s::DiagonalTensorMap, pow::Real; tol::Real = eps(real(scalartype(s)))^(3 / 4)) - # Relative tol w.r.t. largest abs value of `s` (use norm(∘, Inf) to make differentiable) - tol *= norm(s, Inf) - spow = DiagonalTensorMap(_safe_pow.(s.data, pow, tol), space(s, 1)) - return spow + return DiagonalTensorMap(_safe_pow.(s.data, pow, tol, _maxabs(s.data)), space(s, 1)) end function sdiag_pow( s::AbstractTensorMap{T, S, 1, 1}, pow::Real; tol::Real = eps(real(scalartype(s)))^(3 / 4) From 40f5f3c817cf461de2c80ece59b8522b92b4b8c5 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 24 Aug 2026 01:51:59 -0400 Subject: [PATCH 077/102] Force real-valued accumulator --- ext/PEPSKitGPUArraysExt.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 952e272f5..608ab180f 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -33,8 +33,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 From 71ea07800cba3a4a6ae3b4c34e061b89478d7034 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 15 Sep 2026 02:25:46 -0400 Subject: [PATCH 078/102] Another small fix --- ext/PEPSKitGPUArraysExt.jl | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 608ab180f..76a99c624 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -76,17 +76,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 @@ -116,7 +114,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}}}() @@ -147,7 +145,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 @@ -190,7 +188,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 @@ -209,7 +207,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)) @@ -289,8 +287,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 6ea240c57add5520a7a9804a744f3d03e31550d0 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 15 Sep 2026 07:18:58 -0400 Subject: [PATCH 079/102] Move batching logic into MAK as much as possible --- ext/PEPSKitGPUArraysExt.jl | 169 ++++++------------------------------- 1 file changed, 24 insertions(+), 145 deletions(-) diff --git a/ext/PEPSKitGPUArraysExt.jl b/ext/PEPSKitGPUArraysExt.jl index 76a99c624..af694983b 100644 --- a/ext/PEPSKitGPUArraysExt.jl +++ b/ext/PEPSKitGPUArraysExt.jl @@ -6,8 +6,6 @@ using PEPSKit using TensorKit using TensorKit: MatrixAlgebraKit as MAK -const BATCH_THRESHOLD = 4 - # 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 several caches, for SimultaneousCTMRG and SU, @@ -67,8 +65,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 @@ -84,7 +80,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 @@ -94,9 +98,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[] @@ -114,68 +115,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 @@ -188,31 +127,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 @@ -231,19 +146,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( @@ -253,47 +167,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 From 5e90e25615036e8af94fa0e9d619862d07be9a52 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 15 Sep 2026 10:05:53 -0400 Subject: [PATCH 080/102] Try to make formatter happy --- test/cuda/ctmrg/unitcell.jl | 6 +++--- test/rocm/ctmrg/unitcell.jl | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) 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/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) ]..., ) ) From 6e8340034c1b6e18cbd353a9c6458b62efde829f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 15 Sep 2026 10:18:27 -0400 Subject: [PATCH 081/102] Fix missing docstring refs --- src/algorithms/ctmrg/ctmrg.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 4d452e970..d3fb376a1 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -261,11 +261,15 @@ edge_spectra(Ts, alg) = map(T -> edge_spectrum(T, alg), Ts) """ corner_spectrum(C, alg) - edge_spectrum(T, alg) -The spectrum of a corner or edge, used to measure CTMRG convergence. +The spectrum of a corner, used to measure CTMRG convergence. """ corner_spectrum(C, alg) = svd_vals(C) +""" + edge_spectrum(T, alg) + +The spectrum of an edge, used to measure CTMRG convergence. +""" edge_spectrum(T, alg) = svd_vals(T) function calc_convergence(env, CS_old, TS_old, alg) From 26cbe25cbbed2e67a4369085888e23ef1a80517c Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 15 Sep 2026 11:21:18 -0400 Subject: [PATCH 082/102] Fix stale checks in the GPU gradient tests --- test/cuda/gradients/ctmrg_gradients.jl | 18 +++++------------- test/rocm/gradients/ctmrg_gradients.jl | 18 +++++------------- 2 files changed, 10 insertions(+), 26 deletions(-) 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/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 68685b07a40752652346f77bc54348e9b81d00a1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 14 Sep 2026 16:25:40 +0200 Subject: [PATCH 083/102] Incremental trimming of duplicated tests --- .buildkite/pipeline.yml | 2 +- src/operators/transfermatrix.jl | 8 +- test/bondenv/benv_ctm.jl | 11 +- test/bondenv/benv_gaugefix.jl | 11 +- test/bondenv/bond_truncate.jl | 11 +- test/boundarymps/vumps.jl | 16 +- test/bp/expvals.jl | 10 +- test/bp/gaugefix.jl | 10 +- test/bp/rotation.jl | 10 +- test/bp/unitcell.jl | 13 +- test/compress/local.jl | 15 +- test/ctmrg/contractions.jl | 14 +- test/ctmrg/correlation_length.jl | 10 +- test/ctmrg/fixed_iterscheme.jl | 15 +- test/ctmrg/flavors.jl | 14 + test/ctmrg/gaugefix.jl | 13 +- test/ctmrg/initialization.jl | 12 +- test/ctmrg/jacobian_real_linear.jl | 11 +- test/ctmrg/partition_function.jl | 13 +- test/ctmrg/pepo.jl | 13 +- test/ctmrg/suweight.jl | 11 +- test/ctmrg/unitcell.jl | 12 +- 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/gradients/c4v_ctmrg_gradients.jl | 10 +- test/gradients/ctmrg_gradients.jl | 12 +- 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/boundarymps/vumps.jl | 17 +- test/testsuite/bp/gaugefix.jl | 3 + test/testsuite/bp/rotation.jl | 2 + test/testsuite/bp/unitcell.jl | 2 + test/testsuite/ctmrg/unitcell.jl | 10 +- test/timeevol/cluster_projectors.jl | 14 +- test/timeevol/j1j2_finiteT.jl | 10 +- test/timeevol/sitedep_truncation.jl | 12 +- test/timeevol/tf_ising_finiteT.jl | 10 +- test/timeevol/timestep.jl | 10 +- test/toolbox/densitymatrices.jl | 12 +- test/utility/correlator.jl | 14 +- test/utility/eigh_wrapper.jl | 10 +- test/utility/retractions.jl | 10 +- test/utility/svd_wrapper.jl | 10 +- test/utility/symmetrization.jl | 16 +- 104 files changed, 385 insertions(+), 6625 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 e8f443ba0..fe66c8d02 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/src/operators/transfermatrix.jl b/src/operators/transfermatrix.jl index e2724b25b..a78baa6ed 100644 --- a/src/operators/transfermatrix.jl +++ b/src/operators/transfermatrix.jl @@ -154,10 +154,10 @@ function initialize_mps( return InfiniteMPS( [ f( - TorA, - virtualspaces[_prev(i, end)] * _elementwise_dual(north_virtualspace(O, i)), - virtualspaces[mod1(i, end)], - ) for i in 1:length(O) + TorA, + virtualspaces[_prev(i, end)] * _elementwise_dual(north_virtualspace(O, i)), + virtualspaces[mod1(i, end)], + ) for i in 1:length(O) ]; kwargs... ) end diff --git a/test/bondenv/benv_ctm.jl b/test/bondenv/benv_ctm.jl index b740fc9d2..08f559f8f 100644 --- a/test/bondenv/benv_ctm.jl +++ b/test/bondenv/benv_ctm.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.bondenv_ctm(Vector) end + +if CUDA.functional() + TestSuite.bondenv_ctm(CuArray) +end + +if AMDGPU.functional() + TestSuite.bondenv_ctm(ROCArray) +end diff --git a/test/bondenv/benv_gaugefix.jl b/test/bondenv/benv_gaugefix.jl index 4d25ba037..8df16a076 100644 --- a/test/bondenv/benv_gaugefix.jl +++ b/test/bondenv/benv_gaugefix.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.bondenv_gaugefix(Vector) end + +if CUDA.functional() + TestSuite.bondenv_gaugefix(CuArray) +end + +if AMDGPU.functional() + TestSuite.bondenv_gaugefix(ROCArray) +end diff --git a/test/bondenv/bond_truncate.jl b/test/bondenv/bond_truncate.jl index eb14e97cb..662dcbf27 100644 --- a/test/bondenv/bond_truncate.jl +++ b/test/bondenv/bond_truncate.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.bondenv_truncate(Vector) end + +if CUDA.functional() + TestSuite.bondenv_truncate(CuArray) +end + +if AMDGPU.functional() + TestSuite.bondenv_truncate(ROCArray) +end diff --git a/test/boundarymps/vumps.jl b/test/boundarymps/vumps.jl index 1906704fb..4db249123 100644 --- a/test/boundarymps/vumps.jl +++ b/test/boundarymps/vumps.jl @@ -1,4 +1,4 @@ -Random.seed!(29384293742893) +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -11,3 +11,17 @@ if !is_buildkite TestSuite.boundary_mps_fermionic_peps(Vector) TestSuite.boundary_mps_pepo_runthrough(Vector) end + +if CUDA.functional() + TestSuite.boundary_mps_one_one_peps(CuArray) + TestSuite.boundary_mps_two_two_peps(CuArray) + TestSuite.boundary_mps_fermionic_peps(CuArray) + TestSuite.boundary_mps_pepo_runthrough(CuArray) +end + +if AMDGPU.functional() + TestSuite.boundary_mps_one_one_peps(ROCArray) + TestSuite.boundary_mps_two_two_peps(ROCArray) + TestSuite.boundary_mps_fermionic_peps(ROCArray) + TestSuite.boundary_mps_pepo_runthrough(ROCArray) +end diff --git a/test/bp/expvals.jl b/test/bp/expvals.jl index 73a507472..13a5c7379 100644 --- a/test/bp/expvals.jl +++ b/test/bp/expvals.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -8,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.bp_expvals(Vector) end + +if CUDA.functional() + TestSuite.bp_expvals(CuArray) +end + +if AMDGPU.functional() + TestSuite.bp_expvals(ROCArray) +end diff --git a/test/bp/gaugefix.jl b/test/bp/gaugefix.jl index bff0d4cac..93254f64b 100644 --- a/test/bp/gaugefix.jl +++ b/test/bp/gaugefix.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -8,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.bp_gaugefix_bp_vs_su(Vector) end + +if CUDA.functional() + TestSuite.bp_gaugefix_bp_vs_su(CuArray) +end + +if AMDGPU.functional() + TestSuite.bp_gaugefix_bp_vs_su(ROCArray) +end diff --git a/test/bp/rotation.jl b/test/bp/rotation.jl index 984594105..3777b7ab0 100644 --- a/test/bp/rotation.jl +++ b/test/bp/rotation.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -8,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.bp_rotations(Vector) end + +if CUDA.functional() + TestSuite.bp_rotations(CuArray) +end + +if AMDGPU.functional() + TestSuite.bp_rotations(ROCArray) +end diff --git a/test/bp/unitcell.jl b/test/bp/unitcell.jl index 7e7e31a28..9276c9fd6 100644 --- a/test/bp/unitcell.jl +++ b/test/bp/unitcell.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -10,3 +9,13 @@ if !is_buildkite TestSuite.bp_unitcell_random_cartesian_spaces(Vector) TestSuite.bp_unitcell_specific_u1_spaces(Vector) end + +if CUDA.functional() + TestSuite.bp_unitcell_random_cartesian_spaces(CuArray) + TestSuite.bp_unitcell_specific_u1_spaces(CuArray) +end + +if AMDGPU.functional() + TestSuite.bp_unitcell_random_cartesian_spaces(ROCArray) + TestSuite.bp_unitcell_specific_u1_spaces(ROCArray) +end diff --git a/test/compress/local.jl b/test/compress/local.jl index df0be6fcf..52fd94b6c 100644 --- a/test/compress/local.jl +++ b/test/compress/local.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -11,3 +10,15 @@ if !is_buildkite TestSuite.compress_cost_function(Vector) TestSuite.compress_virtual_space_matching(Vector) end + +if CUDA.functional() + TestSuite.compress_fermionic_twists(CuArray) + TestSuite.compress_cost_function(CuArray) + TestSuite.compress_virtual_space_matching(CuArray) +end + +if AMDGPU.functional() + TestSuite.compress_fermionic_twists(ROCArray) + TestSuite.compress_cost_function(ROCArray) + TestSuite.compress_virtual_space_matching(ROCArray) +end diff --git a/test/ctmrg/contractions.jl b/test/ctmrg/contractions.jl index c47ef3865..8180e2d00 100644 --- a/test/ctmrg/contractions.jl +++ b/test/ctmrg/contractions.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -10,3 +10,15 @@ if !is_buildkite TestSuite.ctmrg_contractions_specific_u1_spaces(Vector) TestSuite.ctmrg_contractions_random_fermionic_spaces(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_contractions_random_cartesian_spaces(CuArray) + TestSuite.ctmrg_contractions_specific_u1_spaces(CuArray) + TestSuite.ctmrg_contractions_random_fermionic_spaces(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_contractions_random_cartesian_spaces(ROCArray) + TestSuite.ctmrg_contractions_specific_u1_spaces(ROCArray) + TestSuite.ctmrg_contractions_random_fermionic_spaces(ROCArray) +end diff --git a/test/ctmrg/correlation_length.jl b/test/ctmrg/correlation_length.jl index e3998e7b8..914b817b6 100644 --- a/test/ctmrg/correlation_length.jl +++ b/test/ctmrg/correlation_length.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -8,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.ctmrg_correlation_length(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_correlation_length(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_correlation_length(ROCArray) +end diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 50edc6ab8..63776bdba 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -11,3 +10,15 @@ if !is_buildkite TestSuite.ctmrg_fixed_iterscheme_c4v(Vector) TestSuite.ctmrg_fixed_iterscheme_divide_and_conquer(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_fixed_iterscheme_asymmetric(CuArray) + TestSuite.ctmrg_fixed_iterscheme_c4v(CuArray) + TestSuite.ctmrg_fixed_iterscheme_divide_and_conquer(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_fixed_iterscheme_asymmetric(ROCArray) + TestSuite.ctmrg_fixed_iterscheme_c4v(ROCArray) + TestSuite.ctmrg_fixed_iterscheme_divide_and_conquer(ROCArray) +end diff --git a/test/ctmrg/flavors.jl b/test/ctmrg/flavors.jl index 557c9d451..0f1b05dc7 100644 --- a/test/ctmrg/flavors.jl +++ b/test/ctmrg/flavors.jl @@ -1,3 +1,5 @@ +using PEPSKit, CUDA, AMDGPU + @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -8,3 +10,15 @@ if !is_buildkite TestSuite.ctmrg_flavors_fixedspace_truncation(Vector) TestSuite.ctmrg_flavors_c4v(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_flavors_unitcells(CuArray) + TestSuite.ctmrg_flavors_fixedspace_truncation(CuArray) + TestSuite.ctmrg_flavors_c4v(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_flavors_unitcells(ROCArray) + TestSuite.ctmrg_flavors_fixedspace_truncation(ROCArray) + TestSuite.ctmrg_flavors_c4v(ROCArray) +end diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index a8e6e1afd..7fc63a833 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -10,3 +9,13 @@ if !is_buildkite TestSuite.ctmrg_gaugefix_asymmetric(Vector) TestSuite.ctmrg_gaugefix_c4v(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_gaugefix_asymmetric(CuArray) + TestSuite.ctmrg_gaugefix_c4v(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_gaugefix_asymmetric(ROCArray) + TestSuite.ctmrg_gaugefix_c4v(ROCArray) +end diff --git a/test/ctmrg/initialization.jl b/test/ctmrg/initialization.jl index 4432e54da..d8444ce90 100644 --- a/test/ctmrg/initialization.jl +++ b/test/ctmrg/initialization.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +9,13 @@ if !is_buildkite TestSuite.ctmrg_initialization_critical_ising(Vector) TestSuite.ctmrg_initialization_peps(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_initialization_critical_ising(CuArray) + TestSuite.ctmrg_initialization_peps(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_initialization_critical_ising(ROCArray) + TestSuite.ctmrg_initialization_peps(ROCArray) +end diff --git a/test/ctmrg/jacobian_real_linear.jl b/test/ctmrg/jacobian_real_linear.jl index b9d66acf5..3298d651a 100644 --- a/test/ctmrg/jacobian_real_linear.jl +++ b/test/ctmrg/jacobian_real_linear.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.ctmrg_jacobian_real_linear(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_jacobian_real_linear(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_jacobian_real_linear(ROCArray) +end diff --git a/test/ctmrg/partition_function.jl b/test/ctmrg/partition_function.jl index 50188bd3b..4b8ea309f 100644 --- a/test/ctmrg/partition_function.jl +++ b/test/ctmrg/partition_function.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -10,3 +9,13 @@ if !is_buildkite TestSuite.ctmrg_partition_function_spaces(Vector) TestSuite.ctmrg_partition_function(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_partition_function_spaces(CuArray) + TestSuite.ctmrg_partition_function(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_partition_function_spaces(ROCArray) + TestSuite.ctmrg_partition_function(ROCArray) +end diff --git a/test/ctmrg/pepo.jl b/test/ctmrg/pepo.jl index be56f52c7..fc0ef1d47 100644 --- a/test/ctmrg/pepo.jl +++ b/test/ctmrg/pepo.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -10,3 +9,13 @@ if !is_buildkite TestSuite.ctmrg_pepo_runthroughs(Vector) TestSuite.ctmrg_pepo_fixed_point(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_pepo_runthroughs(CuArray) + TestSuite.ctmrg_pepo_fixed_point(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_pepo_runthroughs(ROCArray) + TestSuite.ctmrg_pepo_fixed_point(ROCArray) +end diff --git a/test/ctmrg/suweight.jl b/test/ctmrg/suweight.jl index fa1cc1984..67f0eedef 100644 --- a/test/ctmrg/suweight.jl +++ b/test/ctmrg/suweight.jl @@ -1,5 +1,4 @@ -using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.ctmrg_suweight(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_suweight(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_suweight(ROCArray) +end diff --git a/test/ctmrg/unitcell.jl b/test/ctmrg/unitcell.jl index 6599ce32f..4ff5e4c76 100644 --- a/test/ctmrg/unitcell.jl +++ b/test/ctmrg/unitcell.jl @@ -1,5 +1,5 @@ using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -10,3 +10,13 @@ if !is_buildkite TestSuite.ctmrg_unitcell_random_cartesian_spaces(Vector) TestSuite.ctmrg_unitcell_specific_u1_spaces(Vector) end + +if CUDA.functional() + TestSuite.ctmrg_unitcell_random_cartesian_spaces(CuArray) + TestSuite.ctmrg_unitcell_specific_u1_spaces(CuArray) +end + +if AMDGPU.functional() + TestSuite.ctmrg_unitcell_random_cartesian_spaces(ROCArray) + TestSuite.ctmrg_unitcell_specific_u1_spaces(ROCArray) +end 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/gradients/c4v_ctmrg_gradients.jl b/test/gradients/c4v_ctmrg_gradients.jl index 9bc6f9905..ea9a38a58 100644 --- a/test/gradients/c4v_ctmrg_gradients.jl +++ b/test/gradients/c4v_ctmrg_gradients.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -8,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.gradients_c4v(Vector) end + +if CUDA.functional() + TestSuite.gradients_c4v(CuArray) +end + +if AMDGPU.functional() + TestSuite.gradients_c4v(ROCArray) +end diff --git a/test/gradients/ctmrg_gradients.jl b/test/gradients/ctmrg_gradients.jl index ce50580a8..73135e790 100644 --- a/test/gradients/ctmrg_gradients.jl +++ b/test/gradients/ctmrg_gradients.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +9,13 @@ if !is_buildkite TestSuite.gradients_asymmetric(Vector) TestSuite.gradients_asymmetric_276(Vector) end + +if CUDA.functional() + TestSuite.gradients_asymmetric(CuArray) + TestSuite.gradients_asymmetric_276(CuArray) +end + +if AMDGPU.functional() + TestSuite.gradients_asymmetric(ROCArray) + TestSuite.gradients_asymmetric_276(ROCArray) +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/boundarymps/vumps.jl b/test/testsuite/boundarymps/vumps.jl index df07e1593..50c2ead4d 100644 --- a/test/testsuite/boundarymps/vumps.jl +++ b/test/testsuite/boundarymps/vumps.jl @@ -15,14 +15,21 @@ function boundary_mps_one_one_peps(AT) return @testset "(1, 1) PEPS ($AT)" begin Vpeps = ComplexSpace(2) psi = adapt(AT, InfinitePEPS(Vpeps, Vpeps)) + @test storagetype(psi) <: AT T = PEPSKit.InfiniteTransferPEPS(psi, 1, 1) + @test storagetype(T) <: AT foreach(V -> (@test V == Vpeps ⊗ Vpeps'), physicalspace(T)) mps = initialize_mps(T, [ComplexSpace(20)]) + @test storagetype(mps) <: AT 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 + @static if VERSION < v"1.11.0-rc" # so [sources] isn't used + mps2, = changebonds(mps, T, OptimalExpand(; trscheme = truncrank(30))) # TODO: update `trscheme` to `trunc` once MPSKit does + else + mps2, = changebonds(mps, T, OptimalExpand(; trunc = truncrank(30))) # TODO: update `trunc` to `trunc` once MPSKit does + end mps2, env2, ϵ = leading_boundary(mps2, T, vumps_alg) N2 = abs(sum(expectation_value(mps2, T))) @test N ≈ N2 rtol = 1.0e-2 @@ -39,9 +46,12 @@ function boundary_mps_two_two_peps(AT) return @testset "(2, 2) PEPS ($AT)" begin Vpeps = ComplexSpace(2) psi = adapt(AT, InfinitePEPS(Vpeps, Vpeps; unitcell = (2, 2))) + @test storagetype(psi) <: AT T = PEPSKit.MultilineTransferPEPS(psi, 1) + @test storagetype(T) <: AT # 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)) + @test storagetype(mps) <: AT mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(prod(expectation_value(mps, T))) @@ -60,12 +70,16 @@ function boundary_mps_fermionic_peps(AT) χ = Vect[fℤ₂](0 => 10, 1 => 10) psi = adapt(AT, InfinitePEPS(D, d; unitcell = (1, 1))) + @test storagetype(psi) <: AT n = InfiniteSquareNetwork(psi) + @test storagetype(n) <: AT T = InfiniteTransferPEPS(psi, 1, 1) + @test storagetype(T) <: AT foreach(V -> (@test V == D ⊗ D'), physicalspace(T)) # compare boundary MPS contraction to CTMRG contraction mps = initialize_mps(T, [χ]) + @test storagetype(mps) <: AT mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N_vumps = abs(prod(expectation_value(mps, T))) @@ -120,6 +134,7 @@ function boundary_mps_pepo_runthrough(AT) foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpeps'), physicalspace(T)) mps = initialize_mps(rand, scalartype(T), T, [ComplexSpace(10)]) + @test storagetype(mps) <: AT mps, env, ϵ = leading_boundary(mps, T, vumps_alg) f = abs(prod(expectation_value(mps, T))) diff --git a/test/testsuite/bp/gaugefix.jl b/test/testsuite/bp/gaugefix.jl index 404784e1b..db1e6afa3 100644 --- a/test/testsuite/bp/gaugefix.jl +++ b/test/testsuite/bp/gaugefix.jl @@ -51,9 +51,11 @@ function bp_gaugefix_bp_vs_su(AT) peps0[2, c] = copy(peps0[1, c + 1]) end end + @test storagetype(peps0) <: AT # start by gauging with SU peps1, wts1 = gauge_fix(peps0, SUGauge(; maxiter, tol)) + @test storagetype(peps1) <: AT for (a0, a1) in zip(peps0.A, peps1.A) @test space(a0) == space(a1) end @@ -66,6 +68,7 @@ function bp_gaugefix_bp_vs_su(AT) # 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) <: AT env, err = leading_boundary(env, peps1, bp_alg) if bipartite @test _is_bipartite(env) diff --git a/test/testsuite/bp/rotation.jl b/test/testsuite/bp/rotation.jl index e38f9131e..778077790 100644 --- a/test/testsuite/bp/rotation.jl +++ b/test/testsuite/bp/rotation.jl @@ -33,7 +33,9 @@ function bp_rotations(AT) ψDNs = random_dual!(fill(D, unitcell)) ψDEs = random_dual!(fill(D, unitcell)) ψ = adapt(AT, InfinitePEPS(ψds, ψDNs, ψDEs)) + @test storagetype(ψ) <: AT env = BPEnv(ψ) + @test storagetype(env) <: AT op = adapt(AT, randn(d → d)) meas1 = meas_sites(op, ψ, env) diff --git a/test/testsuite/bp/unitcell.jl b/test/testsuite/bp/unitcell.jl index 9c88d281b..f64b8b7ab 100644 --- a/test/testsuite/bp/unitcell.jl +++ b/test/testsuite/bp/unitcell.jl @@ -9,7 +9,9 @@ elt = ComplexF64 function test_unitcell(AT, unitcell, Pspaces, Nspaces, Espaces) peps = adapt(AT, InfinitePEPS(randn, elt, Pspaces, Nspaces, Espaces)) + @test storagetype(peps) <: AT env0 = BPEnv(ones, elt, peps) + @test storagetype(env0) <: AT alg = BeliefPropagation() # apply one BP iteration diff --git a/test/testsuite/ctmrg/unitcell.jl b/test/testsuite/ctmrg/unitcell.jl index 848e1ed6e..5516e64c2 100644 --- a/test/testsuite/ctmrg/unitcell.jl +++ b/test/testsuite/ctmrg/unitcell.jl @@ -29,11 +29,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 diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 56da57e98..064b34fa6 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -10,3 +10,15 @@ if !is_buildkite TestSuite.timeevol_cluster_identity_gate(Vector) TestSuite.timeevol_cluster_hubbard(Vector) end + +if CUDA.functional() + TestSuite.timeevol_cluster_bond_truncation(CuArray) + TestSuite.timeevol_cluster_identity_gate(CuArray) + TestSuite.timeevol_cluster_hubbard(CuArray) +end + +if AMDGPU.functional() + TestSuite.timeevol_cluster_bond_truncation(ROCArray) + TestSuite.timeevol_cluster_identity_gate(ROCArray) + TestSuite.timeevol_cluster_hubbard(ROCArray) +end diff --git a/test/timeevol/j1j2_finiteT.jl b/test/timeevol/j1j2_finiteT.jl index 856214d70..b9f46c24f 100644 --- a/test/timeevol/j1j2_finiteT.jl +++ b/test/timeevol/j1j2_finiteT.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -8,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.timeevol_j1j2_finiteT(Vector) end + +if CUDA.functional() + TestSuite.timeevol_j1j2_finiteT(CuArray) +end + +if AMDGPU.functional() + TestSuite.timeevol_j1j2_finiteT(ROCArray) +end diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index 220da6dec..f2b618155 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +9,13 @@ if !is_buildkite TestSuite.timeevol_sitedep_rotation(Vector) TestSuite.timeevol_sitedep_su(Vector) end + +if CUDA.functional() + TestSuite.timeevol_sitedep_rotation(CuArray) + TestSuite.timeevol_sitedep_su(CuArray) +end + +if AMDGPU.functional() + TestSuite.timeevol_sitedep_rotation(ROCArray) + TestSuite.timeevol_sitedep_su(ROCArray) +end diff --git a/test/timeevol/tf_ising_finiteT.jl b/test/timeevol/tf_ising_finiteT.jl index 6c6e1c74e..72347dd89 100644 --- a/test/timeevol/tf_ising_finiteT.jl +++ b/test/timeevol/tf_ising_finiteT.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -8,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.timeevol_ising_finiteT(Vector) end + +if CUDA.functional() + TestSuite.timeevol_ising_finiteT(CuArray) +end + +if AMDGPU.functional() + TestSuite.timeevol_ising_finiteT(ROCArray) +end diff --git a/test/timeevol/timestep.jl b/test/timeevol/timestep.jl index 8d426f5cd..a3692114c 100644 --- a/test/timeevol/timestep.jl +++ b/test/timeevol/timestep.jl @@ -1,4 +1,4 @@ -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -8,3 +8,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.timeevol_timestep(Vector) end + +if CUDA.functional() + TestSuite.timeevol_timestep(CuArray) +end + +if AMDGPU.functional() + TestSuite.timeevol_timestep(ROCArray) +end diff --git a/test/toolbox/densitymatrices.jl b/test/toolbox/densitymatrices.jl index 3030ec187..97f9199b2 100644 --- a/test/toolbox/densitymatrices.jl +++ b/test/toolbox/densitymatrices.jl @@ -1,5 +1,5 @@ using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -12,3 +12,13 @@ if !is_buildkite TestSuite.toolbox_densitymatrix_too_many_layers(Vector) TestSuite.toolbox_densitymatrix_generic_fallback(Vector) end + +if CUDA.functional() + TestSuite.toolbox_single_layer_densitymatrix(CuArray) + TestSuite.toolbox_double_layer_densitymatrix(CuArray) +end + +if AMDGPU.functional() + TestSuite.toolbox_single_layer_densitymatrix(ROCArray) + TestSuite.toolbox_double_layer_densitymatrix(ROCArray) +end diff --git a/test/utility/correlator.jl b/test/utility/correlator.jl index 94dec8796..829fa6df6 100644 --- a/test/utility/correlator.jl +++ b/test/utility/correlator.jl @@ -1,5 +1,5 @@ using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -11,3 +11,15 @@ if !is_buildkite TestSuite.utility_correlator_purified_ipepo(Vector) TestSuite.utility_correlator_single_layer_ipepo(Vector) end + +if CUDA.functional() + TestSuite.utility_correlator_infinite_peps(CuArray) + TestSuite.utility_correlator_purified_ipepo(CuArray) + TestSuite.utility_correlator_single_layer_ipepo(CuArray) +end + +if AMDGPU.functional() + TestSuite.utility_correlator_infinite_peps(ROCArray) + TestSuite.utility_correlator_purified_ipepo(ROCArray) + TestSuite.utility_correlator_single_layer_ipepo(ROCArray) +end diff --git a/test/utility/eigh_wrapper.jl b/test/utility/eigh_wrapper.jl index 74435d9e3..6c882b483 100644 --- a/test/utility/eigh_wrapper.jl +++ b/test/utility/eigh_wrapper.jl @@ -1,5 +1,5 @@ using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +9,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.utility_eigh_wrapper(Vector) end + +if CUDA.functional() + TestSuite.utility_eigh_wrapper(CuArray) +end + +if AMDGPU.functional() + TestSuite.utility_eigh_wrapper(ROCArray) +end diff --git a/test/utility/retractions.jl b/test/utility/retractions.jl index 24ee96426..93e570310 100644 --- a/test/utility/retractions.jl +++ b/test/utility/retractions.jl @@ -1,5 +1,5 @@ using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +9,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.utility_retractions(Vector) end + +if CUDA.functional() + TestSuite.utility_retractions(CuArray) +end + +if AMDGPU.functional() + TestSuite.utility_retractions(ROCArray) +end diff --git a/test/utility/svd_wrapper.jl b/test/utility/svd_wrapper.jl index 8abda1b93..b13497c59 100644 --- a/test/utility/svd_wrapper.jl +++ b/test/utility/svd_wrapper.jl @@ -1,5 +1,5 @@ using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -9,3 +9,11 @@ is_buildkite = get(ENV, "BUILDKITE", "false") == "true" if !is_buildkite TestSuite.utility_svd_wrapper(Vector) end + +if CUDA.functional() + TestSuite.utility_svd_wrapper(CuArray) +end + +if AMDGPU.functional() + TestSuite.utility_svd_wrapper(ROCArray) +end diff --git a/test/utility/symmetrization.jl b/test/utility/symmetrization.jl index 6b22bce8b..143b0f2e8 100644 --- a/test/utility/symmetrization.jl +++ b/test/utility/symmetrization.jl @@ -1,5 +1,5 @@ using Test -using PEPSKit +using PEPSKit, CUDA, AMDGPU @isdefined(TestSuite) || include("../testsuite/TestSuite.jl") using .TestSuite @@ -12,3 +12,17 @@ if !is_buildkite TestSuite.utility_symmetrization_rotate(Vector) TestSuite.utility_symmetrization_rotate_reflect(Vector) end + +if CUDA.functional() + TestSuite.utility_symmetrization_reflect_depth(CuArray) + TestSuite.utility_symmetrization_reflect_width(CuArray) + TestSuite.utility_symmetrization_rotate(CuArray) + TestSuite.utility_symmetrization_rotate_reflect(CuArray) +end + +if AMDGPU.functional() + TestSuite.utility_symmetrization_reflect_depth(ROCArray) + TestSuite.utility_symmetrization_reflect_width(ROCArray) + TestSuite.utility_symmetrization_rotate(ROCArray) + TestSuite.utility_symmetrization_rotate_reflect(ROCArray) +end From 9cf720fbbe4247d407ae33185c04ffce19809450 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 21 Sep 2026 07:11:20 -0400 Subject: [PATCH 084/102] Use new TO version --- Project.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 467850ff7..bbecb6dc9 100644 --- a/Project.toml +++ b/Project.toml @@ -39,7 +39,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] @@ -67,7 +66,7 @@ Random = "1" Statistics = "1" TensorKit = "0.16.5, 0.17" TensorKitTensors = "0.3.1" -TensorOperations = "5" +TensorOperations = "5.8.1" TupleTools = "1.6.0" VectorInterface = "0.4, 0.5, 0.6" Zygote = "0.6, 0.7" From ef09f9399432980019308a7026527cea86e4e203 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 21 Sep 2026 08:46:23 -0400 Subject: [PATCH 085/102] Fixups --- src/operators/transfermatrix.jl | 8 ++++---- test/testsuite/bondenv/benv_ctm.jl | 2 +- test/testsuite/ctmrg/unitcell.jl | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/operators/transfermatrix.jl b/src/operators/transfermatrix.jl index a78baa6ed..e2724b25b 100644 --- a/src/operators/transfermatrix.jl +++ b/src/operators/transfermatrix.jl @@ -154,10 +154,10 @@ function initialize_mps( return InfiniteMPS( [ f( - TorA, - virtualspaces[_prev(i, end)] * _elementwise_dual(north_virtualspace(O, i)), - virtualspaces[mod1(i, end)], - ) for i in 1:length(O) + TorA, + virtualspaces[_prev(i, end)] * _elementwise_dual(north_virtualspace(O, i)), + virtualspaces[mod1(i, end)], + ) for i in 1:length(O) ]; kwargs... ) end diff --git a/test/testsuite/bondenv/benv_ctm.jl b/test/testsuite/bondenv/benv_ctm.jl index b740d58a0..8332d276b 100644 --- a/test/testsuite/bondenv/benv_ctm.jl +++ b/test/testsuite/bondenv/benv_ctm.jl @@ -13,7 +13,7 @@ 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(AT, t::Float64 = 1.0, U::Float64 = 8.0) - H = hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2) + H = adapt(AT, 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(AT, InfinitePEPS(rand, ComplexF64, Vphy, Vphy; unitcell = (Nr, Nc))) wts = SUWeight(peps) diff --git a/test/testsuite/ctmrg/unitcell.jl b/test/testsuite/ctmrg/unitcell.jl index 5516e64c2..848e1ed6e 100644 --- a/test/testsuite/ctmrg/unitcell.jl +++ b/test/testsuite/ctmrg/unitcell.jl @@ -29,11 +29,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 22bdd265e152dec29b2918157a71ccd69184d023 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 21 Sep 2026 08:46:56 -0400 Subject: [PATCH 086/102] Use backend and allocator for the VUMPS transfer matrices --- .../contractions/vumps_contractions.jl | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/algorithms/contractions/vumps_contractions.jl b/src/algorithms/contractions/vumps_contractions.jl index 4d58c12de..27ac960d5 100644 --- a/src/algorithms/contractions/vumps_contractions.jl +++ b/src/algorithms/contractions/vumps_contractions.jl @@ -4,27 +4,28 @@ function MPSKit.transfer_left( GL::GenericMPSTensor{S, N}, O::Union{PEPSSandwich, PEPOSandwich}, - A::GenericMPSTensor{S, N}, Ā::GenericMPSTensor{S, N}, + A::GenericMPSTensor{S, N}, Ā::GenericMPSTensor{S, N}; kwargs... ) where {S, N} Ā = twistdual(Ā, 2:N) - return mps_transfer_left(GL, O, A, Ā) + return mps_transfer_left(GL, O, A, Ā; kwargs...) end function MPSKit.transfer_right( GR::GenericMPSTensor{S, N}, O::Union{PEPSSandwich, PEPOSandwich}, - A::GenericMPSTensor{S, N}, Ā::GenericMPSTensor{S, N}, + A::GenericMPSTensor{S, N}, Ā::GenericMPSTensor{S, N}; kwargs... ) where {S, N} Ā = twistdual(Ā, 2:N) - return mps_transfer_right(GR, O, A, Ā) + return mps_transfer_right(GR, O, A, Ā; kwargs...) end ## PEPS function mps_transfer_left( GL::GenericMPSTensor{S, 3}, O::PEPSSandwich, - A::GenericMPSTensor{S, 3}, Ā::GenericMPSTensor{S, 3}, + A::GenericMPSTensor{S, 3}, Ā::GenericMPSTensor{S, 3}; + backend = DefaultBackend(), allocator = DefaultAllocator() ) where {S} - return @autoopt @tensor GL′[χ_SE D_E_above D_E_below; χ_NE] := + return @autoopt @tensor backend = backend allocator = allocator GL′[χ_SE D_E_above D_E_below; χ_NE] := GL[χ_SW D_W_above D_W_below; χ_NW] * conj(Ā[χ_SW D_S_above D_S_below; χ_SE]) * ket(O)[d; D_N_above D_E_above D_S_above D_W_above] * @@ -34,9 +35,10 @@ end function mps_transfer_right( GR::GenericMPSTensor{S, 3}, O::PEPSSandwich, - A::GenericMPSTensor{S, 3}, Ā::GenericMPSTensor{S, 3}, + A::GenericMPSTensor{S, 3}, Ā::GenericMPSTensor{S, 3}; + backend = DefaultBackend(), allocator = DefaultAllocator() ) where {S} - return @autoopt @tensor GR′[χ_NW D_W_above D_W_below; χ_SW] := + return @autoopt @tensor backend = backend allocator = allocator GR′[χ_NW D_W_above D_W_below; χ_SW] := GR[χ_NE D_E_above D_E_below; χ_SE] * conj(Ā[χ_SW D_S_above D_S_below; χ_SE]) * ket(O)[d; D_N_above D_E_above D_S_above D_W_above] * @@ -48,7 +50,8 @@ end @generated function mps_transfer_left( GL::GenericMPSTensor{S, N}, O::PEPOSandwich{H}, - A::GenericMPSTensor{S, N}, Ā::GenericMPSTensor{S, N}, + A::GenericMPSTensor{S, N}, Ā::GenericMPSTensor{S, N}; + backend = DefaultBackend(), allocator = DefaultAllocator() ) where {S, N, H} # sanity check @assert H == N - 3 @@ -67,12 +70,13 @@ end pepo_es..., ) - return macroexpand(@__MODULE__, :(return @autoopt @tensor $GL´_e := $rhs)) + return macroexpand(@__MODULE__, :(return @autoopt @tensor backend = $backend allocator = $allocator $GL´_e := $rhs)) end @generated function mps_transfer_right( GR::GenericMPSTensor{S, N}, O::PEPOSandwich{H}, - A::GenericMPSTensor{S, N}, Ā::GenericMPSTensor{S, N}, + A::GenericMPSTensor{S, N}, Ā::GenericMPSTensor{S, N}; + backend = DefaultBackend(), allocator = DefaultAllocator() ) where {S, N, H} # sanity check @assert H == N - 3 @@ -91,7 +95,7 @@ end pepo_es..., ) - return macroexpand(@__MODULE__, :(return @autoopt @tensor $GR´_e := $rhs)) + return macroexpand(@__MODULE__, :(return @autoopt @tensor backend = $backend allocator = $allocator $GR´_e := $rhs)) end @generated function environment_overlap( From 2cca8bc6c796e47cf14dfbfaf47ac460e2f77828 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 21 Sep 2026 11:37:51 -0400 Subject: [PATCH 087/102] Test fixes --- .../contractions/vumps_contractions.jl | 4 ++-- test/testsuite/boundarymps/vumps.jl | 16 ++++++++++------ test/testsuite/bp/gaugefix.jl | 2 +- test/testsuite/utility/eigh_wrapper.jl | 6 +++--- test/utility/eigh_wrapper.jl | 3 ++- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/algorithms/contractions/vumps_contractions.jl b/src/algorithms/contractions/vumps_contractions.jl index 27ac960d5..bee3cbd0f 100644 --- a/src/algorithms/contractions/vumps_contractions.jl +++ b/src/algorithms/contractions/vumps_contractions.jl @@ -70,7 +70,7 @@ end pepo_es..., ) - return macroexpand(@__MODULE__, :(return @autoopt @tensor backend = $backend allocator = $allocator $GL´_e := $rhs)) + return macroexpand(@__MODULE__, :(return @autoopt @tensor backend = backend allocator = allocator $GL´_e := $rhs)) end @generated function mps_transfer_right( @@ -95,7 +95,7 @@ end pepo_es..., ) - return macroexpand(@__MODULE__, :(return @autoopt @tensor backend = $backend allocator = $allocator $GR´_e := $rhs)) + return macroexpand(@__MODULE__, :(return @autoopt @tensor backend = backend allocator = allocator $GR´_e := $rhs)) end @generated function environment_overlap( diff --git a/test/testsuite/boundarymps/vumps.jl b/test/testsuite/boundarymps/vumps.jl index 50c2ead4d..9f8545750 100644 --- a/test/testsuite/boundarymps/vumps.jl +++ b/test/testsuite/boundarymps/vumps.jl @@ -50,7 +50,7 @@ function boundary_mps_two_two_peps(AT) T = PEPSKit.MultilineTransferPEPS(psi, 1) @test storagetype(T) <: AT # 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(AT, initialize_mps(rand, scalartype(T), T, fill(ComplexSpace(20), 2, 2))) @test storagetype(mps) <: AT mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(prod(expectation_value(mps, T))) @@ -78,7 +78,7 @@ function boundary_mps_fermionic_peps(AT) foreach(V -> (@test V == D ⊗ D'), physicalspace(T)) # compare boundary MPS contraction to CTMRG contraction - mps = initialize_mps(T, [χ]) + mps = adapt(AT, initialize_mps(T, [χ])) @test storagetype(mps) <: AT mps, env, ϵ = leading_boundary(mps, T, vumps_alg) N_vumps = abs(prod(expectation_value(mps, T))) @@ -130,21 +130,25 @@ function boundary_mps_pepo_runthrough(AT) # single-layer PEPO O = ising_pepo(1) psi = adapt(AT, PEPSKit.initializePEPS(O, Vpeps)) - T = InfiniteTransferPEPO(psi, O, 1, 1) + @test storagetype(psi) <: AT + T = InfiniteTransferPEPO(psi, adapt(AT, O), 1, 1) + @test storagetype(T) <: AT foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpeps'), physicalspace(T)) - mps = initialize_mps(rand, scalartype(T), T, [ComplexSpace(10)]) + mps = adapt(AT, initialize_mps(rand, scalartype(T), T, [ComplexSpace(10)])) @test storagetype(mps) <: AT 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 = initializePEPS(O2, Vpeps) + psi2 = adapt(AT, initializePEPS(O2, Vpeps)) + @test storagetype(psi2) <: AT T2 = InfiniteTransferPEPO(psi, O2, 1, 1) foreach(V -> (@test V == Vpeps ⊗ Vpepo ⊗ Vpepo ⊗ Vpeps'), physicalspace(T2)) - mps2 = initialize_mps(rand, scalartype(T2), T2, [ComplexSpace(8)]) + mps2 = adapt(AT, initialize_mps(rand, scalartype(T2), T2, [ComplexSpace(8)])) + @test storagetype(mps2) <: AT mps2, env2, ϵ = leading_boundary(mps2, T2, vumps_alg) f = abs(prod(expectation_value(mps2, T2))) end diff --git a/test/testsuite/bp/gaugefix.jl b/test/testsuite/bp/gaugefix.jl index db1e6afa3..e0eba4d87 100644 --- a/test/testsuite/bp/gaugefix.jl +++ b/test/testsuite/bp/gaugefix.jl @@ -88,7 +88,7 @@ function bp_gaugefix_bp_vs_su(AT) 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(space(X, 1)), 1) + @test tmp ≈ twistdual(adapt(AT, TensorKit.id(space(X, 1))), 1) # BP should differ from SU only by a unitary gauge transformation @test inv(X) ≈ adjoint(X) ≈ Xinv end diff --git a/test/testsuite/utility/eigh_wrapper.jl b/test/testsuite/utility/eigh_wrapper.jl index d41fafcdb..24357e49d 100644 --- a/test/testsuite/utility/eigh_wrapper.jl +++ b/test/testsuite/utility/eigh_wrapper.jl @@ -15,7 +15,7 @@ function lossfun(A, alg, R = randn(space(A)), trunc = notrunc()) return real(dot(R, V * V')) + dot(D, D) # Overlap with random tensor R is gauge-invariant and differentiable end -function utility_eigh_wrapper(AT) +function utility_eigh_wrapper(AT; default_alg = :QRIteration) return @testset "eigh_wrapper ($AT)" begin dtype = ComplexF64 n = 20 @@ -28,8 +28,8 @@ function utility_eigh_wrapper(AT) R = adapt(AT, 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 = default_alg), rrule_alg = (; alg = :FullPullback)) + trunc_alg = EighAdjoint(; fwd_alg = (; alg = default_alg), rrule_alg = (; alg = :TruncPullback)) iter_alg = EighAdjoint(; fwd_alg = (; alg = :Lanczos), rrule_alg = (; alg = :TruncPullback)) @testset "Non-truncated eigh" begin diff --git a/test/utility/eigh_wrapper.jl b/test/utility/eigh_wrapper.jl index 6c882b483..4ba4d8fe0 100644 --- a/test/utility/eigh_wrapper.jl +++ b/test/utility/eigh_wrapper.jl @@ -11,7 +11,8 @@ if !is_buildkite end if CUDA.functional() - TestSuite.utility_eigh_wrapper(CuArray) + # CUSOLVER doesn't provide QRIteration for eigh + TestSuite.utility_eigh_wrapper(CuArray; default_alg = :DivideAndConquer) end if AMDGPU.functional() From 2a55870b85e44d4420352112d3e379bf8a3e1172 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 21 Sep 2026 12:12:07 -0400 Subject: [PATCH 088/102] Fix bp/unitcell test --- test/testsuite/bp/unitcell.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testsuite/bp/unitcell.jl b/test/testsuite/bp/unitcell.jl index f64b8b7ab..6c178c074 100644 --- a/test/testsuite/bp/unitcell.jl +++ b/test/testsuite/bp/unitcell.jl @@ -23,7 +23,7 @@ function test_unitcell(AT, unitcell, Pspaces, Nspaces, Espaces) # compute random expecation value to test matching bonds random_op = LocalOperator( Pspaces, ( - (c,) => randn(elt, Pspaces[c], Pspaces[c]) + (c,) => adapt(AT, randn(elt, Pspaces[c], Pspaces[c])) for c in CartesianIndices(unitcell) )..., ) From bb2e924cf11f725a5e2c324fbd34e8d00ca07eee Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 22 Sep 2026 01:31:21 -0400 Subject: [PATCH 089/102] More incremental fixes --- src/algorithms/time_evolution/simpleupdate3site.jl | 4 ++-- test/testsuite/gradients/ctmrg_gradients.jl | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index da79055e2..2b5b87673 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -1,6 +1,6 @@ function _fuse_physicalspaces(O::GenericMPSTensor{S, 5}) where {S <: ElementarySpace} V1, V2 = codomain(O, 2), codomain(O, 3) - F = isomorphism(Int, fuse(V1, V2), V1 ⊗ V2) + F = isomorphism(similarstoragetype(O, Int), fuse(V1, V2), V1 ⊗ V2) @plansor O_fused[-1 -2 -4 -5; -6] := F[-2; 2 3] * O[-1 2 3 -4 -5; -6] return O_fused, F end @@ -8,7 +8,7 @@ end function _unfuse_physicalspace( O::GenericMPSTensor{S, 4}, Vout::ElementarySpace, Vin::ElementarySpace = Vout' ) where {S <: ElementarySpace} - F = isomorphism(Int, Vout ⊗ Vin, fuse(Vout ⊗ Vin)) + F = isomorphism(similarstoragetype(O, Int), Vout ⊗ Vin, fuse(Vout ⊗ Vin)) @plansor O_unfused[-1 -2 -3 -4 -5; -6] := F[-2 -3; 1] * O[-1 1 -4 -5; -6] return O_unfused, F end diff --git a/test/testsuite/gradients/ctmrg_gradients.jl b/test/testsuite/gradients/ctmrg_gradients.jl index c6c9e995b..4cdef7177 100644 --- a/test/testsuite/gradients/ctmrg_gradients.jl +++ b/test/testsuite/gradients/ctmrg_gradients.jl @@ -95,6 +95,7 @@ function gradients_asymmetric(AT) @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) + model = adapt(AT, models[i]) dir = adapt(AT, InfinitePEPS(Pspace, Vspace)) psi = adapt(AT, InfinitePEPS(Pspace, Vspace)) # instantiate to avoid having to type this twice... @@ -128,7 +129,7 @@ function gradients_asymmetric(AT) concrete_ctmrg_alg; alg_rrule = concrete_gradient_alg, ) - return cost_function(psi, env2, models[i]) + return cost_function(psi, env2, model) end return E, only(g) From 381f3b213c72a64f44d3e0cef1d051d8775eb530 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 22 Sep 2026 02:31:34 -0400 Subject: [PATCH 090/102] Another missed AT in tf_ising_finiteT --- test/testsuite/timeevol/tf_ising_finiteT.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/testsuite/timeevol/tf_ising_finiteT.jl b/test/testsuite/timeevol/tf_ising_finiteT.jl index b6e2fce53..51ff21c07 100644 --- a/test/testsuite/timeevol/tf_ising_finiteT.jl +++ b/test/testsuite/timeevol/tf_ising_finiteT.jl @@ -19,11 +19,11 @@ function converge_env(state, χ::Int) return env end -function measure_mag(pepo::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) +function measure_mag(AT, pepo::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) r, c = 1, 1 lattice = physicalspace(pepo) - Mx = LocalOperator(lattice, ((r, c),) => σˣ(Float64, Trivial)) - Mz = LocalOperator(lattice, ((r, c),) => σᶻ(Float64, Trivial)) + Mx = LocalOperator(lattice, ((r, c),) => adapt(AT, σˣ(Float64, Trivial))) + Mz = LocalOperator(lattice, ((r, c),) => adapt(AT, σᶻ(Float64, Trivial))) if purified magx = expectation_value(pepo, Mx, pepo, env) magz = expectation_value(pepo, Mz, pepo, env) @@ -61,7 +61,7 @@ function timeevol_ising_finiteT(AT) pepo, = gauge_fix(pepo, BPGauge(), bp_env) env = converge_env(InfinitePartitionFunction(pepo), 16) - result_β = measure_mag(pepo, env) + result_β = measure_mag(AT, pepo, env) @info "tr(σ(x,z)ρ) at T = $(1 / β): $(result_β)." @test β ≈ info.t @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) @@ -70,7 +70,7 @@ function timeevol_ising_finiteT(AT) pepo2, = compress((pepo, pepo), LocalTruncation(trunc_pepo)) normalize!.(pepo2.A) env2 = converge_env(InfinitePartitionFunction(pepo2), 16) - result_2β = measure_mag(pepo2, env2) + result_2β = measure_mag(AT, pepo2, env2) @info "tr(σ(x,z)ρ) at T = $(1 / (2β)): $(result_2β)." @test isapprox(abs.(result_2β), bm_2β, rtol = 5.0e-3) @@ -78,7 +78,7 @@ function timeevol_ising_finiteT(AT) 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) + result_2β′ = measure_mag(AT, 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) From f5c414fe92b6d55fa8451f362a01164440ee1879 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 22 Sep 2026 07:06:13 -0400 Subject: [PATCH 091/102] Various ctmrg fixes --- test/ctmrg/fixed_iterscheme.jl | 8 +++++--- test/ctmrg/flavors.jl | 3 ++- test/testsuite/ctmrg/fixed_iterscheme.jl | 16 ++++++++-------- test/testsuite/ctmrg/flavors.jl | 10 +++++----- test/testsuite/ctmrg/initialization.jl | 2 +- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 63776bdba..84b0be40d 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -12,9 +12,11 @@ if !is_buildkite end if CUDA.functional() - TestSuite.ctmrg_fixed_iterscheme_asymmetric(CuArray) - TestSuite.ctmrg_fixed_iterscheme_c4v(CuArray) - TestSuite.ctmrg_fixed_iterscheme_divide_and_conquer(CuArray) + TestSuite.ctmrg_fixed_iterscheme_asymmetric(CuArray; svd_alg = :QRIteration) + # CUSOLVER doesn't provide heev + TestSuite.ctmrg_fixed_iterscheme_c4v(CuArray; eigh_alg = :DivideAndConquer) + # CUSOLVER doesn't provide gesdd + #TestSuite.ctmrg_fixed_iterscheme_divide_and_conquer(CuArray) end if AMDGPU.functional() diff --git a/test/ctmrg/flavors.jl b/test/ctmrg/flavors.jl index 0f1b05dc7..e0b045c7c 100644 --- a/test/ctmrg/flavors.jl +++ b/test/ctmrg/flavors.jl @@ -14,7 +14,8 @@ end if CUDA.functional() TestSuite.ctmrg_flavors_unitcells(CuArray) TestSuite.ctmrg_flavors_fixedspace_truncation(CuArray) - TestSuite.ctmrg_flavors_c4v(CuArray) + # CUSOLVER doesn't provide heev + TestSuite.ctmrg_flavors_c4v(CuArray; eigh_alg = :DivideAndConquer) end if AMDGPU.functional() diff --git a/test/testsuite/ctmrg/fixed_iterscheme.jl b/test/testsuite/ctmrg/fixed_iterscheme.jl index a360aa70a..f0f982283 100644 --- a/test/testsuite/ctmrg/fixed_iterscheme.jl +++ b/test/testsuite/ctmrg/fixed_iterscheme.jl @@ -20,12 +20,12 @@ using PEPSKit.Defaults: ctmrg_tol # initialize parameters D = 2 χ = 16 -svd_algs = [(; alg = :DivideAndConquer), (; alg = :GKL)] projector_algs_asymm = [:HalfInfiniteProjector] #, :FullInfiniteProjector] unitcells = [(1, 1), (3, 4)] atol = 1.0e-5 -function ctmrg_fixed_iterscheme_asymmetric(AT) +function ctmrg_fixed_iterscheme_asymmetric(AT; svd_alg = :DivideAndConquer) + svd_algs = [(; alg = svd_alg), (; alg = :GKL)] # test for element-wise convergence after application of fixed step return @testset "$unitcell unit cell with $(decomposition_alg.alg) and $projector_alg ($AT)" for ( unitcell, decomposition_alg, projector_alg, @@ -61,12 +61,12 @@ function ctmrg_fixed_iterscheme_asymmetric(AT) end # test same thing for C4v CTMRG -c4v_algs = [ - (:C4vQRProjector, (; alg = :Householder)), - (:C4vEighProjector, (; alg = :QRIteration)), - (:C4vEighProjector, (; alg = :Lanczos)), -] -function ctmrg_fixed_iterscheme_c4v(AT) +function ctmrg_fixed_iterscheme_c4v(AT; eigh_alg = :QRIteration) + c4v_algs = [ + (:C4vQRProjector, (; alg = :Householder)), + (:C4vEighProjector, (; alg = eigh_alg)), + (:C4vEighProjector, (; alg = :Lanczos)), + ] return @testset "$(decomposition_alg.alg) and $projector_alg ($AT)" for (projector_alg, decomposition_alg) in c4v_algs # initialize states diff --git a/test/testsuite/ctmrg/flavors.jl b/test/testsuite/ctmrg/flavors.jl index ef8833b56..10fd4c524 100644 --- a/test/testsuite/ctmrg/flavors.jl +++ b/test/testsuite/ctmrg/flavors.jl @@ -11,10 +11,6 @@ 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] function ctmrg_flavors_unitcells(AT) @@ -70,7 +66,11 @@ function ctmrg_flavors_fixedspace_truncation(AT) end end -function ctmrg_flavors_c4v(AT) +function ctmrg_flavors_c4v(AT; eigh_alg = :QRIteration) + projector_algs_c4v = [ + (:C4vQRProjector, :Householder), + (:C4vEighProjector, eigh_alg), (:C4vEighProjector, :Lanczos), + ] return @testset "C4v with ($T) - ($projector_alg, $decomp_alg) ($AT)" for (T, (projector_alg, decomp_alg)) in Iterators.product(Ts, projector_algs_c4v) diff --git a/test/testsuite/ctmrg/initialization.jl b/test/testsuite/ctmrg/initialization.jl index 927786119..316bb3dd5 100644 --- a/test/testsuite/ctmrg/initialization.jl +++ b/test/testsuite/ctmrg/initialization.jl @@ -48,7 +48,7 @@ function ctmrg_initialization_critical_ising(AT) @test info.convergence_error ≤ tol # specific custom starting product state - p_data = ComplexF64[1; 0;;] + p_data = adapt(AT, ComplexF64[1; 0;;]) p = 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) From b5b7c246d170502a607933b6cda5f0212f19fa1c Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 22 Sep 2026 07:17:06 -0400 Subject: [PATCH 092/102] Formatter --- test/ctmrg/fixed_iterscheme.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 84b0be40d..81f6a969c 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -13,7 +13,7 @@ end if CUDA.functional() TestSuite.ctmrg_fixed_iterscheme_asymmetric(CuArray; svd_alg = :QRIteration) - # CUSOLVER doesn't provide heev + # CUSOLVER doesn't provide heev TestSuite.ctmrg_fixed_iterscheme_c4v(CuArray; eigh_alg = :DivideAndConquer) # CUSOLVER doesn't provide gesdd #TestSuite.ctmrg_fixed_iterscheme_divide_and_conquer(CuArray) From 148a6982371b8c2ba87cfa853c865c42c3b1e698 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 22 Sep 2026 10:16:47 -0400 Subject: [PATCH 093/102] Fix the c4v gradients --- src/algorithms/optimization/implicit_differentiation.jl | 6 +++--- test/testsuite/gradients/c4v_ctmrg_gradients.jl | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/algorithms/optimization/implicit_differentiation.jl b/src/algorithms/optimization/implicit_differentiation.jl index f00632504..1553df987 100644 --- a/src/algorithms/optimization/implicit_differentiation.jl +++ b/src/algorithms/optimization/implicit_differentiation.jl @@ -496,7 +496,7 @@ function _rrule( UL = left_null(U) # instantiate the differentiable variables corresponding to the intermediate projector of the contraction algorithm - u = zeros(scalartype(U), space(UL, numind(UL))' ← space(U, numind(U))') + u = zeros(storagetype(U), space(UL, numind(UL))' ← space(U, numind(U))') # prepare pullback of C4v CTMRG environment constructor (artefact of reusing asymmetric environment type for C4v symmetric contraction) _, c4v_env_vjp = rrule_via_ad(config, CTMRGEnv, C, E) @@ -640,10 +640,10 @@ function PEPSKit._rrule( # instantiate the variables used in the characteristic equations u = map(zip(U, UL)) do (Uc, ULc) - return zeros(scalartype(Uc), space(ULc, numind(ULc))' ← space(Uc, numind(Uc))') + return zeros(storagetype(Uc), space(ULc, numind(ULc))' ← space(Uc, numind(Uc))') end v = map(zip(V, VR)) do (Vc, VRc) - return zeros(scalartype(Vc), space(Vc, 1) ← space(VRc, 1)) + return zeros(storagetype(Vc), space(Vc, 1) ← space(VRc, 1)) end is = sdiag_pow.(s, -1) # also treat them as general complex tensors diff --git a/test/testsuite/gradients/c4v_ctmrg_gradients.jl b/test/testsuite/gradients/c4v_ctmrg_gradients.jl index 4f6cd5db4..882f16631 100644 --- a/test/testsuite/gradients/c4v_ctmrg_gradients.jl +++ b/test/testsuite/gradients/c4v_ctmrg_gradients.jl @@ -89,6 +89,7 @@ function gradients_c4v(AT) Random.seed!(sd) dir = adapt(AT, InfinitePEPS(Pspace, Vspace)) psi = adapt(AT, InfinitePEPS(Pspace, Vspace)) + model = adapt(AT, models[i]) symmetrize!(psi, symmetry) symmetrize!(dir, symmetry) # instantiate to avoid having to type this twice... @@ -123,7 +124,7 @@ function gradients_c4v(AT) contrete_ctmrg_alg; alg_rrule = concrete_gradient_alg, ) - return cost_function(psi, env2, models[i]) + return cost_function(psi, env2, model) end g = only(g) symmetrize!(g, symmetry) From b16a40fc760a0490df86752e126fe00599f3edb1 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 23 Sep 2026 04:35:48 -0400 Subject: [PATCH 094/102] 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 d3fb376a1..d61d0e432 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -136,6 +136,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 0f304ca251e6e551399b180315a697e8027d6552 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 23 Sep 2026 10:03:33 -0400 Subject: [PATCH 095/102] Trim down the GPU tests so they run faster on CI --- test/ctmrg/flavors.jl | 12 ++++----- test/ctmrg/gaugefix.jl | 8 +++--- test/ctmrg/unitcell.jl | 8 +++--- test/gradients/c4v_ctmrg_gradients.jl | 4 +-- test/gradients/ctmrg_gradients.jl | 4 +-- test/testsuite/ctmrg/flavors.jl | 25 ++++++++++++----- test/testsuite/ctmrg/gaugefix.jl | 27 ++++++++++++++----- test/testsuite/ctmrg/unitcell.jl | 12 ++++++--- .../gradients/c4v_ctmrg_gradients.jl | 20 +++++++++++--- test/testsuite/gradients/ctmrg_gradients.jl | 24 ++++++++++++++--- 10 files changed, 103 insertions(+), 41 deletions(-) diff --git a/test/ctmrg/flavors.jl b/test/ctmrg/flavors.jl index e0b045c7c..93811981c 100644 --- a/test/ctmrg/flavors.jl +++ b/test/ctmrg/flavors.jl @@ -12,14 +12,14 @@ if !is_buildkite end if CUDA.functional() - TestSuite.ctmrg_flavors_unitcells(CuArray) - TestSuite.ctmrg_flavors_fixedspace_truncation(CuArray) + TestSuite.ctmrg_flavors_unitcells(CuArray; minimal = true) + TestSuite.ctmrg_flavors_fixedspace_truncation(CuArray; minimal = true) # CUSOLVER doesn't provide heev - TestSuite.ctmrg_flavors_c4v(CuArray; eigh_alg = :DivideAndConquer) + TestSuite.ctmrg_flavors_c4v(CuArray; eigh_alg = :DivideAndConquer, minimal = true) end if AMDGPU.functional() - TestSuite.ctmrg_flavors_unitcells(ROCArray) - TestSuite.ctmrg_flavors_fixedspace_truncation(ROCArray) - TestSuite.ctmrg_flavors_c4v(ROCArray) + TestSuite.ctmrg_flavors_unitcells(ROCArray; minimal = true) + TestSuite.ctmrg_flavors_fixedspace_truncation(ROCArray; minimal = true) + TestSuite.ctmrg_flavors_c4v(ROCArray; minimal = true) end diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 7fc63a833..94c11885b 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -11,11 +11,11 @@ if !is_buildkite end if CUDA.functional() - TestSuite.ctmrg_gaugefix_asymmetric(CuArray) - TestSuite.ctmrg_gaugefix_c4v(CuArray) + TestSuite.ctmrg_gaugefix_asymmetric(CuArray; minimal = true) + TestSuite.ctmrg_gaugefix_c4v(CuArray; minimal = true) end if AMDGPU.functional() - TestSuite.ctmrg_gaugefix_asymmetric(ROCArray) - TestSuite.ctmrg_gaugefix_c4v(ROCArray) + TestSuite.ctmrg_gaugefix_asymmetric(ROCArray; minimal = true) + TestSuite.ctmrg_gaugefix_c4v(ROCArray; minimal = true) end diff --git a/test/ctmrg/unitcell.jl b/test/ctmrg/unitcell.jl index 4ff5e4c76..53c80b787 100644 --- a/test/ctmrg/unitcell.jl +++ b/test/ctmrg/unitcell.jl @@ -12,11 +12,11 @@ if !is_buildkite end if CUDA.functional() - TestSuite.ctmrg_unitcell_random_cartesian_spaces(CuArray) - TestSuite.ctmrg_unitcell_specific_u1_spaces(CuArray) + TestSuite.ctmrg_unitcell_random_cartesian_spaces(CuArray; minimal = true) + TestSuite.ctmrg_unitcell_specific_u1_spaces(CuArray; minimal = true) end if AMDGPU.functional() - TestSuite.ctmrg_unitcell_random_cartesian_spaces(ROCArray) - TestSuite.ctmrg_unitcell_specific_u1_spaces(ROCArray) + TestSuite.ctmrg_unitcell_random_cartesian_spaces(ROCArray; minimal = true) + TestSuite.ctmrg_unitcell_specific_u1_spaces(ROCArray; minimal = true) end diff --git a/test/gradients/c4v_ctmrg_gradients.jl b/test/gradients/c4v_ctmrg_gradients.jl index ea9a38a58..bbb900dcf 100644 --- a/test/gradients/c4v_ctmrg_gradients.jl +++ b/test/gradients/c4v_ctmrg_gradients.jl @@ -10,9 +10,9 @@ if !is_buildkite end if CUDA.functional() - TestSuite.gradients_c4v(CuArray) + TestSuite.gradients_c4v(CuArray; minimal = true) end if AMDGPU.functional() - TestSuite.gradients_c4v(ROCArray) + TestSuite.gradients_c4v(ROCArray; minimal = true) end diff --git a/test/gradients/ctmrg_gradients.jl b/test/gradients/ctmrg_gradients.jl index 73135e790..7744a93c9 100644 --- a/test/gradients/ctmrg_gradients.jl +++ b/test/gradients/ctmrg_gradients.jl @@ -11,11 +11,11 @@ if !is_buildkite end if CUDA.functional() - TestSuite.gradients_asymmetric(CuArray) + TestSuite.gradients_asymmetric(CuArray; minimal = true) TestSuite.gradients_asymmetric_276(CuArray) end if AMDGPU.functional() - TestSuite.gradients_asymmetric(ROCArray) + TestSuite.gradients_asymmetric(ROCArray; minimal = true) TestSuite.gradients_asymmetric_276(ROCArray) end diff --git a/test/testsuite/ctmrg/flavors.jl b/test/testsuite/ctmrg/flavors.jl index 10fd4c524..a849124a9 100644 --- a/test/testsuite/ctmrg/flavors.jl +++ b/test/testsuite/ctmrg/flavors.jl @@ -13,9 +13,15 @@ unitcells = [(1, 1), (3, 4)] projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] Ts = [Float64, ComplexF64] -function ctmrg_flavors_unitcells(AT) +# minimal subsets of the combinations tested below which still cover every option value at least once +minimal_combinations_unitcells = [((1, 1), :HalfInfiniteProjector), ((3, 4), :FullInfiniteProjector)] +minimal_combinations_fixedspace = [ + (:SequentialCTMRG, :FullInfiniteProjector), (:SimultaneousCTMRG, :HalfInfiniteProjector), +] + +function ctmrg_flavors_unitcells(AT; minimal::Bool = false) return @testset "$(unitcell) unit cell with $projector_alg ($AT)" for (unitcell, projector_alg) in - Iterators.product(unitcells, projector_algs_asymm) + (minimal ? minimal_combinations_unitcells : Iterators.product(unitcells, projector_algs_asymm)) # compute environments Random.seed!(32350283290358) psi = adapt(AT, InfinitePEPS(ComplexSpace(2), ComplexSpace(D); unitcell)) @@ -48,10 +54,12 @@ function ctmrg_flavors_unitcells(AT) end end -function ctmrg_flavors_fixedspace_truncation(AT) +function ctmrg_flavors_fixedspace_truncation(AT; minimal::Bool = false) # test fixedspace actually fixes space - return @testset "Fixedspace truncation using $alg and $projector_alg ($AT)" for (alg, projector_alg) in - Iterators.product([:SequentialCTMRG, :SimultaneousCTMRG], projector_algs_asymm) + return @testset "Fixedspace truncation using $alg and $projector_alg ($AT)" for (alg, projector_alg) in ( + minimal ? minimal_combinations_fixedspace : + 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(AT, InfinitePEPS(Ds, Ds, Ds)) @@ -66,13 +74,16 @@ function ctmrg_flavors_fixedspace_truncation(AT) end end -function ctmrg_flavors_c4v(AT; eigh_alg = :QRIteration) +function ctmrg_flavors_c4v(AT; eigh_alg = :QRIteration, minimal::Bool = false) projector_algs_c4v = [ (:C4vQRProjector, :Householder), (:C4vEighProjector, eigh_alg), (:C4vEighProjector, :Lanczos), ] - return @testset "C4v with ($T) - ($projector_alg, $decomp_alg) ($AT)" for (T, (projector_alg, decomp_alg)) in + # minimal: test each projector alg once, alternating between scalar types + combinations = minimal ? zip(Iterators.cycle(Ts), projector_algs_c4v) : Iterators.product(Ts, projector_algs_c4v) + return @testset "C4v with ($T) - ($projector_alg, $decomp_alg) ($AT)" for (T, (projector_alg, decomp_alg)) in + combinations Random.seed!(29358293829382) symm = RotateReflect() diff --git a/test/testsuite/ctmrg/gaugefix.jl b/test/testsuite/ctmrg/gaugefix.jl index ce6597734..89b933d65 100644 --- a/test/testsuite/ctmrg/gaugefix.jl +++ b/test/testsuite/ctmrg/gaugefix.jl @@ -15,6 +15,17 @@ projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] projector_algs_c4v = [:C4vEighProjector, :C4vQRProjector] gauge_algs_asymm = [ScramblingEnvGauge()] gauge_algs_c4v = [ScramblingEnvGaugeC4v()] + +# minimal subsets of the combinations above which still cover every option value at least once +minimal_combinations_asymm = [ + (ComplexSpace, Float64, (1, 1), SequentialCTMRG, :HalfInfiniteProjector, ScramblingEnvGauge()), + (Z2Space, ComplexF64, (2, 2), SimultaneousCTMRG, :FullInfiniteProjector, ScramblingEnvGauge()), + (ComplexSpace, ComplexF64, (3, 2), SimultaneousCTMRG, :HalfInfiniteProjector, ScramblingEnvGauge()), +] +minimal_combinations_c4v = [ + (ComplexSpace, Float64, :C4vEighProjector, ScramblingEnvGaugeC4v()), + (Z2Space, ComplexF64, :C4vQRProjector, ScramblingEnvGaugeC4v()), +] tol = 1.0e-6 # large tol due to χ=6 χ = 6 atol = 1.0e-4 @@ -72,11 +83,14 @@ function _preconverged_env_c4v(S, ::Type{T}) where {T} end end -function ctmrg_gaugefix_asymmetric(AT) +function ctmrg_gaugefix_asymmetric(AT; minimal::Bool = false) return @testset "($S) - ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg) - ($gauge_alg) - ($AT)" 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 + ) in ( + minimal ? minimal_combinations_asymm : + Iterators.product( + spacetypes, scalartypes, unitcells, ctmrg_algs_asymm, projector_algs_asymm, gauge_algs_asymm + ) ) alg = ctmrg_alg(; tol, projector_alg) env_pre, psi = _preconverged_env(S, T, unitcell) @@ -92,11 +106,12 @@ function ctmrg_gaugefix_asymmetric(AT) end end -function ctmrg_gaugefix_c4v(AT) +function ctmrg_gaugefix_c4v(AT; minimal::Bool = false) return @testset "($S) - ($T) - ($projector_alg) - ($gauge_alg) - ($AT)" for ( S, T, projector_alg, gauge_alg, - ) in Iterators.product( - spacetypes, scalartypes, projector_algs_c4v, gauge_algs_c4v + ) in ( + minimal ? minimal_combinations_c4v : + Iterators.product(spacetypes, scalartypes, projector_algs_c4v, gauge_algs_c4v) ) alg = C4vCTMRG(; tol, projector_alg) env_pre, psi = _preconverged_env_c4v(S, T) diff --git a/test/testsuite/ctmrg/unitcell.jl b/test/testsuite/ctmrg/unitcell.jl index 848e1ed6e..fb7b24e70 100644 --- a/test/testsuite/ctmrg/unitcell.jl +++ b/test/testsuite/ctmrg/unitcell.jl @@ -12,6 +12,8 @@ ctm_algs = [ SimultaneousCTMRG(; projector_alg = :HalfInfiniteProjector), SimultaneousCTMRG(; projector_alg = :FullInfiniteProjector), ] +# minimal subset which still covers both CTMRG and both projector algs +ctm_algs_minimal = ctm_algs[[1, 4]] function test_unitcell( AT, ctm_alg, unitcell, @@ -47,9 +49,10 @@ function test_unitcell( return nothing end -function ctmrg_unitcell_random_cartesian_spaces(AT) +function ctmrg_unitcell_random_cartesian_spaces(AT; minimal::Bool = false) Random.seed!(91283219347) - return @testset "Random Cartesian spaces with $ctm_alg ($AT)" for ctm_alg in ctm_algs + return @testset "Random Cartesian spaces with $ctm_alg ($AT)" for ctm_alg in + (minimal ? ctm_algs_minimal : ctm_algs) unitcell = (3, 3) Pspaces = ComplexSpace.(rand(2:3, unitcell...)) @@ -67,9 +70,10 @@ function ctmrg_unitcell_random_cartesian_spaces(AT) end end -function ctmrg_unitcell_specific_u1_spaces(AT) +function ctmrg_unitcell_specific_u1_spaces(AT; minimal::Bool = false) Random.seed!(91283219347) - return @testset "Specific U1 spaces with $ctm_alg ($AT)" for ctm_alg in ctm_algs + return @testset "Specific U1 spaces with $ctm_alg ($AT)" for ctm_alg in + (minimal ? ctm_algs_minimal : ctm_algs) unitcell = (2, 2) PA = U1Space(-1 => 1, 0 => 1) diff --git a/test/testsuite/gradients/c4v_ctmrg_gradients.jl b/test/testsuite/gradients/c4v_ctmrg_gradients.jl index 882f16631..41cc4238d 100644 --- a/test/testsuite/gradients/c4v_ctmrg_gradients.jl +++ b/test/testsuite/gradients/c4v_ctmrg_gradients.jl @@ -29,6 +29,19 @@ gradient_algs = [[nothing, :FixedPointGradient, :ImplicitGradient]] gradient_solver_algs = [[:GeomSum, :ManualIter, :GMRES, :BiCGStab, :Arnoldi]] steps = -0.01:0.005:0.01 +# minimal subset of (ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, gradient_solver_alg) +# combinations which still covers every option value at least once per model +minimal_combinations = [ + [ + (:C4vCTMRG, :C4vEighProjector, :FullPullback, nothing, nothing), + (:C4vCTMRG, :C4vQRProjector, :FullPullback, :ImplicitGradient, :GMRES), + (:C4vCTMRG, :C4vEighProjector, :TruncPullback, :FixedPointGradient, :GeomSum), + (:C4vCTMRG, :C4vQRProjector, :FullPullback, :FixedPointGradient, :ManualIter), + (:C4vCTMRG, :C4vEighProjector, :TruncPullback, :FixedPointGradient, :BiCGStab), + (:C4vCTMRG, :C4vQRProjector, :FullPullback, :FixedPointGradient, :Arnoldi), + ], +] + # record which rrule alg is compatible with which projector alg allowed_rrule_algs = Dict( :C4vEighProjector => keys(PEPSKit.EIGH_RRULE_SYMBOLS), @@ -38,7 +51,7 @@ allowed_rrule_algs = Dict( # be selective on which configurations to test the naive gradient for naive_gradient_combinations = [(:C4vCTMRG, :C4vEighProjector, :FullPullback), (:C4vCTMRG, :C4vQRProjector, :FullPullback)] -function gradients_c4v(AT) +function gradients_c4v(AT; minimal::Bool = false) naive_gradient_done = Set() return @testset "AD C4v CTMRG energy gradients for $(names[i]) model ($AT)" verbose = true for i in eachindex( @@ -54,8 +67,9 @@ function gradients_c4v(AT) 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 + ) in ( + minimal ? minimal_combinations[i] : + Iterators.product(calgs, palgs, dalgs, galgs, gsalgs) ) # check for allowed algorithm combinations when testing naive gradient diff --git a/test/testsuite/gradients/ctmrg_gradients.jl b/test/testsuite/gradients/ctmrg_gradients.jl index 4cdef7177..19b8023f9 100644 --- a/test/testsuite/gradients/ctmrg_gradients.jl +++ b/test/testsuite/gradients/ctmrg_gradients.jl @@ -29,6 +29,23 @@ gradient_solver_algs = [ ] steps = -0.01:0.005:0.01 +# minimal subset of (ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg, gradient_solver_alg) +# combinations which still covers every option value at least once per model +minimal_combinations = [ + [ + (:SimultaneousCTMRG, :HalfInfiniteProjector, :FullPullback, nothing, nothing), + (:SimultaneousCTMRG, :HalfInfiniteProjector, :FullPullback, :ImplicitGradient, :GMRES), + (:SequentialCTMRG, :FullInfiniteProjector, :TruncPullback, :FixedPointGradient, :GeomSum), + (:SimultaneousCTMRG, :FullInfiniteProjector, :Arnoldi, :FixedPointGradient, :ManualIter), + (:SequentialCTMRG, :HalfInfiniteProjector, :FullPullback, :FixedPointGradient, :BiCGStab), + (:SimultaneousCTMRG, :HalfInfiniteProjector, :Arnoldi, :FixedPointGradient, :Arnoldi), + ], + [ + (:SimultaneousCTMRG, :HalfInfiniteProjector, :FullPullback, :ImplicitGradient, :GMRES), + (:SequentialCTMRG, :FullInfiniteProjector, :Arnoldi, :FixedPointGradient, :GeomSum), + ], +] + # don't check naive AD gradients for all algorithm combinations, since it's slow naive_gradient_combinations = [ (:SimultaneousCTMRG, :HalfInfiniteProjector, :FullPullback), @@ -46,7 +63,7 @@ function _check_disallowed_combination( return false end -function gradients_asymmetric(AT) +function gradients_asymmetric(AT; minimal::Bool = false) naive_gradient_done = Set() return @testset "AD CTMRG energy gradients for $(names[i]) model ($AT)" verbose = true for i in eachindex( @@ -62,8 +79,9 @@ function gradients_asymmetric(AT) 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 + ) in ( + minimal ? minimal_combinations[i] : + Iterators.product(calgs, palgs, salgs, galgs, gsalgs) ) # only run GMRES for the implicit gradient, and skip distinction between decomposition rrule algs From b78287fd1b3185f9dccc2b2b86b4b788c2306bf3 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 23 Sep 2026 10:06:24 -0400 Subject: [PATCH 096/102] Don't run a test on AMDGPU that needs geev --- test/bp/gaugefix.jl | 4 +++- test/testsuite/bp/gaugefix.jl | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/bp/gaugefix.jl b/test/bp/gaugefix.jl index 93254f64b..251792d15 100644 --- a/test/bp/gaugefix.jl +++ b/test/bp/gaugefix.jl @@ -14,5 +14,7 @@ if CUDA.functional() end if AMDGPU.functional() - TestSuite.bp_gaugefix_bp_vs_su(ROCArray) + # rocSOLVER doesn't offer a general eigensolver yet, + # only Hermitian + TestSuite.bp_gaugefix_bp_vs_su(ROCArray; posdef_msgs = [true]) end diff --git a/test/testsuite/bp/gaugefix.jl b/test/testsuite/bp/gaugefix.jl index e0eba4d87..991b46a52 100644 --- a/test/testsuite/bp/gaugefix.jl +++ b/test/testsuite/bp/gaugefix.jl @@ -6,10 +6,10 @@ using PEPSKit, Adapt using PEPSKit: compare_weights, random_dual!, twistdual using PEPSKit: _next, _is_bipartite -function bp_gaugefix_bp_vs_su(AT) +function bp_gaugefix_bp_vs_su(AT; posdef_msgs = [true, false]) return @testset "BP vs SU ($AT) ($S, bipartite = $(bipartite), posdef msgs = $h)" for (S, bipartite, h) in Iterators.product( - [U1Irrep, FermionParity], [true, false], [true, false] + [U1Irrep, FermionParity], [true, false], posdef_msgs ) unitcell = bipartite ? (2, 2) : (2, 3) elt = ComplexF64 From af9eaa212f96e82f92b9e6da366acc9c3f16be6e Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 23 Sep 2026 10:10:37 -0400 Subject: [PATCH 097/102] Formatter --- test/testsuite/bp/gaugefix.jl | 2 +- test/testsuite/ctmrg/gaugefix.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/testsuite/bp/gaugefix.jl b/test/testsuite/bp/gaugefix.jl index 991b46a52..c0a4df34f 100644 --- a/test/testsuite/bp/gaugefix.jl +++ b/test/testsuite/bp/gaugefix.jl @@ -9,7 +9,7 @@ using PEPSKit: _next, _is_bipartite function bp_gaugefix_bp_vs_su(AT; posdef_msgs = [true, false]) return @testset "BP vs SU ($AT) ($S, bipartite = $(bipartite), posdef msgs = $h)" for (S, bipartite, h) in Iterators.product( - [U1Irrep, FermionParity], [true, false], posdef_msgs + [U1Irrep, FermionParity], [true, false], posdef_msgs ) unitcell = bipartite ? (2, 2) : (2, 3) elt = ComplexF64 diff --git a/test/testsuite/ctmrg/gaugefix.jl b/test/testsuite/ctmrg/gaugefix.jl index 89b933d65..82f6ebe23 100644 --- a/test/testsuite/ctmrg/gaugefix.jl +++ b/test/testsuite/ctmrg/gaugefix.jl @@ -89,8 +89,8 @@ function ctmrg_gaugefix_asymmetric(AT; minimal::Bool = false) ) in ( minimal ? minimal_combinations_asymm : Iterators.product( - spacetypes, scalartypes, unitcells, ctmrg_algs_asymm, projector_algs_asymm, gauge_algs_asymm - ) + spacetypes, scalartypes, unitcells, ctmrg_algs_asymm, projector_algs_asymm, gauge_algs_asymm + ) ) alg = ctmrg_alg(; tol, projector_alg) env_pre, psi = _preconverged_env(S, T, unitcell) From 65eeaf848abf2406773e32609786aaf7c3cccd2f Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 23 Sep 2026 15:08:27 -0400 Subject: [PATCH 098/102] Fix pullback for GMRES --- .../contractions/ctmrg/characteristic_equations.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/algorithms/contractions/ctmrg/characteristic_equations.jl b/src/algorithms/contractions/ctmrg/characteristic_equations.jl index c554f95e1..666bce30f 100644 --- a/src/algorithms/contractions/ctmrg/characteristic_equations.jl +++ b/src/algorithms/contractions/ctmrg/characteristic_equations.jl @@ -272,12 +272,10 @@ function ChainRulesCore.rrule( return C, squareroot_pullback end function _squareroot_pullback(C::AbstractMatrix) - Fdata = similar(C) - for j in axes(Fdata, 2), i in axes(Fdata, 1) - # Taking the diagonal only is okay, when dA is diagonal anyway: Fdata[i, i] = 1 / (2 * conj(C[i, i])) - Fdata[i, j] = 1 / conj(C[i, i] + C[j, j]) - end - return Fdata + # Taking the diagonal only is okay, when dA is diagonal anyway: Fdata[i, i] = 1 / (2 * conj(C[i, i])) + Cd = diagview(C) + Cdt = transpose(Cd) + return @. 1 / conj(Cd + Cdt) end # take fourth root of diagonal TensorMap, but with complex non-diagonal adjoint From a2d7259ccc69b294001a607aaacb48aee53ee5b6 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 24 Sep 2026 05:08:24 -0400 Subject: [PATCH 099/102] Soothe CI a bit more --- Project.toml | 2 +- test/ctmrg/pepo.jl | 4 ++-- test/testsuite/ctmrg/pepo.jl | 19 ++++++++++++++----- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Project.toml b/Project.toml index bbecb6dc9..aadfa83ba 100644 --- a/Project.toml +++ b/Project.toml @@ -68,6 +68,6 @@ TensorKit = "0.16.5, 0.17" TensorKitTensors = "0.3.1" TensorOperations = "5.8.1" TupleTools = "1.6.0" -VectorInterface = "0.4, 0.5, 0.6" +VectorInterface = "0.4, 0.5, 0.6, 0.7" Zygote = "0.6, 0.7" julia = "1.10" diff --git a/test/ctmrg/pepo.jl b/test/ctmrg/pepo.jl index fc0ef1d47..6fd7e6c5e 100644 --- a/test/ctmrg/pepo.jl +++ b/test/ctmrg/pepo.jl @@ -11,11 +11,11 @@ if !is_buildkite end if CUDA.functional() - TestSuite.ctmrg_pepo_runthroughs(CuArray) + TestSuite.ctmrg_pepo_runthroughs(CuArray; minimal = true) TestSuite.ctmrg_pepo_fixed_point(CuArray) end if AMDGPU.functional() - TestSuite.ctmrg_pepo_runthroughs(ROCArray) + TestSuite.ctmrg_pepo_runthroughs(ROCArray; minimal = true) TestSuite.ctmrg_pepo_fixed_point(ROCArray) end diff --git a/test/testsuite/ctmrg/pepo.jl b/test/testsuite/ctmrg/pepo.jl index 1ed914d57..0258e943d 100644 --- a/test/testsuite/ctmrg/pepo.jl +++ b/test/testsuite/ctmrg/pepo.jl @@ -57,10 +57,19 @@ beta = 0.2391 # slightly lower temperature than βc ≈ 0.2216544 # cover all different flavors ctm_styles = [:SequentialCTMRG, :SimultaneousCTMRG] projector_algs = [:HalfInfiniteProjector, :FullInfiniteProjector] - -function ctmrg_pepo_runthroughs(AT) - return @testset "PEPO CTMRG runthroughs for unitcell=$(unitcell) ($AT)" for unitcell in - [(1, 1, 1), (1, 1, 2)] +unitcells = [(1, 1, 1), (1, 1, 2)] + +# minimal subset of the combinations tested below which still covers every option value at least once +minimal_combinations = [ + ((1, 1, 1), [(:SequentialCTMRG, :FullInfiniteProjector)]), + ((1, 1, 2), [(:SimultaneousCTMRG, :HalfInfiniteProjector)]), +] + +function ctmrg_pepo_runthroughs(AT; minimal::Bool = false) + return @testset "PEPO CTMRG runthroughs for unitcell=$(unitcell) ($AT)" for (unitcell, algs) in ( + minimal ? minimal_combinations : + [(uc, Iterators.product(ctm_styles, projector_algs)) for uc in unitcells] + ) Random.seed!(81812781144) O, M, E = three_dimensional_classical_ising(AT; beta) @@ -77,7 +86,7 @@ function ctmrg_pepo_runthroughs(AT) @testset "PEPO CTMRG contraction using $alg with $projector_alg" for ( alg, projector_alg, - ) in Iterators.product(ctm_styles, projector_algs) + ) in algs env, = leading_boundary(env0, n; alg, maxiter = 150, projector_alg) end end From 6e8b5de44d8ffdb9c7177bcc7550e10e11b273e7 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 24 Sep 2026 07:14:55 -0400 Subject: [PATCH 100/102] Extend timeout and use 1.13 --- .buildkite/pipeline.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index fe66c8d02..38c01e458 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -15,12 +15,12 @@ steps: agents: queue: "{{matrix.queue}}" if: build.message !~ /\[skip tests\]/ - timeout_in_minutes: 90 + timeout_in_minutes: 120 matrix: setup: julia: - "1.10" - - "1.12" + - "1.13" queue: - "cuda" - "rocm" From 37dca7ead319cecdad8335ce6c595bff95b1a5d8 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 24 Sep 2026 08:56:39 -0400 Subject: [PATCH 101/102] Fix codecov token --- .buildkite/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 38c01e458..04b807804 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,5 +1,5 @@ env: - SECRET_CODECOV_TOKEN: "MH6hHjQi7vG2V1Yfotv5/z5Dkx1k5SdyGYlGTFXiQr22XksJgsXaBuvFKUrjC7JwcpBsOVU8103LuMKl3m7VJ35WzHZrOssYycVbdGcb2kloc6xvUOsN2R5BrhCQ4Pii0l6ZeVRjCnZVkcmb0Rf4glGFyfibCrqniry8RLhblsuFKFsijRK4OxiWYEs1IvUulN+ER8tEsEtw4+ZqC5nbLGMSnUG/saPkDQOVIBscvikbKEnBcCXBheGPktF+Y/cy/1Xa+FiBPoZcypwTeAjKG1g0MqyHXjaYekb/7fekaj+hukGaeJSCXxY8KEb2IZCh+Y36Tp6y6qsIp/AdtEnCpQ==;U2FsdGVkX18WQxvGLspPwzC4aDe+U7TXU+itebTbgh8LUkE6GukxxReHYiDZ6IrBiVvSGTVJMquW0c8KsOI1pw==" + SECRET_CODECOV_TOKEN: "yXWVfuSYtwx4ksmqquaMEj0TWi42b1QZxPEwJccOBQ0fSZqNdkqGS/Z2fZ4bEPOoUGEzFPlqn75ZVe9nwf4XlbZCN0mPyYcA3DQiqAwO+9SskfwolCYBfI2RsBsVlZy7YXDHRH5KTGvelU0/fuuCt/DAk2j+Xk9HOZr4kx5RxEG1dKvBzUGB8q5phgJjvm0DUQ3w12iJMVeQVWU02P6dmrDPdiUJzoTzF0Gqpo4ZaKBfK9u58WP5Ao6vwNcffVoOiy4fiH93oXExOKoc2dqlKeAyLBHVXAy6wtKxZvNOrigyqKp0UEdmhkIe4iNNzo3bg3o0AIaS3HbkvPaU0ttg7Q==;U2FsdGVkX18oOnMGdD2pf5EYcmGkA11S5cIoWyXhsCDO9HMj9r0sNxou8l5EkreJ2FZDJs1AaIMf1X9tHs3WnQ==" steps: - label: "Julia {{matrix.julia}} -- {{matrix.queue}} / {{matrix.group}}" From f3054706951c94052b345f2286697337aec99129 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 29 Sep 2026 14:31:43 +0200 Subject: [PATCH 102/102] Try the guarding branch --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index aadfa83ba..e50f6f023 100644 --- a/Project.toml +++ b/Project.toml @@ -38,7 +38,7 @@ 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 = "ksh/gesvdx-rank1-guard", url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl"} TensorKit = {rev = "ksh/batched_svd", url = "https://github.com/QuantumKitHub/TensorKit.jl"} [extensions]