diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml deleted file mode 100644 index 4c49a86..0000000 --- a/.JuliaFormatter.toml +++ /dev/null @@ -1,3 +0,0 @@ -# See https://domluna.github.io/JuliaFormatter.jl/stable/ for a list of options -style = "blue" -indent = 2 diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index 456fa05..0614de9 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -2,7 +2,7 @@ name: "CompatHelper" on: schedule: - - cron: 0 0 * * * + - cron: '0 0 * * *' workflow_dispatch: permissions: contents: write diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index 3f78afc..1525861 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -1,11 +1,14 @@ name: "Format Check" on: - push: - branches: - - 'main' - tags: '*' - pull_request: + pull_request_target: + paths: ['**/*.jl'] + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + actions: write + pull-requests: write jobs: format-check: diff --git a/.gitignore b/.gitignore index 10593a9..7085ca8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,10 @@ .vscode/ Manifest.toml benchmark/*.json +dev/ +docs/LocalPreferences.toml docs/Manifest.toml docs/build/ docs/src/index.md +examples/LocalPreferences.toml +test/LocalPreferences.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 88bc8b4..3fc4743 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ ci: - skip: [julia-formatter] + skip: [runic] repos: - repo: https://github.com/pre-commit/pre-commit-hooks @@ -11,7 +11,7 @@ repos: - id: end-of-file-fixer exclude_types: [markdown] # incompatible with Literate.jl -- repo: "https://github.com/domluna/JuliaFormatter.jl" - rev: v2.1.6 +- repo: https://github.com/fredrikekre/runic-pre-commit + rev: v2.0.1 hooks: - - id: "julia-formatter" + - id: runic diff --git a/Project.toml b/Project.toml index 01cfba3..bd3fdcc 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ITensorMPS" uuid = "0d1a4710-d33b-49a5-8f18-73bdf49b47e2" authors = ["Matthew Fishman ", "Miles Stoudenmire "] -version = "0.3.21" +version = "0.3.22" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" diff --git a/README.md b/README.md index 173a792..4301402 100644 --- a/README.md +++ b/README.md @@ -52,39 +52,39 @@ the behavior of quantum systems. ````julia using ITensors, ITensorMPS let - # Create 100 spin-one indices - N = 100 - sites = siteinds("S=1", N) - - # Input operator terms which define - # a Hamiltonian matrix, and convert - # these terms to an MPO tensor network - # (here we make the 1D Heisenberg model) - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - H = MPO(os, sites) - - # Create an initial random matrix product state - psi0 = random_mps(sites) - - # Plan to do 5 passes or 'sweeps' of DMRG, - # setting maximum MPS internal dimensions - # for each sweep and maximum truncation cutoff - # used when adapting internal dimensions: - nsweeps = 5 - maxdim = [10, 20, 100, 100, 200] - cutoff = 1E-10 - - # Run the DMRG algorithm, returning energy - # (dominant eigenvalue) and optimized MPS - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) - println("Final energy = $energy") - - nothing + # Create 100 spin-one indices + N = 100 + sites = siteinds("S=1", N) + + # Input operator terms which define + # a Hamiltonian matrix, and convert + # these terms to an MPO tensor network + # (here we make the 1D Heisenberg model) + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + H = MPO(os, sites) + + # Create an initial random matrix product state + psi0 = random_mps(sites) + + # Plan to do 5 passes or 'sweeps' of DMRG, + # setting maximum MPS internal dimensions + # for each sweep and maximum truncation cutoff + # used when adapting internal dimensions: + nsweeps = 5 + maxdim = [10, 20, 100, 100, 200] + cutoff = 1.0e-10 + + # Run the DMRG algorithm, returning energy + # (dominant eigenvalue) and optimized MPS + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) + println("Final energy = $energy") + + nothing end # output diff --git a/docs/make.jl b/docs/make.jl index d0bb5ca..e73ab7d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,54 +2,54 @@ using ITensorMPS using ITensors using Documenter: Documenter, DocMeta, deploydocs, makedocs -DocMeta.setdocmeta!(ITensorMPS, :DocTestSetup, :(using ITensorMPS); recursive=true) -DocMeta.setdocmeta!(ITensors, :DocTestSetup, :(using ITensors); recursive=true) +DocMeta.setdocmeta!(ITensorMPS, :DocTestSetup, :(using ITensorMPS); recursive = true) +DocMeta.setdocmeta!(ITensors, :DocTestSetup, :(using ITensors); recursive = true) include("make_index.jl") makedocs(; - # Allows using ITensors.jl docstrings in ITensorMPS.jl documentation: - # https://github.com/JuliaDocs/Documenter.jl/issues/1734 - modules=[ITensorMPS, ITensors], - authors="ITensor developers and contributors", - sitename="ITensorMPS.jl", - format=Documenter.HTML(; - canonical="https://itensor.github.io/ITensorMPS.jl", - edit_link="main", - assets=["assets/favicon.ico", "assets/extras.css"], - prettyurls=false, - ), - pages=[ - "Home" => "index.md", - "Tutorials" => [ - "DMRG" => "tutorials/DMRG.md", - "Quantum Number Conserving DMRG" => "tutorials/QN_DMRG.md", - "MPS Time Evolution" => "tutorials/MPSTimeEvolution.md", + # Allows using ITensors.jl docstrings in ITensorMPS.jl documentation: + # https://github.com/JuliaDocs/Documenter.jl/issues/1734 + modules = [ITensorMPS, ITensors], + authors = "ITensor developers and contributors", + sitename = "ITensorMPS.jl", + format = Documenter.HTML(; + canonical = "https://itensor.github.io/ITensorMPS.jl", + edit_link = "main", + assets = ["assets/favicon.ico", "assets/extras.css"], + prettyurls = false, + ), + pages = [ + "Home" => "index.md", + "Tutorials" => [ + "DMRG" => "tutorials/DMRG.md", + "Quantum Number Conserving DMRG" => "tutorials/QN_DMRG.md", + "MPS Time Evolution" => "tutorials/MPSTimeEvolution.md", + ], + "Code Examples" => [ + "MPS and MPO Examples" => "examples/MPSandMPO.md", + "DMRG Examples" => "examples/DMRG.md", + "Physics (SiteType) System Examples" => "examples/Physics.md", + ], + "Documentation" => [ + "MPS and MPO" => "MPSandMPO.md", + "SiteType and op, state, val functions" => "SiteType.md", + "SiteTypes Included with ITensor" => "IncludedSiteTypes.md", + "DMRG" => [ + "DMRG.md", + "Sweeps.md", + "ProjMPO.md", + "ProjMPOSum.md", + "Observer.md", + "DMRGObserver.md", + ], + "OpSum" => "OpSum.md", + ], + "Frequently Asked Questions" => + ["DMRG FAQs" => "faq/DMRG.md", "Quantum Number (QN) FAQs" => "faq/QN.md"], + "HDF5 File Formats" => "HDF5FileFormats.md", ], - "Code Examples" => [ - "MPS and MPO Examples" => "examples/MPSandMPO.md", - "DMRG Examples" => "examples/DMRG.md", - "Physics (SiteType) System Examples" => "examples/Physics.md", - ], - "Documentation" => [ - "MPS and MPO" => "MPSandMPO.md", - "SiteType and op, state, val functions" => "SiteType.md", - "SiteTypes Included with ITensor" => "IncludedSiteTypes.md", - "DMRG" => [ - "DMRG.md", - "Sweeps.md", - "ProjMPO.md", - "ProjMPOSum.md", - "Observer.md", - "DMRGObserver.md", - ], - "OpSum" => "OpSum.md", - ], - "Frequently Asked Questions" => - ["DMRG FAQs" => "faq/DMRG.md", "Quantum Number (QN) FAQs" => "faq/QN.md"], - "HDF5 File Formats" => "HDF5FileFormats.md", - ], - warnonly=true, + warnonly = true, ) -deploydocs(; repo="github.com/ITensor/ITensorMPS.jl", devbranch="main", push_preview=true) +deploydocs(; repo = "github.com/ITensor/ITensorMPS.jl", devbranch = "main", push_preview = true) diff --git a/docs/make_index.jl b/docs/make_index.jl index 6718c7c..6fe420c 100644 --- a/docs/make_index.jl +++ b/docs/make_index.jl @@ -2,20 +2,20 @@ using Literate: Literate using ITensorMPS: ITensorMPS function ccq_logo(content) - include_ccq_logo = """ + include_ccq_logo = """ ```@raw html Flatiron Center for Computational Quantum Physics logo. Flatiron Center for Computational Quantum Physics logo. ``` """ - content = replace(content, "{CCQ_LOGO}" => include_ccq_logo) - return content + content = replace(content, "{CCQ_LOGO}" => include_ccq_logo) + return content end Literate.markdown( - joinpath(pkgdir(ITensorMPS), "examples", "README.jl"), - joinpath(pkgdir(ITensorMPS), "docs", "src"); - flavor=Literate.DocumenterFlavor(), - name="index", - postprocess=ccq_logo, + joinpath(pkgdir(ITensorMPS), "examples", "README.jl"), + joinpath(pkgdir(ITensorMPS), "docs", "src"); + flavor = Literate.DocumenterFlavor(), + name = "index", + postprocess = ccq_logo, ) diff --git a/docs/make_readme.jl b/docs/make_readme.jl index 4e6db12..f49c7b4 100644 --- a/docs/make_readme.jl +++ b/docs/make_readme.jl @@ -2,20 +2,20 @@ using Literate: Literate using ITensorMPS: ITensorMPS function ccq_logo(content) - include_ccq_logo = """ + include_ccq_logo = """ Flatiron Center for Computational Quantum Physics logo. """ - content = replace(content, "{CCQ_LOGO}" => include_ccq_logo) - return content + content = replace(content, "{CCQ_LOGO}" => include_ccq_logo) + return content end Literate.markdown( - joinpath(pkgdir(ITensorMPS), "examples", "README.jl"), - joinpath(pkgdir(ITensorMPS)); - flavor=Literate.CommonMarkFlavor(), - name="README", - postprocess=ccq_logo, + joinpath(pkgdir(ITensorMPS), "examples", "README.jl"), + joinpath(pkgdir(ITensorMPS)); + flavor = Literate.CommonMarkFlavor(), + name = "README", + postprocess = ccq_logo, ) diff --git a/docs/src/tutorials/tebd.jl b/docs/src/tutorials/tebd.jl index 7dfb452..202ba33 100644 --- a/docs/src/tutorials/tebd.jl +++ b/docs/src/tutorials/tebd.jl @@ -1,46 +1,46 @@ using ITensors, ITensorMPS let - N = 100 - cutoff = 1E-8 - tau = 0.1 - ttotal = 5.0 - - # Make an array of 'site' indices - s = siteinds("S=1/2", N; conserve_qns=true) - - # Make gates (1,2),(2,3),(3,4),... - gates = ITensor[] - for j in 1:(N - 1) - s1 = s[j] - s2 = s[j + 1] - hj = - op("Sz", s1) * op("Sz", s2) + - 1 / 2 * op("S+", s1) * op("S-", s2) + - 1 / 2 * op("S-", s1) * op("S+", s2) - Gj = exp(-1.0im * tau / 2 * hj) - push!(gates, Gj) - end - # Include gates in reverse order too - # (N,N-1),(N-1,N-2),... - append!(gates, reverse(gates)) - - # Initialize psi to be a product state (alternating up and down) - psi = MPS(s, n -> isodd(n) ? "Up" : "Dn") - - c = div(N, 2) # center site - - # Compute and print at each time step - # then apply the gates to go to the next time - for t in 0.0:tau:ttotal - Sz = expect(psi, "Sz"; sites=c) - println("$t $Sz") - - t ≈ ttotal && break - - psi = apply(gates, psi; cutoff) - normalize!(psi) - end - - return nothing + N = 100 + cutoff = 1.0e-8 + tau = 0.1 + ttotal = 5.0 + + # Make an array of 'site' indices + s = siteinds("S=1/2", N; conserve_qns = true) + + # Make gates (1,2),(2,3),(3,4),... + gates = ITensor[] + for j in 1:(N - 1) + s1 = s[j] + s2 = s[j + 1] + hj = + op("Sz", s1) * op("Sz", s2) + + 1 / 2 * op("S+", s1) * op("S-", s2) + + 1 / 2 * op("S-", s1) * op("S+", s2) + Gj = exp(-1.0im * tau / 2 * hj) + push!(gates, Gj) + end + # Include gates in reverse order too + # (N,N-1),(N-1,N-2),... + append!(gates, reverse(gates)) + + # Initialize psi to be a product state (alternating up and down) + psi = MPS(s, n -> isodd(n) ? "Up" : "Dn") + + c = div(N, 2) # center site + + # Compute and print at each time step + # then apply the gates to go to the next time + for t in 0.0:tau:ttotal + Sz = expect(psi, "Sz"; sites = c) + println("$t $Sz") + + t ≈ ttotal && break + + psi = apply(gates, psi; cutoff) + normalize!(psi) + end + + return nothing end diff --git a/examples/README.jl b/examples/README.jl index 17f8946..2a3a5c4 100644 --- a/examples/README.jl +++ b/examples/README.jl @@ -1,5 +1,5 @@ # # ITensorMPS.jl -# +# # [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://docs.itensor.org/ITensorMPS/stable/) # [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://docs.itensor.org/ITensorMPS/dev/) # [![Build Status](https://github.com/ITensor/ITensorMPS.jl/actions/workflows/Tests.yml/badge.svg?branch=main)](https://github.com/ITensor/ITensorMPS.jl/actions/workflows/Tests.yml?query=branch%3Amain) @@ -47,39 +47,39 @@ using ITensors, ITensorMPS let - ## Create 100 spin-one indices - N = 100 - sites = siteinds("S=1", N) + ## Create 100 spin-one indices + N = 100 + sites = siteinds("S=1", N) - ## Input operator terms which define - ## a Hamiltonian matrix, and convert - ## these terms to an MPO tensor network - ## (here we make the 1D Heisenberg model) - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - H = MPO(os, sites) + ## Input operator terms which define + ## a Hamiltonian matrix, and convert + ## these terms to an MPO tensor network + ## (here we make the 1D Heisenberg model) + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + H = MPO(os, sites) - ## Create an initial random matrix product state - psi0 = random_mps(sites) + ## Create an initial random matrix product state + psi0 = random_mps(sites) - ## Plan to do 5 passes or 'sweeps' of DMRG, - ## setting maximum MPS internal dimensions - ## for each sweep and maximum truncation cutoff - ## used when adapting internal dimensions: - nsweeps = 5 - maxdim = [10, 20, 100, 100, 200] - cutoff = 1E-10 + ## Plan to do 5 passes or 'sweeps' of DMRG, + ## setting maximum MPS internal dimensions + ## for each sweep and maximum truncation cutoff + ## used when adapting internal dimensions: + nsweeps = 5 + maxdim = [10, 20, 100, 100, 200] + cutoff = 1.0e-10 - ## Run the DMRG algorithm, returning energy - ## (dominant eigenvalue) and optimized MPS - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) - println("Final energy = $energy") + ## Run the DMRG algorithm, returning energy + ## (dominant eigenvalue) and optimized MPS + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) + println("Final energy = $energy") - nothing + nothing end ## output diff --git a/examples/autodiff/circuit_optimization/op.jl b/examples/autodiff/circuit_optimization/op.jl index bdb40e6..edb2f40 100644 --- a/examples/autodiff/circuit_optimization/op.jl +++ b/examples/autodiff/circuit_optimization/op.jl @@ -3,7 +3,7 @@ using Zygote s = siteind("Qubit") -f(x) = op("Ry", s; θ=x)[1, 1] +f(x) = op("Ry", s; θ = x)[1, 1] x = 0.2 @show f(x), cos(x / 2) @@ -14,38 +14,38 @@ x = 0.2 ψp = state(s, "+") function loss(x) - U = op("Ry", s; θ=x) - Uψ0 = replaceprime(U * ψ0, 1 => 0) - return -(dag(ψp) * Uψ0)[] + U = op("Ry", s; θ = x) + Uψ0 = replaceprime(U * ψ0, 1 => 0) + return -(dag(ψp) * Uψ0)[] end # Extremely simple gradient descent implementation, # where gradients are computing with automatic differentiation # using Zygote. function gradient_descent(f, x0; γ, nsteps, grad_tol) - @show γ, nsteps - x = x0 - f_x = f(x) - ∇f_x = f'(x) - step = 0 - @show step, x, f_x, ∇f_x - for step in 1:nsteps - x -= γ * ∇f_x + @show γ, nsteps + x = x0 f_x = f(x) ∇f_x = f'(x) + step = 0 @show step, x, f_x, ∇f_x - if norm(∇f_x) ≤ grad_tol - break + for step in 1:nsteps + x -= γ * ∇f_x + f_x = f(x) + ∇f_x = f'(x) + @show step, x, f_x, ∇f_x + if norm(∇f_x) ≤ grad_tol + break + end end - end - return x, f_x, ∇f_x + return x, f_x, ∇f_x end x0 = 0 γ = 2.0 # Learning rate nsteps = 30 # Number of steps of gradient descent -grad_tol = 1e-4 # Stop if gradient falls below this value -x, loss_x, ∇loss_x = gradient_descent(loss, x0; γ=γ, nsteps=nsteps, grad_tol=grad_tol) +grad_tol = 1.0e-4 # Stop if gradient falls below this value +x, loss_x, ∇loss_x = gradient_descent(loss, x0; γ = γ, nsteps = nsteps, grad_tol = grad_tol) @show x0, loss(x0) @show x, loss(x) diff --git a/examples/autodiff/circuit_optimization/state_preparation.jl b/examples/autodiff/circuit_optimization/state_preparation.jl index bb09325..ac268a2 100644 --- a/examples/autodiff/circuit_optimization/state_preparation.jl +++ b/examples/autodiff/circuit_optimization/state_preparation.jl @@ -5,23 +5,23 @@ using Zygote nsites = 20 # Number of sites nlayers = 3 # Layers of gates in the ansatz -gradtol = 1e-4 # Tolerance for stopping gradient descent +gradtol = 1.0e-4 # Tolerance for stopping gradient descent # A layer of the circuit we want to optimize function layer(nsites, θ⃗) - RY_layer = [("Ry", (n,), (θ=θ⃗[n],)) for n in 1:nsites] - CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)] - return [RY_layer; CX_layer] + RY_layer = [("Ry", (n,), (θ = θ⃗[n],)) for n in 1:nsites] + CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)] + return [RY_layer; CX_layer] end # The variational circuit we want to optimize function variational_circuit(nsites, nlayers, θ⃗) - range = 1:nsites - circuit = layer(nsites, θ⃗[range]) - for n in 1:(nlayers - 1) - circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])] - end - return circuit + range = 1:nsites + circuit = layer(nsites, θ⃗[range]) + for n in 1:(nlayers - 1) + circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])] + end + return circuit end Random.seed!(1234) @@ -35,7 +35,7 @@ Uᵗᵃʳᵍᵉᵗ = ops(𝒰ᵗᵃʳᵍᵉᵗ, s) ψ0 = MPS(s, "0") # Create the random target state -ψᵗᵃʳᵍᵉᵗ = apply(Uᵗᵃʳᵍᵉᵗ, ψ0; cutoff=1e-8) +ψᵗᵃʳᵍᵉᵗ = apply(Uᵗᵃʳᵍᵉᵗ, ψ0; cutoff = 1.0e-8) # # The loss function, a function of the gate parameters @@ -44,12 +44,12 @@ Uᵗᵃʳᵍᵉᵗ = ops(𝒰ᵗᵃʳᵍᵉᵗ, s) # loss(θ⃗) = -|⟨θ⃗ᵗᵃʳᵍᵉᵗ|U(θ⃗)|0⟩|² = -|⟨θ⃗ᵗᵃʳᵍᵉᵗ|θ⃗⟩|² # function loss(θ⃗) - nsites = length(ψ0) - s = siteinds(ψ0) - 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) - Uθ⃗ = ops(𝒰θ⃗, s) - ψθ⃗ = apply(Uθ⃗, ψ0) - return -abs(inner(ψᵗᵃʳᵍᵉᵗ, ψθ⃗))^2 + nsites = length(ψ0) + s = siteinds(ψ0) + 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) + Uθ⃗ = ops(𝒰θ⃗, s) + ψθ⃗ = apply(Uθ⃗, ψ0) + return -abs(inner(ψᵗᵃʳᵍᵉᵗ, ψθ⃗))^2 end θ⃗₀ = randn!(copy(θ⃗ᵗᵃʳᵍᵉᵗ)) @@ -57,7 +57,7 @@ end @show loss(θ⃗₀), loss(θ⃗ᵗᵃʳᵍᵉᵗ) loss_∇loss(x) = (loss(x), convert(Vector, loss'(x))) -algorithm = LBFGS(; gradtol=gradtol, verbosity=2) +algorithm = LBFGS(; gradtol = gradtol, verbosity = 2) θ⃗ₒₚₜ, lossₒₚₜ, ∇lossₒₚₜ, numfg, normgradhistory = optimize(loss_∇loss, θ⃗₀, algorithm) @show loss(θ⃗ₒₚₜ), loss(θ⃗ᵗᵃʳᵍᵉᵗ) diff --git a/examples/autodiff/circuit_optimization/vqe.jl b/examples/autodiff/circuit_optimization/vqe.jl index edaeeae..81818ce 100644 --- a/examples/autodiff/circuit_optimization/vqe.jl +++ b/examples/autodiff/circuit_optimization/vqe.jl @@ -5,41 +5,41 @@ using Zygote nsites = 4 # Number of sites nlayers = 2 # Layers of gates in the ansatz -gradtol = 1e-4 # Tolerance for stopping gradient descent +gradtol = 1.0e-4 # Tolerance for stopping gradient descent # The Hamiltonian we are minimizing function ising_hamiltonian(nsites; h) - ℋ = OpSum() - for j in 1:(nsites - 1) - ℋ -= 1, "Z", j, "Z", j + 1 - end - for j in 1:nsites - ℋ += h, "X", j - end - return ℋ + ℋ = OpSum() + for j in 1:(nsites - 1) + ℋ -= 1, "Z", j, "Z", j + 1 + end + for j in 1:nsites + ℋ += h, "X", j + end + return ℋ end # A layer of the circuit we want to optimize function layer(nsites, θ⃗) - RY_layer = [("Ry", (n,), (θ=θ⃗[n],)) for n in 1:nsites] - CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)] - return [RY_layer; CX_layer] + RY_layer = [("Ry", (n,), (θ = θ⃗[n],)) for n in 1:nsites] + CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)] + return [RY_layer; CX_layer] end # The variational circuit we want to optimize function variational_circuit(nsites, nlayers, θ⃗) - range = 1:nsites - circuit = layer(nsites, θ⃗[range]) - for n in 1:(nlayers - 1) - circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])] - end - return circuit + range = 1:nsites + circuit = layer(nsites, θ⃗[range]) + for n in 1:(nlayers - 1) + circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])] + end + return circuit end s = siteinds("Qubit", nsites) h = 1.3 -ℋ = ising_hamiltonian(nsites; h=h) +ℋ = ising_hamiltonian(nsites; h = h) H = MPO(ℋ, s) ψ0 = MPS(s, "0") @@ -50,12 +50,12 @@ H = MPO(ℋ, s) # loss(θ⃗) = ⟨0|U(θ⃗)† H U(θ⃗)|0⟩ = ⟨θ⃗|H|θ⃗⟩ # function loss(θ⃗) - nsites = length(ψ0) - s = siteinds(ψ0) - 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) - Uθ⃗ = ops(𝒰θ⃗, s) - ψθ⃗ = apply(Uθ⃗, ψ0; cutoff=1e-8) - return inner(ψθ⃗, H, ψθ⃗; cutoff=1e-8) + nsites = length(ψ0) + s = siteinds(ψ0) + 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) + Uθ⃗ = ops(𝒰θ⃗, s) + ψθ⃗ = apply(Uθ⃗, ψ0; cutoff = 1.0e-8) + return inner(ψθ⃗, H, ψθ⃗; cutoff = 1.0e-8) end Random.seed!(1234) @@ -66,14 +66,14 @@ Random.seed!(1234) println("\nOptimize circuit with gradient optimization") loss_∇loss(x) = (loss(x), convert(Vector, loss'(x))) -algorithm = LBFGS(; gradtol=1e-3, verbosity=2) +algorithm = LBFGS(; gradtol = 1.0e-3, verbosity = 2) θ⃗ₒₚₜ, lossₒₚₜ, ∇lossₒₚₜ, numfg, normgradhistory = optimize(loss_∇loss, θ⃗₀, algorithm) @show loss(θ⃗ₒₚₜ) println("\nRun DMRG as a comparison") -e_dmrg, ψ_dmrg = dmrg(H, ψ0; nsweeps=5, maxdim=10) +e_dmrg, ψ_dmrg = dmrg(H, ψ0; nsweeps = 5, maxdim = 10) println("\nCompare variational circuit energy to DMRG energy") @show loss(θ⃗ₒₚₜ), e_dmrg diff --git a/examples/autodiff/mps_autodiff.jl b/examples/autodiff/mps_autodiff.jl index 0fbc641..6bd1ea7 100644 --- a/examples/autodiff/mps_autodiff.jl +++ b/examples/autodiff/mps_autodiff.jl @@ -3,25 +3,25 @@ using OptimKit using Zygote function ising(n; J, h) - os = OpSum() - for j in 1:(n - 1) - os -= J, "Z", j, "Z", j + 1 - end - for j in 1:n - os -= h, "X", j - end - return os + os = OpSum() + for j in 1:(n - 1) + os -= J, "Z", j, "Z", j + 1 + end + for j in 1:n + os -= h, "X", j + end + return os end function loss(H, ψ) - n = length(ψ) - ψHψ = ITensor(1.0) - ψψ = ITensor(1.0) - for j in 1:n - ψHψ = ψHψ * dag(ψ[j]') * H[j] * ψ[j] - ψψ = ψψ * replaceinds(dag(ψ[j]'), s[j]' => s[j]) * ψ[j] - end - return ψHψ[] / ψψ[] + n = length(ψ) + ψHψ = ITensor(1.0) + ψψ = ITensor(1.0) + for j in 1:n + ψHψ = ψHψ * dag(ψ[j]') * H[j] * ψ[j] + ψψ = ψψ * replaceinds(dag(ψ[j]'), s[j]' => s[j]) * ψ[j] + end + return ψHψ[] / ψψ[] end n = 10 @@ -31,18 +31,18 @@ h = 0.5 # Loss function only works with `Vector{ITensor}`, # extract with `ITensorMPS.data`. -ψ0 = ITensorMPS.data(random_mps(s; linkdims=10)) +ψ0 = ITensorMPS.data(random_mps(s; linkdims = 10)) H = ITensorMPS.data(MPO(ising(n; J, h), s)) loss(ψ) = loss(H, ψ) -optimizer = LBFGS(; maxiter=25, verbosity=2) +optimizer = LBFGS(; maxiter = 25, verbosity = 2) function loss_and_grad(x) - y, (∇,) = withgradient(loss, x) - return y, ∇ + y, (∇,) = withgradient(loss, x) + return y, ∇ end ψ, fs, gs, niter, normgradhistory = optimize(loss_and_grad, ψ0, optimizer) -Edmrg, ψdmrg = dmrg(MPO(H), MPS(ψ0); nsweeps=10, cutoff=1e-8) +Edmrg, ψdmrg = dmrg(MPO(H), MPS(ψ0); nsweeps = 10, cutoff = 1.0e-8) @show loss(ψ0), norm(loss'(ψ0)) @show loss(ψ), norm(loss'(ψ)) diff --git a/examples/autodiff/ops/ops_ad.jl b/examples/autodiff/ops/ops_ad.jl index c509388..dde005d 100644 --- a/examples/autodiff/ops/ops_ad.jl +++ b/examples/autodiff/ops/ops_ad.jl @@ -5,8 +5,8 @@ using Zygote s = siteinds("S=1/2", 4) function f1(x) - y = ITensor(Op("Ry", 1; θ=x), s) - return y[1, 1] + y = ITensor(Op("Ry", 1; θ = x), s) + return y[1, 1] end @show x = 2.0 @@ -15,8 +15,8 @@ end @show f1'(x) function f2(x) - y = exp(ITensor(Op("Ry", 1; θ=x), s)) - return y[1, 1] + y = exp(ITensor(Op("Ry", 1; θ = x), s)) + return y[1, 1] end @show x = 2.0 @@ -25,8 +25,8 @@ end @show f2'(x) function f3(x) - y = Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x) - return y[1].params.θ + y = Op("Ry", 1; θ = x) + Op("Ry", 1; θ = x) + return y[1].params.θ end @show x = 2.0 @@ -35,8 +35,8 @@ end @show f3'(x) function f4(x) - y = ITensor(Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x), s) - return y[1, 1] + y = ITensor(Op("Ry", 1; θ = x) + Op("Ry", 1; θ = x), s) + return y[1, 1] end @show x = 2.0 @@ -45,8 +45,8 @@ end @show f4'(x) function f5(x) - y = exp(ITensor(Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x), s)) - return y[1, 1] + y = exp(ITensor(Op("Ry", 1; θ = x) + Op("Ry", 1; θ = x), s)) + return y[1, 1] end @show x = 2.0 @@ -55,8 +55,8 @@ end @show f5'(x) function f6(x) - y = ITensor(exp(Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x)), s) - return y[1, 1] + y = ITensor(exp(Op("Ry", 1; θ = x) + Op("Ry", 1; θ = x)), s) + return y[1, 1] end @show x = 2.0 @@ -65,8 +65,8 @@ end @show f6'(x) function f7(x) - y = ITensor(2 * Op("Ry", 1; θ=x), s) - return y[1, 1] + y = ITensor(2 * Op("Ry", 1; θ = x), s) + return y[1, 1] end @show x = 2.0 @@ -75,8 +75,8 @@ end @show f7'(x) function f8(x) - y = ITensor(2 * (Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x)), s) - return y[1, 1] + y = ITensor(2 * (Op("Ry", 1; θ = x) + Op("Ry", 1; θ = x)), s) + return y[1, 1] end @show x = 2.0 @@ -85,8 +85,8 @@ end @show f8'(x) function f9(x) - y = ITensor(Op("Ry", 1; θ=x) * Op("Ry", 2; θ=x), s) - return y[1, 1] + y = ITensor(Op("Ry", 1; θ = x) * Op("Ry", 2; θ = x), s) + return y[1, 1] end @show x = 2.0 @@ -95,8 +95,8 @@ end @show f9'(x) function f10(x) - y = ITensor(exp(-x * Op("X", 1) * Op("X", 2)), s) - return norm(y) + y = ITensor(exp(-x * Op("X", 1) * Op("X", 2)), s) + return norm(y) end @show x = 2.0 @@ -107,10 +107,10 @@ end V = random_itensor(s[1], s[2]) function f11(x) - y = exp(-x * Op("X", 1) * Op("X", 2)) - y *= exp(-x * Op("X", 1) * Op("X", 2)) - U = Prod{ITensor}(y, s) - return norm(U(V)) + y = exp(-x * Op("X", 1) * Op("X", 2)) + y *= exp(-x * Op("X", 1) * Op("X", 2)) + U = Prod{ITensor}(y, s) + return norm(U(V)) end @show x = 2.0 @@ -119,9 +119,9 @@ end @show f11'(x) function f12(x) - y = exp(-x * (Op("X", 1) + Op("Z", 1) + Op("Z", 1)); alg=Trotter{1}(1)) - U = Prod{ITensor}(y, s) - return norm(U(V)) + y = exp(-x * (Op("X", 1) + Op("Z", 1) + Op("Z", 1)); alg = Trotter{1}(1)) + U = Prod{ITensor}(y, s) + return norm(U(V)) end @show x = 2.0 diff --git a/examples/autodiff/ops/trotter_ad_1.jl b/examples/autodiff/ops/trotter_ad_1.jl index c6c8fc2..5892839 100644 --- a/examples/autodiff/ops/trotter_ad_1.jl +++ b/examples/autodiff/ops/trotter_ad_1.jl @@ -3,14 +3,14 @@ using Zygote using OptimKit function ising(n; h) - ℋ = Sum{Op}() - for j in 1:(n - 1) - ℋ -= "Z", j, "Z", j + 1 - end - for j in 1:n - ℋ += h, "X", j - end - return ℋ + ℋ = Sum{Op}() + for j in 1:(n - 1) + ℋ -= "Z", j, "Z", j + 1 + end + for j in 1:n + ℋ += h, "X", j + end + return ℋ end n = 4 @@ -20,7 +20,7 @@ h = 1.1 ℋ = ising(n; h) βᶠ = 1.0 -𝒰 = exp(-βᶠ * ℋ; alg=Trotter{1}(5)) +𝒰 = exp(-βᶠ * ℋ; alg = Trotter{1}(5)) U = Prod{ITensor}(𝒰, s) @@ -30,10 +30,10 @@ U = Prod{ITensor}(𝒰, s) Uψ = U(ψ) function loss(β) - 𝒰ᵝ = exp(-β[1] * ℋ; alg=Trotter{1}(5)) - Uᵝ = Prod{ITensor}(𝒰ᵝ, s) - Uᵝψ = Uᵝ(ψ) - return -abs(inner(Uψ, Uᵝψ))^2 / (norm(Uψ) * norm(Uᵝψ))^2 + 𝒰ᵝ = exp(-β[1] * ℋ; alg = Trotter{1}(5)) + Uᵝ = Prod{ITensor}(𝒰ᵝ, s) + Uᵝψ = Uᵝ(ψ) + return -abs(inner(Uψ, Uᵝψ))^2 / (norm(Uψ) * norm(Uᵝψ))^2 end β⁰ = [0.0] @@ -44,7 +44,7 @@ end @show loss'(βᶠ) loss_∇loss(β) = (loss(β), convert(Vector, loss'(β))) -algorithm = LBFGS(; gradtol=1e-3, verbosity=2) +algorithm = LBFGS(; gradtol = 1.0e-3, verbosity = 2) βᵒᵖᵗ, _ = optimize(loss_∇loss, β⁰, algorithm) @show loss(βᵒᵖᵗ) diff --git a/examples/autodiff/ops/trotter_ad_2.jl b/examples/autodiff/ops/trotter_ad_2.jl index 163dcc7..9d0eee7 100644 --- a/examples/autodiff/ops/trotter_ad_2.jl +++ b/examples/autodiff/ops/trotter_ad_2.jl @@ -3,14 +3,14 @@ using Zygote using OptimKit function ising(n; h) - ℋ = Sum{Op}() - for j in 1:(n - 1) - ℋ -= "Z", j, "Z", j + 1 - end - for j in 1:n - ℋ += h, "X", j - end - return ℋ + ℋ = Sum{Op}() + for j in 1:(n - 1) + ℋ -= "Z", j, "Z", j + 1 + end + for j in 1:n + ℋ += h, "X", j + end + return ℋ end n = 4 @@ -19,8 +19,8 @@ s = siteinds("S=1/2", n) β = 1.0 hᶠ = 1.1 -ℋᶠ = ising(n; h=hᶠ) -𝒰ᶠ = exp(-β * ℋᶠ; alg=Trotter{1}(5)) +ℋᶠ = ising(n; h = hᶠ) +𝒰ᶠ = exp(-β * ℋᶠ; alg = Trotter{1}(5)) Uᶠ = Prod{ITensor}(𝒰ᶠ, s) @@ -30,11 +30,11 @@ Uᶠ = Prod{ITensor}(𝒰ᶠ, s) Uᶠψ = Uᶠ(ψ) function loss(h) - ℋ = ising(n; h=h[1]) - 𝒰ʰ = exp(-β * ℋ; alg=Trotter{1}(5)) - Uʰ = Prod{ITensor}(𝒰ʰ, s) - Uʰψ = Uʰ(ψ) - return -abs(inner(Uᶠψ, Uʰψ))^2 / (norm(Uᶠψ) * norm(Uʰψ))^2 + ℋ = ising(n; h = h[1]) + 𝒰ʰ = exp(-β * ℋ; alg = Trotter{1}(5)) + Uʰ = Prod{ITensor}(𝒰ʰ, s) + Uʰψ = Uʰ(ψ) + return -abs(inner(Uᶠψ, Uʰψ))^2 / (norm(Uᶠψ) * norm(Uʰψ))^2 end h⁰ = [0.0] @@ -45,7 +45,7 @@ h⁰ = [0.0] @show loss'(hᶠ) loss_∇loss(h) = (loss(h), convert(Vector, loss'(h))) -algorithm = LBFGS(; gradtol=1e-3, verbosity=2) +algorithm = LBFGS(; gradtol = 1.0e-3, verbosity = 2) hᵒᵖᵗ, _ = optimize(loss_∇loss, h⁰, algorithm) @show loss(hᵒᵖᵗ) diff --git a/examples/autodiff/ops/vqe.jl b/examples/autodiff/ops/vqe.jl index 6757a8d..d4213af 100644 --- a/examples/autodiff/ops/vqe.jl +++ b/examples/autodiff/ops/vqe.jl @@ -5,18 +5,18 @@ using Zygote nsites = 4 # Number of sites nlayers = 2 # Layers of gates in the ansatz -gradtol = 1e-4 # Tolerance for stopping gradient descent +gradtol = 1.0e-4 # Tolerance for stopping gradient descent # The Hamiltonian we are minimizing function ising_hamiltonian(nsites; h) - ℋ = Sum{Op}() - for j in 1:(nsites - 1) - ℋ -= "Z", j, "Z", j + 1 - end - for j in 1:nsites - ℋ += h, "X", j - end - return ℋ + ℋ = Sum{Op}() + for j in 1:(nsites - 1) + ℋ -= "Z", j, "Z", j + 1 + end + for j in 1:nsites + ℋ += h, "X", j + end + return ℋ end ## # A layer of the circuit we want to optimize @@ -38,30 +38,30 @@ end # A layer of the circuit we want to optimize function layer(nsites, θ⃗) - l = Prod{Op}() - for n in 1:nsites - l = Op("Ry", n; θ=θ⃗[n]) * l - end - for n in 1:2:(nsites - 1) - l = Op("CX", n, n + 1) * l - end - return l + l = Prod{Op}() + for n in 1:nsites + l = Op("Ry", n; θ = θ⃗[n]) * l + end + for n in 1:2:(nsites - 1) + l = Op("CX", n, n + 1) * l + end + return l end # The variational circuit we want to optimize function variational_circuit(nsites, nlayers, θ⃗) - range = 1:nsites - circuit = layer(nsites, θ⃗[range]) - for n in 1:(nlayers - 1) - circuit = layer(nsites, θ⃗[range .+ n * nsites]) * circuit - end - return circuit + range = 1:nsites + circuit = layer(nsites, θ⃗[range]) + for n in 1:(nlayers - 1) + circuit = layer(nsites, θ⃗[range .+ n * nsites]) * circuit + end + return circuit end s = siteinds("Qubit", nsites) h = 1.3 -ℋ = ising_hamiltonian(nsites; h=h) +ℋ = ising_hamiltonian(nsites; h = h) H = MPO(ℋ, s) ψ0 = MPS(s, "0") @@ -72,12 +72,12 @@ H = MPO(ℋ, s) # loss(θ⃗) = ⟨0|U(θ⃗)† H U(θ⃗)|0⟩ = ⟨θ⃗|H|θ⃗⟩ # function loss(θ⃗) - nsites = length(ψ0) - s = siteinds(ψ0) - 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) - Uθ⃗ = Prod{ITensor}(𝒰θ⃗, s) - ψθ⃗ = apply(Uθ⃗, ψ0; cutoff=1e-8) - return inner(ψθ⃗', H, ψθ⃗; cutoff=1e-8) + nsites = length(ψ0) + s = siteinds(ψ0) + 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) + Uθ⃗ = Prod{ITensor}(𝒰θ⃗, s) + ψθ⃗ = apply(Uθ⃗, ψ0; cutoff = 1.0e-8) + return inner(ψθ⃗', H, ψθ⃗; cutoff = 1.0e-8) end Random.seed!(1234) @@ -88,14 +88,14 @@ Random.seed!(1234) println("\nOptimize circuit with gradient optimization") loss_∇loss(x) = (loss(x), convert(Vector, loss'(x))) -algorithm = LBFGS(; gradtol=1e-3, verbosity=2) +algorithm = LBFGS(; gradtol = 1.0e-3, verbosity = 2) θ⃗ₒₚₜ, lossₒₚₜ, ∇lossₒₚₜ, numfg, normgradhistory = optimize(loss_∇loss, θ⃗₀, algorithm) @show loss(θ⃗ₒₚₜ) println("\nRun DMRG as a comparison") -e_dmrg, ψ_dmrg = dmrg(H, ψ0; nsweeps=5, maxdim=10) +e_dmrg, ψ_dmrg = dmrg(H, ψ0; nsweeps = 5, maxdim = 10) println("\nCompare variational circuit energy to DMRG energy") @show loss(θ⃗ₒₚₜ), e_dmrg diff --git a/examples/dmrg/1d_heisenberg.jl b/examples/dmrg/1d_heisenberg.jl index 4718721..f6ce993 100644 --- a/examples/dmrg/1d_heisenberg.jl +++ b/examples/dmrg/1d_heisenberg.jl @@ -5,34 +5,34 @@ using Random Random.seed!(1234) let - N = 100 + N = 100 - # Create N spin-one degrees of freedom - sites = siteinds("S=1", N) - # Alternatively can make spin-half sites instead - #sites = siteinds("S=1/2", N) + # Create N spin-one degrees of freedom + sites = siteinds("S=1", N) + # Alternatively can make spin-half sites instead + #sites = siteinds("S=1/2", N) - # Input operator terms which define a Hamiltonian - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - # Convert these terms to an MPO tensor network - H = MPO(os, sites) + # Input operator terms which define a Hamiltonian + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + # Convert these terms to an MPO tensor network + H = MPO(os, sites) - # Create an initial random matrix product state - psi0 = random_mps(sites; linkdims=10) + # Create an initial random matrix product state + psi0 = random_mps(sites; linkdims = 10) - # Plan to do 5 DMRG sweeps: - nsweeps = 5 - # Set maximum MPS bond dimensions for each sweep - maxdim = [10, 20, 100, 100, 200] - # Set maximum truncation error allowed when adapting bond dimensions - cutoff = [1E-11] + # Plan to do 5 DMRG sweeps: + nsweeps = 5 + # Set maximum MPS bond dimensions for each sweep + maxdim = [10, 20, 100, 100, 200] + # Set maximum truncation error allowed when adapting bond dimensions + cutoff = [1.0e-11] - # Run the DMRG algorithm, returning energy and optimized MPS - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) - @printf("Final energy = %.12f\n", energy) + # Run the DMRG algorithm, returning energy and optimized MPS + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) + @printf("Final energy = %.12f\n", energy) end diff --git a/examples/dmrg/1d_heisenberg_conserve_spin.jl b/examples/dmrg/1d_heisenberg_conserve_spin.jl index f8ec1fb..0bf378d 100644 --- a/examples/dmrg/1d_heisenberg_conserve_spin.jl +++ b/examples/dmrg/1d_heisenberg_conserve_spin.jl @@ -11,29 +11,29 @@ ITensors.enable_threaded_blocksparse() #ITensors.disable_threaded_blocksparse() let - N = 100 + N = 100 - sites = siteinds("S=1", N; conserve_qns=true) + sites = siteinds("S=1", N; conserve_qns = true) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(os, sites) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = MPO(os, sites) - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - psi0 = random_mps(sites, state; linkdims=10) + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + psi0 = random_mps(sites, state; linkdims = 10) - # Plan to do 5 DMRG sweeps: - nsweeps = 5 - # Set maximum MPS bond dimensions for each sweep - maxdim = [10, 20, 100, 100, 200] - # Set maximum truncation error allowed when adapting bond dimensions - cutoff = [1E-11] + # Plan to do 5 DMRG sweeps: + nsweeps = 5 + # Set maximum MPS bond dimensions for each sweep + maxdim = [10, 20, 100, 100, 200] + # Set maximum truncation error allowed when adapting bond dimensions + cutoff = [1.0e-11] - # Run the DMRG algorithm, returning energy and optimized MPS - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) - @printf("Final energy = %.12f\n", energy) + # Run the DMRG algorithm, returning energy and optimized MPS + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) + @printf("Final energy = %.12f\n", energy) end diff --git a/examples/dmrg/1d_hubbard_extended.jl b/examples/dmrg/1d_hubbard_extended.jl index 6e10091..89896c5 100644 --- a/examples/dmrg/1d_hubbard_extended.jl +++ b/examples/dmrg/1d_hubbard_extended.jl @@ -6,88 +6,88 @@ using ITensors, ITensorMPS # let - N = 20 - Npart = 10 - t1 = 1.0 - t2 = 0.2 - U = 1.0 - V1 = 0.5 + N = 20 + Npart = 10 + t1 = 1.0 + t2 = 0.2 + U = 1.0 + V1 = 0.5 - sites = siteinds("Electron", N; conserve_qns=true) + sites = siteinds("Electron", N; conserve_qns = true) - os = OpSum() - for b in 1:(N - 1) - os -= t1, "Cdagup", b, "Cup", b + 1 - os -= t1, "Cdagup", b + 1, "Cup", b - os -= t1, "Cdagdn", b, "Cdn", b + 1 - os -= t1, "Cdagdn", b + 1, "Cdn", b - os += V1, "Ntot", b, "Ntot", b + 1 - end - for b in 1:(N - 2) - os -= t2, "Cdagup", b, "Cup", b + 2 - os -= t2, "Cdagup", b + 2, "Cup", b - os -= t2, "Cdagdn", b, "Cdn", b + 2 - os -= t2, "Cdagdn", b + 2, "Cdn", b - end - for i in 1:N - os += U, "Nupdn", i - end - H = MPO(os, sites) + os = OpSum() + for b in 1:(N - 1) + os -= t1, "Cdagup", b, "Cup", b + 1 + os -= t1, "Cdagup", b + 1, "Cup", b + os -= t1, "Cdagdn", b, "Cdn", b + 1 + os -= t1, "Cdagdn", b + 1, "Cdn", b + os += V1, "Ntot", b, "Ntot", b + 1 + end + for b in 1:(N - 2) + os -= t2, "Cdagup", b, "Cup", b + 2 + os -= t2, "Cdagup", b + 2, "Cup", b + os -= t2, "Cdagdn", b, "Cdn", b + 2 + os -= t2, "Cdagdn", b + 2, "Cdn", b + end + for i in 1:N + os += U, "Nupdn", i + end + H = MPO(os, sites) - nsweeps = 6 - maxdim = [50, 100, 200, 400, 800, 800] - cutoff = [1E-12] + nsweeps = 6 + maxdim = [50, 100, 200, 400, 800, 800] + cutoff = [1.0e-12] - state = ["Emp" for n in 1:N] - p = Npart - for i in N:-1:1 - if p > i - println("Doubly occupying site $i") - state[i] = "UpDn" - p -= 2 - elseif p > 0 - println("Singly occupying site $i") - state[i] = (isodd(i) ? "Up" : "Dn") - p -= 1 + state = ["Emp" for n in 1:N] + p = Npart + for i in N:-1:1 + if p > i + println("Doubly occupying site $i") + state[i] = "UpDn" + p -= 2 + elseif p > 0 + println("Singly occupying site $i") + state[i] = (isodd(i) ? "Up" : "Dn") + p -= 1 + end end - end - # Initialize wavefunction to be bond - # dimension 10 random MPS with number - # of particles the same as `state` - psi0 = random_mps(sites, state; linkdims=10) + # Initialize wavefunction to be bond + # dimension 10 random MPS with number + # of particles the same as `state` + psi0 = random_mps(sites, state; linkdims = 10) - # Check total number of particles: - @show flux(psi0) + # Check total number of particles: + @show flux(psi0) - # Start DMRG calculation: - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) + # Start DMRG calculation: + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) - upd = fill(0.0, N) - dnd = fill(0.0, N) - for j in 1:N - orthogonalize!(psi, j) - psidag_j = dag(prime(psi[j], "Site")) - upd[j] = scalar(psidag_j * op(sites, "Nup", j) * psi[j]) - dnd[j] = scalar(psidag_j * op(sites, "Ndn", j) * psi[j]) - end + upd = fill(0.0, N) + dnd = fill(0.0, N) + for j in 1:N + orthogonalize!(psi, j) + psidag_j = dag(prime(psi[j], "Site")) + upd[j] = scalar(psidag_j * op(sites, "Nup", j) * psi[j]) + dnd[j] = scalar(psidag_j * op(sites, "Ndn", j) * psi[j]) + end - println("Up Density:") - for j in 1:N - println("$j $(upd[j])") - end - println() + println("Up Density:") + for j in 1:N + println("$j $(upd[j])") + end + println() - println("Dn Density:") - for j in 1:N - println("$j $(dnd[j])") - end - println() + println("Dn Density:") + for j in 1:N + println("$j $(dnd[j])") + end + println() - println("Total Density:") - for j in 1:N - println("$j $(upd[j]+dnd[j])") - end - println() + println("Total Density:") + for j in 1:N + println("$j $(upd[j] + dnd[j])") + end + println() - println("\nGround State Energy = $energy") + println("\nGround State Energy = $energy") end diff --git a/examples/dmrg/1d_ising_with_observer.jl b/examples/dmrg/1d_ising_with_observer.jl index 76194be..7d6baf1 100644 --- a/examples/dmrg/1d_ising_with_observer.jl +++ b/examples/dmrg/1d_ising_with_observer.jl @@ -7,77 +7,77 @@ using ITensors, ITensorMPS Get MPO of transverse field Ising model Hamiltonian with field strength h """ function tfimMPO(sites, h::Float64) - # Input operator terms which define a Hamiltonian - N = length(sites) - os = OpSum() - for j in 1:(N - 1) - os -= 1, "Z", j, "Z", j + 1 - end - for j in 1:N - os += h, "X", j - end - # Convert these terms to an MPO tensor network - return MPO(os, sites) + # Input operator terms which define a Hamiltonian + N = length(sites) + os = OpSum() + for j in 1:(N - 1) + os -= 1, "Z", j, "Z", j + 1 + end + for j in 1:N + os += h, "X", j + end + # Convert these terms to an MPO tensor network + return MPO(os, sites) end let - N = 100 - sites = siteinds("S=1/2", N) - psi0 = random_mps(sites; linkdims=10) + N = 100 + sites = siteinds("S=1/2", N) + psi0 = random_mps(sites; linkdims = 10) - # define parameters for DMRG sweeps - nsweeps = 15 - maxdim = [10, 20, 100, 100, 200] - cutoff = [1E-10] + # define parameters for DMRG sweeps + nsweeps = 15 + maxdim = [10, 20, 100, 100, 200] + cutoff = [1.0e-10] - #= + #= create observer which will measure Sᶻ at each site during the dmrg sweeps and track energies after each sweep. in addition it will stop the computation if energy converges within 1E-7 tolerance =# - let - Sz_observer = DMRGObserver(["Sz"], sites; energy_tol=1E-7) + let + Sz_observer = DMRGObserver(["Sz"], sites; energy_tol = 1.0e-7) - # we will now run DMRG calculation for different values - # of the transverse field and check how local observables - # converge to their ground state values + # we will now run DMRG calculation for different values + # of the transverse field and check how local observables + # converge to their ground state values - println("Running DMRG for TFIM with h=0.1") - println("================================") - H = tfimMPO(sites, 0.1) - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff, observer=Sz_observer) + println("Running DMRG for TFIM with h=0.1") + println("================================") + H = tfimMPO(sites, 0.1) + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff, observer = Sz_observer) - for (i, Szs) in enumerate(measurements(Sz_observer)["Sz"]) - println("<Σ Sz> after sweep $i = ", sum(Szs) / N) + for (i, Szs) in enumerate(measurements(Sz_observer)["Sz"]) + println("<Σ Sz> after sweep $i = ", sum(Szs) / N) + end end - end - let - println("\nRunning DMRG for TFIM with h=1.0 (critical point)") - println("================================") - Sz_observer = DMRGObserver(["Sz"], sites; energy_tol=1E-7) - H = tfimMPO(sites, 1.0) - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff, observer=Sz_observer) + let + println("\nRunning DMRG for TFIM with h=1.0 (critical point)") + println("================================") + Sz_observer = DMRGObserver(["Sz"], sites; energy_tol = 1.0e-7) + H = tfimMPO(sites, 1.0) + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff, observer = Sz_observer) - for (i, Szs) in enumerate(measurements(Sz_observer)["Sz"]) - println("<Σ Sz> after sweep $i = ", sum(Szs) / N) + for (i, Szs) in enumerate(measurements(Sz_observer)["Sz"]) + println("<Σ Sz> after sweep $i = ", sum(Szs) / N) + end end - end - let - println("\nRunning DMRG for TFIM with h=5.") - println("================================") - Sz_Sx_observer = DMRGObserver(["Sz", "Sx"], sites; energy_tol=1E-7) - H = tfimMPO(sites, 5.0) - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff, observer=Sz_Sx_observer) + let + println("\nRunning DMRG for TFIM with h=5.") + println("================================") + Sz_Sx_observer = DMRGObserver(["Sz", "Sx"], sites; energy_tol = 1.0e-7) + H = tfimMPO(sites, 5.0) + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff, observer = Sz_Sx_observer) - for (i, Szs) in enumerate(measurements(Sz_Sx_observer)["Sz"]) - println("<Σ Sz> after sweep $i = ", sum(Szs) / N) - end - println() - for (i, Sxs) in enumerate(measurements(Sz_Sx_observer)["Sx"]) - println("<Σ Sx> after sweep $i = ", sum(Sxs) / N) + for (i, Szs) in enumerate(measurements(Sz_Sx_observer)["Sz"]) + println("<Σ Sz> after sweep $i = ", sum(Szs) / N) + end + println() + for (i, Sxs) in enumerate(measurements(Sz_Sx_observer)["Sx"]) + println("<Σ Sx> after sweep $i = ", sum(Sxs) / N) + end end - end end diff --git a/examples/dmrg/2d_heisenberg_conserve_spin.jl b/examples/dmrg/2d_heisenberg_conserve_spin.jl index 8788784..08adbc4 100644 --- a/examples/dmrg/2d_heisenberg_conserve_spin.jl +++ b/examples/dmrg/2d_heisenberg_conserve_spin.jl @@ -1,34 +1,34 @@ using ITensors, ITensorMPS let - Ny = 6 - Nx = 12 + Ny = 6 + Nx = 12 - N = Nx * Ny + N = Nx * Ny - sites = siteinds("S=1/2", N; conserve_qns=true) + sites = siteinds("S=1/2", N; conserve_qns = true) - lattice = square_lattice(Nx, Ny; yperiodic=false) + lattice = square_lattice(Nx, Ny; yperiodic = false) - os = OpSum() - for b in lattice - os += 0.5, "S+", b.s1, "S-", b.s2 - os += 0.5, "S-", b.s1, "S+", b.s2 - os += "Sz", b.s1, "Sz", b.s2 - end - H = MPO(os, sites) + os = OpSum() + for b in lattice + os += 0.5, "S+", b.s1, "S-", b.s2 + os += 0.5, "S-", b.s1, "S+", b.s2 + os += "Sz", b.s1, "Sz", b.s2 + end + H = MPO(os, sites) - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - # Initialize wavefunction to a random MPS - # of bond-dimension 10 with same quantum - # numbers as `state` - psi0 = random_mps(sites, state; linkdims=20) + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + # Initialize wavefunction to a random MPS + # of bond-dimension 10 with same quantum + # numbers as `state` + psi0 = random_mps(sites, state; linkdims = 20) - nsweeps = 10 - maxdim = [20, 60, 100, 100, 200, 400, 800] - cutoff = [1E-8] + nsweeps = 10 + maxdim = [20, 60, 100, 100, 200, 400, 800] + cutoff = [1.0e-8] - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff) - return nothing + return nothing end diff --git a/examples/dmrg/2d_hubbard_conserve_momentum.jl b/examples/dmrg/2d_hubbard_conserve_momentum.jl index 27c8535..7650626 100644 --- a/examples/dmrg/2d_hubbard_conserve_momentum.jl +++ b/examples/dmrg/2d_hubbard_conserve_momentum.jl @@ -30,66 +30,66 @@ end; ``` """ function main(; - Nx::Int=8, - Ny::Int=4, - U::Float64=4.0, - t::Float64=1.0, - maxdim::Int=3000, - conserve_ky=true, - threaded_blocksparse=false, - nsweeps=10, - random_init=true, - seed=1234, -) - # Helps make results reproducible when comparing - # sequential vs. threaded. - itensor_rng = Xoshiro() - Random.seed!(itensor_rng, seed) + Nx::Int = 8, + Ny::Int = 4, + U::Float64 = 4.0, + t::Float64 = 1.0, + maxdim::Int = 3000, + conserve_ky = true, + threaded_blocksparse = false, + nsweeps = 10, + random_init = true, + seed = 1234, + ) + # Helps make results reproducible when comparing + # sequential vs. threaded. + itensor_rng = Xoshiro() + Random.seed!(itensor_rng, seed) - @show Threads.nthreads() + @show Threads.nthreads() - # Disable other threading - BLAS.set_num_threads(1) - Strided.set_num_threads(1) + # Disable other threading + BLAS.set_num_threads(1) + Strided.set_num_threads(1) - ITensors.enable_threaded_blocksparse(threaded_blocksparse) - @show ITensors.using_threaded_blocksparse() + ITensors.enable_threaded_blocksparse(threaded_blocksparse) + @show ITensors.using_threaded_blocksparse() - N = Nx * Ny + N = Nx * Ny - maxdim = min.([100, 200, 400, 800, 2000, 3000, maxdim], maxdim) - cutoff = [1e-6] - noise = [1e-6, 1e-7, 1e-8, 0.0] + maxdim = min.([100, 200, 400, 800, 2000, 3000, maxdim], maxdim) + cutoff = [1.0e-6] + noise = [1.0e-6, 1.0e-7, 1.0e-8, 0.0] - sites = siteinds("ElecK", N; conserve_qns=true, conserve_ky, modulus_ky=Ny) + sites = siteinds("ElecK", N; conserve_qns = true, conserve_ky, modulus_ky = Ny) - os = hubbard(; Nx, Ny, t, U, ky=true) - H = MPO(os, sites) + os = hubbard(; Nx, Ny, t, U, ky = true) + H = MPO(os, sites) - # Number of structural nonzero elements in a bulk - # Hamiltonian MPO tensor - @show nnz(H[end ÷ 2]) - @show nnzblocks(H[end ÷ 2]) + # Number of structural nonzero elements in a bulk + # Hamiltonian MPO tensor + @show nnz(H[end ÷ 2]) + @show nnzblocks(H[end ÷ 2]) - # Create starting state with checkerboard - # pattern - state = map(CartesianIndices((Ny, Nx))) do I - return iseven(I[1]) ⊻ iseven(I[2]) ? "↓" : "↑" - end - display(state) + # Create starting state with checkerboard + # pattern + state = map(CartesianIndices((Ny, Nx))) do I + return iseven(I[1]) ⊻ iseven(I[2]) ? "↓" : "↑" + end + display(state) - psi0 = if random_init - random_mps(itensor_rng, sites, state; linkdims=2) - else - MPS(sites, state) - end - @time @show inner(psi0', H, psi0) + psi0 = if random_init + random_mps(itensor_rng, sites, state; linkdims = 2) + else + MPS(sites, state) + end + @time @show inner(psi0', H, psi0) - energy, psi = @time dmrg(H, psi0; nsweeps, maxdim, cutoff, noise) - @show Nx, Ny - @show t, U - @show flux(psi) - @show maxlinkdim(psi) - @show energy - return energy, H, psi + energy, psi = @time dmrg(H, psi0; nsweeps, maxdim, cutoff, noise) + @show Nx, Ny + @show t, U + @show flux(psi) + @show maxlinkdim(psi) + @show energy + return energy, H, psi end diff --git a/examples/dmrg/2d_hubbard_conserve_particles.jl b/examples/dmrg/2d_hubbard_conserve_particles.jl index 2c1b400..29713ec 100644 --- a/examples/dmrg/2d_hubbard_conserve_particles.jl +++ b/examples/dmrg/2d_hubbard_conserve_particles.jl @@ -1,44 +1,44 @@ using ITensors, ITensorMPS -function main(; Nx=6, Ny=3, U=4.0, t=1.0) - N = Nx * Ny - - nsweeps = 10 - maxdim = [100, 200, 400, 800, 1600] - cutoff = [1E-6] - noise = [1E-6, 1E-7, 1E-8, 0.0] - - sites = siteinds("Electron", N; conserve_qns=true) - - lattice = square_lattice(Nx, Ny; yperiodic=true) - - os = OpSum() - for b in lattice - os -= t, "Cdagup", b.s1, "Cup", b.s2 - os -= t, "Cdagup", b.s2, "Cup", b.s1 - os -= t, "Cdagdn", b.s1, "Cdn", b.s2 - os -= t, "Cdagdn", b.s2, "Cdn", b.s1 - end - for n in 1:N - os += U, "Nupdn", n - end - H = MPO(os, sites) - - # Half filling - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - - # Initialize wavefunction to a random MPS - # of bond-dimension 10 with same quantum - # numbers as `state` - psi0 = random_mps(sites, state) - - energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff, noise) - @show t, U - @show flux(psi) - @show maxlinkdim(psi) - @show energy - - return nothing +function main(; Nx = 6, Ny = 3, U = 4.0, t = 1.0) + N = Nx * Ny + + nsweeps = 10 + maxdim = [100, 200, 400, 800, 1600] + cutoff = [1.0e-6] + noise = [1.0e-6, 1.0e-7, 1.0e-8, 0.0] + + sites = siteinds("Electron", N; conserve_qns = true) + + lattice = square_lattice(Nx, Ny; yperiodic = true) + + os = OpSum() + for b in lattice + os -= t, "Cdagup", b.s1, "Cup", b.s2 + os -= t, "Cdagup", b.s2, "Cup", b.s1 + os -= t, "Cdagdn", b.s1, "Cdn", b.s2 + os -= t, "Cdagdn", b.s2, "Cdn", b.s1 + end + for n in 1:N + os += U, "Nupdn", n + end + H = MPO(os, sites) + + # Half filling + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + + # Initialize wavefunction to a random MPS + # of bond-dimension 10 with same quantum + # numbers as `state` + psi0 = random_mps(sites, state) + + energy, psi = dmrg(H, psi0; nsweeps, maxdim, cutoff, noise) + @show t, U + @show flux(psi) + @show maxlinkdim(psi) + @show energy + + return nothing end main() diff --git a/examples/dmrg/threaded_blocksparse/2d_hubbard_conserve_momentum.jl b/examples/dmrg/threaded_blocksparse/2d_hubbard_conserve_momentum.jl index 9905e96..94cfda4 100644 --- a/examples/dmrg/threaded_blocksparse/2d_hubbard_conserve_momentum.jl +++ b/examples/dmrg/threaded_blocksparse/2d_hubbard_conserve_momentum.jl @@ -6,80 +6,80 @@ include(joinpath(@__DIR__, "..", "..", "src", "electronk.jl")) include(joinpath(@__DIR__, "..", "..", "src", "hubbard.jl")) function main(; - Nx::Int=6, - Ny::Int=3, - U::Float64=4.0, - t::Float64=1.0, - maxdim::Int=3000, - conserve_ky=true, - nsweeps=10, - blas_num_threads=1, - strided_num_threads=1, - threaded_blocksparse=false, - outputlevel=1, - seed=1234, -) - Random.seed!(seed) - ITensors.Strided.set_num_threads(strided_num_threads) - BLAS.set_num_threads(blas_num_threads) - ITensors.enable_threaded_blocksparse(threaded_blocksparse) + Nx::Int = 6, + Ny::Int = 3, + U::Float64 = 4.0, + t::Float64 = 1.0, + maxdim::Int = 3000, + conserve_ky = true, + nsweeps = 10, + blas_num_threads = 1, + strided_num_threads = 1, + threaded_blocksparse = false, + outputlevel = 1, + seed = 1234, + ) + Random.seed!(seed) + ITensors.Strided.set_num_threads(strided_num_threads) + BLAS.set_num_threads(blas_num_threads) + ITensors.enable_threaded_blocksparse(threaded_blocksparse) - if outputlevel > 0 - @show Threads.nthreads() - @show Sys.CPU_THREADS - @show BLAS.get_num_threads() - @show ITensors.Strided.get_num_threads() - @show ITensors.using_threaded_blocksparse() - println() - end + if outputlevel > 0 + @show Threads.nthreads() + @show Sys.CPU_THREADS + @show BLAS.get_num_threads() + @show ITensors.Strided.get_num_threads() + @show ITensors.using_threaded_blocksparse() + println() + end - N = Nx * Ny + N = Nx * Ny - maxdim = min.([100, 200, 400, 800, 2000, 3000, maxdim], maxdim) - cutoff = [1E-6] - noise = [1E-6, 1E-7, 1E-8, 0.0] + maxdim = min.([100, 200, 400, 800, 2000, 3000, maxdim], maxdim) + cutoff = [1.0e-6] + noise = [1.0e-6, 1.0e-7, 1.0e-8, 0.0] - sites = siteinds("ElecK", N; conserve_qns=true, conserve_ky, modulus_ky=Ny) + sites = siteinds("ElecK", N; conserve_qns = true, conserve_ky, modulus_ky = Ny) - os = hubbard(; Nx, Ny, t, U, ky=true) - H = MPO(os, sites) + os = hubbard(; Nx, Ny, t, U, ky = true) + H = MPO(os, sites) - # Number of structural nonzero elements in a bulk - # Hamiltonian MPO tensor - if outputlevel > 0 - @show nnz(H[end ÷ 2]) - @show nnzblocks(H[end ÷ 2]) - end + # Number of structural nonzero elements in a bulk + # Hamiltonian MPO tensor + if outputlevel > 0 + @show nnz(H[end ÷ 2]) + @show nnzblocks(H[end ÷ 2]) + end - # Create starting state with checkerboard - # pattern - state = map(CartesianIndices((Ny, Nx))) do I - return iseven(I[1]) ⊻ iseven(I[2]) ? "↓" : "↑" - end - display(state) + # Create starting state with checkerboard + # pattern + state = map(CartesianIndices((Ny, Nx))) do I + return iseven(I[1]) ⊻ iseven(I[2]) ? "↓" : "↑" + end + display(state) - psi0 = random_mps(sites, state; linkdims=10) + psi0 = random_mps(sites, state; linkdims = 10) - energy, psi = @time dmrg(H, psi0; nsweeps, maxdim, cutoff, noise, outputlevel) + energy, psi = @time dmrg(H, psi0; nsweeps, maxdim, cutoff, noise, outputlevel) - if outputlevel > 0 - @show Nx, Ny - @show t, U - @show flux(psi) - @show maxlinkdim(psi) - @show energy - end - return nothing + if outputlevel > 0 + @show Nx, Ny + @show t, U + @show flux(psi) + @show maxlinkdim(psi) + @show energy + end + return nothing end println("################################") println("Compilation") println("################################") println("Without threaded block sparse:\n") -main(; nsweeps=2, threaded_blocksparse=false, outputlevel=0) +main(; nsweeps = 2, threaded_blocksparse = false, outputlevel = 0) println() println("With threaded block sparse:\n") -main(; nsweeps=2, threaded_blocksparse=true, outputlevel=0) +main(; nsweeps = 2, threaded_blocksparse = true, outputlevel = 0) println() println("################################") @@ -87,8 +87,8 @@ println("Runtime") println("################################") println() println("Without threaded block sparse:\n") -main(; nsweeps=10, threaded_blocksparse=false) +main(; nsweeps = 10, threaded_blocksparse = false) println() println("With threaded block sparse:\n") -main(; nsweeps=10, threaded_blocksparse=true) +main(; nsweeps = 10, threaded_blocksparse = true) println() diff --git a/examples/dmrg/write_to_disk/1d_heisenberg.jl b/examples/dmrg/write_to_disk/1d_heisenberg.jl index 6391e86..5b99f6b 100644 --- a/examples/dmrg/write_to_disk/1d_heisenberg.jl +++ b/examples/dmrg/write_to_disk/1d_heisenberg.jl @@ -8,29 +8,29 @@ Random.seed!(1234) Strided.set_num_threads(1) let - N = 100 + N = 100 - sites = siteinds("S=1", N; conserve_qns=true) + sites = siteinds("S=1", N; conserve_qns = true) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(os, sites) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = MPO(os, sites) - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - psi0 = random_mps(sites, state; linkdims=10) + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + psi0 = random_mps(sites, state; linkdims = 10) - # Plan to do 5 DMRG sweeps: - nsweeps = 5 - # Set maximum MPS bond dimensions for each sweep - maxdim = [10, 20, 100, 100, 200] - # Set maximum truncation error allowed when adapting bond dimensions - cutoff = 1E-10 + # Plan to do 5 DMRG sweeps: + nsweeps = 5 + # Set maximum MPS bond dimensions for each sweep + maxdim = [10, 20, 100, 100, 200] + # Set maximum truncation error allowed when adapting bond dimensions + cutoff = 1.0e-10 - # Run the DMRG algorithm, returning energy and optimized MPS - energy, psi = dmrg(H, psi0; nsweeps, cutoff, maxdim, write_when_maxdim_exceeds=25) - @printf("Final energy = %.12f\n", energy) + # Run the DMRG algorithm, returning energy and optimized MPS + energy, psi = dmrg(H, psi0; nsweeps, cutoff, maxdim, write_when_maxdim_exceeds = 25) + @printf("Final energy = %.12f\n", energy) end diff --git a/examples/exact_diagonalization/exact_diagonalization.jl b/examples/exact_diagonalization/exact_diagonalization.jl index 2016ab8..9ed4338 100644 --- a/examples/exact_diagonalization/exact_diagonalization.jl +++ b/examples/exact_diagonalization/exact_diagonalization.jl @@ -10,55 +10,55 @@ Strided.disable_threads() ITensors.disable_threaded_blocksparse() function heisenberg(n) - os = OpSum() - for j in 1:(n - 1) - os += 1 / 2, "S+", j, "S-", j + 1 - os += 1 / 2, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - return os + os = OpSum() + for j in 1:(n - 1) + os += 1 / 2, "S+", j, "S-", j + 1 + os += 1 / 2, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + return os end -function main(n; blas_num_threads=Sys.CPU_THREADS, fuse=true, binary=true) - if n > 16 - @warn "System size of $n is likely too large for exact diagonalization." - end +function main(n; blas_num_threads = Sys.CPU_THREADS, fuse = true, binary = true) + if n > 16 + @warn "System size of $n is likely too large for exact diagonalization." + end - BLAS.set_num_threads(blas_num_threads) + BLAS.set_num_threads(blas_num_threads) - # Hilbert space - s = siteinds("S=1/2", n; conserve_qns=true) - H = MPO(heisenberg(n), s) - initstate(j) = isodd(j) ? "↑" : "↓" - ψ0 = random_mps(s, initstate; linkdims=10) + # Hilbert space + s = siteinds("S=1/2", n; conserve_qns = true) + H = MPO(heisenberg(n), s) + initstate(j) = isodd(j) ? "↑" : "↓" + ψ0 = random_mps(s, initstate; linkdims = 10) - edmrg, ψdmrg = dmrg(H, ψ0; nsweeps=10, cutoff=1e-6) + edmrg, ψdmrg = dmrg(H, ψ0; nsweeps = 10, cutoff = 1.0e-6) - if fuse - if binary - println("Fuse the indices using a binary tree") - T = fusion_tree_binary(s) - H_full = @time fuse_inds_binary(H, T) - ψ0_full = @time fuse_inds_binary(ψ0, T) + if fuse + if binary + println("Fuse the indices using a binary tree") + T = fusion_tree_binary(s) + H_full = @time fuse_inds_binary(H, T) + ψ0_full = @time fuse_inds_binary(ψ0, T) + else + println("Fuse the indices using an unbalances tree") + T = fusion_tree(s) + H_full = @time fuse_inds(H, T) + ψ0_full = @time fuse_inds(ψ0, T) + end else - println("Fuse the indices using an unbalances tree") - T = fusion_tree(s) - H_full = @time fuse_inds(H, T) - ψ0_full = @time fuse_inds(ψ0, T) - end - else - println("Don't fuse the indices") - @disable_warn_order begin - H_full = @time contract(H) - ψ0_full = @time contract(ψ0) + println("Don't fuse the indices") + @disable_warn_order begin + H_full = @time contract(H) + ψ0_full = @time contract(ψ0) + end end - end - vals, vecs, info = @time eigsolve( - H_full, ψ0_full, 1, :SR; ishermitian=true, tol=1e-6, krylovdim=30, eager=true - ) + vals, vecs, info = @time eigsolve( + H_full, ψ0_full, 1, :SR; ishermitian = true, tol = 1.0e-6, krylovdim = 30, eager = true + ) - @show edmrg, vals[1] + return @show edmrg, vals[1] end main(14) diff --git a/examples/exact_diagonalization/fuse_inds.jl b/examples/exact_diagonalization/fuse_inds.jl index 82f90e9..56870bc 100644 --- a/examples/exact_diagonalization/fuse_inds.jl +++ b/examples/exact_diagonalization/fuse_inds.jl @@ -1,91 +1,91 @@ using ITensors, ITensorMPS function fusion_tree(s::Vector{<:Index}) - n = length(s) - Cs = Vector{ITensor}(undef, n - 1) - cj = s[1] - for j in 1:(n - 1) - fuse_inds = (cj, s[j + 1]) - Cj = combiner(fuse_inds...) - Cs[j] = Cj - cj = uniqueind(Cj, fuse_inds) - end - return Cs + n = length(s) + Cs = Vector{ITensor}(undef, n - 1) + cj = s[1] + for j in 1:(n - 1) + fuse_inds = (cj, s[j + 1]) + Cj = combiner(fuse_inds...) + Cs[j] = Cj + cj = uniqueind(Cj, fuse_inds) + end + return Cs end function fuse_inds(A::MPS, fusion_tree::Vector{ITensor}) - n = length(A) - A_fused = A[1] - for j in 2:n - A_fused = A_fused * A[j] * fusion_tree[j - 1] - end - return A_fused + n = length(A) + A_fused = A[1] + for j in 2:n + A_fused = A_fused * A[j] * fusion_tree[j - 1] + end + return A_fused end function fuse_inds(A::MPO, fusion_tree::Vector{ITensor}) - n = length(A) - A_fused = A[1] - for j in 2:n - A_fused = A_fused * A[j] * dag(fusion_tree[j - 1]) * fusion_tree[j - 1]' - end - return A_fused + n = length(A) + A_fused = A[1] + for j in 2:n + A_fused = A_fused * A[j] * dag(fusion_tree[j - 1]) * fusion_tree[j - 1]' + end + return A_fused end -function fusion_tree_binary_layer(s::Vector{IndexT}; layer=1) where {IndexT<:Index} - n = length(s) - Cs = ITensor[] - cs = IndexT[] - for j in 1:2:(n - 1) - fuse_inds = (s[j], s[j + 1]) - Cj = combiner(fuse_inds...; tags="n=$(j)⊗$(j + 1),l=$(layer)") - push!(Cs, Cj) - cj = uniqueind(Cj, fuse_inds) - push!(cs, cj) - end - if isodd(n) - push!(cs, last(s)) - end - return Cs, cs +function fusion_tree_binary_layer(s::Vector{IndexT}; layer = 1) where {IndexT <: Index} + n = length(s) + Cs = ITensor[] + cs = IndexT[] + for j in 1:2:(n - 1) + fuse_inds = (s[j], s[j + 1]) + Cj = combiner(fuse_inds...; tags = "n=$(j)⊗$(j + 1),l=$(layer)") + push!(Cs, Cj) + cj = uniqueind(Cj, fuse_inds) + push!(cs, cj) + end + if isodd(n) + push!(cs, last(s)) + end + return Cs, cs end -function fusion_tree_binary(s::Vector{<:Index}; depth=ceil(Int, log2(length(s)))) - Cs = Vector{ITensor}[] - c_layer = s - for layer in 1:depth - C_layer, c_layer = fusion_tree_binary_layer(c_layer; layer) - push!(Cs, C_layer) - end - return Cs +function fusion_tree_binary(s::Vector{<:Index}; depth = ceil(Int, log2(length(s)))) + Cs = Vector{ITensor}[] + c_layer = s + for layer in 1:depth + C_layer, c_layer = fusion_tree_binary_layer(c_layer; layer) + push!(Cs, C_layer) + end + return Cs end function fuse_tensors(A::MPS, fusion_tree_layer::Vector{ITensor}, j::Int) - return A[j] * A[j + 1] * fusion_tree_layer[(j + 1) ÷ 2] + return A[j] * A[j + 1] * fusion_tree_layer[(j + 1) ÷ 2] end function fuse_tensors(A::MPO, fusion_tree_layer::Vector{ITensor}, j::Int) - return A[j] * - A[j + 1] * - dag(fusion_tree_layer[(j + 1) ÷ 2]) * - fusion_tree_layer[(j + 1) ÷ 2]' + return A[j] * + A[j + 1] * + dag(fusion_tree_layer[(j + 1) ÷ 2]) * + fusion_tree_layer[(j + 1) ÷ 2]' end -function fuse_inds_binary_layer(A::Union{MPS,MPO}, fusion_tree_layer::Vector{ITensor}) - n = length(fusion_tree_layer) - A_fused = ITensor[] - for j in 1:2:(2n) - push!(A_fused, fuse_tensors(A, fusion_tree_layer, j)) - end - if isodd(length(A)) - push!(A_fused, A[end]) - end - return typeof(A)(A_fused) +function fuse_inds_binary_layer(A::Union{MPS, MPO}, fusion_tree_layer::Vector{ITensor}) + n = length(fusion_tree_layer) + A_fused = ITensor[] + for j in 1:2:(2n) + push!(A_fused, fuse_tensors(A, fusion_tree_layer, j)) + end + if isodd(length(A)) + push!(A_fused, A[end]) + end + return typeof(A)(A_fused) end -function fuse_inds_binary(A::Union{MPS,MPO}, fusion_tree::Vector{Vector{ITensor}}) - depth = length(fusion_tree) - A_fused = A - for layer in 1:depth - A_fused = fuse_inds_binary_layer(A_fused, fusion_tree[layer]) - end - return only(A_fused) +function fuse_inds_binary(A::Union{MPS, MPO}, fusion_tree::Vector{Vector{ITensor}}) + depth = length(fusion_tree) + A_fused = A + for layer in 1:depth + A_fused = fuse_inds_binary_layer(A_fused, fusion_tree[layer]) + end + return only(A_fused) end diff --git a/examples/finite_temperature/metts.jl b/examples/finite_temperature/metts.jl index 355fcc3..56a7f37 100644 --- a/examples/finite_temperature/metts.jl +++ b/examples/finite_temperature/metts.jl @@ -14,11 +14,11 @@ For more information on METTS, see the following references: =# function ITensors.op(::OpName"expτSS", ::SiteType"S=1/2", s1::Index, s2::Index; τ) - h = - 1 / 2 * op("S+", s1) * op("S-", s2) + - 1 / 2 * op("S-", s1) * op("S+", s2) + - op("Sz", s1) * op("Sz", s2) - return exp(τ * h) + h = + 1 / 2 * op("S+", s1) * op("S-", s2) + + 1 / 2 * op("S-", s1) * op("S+", s2) + + op("Sz", s1) * op("Sz", s2) + return exp(τ * h) end """ @@ -27,89 +27,89 @@ the average and the standard error (= the width of distribution of the numbers) """ function avg_err(v::Vector) - N = length(v) - avg = v[1] / N - avg2 = v[1]^2 / N - for j in 2:N - avg += v[j] / N - avg2 += v[j]^2 / N - end - return avg, √((avg2 - avg^2) / N) + N = length(v) + avg = v[1] / N + avg2 = v[1]^2 / N + for j in 2:N + avg += v[j] / N + avg2 += v[j]^2 / N + end + return avg, √((avg2 - avg^2) / N) end -function main(; N=10, cutoff=1E-8, δτ=0.1, beta=2.0, NMETTS=3000, Nwarm=10) - - # Make an array of 'site' indices - s = siteinds("S=1/2", N) - - # Make gates (1,2),(2,3),(3,4),... - gates = ops([("expτSS", (n, n + 1), (τ=-δτ / 2,)) for n in 1:(N - 1)], s) - # Include gates in reverse order to complete Trotter formula - append!(gates, reverse(gates)) +function main(; N = 10, cutoff = 1.0e-8, δτ = 0.1, beta = 2.0, NMETTS = 3000, Nwarm = 10) - # Make y-rotation gates to use in METTS collapses - Ry_gates = ops([("Ry", n, (θ=π / 2,)) for n in 1:N], s) + # Make an array of 'site' indices + s = siteinds("S=1/2", N) - # Arbitrary initial state - psi = random_mps(s) + # Make gates (1,2),(2,3),(3,4),... + gates = ops([("expτSS", (n, n + 1), (τ = -δτ / 2,)) for n in 1:(N - 1)], s) + # Include gates in reverse order to complete Trotter formula + append!(gates, reverse(gates)) - # Make H for measuring the energy - terms = OpSum() - for j in 1:(N - 1) - terms += 1 / 2, "S+", j, "S-", j + 1 - terms += 1 / 2, "S-", j, "S+", j + 1 - terms += "Sz", j, "Sz", j + 1 - end - H = MPO(terms, s) + # Make y-rotation gates to use in METTS collapses + Ry_gates = ops([("Ry", n, (θ = π / 2,)) for n in 1:N], s) - # Make τ_range and check δτ is commensurate - τ_range = δτ:δτ:(beta / 2) - if norm(length(τ_range) * δτ - beta / 2) > 1E-10 - error("Time step δτ=$δτ not commensurate with beta/2=$(beta/2)") - end - - energies = Float64[] - - for step in 1:(Nwarm + NMETTS) - if step <= Nwarm - println("Making warmup METTS number $step") - else - println("Making METTS number $(step-Nwarm)") - end + # Arbitrary initial state + psi = random_mps(s) - # Do the time evolution by applying the gates - for τ in τ_range - psi = apply(gates, psi; cutoff) - normalize!(psi) + # Make H for measuring the energy + terms = OpSum() + for j in 1:(N - 1) + terms += 1 / 2, "S+", j, "S-", j + 1 + terms += 1 / 2, "S-", j, "S+", j + 1 + terms += "Sz", j, "Sz", j + 1 end + H = MPO(terms, s) - # Measure properties after >= Nwarm - # METTS have been made - if step > Nwarm - energy = inner(psi', H, psi) - push!(energies, energy) - @printf(" Energy of METTS %d = %.4f\n", step - Nwarm, energy) - a_E, err_E = avg_err(energies) - @printf( - " Estimated Energy = %.4f +- %.4f [%.4f,%.4f]\n", - a_E, - err_E, - a_E - err_E, - a_E + err_E - ) + # Make τ_range and check δτ is commensurate + τ_range = δτ:δτ:(beta / 2) + if norm(length(τ_range) * δτ - beta / 2) > 1.0e-10 + error("Time step δτ=$δτ not commensurate with beta/2=$(beta / 2)") end - # Measure in X or Z basis on alternating steps - if step % 2 == 1 - psi = apply(Ry_gates, psi) - samp = sample!(psi) - new_state = [samp[j] == 1 ? "X+" : "X-" for j in 1:N] - else - samp = sample!(psi) - new_state = [samp[j] == 1 ? "Z+" : "Z-" for j in 1:N] + energies = Float64[] + + for step in 1:(Nwarm + NMETTS) + if step <= Nwarm + println("Making warmup METTS number $step") + else + println("Making METTS number $(step - Nwarm)") + end + + # Do the time evolution by applying the gates + for τ in τ_range + psi = apply(gates, psi; cutoff) + normalize!(psi) + end + + # Measure properties after >= Nwarm + # METTS have been made + if step > Nwarm + energy = inner(psi', H, psi) + push!(energies, energy) + @printf(" Energy of METTS %d = %.4f\n", step - Nwarm, energy) + a_E, err_E = avg_err(energies) + @printf( + " Estimated Energy = %.4f +- %.4f [%.4f,%.4f]\n", + a_E, + err_E, + a_E - err_E, + a_E + err_E + ) + end + + # Measure in X or Z basis on alternating steps + if step % 2 == 1 + psi = apply(Ry_gates, psi) + samp = sample!(psi) + new_state = [samp[j] == 1 ? "X+" : "X-" for j in 1:N] + else + samp = sample!(psi) + new_state = [samp[j] == 1 ? "Z+" : "Z-" for j in 1:N] + end + psi = MPS(s, new_state) end - psi = MPS(s, new_state) - end - return nothing + return nothing end diff --git a/examples/finite_temperature/purification.jl b/examples/finite_temperature/purification.jl index fd374e4..0719a30 100644 --- a/examples/finite_temperature/purification.jl +++ b/examples/finite_temperature/purification.jl @@ -14,43 +14,43 @@ For more information see the following references: =# function ITensors.op(::OpName"expτSS", ::SiteType"S=1/2", s1::Index, s2::Index; τ) - h = - 1 / 2 * op("S+", s1) * op("S-", s2) + - 1 / 2 * op("S-", s1) * op("S+", s2) + - op("Sz", s1) * op("Sz", s2) - return exp(τ * h) + h = + 1 / 2 * op("S+", s1) * op("S-", s2) + + 1 / 2 * op("S-", s1) * op("S+", s2) + + op("Sz", s1) * op("Sz", s2) + return exp(τ * h) end -function main(; N=10, cutoff=1E-8, δτ=0.1, beta_max=2.0) - - # Make an array of 'site' indices - s = siteinds("S=1/2", N; conserve_qns=true) - - # Make gates (1,2),(2,3),(3,4),... - gates = ops([("expτSS", (n, n + 1), (τ=-δτ / 2,)) for n in 1:(N - 1)], s) - # Include gates in reverse order to complete Trotter formula - append!(gates, reverse(gates)) - - # Initial state is infinite-temperature mixed state - rho = MPO(s, "Id") ./ √2 - - # Make H for measuring the energy - terms = OpSum() - for j in 1:(N - 1) - terms += 1 / 2, "S+", j, "S-", j + 1 - terms += 1 / 2, "S-", j, "S+", j + 1 - terms += "Sz", j, "Sz", j + 1 - end - H = MPO(terms, s) - - # Do the time evolution by applying the gates - # for Nsteps steps - for β in 0:δτ:beta_max - energy = inner(rho, H) - @printf("β = %.2f energy = %.8f\n", β, energy) - rho = apply(gates, rho; cutoff) - rho = rho / tr(rho) - end - - return nothing +function main(; N = 10, cutoff = 1.0e-8, δτ = 0.1, beta_max = 2.0) + + # Make an array of 'site' indices + s = siteinds("S=1/2", N; conserve_qns = true) + + # Make gates (1,2),(2,3),(3,4),... + gates = ops([("expτSS", (n, n + 1), (τ = -δτ / 2,)) for n in 1:(N - 1)], s) + # Include gates in reverse order to complete Trotter formula + append!(gates, reverse(gates)) + + # Initial state is infinite-temperature mixed state + rho = MPO(s, "Id") ./ √2 + + # Make H for measuring the energy + terms = OpSum() + for j in 1:(N - 1) + terms += 1 / 2, "S+", j, "S-", j + 1 + terms += 1 / 2, "S-", j, "S+", j + 1 + terms += "Sz", j, "Sz", j + 1 + end + H = MPO(terms, s) + + # Do the time evolution by applying the gates + # for Nsteps steps + for β in 0:δτ:beta_max + energy = inner(rho, H) + @printf("β = %.2f energy = %.8f\n", β, energy) + rho = apply(gates, rho; cutoff) + rho = rho / tr(rho) + end + + return nothing end diff --git a/examples/gate_evolution/mpo_gate_evolution.jl b/examples/gate_evolution/mpo_gate_evolution.jl index c61813b..521d907 100644 --- a/examples/gate_evolution/mpo_gate_evolution.jl +++ b/examples/gate_evolution/mpo_gate_evolution.jl @@ -1,65 +1,65 @@ using ITensors, ITensorMPS function ITensors.op(::OpName"expτSS", ::SiteType"S=1/2", s1::Index, s2::Index; τ) - h = - 1 / 2 * op("S+", s1) * op("S-", s2) + - 1 / 2 * op("S-", s1) * op("S+", s2) + - op("Sz", s1) * op("Sz", s2) - return exp(τ * h) + h = + 1 / 2 * op("S+", s1) * op("S-", s2) + + 1 / 2 * op("S-", s1) * op("S+", s2) + + op("Sz", s1) * op("Sz", s2) + return exp(τ * h) end -function main(; N=10, cutoff=1E-8, δt=0.1, ttotal=5.0) - # Compute the number of steps to do - Nsteps = Int(ttotal / δt) +function main(; N = 10, cutoff = 1.0e-8, δt = 0.1, ttotal = 5.0) + # Compute the number of steps to do + Nsteps = Int(ttotal / δt) - # Make an array of 'site' indices - s = siteinds("S=1/2", N; conserve_qns=true) + # Make an array of 'site' indices + s = siteinds("S=1/2", N; conserve_qns = true) - # Make gates (1,2),(2,3),(3,4),... - gates = ops([("expτSS", (n, n + 1), (τ=-δt * im / 2,)) for n in 1:(N - 1)], s) + # Make gates (1,2),(2,3),(3,4),... + gates = ops([("expτSS", (n, n + 1), (τ = -δt * im / 2,)) for n in 1:(N - 1)], s) - # Include gates in reverse order too - # (N,N-1),(N-1,N-2),... - append!(gates, reverse(gates)) + # Include gates in reverse order too + # (N,N-1),(N-1,N-2),... + append!(gates, reverse(gates)) - # Function that measures on site n - function measure_Sz(psi::MPS, n) - psi = orthogonalize(psi, n) - sn = siteind(psi, n) - Sz = scalar(dag(prime(psi[n], "Site")) * op("Sz", sn) * psi[n]) - return real(Sz) - end + # Function that measures on site n + function measure_Sz(psi::MPS, n) + psi = orthogonalize(psi, n) + sn = siteind(psi, n) + Sz = scalar(dag(prime(psi[n], "Site")) * op("Sz", sn) * psi[n]) + return real(Sz) + end - # Initialize psi to be a product state (alternating up and down) - psi0 = MPS(s, n -> isodd(n) ? "Up" : "Dn") + # Initialize psi to be a product state (alternating up and down) + psi0 = MPS(s, n -> isodd(n) ? "Up" : "Dn") - c = div(N, 2) + c = div(N, 2) - # Compute and print initial value - t = 0.0 - Sz = measure_Sz(psi0, c) - println("$t $Sz") - - # Do the time evolution by applying the gates - # for Nsteps steps - psi = psi0 - for step in 1:Nsteps - psi = apply(gates, psi; cutoff) - t += δt - Sz = measure_Sz(psi, c) + # Compute and print initial value + t = 0.0 + Sz = measure_Sz(psi0, c) println("$t $Sz") - end - # Now do the same evolution with an MPO - rho0 = MPO(psi0) - rho = rho0 - for step in 1:Nsteps - rho = apply(gates, rho; cutoff, apply_dag=true) - t += δt - end - @show inner(psi', rho, psi) - @show inner(psi, psi) - @show tr(rho) + # Do the time evolution by applying the gates + # for Nsteps steps + psi = psi0 + for step in 1:Nsteps + psi = apply(gates, psi; cutoff) + t += δt + Sz = measure_Sz(psi, c) + println("$t $Sz") + end + + # Now do the same evolution with an MPO + rho0 = MPO(psi0) + rho = rho0 + for step in 1:Nsteps + rho = apply(gates, rho; cutoff, apply_dag = true) + t += δt + end + @show inner(psi', rho, psi) + @show inner(psi, psi) + @show tr(rho) - return nothing + return nothing end diff --git a/examples/gate_evolution/quantum_simulator.jl b/examples/gate_evolution/quantum_simulator.jl index ca4ddc3..b116a03 100644 --- a/examples/gate_evolution/quantum_simulator.jl +++ b/examples/gate_evolution/quantum_simulator.jl @@ -13,14 +13,14 @@ CX = ops(s, [("CX", n, m) for n in 1:N, m in 1:N]) # Change to the state |1010...⟩ gates = [X[n] for n in 1:2:N] -ψ = apply(gates, ψ0; cutoff=1e-15) +ψ = apply(gates, ψ0; cutoff = 1.0e-15) @assert inner(ψ, MPS(s, n -> isodd(n) ? "1" : "0")) ≈ 1 # Change to the state |10111011...⟩ append!(gates, [CX[n, n + 3] for n in 1:4:(N - 3)]) -ψ = apply(gates, ψ0; cutoff=1e-15) +ψ = apply(gates, ψ0; cutoff = 1.0e-15) @assert inner(ψ, MPS(s, ["1", "0", "1", "1", "1", "0", "1", "1", "1", "0"])) ≈ 1 # Change the state |10111011...⟩ to the (|+⟩, |-⟩) basis append!(gates, [H[n] for n in 1:N]) -ψ = apply(gates, ψ0; cutoff=1e-15) +ψ = apply(gates, ψ0; cutoff = 1.0e-15) diff --git a/examples/mps_mpo_algebra/mps_density_matrix.jl b/examples/mps_mpo_algebra/mps_density_matrix.jl index 49abf39..4fea4bf 100644 --- a/examples/mps_mpo_algebra/mps_density_matrix.jl +++ b/examples/mps_mpo_algebra/mps_density_matrix.jl @@ -2,9 +2,9 @@ using ITensors, ITensorMPS N = 4 nmps = 3 -cutoff = 1e-8 +cutoff = 1.0e-8 s = siteinds("S=1/2", N) -ψs = [random_mps(s; linkdims=2) for _ in 1:nmps] +ψs = [random_mps(s; linkdims = 2) for _ in 1:nmps] ρs = [outer(ψ, ψ; cutoff) for ψ in ψs] ρ = sum(ρs; cutoff) diff --git a/examples/solvers/01_tdvp.jl b/examples/solvers/01_tdvp.jl index d4be3bc..d7cecd2 100644 --- a/examples/solvers/01_tdvp.jl +++ b/examples/solvers/01_tdvp.jl @@ -1,41 +1,41 @@ using ITensorMPS: MPO, OpSum, dmrg, inner, random_mps, siteinds, tdvp function main() - n = 10 - s = siteinds("S=1/2", n) - - function heisenberg(n) - os = OpSum() - for j in 1:(n - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + n = 10 + s = siteinds("S=1/2", n) + + function heisenberg(n) + os = OpSum() + for j in 1:(n - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + return os end - return os - end - - H = MPO(heisenberg(n), s) - ψ = random_mps(s, "↑"; linkdims=10) - - @show inner(ψ', H, ψ) / inner(ψ, ψ) - - ϕ = tdvp( - H, - -20.0, - ψ; - time_step=-1.0, - maxdim=30, - cutoff=1e-10, - normalize=true, - reverse_step=false, - outputlevel=1, - ) - @show inner(ϕ', H, ϕ) / inner(ϕ, ϕ) - - e2, ϕ2 = dmrg(H, ψ; nsweeps=10, maxdim=20, cutoff=1e-10) - @show inner(ϕ2', H, ϕ2) / inner(ϕ2, ϕ2), e2 - - return nothing + + H = MPO(heisenberg(n), s) + ψ = random_mps(s, "↑"; linkdims = 10) + + @show inner(ψ', H, ψ) / inner(ψ, ψ) + + ϕ = tdvp( + H, + -20.0, + ψ; + time_step = -1.0, + maxdim = 30, + cutoff = 1.0e-10, + normalize = true, + reverse_step = false, + outputlevel = 1, + ) + @show inner(ϕ', H, ϕ) / inner(ϕ, ϕ) + + e2, ϕ2 = dmrg(H, ψ; nsweeps = 10, maxdim = 20, cutoff = 1.0e-10) + @show inner(ϕ2', H, ϕ2) / inner(ϕ2, ϕ2), e2 + + return nothing end main() diff --git a/examples/solvers/02_dmrg-x.jl b/examples/solvers/02_dmrg-x.jl index 4305136..fad9fd6 100644 --- a/examples/solvers/02_dmrg-x.jl +++ b/examples/solvers/02_dmrg-x.jl @@ -2,41 +2,41 @@ using ITensorMPS: MPO, MPS, OpSum, dmrg_x, inner, siteinds using Random: Random function main() - function heisenberg(n; h=zeros(n)) - os = OpSum() - for j in 1:(n - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + function heisenberg(n; h = zeros(n)) + os = OpSum() + for j in 1:(n - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + for j in 1:n + if h[j] ≠ 0 + os -= h[j], "Sz", j + end + end + return os end - for j in 1:n - if h[j] ≠ 0 - os -= h[j], "Sz", j - end - end - return os - end - n = 10 - s = siteinds("S=1/2", n) + n = 10 + s = siteinds("S=1/2", n) - Random.seed!(12) + Random.seed!(12) - # MBL when W > 3.5-4 - W = 12 - # Random fields h ∈ [-W, W] - h = W * (2 * rand(n) .- 1) - H = MPO(heisenberg(n; h), s) + # MBL when W > 3.5-4 + W = 12 + # Random fields h ∈ [-W, W] + h = W * (2 * rand(n) .- 1) + H = MPO(heisenberg(n; h), s) - initstate = rand(["↑", "↓"], n) - ψ = MPS(s, initstate) - e, ϕ = dmrg_x(H, ψ; nsweeps=10, maxdim=20, cutoff=1e-10, normalize=true, outputlevel=1) + initstate = rand(["↑", "↓"], n) + ψ = MPS(s, initstate) + e, ϕ = dmrg_x(H, ψ; nsweeps = 10, maxdim = 20, cutoff = 1.0e-10, normalize = true, outputlevel = 1) - @show inner(ψ', H, ψ) / inner(ψ, ψ) - @show inner(H, ψ, H, ψ) - inner(ψ', H, ψ)^2 - @show inner(ϕ', H, ϕ) / inner(ϕ, ϕ), e - @show inner(H, ϕ, H, ϕ) - inner(ϕ', H, ϕ)^2 - return nothing + @show inner(ψ', H, ψ) / inner(ψ, ψ) + @show inner(H, ψ, H, ψ) - inner(ψ', H, ψ)^2 + @show inner(ϕ', H, ϕ) / inner(ϕ, ϕ), e + @show inner(H, ϕ, H, ϕ) - inner(ϕ', H, ϕ)^2 + return nothing end main() diff --git a/examples/solvers/03_models.jl b/examples/solvers/03_models.jl index 51f5aee..0e43738 100644 --- a/examples/solvers/03_models.jl +++ b/examples/solvers/03_models.jl @@ -1,20 +1,20 @@ using ITensorMPS: OpSum -function heisenberg(n; J=1.0, J2=0.0) - ℋ = OpSum() - if !iszero(J) - for j in 1:(n - 1) - ℋ += J / 2, "S+", j, "S-", j + 1 - ℋ += J / 2, "S-", j, "S+", j + 1 - ℋ += J, "Sz", j, "Sz", j + 1 +function heisenberg(n; J = 1.0, J2 = 0.0) + ℋ = OpSum() + if !iszero(J) + for j in 1:(n - 1) + ℋ += J / 2, "S+", j, "S-", j + 1 + ℋ += J / 2, "S-", j, "S+", j + 1 + ℋ += J, "Sz", j, "Sz", j + 1 + end end - end - if !iszero(J2) - for j in 1:(n - 2) - ℋ += J2 / 2, "S+", j, "S-", j + 2 - ℋ += J2 / 2, "S-", j, "S+", j + 2 - ℋ += J2, "Sz", j, "Sz", j + 2 + if !iszero(J2) + for j in 1:(n - 2) + ℋ += J2 / 2, "S+", j, "S-", j + 2 + ℋ += J2 / 2, "S-", j, "S+", j + 2 + ℋ += J2, "Sz", j, "Sz", j + 2 + end end - end - return ℋ + return ℋ end diff --git a/examples/solvers/03_tdvp_time_dependent.jl b/examples/solvers/03_tdvp_time_dependent.jl index 8190311..27575b2 100644 --- a/examples/solvers/03_tdvp_time_dependent.jl +++ b/examples/solvers/03_tdvp_time_dependent.jl @@ -23,150 +23,150 @@ using CUDA: cu main(; eltype=Float32, device=cu) ``` """ -function main(; eltype=Float64, device=identity) - Random.seed!(1234) - - # Time dependent Hamiltonian is: - # H(t) = H₁(t) + H₂(t) + … - # = f₁(t) H₁(0) + f₂(t) H₂(0) + … - # = cos(ω₁t) H₁(0) + cos(ω₂t) H₂(0) + … - - # Number of sites - n = 6 - - # How much information to output from TDVP - # Set to 2 to get information about each bond/site - # evolution, and 3 to get information about the - # updater. - outputlevel = 3 - - # Frequency of time dependent terms - ω₁ = one(eltype) / 10 - ω₂ = one(eltype) / 5 - - # Nearest and next-nearest neighbor - # Heisenberg couplings. - J₁ = one(eltype) - J₂ = one(eltype) - - time_step = one(eltype) / 10 - time_stop = one(eltype) - - # nsite-update TDVP - nsite = 2 - - # Starting state bond/link dimension. - # A product state starting state can - # cause issues for TDVP without - # subspace expansion. - start_linkdim = 4 - - # TDVP truncation parameters - maxdim = 100 - cutoff = √(eps(eltype)) - - tol = 10 * eps(eltype) - - @show n - @show ω₁, ω₂ - @show J₁, J₂ - @show maxdim, cutoff, nsite - @show start_linkdim - @show time_step, time_stop - - ω⃗ = (ω₁, ω₂) - f⃗ = map(ω -> (t -> cos(ω * t)), ω⃗) - - # H₀ = H(0) = H₁(0) + H₂(0) + … - ℋ₁₀ = heisenberg(n; J=J₁, J2=zero(eltype)) - ℋ₂₀ = heisenberg(n; J=zero(eltype), J2=J₂) - ℋ⃗₀ = (ℋ₁₀, ℋ₂₀) - - s = siteinds("S=1/2", n) - - H⃗₀ = map(ℋ₀ -> device(MPO(eltype, ℋ₀, s)), ℋ⃗₀) - - # Initial state, ψ₀ = ψ(0) - # Initialize as complex since that is what OrdinaryDiffEq.jl/DifferentialEquations.jl - # expects. - ψ₀ = device( - complex.(random_mps(eltype, s, j -> isodd(j) ? "↑" : "↓"; linkdims=start_linkdim)) - ) - - @show norm(ψ₀) - - println() - println("#"^100) - println("Running TDVP with ODE updater") - println("#"^100) - println() - - ψₜ_ode = tdvp( - -im * TimeDependentSum(f⃗, H⃗₀), - time_stop, - ψ₀; - updater=ode_updater, - updater_kwargs=(; reltol=tol, abstol=tol), - time_step, - maxdim, - cutoff, - nsite, - outputlevel, - ) - - println() - println("Finished running TDVP with ODE updater") - println() - - println() - println("#"^100) - println("Running TDVP with Krylov updater") - println("#"^100) - println() - - ψₜ_krylov = tdvp( - -im * TimeDependentSum(f⃗, H⃗₀), - time_stop, - ψ₀; - updater=krylov_updater, - updater_kwargs=(; tol, eager=true), - time_step, - cutoff, - nsite, - outputlevel, - ) - - println() - println("Finished running TDVP with Krylov updater") - println() - - println() - println("#"^100) - println("Running full state evolution with ODE updater") - println("#"^100) - println() - - @disable_warn_order begin - ψₜ_full, _ = ode_updater( - -im * TimeDependentSum(f⃗, contract.(H⃗₀)), - contract(ψ₀); - internal_kwargs=(; time_step=time_stop, outputlevel), - reltol=tol, - abstol=tol, +function main(; eltype = Float64, device = identity) + Random.seed!(1234) + + # Time dependent Hamiltonian is: + # H(t) = H₁(t) + H₂(t) + … + # = f₁(t) H₁(0) + f₂(t) H₂(0) + … + # = cos(ω₁t) H₁(0) + cos(ω₂t) H₂(0) + … + + # Number of sites + n = 6 + + # How much information to output from TDVP + # Set to 2 to get information about each bond/site + # evolution, and 3 to get information about the + # updater. + outputlevel = 3 + + # Frequency of time dependent terms + ω₁ = one(eltype) / 10 + ω₂ = one(eltype) / 5 + + # Nearest and next-nearest neighbor + # Heisenberg couplings. + J₁ = one(eltype) + J₂ = one(eltype) + + time_step = one(eltype) / 10 + time_stop = one(eltype) + + # nsite-update TDVP + nsite = 2 + + # Starting state bond/link dimension. + # A product state starting state can + # cause issues for TDVP without + # subspace expansion. + start_linkdim = 4 + + # TDVP truncation parameters + maxdim = 100 + cutoff = √(eps(eltype)) + + tol = 10 * eps(eltype) + + @show n + @show ω₁, ω₂ + @show J₁, J₂ + @show maxdim, cutoff, nsite + @show start_linkdim + @show time_step, time_stop + + ω⃗ = (ω₁, ω₂) + f⃗ = map(ω -> (t -> cos(ω * t)), ω⃗) + + # H₀ = H(0) = H₁(0) + H₂(0) + … + ℋ₁₀ = heisenberg(n; J = J₁, J2 = zero(eltype)) + ℋ₂₀ = heisenberg(n; J = zero(eltype), J2 = J₂) + ℋ⃗₀ = (ℋ₁₀, ℋ₂₀) + + s = siteinds("S=1/2", n) + + H⃗₀ = map(ℋ₀ -> device(MPO(eltype, ℋ₀, s)), ℋ⃗₀) + + # Initial state, ψ₀ = ψ(0) + # Initialize as complex since that is what OrdinaryDiffEq.jl/DifferentialEquations.jl + # expects. + ψ₀ = device( + complex.(random_mps(eltype, s, j -> isodd(j) ? "↑" : "↓"; linkdims = start_linkdim)) ) - end - println() - println("Finished full state evolution with ODE updater") - println() + @show norm(ψ₀) + + println() + println("#"^100) + println("Running TDVP with ODE updater") + println("#"^100) + println() + + ψₜ_ode = tdvp( + -im * TimeDependentSum(f⃗, H⃗₀), + time_stop, + ψ₀; + updater = ode_updater, + updater_kwargs = (; reltol = tol, abstol = tol), + time_step, + maxdim, + cutoff, + nsite, + outputlevel, + ) - @show norm(ψₜ_ode) - @show norm(ψₜ_krylov) - @show norm(ψₜ_full) + println() + println("Finished running TDVP with ODE updater") + println() + + println() + println("#"^100) + println("Running TDVP with Krylov updater") + println("#"^100) + println() + + ψₜ_krylov = tdvp( + -im * TimeDependentSum(f⃗, H⃗₀), + time_stop, + ψ₀; + updater = krylov_updater, + updater_kwargs = (; tol, eager = true), + time_step, + cutoff, + nsite, + outputlevel, + ) - @show 1 - abs(inner(contract(ψₜ_ode), ψₜ_full)) - @show 1 - abs(inner(contract(ψₜ_krylov), ψₜ_full)) - return nothing + println() + println("Finished running TDVP with Krylov updater") + println() + + println() + println("#"^100) + println("Running full state evolution with ODE updater") + println("#"^100) + println() + + @disable_warn_order begin + ψₜ_full, _ = ode_updater( + -im * TimeDependentSum(f⃗, contract.(H⃗₀)), + contract(ψ₀); + internal_kwargs = (; time_step = time_stop, outputlevel), + reltol = tol, + abstol = tol, + ) + end + + println() + println("Finished full state evolution with ODE updater") + println() + + @show norm(ψₜ_ode) + @show norm(ψₜ_krylov) + @show norm(ψₜ_full) + + @show 1 - abs(inner(contract(ψₜ_ode), ψₜ_full)) + @show 1 - abs(inner(contract(ψₜ_krylov), ψₜ_full)) + return nothing end main() diff --git a/examples/solvers/03_updaters.jl b/examples/solvers/03_updaters.jl index a2436da..c36a99a 100644 --- a/examples/solvers/03_updaters.jl +++ b/examples/solvers/03_updaters.jl @@ -4,20 +4,20 @@ using ITensorMPS: TimeDependentSum, to_vec using KrylovKit: exponentiate using OrdinaryDiffEqTsit5: ODEProblem, Tsit5, solve -function ode_updater(operator, init; internal_kwargs, alg=Tsit5(), kwargs...) - @compat (; current_time, time_step) = (; current_time=zero(Bool), internal_kwargs...) - time_span = typeof(time_step).((current_time, current_time + time_step)) - init_vec, to_itensor = to_vec(init) - f(init::ITensor, p, t) = operator(t)(init) - f(init_vec::AbstractArray, p, t) = to_vec(f(to_itensor(init_vec), p, t))[1] - prob = ODEProblem(f, init_vec, time_span) - sol = solve(prob, alg; kwargs...) - state_vec = sol.u[end] - return to_itensor(state_vec), (;) +function ode_updater(operator, init; internal_kwargs, alg = Tsit5(), kwargs...) + @compat (; current_time, time_step) = (; current_time = zero(Bool), internal_kwargs...) + time_span = typeof(time_step).((current_time, current_time + time_step)) + init_vec, to_itensor = to_vec(init) + f(init::ITensor, p, t) = operator(t)(init) + f(init_vec::AbstractArray, p, t) = to_vec(f(to_itensor(init_vec), p, t))[1] + prob = ODEProblem(f, init_vec, time_span) + sol = solve(prob, alg; kwargs...) + state_vec = sol.u[end] + return to_itensor(state_vec), (;) end function krylov_updater(operator, init; internal_kwargs, kwargs...) - @compat (; current_time, time_step) = (; current_time=zero(Bool), internal_kwargs...) - state, info = exponentiate(operator(current_time), time_step, init; kwargs...) - return state, (; info) + @compat (; current_time, time_step) = (; current_time = zero(Bool), internal_kwargs...) + state, info = exponentiate(operator(current_time), time_step, init; kwargs...) + return state, (; info) end diff --git a/examples/solvers/04_tdvp_observers.jl b/examples/solvers/04_tdvp_observers.jl index 2d6b807..298324c 100644 --- a/examples/solvers/04_tdvp_observers.jl +++ b/examples/solvers/04_tdvp_observers.jl @@ -2,44 +2,44 @@ using ITensorMPS: MPO, MPS, OpSum, expect, inner, siteinds, tdvp using Observers: observer function main() - function heisenberg(N) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + function heisenberg(N) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + return os end - return os - end - N = 10 - s = siteinds("S=1/2", N; conserve_qns=true) - H = MPO(heisenberg(N), s) + N = 10 + s = siteinds("S=1/2", N; conserve_qns = true) + H = MPO(heisenberg(N), s) - step(; sweep) = sweep - current_time(; current_time) = current_time - return_state(; state) = state - measure_sz(; state) = expect(state, "Sz"; sites=length(state) ÷ 2) - obs = observer( - "steps" => step, "times" => current_time, "states" => return_state, "sz" => measure_sz - ) + step(; sweep) = sweep + current_time(; current_time) = current_time + return_state(; state) = state + measure_sz(; state) = expect(state, "Sz"; sites = length(state) ÷ 2) + obs = observer( + "steps" => step, "times" => current_time, "states" => return_state, "sz" => measure_sz + ) - init = MPS(s, n -> isodd(n) ? "Up" : "Dn") - state = tdvp( - H, -1.0im, init; time_step=-0.1im, cutoff=1e-12, (step_observer!)=obs, outputlevel=1 - ) + init = MPS(s, n -> isodd(n) ? "Up" : "Dn") + state = tdvp( + H, -1.0im, init; time_step = -0.1im, cutoff = 1.0e-12, (step_observer!) = obs, outputlevel = 1 + ) - println("\nResults") - println("=======") - for n in 1:length(obs.steps) - print("step = ", obs.steps[n]) - print(", time = ", round(obs.times[n]; digits=3)) - print(", |⟨ψⁿ|ψⁱ⟩| = ", round(abs(inner(obs.states[n], init)); digits=3)) - print(", |⟨ψⁿ|ψᶠ⟩| = ", round(abs(inner(obs.states[n], state)); digits=3)) - print(", ⟨Sᶻ⟩ = ", round(obs.sz[n]; digits=3)) - println() - end - return nothing + println("\nResults") + println("=======") + for n in 1:length(obs.steps) + print("step = ", obs.steps[n]) + print(", time = ", round(obs.times[n]; digits = 3)) + print(", |⟨ψⁿ|ψⁱ⟩| = ", round(abs(inner(obs.states[n], init)); digits = 3)) + print(", |⟨ψⁿ|ψᶠ⟩| = ", round(abs(inner(obs.states[n], state)); digits = 3)) + print(", ⟨Sᶻ⟩ = ", round(obs.sz[n]; digits = 3)) + println() + end + return nothing end main() diff --git a/examples/src/electronk.jl b/examples/src/electronk.jl index 22a73f1..f1ac727 100644 --- a/examples/src/electronk.jl +++ b/examples/src/electronk.jl @@ -1,63 +1,63 @@ using ITensors, ITensorMPS function ITensors.space( - ::SiteType"ElecK", - n::Int; - conserve_qns=false, - conserve_sz=conserve_qns, - conserve_nf=conserve_qns, - conserve_nfparity=conserve_qns, - conserve_ky=false, - qnname_sz="Sz", - qnname_nf="Nf", - qnname_nfparity="NfParity", - qnname_ky="Ky", - modulus_ky=nothing, - # Deprecated - conserve_parity=nothing, -) - if !isnothing(conserve_parity) - conserve_nfparity = conserve_parity - end - if conserve_ky && conserve_sz && conserve_nf - mod = (n - 1) % modulus_ky - mod2 = (2 * mod) % modulus_ky - return [ - QN((qnname_nf, 0, -1), (qnname_sz, 0), (qnname_ky, 0, modulus_ky)) => 1 - QN((qnname_nf, 1, -1), (qnname_sz, 1), (qnname_ky, mod, modulus_ky)) => 1 - QN((qnname_nf, 1, -1), (qnname_sz, -1), (qnname_ky, mod, modulus_ky)) => 1 - QN((qnname_nf, 2, -1), (qnname_sz, 0), (qnname_ky, mod2, modulus_ky)) => 1 - ] - elseif conserve_ky - error("Cannot conserve ky without conserving sz and nf") - elseif conserve_sz && conserve_nf - return [ - QN((qnname_nf, 0, -1), (qnname_sz, 0)) => 1 - QN((qnname_nf, 1, -1), (qnname_sz, +1)) => 1 - QN((qnname_nf, 1, -1), (qnname_sz, -1)) => 1 - QN((qnname_nf, 2, -1), (qnname_sz, 0)) => 1 - ] - elseif conserve_nf - return [ - QN(qnname_nf, 0, -1) => 1 - QN(qnname_nf, 1, -1) => 2 - QN(qnname_nf, 2, -1) => 1 - ] - elseif conserve_sz - return [ - QN((qnname_sz, 0), (qnname_nfparity, 0, -2)) => 1 - QN((qnname_sz, +1), (qnname_nfparity, 1, -2)) => 1 - QN((qnname_sz, -1), (qnname_nfparity, 1, -2)) => 1 - QN((qnname_sz, 0), (qnname_nfparity, 0, -2)) => 1 - ] - elseif conserve_nfparity - return [ - QN(qnname_nfparity, 0, -2) => 1 - QN(qnname_nfparity, 1, -2) => 2 - QN(qnname_nfparity, 0, -2) => 1 - ] - end - return 4 + ::SiteType"ElecK", + n::Int; + conserve_qns = false, + conserve_sz = conserve_qns, + conserve_nf = conserve_qns, + conserve_nfparity = conserve_qns, + conserve_ky = false, + qnname_sz = "Sz", + qnname_nf = "Nf", + qnname_nfparity = "NfParity", + qnname_ky = "Ky", + modulus_ky = nothing, + # Deprecated + conserve_parity = nothing, + ) + if !isnothing(conserve_parity) + conserve_nfparity = conserve_parity + end + if conserve_ky && conserve_sz && conserve_nf + mod = (n - 1) % modulus_ky + mod2 = (2 * mod) % modulus_ky + return [ + QN((qnname_nf, 0, -1), (qnname_sz, 0), (qnname_ky, 0, modulus_ky)) => 1 + QN((qnname_nf, 1, -1), (qnname_sz, 1), (qnname_ky, mod, modulus_ky)) => 1 + QN((qnname_nf, 1, -1), (qnname_sz, -1), (qnname_ky, mod, modulus_ky)) => 1 + QN((qnname_nf, 2, -1), (qnname_sz, 0), (qnname_ky, mod2, modulus_ky)) => 1 + ] + elseif conserve_ky + error("Cannot conserve ky without conserving sz and nf") + elseif conserve_sz && conserve_nf + return [ + QN((qnname_nf, 0, -1), (qnname_sz, 0)) => 1 + QN((qnname_nf, 1, -1), (qnname_sz, +1)) => 1 + QN((qnname_nf, 1, -1), (qnname_sz, -1)) => 1 + QN((qnname_nf, 2, -1), (qnname_sz, 0)) => 1 + ] + elseif conserve_nf + return [ + QN(qnname_nf, 0, -1) => 1 + QN(qnname_nf, 1, -1) => 2 + QN(qnname_nf, 2, -1) => 1 + ] + elseif conserve_sz + return [ + QN((qnname_sz, 0), (qnname_nfparity, 0, -2)) => 1 + QN((qnname_sz, +1), (qnname_nfparity, 1, -2)) => 1 + QN((qnname_sz, -1), (qnname_nfparity, 1, -2)) => 1 + QN((qnname_sz, 0), (qnname_nfparity, 0, -2)) => 1 + ] + elseif conserve_nfparity + return [ + QN(qnname_nfparity, 0, -2) => 1 + QN(qnname_nfparity, 1, -2) => 2 + QN(qnname_nfparity, 0, -2) => 1 + ] + end + return 4 end ITensors.state(::StateName"Emp", ::SiteType"ElecK") = [1.0 0.0 0.0 0.0] @@ -70,126 +70,126 @@ ITensors.state(::StateName"↓", st::SiteType"ElecK") = state(StateName("Dn"), s ITensors.state(::StateName"↑↓", st::SiteType"ElecK") = state(StateName("UpDn"), st) function ITensors.op!(Op::ITensor, ::OpName"Nup", ::SiteType"ElecK", s::Index) - Op[s' => 2, s => 2] = 1.0 - return Op[s' => 4, s => 4] = 1.0 + Op[s' => 2, s => 2] = 1.0 + return Op[s' => 4, s => 4] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"Ndn", ::SiteType"ElecK", s::Index) - Op[s' => 3, s => 3] = 1.0 - return Op[s' => 4, s => 4] = 1.0 + Op[s' => 3, s => 3] = 1.0 + return Op[s' => 4, s => 4] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"Nupdn", ::SiteType"ElecK", s::Index) - return Op[s' => 4, s => 4] = 1.0 + return Op[s' => 4, s => 4] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"Ntot", ::SiteType"ElecK", s::Index) - Op[s' => 2, s => 2] = 1.0 - Op[s' => 3, s => 3] = 1.0 - return Op[s' => 4, s => 4] = 2.0 + Op[s' => 2, s => 2] = 1.0 + Op[s' => 3, s => 3] = 1.0 + return Op[s' => 4, s => 4] = 2.0 end function ITensors.op!(Op::ITensor, ::OpName"Cup", ::SiteType"ElecK", s::Index) - Op[s' => 1, s => 2] = 1.0 - return Op[s' => 3, s => 4] = 1.0 + Op[s' => 1, s => 2] = 1.0 + return Op[s' => 3, s => 4] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"Cdagup", ::SiteType"ElecK", s::Index) - Op[s' => 2, s => 1] = 1.0 - return Op[s' => 4, s => 3] = 1.0 + Op[s' => 2, s => 1] = 1.0 + return Op[s' => 4, s => 3] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"Cdn", ::SiteType"ElecK", s::Index) - Op[s' => 1, s => 3] = 1.0 - return Op[s' => 2, s => 4] = -1.0 + Op[s' => 1, s => 3] = 1.0 + return Op[s' => 2, s => 4] = -1.0 end function ITensors.op!(Op::ITensor, ::OpName"Cdagdn", ::SiteType"ElecK", s::Index) - Op[s' => 3, s => 1] = 1.0 - return Op[s' => 4, s => 2] = -1.0 + Op[s' => 3, s => 1] = 1.0 + return Op[s' => 4, s => 2] = -1.0 end function ITensors.op!(Op::ITensor, ::OpName"Aup", ::SiteType"ElecK", s::Index) - Op[s' => 1, s => 2] = 1.0 - return Op[s' => 3, s => 4] = 1.0 + Op[s' => 1, s => 2] = 1.0 + return Op[s' => 3, s => 4] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"Adagup", ::SiteType"ElecK", s::Index) - Op[s' => 2, s => 1] = 1.0 - return Op[s' => 4, s => 3] = 1.0 + Op[s' => 2, s => 1] = 1.0 + return Op[s' => 4, s => 3] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"Adn", ::SiteType"ElecK", s::Index) - Op[s' => 1, s => 3] = 1.0 - return Op[s' => 2, s => 4] = 1.0 + Op[s' => 1, s => 3] = 1.0 + return Op[s' => 2, s => 4] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"Adagdn", ::SiteType"ElecK", s::Index) - Op[s' => 3, s => 1] = 1.0 - return Op[s' => 2, s => 4] = 1.0 + Op[s' => 3, s => 1] = 1.0 + return Op[s' => 2, s => 4] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"F", ::SiteType"ElecK", s::Index) - Op[s' => 1, s => 1] = +1.0 - Op[s' => 2, s => 2] = -1.0 - Op[s' => 3, s => 3] = -1.0 - return Op[s' => 4, s => 4] = +1.0 + Op[s' => 1, s => 1] = +1.0 + Op[s' => 2, s => 2] = -1.0 + Op[s' => 3, s => 3] = -1.0 + return Op[s' => 4, s => 4] = +1.0 end function ITensors.op!(Op::ITensor, ::OpName"Fup", ::SiteType"ElecK", s::Index) - Op[s' => 1, s => 1] = +1.0 - Op[s' => 2, s => 2] = -1.0 - Op[s' => 3, s => 3] = +1.0 - return Op[s' => 4, s => 4] = -1.0 + Op[s' => 1, s => 1] = +1.0 + Op[s' => 2, s => 2] = -1.0 + Op[s' => 3, s => 3] = +1.0 + return Op[s' => 4, s => 4] = -1.0 end function ITensors.op!(Op::ITensor, ::OpName"Fdn", ::SiteType"ElecK", s::Index) - Op[s' => 1, s => 1] = +1.0 - Op[s' => 2, s => 2] = +1.0 - Op[s' => 3, s => 3] = -1.0 - return Op[s' => 4, s => 4] = -1.0 + Op[s' => 1, s => 1] = +1.0 + Op[s' => 2, s => 2] = +1.0 + Op[s' => 3, s => 3] = -1.0 + return Op[s' => 4, s => 4] = -1.0 end function ITensors.op!(Op::ITensor, ::OpName"Sz", ::SiteType"ElecK", s::Index) - Op[s' => 2, s => 2] = +0.5 - return Op[s' => 3, s => 3] = -0.5 + Op[s' => 2, s => 2] = +0.5 + return Op[s' => 3, s => 3] = -0.5 end function ITensors.op!(Op::ITensor, ::OpName"Sᶻ", st::SiteType"ElecK", s::Index) - return op!(Op, OpName("Sz"), st, s) + return op!(Op, OpName("Sz"), st, s) end function ITensors.op!(Op::ITensor, ::OpName"Sx", ::SiteType"ElecK", s::Index) - Op[s' => 2, s => 3] = 0.5 - return Op[s' => 3, s => 2] = 0.5 + Op[s' => 2, s => 3] = 0.5 + return Op[s' => 3, s => 2] = 0.5 end function ITensors.op!(Op::ITensor, ::OpName"Sˣ", st::SiteType"ElecK", s::Index) - return op!(Op, OpName("Sx"), st, s) + return op!(Op, OpName("Sx"), st, s) end function ITensors.op!(Op::ITensor, ::OpName"S+", ::SiteType"ElecK", s::Index) - return Op[s' => 2, s => 3] = 1.0 + return Op[s' => 2, s => 3] = 1.0 end op!(Op::ITensor, ::OpName"S⁺", st::SiteType"ElecK", s::Index) = op!(Op, OpName("S+"), st, s) op!(Op::ITensor, ::OpName"Sp", st::SiteType"ElecK", s::Index) = op!(Op, OpName("S+"), st, s) function op!(Op::ITensor, ::OpName"Splus", st::SiteType"ElecK", s::Index) - return op!(Op, OpName("S+"), st, s) + return op!(Op, OpName("S+"), st, s) end function op!(Op::ITensor, ::OpName"S-", ::SiteType"ElecK", s::Index) - return Op[s' => 3, s => 2] = 1.0 + return Op[s' => 3, s => 2] = 1.0 end function ITensors.op!(Op::ITensor, ::OpName"S⁻", st::SiteType"ElecK", s::Index) - return op!(Op, OpName("S-"), st, s) + return op!(Op, OpName("S-"), st, s) end function ITensors.op!(Op::ITensor, ::OpName"Sm", st::SiteType"ElecK", s::Index) - return op!(Op, OpName("S-"), st, s) + return op!(Op, OpName("S-"), st, s) end function ITensors.op!(Op::ITensor, ::OpName"Sminus", st::SiteType"ElecK", s::Index) - return op!(Op, OpName("S-"), st, s) + return op!(Op, OpName("S-"), st, s) end ITensors.has_fermion_string(::OpName"Cup", ::SiteType"ElecK") = true diff --git a/examples/src/hubbard.jl b/examples/src/hubbard.jl index 30bf2ef..125db7f 100644 --- a/examples/src/hubbard.jl +++ b/examples/src/hubbard.jl @@ -1,84 +1,83 @@ - -function hubbard_1d(; N::Int, t=1.0, U=0.0) - opsum = OpSum() - for b in 1:(N - 1) - opsum -= t, "Cdagup", b, "Cup", b + 1 - opsum -= t, "Cdagup", b + 1, "Cup", b - opsum -= t, "Cdagdn", b, "Cdn", b + 1 - opsum -= t, "Cdagdn", b + 1, "Cdn", b - end - if U ≠ 0 - for n in 1:N - opsum += U, "Nupdn", n +function hubbard_1d(; N::Int, t = 1.0, U = 0.0) + opsum = OpSum() + for b in 1:(N - 1) + opsum -= t, "Cdagup", b, "Cup", b + 1 + opsum -= t, "Cdagup", b + 1, "Cup", b + opsum -= t, "Cdagdn", b, "Cdn", b + 1 + opsum -= t, "Cdagdn", b + 1, "Cdn", b + end + if U ≠ 0 + for n in 1:N + opsum += U, "Nupdn", n + end end - end - return opsum + return opsum end -function hubbard_2d(; Nx::Int, Ny::Int, t=1.0, U=0.0, yperiodic::Bool=true) - N = Nx * Ny - lattice = square_lattice(Nx, Ny; yperiodic=yperiodic) - opsum = OpSum() - for b in lattice - opsum -= t, "Cdagup", b.s1, "Cup", b.s2 - opsum -= t, "Cdagup", b.s2, "Cup", b.s1 - opsum -= t, "Cdagdn", b.s1, "Cdn", b.s2 - opsum -= t, "Cdagdn", b.s2, "Cdn", b.s1 - end - if U ≠ 0 - for n in 1:N - opsum += U, "Nupdn", n +function hubbard_2d(; Nx::Int, Ny::Int, t = 1.0, U = 0.0, yperiodic::Bool = true) + N = Nx * Ny + lattice = square_lattice(Nx, Ny; yperiodic = yperiodic) + opsum = OpSum() + for b in lattice + opsum -= t, "Cdagup", b.s1, "Cup", b.s2 + opsum -= t, "Cdagup", b.s2, "Cup", b.s1 + opsum -= t, "Cdagdn", b.s1, "Cdn", b.s2 + opsum -= t, "Cdagdn", b.s2, "Cdn", b.s1 + end + if U ≠ 0 + for n in 1:N + opsum += U, "Nupdn", n + end end - end - return opsum + return opsum end -function hubbard_2d_ky(; Nx::Int, Ny::Int, t=1.0, U=0.0) - opsum = OpSum() - for x in 0:(Nx - 1) - for ky in 0:(Ny - 1) - s = x * Ny + ky + 1 - disp = -2 * t * cos((2 * π / Ny) * ky) - if abs(disp) > 1e-12 - opsum += disp, "Nup", s - opsum += disp, "Ndn", s - end +function hubbard_2d_ky(; Nx::Int, Ny::Int, t = 1.0, U = 0.0) + opsum = OpSum() + for x in 0:(Nx - 1) + for ky in 0:(Ny - 1) + s = x * Ny + ky + 1 + disp = -2 * t * cos((2 * π / Ny) * ky) + if abs(disp) > 1.0e-12 + opsum += disp, "Nup", s + opsum += disp, "Ndn", s + end + end end - end - for x in 0:(Nx - 2) - for ky in 0:(Ny - 1) - s1 = x * Ny + ky + 1 - s2 = (x + 1) * Ny + ky + 1 - opsum -= t, "Cdagup", s1, "Cup", s2 - opsum -= t, "Cdagup", s2, "Cup", s1 - opsum -= t, "Cdagdn", s1, "Cdn", s2 - opsum -= t, "Cdagdn", s2, "Cdn", s1 + for x in 0:(Nx - 2) + for ky in 0:(Ny - 1) + s1 = x * Ny + ky + 1 + s2 = (x + 1) * Ny + ky + 1 + opsum -= t, "Cdagup", s1, "Cup", s2 + opsum -= t, "Cdagup", s2, "Cup", s1 + opsum -= t, "Cdagdn", s1, "Cdn", s2 + opsum -= t, "Cdagdn", s2, "Cdn", s1 + end end - end - if U ≠ 0 - for x in 0:(Nx - 1) - for ky in 0:(Ny - 1) - for py in 0:(Ny - 1) - for qy in 0:(Ny - 1) - s1 = x * Ny + (ky + qy + Ny) % Ny + 1 - s2 = x * Ny + (py - qy + Ny) % Ny + 1 - s3 = x * Ny + py + 1 - s4 = x * Ny + ky + 1 - opsum += (U / Ny), "Cdagdn", s1, "Cdagup", s2, "Cup", s3, "Cdn", s4 - end + if U ≠ 0 + for x in 0:(Nx - 1) + for ky in 0:(Ny - 1) + for py in 0:(Ny - 1) + for qy in 0:(Ny - 1) + s1 = x * Ny + (ky + qy + Ny) % Ny + 1 + s2 = x * Ny + (py - qy + Ny) % Ny + 1 + s3 = x * Ny + py + 1 + s4 = x * Ny + ky + 1 + opsum += (U / Ny), "Cdagdn", s1, "Cdagup", s2, "Cup", s3, "Cdn", s4 + end + end + end end - end end - end - return opsum + return opsum end -function hubbard(; Nx::Int, Ny::Int=1, t=1.0, U=0.0, yperiodic::Bool=true, ky::Bool=false) - return opsum = if Ny == 1 - hubbard_1d(; N=Nx, t, U) - elseif ky - hubbard_2d_ky(; Nx, Ny, t, U) - else - hubbard_2d(; Nx, Ny, yperiodic, t, U) - end +function hubbard(; Nx::Int, Ny::Int = 1, t = 1.0, U = 0.0, yperiodic::Bool = true, ky::Bool = false) + return opsum = if Ny == 1 + hubbard_1d(; N = Nx, t, U) + elseif ky + hubbard_2d_ky(; Nx, Ny, t, U) + else + hubbard_2d(; Nx, Ny, yperiodic, t, U) + end end diff --git a/ext/ITensorMPSChainRulesCoreExt/abstractmps.jl b/ext/ITensorMPSChainRulesCoreExt/abstractmps.jl index 09f396c..319242e 100644 --- a/ext/ITensorMPSChainRulesCoreExt/abstractmps.jl +++ b/ext/ITensorMPSChainRulesCoreExt/abstractmps.jl @@ -1,192 +1,192 @@ using Adapt: adapt using ChainRulesCore: ChainRulesCore, HasReverseMode, NoTangent, RuleConfig, rrule_via_ad using ITensors: - ITensors, ITensor, dag, hassameinds, inds, itensor, mapprime, replaceprime, swapprime + ITensors, ITensor, dag, hassameinds, inds, itensor, mapprime, replaceprime, swapprime using ITensorMPS: ITensorMPS, MPO, MPS, apply, inner, siteinds using NDTensors: datatype function ChainRulesCore.rrule( - ::Type{T}, x::Vector{<:ITensor}; kwargs... -) where {T<:Union{MPS,MPO}} - y = T(x; kwargs...) - function T_pullback(ȳ) - ȳtensors = ȳ.data - n = length(ȳtensors) - envL = [ȳtensors[1] * dag(x[1])] - envR = [ȳtensors[n] * dag(x[n])] - for j in 2:(n - 1) - push!(envL, envL[j - 1] * ȳtensors[j] * dag(x[j])) - push!(envR, envR[j - 1] * ȳtensors[n + 1 - j] * dag(x[n + 1 - j])) - end + ::Type{T}, x::Vector{<:ITensor}; kwargs... + ) where {T <: Union{MPS, MPO}} + y = T(x; kwargs...) + function T_pullback(ȳ) + ȳtensors = ȳ.data + n = length(ȳtensors) + envL = [ȳtensors[1] * dag(x[1])] + envR = [ȳtensors[n] * dag(x[n])] + for j in 2:(n - 1) + push!(envL, envL[j - 1] * ȳtensors[j] * dag(x[j])) + push!(envR, envR[j - 1] * ȳtensors[n + 1 - j] * dag(x[n + 1 - j])) + end - x̄ = ITensor[] - push!(x̄, ȳtensors[1] * envR[n - 1]) - for j in 2:(n - 1) - push!(x̄, envL[j - 1] * ȳtensors[j] * envR[n - j]) + x̄ = ITensor[] + push!(x̄, ȳtensors[1] * envR[n - 1]) + for j in 2:(n - 1) + push!(x̄, envL[j - 1] * ȳtensors[j] * envR[n - j]) + end + push!(x̄, envL[n - 1] * ȳtensors[n]) + return (NoTangent(), x̄) end - push!(x̄, envL[n - 1] * ȳtensors[n]) - return (NoTangent(), x̄) - end - return y, T_pullback + return y, T_pullback end function ChainRulesCore.rrule( - ::typeof(inner), x1::T, x2::T; kwargs... -) where {T<:Union{MPS,MPO}} - if !hassameinds(siteinds, x1, x2) - error( - "Taking gradients of `inner(::MPS, ::MPS)` is not supported if the site indices of the input MPS don't match. If you input `inner(x, Ay)` where `Ay` is the result of something like `contract(A::MPO, y::MPS)`, try `inner(x', Ay)` or `inner(x, replaceprime(Ay, 1 => 0))`instead.", - ) - end - y = inner(x1, x2) - function inner_pullback(ȳ) - x̄1 = dag(ȳ) * x2 - # `dag` of `x1` gets reversed by `inner` - x̄2 = x1 * ȳ - return (NoTangent(), x̄1, x̄2) - end - return y, inner_pullback + ::typeof(inner), x1::T, x2::T; kwargs... + ) where {T <: Union{MPS, MPO}} + if !hassameinds(siteinds, x1, x2) + error( + "Taking gradients of `inner(::MPS, ::MPS)` is not supported if the site indices of the input MPS don't match. If you input `inner(x, Ay)` where `Ay` is the result of something like `contract(A::MPO, y::MPS)`, try `inner(x', Ay)` or `inner(x, replaceprime(Ay, 1 => 0))`instead.", + ) + end + y = inner(x1, x2) + function inner_pullback(ȳ) + x̄1 = dag(ȳ) * x2 + # `dag` of `x1` gets reversed by `inner` + x̄2 = x1 * ȳ + return (NoTangent(), x̄1, x̄2) + end + return y, inner_pullback end # TODO: Define a more general version in ITensors.jl -function _contract(::Type{ITensor}, ψ::Union{MPS,MPO}, ϕ::Union{MPS,MPO}; kwargs...) - n = length(ψ) - @assert length(ϕ) == length(ψ) - - jcenter = findfirst(j -> !hassameinds(siteinds(ψ, j), siteinds(ϕ, j)), 1:n) - - Tᴸ = adapt(datatype(ψ[1]), ITensor(1)) - for j in 1:jcenter - Tᴸ = Tᴸ * ψ[j] * ϕ[j] - end - Tᴿ = adapt(datatype(ψ[end]), ITensor(1)) - for j in reverse((jcenter + 1):length(ψ)) - Tᴿ = Tᴿ * ψ[j] * ϕ[j] - end - return Tᴸ * Tᴿ +function _contract(::Type{ITensor}, ψ::Union{MPS, MPO}, ϕ::Union{MPS, MPO}; kwargs...) + n = length(ψ) + @assert length(ϕ) == length(ψ) + + jcenter = findfirst(j -> !hassameinds(siteinds(ψ, j), siteinds(ϕ, j)), 1:n) + + Tᴸ = adapt(datatype(ψ[1]), ITensor(1)) + for j in 1:jcenter + Tᴸ = Tᴸ * ψ[j] * ϕ[j] + end + Tᴿ = adapt(datatype(ψ[end]), ITensor(1)) + for j in reverse((jcenter + 1):length(ψ)) + Tᴿ = Tᴿ * ψ[j] * ϕ[j] + end + return Tᴸ * Tᴿ end function _contract(::Type{MPO}, ψ::MPS, ϕ::MPS; kwargs...) - ψmat = convert(MPO, ψ) - ϕmat = convert(MPO, ϕ) - return contract(ψmat, ϕmat; kwargs...) + ψmat = convert(MPO, ψ) + ϕmat = convert(MPO, ϕ) + return contract(ψmat, ϕmat; kwargs...) end # quick and dirty # is this sufficient always? function _is_mps_or_hermitian_mpo(x::MPO; kwargs...) - s = siteinds(x) - return all(eachindex(x)) do i - isapprox(x[i], swapprime(dag(x[i]), 0 => 1; inds=s[i]); kwargs...) - end + s = siteinds(x) + return all(eachindex(x)) do i + isapprox(x[i], swapprime(dag(x[i]), 0 => 1; inds = s[i]); kwargs...) + end end _is_mps_or_hermitian_mpo(x::MPS; kwargs...) = true function ChainRulesCore.rrule( - ::typeof(apply), x1::Vector{ITensor}, x2::Union{MPS,MPO}; apply_dag=false, kwargs... -) - #if apply_dag && !_is_mps_or_hermitian_mpo(x2) - #error( - #"For now, we only support taking derivatives of MPO gate application with `apply_dag=true` for Hermitian MPOs. As an alternative, you can manually apply the gate once on each side of the MPO.", - #) - #end - - N = length(x1) + 1 - - # Apply circuit and store intermediates in the forward direction - x1x2 = Vector{typeof(x2)}(undef, N) - x1x2[1] = x2 - for n in 2:N - x1x2[n] = apply(x1[n - 1], x1x2[n - 1]; move_sites_back=true, apply_dag, kwargs...) - end - y = x1x2[end] - - function apply_pullback(ȳ) - x1x2dag = dag.(x1x2) - x1dag = [swapprime(dag(x), 0 => 1) for x in x1] - - # Apply circuit and store intermediates in the reverse direction - x1dag_ȳ = Vector{typeof(x2)}(undef, N) - x1dag_ȳ[end] = ȳ - for n in (N - 1):-1:1 - x1dag_ȳ[n] = apply( - x1dag[n], x1dag_ȳ[n + 1]; move_sites_back=true, apply_dag, kwargs... - ) + ::typeof(apply), x1::Vector{ITensor}, x2::Union{MPS, MPO}; apply_dag = false, kwargs... + ) + #if apply_dag && !_is_mps_or_hermitian_mpo(x2) + #error( + #"For now, we only support taking derivatives of MPO gate application with `apply_dag=true` for Hermitian MPOs. As an alternative, you can manually apply the gate once on each side of the MPO.", + #) + #end + + N = length(x1) + 1 + + # Apply circuit and store intermediates in the forward direction + x1x2 = Vector{typeof(x2)}(undef, N) + x1x2[1] = x2 + for n in 2:N + x1x2[n] = apply(x1[n - 1], x1x2[n - 1]; move_sites_back = true, apply_dag, kwargs...) end + y = x1x2[end] + + function apply_pullback(ȳ) + x1x2dag = dag.(x1x2) + x1dag = [swapprime(dag(x), 0 => 1) for x in x1] + + # Apply circuit and store intermediates in the reverse direction + x1dag_ȳ = Vector{typeof(x2)}(undef, N) + x1dag_ȳ[end] = ȳ + for n in (N - 1):-1:1 + x1dag_ȳ[n] = apply( + x1dag[n], x1dag_ȳ[n + 1]; move_sites_back = true, apply_dag, kwargs... + ) + end - x̄1 = similar(x1) - for n in 1:length(x1) - # check if it's not a noisy gate (rank-3 tensor) - if iseven(length(inds(x1[n]))) - gateinds = inds(x1[n]; plev=0) - if x2 isa MPS - ξ̃ = prime(x1dag_ȳ[n + 1], gateinds) - ϕ̃ = x1x2dag[n] - x̄1[n] = _contract(ITensor, ξ̃, ϕ̃; kwargs...) - else - # apply U on one side of the MPO - if apply_dag - ishermitian = _is_mps_or_hermitian_mpo(x2) - - if ishermitian - ϕ̃ = swapprime(x1x2dag[n], 0 => 1) - ϕ̃ = apply(x1[n], ϕ̃; move_sites_back=true, apply_dag=false, kwargs...) - ϕ̃ = mapprime(ϕ̃, 1 => 2, 0 => 1) - ϕ̃ = replaceprime(ϕ̃, 1 => 0; inds=gateinds') - - ξ̃ = 2 * dag(x1dag_ȳ[n + 1])' - x̄1[n] = _contract(ITensor, ξ̃, ϕ̃; kwargs...) + x̄1 = similar(x1) + for n in 1:length(x1) + # check if it's not a noisy gate (rank-3 tensor) + if iseven(length(inds(x1[n]))) + gateinds = inds(x1[n]; plev = 0) + if x2 isa MPS + ξ̃ = prime(x1dag_ȳ[n + 1], gateinds) + ϕ̃ = x1x2dag[n] + x̄1[n] = _contract(ITensor, ξ̃, ϕ̃; kwargs...) + else + # apply U on one side of the MPO + if apply_dag + ishermitian = _is_mps_or_hermitian_mpo(x2) + + if ishermitian + ϕ̃ = swapprime(x1x2dag[n], 0 => 1) + ϕ̃ = apply(x1[n], ϕ̃; move_sites_back = true, apply_dag = false, kwargs...) + ϕ̃ = mapprime(ϕ̃, 1 => 2, 0 => 1) + ϕ̃ = replaceprime(ϕ̃, 1 => 0; inds = gateinds') + + ξ̃ = 2 * dag(x1dag_ȳ[n + 1])' + x̄1[n] = _contract(ITensor, ξ̃, ϕ̃; kwargs...) + else + # prepare contribution from taking the derivative w.r.t. Q + # M = x1Q†, QM -> M†W̄ = Qx1†W̄ + ϕ̃ = swapprime(x1x2dag[n], 0 => 1) + ϕ̃ = apply(x1[n], ϕ̃; move_sites_back = true, apply_dag = false, kwargs...) + ϕ̃ = mapprime(ϕ̃, 1 => 2, 0 => 1) + ϕ̃ = replaceprime(ϕ̃, 1 => 0; inds = gateinds') + ξ̃ = mapprime(x1dag_ȳ[n + 1], 0 => 2) + x̄1[n] = _contract(ITensor, ξ̃, ϕ̃; kwargs...) + + # prepare contribution from taking the derivative w.r.t. Q† + # M = Qx1, MQ† -> W̄†M = W̄†Qx1 + ϕ̃ = apply(x1[n], x1x2[n]; move_sites_back = true, apply_dag = false, kwargs...) + ϕ̃ = mapprime(ϕ̃, 1 => 2, 0 => 1) + ϕ̃ = replaceprime(ϕ̃, 1 => 0; inds = gateinds') + ξ̃ = dag(x1dag_ȳ[n + 1])' + x̄1[n] += _contract(ITensor, ξ̃, ϕ̃; kwargs...) + end + else + ϕ̃ = mapprime(x1x2dag[n], 0 => 2) + ϕ̃ = replaceprime(ϕ̃, 1 => 0; inds = gateinds') + ξ̃ = mapprime(x1dag_ȳ[n + 1], 0 => 2) + x̄1[n] = _contract(ITensor, ξ̃, ϕ̃; kwargs...) + end + end else - # prepare contribution from taking the derivative w.r.t. Q - # M = x1Q†, QM -> M†W̄ = Qx1†W̄ - ϕ̃ = swapprime(x1x2dag[n], 0 => 1) - ϕ̃ = apply(x1[n], ϕ̃; move_sites_back=true, apply_dag=false, kwargs...) - ϕ̃ = mapprime(ϕ̃, 1 => 2, 0 => 1) - ϕ̃ = replaceprime(ϕ̃, 1 => 0; inds=gateinds') - ξ̃ = mapprime(x1dag_ȳ[n + 1], 0 => 2) - x̄1[n] = _contract(ITensor, ξ̃, ϕ̃; kwargs...) - - # prepare contribution from taking the derivative w.r.t. Q† - # M = Qx1, MQ† -> W̄†M = W̄†Qx1 - ϕ̃ = apply(x1[n], x1x2[n]; move_sites_back=true, apply_dag=false, kwargs...) - ϕ̃ = mapprime(ϕ̃, 1 => 2, 0 => 1) - ϕ̃ = replaceprime(ϕ̃, 1 => 0; inds=gateinds') - ξ̃ = dag(x1dag_ȳ[n + 1])' - x̄1[n] += _contract(ITensor, ξ̃, ϕ̃; kwargs...) + s = inds(x1[n]) + x̄1[n] = itensor(zeros(dim.(s)), s...) end - else - ϕ̃ = mapprime(x1x2dag[n], 0 => 2) - ϕ̃ = replaceprime(ϕ̃, 1 => 0; inds=gateinds') - ξ̃ = mapprime(x1dag_ȳ[n + 1], 0 => 2) - x̄1[n] = _contract(ITensor, ξ̃, ϕ̃; kwargs...) - end end - else - s = inds(x1[n]) - x̄1[n] = itensor(zeros(dim.(s)), s...) - end + x̄2 = x1dag_ȳ[1] + return (NoTangent(), x̄1, x̄2) end - x̄2 = x1dag_ȳ[1] - return (NoTangent(), x̄1, x̄2) - end - return y, apply_pullback + return y, apply_pullback end function ChainRulesCore.rrule( - config::RuleConfig{>:HasReverseMode}, - ::typeof(map), - f, - x::Union{MPS,MPO}; - set_limits::Bool=true, -) - y_data, pullback_data = rrule_via_ad(config, map, f, ITensorMPS.data(x)) - function map_pullback(ȳ) - dmap, df, dx_data = pullback_data(ȳ) - return dmap, df, MPS(dx_data) - end - y = typeof(x)(y_data) - if !set_limits - y = ITensorMPS.set_ortho_lims(y, ITensorMPS.ortho_lims(x)) - end - return y, map_pullback + config::RuleConfig{>:HasReverseMode}, + ::typeof(map), + f, + x::Union{MPS, MPO}; + set_limits::Bool = true, + ) + y_data, pullback_data = rrule_via_ad(config, map, f, ITensorMPS.data(x)) + function map_pullback(ȳ) + dmap, df, dx_data = pullback_data(ȳ) + return dmap, df, MPS(dx_data) + end + y = typeof(x)(y_data) + if !set_limits + y = ITensorMPS.set_ortho_lims(y, ITensorMPS.ortho_lims(x)) + end + return y, map_pullback end diff --git a/ext/ITensorMPSChainRulesCoreExt/indexset.jl b/ext/ITensorMPSChainRulesCoreExt/indexset.jl index 154f3f3..0ad5c91 100644 --- a/ext/ITensorMPSChainRulesCoreExt/indexset.jl +++ b/ext/ITensorMPSChainRulesCoreExt/indexset.jl @@ -1,34 +1,34 @@ using ChainRulesCore: ChainRulesCore, unthunk using Compat: Returns using ITensors: - addtags, - noprime, - prime, - removetags, - replaceinds, - replaceprime, - replacetags, - setprime, - settags + addtags, + noprime, + prime, + removetags, + replaceinds, + replaceprime, + replacetags, + setprime, + settags using ITensorMPS: MPO, MPS for fname in ( - :prime, :setprime, :noprime, :replaceprime, :addtags, :removetags, :replacetags, :settags -) - @eval begin - function ChainRulesCore.rrule(f::typeof($fname), x::Union{MPS,MPO}, a...; kwargs...) - y = f(x, a...; kwargs...) - function f_pullback(ȳ) - x̄ = copy(unthunk(ȳ)) - for j in eachindex(x̄) - x̄[j] = replaceinds(ȳ[j], inds(y[j]) => inds(x[j])) + :prime, :setprime, :noprime, :replaceprime, :addtags, :removetags, :replacetags, :settags, + ) + @eval begin + function ChainRulesCore.rrule(f::typeof($fname), x::Union{MPS, MPO}, a...; kwargs...) + y = f(x, a...; kwargs...) + function f_pullback(ȳ) + x̄ = copy(unthunk(ȳ)) + for j in eachindex(x̄) + x̄[j] = replaceinds(ȳ[j], inds(y[j]) => inds(x[j])) + end + ā = map(Returns(NoTangent()), a) + return (NoTangent(), x̄, ā...) + end + return y, f_pullback end - ā = map(Returns(NoTangent()), a) - return (NoTangent(), x̄, ā...) - end - return y, f_pullback end - end end -ChainRulesCore.rrule(::typeof(adjoint), x::Union{MPS,MPO}) = ChainRulesCore.rrule(prime, x) +ChainRulesCore.rrule(::typeof(adjoint), x::Union{MPS, MPO}) = ChainRulesCore.rrule(prime, x) diff --git a/ext/ITensorMPSChainRulesCoreExt/mpo.jl b/ext/ITensorMPSChainRulesCoreExt/mpo.jl index 3da69ac..292124e 100644 --- a/ext/ITensorMPSChainRulesCoreExt/mpo.jl +++ b/ext/ITensorMPSChainRulesCoreExt/mpo.jl @@ -4,27 +4,27 @@ using ITensorMPS: MPO, MPS, firstsiteinds, siteinds using LinearAlgebra: tr function ChainRulesCore.rrule( - ::typeof(contract), alg::Algorithm, x1::MPO, x2::MPO; kwargs... -) - y = contract(alg, x1, x2; kwargs...) - function contract_pullback(ȳ) - x̄1 = contract(alg, ȳ, dag(x2); kwargs...) - x̄2 = contract(alg, dag(x1), ȳ; kwargs...) - return (NoTangent(), NoTangent(), x̄1, x̄2) - end - return y, contract_pullback + ::typeof(contract), alg::Algorithm, x1::MPO, x2::MPO; kwargs... + ) + y = contract(alg, x1, x2; kwargs...) + function contract_pullback(ȳ) + x̄1 = contract(alg, ȳ, dag(x2); kwargs...) + x̄2 = contract(alg, dag(x1), ȳ; kwargs...) + return (NoTangent(), NoTangent(), x̄1, x̄2) + end + return y, contract_pullback end function ChainRulesCore.rrule( - ::typeof(contract), alg::Algorithm, x1::MPO, x2::MPS; kwargs... -) - y = contract(alg, x1, x2; kwargs...) - function contract_pullback(ȳ) - x̄1 = _contract(MPO, ȳ, dag(x2); kwargs...) - x̄2 = contract(alg, dag(x1), ȳ; kwargs...) - return (NoTangent(), NoTangent(), x̄1, x̄2) - end - return y, contract_pullback + ::typeof(contract), alg::Algorithm, x1::MPO, x2::MPS; kwargs... + ) + y = contract(alg, x1, x2; kwargs...) + function contract_pullback(ȳ) + x̄1 = _contract(MPO, ȳ, dag(x2); kwargs...) + x̄2 = contract(alg, dag(x1), ȳ; kwargs...) + return (NoTangent(), NoTangent(), x̄1, x̄2) + end + return y, contract_pullback end ## function ChainRulesCore.rrule(::typeof(*), x1::MPO, x2::MPO; alg, kwargs...) @@ -32,53 +32,53 @@ end ## end function ChainRulesCore.rrule(::typeof(+), x1::MPO, x2::MPO; kwargs...) - y = +(x1, x2; kwargs...) - function add_pullback(ȳ) - return (NoTangent(), ȳ, ȳ) - end - return y, add_pullback + y = +(x1, x2; kwargs...) + function add_pullback(ȳ) + return (NoTangent(), ȳ, ȳ) + end + return y, add_pullback end function ChainRulesCore.rrule(::typeof(-), x1::MPO, x2::MPO; kwargs...) - y = -(x1, x2; kwargs...) - function subtract_pullback(ȳ) - return (NoTangent(), ȳ, -ȳ) - end - return y, subtract_pullback + y = -(x1, x2; kwargs...) + function subtract_pullback(ȳ) + return (NoTangent(), ȳ, -ȳ) + end + return y, subtract_pullback end -function ChainRulesCore.rrule(::typeof(tr), x::MPO; plev=(0 => 1), kwargs...) - y = tr(x; plev, kwargs...) - function tr_pullback(ȳ) - s = noprime(firstsiteinds(x)) - n = length(s) - x̄ = MPO(s, "Id") - for j in 1:n - x̄[j] = mapprime(x̄[j], 0 => first(plev), 1 => last(plev)) +function ChainRulesCore.rrule(::typeof(tr), x::MPO; plev = (0 => 1), kwargs...) + y = tr(x; plev, kwargs...) + function tr_pullback(ȳ) + s = noprime(firstsiteinds(x)) + n = length(s) + x̄ = MPO(s, "Id") + for j in 1:n + x̄[j] = mapprime(x̄[j], 0 => first(plev), 1 => last(plev)) + end + return (NoTangent(), ȳ * x̄) end - return (NoTangent(), ȳ * x̄) - end - return y, tr_pullback + return y, tr_pullback end function ChainRulesCore.rrule(::typeof(inner), x1::MPS, x2::MPO, x3::MPS; kwargs...) - if !hassameinds(siteinds, x1, (x2, x3)) || !hassameinds(siteinds, x3, (x2, x1)) - error( - "Taking gradients of `inner(x::MPS, A::MPO, y::MPS)` is not supported if the site indices of the input MPS and MPO don't match. Try using if you input `inner(x, A, y), try `inner(x', A, y)` instead.", - ) - end + if !hassameinds(siteinds, x1, (x2, x3)) || !hassameinds(siteinds, x3, (x2, x1)) + error( + "Taking gradients of `inner(x::MPS, A::MPO, y::MPS)` is not supported if the site indices of the input MPS and MPO don't match. Try using if you input `inner(x, A, y), try `inner(x', A, y)` instead.", + ) + end - y = inner(x1, x2, x3; kwargs...) - function inner_pullback(ȳ) - x̄1 = dag(ȳ) * contract(x2, x3; kwargs...) - x̄2 = ȳ * dag(_contract(MPO, dag(x1), x3; kwargs...)) - x̄3 = contract(dag(x2), x1; kwargs...) * ȳ + y = inner(x1, x2, x3; kwargs...) + function inner_pullback(ȳ) + x̄1 = dag(ȳ) * contract(x2, x3; kwargs...) + x̄2 = ȳ * dag(_contract(MPO, dag(x1), x3; kwargs...)) + x̄3 = contract(dag(x2), x1; kwargs...) * ȳ - @assert siteinds(x1) == siteinds(x̄1) - @assert hassameinds(siteinds, x2, x̄2) - @assert siteinds(x3) == siteinds(x̄3) + @assert siteinds(x1) == siteinds(x̄1) + @assert hassameinds(siteinds, x2, x̄2) + @assert siteinds(x3) == siteinds(x̄3) - return (NoTangent(), x̄1, x̄2, x̄3) - end - return y, inner_pullback + return (NoTangent(), x̄1, x̄2, x̄3) + end + return y, inner_pullback end diff --git a/ext/ITensorMPSHDF5Ext/mpo.jl b/ext/ITensorMPSHDF5Ext/mpo.jl index 90a53a0..28b9176 100644 --- a/ext/ITensorMPSHDF5Ext/mpo.jl +++ b/ext/ITensorMPSHDF5Ext/mpo.jl @@ -2,27 +2,28 @@ using HDF5: HDF5, attributes, create_group, open_group, read, write using ITensors: ITensor using ITensorMPS: MPO -function HDF5.write(parent::Union{HDF5.File,HDF5.Group}, name::AbstractString, M::MPO) - g = create_group(parent, name) - attributes(g)["type"] = "MPO" - attributes(g)["version"] = 1 - N = length(M) - write(g, "rlim", M.rlim) - write(g, "llim", M.llim) - write(g, "length", N) - for n in 1:N - write(g, "MPO[$(n)]", M[n]) - end +function HDF5.write(parent::Union{HDF5.File, HDF5.Group}, name::AbstractString, M::MPO) + g = create_group(parent, name) + attributes(g)["type"] = "MPO" + attributes(g)["version"] = 1 + N = length(M) + write(g, "rlim", M.rlim) + write(g, "llim", M.llim) + write(g, "length", N) + for n in 1:N + write(g, "MPO[$(n)]", M[n]) + end + return end -function HDF5.read(parent::Union{HDF5.File,HDF5.Group}, name::AbstractString, ::Type{MPO}) - g = open_group(parent, name) - if read(attributes(g)["type"]) != "MPO" - error("HDF5 group or file does not contain MPO data") - end - N = read(g, "length") - rlim = read(g, "rlim") - llim = read(g, "llim") - v = [read(g, "MPO[$(i)]", ITensor) for i in 1:N] - return MPO(v, llim, rlim) +function HDF5.read(parent::Union{HDF5.File, HDF5.Group}, name::AbstractString, ::Type{MPO}) + g = open_group(parent, name) + if read(attributes(g)["type"]) != "MPO" + error("HDF5 group or file does not contain MPO data") + end + N = read(g, "length") + rlim = read(g, "rlim") + llim = read(g, "llim") + v = [read(g, "MPO[$(i)]", ITensor) for i in 1:N] + return MPO(v, llim, rlim) end diff --git a/ext/ITensorMPSHDF5Ext/mps.jl b/ext/ITensorMPSHDF5Ext/mps.jl index 619a381..9ca2c98 100644 --- a/ext/ITensorMPSHDF5Ext/mps.jl +++ b/ext/ITensorMPSHDF5Ext/mps.jl @@ -2,27 +2,28 @@ using HDF5: HDF5, attributes, create_group, open_group, read, write using ITensors: ITensor using ITensorMPS: MPS -function HDF5.write(parent::Union{HDF5.File,HDF5.Group}, name::AbstractString, M::MPS) - g = create_group(parent, name) - attributes(g)["type"] = "MPS" - attributes(g)["version"] = 1 - N = length(M) - write(g, "length", N) - write(g, "rlim", M.rlim) - write(g, "llim", M.llim) - for n in 1:N - write(g, "MPS[$(n)]", M[n]) - end +function HDF5.write(parent::Union{HDF5.File, HDF5.Group}, name::AbstractString, M::MPS) + g = create_group(parent, name) + attributes(g)["type"] = "MPS" + attributes(g)["version"] = 1 + N = length(M) + write(g, "length", N) + write(g, "rlim", M.rlim) + write(g, "llim", M.llim) + for n in 1:N + write(g, "MPS[$(n)]", M[n]) + end + return end -function HDF5.read(parent::Union{HDF5.File,HDF5.Group}, name::AbstractString, ::Type{MPS}) - g = open_group(parent, name) - if read(attributes(g)["type"]) != "MPS" - error("HDF5 group or file does not contain MPS data") - end - N = read(g, "length") - rlim = read(g, "rlim") - llim = read(g, "llim") - v = [read(g, "MPS[$(i)]", ITensor) for i in 1:N] - return MPS(v, llim, rlim) +function HDF5.read(parent::Union{HDF5.File, HDF5.Group}, name::AbstractString, ::Type{MPS}) + g = open_group(parent, name) + if read(attributes(g)["type"]) != "MPS" + error("HDF5 group or file does not contain MPS data") + end + N = read(g, "length") + rlim = read(g, "rlim") + llim = read(g, "llim") + v = [read(g, "MPS[$(i)]", ITensor) for i in 1:N] + return MPS(v, llim, rlim) end diff --git a/ext/ITensorMPSObserversExt/ITensorMPSObserversExt.jl b/ext/ITensorMPSObserversExt/ITensorMPSObserversExt.jl index f353b99..139bca2 100644 --- a/ext/ITensorMPSObserversExt/ITensorMPSObserversExt.jl +++ b/ext/ITensorMPSObserversExt/ITensorMPSObserversExt.jl @@ -4,6 +4,6 @@ using Observers.DataFrames: AbstractDataFrame using ITensorMPS: ITensorMPS function ITensorMPS.update_observer!(observer::AbstractDataFrame; kwargs...) - return Observers.update!(observer; kwargs...) + return Observers.update!(observer; kwargs...) end end diff --git a/ext/ITensorMPSPackageCompilerExt/compile.jl b/ext/ITensorMPSPackageCompilerExt/compile.jl index c14d0e9..e178931 100644 --- a/ext/ITensorMPSPackageCompilerExt/compile.jl +++ b/ext/ITensorMPSPackageCompilerExt/compile.jl @@ -3,24 +3,24 @@ using ITensors: ITensors using PackageCompiler: PackageCompiler function ITensors.compile( - ::Algorithm"PackageCompiler"; - dir::AbstractString=ITensors.default_compile_dir(), - filename::AbstractString=ITensors.default_compile_filename(), -) - if !isdir(dir) - println("""The directory "$dir" doesn't exist yet, creating it now.""") - println() - mkdir(dir) - end - path = joinpath(dir, filename) - println( - """Creating the system image "$path" containing the compiled version of ITensorMPS. This may take a few minutes.""", - ) - PackageCompiler.create_sysimage( - :ITensorMPS; - sysimage_path=path, - precompile_execution_file=joinpath(@__DIR__, "precompile_itensormps.jl"), - ) - println(ITensors.compile_note(; dir, filename)) - return path + ::Algorithm"PackageCompiler"; + dir::AbstractString = ITensors.default_compile_dir(), + filename::AbstractString = ITensors.default_compile_filename(), + ) + if !isdir(dir) + println("""The directory "$dir" doesn't exist yet, creating it now.""") + println() + mkdir(dir) + end + path = joinpath(dir, filename) + println( + """Creating the system image "$path" containing the compiled version of ITensorMPS. This may take a few minutes.""", + ) + PackageCompiler.create_sysimage( + :ITensorMPS; + sysimage_path = path, + precompile_execution_file = joinpath(@__DIR__, "precompile_itensormps.jl"), + ) + println(ITensors.compile_note(; dir, filename)) + return path end diff --git a/ext/ITensorMPSPackageCompilerExt/precompile_itensormps.jl b/ext/ITensorMPSPackageCompilerExt/precompile_itensormps.jl index ebcadde..70730a4 100644 --- a/ext/ITensorMPSPackageCompilerExt/precompile_itensormps.jl +++ b/ext/ITensorMPSPackageCompilerExt/precompile_itensormps.jl @@ -10,19 +10,19 @@ using ITensorMPS: MPO, OpSum, dmrg, random_mps, siteinds # "runtests.jl")) function main(; N, dmrg_kwargs) - opsum = OpSum() - for j in 1:(N - 1) - opsum += 0.5, "S+", j, "S-", j + 1 - opsum += 0.5, "S-", j, "S+", j + 1 - opsum += "Sz", j, "Sz", j + 1 - end - for conserve_qns in (false, true) - sites = siteinds("S=1", N; conserve_qns) - H = MPO(opsum, sites) - ψ0 = random_mps(sites, j -> isodd(j) ? "↑" : "↓"; linkdims=2) - dmrg(H, ψ0; outputlevel=0, dmrg_kwargs...) - end - return nothing + opsum = OpSum() + for j in 1:(N - 1) + opsum += 0.5, "S+", j, "S-", j + 1 + opsum += 0.5, "S-", j, "S+", j + 1 + opsum += "Sz", j, "Sz", j + 1 + end + for conserve_qns in (false, true) + sites = siteinds("S=1", N; conserve_qns) + H = MPO(opsum, sites) + ψ0 = random_mps(sites, j -> isodd(j) ? "↑" : "↓"; linkdims = 2) + dmrg(H, ψ0; outputlevel = 0, dmrg_kwargs...) + end + return nothing end -main(; N=6, dmrg_kwargs=(; nsweeps=3, maxdim=10, cutoff=1e-13)) +main(; N = 6, dmrg_kwargs = (; nsweeps = 3, maxdim = 10, cutoff = 1.0e-13)) diff --git a/ext/ITensorMPSZygoteRulesExt/ITensorMPSZygoteRulesExt.jl b/ext/ITensorMPSZygoteRulesExt/ITensorMPSZygoteRulesExt.jl index f79322b..9aed0e7 100644 --- a/ext/ITensorMPSZygoteRulesExt/ITensorMPSZygoteRulesExt.jl +++ b/ext/ITensorMPSZygoteRulesExt/ITensorMPSZygoteRulesExt.jl @@ -6,9 +6,9 @@ using ZygoteRules: @adjoint # Needed for defining the rule for `adjoint(A::ITensor)` # which currently doesn't work by overloading `ChainRulesCore.rrule` # since it is defined in `Zygote`, which takes precedent. -@adjoint function Base.adjoint(x::Union{MPS,MPO}) - y, adjoint_rrule_pullback = ChainRulesCore.rrule(adjoint, x) - adjoint_pullback(ȳ) = Base.tail(adjoint_rrule_pullback(ȳ)) - return y, adjoint_pullback +@adjoint function Base.adjoint(x::Union{MPS, MPO}) + y, adjoint_rrule_pullback = ChainRulesCore.rrule(adjoint, x) + adjoint_pullback(ȳ) = Base.tail(adjoint_rrule_pullback(ȳ)) + return y, adjoint_pullback end end diff --git a/src/abstractmps.jl b/src/abstractmps.jl index d838ee1..5304188 100644 --- a/src/abstractmps.jl +++ b/src/abstractmps.jl @@ -25,20 +25,20 @@ Base.size(m::AbstractMPS) = size(data(m)) Base.ndims(m::AbstractMPS) = ndims(data(m)) function promote_itensor_eltype(m::Vector{ITensor}) - T = isassigned(m, 1) ? eltype(m[1]) : Number - for n in 2:length(m) - Tn = isassigned(m, n) ? eltype(m[n]) : Number - T = promote_type(T, Tn) - end - return T + T = isassigned(m, 1) ? eltype(m[1]) : Number + for n in 2:length(m) + Tn = isassigned(m, n) ? eltype(m[n]) : Number + T = promote_type(T, Tn) + end + return T end function LinearAlgebra.promote_leaf_eltypes(m::Vector{ITensor}) - return promote_itensor_eltype(m) + return promote_itensor_eltype(m) end function LinearAlgebra.promote_leaf_eltypes(m::AbstractMPS) - return LinearAlgebra.promote_leaf_eltypes(data(m)) + return LinearAlgebra.promote_leaf_eltypes(data(m)) end """ @@ -74,7 +74,7 @@ Base.imag(ψ::AbstractMPS) = imag.(ψ) Base.conj(ψ::AbstractMPS) = conj.(ψ) function convert_leaf_eltype(eltype::Type, ψ::AbstractMPS) - return map(ψᵢ -> convert_leaf_eltype(eltype, ψᵢ), ψ; set_limits=false) + return map(ψᵢ -> convert_leaf_eltype(eltype, ψᵢ), ψ; set_limits = false) end """ @@ -94,11 +94,11 @@ leftlim(m::AbstractMPS) = m.llim rightlim(m::AbstractMPS) = m.rlim function setleftlim!(m::AbstractMPS, new_ll::Integer) - return m.llim = new_ll + return m.llim = new_ll end function setrightlim!(m::AbstractMPS, new_rl::Integer) - return m.rlim = new_rl + return m.rlim = new_rl end """ @@ -123,7 +123,7 @@ s = siteinds("S=½", 5) ``` """ function ortho_lims(ψ::AbstractMPS) - return (leftlim(ψ) + 1):(rightlim(ψ) - 1) + return (leftlim(ψ) + 1):(rightlim(ψ) - 1) end """ @@ -139,13 +139,13 @@ If you are modifying an MPS/MPO and want the orthogonality limits to be preserved, please see the `@preserve_ortho` macro. """ function set_ortho_lims!(ψ::AbstractMPS, r::UnitRange{Int}) - setleftlim!(ψ, first(r) - 1) - setrightlim!(ψ, last(r) + 1) - return ψ + setleftlim!(ψ, first(r) - 1) + setrightlim!(ψ, last(r) + 1) + return ψ end function set_ortho_lims(ψ::AbstractMPS, r::UnitRange{Int}) - return set_ortho_lims!(copy(ψ), r) + return set_ortho_lims!(copy(ψ), r) end reset_ortho_lims!(ψ::AbstractMPS) = set_ortho_lims!(ψ, 1:length(ψ)) @@ -154,10 +154,10 @@ isortho(m::AbstractMPS) = leftlim(m) + 1 == rightlim(m) - 1 # Could also define as `only(ortho_lims)` function orthocenter(m::AbstractMPS) - !isortho(m) && error( - "$(typeof(m)) has no well-defined orthogonality center, orthogonality center is on the range $(ortho_lims(m)).", - ) - return leftlim(m) + 1 + !isortho(m) && error( + "$(typeof(m)) has no well-defined orthogonality center, orthogonality center is on the range $(ortho_lims(m)).", + ) + return leftlim(m) + 1 end getindex(M::AbstractMPS, n) = getindex(data(M), n) @@ -204,36 +204,36 @@ end ``` """ macro preserve_ortho(ψ, block) - quote - if $(esc(ψ)) isa AbstractMPS - local ortho_limsψ = ortho_lims($(esc(ψ))) - else - local ortho_limsψ = ortho_lims.($(esc(ψ))) - end - r = $(esc(block)) - if $(esc(ψ)) isa AbstractMPS - set_ortho_lims!($(esc(ψ)), ortho_limsψ) - else - set_ortho_lims!.($(esc(ψ)), ortho_limsψ) + return quote + if $(esc(ψ)) isa AbstractMPS + local ortho_limsψ = ortho_lims($(esc(ψ))) + else + local ortho_limsψ = ortho_lims.($(esc(ψ))) + end + r = $(esc(block)) + if $(esc(ψ)) isa AbstractMPS + set_ortho_lims!($(esc(ψ)), ortho_limsψ) + else + set_ortho_lims!.($(esc(ψ)), ortho_limsψ) + end + r end - r - end end -function setindex!(M::AbstractMPS, T::ITensor, n::Integer; set_limits::Bool=true) - if set_limits - (n <= leftlim(M)) && setleftlim!(M, n - 1) - (n >= rightlim(M)) && setrightlim!(M, n + 1) - end - data(M)[n] = T - return M +function setindex!(M::AbstractMPS, T::ITensor, n::Integer; set_limits::Bool = true) + if set_limits + (n <= leftlim(M)) && setleftlim!(M, n - 1) + (n >= rightlim(M)) && setrightlim!(M, n + 1) + end + data(M)[n] = T + return M end -function setindex!(M::MPST, v::MPST, ::Colon) where {MPST<:AbstractMPS} - setleftlim!(M, leftlim(v)) - setrightlim!(M, rightlim(v)) - data(M)[:] = data(v) - return M +function setindex!(M::MPST, v::MPST, ::Colon) where {MPST <: AbstractMPS} + setleftlim!(M, leftlim(v)) + setrightlim!(M, rightlim(v)) + data(M)[:] = data(v) + return M end setindex!(M::AbstractMPS, v::Vector{<:ITensor}, ::Colon) = setindex!(M, MPS(v), :) @@ -349,9 +349,9 @@ MPS or MPO tensor on site j to site j+1. If there is no link Index, return `nothing`. """ function linkind(M::AbstractMPS, j::Integer) - N = length(M) - (j ≥ length(M) || j < 1) && return nothing - return commonind(M[j], M[j + 1]) + N = length(M) + (j ≥ length(M) || j < 1) && return nothing + return commonind(M[j], M[j + 1]) end """ @@ -362,15 +362,15 @@ Get all of the link or bond Indices connecting the MPS or MPO tensor on site j to site j+1. """ function linkinds(M::AbstractMPS, j::Integer) - N = length(M) - (j ≥ length(M) || j < 1) && return IndexSet() - return commoninds(M[j], M[j + 1]) + N = length(M) + (j ≥ length(M) || j < 1) && return IndexSet() + return commoninds(M[j], M[j + 1]) end linkinds(ψ::AbstractMPS) = [linkind(ψ, b) for b in 1:(length(ψ) - 1)] function linkinds(::typeof(all), ψ::AbstractMPS) - return IndexSet[linkinds(ψ, b) for b in 1:(length(ψ) - 1)] + return IndexSet[linkinds(ψ, b) for b in 1:(length(ψ) - 1)] end # @@ -390,13 +390,13 @@ defaultlinktags(b::Integer) = TagSet("Link,l=$b") Return true if the MPS/MPO has default link tags. """ function hasdefaultlinktags(ψ::AbstractMPS) - ls = linkinds(all, ψ) - for (b, lb) in enumerate(ls) - if length(lb) ≠ 1 || tags(only(lb)) ≠ defaultlinktags(b) - return false + ls = linkinds(all, ψ) + for (b, lb) in enumerate(ls) + if length(lb) ≠ 1 || tags(only(lb)) ≠ defaultlinktags(b) + return false + end end - end - return true + return true end """ @@ -419,12 +419,12 @@ eachsiteinds(ψ::AbstractMPS) = (siteinds(ψ, n) for n in eachindex(ψ)) Return true if the MPS/MPO has no link indices. """ function hasnolinkinds(ψ::AbstractMPS) - for l in eachlinkinds(ψ) - if length(l) > 0 - return false + for l in eachlinkinds(ψ) + if length(l) > 0 + return false + end end - end - return true + return true end """ @@ -433,19 +433,19 @@ end If any link indices are missing, insert default ones. """ function insertlinkinds(ψ::AbstractMPS) - ψ = copy(ψ) - space = hasqns(ψ) ? [QN() => 1] : 1 - linkind(b::Integer) = Index(space; tags=defaultlinktags(b)) - for b in 1:(length(ψ) - 1) - if length(linkinds(ψ, b)) == 0 - lb = ITensor(1, linkind(b)) - @preserve_ortho ψ begin - ψ[b] = ψ[b] * lb - ψ[b + 1] = ψ[b + 1] * dag(lb) - end + ψ = copy(ψ) + space = hasqns(ψ) ? [QN() => 1] : 1 + linkind(b::Integer) = Index(space; tags = defaultlinktags(b)) + for b in 1:(length(ψ) - 1) + if length(linkinds(ψ, b)) == 0 + lb = ITensor(1, linkind(b)) + @preserve_ortho ψ begin + ψ[b] = ψ[b] * lb + ψ[b + 1] = ψ[b + 1] * dag(lb) + end + end end - end - return ψ + return ψ end """ @@ -457,9 +457,9 @@ tensor to use dense storage and remove any QN or other sparse structure information, if it is not dense already. """ function dense(ψ::AbstractMPS) - ψ = copy(ψ) - @preserve_ortho ψ ψ .= dense.(ψ) - return ψ + ψ = copy(ψ) + @preserve_ortho ψ ψ .= dense.(ψ) + return ψ end """ @@ -469,17 +469,17 @@ end Get the site index (or indices) of MPO `A` that is unique to `A` (not shared with MPS/MPO `B`). """ function SiteTypes.siteinds( - f::Union{typeof(uniqueinds),typeof(uniqueind)}, - A::AbstractMPS, - B::AbstractMPS, - j::Integer; - kwargs..., -) - N = length(A) - N == 1 && return f(A[j], B[j]; kwargs...) - j == 1 && return f(A[j], A[j + 1], B[j]; kwargs...) - j == N && return f(A[j], A[j - 1], B[j]; kwargs...) - return f(A[j], A[j - 1], A[j + 1], B[j]; kwargs...) + f::Union{typeof(uniqueinds), typeof(uniqueind)}, + A::AbstractMPS, + B::AbstractMPS, + j::Integer; + kwargs..., + ) + N = length(A) + N == 1 && return f(A[j], B[j]; kwargs...) + j == 1 && return f(A[j], A[j + 1], B[j]; kwargs...) + j == N && return f(A[j], A[j - 1], B[j]; kwargs...) + return f(A[j], A[j - 1], A[j + 1], B[j]; kwargs...) end """ @@ -489,9 +489,9 @@ end Get the site indices of MPO `A` that are unique to `A` (not shared with MPS/MPO `B`), as a `Vector{<:Index}`. """ function SiteTypes.siteinds( - f::Union{typeof(uniqueinds),typeof(uniqueind)}, A::AbstractMPS, B::AbstractMPS; kwargs... -) - return [siteinds(f, A, B, j; kwargs...) for j in eachindex(A)] + f::Union{typeof(uniqueinds), typeof(uniqueind)}, A::AbstractMPS, B::AbstractMPS; kwargs... + ) + return [siteinds(f, A, B, j; kwargs...) for j in eachindex(A)] end """ @@ -501,13 +501,13 @@ end Get the site index (or indices) of the `j`th MPO tensor of `A` that is shared with MPS/MPO `B`. """ function SiteTypes.siteinds( - f::Union{typeof(commoninds),typeof(commonind)}, - A::AbstractMPS, - B::AbstractMPS, - j::Integer; - kwargs..., -) - return f(A[j], B[j]; kwargs...) + f::Union{typeof(commoninds), typeof(commonind)}, + A::AbstractMPS, + B::AbstractMPS, + j::Integer; + kwargs..., + ) + return f(A[j], B[j]; kwargs...) end """ @@ -517,9 +517,9 @@ end Get a vector of the site index (or indices) of MPO `A` that is shared with MPS/MPO `B`. """ function SiteTypes.siteinds( - f::Union{typeof(commoninds),typeof(commonind)}, A::AbstractMPS, B::AbstractMPS; kwargs... -) - return [siteinds(f, A, B, j) for j in eachindex(A)] + f::Union{typeof(commoninds), typeof(commonind)}, A::AbstractMPS, B::AbstractMPS; kwargs... + ) + return [siteinds(f, A, B, j) for j in eachindex(A)] end keys(ψ::AbstractMPS) = keys(data(ψ)) @@ -623,16 +623,16 @@ You can choose different filters, like prime level and tags, with the `kwargs`. """ function SiteTypes.siteind(::typeof(first), M::AbstractMPS, j::Integer; kwargs...) - N = length(M) - (N == 1) && return firstind(M[1]; kwargs...) - if j == 1 - si = uniqueind(M[j], M[j + 1]; kwargs...) - elseif j == N - si = uniqueind(M[j], M[j - 1]; kwargs...) - else - si = uniqueind(M[j], M[j - 1], M[j + 1]; kwargs...) - end - return si + N = length(M) + (N == 1) && return firstind(M[1]; kwargs...) + if j == 1 + si = uniqueind(M[j], M[j + 1]; kwargs...) + elseif j == N + si = uniqueind(M[j], M[j - 1]; kwargs...) + else + si = uniqueind(M[j], M[j - 1], M[j + 1]; kwargs...) + end + return si end """ @@ -645,313 +645,313 @@ Optionally filter prime tags and prime levels with keyword arguments like `plev` and `tags`. """ function SiteTypes.siteinds(M::AbstractMPS, j::Integer; kwargs...) - N = length(M) - (N == 1) && return inds(M[1]; kwargs...) - if j == 1 - si = uniqueinds(M[j], M[j + 1]; kwargs...) - elseif j == N - si = uniqueinds(M[j], M[j - 1]; kwargs...) - else - si = uniqueinds(M[j], M[j - 1], M[j + 1]; kwargs...) - end - return si + N = length(M) + (N == 1) && return inds(M[1]; kwargs...) + if j == 1 + si = uniqueinds(M[j], M[j + 1]; kwargs...) + elseif j == N + si = uniqueinds(M[j], M[j - 1]; kwargs...) + else + si = uniqueinds(M[j], M[j - 1], M[j + 1]; kwargs...) + end + return si end function SiteTypes.siteinds(::typeof(all), ψ::AbstractMPS, n::Integer; kwargs...) - return siteinds(ψ, n; kwargs...) + return siteinds(ψ, n; kwargs...) end function SiteTypes.siteinds(::typeof(first), ψ::AbstractMPS; kwargs...) - return [siteind(first, ψ, j; kwargs...) for j in 1:length(ψ)] + return [siteind(first, ψ, j; kwargs...) for j in 1:length(ψ)] end function SiteTypes.siteinds(::typeof(only), ψ::AbstractMPS; kwargs...) - return [siteind(only, ψ, j; kwargs...) for j in 1:length(ψ)] + return [siteind(only, ψ, j; kwargs...) for j in 1:length(ψ)] end function SiteTypes.siteinds(::typeof(all), ψ::AbstractMPS; kwargs...) - return [siteinds(ψ, j; kwargs...) for j in 1:length(ψ)] + return [siteinds(ψ, j; kwargs...) for j in 1:length(ψ)] end function replaceinds!(::typeof(linkinds), M::AbstractMPS, l̃s::Vector{<:Index}) - for i in eachindex(M)[1:(end - 1)] - l = linkind(M, i) - l̃ = l̃s[i] - if !isnothing(l) - @preserve_ortho M begin - M[i] = replaceinds(M[i], l => l̃) - M[i + 1] = replaceinds(M[i + 1], l => l̃) - end + for i in eachindex(M)[1:(end - 1)] + l = linkind(M, i) + l̃ = l̃s[i] + if !isnothing(l) + @preserve_ortho M begin + M[i] = replaceinds(M[i], l => l̃) + M[i + 1] = replaceinds(M[i + 1], l => l̃) + end + end end - end - return M + return M end function replaceinds(::typeof(linkinds), M::AbstractMPS, l̃s::Vector{<:Index}) - return replaceinds!(linkinds, copy(M), l̃s) + return replaceinds!(linkinds, copy(M), l̃s) end # TODO: change kwarg from `set_limits` to `preserve_ortho` -function map!(f::Function, M::AbstractMPS; set_limits::Bool=true) - for i in eachindex(M) - M[i, set_limits = set_limits] = f(M[i]) - end - return M +function map!(f::Function, M::AbstractMPS; set_limits::Bool = true) + for i in eachindex(M) + M[i, set_limits = set_limits] = f(M[i]) + end + return M end # TODO: change kwarg from `set_limits` to `preserve_ortho` -function map(f::Function, M::AbstractMPS; set_limits::Bool=true) - return map!(f, copy(M); set_limits=set_limits) +function map(f::Function, M::AbstractMPS; set_limits::Bool = true) + return map!(f, copy(M); set_limits = set_limits) end for (fname, fname!) in [ - (:(ITensors.dag), :(dag!)), - (:(ITensors.prime), :(ITensors.prime!)), - (:(ITensors.setprime), :(ITensors.setprime!)), - (:(ITensors.noprime), :(ITensors.noprime!)), - (:(ITensors.swapprime), :(ITensors.swapprime!)), - (:(ITensors.replaceprime), :(ITensors.replaceprime!)), - (:(TagSets.addtags), :(ITensors.addtags!)), - (:(TagSets.removetags), :(ITensors.removetags!)), - (:(TagSets.replacetags), :(ITensors.replacetags!)), - (:(ITensors.settags), :(ITensors.settags!)), -] - @eval begin - """ - $($fname)[!](M::MPS, args...; kwargs...) - $($fname)[!](M::MPO, args...; kwargs...) - - Apply $($fname) to all ITensors of an MPS/MPO, returning a new MPS/MPO. - - The ITensors of the MPS/MPO will be a view of the storage of the original ITensors. Alternatively apply the function in-place. - """ - function $fname(M::AbstractMPS, args...; set_limits::Bool=false, kwargs...) - return map(m -> $fname(m, args...; kwargs...), M; set_limits=set_limits) - end + (:(ITensors.dag), :(dag!)), + (:(ITensors.prime), :(ITensors.prime!)), + (:(ITensors.setprime), :(ITensors.setprime!)), + (:(ITensors.noprime), :(ITensors.noprime!)), + (:(ITensors.swapprime), :(ITensors.swapprime!)), + (:(ITensors.replaceprime), :(ITensors.replaceprime!)), + (:(TagSets.addtags), :(ITensors.addtags!)), + (:(TagSets.removetags), :(ITensors.removetags!)), + (:(TagSets.replacetags), :(ITensors.replacetags!)), + (:(ITensors.settags), :(ITensors.settags!)), + ] + @eval begin + """ + $($fname)[!](M::MPS, args...; kwargs...) + $($fname)[!](M::MPO, args...; kwargs...) + + Apply $($fname) to all ITensors of an MPS/MPO, returning a new MPS/MPO. + + The ITensors of the MPS/MPO will be a view of the storage of the original ITensors. Alternatively apply the function in-place. + """ + function $fname(M::AbstractMPS, args...; set_limits::Bool = false, kwargs...) + return map(m -> $fname(m, args...; kwargs...), M; set_limits = set_limits) + end - function $(fname!)(M::AbstractMPS, args...; set_limits::Bool=false, kwargs...) - return map!(m -> $fname(m, args...; kwargs...), M; set_limits=set_limits) + function $(fname!)(M::AbstractMPS, args...; set_limits::Bool = false, kwargs...) + return map!(m -> $fname(m, args...; kwargs...), M; set_limits = set_limits) + end end - end end adjoint(M::AbstractMPS) = prime(M) function hascommoninds(::typeof(siteinds), A::AbstractMPS, B::AbstractMPS) - N = length(A) - for n in 1:N - !hascommoninds(siteinds(A, n), siteinds(B, n)) && return false - end - return true + N = length(A) + for n in 1:N + !hascommoninds(siteinds(A, n), siteinds(B, n)) && return false + end + return true end function check_hascommoninds(::typeof(siteinds), A::AbstractMPS, B::AbstractMPS) - N = length(A) - if length(B) ≠ N - throw( - DimensionMismatch( - "$(typeof(A)) and $(typeof(B)) have mismatched lengths $N and $(length(B))." - ), - ) - end - for n in 1:N - !hascommoninds(siteinds(A, n), siteinds(B, n)) && error( - "$(typeof(A)) A and $(typeof(B)) B must share site indices. On site $n, A has site indices $(siteinds(A, n)) while B has site indices $(siteinds(B, n)).", - ) - end - return nothing + N = length(A) + if length(B) ≠ N + throw( + DimensionMismatch( + "$(typeof(A)) and $(typeof(B)) have mismatched lengths $N and $(length(B))." + ), + ) + end + for n in 1:N + !hascommoninds(siteinds(A, n), siteinds(B, n)) && error( + "$(typeof(A)) A and $(typeof(B)) B must share site indices. On site $n, A has site indices $(siteinds(A, n)) while B has site indices $(siteinds(B, n)).", + ) + end + return nothing end function map!(f::Function, ::typeof(linkinds), M::AbstractMPS) - for i in eachindex(M)[1:(end - 1)] - l = linkinds(M, i) - if !isempty(l) - l̃ = f(l) - @preserve_ortho M begin - M[i] = replaceinds(M[i], l, l̃) - M[i + 1] = replaceinds(M[i + 1], l, l̃) - end + for i in eachindex(M)[1:(end - 1)] + l = linkinds(M, i) + if !isempty(l) + l̃ = f(l) + @preserve_ortho M begin + M[i] = replaceinds(M[i], l, l̃) + M[i + 1] = replaceinds(M[i + 1], l, l̃) + end + end end - end - return M + return M end map(f::Function, ::typeof(linkinds), M::AbstractMPS) = map!(f, linkinds, copy(M)) function map!(f::Function, ::typeof(siteinds), M::AbstractMPS) - for i in eachindex(M) - s = siteinds(M, i) - if !isempty(s) - @preserve_ortho M begin - M[i] = replaceinds(M[i], s, f(s)) - end + for i in eachindex(M) + s = siteinds(M, i) + if !isempty(s) + @preserve_ortho M begin + M[i] = replaceinds(M[i], s, f(s)) + end + end end - end - return M + return M end map(f::Function, ::typeof(siteinds), M::AbstractMPS) = map!(f, siteinds, copy(M)) function map!( - f::Function, ::typeof(siteinds), ::typeof(commoninds), M1::AbstractMPS, M2::AbstractMPS -) - length(M1) != length(M2) && error("MPOs/MPSs must be the same length") - for i in eachindex(M1) - s = siteinds(commoninds, M1, M2, i) - if !isempty(s) - s̃ = f(s) - @preserve_ortho (M1, M2) begin - M1[i] = replaceinds(M1[i], s .=> s̃) - M2[i] = replaceinds(M2[i], s .=> s̃) - end - end - end - return M1, M2 + f::Function, ::typeof(siteinds), ::typeof(commoninds), M1::AbstractMPS, M2::AbstractMPS + ) + length(M1) != length(M2) && error("MPOs/MPSs must be the same length") + for i in eachindex(M1) + s = siteinds(commoninds, M1, M2, i) + if !isempty(s) + s̃ = f(s) + @preserve_ortho (M1, M2) begin + M1[i] = replaceinds(M1[i], s .=> s̃) + M2[i] = replaceinds(M2[i], s .=> s̃) + end + end + end + return M1, M2 end function map!( - f::Function, ::typeof(siteinds), ::typeof(uniqueinds), M1::AbstractMPS, M2::AbstractMPS -) - length(M1) != length(M2) && error("MPOs/MPSs must be the same length") - for i in eachindex(M1) - s = siteinds(uniqueinds, M1, M2, i) - if !isempty(s) - @preserve_ortho M1 begin - M1[i] = replaceinds(M1[i], s .=> f(s)) - end + f::Function, ::typeof(siteinds), ::typeof(uniqueinds), M1::AbstractMPS, M2::AbstractMPS + ) + length(M1) != length(M2) && error("MPOs/MPSs must be the same length") + for i in eachindex(M1) + s = siteinds(uniqueinds, M1, M2, i) + if !isempty(s) + @preserve_ortho M1 begin + M1[i] = replaceinds(M1[i], s .=> f(s)) + end + end end - end - return M1 + return M1 end function map( - f::Function, - ffilter::typeof(siteinds), - fsubset::Union{typeof(commoninds),typeof(uniqueinds)}, - M1::AbstractMPS, - M2::AbstractMPS, -) - return map!(f, ffilter, fsubset, copy(M1), copy(M2)) + f::Function, + ffilter::typeof(siteinds), + fsubset::Union{typeof(commoninds), typeof(uniqueinds)}, + M1::AbstractMPS, + M2::AbstractMPS, + ) + return map!(f, ffilter, fsubset, copy(M1), copy(M2)) end function hassameinds(::typeof(siteinds), M1::AbstractMPS, M2::AbstractMPS) - length(M1) ≠ length(M2) && return false - for n in 1:length(M1) - !hassameinds(siteinds(all, M1, n), siteinds(all, M2, n)) && return false - end - return true + length(M1) ≠ length(M2) && return false + for n in 1:length(M1) + !hassameinds(siteinds(all, M1, n), siteinds(all, M2, n)) && return false + end + return true end function hassamenuminds(::typeof(siteinds), M1::AbstractMPS, M2::AbstractMPS) - length(M1) ≠ length(M2) && return false - for n in 1:length(M1) - length(siteinds(M1, n)) ≠ length(siteinds(M2, n)) && return false - end - return true + length(M1) ≠ length(M2) && return false + for n in 1:length(M1) + length(siteinds(M1, n)) ≠ length(siteinds(M2, n)) && return false + end + return true end for (fname, fname!) in [ - (:(NDTensors.sim), :(sim!)), - (:(ITensors.prime), :(ITensors.prime!)), - (:(ITensors.setprime), :(ITensors.setprime!)), - (:(ITensors.noprime), :(ITensors.noprime!)), - (:(TagSets.addtags), :(ITensors.addtags!)), - (:(TagSets.removetags), :(ITensors.removetags!)), - (:(TagSets.replacetags), :(ITensors.replacetags!)), - (:(ITensors.settags), :(ITensors.settags!)), -] - @eval begin - """ - $($fname)[!](linkinds, M::MPS, args...; kwargs...) - $($fname)[!](linkinds, M::MPO, args...; kwargs...) - - Apply $($fname) to all link indices of an MPS/MPO, returning a new MPS/MPO. - - The ITensors of the MPS/MPO will be a view of the storage of the original ITensors. - """ - function $fname(ffilter::typeof(linkinds), M::AbstractMPS, args...; kwargs...) - return map(i -> $fname(i, args...; kwargs...), ffilter, M) - end - - function $(fname!)(ffilter::typeof(linkinds), M::AbstractMPS, args...; kwargs...) - return map!(i -> $fname(i, args...; kwargs...), ffilter, M) - end - - """ - $($fname)[!](siteinds, M::MPS, args...; kwargs...) - $($fname)[!](siteinds, M::MPO, args...; kwargs...) - - Apply $($fname) to all site indices of an MPS/MPO, returning a new MPS/MPO. - - The ITensors of the MPS/MPO will be a view of the storage of the original ITensors. - """ - function $fname(ffilter::typeof(siteinds), M::AbstractMPS, args...; kwargs...) - return map(i -> $fname(i, args...; kwargs...), ffilter, M) - end + (:(NDTensors.sim), :(sim!)), + (:(ITensors.prime), :(ITensors.prime!)), + (:(ITensors.setprime), :(ITensors.setprime!)), + (:(ITensors.noprime), :(ITensors.noprime!)), + (:(TagSets.addtags), :(ITensors.addtags!)), + (:(TagSets.removetags), :(ITensors.removetags!)), + (:(TagSets.replacetags), :(ITensors.replacetags!)), + (:(ITensors.settags), :(ITensors.settags!)), + ] + @eval begin + """ + $($fname)[!](linkinds, M::MPS, args...; kwargs...) + $($fname)[!](linkinds, M::MPO, args...; kwargs...) + + Apply $($fname) to all link indices of an MPS/MPO, returning a new MPS/MPO. + + The ITensors of the MPS/MPO will be a view of the storage of the original ITensors. + """ + function $fname(ffilter::typeof(linkinds), M::AbstractMPS, args...; kwargs...) + return map(i -> $fname(i, args...; kwargs...), ffilter, M) + end - function $(fname!)(ffilter::typeof(siteinds), M::AbstractMPS, args...; kwargs...) - return map!(i -> $fname(i, args...; kwargs...), ffilter, M) - end + function $(fname!)(ffilter::typeof(linkinds), M::AbstractMPS, args...; kwargs...) + return map!(i -> $fname(i, args...; kwargs...), ffilter, M) + end - """ - $($fname)[!](siteinds, commoninds, M1::MPO, M2::MPS, args...; kwargs...) - $($fname)[!](siteinds, commoninds, M1::MPO, M2::MPO, args...; kwargs...) + """ + $($fname)[!](siteinds, M::MPS, args...; kwargs...) + $($fname)[!](siteinds, M::MPO, args...; kwargs...) - Apply $($fname) to the site indices that are shared by `M1` and `M2`. + Apply $($fname) to all site indices of an MPS/MPO, returning a new MPS/MPO. - Returns new MPSs/MPOs. The ITensors of the MPSs/MPOs will be a view of the storage of the original ITensors. - """ - function $fname( - ffilter::typeof(siteinds), - fsubset::typeof(commoninds), - M1::AbstractMPS, - M2::AbstractMPS, - args...; - kwargs..., - ) - return map(i -> $fname(i, args...; kwargs...), ffilter, fsubset, M1, M2) - end + The ITensors of the MPS/MPO will be a view of the storage of the original ITensors. + """ + function $fname(ffilter::typeof(siteinds), M::AbstractMPS, args...; kwargs...) + return map(i -> $fname(i, args...; kwargs...), ffilter, M) + end - function $(fname!)( - ffilter::typeof(siteinds), - fsubset::typeof(commoninds), - M1::AbstractMPS, - M2::AbstractMPS, - args...; - kwargs..., - ) - return map!(i -> $fname(i, args...; kwargs...), ffilter, fsubset, M1, M2) - end + function $(fname!)(ffilter::typeof(siteinds), M::AbstractMPS, args...; kwargs...) + return map!(i -> $fname(i, args...; kwargs...), ffilter, M) + end - """ - $($fname)[!](siteinds, uniqueinds, M1::MPO, M2::MPS, args...; kwargs...) + """ + $($fname)[!](siteinds, commoninds, M1::MPO, M2::MPS, args...; kwargs...) + $($fname)[!](siteinds, commoninds, M1::MPO, M2::MPO, args...; kwargs...) + + Apply $($fname) to the site indices that are shared by `M1` and `M2`. + + Returns new MPSs/MPOs. The ITensors of the MPSs/MPOs will be a view of the storage of the original ITensors. + """ + function $fname( + ffilter::typeof(siteinds), + fsubset::typeof(commoninds), + M1::AbstractMPS, + M2::AbstractMPS, + args...; + kwargs..., + ) + return map(i -> $fname(i, args...; kwargs...), ffilter, fsubset, M1, M2) + end - Apply $($fname) to the site indices of `M1` that are not shared with `M2`. Returns new MPSs/MPOs. + function $(fname!)( + ffilter::typeof(siteinds), + fsubset::typeof(commoninds), + M1::AbstractMPS, + M2::AbstractMPS, + args...; + kwargs..., + ) + return map!(i -> $fname(i, args...; kwargs...), ffilter, fsubset, M1, M2) + end - The ITensors of the MPSs/MPOs will be a view of the storage of the original ITensors. - """ - function $fname( - ffilter::typeof(siteinds), - fsubset::typeof(uniqueinds), - M1::AbstractMPS, - M2::AbstractMPS, - args...; - kwargs..., - ) - return map(i -> $fname(i, args...; kwargs...), ffilter, fsubset, M1, M2) - end + """ + $($fname)[!](siteinds, uniqueinds, M1::MPO, M2::MPS, args...; kwargs...) + + Apply $($fname) to the site indices of `M1` that are not shared with `M2`. Returns new MPSs/MPOs. + + The ITensors of the MPSs/MPOs will be a view of the storage of the original ITensors. + """ + function $fname( + ffilter::typeof(siteinds), + fsubset::typeof(uniqueinds), + M1::AbstractMPS, + M2::AbstractMPS, + args...; + kwargs..., + ) + return map(i -> $fname(i, args...; kwargs...), ffilter, fsubset, M1, M2) + end - function $(fname!)( - ffilter::typeof(siteinds), - fsubset::typeof(uniqueinds), - M1::AbstractMPS, - M2::AbstractMPS, - args...; - kwargs..., - ) - return map!(i -> $fname(i, args...; kwargs...), ffilter, fsubset, M1, M2) + function $(fname!)( + ffilter::typeof(siteinds), + fsubset::typeof(uniqueinds), + M1::AbstractMPS, + M2::AbstractMPS, + args...; + kwargs..., + ) + return map!(i -> $fname(i, args...; kwargs...), ffilter, fsubset, M1, M2) + end end - end end """ @@ -964,13 +964,13 @@ The minimum this will return is `1`, even if there are no link indices. """ function maxlinkdim(M::AbstractMPS) - md = 1 - for b in eachindex(M)[1:(end - 1)] - l = linkind(M, b) - linkdim = isnothing(l) ? 1 : dim(l) - md = max(md, linkdim) - end - return md + md = 1 + for b in eachindex(M)[1:(end - 1)] + l = linkind(M, b) + linkdim = isnothing(l) ? 1 : dim(l) + md = max(md, linkdim) + end + return md end """ @@ -983,167 +983,167 @@ MPS or MPO tensor on site j to site j+1. If there is no link Index, return `nothing`. """ function linkdim(ψ::AbstractMPS, b::Integer) - l = linkind(ψ, b) - isnothing(l) && return nothing - return dim(l) + l = linkind(ψ, b) + isnothing(l) && return nothing + return dim(l) end linkdims(ψ::AbstractMPS) = [linkdim(ψ, b) for b in 1:(length(ψ) - 1)] function inner_mps_mps_deprecation_warning() - return """ - Calling `inner(x::MPS, y::MPS)` where the site indices of the `MPS` `x` and `y` - don't match is deprecated as of ITensor v0.3 and will result in an error in ITensor -v0.4. Likely you are attempting to take the inner product of MPS that have site indices -with mismatched prime levels. The most common cause of this is something like the following: + return """ + Calling `inner(x::MPS, y::MPS)` where the site indices of the `MPS` `x` and `y` + don't match is deprecated as of ITensor v0.3 and will result in an error in ITensor + v0.4. Likely you are attempting to take the inner product of MPS that have site indices + with mismatched prime levels. The most common cause of this is something like the following: - ```julia - s = siteinds("S=1/2") - psi = random_mps(s) - H = MPO(s, "Id") - Hpsi = contract(H, psi; cutoff=1e-8) # or `Hpsi = *(H, psi; cutoff=1e-8)` - inner(psi, Hpsi) - ``` + ```julia + s = siteinds("S=1/2") + psi = random_mps(s) + H = MPO(s, "Id") + Hpsi = contract(H, psi; cutoff=1e-8) # or `Hpsi = *(H, psi; cutoff=1e-8)` + inner(psi, Hpsi) + ``` - `psi` has the Index structure `-s-(psi)` and `H` has the Index structure - `-s'-(H)-s-`, so the contraction follows as: `-s'-(H)-s-(psi) ≈ -s'-(Hpsi)`. - Then, the prime levels of `Hpsi` and `psi` don't match in `inner(psi, Hpsi)`. + `psi` has the Index structure `-s-(psi)` and `H` has the Index structure + `-s'-(H)-s-`, so the contraction follows as: `-s'-(H)-s-(psi) ≈ -s'-(Hpsi)`. + Then, the prime levels of `Hpsi` and `psi` don't match in `inner(psi, Hpsi)`. - There are a few ways to fix this. You can simply change: + There are a few ways to fix this. You can simply change: - ```julia - inner(psi, Hpsi) - ``` + ```julia + inner(psi, Hpsi) + ``` - to: + to: - ```julia - inner(psi', Hpsi) - ``` + ```julia + inner(psi', Hpsi) + ``` - in which case both `psi'` and `Hpsi` have primed site indices. Alternatively, - you can use the `apply` function instead of the `contract` function, which - calls `contract` and unprimes the resulting MPS: + in which case both `psi'` and `Hpsi` have primed site indices. Alternatively, + you can use the `apply` function instead of the `contract` function, which + calls `contract` and unprimes the resulting MPS: - ```julia - Hpsi = apply(H, psi; cutoff=1e-8) # or `Hpsi = H(psi; cutoff=1e-8)` - inner(psi, Hpsi) - ``` + ```julia + Hpsi = apply(H, psi; cutoff=1e-8) # or `Hpsi = H(psi; cutoff=1e-8)` + inner(psi, Hpsi) + ``` - Finally, if you only compute `Hpsi` to pass to the `inner` function, consider using: + Finally, if you only compute `Hpsi` to pass to the `inner` function, consider using: - ```julia - inner(psi', H, psi) - ``` + ```julia + inner(psi', H, psi) + ``` - directly which is calculated exactly and is more efficient. Alternatively, you can use: + directly which is calculated exactly and is more efficient. Alternatively, you can use: - ```julia - inner(psi, Apply(H, psi)) - ``` + ```julia + inner(psi, Apply(H, psi)) + ``` - in which case `Apply(H, psi)` represents the "lazy" evaluation of - `apply(H, psi)` and internally calls something equivalent to `inner(psi', H, psi)`. + in which case `Apply(H, psi)` represents the "lazy" evaluation of + `apply(H, psi)` and internally calls something equivalent to `inner(psi', H, psi)`. - Although the new behavior seems less convenient, it makes it easier to - generalize `inner(::MPS, ::MPS)` to other types of inputs, like `MPS` with - different tag and prime conventions, multiple sites per tensor, `ITensor` inputs, etc. - """ + Although the new behavior seems less convenient, it makes it easier to + generalize `inner(::MPS, ::MPS)` to other types of inputs, like `MPS` with + different tag and prime conventions, multiple sites per tensor, `ITensor` inputs, etc. + """ end # Implement below, define here so it can be used in `deprecate_make_inds_match!`. function _log_or_not_dot end function deprecate_make_inds_match!( - ::typeof(_log_or_not_dot), - M1dag::MPST, - M2::MPST, - loginner::Bool; - make_inds_match::Bool=true, -) where {MPST<:AbstractMPS} - siteindsM1dag = siteinds(all, M1dag) - siteindsM2 = siteinds(all, M2) - N = length(M2) - if any(n -> length(n) > 1, siteindsM1dag) || - any(n -> length(n) > 1, siteindsM2) || - !hassamenuminds(siteinds, M1dag, M2) - # If the MPS have more than one site Indices on any site or they don't have - # the same number of site indices on each site, don't try to make the - # indices match - if !hassameinds(siteinds, M1dag, M2) - n = findfirst(n -> !hassameinds(siteinds(M1dag, n), siteinds(M2, n)), 1:N) - error( - """Calling `dot(ϕ::MPS/MPO, ψ::MPS/MPO)` with multiple site indices per - MPS/MPO tensor but the site indices don't match. Even with `make_inds_match = true`, - the case of multiple site indices per MPS/MPO is not handled automatically. - The sites with unmatched site indices are: - - inds(ϕ[$n]) = $(inds(M1dag[n])) - - inds(ψ[$n]) = $(inds(M2[n])) - - Make sure the site indices of your MPO/MPS match. You may need to prime - one of the MPS, such as `dot(ϕ', ψ)`.""" - ) - end - make_inds_match = false - end - if !hassameinds(siteinds, M1dag, M2) && make_inds_match - ITensors.warn_once(inner_mps_mpo_mps_deprecation_warning(), :inner_mps_mps) - replace_siteinds!(M1dag, siteindsM2) - end - return M1dag, M2 + ::typeof(_log_or_not_dot), + M1dag::MPST, + M2::MPST, + loginner::Bool; + make_inds_match::Bool = true, + ) where {MPST <: AbstractMPS} + siteindsM1dag = siteinds(all, M1dag) + siteindsM2 = siteinds(all, M2) + N = length(M2) + if any(n -> length(n) > 1, siteindsM1dag) || + any(n -> length(n) > 1, siteindsM2) || + !hassamenuminds(siteinds, M1dag, M2) + # If the MPS have more than one site Indices on any site or they don't have + # the same number of site indices on each site, don't try to make the + # indices match + if !hassameinds(siteinds, M1dag, M2) + n = findfirst(n -> !hassameinds(siteinds(M1dag, n), siteinds(M2, n)), 1:N) + error( + """Calling `dot(ϕ::MPS/MPO, ψ::MPS/MPO)` with multiple site indices per + MPS/MPO tensor but the site indices don't match. Even with `make_inds_match = true`, + the case of multiple site indices per MPS/MPO is not handled automatically. + The sites with unmatched site indices are: + + inds(ϕ[$n]) = $(inds(M1dag[n])) + + inds(ψ[$n]) = $(inds(M2[n])) + + Make sure the site indices of your MPO/MPS match. You may need to prime + one of the MPS, such as `dot(ϕ', ψ)`.""" + ) + end + make_inds_match = false + end + if !hassameinds(siteinds, M1dag, M2) && make_inds_match + ITensors.warn_once(inner_mps_mpo_mps_deprecation_warning(), :inner_mps_mps) + replace_siteinds!(M1dag, siteindsM2) + end + return M1dag, M2 end function _log_or_not_dot( - M1::MPST, M2::MPST, loginner::Bool; make_inds_match::Bool=true -)::Number where {MPST<:AbstractMPS} - N = length(M1) - if length(M2) != N - throw(DimensionMismatch("inner: mismatched lengths $N and $(length(M2))")) - end - M1dag = dag(M1) - sim!(linkinds, M1dag) - M1dag, M2 = deprecate_make_inds_match!( - _log_or_not_dot, M1dag, M2, loginner; make_inds_match - ) - check_hascommoninds(siteinds, M1dag, M2) - O = M1dag[1] * M2[1] - - if loginner - normO = norm(O) - log_inner_tot = log(normO) - O ./= normO - end - - for j in eachindex(M1)[2:end] - O = (O * M1dag[j]) * M2[j] + M1::MPST, M2::MPST, loginner::Bool; make_inds_match::Bool = true + )::Number where {MPST <: AbstractMPS} + N = length(M1) + if length(M2) != N + throw(DimensionMismatch("inner: mismatched lengths $N and $(length(M2))")) + end + M1dag = dag(M1) + sim!(linkinds, M1dag) + M1dag, M2 = deprecate_make_inds_match!( + _log_or_not_dot, M1dag, M2, loginner; make_inds_match + ) + check_hascommoninds(siteinds, M1dag, M2) + O = M1dag[1] * M2[1] if loginner - normO = norm(O) - log_inner_tot += log(normO) - O ./= normO + normO = norm(O) + log_inner_tot = log(normO) + O ./= normO end - end - if loginner - if !isreal(O[]) || real(O[]) < 0 - log_inner_tot += log(complex(O[])) + for j in eachindex(M1)[2:end] + O = (O * M1dag[j]) * M2[j] + + if loginner + normO = norm(O) + log_inner_tot += log(normO) + O ./= normO + end end - return log_inner_tot - end - dot_M1_M2 = O[] + if loginner + if !isreal(O[]) || real(O[]) < 0 + log_inner_tot += log(complex(O[])) + end + return log_inner_tot + end - if !isfinite(dot_M1_M2) - @warn "The inner product (or norm²) you are computing is very large " * - "($dot_M1_M2). You should consider using `lognorm` or `loginner` instead, " * - "which will help avoid floating point errors. For example if you are trying " * - "to normalize your MPS/MPO `A`, the normalized MPS/MPO `B` would be given by " * - "`B = A ./ z` where `z = exp(lognorm(A) / length(A))`." - end + dot_M1_M2 = O[] - return dot_M1_M2 + if !isfinite(dot_M1_M2) + @warn "The inner product (or norm²) you are computing is very large " * + "($dot_M1_M2). You should consider using `lognorm` or `loginner` instead, " * + "which will help avoid floating point errors. For example if you are trying " * + "to normalize your MPS/MPO `A`, the normalized MPS/MPO `B` would be given by " * + "`B = A ./ z` where `z = exp(lognorm(A) / length(A))`." + end + + return dot_M1_M2 end """ @@ -1154,8 +1154,8 @@ Same as [`inner`](@ref). See also [`loginner`](@ref), [`logdot`](@ref). """ -function LinearAlgebra.dot(M1::MPST, M2::MPST; kwargs...) where {MPST<:AbstractMPS} - return _log_or_not_dot(M1, M2, false; kwargs...) +function LinearAlgebra.dot(M1::MPST, M2::MPST; kwargs...) where {MPST <: AbstractMPS} + return _log_or_not_dot(M1, M2, false; kwargs...) end """ @@ -1166,21 +1166,21 @@ Same as [`loginner`](@ref). See also [`inner`](@ref), [`dot`](@ref). """ -function logdot(M1::MPST, M2::MPST; kwargs...) where {MPST<:AbstractMPS} - return _log_or_not_dot(M1, M2, true; kwargs...) +function logdot(M1::MPST, M2::MPST; kwargs...) where {MPST <: AbstractMPS} + return _log_or_not_dot(M1, M2, true; kwargs...) end function make_inds_match_docstring_warning() - return """ - !!! compat "ITensors 0.3" - Before ITensors 0.3, `inner` had a keyword argument `make_inds_match` that default to `true`. - When true, the function attempted to make the site indices match before contracting. So for example, the - inputs could have different site indices, as long as they have the same dimensions or QN blocks. - This behavior was fragile since it only worked for MPS with single site indices per tensor, - and as of ITensors 0.3 has been deprecated. As of ITensors 0.3 you will need to make sure - the MPS or MPO you input have compatible site indices to contract over, such as by making - sure the prime levels match properly. - """ + return """ + !!! compat "ITensors 0.3" + Before ITensors 0.3, `inner` had a keyword argument `make_inds_match` that default to `true`. + When true, the function attempted to make the site indices match before contracting. So for example, the + inputs could have different site indices, as long as they have the same dimensions or QN blocks. + This behavior was fragile since it only worked for MPS with single site indices per tensor, + and as of ITensors 0.3 has been deprecated. As of ITensors 0.3 you will need to make sure + the MPS or MPO you input have compatible site indices to contract over, such as by making + sure the prime levels match properly. + """ end """ @@ -1197,7 +1197,7 @@ Same as [`dot`](@ref). See also [`loginner`](@ref), [`logdot`](@ref). """ -inner(M1::MPST, M2::MPST; kwargs...) where {MPST<:AbstractMPS} = dot(M1, M2; kwargs...) +inner(M1::MPST, M2::MPST; kwargs...) where {MPST <: AbstractMPS} = dot(M1, M2; kwargs...) """ loginner(A::MPS, B::MPS) @@ -1213,8 +1213,8 @@ Same as [`logdot`](@ref). See also [`inner`](@ref), [`dot`](@ref). """ -function loginner(M1::MPST, M2::MPST; kwargs...) where {MPST<:AbstractMPS} - return logdot(M1, M2; kwargs...) +function loginner(M1::MPST, M2::MPST; kwargs...) where {MPST <: AbstractMPS} + return logdot(M1, M2; kwargs...) end """ @@ -1230,17 +1230,17 @@ the full inner product of the MPS/MPO with itself. See also [`lognorm`](@ref). """ function norm(M::AbstractMPS) - if isortho(M) - return norm(M[orthocenter(M)]) - end - norm2_M = dot(M, M) - rtol = eps(real(scalartype(M))) * 10 - atol = rtol - if !IsApprox.isreal(norm2_M, Approx(; rtol=rtol, atol=atol)) - @warn "norm² is $norm2_M, which is not real up to a relative tolerance of " * - "$rtol and an absolute tolerance of $atol. Taking the real part, which may not be accurate." - end - return sqrt(real(norm2_M)) + if isortho(M) + return norm(M[orthocenter(M)]) + end + norm2_M = dot(M, M) + rtol = eps(real(scalartype(M))) * 10 + atol = rtol + if !IsApprox.isreal(norm2_M, Approx(; rtol = rtol, atol = atol)) + @warn "norm² is $norm2_M, which is not real up to a relative tolerance of " * + "$rtol and an absolute tolerance of $atol. Taking the real part, which may not be accurate." + end + return sqrt(real(norm2_M)) end """ @@ -1255,43 +1255,43 @@ large numbers of sites the norm can diverge or approach zero. See also [`norm`](@ref), [`logdot`](@ref). """ function lognorm(M::AbstractMPS) - if isortho(M) - return log(norm(M[orthocenter(M)])) - end - lognorm2_M = logdot(M, M) - rtol = eps(real(scalartype(M))) * 10 - atol = rtol - if !IsApprox.isreal(lognorm2_M, Approx(; rtol=rtol, atol=atol)) - @warn "log(norm²) is $lognorm2_M, which is not real up to a relative tolerance " * - "of $rtol and an absolute tolerance of $atol. Taking the real part, which may not be accurate." - end - return real(lognorm2_M) / 2 + if isortho(M) + return log(norm(M[orthocenter(M)])) + end + lognorm2_M = logdot(M, M) + rtol = eps(real(scalartype(M))) * 10 + atol = rtol + if !IsApprox.isreal(lognorm2_M, Approx(; rtol = rtol, atol = atol)) + @warn "log(norm²) is $lognorm2_M, which is not real up to a relative tolerance " * + "of $rtol and an absolute tolerance of $atol. Taking the real part, which may not be accurate." + end + return real(lognorm2_M) / 2 end function isapprox( - x::AbstractMPS, - y::AbstractMPS; - atol::Real=0, - rtol::Real=Base.rtoldefault( - LinearAlgebra.promote_leaf_eltypes(x), LinearAlgebra.promote_leaf_eltypes(y), atol - ), -) - d = norm(x - y) - if isfinite(d) - return d <= max(atol, rtol * max(norm(x), norm(y))) - else - error("In `isapprox(x::MPS, y::MPS)`, `norm(x - y)` is not finite") - end + x::AbstractMPS, + y::AbstractMPS; + atol::Real = 0, + rtol::Real = Base.rtoldefault( + LinearAlgebra.promote_leaf_eltypes(x), LinearAlgebra.promote_leaf_eltypes(y), atol + ), + ) + d = norm(x - y) + if isfinite(d) + return d <= max(atol, rtol * max(norm(x), norm(y))) + else + error("In `isapprox(x::MPS, y::MPS)`, `norm(x - y)` is not finite") + end end # copy an MPS/MPO, but do a deep copy of the tensors in the # range of the orthogonality center. function deepcopy_ortho_center(M::AbstractMPS) - M = copy(M) - c = ortho_lims(M) - # TODO: define `getindex(::AbstractMPS, I)` to return `AbstractMPS` - M[c] = deepcopy(typeof(M)(M[c])) - return M + M = copy(M) + c = ortho_lims(M) + # TODO: define `getindex(::AbstractMPS, I)` to return `AbstractMPS` + M[c] = deepcopy(typeof(M)(M[c])) + return M end """ @@ -1305,8 +1305,8 @@ of the orthogonality center to avoid numerical overflow in the case of diverging See also [`normalize!`](@ref), [`norm`](@ref), [`lognorm`](@ref). """ -function normalize(M::AbstractMPS; (lognorm!)=[]) - return normalize!(deepcopy_ortho_center(M); (lognorm!)=(lognorm!)) +function normalize(M::AbstractMPS; (lognorm!) = []) + return normalize!(deepcopy_ortho_center(M); (lognorm!) = (lognorm!)) end """ @@ -1332,20 +1332,20 @@ lognorm_ψ[1] == -Inf # There was an infinite norm See also [`normalize`](@ref), [`norm`](@ref), [`lognorm`](@ref). """ -function LinearAlgebra.normalize!(M::AbstractMPS; (lognorm!)=[]) - c = ortho_lims(M) - lognorm_M = lognorm(M) - push!(lognorm!, lognorm_M) - if lognorm_M == -Inf +function LinearAlgebra.normalize!(M::AbstractMPS; (lognorm!) = []) + c = ortho_lims(M) + lognorm_M = lognorm(M) + push!(lognorm!, lognorm_M) + if lognorm_M == -Inf + return M + end + z = exp(lognorm_M / length(c)) + # XXX: this is not modifying `M` in-place. + # M[c] ./= z + for n in c + M[n] ./= z + end return M - end - z = exp(lognorm_M / length(c)) - # XXX: this is not modifying `M` in-place. - # M[c] ./= z - for n in c - M[n] ./= z - end - return M end """ @@ -1362,132 +1362,132 @@ Note that if the MPS/MPO are not normalized, the normalizations may diverge and directly (or `lognorm(A - B)` if you expect the result may be very large). """ function dist(A::AbstractMPS, B::AbstractMPS) - return sqrt(abs(inner(A, A) + inner(B, B) - 2 * real(inner(A, B)))) + return sqrt(abs(inner(A, A) + inner(B, B) - 2 * real(inner(A, B)))) end function site_combiners(ψ::AbstractMPS) - N = length(ψ) - Cs = Vector{ITensor}(undef, N) - for n in 1:N - s = siteinds(all, ψ, n) - Cs[n] = combiner(s; tags=commontags(s)) - end - return Cs + N = length(ψ) + Cs = Vector{ITensor}(undef, N) + for n in 1:N + s = siteinds(all, ψ, n) + Cs[n] = combiner(s; tags = commontags(s)) + end + return Cs end # The maximum link dimensions when adding MPS/MPO function _add_maxlinkdims(ψ⃗::AbstractMPS...) - N = length(ψ⃗[1]) - maxdims = Vector{Int}(undef, N - 1) - for b in 1:(N - 1) - maxdims[b] = sum(ψ -> linkdim(ψ, b), ψ⃗) - end - return maxdims + N = length(ψ⃗[1]) + maxdims = Vector{Int}(undef, N - 1) + for b in 1:(N - 1) + maxdims[b] = sum(ψ -> linkdim(ψ, b), ψ⃗) + end + return maxdims end function +( - ::Algorithm"densitymatrix", ψ⃗::MPST...; cutoff=1e-15, kwargs... -) where {MPST<:AbstractMPS} - if !all(ψ -> hassameinds(siteinds, first(ψ⃗), ψ), ψ⃗) - error("In `+(::MPS/MPO...)`, the input `MPS` or `MPO` do not have the same site + ::Algorithm"densitymatrix", ψ⃗::MPST...; cutoff = 1.0e-15, kwargs... + ) where {MPST <: AbstractMPS} + if !all(ψ -> hassameinds(siteinds, first(ψ⃗), ψ), ψ⃗) + error("In `+(::MPS/MPO...)`, the input `MPS` or `MPO` do not have the same site indices. For example, the site indices of the first site are $(siteinds.(ψ⃗, 1))") - end + end - Nₘₚₛ = length(ψ⃗) + Nₘₚₛ = length(ψ⃗) - @assert all(ψᵢ -> length(ψ⃗[1]) == length(ψᵢ), ψ⃗) + @assert all(ψᵢ -> length(ψ⃗[1]) == length(ψᵢ), ψ⃗) - N = length(ψ⃗[1]) + N = length(ψ⃗[1]) - ψ⃗ = copy.(ψ⃗) + ψ⃗ = copy.(ψ⃗) - X⃗ = site_combiners(ψ⃗[1]) + X⃗ = site_combiners(ψ⃗[1]) - for ψᵢ in ψ⃗ - @preserve_ortho ψᵢ ψᵢ .*= X⃗ - end + for ψᵢ in ψ⃗ + @preserve_ortho ψᵢ ψᵢ .*= X⃗ + end - ψ⃗ = convert.(MPS, ψ⃗) + ψ⃗ = convert.(MPS, ψ⃗) - s = siteinds(ψ⃗[1]) + s = siteinds(ψ⃗[1]) - ψ⃗ = orthogonalize.(ψ⃗, N) + ψ⃗ = orthogonalize.(ψ⃗, N) - ψ = MPS(N) + ψ = MPS(N) - ρ⃗ₙ = [prime(ψᵢ[N], s[N]) * dag(ψᵢ[N]) for ψᵢ in ψ⃗] - ρₙ = sum(ρ⃗ₙ) + ρ⃗ₙ = [prime(ψᵢ[N], s[N]) * dag(ψᵢ[N]) for ψᵢ in ψ⃗] + ρₙ = sum(ρ⃗ₙ) - # Maximum theoretical link dimensions - add_maxlinkdims = _add_maxlinkdims(ψ⃗...) + # Maximum theoretical link dimensions + add_maxlinkdims = _add_maxlinkdims(ψ⃗...) - C⃗ₙ = last.(ψ⃗) - for n in reverse(2:N) - Dₙ, Vₙ, spec = eigen( - ρₙ; - ishermitian=true, - tags=tags(linkind(ψ⃗[1], n - 1)), - cutoff=cutoff, - maxdim=add_maxlinkdims[n - 1], - kwargs..., - ) - lₙ₋₁ = commonind(Dₙ, Vₙ) + C⃗ₙ = last.(ψ⃗) + for n in reverse(2:N) + Dₙ, Vₙ, spec = eigen( + ρₙ; + ishermitian = true, + tags = tags(linkind(ψ⃗[1], n - 1)), + cutoff = cutoff, + maxdim = add_maxlinkdims[n - 1], + kwargs..., + ) + lₙ₋₁ = commonind(Dₙ, Vₙ) - # Update the total state - ψ[n] = Vₙ + # Update the total state + ψ[n] = Vₙ - # Compute the new density matrix - C⃗ₙ₋₁ = [ψ⃗[i][n - 1] * C⃗ₙ[i] * dag(Vₙ) for i in 1:Nₘₚₛ] - C⃗ₙ₋₁′ = [prime(Cₙ₋₁, (s[n - 1], lₙ₋₁)) for Cₙ₋₁ in C⃗ₙ₋₁] - ρ⃗ₙ₋₁ = C⃗ₙ₋₁′ .* dag.(C⃗ₙ₋₁) - ρₙ₋₁ = sum(ρ⃗ₙ₋₁) + # Compute the new density matrix + C⃗ₙ₋₁ = [ψ⃗[i][n - 1] * C⃗ₙ[i] * dag(Vₙ) for i in 1:Nₘₚₛ] + C⃗ₙ₋₁′ = [prime(Cₙ₋₁, (s[n - 1], lₙ₋₁)) for Cₙ₋₁ in C⃗ₙ₋₁] + ρ⃗ₙ₋₁ = C⃗ₙ₋₁′ .* dag.(C⃗ₙ₋₁) + ρₙ₋₁ = sum(ρ⃗ₙ₋₁) - C⃗ₙ = C⃗ₙ₋₁ - ρₙ = ρₙ₋₁ - end + C⃗ₙ = C⃗ₙ₋₁ + ρₙ = ρₙ₋₁ + end - ψ[1] = sum(C⃗ₙ) - ψ .*= dag.(X⃗) + ψ[1] = sum(C⃗ₙ) + ψ .*= dag.(X⃗) - set_ortho_lims!(ψ, 1:1) + set_ortho_lims!(ψ, 1:1) - return convert(MPST, ψ) + return convert(MPST, ψ) end -function +(::Algorithm"directsum", ψ⃗::MPST...) where {MPST<:AbstractMPS} - n = length(first(ψ⃗)) - @assert all(ψᵢ -> length(first(ψ⃗)) == length(ψᵢ), ψ⃗) +function +(::Algorithm"directsum", ψ⃗::MPST...) where {MPST <: AbstractMPS} + n = length(first(ψ⃗)) + @assert all(ψᵢ -> length(first(ψ⃗)) == length(ψᵢ), ψ⃗) - # Output tensor - ϕ = MPST(n) + # Output tensor + ϕ = MPST(n) - # Direct sum first tensor - j = 1 - l⃗j = map(ψᵢ -> linkind(ψᵢ, j), ψ⃗) - ϕj, (lj,) = directsum( - (ψ⃗[i][j] => (l⃗j[i],) for i in 1:length(ψ⃗))...; tags=[tags(first(l⃗j))] - ) - ljm_prev = lj - ϕ[j] = ϕj - for j in 2:(n - 1) - l⃗jm = map(ψᵢ -> linkind(ψᵢ, j - 1), ψ⃗) + # Direct sum first tensor + j = 1 l⃗j = map(ψᵢ -> linkind(ψᵢ, j), ψ⃗) - ϕj, (ljm, lj) = directsum( - (ψ⃗[i][j] => (l⃗jm[i], l⃗j[i]) for i in 1:length(ψ⃗))...; - tags=[tags(first(l⃗jm)), tags(first(l⃗j))], + ϕj, (lj,) = directsum( + (ψ⃗[i][j] => (l⃗j[i],) for i in 1:length(ψ⃗))...; tags = [tags(first(l⃗j))] ) - ϕj = replaceind(ϕj, ljm => dag(ljm_prev)) ljm_prev = lj ϕ[j] = ϕj - end - j = n - l⃗jm = map(ψᵢ -> linkind(ψᵢ, j - 1), ψ⃗) - ϕj, (ljm,) = directsum( - (ψ⃗[i][j] => (l⃗jm[i],) for i in 1:length(ψ⃗))...; tags=[tags(first(l⃗jm))] - ) - ϕj = replaceind(ϕj, ljm => dag(ljm_prev)) - ϕ[j] = ϕj - return ϕ + for j in 2:(n - 1) + l⃗jm = map(ψᵢ -> linkind(ψᵢ, j - 1), ψ⃗) + l⃗j = map(ψᵢ -> linkind(ψᵢ, j), ψ⃗) + ϕj, (ljm, lj) = directsum( + (ψ⃗[i][j] => (l⃗jm[i], l⃗j[i]) for i in 1:length(ψ⃗))...; + tags = [tags(first(l⃗jm)), tags(first(l⃗j))], + ) + ϕj = replaceind(ϕj, ljm => dag(ljm_prev)) + ljm_prev = lj + ϕ[j] = ϕj + end + j = n + l⃗jm = map(ψᵢ -> linkind(ψᵢ, j - 1), ψ⃗) + ϕj, (ljm,) = directsum( + (ψ⃗[i][j] => (l⃗jm[i],) for i in 1:length(ψ⃗))...; tags = [tags(first(l⃗jm))] + ) + ϕj = replaceind(ϕj, ljm => dag(ljm_prev)) + ϕ[j] = ϕj + return ϕ end """ @@ -1552,8 +1552,8 @@ println() inner(ψ₃, ψ₁) + 2 * inner(ψ₃, ψ₂) + inner(ψ₃, ψ₃) ``` """ -function +(ψ⃗::AbstractMPS...; alg=Algorithm"densitymatrix"(), kwargs...) - return +(Algorithm(alg), ψ⃗...; kwargs...) +function +(ψ⃗::AbstractMPS...; alg = Algorithm"densitymatrix"(), kwargs...) + return +(Algorithm(alg), ψ⃗...; kwargs...) end +(ψ::AbstractMPS) = ψ @@ -1562,7 +1562,7 @@ add(ψ⃗::AbstractMPS...; kwargs...) = +(ψ⃗...; kwargs...) -(ψ₁::AbstractMPS, ψ₂::AbstractMPS; kwargs...) = +(ψ₁, -ψ₂; kwargs...) -add(A::T, B::T; kwargs...) where {T<:AbstractMPS} = +(A, B; kwargs...) +add(A::T, B::T; kwargs...) where {T <: AbstractMPS} = +(A, B; kwargs...) """ sum(A::Vector{MPS}; kwargs...) @@ -1577,10 +1577,10 @@ truncation. - `cutoff::Real`: singular value truncation cutoff - `maxdim::Int`: maximum MPS/MPO bond dimension """ -function sum(ψ⃗::Vector{T}; kwargs...) where {T<:AbstractMPS} - iszero(length(ψ⃗)) && return T() - isone(length(ψ⃗)) && return copy(only(ψ⃗)) - return +(ψ⃗...; kwargs...) +function sum(ψ⃗::Vector{T}; kwargs...) where {T <: AbstractMPS} + iszero(length(ψ⃗)) && return T() + isone(length(ψ⃗)) && return copy(only(ψ⃗)) + return +(ψ⃗...; kwargs...) end """ @@ -1600,66 +1600,66 @@ bond indices is performed. Afterward, tensors Either modify in-place with `orthogonalize!` or out-of-place with `orthogonalize`. """ -function orthogonalize!(M::AbstractMPS, j::Int; maxdim=nothing, normalize=nothing) - # TODO: Delete `maxdim` and `normalize` keyword arguments. - @debug_check begin - if !(1 <= j <= length(M)) - error("Input j=$j to `orthogonalize!` out of range (valid range = 1:$(length(M)))") - end - end - while leftlim(M) < (j - 1) - (leftlim(M) < 0) && setleftlim!(M, 0) - b = leftlim(M) + 1 - linds = uniqueinds(M[b], M[b + 1]) - lb = linkind(M, b) - if !isnothing(lb) - ltags = tags(lb) - else - ltags = TagSet("Link,l=$b") - end - L, R = factorize(M[b], linds; tags=ltags, maxdim) - M[b] = L - M[b + 1] *= R - setleftlim!(M, b) - if rightlim(M) < leftlim(M) + 2 - setrightlim!(M, leftlim(M) + 2) - end - end - - N = length(M) - - while rightlim(M) > (j + 1) - (rightlim(M) > (N + 1)) && setrightlim!(M, N + 1) - b = rightlim(M) - 2 - rinds = uniqueinds(M[b + 1], M[b]) - lb = linkind(M, b) - if !isnothing(lb) - ltags = tags(lb) - else - ltags = TagSet("Link,l=$b") +function orthogonalize!(M::AbstractMPS, j::Int; maxdim = nothing, normalize = nothing) + # TODO: Delete `maxdim` and `normalize` keyword arguments. + @debug_check begin + if !(1 <= j <= length(M)) + error("Input j=$j to `orthogonalize!` out of range (valid range = 1:$(length(M)))") + end + end + while leftlim(M) < (j - 1) + (leftlim(M) < 0) && setleftlim!(M, 0) + b = leftlim(M) + 1 + linds = uniqueinds(M[b], M[b + 1]) + lb = linkind(M, b) + if !isnothing(lb) + ltags = tags(lb) + else + ltags = TagSet("Link,l=$b") + end + L, R = factorize(M[b], linds; tags = ltags, maxdim) + M[b] = L + M[b + 1] *= R + setleftlim!(M, b) + if rightlim(M) < leftlim(M) + 2 + setrightlim!(M, leftlim(M) + 2) + end end - L, R = factorize(M[b + 1], rinds; tags=ltags, maxdim) - M[b + 1] = L - M[b] *= R - setrightlim!(M, b + 1) - if leftlim(M) > rightlim(M) - 2 - setleftlim!(M, rightlim(M) - 2) + N = length(M) + + while rightlim(M) > (j + 1) + (rightlim(M) > (N + 1)) && setrightlim!(M, N + 1) + b = rightlim(M) - 2 + rinds = uniqueinds(M[b + 1], M[b]) + lb = linkind(M, b) + if !isnothing(lb) + ltags = tags(lb) + else + ltags = TagSet("Link,l=$b") + end + L, R = factorize(M[b + 1], rinds; tags = ltags, maxdim) + M[b + 1] = L + M[b] *= R + + setrightlim!(M, b + 1) + if leftlim(M) > rightlim(M) - 2 + setleftlim!(M, rightlim(M) - 2) + end end - end - return M + return M end # Allows overloading `orthogonalize!` based on the projected # MPO type. By default just calls `orthogonalize!` on the MPS. function orthogonalize!(PH, M::AbstractMPS, j::Int; kwargs...) - return orthogonalize!(M, j; kwargs...) + return orthogonalize!(M, j; kwargs...) end function orthogonalize(ψ0::AbstractMPS, args...; kwargs...) - ψ = copy(ψ0) - orthogonalize!(ψ, args...; kwargs...) - return ψ + ψ = copy(ψ0) + orthogonalize!(ψ, args...; kwargs...) + return ψ end """ @@ -1685,52 +1685,52 @@ end truncate!(ψ; maxdim=5, cutoff=1E-7, callback) ``` """ -function truncate!(M::AbstractMPS; alg="frobenius", kwargs...) - return truncate!(Algorithm(alg), M; kwargs...) +function truncate!(M::AbstractMPS; alg = "frobenius", kwargs...) + return truncate!(Algorithm(alg), M; kwargs...) end function truncate!( - ::Algorithm"frobenius", - M::AbstractMPS; - site_range=1:length(M), - callback=Returns(nothing), - kwargs..., -) - # Left-orthogonalize all tensors to make - # truncations controlled - orthogonalize!(M, last(site_range)) - - # Perform truncations in a right-to-left sweep - for j in reverse((first(site_range) + 1):last(site_range)) - rinds = uniqueinds(M[j], M[j - 1]) - ltags = tags(commonind(M[j], M[j - 1])) - U, S, V, spec = svd(M[j], rinds; lefttags=ltags, kwargs...) - M[j] = U - M[j - 1] *= (S * V) - setrightlim!(M, j) - callback(; link=(j => j - 1), truncation_error=spec.truncerr) - end - return M + ::Algorithm"frobenius", + M::AbstractMPS; + site_range = 1:length(M), + callback = Returns(nothing), + kwargs..., + ) + # Left-orthogonalize all tensors to make + # truncations controlled + orthogonalize!(M, last(site_range)) + + # Perform truncations in a right-to-left sweep + for j in reverse((first(site_range) + 1):last(site_range)) + rinds = uniqueinds(M[j], M[j - 1]) + ltags = tags(commonind(M[j], M[j - 1])) + U, S, V, spec = svd(M[j], rinds; lefttags = ltags, kwargs...) + M[j] = U + M[j - 1] *= (S * V) + setrightlim!(M, j) + callback(; link = (j => j - 1), truncation_error = spec.truncerr) + end + return M end function truncate(ψ0::AbstractMPS; kwargs...) - ψ = copy(ψ0) - truncate!(ψ; kwargs...) - return ψ + ψ = copy(ψ0) + truncate!(ψ; kwargs...) + return ψ end # Make `*` an alias for `contract` of two `AbstractMPS` *(A::AbstractMPS, B::AbstractMPS; kwargs...) = contract(A, B; kwargs...) function _apply_to_orthocenter!(f, ψ::AbstractMPS, x) - limsψ = ortho_lims(ψ) - n = first(limsψ) - ψ[n] = f(ψ[n], x) - return ψ + limsψ = ortho_lims(ψ) + n = first(limsψ) + ψ[n] = f(ψ[n], x) + return ψ end function _apply_to_orthocenter(f, ψ::AbstractMPS, x) - return _apply_to_orthocenter!(f, copy(ψ), x) + return _apply_to_orthocenter!(f, copy(ψ), x) end """ @@ -1763,55 +1763,55 @@ LinearAlgebra.rmul!(ψ::AbstractMPS, α::Number) = _apply_to_orthocenter!(*, ψ, Sets a contiguous range of MPS/MPO tensors """ -function setindex!(ψ::MPST, ϕ::MPST, r::UnitRange{Int64}) where {MPST<:AbstractMPS} - @assert length(r) == length(ϕ) - # TODO: accept r::Union{AbstractRange{Int}, Vector{Int}} - # if r isa AbstractRange - # @assert step(r) = 1 - # else - # all(==(1), diff(r)) - # end - llim = leftlim(ψ) - rlim = rightlim(ψ) - for (j, n) in enumerate(r) - ψ[n] = ϕ[j] - end - if llim + 1 ≥ r[1] - setleftlim!(ψ, leftlim(ϕ) + r[1] - 1) - end - if rlim - 1 ≤ r[end] - setrightlim!(ψ, rightlim(ϕ) + r[1] - 1) - end - return ψ +function setindex!(ψ::MPST, ϕ::MPST, r::UnitRange{Int64}) where {MPST <: AbstractMPS} + @assert length(r) == length(ϕ) + # TODO: accept r::Union{AbstractRange{Int}, Vector{Int}} + # if r isa AbstractRange + # @assert step(r) = 1 + # else + # all(==(1), diff(r)) + # end + llim = leftlim(ψ) + rlim = rightlim(ψ) + for (j, n) in enumerate(r) + ψ[n] = ϕ[j] + end + if llim + 1 ≥ r[1] + setleftlim!(ψ, leftlim(ϕ) + r[1] - 1) + end + if rlim - 1 ≤ r[end] + setrightlim!(ψ, rightlim(ϕ) + r[1] - 1) + end + return ψ end _isodd_fermionic_parity(s::Index, ::Integer) = false function _isodd_fermionic_parity(s::QNIndex, n::Integer) - qn_n = qn(space(s)[n]) - fermionic_qn_pos = findfirst(q -> isfermionic(q), qn_n) - isnothing(fermionic_qn_pos) && return false - return isodd(val(qn_n[fermionic_qn_pos])) + qn_n = qn(space(s)[n]) + fermionic_qn_pos = findfirst(q -> isfermionic(q), qn_n) + isnothing(fermionic_qn_pos) && return false + return isodd(val(qn_n[fermionic_qn_pos])) end function _fermionic_swap(s1::Index, s2::Index) - T = ITensor(QN(), s1', s2', dag(s1), dag(s2)) - for b in nzblocks(T) - dval = 1.0 - # Must be a diagonal block - ((b[1] ≠ b[3]) || (b[2] ≠ b[4])) && continue - n1, n2 = b[1], b[2] - if _isodd_fermionic_parity(s1, n1) && _isodd_fermionic_parity(s2, n2) - dval = -1.0 - end - Tb = ITensors.blockview(tensor(T), b) - mat_dim = prod(dims(Tb)[1:2]) - Tbr = reshape(Tb, mat_dim, mat_dim) - for i in diagind(Tbr) - NDTensors.setdiagindex!(Tbr, dval, i) + T = ITensor(QN(), s1', s2', dag(s1), dag(s2)) + for b in nzblocks(T) + dval = 1.0 + # Must be a diagonal block + ((b[1] ≠ b[3]) || (b[2] ≠ b[4])) && continue + n1, n2 = b[1], b[2] + if _isodd_fermionic_parity(s1, n1) && _isodd_fermionic_parity(s2, n2) + dval = -1.0 + end + Tb = ITensors.blockview(tensor(T), b) + mat_dim = prod(dims(Tb)[1:2]) + Tbr = reshape(Tb, mat_dim, mat_dim) + for i in diagind(Tbr) + NDTensors.setdiagindex!(Tbr, dval, i) + end end - end - return T + return T end # TODO: add a version that determines the sites @@ -1836,102 +1836,102 @@ should be within `r`. Optionally, permute the order of the sites with `perm`. """ function setindex!( - ψ::MPST, - A::ITensor, - r::UnitRange{Int}; - orthocenter::Integer=last(r), - perm=nothing, - kwargs..., -) where {MPST<:AbstractMPS} - # Replace the sites of ITensor ψ - # with the tensor A, splitting up A - # into MPS tensors - firstsite = first(r) - lastsite = last(r) - @assert firstsite ≤ ITensorMPS.orthocenter(ψ) ≤ lastsite - @assert firstsite ≤ leftlim(ψ) + 1 - @assert rightlim(ψ) - 1 ≤ lastsite - - # TODO: allow orthocenter outside of this - # range, and orthogonalize/truncate as needed - @assert firstsite ≤ orthocenter ≤ lastsite - - # Check that A has the proper common - # indices with ψ - lind = linkind(ψ, firstsite - 1) - rind = linkind(ψ, lastsite) - - sites = [siteinds(ψ, j) for j in firstsite:lastsite] - - #s = collect(Iterators.flatten(sites)) - indsA = filter(x -> !isnothing(x), [lind, Iterators.flatten(sites)..., rind]) - @assert hassameinds(A, indsA) - - # For MPO case, restrict to 0 prime level - #sites = filter(hasplev(0), sites) - - if !isnothing(perm) - sites0 = sites - sites = sites0[[perm...]] - # Check if the site indices - # are fermionic - if !using_auto_fermion() && any(ITensors.anyfermionic, sites) - if length(sites) == 2 && ψ isa MPS - if all(ITensors.allfermionic, sites) - s0 = Index.(sites0) - - # TODO: the Fermionic swap is could be diagonal, - # if we combine the site indices - #C = combiner(s0[1], s0[2]) - #c = combinedind(C) - #AC = A * C - #AC = noprime(AC * _fermionic_swap(c)) - #A = AC * dag(C) - - FSWAP = adapt(datatype(A), _fermionic_swap(s0[1], s0[2])) - A = noprime(A * FSWAP) + ψ::MPST, + A::ITensor, + r::UnitRange{Int}; + orthocenter::Integer = last(r), + perm = nothing, + kwargs..., + ) where {MPST <: AbstractMPS} + # Replace the sites of ITensor ψ + # with the tensor A, splitting up A + # into MPS tensors + firstsite = first(r) + lastsite = last(r) + @assert firstsite ≤ ITensorMPS.orthocenter(ψ) ≤ lastsite + @assert firstsite ≤ leftlim(ψ) + 1 + @assert rightlim(ψ) - 1 ≤ lastsite + + # TODO: allow orthocenter outside of this + # range, and orthogonalize/truncate as needed + @assert firstsite ≤ orthocenter ≤ lastsite + + # Check that A has the proper common + # indices with ψ + lind = linkind(ψ, firstsite - 1) + rind = linkind(ψ, lastsite) + + sites = [siteinds(ψ, j) for j in firstsite:lastsite] + + #s = collect(Iterators.flatten(sites)) + indsA = filter(x -> !isnothing(x), [lind, Iterators.flatten(sites)..., rind]) + @assert hassameinds(A, indsA) + + # For MPO case, restrict to 0 prime level + #sites = filter(hasplev(0), sites) + + if !isnothing(perm) + sites0 = sites + sites = sites0[[perm...]] + # Check if the site indices + # are fermionic + if !using_auto_fermion() && any(ITensors.anyfermionic, sites) + if length(sites) == 2 && ψ isa MPS + if all(ITensors.allfermionic, sites) + s0 = Index.(sites0) + + # TODO: the Fermionic swap is could be diagonal, + # if we combine the site indices + #C = combiner(s0[1], s0[2]) + #c = combinedind(C) + #AC = A * C + #AC = noprime(AC * _fermionic_swap(c)) + #A = AC * dag(C) + + FSWAP = adapt(datatype(A), _fermionic_swap(s0[1], s0[2])) + A = noprime(A * FSWAP) + end + elseif ψ isa MPO + @warn "In setindex!(MPO, ::ITensor, ::UnitRange), " * + "fermionic signs are only not handled properly for non-trivial " * + "permutations of sites. Please inform the developers of ITensors " * + "if you require this feature (otherwise, fermionic signs can be " * + "put in manually with fermionic swap gates)." + else + @warn "In setindex!(::Union{MPS, MPO}, ::ITensor, ::UnitRange), " * + "fermionic signs are only handled properly for permutations involving 2 sites. " * + "The original sites are $sites0, with a permutation $perm. " * + "To have the fermion sign handled correctly, we recommend performing your permutation " * + "pairwise." + end end - elseif ψ isa MPO - @warn "In setindex!(MPO, ::ITensor, ::UnitRange), " * - "fermionic signs are only not handled properly for non-trivial " * - "permutations of sites. Please inform the developers of ITensors " * - "if you require this feature (otherwise, fermionic signs can be " * - "put in manually with fermionic swap gates)." - else - @warn "In setindex!(::Union{MPS, MPO}, ::ITensor, ::UnitRange), " * - "fermionic signs are only handled properly for permutations involving 2 sites. " * - "The original sites are $sites0, with a permutation $perm. " * - "To have the fermion sign handled correctly, we recommend performing your permutation " * - "pairwise." - end - end - end - - # use the first link index if present, otherwise use the default tag - linktags = TagSet[ - (b=linkind(ψ, i); isnothing(b) ? defaultlinkindtags(i) : tags(b)) for - i in firstsite:(lastsite - 1) - ] - - ψA = MPST( - A, - sites; - leftinds=lind, - orthocenter=orthocenter - first(r) + 1, - tags=linktags, - kwargs..., - ) - #@assert prod(ψA) ≈ A - - ψ[firstsite:lastsite] = ψA - - return ψ + end + + # use the first link index if present, otherwise use the default tag + linktags = TagSet[ + (b = linkind(ψ, i); isnothing(b) ? defaultlinkindtags(i) : tags(b)) for + i in firstsite:(lastsite - 1) + ] + + ψA = MPST( + A, + sites; + leftinds = lind, + orthocenter = orthocenter - first(r) + 1, + tags = linktags, + kwargs..., + ) + #@assert prod(ψA) ≈ A + + ψ[firstsite:lastsite] = ψA + + return ψ end function setindex!( - ψ::MPST, A::ITensor, r::UnitRange{Int}, args::Pair{Symbol}...; kwargs... -) where {MPST<:AbstractMPS} - return setindex!(ψ, A, r; args..., kwargs...) + ψ::MPST, A::ITensor, r::UnitRange{Int}, args::Pair{Symbol}...; kwargs... + ) where {MPST <: AbstractMPS} + return setindex!(ψ, A, r; args..., kwargs...) end replacesites!(ψ::AbstractMPS, args...; kwargs...) = setindex!(ψ, args...; kwargs...) @@ -1963,47 +1963,47 @@ by site according to the site indices `sites`. - `maxdim`: the maximum link dimension. """ function (::Type{MPST})( - A::ITensor, - sites; - leftinds=nothing, - orthocenter::Integer=length(sites), - tags=[defaultlinktags(i) for i in 1:(length(sites) - 1)], - kwargs..., -) where {MPST<:AbstractMPS} - N = length(sites) - for s in sites - @assert hasinds(A, s) - end - @assert isnothing(leftinds) || hasinds(A, leftinds) - - @assert 1 ≤ orthocenter ≤ N - - ψ = Vector{ITensor}(undef, N) - Ã = A - l = leftinds - # TODO: To minimize work, loop from - # 1:orthocenter and reverse(orthocenter:N) - # so the orthogonality center is set correctly. - for n in 1:(N - 1) - Lis = IndexSet(sites[n]) - if !isnothing(l) - Lis = unioninds(Lis, l) - end - L, R = factorize(Ã, Lis; kwargs..., tags=tags[n], ortho="left") - l = commonind(L, R) - ψ[n] = L - Ã = R - end - ψ[N] = Ã - M = MPST(ψ) - setleftlim!(M, N - 1) - setrightlim!(M, N + 1) - M = orthogonalize(M, orthocenter) - return M -end - -function (::Type{MPST})(A::AbstractArray, sites; kwargs...) where {MPST<:AbstractMPS} - return MPST(itensor(A, sites...), sites; kwargs...) + A::ITensor, + sites; + leftinds = nothing, + orthocenter::Integer = length(sites), + tags = [defaultlinktags(i) for i in 1:(length(sites) - 1)], + kwargs..., + ) where {MPST <: AbstractMPS} + N = length(sites) + for s in sites + @assert hasinds(A, s) + end + @assert isnothing(leftinds) || hasinds(A, leftinds) + + @assert 1 ≤ orthocenter ≤ N + + ψ = Vector{ITensor}(undef, N) + Ã = A + l = leftinds + # TODO: To minimize work, loop from + # 1:orthocenter and reverse(orthocenter:N) + # so the orthogonality center is set correctly. + for n in 1:(N - 1) + Lis = IndexSet(sites[n]) + if !isnothing(l) + Lis = unioninds(Lis, l) + end + L, R = factorize(Ã, Lis; kwargs..., tags = tags[n], ortho = "left") + l = commonind(L, R) + ψ[n] = L + Ã = R + end + ψ[N] = Ã + M = MPST(ψ) + setleftlim!(M, N - 1) + setrightlim!(M, N + 1) + M = orthogonalize(M, orthocenter) + return M +end + +function (::Type{MPST})(A::AbstractArray, sites; kwargs...) where {MPST <: AbstractMPS} + return MPST(itensor(A, sites...), sites; kwargs...) end """ @@ -2011,20 +2011,20 @@ end Swap the sites `b` and `b+1`. """ -function swapbondsites(ψ::AbstractMPS, b::Integer; ortho="right", kwargs...) - ψ = copy(ψ) - if ortho == "left" - orthocenter = b + 1 - elseif ortho == "right" - orthocenter = b - end - if leftlim(ψ) < b - 1 - ψ = orthogonalize(ψ, b) - elseif rightlim(ψ) > b + 2 - ψ = orthogonalize(ψ, b + 1) - end - ψ[b:(b + 1), orthocenter = orthocenter, perm = [2, 1], kwargs...] = ψ[b] * ψ[b + 1] - return ψ +function swapbondsites(ψ::AbstractMPS, b::Integer; ortho = "right", kwargs...) + ψ = copy(ψ) + if ortho == "left" + orthocenter = b + 1 + elseif ortho == "right" + orthocenter = b + end + if leftlim(ψ) < b - 1 + ψ = orthogonalize(ψ, b) + elseif rightlim(ψ) > b + 2 + ψ = orthogonalize(ψ, b + 1) + end + ψ[b:(b + 1), orthocenter = orthocenter, perm = [2, 1], kwargs...] = ψ[b] * ψ[b + 1] + return ψ end """ @@ -2037,83 +2037,83 @@ This is done with a series a pairwise swaps, and can introduce a lot of entanglement into your state, so use with caution. """ function movesite( - ψ::AbstractMPS, n1n2::Pair{Int,Int}; orthocenter::Integer=last(n1n2), kwargs... -) - n1, n2 = n1n2 - n1 == n2 && return copy(ψ) - ψ = orthogonalize(ψ, n2) - r = n1:(n2 - 1) - ortho = "left" - if n1 > n2 - r = reverse(n2:(n1 - 1)) - ortho = "right" - end - for n in r - ψ = swapbondsites(ψ, n; ortho=ortho, kwargs...) - end - ψ = orthogonalize(ψ, orthocenter) - return ψ + ψ::AbstractMPS, n1n2::Pair{Int, Int}; orthocenter::Integer = last(n1n2), kwargs... + ) + n1, n2 = n1n2 + n1 == n2 && return copy(ψ) + ψ = orthogonalize(ψ, n2) + r = n1:(n2 - 1) + ortho = "left" + if n1 > n2 + r = reverse(n2:(n1 - 1)) + ortho = "right" + end + for n in r + ψ = swapbondsites(ψ, n; ortho = ortho, kwargs...) + end + ψ = orthogonalize(ψ, orthocenter) + return ψ end # Helper function for permuting a vector for the # movesites function. -function _movesite(ns::Vector{Int}, n1n2::Pair{Int,Int}) - n1, n2 = n1n2 - n1 == n2 && return copy(ns) - r = n1:(n2 - 1) - if n1 > n2 - r = reverse(n2:(n1 - 1)) - end - for n in r - ns = replace(ns, n => n + 1, n + 1 => n) - end - return ns +function _movesite(ns::Vector{Int}, n1n2::Pair{Int, Int}) + n1, n2 = n1n2 + n1 == n2 && return copy(ns) + r = n1:(n2 - 1) + if n1 > n2 + r = reverse(n2:(n1 - 1)) + end + for n in r + ns = replace(ns, n => n + 1, n + 1 => n) + end + return ns end function _movesites(ψ::AbstractMPS, ns::Vector{Int}, ns′::Vector{Int}; kwargs...) - ψ = copy(ψ) - N = length(ns) - @assert N == length(ns′) - for i in 1:N - ψ = movesite(ψ, ns[i] => ns′[i]; kwargs...) - ns = _movesite(ns, ns[i] => ns′[i]) - end - return ψ, ns + ψ = copy(ψ) + N = length(ns) + @assert N == length(ns′) + for i in 1:N + ψ = movesite(ψ, ns[i] => ns′[i]; kwargs...) + ns = _movesite(ns, ns[i] => ns′[i]) + end + return ψ, ns end # TODO: make a permutesites(::MPS/MPO, perm) # function that takes a permutation of the sites # p(1:N) for N sites -function movesites(ψ::AbstractMPS, nsns′::Vector{Pair{Int,Int}}; kwargs...) - ns = first.(nsns′) - ns′ = last.(nsns′) - ψ = copy(ψ) - N = length(ns) - @assert N == length(ns′) - p = sortperm(ns′) - ns = ns[p] - ns′ = ns′[p] - ns = collect(ns) - while ns ≠ ns′ - ψ, ns = _movesites(ψ, ns, ns′; kwargs...) - end - return ψ +function movesites(ψ::AbstractMPS, nsns′::Vector{Pair{Int, Int}}; kwargs...) + ns = first.(nsns′) + ns′ = last.(nsns′) + ψ = copy(ψ) + N = length(ns) + @assert N == length(ns′) + p = sortperm(ns′) + ns = ns[p] + ns′ = ns′[p] + ns = collect(ns) + while ns ≠ ns′ + ψ, ns = _movesites(ψ, ns, ns′; kwargs...) + end + return ψ end # TODO: call the Vector{Pair{Int, Int}} version function movesites(ψ::AbstractMPS, ns, ns′; kwargs...) - ψ = copy(ψ) - N = length(ns) - @assert N == length(ns′) - p = sortperm(ns′) - ns = ns[p] - ns′ = ns′[p] - ns = collect(ns) - for i in 1:N - ψ = movesite(ψ, ns[i] => ns′[i]; kwargs...) - ns = _movesite(ns, ns[i] => ns′[i]) - end - return ψ + ψ = copy(ψ) + N = length(ns) + @assert N == length(ns′) + p = sortperm(ns′) + ns = ns[p] + ns′ = ns′[p] + ns = collect(ns) + for i in 1:N + ψ = movesite(ψ, ns[i] => ns′[i]; kwargs...) + ns = _movesite(ns, ns[i] => ns′[i]) + end + return ψ end """ @@ -2141,38 +2141,38 @@ to false. MPO, move the sites of the MPS or MPO back to their original locations. """ function product( - o::ITensor, - ψ::AbstractMPS, - ns=findsites(ψ, o); - move_sites_back::Bool=true, - apply_dag::Bool=false, - kwargs..., -) - N = length(ns) - ns = sort(ns) - - # TODO: make this smarter by minimizing - # distance to orthogonalization. - # For example, if ITensors.orthocenter(ψ) > ns[end], - # set to ns[end]. - ψ = orthogonalize(ψ, ns[1]) - diff_ns = diff(ns) - ns′ = ns - if any(!=(1), diff_ns) - ns′ = [ns[1] + n - 1 for n in 1:N] - ψ = movesites(ψ, ns .=> ns′; kwargs...) - end - ϕ = ψ[ns′[1]] - for n in 2:N - ϕ *= ψ[ns′[n]] - end - ϕ = product(o, ϕ; apply_dag=apply_dag) - ψ[ns′[1]:ns′[end], kwargs...] = ϕ - if move_sites_back - # Move the sites back to their original positions - ψ = movesites(ψ, ns′ .=> ns; kwargs...) - end - return ψ + o::ITensor, + ψ::AbstractMPS, + ns = findsites(ψ, o); + move_sites_back::Bool = true, + apply_dag::Bool = false, + kwargs..., + ) + N = length(ns) + ns = sort(ns) + + # TODO: make this smarter by minimizing + # distance to orthogonalization. + # For example, if ITensors.orthocenter(ψ) > ns[end], + # set to ns[end]. + ψ = orthogonalize(ψ, ns[1]) + diff_ns = diff(ns) + ns′ = ns + if any(!=(1), diff_ns) + ns′ = [ns[1] + n - 1 for n in 1:N] + ψ = movesites(ψ, ns .=> ns′; kwargs...) + end + ϕ = ψ[ns′[1]] + for n in 2:N + ϕ *= ψ[ns′[n]] + end + ϕ = product(o, ϕ; apply_dag = apply_dag) + ψ[ns′[1]:ns′[end], kwargs...] = ϕ + if move_sites_back + # Move the sites back to their original positions + ψ = movesites(ψ, ns′ .=> ns; kwargs...) + end + return ψ end """ @@ -2274,23 +2274,23 @@ expτH = ops(os, s) ``` """ function product( - As::Vector{ITensor}, - ψ::AbstractMPS; - move_sites_back_between_gates::Bool=true, - move_sites_back::Bool=true, - kwargs..., -) - Aψ = ψ - for A in As - Aψ = product(A, Aψ; move_sites_back=move_sites_back_between_gates, kwargs...) - end - if !move_sites_back_between_gates && move_sites_back - s = siteinds(Aψ) - ns = 1:length(ψ) - ñs = [findsite(ψ, i) for i in s] - Aψ = movesites(Aψ, ns .=> ñs; kwargs...) - end - return Aψ + As::Vector{ITensor}, + ψ::AbstractMPS; + move_sites_back_between_gates::Bool = true, + move_sites_back::Bool = true, + kwargs..., + ) + Aψ = ψ + for A in As + Aψ = product(A, Aψ; move_sites_back = move_sites_back_between_gates, kwargs...) + end + if !move_sites_back_between_gates && move_sites_back + s = siteinds(Aψ) + ns = 1:length(ψ) + ñs = [findsite(ψ, i) for i in s] + Aψ = movesites(Aψ, ns .=> ñs; kwargs...) + end + return Aψ end # Apply in the reverse order for proper order of operations @@ -2307,11 +2307,11 @@ end # # U|ψ⟩ = Z₁X₁|ψ⟩ # apply(U, function product(o::Prod{ITensor}, ψ::AbstractMPS; kwargs...) - return product(reverse(terms(o)), ψ; kwargs...) + return product(reverse(terms(o)), ψ; kwargs...) end function (o::Prod{ITensor})(ψ::AbstractMPS; kwargs...) - return apply(o, ψ; kwargs...) + return apply(o, ψ; kwargs...) end # @@ -2349,21 +2349,21 @@ the tensors in the network. The name `totalqn` is an alias for `flux`. """ function flux(M::AbstractMPS) - hasqns(M) || return nothing - q = QN() - for j in (M.llim + 1):(M.rlim - 1) - q += flux(M[j]) - end - return q + hasqns(M) || return nothing + q = QN() + for j in (M.llim + 1):(M.rlim - 1) + q += flux(M[j]) + end + return q end totalqn(M::AbstractMPS) = flux(M) function checkflux(M::AbstractMPS) - for m in M - checkflux(m) - end - return nothing + for m in M + checkflux(m) + end + return nothing end """ @@ -2375,26 +2375,26 @@ Then, only keep the blocks with `norm(b) > tol`. This can make the ITensors of the MPS/MPO more sparse, and is particularly helpful as a preprocessing step on a local Hamiltonian MPO for DMRG. """ -function splitblocks!(::typeof(linkinds), M::AbstractMPS; tol=0) - for i in eachindex(M)[1:(end - 1)] - l = linkind(M, i) - if !isnothing(l) - @preserve_ortho M begin - M[i] = splitblocks(M[i], l) - M[i + 1] = splitblocks(M[i + 1], l) - end +function splitblocks!(::typeof(linkinds), M::AbstractMPS; tol = 0) + for i in eachindex(M)[1:(end - 1)] + l = linkind(M, i) + if !isnothing(l) + @preserve_ortho M begin + M[i] = splitblocks(M[i], l) + M[i + 1] = splitblocks(M[i + 1], l) + end + end end - end - return M + return M end -function splitblocks(::typeof(linkinds), M::AbstractMPS; tol=0) - return splitblocks!(linkinds, copy(M); tol=0) +function splitblocks(::typeof(linkinds), M::AbstractMPS; tol = 0) + return splitblocks!(linkinds, copy(M); tol = 0) end -removeqns(M::AbstractMPS) = map(removeqns, M; set_limits=false) +removeqns(M::AbstractMPS) = map(removeqns, M; set_limits = false) function QuantumNumbers.removeqn(M::AbstractMPS, qn_name::String) - return map(m -> removeqn(m, qn_name), M; set_limits=false) + return map(m -> removeqn(m, qn_name), M; set_limits = false) end # @@ -2402,32 +2402,32 @@ end # BroadcastStyle(MPST::Type{<:AbstractMPS}) = Style{MPST}() -function BroadcastStyle(::Style{MPST}, ::DefaultArrayStyle{N}) where {N,MPST<:AbstractMPS} - return Style{MPST}() +function BroadcastStyle(::Style{MPST}, ::DefaultArrayStyle{N}) where {N, MPST <: AbstractMPS} + return Style{MPST}() end broadcastable(ψ::AbstractMPS) = ψ function copyto!(ψ::AbstractMPS, b::Broadcasted) - copyto!(data(ψ), b) - # In general, we assume the broadcast operation - # will mess up the orthogonality - # TODO: special case for `prime`, `settags`, etc. - reset_ortho_lims!(ψ) - return ψ + copyto!(data(ψ), b) + # In general, we assume the broadcast operation + # will mess up the orthogonality + # TODO: special case for `prime`, `settags`, etc. + reset_ortho_lims!(ψ) + return ψ end -function Base.similar(bc::Broadcasted{Style{MPST}}, ElType::Type) where {MPST<:AbstractMPS} - return similar(Array{ElType}, axes(bc)) +function Base.similar(bc::Broadcasted{Style{MPST}}, ElType::Type) where {MPST <: AbstractMPS} + return similar(Array{ElType}, axes(bc)) end function Base.similar( - bc::Broadcasted{Style{MPST}}, ::Type{ITensor} -) where {MPST<:AbstractMPS} - # In general, we assume the broadcast operation - # will mess up the orthogonality so we use - # a generic constructor where we don't specify - # the orthogonality limits. - return MPST(similar(Array{ITensor}, axes(bc))) + bc::Broadcasted{Style{MPST}}, ::Type{ITensor} + ) where {MPST <: AbstractMPS} + # In general, we assume the broadcast operation + # will mess up the orthogonality so we use + # a generic constructor where we don't specify + # the orthogonality limits. + return MPST(similar(Array{ITensor}, axes(bc))) end # @@ -2435,18 +2435,19 @@ end # function Base.show(io::IO, M::AbstractMPS) - print(io, "$(typeof(M))") - (length(M) > 0) && print(io, "\n") - for i in eachindex(M) - if !isassigned(M, i) - println(io, "#undef") - else - A = M[i] - if order(A) != 0 - println(io, "[$i] $(inds(A))") - else - println(io, "[$i] ITensor()") - end - end - end + print(io, "$(typeof(M))") + (length(M) > 0) && print(io, "\n") + for i in eachindex(M) + if !isassigned(M, i) + println(io, "#undef") + else + A = M[i] + if order(A) != 0 + println(io, "[$i] $(inds(A))") + else + println(io, "[$i] ITensor()") + end + end + end + return end diff --git a/src/abstractprojmpo/abstractprojmpo.jl b/src/abstractprojmpo/abstractprojmpo.jl index 05aae2d..c8320ef 100644 --- a/src/abstractprojmpo/abstractprojmpo.jl +++ b/src/abstractprojmpo/abstractprojmpo.jl @@ -23,34 +23,34 @@ the length of the MPO used to construct it """ Base.length(P::AbstractProjMPO) = length(P.H) -function lproj(P::AbstractProjMPO)::Union{ITensor,OneITensor} - (P.lpos <= 0) && return OneITensor() - return P.LR[P.lpos] +function lproj(P::AbstractProjMPO)::Union{ITensor, OneITensor} + (P.lpos <= 0) && return OneITensor() + return P.LR[P.lpos] end -function rproj(P::AbstractProjMPO)::Union{ITensor,OneITensor} - (P.rpos >= length(P) + 1) && return OneITensor() - return P.LR[P.rpos] +function rproj(P::AbstractProjMPO)::Union{ITensor, OneITensor} + (P.rpos >= length(P) + 1) && return OneITensor() + return P.LR[P.rpos] end function ITensors.contract(P::AbstractProjMPO, v::ITensor)::ITensor - itensor_map = Union{ITensor,OneITensor}[lproj(P)] - append!(itensor_map, P.H[site_range(P)]) - push!(itensor_map, rproj(P)) - - # Reverse the contraction order of the map if - # the first tensor is a scalar (for example we - # are at the left edge of the system) - if dim(first(itensor_map)) == 1 - reverse!(itensor_map) - end - - # Apply the map - Hv = v - for it in itensor_map - Hv *= it - end - return Hv + itensor_map = Union{ITensor, OneITensor}[lproj(P)] + append!(itensor_map, P.H[site_range(P)]) + push!(itensor_map, rproj(P)) + + # Reverse the contraction order of the map if + # the first tensor is a scalar (for example we + # are at the left edge of the system) + if dim(first(itensor_map)) == 1 + reverse!(itensor_map) + end + + # Apply the map + Hv = v + for it in itensor_map + Hv *= it + end + return Hv end """ @@ -68,20 +68,20 @@ as `v`. The operator overload `P(v)` is shorthand for `product(P,v)`. """ function product(P::AbstractProjMPO, v::ITensor)::ITensor - Pv = contract(P, v) - if order(Pv) != order(v) - error( - string( - "The order of the ProjMPO-ITensor product P*v is not equal to the order of the ITensor v, ", - "this is probably due to an index mismatch.\nCommon reasons for this error: \n", - "(1) You are trying to multiply the ProjMPO with the $(nsite(P))-site wave-function at the wrong position.\n", - "(2) `orthogonalize!` was called, changing the MPS without updating the ProjMPO.\n\n", - "P*v inds: $(inds(Pv)) \n\n", - "v inds: $(inds(v))", - ), - ) - end - return noprime(Pv) + Pv = contract(P, v) + if order(Pv) != order(v) + error( + string( + "The order of the ProjMPO-ITensor product P*v is not equal to the order of the ITensor v, ", + "this is probably due to an index mismatch.\nCommon reasons for this error: \n", + "(1) You are trying to multiply the ProjMPO with the $(nsite(P))-site wave-function at the wrong position.\n", + "(2) `orthogonalize!` was called, changing the MPS without updating the ProjMPO.\n\n", + "P*v inds: $(inds(Pv)) \n\n", + "v inds: $(inds(v))", + ), + ) + end + return noprime(Pv) end (P::AbstractProjMPO)(v::ITensor) = product(P, v) @@ -94,11 +94,11 @@ or ComplexF64) of the tensors in the ProjMPO `P`. """ function Base.eltype(P::AbstractProjMPO)::Type - ElType = eltype(lproj(P)) - for j in site_range(P) - ElType = promote_type(ElType, eltype(P.H[j])) - end - return promote_type(ElType, eltype(rproj(P))) + ElType = eltype(lproj(P)) + for j in site_range(P) + ElType = promote_type(ElType, eltype(P.H[j])) + end + return promote_type(ElType, eltype(rproj(P))) end """ @@ -113,78 +113,78 @@ indices `(a,s1,s2,b)` to the space `(a',s1',s2',b')` then the size is `(d,d)` where `d = dim(a)*dim(s1)*dim(s1)*dim(b)` """ -function Base.size(P::AbstractProjMPO)::Tuple{Int,Int} - d = 1 - for i in inds(lproj(P)) - plev(i) > 0 && (d *= dim(i)) - end - for j in site_range(P) - for i in inds(P.H[j]) - plev(i) > 0 && (d *= dim(i)) +function Base.size(P::AbstractProjMPO)::Tuple{Int, Int} + d = 1 + for i in inds(lproj(P)) + plev(i) > 0 && (d *= dim(i)) end - end - for i in inds(rproj(P)) - plev(i) > 0 && (d *= dim(i)) - end - return (d, d) + for j in site_range(P) + for i in inds(P.H[j]) + plev(i) > 0 && (d *= dim(i)) + end + end + for i in inds(rproj(P)) + plev(i) > 0 && (d *= dim(i)) + end + return (d, d) end -function _makeL!(P::AbstractProjMPO, psi::MPS, k::Int)::Union{ITensor,Nothing} - # Save the last `L` that is made to help with caching - # for DiskProjMPO - ll = P.lpos - if ll ≥ k - # Special case when nothing has to be done. - # Still need to change the position if lproj is - # being moved backward. +function _makeL!(P::AbstractProjMPO, psi::MPS, k::Int)::Union{ITensor, Nothing} + # Save the last `L` that is made to help with caching + # for DiskProjMPO + ll = P.lpos + if ll ≥ k + # Special case when nothing has to be done. + # Still need to change the position if lproj is + # being moved backward. + P.lpos = k + return nothing + end + # Make sure ll is at least 0 for the generic logic below + ll = max(ll, 0) + L = lproj(P) + while ll < k + L = L * psi[ll + 1] * P.H[ll + 1] * dag(prime(psi[ll + 1])) + P.LR[ll + 1] = L + ll += 1 + end + # Needed when moving lproj backward. P.lpos = k - return nothing - end - # Make sure ll is at least 0 for the generic logic below - ll = max(ll, 0) - L = lproj(P) - while ll < k - L = L * psi[ll + 1] * P.H[ll + 1] * dag(prime(psi[ll + 1])) - P.LR[ll + 1] = L - ll += 1 - end - # Needed when moving lproj backward. - P.lpos = k - return L + return L end function makeL!(P::AbstractProjMPO, psi::MPS, k::Int) - _makeL!(P, psi, k) - return P + _makeL!(P, psi, k) + return P end -function _makeR!(P::AbstractProjMPO, psi::MPS, k::Int)::Union{ITensor,Nothing} - # Save the last `R` that is made to help with caching - # for DiskProjMPO - rl = P.rpos - if rl ≤ k - # Special case when nothing has to be done. - # Still need to change the position if rproj is - # being moved backward. +function _makeR!(P::AbstractProjMPO, psi::MPS, k::Int)::Union{ITensor, Nothing} + # Save the last `R` that is made to help with caching + # for DiskProjMPO + rl = P.rpos + if rl ≤ k + # Special case when nothing has to be done. + # Still need to change the position if rproj is + # being moved backward. + P.rpos = k + return nothing + end + N = length(P.H) + # Make sure rl is no bigger than `N + 1` for the generic logic below + rl = min(rl, N + 1) + R = rproj(P) + while rl > k + R = R * psi[rl - 1] * P.H[rl - 1] * dag(prime(psi[rl - 1])) + P.LR[rl - 1] = R + rl -= 1 + end P.rpos = k - return nothing - end - N = length(P.H) - # Make sure rl is no bigger than `N + 1` for the generic logic below - rl = min(rl, N + 1) - R = rproj(P) - while rl > k - R = R * psi[rl - 1] * P.H[rl - 1] * dag(prime(psi[rl - 1])) - P.LR[rl - 1] = R - rl -= 1 - end - P.rpos = k - return R + return R end function makeR!(P::AbstractProjMPO, psi::MPS, k::Int) - _makeR!(P, psi, k) - return P + _makeR!(P, psi, k) + return P end """ @@ -200,9 +200,9 @@ the previous projected MPO tensors for this operation to succeed. """ function position!(P::AbstractProjMPO, psi::MPS, pos::Int) - makeL!(P, psi, pos - 1) - makeR!(P, psi, pos + nsite(P)) - return P + makeL!(P, psi, pos - 1) + makeR!(P, psi, pos + nsite(P)) + return P end """ @@ -219,33 +219,33 @@ the values `"left"` or `"right"` depending on the sweeping direction of the DMRG calculation. """ function noiseterm(P::AbstractProjMPO, phi::ITensor, ortho::String)::ITensor - if nsite(P) != 2 - error("noise term only defined for 2-site ProjMPO") - end - - site_range_P = site_range(P) - if ortho == "left" - AL = P.H[first(site_range_P)] - AL = lproj(P) * AL - nt = AL * phi - elseif ortho == "right" - AR = P.H[last(site_range_P)] - AR = AR * rproj(P) - nt = phi * AR - else - error("In noiseterm, got ortho = $ortho, only supports `left` and `right`") - end - nt = nt * dag(noprime(nt)) - - return nt + if nsite(P) != 2 + error("noise term only defined for 2-site ProjMPO") + end + + site_range_P = site_range(P) + if ortho == "left" + AL = P.H[first(site_range_P)] + AL = lproj(P) * AL + nt = AL * phi + elseif ortho == "right" + AR = P.H[last(site_range_P)] + AR = AR * rproj(P) + nt = phi * AR + else + error("In noiseterm, got ortho = $ortho, only supports `left` and `right`") + end + nt = nt * dag(noprime(nt)) + + return nt end function checkflux(P::AbstractProjMPO) - checkflux(P.H) - for n in length(P.LR) - if isassigned(P.LR, n) - checkflux(P.LR[n]) + checkflux(P.H) + for n in length(P.LR) + if isassigned(P.LR, n) + checkflux(P.LR[n]) + end end - end - return nothing + return nothing end diff --git a/src/abstractprojmpo/diskprojmpo.jl b/src/abstractprojmpo/diskprojmpo.jl index dd9d1df..b7f34ea 100644 --- a/src/abstractprojmpo/diskprojmpo.jl +++ b/src/abstractprojmpo/diskprojmpo.jl @@ -1,4 +1,3 @@ - """ A DiskProjMPO computes and stores the projection of an MPO into a basis defined by an MPS, leaving a @@ -22,107 +21,107 @@ The environment tensors are stored on disk, which is helpful for large bond dimensions if they cannot fit in memory. """ mutable struct DiskProjMPO <: AbstractProjMPO - lpos::Int - rpos::Int - nsite::Int - H::MPO - LR::DiskVector{ITensor} - Lcache::Union{ITensor,OneITensor} - lposcache::Union{Int,Nothing} - Rcache::Union{ITensor,OneITensor} - rposcache::Union{Int,Nothing} + lpos::Int + rpos::Int + nsite::Int + H::MPO + LR::DiskVector{ITensor} + Lcache::Union{ITensor, OneITensor} + lposcache::Union{Int, Nothing} + Rcache::Union{ITensor, OneITensor} + rposcache::Union{Int, Nothing} end function copy(P::DiskProjMPO) - return DiskProjMPO( - P.lpos, - P.rpos, - P.nsite, - copy(P.H), - copy(P.LR), - P.Lcache, - P.lposcache, - P.Rcache, - P.rposcache, - ) + return DiskProjMPO( + P.lpos, + P.rpos, + P.nsite, + copy(P.H), + copy(P.LR), + P.Lcache, + P.lposcache, + P.Rcache, + P.rposcache, + ) end function set_nsite!(P::DiskProjMPO, nsite) - P.nsite = nsite - return P + P.nsite = nsite + return P end function DiskProjMPO(H::MPO) - return new( - 0, - length(H) + 1, - 2, - H, - disk(Vector{ITensor}(undef, length(H))), - OneITensor, - nothing, - OneITensor, - nothing, - ) + return new( + 0, + length(H) + 1, + 2, + H, + disk(Vector{ITensor}(undef, length(H))), + OneITensor, + nothing, + OneITensor, + nothing, + ) end function disk(pm::ProjMPO; kwargs...) - return DiskProjMPO( - pm.lpos, - pm.rpos, - pm.nsite, - pm.H, - disk(pm.LR; kwargs...), - lproj(pm), - pm.lpos, - rproj(pm), - pm.rpos, - ) + return DiskProjMPO( + pm.lpos, + pm.rpos, + pm.nsite, + pm.H, + disk(pm.LR; kwargs...), + lproj(pm), + pm.lpos, + rproj(pm), + pm.rpos, + ) end disk(pm::DiskProjMPO; kwargs...) = pm # Special overload of lproj which uses the cached # version of the left projected MPO, and if the # cache doesn't exist it loads it from disk. -function lproj(P::DiskProjMPO)::Union{ITensor,OneITensor} - (P.lpos <= 0) && return OneITensor() - if (P.lpos ≠ P.lposcache) || (P.lpos == 1) - # Need to update the cache - P.Lcache = P.LR[P.lpos] - P.lposcache = P.lpos - end - return P.Lcache +function lproj(P::DiskProjMPO)::Union{ITensor, OneITensor} + (P.lpos <= 0) && return OneITensor() + if (P.lpos ≠ P.lposcache) || (P.lpos == 1) + # Need to update the cache + P.Lcache = P.LR[P.lpos] + P.lposcache = P.lpos + end + return P.Lcache end # Special overload of rproj which uses the cached # version of the right projected MPO, and if the # cache doesn't exist it loads it from disk. -function rproj(P::DiskProjMPO)::Union{ITensor,OneITensor} - (P.rpos >= length(P) + 1) && return OneITensor() - if (P.rpos ≠ P.rposcache) || (P.rpos == length(P)) - # Need to update the cache - P.Rcache = P.LR[P.rpos] - P.rposcache = P.rpos - end - return P.Rcache +function rproj(P::DiskProjMPO)::Union{ITensor, OneITensor} + (P.rpos >= length(P) + 1) && return OneITensor() + if (P.rpos ≠ P.rposcache) || (P.rpos == length(P)) + # Need to update the cache + P.Rcache = P.LR[P.rpos] + P.rposcache = P.rpos + end + return P.Rcache end function makeL!(P::DiskProjMPO, psi::MPS, k::Int) - L = _makeL!(P, psi, k) - if !isnothing(L) - # Cache the result - P.Lcache = L - P.lposcache = P.lpos - end - return P + L = _makeL!(P, psi, k) + if !isnothing(L) + # Cache the result + P.Lcache = L + P.lposcache = P.lpos + end + return P end function makeR!(P::DiskProjMPO, psi::MPS, k::Int) - R = _makeR!(P, psi, k) - if !isnothing(R) - # Cache the result - P.Rcache = R - P.rposcache = P.rpos - end - return P + R = _makeR!(P, psi, k) + if !isnothing(R) + # Cache the result + P.Rcache = R + P.rposcache = P.rpos + end + return P end diff --git a/src/abstractprojmpo/projmpo.jl b/src/abstractprojmpo/projmpo.jl index d8c2a84..5341980 100644 --- a/src/abstractprojmpo/projmpo.jl +++ b/src/abstractprojmpo/projmpo.jl @@ -1,4 +1,3 @@ - """ A ProjMPO computes and stores the projection of an MPO into a basis defined by an MPS, leaving a @@ -19,17 +18,17 @@ o--o--o- -o--o--o--o--o--o |psi> ``` """ mutable struct ProjMPO <: AbstractProjMPO - lpos::Int - rpos::Int - nsite::Int - H::MPO - LR::Vector{ITensor} + lpos::Int + rpos::Int + nsite::Int + H::MPO + LR::Vector{ITensor} end ProjMPO(H::MPO) = ProjMPO(0, length(H) + 1, 2, H, Vector{ITensor}(undef, length(H))) copy(P::ProjMPO) = ProjMPO(P.lpos, P.rpos, P.nsite, copy(P.H), copy(P.LR)) function set_nsite!(P::ProjMPO, nsite) - P.nsite = nsite - return P + P.nsite = nsite + return P end diff --git a/src/abstractprojmpo/projmpo_mps.jl b/src/abstractprojmpo/projmpo_mps.jl index c4c0ca7..cc3d119 100644 --- a/src/abstractprojmpo/projmpo_mps.jl +++ b/src/abstractprojmpo/projmpo_mps.jl @@ -1,49 +1,49 @@ mutable struct ProjMPO_MPS - PH::ProjMPO - pm::Vector{ProjMPS} - weight::Float64 + PH::ProjMPO + pm::Vector{ProjMPS} + weight::Float64 end copy(P::ProjMPO_MPS) = ProjMPO_MPS(copy(P.PH), copy.(P.pm), P.weight) -function ProjMPO_MPS(H::MPO, mpsv::Vector{MPS}; weight=1.0) - return ProjMPO_MPS(ProjMPO(H), [ProjMPS(m) for m in mpsv], weight) +function ProjMPO_MPS(H::MPO, mpsv::Vector{MPS}; weight = 1.0) + return ProjMPO_MPS(ProjMPO(H), [ProjMPS(m) for m in mpsv], weight) end -ProjMPO_MPS(H::MPO, Ms::MPS...; weight=1.0) = ProjMPO_MPS(H, [Ms...], weight) +ProjMPO_MPS(H::MPO, Ms::MPS...; weight = 1.0) = ProjMPO_MPS(H, [Ms...], weight) nsite(P::ProjMPO_MPS) = nsite(P.PH) function set_nsite!(Ps::ProjMPO_MPS, nsite) - set_nsite!(Ps.PH, nsite) - for P in Ps.pm - set_nsite!(P, nsite) - end - return Ps + set_nsite!(Ps.PH, nsite) + for P in Ps.pm + set_nsite!(P, nsite) + end + return Ps end Base.length(P::ProjMPO_MPS) = length(P.PH) function site_range(P::ProjMPO_MPS) - r = site_range(P.PH) - @assert all(m -> site_range(m) == r, P.pm) - return r + r = site_range(P.PH) + @assert all(m -> site_range(m) == r, P.pm) + return r end function product(P::ProjMPO_MPS, v::ITensor)::ITensor - Pv = product(P.PH, v) - for p in P.pm - Pv += P.weight * product(p, v) - end - return Pv + Pv = product(P.PH, v) + for p in P.pm + Pv += P.weight * product(p, v) + end + return Pv end function Base.eltype(P::ProjMPO_MPS) - elT = eltype(P.PH) - for p in P.pm - elT = promote_type(elT, eltype(p)) - end - return elT + elT = eltype(P.PH) + for p in P.pm + elT = promote_type(elT, eltype(p)) + end + return elT end (P::ProjMPO_MPS)(v::ITensor) = product(P, v) @@ -51,17 +51,17 @@ end Base.size(P::ProjMPO_MPS) = size(P.H) function position!(P::ProjMPO_MPS, psi::MPS, pos::Int) - position!(P.PH, psi, pos) - for p in P.pm - position!(p, psi, pos) - end - return P + position!(P.PH, psi, pos) + for p in P.pm + position!(p, psi, pos) + end + return P end noiseterm(P::ProjMPO_MPS, phi::ITensor, dir::String) = noiseterm(P.PH, phi, dir) function checkflux(P::ProjMPO_MPS) - checkflux(P.PH) - foreach(checkflux, P.pm) - return nothing + checkflux(P.PH) + foreach(checkflux, P.pm) + return nothing end diff --git a/src/abstractprojmpo/projmposum.jl b/src/abstractprojmpo/projmposum.jl index 3d5d5d9..5243b24 100644 --- a/src/abstractprojmpo/projmposum.jl +++ b/src/abstractprojmpo/projmposum.jl @@ -5,28 +5,28 @@ abstract type AbstractSum end terms(sum::AbstractSum) = sum.terms function set_terms(sum::AbstractSum, terms) - return error("Please implement `set_terms` for the `AbstractSum` type `$(typeof(sum))`.") + return error("Please implement `set_terms` for the `AbstractSum` type `$(typeof(sum))`.") end copy(P::AbstractSum) = typeof(P)(copy.(terms(P))) function nsite(P::AbstractSum) - @assert allequal(nsite.(terms(P))) - return nsite(first(terms(P))) + @assert allequal(nsite.(terms(P))) + return nsite(first(terms(P))) end function set_nsite!(A::AbstractSum, nsite) - return set_terms(A, map(term -> set_nsite!(term, nsite), terms(A))) + return set_terms(A, map(term -> set_nsite!(term, nsite), terms(A))) end function length(A::AbstractSum) - @assert allequal(length.(terms(A))) - return length(first(terms(A))) + @assert allequal(length.(terms(A))) + return length(first(terms(A))) end function site_range(A::AbstractSum) - @assert allequal(Iterators.map(site_range, terms(A))) - return site_range(first(terms(A))) + @assert allequal(Iterators.map(site_range, terms(A))) + return site_range(first(terms(A))) end """ @@ -71,8 +71,8 @@ then the size is `(d,d)` where `d = dim(a)*dim(s1)*dim(s1)*dim(b)` """ function size(A::AbstractSum) - @assert allequal(size.(terms(A))) - return size(first(terms(A))) + @assert allequal(size.(terms(A))) + return size(first(terms(A))) end """ @@ -88,8 +88,8 @@ the previous projected MPO tensors for this operation to succeed. """ function position!(A::AbstractSum, psi::MPS, pos::Int) - new_terms = map(term -> position!(term, psi, pos), terms(A)) - return set_terms(A, new_terms) + new_terms = map(term -> position!(term, psi, pos), terms(A)) + return set_terms(A, new_terms) end """ @@ -106,7 +106,7 @@ the values `"left"` or `"right"` depending on the sweeping direction of the DMRG calculation. """ function noiseterm(A::AbstractSum, phi::ITensor, dir::String) - return sum(t -> noiseterm(t, phi, dir), terms(A)) + return sum(t -> noiseterm(t, phi, dir), terms(A)) end """ @@ -116,12 +116,12 @@ Call `disk` on each term of an AbstractSum, to enable saving of cached data to hard disk. """ function disk(sum::AbstractSum; disk_kwargs...) - return set_terms(sum, map(t -> disk(t; disk_kwargs...), terms(sum))) + return set_terms(sum, map(t -> disk(t; disk_kwargs...), terms(sum))) end function checkflux(sum::AbstractSum) - foreach(checkflux, terms(sum)) - return nothing + foreach(checkflux, terms(sum)) + return nothing end # @@ -129,14 +129,14 @@ end # struct SequentialSum{T} <: AbstractSum - terms::Vector{T} + terms::Vector{T} end -function SequentialSum{T}(mpos::Vector{MPO}) where {T<:AbstractProjMPO} - return SequentialSum([T(M) for M in mpos]) +function SequentialSum{T}(mpos::Vector{MPO}) where {T <: AbstractProjMPO} + return SequentialSum([T(M) for M in mpos]) end -SequentialSum{T}(Ms::MPO...) where {T<:AbstractProjMPO} = SequentialSum{T}([Ms...]) +SequentialSum{T}(Ms::MPO...) where {T <: AbstractProjMPO} = SequentialSum{T}([Ms...]) set_terms(sum::SequentialSum, terms) = SequentialSum(terms) diff --git a/src/abstractprojmpo/projmps.jl b/src/abstractprojmpo/projmps.jl index 0ad60a5..d951d14 100644 --- a/src/abstractprojmpo/projmps.jl +++ b/src/abstractprojmpo/projmps.jl @@ -1,10 +1,9 @@ - mutable struct ProjMPS - lpos::Int - rpos::Int - nsite::Int - M::MPS - LR::Vector{ITensor} + lpos::Int + rpos::Int + nsite::Int + M::MPS + LR::Vector{ITensor} end ProjMPS(M::MPS) = ProjMPS(0, length(M) + 1, 2, M, Vector{ITensor}(undef, length(M))) @@ -17,40 +16,40 @@ nsite(P::ProjMPS) = P.nsite site_range(P::ProjMPS) = (P.lpos + 1):(P.rpos - 1) function set_nsite!(P::ProjMPS, nsite) - P.nsite = nsite - return P + P.nsite = nsite + return P end Base.length(P::ProjMPS) = length(P.M) function lproj(P::ProjMPS) - (P.lpos <= 0) && return nothing - return P.LR[P.lpos] + (P.lpos <= 0) && return nothing + return P.LR[P.lpos] end function rproj(P::ProjMPS) - (P.rpos >= length(P) + 1) && return nothing - return P.LR[P.rpos] + (P.rpos >= length(P) + 1) && return nothing + return P.LR[P.rpos] end function product(P::ProjMPS, v::ITensor)::ITensor - if nsite(P) != 2 - error("Only two-site ProjMPS currently supported") - end + if nsite(P) != 2 + error("Only two-site ProjMPS currently supported") + end - Lpm = dag(prime(P.M[P.lpos + 1], "Link")) - !isnothing(lproj(P)) && (Lpm *= lproj(P)) + Lpm = dag(prime(P.M[P.lpos + 1], "Link")) + !isnothing(lproj(P)) && (Lpm *= lproj(P)) - Rpm = dag(prime(P.M[P.rpos - 1], "Link")) - !isnothing(rproj(P)) && (Rpm *= rproj(P)) + Rpm = dag(prime(P.M[P.rpos - 1], "Link")) + !isnothing(rproj(P)) && (Rpm *= rproj(P)) - pm = Lpm * Rpm + pm = Lpm * Rpm - pv = scalar(pm * v) + pv = scalar(pm * v) - Mv = pv * dag(pm) + Mv = pv * dag(pm) - return noprime(Mv) + return noprime(Mv) end #function Base.eltype(P::ProjMPS) @@ -84,47 +83,49 @@ end #end function makeL!(P::ProjMPS, psi::MPS, k::Int) - while P.lpos < k - ll = P.lpos - if ll <= 0 - P.LR[1] = psi[1] * dag(prime(P.M[1], "Link")) - P.lpos = 1 - else - P.LR[ll + 1] = P.LR[ll] * psi[ll + 1] * dag(prime(P.M[ll + 1], "Link")) - P.lpos += 1 + while P.lpos < k + ll = P.lpos + if ll <= 0 + P.LR[1] = psi[1] * dag(prime(P.M[1], "Link")) + P.lpos = 1 + else + P.LR[ll + 1] = P.LR[ll] * psi[ll + 1] * dag(prime(P.M[ll + 1], "Link")) + P.lpos += 1 + end end - end + return end function makeR!(P::ProjMPS, psi::MPS, k::Int) - N = length(P.M) - while P.rpos > k - rl = P.rpos - if rl >= N + 1 - P.LR[N] = psi[N] * dag(prime(P.M[N], "Link")) - P.rpos = N - else - P.LR[rl - 1] = P.LR[rl] * psi[rl - 1] * dag(prime(P.M[rl - 1], "Link")) - P.rpos -= 1 + N = length(P.M) + while P.rpos > k + rl = P.rpos + if rl >= N + 1 + P.LR[N] = psi[N] * dag(prime(P.M[N], "Link")) + P.rpos = N + else + P.LR[rl - 1] = P.LR[rl] * psi[rl - 1] * dag(prime(P.M[rl - 1], "Link")) + P.rpos -= 1 + end end - end + return end function position!(P::ProjMPS, psi::MPS, pos::Int) - makeL!(P, psi, pos - 1) - makeR!(P, psi, pos + nsite(P)) - - #These next two lines are needed - #when moving lproj and rproj backward - P.lpos = pos - 1 - P.rpos = pos + nsite(P) - return P + makeL!(P, psi, pos - 1) + makeR!(P, psi, pos + nsite(P)) + + #These next two lines are needed + #when moving lproj and rproj backward + P.lpos = pos - 1 + P.rpos = pos + nsite(P) + return P end function checkflux(P::ProjMPS) - checkflux(P.M) - foreach(eachindex(P.LR)) do i - isassigned(P.LR, i) && checkflux(P.LR[i]) - end - return nothing + checkflux(P.M) + foreach(eachindex(P.LR)) do i + isassigned(P.LR, i) && checkflux(P.LR[i]) + end + return nothing end diff --git a/src/adapt.jl b/src/adapt.jl index b73ab27..fbece31 100644 --- a/src/adapt.jl +++ b/src/adapt.jl @@ -1,2 +1,2 @@ using Adapt: Adapt -Adapt.adapt_structure(to, x::Union{MPS,MPO}) = map(xᵢ -> adapt(to, xᵢ), x) +Adapt.adapt_structure(to, x::Union{MPS, MPO}) = map(xᵢ -> adapt(to, xᵢ), x) diff --git a/src/deprecated.jl b/src/deprecated.jl index 22f225e..53ad82a 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -27,13 +27,13 @@ # Deprecated syntax for specifying link dimensions. @deprecate randomMPS(elt::Type{<:Number}, sites::Vector{<:Index}, state, linkdims::Integer) random_mps( - elt, sites, state; linkdims + elt, sites, state; linkdims ) @deprecate randomMPS(elt::Type{<:Number}, sites::Vector{<:Index}, linkdims::Integer) random_mps( - elt, sites; linkdims + elt, sites; linkdims ) @deprecate randomMPS(sites::Vector{<:Index}, state, linkdims::Integer) random_mps( - sites, state; linkdims + sites, state; linkdims ) @deprecate randomMPS(sites::Vector{<:Index}, linkdims::Integer) random_mps(sites; linkdims) @@ -58,49 +58,49 @@ map_linkinds!(f::Function, M::AbstractMPS) = map!(f, linkinds, M) map_linkinds(f::Function, M::AbstractMPS) = map(f, linkinds, M) function map_common_siteinds!(f::Function, M1::AbstractMPS, M2::AbstractMPS) - return map!(f, siteinds, commoninds, M1, M2) + return map!(f, siteinds, commoninds, M1, M2) end function map_common_siteinds(f::Function, M1::AbstractMPS, M2::AbstractMPS) - return map(f, siteinds, commoninds, M1, M2) + return map(f, siteinds, commoninds, M1, M2) end function map_unique_siteinds!(f::Function, M1::AbstractMPS, M2::AbstractMPS) - return map!(f, siteinds, uniqueinds, M1, M2) + return map!(f, siteinds, uniqueinds, M1, M2) end function map_unique_siteinds(f::Function, M1::AbstractMPS, M2::AbstractMPS) - return map(f, siteinds, uniqueinds, M1, M2) + return map(f, siteinds, uniqueinds, M1, M2) end for fname in (:sim, :prime, :setprime, :noprime, :addtags, :removetags, :replacetags, :settags) - @eval begin - function $(Symbol(fname, :_linkinds))(M::AbstractMPS, args...; kwargs...) - return map(i -> $fname(i, args...; kwargs...), linkinds, M) + @eval begin + function $(Symbol(fname, :_linkinds))(M::AbstractMPS, args...; kwargs...) + return map(i -> $fname(i, args...; kwargs...), linkinds, M) + end + function $(Symbol(fname, :_linkinds!))(M::AbstractMPS, args...; kwargs...) + return map!(i -> $fname(i, args...; kwargs...), linkinds, M) + end + function $(Symbol(fname, :_common_siteinds))( + M1::AbstractMPS, M2::AbstractMPS, args...; kwargs... + ) + return map(i -> $fname(i, args...; kwargs...), siteinds, commoninds, M1, M2) + end + function $(Symbol(fname, :_common_siteinds!))( + M1::AbstractMPS, M2::AbstractMPS, args...; kwargs... + ) + return map!(i -> $fname(i, args...; kwargs...), siteinds, commoninds, M1, M2) + end + function $(Symbol(fname, :_unique_siteinds))( + M1::AbstractMPS, M2::AbstractMPS, args...; kwargs... + ) + return map(i -> $fname(i, args...; kwargs...), siteinds, uniqueinds, M1, M2) + end + function $(Symbol(fname, :_unique_siteinds!))( + M1::AbstractMPS, M2::AbstractMPS, args...; kwargs... + ) + return map!(i -> $fname(i, args...; kwargs...), siteinds, uniqueinds, M1, M2) + end end - function $(Symbol(fname, :_linkinds!))(M::AbstractMPS, args...; kwargs...) - return map!(i -> $fname(i, args...; kwargs...), linkinds, M) - end - function $(Symbol(fname, :_common_siteinds))( - M1::AbstractMPS, M2::AbstractMPS, args...; kwargs... - ) - return map(i -> $fname(i, args...; kwargs...), siteinds, commoninds, M1, M2) - end - function $(Symbol(fname, :_common_siteinds!))( - M1::AbstractMPS, M2::AbstractMPS, args...; kwargs... - ) - return map!(i -> $fname(i, args...; kwargs...), siteinds, commoninds, M1, M2) - end - function $(Symbol(fname, :_unique_siteinds))( - M1::AbstractMPS, M2::AbstractMPS, args...; kwargs... - ) - return map(i -> $fname(i, args...; kwargs...), siteinds, uniqueinds, M1, M2) - end - function $(Symbol(fname, :_unique_siteinds!))( - M1::AbstractMPS, M2::AbstractMPS, args...; kwargs... - ) - return map!(i -> $fname(i, args...; kwargs...), siteinds, uniqueinds, M1, M2) - end - end end diff --git a/src/dmrg.jl b/src/dmrg.jl index 19c3e6f..6f91a9f 100644 --- a/src/dmrg.jl +++ b/src/dmrg.jl @@ -5,54 +5,54 @@ using Printf: @printf using TupleTools: TupleTools function permute( - M::AbstractMPS, ::Tuple{typeof(linkind),typeof(siteinds),typeof(linkind)} -)::typeof(M) - M̃ = typeof(M)(length(M)) - for n in 1:length(M) - lₙ₋₁ = linkind(M, n - 1) - lₙ = linkind(M, n) - s⃗ₙ = TupleTools.sort(Tuple(siteinds(M, n)); by=plev) - M̃[n] = ITensors.permute(M[n], filter(!isnothing, (lₙ₋₁, s⃗ₙ..., lₙ))) - end - set_ortho_lims!(M̃, ortho_lims(M)) - return M̃ + M::AbstractMPS, ::Tuple{typeof(linkind), typeof(siteinds), typeof(linkind)} + )::typeof(M) + M̃ = typeof(M)(length(M)) + for n in 1:length(M) + lₙ₋₁ = linkind(M, n - 1) + lₙ = linkind(M, n) + s⃗ₙ = TupleTools.sort(Tuple(siteinds(M, n)); by = plev) + M̃[n] = ITensors.permute(M[n], filter(!isnothing, (lₙ₋₁, s⃗ₙ..., lₙ))) + end + set_ortho_lims!(M̃, ortho_lims(M)) + return M̃ end function dmrg(H::MPO, psi0::MPS, sweeps::Sweeps; kwargs...) - check_hascommoninds(siteinds, H, psi0) - check_hascommoninds(siteinds, H, psi0') - # Permute the indices to have a better memory layout - # and minimize permutations - H = permute(H, (linkind, siteinds, linkind)) - PH = ProjMPO(H) - return dmrg(PH, psi0, sweeps; kwargs...) + check_hascommoninds(siteinds, H, psi0) + check_hascommoninds(siteinds, H, psi0') + # Permute the indices to have a better memory layout + # and minimize permutations + H = permute(H, (linkind, siteinds, linkind)) + PH = ProjMPO(H) + return dmrg(PH, psi0, sweeps; kwargs...) end function dmrg(Hs::Vector{MPO}, psi0::MPS, sweeps::Sweeps; kwargs...) - for H in Hs - check_hascommoninds(siteinds, H, psi0) - check_hascommoninds(siteinds, H, psi0') - end - Hs .= permute.(Hs, Ref((linkind, siteinds, linkind))) - PHS = ProjMPOSum(Hs) - return dmrg(PHS, psi0, sweeps; kwargs...) + for H in Hs + check_hascommoninds(siteinds, H, psi0) + check_hascommoninds(siteinds, H, psi0') + end + Hs .= permute.(Hs, Ref((linkind, siteinds, linkind))) + PHS = ProjMPOSum(Hs) + return dmrg(PHS, psi0, sweeps; kwargs...) end -function dmrg(H::MPO, Ms::Vector{MPS}, psi0::MPS, sweeps::Sweeps; weight=true, kwargs...) - check_hascommoninds(siteinds, H, psi0) - check_hascommoninds(siteinds, H, psi0') - for M in Ms - check_hascommoninds(siteinds, M, psi0) - end - H = permute(H, (linkind, siteinds, linkind)) - Ms .= permute.(Ms, Ref((linkind, siteinds, linkind))) - if weight <= 0 - error( - "weight parameter should be > 0.0 in call to excited-state dmrg (value passed was weight=$weight)", - ) - end - PMM = ProjMPO_MPS(H, Ms; weight) - return dmrg(PMM, psi0, sweeps; kwargs...) +function dmrg(H::MPO, Ms::Vector{MPS}, psi0::MPS, sweeps::Sweeps; weight = true, kwargs...) + check_hascommoninds(siteinds, H, psi0) + check_hascommoninds(siteinds, H, psi0') + for M in Ms + check_hascommoninds(siteinds, M, psi0) + end + H = permute(H, (linkind, siteinds, linkind)) + Ms .= permute.(Ms, Ref((linkind, siteinds, linkind))) + if weight <= 0 + error( + "weight parameter should be > 0.0 in call to excited-state dmrg (value passed was weight=$weight)", + ) + end + PMM = ProjMPO_MPS(H, Ms; weight) + return dmrg(PMM, psi0, sweeps; kwargs...) end using NDTensors.TypeParameterAccessors: unwrap_array_type @@ -156,235 +156,235 @@ Optional keyword arguments: [KrylovKit.eigsolve](https://jutho.github.io/KrylovKit.jl/stable/man/eig/#KrylovKit.eigsolve). """ function dmrg( - PH, - psi0::MPS, - sweeps::Sweeps; - which_decomp=nothing, - svd_alg=nothing, - observer=NoObserver(), - outputlevel=1, - write_when_maxdim_exceeds=nothing, - write_path=tempdir(), - # eigsolve kwargs - eigsolve_tol=1e-14, - eigsolve_krylovdim=3, - eigsolve_maxiter=1, - eigsolve_verbosity=0, - eigsolve_which_eigenvalue=:SR, - ishermitian=true, -) - if length(psi0) == 1 - error( - "`dmrg` currently does not support system sizes of 1. You can diagonalize the MPO tensor directly with tools like `LinearAlgebra.eigen`, `KrylovKit.eigsolve`, etc.", + PH, + psi0::MPS, + sweeps::Sweeps; + which_decomp = nothing, + svd_alg = nothing, + observer = NoObserver(), + outputlevel = 1, + write_when_maxdim_exceeds = nothing, + write_path = tempdir(), + # eigsolve kwargs + eigsolve_tol = 1.0e-14, + eigsolve_krylovdim = 3, + eigsolve_maxiter = 1, + eigsolve_verbosity = 0, + eigsolve_which_eigenvalue = :SR, + ishermitian = true, ) - end - - @debug_check begin - # Debug level checks - # Enable with ITensors.enable_debug_checks() - checkflux(psi0) - checkflux(PH) - end - - psi = copy(psi0) - N = length(psi) - if !isortho(psi) || orthocenter(psi) != 1 - psi = orthogonalize!(PH, psi, 1) - end - @assert isortho(psi) && orthocenter(psi) == 1 - - if !isnothing(write_when_maxdim_exceeds) - if (maxlinkdim(psi) > write_when_maxdim_exceeds) || - (maxdim(sweeps, 1) > write_when_maxdim_exceeds) - PH = disk(PH; path=write_path) + if length(psi0) == 1 + error( + "`dmrg` currently does not support system sizes of 1. You can diagonalize the MPO tensor directly with tools like `LinearAlgebra.eigen`, `KrylovKit.eigsolve`, etc.", + ) end - end - PH = position!(PH, psi, 1) - energy = 0.0 - - for sw in 1:nsweep(sweeps) - sw_time = @elapsed begin - maxtruncerr = 0.0 - - if !isnothing(write_when_maxdim_exceeds) && - maxdim(sweeps, sw) > write_when_maxdim_exceeds - if outputlevel >= 2 - println( - "\nWriting environment tensors do disk (write_when_maxdim_exceeds = $write_when_maxdim_exceeds and maxdim(sweeps, sw) = $(maxdim(sweeps, sw))).\nFiles located at path=$write_path\n", - ) - end - PH = disk(PH; path=write_path) - end - - for (b, ha) in sweepnext(N) - @debug_check begin - checkflux(psi) - checkflux(PH) - end - @timeit_debug timer "dmrg: position!" begin - PH = position!(PH, psi, b) - end - - @debug_check begin - checkflux(psi) - checkflux(PH) - end - - @timeit_debug timer "dmrg: psi[b]*psi[b+1]" begin - phi = psi[b] * psi[b + 1] - end - - @timeit_debug timer "dmrg: eigsolve" begin - vals, vecs = eigsolve( - PH, - phi, - 1, - eigsolve_which_eigenvalue; - ishermitian, - tol=eigsolve_tol, - krylovdim=eigsolve_krylovdim, - maxiter=eigsolve_maxiter, - verbosity=eigsolve_verbosity, - ) - end - - energy = vals[1] - ## Right now there is a conversion problem in CUDA.jl where `UnifiedMemory` Arrays are being converted - ## into `DeviceMemory`. This conversion line is here temporarily to fix that problem when it arises - ## Adapt is only called when using CUDA backend. CPU will work as implemented previously. - ## TODO this might be the only place we really need iscu if its not fixed. - phi = if NDTensors.iscu(phi) && NDTensors.iscu(vecs[1]) - adapt(ITensors.set_eltype(unwrap_array_type(phi), eltype(vecs[1])), vecs[1]) - else - vecs[1] - end - - ortho = ha == 1 ? "left" : "right" - - drho = nothing - if noise(sweeps, sw) > 0 - @timeit_debug timer "dmrg: noiseterm" begin - # Use noise term when determining new MPS basis. - # This is used to preserve the element type of the MPS. - elt = real(scalartype(psi)) - drho = elt(noise(sweeps, sw)) * noiseterm(PH, phi, ortho) - end - end + @debug_check begin + # Debug level checks + # Enable with ITensors.enable_debug_checks() + checkflux(psi0) + checkflux(PH) + end - @debug_check begin - checkflux(phi) - end + psi = copy(psi0) + N = length(psi) + if !isortho(psi) || orthocenter(psi) != 1 + psi = orthogonalize!(PH, psi, 1) + end + @assert isortho(psi) && orthocenter(psi) == 1 - @timeit_debug timer "dmrg: replacebond!" begin - spec = replacebond!( - PH, - psi, - b, - phi; - maxdim=maxdim(sweeps, sw), - mindim=mindim(sweeps, sw), - cutoff=cutoff(sweeps, sw), - eigen_perturbation=drho, - ortho, - normalize=true, - which_decomp, - svd_alg, - ) + if !isnothing(write_when_maxdim_exceeds) + if (maxlinkdim(psi) > write_when_maxdim_exceeds) || + (maxdim(sweeps, 1) > write_when_maxdim_exceeds) + PH = disk(PH; path = write_path) end - maxtruncerr = max(maxtruncerr, spec.truncerr) - - @debug_check begin - checkflux(psi) - checkflux(PH) + end + PH = position!(PH, psi, 1) + energy = 0.0 + + for sw in 1:nsweep(sweeps) + sw_time = @elapsed begin + maxtruncerr = 0.0 + + if !isnothing(write_when_maxdim_exceeds) && + maxdim(sweeps, sw) > write_when_maxdim_exceeds + if outputlevel >= 2 + println( + "\nWriting environment tensors do disk (write_when_maxdim_exceeds = $write_when_maxdim_exceeds and maxdim(sweeps, sw) = $(maxdim(sweeps, sw))).\nFiles located at path=$write_path\n", + ) + end + PH = disk(PH; path = write_path) + end + + for (b, ha) in sweepnext(N) + @debug_check begin + checkflux(psi) + checkflux(PH) + end + + @timeit_debug timer "dmrg: position!" begin + PH = position!(PH, psi, b) + end + + @debug_check begin + checkflux(psi) + checkflux(PH) + end + + @timeit_debug timer "dmrg: psi[b]*psi[b+1]" begin + phi = psi[b] * psi[b + 1] + end + + @timeit_debug timer "dmrg: eigsolve" begin + vals, vecs = eigsolve( + PH, + phi, + 1, + eigsolve_which_eigenvalue; + ishermitian, + tol = eigsolve_tol, + krylovdim = eigsolve_krylovdim, + maxiter = eigsolve_maxiter, + verbosity = eigsolve_verbosity, + ) + end + + energy = vals[1] + ## Right now there is a conversion problem in CUDA.jl where `UnifiedMemory` Arrays are being converted + ## into `DeviceMemory`. This conversion line is here temporarily to fix that problem when it arises + ## Adapt is only called when using CUDA backend. CPU will work as implemented previously. + ## TODO this might be the only place we really need iscu if its not fixed. + phi = if NDTensors.iscu(phi) && NDTensors.iscu(vecs[1]) + adapt(ITensors.set_eltype(unwrap_array_type(phi), eltype(vecs[1])), vecs[1]) + else + vecs[1] + end + + ortho = ha == 1 ? "left" : "right" + + drho = nothing + if noise(sweeps, sw) > 0 + @timeit_debug timer "dmrg: noiseterm" begin + # Use noise term when determining new MPS basis. + # This is used to preserve the element type of the MPS. + elt = real(scalartype(psi)) + drho = elt(noise(sweeps, sw)) * noiseterm(PH, phi, ortho) + end + end + + @debug_check begin + checkflux(phi) + end + + @timeit_debug timer "dmrg: replacebond!" begin + spec = replacebond!( + PH, + psi, + b, + phi; + maxdim = maxdim(sweeps, sw), + mindim = mindim(sweeps, sw), + cutoff = cutoff(sweeps, sw), + eigen_perturbation = drho, + ortho, + normalize = true, + which_decomp, + svd_alg, + ) + end + maxtruncerr = max(maxtruncerr, spec.truncerr) + + @debug_check begin + checkflux(psi) + checkflux(PH) + end + + if outputlevel >= 2 + @printf("Sweep %d, half %d, bond (%d,%d) energy=%s\n", sw, ha, b, b + 1, energy) + @printf( + " Truncated using cutoff=%.1E maxdim=%d mindim=%d\n", + cutoff(sweeps, sw), + maxdim(sweeps, sw), + mindim(sweeps, sw) + ) + @printf( + " Trunc. err=%.2E, bond dimension %d\n", spec.truncerr, dim(linkind(psi, b)) + ) + flush(stdout) + end + + sweep_is_done = (b == 1 && ha == 2) + measure!( + observer; + energy, + psi, + projected_operator = PH, + bond = b, + sweep = sw, + half_sweep = ha, + spec, + outputlevel, + sweep_is_done, + ) + end end - - if outputlevel >= 2 - @printf("Sweep %d, half %d, bond (%d,%d) energy=%s\n", sw, ha, b, b + 1, energy) - @printf( - " Truncated using cutoff=%.1E maxdim=%d mindim=%d\n", - cutoff(sweeps, sw), - maxdim(sweeps, sw), - mindim(sweeps, sw) - ) - @printf( - " Trunc. err=%.2E, bond dimension %d\n", spec.truncerr, dim(linkind(psi, b)) - ) - flush(stdout) + if outputlevel >= 1 + @printf( + "After sweep %d energy=%s maxlinkdim=%d maxerr=%.2E time=%.3f\n", + sw, + energy, + maxlinkdim(psi), + maxtruncerr, + sw_time + ) + flush(stdout) end - - sweep_is_done = (b == 1 && ha == 2) - measure!( - observer; - energy, - psi, - projected_operator=PH, - bond=b, - sweep=sw, - half_sweep=ha, - spec, - outputlevel, - sweep_is_done, - ) - end + isdone = checkdone!(observer; energy, psi, sweep = sw, outputlevel) + isdone && break end - if outputlevel >= 1 - @printf( - "After sweep %d energy=%s maxlinkdim=%d maxerr=%.2E time=%.3f\n", - sw, - energy, - maxlinkdim(psi), - maxtruncerr, - sw_time - ) - flush(stdout) - end - isdone = checkdone!(observer; energy, psi, sweep=sw, outputlevel) - isdone && break - end - return (energy, psi) + return (energy, psi) end function _dmrg_sweeps(; - nsweeps, - maxdim=default_maxdim(), - mindim=default_mindim(), - cutoff=default_cutoff(Float64), - noise=default_noise(), -) - sweeps = Sweeps(nsweeps) - setmaxdim!(sweeps, maxdim...) - setmindim!(sweeps, mindim...) - setcutoff!(sweeps, cutoff...) - setnoise!(sweeps, noise...) - return sweeps + nsweeps, + maxdim = default_maxdim(), + mindim = default_mindim(), + cutoff = default_cutoff(Float64), + noise = default_noise(), + ) + sweeps = Sweeps(nsweeps) + setmaxdim!(sweeps, maxdim...) + setmindim!(sweeps, mindim...) + setcutoff!(sweeps, cutoff...) + setnoise!(sweeps, noise...) + return sweeps end function dmrg( - x1, - x2, - psi0::MPS; - nsweeps, - maxdim=default_maxdim(), - mindim=default_mindim(), - cutoff=default_cutoff(Float64), - noise=default_noise(), - kwargs..., -) - return dmrg( - x1, x2, psi0, _dmrg_sweeps(; nsweeps, maxdim, mindim, cutoff, noise); kwargs... - ) + x1, + x2, + psi0::MPS; + nsweeps, + maxdim = default_maxdim(), + mindim = default_mindim(), + cutoff = default_cutoff(Float64), + noise = default_noise(), + kwargs..., + ) + return dmrg( + x1, x2, psi0, _dmrg_sweeps(; nsweeps, maxdim, mindim, cutoff, noise); kwargs... + ) end function dmrg( - x1, - psi0::MPS; - nsweeps, - maxdim=default_maxdim(), - mindim=default_mindim(), - cutoff=default_cutoff(Float64), - noise=default_noise(), - kwargs..., -) - return dmrg(x1, psi0, _dmrg_sweeps(; nsweeps, maxdim, mindim, cutoff, noise); kwargs...) + x1, + psi0::MPS; + nsweeps, + maxdim = default_maxdim(), + mindim = default_mindim(), + cutoff = default_cutoff(Float64), + noise = default_noise(), + kwargs..., + ) + return dmrg(x1, psi0, _dmrg_sweeps(; nsweeps, maxdim, mindim, cutoff, noise); kwargs...) end diff --git a/src/exports.jl b/src/exports.jl index 749a73c..6694f07 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -1,174 +1,174 @@ using LinearAlgebra: ⋅ export - # Exports that were removed from ITensors.jl - # when ITensors.ITensorMPS was moved to ITensorMPS.jl. - @OpName_str, - @SiteType_str, - @StateName_str, - @TagType_str, - @ValName_str, - Apply, - Op, - OpName, - Ops, - Prod, - Scaled, - SiteType, - Spectrum, - StateName, - Sum, - TagType, - Trotter, - ValName, - apply, - argsdict, - coefficient, - contract, - convert_leaf_eltype, - eigs, - entropy, - has_fermion_string, - hassameinds, - linkindex, - ops, - replaceprime, - siteindex, - splitblocks, - tr, - truncerror, - val, + # Exports that were removed from ITensors.jl + # when ITensors.ITensorMPS was moved to ITensorMPS.jl. + @OpName_str, + @SiteType_str, + @StateName_str, + @TagType_str, + @ValName_str, + Apply, + Op, + OpName, + Ops, + Prod, + Scaled, + SiteType, + Spectrum, + StateName, + Sum, + TagType, + Trotter, + ValName, + apply, + argsdict, + coefficient, + contract, + convert_leaf_eltype, + eigs, + entropy, + has_fermion_string, + hassameinds, + linkindex, + ops, + replaceprime, + siteindex, + splitblocks, + tr, + truncerror, + val, - # lattices.jl - Lattice, - LatticeBond, - square_lattice, - triangular_lattice, + # lattices.jl + Lattice, + LatticeBond, + square_lattice, + triangular_lattice, - # solvers - TimeDependentSum, - dmrg_x, - expand, - linsolve, - tdvp, - to_vec, + # solvers + TimeDependentSum, + dmrg_x, + expand, + linsolve, + tdvp, + to_vec, - # dmrg.jl - dmrg, - # abstractmps.jl - # Macros - @preserve_ortho, - # Methods - AbstractMPS, - add, - common_siteind, - common_siteinds, - findfirstsiteind, - findfirstsiteinds, - findsite, - findsites, - firstsiteind, - firstsiteinds, - logdot, - loginner, - lognorm, - movesite, - movesites, - ortho_lims, - orthocenter, - promote_itensor_eltype, - reset_ortho_lims!, - set_ortho_lims!, - siteinds, - sim!, - # autompo/ - AutoMPO, - OpSum, - add!, - # mpo.jl - # Types - MPO, - # Methods - error_contract, - maxlinkdim, - orthogonalize, - orthogonalize!, - outer, - projector, - random_mpo, - truncate, - truncate!, - unique_siteind, - unique_siteinds, - # mps.jl - # Types - MPS, - # Methods - ⋅, - dot, - correlation_matrix, - expect, - inner, - isortho, - linkdim, - linkdims, - linkind, - linkinds, - op, - productMPS, - random_mps, - replacebond, - replacebond!, - sample, - sample!, - siteind, - siteinds, - state, - replace_siteinds!, - replace_siteinds, - swapbondsites, - totalqn, - # observer.jl - # Types - AbstractObserver, - DMRGObserver, - DMRGMeasurement, - NoObserver, - # Methods - checkdone!, - energies, - measure!, - measurements, - truncerrors, - # projmpo.jl - disk, - ProjMPO, - lproj, - product, - rproj, - noiseterm, - nsite, - position!, - # projmposum.jl - ProjMPOSum, - # projmpo_mps.jl - ProjMPO_MPS, - # sweeps.jl - Sweeps, - cutoff, - cutoff!, - get_cutoffs, - get_maxdims, - get_mindims, - get_noises, - maxdim, - maxdim!, - mindim, - mindim!, - noise, - noise!, - nsweep, - setmaxdim!, - setmindim!, - setcutoff!, - setnoise!, - sweepnext + # dmrg.jl + dmrg, + # abstractmps.jl + # Macros + @preserve_ortho, + # Methods + AbstractMPS, + add, + common_siteind, + common_siteinds, + findfirstsiteind, + findfirstsiteinds, + findsite, + findsites, + firstsiteind, + firstsiteinds, + logdot, + loginner, + lognorm, + movesite, + movesites, + ortho_lims, + orthocenter, + promote_itensor_eltype, + reset_ortho_lims!, + set_ortho_lims!, + siteinds, + sim!, + # autompo/ + AutoMPO, + OpSum, + add!, + # mpo.jl + # Types + MPO, + # Methods + error_contract, + maxlinkdim, + orthogonalize, + orthogonalize!, + outer, + projector, + random_mpo, + truncate, + truncate!, + unique_siteind, + unique_siteinds, + # mps.jl + # Types + MPS, + # Methods + ⋅, + dot, + correlation_matrix, + expect, + inner, + isortho, + linkdim, + linkdims, + linkind, + linkinds, + op, + productMPS, + random_mps, + replacebond, + replacebond!, + sample, + sample!, + siteind, + siteinds, + state, + replace_siteinds!, + replace_siteinds, + swapbondsites, + totalqn, + # observer.jl + # Types + AbstractObserver, + DMRGObserver, + DMRGMeasurement, + NoObserver, + # Methods + checkdone!, + energies, + measure!, + measurements, + truncerrors, + # projmpo.jl + disk, + ProjMPO, + lproj, + product, + rproj, + noiseterm, + nsite, + position!, + # projmposum.jl + ProjMPOSum, + # projmpo_mps.jl + ProjMPO_MPS, + # sweeps.jl + Sweeps, + cutoff, + cutoff!, + get_cutoffs, + get_maxdims, + get_mindims, + get_noises, + maxdim, + maxdim!, + mindim, + mindim!, + noise, + noise!, + nsweep, + setmaxdim!, + setmindim!, + setcutoff!, + setnoise!, + sweepnext diff --git a/src/imports.jl b/src/imports.jl index d3a4b93..216c5cb 100644 --- a/src/imports.jl +++ b/src/imports.jl @@ -2,172 +2,172 @@ # module from submodules or from `ITensors` so they can # be reexported. using ITensors.SiteTypes: - @OpName_str, - @SiteType_str, - @StateName_str, - @TagType_str, - @ValName_str, - OpName, - SiteType, - StateName, - TagType, - ValName, - ops + @OpName_str, + @SiteType_str, + @StateName_str, + @TagType_str, + @ValName_str, + OpName, + SiteType, + StateName, + TagType, + ValName, + ops using ITensors.Ops: Trotter import Base: - # types - Array, - CartesianIndices, - Vector, - NTuple, - Tuple, - # symbols - +, - -, - *, - ^, - /, - ==, - <, - >, - !, - # functions - adjoint, - allunique, - axes, - complex, - conj, - convert, - copy, - copyto!, - deepcopy, - deleteat!, - eachindex, - eltype, - fill!, - filter, - filter!, - findall, - findfirst, - getindex, - hash, - imag, - intersect, - intersect!, - isapprox, - isassigned, - isempty, - isless, - isreal, - iszero, - iterate, - keys, - lastindex, - length, - map, - map!, - ndims, - print, - promote_rule, - push!, - real, - resize!, - setdiff, - setdiff!, - setindex!, - show, - similar, - size, - summary, - truncate, - zero, - # macros - @propagate_inbounds + # types + Array, + CartesianIndices, + Vector, + NTuple, + Tuple, + # symbols + +, + -, + *, + ^, + /, + ==, + <, + >, + !, + # functions + adjoint, + allunique, + axes, + complex, + conj, + convert, + copy, + copyto!, + deepcopy, + deleteat!, + eachindex, + eltype, + fill!, + filter, + filter!, + findall, + findfirst, + getindex, + hash, + imag, + intersect, + intersect!, + isapprox, + isassigned, + isempty, + isless, + isreal, + iszero, + iterate, + keys, + lastindex, + length, + map, + map!, + ndims, + print, + promote_rule, + push!, + real, + resize!, + setdiff, + setdiff!, + setindex!, + show, + similar, + size, + summary, + truncate, + zero, + # macros + @propagate_inbounds import Base.Broadcast: - # types - AbstractArrayStyle, - Broadcasted, - BroadcastStyle, - DefaultArrayStyle, - Style, - # functions - _broadcast_getindex, - broadcasted, - broadcastable, - instantiate + # types + AbstractArrayStyle, + Broadcasted, + BroadcastStyle, + DefaultArrayStyle, + Style, + # functions + _broadcast_getindex, + broadcasted, + broadcastable, + instantiate import ..ITensors.NDTensors: - Algorithm, - @Algorithm_str, - EmptyNumber, - _Tuple, - _NTuple, - blas_get_num_threads, - datatype, - dense, - diagind, - disable_auto_fermion, - double_precision, - eachblock, - eachdiagblock, - enable_auto_fermion, - fill!!, - randn!!, - permutedims, - permutedims! + Algorithm, + @Algorithm_str, + EmptyNumber, + _Tuple, + _NTuple, + blas_get_num_threads, + datatype, + dense, + diagind, + disable_auto_fermion, + double_precision, + eachblock, + eachdiagblock, + enable_auto_fermion, + fill!!, + randn!!, + permutedims, + permutedims! import ..ITensors: - AbstractRNG, - Apply, - apply, - argument, - Broadcasted, - @Algorithm_str, - checkflux, - convert_leaf_eltype, - commontags, - @debug_check, - dag, - data, - DefaultArrayStyle, - DiskVector, - flux, - hascommoninds, - hasqns, - hassameinds, - inner, - isfermionic, - maxdim, - mindim, - noprime, - noprime!, - norm, - normalize, - outer, - OneITensor, - permute, - prime, - prime!, - product, - QNIndex, - replaceinds, - replaceprime, - replacetags, - setprime, - sim, - site, - splitblocks, - store, - Style, - sum, - swapprime, - symmetrystyle, - terms, - @timeit_debug, - truncate!, - which_op + AbstractRNG, + Apply, + apply, + argument, + Broadcasted, + @Algorithm_str, + checkflux, + convert_leaf_eltype, + commontags, + @debug_check, + dag, + data, + DefaultArrayStyle, + DiskVector, + flux, + hascommoninds, + hasqns, + hassameinds, + inner, + isfermionic, + maxdim, + mindim, + noprime, + noprime!, + norm, + normalize, + outer, + OneITensor, + permute, + prime, + prime!, + product, + QNIndex, + replaceinds, + replaceprime, + replacetags, + setprime, + sim, + site, + splitblocks, + store, + Style, + sum, + swapprime, + symmetrystyle, + terms, + @timeit_debug, + truncate!, + which_op import ..ITensors.Ops: params diff --git a/src/lattices/lattices.jl b/src/lattices/lattices.jl index 8e1c973..d8e1db0 100644 --- a/src/lattices/lattices.jl +++ b/src/lattices/lattices.jl @@ -1,4 +1,3 @@ - """ A LatticeBond is a struct which represents a single bond in a geometrical lattice or @@ -16,13 +15,13 @@ LatticeBond has the following data fields: - type::String -- optional description of bond type """ struct LatticeBond - s1::Int - s2::Int - x1::Float64 - y1::Float64 - x2::Float64 - y2::Float64 - type::String + s1::Int + s2::Int + x1::Float64 + y1::Float64 + x2::Float64 + y2::Float64 + type::String end """ @@ -40,14 +39,14 @@ the (x,y) coordinates of the two sites and an optional type string. """ function LatticeBond(s1::Int, s2::Int) - return LatticeBond(s1, s2, 0.0, 0.0, 0.0, 0.0, "") + return LatticeBond(s1, s2, 0.0, 0.0, 0.0, 0.0, "") end function LatticeBond( - s1::Int, s2::Int, x1::Real, y1::Real, x2::Real, y2::Real, bondtype::String="" -) - cf(x) = convert(Float64, x) - return LatticeBond(s1, s2, cf(x1), cf(y1), cf(x2), cf(y2), bondtype) + s1::Int, s2::Int, x1::Real, y1::Real, x2::Real, y2::Real, bondtype::String = "" + ) + cf(x) = convert(Float64, x) + return LatticeBond(s1, s2, cf(x1), cf(y1), cf(x2), cf(y2), bondtype) end """ @@ -68,28 +67,28 @@ but can be made periodic in the y direction by specifying the keyword argument `yperiodic=true`. """ -function square_lattice(Nx::Int, Ny::Int; yperiodic=false)::Lattice - yperiodic = yperiodic && (Ny > 2) - N = Nx * Ny - Nbond = 2N - Ny + (yperiodic ? 0 : -Nx) - latt = Lattice(undef, Nbond) - b = 0 - for n in 1:N - x = div(n - 1, Ny) + 1 - y = mod(n - 1, Ny) + 1 - if x < Nx - latt[b += 1] = LatticeBond(n, n + Ny, x, y, x + 1, y) - end - if Ny > 1 - if y < Ny - latt[b += 1] = LatticeBond(n, n + 1, x, y, x, y + 1) - end - if yperiodic && y == 1 - latt[b += 1] = LatticeBond(n, n + Ny - 1, x, y, x, y + Ny - 1) - end +function square_lattice(Nx::Int, Ny::Int; yperiodic = false)::Lattice + yperiodic = yperiodic && (Ny > 2) + N = Nx * Ny + Nbond = 2N - Ny + (yperiodic ? 0 : -Nx) + latt = Lattice(undef, Nbond) + b = 0 + for n in 1:N + x = div(n - 1, Ny) + 1 + y = mod(n - 1, Ny) + 1 + if x < Nx + latt[b += 1] = LatticeBond(n, n + Ny, x, y, x + 1, y) + end + if Ny > 1 + if y < Ny + latt[b += 1] = LatticeBond(n, n + 1, x, y, x, y + 1) + end + if yperiodic && y == 1 + latt[b += 1] = LatticeBond(n, n + Ny - 1, x, y, x, y + Ny - 1) + end + end end - end - return latt + return latt end """ @@ -105,36 +104,36 @@ but can be made periodic in the y direction by specifying the keyword argument `yperiodic=true`. """ -function triangular_lattice(Nx::Int, Ny::Int; yperiodic=false)::Lattice - yperiodic = yperiodic && (Ny > 2) - N = Nx * Ny - Nbond = 3N - 2Ny + (yperiodic ? 0 : -2Nx + 1) - latt = Lattice(undef, Nbond) - b = 0 - for n in 1:N - x = div(n - 1, Ny) + 1 - y = mod(n - 1, Ny) + 1 +function triangular_lattice(Nx::Int, Ny::Int; yperiodic = false)::Lattice + yperiodic = yperiodic && (Ny > 2) + N = Nx * Ny + Nbond = 3N - 2Ny + (yperiodic ? 0 : -2Nx + 1) + latt = Lattice(undef, Nbond) + b = 0 + for n in 1:N + x = div(n - 1, Ny) + 1 + y = mod(n - 1, Ny) + 1 - # x-direction bonds - if x < Nx - latt[b += 1] = LatticeBond(n, n + Ny) - end + # x-direction bonds + if x < Nx + latt[b += 1] = LatticeBond(n, n + Ny) + end - # 2d bonds - if Ny > 1 - # vertical / y-periodic diagonal bond - if (n + 1 <= N) && ((y < Ny) || yperiodic) - latt[b += 1] = LatticeBond(n, n + 1) - end - # periodic vertical bond - if yperiodic && y == 1 - latt[b += 1] = LatticeBond(n, n + Ny - 1) - end - # diagonal bonds - if x < Nx && y < Ny - latt[b += 1] = LatticeBond(n, n + Ny + 1) - end + # 2d bonds + if Ny > 1 + # vertical / y-periodic diagonal bond + if (n + 1 <= N) && ((y < Ny) || yperiodic) + latt[b += 1] = LatticeBond(n, n + 1) + end + # periodic vertical bond + if yperiodic && y == 1 + latt[b += 1] = LatticeBond(n, n + Ny - 1) + end + # diagonal bonds + if x < Nx && y < Ny + latt[b += 1] = LatticeBond(n, n + Ny + 1) + end + end end - end - return latt + return latt end diff --git a/src/lib/Experimental/src/dmrg.jl b/src/lib/Experimental/src/dmrg.jl index 6739c13..491dbb7 100644 --- a/src/lib/Experimental/src/dmrg.jl +++ b/src/lib/Experimental/src/dmrg.jl @@ -1,17 +1,17 @@ using ..ITensorMPS: - MPS, - alternating_update, - compose_observers, - default_observer, - eigsolve_updater, - values_observer + MPS, + alternating_update, + compose_observers, + default_observer, + eigsolve_updater, + values_observer function dmrg( - operator, init::MPS; updater=eigsolve_updater, (observer!)=default_observer(), kwargs... -) - info_ref! = Ref{Any}() - info_observer! = values_observer(; info=(info_ref!)) - observer! = compose_observers(observer!, info_observer!) - state = alternating_update(operator, init; updater, observer!, kwargs...) - return info_ref![].eigval, state + operator, init::MPS; updater = eigsolve_updater, (observer!) = default_observer(), kwargs... + ) + info_ref! = Ref{Any}() + info_observer! = values_observer(; info = (info_ref!)) + observer! = compose_observers(observer!, info_observer!) + state = alternating_update(operator, init; updater, observer!, kwargs...) + return info_ref![].eigval, state end diff --git a/src/mpo.jl b/src/mpo.jl index 9acd561..4025b32 100644 --- a/src/mpo.jl +++ b/src/mpo.jl @@ -11,13 +11,13 @@ A finite size matrix product operator type. Keeps track of the orthogonality center. """ mutable struct MPO <: AbstractMPS - data::Vector{ITensor} - llim::Int - rlim::Int + data::Vector{ITensor} + llim::Int + rlim::Int end -function MPO(A::Vector{<:ITensor}; ortho_lims::UnitRange=1:length(A)) - return MPO(A, first(ortho_lims) - 1, last(ortho_lims) + 1) +function MPO(A::Vector{<:ITensor}; ortho_lims::UnitRange = 1:length(A)) + return MPO(A, first(ortho_lims) - 1, last(ortho_lims) + 1) end set_data(A::MPO, data::Vector{ITensor}) = MPO(data, A.llim, A.rlim) @@ -25,35 +25,35 @@ set_data(A::MPO, data::Vector{ITensor}) = MPO(data, A.llim, A.rlim) MPO() = MPO(ITensor[], 0, 0) function convert(::Type{MPS}, M::MPO) - return MPS(data(M); ortho_lims=ortho_lims(M)) + return MPS(data(M); ortho_lims = ortho_lims(M)) end function convert(::Type{MPO}, M::MPS) - return MPO(data(M); ortho_lims=ortho_lims(M)) + return MPO(data(M); ortho_lims = ortho_lims(M)) end -function MPO(::Type{ElT}, sites::Vector{<:Index}) where {ElT<:Number} - N = length(sites) - v = Vector{ITensor}(undef, N) - if N == 0 - return MPO() - elseif N == 1 - v[1] = ITensor(ElT, dag(sites[1]), sites[1]') - return MPO(v) - end - space_ii = all(hasqns, sites) ? [QN() => 1] : 1 - l = [Index(space_ii, "Link,l=$ii") for ii in 1:(N - 1)] - for ii in eachindex(sites) - s = sites[ii] - if ii == 1 - v[ii] = ITensor(ElT, dag(s), s', l[ii]) - elseif ii == N - v[ii] = ITensor(ElT, dag(l[ii - 1]), dag(s), s') - else - v[ii] = ITensor(ElT, dag(l[ii - 1]), dag(s), s', l[ii]) +function MPO(::Type{ElT}, sites::Vector{<:Index}) where {ElT <: Number} + N = length(sites) + v = Vector{ITensor}(undef, N) + if N == 0 + return MPO() + elseif N == 1 + v[1] = ITensor(ElT, dag(sites[1]), sites[1]') + return MPO(v) end - end - return MPO(v) + space_ii = all(hasqns, sites) ? [QN() => 1] : 1 + l = [Index(space_ii, "Link,l=$ii") for ii in 1:(N - 1)] + for ii in eachindex(sites) + s = sites[ii] + if ii == 1 + v[ii] = ITensor(ElT, dag(s), s', l[ii]) + elseif ii == N + v[ii] = ITensor(ElT, dag(l[ii - 1]), dag(s), s') + else + v[ii] = ITensor(ElT, dag(l[ii - 1]), dag(s), s', l[ii]) + end + end + return MPO(v) end MPO(sites::Vector{<:Index}) = MPO(Float64, sites) @@ -71,36 +71,36 @@ MPO(N::Int) = MPO(Vector{ITensor}(undef, N)) Make an MPO with pairs of sites `s[i]` and `s[i]'` and operators `ops` on each site. """ -function MPO(::Type{ElT}, sites::Vector{<:Index}, ops::Vector) where {ElT<:Number} - N = length(sites) - os = Prod{Op}() - for n in 1:N - os *= Op(ops[n], n) - end - M = MPO(ElT, os, sites) - - # Currently, OpSum does not output the optimally truncated - # MPO (see https://github.com/ITensor/ITensors.jl/issues/526) - # So here, we need to first normalize, then truncate, then - # return the normalization. - lognormM = lognorm(M) - M ./= exp(lognormM / N) - truncate!(M; cutoff=1e-15) - M .*= exp(lognormM / N) - return M +function MPO(::Type{ElT}, sites::Vector{<:Index}, ops::Vector) where {ElT <: Number} + N = length(sites) + os = Prod{Op}() + for n in 1:N + os *= Op(ops[n], n) + end + M = MPO(ElT, os, sites) + + # Currently, OpSum does not output the optimally truncated + # MPO (see https://github.com/ITensor/ITensors.jl/issues/526) + # So here, we need to first normalize, then truncate, then + # return the normalization. + lognormM = lognorm(M) + M ./= exp(lognormM / N) + truncate!(M; cutoff = 1.0e-15) + M .*= exp(lognormM / N) + return M end -function MPO(::Type{ElT}, sites::Vector{<:Index}, fops::Function) where {ElT<:Number} - ops = [fops(n) for n in 1:length(sites)] - return MPO(ElT, sites, ops) +function MPO(::Type{ElT}, sites::Vector{<:Index}, fops::Function) where {ElT <: Number} + ops = [fops(n) for n in 1:length(sites)] + return MPO(ElT, sites, ops) end MPO(sites::Vector{<:Index}, ops) = MPO(Float64, sites, ops) function MPO(sites::Vector{<:Index}, os::OpSum) - return error( - "To construct an MPO from an OpSum `opsum` and a set of indices `sites`, you must use MPO(opsum, sites)", - ) + return error( + "To construct an MPO from an OpSum `opsum` and a set of indices `sites`, you must use MPO(opsum, sites)", + ) end """ @@ -109,49 +109,49 @@ end Make an MPO with pairs of sites `s[i]` and `s[i]'` and operator `op` on every site. """ -function MPO(::Type{ElT}, sites::Vector{<:Index}, op::String) where {ElT<:Number} - return MPO(ElT, sites, fill(op, length(sites))) +function MPO(::Type{ElT}, sites::Vector{<:Index}, op::String) where {ElT <: Number} + return MPO(ElT, sites, fill(op, length(sites))) end MPO(sites::Vector{<:Index}, op::String) = MPO(Float64, sites, op) -function MPO(::Type{ElT}, sites::Vector{<:Index}, op::Matrix{<:Number}) where {ElT<:Number} - # return MPO(ElT, sites, fill(op, length(sites))) - return error( - "Not defined on purpose because of potential ambiguity with `MPO(A::Array, sites::Vector)`. Pass the on-site matrices as functions like `MPO(sites, n -> [1 0; 0 1])` instead.", - ) +function MPO(::Type{ElT}, sites::Vector{<:Index}, op::Matrix{<:Number}) where {ElT <: Number} + # return MPO(ElT, sites, fill(op, length(sites))) + return error( + "Not defined on purpose because of potential ambiguity with `MPO(A::Array, sites::Vector)`. Pass the on-site matrices as functions like `MPO(sites, n -> [1 0; 0 1])` instead.", + ) end -MPO(sites::Vector{<:Index}, op::Matrix{ElT}) where {ElT<:Number} = MPO(ElT, sites, op) +MPO(sites::Vector{<:Index}, op::Matrix{ElT}) where {ElT <: Number} = MPO(ElT, sites, op) -function random_mpo(sites::Vector{<:Index}, m::Int=1) - return random_mpo(Random.default_rng(), sites, m) +function random_mpo(sites::Vector{<:Index}, m::Int = 1) + return random_mpo(Random.default_rng(), sites, m) end -function random_mpo(rng::AbstractRNG, sites::Vector{<:Index}, m::Int=1) - M = MPO(sites, "Id") - for i in eachindex(sites) - randn!(rng, M[i]) - normalize!(M[i]) - end - m > 1 && throw(ArgumentError("random_mpo: currently only m==1 supported")) - return M +function random_mpo(rng::AbstractRNG, sites::Vector{<:Index}, m::Int = 1) + M = MPO(sites, "Id") + for i in eachindex(sites) + randn!(rng, M[i]) + normalize!(M[i]) + end + m > 1 && throw(ArgumentError("random_mpo: currently only m==1 supported")) + return M end function MPO(A::ITensor, sites::Vector{<:Index}; kwargs...) - return MPO(A, IndexSet.(prime.(sites), dag.(sites)); kwargs...) + return MPO(A, IndexSet.(prime.(sites), dag.(sites)); kwargs...) end function outer_mps_mps_deprecation_warning() - return "Calling `outer(ψ::MPS, ϕ::MPS)` for MPS `ψ` and `ϕ` with shared indices is deprecated. Currently, we automatically prime `ψ` to make sure the site indices don't clash, but that will no longer be the case in ITensors v0.4. To upgrade your code, call `outer(ψ', ϕ)`. Although the new interface seems less convenient, it will allow `outer` to accept more general outer products going forward, such as outer products where some indices are shared (a batched outer product) or outer products of MPS between site indices that aren't just related by a single prime level." + return "Calling `outer(ψ::MPS, ϕ::MPS)` for MPS `ψ` and `ϕ` with shared indices is deprecated. Currently, we automatically prime `ψ` to make sure the site indices don't clash, but that will no longer be the case in ITensors v0.4. To upgrade your code, call `outer(ψ', ϕ)`. Although the new interface seems less convenient, it will allow `outer` to accept more general outer products going forward, such as outer products where some indices are shared (a batched outer product) or outer products of MPS between site indices that aren't just related by a single prime level." end function deprecate_make_inds_unmatch(::typeof(outer), ψ::MPS, ϕ::MPS; kw...) - if hassameinds(siteinds, ψ, ϕ) - ITensors.warn_once(outer_mps_mps_deprecation_warning(), :outer_mps_mps) - ψ = ψ' - end - return ψ, ϕ + if hassameinds(siteinds, ψ, ϕ) + ITensors.warn_once(outer_mps_mps_deprecation_warning(), :outer_mps_mps) + ψ = ψ' + end + return ψ, ϕ end """ @@ -201,11 +201,11 @@ the same arguments as `contract(::MPO, ::MPO; kwargs...)`. See also [`apply`](@ref), [`contract`](@ref). """ function outer(ψ::MPS, ϕ::MPS; kw...) - ψ, ϕ = deprecate_make_inds_unmatch(outer, ψ, ϕ; kw...) + ψ, ϕ = deprecate_make_inds_unmatch(outer, ψ, ϕ; kw...) - ψmat = convert(MPO, ψ) - ϕmat = convert(MPO, dag(ϕ)) - return contract(ψmat, ϕmat; kw...) + ψmat = convert(MPO, ψ) + ϕmat = convert(MPO, dag(ϕ)) + return contract(ψmat, ϕmat; kw...) end """ @@ -226,12 +226,12 @@ the same as those accepted by `contract(::MPO, ::MPO; kw...)`. See also [`outer`](@ref), [`contract`](@ref). """ -function projector(ψ::MPS; normalize::Bool=true, kw...) - ψψᴴ = outer(ψ', ψ; kw...) - if normalize - normalize!(ψψᴴ[orthocenter(ψψᴴ)]) - end - return ψψᴴ +function projector(ψ::MPS; normalize::Bool = true, kw...) + ψψᴴ = outer(ψ', ψ; kw...) + if normalize + normalize!(ψψᴴ[orthocenter(ψψᴴ)]) + end + return ψψᴴ end # XXX: rename originalsiteind? @@ -241,7 +241,7 @@ end Get the first site Index of the MPO found, by default with prime level 0. """ -SiteTypes.siteind(M::MPO, j::Int; kwargs...) = siteind(first, M, j; plev=0, kwargs...) +SiteTypes.siteind(M::MPO, j::Int; kwargs...) = siteind(first, M, j; plev = 0, kwargs...) # TODO: make this return the site indices that would have # been used to create the MPO? I.e.: @@ -253,19 +253,19 @@ Get a Vector of IndexSets of all the site indices of M. """ SiteTypes.siteinds(M::MPO; kwargs...) = siteinds(all, M; kwargs...) -function SiteTypes.siteinds(Mψ::Tuple{MPO,MPS}, n::Int; kwargs...) - return siteinds(uniqueinds, Mψ[1], Mψ[2], n; kwargs...) +function SiteTypes.siteinds(Mψ::Tuple{MPO, MPS}, n::Int; kwargs...) + return siteinds(uniqueinds, Mψ[1], Mψ[2], n; kwargs...) end -function nsites(Mψ::Tuple{MPO,MPS}) - M, ψ = Mψ - N = length(M) - @assert N == length(ψ) - return N +function nsites(Mψ::Tuple{MPO, MPS}) + M, ψ = Mψ + N = length(M) + @assert N == length(ψ) + return N end -function SiteTypes.siteinds(Mψ::Tuple{MPO,MPS}; kwargs...) - return [siteinds(Mψ, n; kwargs...) for n in 1:nsites(Mψ)] +function SiteTypes.siteinds(Mψ::Tuple{MPO, MPS}; kwargs...) + return [siteinds(Mψ, n; kwargs...) for n in 1:nsites(Mψ)] end # XXX: rename originalsiteinds? @@ -276,128 +276,128 @@ Get a Vector of the first site Index found on each site of M. By default, it finds the first site Index with prime level 0. """ -firstsiteinds(M::MPO; kwargs...) = siteinds(first, M; plev=0, kwargs...) - -function hassameinds(::typeof(siteinds), ψ::MPS, Hϕ::Tuple{MPO,MPS}) - N = length(ψ) - @assert N == length(Hϕ[1]) == length(Hϕ[1]) - for n in 1:N - !hassameinds(siteinds(Hϕ, n), siteinds(ψ, n)) && return false - end - return true +firstsiteinds(M::MPO; kwargs...) = siteinds(first, M; plev = 0, kwargs...) + +function hassameinds(::typeof(siteinds), ψ::MPS, Hϕ::Tuple{MPO, MPS}) + N = length(ψ) + @assert N == length(Hϕ[1]) == length(Hϕ[1]) + for n in 1:N + !hassameinds(siteinds(Hϕ, n), siteinds(ψ, n)) && return false + end + return true end function inner_mps_mpo_mps_deprecation_warning() - return """ - Calling `inner(x::MPS, A::MPO, y::MPS)` where the site indices of the `MPS` - `x` and the `MPS` resulting from contracting `MPO` `A` with `MPS` `y` don't - match is deprecated as of ITensors v0.3 and will result in an error in ITensors - v0.4. The most common cause of this is something like the following: + return """ + Calling `inner(x::MPS, A::MPO, y::MPS)` where the site indices of the `MPS` + `x` and the `MPS` resulting from contracting `MPO` `A` with `MPS` `y` don't + match is deprecated as of ITensors v0.3 and will result in an error in ITensors + v0.4. The most common cause of this is something like the following: - ```julia - s = siteinds("S=1/2") - psi = random_mps(s) - H = MPO(s, "Id") - inner(psi, H, psi) - ``` + ```julia + s = siteinds("S=1/2") + psi = random_mps(s) + H = MPO(s, "Id") + inner(psi, H, psi) + ``` - `psi` has the Index structure `-s-(psi)` and `H` has the Index structure - `-s'-(H)-s-`, so the Index structure of would be `(dag(psi)-s- -s'-(H)-s-(psi)` - unless the prime levels were fixed. Previously we tried fixing the prime level - in situations like this, but we will no longer be doing that going forward. + `psi` has the Index structure `-s-(psi)` and `H` has the Index structure + `-s'-(H)-s-`, so the Index structure of would be `(dag(psi)-s- -s'-(H)-s-(psi)` + unless the prime levels were fixed. Previously we tried fixing the prime level + in situations like this, but we will no longer be doing that going forward. - There are a few ways to fix this. You can simply change: + There are a few ways to fix this. You can simply change: - ```julia - inner(psi, H, psi) - ``` + ```julia + inner(psi, H, psi) + ``` - to: + to: - ```julia - inner(psi', H, psi) - ``` + ```julia + inner(psi', H, psi) + ``` - in which case the Index structure will be `(dag(psi)-s'-(H)-s-(psi)`. + in which case the Index structure will be `(dag(psi)-s'-(H)-s-(psi)`. - Alternatively, you can use the `Apply` function: + Alternatively, you can use the `Apply` function: - ```julia + ```julia - inner(psi, Apply(H, psi)) - ``` + inner(psi, Apply(H, psi)) + ``` - In this case, `Apply(H, psi)` represents the "lazy" evaluation of - `apply(H, psi)`. The function `apply(H, psi)` performs the contraction of - `H` with `psi` and then unprimes the results, so this versions ensures that - the prime levels of the inner product will match. + In this case, `Apply(H, psi)` represents the "lazy" evaluation of + `apply(H, psi)`. The function `apply(H, psi)` performs the contraction of + `H` with `psi` and then unprimes the results, so this versions ensures that + the prime levels of the inner product will match. - Although the new behavior seems less convenient, it makes it easier to - generalize `inner(::MPS, ::MPO, ::MPS)` to other types of inputs, like `MPS` - and `MPO` with different tag and prime conventions, multiple sites per tensor, - `ITensor` inputs, etc. - """ + Although the new behavior seems less convenient, it makes it easier to + generalize `inner(::MPS, ::MPO, ::MPS)` to other types of inputs, like `MPS` + and `MPO` with different tag and prime conventions, multiple sites per tensor, + `ITensor` inputs, etc. + """ end function deprecate_make_inds_match!( - ::typeof(dot), ydag::MPS, A::MPO, x::MPS; make_inds_match::Bool=true -) - N = length(x) - if !hassameinds(siteinds, ydag, (A, x)) - sAx = siteinds((A, x)) - if any(s -> length(s) > 1, sAx) - n = findfirst(n -> !hassameinds(siteinds(ydag, n), siteinds((A, x), n)), 1:N) - error( - """Calling `dot(ϕ::MPS, H::MPO, ψ::MPS)` with multiple site indices per MPO/MPS tensor but the site indices don't match. Even with `make_inds_match = true`, the case of multiple site indices per MPO/MPS is not handled automatically. The sites with unmatched site indices are: - - inds(ϕ[$n]) = $(inds(ydag[n])) - - inds(H[$n]) = $(inds(A[n])) - - inds(ψ[$n]) = $(inds(x[n])) - - Make sure the site indices of your MPO/MPS match. You may need to prime one of the MPS, such as `dot(ϕ', H, ψ)`.""", - ) - end - if !hassameinds(siteinds, ydag, (A, x)) && make_inds_match - ITensors.warn_once(inner_mps_mpo_mps_deprecation_warning(), :inner_mps_mpo_mps) - replace_siteinds!(ydag, sAx) + ::typeof(dot), ydag::MPS, A::MPO, x::MPS; make_inds_match::Bool = true + ) + N = length(x) + if !hassameinds(siteinds, ydag, (A, x)) + sAx = siteinds((A, x)) + if any(s -> length(s) > 1, sAx) + n = findfirst(n -> !hassameinds(siteinds(ydag, n), siteinds((A, x), n)), 1:N) + error( + """Calling `dot(ϕ::MPS, H::MPO, ψ::MPS)` with multiple site indices per MPO/MPS tensor but the site indices don't match. Even with `make_inds_match = true`, the case of multiple site indices per MPO/MPS is not handled automatically. The sites with unmatched site indices are: + + inds(ϕ[$n]) = $(inds(ydag[n])) + + inds(H[$n]) = $(inds(A[n])) + + inds(ψ[$n]) = $(inds(x[n])) + + Make sure the site indices of your MPO/MPS match. You may need to prime one of the MPS, such as `dot(ϕ', H, ψ)`.""", + ) + end + if !hassameinds(siteinds, ydag, (A, x)) && make_inds_match + ITensors.warn_once(inner_mps_mpo_mps_deprecation_warning(), :inner_mps_mpo_mps) + replace_siteinds!(ydag, sAx) + end end - end - return ydag, A, x + return ydag, A, x end function _log_or_not_dot( - y::MPS, A::MPO, x::MPS, loginner::Bool; make_inds_match::Bool=true, kwargs... -)::Number - N = length(A) - check_hascommoninds(siteinds, A, x) - ydag = dag(y) - sim!(linkinds, ydag) - ydag, A, x = deprecate_make_inds_match!(dot, ydag, A, x; make_inds_match) - check_hascommoninds(siteinds, A, y) - O = ydag[1] * A[1] * x[1] - if loginner - normO = norm(O) - log_inner_tot = log(normO) - O ./= normO - end - for j in 2:N - O = O * ydag[j] * A[j] * x[j] + y::MPS, A::MPO, x::MPS, loginner::Bool; make_inds_match::Bool = true, kwargs... + )::Number + N = length(A) + check_hascommoninds(siteinds, A, x) + ydag = dag(y) + sim!(linkinds, ydag) + ydag, A, x = deprecate_make_inds_match!(dot, ydag, A, x; make_inds_match) + check_hascommoninds(siteinds, A, y) + O = ydag[1] * A[1] * x[1] if loginner - normO = norm(O) - log_inner_tot += log(normO) - O ./= normO + normO = norm(O) + log_inner_tot = log(normO) + O ./= normO end - end - if loginner - if !isreal(O[]) || real(O[]) < 0 - log_inner_tot += log(complex(O[])) + for j in 2:N + O = O * ydag[j] * A[j] * x[j] + if loginner + normO = norm(O) + log_inner_tot += log(normO) + O ./= normO + end + end + if loginner + if !isreal(O[]) || real(O[]) < 0 + log_inner_tot += log(complex(O[])) + end + return log_inner_tot + else + return O[] end - return log_inner_tot - else - return O[] - end end """ @@ -405,8 +405,8 @@ end Same as [`inner`](@ref). """ -function LinearAlgebra.dot(y::MPS, A::MPO, x::MPS; make_inds_match::Bool=true, kwargs...) - return _log_or_not_dot(y, A, x, false; make_inds_match=make_inds_match, kwargs...) +function LinearAlgebra.dot(y::MPS, A::MPO, x::MPS; make_inds_match::Bool = true, kwargs...) + return _log_or_not_dot(y, A, x, false; make_inds_match = make_inds_match, kwargs...) end """ @@ -415,8 +415,8 @@ end This is useful for larger MPS/MPO, where in the limit of large numbers of sites the inner product can diverge or approach zero. Same as [`loginner`](@ref). """ -function logdot(y::MPS, A::MPO, x::MPS; make_inds_match::Bool=true, kwargs...) - return _log_or_not_dot(y, A, x, true; make_inds_match=make_inds_match, kwargs...) +function logdot(y::MPS, A::MPO, x::MPS; make_inds_match::Bool = true, kwargs...) + return _log_or_not_dot(y, A, x, true; make_inds_match = make_inds_match, kwargs...) end """ @@ -449,8 +449,8 @@ Same as [`dot`](@ref). """ inner(y::MPS, A::MPO, x::MPS; kwargs...) = dot(y, A, x; kwargs...) -function inner(y::MPS, Ax::Apply{Tuple{MPO,MPS}}) - return inner(y', Ax.args[1], Ax.args[2]) +function inner(y::MPS, Ax::Apply{Tuple{MPO, MPS}}) + return inner(y', Ax.args[1], Ax.args[2]) end """ @@ -465,42 +465,42 @@ loginner(y::MPS, A::MPO, x::MPS; kwargs...) = logdot(y, A, x; kwargs...) Same as [`inner`](@ref). """ function LinearAlgebra.dot( - B::MPO, y::MPS, A::MPO, x::MPS; make_inds_match::Bool=true, kwargs... -)::Number - !make_inds_match && error( - "make_inds_match = false not currently supported in dot(::MPO, ::MPS, ::MPO, ::MPS)" - ) - N = length(B) - if length(y) != N || length(x) != N || length(A) != N - throw( - DimensionMismatch( - "inner: mismatched lengths $N and $(length(x)) or $(length(y)) or $(length(A))" - ), + B::MPO, y::MPS, A::MPO, x::MPS; make_inds_match::Bool = true, kwargs... + )::Number + !make_inds_match && error( + "make_inds_match = false not currently supported in dot(::MPO, ::MPS, ::MPO, ::MPS)" ) - end - check_hascommoninds(siteinds, A, x) - check_hascommoninds(siteinds, B, y) - for j in eachindex(B) - !hascommoninds( - uniqueinds(siteinds(A, j), siteinds(x, j)), uniqueinds(siteinds(B, j), siteinds(y, j)) - ) && error( - "$(typeof(x)) Ax and $(typeof(y)) By must share site indices. On site $j, Ax has site indices $(uniqueinds(siteinds(A, j), (siteinds(x, j)))) while By has site indices $(uniqueinds(siteinds(B, j), siteinds(y, j))).", - ) - end - ydag = dag(y) - Bdag = dag(B) - sim!(linkinds, ydag) - sim!(linkinds, Bdag) - yB = ydag[1] * Bdag[1] - Ax = A[1] * x[1] - O = yB * Ax - for j in 2:N - yB = ydag[j] * Bdag[j] - Ax = A[j] * x[j] - yB *= O + N = length(B) + if length(y) != N || length(x) != N || length(A) != N + throw( + DimensionMismatch( + "inner: mismatched lengths $N and $(length(x)) or $(length(y)) or $(length(A))" + ), + ) + end + check_hascommoninds(siteinds, A, x) + check_hascommoninds(siteinds, B, y) + for j in eachindex(B) + !hascommoninds( + uniqueinds(siteinds(A, j), siteinds(x, j)), uniqueinds(siteinds(B, j), siteinds(y, j)) + ) && error( + "$(typeof(x)) Ax and $(typeof(y)) By must share site indices. On site $j, Ax has site indices $(uniqueinds(siteinds(A, j), (siteinds(x, j)))) while By has site indices $(uniqueinds(siteinds(B, j), siteinds(y, j))).", + ) + end + ydag = dag(y) + Bdag = dag(B) + sim!(linkinds, ydag) + sim!(linkinds, Bdag) + yB = ydag[1] * Bdag[1] + Ax = A[1] * x[1] O = yB * Ax - end - return O[] + for j in 2:N + yB = ydag[j] * Bdag[j] + Ax = A[j] * x[j] + yB *= O + O = yB * Ax + end + return O[] end # TODO: maybe make these into tuple inputs? @@ -524,39 +524,39 @@ Same as [`dot`](@ref). """ inner(B::MPO, y::MPS, A::MPO, x::MPS) = dot(B, y, A, x) -function LinearAlgebra.dot(M1::MPO, M2::MPO; make_inds_match::Bool=false, kwargs...) - if make_inds_match - error("In dot(::MPO, ::MPO), make_inds_match is not currently supported") - end - return _log_or_not_dot(M1, M2, false; make_inds_match=make_inds_match) +function LinearAlgebra.dot(M1::MPO, M2::MPO; make_inds_match::Bool = false, kwargs...) + if make_inds_match + error("In dot(::MPO, ::MPO), make_inds_match is not currently supported") + end + return _log_or_not_dot(M1, M2, false; make_inds_match = make_inds_match) end # TODO: implement by combining the MPO indices and converting # to MPS -function logdot(M1::MPO, M2::MPO; make_inds_match::Bool=false, kwargs...) - if make_inds_match - error("In dot(::MPO, ::MPO), make_inds_match is not currently supported") - end - return _log_or_not_dot(M1, M2, true; make_inds_match=make_inds_match) +function logdot(M1::MPO, M2::MPO; make_inds_match::Bool = false, kwargs...) + if make_inds_match + error("In dot(::MPO, ::MPO), make_inds_match is not currently supported") + end + return _log_or_not_dot(M1, M2, true; make_inds_match = make_inds_match) end -function LinearAlgebra.tr(M::MPO; plev::Pair{Int,Int}=0 => 1, tags::Pair=ts"" => ts"") - N = length(M) - # - # TODO: choose whether to contract or trace - # first depending on the bond dimension. The scaling is: - # - # 1. Trace last: O(χ²d²) + O(χd²) - # 2. Trace first: O(χ²d²) + O(χ²) - # - # So tracing first is better if d > √χ. - # - L = tr(M[1]; plev=plev, tags=tags) - for j in 2:N - L *= M[j] - L = tr(L; plev=plev, tags=tags) - end - return L +function LinearAlgebra.tr(M::MPO; plev::Pair{Int, Int} = 0 => 1, tags::Pair = ts"" => ts"") + N = length(M) + # + # TODO: choose whether to contract or trace + # first depending on the bond dimension. The scaling is: + # + # 1. Trace last: O(χ²d²) + O(χd²) + # 2. Trace first: O(χ²d²) + O(χ²) + # + # So tracing first is better if d > √χ. + # + L = tr(M[1]; plev = plev, tags = tags) + for j in 2:N + L *= M[j] + L = tr(L; plev = plev, tags = tags) + end + return L end """ @@ -573,16 +573,16 @@ indices of `y` with the site indices of `A` that are not common with `x`. """ function error_contract(y::MPS, A::MPO, x::MPS; kwargs...) - N = length(A) - if length(y) != N || length(x) != N - throw( - DimensionMismatch("inner: mismatched lengths $N and $(length(x)) or $(length(y))") - ) - end - iyy = dot(y, y; kwargs...) - iyax = dot(y', A, x; kwargs...) - iaxax = dot(A, x, A, x; kwargs...) - return sqrt(abs(1.0 + (iyy - 2 * real(iyax)) / iaxax)) + N = length(A) + if length(y) != N || length(x) != N + throw( + DimensionMismatch("inner: mismatched lengths $N and $(length(x)) or $(length(y))") + ) + end + iyy = dot(y, y; kwargs...) + iyax = dot(y', A, x; kwargs...) + iaxax = dot(A, x, A, x; kwargs...) + return sqrt(abs(1.0 + (iyy - 2 * real(iyax)) / iaxax)) end error_contract(y::MPS, x::MPS, A::MPO) = error_contract(y, A, x) @@ -597,37 +597,37 @@ Equivalent to `replaceprime(contract(A, x; kwargs...), 2 => 1)`. See also [`contract`](@ref) for details about the arguments available. """ -function apply(A::MPO, ψ::MPS; alg=Algorithm"densitymatrix"(), kwargs...) - return apply(Algorithm(alg), A, ψ; kwargs...) +function apply(A::MPO, ψ::MPS; alg = Algorithm"densitymatrix"(), kwargs...) + return apply(Algorithm(alg), A, ψ; kwargs...) end function apply(alg::Algorithm, A::MPO, ψ::MPS; kwargs...) - Aψ = contract(alg, A, ψ; kwargs...) - return replaceprime(Aψ, 1 => 0) + Aψ = contract(alg, A, ψ; kwargs...) + return replaceprime(Aψ, 1 => 0) end (A::MPO)(ψ::MPS; kwargs...) = apply(A, ψ; kwargs...) function Apply(A::MPO, ψ::MPS; kwargs...) - return ITensors.LazyApply.Applied(apply, (A, ψ), NamedTuple(kwargs)) + return ITensors.LazyApply.Applied(apply, (A, ψ), NamedTuple(kwargs)) end -function ITensors.contract(A::MPO, ψ::MPS; alg=nothing, method=alg, kwargs...) - # TODO: Delete `method` since it is deprecated. - alg = NDTensors.replace_nothing(method, "densitymatrix") - - # Keyword argument deprecations - # TODO: Delete these. - if alg == "DensityMatrix" - @warn "In contract, method DensityMatrix is deprecated in favor of densitymatrix" - alg = "densitymatrix" - end - if alg == "Naive" - @warn "In contract, `alg=\"Naive\"` is deprecated in favor of `alg=\"naive\"`" - alg = "naive" - end - - return contract(Algorithm(alg), A, ψ; kwargs...) +function ITensors.contract(A::MPO, ψ::MPS; alg = nothing, method = alg, kwargs...) + # TODO: Delete `method` since it is deprecated. + alg = NDTensors.replace_nothing(method, "densitymatrix") + + # Keyword argument deprecations + # TODO: Delete these. + if alg == "DensityMatrix" + @warn "In contract, method DensityMatrix is deprecated in favor of densitymatrix" + alg = "densitymatrix" + end + if alg == "Naive" + @warn "In contract, `alg=\"Naive\"` is deprecated in favor of `alg=\"naive\"`" + alg = "naive" + end + + return contract(Algorithm(alg), A, ψ; kwargs...) end contract_mpo_mps_doc = """ @@ -690,193 +690,193 @@ ITensors.contract(ψ::MPS, A::MPO; kwargs...) = contract(A, ψ; kwargs...) #@doc (@doc contract(::MPO, ::MPS)) *(::MPO, ::MPS) function ITensors.contract( - ::Algorithm"densitymatrix", - A::MPO, - ψ::MPS; - cutoff=1e-13, - maxdim=maxlinkdim(A) * maxlinkdim(ψ), - mindim=1, - normalize=false, - kwargs..., -)::MPS - n = length(A) - n != length(ψ) && - throw(DimensionMismatch("lengths of MPO ($n) and MPS ($(length(ψ))) do not match")) - if n == 1 - return MPS([A[1] * ψ[1]]) - end - mindim = max(mindim, 1) - requested_maxdim = maxdim - ψ_out = similar(ψ) - - any(i -> isempty(i), siteinds(commoninds, A, ψ)) && - error("In `contract(A::MPO, x::MPS)`, `A` and `x` must share a set of site indices") - - # In case A and ψ have the same link indices - A = sim(linkinds, A) - - ψ_c = dag(ψ) - A_c = dag(A) - - # To not clash with the link indices of A and ψ - sim!(linkinds, A_c) - sim!(linkinds, ψ_c) - sim!(siteinds, commoninds, A_c, ψ_c) - - # A version helpful for making the density matrix - simA_c = sim(siteinds, uniqueinds, A_c, ψ_c) - - # Store the left environment tensors - E = Vector{ITensor}(undef, n - 1) - - E[1] = ψ[1] * A[1] * A_c[1] * ψ_c[1] - for j in 2:(n - 1) - E[j] = E[j - 1] * ψ[j] * A[j] * A_c[j] * ψ_c[j] - end - R = ψ[n] * A[n] - simR_c = ψ_c[n] * simA_c[n] - ρ = E[n - 1] * R * simR_c - l = linkind(ψ, n - 1) - ts = isnothing(l) ? "" : tags(l) - Lis = siteinds(uniqueinds, A, ψ, n) - Ris = siteinds(uniqueinds, simA_c, ψ_c, n) - F = eigen(ρ, Lis, Ris; ishermitian=true, tags=ts, cutoff, maxdim, mindim, kwargs...) - D, U, Ut = F.D, F.V, F.Vt - l_renorm, r_renorm = F.l, F.r - ψ_out[n] = Ut - R = R * dag(Ut) * ψ[n - 1] * A[n - 1] - simR_c = simR_c * U * ψ_c[n - 1] * simA_c[n - 1] - for j in reverse(2:(n - 1)) - # Determine smallest maxdim to use - cip = commoninds(ψ[j], E[j - 1]) - ciA = commoninds(A[j], E[j - 1]) - prod_dims = dim(cip) * dim(ciA) - maxdim = min(prod_dims, requested_maxdim) - - s = siteinds(uniqueinds, A, ψ, j) - s̃ = siteinds(uniqueinds, simA_c, ψ_c, j) - ρ = E[j - 1] * R * simR_c - l = linkind(ψ, j - 1) + ::Algorithm"densitymatrix", + A::MPO, + ψ::MPS; + cutoff = 1.0e-13, + maxdim = maxlinkdim(A) * maxlinkdim(ψ), + mindim = 1, + normalize = false, + kwargs..., + )::MPS + n = length(A) + n != length(ψ) && + throw(DimensionMismatch("lengths of MPO ($n) and MPS ($(length(ψ))) do not match")) + if n == 1 + return MPS([A[1] * ψ[1]]) + end + mindim = max(mindim, 1) + requested_maxdim = maxdim + ψ_out = similar(ψ) + + any(i -> isempty(i), siteinds(commoninds, A, ψ)) && + error("In `contract(A::MPO, x::MPS)`, `A` and `x` must share a set of site indices") + + # In case A and ψ have the same link indices + A = sim(linkinds, A) + + ψ_c = dag(ψ) + A_c = dag(A) + + # To not clash with the link indices of A and ψ + sim!(linkinds, A_c) + sim!(linkinds, ψ_c) + sim!(siteinds, commoninds, A_c, ψ_c) + + # A version helpful for making the density matrix + simA_c = sim(siteinds, uniqueinds, A_c, ψ_c) + + # Store the left environment tensors + E = Vector{ITensor}(undef, n - 1) + + E[1] = ψ[1] * A[1] * A_c[1] * ψ_c[1] + for j in 2:(n - 1) + E[j] = E[j - 1] * ψ[j] * A[j] * A_c[j] * ψ_c[j] + end + R = ψ[n] * A[n] + simR_c = ψ_c[n] * simA_c[n] + ρ = E[n - 1] * R * simR_c + l = linkind(ψ, n - 1) ts = isnothing(l) ? "" : tags(l) - Lis = IndexSet(s..., l_renorm) - Ris = IndexSet(s̃..., r_renorm) - F = eigen(ρ, Lis, Ris; ishermitian=true, tags=ts, cutoff, maxdim, mindim, kwargs...) + Lis = siteinds(uniqueinds, A, ψ, n) + Ris = siteinds(uniqueinds, simA_c, ψ_c, n) + F = eigen(ρ, Lis, Ris; ishermitian = true, tags = ts, cutoff, maxdim, mindim, kwargs...) D, U, Ut = F.D, F.V, F.Vt l_renorm, r_renorm = F.l, F.r - ψ_out[j] = Ut - R = R * dag(Ut) * ψ[j - 1] * A[j - 1] - simR_c = simR_c * U * ψ_c[j - 1] * simA_c[j - 1] - end - if normalize - R ./= norm(R) - end - ψ_out[1] = R - setleftlim!(ψ_out, 0) - setrightlim!(ψ_out, 2) - return ψ_out + ψ_out[n] = Ut + R = R * dag(Ut) * ψ[n - 1] * A[n - 1] + simR_c = simR_c * U * ψ_c[n - 1] * simA_c[n - 1] + for j in reverse(2:(n - 1)) + # Determine smallest maxdim to use + cip = commoninds(ψ[j], E[j - 1]) + ciA = commoninds(A[j], E[j - 1]) + prod_dims = dim(cip) * dim(ciA) + maxdim = min(prod_dims, requested_maxdim) + + s = siteinds(uniqueinds, A, ψ, j) + s̃ = siteinds(uniqueinds, simA_c, ψ_c, j) + ρ = E[j - 1] * R * simR_c + l = linkind(ψ, j - 1) + ts = isnothing(l) ? "" : tags(l) + Lis = IndexSet(s..., l_renorm) + Ris = IndexSet(s̃..., r_renorm) + F = eigen(ρ, Lis, Ris; ishermitian = true, tags = ts, cutoff, maxdim, mindim, kwargs...) + D, U, Ut = F.D, F.V, F.Vt + l_renorm, r_renorm = F.l, F.r + ψ_out[j] = Ut + R = R * dag(Ut) * ψ[j - 1] * A[j - 1] + simR_c = simR_c * U * ψ_c[j - 1] * simA_c[j - 1] + end + if normalize + R ./= norm(R) + end + ψ_out[1] = R + setleftlim!(ψ_out, 0) + setrightlim!(ψ_out, 2) + return ψ_out end -function _contract(::Algorithm"naive", A, ψ; truncate=true, kwargs...) - A = sim(linkinds, A) - ψ = sim(linkinds, ψ) - - N = length(A) - if N != length(ψ) - throw(DimensionMismatch("lengths of MPO ($N) and MPS ($(length(ψ))) do not match")) - end - - ψ_out = typeof(ψ)(N) - for j in 1:N - ψ_out[j] = A[j] * ψ[j] - end - - for b in 1:(N - 1) - Al = commoninds(A[b], A[b + 1]) - ψl = commoninds(ψ[b], ψ[b + 1]) - l = [Al..., ψl...] - if !isempty(l) - C = combiner(l) - ψ_out[b] *= C - ψ_out[b + 1] *= dag(C) +function _contract(::Algorithm"naive", A, ψ; truncate = true, kwargs...) + A = sim(linkinds, A) + ψ = sim(linkinds, ψ) + + N = length(A) + if N != length(ψ) + throw(DimensionMismatch("lengths of MPO ($N) and MPS ($(length(ψ))) do not match")) + end + + ψ_out = typeof(ψ)(N) + for j in 1:N + ψ_out[j] = A[j] * ψ[j] + end + + for b in 1:(N - 1) + Al = commoninds(A[b], A[b + 1]) + ψl = commoninds(ψ[b], ψ[b + 1]) + l = [Al..., ψl...] + if !isempty(l) + C = combiner(l) + ψ_out[b] *= C + ψ_out[b + 1] *= dag(C) + end end - end - if truncate - truncate!(ψ_out; kwargs...) - end + if truncate + truncate!(ψ_out; kwargs...) + end - return ψ_out + return ψ_out end function ITensors.contract(alg::Algorithm"naive", A::MPO, ψ::MPS; kwargs...) - return _contract(alg, A, ψ; kwargs...) + return _contract(alg, A, ψ; kwargs...) end -function ITensors.contract(A::MPO, B::MPO; alg="zipup", kwargs...) - return contract(Algorithm(alg), A, B; kwargs...) +function ITensors.contract(A::MPO, B::MPO; alg = "zipup", kwargs...) + return contract(Algorithm(alg), A, B; kwargs...) end function ITensors.contract(alg::Algorithm"naive", A::MPO, B::MPO; kwargs...) - return _contract(alg, A, B; kwargs...) + return _contract(alg, A, B; kwargs...) end function ITensors.contract( - ::Algorithm"zipup", - A::MPO, - B::MPO; - cutoff=1e-14, - maxdim=maxlinkdim(A) * maxlinkdim(B), - mindim=1, - kwargs..., -) - if hassameinds(siteinds, A, B) - error( - "In `contract(A::MPO, B::MPO)`, MPOs A and B have the same site indices. The indices of the MPOs in the contraction are taken literally, and therefore they should only share one site index per site so the contraction results in an MPO. You may want to use `replaceprime(contract(A', B), 2 => 1)` or `apply(A, B)` which automatically adjusts the prime levels assuming the input MPOs have pairs of primed and unprimed indices.", + ::Algorithm"zipup", + A::MPO, + B::MPO; + cutoff = 1.0e-14, + maxdim = maxlinkdim(A) * maxlinkdim(B), + mindim = 1, + kwargs..., ) - end - N = length(A) - N != length(B) && - throw(DimensionMismatch("lengths of MPOs A ($N) and B ($(length(B))) do not match")) - # Special case for a single site - N == 1 && return MPO([A[1] * B[1]]) - A = orthogonalize(A, 1) - B = orthogonalize(B, 1) - A = sim(linkinds, A) - sA = siteinds(uniqueinds, A, B) - sB = siteinds(uniqueinds, B, A) - C = MPO(N) - lCᵢ = Index[] - R = ITensor(true) - for i in 1:(N - 2) - RABᵢ = R * A[i] * B[i] + if hassameinds(siteinds, A, B) + error( + "In `contract(A::MPO, B::MPO)`, MPOs A and B have the same site indices. The indices of the MPOs in the contraction are taken literally, and therefore they should only share one site index per site so the contraction results in an MPO. You may want to use `replaceprime(contract(A', B), 2 => 1)` or `apply(A, B)` which automatically adjusts the prime levels assuming the input MPOs have pairs of primed and unprimed indices.", + ) + end + N = length(A) + N != length(B) && + throw(DimensionMismatch("lengths of MPOs A ($N) and B ($(length(B))) do not match")) + # Special case for a single site + N == 1 && return MPO([A[1] * B[1]]) + A = orthogonalize(A, 1) + B = orthogonalize(B, 1) + A = sim(linkinds, A) + sA = siteinds(uniqueinds, A, B) + sB = siteinds(uniqueinds, B, A) + C = MPO(N) + lCᵢ = Index[] + R = ITensor(true) + for i in 1:(N - 2) + RABᵢ = R * A[i] * B[i] + left_inds = [sA[i]..., sB[i]..., lCᵢ...] + C[i], R = factorize( + RABᵢ, + left_inds; + ortho = "left", + tags = commontags(linkinds(A, i)), + cutoff, + maxdim, + mindim, + kwargs..., + ) + lCᵢ = dag(commoninds(C[i], R)) + end + i = N - 1 + RABᵢ = R * A[i] * B[i] * A[i + 1] * B[i + 1] left_inds = [sA[i]..., sB[i]..., lCᵢ...] - C[i], R = factorize( - RABᵢ, - left_inds; - ortho="left", - tags=commontags(linkinds(A, i)), - cutoff, - maxdim, - mindim, - kwargs..., + C[N - 1], C[N] = factorize( + RABᵢ, + left_inds; + ortho = "right", + tags = commontags(linkinds(A, i)), + cutoff, + maxdim, + mindim, + kwargs..., ) - lCᵢ = dag(commoninds(C[i], R)) - end - i = N - 1 - RABᵢ = R * A[i] * B[i] * A[i + 1] * B[i + 1] - left_inds = [sA[i]..., sB[i]..., lCᵢ...] - C[N - 1], C[N] = factorize( - RABᵢ, - left_inds; - ortho="right", - tags=commontags(linkinds(A, i)), - cutoff, - maxdim, - mindim, - kwargs..., - ) - truncate!(C; kwargs...) - return C + truncate!(C; kwargs...) + return C end """ @@ -890,12 +890,12 @@ Equivalent to `replaceprime(contract(A', B; kwargs...), 2 => 1)`. See also [`contract`](@ref) for details about the arguments available. """ function apply(A::MPO, B::MPO; kwargs...) - AB = contract(A', B; kwargs...) - return replaceprime(AB, 2 => 1) + AB = contract(A', B; kwargs...) + return replaceprime(AB, 2 => 1) end function apply(A1::MPO, A2::MPO, A3::MPO, As::MPO...; kwargs...) - return apply(apply(A1, A2; kwargs...), A3, As...; kwargs...) + return apply(apply(A1, A2; kwargs...), A3, As...; kwargs...) end (A::MPO)(B::MPO; kwargs...) = apply(A, B; kwargs...) @@ -985,66 +985,66 @@ The MPO `M` should have an (approximately) positive spectrum. """ function sample(M::MPO) - return sample(Random.default_rng(), M) + return sample(Random.default_rng(), M) end function sample(rng::AbstractRNG, M::MPO) - N = length(M) - s = siteinds(M) - R = Vector{ITensor}(undef, N) - R[N] = M[N] * δ(dag(s[N])) - for n in reverse(1:(N - 1)) - R[n] = M[n] * δ(dag(s[n])) * R[n + 1] - end - - if abs(1.0 - R[1][]) > 1E-8 - error("sample: MPO is not normalized, norm=$(norm(M[1]))") - end - - result = zeros(Int, N) - ρj = M[1] * R[2] - Lj = ITensor() - - for j in 1:N - s = siteind(M, j) - d = dim(s) - # Compute the probability of each state - # one-by-one and stop when the random - # number r is below the total prob so far - pdisc = 0.0 - r = rand(rng) - # Will need n, An, and pn below - n = 1 - projn = ITensor() - pn = 0.0 - while n <= d - projn = ITensor(s) - projn[s => n] = 1.0 - pnc = (ρj * projn * prime(projn))[] - if imag(pnc) > 1e-8 - @warn "In sample, probability $pnc is complex." - end - pn = real(pnc) - pdisc += pn - (r < pdisc) && break - n += 1 + N = length(M) + s = siteinds(M) + R = Vector{ITensor}(undef, N) + R[N] = M[N] * δ(dag(s[N])) + for n in reverse(1:(N - 1)) + R[n] = M[n] * δ(dag(s[n])) * R[n + 1] end - result[j] = n - if j < N - if j == 1 - Lj = M[j] * projn * prime(projn) - elseif j > 1 - Lj = Lj * M[j] * projn * prime(projn) - end - if j == N - 1 - ρj = Lj * M[j + 1] - else - ρj = Lj * M[j + 1] * R[j + 2] - end - s = siteind(M, j + 1) - normj = (ρj * δ(s', s))[] - ρj ./= normj + + if abs(1.0 - R[1][]) > 1.0e-8 + error("sample: MPO is not normalized, norm=$(norm(M[1]))") + end + + result = zeros(Int, N) + ρj = M[1] * R[2] + Lj = ITensor() + + for j in 1:N + s = siteind(M, j) + d = dim(s) + # Compute the probability of each state + # one-by-one and stop when the random + # number r is below the total prob so far + pdisc = 0.0 + r = rand(rng) + # Will need n, An, and pn below + n = 1 + projn = ITensor() + pn = 0.0 + while n <= d + projn = ITensor(s) + projn[s => n] = 1.0 + pnc = (ρj * projn * prime(projn))[] + if imag(pnc) > 1.0e-8 + @warn "In sample, probability $pnc is complex." + end + pn = real(pnc) + pdisc += pn + (r < pdisc) && break + n += 1 + end + result[j] = n + if j < N + if j == 1 + Lj = M[j] * projn * prime(projn) + elseif j > 1 + Lj = Lj * M[j] * projn * prime(projn) + end + if j == N - 1 + ρj = Lj * M[j + 1] + else + ρj = Lj * M[j + 1] * R[j + 2] + end + s = siteind(M, j + 1) + normj = (ρj * δ(s', s))[] + ρj ./= normj + end end - end - return result + return result end diff --git a/src/mps.jl b/src/mps.jl index de71318..d5fcbea 100644 --- a/src/mps.jl +++ b/src/mps.jl @@ -10,13 +10,13 @@ A finite size matrix product state type. Keeps track of the orthogonality center. """ mutable struct MPS <: AbstractMPS - data::Vector{ITensor} - llim::Int - rlim::Int + data::Vector{ITensor} + llim::Int + rlim::Int end -function MPS(A::Vector{<:ITensor}; ortho_lims::UnitRange=1:length(A)) - return MPS(A, first(ortho_lims) - 1, last(ortho_lims) + 1) +function MPS(A::Vector{<:ITensor}; ortho_lims::UnitRange = 1:length(A)) + return MPS(A, first(ortho_lims) - 1, last(ortho_lims) + 1) end set_data(A::MPS, data::Vector{ITensor}) = MPS(data, A.llim, A.rlim) @@ -40,8 +40,8 @@ MPS() = MPS(ITensor[], 0, 0) Construct an MPS with N sites with default constructed ITensors. """ -function MPS(N::Int; ortho_lims::UnitRange=1:N) - return MPS(Vector{ITensor}(undef, N); ortho_lims=ortho_lims) +function MPS(N::Int; ortho_lims::UnitRange = 1:N) + return MPS(Vector{ITensor}(undef, N); ortho_lims = ortho_lims) end """ @@ -55,173 +55,173 @@ In the future we may generalize `linkdims` to allow specifying each individual l and additionally allow specifying quantum numbers. """ function MPS( - ::Type{T}, sites::Vector{<:Index}; linkdims::Union{Integer,Vector{<:Integer}}=1 -) where {T<:Number} - _linkdims = _fill_linkdims(linkdims, sites) - N = length(sites) - v = Vector{ITensor}(undef, N) - if N == 1 - v[1] = ITensor(T, sites[1]) - return MPS(v) - end - - spaces = if hasqns(sites) - [[QN() => _linkdims[j]] for j in 1:(N - 1)] - else - [_linkdims[j] for j in 1:(N - 1)] - end - - l = [Index(spaces[ii], "Link,l=$ii") for ii in 1:(N - 1)] - for ii in eachindex(sites) - s = sites[ii] - if ii == 1 - v[ii] = ITensor(T, l[ii], s) - elseif ii == N - v[ii] = ITensor(T, dag(l[ii - 1]), s) + ::Type{T}, sites::Vector{<:Index}; linkdims::Union{Integer, Vector{<:Integer}} = 1 + ) where {T <: Number} + _linkdims = _fill_linkdims(linkdims, sites) + N = length(sites) + v = Vector{ITensor}(undef, N) + if N == 1 + v[1] = ITensor(T, sites[1]) + return MPS(v) + end + + spaces = if hasqns(sites) + [[QN() => _linkdims[j]] for j in 1:(N - 1)] else - v[ii] = ITensor(T, dag(l[ii - 1]), s, l[ii]) + [_linkdims[j] for j in 1:(N - 1)] + end + + l = [Index(spaces[ii], "Link,l=$ii") for ii in 1:(N - 1)] + for ii in eachindex(sites) + s = sites[ii] + if ii == 1 + v[ii] = ITensor(T, l[ii], s) + elseif ii == N + v[ii] = ITensor(T, dag(l[ii - 1]), s) + else + v[ii] = ITensor(T, dag(l[ii - 1]), s, l[ii]) + end end - end - return MPS(v) + return MPS(v) end MPS(sites::Vector{<:Index}, args...; kwargs...) = MPS(Float64, sites, args...; kwargs...) function randomU(eltype::Type{<:Number}, s1::Index, s2::Index) - return randomU(Random.default_rng(), eltype, s1, s2) + return randomU(Random.default_rng(), eltype, s1, s2) end function randomU(rng::AbstractRNG, eltype::Type{<:Number}, s1::Index, s2::Index) - if !hasqns(s1) && !hasqns(s2) - mdim = dim(s1) * dim(s2) - RM = randn(rng, eltype, mdim, mdim) - Q, _ = NDTensors.qr_positive(RM) - G = itensor(Q, dag(s1), dag(s2), s1', s2') - else - M = random_itensor(rng, eltype, QN(), s1', s2', dag(s1), dag(s2)) - U, S, V = svd(M, (s1', s2')) - u = commonind(U, S) - v = commonind(S, V) - replaceind!(U, u, v) - G = U * V - end - return G + if !hasqns(s1) && !hasqns(s2) + mdim = dim(s1) * dim(s2) + RM = randn(rng, eltype, mdim, mdim) + Q, _ = NDTensors.qr_positive(RM) + G = itensor(Q, dag(s1), dag(s2), s1', s2') + else + M = random_itensor(rng, eltype, QN(), s1', s2', dag(s1), dag(s2)) + U, S, V = svd(M, (s1', s2')) + u = commonind(U, S) + v = commonind(S, V) + replaceind!(U, u, v) + G = U * V + end + return G end -function randomizeMPS!(eltype::Type{<:Number}, M::MPS, sites::Vector{<:Index}, linkdims=1) - return randomizeMPS!(Random.default_rng(), eltype, M, sites, linkdims) +function randomizeMPS!(eltype::Type{<:Number}, M::MPS, sites::Vector{<:Index}, linkdims = 1) + return randomizeMPS!(Random.default_rng(), eltype, M, sites, linkdims) end function randomizeMPS!( - rng::AbstractRNG, eltype::Type{<:Number}, M::MPS, sites::Vector{<:Index}, linkdims=1 -) - _linkdims = _fill_linkdims(linkdims, sites) - if isone(length(sites)) - randn!(rng, M[1]) - normalize!(M) - return M - end - N = length(sites) - c = div(N, 2) - max_pass = 100 - for pass in 1:max_pass, half in 1:2 - if half == 1 - (db, brange) = (+1, 1:1:(N - 1)) - else - (db, brange) = (-1, N:-1:2) + rng::AbstractRNG, eltype::Type{<:Number}, M::MPS, sites::Vector{<:Index}, linkdims = 1 + ) + _linkdims = _fill_linkdims(linkdims, sites) + if isone(length(sites)) + randn!(rng, M[1]) + normalize!(M) + return M end - for b in brange - s1 = sites[b] - s2 = sites[b + db] - G = randomU(rng, eltype, s1, s2) - T = noprime(G * M[b] * M[b + db]) - rinds = uniqueinds(M[b], M[b + db]) - - b_dim = half == 1 ? b : b + db - U, S, V = svd(T, rinds; maxdim=_linkdims[b_dim], utags="Link,l=$(b-1)") - M[b] = U - M[b + db] = S * V - M[b + db] /= norm(M[b + db]) + N = length(sites) + c = div(N, 2) + max_pass = 100 + for pass in 1:max_pass, half in 1:2 + if half == 1 + (db, brange) = (+1, 1:1:(N - 1)) + else + (db, brange) = (-1, N:-1:2) + end + for b in brange + s1 = sites[b] + s2 = sites[b + db] + G = randomU(rng, eltype, s1, s2) + T = noprime(G * M[b] * M[b + db]) + rinds = uniqueinds(M[b], M[b + db]) + + b_dim = half == 1 ? b : b + db + U, S, V = svd(T, rinds; maxdim = _linkdims[b_dim], utags = "Link,l=$(b - 1)") + M[b] = U + M[b + db] = S * V + M[b + db] /= norm(M[b + db]) + end + if half == 2 && dim(commonind(M[c], M[c + 1])) >= _linkdims[c] + break + end end - if half == 2 && dim(commonind(M[c], M[c + 1])) >= _linkdims[c] - break + setleftlim!(M, 0) + setrightlim!(M, 2) + return if dim(commonind(M[c], M[c + 1])) < _linkdims[c] + @warn "MPS center bond dimension is less than requested (you requested $(_linkdims[c]), but in practice it is $(dim(commonind(M[c], M[c + 1]))). This is likely due to technicalities of truncating quantum number sectors." end - end - setleftlim!(M, 0) - setrightlim!(M, 2) - if dim(commonind(M[c], M[c + 1])) < _linkdims[c] - @warn "MPS center bond dimension is less than requested (you requested $(_linkdims[c]), but in practice it is $(dim(commonind(M[c], M[c + 1]))). This is likely due to technicalities of truncating quantum number sectors." - end end function randomCircuitMPS( - eltype::Type{<:Number}, sites::Vector{<:Index}, linkdims::Vector{<:Integer}; kwargs... -) - return randomCircuitMPS(Random.default_rng(), eltype, sites, linkdims; kwargs...) + eltype::Type{<:Number}, sites::Vector{<:Index}, linkdims::Vector{<:Integer}; kwargs... + ) + return randomCircuitMPS(Random.default_rng(), eltype, sites, linkdims; kwargs...) end function randomCircuitMPS( - rng::AbstractRNG, - eltype::Type{<:Number}, - sites::Vector{<:Index}, - linkdims::Vector{<:Integer}; - kwargs..., -) - N = length(sites) - M = MPS(N) - - if N == 1 - M[1] = ITensor(randn(rng, eltype, dim(sites[1])), sites[1]) - M[1] /= norm(M[1]) + rng::AbstractRNG, + eltype::Type{<:Number}, + sites::Vector{<:Index}, + linkdims::Vector{<:Integer}; + kwargs..., + ) + N = length(sites) + M = MPS(N) + + if N == 1 + M[1] = ITensor(randn(rng, eltype, dim(sites[1])), sites[1]) + M[1] /= norm(M[1]) + return M + end + + l = Vector{Index}(undef, N) + + d = dim(sites[N]) + chi = min(linkdims[N - 1], d) + l[N - 1] = Index(chi, "Link,l=$(N - 1)") + O = NDTensors.random_unitary(rng, eltype, chi, d) + M[N] = itensor(O, l[N - 1], sites[N]) + + for j in (N - 1):-1:2 + chi *= dim(sites[j]) + chi = min(linkdims[j - 1], chi) + l[j - 1] = Index(chi, "Link,l=$(j - 1)") + O = NDTensors.random_unitary(rng, eltype, chi, dim(sites[j]) * dim(l[j])) + T = reshape(O, (chi, dim(sites[j]), dim(l[j]))) + M[j] = itensor(T, l[j - 1], sites[j], l[j]) + end + + O = NDTensors.random_unitary(rng, eltype, 1, dim(sites[1]) * dim(l[1])) + l0 = Index(1, "Link,l=0") + T = reshape(O, (1, dim(sites[1]), dim(l[1]))) + M[1] = itensor(T, l0, sites[1], l[1]) + M[1] *= onehot(eltype, l0 => 1) + + M.llim = 0 + M.rlim = 2 + return M - end - - l = Vector{Index}(undef, N) - - d = dim(sites[N]) - chi = min(linkdims[N - 1], d) - l[N - 1] = Index(chi, "Link,l=$(N-1)") - O = NDTensors.random_unitary(rng, eltype, chi, d) - M[N] = itensor(O, l[N - 1], sites[N]) - - for j in (N - 1):-1:2 - chi *= dim(sites[j]) - chi = min(linkdims[j - 1], chi) - l[j - 1] = Index(chi, "Link,l=$(j-1)") - O = NDTensors.random_unitary(rng, eltype, chi, dim(sites[j]) * dim(l[j])) - T = reshape(O, (chi, dim(sites[j]), dim(l[j]))) - M[j] = itensor(T, l[j - 1], sites[j], l[j]) - end - - O = NDTensors.random_unitary(rng, eltype, 1, dim(sites[1]) * dim(l[1])) - l0 = Index(1, "Link,l=0") - T = reshape(O, (1, dim(sites[1]), dim(l[1]))) - M[1] = itensor(T, l0, sites[1], l[1]) - M[1] *= onehot(eltype, l0 => 1) - - M.llim = 0 - M.rlim = 2 - - return M end function randomCircuitMPS(sites::Vector{<:Index}, linkdims::Vector{<:Integer}; kwargs...) - return randomCircuitMPS(Random.default_rng(), sites, linkdims; kwargs...) + return randomCircuitMPS(Random.default_rng(), sites, linkdims; kwargs...) end function randomCircuitMPS( - rng::AbstractRNG, sites::Vector{<:Index}, linkdims::Vector{<:Integer}; kwargs... -) - return randomCircuitMPS(rng, Float64, sites, linkdims; kwargs...) + rng::AbstractRNG, sites::Vector{<:Index}, linkdims::Vector{<:Integer}; kwargs... + ) + return randomCircuitMPS(rng, Float64, sites, linkdims; kwargs...) end function _fill_linkdims(linkdims::Vector{<:Integer}, sites::Vector{<:Index}) - @assert length(linkdims) == length(sites) - 1 - return linkdims + @assert length(linkdims) == length(sites) - 1 + return linkdims end function _fill_linkdims(linkdims::Integer, sites::Vector{<:Index}) - return fill(linkdims, length(sites) - 1) + return fill(linkdims, length(sites) - 1) end """ @@ -235,25 +235,25 @@ type `eltype`. MPS with non-uniform bond dimension. """ function random_mps( - ::Type{ElT}, sites::Vector{<:Index}; linkdims::Union{Integer,Vector{<:Integer}}=1 -) where {ElT<:Number} - return random_mps(Random.default_rng(), ElT, sites; linkdims) + ::Type{ElT}, sites::Vector{<:Index}; linkdims::Union{Integer, Vector{<:Integer}} = 1 + ) where {ElT <: Number} + return random_mps(Random.default_rng(), ElT, sites; linkdims) end function random_mps( - rng::AbstractRNG, - ::Type{ElT}, - sites::Vector{<:Index}; - linkdims::Union{Integer,Vector{<:Integer}}=1, -) where {ElT<:Number} - _linkdims = _fill_linkdims(linkdims, sites) - if any(hasqns, sites) - error("initial state required to use random_mps with QNs") - end - - # For non-QN-conserving MPS, instantiate - # the random MPS directly as a circuit: - return randomCircuitMPS(rng, ElT, sites, _linkdims) + rng::AbstractRNG, + ::Type{ElT}, + sites::Vector{<:Index}; + linkdims::Union{Integer, Vector{<:Integer}} = 1, + ) where {ElT <: Number} + _linkdims = _fill_linkdims(linkdims, sites) + if any(hasqns, sites) + error("initial state required to use random_mps with QNs") + end + + # For non-QN-conserving MPS, instantiate + # the random MPS directly as a circuit: + return randomCircuitMPS(rng, ElT, sites, _linkdims) end """ @@ -267,52 +267,52 @@ default has element type `Float64`. `length(linkdims) == length(sites) - 1` for constructing an MPS with non-uniform bond dimension. """ -function random_mps(sites::Vector{<:Index}; linkdims::Union{Integer,Vector{<:Integer}}=1) - return random_mps(Random.default_rng(), sites; linkdims) +function random_mps(sites::Vector{<:Index}; linkdims::Union{Integer, Vector{<:Integer}} = 1) + return random_mps(Random.default_rng(), sites; linkdims) end function random_mps( - rng::AbstractRNG, sites::Vector{<:Index}; linkdims::Union{Integer,Vector{<:Integer}}=1 -) - return random_mps(rng, Float64, sites; linkdims) + rng::AbstractRNG, sites::Vector{<:Index}; linkdims::Union{Integer, Vector{<:Integer}} = 1 + ) + return random_mps(rng, Float64, sites; linkdims) end function random_mps( - sites::Vector{<:Index}, state; linkdims::Union{Integer,Vector{<:Integer}}=1 -) - return random_mps(Random.default_rng(), sites, state; linkdims) + sites::Vector{<:Index}, state; linkdims::Union{Integer, Vector{<:Integer}} = 1 + ) + return random_mps(Random.default_rng(), sites, state; linkdims) end function random_mps( - rng::AbstractRNG, - sites::Vector{<:Index}, - state; - linkdims::Union{Integer,Vector{<:Integer}}=1, -) - return random_mps(rng, Float64, sites, state; linkdims) + rng::AbstractRNG, + sites::Vector{<:Index}, + state; + linkdims::Union{Integer, Vector{<:Integer}} = 1, + ) + return random_mps(rng, Float64, sites, state; linkdims) end function random_mps( - eltype::Type{<:Number}, - sites::Vector{<:Index}, - state; - linkdims::Union{Integer,Vector{<:Integer}}=1, -) - return random_mps(Random.default_rng(), eltype, sites, state; linkdims) + eltype::Type{<:Number}, + sites::Vector{<:Index}, + state; + linkdims::Union{Integer, Vector{<:Integer}} = 1, + ) + return random_mps(Random.default_rng(), eltype, sites, state; linkdims) end function random_mps( - rng::AbstractRNG, - eltype::Type{<:Number}, - sites::Vector{<:Index}, - state; - linkdims::Union{Integer,Vector{<:Integer}}=1, -)::MPS - M = MPS(eltype, sites, state) - if any(>(1), linkdims) - randomizeMPS!(rng, eltype, M, sites, linkdims) - end - return M + rng::AbstractRNG, + eltype::Type{<:Number}, + sites::Vector{<:Index}, + state; + linkdims::Union{Integer, Vector{<:Integer}} = 1, + )::MPS + M = MPS(eltype, sites, state) + if any(>(1), linkdims) + randomizeMPS!(rng, eltype, M, sites, linkdims) + end + return M end @doc """ @@ -332,41 +332,41 @@ random MPS. Construct a product state MPS with element type `T` and nonzero values determined from the input IndexVals. """ -function MPS(::Type{T}, ivals::Vector{<:Pair{<:Index}}) where {T<:Number} - N = length(ivals) - M = MPS(N) - - if N == 1 - M[1] = ITensor(T, ind(ivals[1])) - M[1][ivals[1]] = one(T) - return M - end +function MPS(::Type{T}, ivals::Vector{<:Pair{<:Index}}) where {T <: Number} + N = length(ivals) + M = MPS(N) + + if N == 1 + M[1] = ITensor(T, ind(ivals[1])) + M[1][ivals[1]] = one(T) + return M + end - if hasqns(ind(ivals[1])) - lflux = QN() - for j in 1:(N - 1) - lflux += qn(ivals[j]) + if hasqns(ind(ivals[1])) + lflux = QN() + for j in 1:(N - 1) + lflux += qn(ivals[j]) + end + links = Vector{QNIndex}(undef, N - 1) + for j in (N - 1):-1:1 + links[j] = dag(Index(lflux => 1; tags = "Link,l=$j")) + lflux -= qn(ivals[j]) + end + else + links = [Index(1, "Link,l=$n") for n in 1:(N - 1)] end - links = Vector{QNIndex}(undef, N - 1) - for j in (N - 1):-1:1 - links[j] = dag(Index(lflux => 1; tags="Link,l=$j")) - lflux -= qn(ivals[j]) + + M[1] = ITensor(T, ind(ivals[1]), links[1]) + M[1][ivals[1], links[1] => 1] = one(T) + for n in 2:(N - 1) + s = ind(ivals[n]) + M[n] = ITensor(T, dag(links[n - 1]), s, links[n]) + M[n][links[n - 1] => 1, ivals[n], links[n] => 1] = one(T) end - else - links = [Index(1, "Link,l=$n") for n in 1:(N - 1)] - end - - M[1] = ITensor(T, ind(ivals[1]), links[1]) - M[1][ivals[1], links[1] => 1] = one(T) - for n in 2:(N - 1) - s = ind(ivals[n]) - M[n] = ITensor(T, dag(links[n - 1]), s, links[n]) - M[n][links[n - 1] => 1, ivals[n], links[n] => 1] = one(T) - end - M[N] = ITensor(T, dag(links[N - 1]), ind(ivals[N])) - M[N][links[N - 1] => 1, ivals[N]] = one(T) - - return M + M[N] = ITensor(T, dag(links[N - 1]), ind(ivals[N])) + M[N][links[N - 1] => 1, ivals[N]] = one(T) + + return M end # For backwards compatibility @@ -407,54 +407,54 @@ phi = MPS(sites, "Up") ``` """ function MPS(eltype::Type{<:Number}, sites::Vector{<:Index}, states_) - if length(sites) != length(states_) - throw(DimensionMismatch("Number of sites and and initial vals don't match")) - end - N = length(states_) - M = MPS(N) - - if N == 1 - M[1] = state(sites[1], states_[1]) - return convert_leaf_eltype(eltype, M) - end + if length(sites) != length(states_) + throw(DimensionMismatch("Number of sites and and initial vals don't match")) + end + N = length(states_) + M = MPS(N) + + if N == 1 + M[1] = state(sites[1], states_[1]) + return convert_leaf_eltype(eltype, M) + end - states = [state(sites[j], states_[j]) for j in 1:N] + states = [state(sites[j], states_[j]) for j in 1:N] - if hasqns(states[1]) - lflux = QN() - for j in 1:(N - 1) - lflux += flux(states[j]) + if hasqns(states[1]) + lflux = QN() + for j in 1:(N - 1) + lflux += flux(states[j]) + end + links = Vector{QNIndex}(undef, N - 1) + for j in (N - 1):-1:1 + links[j] = dag(Index(lflux => 1; tags = "Link,l=$j")) + lflux -= flux(states[j]) + end + else + links = [Index(1; tags = "Link,l=$n") for n in 1:N] end - links = Vector{QNIndex}(undef, N - 1) - for j in (N - 1):-1:1 - links[j] = dag(Index(lflux => 1; tags="Link,l=$j")) - lflux -= flux(states[j]) + + M[1] = ITensor(sites[1], links[1]) + M[1] += states[1] * state(links[1], 1) + for n in 2:(N - 1) + M[n] = ITensor(dag(links[n - 1]), sites[n], links[n]) + M[n] += state(dag(links[n - 1]), 1) * states[n] * state(links[n], 1) end - else - links = [Index(1; tags="Link,l=$n") for n in 1:N] - end - - M[1] = ITensor(sites[1], links[1]) - M[1] += states[1] * state(links[1], 1) - for n in 2:(N - 1) - M[n] = ITensor(dag(links[n - 1]), sites[n], links[n]) - M[n] += state(dag(links[n - 1]), 1) * states[n] * state(links[n], 1) - end - M[N] = ITensor(dag(links[N - 1]), sites[N]) - M[N] += state(dag(links[N - 1]), 1) * states[N] - - return convert_leaf_eltype(eltype, M) + M[N] = ITensor(dag(links[N - 1]), sites[N]) + M[N] += state(dag(links[N - 1]), 1) * states[N] + + return convert_leaf_eltype(eltype, M) end function MPS( - ::Type{T}, sites::Vector{<:Index}, state::Union{String,Integer} -) where {T<:Number} - return MPS(T, sites, fill(state, length(sites))) + ::Type{T}, sites::Vector{<:Index}, state::Union{String, Integer} + ) where {T <: Number} + return MPS(T, sites, fill(state, length(sites))) end -function MPS(::Type{T}, sites::Vector{<:Index}, states::Function) where {T<:Number} - states_vec = [states(n) for n in 1:length(sites)] - return MPS(T, sites, states_vec) +function MPS(::Type{T}, sites::Vector{<:Index}, states::Function) where {T <: Number} + states_vec = [states(n) for n in 1:length(sites)] + return MPS(T, sites, states_vec) end """ @@ -491,11 +491,11 @@ SiteTypes.siteind(M::MPS, j::Int; kwargs...) = siteind(first, M, j; kwargs...) Get the only site Index of the MPS. Return `nothing` if none is found. """ function SiteTypes.siteind(::typeof(only), M::MPS, j::Int; kwargs...) - is = siteinds(M, j; kwargs...) - if isempty(is) - return nothing - end - return only(is) + is = siteinds(M, j; kwargs...) + if isempty(is) + return nothing + end + return only(is) end """ @@ -515,11 +515,11 @@ Get a vector of the all site Indices found on each tensor of the MPS. Returns a SiteTypes.siteinds(M::MPS; kwargs...) = siteinds(first, M; kwargs...) function replace_siteinds!(M::MPS, sites) - for j in eachindex(M) - sj = only(siteinds(M, j)) - M[j] = replaceinds(M[j], sj => sites[j]) - end - return M + for j in eachindex(M) + sj = only(siteinds(M, j)) + M[j] = replaceinds(M[j], sj => sites[j]) + end + return M end replace_siteinds(M::MPS, sites) = replace_siteinds!(copy(M), sites) @@ -532,65 +532,65 @@ Factorize the ITensor `phi` and replace the ITensors the orthogonality with `ortho="left"/"right"`. """ function replacebond!( - M::MPS, - b::Int, - phi::ITensor; - normalize=nothing, - swapsites=nothing, - ortho=nothing, - # Decomposition kwargs - which_decomp=nothing, - mindim=nothing, - maxdim=nothing, - cutoff=nothing, - eigen_perturbation=nothing, - # svd kwargs - svd_alg=nothing, - use_absolute_cutoff=nothing, - use_relative_cutoff=nothing, - min_blockdim=nothing, -) - normalize = NDTensors.replace_nothing(normalize, false) - swapsites = NDTensors.replace_nothing(swapsites, false) - ortho = NDTensors.replace_nothing(ortho, "left") - - indsMb = inds(M[b]) - if swapsites - sb = siteind(M, b) - sbp1 = siteind(M, b + 1) - indsMb = replaceind(indsMb, sb, sbp1) - end - L, R, spec = factorize( - phi, - indsMb; - mindim, - maxdim, - cutoff, - ortho, - which_decomp, - eigen_perturbation, - svd_alg, - tags=tags(linkind(M, b)), - use_absolute_cutoff, - use_relative_cutoff, - min_blockdim, - ) - M[b] = L - M[b + 1] = R - if ortho == "left" - leftlim(M) == b - 1 && setleftlim!(M, leftlim(M) + 1) - rightlim(M) == b + 1 && setrightlim!(M, rightlim(M) + 1) - normalize && (M[b + 1] ./= norm(M[b + 1])) - elseif ortho == "right" - leftlim(M) == b && setleftlim!(M, leftlim(M) - 1) - rightlim(M) == b + 2 && setrightlim!(M, rightlim(M) - 1) - normalize && (M[b] ./= norm(M[b])) - else - error( - "In replacebond!, got ortho = $ortho, only currently supports `left` and `right`." + M::MPS, + b::Int, + phi::ITensor; + normalize = nothing, + swapsites = nothing, + ortho = nothing, + # Decomposition kwargs + which_decomp = nothing, + mindim = nothing, + maxdim = nothing, + cutoff = nothing, + eigen_perturbation = nothing, + # svd kwargs + svd_alg = nothing, + use_absolute_cutoff = nothing, + use_relative_cutoff = nothing, + min_blockdim = nothing, ) - end - return spec + normalize = NDTensors.replace_nothing(normalize, false) + swapsites = NDTensors.replace_nothing(swapsites, false) + ortho = NDTensors.replace_nothing(ortho, "left") + + indsMb = inds(M[b]) + if swapsites + sb = siteind(M, b) + sbp1 = siteind(M, b + 1) + indsMb = replaceind(indsMb, sb, sbp1) + end + L, R, spec = factorize( + phi, + indsMb; + mindim, + maxdim, + cutoff, + ortho, + which_decomp, + eigen_perturbation, + svd_alg, + tags = tags(linkind(M, b)), + use_absolute_cutoff, + use_relative_cutoff, + min_blockdim, + ) + M[b] = L + M[b + 1] = R + if ortho == "left" + leftlim(M) == b - 1 && setleftlim!(M, leftlim(M) + 1) + rightlim(M) == b + 1 && setrightlim!(M, rightlim(M) + 1) + normalize && (M[b + 1] ./= norm(M[b + 1])) + elseif ortho == "right" + leftlim(M) == b && setleftlim!(M, leftlim(M) - 1) + rightlim(M) == b + 2 && setrightlim!(M, rightlim(M) - 1) + normalize && (M[b] ./= norm(M[b])) + else + error( + "In replacebond!, got ortho = $ortho, only currently supports `left` and `right`." + ) + end + return spec end """ @@ -599,15 +599,15 @@ end Like `replacebond!`, but returns the new MPS. """ function replacebond(M0::MPS, b::Int, phi::ITensor; kwargs...) - M = copy(M0) - replacebond!(M, b, phi; kwargs...) - return M + M = copy(M0) + replacebond!(M, b, phi; kwargs...) + return M end # Allows overloading `replacebond!` based on the projected # MPO type. By default just calls `replacebond!` on the MPS. function replacebond!(PH, M::MPS, b::Int, phi::ITensor; kwargs...) - return replacebond!(M, b, phi; kwargs...) + return replacebond!(M, b, phi; kwargs...) end """ @@ -623,12 +623,12 @@ orthogonalize!(m,1) will be called before computing the sample. """ function sample!(m::MPS) - return sample!(Random.default_rng(), m) + return sample!(Random.default_rng(), m) end function sample!(rng::AbstractRNG, m::MPS) - orthogonalize!(m, 1) - return sample(rng, m) + orthogonalize!(m, 1) + return sample(rng, m) end """ @@ -642,52 +642,52 @@ squaring the components of the tensor that the MPS represents """ function sample(m::MPS) - return sample(Random.default_rng(), m) + return sample(Random.default_rng(), m) end function sample(rng::AbstractRNG, m::MPS) - N = length(m) - - if orthocenter(m) != 1 - error("sample: MPS m must have orthocenter(m)==1") - end - if abs(1.0 - norm(m[1])) > 1E-8 - error("sample: MPS is not normalized, norm=$(norm(m[1]))") - end - - ElT = scalartype(m) - - result = zeros(Int, N) - A = m[1] - - for j in 1:N - s = siteind(m, j) - d = dim(s) - # Compute the probability of each state - # one-by-one and stop when the random - # number r is below the total prob so far - pdisc = zero(real(ElT)) - r = rand(rng) - # Will need n,An, and pn below - n = 1 - An = ITensor() - pn = zero(real(ElT)) - while n <= d - projn = ITensor(s) - projn[s => n] = one(ElT) - An = A * dag(adapt(datatype(A), projn)) - pn = real(scalar(dag(An) * An)) - pdisc += pn - (r < pdisc) && break - n += 1 + N = length(m) + + if orthocenter(m) != 1 + error("sample: MPS m must have orthocenter(m)==1") + end + if abs(1.0 - norm(m[1])) > 1.0e-8 + error("sample: MPS is not normalized, norm=$(norm(m[1]))") end - result[j] = n - if j < N - A = m[j + 1] * An - A *= (one(ElT) / sqrt(pn)) + + ElT = scalartype(m) + + result = zeros(Int, N) + A = m[1] + + for j in 1:N + s = siteind(m, j) + d = dim(s) + # Compute the probability of each state + # one-by-one and stop when the random + # number r is below the total prob so far + pdisc = zero(real(ElT)) + r = rand(rng) + # Will need n,An, and pn below + n = 1 + An = ITensor() + pn = zero(real(ElT)) + while n <= d + projn = ITensor(s) + projn[s => n] = one(ElT) + An = A * dag(adapt(datatype(A), projn)) + pn = real(scalar(dag(An) * An)) + pdisc += pn + (r < pdisc) && break + n += 1 + end + result[j] = n + if j < N + A = m[j + 1] * An + A *= (one(ElT) / sqrt(pn)) + end end - end - return result + return result end _op_prod(o1::AbstractString, o2::AbstractString) = "$o1 * $o2" @@ -737,210 +737,210 @@ Cuu = correlation_matrix(psi, "Cdagup", "Cup"; sites=2:8) ``` """ function correlation_matrix( - psi::MPS, _Op1, _Op2; sites=1:length(psi), site_range=nothing, ishermitian=nothing -) - if !isnothing(site_range) - @warn "The `site_range` keyword arg. to `correlation_matrix` is deprecated: use the keyword `sites` instead" - sites = site_range - end - if !(sites isa AbstractRange) - sites = collect(sites) - end - - start_site = first(sites) - end_site = last(sites) - - N = length(psi) - ElT = scalartype(psi) - s = siteinds(psi) - - Op1 = _Op1 #make copies into which we can insert "F" string operators, and then restore. - Op2 = _Op2 - onsiteOp = _op_prod(Op1, Op2) - fermionic1 = has_fermion_string(Op1, s[start_site]) - fermionic2 = has_fermion_string(Op2, s[end_site]) - if fermionic1 != fermionic2 - error( - "correlation_matrix: Mixed fermionic and bosonic operators are not supported yet." + psi::MPS, _Op1, _Op2; sites = 1:length(psi), site_range = nothing, ishermitian = nothing ) - end - - # Decide if we need to calculate a non-hermitian corr. matrix, which is roughly double the work. - is_cm_hermitian = ishermitian - if isnothing(is_cm_hermitian) - # Assume correlation matrix is non-hermitian - is_cm_hermitian = false - O1 = op(Op1, s, start_site) - O2 = op(Op2, s, start_site) - O1 /= norm(O1) - O2 /= norm(O2) - #We need to decide if O1 ∝ O2 or O1 ∝ O2^dagger allowing for some round off errors. - eps = 1e-10 - is_op_proportional = norm(O1 - O2) < eps - is_op_hermitian = norm(O1 - dag(swapprime(O2, 0, 1))) < eps - if is_op_proportional || is_op_hermitian - is_cm_hermitian = true + if !isnothing(site_range) + @warn "The `site_range` keyword arg. to `correlation_matrix` is deprecated: use the keyword `sites` instead" + sites = site_range end - # finally if they are both fermionic and proportional then the corr matrix will - # be anti symmetric insterad of Hermitian. Handle things like - # at this point we know fermionic2=fermionic1, but we put them both in the if - # to clarify the meaning of what we are doing. - if is_op_proportional && fermionic1 && fermionic2 - is_cm_hermitian = false + if !(sites isa AbstractRange) + sites = collect(sites) end - end - - psi = orthogonalize(psi, start_site) - norm2_psi = norm(psi[start_site])^2 - - # Nb = size of block of correlation matrix - Nb = length(sites) - - C = zeros(ElT, Nb, Nb) - if start_site == 1 - L = ITensor(1.0) - else - lind = commonind(psi[start_site], psi[start_site - 1]) - L = delta(dag(lind), lind') - end - pL = start_site - 1 - - for (ni, i) in enumerate(sites[1:(end - 1)]) - while pL < i - 1 - pL += 1 - sᵢ = siteind(psi, pL) - L = (L * psi[pL]) * prime(dag(psi[pL]), !sᵢ) + start_site = first(sites) + end_site = last(sites) + + N = length(psi) + ElT = scalartype(psi) + s = siteinds(psi) + + Op1 = _Op1 #make copies into which we can insert "F" string operators, and then restore. + Op2 = _Op2 + onsiteOp = _op_prod(Op1, Op2) + fermionic1 = has_fermion_string(Op1, s[start_site]) + fermionic2 = has_fermion_string(Op2, s[end_site]) + if fermionic1 != fermionic2 + error( + "correlation_matrix: Mixed fermionic and bosonic operators are not supported yet." + ) end - Li = L * psi[i] - - # Get j == i diagonal correlations - rind = commonind(psi[i], psi[i + 1]) - oᵢ = adapt(datatype(Li), op(onsiteOp, s, i)) - C[ni, ni] = ((Li * oᵢ) * prime(dag(psi[i]), !rind))[] / norm2_psi - - # Get j > i correlations - if !using_auto_fermion() && fermionic2 - Op1 = "$Op1 * F" + # Decide if we need to calculate a non-hermitian corr. matrix, which is roughly double the work. + is_cm_hermitian = ishermitian + if isnothing(is_cm_hermitian) + # Assume correlation matrix is non-hermitian + is_cm_hermitian = false + O1 = op(Op1, s, start_site) + O2 = op(Op2, s, start_site) + O1 /= norm(O1) + O2 /= norm(O2) + #We need to decide if O1 ∝ O2 or O1 ∝ O2^dagger allowing for some round off errors. + eps = 1.0e-10 + is_op_proportional = norm(O1 - O2) < eps + is_op_hermitian = norm(O1 - dag(swapprime(O2, 0, 1))) < eps + if is_op_proportional || is_op_hermitian + is_cm_hermitian = true + end + # finally if they are both fermionic and proportional then the corr matrix will + # be anti symmetric insterad of Hermitian. Handle things like + # at this point we know fermionic2=fermionic1, but we put them both in the if + # to clarify the meaning of what we are doing. + if is_op_proportional && fermionic1 && fermionic2 + is_cm_hermitian = false + end end - oᵢ = adapt(datatype(Li), op(Op1, s, i)) + psi = orthogonalize(psi, start_site) + norm2_psi = norm(psi[start_site])^2 - Li12 = (dag(psi[i])' * oᵢ) * Li - pL12 = i + # Nb = size of block of correlation matrix + Nb = length(sites) - for (n, j) in enumerate(sites[(ni + 1):end]) - nj = ni + n + C = zeros(ElT, Nb, Nb) - while pL12 < j - 1 - pL12 += 1 - if !using_auto_fermion() && fermionic2 - oᵢ = adapt(datatype(psi[pL12]), op("F", s[pL12])) - Li12 *= (oᵢ * dag(psi[pL12])') - else - sᵢ = siteind(psi, pL12) - Li12 *= prime(dag(psi[pL12]), !sᵢ) - end - Li12 *= psi[pL12] - end - - lind = commonind(psi[j], Li12) - Li12 *= psi[j] - - oⱼ = adapt(datatype(Li12), op(Op2, s, j)) - sⱼ = siteind(psi, j) - val = (Li12 * oⱼ) * prime(dag(psi[j]), (sⱼ, lind)) - - # XXX: This gives a different fermion sign with - # ITensors.enable_auto_fermion() - # val = prime(dag(psi[j]), (sⱼ, lind)) * (oⱼ * Li12) - - C[ni, nj] = scalar(val) / norm2_psi - if is_cm_hermitian - C[nj, ni] = conj(C[ni, nj]) - end - - pL12 += 1 - if !using_auto_fermion() && fermionic2 - oᵢ = adapt(datatype(psi[pL12]), op("F", s[pL12])) - Li12 *= (oᵢ * dag(psi[pL12])') - else - sᵢ = siteind(psi, pL12) - Li12 *= prime(dag(psi[pL12]), !sᵢ) - end - @assert pL12 == j - end #for j - Op1 = _Op1 #"Restore Op1 with no Fs" - - if !is_cm_hermitian #If isHermitian=false the we must calculate the below diag elements explicitly. - - # Get j < i correlations by swapping the operators - if !using_auto_fermion() && fermionic1 - Op2 = "$Op2 * F" - end - oᵢ = adapt(datatype(psi[i]), op(Op2, s, i)) - Li21 = (Li * oᵢ) * dag(psi[i])' - pL21 = i - if !using_auto_fermion() && fermionic1 - Li21 = -Li21 #Required because we swapped fermionic ops, instead of sweeping right to left. - end - - for (n, j) in enumerate(sites[(ni + 1):end]) - nj = ni + n - - while pL21 < j - 1 - pL21 += 1 - if !using_auto_fermion() && fermionic1 - oᵢ = adapt(datatype(psi[pL21]), op("F", s[pL21])) - Li21 *= oᵢ * dag(psi[pL21])' - else - sᵢ = siteind(psi, pL21) - Li21 *= prime(dag(si[pL21]), !sᵢ) - end - Li21 *= psi[pL21] + if start_site == 1 + L = ITensor(1.0) + else + lind = commonind(psi[start_site], psi[start_site - 1]) + L = delta(dag(lind), lind') + end + pL = start_site - 1 + + for (ni, i) in enumerate(sites[1:(end - 1)]) + while pL < i - 1 + pL += 1 + sᵢ = siteind(psi, pL) + L = (L * psi[pL]) * prime(dag(psi[pL]), !sᵢ) end - lind = commonind(psi[j], Li21) - Li21 *= psi[j] + Li = L * psi[i] - oⱼ = adapt(datatype(psi[j]), op(Op1, s, j)) - sⱼ = siteind(psi, j) - val = (prime(dag(psi[j]), (sⱼ, lind)) * (oⱼ * Li21))[] - C[nj, ni] = val / norm2_psi + # Get j == i diagonal correlations + rind = commonind(psi[i], psi[i + 1]) + oᵢ = adapt(datatype(Li), op(onsiteOp, s, i)) + C[ni, ni] = ((Li * oᵢ) * prime(dag(psi[i]), !rind))[] / norm2_psi - pL21 += 1 - if !using_auto_fermion() && fermionic1 - oᵢ = adapt(datatype(psi[pL21]), op("F", s[pL21])) - Li21 *= (oᵢ * dag(psi[pL21])') - else - sᵢ = siteind(psi, pL21) - Li21 *= prime(dag(psi[pL21]), !sᵢ) + # Get j > i correlations + if !using_auto_fermion() && fermionic2 + Op1 = "$Op1 * F" end - @assert pL21 == j - end #for j - Op2 = _Op2 #"Restore Op2 with no Fs" - end #if is_cm_hermitian - pL += 1 + oᵢ = adapt(datatype(Li), op(Op1, s, i)) + + Li12 = (dag(psi[i])' * oᵢ) * Li + pL12 = i + + for (n, j) in enumerate(sites[(ni + 1):end]) + nj = ni + n + + while pL12 < j - 1 + pL12 += 1 + if !using_auto_fermion() && fermionic2 + oᵢ = adapt(datatype(psi[pL12]), op("F", s[pL12])) + Li12 *= (oᵢ * dag(psi[pL12])') + else + sᵢ = siteind(psi, pL12) + Li12 *= prime(dag(psi[pL12]), !sᵢ) + end + Li12 *= psi[pL12] + end + + lind = commonind(psi[j], Li12) + Li12 *= psi[j] + + oⱼ = adapt(datatype(Li12), op(Op2, s, j)) + sⱼ = siteind(psi, j) + val = (Li12 * oⱼ) * prime(dag(psi[j]), (sⱼ, lind)) + + # XXX: This gives a different fermion sign with + # ITensors.enable_auto_fermion() + # val = prime(dag(psi[j]), (sⱼ, lind)) * (oⱼ * Li12) + + C[ni, nj] = scalar(val) / norm2_psi + if is_cm_hermitian + C[nj, ni] = conj(C[ni, nj]) + end + + pL12 += 1 + if !using_auto_fermion() && fermionic2 + oᵢ = adapt(datatype(psi[pL12]), op("F", s[pL12])) + Li12 *= (oᵢ * dag(psi[pL12])') + else + sᵢ = siteind(psi, pL12) + Li12 *= prime(dag(psi[pL12]), !sᵢ) + end + @assert pL12 == j + end #for j + Op1 = _Op1 #"Restore Op1 with no Fs" + + if !is_cm_hermitian #If isHermitian=false the we must calculate the below diag elements explicitly. + + # Get j < i correlations by swapping the operators + if !using_auto_fermion() && fermionic1 + Op2 = "$Op2 * F" + end + oᵢ = adapt(datatype(psi[i]), op(Op2, s, i)) + Li21 = (Li * oᵢ) * dag(psi[i])' + pL21 = i + if !using_auto_fermion() && fermionic1 + Li21 = -Li21 #Required because we swapped fermionic ops, instead of sweeping right to left. + end + + for (n, j) in enumerate(sites[(ni + 1):end]) + nj = ni + n + + while pL21 < j - 1 + pL21 += 1 + if !using_auto_fermion() && fermionic1 + oᵢ = adapt(datatype(psi[pL21]), op("F", s[pL21])) + Li21 *= oᵢ * dag(psi[pL21])' + else + sᵢ = siteind(psi, pL21) + Li21 *= prime(dag(si[pL21]), !sᵢ) + end + Li21 *= psi[pL21] + end + + lind = commonind(psi[j], Li21) + Li21 *= psi[j] + + oⱼ = adapt(datatype(psi[j]), op(Op1, s, j)) + sⱼ = siteind(psi, j) + val = (prime(dag(psi[j]), (sⱼ, lind)) * (oⱼ * Li21))[] + C[nj, ni] = val / norm2_psi + + pL21 += 1 + if !using_auto_fermion() && fermionic1 + oᵢ = adapt(datatype(psi[pL21]), op("F", s[pL21])) + Li21 *= (oᵢ * dag(psi[pL21])') + else + sᵢ = siteind(psi, pL21) + Li21 *= prime(dag(psi[pL21]), !sᵢ) + end + @assert pL21 == j + end #for j + Op2 = _Op2 #"Restore Op2 with no Fs" + end #if is_cm_hermitian + + pL += 1 + sᵢ = siteind(psi, i) + L = Li * prime(dag(psi[i]), !sᵢ) + end #for i + + # Get last diagonal element of C + i = end_site + while pL < i - 1 + pL += 1 + sᵢ = siteind(psi, pL) + L = L * psi[pL] * prime(dag(psi[pL]), !sᵢ) + end + lind = commonind(psi[i], psi[i - 1]) + oᵢ = adapt(datatype(psi[i]), op(onsiteOp, s, i)) sᵢ = siteind(psi, i) - L = Li * prime(dag(psi[i]), !sᵢ) - end #for i - - # Get last diagonal element of C - i = end_site - while pL < i - 1 - pL += 1 - sᵢ = siteind(psi, pL) - L = L * psi[pL] * prime(dag(psi[pL]), !sᵢ) - end - lind = commonind(psi[i], psi[i - 1]) - oᵢ = adapt(datatype(psi[i]), op(onsiteOp, s, i)) - sᵢ = siteind(psi, i) - val = (L * (oᵢ * psi[i]) * prime(dag(psi[i]), (sᵢ, lind)))[] - C[Nb, Nb] = val / norm2_psi - - return C + val = (L * (oᵢ * psi[i]) * prime(dag(psi[i]), (sᵢ, lind)))[] + C[Nb, Nb] = val / norm2_psi + + return C end """ @@ -982,55 +982,55 @@ dens = expect(psi, "Ntot") updens, dndens = expect(psi, "Nup", "Ndn") # pass more than one operator ``` """ -function expect(psi::MPS, ops; sites=1:length(psi), site_range=nothing) - psi = copy(psi) - N = length(psi) - ElT = scalartype(psi) - s = siteinds(psi) - - if !isnothing(site_range) - @warn "The `site_range` keyword arg. to `expect` is deprecated: use the keyword `sites` instead" - sites = site_range - end - - site_range = (sites isa AbstractRange) ? sites : collect(sites) - Ns = length(site_range) - start_site = first(site_range) - - el_types = map(o -> ishermitian(op(o, s[start_site])) ? real(ElT) : ElT, ops) - - psi = orthogonalize(psi, start_site) - norm2_psi = norm(psi)^2 - iszero(norm2_psi) && error("MPS has zero norm in function `expect`") - - ex = map((o, el_t) -> zeros(el_t, Ns), ops, el_types) - for (entry, j) in enumerate(site_range) - psi = orthogonalize(psi, j) - for (n, opname) in enumerate(ops) - oⱼ = adapt(datatype(psi[j]), op(opname, s[j])) - val = inner(psi[j], apply(oⱼ, psi[j])) / norm2_psi - ex[n][entry] = (el_types[n] <: Real) ? real(val) : val +function expect(psi::MPS, ops; sites = 1:length(psi), site_range = nothing) + psi = copy(psi) + N = length(psi) + ElT = scalartype(psi) + s = siteinds(psi) + + if !isnothing(site_range) + @warn "The `site_range` keyword arg. to `expect` is deprecated: use the keyword `sites` instead" + sites = site_range + end + + site_range = (sites isa AbstractRange) ? sites : collect(sites) + Ns = length(site_range) + start_site = first(site_range) + + el_types = map(o -> ishermitian(op(o, s[start_site])) ? real(ElT) : ElT, ops) + + psi = orthogonalize(psi, start_site) + norm2_psi = norm(psi)^2 + iszero(norm2_psi) && error("MPS has zero norm in function `expect`") + + ex = map((o, el_t) -> zeros(el_t, Ns), ops, el_types) + for (entry, j) in enumerate(site_range) + psi = orthogonalize(psi, j) + for (n, opname) in enumerate(ops) + oⱼ = adapt(datatype(psi[j]), op(opname, s[j])) + val = inner(psi[j], apply(oⱼ, psi[j])) / norm2_psi + ex[n][entry] = (el_types[n] <: Real) ? real(val) : val + end end - end - if sites isa Number - return map(arr -> arr[1], ex) - end - return ex + if sites isa Number + return map(arr -> arr[1], ex) + end + return ex end function expect(psi::MPS, op::AbstractString; kwargs...) - return first(expect(psi, (op,); kwargs...)) + return first(expect(psi, (op,); kwargs...)) end function expect(psi::MPS, op::Matrix{<:Number}; kwargs...) - return first(expect(psi, (op,); kwargs...)) + return first(expect(psi, (op,); kwargs...)) end function expect(psi::MPS, op1::AbstractString, ops::AbstractString...; kwargs...) - return expect(psi, (op1, ops...); kwargs...) + return expect(psi, (op1, ops...); kwargs...) end function expect(psi::MPS, op1::Matrix{<:Number}, ops::Matrix{<:Number}...; kwargs...) - return expect(psi, (op1, ops...); kwargs...) + return expect(psi, (op1, ops...); kwargs...) end diff --git a/src/observer.jl b/src/observer.jl index 4436d36..a2646b6 100644 --- a/src/observer.jl +++ b/src/observer.jl @@ -1,4 +1,3 @@ - abstract type AbstractObserver end measure!(o::AbstractObserver; kwargs...) = nothing @@ -28,13 +27,13 @@ the `dmrg` function to return early if an energy convergence criterion is met. """ struct DMRGObserver{T} <: AbstractObserver - ops::Vector{String} - sites::Vector{<:Index} - measurements::Dict{String,DMRGMeasurement} - energies::Vector{T} - truncerrs::Vector{Float64} - etol::Float64 - minsweeps::Int64 + ops::Vector{String} + sites::Vector{<:Index} + measurements::Dict{String, DMRGMeasurement} + energies::Vector{T} + truncerrs::Vector{Float64} + etol::Float64 + minsweeps::Int64 end """ @@ -54,16 +53,16 @@ Optional keyword arguments: - minsweeps: do at least this many sweeps - energy_type: type to use when storing energies at each step """ -function DMRGObserver(; energy_tol=0.0, minsweeps=2, energy_type=Float64) - return DMRGObserver( - String[], - Index[], - Dict{String,DMRGMeasurement}(), - energy_type[], - Float64[], - energy_tol, - minsweeps, - ) +function DMRGObserver(; energy_tol = 0.0, minsweeps = 2, energy_type = Float64) + return DMRGObserver( + String[], + Index[], + Dict{String, DMRGMeasurement}(), + energy_type[], + Float64[], + energy_tol, + minsweeps, + ) end """ @@ -96,16 +95,16 @@ Optional keyword arguments: - energy_type: type to use when storing energies at each step """ function DMRGObserver( - ops::Vector{String}, - sites::Vector{<:Index}; - energy_tol=0.0, - minsweeps=2, - energy_type=Float64, -) - measurements = Dict(o => DMRGMeasurement() for o in ops) - return DMRGObserver{energy_type}( - ops, sites, measurements, energy_type[], Float64[], energy_tol, minsweeps - ) + ops::Vector{String}, + sites::Vector{<:Index}; + energy_tol = 0.0, + minsweeps = 2, + energy_type = Float64, + ) + measurements = Dict(o => DMRGMeasurement() for o in ops) + return DMRGObserver{energy_type}( + ops, sites, measurements, energy_type[], Float64[], energy_tol, minsweeps + ) end """ @@ -135,56 +134,57 @@ observer_ops(obs::DMRGObserver) = obs.ops truncerrors(obs::DMRGObserver) = obs.truncerrs function measurelocalops!(obs::DMRGObserver, wf::ITensor, i::Int) - for o in observer_ops(obs) - # Moves to GPU if needed - oⱼ = adapt(datatype(wf), op(observer_sites(obs), o, i)) - m = dot(wf, apply(oⱼ, wf)) - imag(m) > 1e-8 && (@warn "encountered finite imaginary part when measuring $o") - measurements(obs)[o][end][i] = real(m) - end + for o in observer_ops(obs) + # Moves to GPU if needed + oⱼ = adapt(datatype(wf), op(observer_sites(obs), o, i)) + m = dot(wf, apply(oⱼ, wf)) + imag(m) > 1.0e-8 && (@warn "encountered finite imaginary part when measuring $o") + measurements(obs)[o][end][i] = real(m) + end + return end function measure!(obs::DMRGObserver; kwargs...) - half_sweep = kwargs[:half_sweep] - b = kwargs[:bond] - energy = kwargs[:energy] - psi = kwargs[:psi] - truncerr = truncerror(kwargs[:spec]) - - if half_sweep == 2 - N = length(psi) - - if b == (N - 1) - for o in observer_ops(obs) - push!(measurements(obs)[o], zeros(N)) - end - push!(truncerrors(obs), 0.0) - end - - # when sweeping left the orthogonality center is located - # at site n=b after the bond update. - # We want to measure at n=b+1 because there the tensor has been - # already fully updated (by the right and left pass of the sweep). - wf = psi[b] * psi[b + 1] - measurelocalops!(obs, wf, b + 1) - - if b == 1 - push!(energies(obs), energy) - measurelocalops!(obs, wf, b) + half_sweep = kwargs[:half_sweep] + b = kwargs[:bond] + energy = kwargs[:energy] + psi = kwargs[:psi] + truncerr = truncerror(kwargs[:spec]) + + return if half_sweep == 2 + N = length(psi) + + if b == (N - 1) + for o in observer_ops(obs) + push!(measurements(obs)[o], zeros(N)) + end + push!(truncerrors(obs), 0.0) + end + + # when sweeping left the orthogonality center is located + # at site n=b after the bond update. + # We want to measure at n=b+1 because there the tensor has been + # already fully updated (by the right and left pass of the sweep). + wf = psi[b] * psi[b + 1] + measurelocalops!(obs, wf, b + 1) + + if b == 1 + push!(energies(obs), energy) + measurelocalops!(obs, wf, b) + end + truncerr > truncerrors(obs)[end] && (truncerrors(obs)[end] = truncerr) end - truncerr > truncerrors(obs)[end] && (truncerrors(obs)[end] = truncerr) - end end function checkdone!( - o::DMRGObserver; outputlevel=false, energy=nothing, psi=nothing, sweep=nothing -) - if ( - length(real(energies(o))) > o.minsweeps && - abs(real(energies(o))[end] - real(energies(o))[end - 1]) < o.etol - ) - outputlevel > 0 && println("Energy difference less than $(o.etol), stopping DMRG") - return true - end - return false + o::DMRGObserver; outputlevel = false, energy = nothing, psi = nothing, sweep = nothing + ) + if ( + length(real(energies(o))) > o.minsweeps && + abs(real(energies(o))[end] - real(energies(o))[end - 1]) < o.etol + ) + outputlevel > 0 && println("Energy difference less than $(o.etol), stopping DMRG") + return true + end + return false end diff --git a/src/opsum_to_mpo/matelem.jl b/src/opsum_to_mpo/matelem.jl index 5baa776..eb6aabf 100644 --- a/src/opsum_to_mpo/matelem.jl +++ b/src/opsum_to_mpo/matelem.jl @@ -3,9 +3,9 @@ ################################## struct MatElem{T} - row::Int - col::Int - val::T + row::Int + col::Int + val::T end #function Base.show(io::IO,m::MatElem) @@ -13,28 +13,28 @@ end #end function toMatrix(els::Vector{MatElem{T}})::Matrix{T} where {T} - nr = 0 - nc = 0 - for el in els - nr = max(nr, el.row) - nc = max(nc, el.col) - end - M = zeros(T, nr, nc) - for el in els - M[el.row, el.col] = el.val - end - return M + nr = 0 + nc = 0 + for el in els + nr = max(nr, el.row) + nc = max(nc, el.col) + end + M = zeros(T, nr, nc) + for el in els + M[el.row, el.col] = el.val + end + return M end function Base.:(==)(m1::MatElem{T}, m2::MatElem{T})::Bool where {T} - return (m1.row == m2.row && m1.col == m2.col && m1.val == m2.val) + return (m1.row == m2.row && m1.col == m2.col && m1.val == m2.val) end function Base.isless(m1::MatElem{T}, m2::MatElem{T})::Bool where {T} - if m1.row != m2.row - return m1.row < m2.row - elseif m1.col != m2.col - return m1.col < m2.col - end - return m1.val < m2.val + if m1.row != m2.row + return m1.row < m2.row + elseif m1.col != m2.col + return m1.col < m2.col + end + return m1.val < m2.val end diff --git a/src/opsum_to_mpo/opsum_to_mpo.jl b/src/opsum_to_mpo/opsum_to_mpo.jl index 114eec3..65cfa72 100644 --- a/src/opsum_to_mpo/opsum_to_mpo.jl +++ b/src/opsum_to_mpo/opsum_to_mpo.jl @@ -3,146 +3,146 @@ using NDTensors: using_auto_fermion # `ValType::Type{<:Number}` is used instead of `ValType::Type` for efficiency, possibly due to increased method specialization. # See https://github.com/ITensor/ITensors.jl/pull/1183. function svdMPO( - ValType::Type{<:Number}, os::OpSum{C}, sites; mindim=1, maxdim=typemax(Int), cutoff=1e-15 -)::MPO where {C} - N = length(sites) - - # Specifying the element type with `Matrix{ValType}[...]` improves type inference and therefore efficiency. - # See https://github.com/ITensor/ITensors.jl/pull/1183. - Vs = Matrix{ValType}[Matrix{ValType}(undef, 1, 1) for n in 1:N] - tempMPO = [MatElem{Scaled{C,Prod{Op}}}[] for n in 1:N] - - function crosses_bond(t::Scaled{C,Prod{Op}}, n::Int) where {C} - return (only(site(t[1])) <= n <= only(site(t[end]))) - end - - rightmaps = [Dict{Vector{Op},Int}() for _ in 1:N] - - for n in 1:N - leftbond_coefs = MatElem{ValType}[] - - leftmap = Dict{Vector{Op},Int}() - for term in os - crosses_bond(term, n) || continue - - left = filter(t -> (only(site(t)) < n), terms(term)) - onsite = filter(t -> (only(site(t)) == n), terms(term)) - right = filter(t -> (only(site(t)) > n), terms(term)) - - bond_row = -1 - bond_col = -1 - if !isempty(left) - bond_row = posInLink!(leftmap, left) - bond_col = posInLink!(rightmaps[n - 1], vcat(onsite, right)) - bond_coef = convert(ValType, coefficient(term)) - push!(leftbond_coefs, MatElem(bond_row, bond_col, bond_coef)) - end - - A_row = bond_col - A_col = posInLink!(rightmaps[n], right) - site_coef = one(C) - if A_row == -1 - site_coef = coefficient(term) - end - if isempty(onsite) - if !using_auto_fermion() && isfermionic(right, sites) - push!(onsite, Op("F", n)) - else - push!(onsite, Op("Id", n)) - end - end - el = MatElem(A_row, A_col, site_coef * Prod(onsite)) - push!(tempMPO[n], el) - end - remove_dups!(tempMPO[n]) - if n > 1 && !isempty(leftbond_coefs) - M = toMatrix(leftbond_coefs) - U, S, V = svd(M) - P = S .^ 2 - truncate!(P; maxdim=maxdim, cutoff=cutoff, mindim=mindim) - tdim = length(P) - nc = size(M, 2) - Vs[n - 1] = Matrix{ValType}(V[1:nc, 1:tdim]) - end - end + ValType::Type{<:Number}, os::OpSum{C}, sites; mindim = 1, maxdim = typemax(Int), cutoff = 1.0e-15 + )::MPO where {C} + N = length(sites) - llinks = Vector{Index{Int}}(undef, N + 1) - llinks[1] = Index(2, "Link,l=0") + # Specifying the element type with `Matrix{ValType}[...]` improves type inference and therefore efficiency. + # See https://github.com/ITensor/ITensors.jl/pull/1183. + Vs = Matrix{ValType}[Matrix{ValType}(undef, 1, 1) for n in 1:N] + tempMPO = [MatElem{Scaled{C, Prod{Op}}}[] for n in 1:N] - H = MPO(sites) - - for n in 1:N - VL = Matrix{ValType}(undef, 1, 1) - if n > 1 - VL = Vs[n - 1] + function crosses_bond(t::Scaled{C, Prod{Op}}, n::Int) where {C} + return (only(site(t[1])) <= n <= only(site(t[end]))) end - VR = Vs[n] - tdim = isempty(rightmaps[n]) ? 0 : size(VR, 2) - - llinks[n + 1] = Index(2 + tdim, "Link,l=$n") - - ll = llinks[n] - rl = llinks[n + 1] - H[n] = ITensor() + rightmaps = [Dict{Vector{Op}, Int}() for _ in 1:N] + + for n in 1:N + leftbond_coefs = MatElem{ValType}[] + + leftmap = Dict{Vector{Op}, Int}() + for term in os + crosses_bond(term, n) || continue + + left = filter(t -> (only(site(t)) < n), terms(term)) + onsite = filter(t -> (only(site(t)) == n), terms(term)) + right = filter(t -> (only(site(t)) > n), terms(term)) + + bond_row = -1 + bond_col = -1 + if !isempty(left) + bond_row = posInLink!(leftmap, left) + bond_col = posInLink!(rightmaps[n - 1], vcat(onsite, right)) + bond_coef = convert(ValType, coefficient(term)) + push!(leftbond_coefs, MatElem(bond_row, bond_col, bond_coef)) + end + + A_row = bond_col + A_col = posInLink!(rightmaps[n], right) + site_coef = one(C) + if A_row == -1 + site_coef = coefficient(term) + end + if isempty(onsite) + if !using_auto_fermion() && isfermionic(right, sites) + push!(onsite, Op("F", n)) + else + push!(onsite, Op("Id", n)) + end + end + el = MatElem(A_row, A_col, site_coef * Prod(onsite)) + push!(tempMPO[n], el) + end + remove_dups!(tempMPO[n]) + if n > 1 && !isempty(leftbond_coefs) + M = toMatrix(leftbond_coefs) + U, S, V = svd(M) + P = S .^ 2 + truncate!(P; maxdim = maxdim, cutoff = cutoff, mindim = mindim) + tdim = length(P) + nc = size(M, 2) + Vs[n - 1] = Matrix{ValType}(V[1:nc, 1:tdim]) + end + end - for el in tempMPO[n] - A_row = el.row - A_col = el.col - t = el.val - (abs(coefficient(t)) > eps()) || continue + llinks = Vector{Index{Int}}(undef, N + 1) + llinks[1] = Index(2, "Link,l=0") - M = zeros(ValType, dim(ll), dim(rl)) + H = MPO(sites) - ct = convert(ValType, coefficient(t)) - if A_row == -1 && A_col == -1 #onsite term - M[end, 1] += ct - elseif A_row == -1 #term starting on site n - for c in 1:size(VR, 2) - z = ct * VR[A_col, c] - M[end, 1 + c] += z + for n in 1:N + VL = Matrix{ValType}(undef, 1, 1) + if n > 1 + VL = Vs[n - 1] end - elseif A_col == -1 #term ending on site n - for r in 1:size(VL, 2) - z = ct * conj(VL[A_row, r]) - M[1 + r, 1] += z + VR = Vs[n] + tdim = isempty(rightmaps[n]) ? 0 : size(VR, 2) + + llinks[n + 1] = Index(2 + tdim, "Link,l=$n") + + ll = llinks[n] + rl = llinks[n + 1] + + H[n] = ITensor() + + for el in tempMPO[n] + A_row = el.row + A_col = el.col + t = el.val + (abs(coefficient(t)) > eps()) || continue + + M = zeros(ValType, dim(ll), dim(rl)) + + ct = convert(ValType, coefficient(t)) + if A_row == -1 && A_col == -1 #onsite term + M[end, 1] += ct + elseif A_row == -1 #term starting on site n + for c in 1:size(VR, 2) + z = ct * VR[A_col, c] + M[end, 1 + c] += z + end + elseif A_col == -1 #term ending on site n + for r in 1:size(VL, 2) + z = ct * conj(VL[A_row, r]) + M[1 + r, 1] += z + end + else + for r in 1:size(VL, 2), c in 1:size(VR, 2) + z = ct * conj(VL[A_row, r]) * VR[A_col, c] + M[1 + r, 1 + c] += z + end + end + + T = itensor(M, ll, rl) + H[n] += T * computeSiteProd(sites, argument(t)) end - else - for r in 1:size(VL, 2), c in 1:size(VR, 2) - z = ct * conj(VL[A_row, r]) * VR[A_col, c] - M[1 + r, 1 + c] += z - end - end - T = itensor(M, ll, rl) - H[n] += T * computeSiteProd(sites, argument(t)) + # + # Special handling of starting and + # ending identity operators: + # + idM = zeros(ValType, dim(ll), dim(rl)) + idM[1, 1] = 1.0 + idM[end, end] = 1.0 + T = itensor(idM, ll, rl) + H[n] += T * computeSiteProd(sites, Prod([Op("Id", n)])) end - # - # Special handling of starting and - # ending identity operators: - # - idM = zeros(ValType, dim(ll), dim(rl)) - idM[1, 1] = 1.0 - idM[end, end] = 1.0 - T = itensor(idM, ll, rl) - H[n] += T * computeSiteProd(sites, Prod([Op("Id", n)])) - end - - L = ITensor(llinks[1]) - L[end] = 1.0 + L = ITensor(llinks[1]) + L[end] = 1.0 - R = ITensor(llinks[N + 1]) - R[1] = 1.0 + R = ITensor(llinks[N + 1]) + R[1] = 1.0 - H[1] *= L - H[N] *= R + H[1] *= L + H[N] *= R - return H + return H end #svdMPO function svdMPO(os::OpSum{C}, sites; kwargs...)::MPO where {C} - # Function barrier to improve type stability - ValType = determineValType(terms(os)) - return svdMPO(ValType, os, sites; kwargs...) + # Function barrier to improve type stability + ValType = determineValType(terms(os)) + return svdMPO(ValType, os, sites; kwargs...) end diff --git a/src/opsum_to_mpo/opsum_to_mpo_generic.jl b/src/opsum_to_mpo/opsum_to_mpo_generic.jl index 915fd0e..bc454dd 100644 --- a/src/opsum_to_mpo/opsum_to_mpo_generic.jl +++ b/src/opsum_to_mpo/opsum_to_mpo_generic.jl @@ -57,12 +57,12 @@ opsum += (0.5,"S+",4,"S-",5) opsum .+= (0.5,"S+",5,"S-",6) ``` """ -function add!(os::OpSum, o::Scaled{C,Prod{Op}}) where {C} - push!(terms(os), o) - return os +function add!(os::OpSum, o::Scaled{C, Prod{Op}}) where {C} + push!(terms(os), o) + return os end add!(os::OpSum, o::Op) = add!(os, Prod{Op}() * o) -add!(os::OpSum, o::Scaled{C,Op}) where {C} = add!(os, Prod{Op}() * o) +add!(os::OpSum, o::Scaled{C, Op}) where {C} = add!(os, Prod{Op}() * o) add!(os::OpSum, o::Prod{Op}) = add!(os, one(Float64) * o) add!(os::OpSum, o::Tuple) = add!(os, Ops.op_term(o)) add!(os::OpSum, a1::String, args...) = add!(os, (a1, args...)) @@ -70,13 +70,13 @@ add!(os::OpSum, a1::Number, args...) = add!(os, (a1, args...)) subtract!(os::OpSum, o::Tuple) = add!(os, -Ops.op_term(o)) function isfermionic(t::Vector{Op}, sites) - p = +1 - for op in t - if has_fermion_string(ITensors.name(op), sites[site(op)]) - p *= -1 + p = +1 + for op in t + if has_fermion_string(ITensors.name(op), sites[site(op)]) + p *= -1 + end end - end - return (p == -1) + return (p == -1) end # @@ -99,146 +99,146 @@ Base.BroadcastStyle(::OpSumStyle, ::Broadcast.Style{Tuple}) = OpSumAddTermStyle( Broadcast.instantiate(bc::Broadcast.Broadcasted{OpSumAddTermStyle}) = bc -function Base.copyto!(os, bc::Broadcast.Broadcasted{OpSumAddTermStyle,<:Any,typeof(+)}) - add!(os, bc.args[2]) - return os +function Base.copyto!(os, bc::Broadcast.Broadcasted{OpSumAddTermStyle, <:Any, typeof(+)}) + add!(os, bc.args[2]) + return os end -function Base.copyto!(os, bc::Broadcast.Broadcasted{OpSumAddTermStyle,<:Any,typeof(-)}) - subtract!(os, bc.args[2]) - return os +function Base.copyto!(os, bc::Broadcast.Broadcasted{OpSumAddTermStyle, <:Any, typeof(-)}) + subtract!(os, bc.args[2]) + return os end # XXX: Create a new function name for this. -isempty(op_qn::Pair{Vector{Op},QN}) = isempty(op_qn.first) +isempty(op_qn::Pair{Vector{Op}, QN}) = isempty(op_qn.first) # the key type is Prod{Op} for the dense case # and is Pair{Prod{Op},QN} for the QN conserving case -function posInLink!(linkmap::Dict{K,Int}, k::K)::Int where {K} - isempty(k) && return -1 - pos = get(linkmap, k, -1) - if pos == -1 - pos = length(linkmap) + 1 - linkmap[k] = pos - end - return pos +function posInLink!(linkmap::Dict{K, Int}, k::K)::Int where {K} + isempty(k) && return -1 + pos = get(linkmap, k, -1) + if pos == -1 + pos = length(linkmap) + 1 + linkmap[k] = pos + end + return pos end # TODO: Define as `C`. Rename `coefficient_type`. -function determineValType(terms::Vector{Scaled{C,Prod{Op}}}) where {C} - for t in terms - (!isreal(coefficient(t))) && return ComplexF64 - end - return Float64 +function determineValType(terms::Vector{Scaled{C, Prod{Op}}}) where {C} + for t in terms + (!isreal(coefficient(t))) && return ComplexF64 + end + return Float64 end function computeSiteProd(sites, ops::Prod{Op})::ITensor - i = only(site(ops[1])) - T = op(sites[i], which_op(ops[1]); params(ops[1])...) - for j in 2:length(ops) - (only(site(ops[j])) != i) && error("Mismatch of site number in computeSiteProd") - opj = op(sites[i], which_op(ops[j]); params(ops[j])...) - T = product(T, opj) - end - return T + i = only(site(ops[1])) + T = op(sites[i], which_op(ops[1]); params(ops[1])...) + for j in 2:length(ops) + (only(site(ops[j])) != i) && error("Mismatch of site number in computeSiteProd") + opj = op(sites[i], which_op(ops[j]); params(ops[j])...) + T = product(T, opj) + end + return T end function remove_dups!(v::Vector{T}) where {T} - N = length(v) - (N == 0) && return nothing - sort!(v) - n = 1 - u = 2 - while u <= N - while u < N && v[u] == v[n] - u += 1 - end - if v[u] != v[n] - v[n + 1] = v[u] - n += 1 + N = length(v) + (N == 0) && return nothing + sort!(v) + n = 1 + u = 2 + while u <= N + while u < N && v[u] == v[n] + u += 1 + end + if v[u] != v[n] + v[n + 1] = v[u] + n += 1 + end + u += 1 end - u += 1 - end - resize!(v, n) - return nothing + resize!(v, n) + return nothing end #remove_dups! function sorteachterm(os::OpSum, sites) - os = copy(os) - - for (j, t) in enumerate(os) - if maximum(ITensors.sites(t)) > length(sites) - error( - "The OpSum contains a term $t that extends beyond the number of sites $(length(sites)).", - ) - end - - # Sort operators in t by site order and - # save the permutation used, "perm", for analysis below - Nt = length(t) - perm = Vector{Int}(undef, Nt) - sortperm!(perm, terms(t); alg=InsertionSort, lt=(o1, o2) -> (site(o1) < site(o2))) - # Apply permutation: - t = coefficient(t) * Prod(terms(t)[perm]) - - # prevsite keeps track of whether we are switching - # to a new site to make sure F string - # is only placed at most once for each site - prevsite = typemax(Int) - t_parity = +1 - for n in reverse(1:Nt) - site_n = only(site(t[n])) - if !using_auto_fermion() && (t_parity == -1) && (site_n < prevsite) - # Insert local piece of Jordan-Wigner string emanating - # from fermionic operators to the right - # (Remaining F operators will be put in by svdMPO) - terms(t)[n] = Op("$(which_op(t[n])) * F", site_n) - end - prevsite = site_n - - if has_fermion_string(which_op(t[n]), sites[site_n]) - t_parity = -t_parity - else - # Ignore bosonic operators in perm - # by zeroing corresponding entries - perm[n] = 0 - end + os = copy(os) + + for (j, t) in enumerate(os) + if maximum(ITensors.sites(t)) > length(sites) + error( + "The OpSum contains a term $t that extends beyond the number of sites $(length(sites)).", + ) + end + + # Sort operators in t by site order and + # save the permutation used, "perm", for analysis below + Nt = length(t) + perm = Vector{Int}(undef, Nt) + sortperm!(perm, terms(t); alg = InsertionSort, lt = (o1, o2) -> (site(o1) < site(o2))) + # Apply permutation: + t = coefficient(t) * Prod(terms(t)[perm]) + + # prevsite keeps track of whether we are switching + # to a new site to make sure F string + # is only placed at most once for each site + prevsite = typemax(Int) + t_parity = +1 + for n in reverse(1:Nt) + site_n = only(site(t[n])) + if !using_auto_fermion() && (t_parity == -1) && (site_n < prevsite) + # Insert local piece of Jordan-Wigner string emanating + # from fermionic operators to the right + # (Remaining F operators will be put in by svdMPO) + terms(t)[n] = Op("$(which_op(t[n])) * F", site_n) + end + prevsite = site_n + + if has_fermion_string(which_op(t[n]), sites[site_n]) + t_parity = -t_parity + else + # Ignore bosonic operators in perm + # by zeroing corresponding entries + perm[n] = 0 + end + end + + (t_parity == -1) && + error("Parity-odd fermionic terms not yet supported by OpSum to MPO conversion") + + # Keep only fermionic op positions (non-zero entries) + filter!(!iszero, perm) + # and account for anti-commuting, fermionic operators + # during above sort; put resulting sign into coef + t *= ITensors.parity_sign(perm) + terms(os)[j] = t end - (t_parity == -1) && - error("Parity-odd fermionic terms not yet supported by OpSum to MPO conversion") - - # Keep only fermionic op positions (non-zero entries) - filter!(!iszero, perm) - # and account for anti-commuting, fermionic operators - # during above sort; put resulting sign into coef - t *= ITensors.parity_sign(perm) - terms(os)[j] = t - end - - return os + return os end function sortmergeterms(os::OpSum{C}) where {C} - os_sorted_terms = sort(terms(os)) - os = Sum(os_sorted_terms) - # Merge (add) terms with same operators - merge_os_data = Scaled{C,Prod{Op}}[] - last_term = copy(os[1]) - last_term_coef = coefficient(last_term) - for n in 2:length(os) - if argument(os[n]) == argument(last_term) - last_term_coef += coefficient(os[n]) - last_term = last_term_coef * argument(last_term) - else - push!(merge_os_data, last_term) - last_term = os[n] - last_term_coef = coefficient(last_term) + os_sorted_terms = sort(terms(os)) + os = Sum(os_sorted_terms) + # Merge (add) terms with same operators + merge_os_data = Scaled{C, Prod{Op}}[] + last_term = copy(os[1]) + last_term_coef = coefficient(last_term) + for n in 2:length(os) + if argument(os[n]) == argument(last_term) + last_term_coef += coefficient(os[n]) + last_term = last_term_coef * argument(last_term) + else + push!(merge_os_data, last_term) + last_term = os[n] + last_term_coef = coefficient(last_term) + end end - end - push!(merge_os_data, last_term) - os = Sum(merge_os_data) - return os + push!(merge_os_data, last_term) + os = Sum(merge_os_data) + return os end """ @@ -287,63 +287,63 @@ H = MPO(Float32,os,sites) H = MPO(os,sites; splitblocks=false) ``` """ -function MPO(os::OpSum, sites::Vector{<:Index}; splitblocks=true, kwargs...)::MPO - length(terms(os)) == 0 && error("OpSum has no terms") - - os = deepcopy(os) - os = sorteachterm(os, sites) - os = sortmergeterms(os) - - if hasqns(sites[1]) - return qn_svdMPO(os, sites; kwargs...) - end - M = svdMPO(os, sites; kwargs...) - if splitblocks - M = ITensors.splitblocks(linkinds, M) - end - return M +function MPO(os::OpSum, sites::Vector{<:Index}; splitblocks = true, kwargs...)::MPO + length(terms(os)) == 0 && error("OpSum has no terms") + + os = deepcopy(os) + os = sorteachterm(os, sites) + os = sortmergeterms(os) + + if hasqns(sites[1]) + return qn_svdMPO(os, sites; kwargs...) + end + M = svdMPO(os, sites; kwargs...) + if splitblocks + M = ITensors.splitblocks(linkinds, M) + end + return M end function MPO(elt::Type{<:Number}, os::OpSum, sites::Vector{<:Index}; kwargs...) - return NDTensors.convert_scalartype(elt, MPO(os, sites; kwargs...)) + return NDTensors.convert_scalartype(elt, MPO(os, sites; kwargs...)) end # Conversion from other formats function MPO(eltype::Type{<:Number}, o::Op, s::Vector{<:Index}; kwargs...) - return MPO(eltype, OpSum{Float64}() + o, s; kwargs...) + return MPO(eltype, OpSum{Float64}() + o, s; kwargs...) end function MPO( - eltype::Type{<:Number}, o::Scaled{C,Op}, s::Vector{<:Index}; kwargs... -) where {C} - return MPO(eltype, OpSum{C}() + o, s; kwargs...) + eltype::Type{<:Number}, o::Scaled{C, Op}, s::Vector{<:Index}; kwargs... + ) where {C} + return MPO(eltype, OpSum{C}() + o, s; kwargs...) end function MPO(eltype::Type{<:Number}, o::Sum{Op}, s::Vector{<:Index}; kwargs...) - return MPO(eltype, OpSum{Float64}() + o, s; kwargs...) + return MPO(eltype, OpSum{Float64}() + o, s; kwargs...) end function MPO(eltype::Type{<:Number}, o::Prod{Op}, s::Vector{<:Index}; kwargs...) - return MPO(eltype, OpSum{Float64}() + o, s; kwargs...) + return MPO(eltype, OpSum{Float64}() + o, s; kwargs...) end function MPO( - eltype::Type{<:Number}, o::Scaled{C,Prod{Op}}, s::Vector{<:Index}; kwargs... -) where {C} - return MPO(eltype, OpSum{C}() + o, s; kwargs...) + eltype::Type{<:Number}, o::Scaled{C, Prod{Op}}, s::Vector{<:Index}; kwargs... + ) where {C} + return MPO(eltype, OpSum{C}() + o, s; kwargs...) end function MPO( - eltype::Type{<:Number}, o::Sum{Scaled{C,Op}}, s::Vector{<:Index}; kwargs... -) where {C} - return MPO(eltype, OpSum{C}() + o, s; kwargs...) + eltype::Type{<:Number}, o::Sum{Scaled{C, Op}}, s::Vector{<:Index}; kwargs... + ) where {C} + return MPO(eltype, OpSum{C}() + o, s; kwargs...) end # Like `Ops.OpSumLike` but without `OpSum` included. const OpSumLikeWithoutOpSum{C} = Union{ - Op,Scaled{C,Op},Sum{Op},Prod{Op},Scaled{C,Prod{Op}},Sum{Scaled{C,Op}} + Op, Scaled{C, Op}, Sum{Op}, Prod{Op}, Scaled{C, Prod{Op}}, Sum{Scaled{C, Op}}, } function MPO(o::OpSumLikeWithoutOpSum, s::Vector{<:Index}; kwargs...) - return MPO(Float64, o, s; kwargs...) + return MPO(Float64, o, s; kwargs...) end diff --git a/src/opsum_to_mpo/opsum_to_mpo_qn.jl b/src/opsum_to_mpo/opsum_to_mpo_qn.jl index 25f75b3..341dc71 100644 --- a/src/opsum_to_mpo/opsum_to_mpo_qn.jl +++ b/src/opsum_to_mpo/opsum_to_mpo_qn.jl @@ -3,259 +3,259 @@ using NDTensors: using_auto_fermion # `ValType::Type{<:Number}` is used instead of `ValType::Type` for efficiency, possibly due to increased method specialization. # See https://github.com/ITensor/ITensors.jl/pull/1183. function qn_svdMPO( - ValType::Type{<:Number}, os::OpSum{C}, sites; mindim=1, maxdim=typemax(Int), cutoff=1e-15 -)::MPO where {C} - N = length(sites) - - # Specifying the element type with `Dict{QN,Matrix{ValType}}[...]` improves type inference and therefore efficiency. - # See https://github.com/ITensor/ITensors.jl/pull/1183. - Vs = Dict{QN,Matrix{ValType}}[Dict{QN,Matrix{ValType}}() for n in 1:(N + 1)] - sparse_MPO = [QNMatElem{Scaled{C,Prod{Op}}}[] for n in 1:N] - - function crosses_bond(t::Scaled{C,Prod{Op}}, n::Int) - return (only(site(t[1])) <= n <= only(site(t[end]))) - end - - # A cache of the ITensor operators on a certain site - # of a certain type - op_cache = Dict{Pair{String,Int},ITensor}() - function calcQN(term::Vector{Op}) - q = QN() - for st in term - op_tensor = get(op_cache, which_op(st) => only(site(st)), nothing) - if op_tensor === nothing - op_tensor = op(sites[only(site(st))], which_op(st); params(st)...) - op_cache[which_op(st) => only(site(st))] = op_tensor - end - q -= flux(op_tensor) + ValType::Type{<:Number}, os::OpSum{C}, sites; mindim = 1, maxdim = typemax(Int), cutoff = 1.0e-15 + )::MPO where {C} + N = length(sites) + + # Specifying the element type with `Dict{QN,Matrix{ValType}}[...]` improves type inference and therefore efficiency. + # See https://github.com/ITensor/ITensors.jl/pull/1183. + Vs = Dict{QN, Matrix{ValType}}[Dict{QN, Matrix{ValType}}() for n in 1:(N + 1)] + sparse_MPO = [QNMatElem{Scaled{C, Prod{Op}}}[] for n in 1:N] + + function crosses_bond(t::Scaled{C, Prod{Op}}, n::Int) + return (only(site(t[1])) <= n <= only(site(t[end]))) end - return q - end - - Hflux = -calcQN(terms(first(terms(os)))) - - rightmap = Dict{Pair{Vector{Op},QN},Int}() - next_rightmap = Dict{Pair{Vector{Op},QN},Int}() - - for n in 1:N - h_sparse = Dict{QN,Vector{MatElem{ValType}}}() - - leftmap = Dict{Pair{Vector{Op},QN},Int}() - for term in os - crosses_bond(term, n) || continue - - left = filter(t -> (only(site(t)) < n), terms(term)) - onsite = filter(t -> (only(site(t)) == n), terms(term)) - right = filter(t -> (only(site(t)) > n), terms(term)) - - lqn = calcQN(left) - sqn = calcQN(onsite) - - bond_row = -1 - bond_col = -1 - if !isempty(left) - bond_row = posInLink!(leftmap, left => lqn) - bond_col = posInLink!(rightmap, vcat(onsite, right) => lqn) - bond_coef = convert(ValType, coefficient(term)) - q_h_sparse = get!(h_sparse, lqn, MatElem{ValType}[]) - push!(q_h_sparse, MatElem(bond_row, bond_col, bond_coef)) - end - - rqn = sqn + lqn - A_row = bond_col - A_col = posInLink!(next_rightmap, right => rqn) - site_coef = one(C) - if A_row == -1 - site_coef = coefficient(term) - end - if isempty(onsite) - if !using_auto_fermion() && isfermionic(right, sites) - push!(onsite, Op("F", n)) - else - push!(onsite, Op("Id", n)) + + # A cache of the ITensor operators on a certain site + # of a certain type + op_cache = Dict{Pair{String, Int}, ITensor}() + function calcQN(term::Vector{Op}) + q = QN() + for st in term + op_tensor = get(op_cache, which_op(st) => only(site(st)), nothing) + if op_tensor === nothing + op_tensor = op(sites[only(site(st))], which_op(st); params(st)...) + op_cache[which_op(st) => only(site(st))] = op_tensor + end + q -= flux(op_tensor) end - end - el = QNMatElem(lqn, rqn, A_row, A_col, site_coef * Prod(onsite)) - push!(sparse_MPO[n], el) - end - remove_dups!(sparse_MPO[n]) - - if n > 1 && !isempty(h_sparse) - for (q, mat) in h_sparse - h = toMatrix(mat) - U, S, V = svd(h) - P = S .^ 2 - truncate!(P; maxdim, cutoff, mindim) - tdim = length(P) - Vs[n][q] = Matrix{ValType}(V[:, 1:tdim]) - end + return q end - rightmap = next_rightmap - next_rightmap = Dict{Pair{Vector{Op},QN},Int}() - end - - # - # Make MPO link indices - # - llinks = Vector{QNIndex}(undef, N + 1) - # Set dir=In for fermionic ordering, avoid arrow sign - # : - linkdir = using_auto_fermion() ? ITensors.In : ITensors.Out - llinks[1] = Index([QN() => 1, Hflux => 1]; tags="Link,l=0", dir=linkdir) - for n in 1:N - qi = Vector{Pair{QN,Int}}() - push!(qi, QN() => 1) - for (q, Vq) in Vs[n + 1] - cols = size(Vq, 2) - if using_auto_fermion() # - push!(qi, (-q) => cols) - else - push!(qi, q => cols) - end - end - push!(qi, Hflux => 1) - llinks[n + 1] = Index(qi...; tags="Link,l=$n", dir=linkdir) - end - - H = MPO(N) - - # Find location where block of Index i - # matches QN q, but *not* 1 or dim(i) - # which are special ending/starting states - function qnblock(i::Index, q::QN) - for b in 2:(nblocks(i) - 1) - flux(i, Block(b)) == q && return b - end - return error("Could not find block of QNIndex with matching QN") - end - qnblockdim(i::Index, q::QN) = blockdim(i, qnblock(i, q)) - - for n in 1:N - ll = llinks[n] - rl = llinks[n + 1] - - begin_block = Dict{Tuple{QN,Vector{Op}},Matrix{ValType}}() - cont_block = Dict{Tuple{QN,Vector{Op}},Matrix{ValType}}() - end_block = Dict{Tuple{QN,Vector{Op}},Matrix{ValType}}() - onsite_block = Dict{Tuple{QN,Vector{Op}},Matrix{ValType}}() - - for el in sparse_MPO[n] - t = el.val - (abs(coefficient(t)) > eps()) || continue - A_row = el.row - A_col = el.col - ct = convert(ValType, coefficient(t)) - - ldim = (A_row == -1) ? 1 : qnblockdim(ll, el.rowqn) - rdim = (A_col == -1) ? 1 : qnblockdim(rl, el.colqn) - zero_mat() = zeros(ValType, ldim, rdim) - - if A_row == -1 && A_col == -1 - # Onsite term - M = get!(onsite_block, (el.rowqn, terms(t)), zeros(ValType, 1, 1)) - M[1, 1] += ct - elseif A_row == -1 - # Operator beginning a term on site n - M = get!(begin_block, (el.rowqn, terms(t)), zero_mat()) - VR = Vs[n + 1][el.colqn] - for c in 1:size(VR, 2) - M[1, c] += ct * VR[A_col, c] + Hflux = -calcQN(terms(first(terms(os)))) + + rightmap = Dict{Pair{Vector{Op}, QN}, Int}() + next_rightmap = Dict{Pair{Vector{Op}, QN}, Int}() + + for n in 1:N + h_sparse = Dict{QN, Vector{MatElem{ValType}}}() + + leftmap = Dict{Pair{Vector{Op}, QN}, Int}() + for term in os + crosses_bond(term, n) || continue + + left = filter(t -> (only(site(t)) < n), terms(term)) + onsite = filter(t -> (only(site(t)) == n), terms(term)) + right = filter(t -> (only(site(t)) > n), terms(term)) + + lqn = calcQN(left) + sqn = calcQN(onsite) + + bond_row = -1 + bond_col = -1 + if !isempty(left) + bond_row = posInLink!(leftmap, left => lqn) + bond_col = posInLink!(rightmap, vcat(onsite, right) => lqn) + bond_coef = convert(ValType, coefficient(term)) + q_h_sparse = get!(h_sparse, lqn, MatElem{ValType}[]) + push!(q_h_sparse, MatElem(bond_row, bond_col, bond_coef)) + end + + rqn = sqn + lqn + A_row = bond_col + A_col = posInLink!(next_rightmap, right => rqn) + site_coef = one(C) + if A_row == -1 + site_coef = coefficient(term) + end + if isempty(onsite) + if !using_auto_fermion() && isfermionic(right, sites) + push!(onsite, Op("F", n)) + else + push!(onsite, Op("Id", n)) + end + end + el = QNMatElem(lqn, rqn, A_row, A_col, site_coef * Prod(onsite)) + push!(sparse_MPO[n], el) + end + remove_dups!(sparse_MPO[n]) + + if n > 1 && !isempty(h_sparse) + for (q, mat) in h_sparse + h = toMatrix(mat) + U, S, V = svd(h) + P = S .^ 2 + truncate!(P; maxdim, cutoff, mindim) + tdim = length(P) + Vs[n][q] = Matrix{ValType}(V[:, 1:tdim]) + end end - elseif A_col == -1 - # Operator ending a term on site n - M = get!(end_block, (el.rowqn, terms(t)), zero_mat()) - VL = Vs[n][el.rowqn] - for r in 1:size(VL, 2) - M[r, 1] += ct * conj(VL[A_row, r]) + + rightmap = next_rightmap + next_rightmap = Dict{Pair{Vector{Op}, QN}, Int}() + end + + # + # Make MPO link indices + # + llinks = Vector{QNIndex}(undef, N + 1) + # Set dir=In for fermionic ordering, avoid arrow sign + # : + linkdir = using_auto_fermion() ? ITensors.In : ITensors.Out + llinks[1] = Index([QN() => 1, Hflux => 1]; tags = "Link,l=0", dir = linkdir) + for n in 1:N + qi = Vector{Pair{QN, Int}}() + push!(qi, QN() => 1) + for (q, Vq) in Vs[n + 1] + cols = size(Vq, 2) + if using_auto_fermion() # + push!(qi, (-q) => cols) + else + push!(qi, q => cols) + end end - else - # Operator continuing a term on site n - M = get!(cont_block, (el.rowqn, terms(t)), zero_mat()) - VL = Vs[n][el.rowqn] - VR = Vs[n + 1][el.colqn] - for r in 1:size(VL, 2), c in 1:size(VR, 2) - M[r, c] += ct * conj(VL[A_row, r]) * VR[A_col, c] + push!(qi, Hflux => 1) + llinks[n + 1] = Index(qi...; tags = "Link,l=$n", dir = linkdir) + end + + H = MPO(N) + + # Find location where block of Index i + # matches QN q, but *not* 1 or dim(i) + # which are special ending/starting states + function qnblock(i::Index, q::QN) + for b in 2:(nblocks(i) - 1) + flux(i, Block(b)) == q && return b end - end + return error("Could not find block of QNIndex with matching QN") end + qnblockdim(i::Index, q::QN) = blockdim(i, qnblock(i, q)) + + for n in 1:N + ll = llinks[n] + rl = llinks[n + 1] + + begin_block = Dict{Tuple{QN, Vector{Op}}, Matrix{ValType}}() + cont_block = Dict{Tuple{QN, Vector{Op}}, Matrix{ValType}}() + end_block = Dict{Tuple{QN, Vector{Op}}, Matrix{ValType}}() + onsite_block = Dict{Tuple{QN, Vector{Op}}, Matrix{ValType}}() + + for el in sparse_MPO[n] + t = el.val + (abs(coefficient(t)) > eps()) || continue + A_row = el.row + A_col = el.col + ct = convert(ValType, coefficient(t)) + + ldim = (A_row == -1) ? 1 : qnblockdim(ll, el.rowqn) + rdim = (A_col == -1) ? 1 : qnblockdim(rl, el.colqn) + zero_mat() = zeros(ValType, ldim, rdim) + + if A_row == -1 && A_col == -1 + # Onsite term + M = get!(onsite_block, (el.rowqn, terms(t)), zeros(ValType, 1, 1)) + M[1, 1] += ct + elseif A_row == -1 + # Operator beginning a term on site n + M = get!(begin_block, (el.rowqn, terms(t)), zero_mat()) + VR = Vs[n + 1][el.colqn] + for c in 1:size(VR, 2) + M[1, c] += ct * VR[A_col, c] + end + elseif A_col == -1 + # Operator ending a term on site n + M = get!(end_block, (el.rowqn, terms(t)), zero_mat()) + VL = Vs[n][el.rowqn] + for r in 1:size(VL, 2) + M[r, 1] += ct * conj(VL[A_row, r]) + end + else + # Operator continuing a term on site n + M = get!(cont_block, (el.rowqn, terms(t)), zero_mat()) + VL = Vs[n][el.rowqn] + VR = Vs[n + 1][el.colqn] + for r in 1:size(VL, 2), c in 1:size(VR, 2) + M[r, c] += ct * conj(VL[A_row, r]) * VR[A_col, c] + end + end + end - H[n] = ITensor() - - # Helper functions to compute block locations - # of various blocks within the onsite blocks, - # begin blocks, etc. - loc_onsite(rq, cq) = Block(nblocks(ll), 1) - loc_begin(rq, cq) = Block(nblocks(ll), qnblock(rl, cq)) - loc_cont(rq, cq) = Block(qnblock(ll, rq), qnblock(rl, cq)) - loc_end(rq, cq) = Block(qnblock(ll, rq), 1) - - for (loc, block) in ( - (loc_onsite, onsite_block), - (loc_begin, begin_block), - (loc_end, end_block), - (loc_cont, cont_block), - ) - for (q_op, M) in block - op_prod = q_op[2] - Op = computeSiteProd(sites, Prod(op_prod)) - (nnzblocks(Op) == 0) && continue - - rq = q_op[1] - sq = flux(Op) - cq = rq - if !isnothing(sq) - # By convention, if `Op` has no blocks it has a flux - # of `nothing`, catch this case - cq -= sq - - if using_auto_fermion() - # : - # MPO is defined with Index order - # of (rl,s[n]',s[n],cl) where rl = row link, cl = col link - # so compute sign that would result by permuting cl from - # second position to last position: - if fparity(sq) == 1 && fparity(cq) == 1 - Op .*= -1 + H[n] = ITensor() + + # Helper functions to compute block locations + # of various blocks within the onsite blocks, + # begin blocks, etc. + loc_onsite(rq, cq) = Block(nblocks(ll), 1) + loc_begin(rq, cq) = Block(nblocks(ll), qnblock(rl, cq)) + loc_cont(rq, cq) = Block(qnblock(ll, rq), qnblock(rl, cq)) + loc_end(rq, cq) = Block(qnblock(ll, rq), 1) + + for (loc, block) in ( + (loc_onsite, onsite_block), + (loc_begin, begin_block), + (loc_end, end_block), + (loc_cont, cont_block), + ) + for (q_op, M) in block + op_prod = q_op[2] + Op = computeSiteProd(sites, Prod(op_prod)) + (nnzblocks(Op) == 0) && continue + + rq = q_op[1] + sq = flux(Op) + cq = rq + if !isnothing(sq) + # By convention, if `Op` has no blocks it has a flux + # of `nothing`, catch this case + cq -= sq + + if using_auto_fermion() + # : + # MPO is defined with Index order + # of (rl,s[n]',s[n],cl) where rl = row link, cl = col link + # so compute sign that would result by permuting cl from + # second position to last position: + if fparity(sq) == 1 && fparity(cq) == 1 + Op .*= -1 + end + end + end + + b = loc(rq, cq) + T = ITensors.NDTensors.BlockSparseTensor(ValType, [b], (dag(ll), rl)) + T[b] .= M + + H[n] += (itensor(T) * Op) end - end end - b = loc(rq, cq) + # Put in ending identity operator + Id = op("Id", sites[n]) + b = Block(1, 1) T = ITensors.NDTensors.BlockSparseTensor(ValType, [b], (dag(ll), rl)) - T[b] .= M + T[b] = 1 + H[n] += (itensor(T) * Id) - H[n] += (itensor(T) * Op) - end - end + # Put in starting identity operator + b = Block(nblocks(ll), nblocks(rl)) + T = ITensors.NDTensors.BlockSparseTensor(ValType, [b], (dag(ll), rl)) + T[b] = 1 + H[n] += (itensor(T) * Id) + end # for n in 1:N + + L = ITensor(llinks[1]) + L[llinks[1] => end] = 1.0 + H[1] *= L + + R = ITensor(dag(llinks[N + 1])) + R[dag(llinks[N + 1]) => 1] = 1.0 + H[N] *= R - # Put in ending identity operator - Id = op("Id", sites[n]) - b = Block(1, 1) - T = ITensors.NDTensors.BlockSparseTensor(ValType, [b], (dag(ll), rl)) - T[b] = 1 - H[n] += (itensor(T) * Id) - - # Put in starting identity operator - b = Block(nblocks(ll), nblocks(rl)) - T = ITensors.NDTensors.BlockSparseTensor(ValType, [b], (dag(ll), rl)) - T[b] = 1 - H[n] += (itensor(T) * Id) - end # for n in 1:N - - L = ITensor(llinks[1]) - L[llinks[1] => end] = 1.0 - H[1] *= L - - R = ITensor(dag(llinks[N + 1])) - R[dag(llinks[N + 1]) => 1] = 1.0 - H[N] *= R - - return H + return H end #qn_svdMPO function qn_svdMPO(os::OpSum{C}, sites; kwargs...)::MPO where {C} - # Function barrier to improve type stability - ValType = determineValType(terms(os)) - return qn_svdMPO(ValType, os, sites; kwargs...) + # Function barrier to improve type stability + ValType = determineValType(terms(os)) + return qn_svdMPO(ValType, os, sites; kwargs...) end diff --git a/src/opsum_to_mpo/qnmatelem.jl b/src/opsum_to_mpo/qnmatelem.jl index 7ec55c4..5c21646 100644 --- a/src/opsum_to_mpo/qnmatelem.jl +++ b/src/opsum_to_mpo/qnmatelem.jl @@ -1,30 +1,30 @@ struct QNMatElem{T} - rowqn::QN - colqn::QN - row::Int - col::Int - val::T + rowqn::QN + colqn::QN + row::Int + col::Int + val::T end function Base.:(==)(m1::QNMatElem{T}, m2::QNMatElem{T})::Bool where {T} - return ( - m1.row == m2.row && - m1.col == m2.col && - m1.val == m2.val && - m1.rowqn == m2.rowqn && - m1.colqn == m2.colqn - ) + return ( + m1.row == m2.row && + m1.col == m2.col && + m1.val == m2.val && + m1.rowqn == m2.rowqn && + m1.colqn == m2.colqn + ) end function Base.isless(m1::QNMatElem{T}, m2::QNMatElem{T})::Bool where {T} - if m1.rowqn != m2.rowqn - return m1.rowqn < m2.rowqn - elseif m1.colqn != m2.colqn - return m1.colqn < m2.colqn - elseif m1.row != m2.row - return m1.row < m2.row - elseif m1.col != m2.col - return m1.col < m2.col - end - return m1.val < m2.val + if m1.rowqn != m2.rowqn + return m1.rowqn < m2.rowqn + elseif m1.colqn != m2.colqn + return m1.colqn < m2.colqn + elseif m1.row != m2.row + return m1.row < m2.row + elseif m1.col != m2.col + return m1.col < m2.col + end + return m1.val < m2.val end diff --git a/src/solvers/ITensorsExtensions.jl b/src/solvers/ITensorsExtensions.jl index 90ed8b1..fc5ee9d 100644 --- a/src/solvers/ITensorsExtensions.jl +++ b/src/solvers/ITensorsExtensions.jl @@ -1,9 +1,9 @@ module ITensorsExtensions using ITensors: ITensor, array, inds, itensor function to_vec(x::ITensor) - function to_itensor(x_vec) - return itensor(x_vec, inds(x)) - end - return vec(array(x)), to_itensor + function to_itensor(x_vec) + return itensor(x_vec, inds(x)) + end + return vec(array(x)), to_itensor end end diff --git a/src/solvers/alternating_update.jl b/src/solvers/alternating_update.jl index 6206123..8cdac45 100644 --- a/src/solvers/alternating_update.jl +++ b/src/solvers/alternating_update.jl @@ -1,114 +1,114 @@ using ITensors: ITensors, permute function _extend_sweeps_param(param, nsweeps) - if param isa Number - eparam = fill(param, nsweeps) - else - length(param) == nsweeps && return param - eparam = Vector(undef, nsweeps) - eparam[1:length(param)] = param - eparam[(length(param) + 1):end] .= param[end] - end - return eparam + if param isa Number + eparam = fill(param, nsweeps) + else + length(param) == nsweeps && return param + eparam = Vector(undef, nsweeps) + eparam[1:length(param)] = param + eparam[(length(param) + 1):end] .= param[end] + end + return eparam end function process_sweeps(; nsweeps, maxdim, mindim, cutoff, noise) - maxdim = _extend_sweeps_param(maxdim, nsweeps) - mindim = _extend_sweeps_param(mindim, nsweeps) - cutoff = _extend_sweeps_param(cutoff, nsweeps) - noise = _extend_sweeps_param(noise, nsweeps) - return (; maxdim, mindim, cutoff, noise) + maxdim = _extend_sweeps_param(maxdim, nsweeps) + mindim = _extend_sweeps_param(mindim, nsweeps) + cutoff = _extend_sweeps_param(cutoff, nsweeps) + noise = _extend_sweeps_param(noise, nsweeps) + return (; maxdim, mindim, cutoff, noise) end function alternating_update( - operator, - init::MPS; - updater, - updater_kwargs=(;), - nsweeps=default_nsweeps(), - checkdone=default_checkdone(), - write_when_maxdim_exceeds=default_write_when_maxdim_exceeds(), - nsite=default_nsite(), - reverse_step=default_reverse_step(), - time_start=default_time_start(), - time_step=default_time_step(), - order=default_order(), - (observer!)=default_observer(), - (sweep_observer!)=default_sweep_observer(), - outputlevel=default_outputlevel(), - normalize=default_normalize(), - maxdim=default_maxdim(), - mindim=default_mindim(), - cutoff=default_cutoff(ITensors.scalartype(init)), - noise=default_noise(), -) - reduced_operator = ITensorMPS.reduced_operator(operator) - if isnothing(nsweeps) - return error("Must specify `nsweeps`.") - end - maxdim, mindim, cutoff, noise = process_sweeps(; nsweeps, maxdim, mindim, cutoff, noise) - forward_order = TDVPOrder(order, Base.Forward) - state = copy(init) - # Keep track of the start of the current time step. - # Helpful for tracking the total time, for example - # when using time-dependent updaters. - # This will be passed as a keyword argument to the - # `updater`. - current_time = time_start - info = nothing - for sweep in 1:nsweeps - if !isnothing(write_when_maxdim_exceeds) && maxdim[sweep] > write_when_maxdim_exceeds - if outputlevel >= 2 - println( - "write_when_maxdim_exceeds = $write_when_maxdim_exceeds and maxdim(sweeps, sw) = $(maxdim(sweeps, sweep)), writing environment tensors to disk", - ) - end - reduced_operator = disk(reduced_operator) - end - sweep_elapsed_time = @elapsed begin - state, reduced_operator, info = sweep_update( - forward_order, - reduced_operator, - state; + operator, + init::MPS; updater, - updater_kwargs, - nsite, - current_time, - time_step, - reverse_step, - sweep, - observer!, - normalize, - outputlevel, - maxdim=maxdim[sweep], - mindim=mindim[sweep], - cutoff=cutoff[sweep], - noise=noise[sweep], - ) - end - if !isnothing(time_step) - current_time += time_step - end - update_observer!( - sweep_observer!; state, reduced_operator, sweep, outputlevel, current_time + updater_kwargs = (;), + nsweeps = default_nsweeps(), + checkdone = default_checkdone(), + write_when_maxdim_exceeds = default_write_when_maxdim_exceeds(), + nsite = default_nsite(), + reverse_step = default_reverse_step(), + time_start = default_time_start(), + time_step = default_time_step(), + order = default_order(), + (observer!) = default_observer(), + (sweep_observer!) = default_sweep_observer(), + outputlevel = default_outputlevel(), + normalize = default_normalize(), + maxdim = default_maxdim(), + mindim = default_mindim(), + cutoff = default_cutoff(ITensors.scalartype(init)), + noise = default_noise(), ) - if outputlevel >= 1 - print("After sweep ", sweep, ":") - print(" maxlinkdim=", maxlinkdim(state)) - @printf(" maxerr=%.2E", info.maxtruncerr) - if !isnothing(current_time) - print(" current_time=", round(current_time; digits=3)) - end - print(" time=", round(sweep_elapsed_time; digits=3)) - println() - flush(stdout) + reduced_operator = ITensorMPS.reduced_operator(operator) + if isnothing(nsweeps) + return error("Must specify `nsweeps`.") end - isdone = checkdone(; - state, sweep, outputlevel, observer=(observer!), sweep_observer=(sweep_observer!) - ) - isdone && break - end - return state + maxdim, mindim, cutoff, noise = process_sweeps(; nsweeps, maxdim, mindim, cutoff, noise) + forward_order = TDVPOrder(order, Base.Forward) + state = copy(init) + # Keep track of the start of the current time step. + # Helpful for tracking the total time, for example + # when using time-dependent updaters. + # This will be passed as a keyword argument to the + # `updater`. + current_time = time_start + info = nothing + for sweep in 1:nsweeps + if !isnothing(write_when_maxdim_exceeds) && maxdim[sweep] > write_when_maxdim_exceeds + if outputlevel >= 2 + println( + "write_when_maxdim_exceeds = $write_when_maxdim_exceeds and maxdim(sweeps, sw) = $(maxdim(sweeps, sweep)), writing environment tensors to disk", + ) + end + reduced_operator = disk(reduced_operator) + end + sweep_elapsed_time = @elapsed begin + state, reduced_operator, info = sweep_update( + forward_order, + reduced_operator, + state; + updater, + updater_kwargs, + nsite, + current_time, + time_step, + reverse_step, + sweep, + observer!, + normalize, + outputlevel, + maxdim = maxdim[sweep], + mindim = mindim[sweep], + cutoff = cutoff[sweep], + noise = noise[sweep], + ) + end + if !isnothing(time_step) + current_time += time_step + end + update_observer!( + sweep_observer!; state, reduced_operator, sweep, outputlevel, current_time + ) + if outputlevel >= 1 + print("After sweep ", sweep, ":") + print(" maxlinkdim=", maxlinkdim(state)) + @printf(" maxerr=%.2E", info.maxtruncerr) + if !isnothing(current_time) + print(" current_time=", round(current_time; digits = 3)) + end + print(" time=", round(sweep_elapsed_time; digits = 3)) + println() + flush(stdout) + end + isdone = checkdone(; + state, sweep, outputlevel, observer = (observer!), sweep_observer = (sweep_observer!) + ) + isdone && break + end + return state end # Assume it is already in a reduced basis. diff --git a/src/solvers/applyexp.jl b/src/solvers/applyexp.jl index 92121b8..607feca 100644 --- a/src/solvers/applyexp.jl +++ b/src/solvers/applyexp.jl @@ -8,124 +8,124 @@ using Printf: @printf # function assemble_lanczos_vecs(lanczos_vectors, linear_comb, norm) - #if length(lanczos_vectors) != length(linear_comb) - # @show length(lanczos_vectors) - # @show length(linear_comb) - #end - xt = norm * linear_comb[1] * lanczos_vectors[1] - for i in 2:length(lanczos_vectors) - xt += norm * linear_comb[i] * lanczos_vectors[i] - end - return xt + #if length(lanczos_vectors) != length(linear_comb) + # @show length(lanczos_vectors) + # @show length(linear_comb) + #end + xt = norm * linear_comb[1] * lanczos_vectors[1] + for i in 2:length(lanczos_vectors) + xt += norm * linear_comb[i] * lanczos_vectors[i] + end + return xt end struct ApplyExpInfo - numops::Int - converged::Int + numops::Int + converged::Int end -function applyexp(H, tau::Number, x0; maxiter=30, tol=1e-12, outputlevel=0, normcutoff=1e-7) - # Initialize Lanczos vectors - v1 = copy(x0) - nrm = norm(v1) - v1 /= nrm - lanczos_vectors = [v1] - - ElT = promote_type(typeof(tau), eltype(x0)) +function applyexp(H, tau::Number, x0; maxiter = 30, tol = 1.0e-12, outputlevel = 0, normcutoff = 1.0e-7) + # Initialize Lanczos vectors + v1 = copy(x0) + nrm = norm(v1) + v1 /= nrm + lanczos_vectors = [v1] - bigTmat = zeros(ElT, maxiter + 3, maxiter + 3) + ElT = promote_type(typeof(tau), eltype(x0)) - nmatvec = 0 + bigTmat = zeros(ElT, maxiter + 3, maxiter + 3) - v0 = nothing - beta = 0.0 - for iter in 1:maxiter - tmat_size = iter + 1 + nmatvec = 0 - # Matrix-vector multiplication - w = H(v1) - nmatvec += 1 + v0 = nothing + beta = 0.0 + for iter in 1:maxiter + tmat_size = iter + 1 - avnorm = norm(w) - alpha = dot(w, v1) + # Matrix-vector multiplication + w = H(v1) + nmatvec += 1 - bigTmat[iter, iter] = alpha + avnorm = norm(w) + alpha = dot(w, v1) - w -= alpha * v1 - if iter > 1 - w -= beta * v0 - end - v0 = copy(v1) - - beta = norm(w) - - # check for Lanczos sequence exhaustion - if abs(beta) < normcutoff - # Assemble the time evolved state - tmat = bigTmat[1:tmat_size, 1:tmat_size] - tmat_exp = exp(tau * tmat) - linear_comb = tmat_exp[:, 1] - xt = assemble_lanczos_vecs(lanczos_vectors, linear_comb, nrm) - return xt, ApplyExpInfo(nmatvec, 1) - end + bigTmat[iter, iter] = alpha - # update next lanczos vector - v1 = copy(w) - v1 /= beta - push!(lanczos_vectors, v1) - bigTmat[iter + 1, iter] = beta - bigTmat[iter, iter + 1] = beta - - # Convergence check - if iter > 0 - # Prepare extended T-matrix for exponentiation - tmat_ext_size = tmat_size + 2 - tmat_ext = bigTmat[1:tmat_ext_size, 1:tmat_ext_size] - - tmat_ext[tmat_size - 1, tmat_size] = 0.0 - tmat_ext[tmat_size + 1, tmat_size] = 1.0 - - # Exponentiate extended T-matrix - tmat_ext_exp = exp(tau * tmat_ext) - - ϕ1 = abs(nrm * tmat_ext_exp[tmat_size, 1]) - ϕ2 = abs(nrm * tmat_ext_exp[tmat_size + 1, 1] * avnorm) - - if ϕ1 > 10 * ϕ2 - error = ϕ2 - elseif (ϕ1 > ϕ2) - error = (ϕ1 * ϕ2) / (ϕ1 - ϕ2) - else - error = ϕ1 - end - - if outputlevel >= 3 - @printf(" Iteration: %d, Error: %.2E\n", iter, error) - end - - if ((error < tol) || (iter == maxiter)) - converged = 1 - if (iter == maxiter) - println("warning: applyexp not converged in $maxiter steps") - converged = 0 + w -= alpha * v1 + if iter > 1 + w -= beta * v0 end - - # Assemble the time evolved state - linear_comb = tmat_ext_exp[:, 1] - xt = assemble_lanczos_vecs(lanczos_vectors, linear_comb, nrm) - - if outputlevel >= 3 - println(" Number of iterations: $iter") + v0 = copy(v1) + + beta = norm(w) + + # check for Lanczos sequence exhaustion + if abs(beta) < normcutoff + # Assemble the time evolved state + tmat = bigTmat[1:tmat_size, 1:tmat_size] + tmat_exp = exp(tau * tmat) + linear_comb = tmat_exp[:, 1] + xt = assemble_lanczos_vecs(lanczos_vectors, linear_comb, nrm) + return xt, ApplyExpInfo(nmatvec, 1) end - return xt, ApplyExpInfo(nmatvec, converged) - end - end # end convergence test - end # iter - - if outputlevel >= 0 - println("In applyexp, number of matrix-vector multiplies: ", nmatvec) - end + # update next lanczos vector + v1 = copy(w) + v1 /= beta + push!(lanczos_vectors, v1) + bigTmat[iter + 1, iter] = beta + bigTmat[iter, iter + 1] = beta + + # Convergence check + if iter > 0 + # Prepare extended T-matrix for exponentiation + tmat_ext_size = tmat_size + 2 + tmat_ext = bigTmat[1:tmat_ext_size, 1:tmat_ext_size] + + tmat_ext[tmat_size - 1, tmat_size] = 0.0 + tmat_ext[tmat_size + 1, tmat_size] = 1.0 + + # Exponentiate extended T-matrix + tmat_ext_exp = exp(tau * tmat_ext) + + ϕ1 = abs(nrm * tmat_ext_exp[tmat_size, 1]) + ϕ2 = abs(nrm * tmat_ext_exp[tmat_size + 1, 1] * avnorm) + + if ϕ1 > 10 * ϕ2 + error = ϕ2 + elseif (ϕ1 > ϕ2) + error = (ϕ1 * ϕ2) / (ϕ1 - ϕ2) + else + error = ϕ1 + end + + if outputlevel >= 3 + @printf(" Iteration: %d, Error: %.2E\n", iter, error) + end + + if ((error < tol) || (iter == maxiter)) + converged = 1 + if (iter == maxiter) + println("warning: applyexp not converged in $maxiter steps") + converged = 0 + end + + # Assemble the time evolved state + linear_comb = tmat_ext_exp[:, 1] + xt = assemble_lanczos_vecs(lanczos_vectors, linear_comb, nrm) + + if outputlevel >= 3 + println(" Number of iterations: $iter") + end + + return xt, ApplyExpInfo(nmatvec, converged) + end + end # end convergence test + end # iter + + if outputlevel >= 0 + println("In applyexp, number of matrix-vector multiplies: ", nmatvec) + end - return x0 + return x0 end diff --git a/src/solvers/contract.jl b/src/solvers/contract.jl index 1919fdd..c1c9a51 100644 --- a/src/solvers/contract.jl +++ b/src/solvers/contract.jl @@ -1,41 +1,41 @@ using ITensors: ITensors, Index, ITensor, @Algorithm_str, commoninds, contract, hasind, sim function contract_operator_state_updater(operator, init; internal_kwargs) - # TODO: Use `contract(operator)`. - state = ITensor(true) - for j in (operator.lpos + 1):(operator.rpos - 1) - state *= operator.input_state[j] - end - state = contract(operator, state) - return state, (;) + # TODO: Use `contract(operator)`. + state = ITensor(true) + for j in (operator.lpos + 1):(operator.rpos - 1) + state *= operator.input_state[j] + end + state = contract(operator, state) + return state, (;) end function default_contract_init(operator::MPO, input_state::MPS) - input_state = deepcopy(input_state) - s = only.(siteinds(uniqueinds, operator, input_state)) - # TODO: Fix issue with `replace_siteinds`, seems to be modifying in-place. - return replace_siteinds(deepcopy(input_state), s) + input_state = deepcopy(input_state) + s = only.(siteinds(uniqueinds, operator, input_state)) + # TODO: Fix issue with `replace_siteinds`, seems to be modifying in-place. + return replace_siteinds(deepcopy(input_state), s) end function ITensors.contract( - ::Algorithm"fit", - operator::MPO, - input_state::MPS; - init=default_contract_init(operator, input_state), - kwargs..., -) - # Fix siteinds of `init` if needed. - # This is needed to work around an issue that `apply` - # can't be customized right now, and just uses the same `init` - # as that of `contract`. - # TODO: Allow customization of `apply` and remove this. - s = only.(siteinds(uniqueinds, operator, input_state)) - if !all(p -> p[1] == [2], zip(s, siteinds(init))) - # TODO: Fix issue with `replace_siteinds`, seems to be modifying in-place. - init = replace_siteinds(deepcopy(init), s) - end - reduced_operator = ReducedContractProblem(input_state, operator) - return alternating_update( - reduced_operator, init; updater=contract_operator_state_updater, kwargs... - ) + ::Algorithm"fit", + operator::MPO, + input_state::MPS; + init = default_contract_init(operator, input_state), + kwargs..., + ) + # Fix siteinds of `init` if needed. + # This is needed to work around an issue that `apply` + # can't be customized right now, and just uses the same `init` + # as that of `contract`. + # TODO: Allow customization of `apply` and remove this. + s = only.(siteinds(uniqueinds, operator, input_state)) + if !all(p -> p[1] == [2], zip(s, siteinds(init))) + # TODO: Fix issue with `replace_siteinds`, seems to be modifying in-place. + init = replace_siteinds(deepcopy(init), s) + end + reduced_operator = ReducedContractProblem(input_state, operator) + return alternating_update( + reduced_operator, init; updater = contract_operator_state_updater, kwargs... + ) end diff --git a/src/solvers/dmrg.jl b/src/solvers/dmrg.jl index 90fc9df..33f84e6 100644 --- a/src/solvers/dmrg.jl +++ b/src/solvers/dmrg.jl @@ -2,22 +2,22 @@ using ITensors: ITensors using KrylovKit: eigsolve function eigsolve_updater( - operator, - init; - internal_kwargs, - which_eigval=:SR, - ishermitian=true, - tol=10^2 * eps(real(ITensors.scalartype(init))), - krylovdim=3, - maxiter=1, - verbosity=0, - eager=false, -) - howmany = 1 - eigvals, eigvecs, info = eigsolve( - operator, init, howmany, which_eigval; ishermitian, tol, krylovdim, maxiter, verbosity - ) - return eigvecs[1], (; info, eigval=eigvals[1]) + operator, + init; + internal_kwargs, + which_eigval = :SR, + ishermitian = true, + tol = 10^2 * eps(real(ITensors.scalartype(init))), + krylovdim = 3, + maxiter = 1, + verbosity = 0, + eager = false, + ) + howmany = 1 + eigvals, eigvecs, info = eigsolve( + operator, init, howmany, which_eigval; ishermitian, tol, krylovdim, maxiter, verbosity + ) + return eigvecs[1], (; info, eigval = eigvals[1]) end # A version of `dmrg` based on `alternating_update` and `eigsolve_updater` diff --git a/src/solvers/dmrg_x.jl b/src/solvers/dmrg_x.jl index 48201e6..8d1edca 100644 --- a/src/solvers/dmrg_x.jl +++ b/src/solvers/dmrg_x.jl @@ -2,22 +2,22 @@ using ITensors: array, contract, dag, uniqueind, onehot using LinearAlgebra: eigen function eigen_updater(operator, state; internal_kwargs) - contracted_operator = contract(operator, ITensor(true)) - d, u = eigen(contracted_operator; ishermitian=true) - u_ind = uniqueind(u, contracted_operator) - u′_ind = uniqueind(d, u) - max_overlap, max_index = findmax(abs, array(state * dag(u))) - u_max = u * dag(onehot(eltype(u), u_ind => max_index)) - d_max = d[u′_ind => max_index, u_ind => max_index] - return u_max, (; eigval=d_max) + contracted_operator = contract(operator, ITensor(true)) + d, u = eigen(contracted_operator; ishermitian = true) + u_ind = uniqueind(u, contracted_operator) + u′_ind = uniqueind(d, u) + max_overlap, max_index = findmax(abs, array(state * dag(u))) + u_max = u * dag(onehot(eltype(u), u_ind => max_index)) + d_max = d[u′_ind => max_index, u_ind => max_index] + return u_max, (; eigval = d_max) end function dmrg_x( - operator, state::MPS; updater=eigen_updater, (observer!)=default_observer(), kwargs... -) - info_ref = Ref{Any}() - info_observer = values_observer(; info=info_ref) - observer = compose_observers(observer!, info_observer) - eigvec = alternating_update(operator, state; updater, (observer!)=observer, kwargs...) - return info_ref[].eigval, eigvec + operator, state::MPS; updater = eigen_updater, (observer!) = default_observer(), kwargs... + ) + info_ref = Ref{Any}() + info_observer = values_observer(; info = info_ref) + observer = compose_observers(observer!, info_observer) + eigvec = alternating_update(operator, state; updater, (observer!) = observer, kwargs...) + return info_ref[].eigval, eigvec end diff --git a/src/solvers/expand.jl b/src/solvers/expand.jl index c970e45..0e09ed3 100644 --- a/src/solvers/expand.jl +++ b/src/solvers/expand.jl @@ -1,19 +1,19 @@ using Adapt: adapt using ITensors: - ITensors, - Algorithm, - Index, - ITensor, - @Algorithm_str, - δ, - commonind, - dag, - denseblocks, - directsum, - hasqns, - prime, - scalartype, - uniqueinds + ITensors, + Algorithm, + Index, + ITensor, + @Algorithm_str, + δ, + commonind, + dag, + denseblocks, + directsum, + hasqns, + prime, + scalartype, + uniqueinds using LinearAlgebra: normalize, svd, tr using NDTensors: unwrap_array_type @@ -27,32 +27,32 @@ using NDTensors: unwrap_array_type # instead of operator|state>. Is that needed? function expand(state, reference; alg, kwargs...) - return expand(Algorithm(alg), state, reference; kwargs...) + return expand(Algorithm(alg), state, reference; kwargs...) end function expand_cutoff_doctring() - return """ - The cutoff is used to control the truncation of the expanded - basis and defaults to half the precision of the scalar type - of the input state, i.e. ~1e-8 for `Float64`. - """ + return """ + The cutoff is used to control the truncation of the expanded + basis and defaults to half the precision of the scalar type + of the input state, i.e. ~1e-8 for `Float64`. + """ end function expand_warning_doctring() - return """ - !!! warning - Users are not given many customization options just yet as we - gain more experience on the right balance between efficacy of the - expansion and performance in various scenarios, and default values - and keyword arguments are subject to change as we learn more about - how to best use the method. - """ + return """ + !!! warning + Users are not given many customization options just yet as we + gain more experience on the right balance between efficacy of the + expansion and performance in various scenarios, and default values + and keyword arguments are subject to change as we learn more about + how to best use the method. + """ end function expand_citation_docstring() - return """ - [^global_expansion]: Time Dependent Variational Principle with Ancillary Krylov Subspace. Mingru Yang, Steven R. White, [arXiv:2005.06104](https://arxiv.org/abs/2005.06104) - """ + return """ + [^global_expansion]: Time Dependent Variational Principle with Ancillary Krylov Subspace. Mingru Yang, Steven R. White, [arXiv:2005.06104](https://arxiv.org/abs/2005.06104) + """ end """ @@ -72,54 +72,54 @@ $(expand_warning_doctring()) $(expand_citation_docstring()) """ function expand( - ::Algorithm"orthogonalize", - state::MPS, - references::Vector{MPS}; - cutoff=(√(eps(real(scalartype(state))))), -) - n = length(state) - state = orthogonalize(state, n) - references = map(reference -> orthogonalize(reference, n), references) - s = siteinds(state) - for j in reverse(2:n) - # SVD state[j] to compute basisⱼ - linds = [s[j - 1]; linkinds(state, j - 1)] - _, λⱼ, basisⱼ = svd(state[j], linds; righttags="bψ_$j,Link") - rinds = uniqueinds(basisⱼ, λⱼ) - # Make projectorⱼ - idⱼ = prod(rinds) do r - return adapt(unwrap_array_type(basisⱼ), denseblocks(δ(scalartype(state), r', dag(r)))) + ::Algorithm"orthogonalize", + state::MPS, + references::Vector{MPS}; + cutoff = (√(eps(real(scalartype(state))))), + ) + n = length(state) + state = orthogonalize(state, n) + references = map(reference -> orthogonalize(reference, n), references) + s = siteinds(state) + for j in reverse(2:n) + # SVD state[j] to compute basisⱼ + linds = [s[j - 1]; linkinds(state, j - 1)] + _, λⱼ, basisⱼ = svd(state[j], linds; righttags = "bψ_$j,Link") + rinds = uniqueinds(basisⱼ, λⱼ) + # Make projectorⱼ + idⱼ = prod(rinds) do r + return adapt(unwrap_array_type(basisⱼ), denseblocks(δ(scalartype(state), r', dag(r)))) + end + projectorⱼ = idⱼ - prime(basisⱼ, rinds) * dag(basisⱼ) + # Sum reference density matrices + ρⱼ = sum(reference -> prime(reference[j], rinds) * dag(reference[j]), references) + ρⱼ /= tr(ρⱼ) + # Apply projectorⱼ + ρⱼ_projected = apply(apply(projectorⱼ, ρⱼ), projectorⱼ) + expanded_basisⱼ = basisⱼ + if norm(ρⱼ_projected) > 10^3 * eps(real(scalartype(state))) + # Diagonalize projected density matrix ρⱼ_projected + # to compute reference_basisⱼ, which spans part of right basis + # of references which is orthogonal to right basis of state + dⱼ, reference_basisⱼ = eigen( + ρⱼ_projected; cutoff, ishermitian = true, righttags = "bϕ_$j,Link" + ) + state_indⱼ = only(commoninds(basisⱼ, λⱼ)) + reference_indⱼ = only(commoninds(reference_basisⱼ, dⱼ)) + expanded_basisⱼ, expanded_indⱼ = directsum( + basisⱼ => state_indⱼ, reference_basisⱼ => reference_indⱼ + ) + end + # Shift ortho center one site left using dag(expanded_basisⱼ) + # and replace tensor at site j with expanded_basisⱼ + state[j - 1] = state[j - 1] * (state[j] * dag(expanded_basisⱼ)) + state[j] = expanded_basisⱼ + for reference in references + reference[j - 1] = reference[j - 1] * (reference[j] * dag(expanded_basisⱼ)) + reference[j] = expanded_basisⱼ + end end - projectorⱼ = idⱼ - prime(basisⱼ, rinds) * dag(basisⱼ) - # Sum reference density matrices - ρⱼ = sum(reference -> prime(reference[j], rinds) * dag(reference[j]), references) - ρⱼ /= tr(ρⱼ) - # Apply projectorⱼ - ρⱼ_projected = apply(apply(projectorⱼ, ρⱼ), projectorⱼ) - expanded_basisⱼ = basisⱼ - if norm(ρⱼ_projected) > 10^3 * eps(real(scalartype(state))) - # Diagonalize projected density matrix ρⱼ_projected - # to compute reference_basisⱼ, which spans part of right basis - # of references which is orthogonal to right basis of state - dⱼ, reference_basisⱼ = eigen( - ρⱼ_projected; cutoff, ishermitian=true, righttags="bϕ_$j,Link" - ) - state_indⱼ = only(commoninds(basisⱼ, λⱼ)) - reference_indⱼ = only(commoninds(reference_basisⱼ, dⱼ)) - expanded_basisⱼ, expanded_indⱼ = directsum( - basisⱼ => state_indⱼ, reference_basisⱼ => reference_indⱼ - ) - end - # Shift ortho center one site left using dag(expanded_basisⱼ) - # and replace tensor at site j with expanded_basisⱼ - state[j - 1] = state[j - 1] * (state[j] * dag(expanded_basisⱼ)) - state[j] = expanded_basisⱼ - for reference in references - reference[j - 1] = reference[j - 1] * (reference[j] * dag(expanded_basisⱼ)) - reference[j] = expanded_basisⱼ - end - end - return state + return state end """ @@ -144,18 +144,18 @@ $(expand_warning_doctring()) $(expand_citation_docstring()) """ function expand( - ::Algorithm"global_krylov", - state::MPS, - operator::MPO; - krylovdim=2, - cutoff=(√(eps(real(scalartype(state))))), - apply_kwargs=(; maxdim=maxlinkdim(state) + 1), -) - # TODO: Try replacing this logic with `Base.accumulate`. - references = Vector{MPS}(undef, krylovdim) - for k in 1:krylovdim - previous_reference = get(references, k - 1, state) - references[k] = normalize(apply(operator, previous_reference; apply_kwargs...)) - end - return expand(state, references; alg="orthogonalize", cutoff) + ::Algorithm"global_krylov", + state::MPS, + operator::MPO; + krylovdim = 2, + cutoff = (√(eps(real(scalartype(state))))), + apply_kwargs = (; maxdim = maxlinkdim(state) + 1), + ) + # TODO: Try replacing this logic with `Base.accumulate`. + references = Vector{MPS}(undef, krylovdim) + for k in 1:krylovdim + previous_reference = get(references, k - 1, state) + references[k] = normalize(apply(operator, previous_reference; apply_kwargs...)) + end + return expand(state, references; alg = "orthogonalize", cutoff) end diff --git a/src/solvers/linsolve.jl b/src/solvers/linsolve.jl index 9ed68f3..70c7973 100644 --- a/src/solvers/linsolve.jl +++ b/src/solvers/linsolve.jl @@ -1,15 +1,15 @@ using KrylovKit: KrylovKit, linsolve function linsolve_updater(problem, init; internal_kwargs, coefficients, kwargs...) - x, info = linsolve( - operator(problem), - constant_term(problem), - init, - coefficients[1], - coefficients[2]; - kwargs..., - ) - return x, (; info) + x, info = linsolve( + operator(problem), + constant_term(problem), + init, + coefficients[1], + coefficients[2]; + kwargs..., + ) + return x, (; info) end """ @@ -35,16 +35,16 @@ Keyword arguments: See `KrylovKit.jl` documentation for more details on available keyword arguments. """ function KrylovKit.linsolve( - operator, - constant_term::MPS, - init::MPS, - coefficient1::Number=false, - coefficient2::Number=true; - updater=linsolve_updater, - updater_kwargs=(;), - kwargs..., -) - reduced_problem = ReducedLinearProblem(operator, constant_term) - updater_kwargs = (; coefficients=(coefficient1, coefficient2), updater_kwargs...) - return alternating_update(reduced_problem, init; updater, updater_kwargs, kwargs...) + operator, + constant_term::MPS, + init::MPS, + coefficient1::Number = false, + coefficient2::Number = true; + updater = linsolve_updater, + updater_kwargs = (;), + kwargs..., + ) + reduced_problem = ReducedLinearProblem(operator, constant_term) + updater_kwargs = (; coefficients = (coefficient1, coefficient2), updater_kwargs...) + return alternating_update(reduced_problem, init; updater, updater_kwargs, kwargs...) end diff --git a/src/solvers/reducedconstantterm.jl b/src/solvers/reducedconstantterm.jl index 52c882b..3e055bb 100644 --- a/src/solvers/reducedconstantterm.jl +++ b/src/solvers/reducedconstantterm.jl @@ -11,142 +11,142 @@ MPS held constant by the ProjMPS. ``` """ mutable struct ReducedConstantTerm <: AbstractProjMPO - lpos::Int - rpos::Int - nsite::Int - state::MPS - environments::Vector{ITensor} + lpos::Int + rpos::Int + nsite::Int + state::MPS + environments::Vector{ITensor} end function ReducedConstantTerm(state::MPS) - lpos = 0 - rpos = length(state) + 1 - nsite = 2 - environments = Vector{ITensor}(undef, length(state)) - return ReducedConstantTerm(lpos, rpos, nsite, state, environments) + lpos = 0 + rpos = length(state) + 1 + nsite = 2 + environments = Vector{ITensor}(undef, length(state)) + return ReducedConstantTerm(lpos, rpos, nsite, state, environments) end function Base.getproperty(reduced_state::ReducedConstantTerm, sym::Symbol) - # This is for compatibility with `AbstractProjMPO`. - # TODO: Don't use `reduced_state.H`, `reduced_state.LR`, etc. - # in `AbstractProjMPO`. - if sym == :LR - return getfield(reduced_state, :environments) - end - return getfield(reduced_state, sym) + # This is for compatibility with `AbstractProjMPO`. + # TODO: Don't use `reduced_state.H`, `reduced_state.LR`, etc. + # in `AbstractProjMPO`. + if sym == :LR + return getfield(reduced_state, :environments) + end + return getfield(reduced_state, sym) end Base.length(reduced_state::ReducedConstantTerm) = length(reduced_state.state) function Base.copy(reduced_state::ReducedConstantTerm) - return ReducedConstantTerm( - reduced_state.lpos, - reduced_state.rpos, - reduced_state.nsite, - copy(reduced_state.state), - copy(reduced_state.environments), - ) + return ReducedConstantTerm( + reduced_state.lpos, + reduced_state.rpos, + reduced_state.nsite, + copy(reduced_state.state), + copy(reduced_state.environments), + ) end function ITensorMPS.set_nsite!(reduced_state::ReducedConstantTerm, nsite) - reduced_state.nsite = nsite - return reduced_state + reduced_state.nsite = nsite + return reduced_state end function ITensorMPS.makeL!(reduced_state::ReducedConstantTerm, basis::MPS, position::Int) - # Save the last `L` that is made to help with caching - # for DiskProjMPO - ll = reduced_state.lpos - if ll ≥ position - # Special case when nothing has to be done. - # Still need to change the position if lproj is - # being moved backward. + # Save the last `L` that is made to help with caching + # for DiskProjMPO + ll = reduced_state.lpos + if ll ≥ position + # Special case when nothing has to be done. + # Still need to change the position if lproj is + # being moved backward. + reduced_state.lpos = position + return nothing + end + # Make sure ll is at least 0 for the generic logic below + ll = max(ll, 0) + L = lproj(reduced_state) + while ll < position + L = L * basis[ll + 1] * dag(prime(reduced_state.state[ll + 1], "Link")) + reduced_state.environments[ll + 1] = L + ll += 1 + end + # Needed when moving lproj backward. reduced_state.lpos = position - return nothing - end - # Make sure ll is at least 0 for the generic logic below - ll = max(ll, 0) - L = lproj(reduced_state) - while ll < position - L = L * basis[ll + 1] * dag(prime(reduced_state.state[ll + 1], "Link")) - reduced_state.environments[ll + 1] = L - ll += 1 - end - # Needed when moving lproj backward. - reduced_state.lpos = position - return reduced_state + return reduced_state end function ITensorMPS.makeR!(reduced_state::ReducedConstantTerm, basis::MPS, position::Int) - # Save the last `R` that is made to help with caching - # for DiskProjMPO - environment_position = reduced_state.rpos - if environment_position ≤ position - # Special case when nothing has to be done. - # Still need to change the position if rproj is - # being moved backward. + # Save the last `R` that is made to help with caching + # for DiskProjMPO + environment_position = reduced_state.rpos + if environment_position ≤ position + # Special case when nothing has to be done. + # Still need to change the position if rproj is + # being moved backward. + reduced_state.rpos = position + return nothing + end + N = length(reduced_state.state) + # Make sure environment_position is no bigger than `N + 1` for the generic logic below + environment_position = min(environment_position, N + 1) + right_environment = rproj(reduced_state) + while environment_position > position + right_environment = + right_environment * + basis[environment_position - 1] * + dag(prime(reduced_state.state[environment_position - 1], "Link")) + reduced_state.environments[environment_position - 1] = right_environment + environment_position -= 1 + end reduced_state.rpos = position - return nothing - end - N = length(reduced_state.state) - # Make sure environment_position is no bigger than `N + 1` for the generic logic below - environment_position = min(environment_position, N + 1) - right_environment = rproj(reduced_state) - while environment_position > position - right_environment = - right_environment * - basis[environment_position - 1] * - dag(prime(reduced_state.state[environment_position - 1], "Link")) - reduced_state.environments[environment_position - 1] = right_environment - environment_position -= 1 - end - reduced_state.rpos = position - return reduced_state + return reduced_state end function ITensors.contract(reduced_state::ReducedConstantTerm, v::ITensor) - reduced_state_tensors = Union{ITensor,OneITensor}[lproj(reduced_state)] - append!( - reduced_state_tensors, - [prime(t, "Link") for t in reduced_state.state[site_range(reduced_state)]], - ) - push!(reduced_state_tensors, rproj(reduced_state)) + reduced_state_tensors = Union{ITensor, OneITensor}[lproj(reduced_state)] + append!( + reduced_state_tensors, + [prime(t, "Link") for t in reduced_state.state[site_range(reduced_state)]], + ) + push!(reduced_state_tensors, rproj(reduced_state)) - # Reverse the contraction order of the map if - # the first tensor is a scalar (for example we - # are at the left edge of the system) - if dim(first(reduced_state_tensors)) == 1 - reverse!(reduced_state_tensors) - end + # Reverse the contraction order of the map if + # the first tensor is a scalar (for example we + # are at the left edge of the system) + if dim(first(reduced_state_tensors)) == 1 + reverse!(reduced_state_tensors) + end - # Apply the map - inner = v - for t in reduced_state_tensors - inner *= t - end - return inner + # Apply the map + inner = v + for t in reduced_state_tensors + inner *= t + end + return inner end # Contract the reduced constant term down to a since ITensor. function ITensors.contract(reduced_state::ReducedConstantTerm) - reduced_state_tensors = Union{ITensor,OneITensor}[lproj(reduced_state)] - append!( - reduced_state_tensors, - [dag(prime(t, "Link")) for t in reduced_state.state[site_range(reduced_state)]], - ) - push!(reduced_state_tensors, rproj(reduced_state)) + reduced_state_tensors = Union{ITensor, OneITensor}[lproj(reduced_state)] + append!( + reduced_state_tensors, + [dag(prime(t, "Link")) for t in reduced_state.state[site_range(reduced_state)]], + ) + push!(reduced_state_tensors, rproj(reduced_state)) - # Reverse the contraction order of the map if - # the first tensor is a scalar (for example we - # are at the left edge of the system) - if dim(first(reduced_state_tensors)) == 1 - reverse!(reduced_state_tensors) - end + # Reverse the contraction order of the map if + # the first tensor is a scalar (for example we + # are at the left edge of the system) + if dim(first(reduced_state_tensors)) == 1 + reverse!(reduced_state_tensors) + end - # Apply the map - contracted_reduced_state = ITensor(true) - for t in reduced_state_tensors - contracted_reduced_state *= t - end - return contracted_reduced_state + # Apply the map + contracted_reduced_state = ITensor(true) + for t in reduced_state_tensors + contracted_reduced_state *= t + end + return contracted_reduced_state end diff --git a/src/solvers/reducedcontractproblem.jl b/src/solvers/reducedcontractproblem.jl index 916d60c..ca64c1e 100644 --- a/src/solvers/reducedcontractproblem.jl +++ b/src/solvers/reducedcontractproblem.jl @@ -19,104 +19,104 @@ one- and two-site algorithms. ``` """ mutable struct ReducedContractProblem <: AbstractProjMPO - lpos::Int - rpos::Int - nsite::Int - input_state::MPS - operator::MPO - environments::Vector{ITensor} + lpos::Int + rpos::Int + nsite::Int + input_state::MPS + operator::MPO + environments::Vector{ITensor} end function ReducedContractProblem(input_state::MPS, operator::MPO) - lpos = 0 - rpos = length(operator) + 1 - nsite = 2 - environments = Vector{ITensor}(undef, length(operator)) - return ReducedContractProblem(lpos, rpos, nsite, input_state, operator, environments) + lpos = 0 + rpos = length(operator) + 1 + nsite = 2 + environments = Vector{ITensor}(undef, length(operator)) + return ReducedContractProblem(lpos, rpos, nsite, input_state, operator, environments) end function Base.getproperty(reduced_operator::ReducedContractProblem, sym::Symbol) - # This is for compatibility with `AbstractProjMPO`. - # TODO: Don't use `reduced_operator.H`, `reduced_operator.LR`, etc. - # in `AbstractProjMPO`. - if sym === :H - return getfield(reduced_operator, :operator) - elseif sym == :LR - return getfield(reduced_operator, :environments) - end - return getfield(reduced_operator, sym) + # This is for compatibility with `AbstractProjMPO`. + # TODO: Don't use `reduced_operator.H`, `reduced_operator.LR`, etc. + # in `AbstractProjMPO`. + if sym === :H + return getfield(reduced_operator, :operator) + elseif sym == :LR + return getfield(reduced_operator, :environments) + end + return getfield(reduced_operator, sym) end function Base.copy(reduced_operator::ReducedContractProblem) - return ReducedContractProblem( - reduced_operator.lpos, - reduced_operator.rpos, - reduced_operator.nsite, - copy(reduced_operator.input_state), - copy(reduced_operator.operator), - copy(reduced_operator.environments), - ) + return ReducedContractProblem( + reduced_operator.lpos, + reduced_operator.rpos, + reduced_operator.nsite, + copy(reduced_operator.input_state), + copy(reduced_operator.operator), + copy(reduced_operator.environments), + ) end Base.length(reduced_operator::ReducedContractProblem) = length(reduced_operator.operator) function ITensorMPS.set_nsite!(reduced_operator::ReducedContractProblem, nsite) - reduced_operator.nsite = nsite - return reduced_operator + reduced_operator.nsite = nsite + return reduced_operator end function ITensorMPS.makeL!(reduced_operator::ReducedContractProblem, state::MPS, k::Int) - # Save the last `L` that is made to help with caching - # for DiskProjMPO - ll = reduced_operator.lpos - if ll ≥ k - # Special case when nothing has to be done. - # Still need to change the position if lproj is - # being moved backward. + # Save the last `L` that is made to help with caching + # for DiskProjMPO + ll = reduced_operator.lpos + if ll ≥ k + # Special case when nothing has to be done. + # Still need to change the position if lproj is + # being moved backward. + reduced_operator.lpos = k + return nothing + end + # Make sure ll is at least 0 for the generic logic below + ll = max(ll, 0) + L = lproj(reduced_operator) + while ll < k + L = + L * + reduced_operator.input_state[ll + 1] * + reduced_operator.operator[ll + 1] * + dag(state[ll + 1]) + reduced_operator.environments[ll + 1] = L + ll += 1 + end + # Needed when moving lproj backward. reduced_operator.lpos = k - return nothing - end - # Make sure ll is at least 0 for the generic logic below - ll = max(ll, 0) - L = lproj(reduced_operator) - while ll < k - L = - L * - reduced_operator.input_state[ll + 1] * - reduced_operator.operator[ll + 1] * - dag(state[ll + 1]) - reduced_operator.environments[ll + 1] = L - ll += 1 - end - # Needed when moving lproj backward. - reduced_operator.lpos = k - return reduced_operator + return reduced_operator end function ITensorMPS.makeR!(reduced_operator::ReducedContractProblem, state::MPS, k::Int) - # Save the last `R` that is made to help with caching - # for DiskProjMPO - rl = reduced_operator.rpos - if rl ≤ k - # Special case when nothing has to be done. - # Still need to change the position if rproj is - # being moved backward. + # Save the last `R` that is made to help with caching + # for DiskProjMPO + rl = reduced_operator.rpos + if rl ≤ k + # Special case when nothing has to be done. + # Still need to change the position if rproj is + # being moved backward. + reduced_operator.rpos = k + return nothing + end + N = length(reduced_operator.operator) + # Make sure rl is no bigger than `N + 1` for the generic logic below + rl = min(rl, N + 1) + R = rproj(reduced_operator) + while rl > k + R = + R * + reduced_operator.input_state[rl - 1] * + reduced_operator.operator[rl - 1] * + dag(state[rl - 1]) + reduced_operator.environments[rl - 1] = R + rl -= 1 + end reduced_operator.rpos = k - return nothing - end - N = length(reduced_operator.operator) - # Make sure rl is no bigger than `N + 1` for the generic logic below - rl = min(rl, N + 1) - R = rproj(reduced_operator) - while rl > k - R = - R * - reduced_operator.input_state[rl - 1] * - reduced_operator.operator[rl - 1] * - dag(state[rl - 1]) - reduced_operator.environments[rl - 1] = R - rl -= 1 - end - reduced_operator.rpos = k - return reduced_operator + return reduced_operator end diff --git a/src/solvers/reducedlinearproblem.jl b/src/solvers/reducedlinearproblem.jl index 283fdfc..56a5e59 100644 --- a/src/solvers/reducedlinearproblem.jl +++ b/src/solvers/reducedlinearproblem.jl @@ -1,61 +1,61 @@ using ITensors: contract mutable struct ReducedLinearProblem <: AbstractProjMPO - reduced_operator::ProjMPO - reduced_constant_terms::Vector{ReducedConstantTerm} + reduced_operator::ProjMPO + reduced_constant_terms::Vector{ReducedConstantTerm} end # Linear problem updater interface. operator(reduced_problem::ReducedLinearProblem) = reduced_problem.reduced_operator function constant_term(reduced_problem::ReducedLinearProblem) - constant_terms = map(reduced_problem.reduced_constant_terms) do reduced_constant_term - return contract(reduced_constant_term) - end - return dag(only(constant_terms)) + constant_terms = map(reduced_problem.reduced_constant_terms) do reduced_constant_term + return contract(reduced_constant_term) + end + return dag(only(constant_terms)) end function ReducedLinearProblem(operator::MPO, constant_term::MPS) - return ReducedLinearProblem(ProjMPO(operator), [ReducedConstantTerm(constant_term)]) + return ReducedLinearProblem(ProjMPO(operator), [ReducedConstantTerm(constant_term)]) end function ReducedLinearProblem(operator::MPO, constant_terms::Vector{MPS}) - return ReducedLinearProblem(ProjMPO(operator), ReducedConstantTerm.(constant_terms)) + return ReducedLinearProblem(ProjMPO(operator), ReducedConstantTerm.(constant_terms)) end function Base.copy(reduced_problem::ReducedLinearProblem) - return ReducedLinearProblem( - copy(reduced_problem.reduced_operator), copy(reduced_problem.reduced_constant_terms) - ) + return ReducedLinearProblem( + copy(reduced_problem.reduced_operator), copy(reduced_problem.reduced_constant_terms) + ) end function ITensorMPS.nsite(reduced_problem::ReducedLinearProblem) - return nsite(reduced_problem.reduced_operator) + return nsite(reduced_problem.reduced_operator) end function ITensorMPS.set_nsite!(reduced_problem::ReducedLinearProblem, nsite) - set_nsite!(reduced_problem.reduced_operator, nsite) - for m in reduced_problem.reduced_constant_terms - set_nsite!(m, nsite) - end - return reduced_problem + set_nsite!(reduced_problem.reduced_operator, nsite) + for m in reduced_problem.reduced_constant_terms + set_nsite!(m, nsite) + end + return reduced_problem end function ITensorMPS.makeL!(reduced_problem::ReducedLinearProblem, state::MPS, position::Int) - makeL!(reduced_problem.reduced_operator, state, position) - for reduced_constant_term in reduced_problem.reduced_constant_terms - makeL!(reduced_constant_term, state, position) - end - return reduced_problem + makeL!(reduced_problem.reduced_operator, state, position) + for reduced_constant_term in reduced_problem.reduced_constant_terms + makeL!(reduced_constant_term, state, position) + end + return reduced_problem end function ITensorMPS.makeR!(reduced_problem::ReducedLinearProblem, state::MPS, position::Int) - makeR!(reduced_problem.reduced_operator, state, position) - for reduced_constant_term in reduced_problem.reduced_constant_terms - makeR!(reduced_constant_term, state, position) - end - return reduced_problem + makeR!(reduced_problem.reduced_operator, state, position) + for reduced_constant_term in reduced_problem.reduced_constant_terms + makeR!(reduced_constant_term, state, position) + end + return reduced_problem end function ITensors.contract(reduced_problem::ReducedLinearProblem, v::ITensor) - return contract(reduced_problem.reduced_operator, v) + return contract(reduced_problem.reduced_operator, v) end diff --git a/src/solvers/sweep_update.jl b/src/solvers/sweep_update.jl index ac6cc24..9aa24e8 100644 --- a/src/solvers/sweep_update.jl +++ b/src/solvers/sweep_update.jl @@ -3,38 +3,38 @@ using LinearAlgebra: norm, normalize!, svd using Printf: @printf function sweep_update( - order::TDVPOrder, - reduced_operator, - state::MPS; - current_time=nothing, - time_step=nothing, - kwargs..., -) - order_orderings = orderings(order) - order_sub_time_steps = sub_time_steps(order) - if !isnothing(time_step) - order_sub_time_steps = eltype(time_step).(order_sub_time_steps) - order_sub_time_steps *= time_step - end - info = nothing - sub_time_step = nothing - for substep in 1:length(order_sub_time_steps) - if !isnothing(time_step) - sub_time_step = order_sub_time_steps[substep] - end - state, reduced_operator, info = sub_sweep_update( - order_orderings[substep], - reduced_operator, - state; - current_time, - time_step=sub_time_step, - kwargs..., + order::TDVPOrder, + reduced_operator, + state::MPS; + current_time = nothing, + time_step = nothing, + kwargs..., ) + order_orderings = orderings(order) + order_sub_time_steps = sub_time_steps(order) if !isnothing(time_step) - current_time += sub_time_step + order_sub_time_steps = eltype(time_step).(order_sub_time_steps) + order_sub_time_steps *= time_step + end + info = nothing + sub_time_step = nothing + for substep in 1:length(order_sub_time_steps) + if !isnothing(time_step) + sub_time_step = order_sub_time_steps[substep] + end + state, reduced_operator, info = sub_sweep_update( + order_orderings[substep], + reduced_operator, + state; + current_time, + time_step = sub_time_step, + kwargs..., + ) + if !isnothing(time_step) + current_time += sub_time_step + end end - end - return state, reduced_operator, info + return state, reduced_operator, info end isforward(direction::Base.ForwardOrdering) = true @@ -42,11 +42,11 @@ isforward(direction::Base.ReverseOrdering) = false isreverse(direction) = !isforward(direction) function sweep_bonds(direction::Base.ForwardOrdering, n::Int; ncenter::Int) - return 1:(n - ncenter + 1) + return 1:(n - ncenter + 1) end function sweep_bonds(direction::Base.ReverseOrdering, n::Int; ncenter::Int) - return reverse(sweep_bonds(Base.Forward, n; ncenter)) + return reverse(sweep_bonds(Base.Forward, n; ncenter)) end is_forward_done(direction::Base.ForwardOrdering, b, n; ncenter) = (b + ncenter - 1 == n) @@ -54,423 +54,423 @@ is_forward_done(direction::Base.ReverseOrdering, b, n; ncenter) = false is_reverse_done(direction::Base.ForwardOrdering, b, n; ncenter) = false is_reverse_done(direction::Base.ReverseOrdering, b, n; ncenter) = (b == 1) function is_half_sweep_done(direction, b, n; ncenter) - return is_forward_done(direction, b, n; ncenter) || - is_reverse_done(direction, b, n; ncenter) + return is_forward_done(direction, b, n; ncenter) || + is_reverse_done(direction, b, n; ncenter) end function sub_sweep_update( - direction::Base.Ordering, - reduced_operator, - state::MPS; - updater, - updater_kwargs, - which_decomp=nothing, - svd_alg=nothing, - sweep=default_sweep(), - current_time=nothing, - time_step=nothing, - nsite=default_nsite(), - reverse_step=default_reverse_step(), - normalize=default_normalize(), - (observer!)=default_observer(), - outputlevel=default_outputlevel(), - maxdim=default_maxdim(), - mindim=default_mindim(), - cutoff=default_cutoff(ITensors.scalartype(state)), - noise=default_noise(), -) - reduced_operator = copy(reduced_operator) - state = copy(state) - if length(state) == 1 - error( - "`tdvp`, `dmrg`, `linsolve`, etc. currently does not support system sizes of 1. You can diagonalize the MPO tensor directly with tools like `LinearAlgebra.eigen`, `KrylovKit.exponentiate`, etc.", + direction::Base.Ordering, + reduced_operator, + state::MPS; + updater, + updater_kwargs, + which_decomp = nothing, + svd_alg = nothing, + sweep = default_sweep(), + current_time = nothing, + time_step = nothing, + nsite = default_nsite(), + reverse_step = default_reverse_step(), + normalize = default_normalize(), + (observer!) = default_observer(), + outputlevel = default_outputlevel(), + maxdim = default_maxdim(), + mindim = default_mindim(), + cutoff = default_cutoff(ITensors.scalartype(state)), + noise = default_noise(), ) - end - N = length(state) - set_nsite!(reduced_operator, nsite) - if isforward(direction) - if !isortho(state) || orthocenter(state) != 1 - orthogonalize!(state, 1) + reduced_operator = copy(reduced_operator) + state = copy(state) + if length(state) == 1 + error( + "`tdvp`, `dmrg`, `linsolve`, etc. currently does not support system sizes of 1. You can diagonalize the MPO tensor directly with tools like `LinearAlgebra.eigen`, `KrylovKit.exponentiate`, etc.", + ) end - @assert isortho(state) && orthocenter(state) == 1 - position!(reduced_operator, state, 1) - elseif isreverse(direction) - if !isortho(state) || orthocenter(state) != N - nsite + 1 - orthogonalize!(state, N - nsite + 1) + N = length(state) + set_nsite!(reduced_operator, nsite) + if isforward(direction) + if !isortho(state) || orthocenter(state) != 1 + orthogonalize!(state, 1) + end + @assert isortho(state) && orthocenter(state) == 1 + position!(reduced_operator, state, 1) + elseif isreverse(direction) + if !isortho(state) || orthocenter(state) != N - nsite + 1 + orthogonalize!(state, N - nsite + 1) + end + @assert(isortho(state) && (orthocenter(state) == N - nsite + 1)) + position!(reduced_operator, state, N - nsite + 1) end - @assert(isortho(state) && (orthocenter(state) == N - nsite + 1)) - position!(reduced_operator, state, N - nsite + 1) - end - maxtruncerr = 0.0 - info = nothing - for b in sweep_bonds(direction, N; ncenter=nsite) - current_time, maxtruncerr, spec, info = region_update!( - reduced_operator, - state, - b; - updater, - updater_kwargs, - nsite, - reverse_step, - current_time, - outputlevel, - time_step, - normalize, - direction, - noise, - which_decomp, - svd_alg, - cutoff, - maxdim, - mindim, - maxtruncerr, - ) - if outputlevel >= 2 - if nsite == 1 - @printf("Sweep %d, direction %s, bond (%d,) \n", sweep, direction, b) - elseif nsite == 2 - @printf("Sweep %d, direction %s, bond (%d,%d) \n", sweep, direction, b, b + 1) - end - print(" Truncated using") - @printf(" cutoff=%.1E", cutoff) - @printf(" maxdim=%.1E", maxdim) - print(" mindim=", mindim) - print(" current_time=", round(current_time; digits=3)) - println() - if spec != nothing - @printf( - " Trunc. err=%.2E, bond dimension %d\n", spec.truncerr, dim(linkind(state, b)) + maxtruncerr = 0.0 + info = nothing + for b in sweep_bonds(direction, N; ncenter = nsite) + current_time, maxtruncerr, spec, info = region_update!( + reduced_operator, + state, + b; + updater, + updater_kwargs, + nsite, + reverse_step, + current_time, + outputlevel, + time_step, + normalize, + direction, + noise, + which_decomp, + svd_alg, + cutoff, + maxdim, + mindim, + maxtruncerr, + ) + if outputlevel >= 2 + if nsite == 1 + @printf("Sweep %d, direction %s, bond (%d,) \n", sweep, direction, b) + elseif nsite == 2 + @printf("Sweep %d, direction %s, bond (%d,%d) \n", sweep, direction, b, b + 1) + end + print(" Truncated using") + @printf(" cutoff=%.1E", cutoff) + @printf(" maxdim=%.1E", maxdim) + print(" mindim=", mindim) + print(" current_time=", round(current_time; digits = 3)) + println() + if spec != nothing + @printf( + " Trunc. err=%.2E, bond dimension %d\n", spec.truncerr, dim(linkind(state, b)) + ) + end + flush(stdout) + end + update_observer!( + observer!; + state, + reduced_operator, + bond = b, + sweep, + half_sweep = isforward(direction) ? 1 : 2, + spec, + outputlevel, + half_sweep_is_done = is_half_sweep_done(direction, b, N; ncenter = nsite), + current_time, + info, ) - end - flush(stdout) end - update_observer!( - observer!; - state, - reduced_operator, - bond=b, - sweep, - half_sweep=isforward(direction) ? 1 : 2, - spec, - outputlevel, - half_sweep_is_done=is_half_sweep_done(direction, b, N; ncenter=nsite), - current_time, - info, - ) - end - # Just to be sure: - normalize && normalize!(state) - return state, reduced_operator, (; maxtruncerr) + # Just to be sure: + normalize && normalize!(state) + return state, reduced_operator, (; maxtruncerr) end function region_update!( - reduced_operator, - state, - b; - updater, - updater_kwargs, - nsite, - reverse_step, - current_time, - outputlevel, - time_step, - normalize, - direction, - noise, - which_decomp, - svd_alg, - cutoff, - maxdim, - mindim, - maxtruncerr, -) - return region_update!( - Val(nsite), - Val(reverse_step), - reduced_operator, - state, - b; - updater, - updater_kwargs, - current_time, - outputlevel, - time_step, - normalize, - direction, - noise, - which_decomp, - svd_alg, - cutoff, - maxdim, - mindim, - maxtruncerr, - ) + reduced_operator, + state, + b; + updater, + updater_kwargs, + nsite, + reverse_step, + current_time, + outputlevel, + time_step, + normalize, + direction, + noise, + which_decomp, + svd_alg, + cutoff, + maxdim, + mindim, + maxtruncerr, + ) + return region_update!( + Val(nsite), + Val(reverse_step), + reduced_operator, + state, + b; + updater, + updater_kwargs, + current_time, + outputlevel, + time_step, + normalize, + direction, + noise, + which_decomp, + svd_alg, + cutoff, + maxdim, + mindim, + maxtruncerr, + ) end function region_update!( - nsite_val::Val{1}, - reverse_step_val::Val{false}, - reduced_operator, - state, - b; - updater, - updater_kwargs, - current_time, - outputlevel, - time_step, - normalize, - direction, - noise, - which_decomp, - svd_alg, - cutoff, - maxdim, - mindim, - maxtruncerr, -) - N = length(state) - nsite = 1 - # Do 'forwards' evolution step - set_nsite!(reduced_operator, nsite) - position!(reduced_operator, state, b) - reduced_state = state[b] - internal_kwargs = (; current_time, time_step, outputlevel) - reduced_state, info = updater( - reduced_operator, reduced_state; internal_kwargs, updater_kwargs... - ) - if !isnothing(time_step) - current_time += time_step - end - normalize && (reduced_state /= norm(reduced_state)) - spec = nothing - state[b] = reduced_state - if !is_half_sweep_done(direction, b, N; ncenter=nsite) - # Move ortho center - Δ = (isforward(direction) ? +1 : -1) - orthogonalize!(state, b + Δ) - end - return current_time, maxtruncerr, spec, info + nsite_val::Val{1}, + reverse_step_val::Val{false}, + reduced_operator, + state, + b; + updater, + updater_kwargs, + current_time, + outputlevel, + time_step, + normalize, + direction, + noise, + which_decomp, + svd_alg, + cutoff, + maxdim, + mindim, + maxtruncerr, + ) + N = length(state) + nsite = 1 + # Do 'forwards' evolution step + set_nsite!(reduced_operator, nsite) + position!(reduced_operator, state, b) + reduced_state = state[b] + internal_kwargs = (; current_time, time_step, outputlevel) + reduced_state, info = updater( + reduced_operator, reduced_state; internal_kwargs, updater_kwargs... + ) + if !isnothing(time_step) + current_time += time_step + end + normalize && (reduced_state /= norm(reduced_state)) + spec = nothing + state[b] = reduced_state + if !is_half_sweep_done(direction, b, N; ncenter = nsite) + # Move ortho center + Δ = (isforward(direction) ? +1 : -1) + orthogonalize!(state, b + Δ) + end + return current_time, maxtruncerr, spec, info end function region_update!( - nsite_val::Val{1}, - reverse_step_val::Val{true}, - reduced_operator, - state, - b; - updater, - updater_kwargs, - current_time, - outputlevel, - time_step, - normalize, - direction, - noise, - which_decomp, - svd_alg, - cutoff, - maxdim, - mindim, - maxtruncerr, -) - N = length(state) - nsite = 1 - # Do 'forwards' evolution step - set_nsite!(reduced_operator, nsite) - position!(reduced_operator, state, b) - reduced_state = state[b] - internal_kwargs = (; current_time, time_step, outputlevel) - reduced_state, info = updater( - reduced_operator, reduced_state; internal_kwargs, updater_kwargs... - ) - current_time += time_step - normalize && (reduced_state /= norm(reduced_state)) - spec = nothing - state[b] = reduced_state - if !is_half_sweep_done(direction, b, N; ncenter=nsite) - # Do backwards evolution step - b1 = (isforward(direction) ? b + 1 : b) - Δ = (isforward(direction) ? +1 : -1) - uinds = uniqueinds(reduced_state, state[b + Δ]) - U, S, V = svd(reduced_state, uinds) - state[b] = U - bond_reduced_state = S * V - if isforward(direction) - ITensorMPS.setleftlim!(state, b) - elseif isreverse(direction) - ITensorMPS.setrightlim!(state, b) - end - set_nsite!(reduced_operator, nsite - 1) - position!(reduced_operator, state, b1) - internal_kwargs = (; current_time, time_step=(-time_step), outputlevel) - bond_reduced_state, info = updater( - reduced_operator, bond_reduced_state; internal_kwargs, updater_kwargs... + nsite_val::Val{1}, + reverse_step_val::Val{true}, + reduced_operator, + state, + b; + updater, + updater_kwargs, + current_time, + outputlevel, + time_step, + normalize, + direction, + noise, + which_decomp, + svd_alg, + cutoff, + maxdim, + mindim, + maxtruncerr, ) - current_time -= time_step - normalize && (bond_reduced_state ./= norm(bond_reduced_state)) - state[b + Δ] = bond_reduced_state * state[b + Δ] - if isforward(direction) - ITensorMPS.setrightlim!(state, b + Δ + 1) - elseif isreverse(direction) - ITensorMPS.setleftlim!(state, b + Δ - 1) - end + N = length(state) + nsite = 1 + # Do 'forwards' evolution step set_nsite!(reduced_operator, nsite) - end - return current_time, maxtruncerr, spec, info + position!(reduced_operator, state, b) + reduced_state = state[b] + internal_kwargs = (; current_time, time_step, outputlevel) + reduced_state, info = updater( + reduced_operator, reduced_state; internal_kwargs, updater_kwargs... + ) + current_time += time_step + normalize && (reduced_state /= norm(reduced_state)) + spec = nothing + state[b] = reduced_state + if !is_half_sweep_done(direction, b, N; ncenter = nsite) + # Do backwards evolution step + b1 = (isforward(direction) ? b + 1 : b) + Δ = (isforward(direction) ? +1 : -1) + uinds = uniqueinds(reduced_state, state[b + Δ]) + U, S, V = svd(reduced_state, uinds) + state[b] = U + bond_reduced_state = S * V + if isforward(direction) + ITensorMPS.setleftlim!(state, b) + elseif isreverse(direction) + ITensorMPS.setrightlim!(state, b) + end + set_nsite!(reduced_operator, nsite - 1) + position!(reduced_operator, state, b1) + internal_kwargs = (; current_time, time_step = (-time_step), outputlevel) + bond_reduced_state, info = updater( + reduced_operator, bond_reduced_state; internal_kwargs, updater_kwargs... + ) + current_time -= time_step + normalize && (bond_reduced_state ./= norm(bond_reduced_state)) + state[b + Δ] = bond_reduced_state * state[b + Δ] + if isforward(direction) + ITensorMPS.setrightlim!(state, b + Δ + 1) + elseif isreverse(direction) + ITensorMPS.setleftlim!(state, b + Δ - 1) + end + set_nsite!(reduced_operator, nsite) + end + return current_time, maxtruncerr, spec, info end function region_update!( - nsite_val::Val{2}, - reverse_step_val::Val{false}, - reduced_operator, - state, - b; - updater, - updater_kwargs, - current_time, - time_step, - outputlevel, - normalize, - direction, - noise, - which_decomp, - svd_alg, - cutoff, - maxdim, - mindim, - maxtruncerr, -) - N = length(state) - nsite = 2 - # Do 'forwards' evolution step - set_nsite!(reduced_operator, nsite) - position!(reduced_operator, state, b) - reduced_state = state[b] * state[b + 1] - internal_kwargs = (; current_time, time_step, outputlevel) - reduced_state, info = updater( - reduced_operator, reduced_state; internal_kwargs, updater_kwargs... - ) - if !isnothing(time_step) - current_time += time_step - end - normalize && (reduced_state /= norm(reduced_state)) - spec = nothing - ortho = isforward(direction) ? "left" : "right" - drho = nothing - if noise > 0.0 && isforward(direction) - drho = noise * noiseterm(reduced_operator, reduced_state, ortho) - end - spec = replacebond!( - state, - b, - reduced_state; - maxdim, - mindim, - cutoff, - eigen_perturbation=drho, - ortho=ortho, - normalize, - which_decomp, - svd_alg, - ) - maxtruncerr = max(maxtruncerr, spec.truncerr) - return current_time, maxtruncerr, spec, info + nsite_val::Val{2}, + reverse_step_val::Val{false}, + reduced_operator, + state, + b; + updater, + updater_kwargs, + current_time, + time_step, + outputlevel, + normalize, + direction, + noise, + which_decomp, + svd_alg, + cutoff, + maxdim, + mindim, + maxtruncerr, + ) + N = length(state) + nsite = 2 + # Do 'forwards' evolution step + set_nsite!(reduced_operator, nsite) + position!(reduced_operator, state, b) + reduced_state = state[b] * state[b + 1] + internal_kwargs = (; current_time, time_step, outputlevel) + reduced_state, info = updater( + reduced_operator, reduced_state; internal_kwargs, updater_kwargs... + ) + if !isnothing(time_step) + current_time += time_step + end + normalize && (reduced_state /= norm(reduced_state)) + spec = nothing + ortho = isforward(direction) ? "left" : "right" + drho = nothing + if noise > 0.0 && isforward(direction) + drho = noise * noiseterm(reduced_operator, reduced_state, ortho) + end + spec = replacebond!( + state, + b, + reduced_state; + maxdim, + mindim, + cutoff, + eigen_perturbation = drho, + ortho = ortho, + normalize, + which_decomp, + svd_alg, + ) + maxtruncerr = max(maxtruncerr, spec.truncerr) + return current_time, maxtruncerr, spec, info end function region_update!( - nsite_val::Val{2}, - reverse_step_val::Val{true}, - reduced_operator, - state, - b; - updater, - updater_kwargs, - current_time, - time_step, - outputlevel, - normalize, - direction, - noise, - which_decomp, - svd_alg, - cutoff, - maxdim, - mindim, - maxtruncerr, -) - N = length(state) - nsite = 2 - # Do 'forwards' evolution step - set_nsite!(reduced_operator, nsite) - position!(reduced_operator, state, b) - reduced_state = state[b] * state[b + 1] - internal_kwargs = (; current_time, time_step, outputlevel) - reduced_state, info = updater( - reduced_operator, reduced_state; internal_kwargs, updater_kwargs... - ) - current_time += time_step - normalize && (reduced_state /= norm(reduced_state)) - spec = nothing - ortho = isforward(direction) ? "left" : "right" - drho = nothing - if noise > 0.0 && isforward(direction) - drho = noise * noiseterm(reduced_operator, phi, ortho) - end - spec = replacebond!( - state, - b, - reduced_state; - maxdim, - mindim, - cutoff, - eigen_perturbation=drho, - ortho=ortho, - normalize, - which_decomp, - svd_alg, - ) - maxtruncerr = max(maxtruncerr, spec.truncerr) - if !is_half_sweep_done(direction, b, N; ncenter=nsite) - # Do backwards evolution step - b1 = (isforward(direction) ? b + 1 : b) - Δ = (isforward(direction) ? +1 : -1) - bond_reduced_state = state[b1] - set_nsite!(reduced_operator, nsite - 1) - position!(reduced_operator, state, b1) - internal_kwargs = (; current_time, time_step=(-time_step), outputlevel) - bond_reduced_state, info = updater( - reduced_operator, bond_reduced_state; internal_kwargs, updater_kwargs... + nsite_val::Val{2}, + reverse_step_val::Val{true}, + reduced_operator, + state, + b; + updater, + updater_kwargs, + current_time, + time_step, + outputlevel, + normalize, + direction, + noise, + which_decomp, + svd_alg, + cutoff, + maxdim, + mindim, + maxtruncerr, ) - current_time -= time_step - normalize && (bond_reduced_state /= norm(bond_reduced_state)) - state[b1] = bond_reduced_state + N = length(state) + nsite = 2 + # Do 'forwards' evolution step set_nsite!(reduced_operator, nsite) - end - return current_time, maxtruncerr, spec, info + position!(reduced_operator, state, b) + reduced_state = state[b] * state[b + 1] + internal_kwargs = (; current_time, time_step, outputlevel) + reduced_state, info = updater( + reduced_operator, reduced_state; internal_kwargs, updater_kwargs... + ) + current_time += time_step + normalize && (reduced_state /= norm(reduced_state)) + spec = nothing + ortho = isforward(direction) ? "left" : "right" + drho = nothing + if noise > 0.0 && isforward(direction) + drho = noise * noiseterm(reduced_operator, phi, ortho) + end + spec = replacebond!( + state, + b, + reduced_state; + maxdim, + mindim, + cutoff, + eigen_perturbation = drho, + ortho = ortho, + normalize, + which_decomp, + svd_alg, + ) + maxtruncerr = max(maxtruncerr, spec.truncerr) + if !is_half_sweep_done(direction, b, N; ncenter = nsite) + # Do backwards evolution step + b1 = (isforward(direction) ? b + 1 : b) + Δ = (isforward(direction) ? +1 : -1) + bond_reduced_state = state[b1] + set_nsite!(reduced_operator, nsite - 1) + position!(reduced_operator, state, b1) + internal_kwargs = (; current_time, time_step = (-time_step), outputlevel) + bond_reduced_state, info = updater( + reduced_operator, bond_reduced_state; internal_kwargs, updater_kwargs... + ) + current_time -= time_step + normalize && (bond_reduced_state /= norm(bond_reduced_state)) + state[b1] = bond_reduced_state + set_nsite!(reduced_operator, nsite) + end + return current_time, maxtruncerr, spec, info end function region_!( - ::Val{nsite}, - ::Val{reverse_step}, - reduced_operator, - state, - b; - updater, - updater_kwargs, - current_time, - outputlevel, - time_step, - normalize, - direction, - noise, - which_decomp, - svd_alg, - cutoff, - maxdim, - mindim, - maxtruncerr, -) where {nsite,reverse_step} - return error( - "`tdvp`, `dmrg`, `linsolve`, etc. with `nsite=$nsite` and `reverse_step=$reverse_step` not implemented.", - ) + ::Val{nsite}, + ::Val{reverse_step}, + reduced_operator, + state, + b; + updater, + updater_kwargs, + current_time, + outputlevel, + time_step, + normalize, + direction, + noise, + which_decomp, + svd_alg, + cutoff, + maxdim, + mindim, + maxtruncerr, + ) where {nsite, reverse_step} + return error( + "`tdvp`, `dmrg`, `linsolve`, etc. with `nsite=$nsite` and `reverse_step=$reverse_step` not implemented.", + ) end diff --git a/src/solvers/tdvp.jl b/src/solvers/tdvp.jl index 02d8c5c..3681870 100644 --- a/src/solvers/tdvp.jl +++ b/src/solvers/tdvp.jl @@ -2,53 +2,53 @@ using ITensors: Algorithm, @Algorithm_str using KrylovKit: exponentiate function exponentiate_updater(operator, init; internal_kwargs, kwargs...) - state, info = exponentiate(operator, internal_kwargs.time_step, init; kwargs...) - return state, (; info) + state, info = exponentiate(operator, internal_kwargs.time_step, init; kwargs...) + return state, (; info) end function applyexp_updater(operator, init; internal_kwargs, kwargs...) - state, info = applyexp(operator, internal_kwargs.time_step, init; kwargs...) - return state, (; info) + state, info = applyexp(operator, internal_kwargs.time_step, init; kwargs...) + return state, (; info) end tdvp_updater(updater_backend::String) = tdvp_updater(Algorithm(updater_backend)) tdvp_updater(::Algorithm"exponentiate") = exponentiate_updater tdvp_updater(::Algorithm"applyexp") = applyexp_updater function tdvp_updater(updater_backend::Algorithm) - return error("`updater_backend=$(String(updater_backend))` not recognized.") + return error("`updater_backend=$(String(updater_backend))` not recognized.") end function time_step_and_nsteps(t, time_step::Nothing, nsteps::Nothing) - # Default to 1 step. - nsteps = 1 - return time_step_and_nsteps(t, time_step, nsteps) + # Default to 1 step. + nsteps = 1 + return time_step_and_nsteps(t, time_step, nsteps) end function time_step_and_nsteps(t, time_step::Nothing, nsteps) - return t / nsteps, nsteps + return t / nsteps, nsteps end function time_step_and_nsteps(t, time_step, nsteps::Nothing) - nsteps_float = t / time_step - nsteps_rounded = round(nsteps_float) - if nsteps_float ≉ nsteps_rounded - return error("`t / time_step = $t / $time_step = $(t / time_step)` must be an integer.") - end - if real(nsteps_rounded) < 0 - return error( - "computed number of steps is negative ($nsteps_rounded), check that total time ($t) and time step ($time_step) signs agree", - ) - end - return time_step, Int(nsteps_rounded) + nsteps_float = t / time_step + nsteps_rounded = round(nsteps_float) + if nsteps_float ≉ nsteps_rounded + return error("`t / time_step = $t / $time_step = $(t / time_step)` must be an integer.") + end + if real(nsteps_rounded) < 0 + return error( + "computed number of steps is negative ($nsteps_rounded), check that total time ($t) and time step ($time_step) signs agree", + ) + end + return time_step, Int(nsteps_rounded) end function time_step_and_nsteps(t, time_step, nsteps) - if time_step * nsteps ≠ t - return error( - "Calling `tdvp(operator, t, state; time_step, nsteps, kwargs...)` with `t = $t`, `time_step = $time_step`, and `nsteps = $nsteps` must satisfy `time_step * nsteps == t`, while `time_step * nsteps = $time_step * $nsteps = $(time_step * nsteps)`.", - ) - end - return time_step, nsteps + if time_step * nsteps ≠ t + return error( + "Calling `tdvp(operator, t, state; time_step, nsteps, kwargs...)` with `t = $t`, `time_step = $time_step`, and `nsteps = $nsteps` must satisfy `time_step * nsteps == t`, while `time_step * nsteps = $time_step * $nsteps = $(time_step * nsteps)`.", + ) + end + return time_step, nsteps end """ @@ -68,30 +68,30 @@ Returns: """ function tdvp( - operator, - t::Number, - init::MPS; - updater_backend="exponentiate", - updater=tdvp_updater(updater_backend), - reverse_step=true, - time_step=nothing, - time_start=zero(t), - nsweeps=nothing, - nsteps=nsweeps, - (step_observer!)=default_sweep_observer(), - (sweep_observer!)=(step_observer!), - kwargs..., -) - time_step, nsteps = time_step_and_nsteps(t, time_step, nsteps) - return alternating_update( - operator, - init; - updater, - reverse_step, - nsweeps=nsteps, - time_start, - time_step, - sweep_observer!, - kwargs..., - ) + operator, + t::Number, + init::MPS; + updater_backend = "exponentiate", + updater = tdvp_updater(updater_backend), + reverse_step = true, + time_step = nothing, + time_start = zero(t), + nsweeps = nothing, + nsteps = nsweeps, + (step_observer!) = default_sweep_observer(), + (sweep_observer!) = (step_observer!), + kwargs..., + ) + time_step, nsteps = time_step_and_nsteps(t, time_step, nsteps) + return alternating_update( + operator, + init; + updater, + reverse_step, + nsweeps = nsteps, + time_start, + time_step, + sweep_observer!, + kwargs..., + ) end diff --git a/src/solvers/tdvporder.jl b/src/solvers/tdvporder.jl index e37e00e..4db3099 100644 --- a/src/solvers/tdvporder.jl +++ b/src/solvers/tdvporder.jl @@ -1,24 +1,24 @@ -struct TDVPOrder{order,direction} end +struct TDVPOrder{order, direction} end -TDVPOrder(order::Int, direction::Base.Ordering) = TDVPOrder{order,direction}() +TDVPOrder(order::Int, direction::Base.Ordering) = TDVPOrder{order, direction}() orderings(::TDVPOrder) = error("Not implemented") sub_time_steps(::TDVPOrder) = error("Not implemented") -function orderings(::TDVPOrder{1,direction}) where {direction} - return [direction, Base.ReverseOrdering(direction)] +function orderings(::TDVPOrder{1, direction}) where {direction} + return [direction, Base.ReverseOrdering(direction)] end sub_time_steps(::TDVPOrder{1}) = [1, 0] -function orderings(::TDVPOrder{2,direction}) where {direction} - return [direction, Base.ReverseOrdering(direction)] +function orderings(::TDVPOrder{2, direction}) where {direction} + return [direction, Base.ReverseOrdering(direction)] end sub_time_steps(::TDVPOrder{2}) = [1 / 2, 1 / 2] -function orderings(::TDVPOrder{4,direction}) where {direction} - return [direction, Base.ReverseOrdering(direction)] +function orderings(::TDVPOrder{4, direction}) where {direction} + return [direction, Base.ReverseOrdering(direction)] end function sub_time_steps(::TDVPOrder{4}) - s = 1 / (2 - 2^(1 / 3)) - return [s / 2, s / 2, (1 - 2 * s) / 2, (1 - 2 * s) / 2, s / 2, s / 2] + s = 1 / (2 - 2^(1 / 3)) + return [s / 2, s / 2, (1 - 2 * s) / 2, (1 - 2 * s) / 2, s / 2, s / 2] end diff --git a/src/solvers/timedependentsum.jl b/src/solvers/timedependentsum.jl index f2998de..9365b57 100644 --- a/src/solvers/timedependentsum.jl +++ b/src/solvers/timedependentsum.jl @@ -4,50 +4,50 @@ using ITensors: ITensor, inds, permute # # expr(t) = coefficients(expr)[1](t) * terms(expr)[1] + coefficients(expr)[2](t) * terms(expr)[2] + … # -struct TimeDependentSum{Coefficients,Terms} - coefficients::Coefficients - terms::Terms +struct TimeDependentSum{Coefficients, Terms} + coefficients::Coefficients + terms::Terms end coefficients(expr::TimeDependentSum) = expr.coefficients terms(expr::TimeDependentSum) = expr.terms function Base.copy(expr::TimeDependentSum) - return TimeDependentSum(coefficients(expr), copy.(terms(expr))) + return TimeDependentSum(coefficients(expr), copy.(terms(expr))) end function Base.:*(c::Number, expr::TimeDependentSum) - scaled_coefficients = map(coefficient -> (t -> c * coefficient(t)), coefficients(expr)) - return TimeDependentSum(scaled_coefficients, terms(expr)) + scaled_coefficients = map(coefficient -> (t -> c * coefficient(t)), coefficients(expr)) + return TimeDependentSum(scaled_coefficients, terms(expr)) end Base.:*(expr::TimeDependentSum, c::Number) = c * expr # Evaluating a `TimeDependentSum` at a certain time # returns a `ScaledSum` at that time. function (expr::TimeDependentSum)(t::Number) - coefficients_t = map(coefficient -> coefficient(t), coefficients(expr)) - return ScaledSum(coefficients_t, terms(expr)) + coefficients_t = map(coefficient -> coefficient(t), coefficients(expr)) + return ScaledSum(coefficients_t, terms(expr)) end # alternating_update inteface function reduced_operator(operator::TimeDependentSum) - return TimeDependentSum(coefficients(operator), reduced_operator.(terms(operator))) + return TimeDependentSum(coefficients(operator), reduced_operator.(terms(operator))) end function ITensorMPS.set_nsite!(operator::TimeDependentSum, nsite) - foreach(t -> set_nsite!(t, nsite), terms(operator)) - return operator + foreach(t -> set_nsite!(t, nsite), terms(operator)) + return operator end function ITensorMPS.position!(operator::TimeDependentSum, state, position) - foreach(t -> position!(t, state, position), terms(operator)) - return operator + foreach(t -> position!(t, state, position), terms(operator)) + return operator end # Represents the sum of scaled terms: # # H = coefficient[1] * H[1] + coefficient * H[2] + … # -struct ScaledSum{Coefficients,Terms} - coefficients::Coefficients - terms::Terms +struct ScaledSum{Coefficients, Terms} + coefficients::Coefficients + terms::Terms end coefficients(expr::ScaledSum) = expr.coefficients @@ -59,10 +59,10 @@ terms(expr::ScaledSum) = expr.terms # # onto x. function scaledsum_apply(expr, x) - return mapreduce(+, zip(coefficients(expr), terms(expr))) do coefficient_and_term - coefficient, term = coefficient_and_term - return coefficient * term(x) - end + return mapreduce(+, zip(coefficients(expr), terms(expr))) do coefficient_and_term + coefficient, term = coefficient_and_term + return coefficient * term(x) + end end (expr::ScaledSum)(x) = scaledsum_apply(expr, x) (expr::ScaledSum)(x::ITensor) = permute(scaledsum_apply(expr, x), inds(x)) diff --git a/src/solvers/update_observer.jl b/src/solvers/update_observer.jl index 3d388ab..c8c28a8 100644 --- a/src/solvers/update_observer.jl +++ b/src/solvers/update_observer.jl @@ -1,24 +1,24 @@ struct EmptyObserver end update_observer!(observer::EmptyObserver; kwargs...) = observer -struct ValuesObserver{Values<:NamedTuple} - values::Values +struct ValuesObserver{Values <: NamedTuple} + values::Values end function update_observer!(observer::ValuesObserver; kwargs...) - for key in keys(observer.values) - observer.values[key][] = kwargs[key] - end - return observer + for key in keys(observer.values) + observer.values[key][] = kwargs[key] + end + return observer end values_observer(; kwargs...) = ValuesObserver(NamedTuple(kwargs)) -struct ComposedObservers{Observers<:Tuple} - observers::Observers +struct ComposedObservers{Observers <: Tuple} + observers::Observers end compose_observers(observers...) = ComposedObservers(observers) function update_observer!(observer::ComposedObservers; kwargs...) - for observerᵢ in observer.observers - update_observer!(observerᵢ; kwargs...) - end - return observer + for observerᵢ in observer.observers + update_observer!(observerᵢ; kwargs...) + end + return observer end diff --git a/src/sweeps.jl b/src/sweeps.jl index 1f56633..44a5fa0 100644 --- a/src/sweeps.jl +++ b/src/sweeps.jl @@ -16,20 +16,20 @@ parameters are: - `noise(sw,n)` -- noise term coefficient for sweep n """ mutable struct Sweeps - nsweep::Int - maxdim::Vector{Int} - cutoff::Vector{Float64} - mindim::Vector{Int} - noise::Vector{Float64} - - function Sweeps(nsw::Int; maxdim=typemax(Int), cutoff=1E-16, mindim=1, noise=0.0) - sw = new(nsw, fill(typemax(Int), nsw), fill(1E-16, nsw), fill(1, nsw), fill(0.0, nsw)) - setmaxdim!(sw, maxdim...) - setmindim!(sw, mindim...) - setcutoff!(sw, cutoff...) - setnoise!(sw, noise...) - return sw - end + nsweep::Int + maxdim::Vector{Int} + cutoff::Vector{Float64} + mindim::Vector{Int} + noise::Vector{Float64} + + function Sweeps(nsw::Int; maxdim = typemax(Int), cutoff = 1.0e-16, mindim = 1, noise = 0.0) + sw = new(nsw, fill(typemax(Int), nsw), fill(1.0e-16, nsw), fill(1, nsw), fill(0.0, nsw)) + setmaxdim!(sw, maxdim...) + setmindim!(sw, mindim...) + setcutoff!(sw, cutoff...) + setnoise!(sw, noise...) + return sw + end end Sweeps() = Sweeps(0) @@ -71,23 +71,23 @@ Sweeps ``` """ function Sweeps(nsw::Int, d::AbstractMatrix) - sw = Sweeps(nsw) - vars = d[1, :] - for (n, var) in enumerate(vars) - inputs = d[2:end, n] - if var == "maxdim" - maxdim!(sw, inputs...) - elseif var == "cutoff" - cutoff!(sw, inputs...) - elseif var == "mindim" - mindim!(sw, inputs...) - elseif var == "noise" - noise!(sw, float.(inputs)...) - else - error("Sweeps object does not have the field $var") + sw = Sweeps(nsw) + vars = d[1, :] + for (n, var) in enumerate(vars) + inputs = d[2:end, n] + if var == "maxdim" + maxdim!(sw, inputs...) + elseif var == "cutoff" + cutoff!(sw, inputs...) + elseif var == "mindim" + mindim!(sw, inputs...) + elseif var == "noise" + noise!(sw, float.(inputs)...) + else + error("Sweeps object does not have the field $var") + end end - end - return sw + return sw end Sweeps(d::AbstractMatrix) = Sweeps(size(d, 1) - 1, d) @@ -151,10 +151,11 @@ If fewer values are provided, the last value is repeated for the remaining sweeps. """ function setmaxdim!(sw::Sweeps, maxdims::Int...)::Nothing - mdims = collect(maxdims) - for i in 1:nsweep(sw) - sw.maxdim[i] = get(mdims, i, maxdims[end]) - end + mdims = collect(maxdims) + for i in 1:nsweep(sw) + sw.maxdim[i] = get(mdims, i, maxdims[end]) + end + return end maxdim!(sw::Sweeps, maxdims::Int...) = setmaxdim!(sw, maxdims...) @@ -167,10 +168,11 @@ If fewer values are provided, the last value is repeated for the remaining sweeps. """ function setmindim!(sw::Sweeps, mindims::Int...)::Nothing - mdims = collect(mindims) - for i in 1:nsweep(sw) - sw.mindim[i] = get(mdims, i, mindims[end]) - end + mdims = collect(mindims) + for i in 1:nsweep(sw) + sw.mindim[i] = get(mdims, i, mindims[end]) + end + return end mindim!(sw::Sweeps, mindims::Int...) = setmindim!(sw, mindims...) @@ -183,10 +185,11 @@ If fewer values are provided, the last value is repeated for the remaining sweeps. """ function setcutoff!(sw::Sweeps, cutoffs::Real...)::Nothing - cuts = collect(cutoffs) - for i in 1:nsweep(sw) - sw.cutoff[i] = get(cuts, i, cutoffs[end]) - end + cuts = collect(cutoffs) + for i in 1:nsweep(sw) + sw.cutoff[i] = get(cuts, i, cutoffs[end]) + end + return end cutoff!(sw::Sweeps, cutoffs::Real...) = setcutoff!(sw, cutoffs...) @@ -199,31 +202,33 @@ If fewer values are provided, the last value is repeated for the remaining sweeps. """ function setnoise!(sw::Sweeps, noises::Real...)::Nothing - nvals = collect(noises) - for i in 1:nsweep(sw) - sw.noise[i] = get(nvals, i, noises[end]) - end + nvals = collect(noises) + for i in 1:nsweep(sw) + sw.noise[i] = get(nvals, i, noises[end]) + end + return end noise!(sw::Sweeps, noises::Real...) = setnoise!(sw, noises...) function Base.show(io::IO, sw::Sweeps) - println(io, "Sweeps") - for n in 1:nsweep(sw) - @printf( - io, - "%d cutoff=%.1E, maxdim=%d, mindim=%d, noise=%.1E\n", - n, - cutoff(sw, n), - maxdim(sw, n), - mindim(sw, n), - noise(sw, n) - ) - end + println(io, "Sweeps") + for n in 1:nsweep(sw) + @printf( + io, + "%d cutoff=%.1E, maxdim=%d, mindim=%d, noise=%.1E\n", + n, + cutoff(sw, n), + maxdim(sw, n), + mindim(sw, n), + noise(sw, n) + ) + end + return end struct SweepNext - N::Int - ncenter::Int + N::Int + ncenter::Int end """ @@ -236,31 +241,31 @@ number. Takes an optional named argument `ncenter` for use with an n-site MPS or DMRG algorithm, with a default of 2-site. """ -function sweepnext(N::Int; ncenter::Int=2)::SweepNext - if ncenter < 0 - error("ncenter must be non-negative") - end - return SweepNext(N, ncenter) +function sweepnext(N::Int; ncenter::Int = 2)::SweepNext + if ncenter < 0 + error("ncenter must be non-negative") + end + return SweepNext(N, ncenter) end -function Base.iterate(sn::SweepNext, state=(0, 1)) - b, ha = state - if ha == 1 - inc = 1 - bstop = sn.N - sn.ncenter + 2 - else - inc = -1 - bstop = 0 - end - new_b = b + inc - new_ha = ha - done = false - if new_b == bstop - new_b -= inc - new_ha += 1 - if ha == 2 - return nothing +function Base.iterate(sn::SweepNext, state = (0, 1)) + b, ha = state + if ha == 1 + inc = 1 + bstop = sn.N - sn.ncenter + 2 + else + inc = -1 + bstop = 0 + end + new_b = b + inc + new_ha = ha + done = false + if new_b == bstop + new_b -= inc + new_ha += 1 + if ha == 2 + return nothing + end end - end - return ((new_b, new_ha), (new_b, new_ha)) + return ((new_b, new_ha), (new_b, new_ha)) end diff --git a/src/tdvp_sweeps.jl b/src/tdvp_sweeps.jl index d39ea92..689f7a8 100644 --- a/src/tdvp_sweeps.jl +++ b/src/tdvp_sweeps.jl @@ -1,17 +1,17 @@ function process_sweeps(s::Sweeps) - return (; - nsweeps=s.nsweep, maxdim=s.maxdim, mindim=s.mindim, cutoff=s.cutoff, noise=s.noise - ) + return (; + nsweeps = s.nsweep, maxdim = s.maxdim, mindim = s.mindim, cutoff = s.cutoff, noise = s.noise, + ) end function tdvp(H, t::Number, psi0::MPS, sweeps::Sweeps; kwargs...) - return tdvp(H, t, psi0; process_sweeps(sweeps)..., kwargs...) + return tdvp(H, t, psi0; process_sweeps(sweeps)..., kwargs...) end function tdvp(solver, H, t::Number, psi0::MPS, sweeps::Sweeps; kwargs...) - return tdvp(solver, H, t, psi0; process_sweeps(sweeps)..., kwargs...) + return tdvp(solver, H, t, psi0; process_sweeps(sweeps)..., kwargs...) end function dmrg(H, psi0::MPS, sweeps::Sweeps; kwargs...) - return dmrg(H, psi0; process_sweeps(sweeps)..., kwargs...) + return dmrg(H, psi0; process_sweeps(sweeps)..., kwargs...) end diff --git a/src/update_observer.jl b/src/update_observer.jl index 43be20a..643cdab 100644 --- a/src/update_observer.jl +++ b/src/update_observer.jl @@ -1,7 +1,7 @@ function update_observer!(observer; kwargs...) - return error("Not implemented") + return error("Not implemented") end function update_observer!(observer::AbstractObserver; kwargs...) - return measure!(observer; kwargs...) + return measure!(observer; kwargs...) end diff --git a/test/Ops/runtests.jl b/test/Ops/runtests.jl index 36cbca6..749a4bd 100644 --- a/test/Ops/runtests.jl +++ b/test/Ops/runtests.jl @@ -7,12 +7,12 @@ ITensors.BLAS.set_num_threads(1) ITensors.disable_threaded_blocksparse() @testset "$(@__DIR__)" begin - filenames = filter(readdir(@__DIR__)) do f - startswith("test_")(f) && endswith(".jl")(f) - end - @testset "Test $(@__DIR__)/$filename" for filename in filenames - println("Running $(@__DIR__)/$filename") - @time include(filename) - end + filenames = filter(readdir(@__DIR__)) do f + startswith("test_")(f) && endswith(".jl")(f) + end + @testset "Test $(@__DIR__)/$filename" for filename in filenames + println("Running $(@__DIR__)/$filename") + @time include(filename) + end end end diff --git a/test/Ops/test_ops_mpo.jl b/test/Ops/test_ops_mpo.jl index c04faea..0536f65 100644 --- a/test/Ops/test_ops_mpo.jl +++ b/test/Ops/test_ops_mpo.jl @@ -6,94 +6,94 @@ using LinearAlgebra: I, norm using Test: @test, @testset @testset "Ops to MPO" begin - ∑H = Sum{Op}() - ∑H += 1.2, "X", 1, "X", 2 - ∑H += 2, "Z", 1 - ∑H += 2, "Z", 2 + ∑H = Sum{Op}() + ∑H += 1.2, "X", 1, "X", 2 + ∑H += 2, "Z", 1 + ∑H += 2, "Z", 2 - @test ∑H isa Sum{Scaled{Float64,Prod{Op}}} + @test ∑H isa Sum{Scaled{Float64, Prod{Op}}} - s = siteinds("Qubit", 2) - H = MPO(∑H, s) + s = siteinds("Qubit", 2) + H = MPO(∑H, s) - Id(n) = Op(I, n) - X(n) = Op("X", n) - Z(n) = Op("Z", n) - T(o) = ITensor(o, s) - Hfull = 1.2 * T(X(1)) * T(X(2)) + 2 * T(Z(1)) * T(Id(2)) + 2 * T(Id(1)) * T(Z(2)) + Id(n) = Op(I, n) + X(n) = Op("X", n) + Z(n) = Op("Z", n) + T(o) = ITensor(o, s) + Hfull = 1.2 * T(X(1)) * T(X(2)) + 2 * T(Z(1)) * T(Id(2)) + 2 * T(Id(1)) * T(Z(2)) - @test prod(H) ≈ Hfull + @test prod(H) ≈ Hfull - @test prod(MPO(X(1), s)) ≈ T(X(1)) * T(Id(2)) - @test prod(MPO(2X(1), s)) ≈ 2T(X(1)) * T(Id(2)) - @test prod(MPO(X(1) * Z(2), s)) ≈ T(X(1)) * T(Z(2)) - @test prod(MPO(3.5X(1) * Z(2), s)) ≈ 3.5T(X(1)) * T(Z(2)) - @test prod(MPO(X(1) + Z(2), s)) ≈ T(X(1)) * T(Id(2)) + T(Id(1)) * T(Z(2)) - @test prod(MPO(X(1) + 3.3Z(2), s)) ≈ T(X(1)) * T(Id(2)) + 3.3T(Id(1)) * T(Z(2)) - @test prod(MPO((X(1) + Z(2)) / 2, s)) ≈ 0.5T(X(1)) * T(Id(2)) + 0.5T(Id(1)) * T(Z(2)) + @test prod(MPO(X(1), s)) ≈ T(X(1)) * T(Id(2)) + @test prod(MPO(2X(1), s)) ≈ 2T(X(1)) * T(Id(2)) + @test prod(MPO(X(1) * Z(2), s)) ≈ T(X(1)) * T(Z(2)) + @test prod(MPO(3.5X(1) * Z(2), s)) ≈ 3.5T(X(1)) * T(Z(2)) + @test prod(MPO(X(1) + Z(2), s)) ≈ T(X(1)) * T(Id(2)) + T(Id(1)) * T(Z(2)) + @test prod(MPO(X(1) + 3.3Z(2), s)) ≈ T(X(1)) * T(Id(2)) + 3.3T(Id(1)) * T(Z(2)) + @test prod(MPO((X(1) + Z(2)) / 2, s)) ≈ 0.5T(X(1)) * T(Id(2)) + 0.5T(Id(1)) * T(Z(2)) - @testset "OpSum to MPO with repeated terms" begin - ℋ = OpSum() - ℋ += "Z", 1 - ℋ += "Z", 1 - ℋ += "X", 2 - ℋ += "Z", 1 - ℋ += "Z", 1 - ℋ += "X", 2 - ℋ += "X", 2 - ℋ_merged = OpSum() - ℋ_merged += (4, "Z", 1) - ℋ_merged += (3, "X", 2) - @test ITensorMPS.sortmergeterms(ℋ) == ℋ_merged + @testset "OpSum to MPO with repeated terms" begin + ℋ = OpSum() + ℋ += "Z", 1 + ℋ += "Z", 1 + ℋ += "X", 2 + ℋ += "Z", 1 + ℋ += "Z", 1 + ℋ += "X", 2 + ℋ += "X", 2 + ℋ_merged = OpSum() + ℋ_merged += (4, "Z", 1) + ℋ_merged += (3, "X", 2) + @test ITensorMPS.sortmergeterms(ℋ) == ℋ_merged - # Test with repeated terms - s = siteinds("S=1/2", 1) - ℋ = OpSum() + ("Z", 1) + ("Z", 1) - H = MPO(ℋ, s) - @test contract(H) ≈ 2 * op("Z", s, 1) - end + # Test with repeated terms + s = siteinds("S=1/2", 1) + ℋ = OpSum() + ("Z", 1) + ("Z", 1) + H = MPO(ℋ, s) + @test contract(H) ≈ 2 * op("Z", s, 1) + end end function heisenberg_old(N) - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - return os + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + return os end function heisenberg(N) - os = Sum{Op}() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - return os + os = Sum{Op}() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + return os end @testset "OpSum comparison" begin - N = 4 - s = siteinds("S=1/2", N) - os_old = heisenberg_old(N) - os_new = heisenberg(N) - @test os_old isa OpSum - @test os_new isa Sum{Scaled{Float64,Prod{Op}}} - Hold = MPO(os_old, s) - Hnew = MPO(os_new, s) - @test prod(Hold) ≈ prod(Hnew) + N = 4 + s = siteinds("S=1/2", N) + os_old = heisenberg_old(N) + os_new = heisenberg(N) + @test os_old isa OpSum + @test os_new isa Sum{Scaled{Float64, Prod{Op}}} + Hold = MPO(os_old, s) + Hnew = MPO(os_new, s) + @test prod(Hold) ≈ prod(Hnew) end @testset "Square Hamiltonian" begin - N = 4 - ℋ = heisenberg(N) - ℋ² = expand(ℋ^2) - s = siteinds("S=1/2", N) - H = MPO(ℋ, s) - H² = MPO(ℋ², s) - @test norm(replaceprime(H' * H, 2 => 1) - H²) ≈ 0 atol = 1e-14 - @test norm(H(H) - H²) ≈ 0 atol = 1e-14 + N = 4 + ℋ = heisenberg(N) + ℋ² = expand(ℋ^2) + s = siteinds("S=1/2", N) + H = MPO(ℋ, s) + H² = MPO(ℋ², s) + @test norm(replaceprime(H' * H, 2 => 1) - H²) ≈ 0 atol = 1.0e-14 + @test norm(H(H) - H²) ≈ 0 atol = 1.0e-14 end end diff --git a/test/Ops/test_trotter.jl b/test/Ops/test_trotter.jl index f03ea7d..05ac225 100644 --- a/test/Ops/test_trotter.jl +++ b/test/Ops/test_trotter.jl @@ -5,37 +5,37 @@ using ITensors: ITensor, apply, contract, replaceprime using ITensors.Ops: Op, Prod, Sum, Trotter function heisenberg(N) - os = Sum{Op}() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - return os + os = Sum{Op}() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + return os end @testset "Heisenberg Trotter" begin - N = 4 - ℋ = heisenberg(N) - s = siteinds("S=1/2", N) - ψ₀ = MPS(s, n -> isodd(n) ? "↑" : "↓") - t = 1.0 - for nsteps in [10, 100] - for order in [1, 2] #, 4] - 𝒰 = exp(im * t * ℋ; alg=Trotter{order}(nsteps)) - U = Prod{ITensor}(𝒰, s) - ∑H = Sum{ITensor}(ℋ, s) - # XXX: Define this, filling out identities. - # ITensor(ℋ, s) - I = contract(MPO(s, "Id")) - H = 0.0 * contract(MPO(s, "Id")) - for h in ∑H - H += apply(h, I) - end - Uʳᵉᶠψ₀ = replaceprime(exp(im * t * H) * prod(ψ₀), 1 => 0) - atol = max(1e-6, 1 / nsteps^order) - @test prod(U(ψ₀)) ≈ Uʳᵉᶠψ₀ atol = atol + N = 4 + ℋ = heisenberg(N) + s = siteinds("S=1/2", N) + ψ₀ = MPS(s, n -> isodd(n) ? "↑" : "↓") + t = 1.0 + for nsteps in [10, 100] + for order in [1, 2] #, 4] + 𝒰 = exp(im * t * ℋ; alg = Trotter{order}(nsteps)) + U = Prod{ITensor}(𝒰, s) + ∑H = Sum{ITensor}(ℋ, s) + # XXX: Define this, filling out identities. + # ITensor(ℋ, s) + I = contract(MPO(s, "Id")) + H = 0.0 * contract(MPO(s, "Id")) + for h in ∑H + H += apply(h, I) + end + Uʳᵉᶠψ₀ = replaceprime(exp(im * t * H) * prod(ψ₀), 1 => 0) + atol = max(1.0e-6, 1 / nsteps^order) + @test prod(U(ψ₀)) ≈ Uʳᵉᶠψ₀ atol = atol + end end - end end end diff --git a/test/base/backup/test_arraystorage.jl b/test/base/backup/test_arraystorage.jl index d9c25b5..09290a1 100644 --- a/test/base/backup/test_arraystorage.jl +++ b/test/base/backup/test_arraystorage.jl @@ -2,21 +2,21 @@ using ITensors using Test @testset "Test ArrayStorage DMRG QN $conserve_qns" for conserve_qns in (false,) # true) - n = 4 - s = siteinds("S=1/2", n; conserve_qns) - heisenberg_opsum = function (n) - os = OpSum() - for j in 1:(n - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 + n = 4 + s = siteinds("S=1/2", n; conserve_qns) + heisenberg_opsum = function (n) + os = OpSum() + for j in 1:(n - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + return os end - return os - end - H = MPO(heisenberg_opsum(n), s) - ψ = random_mps(s, j -> isodd(j) ? "↑" : "↓"; linkdims=4) - dmrg_kwargs = (; nsweeps=2, cutoff=[1e-4, 1e-12], maxdim=10, outputlevel=0) - e1, ψ1 = dmrg(NDTensors.to_arraystorage.((H, ψ))...; dmrg_kwargs...) - e2, ψ2 = dmrg(H, ψ; dmrg_kwargs...) - @test e1 ≈ e2 + H = MPO(heisenberg_opsum(n), s) + ψ = random_mps(s, j -> isodd(j) ? "↑" : "↓"; linkdims = 4) + dmrg_kwargs = (; nsweeps = 2, cutoff = [1.0e-4, 1.0e-12], maxdim = 10, outputlevel = 0) + e1, ψ1 = dmrg(NDTensors.to_arraystorage.((H, ψ))...; dmrg_kwargs...) + e2, ψ2 = dmrg(H, ψ; dmrg_kwargs...) + @test e1 ≈ e2 end diff --git a/test/base/runtests.jl b/test/base/runtests.jl index 84bca80..7cb0425 100644 --- a/test/base/runtests.jl +++ b/test/base/runtests.jl @@ -6,17 +6,17 @@ ITensors.BLAS.set_num_threads(1) ITensors.disable_threaded_blocksparse() @testset "$(@__DIR__)" begin - filenames = filter(readdir(@__DIR__)) do file - return startswith("test_")(file) && endswith(".jl")(file) - end - @testset "Test $(@__DIR__)/$filename" for filename in filenames - println("Running $(@__DIR__)/$filename") - @time include(filename) - end + filenames = filter(readdir(@__DIR__)) do file + return startswith("test_")(file) && endswith(".jl")(file) + end + @testset "Test $(@__DIR__)/$filename" for filename in filenames + println("Running $(@__DIR__)/$filename") + @time include(filename) + end - test_dirs = ["test_solvers"] - @testset "Test $(@__DIR__)/$test_dir" for test_dir in test_dirs - println("Running $(@__DIR__)/$test_dir/runtests.jl") - @time include(joinpath(@__DIR__, test_dir, "runtests.jl")) - end + test_dirs = ["test_solvers"] + @testset "Test $(@__DIR__)/$test_dir" for test_dir in test_dirs + println("Running $(@__DIR__)/$test_dir/runtests.jl") + @time include(joinpath(@__DIR__, test_dir, "runtests.jl")) + end end diff --git a/test/base/test_abstractprojmpo.jl b/test/base/test_abstractprojmpo.jl index 07134de..b8994a8 100644 --- a/test/base/test_abstractprojmpo.jl +++ b/test/base/test_abstractprojmpo.jl @@ -4,77 +4,77 @@ using Test using ITensorMPS: ITensorMPS @testset "AbstractProjMPO (eltype=$elt, conserve_qns=$conserve_qns)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ), - conserve_qns in [false, true] + Float32, Float64, Complex{Float32}, Complex{Float64}, + ), + conserve_qns in [false, true] - n = 4 - s = siteinds("S=1/2", n; conserve_qns) - o = MPO(elt, s, "I") - x = MPS(elt, s, j -> isodd(j) ? "↑" : "↓") - pmpo = ProjMPO(o) - position!(pmpo, x, 2) - @testset "ProjMPO (storage=$storage)" for storage in (identity, ITensors.disk) - po = storage(pmpo) + n = 4 + s = siteinds("S=1/2", n; conserve_qns) + o = MPO(elt, s, "I") + x = MPS(elt, s, j -> isodd(j) ? "↑" : "↓") + pmpo = ProjMPO(o) + position!(pmpo, x, 2) + @testset "ProjMPO (storage=$storage)" for storage in (identity, ITensors.disk) + po = storage(pmpo) - # `AbstractProjMPO` interface. - @test ITensorMPS.nsite(po) == 2 - @test ITensorMPS.site_range(po) == 2:3 - @test eltype(po) == elt - @test isnothing(ITensorMPS.checkflux(po)) - po_contracted = contract(po, ITensor(one(Bool))) - @test po_contracted isa ITensor - @test ndims(po_contracted) == 8 - @test eltype(po_contracted) == elt + # `AbstractProjMPO` interface. + @test ITensorMPS.nsite(po) == 2 + @test ITensorMPS.site_range(po) == 2:3 + @test eltype(po) == elt + @test isnothing(ITensorMPS.checkflux(po)) + po_contracted = contract(po, ITensor(one(Bool))) + @test po_contracted isa ITensor + @test ndims(po_contracted) == 8 + @test eltype(po_contracted) == elt - # Specific to `ProjMPO`. - @test lproj(po) isa ITensor - @test ndims(lproj(po)) == 3 - @test eltype(lproj(po)) == elt - @test rproj(po) isa ITensor - @test ndims(rproj(po)) == 3 - @test eltype(rproj(po)) == elt - end - @testset "ProjMPOSum (storage=$storage)" for storage in (identity, ITensors.disk) - po = storage(ProjMPOSum([pmpo, pmpo])) + # Specific to `ProjMPO`. + @test lproj(po) isa ITensor + @test ndims(lproj(po)) == 3 + @test eltype(lproj(po)) == elt + @test rproj(po) isa ITensor + @test ndims(rproj(po)) == 3 + @test eltype(rproj(po)) == elt + end + @testset "ProjMPOSum (storage=$storage)" for storage in (identity, ITensors.disk) + po = storage(ProjMPOSum([pmpo, pmpo])) - # `AbstractProjMPO` interface. - @test ITensorMPS.nsite(po) == 2 - @test ITensorMPS.site_range(po) == 2:3 - @test eltype(po) == elt - @test isnothing(ITensorMPS.checkflux(po)) - po_contracted = contract(po, ITensor(one(Bool))) - @test po_contracted isa ITensor - @test ndims(po_contracted) == 8 - @test eltype(po_contracted) == elt + # `AbstractProjMPO` interface. + @test ITensorMPS.nsite(po) == 2 + @test ITensorMPS.site_range(po) == 2:3 + @test eltype(po) == elt + @test isnothing(ITensorMPS.checkflux(po)) + po_contracted = contract(po, ITensor(one(Bool))) + @test po_contracted isa ITensor + @test ndims(po_contracted) == 8 + @test eltype(po_contracted) == elt - # Specific to `ProjMPOSum`. - @test length(ITensorMPS.terms(po)) == 2 - end - @testset "ITensorMPS.ProjMPS" begin - # TODO: Replace with `ProjOuter`, make it into - # a proper `AbstractProjMPO`. - px = ITensorMPS.ProjMPS(x) - position!(px, x, 2) + # Specific to `ProjMPOSum`. + @test length(ITensorMPS.terms(po)) == 2 + end + @testset "ITensorMPS.ProjMPS" begin + # TODO: Replace with `ProjOuter`, make it into + # a proper `AbstractProjMPO`. + px = ITensorMPS.ProjMPS(x) + position!(px, x, 2) - # `AbstractProjMPO` interface. - @test ITensorMPS.nsite(px) == 2 - @test ITensorMPS.site_range(px) == 2:3 - @test_broken eltype(px) == elt - @test isnothing(ITensorMPS.checkflux(px)) - @test_broken contract(px, ITensor(one(Bool))) - end - @testset "ITensorMPS.ProjMPO_MPS" begin - # TODO: Replace with `ProjOuter`, make it into - # a proper `AbstractProjMPO`. - po = ITensorMPS.ProjMPO_MPS(o, [x]) - position!(po, x, 2) + # `AbstractProjMPO` interface. + @test ITensorMPS.nsite(px) == 2 + @test ITensorMPS.site_range(px) == 2:3 + @test_broken eltype(px) == elt + @test isnothing(ITensorMPS.checkflux(px)) + @test_broken contract(px, ITensor(one(Bool))) + end + @testset "ITensorMPS.ProjMPO_MPS" begin + # TODO: Replace with `ProjOuter`, make it into + # a proper `AbstractProjMPO`. + po = ITensorMPS.ProjMPO_MPS(o, [x]) + position!(po, x, 2) - # `AbstractProjMPO` interface. - @test ITensorMPS.nsite(po) == 2 - @test ITensorMPS.site_range(po) == 2:3 - @test_broken eltype(po) == elt - @test isnothing(ITensorMPS.checkflux(po)) - @test_broken contract(po, ITensor(one(Bool))) - end + # `AbstractProjMPO` interface. + @test ITensorMPS.nsite(po) == 2 + @test ITensorMPS.site_range(po) == 2:3 + @test_broken eltype(po) == elt + @test isnothing(ITensorMPS.checkflux(po)) + @test_broken contract(po, ITensor(one(Bool))) + end end diff --git a/test/base/test_algorithm.jl b/test/base/test_algorithm.jl index eb0f2e1..2afb73e 100644 --- a/test/base/test_algorithm.jl +++ b/test/base/test_algorithm.jl @@ -2,38 +2,38 @@ using ITensors using Test @testset "Algorithm" begin - alg = ITensors.Algorithm("X") + alg = ITensors.Algorithm("X") - @test alg isa ITensors.Algorithm"X" - @test alg == ITensors.Algorithm"X"() + @test alg isa ITensors.Algorithm"X" + @test alg == ITensors.Algorithm"X"() - s = siteinds("S=1/2", 4) - A = MPO(s, "Id") - ψ = random_mps(s) + s = siteinds("S=1/2", 4) + A = MPO(s, "Id") + ψ = random_mps(s) - @test_throws MethodError contract(alg, A, ψ) - @test_throws MethodError contract(A, ψ; method="X") - @test_throws MethodError contract(A, ψ; alg="X") - @test contract(ITensors.Algorithm("densitymatrix"), A, ψ) ≈ A * ψ - @test contract(ITensors.Algorithm("naive"), A, ψ) ≈ A * ψ - @test contract(A, ψ; alg="densitymatrix") ≈ A * ψ - @test contract(A, ψ; method="densitymatrix") ≈ A * ψ - @test contract(A, ψ; alg="naive") ≈ A * ψ - @test contract(A, ψ; method="naive") ≈ A * ψ + @test_throws MethodError contract(alg, A, ψ) + @test_throws MethodError contract(A, ψ; method = "X") + @test_throws MethodError contract(A, ψ; alg = "X") + @test contract(ITensors.Algorithm("densitymatrix"), A, ψ) ≈ A * ψ + @test contract(ITensors.Algorithm("naive"), A, ψ) ≈ A * ψ + @test contract(A, ψ; alg = "densitymatrix") ≈ A * ψ + @test contract(A, ψ; method = "densitymatrix") ≈ A * ψ + @test contract(A, ψ; alg = "naive") ≈ A * ψ + @test contract(A, ψ; method = "naive") ≈ A * ψ - B = copy(A) - truncate!(ITensors.Algorithm("frobenius"), B) - @test A ≈ B + B = copy(A) + truncate!(ITensors.Algorithm("frobenius"), B) + @test A ≈ B - B = copy(A) - truncate!(B; alg="frobenius") - @test A ≈ B + B = copy(A) + truncate!(B; alg = "frobenius") + @test A ≈ B - # Custom algorithm - function ITensors.truncate!(::ITensors.Algorithm"my_new_algorithm", A::MPO; cutoff=1e-15) - return "my_new_algorithm was called with cutoff $cutoff" - end - cutoff = 1e-5 - res = truncate!(A; alg="my_new_algorithm", cutoff=cutoff) - @test res == "my_new_algorithm was called with cutoff $cutoff" + # Custom algorithm + function ITensors.truncate!(::ITensors.Algorithm"my_new_algorithm", A::MPO; cutoff = 1.0e-15) + return "my_new_algorithm was called with cutoff $cutoff" + end + cutoff = 1.0e-5 + res = truncate!(A; alg = "my_new_algorithm", cutoff = cutoff) + @test res == "my_new_algorithm was called with cutoff $cutoff" end diff --git a/test/base/test_autompo.jl b/test/base/test_autompo.jl index c4ebeec..5828d1b 100644 --- a/test/base/test_autompo.jl +++ b/test/base/test_autompo.jl @@ -4,1250 +4,1250 @@ using NDTensors: scalartype include(joinpath(@__DIR__, "utils", "util.jl")) -function components_to_opsum(comps, n; reverse::Bool=true) - opsum = OpSum() - for (factor, operators, sites) in comps - # reverse ordering for compatibility - sites = reverse ? (n + 1) .- sites : sites - sites_and_ops = [[Matrix(operator), site] for (operator, site) in zip(operators, sites)] - sites_and_ops = [vcat(sites_and_ops...)...] - opsum += factor, sites_and_ops... - end - return opsum +function components_to_opsum(comps, n; reverse::Bool = true) + opsum = OpSum() + for (factor, operators, sites) in comps + # reverse ordering for compatibility + sites = reverse ? (n + 1) .- sites : sites + sites_and_ops = [[Matrix(operator), site] for (operator, site) in zip(operators, sites)] + sites_and_ops = [vcat(sites_and_ops...)...] + opsum += factor, sites_and_ops... + end + return opsum end function isingMPO(sites)::MPO - H = MPO(sites) - N = length(H) - link = Vector{Index}(undef, N + 1) - for n in 1:(N + 1) - link[n] = Index(3, "Link,Ising,l=$(n-1)") - end - for n in 1:N - s = sites[n] - ll = link[n] - rl = link[n + 1] - H[n] = ITensor(dag(ll), dag(s), s', rl) - H[n] += setelt(ll => 1) * setelt(rl => 1) * op(sites, "Id", n) - H[n] += setelt(ll => 3) * setelt(rl => 3) * op(sites, "Id", n) - H[n] += setelt(ll => 2) * setelt(rl => 1) * op(sites, "Sz", n) - H[n] += setelt(ll => 3) * setelt(rl => 2) * op(sites, "Sz", n) - end - LE = ITensor(link[1]) - LE[3] = 1.0 - RE = ITensor(dag(link[N + 1])) - RE[1] = 1.0 - H[1] *= LE - H[N] *= RE - return H + H = MPO(sites) + N = length(H) + link = Vector{Index}(undef, N + 1) + for n in 1:(N + 1) + link[n] = Index(3, "Link,Ising,l=$(n - 1)") + end + for n in 1:N + s = sites[n] + ll = link[n] + rl = link[n + 1] + H[n] = ITensor(dag(ll), dag(s), s', rl) + H[n] += setelt(ll => 1) * setelt(rl => 1) * op(sites, "Id", n) + H[n] += setelt(ll => 3) * setelt(rl => 3) * op(sites, "Id", n) + H[n] += setelt(ll => 2) * setelt(rl => 1) * op(sites, "Sz", n) + H[n] += setelt(ll => 3) * setelt(rl => 2) * op(sites, "Sz", n) + end + LE = ITensor(link[1]) + LE[3] = 1.0 + RE = ITensor(dag(link[N + 1])) + RE[1] = 1.0 + H[1] *= LE + H[N] *= RE + return H end -function heisenbergMPO(sites, h::Vector{Float64}, onsite::String="Sz")::MPO - H = MPO(sites) - N = length(H) - link = Vector{Index}(undef, N + 1) - for n in 1:(N + 1) - link[n] = Index(5, "Link,Heis,l=$(n-1)") - end - for n in 1:N - s = sites[n] - ll = link[n] - rl = link[n + 1] - H[n] = ITensor(ll, s, s', rl) - H[n] += setelt(ll => 1) * setelt(rl => 1) * op(sites, "Id", n) - H[n] += setelt(ll => 5) * setelt(rl => 5) * op(sites, "Id", n) - H[n] += setelt(ll => 2) * setelt(rl => 1) * op(sites, "S+", n) - H[n] += setelt(ll => 3) * setelt(rl => 1) * op(sites, "S-", n) - H[n] += setelt(ll => 4) * setelt(rl => 1) * op(sites, "Sz", n) - H[n] += setelt(ll => 5) * setelt(rl => 2) * op(sites, "S-", n) * 0.5 - H[n] += setelt(ll => 5) * setelt(rl => 3) * op(sites, "S+", n) * 0.5 - H[n] += setelt(ll => 5) * setelt(rl => 4) * op(sites, "Sz", n) - H[n] += setelt(ll => 5) * setelt(rl => 1) * op(sites, onsite, n) * h[n] - end - H[1] *= setelt(link[1] => 5) - H[N] *= setelt(link[N + 1] => 1) - return H +function heisenbergMPO(sites, h::Vector{Float64}, onsite::String = "Sz")::MPO + H = MPO(sites) + N = length(H) + link = Vector{Index}(undef, N + 1) + for n in 1:(N + 1) + link[n] = Index(5, "Link,Heis,l=$(n - 1)") + end + for n in 1:N + s = sites[n] + ll = link[n] + rl = link[n + 1] + H[n] = ITensor(ll, s, s', rl) + H[n] += setelt(ll => 1) * setelt(rl => 1) * op(sites, "Id", n) + H[n] += setelt(ll => 5) * setelt(rl => 5) * op(sites, "Id", n) + H[n] += setelt(ll => 2) * setelt(rl => 1) * op(sites, "S+", n) + H[n] += setelt(ll => 3) * setelt(rl => 1) * op(sites, "S-", n) + H[n] += setelt(ll => 4) * setelt(rl => 1) * op(sites, "Sz", n) + H[n] += setelt(ll => 5) * setelt(rl => 2) * op(sites, "S-", n) * 0.5 + H[n] += setelt(ll => 5) * setelt(rl => 3) * op(sites, "S+", n) * 0.5 + H[n] += setelt(ll => 5) * setelt(rl => 4) * op(sites, "Sz", n) + H[n] += setelt(ll => 5) * setelt(rl => 1) * op(sites, onsite, n) * h[n] + end + H[1] *= setelt(link[1] => 5) + H[N] *= setelt(link[N + 1] => 1) + return H end function NNheisenbergMPO(sites, J1::Float64, J2::Float64)::MPO - H = MPO(sites) - N = length(H) - link = Vector{Index}(undef, N + 1) - if hasqns(sites[1]) - for n in 1:(N + 1) - link[n] = Index( - [ - QN() => 1, - QN("Sz", -2) => 1, - QN("Sz", +2) => 1, - QN() => 1, - QN("Sz", -2) => 1, - QN("Sz", +2) => 1, - QN() => 2, - ], - "Link,H,l=$(n-1)", - ) + H = MPO(sites) + N = length(H) + link = Vector{Index}(undef, N + 1) + if hasqns(sites[1]) + for n in 1:(N + 1) + link[n] = Index( + [ + QN() => 1, + QN("Sz", -2) => 1, + QN("Sz", +2) => 1, + QN() => 1, + QN("Sz", -2) => 1, + QN("Sz", +2) => 1, + QN() => 2, + ], + "Link,H,l=$(n - 1)", + ) + end + else + for n in 1:(N + 1) + link[n] = Index(8, "Link,H,l=$(n - 1)") + end end - else - for n in 1:(N + 1) - link[n] = Index(8, "Link,H,l=$(n-1)") + for n in 1:N + s = sites[n] + ll = dag(link[n]) + rl = link[n + 1] + H[n] = ITensor(ll, dag(s), s', rl) + H[n] += onehot(ll => 1) * onehot(rl => 1) * op(sites, "Id", n) + H[n] += onehot(ll => 8) * onehot(rl => 8) * op(sites, "Id", n) + + H[n] += onehot(ll => 2) * onehot(rl => 1) * op(sites, "S-", n) + H[n] += onehot(ll => 5) * onehot(rl => 2) * op(sites, "Id", n) + H[n] += onehot(ll => 8) * onehot(rl => 2) * op(sites, "S+", n) * J1 / 2 + H[n] += onehot(ll => 8) * onehot(rl => 5) * op(sites, "S+", n) * J2 / 2 + + H[n] += onehot(ll => 3) * onehot(rl => 1) * op(sites, "S+", n) + H[n] += onehot(ll => 6) * onehot(rl => 3) * op(sites, "Id", n) + H[n] += onehot(ll => 8) * onehot(rl => 3) * op(sites, "S-", n) * J1 / 2 + H[n] += onehot(ll => 8) * onehot(rl => 6) * op(sites, "S-", n) * J2 / 2 + + H[n] += onehot(ll => 4) * onehot(rl => 1) * op(sites, "Sz", n) + H[n] += onehot(ll => 7) * onehot(rl => 4) * op(sites, "Id", n) + H[n] += onehot(ll => 8) * onehot(rl => 4) * op(sites, "Sz", n) * J1 + H[n] += onehot(ll => 8) * onehot(rl => 7) * op(sites, "Sz", n) * J2 end - end - for n in 1:N - s = sites[n] - ll = dag(link[n]) - rl = link[n + 1] - H[n] = ITensor(ll, dag(s), s', rl) - H[n] += onehot(ll => 1) * onehot(rl => 1) * op(sites, "Id", n) - H[n] += onehot(ll => 8) * onehot(rl => 8) * op(sites, "Id", n) - - H[n] += onehot(ll => 2) * onehot(rl => 1) * op(sites, "S-", n) - H[n] += onehot(ll => 5) * onehot(rl => 2) * op(sites, "Id", n) - H[n] += onehot(ll => 8) * onehot(rl => 2) * op(sites, "S+", n) * J1 / 2 - H[n] += onehot(ll => 8) * onehot(rl => 5) * op(sites, "S+", n) * J2 / 2 - - H[n] += onehot(ll => 3) * onehot(rl => 1) * op(sites, "S+", n) - H[n] += onehot(ll => 6) * onehot(rl => 3) * op(sites, "Id", n) - H[n] += onehot(ll => 8) * onehot(rl => 3) * op(sites, "S-", n) * J1 / 2 - H[n] += onehot(ll => 8) * onehot(rl => 6) * op(sites, "S-", n) * J2 / 2 - - H[n] += onehot(ll => 4) * onehot(rl => 1) * op(sites, "Sz", n) - H[n] += onehot(ll => 7) * onehot(rl => 4) * op(sites, "Id", n) - H[n] += onehot(ll => 8) * onehot(rl => 4) * op(sites, "Sz", n) * J1 - H[n] += onehot(ll => 8) * onehot(rl => 7) * op(sites, "Sz", n) * J2 - end - H[1] *= onehot(link[1] => 8) - H[N] *= onehot(dag(link[N + 1]) => 1) - return H + H[1] *= onehot(link[1] => 8) + H[N] *= onehot(dag(link[N + 1]) => 1) + return H end function threeSiteIsingMPO(sites, h::Vector{Float64})::MPO - H = MPO(sites) - N = length(H) - link = Vector{Index}(undef, N + 1) - for n in 1:(N + 1) - link[n] = Index(4, "Link,l=$(n-1)") - end - for n in 1:N - s = sites[n] - ll = link[n] - rl = link[n + 1] - H[n] = ITensor(ll, s, s', rl) - H[n] += setelt(ll => 1) * setelt(rl => 1) * op(sites, "Id", n) - H[n] += setelt(ll => 4) * setelt(rl => 4) * op(sites, "Id", n) - H[n] += setelt(ll => 2) * setelt(rl => 1) * op(sites, "Sz", n) - H[n] += setelt(ll => 3) * setelt(rl => 2) * op(sites, "Sz", n) - H[n] += setelt(ll => 4) * setelt(rl => 3) * op(sites, "Sz", n) - H[n] += setelt(ll => 4) * setelt(rl => 1) * op(sites, "Sx", n) * h[n] - end - H[1] *= setelt(link[1] => 4) - H[N] *= setelt(link[N + 1] => 1) - return H + H = MPO(sites) + N = length(H) + link = Vector{Index}(undef, N + 1) + for n in 1:(N + 1) + link[n] = Index(4, "Link,l=$(n - 1)") + end + for n in 1:N + s = sites[n] + ll = link[n] + rl = link[n + 1] + H[n] = ITensor(ll, s, s', rl) + H[n] += setelt(ll => 1) * setelt(rl => 1) * op(sites, "Id", n) + H[n] += setelt(ll => 4) * setelt(rl => 4) * op(sites, "Id", n) + H[n] += setelt(ll => 2) * setelt(rl => 1) * op(sites, "Sz", n) + H[n] += setelt(ll => 3) * setelt(rl => 2) * op(sites, "Sz", n) + H[n] += setelt(ll => 4) * setelt(rl => 3) * op(sites, "Sz", n) + H[n] += setelt(ll => 4) * setelt(rl => 1) * op(sites, "Sx", n) * h[n] + end + H[1] *= setelt(link[1] => 4) + H[N] *= setelt(link[N + 1] => 1) + return H end function fourSiteIsingMPO(sites)::MPO - H = MPO(sites) - N = length(H) - link = Vector{Index}(undef, N + 1) - for n in 1:(N + 1) - link[n] = Index(5, "Link,l=$(n-1)") - end - for n in 1:N - s = sites[n] - ll = link[n] - rl = link[n + 1] - H[n] = ITensor(ll, s, s', rl) - H[n] += setelt(ll => 1) * setelt(rl => 1) * op(sites, "Id", n) - H[n] += setelt(ll => 5) * setelt(rl => 5) * op(sites, "Id", n) - H[n] += setelt(ll => 2) * setelt(rl => 1) * op(sites, "Sz", n) - H[n] += setelt(ll => 3) * setelt(rl => 2) * op(sites, "Sz", n) - H[n] += setelt(ll => 4) * setelt(rl => 3) * op(sites, "Sz", n) - H[n] += setelt(ll => 5) * setelt(rl => 4) * op(sites, "Sz", n) - end - H[1] *= setelt(link[1] => 5) - H[N] *= setelt(link[N + 1] => 1) - return H + H = MPO(sites) + N = length(H) + link = Vector{Index}(undef, N + 1) + for n in 1:(N + 1) + link[n] = Index(5, "Link,l=$(n - 1)") + end + for n in 1:N + s = sites[n] + ll = link[n] + rl = link[n + 1] + H[n] = ITensor(ll, s, s', rl) + H[n] += setelt(ll => 1) * setelt(rl => 1) * op(sites, "Id", n) + H[n] += setelt(ll => 5) * setelt(rl => 5) * op(sites, "Id", n) + H[n] += setelt(ll => 2) * setelt(rl => 1) * op(sites, "Sz", n) + H[n] += setelt(ll => 3) * setelt(rl => 2) * op(sites, "Sz", n) + H[n] += setelt(ll => 4) * setelt(rl => 3) * op(sites, "Sz", n) + H[n] += setelt(ll => 5) * setelt(rl => 4) * op(sites, "Sz", n) + end + H[1] *= setelt(link[1] => 5) + H[N] *= setelt(link[N + 1] => 1) + return H end @testset "OpSum" begin - N = 10 - - @test !ITensors.using_auto_fermion() - - @testset "Show MPOTerm" begin - os = OpSum() - add!(os, "Sz", 1, "Sz", 2) - @test length(sprint(show, os[1])) > 1 - end - - @testset "Multisite operator" begin - os = OpSum() - os += ("CX", 1, 2) - os += (2.3, "R", 3, 4, "S", 2) - os += ("X", 3) - @test length(os) == 3 - @test coefficient(os[1]) == 1 - @test length(os[1]) == 1 - @test ITensors.which_op(os[1][1]) == "CX" - @test ITensors.sites(os[1][1]) == (1, 2) - @test coefficient(os[2]) == 2.3 - @test length(os[2]) == 2 - @test ITensors.which_op(os[2][1]) == "R" - @test ITensors.sites(os[2][1]) == (3, 4) - @test ITensors.which_op(os[2][2]) == "S" - @test ITensors.sites(os[2][2]) == (2,) - @test coefficient(os[3]) == 1 - @test length(os[3]) == 1 - @test ITensors.which_op(os[3][1]) == "X" - @test ITensors.sites(os[3][1]) == (3,) - - os = OpSum() + ("CX", 1, 2) - @test length(os) == 1 - @test coefficient(os[1]) == 1 - @test length(os[1]) == 1 - @test ITensors.which_op(os[1][1]) == "CX" - @test ITensors.sites(os[1][1]) == (1, 2) - - # Coordinate - os = OpSum() + ("X", (1, 2)) - @test length(os) == 1 - @test coefficient(os[1]) == 1 - @test length(os[1]) == 1 - @test ITensors.which_op(os[1][1]) == "X" - @test ITensors.sites(os[1][1]) == ((1, 2),) - - os = OpSum() + ("CX", 1, 2, (ϕ=π / 3,)) - @test length(os) == 1 - @test coefficient(os[1]) == 1 - @test length(os[1]) == 1 - @test ITensors.which_op(os[1][1]) == "CX" - @test ITensors.sites(os[1][1]) == (1, 2) - @test ITensors.params(os[1][1]) == (ϕ=π / 3,) - - os = OpSum() + ("CX", 1, 2, (ϕ=π / 3,), "CZ", 3, 4, (θ=π / 2,)) - @test length(os) == 1 - @test coefficient(os[1]) == 1 - @test length(os[1]) == 2 - @test ITensors.which_op(os[1][1]) == "CX" - @test ITensors.sites(os[1][1]) == (1, 2) - @test ITensors.params(os[1][1]) == (ϕ=π / 3,) - @test ITensors.which_op(os[1][2]) == "CZ" - @test ITensors.sites(os[1][2]) == (3, 4) - @test ITensors.params(os[1][2]) == (θ=π / 2,) - - os = OpSum() + ("CX", (ϕ=π / 3,), 1, 2, "CZ", (θ=π / 2,), 3, 4) - @test length(os) == 1 - @test coefficient(os[1]) == 1 - @test length(os[1]) == 2 - @test ITensors.which_op(os[1][1]) == "CX" - @test ITensors.sites(os[1][1]) == (1, 2) - @test ITensors.params(os[1][1]) == (ϕ=π / 3,) - @test ITensors.which_op(os[1][2]) == "CZ" - @test ITensors.sites(os[1][2]) == (3, 4) - @test ITensors.params(os[1][2]) == (θ=π / 2,) - - os = OpSum() + ("CX", 1, 2, (ϕ=π / 3,)) - @test length(os) == 1 - @test coefficient(os[1]) == 1 - @test length(os[1]) == 1 - @test ITensors.which_op(os[1][1]) == "CX" - @test ITensors.sites(os[1][1]) == (1, 2) - @test ITensors.params(os[1][1]) == (ϕ=π / 3,) - - os = OpSum() + (1 + 2im, "CRz", (ϕ=π / 3,), 1, 2) - @test length(os) == 1 - @test coefficient(os[1]) == 1 + 2im - @test length(os[1]) == 1 - @test ITensors.which_op(os[1][1]) == "CRz" - @test ITensors.sites(os[1][1]) == (1, 2) - @test ITensors.params(os[1][1]) == (ϕ=π / 3,) - - os = OpSum() + ("CRz", (ϕ=π / 3,), 1, 2) - @test length(os) == 1 - @test coefficient(os[1]) == 1 - @test length(os[1]) == 1 - @test ITensors.which_op(os[1][1]) == "CRz" - @test ITensors.sites(os[1][1]) == (1, 2) - @test ITensors.params(os[1][1]) == (ϕ=π / 3,) - end - - @testset "Show OpSum" begin - os = OpSum() - add!(os, "Sz", 1, "Sz", 2) - add!(os, "Sz", 2, "Sz", 3) - @test length(sprint(show, os)) > 1 - end - - @testset "OpSum algebra (eltype=$elt)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ) - n = 5 - sites = siteinds("S=1/2", n) - O1 = OpSum() - for j in 1:(n - 1) - O1 += "Sz", j, "Sz", j + 1 - end - O2 = OpSum() - for j in 1:n - O2 += "Sx", j - end - O = O1 + 2 * O2 - @test length(O) == 2 * n - 1 - H1 = MPO(elt, O1, sites) - H2 = MPO(elt, O2, sites) - H = H1 + 2 * H2 - @test scalartype(H1) == elt - @test scalartype(H2) == elt - @test scalartype(H) == elt - @test prod(MPO(O, sites)) ≈ prod(H) - - @test scalartype(MPO(elt, Op("Sz", 1), sites)) == elt - @test scalartype(MPO(elt, Op("Sz", 1) + Op("Sz", 2), sites)) == elt - @test scalartype(MPO(elt, 2 * Op("Sz", 1) + 3 * Op("Sz", 2), sites)) == elt - @test scalartype(MPO(elt, 2 * Op("Sz", 1), sites)) == elt - @test scalartype(MPO(elt, Op("Sz", 1) * Op("Sz", 2), sites)) == elt - @test scalartype(MPO(elt, 2 * Op("Sz", 1) * Op("Sz", 2), sites)) == elt - - O = O1 - 2 * O2 - @test length(O) == 2 * n - 1 - H1 = MPO(elt, O1, sites) - H2 = MPO(elt, O2, sites) - H = H1 - 2 * H2 - @test scalartype(H1) == elt - @test scalartype(H2) == elt - @test scalartype(H) == elt - @test prod(MPO(O, sites)) ≈ prod(H) - - O = O1 - O2 / 2 - @test length(O) == 2 * n - 1 - H1 = MPO(elt, O1, sites) - H2 = MPO(elt, O2, sites) - H = H1 - H2 / 2 - @test scalartype(H1) == elt - @test scalartype(H2) == elt - @test scalartype(H) == elt - @test prod(MPO(O, sites)) ≈ prod(H) - end - - @testset "Single creation op" begin - os = OpSum() - add!(os, "Adagup", 3) - sites = siteinds("Electron", N) - W = MPO(os, sites) - psi = makeRandomMPS(sites) - cdu_psi = copy(psi) - cdu_psi[3] = noprime(cdu_psi[3] * op(sites, "Adagup", 3)) - @test inner(psi', W, psi) ≈ inner(cdu_psi, psi) - end - - @testset "Ising" begin - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - @test scalartype(Ha) <: Float64 - He = isingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - - H_complex = MPO(ComplexF64, os, sites) - @test scalartype(H_complex) <: ComplexF64 - @test H_complex ≈ Ha - end - - @testset "Ising" begin - os = OpSum() - for j in 1:(N - 1) - os -= "Sz", j, "Sz", j + 1 - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = -isingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - end - - @testset "Ising-Different Order" begin - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = isingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - end - - @testset "Heisenberg" begin - os = OpSum() - h = rand(N) #random magnetic fields - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - for j in 1:N - os += h[j], "Sz", j - end + N = 10 - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = heisenbergMPO(sites, h) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - end - - @testset "Multiple Onsite Ops" begin - sites = siteinds("S=1", N) - os1 = OpSum() - for j in 1:(N - 1) - os1 += "Sz", j, "Sz", j + 1 - os1 += 0.5, "S+", j, "S-", j + 1 - os1 += 0.5, "S-", j, "S+", j + 1 - end - for j in 1:N - os1 += "Sz * Sz", j - end - Ha1 = MPO(os1, sites) + @test !ITensors.using_auto_fermion() - os2 = OpSum() - for j in 1:(N - 1) - os2 += "Sz", j, "Sz", j + 1 - os2 += 0.5, "S+", j, "S-", j + 1 - os2 += 0.5, "S-", j, "S+", j + 1 - end - for j in 1:N - os2 += "Sz", j, "Sz", j + @testset "Show MPOTerm" begin + os = OpSum() + add!(os, "Sz", 1, "Sz", 2) + @test length(sprint(show, os[1])) > 1 end - Ha2 = MPO(os2, sites) - - He = heisenbergMPO(sites, ones(N), "Sz * Sz") - psi = makeRandomMPS(sites) - Oe = inner(psi', He, psi) - Oa1 = inner(psi', Ha1, psi) - @test Oa1 ≈ Oe - Oa2 = inner(psi', Ha2, psi) - @test Oa2 ≈ Oe - end - - @testset "Three-site ops" begin - os = OpSum() - # To test version of add! taking a coefficient - add!(os, 1.0, "Sz", 1, "Sz", 2, "Sz", 3) - @test length(os) == 1 - for j in 2:(N - 2) - add!(os, "Sz", j, "Sz", j + 1, "Sz", j + 2) - end - h = ones(N) - for j in 1:N - add!(os, h[j], "Sx", j) - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = threeSiteIsingMPO(sites, h) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - end - - @testset "Four-site ops" begin - os = OpSum() - for j in 1:(N - 3) - add!(os, "Sz", j, "Sz", j + 1, "Sz", j + 2, "Sz", j + 3) + + @testset "Multisite operator" begin + os = OpSum() + os += ("CX", 1, 2) + os += (2.3, "R", 3, 4, "S", 2) + os += ("X", 3) + @test length(os) == 3 + @test coefficient(os[1]) == 1 + @test length(os[1]) == 1 + @test ITensors.which_op(os[1][1]) == "CX" + @test ITensors.sites(os[1][1]) == (1, 2) + @test coefficient(os[2]) == 2.3 + @test length(os[2]) == 2 + @test ITensors.which_op(os[2][1]) == "R" + @test ITensors.sites(os[2][1]) == (3, 4) + @test ITensors.which_op(os[2][2]) == "S" + @test ITensors.sites(os[2][2]) == (2,) + @test coefficient(os[3]) == 1 + @test length(os[3]) == 1 + @test ITensors.which_op(os[3][1]) == "X" + @test ITensors.sites(os[3][1]) == (3,) + + os = OpSum() + ("CX", 1, 2) + @test length(os) == 1 + @test coefficient(os[1]) == 1 + @test length(os[1]) == 1 + @test ITensors.which_op(os[1][1]) == "CX" + @test ITensors.sites(os[1][1]) == (1, 2) + + # Coordinate + os = OpSum() + ("X", (1, 2)) + @test length(os) == 1 + @test coefficient(os[1]) == 1 + @test length(os[1]) == 1 + @test ITensors.which_op(os[1][1]) == "X" + @test ITensors.sites(os[1][1]) == ((1, 2),) + + os = OpSum() + ("CX", 1, 2, (ϕ = π / 3,)) + @test length(os) == 1 + @test coefficient(os[1]) == 1 + @test length(os[1]) == 1 + @test ITensors.which_op(os[1][1]) == "CX" + @test ITensors.sites(os[1][1]) == (1, 2) + @test ITensors.params(os[1][1]) == (ϕ = π / 3,) + + os = OpSum() + ("CX", 1, 2, (ϕ = π / 3,), "CZ", 3, 4, (θ = π / 2,)) + @test length(os) == 1 + @test coefficient(os[1]) == 1 + @test length(os[1]) == 2 + @test ITensors.which_op(os[1][1]) == "CX" + @test ITensors.sites(os[1][1]) == (1, 2) + @test ITensors.params(os[1][1]) == (ϕ = π / 3,) + @test ITensors.which_op(os[1][2]) == "CZ" + @test ITensors.sites(os[1][2]) == (3, 4) + @test ITensors.params(os[1][2]) == (θ = π / 2,) + + os = OpSum() + ("CX", (ϕ = π / 3,), 1, 2, "CZ", (θ = π / 2,), 3, 4) + @test length(os) == 1 + @test coefficient(os[1]) == 1 + @test length(os[1]) == 2 + @test ITensors.which_op(os[1][1]) == "CX" + @test ITensors.sites(os[1][1]) == (1, 2) + @test ITensors.params(os[1][1]) == (ϕ = π / 3,) + @test ITensors.which_op(os[1][2]) == "CZ" + @test ITensors.sites(os[1][2]) == (3, 4) + @test ITensors.params(os[1][2]) == (θ = π / 2,) + + os = OpSum() + ("CX", 1, 2, (ϕ = π / 3,)) + @test length(os) == 1 + @test coefficient(os[1]) == 1 + @test length(os[1]) == 1 + @test ITensors.which_op(os[1][1]) == "CX" + @test ITensors.sites(os[1][1]) == (1, 2) + @test ITensors.params(os[1][1]) == (ϕ = π / 3,) + + os = OpSum() + (1 + 2im, "CRz", (ϕ = π / 3,), 1, 2) + @test length(os) == 1 + @test coefficient(os[1]) == 1 + 2im + @test length(os[1]) == 1 + @test ITensors.which_op(os[1][1]) == "CRz" + @test ITensors.sites(os[1][1]) == (1, 2) + @test ITensors.params(os[1][1]) == (ϕ = π / 3,) + + os = OpSum() + ("CRz", (ϕ = π / 3,), 1, 2) + @test length(os) == 1 + @test coefficient(os[1]) == 1 + @test length(os[1]) == 1 + @test ITensors.which_op(os[1][1]) == "CRz" + @test ITensors.sites(os[1][1]) == (1, 2) + @test ITensors.params(os[1][1]) == (ϕ = π / 3,) end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = fourSiteIsingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - end - - @testset "Next-neighbor Heisenberg" begin - os = OpSum() - J1 = 1.0 - J2 = 0.5 - for j in 1:(N - 1) - add!(os, J1, "Sz", j, "Sz", j + 1) - add!(os, J1 * 0.5, "S+", j, "S-", j + 1) - add!(os, J1 * 0.5, "S-", j, "S+", j + 1) + + @testset "Show OpSum" begin + os = OpSum() + add!(os, "Sz", 1, "Sz", 2) + add!(os, "Sz", 2, "Sz", 3) + @test length(sprint(show, os)) > 1 end - for j in 1:(N - 2) - add!(os, J2, "Sz", j, "Sz", j + 2) - add!(os, J2 * 0.5, "S+", j, "S-", j + 2) - add!(os, J2 * 0.5, "S-", j, "S+", j + 2) + + @testset "OpSum algebra (eltype=$elt)" for elt in ( + Float32, Float64, Complex{Float32}, Complex{Float64}, + ) + n = 5 + sites = siteinds("S=1/2", n) + O1 = OpSum() + for j in 1:(n - 1) + O1 += "Sz", j, "Sz", j + 1 + end + O2 = OpSum() + for j in 1:n + O2 += "Sx", j + end + O = O1 + 2 * O2 + @test length(O) == 2 * n - 1 + H1 = MPO(elt, O1, sites) + H2 = MPO(elt, O2, sites) + H = H1 + 2 * H2 + @test scalartype(H1) == elt + @test scalartype(H2) == elt + @test scalartype(H) == elt + @test prod(MPO(O, sites)) ≈ prod(H) + + @test scalartype(MPO(elt, Op("Sz", 1), sites)) == elt + @test scalartype(MPO(elt, Op("Sz", 1) + Op("Sz", 2), sites)) == elt + @test scalartype(MPO(elt, 2 * Op("Sz", 1) + 3 * Op("Sz", 2), sites)) == elt + @test scalartype(MPO(elt, 2 * Op("Sz", 1), sites)) == elt + @test scalartype(MPO(elt, Op("Sz", 1) * Op("Sz", 2), sites)) == elt + @test scalartype(MPO(elt, 2 * Op("Sz", 1) * Op("Sz", 2), sites)) == elt + + O = O1 - 2 * O2 + @test length(O) == 2 * n - 1 + H1 = MPO(elt, O1, sites) + H2 = MPO(elt, O2, sites) + H = H1 - 2 * H2 + @test scalartype(H1) == elt + @test scalartype(H2) == elt + @test scalartype(H) == elt + @test prod(MPO(O, sites)) ≈ prod(H) + + O = O1 - O2 / 2 + @test length(O) == 2 * n - 1 + H1 = MPO(elt, O1, sites) + H2 = MPO(elt, O2, sites) + H = H1 - H2 / 2 + @test scalartype(H1) == elt + @test scalartype(H2) == elt + @test scalartype(H) == elt + @test prod(MPO(O, sites)) ≈ prod(H) end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - - He = NNheisenbergMPO(sites, J1, J2) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - #@test maxlinkdim(Ha) == 8 - end - - @testset "Onsite Regression Test" begin - sites = siteinds("S=1", 4) - os = OpSum() - add!(os, 0.5, "Sx", 1) - add!(os, 0.5, "Sy", 1) - H = MPO(os, sites) - l = commonind(H[1], H[2]) - T = setelt(l => 1) * H[1] - O = op(sites[1], "Sx") + op(sites[1], "Sy") - @test norm(T - 0.5 * O) < 1E-8 - - sites = siteinds("S=1", 2) - os = OpSum() - add!(os, 0.5im, "Sx", 1) - add!(os, 0.5, "Sy", 1) - H = MPO(os, sites) - T = H[1] * H[2] - O = - im * op(sites[1], "Sx") * op(sites[2], "Id") + op(sites[1], "Sy") * op(sites[2], "Id") - @test norm(T - 0.5 * O) < 1E-8 - end - - @testset "+ syntax" begin + @testset "Single creation op" begin - os = OpSum() - os += "Adagup", 3 - sites = siteinds("Electron", N) - W = MPO(os, sites) - psi = makeRandomMPS(sites) - cdu_psi = copy(psi) - cdu_psi[3] = noprime(cdu_psi[3] * op(sites, "Adagup", 3)) - @test inner(psi', W, psi) ≈ inner(cdu_psi, psi) + os = OpSum() + add!(os, "Adagup", 3) + sites = siteinds("Electron", N) + W = MPO(os, sites) + psi = makeRandomMPS(sites) + cdu_psi = copy(psi) + cdu_psi[3] = noprime(cdu_psi[3] * op(sites, "Adagup", 3)) + @test inner(psi', W, psi) ≈ inner(cdu_psi, psi) end @testset "Ising" begin - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = isingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + @test scalartype(Ha) <: Float64 + He = isingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + + H_complex = MPO(ComplexF64, os, sites) + @test scalartype(H_complex) <: ComplexF64 + @test H_complex ≈ Ha + end + + @testset "Ising" begin + os = OpSum() + for j in 1:(N - 1) + os -= "Sz", j, "Sz", j + 1 + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = -isingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe end @testset "Ising-Different Order" begin - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j + 1, "Sz", j - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = isingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = isingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe end @testset "Heisenberg" begin - os = OpSum() - h = rand(N) #random magnetic fields - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - for j in 1:N - os += h[j], "Sz", j - end - - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = heisenbergMPO(sites, h) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe + os = OpSum() + h = rand(N) #random magnetic fields + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + for j in 1:N + os += h[j], "Sz", j + end + + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = heisenbergMPO(sites, h) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe end @testset "Multiple Onsite Ops" begin - sites = siteinds("S=1", N) - os1 = OpSum() - for j in 1:(N - 1) - os1 += "Sz", j, "Sz", j + 1 - os1 += 0.5, "S+", j, "S-", j + 1 - os1 += 0.5, "S-", j, "S+", j + 1 - end - for j in 1:N - os1 += "Sz * Sz", j - end - Ha1 = MPO(os1, sites) - - os2 = OpSum() - for j in 1:(N - 1) - os2 += "Sz", j, "Sz", j + 1 - os2 += 0.5, "S+", j, "S-", j + 1 - os2 += 0.5, "S-", j, "S+", j + 1 - end - for j in 1:N - os2 += "Sz", j, "Sz", j - end - Ha2 = MPO(os2, sites) - - He = heisenbergMPO(sites, ones(N), "Sz * Sz") - psi = makeRandomMPS(sites) - Oe = inner(psi', He, psi) - Oa1 = inner(psi', Ha1, psi) - @test Oa1 ≈ Oe - Oa2 = inner(psi', Ha2, psi) - @test Oa2 ≈ Oe + sites = siteinds("S=1", N) + os1 = OpSum() + for j in 1:(N - 1) + os1 += "Sz", j, "Sz", j + 1 + os1 += 0.5, "S+", j, "S-", j + 1 + os1 += 0.5, "S-", j, "S+", j + 1 + end + for j in 1:N + os1 += "Sz * Sz", j + end + Ha1 = MPO(os1, sites) + + os2 = OpSum() + for j in 1:(N - 1) + os2 += "Sz", j, "Sz", j + 1 + os2 += 0.5, "S+", j, "S-", j + 1 + os2 += 0.5, "S-", j, "S+", j + 1 + end + for j in 1:N + os2 += "Sz", j, "Sz", j + end + Ha2 = MPO(os2, sites) + + He = heisenbergMPO(sites, ones(N), "Sz * Sz") + psi = makeRandomMPS(sites) + Oe = inner(psi', He, psi) + Oa1 = inner(psi', Ha1, psi) + @test Oa1 ≈ Oe + Oa2 = inner(psi', Ha2, psi) + @test Oa2 ≈ Oe end @testset "Three-site ops" begin - os = OpSum() - # To test version of add! taking a coefficient - os += 1.0, "Sz", 1, "Sz", 2, "Sz", 3 - @test length(os) == 1 - for j in 2:(N - 2) - os += "Sz", j, "Sz", j + 1, "Sz", j + 2 - end - h = ones(N) - for j in 1:N - os += h[j], "Sx", j - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = threeSiteIsingMPO(sites, h) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe + os = OpSum() + # To test version of add! taking a coefficient + add!(os, 1.0, "Sz", 1, "Sz", 2, "Sz", 3) + @test length(os) == 1 + for j in 2:(N - 2) + add!(os, "Sz", j, "Sz", j + 1, "Sz", j + 2) + end + h = ones(N) + for j in 1:N + add!(os, h[j], "Sx", j) + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = threeSiteIsingMPO(sites, h) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe end @testset "Four-site ops" begin - os = OpSum() - for j in 1:(N - 3) - os += "Sz", j, "Sz", j + 1, "Sz", j + 2, "Sz", j + 3 - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = fourSiteIsingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe + os = OpSum() + for j in 1:(N - 3) + add!(os, "Sz", j, "Sz", j + 1, "Sz", j + 2, "Sz", j + 3) + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = fourSiteIsingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe end @testset "Next-neighbor Heisenberg" begin - os = OpSum() - J1 = 1.0 - J2 = 0.5 - for j in 1:(N - 1) - os += J1, "Sz", j, "Sz", j + 1 - os += J1 * 0.5, "S+", j, "S-", j + 1 - os += J1 * 0.5, "S-", j, "S+", j + 1 - end - for j in 1:(N - 2) - os += J2, "Sz", j, "Sz", j + 2 - os += J2 * 0.5, "S+", j, "S-", j + 2 - os += J2 * 0.5, "S-", j, "S+", j + 2 - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - - He = NNheisenbergMPO(sites, J1, J2) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - #@test maxlinkdim(Ha) == 8 + os = OpSum() + J1 = 1.0 + J2 = 0.5 + for j in 1:(N - 1) + add!(os, J1, "Sz", j, "Sz", j + 1) + add!(os, J1 * 0.5, "S+", j, "S-", j + 1) + add!(os, J1 * 0.5, "S-", j, "S+", j + 1) + end + for j in 1:(N - 2) + add!(os, J2, "Sz", j, "Sz", j + 2) + add!(os, J2 * 0.5, "S+", j, "S-", j + 2) + add!(os, J2 * 0.5, "S-", j, "S+", j + 2) + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + + He = NNheisenbergMPO(sites, J1, J2) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + #@test maxlinkdim(Ha) == 8 end - #@testset "-= syntax" begin - # os = OpSum() - # os += (-1,"Sz",1,"Sz",2) - # os2 = OpSum() - # os2 -= ("Sz",1,"Sz",2) - # @test os == os2 - #end - @testset "Onsite Regression Test" begin - sites = siteinds("S=1", 4) - os = OpSum() - os += 0.5, "Sx", 1 - os += 0.5, "Sy", 1 - H = MPO(os, sites) - l = commonind(H[1], H[2]) - T = setelt(l => 1) * H[1] - O = op(sites[1], "Sx") + op(sites[1], "Sy") - @test norm(T - 0.5 * O) < 1E-8 - - sites = siteinds("S=1", 2) - os = OpSum() - os += 0.5im, "Sx", 1 - os += 0.5, "Sy", 1 - H = MPO(os, sites) - T = H[1] * H[2] - O = - im * op(sites[1], "Sx") * op(sites[2], "Id") + - op(sites[1], "Sy") * op(sites[2], "Id") - @test norm(T - 0.5 * O) < 1E-8 + sites = siteinds("S=1", 4) + os = OpSum() + add!(os, 0.5, "Sx", 1) + add!(os, 0.5, "Sy", 1) + H = MPO(os, sites) + l = commonind(H[1], H[2]) + T = setelt(l => 1) * H[1] + O = op(sites[1], "Sx") + op(sites[1], "Sy") + @test norm(T - 0.5 * O) < 1.0e-8 + + sites = siteinds("S=1", 2) + os = OpSum() + add!(os, 0.5im, "Sx", 1) + add!(os, 0.5, "Sy", 1) + H = MPO(os, sites) + T = H[1] * H[2] + O = + im * op(sites[1], "Sx") * op(sites[2], "Id") + op(sites[1], "Sy") * op(sites[2], "Id") + @test norm(T - 0.5 * O) < 1.0e-8 end - end - @testset ".+= and .-= syntax" begin + @testset "+ syntax" begin + @testset "Single creation op" begin + os = OpSum() + os += "Adagup", 3 + sites = siteinds("Electron", N) + W = MPO(os, sites) + psi = makeRandomMPS(sites) + cdu_psi = copy(psi) + cdu_psi[3] = noprime(cdu_psi[3] * op(sites, "Adagup", 3)) + @test inner(psi', W, psi) ≈ inner(cdu_psi, psi) + end - #@testset ".-= syntax" begin - # os = OpSum() - # os .+= (-1,"Sz",1,"Sz",2) - # os2 = OpSum() - # os2 .-= ("Sz",1,"Sz",2) - # @test os == os2 - #end + @testset "Ising" begin + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = isingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end - @testset "Single creation op" begin - os = OpSum() - os .+= "Adagup", 3 - sites = siteinds("Electron", N) - W = MPO(os, sites) - psi = makeRandomMPS(sites) - cdu_psi = copy(psi) - cdu_psi[3] = noprime(cdu_psi[3] * op(sites, "Adagup", 3)) - @test inner(psi', W, psi) ≈ inner(cdu_psi, psi) - end + @testset "Ising-Different Order" begin + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j + 1, "Sz", j + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = isingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end - @testset "Ising" begin - os = OpSum() - for j in 1:(N - 1) - os .+= "Sz", j, "Sz", j + 1 - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = isingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - end + @testset "Heisenberg" begin + os = OpSum() + h = rand(N) #random magnetic fields + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + for j in 1:N + os += h[j], "Sz", j + end + + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = heisenbergMPO(sites, h) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end - @testset "Ising-Different Order" begin - os = OpSum() - for j in 1:(N - 1) - os .+= "Sz", j + 1, "Sz", j - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = isingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - end + @testset "Multiple Onsite Ops" begin + sites = siteinds("S=1", N) + os1 = OpSum() + for j in 1:(N - 1) + os1 += "Sz", j, "Sz", j + 1 + os1 += 0.5, "S+", j, "S-", j + 1 + os1 += 0.5, "S-", j, "S+", j + 1 + end + for j in 1:N + os1 += "Sz * Sz", j + end + Ha1 = MPO(os1, sites) + + os2 = OpSum() + for j in 1:(N - 1) + os2 += "Sz", j, "Sz", j + 1 + os2 += 0.5, "S+", j, "S-", j + 1 + os2 += 0.5, "S-", j, "S+", j + 1 + end + for j in 1:N + os2 += "Sz", j, "Sz", j + end + Ha2 = MPO(os2, sites) + + He = heisenbergMPO(sites, ones(N), "Sz * Sz") + psi = makeRandomMPS(sites) + Oe = inner(psi', He, psi) + Oa1 = inner(psi', Ha1, psi) + @test Oa1 ≈ Oe + Oa2 = inner(psi', Ha2, psi) + @test Oa2 ≈ Oe + end - @testset "Heisenberg" begin - os = OpSum() - h = rand(N) #random magnetic fields - for j in 1:(N - 1) - os .+= "Sz", j, "Sz", j + 1 - os .+= 0.5, "S+", j, "S-", j + 1 - os .+= 0.5, "S-", j, "S+", j + 1 - end - for j in 1:N - os .+= h[j], "Sz", j - end - - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = heisenbergMPO(sites, h) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - end + @testset "Three-site ops" begin + os = OpSum() + # To test version of add! taking a coefficient + os += 1.0, "Sz", 1, "Sz", 2, "Sz", 3 + @test length(os) == 1 + for j in 2:(N - 2) + os += "Sz", j, "Sz", j + 1, "Sz", j + 2 + end + h = ones(N) + for j in 1:N + os += h[j], "Sx", j + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = threeSiteIsingMPO(sites, h) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end - @testset "Multiple Onsite Ops" begin - sites = siteinds("S=1", N) - os1 = OpSum() - for j in 1:(N - 1) - os1 .+= "Sz", j, "Sz", j + 1 - os1 .+= 0.5, "S+", j, "S-", j + 1 - os1 .+= 0.5, "S-", j, "S+", j + 1 - end - for j in 1:N - os1 .+= "Sz * Sz", j - end - Ha1 = MPO(os1, sites) - - os2 = OpSum() - for j in 1:(N - 1) - os2 .+= "Sz", j, "Sz", j + 1 - os2 .+= 0.5, "S+", j, "S-", j + 1 - os2 .+= 0.5, "S-", j, "S+", j + 1 - end - for j in 1:N - os2 .+= "Sz", j, "Sz", j - end - Ha2 = MPO(os2, sites) - - He = heisenbergMPO(sites, ones(N), "Sz * Sz") - psi = makeRandomMPS(sites) - Oe = inner(psi', He, psi) - Oa1 = inner(psi', Ha1, psi) - @test Oa1 ≈ Oe - Oa2 = inner(psi', Ha2, psi) - @test Oa2 ≈ Oe - end + @testset "Four-site ops" begin + os = OpSum() + for j in 1:(N - 3) + os += "Sz", j, "Sz", j + 1, "Sz", j + 2, "Sz", j + 3 + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = fourSiteIsingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end - @testset "Three-site ops" begin - os = OpSum() - # To test version of add! taking a coefficient - os .+= 1.0, "Sz", 1, "Sz", 2, "Sz", 3 - @test length(os) == 1 - for j in 2:(N - 2) - os .+= "Sz", j, "Sz", j + 1, "Sz", j + 2 - end - h = ones(N) - for j in 1:N - os .+= h[j], "Sx", j - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = threeSiteIsingMPO(sites, h) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - end + @testset "Next-neighbor Heisenberg" begin + os = OpSum() + J1 = 1.0 + J2 = 0.5 + for j in 1:(N - 1) + os += J1, "Sz", j, "Sz", j + 1 + os += J1 * 0.5, "S+", j, "S-", j + 1 + os += J1 * 0.5, "S-", j, "S+", j + 1 + end + for j in 1:(N - 2) + os += J2, "Sz", j, "Sz", j + 2 + os += J2 * 0.5, "S+", j, "S-", j + 2 + os += J2 * 0.5, "S-", j, "S+", j + 2 + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + + He = NNheisenbergMPO(sites, J1, J2) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + #@test maxlinkdim(Ha) == 8 + end - @testset "Four-site ops" begin - os = OpSum() - for j in 1:(N - 3) - os .+= "Sz", j, "Sz", j + 1, "Sz", j + 2, "Sz", j + 3 - end - sites = siteinds("S=1/2", N) - Ha = MPO(os, sites) - He = fourSiteIsingMPO(sites) - psi = makeRandomMPS(sites) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe + #@testset "-= syntax" begin + # os = OpSum() + # os += (-1,"Sz",1,"Sz",2) + # os2 = OpSum() + # os2 -= ("Sz",1,"Sz",2) + # @test os == os2 + #end + + @testset "Onsite Regression Test" begin + sites = siteinds("S=1", 4) + os = OpSum() + os += 0.5, "Sx", 1 + os += 0.5, "Sy", 1 + H = MPO(os, sites) + l = commonind(H[1], H[2]) + T = setelt(l => 1) * H[1] + O = op(sites[1], "Sx") + op(sites[1], "Sy") + @test norm(T - 0.5 * O) < 1.0e-8 + + sites = siteinds("S=1", 2) + os = OpSum() + os += 0.5im, "Sx", 1 + os += 0.5, "Sy", 1 + H = MPO(os, sites) + T = H[1] * H[2] + O = + im * op(sites[1], "Sx") * op(sites[2], "Id") + + op(sites[1], "Sy") * op(sites[2], "Id") + @test norm(T - 0.5 * O) < 1.0e-8 + end end - @testset "Next-neighbor Heisenberg" begin - os = OpSum() - J1 = 1.0 - J2 = 0.5 - for j in 1:(N - 1) - os .+= J1, "Sz", j, "Sz", j + 1 - os .+= J1 * 0.5, "S+", j, "S-", j + 1 - os .+= J1 * 0.5, "S-", j, "S+", j + 1 - end - for j in 1:(N - 2) - os .+= J2, "Sz", j, "Sz", j + 2 - os .+= J2 * 0.5, "S+", j, "S-", j + 2 - os .+= J2 * 0.5, "S-", j, "S+", j + 2 - end - sites = siteinds("S=1/2", N; conserve_qns=true) - Ha = MPO(os, sites) - - He = NNheisenbergMPO(sites, J1, J2) - psi = random_mps(sites, [isodd(n) ? "Up" : "Dn" for n in 1:N]) - Oa = inner(psi', Ha, psi) - Oe = inner(psi', He, psi) - @test Oa ≈ Oe - #@test maxlinkdim(Ha) == 8 - end + @testset ".+= and .-= syntax" begin + + #@testset ".-= syntax" begin + # os = OpSum() + # os .+= (-1,"Sz",1,"Sz",2) + # os2 = OpSum() + # os2 .-= ("Sz",1,"Sz",2) + # @test os == os2 + #end + + @testset "Single creation op" begin + os = OpSum() + os .+= "Adagup", 3 + sites = siteinds("Electron", N) + W = MPO(os, sites) + psi = makeRandomMPS(sites) + cdu_psi = copy(psi) + cdu_psi[3] = noprime(cdu_psi[3] * op(sites, "Adagup", 3)) + @test inner(psi', W, psi) ≈ inner(cdu_psi, psi) + end - @testset "Onsite Regression Test" begin - sites = siteinds("S=1", 4) - os = OpSum() - os .+= 0.5, "Sx", 1 - os .+= 0.5, "Sy", 1 - H = MPO(os, sites) - l = commonind(H[1], H[2]) - T = setelt(l => 1) * H[1] - O = op(sites[1], "Sx") + op(sites[1], "Sy") - @test norm(T - 0.5 * O) < 1E-8 - - sites = siteinds("S=1", 2) - os = OpSum() - os .+= 0.5im, "Sx", 1 - os .+= 0.5, "Sy", 1 - H = MPO(os, sites) - T = H[1] * H[2] - O = - im * op(sites[1], "Sx") * op(sites[2], "Id") + - op(sites[1], "Sy") * op(sites[2], "Id") - @test norm(T - 0.5 * O) < 1E-8 - end - end - - @testset "Fermionic Operators" begin - N = 5 - s = siteinds("Fermion", N) - - a1 = OpSum() - a1 += "Cdag", 1, "C", 3 - M1 = MPO(a1, s) - - a2 = OpSum() - a2 -= 1, "C", 3, "Cdag", 1 - M2 = MPO(a2, s) - - a3 = OpSum() - a3 += "Cdag", 1, "N", 2, "C", 3 - M3 = MPO(a3, s) - - p011 = MPS(s, [1, 2, 2, 1, 1]) - p110 = MPS(s, [2, 2, 1, 1, 1]) - - @test inner(p110', M1, p011) ≈ -1.0 - @test inner(p110', M2, p011) ≈ -1.0 - @test inner(p110', M3, p011) ≈ -1.0 - - p001 = MPS(s, [1, 1, 2, 1, 1]) - p100 = MPS(s, [2, 1, 1, 1, 1]) - - @test inner(p100', M1, p001) ≈ +1.0 - @test inner(p100', M2, p001) ≈ +1.0 - @test inner(p100', M3, p001) ≈ 0.0 - - # - # Repeat similar test but - # with Electron sites - # - - s = siteinds("Electron", N; conserve_qns=true) - - a1 = OpSum() - a1 += "Cdagup", 1, "Cup", 3 - M1 = MPO(a1, s) - - a2 = OpSum() - a2 -= 1, "Cdn", 3, "Cdagdn", 1 - M2 = MPO(a2, s) - - p0uu = MPS(s, [1, 2, 2, 1, 1]) - puu0 = MPS(s, [2, 2, 1, 1, 1]) - p0ud = MPS(s, [1, 2, 3, 1, 1]) - pdu0 = MPS(s, [3, 2, 1, 1, 1]) - p00u = MPS(s, [1, 1, 2, 1, 1]) - pu00 = MPS(s, [2, 1, 1, 1, 1]) - p00d = MPS(s, [1, 1, 3, 1, 1]) - pd00 = MPS(s, [3, 1, 1, 1, 1]) - - @test inner(puu0', M1, p0uu) ≈ -1.0 - @test inner(pdu0', M2, p0ud) ≈ -1.0 - @test inner(pu00', M1, p00u) ≈ +1.0 - @test inner(pd00', M2, p00d) ≈ +1.0 - end - - @testset "Chemical Hamiltonian Test" begin - for auto_fermion in [false, true] - if auto_fermion - ITensors.enable_auto_fermion() - else - ITensors.disable_auto_fermion() - end - N = 6 - t = randn(N, N) - V = randn(N, N, N, N) - s = siteinds("Electron", N; conserve_qns=true) - - ost = OpSum() - for i in 1:N, j in 1:N - ost += t[i, j], "Cdagup", i, "Cup", j - ost += t[i, j], "Cdagdn", i, "Cdn", j - end - Ht = MPO(ost, s) - - osV = OpSum() - for i in 1:N, j in 1:N, k in 1:N, l in 1:N - osV += V[i, j, k, l], "Cdagup", i, "Cdagup", j, "Cup", k, "Cup", l - osV += V[i, j, k, l], "Cdagup", i, "Cdagdn", j, "Cdn", k, "Cup", l - osV += V[i, j, k, l], "Cdagdn", i, "Cdagup", j, "Cup", k, "Cdn", l - osV += V[i, j, k, l], "Cdagdn", i, "Cdagdn", j, "Cdn", k, "Cdn", l - end - HV = MPO(osV, s) - - for i in 1:N, j in 1:N - stᵢ = fill("0", N) - stⱼ = fill("0", N) - stᵢ[i] = "Up" - stⱼ[j] = "Up" - psiᵢ = MPS(s, stᵢ) - psiⱼ = MPS(s, stⱼ) - @test abs(inner(psiᵢ', Ht, psiⱼ) - t[i, j]) < 1E-10 - end - - for i in 1:N, j in 1:N, k in 1:N, l in 1:N - ((i == j) || (k == l)) && continue - - stᵢⱼ = fill("0", N) - stᵢⱼ[i] = "Up" - stᵢⱼ[j] = "Up" - psiᵢⱼ = MPS(s, stᵢⱼ) - - stₖₗ = fill("0", N) - stₖₗ[k] = "Up" - stₖₗ[l] = "Up" - psiₖₗ = MPS(s, stₖₗ) - - mpo_val = inner(psiᵢⱼ', HV, psiₖₗ) - exact_val = 0.0 - for m in 1:N, n in 1:N, p in 1:N, q in 1:N - if m == i && n == j && p == l && q == k - exact_val += V[i, j, l, k] - elseif m == i && n == j && p == k && q == l - exact_val -= V[i, j, k, l] - elseif m == j && n == i && p == l && q == k - exact_val -= V[j, i, l, k] - elseif m == j && n == i && p == k && q == l - exact_val += V[j, i, k, l] - end + @testset "Ising" begin + os = OpSum() + for j in 1:(N - 1) + os .+= "Sz", j, "Sz", j + 1 + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = isingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end + + @testset "Ising-Different Order" begin + os = OpSum() + for j in 1:(N - 1) + os .+= "Sz", j + 1, "Sz", j + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = isingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end + + @testset "Heisenberg" begin + os = OpSum() + h = rand(N) #random magnetic fields + for j in 1:(N - 1) + os .+= "Sz", j, "Sz", j + 1 + os .+= 0.5, "S+", j, "S-", j + 1 + os .+= 0.5, "S-", j, "S+", j + 1 + end + for j in 1:N + os .+= h[j], "Sz", j + end + + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = heisenbergMPO(sites, h) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end + + @testset "Multiple Onsite Ops" begin + sites = siteinds("S=1", N) + os1 = OpSum() + for j in 1:(N - 1) + os1 .+= "Sz", j, "Sz", j + 1 + os1 .+= 0.5, "S+", j, "S-", j + 1 + os1 .+= 0.5, "S-", j, "S+", j + 1 + end + for j in 1:N + os1 .+= "Sz * Sz", j + end + Ha1 = MPO(os1, sites) + + os2 = OpSum() + for j in 1:(N - 1) + os2 .+= "Sz", j, "Sz", j + 1 + os2 .+= 0.5, "S+", j, "S-", j + 1 + os2 .+= 0.5, "S-", j, "S+", j + 1 + end + for j in 1:N + os2 .+= "Sz", j, "Sz", j + end + Ha2 = MPO(os2, sites) + + He = heisenbergMPO(sites, ones(N), "Sz * Sz") + psi = makeRandomMPS(sites) + Oe = inner(psi', He, psi) + Oa1 = inner(psi', Ha1, psi) + @test Oa1 ≈ Oe + Oa2 = inner(psi', Ha2, psi) + @test Oa2 ≈ Oe + end + + @testset "Three-site ops" begin + os = OpSum() + # To test version of add! taking a coefficient + os .+= 1.0, "Sz", 1, "Sz", 2, "Sz", 3 + @test length(os) == 1 + for j in 2:(N - 2) + os .+= "Sz", j, "Sz", j + 1, "Sz", j + 2 + end + h = ones(N) + for j in 1:N + os .+= h[j], "Sx", j + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = threeSiteIsingMPO(sites, h) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end + + @testset "Four-site ops" begin + os = OpSum() + for j in 1:(N - 3) + os .+= "Sz", j, "Sz", j + 1, "Sz", j + 2, "Sz", j + 3 + end + sites = siteinds("S=1/2", N) + Ha = MPO(os, sites) + He = fourSiteIsingMPO(sites) + psi = makeRandomMPS(sites) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + end + + @testset "Next-neighbor Heisenberg" begin + os = OpSum() + J1 = 1.0 + J2 = 0.5 + for j in 1:(N - 1) + os .+= J1, "Sz", j, "Sz", j + 1 + os .+= J1 * 0.5, "S+", j, "S-", j + 1 + os .+= J1 * 0.5, "S-", j, "S+", j + 1 + end + for j in 1:(N - 2) + os .+= J2, "Sz", j, "Sz", j + 2 + os .+= J2 * 0.5, "S+", j, "S-", j + 2 + os .+= J2 * 0.5, "S-", j, "S+", j + 2 + end + sites = siteinds("S=1/2", N; conserve_qns = true) + Ha = MPO(os, sites) + + He = NNheisenbergMPO(sites, J1, J2) + psi = random_mps(sites, [isodd(n) ? "Up" : "Dn" for n in 1:N]) + Oa = inner(psi', Ha, psi) + Oe = inner(psi', He, psi) + @test Oa ≈ Oe + #@test maxlinkdim(Ha) == 8 + end + + @testset "Onsite Regression Test" begin + sites = siteinds("S=1", 4) + os = OpSum() + os .+= 0.5, "Sx", 1 + os .+= 0.5, "Sy", 1 + H = MPO(os, sites) + l = commonind(H[1], H[2]) + T = setelt(l => 1) * H[1] + O = op(sites[1], "Sx") + op(sites[1], "Sy") + @test norm(T - 0.5 * O) < 1.0e-8 + + sites = siteinds("S=1", 2) + os = OpSum() + os .+= 0.5im, "Sx", 1 + os .+= 0.5, "Sy", 1 + H = MPO(os, sites) + T = H[1] * H[2] + O = + im * op(sites[1], "Sx") * op(sites[2], "Id") + + op(sites[1], "Sy") * op(sites[2], "Id") + @test norm(T - 0.5 * O) < 1.0e-8 end - (k > l) && (exact_val *= -1) - (i > j) && (exact_val *= -1) - @test abs(mpo_val - exact_val) < 1E-10 - end - end - ITensors.disable_auto_fermion() - end - - @testset "Complex OpSum Coefs" begin - N = 4 - - for use_qn in [false, true] - sites = siteinds("S=1/2", N; conserve_qns=use_qn) - os = OpSum() - for i in 1:(N - 1) - os += +1im, "S+", i, "S-", i + 1 - os -= 1im, "S-", i, "S+", i + 1 - end - H = MPO(os, sites) - psiud = MPS(sites, [1, 2, 1, 2]) - psidu = MPS(sites, [2, 1, 1, 2]) - @test inner(psiud', H, psidu) ≈ +1im - @test inner(psidu', H, psiud) ≈ -1im - end - end - - @testset "Non-zero QN MPO" begin - N = 4 - s = siteinds("Boson", N; conserve_qns=true) - - j = 3 - terms = OpSum() - terms += "Adag", j - W = MPO(terms, s) - - function op_mpo(sites, which_op, j) - N = length(sites) - ops = [n < j ? "Id" : (n > j ? "Id" : which_op) for n in 1:N] - M = MPO([op(ops[n], sites[n]) for n in 1:length(sites)]) - q = flux(op(which_op, sites[j])) - links = [Index([n < j ? q => 1 : QN() => 1], "Link,l=$n") for n in 1:N] - for n in 1:(N - 1) - M[n] *= onehot(links[n] => 1) - M[n + 1] *= onehot(dag(links[n]) => 1) - end - return M end - M = op_mpo(s, "Adag", j) - @test norm(prod(W) - prod(M)) < 1E-10 + @testset "Fermionic Operators" begin + N = 5 + s = siteinds("Fermion", N) + + a1 = OpSum() + a1 += "Cdag", 1, "C", 3 + M1 = MPO(a1, s) + + a2 = OpSum() + a2 -= 1, "C", 3, "Cdag", 1 + M2 = MPO(a2, s) + + a3 = OpSum() + a3 += "Cdag", 1, "N", 2, "C", 3 + M3 = MPO(a3, s) + + p011 = MPS(s, [1, 2, 2, 1, 1]) + p110 = MPS(s, [2, 2, 1, 1, 1]) + + @test inner(p110', M1, p011) ≈ -1.0 + @test inner(p110', M2, p011) ≈ -1.0 + @test inner(p110', M3, p011) ≈ -1.0 + + p001 = MPS(s, [1, 1, 2, 1, 1]) + p100 = MPS(s, [2, 1, 1, 1, 1]) - psi = random_mps(s, [isodd(n) ? "1" : "0" for n in 1:length(s)]; linkdims=4) - Mpsi = apply(M, psi; alg="naive") - Wpsi = apply(M, psi; alg="naive") - @test abs(inner(Mpsi, Wpsi) / inner(Mpsi, Mpsi) - 1.0) < 1E-10 - end + @test inner(p100', M1, p001) ≈ +1.0 + @test inner(p100', M2, p001) ≈ +1.0 + @test inner(p100', M3, p001) ≈ 0.0 - @testset "Fermion OpSum Issue 514 Regression Test" begin - N = 4 - s = siteinds("Electron", N; conserve_qns=true) - os1 = OpSum() - os2 = OpSum() + # + # Repeat similar test but + # with Electron sites + # - os1 += "Nup", 1 - os2 += "Cdagup", 1, "Cup", 1 + s = siteinds("Electron", N; conserve_qns = true) - M1 = MPO(os1, s) - M2 = MPO(os2, s) + a1 = OpSum() + a1 += "Cdagup", 1, "Cup", 3 + M1 = MPO(a1, s) - H1 = M1[1] * M1[2] * M1[3] * M1[4] - H2 = M2[1] * M2[2] * M2[3] * M2[4] + a2 = OpSum() + a2 -= 1, "Cdn", 3, "Cdagdn", 1 + M2 = MPO(a2, s) - @test norm(H1 - H2) ≈ 0.0 - end + p0uu = MPS(s, [1, 2, 2, 1, 1]) + puu0 = MPS(s, [2, 2, 1, 1, 1]) + p0ud = MPS(s, [1, 2, 3, 1, 1]) + pdu0 = MPS(s, [3, 2, 1, 1, 1]) + p00u = MPS(s, [1, 1, 2, 1, 1]) + pu00 = MPS(s, [2, 1, 1, 1, 1]) + p00d = MPS(s, [1, 1, 3, 1, 1]) + pd00 = MPS(s, [3, 1, 1, 1, 1]) - @testset "OpSum in-place modification regression test" begin - N = 2 - t = 1.0 - os = OpSum() - for n in 1:(N - 1) - os .-= t, "Cdag", n, "C", n + 1 - os .-= t, "Cdag", n + 1, "C", n + @test inner(puu0', M1, p0uu) ≈ -1.0 + @test inner(pdu0', M2, p0ud) ≈ -1.0 + @test inner(pu00', M1, p00u) ≈ +1.0 + @test inner(pd00', M2, p00d) ≈ +1.0 end - s = siteinds("Fermion", N; conserve_qns=true) - os_original = deepcopy(os) - for i in 1:4 - MPO(os, s) - @test os == os_original + + @testset "Chemical Hamiltonian Test" begin + for auto_fermion in [false, true] + if auto_fermion + ITensors.enable_auto_fermion() + else + ITensors.disable_auto_fermion() + end + N = 6 + t = randn(N, N) + V = randn(N, N, N, N) + s = siteinds("Electron", N; conserve_qns = true) + + ost = OpSum() + for i in 1:N, j in 1:N + ost += t[i, j], "Cdagup", i, "Cup", j + ost += t[i, j], "Cdagdn", i, "Cdn", j + end + Ht = MPO(ost, s) + + osV = OpSum() + for i in 1:N, j in 1:N, k in 1:N, l in 1:N + osV += V[i, j, k, l], "Cdagup", i, "Cdagup", j, "Cup", k, "Cup", l + osV += V[i, j, k, l], "Cdagup", i, "Cdagdn", j, "Cdn", k, "Cup", l + osV += V[i, j, k, l], "Cdagdn", i, "Cdagup", j, "Cup", k, "Cdn", l + osV += V[i, j, k, l], "Cdagdn", i, "Cdagdn", j, "Cdn", k, "Cdn", l + end + HV = MPO(osV, s) + + for i in 1:N, j in 1:N + stᵢ = fill("0", N) + stⱼ = fill("0", N) + stᵢ[i] = "Up" + stⱼ[j] = "Up" + psiᵢ = MPS(s, stᵢ) + psiⱼ = MPS(s, stⱼ) + @test abs(inner(psiᵢ', Ht, psiⱼ) - t[i, j]) < 1.0e-10 + end + + for i in 1:N, j in 1:N, k in 1:N, l in 1:N + ((i == j) || (k == l)) && continue + + stᵢⱼ = fill("0", N) + stᵢⱼ[i] = "Up" + stᵢⱼ[j] = "Up" + psiᵢⱼ = MPS(s, stᵢⱼ) + + stₖₗ = fill("0", N) + stₖₗ[k] = "Up" + stₖₗ[l] = "Up" + psiₖₗ = MPS(s, stₖₗ) + + mpo_val = inner(psiᵢⱼ', HV, psiₖₗ) + exact_val = 0.0 + for m in 1:N, n in 1:N, p in 1:N, q in 1:N + if m == i && n == j && p == l && q == k + exact_val += V[i, j, l, k] + elseif m == i && n == j && p == k && q == l + exact_val -= V[i, j, k, l] + elseif m == j && n == i && p == l && q == k + exact_val -= V[j, i, l, k] + elseif m == j && n == i && p == k && q == l + exact_val += V[j, i, k, l] + end + end + (k > l) && (exact_val *= -1) + (i > j) && (exact_val *= -1) + @test abs(mpo_val - exact_val) < 1.0e-10 + end + end + ITensors.disable_auto_fermion() end - end - @testset "Accuracy Regression Test (Issue 725)" begin - ITensors.space(::SiteType"HardCore") = 2 + @testset "Complex OpSum Coefs" begin + N = 4 + + for use_qn in [false, true] + sites = siteinds("S=1/2", N; conserve_qns = use_qn) + os = OpSum() + for i in 1:(N - 1) + os += +1im, "S+", i, "S-", i + 1 + os -= 1im, "S-", i, "S+", i + 1 + end + H = MPO(os, sites) + psiud = MPS(sites, [1, 2, 1, 2]) + psidu = MPS(sites, [2, 1, 1, 2]) + @test inner(psiud', H, psidu) ≈ +1im + @test inner(psidu', H, psiud) ≈ -1im + end + end - ITensors.state(::StateName"0", ::SiteType"HardCore") = [1.0, 0.0] - ITensors.state(::StateName"1", ::SiteType"HardCore") = [0.0, 1.0] + @testset "Non-zero QN MPO" begin + N = 4 + s = siteinds("Boson", N; conserve_qns = true) + + j = 3 + terms = OpSum() + terms += "Adag", j + W = MPO(terms, s) + + function op_mpo(sites, which_op, j) + N = length(sites) + ops = [n < j ? "Id" : (n > j ? "Id" : which_op) for n in 1:N] + M = MPO([op(ops[n], sites[n]) for n in 1:length(sites)]) + q = flux(op(which_op, sites[j])) + links = [Index([n < j ? q => 1 : QN() => 1], "Link,l=$n") for n in 1:N] + for n in 1:(N - 1) + M[n] *= onehot(links[n] => 1) + M[n + 1] *= onehot(dag(links[n]) => 1) + end + return M + end + M = op_mpo(s, "Adag", j) + + @test norm(prod(W) - prod(M)) < 1.0e-10 - function ITensors.op!(Op::ITensor, ::OpName"N", ::SiteType"HardCore", s::Index) - return Op[s' => 2, s => 2] = 1 + psi = random_mps(s, [isodd(n) ? "1" : "0" for n in 1:length(s)]; linkdims = 4) + Mpsi = apply(M, psi; alg = "naive") + Wpsi = apply(M, psi; alg = "naive") + @test abs(inner(Mpsi, Wpsi) / inner(Mpsi, Mpsi) - 1.0) < 1.0e-10 end - function ITensors.op!(Op::ITensor, ::OpName"Adag", ::SiteType"HardCore", s::Index) - return Op[s' => 1, s => 2] = 1 + @testset "Fermion OpSum Issue 514 Regression Test" begin + N = 4 + s = siteinds("Electron", N; conserve_qns = true) + os1 = OpSum() + os2 = OpSum() + + os1 += "Nup", 1 + os2 += "Cdagup", 1, "Cup", 1 + + M1 = MPO(os1, s) + M2 = MPO(os2, s) + + H1 = M1[1] * M1[2] * M1[3] * M1[4] + H2 = M2[1] * M2[2] * M2[3] * M2[4] + + @test norm(H1 - H2) ≈ 0.0 end - function ITensors.op!(Op::ITensor, ::OpName"A", ::SiteType"HardCore", s::Index) - return Op[s' => 2, s => 1] = 1 + @testset "OpSum in-place modification regression test" begin + N = 2 + t = 1.0 + os = OpSum() + for n in 1:(N - 1) + os .-= t, "Cdag", n, "C", n + 1 + os .-= t, "Cdag", n + 1, "C", n + end + s = siteinds("Fermion", N; conserve_qns = true) + os_original = deepcopy(os) + for i in 1:4 + MPO(os, s) + @test os == os_original + end end - t = 1.0 - V1 = 1E-3 - V2 = 2E-5 + @testset "Accuracy Regression Test (Issue 725)" begin + ITensors.space(::SiteType"HardCore") = 2 - N = 20 - sites = siteinds("HardCore", N) + ITensors.state(::StateName"0", ::SiteType"HardCore") = [1.0, 0.0] + ITensors.state(::StateName"1", ::SiteType"HardCore") = [0.0, 1.0] - os = OpSum() - for j in 1:(N - 1) - os -= t, "Adag", j, "A", j + 1 - os -= t, "A", j, "Adag", j + 1 - os += V1, "N", j, "N", j + 1 - end - for j in 1:(N - 2) - os += V2, "N", j, "N", j + 2 + function ITensors.op!(Op::ITensor, ::OpName"N", ::SiteType"HardCore", s::Index) + return Op[s' => 2, s => 2] = 1 + end + + function ITensors.op!(Op::ITensor, ::OpName"Adag", ::SiteType"HardCore", s::Index) + return Op[s' => 1, s => 2] = 1 + end + + function ITensors.op!(Op::ITensor, ::OpName"A", ::SiteType"HardCore", s::Index) + return Op[s' => 2, s => 1] = 1 + end + + t = 1.0 + V1 = 1.0e-3 + V2 = 2.0e-5 + + N = 20 + sites = siteinds("HardCore", N) + + os = OpSum() + for j in 1:(N - 1) + os -= t, "Adag", j, "A", j + 1 + os -= t, "A", j, "Adag", j + 1 + os += V1, "N", j, "N", j + 1 + end + for j in 1:(N - 2) + os += V2, "N", j, "N", j + 2 + end + H = MPO(os, sites) + psi0 = MPS(sites, n -> isodd(n) ? "0" : "1") + @test abs(inner(psi0', H, psi0) - 0.00018) < 1.0e-10 end - H = MPO(os, sites) - psi0 = MPS(sites, n -> isodd(n) ? "0" : "1") - @test abs(inner(psi0', H, psi0) - 0.00018) < 1E-10 - end - - @testset "Matrix operator representation" begin - dim = 4 - op = rand(dim, dim) - opt = op' - s = [Index(dim), Index(dim)] - a = OpSum() - a += 1.0, op + opt, 1 - a += 1.0, op + opt, 2 - mpoa = MPO(a, s) - b = OpSum() - b += 1.0, op, 1 - b += 1.0, opt, 1 - b += 1.0, op, 2 - b += 1.0, opt, 2 - mpob = MPO(b, s) - @test mpoa ≈ mpob - end - - @testset "Matrix operator representation - hashing bug" begin - n = 4 - dim = 4 - s = siteinds(dim, n) - o = rand(dim, dim) - os = OpSum() - for j in 1:(n - 1) - os += copy(o), j, copy(o), j + 1 + + @testset "Matrix operator representation" begin + dim = 4 + op = rand(dim, dim) + opt = op' + s = [Index(dim), Index(dim)] + a = OpSum() + a += 1.0, op + opt, 1 + a += 1.0, op + opt, 2 + mpoa = MPO(a, s) + b = OpSum() + b += 1.0, op, 1 + b += 1.0, opt, 1 + b += 1.0, op, 2 + b += 1.0, opt, 2 + mpob = MPO(b, s) + @test mpoa ≈ mpob end - H1 = MPO(os, s) - H2 = ITensor() - H2 += op(o, s[1]) * op(o, s[2]) * op("I", s[3]) * op("I", s[4]) - H2 += op("I", s[1]) * op(o, s[2]) * op(o, s[3]) * op("I", s[4]) - H2 += op("I", s[1]) * op("I", s[2]) * op(o, s[3]) * op(o, s[4]) - @test contract(H1) ≈ H2 - end - - @testset "Matrix operator representation - hashing bug" begin - file_path = joinpath(@__DIR__, "utils", "opsum_hash_bug.jld2") - comps, n, dims = load(file_path, "comps", "n", "dims") - s = [Index(d) for d in dims] - for _ in 1:100 - os = components_to_opsum(comps, n) - # Before defining `hash(::Op, h::UInt)`, this - # would randomly throw an error due to - # some hashing issue in `MPO(::OpSum, ...)` - MPO(os, s) + + @testset "Matrix operator representation - hashing bug" begin + n = 4 + dim = 4 + s = siteinds(dim, n) + o = rand(dim, dim) + os = OpSum() + for j in 1:(n - 1) + os += copy(o), j, copy(o), j + 1 + end + H1 = MPO(os, s) + H2 = ITensor() + H2 += op(o, s[1]) * op(o, s[2]) * op("I", s[3]) * op("I", s[4]) + H2 += op("I", s[1]) * op(o, s[2]) * op(o, s[3]) * op("I", s[4]) + H2 += op("I", s[1]) * op("I", s[2]) * op(o, s[3]) * op(o, s[4]) + @test contract(H1) ≈ H2 end - end - @testset "Operator with empty blocks - issue #963" begin - sites = siteinds("Fermion", 2; conserve_qns=true) - opsum1 = OpSum() - for p in 1:2, q in 1:2, r in 1:2, s in 1:2 - opsum1 += "c†", p, "c†", q, "c", r, "c", s + @testset "Matrix operator representation - hashing bug" begin + file_path = joinpath(@__DIR__, "utils", "opsum_hash_bug.jld2") + comps, n, dims = load(file_path, "comps", "n", "dims") + s = [Index(d) for d in dims] + for _ in 1:100 + os = components_to_opsum(comps, n) + # Before defining `hash(::Op, h::UInt)`, this + # would randomly throw an error due to + # some hashing issue in `MPO(::OpSum, ...)` + MPO(os, s) + end end - H1 = MPO(opsum1, sites) - opsum2 = OpSum() - for p in 1:2, q in 1:2, r in 1:2, s in 1:2 - if !(p == q == r == s) - opsum2 += "c†", p, "c†", q, "c", r, "c", s - end + + @testset "Operator with empty blocks - issue #963" begin + sites = siteinds("Fermion", 2; conserve_qns = true) + opsum1 = OpSum() + for p in 1:2, q in 1:2, r in 1:2, s in 1:2 + opsum1 += "c†", p, "c†", q, "c", r, "c", s + end + H1 = MPO(opsum1, sites) + opsum2 = OpSum() + for p in 1:2, q in 1:2, r in 1:2, s in 1:2 + if !(p == q == r == s) + opsum2 += "c†", p, "c†", q, "c", r, "c", s + end + end + H2 = MPO(opsum2, sites) + @test H1 ≈ H2 end - H2 = MPO(opsum2, sites) - @test H1 ≈ H2 - end - @testset "One-site ops bond dimension test" begin - sites = siteinds("S=1/2", N) + @testset "One-site ops bond dimension test" begin + sites = siteinds("S=1/2", N) + + # one-site operator on every site + os = OpSum() + for j in 1:N + os += "Z", j + end + H = MPO(os, sites) + @test all(linkdims(H) .== 2) + + # one-site operator on a single site + os = OpSum() + os += "Z", rand(1:N) + H = MPO(os, sites) + @test all(linkdims(H) .<= 2) + @test_broken all(linkdims(H) .== 1) + end - # one-site operator on every site - os = OpSum() - for j in 1:N - os += "Z", j + @testset "Regression test (Issue 1150): Zero blocks operator" begin + N = 4 + sites = siteinds("Fermion", N; conserve_qns = true) + os = OpSum() + os += (1.111, "Cdag", 3, "Cdag", 4, "C", 2, "C", 1) + os += (2.222, "Cdag", 4, "Cdag", 1, "C", 3, "C", 2) + os += (3.333, "Cdag", 1, "Cdag", 4, "C", 4, "C", 1) + os += (4.444, "Cdag", 2, "Cdag", 3, "C", 1, "C", 4) + # The following operator has C on site 2 twice, resulting + # in a local operator with no blocks (exactly zero), + # causing a certain logical step in working out the column qn + # to fail: + os += (5.555, "Cdag", 4, "Cdag", 4, "C", 2, "C", 2) + @test_nowarn H = MPO(os, sites) end - H = MPO(os, sites) - @test all(linkdims(H) .== 2) - - # one-site operator on a single site - os = OpSum() - os += "Z", rand(1:N) - H = MPO(os, sites) - @test all(linkdims(H) .<= 2) - @test_broken all(linkdims(H) .== 1) - end - - @testset "Regression test (Issue 1150): Zero blocks operator" begin - N = 4 - sites = siteinds("Fermion", N; conserve_qns=true) - os = OpSum() - os += (1.111, "Cdag", 3, "Cdag", 4, "C", 2, "C", 1) - os += (2.222, "Cdag", 4, "Cdag", 1, "C", 3, "C", 2) - os += (3.333, "Cdag", 1, "Cdag", 4, "C", 4, "C", 1) - os += (4.444, "Cdag", 2, "Cdag", 3, "C", 1, "C", 4) - # The following operator has C on site 2 twice, resulting - # in a local operator with no blocks (exactly zero), - # causing a certain logical step in working out the column qn - # to fail: - os += (5.555, "Cdag", 4, "Cdag", 4, "C", 2, "C", 2) - @test_nowarn H = MPO(os, sites) - end end end diff --git a/test/base/test_deprecated.jl b/test/base/test_deprecated.jl index a44bb03..f4a2d30 100644 --- a/test/base/test_deprecated.jl +++ b/test/base/test_deprecated.jl @@ -3,25 +3,25 @@ using ITensorMPS: MPS, maxlinkdim, randomMPS, siteinds using LinearAlgebra: norm using Test: @test, @testset @testset "randomMPS" begin - sites = siteinds("S=1/2", 4) - state = j -> isodd(j) ? "↑" : "↓" - linkdims = 2 - # Deprecated linkdims syntax - for mps in [ - randomMPS(Float64, sites, state; linkdims), - randomMPS(Float64, sites; linkdims), - randomMPS(sites, state; linkdims), - randomMPS(sites, linkdims), + sites = siteinds("S=1/2", 4) + state = j -> isodd(j) ? "↑" : "↓" + linkdims = 2 # Deprecated linkdims syntax - randomMPS(Float64, sites, state, linkdims), - randomMPS(Float64, sites, linkdims), - randomMPS(sites, state, linkdims), - randomMPS(sites, linkdims), - ] - @test mps isa MPS - @test length(mps) == 4 - @test maxlinkdim(mps) == 2 - @test norm(mps) > 0 - end + for mps in [ + randomMPS(Float64, sites, state; linkdims), + randomMPS(Float64, sites; linkdims), + randomMPS(sites, state; linkdims), + randomMPS(sites, linkdims), + # Deprecated linkdims syntax + randomMPS(Float64, sites, state, linkdims), + randomMPS(Float64, sites, linkdims), + randomMPS(sites, state, linkdims), + randomMPS(sites, linkdims), + ] + @test mps isa MPS + @test length(mps) == 4 + @test maxlinkdim(mps) == 2 + @test norm(mps) > 0 + end end end diff --git a/test/base/test_dmrg.jl b/test/base/test_dmrg.jl index 28affb7..19c68f6 100644 --- a/test/base/test_dmrg.jl +++ b/test/base/test_dmrg.jl @@ -2,467 +2,467 @@ using ITensors, Test, Random using ITensorMPS: dmrg, nsite, set_nsite!, siteinds, site_range @testset "Basic DMRG" begin - @testset "Spin-one Heisenberg" begin - N = 10 - sites = siteinds("S=1", N) - - os = OpSum() - for j in 1:(N - 1) - add!(os, "Sz", j, "Sz", j + 1) - add!(os, 0.5, "S+", j, "S-", j + 1) - add!(os, 0.5, "S-", j, "S+", j + 1) + @testset "Spin-one Heisenberg" begin + N = 10 + sites = siteinds("S=1", N) + + os = OpSum() + for j in 1:(N - 1) + add!(os, "Sz", j, "Sz", j + 1) + add!(os, 0.5, "S+", j, "S-", j + 1) + add!(os, 0.5, "S-", j, "S+", j + 1) + end + H = MPO(os, sites) + + psi = random_mps(sites) + + sweeps = Sweeps(3) + @test length(sweeps) == 3 + maxdim!(sweeps, 10, 20, 40) + mindim!(sweeps, 1, 10) + cutoff!(sweeps, 1.0e-11) + noise!(sweeps, 1.0e-10) + str = split(sprint(show, sweeps), '\n') + @test length(str) > 1 + energy, psi = dmrg(H, psi, sweeps; outputlevel = 0) + @test energy < -12.0 end - H = MPO(os, sites) - - psi = random_mps(sites) - - sweeps = Sweeps(3) - @test length(sweeps) == 3 - maxdim!(sweeps, 10, 20, 40) - mindim!(sweeps, 1, 10) - cutoff!(sweeps, 1E-11) - noise!(sweeps, 1E-10) - str = split(sprint(show, sweeps), '\n') - @test length(str) > 1 - energy, psi = dmrg(H, psi, sweeps; outputlevel=0) - @test energy < -12.0 - end - - @testset "QN-conserving Spin-one Heisenberg" begin - N = 10 - sites = siteinds("S=1", N; conserve_qns=true) - - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - H = MPO(os, sites) - - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - psi = random_mps(sites, state; linkdims=4) - - sweeps = Sweeps(3) - @test length(sweeps) == 3 - maxdim!(sweeps, 10, 20, 40) - mindim!(sweeps, 1, 10) - cutoff!(sweeps, 1E-11) - noise!(sweeps, 1E-10) - str = split(sprint(show, sweeps), '\n') - @test length(str) > 1 - energy, psi = dmrg(H, psi, sweeps; outputlevel=0) - @test energy < -12.0 - end - - @testset "QN-conserving Spin-one Heisenberg with disk caching" begin - N = 10 - sites = siteinds("S=1", N; conserve_qns=true) - - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - H = MPO(os, sites) - - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - psi = random_mps(sites, state; linkdims=4) - - sweeps = Sweeps(3) - @test length(sweeps) == 3 - maxdim!(sweeps, 10, 20, 40) - mindim!(sweeps, 1, 10) - cutoff!(sweeps, 1E-11) - noise!(sweeps, 1E-10) - str = split(sprint(show, sweeps), '\n') - @test length(str) > 1 - energy, psi = dmrg(H, psi, sweeps; outputlevel=0, write_when_maxdim_exceeds=15) - @test energy < -12.0 - end - - @testset "ProjMPO with disk caching" begin - N = 10 - sites = siteinds("S=1", N; conserve_qns=true) - - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 + + @testset "QN-conserving Spin-one Heisenberg" begin + N = 10 + sites = siteinds("S=1", N; conserve_qns = true) + + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + H = MPO(os, sites) + + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + psi = random_mps(sites, state; linkdims = 4) + + sweeps = Sweeps(3) + @test length(sweeps) == 3 + maxdim!(sweeps, 10, 20, 40) + mindim!(sweeps, 1, 10) + cutoff!(sweeps, 1.0e-11) + noise!(sweeps, 1.0e-10) + str = split(sprint(show, sweeps), '\n') + @test length(str) > 1 + energy, psi = dmrg(H, psi, sweeps; outputlevel = 0) + @test energy < -12.0 end - H = MPO(os, sites) - - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - psi = random_mps(sites, state; linkdims=4) - PH = ProjMPO(H) - - PHc = copy(PH) - - n = 4 - orthogonalize!(psi, n) - position!(PH, psi, n) - PHdisk = ITensors.disk(PH) - - @test length(PH) == N - @test length(PHdisk) == N - @test site_range(PH) == n:(n + 1) - @test eltype(PH) == Float64 - ## TODO sometimes random_mps gives a linkdim value of 3 - ## which causes an error in `calculated_dim = 3^2 * 4^2` - calculated_dim = - linkdim(psi, n - 1) * - linkdim(psi, n + 1) * - dim(siteind(psi, n)) * - dim(siteind(psi, n + 1)) - @test size(PH) == (calculated_dim, calculated_dim) - @test PH.lpos == n - 1 - @test PH.rpos == n + 2 - @test PHc.lpos == 0 - @test PHc.rpos == N + 1 - @test rproj(PH) ≈ rproj(PHdisk) - @test PHdisk.LR isa ITensors.DiskVector{ITensor} - @test PHdisk.LR[PHdisk.rpos] ≈ PHdisk.Rcache - position!(PH, psi, N) - @test PH.lpos == N - 1 - end - - @testset "ProjMPOSum DMRG with disk caching" begin - N = 10 - sites = siteinds("S=1", N; conserve_qns=true) - - osA = OpSum() - for j in 1:(N - 1) - osA += "Sz", j, "Sz", j + 1 + + @testset "QN-conserving Spin-one Heisenberg with disk caching" begin + N = 10 + sites = siteinds("S=1", N; conserve_qns = true) + + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + H = MPO(os, sites) + + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + psi = random_mps(sites, state; linkdims = 4) + + sweeps = Sweeps(3) + @test length(sweeps) == 3 + maxdim!(sweeps, 10, 20, 40) + mindim!(sweeps, 1, 10) + cutoff!(sweeps, 1.0e-11) + noise!(sweeps, 1.0e-10) + str = split(sprint(show, sweeps), '\n') + @test length(str) > 1 + energy, psi = dmrg(H, psi, sweeps; outputlevel = 0, write_when_maxdim_exceeds = 15) + @test energy < -12.0 end - HA = MPO(osA, sites) - osB = OpSum() - for j in 1:(N - 1) - osB += 0.5, "S+", j, "S-", j + 1 - osB += 0.5, "S-", j, "S+", j + 1 + @testset "ProjMPO with disk caching" begin + N = 10 + sites = siteinds("S=1", N; conserve_qns = true) + + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + H = MPO(os, sites) + + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + psi = random_mps(sites, state; linkdims = 4) + PH = ProjMPO(H) + + PHc = copy(PH) + + n = 4 + orthogonalize!(psi, n) + position!(PH, psi, n) + PHdisk = ITensors.disk(PH) + + @test length(PH) == N + @test length(PHdisk) == N + @test site_range(PH) == n:(n + 1) + @test eltype(PH) == Float64 + ## TODO sometimes random_mps gives a linkdim value of 3 + ## which causes an error in `calculated_dim = 3^2 * 4^2` + calculated_dim = + linkdim(psi, n - 1) * + linkdim(psi, n + 1) * + dim(siteind(psi, n)) * + dim(siteind(psi, n + 1)) + @test size(PH) == (calculated_dim, calculated_dim) + @test PH.lpos == n - 1 + @test PH.rpos == n + 2 + @test PHc.lpos == 0 + @test PHc.rpos == N + 1 + @test rproj(PH) ≈ rproj(PHdisk) + @test PHdisk.LR isa ITensors.DiskVector{ITensor} + @test PHdisk.LR[PHdisk.rpos] ≈ PHdisk.Rcache + position!(PH, psi, N) + @test PH.lpos == N - 1 end - HB = MPO(osB, sites) - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - psi = random_mps(sites, state; linkdims=4) + @testset "ProjMPOSum DMRG with disk caching" begin + N = 10 + sites = siteinds("S=1", N; conserve_qns = true) + + osA = OpSum() + for j in 1:(N - 1) + osA += "Sz", j, "Sz", j + 1 + end + HA = MPO(osA, sites) + + osB = OpSum() + for j in 1:(N - 1) + osB += 0.5, "S+", j, "S-", j + 1 + osB += 0.5, "S-", j, "S+", j + 1 + end + HB = MPO(osB, sites) + + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + psi = random_mps(sites, state; linkdims = 4) + + energy, psi = dmrg( + [HA, HB], psi; nsweeps = 3, maxdim = [10, 20, 30], write_when_maxdim_exceeds = 10 + ) + @test energy < -12.0 + end - energy, psi = dmrg( - [HA, HB], psi; nsweeps=3, maxdim=[10, 20, 30], write_when_maxdim_exceeds=10 - ) - @test energy < -12.0 - end + @testset "ProjMPO: nsite" begin + N = 10 + sites = siteinds("S=1", N) + + os1 = OpSum() + for j in 1:(N - 1) + os1 += 0.5, "S+", j, "S-", j + 1 + os1 += 0.5, "S-", j, "S+", j + 1 + end + os2 = OpSum() + for j in 1:(N - 1) + os2 += "Sz", j, "Sz", j + 1 + end + H1 = MPO(os1, sites) + H2 = MPO(os2, sites) + + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + psi = random_mps(sites, state; linkdims = 4) + PH1 = ProjMPO(H1) + PH = ProjMPOSum([H1, H2]) + PH1c = copy(PH1) + PHc = copy(PH) + @test nsite(PH1) == 2 + @test nsite(PH) == 2 + @test nsite(PH1c) == 2 + @test nsite(PHc) == 2 + + set_nsite!(PH1, 3) + @test nsite(PH1) == 3 + @test nsite(PH1c) == 2 + @test nsite(PHc) == 2 + + set_nsite!(PH, 4) + @test nsite(PH) == 4 + @test nsite(PH1c) == 2 + @test nsite(PHc) == 2 + end - @testset "ProjMPO: nsite" begin - N = 10 - sites = siteinds("S=1", N) + @testset "Transverse field Ising" begin + N = 32 + sites = siteinds("S=1/2", N) + Random.seed!(432) + psi0 = random_mps(sites) + + os = OpSum() + for j in 1:N + j < N && add!(os, -1.0, "Z", j, "Z", j + 1) + add!(os, -1.0, "X", j) + end + H = MPO(os, sites) + + sweeps = Sweeps(5) + maxdim!(sweeps, 10, 20) + cutoff!(sweeps, 1.0e-12) + noise!(sweeps, 1.0e-10) + energy, psi = dmrg(H, psi0, sweeps; outputlevel = 0) + + # Exact energy for transverse field Ising model + # with open boundary conditions at criticality + energy_exact = 1.0 - 1.0 / sin(π / (4 * N + 2)) + @test abs((energy - energy_exact) / energy_exact) < 1.0e-4 + end - os1 = OpSum() - for j in 1:(N - 1) - os1 += 0.5, "S+", j, "S-", j + 1 - os1 += 0.5, "S-", j, "S+", j + 1 + @testset "Compact Sweeps syntax" begin + N = 32 + sites = siteinds("S=1/2", N) + Random.seed!(432) + psi0 = random_mps(sites) + + function ising(N; h = 1.0) + os = OpSum() + for j in 1:N + j < N && (os -= ("Z", j, "Z", j + 1)) + os -= h, "X", j + end + return os + end + + h = 1.0 + H = MPO(ising(N; h = h), sites) + energy, psi = dmrg( + H, psi0; nsweeps = 5, maxdim = [10, 20], cutoff = 1.0e-12, noise = 1.0e-10, outputlevel = 0 + ) + + energy_exact = 1.0 - 1.0 / sin(π / (4 * N + 2)) + @test abs((energy - energy_exact) / energy_exact) < 1.0e-4 end - os2 = OpSum() - for j in 1:(N - 1) - os2 += "Sz", j, "Sz", j + 1 + + @testset "Transverse field Ising, conserve Sz parity" begin + N = 32 + sites = siteinds("S=1/2", N; conserve_szparity = true) + Random.seed!(432) + + state = [isodd(j) ? "↑" : "↓" for j in 1:N] + psi0 = random_mps(sites, state) + + os = OpSum() + for j in 1:N + j < N && add!(os, -1.0, "X", j, "X", j + 1) + add!(os, -1.0, "Z", j) + end + H = MPO(os, sites) + + sweeps = Sweeps(5) + maxdim!(sweeps, 10, 20) + cutoff!(sweeps, 1.0e-12) + noise!(sweeps, 1.0e-10) + energy, psi = dmrg(H, psi0, sweeps; outputlevel = 0) + + # Exact energy for transverse field Ising model + # with open boundary conditions at criticality + energy_exact = 1.0 - 1.0 / sin(π / (4 * N + 2)) + @test abs((energy - energy_exact) / energy_exact) < 1.0e-4 end - H1 = MPO(os1, sites) - H2 = MPO(os2, sites) - - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - psi = random_mps(sites, state; linkdims=4) - PH1 = ProjMPO(H1) - PH = ProjMPOSum([H1, H2]) - PH1c = copy(PH1) - PHc = copy(PH) - @test nsite(PH1) == 2 - @test nsite(PH) == 2 - @test nsite(PH1c) == 2 - @test nsite(PHc) == 2 - - set_nsite!(PH1, 3) - @test nsite(PH1) == 3 - @test nsite(PH1c) == 2 - @test nsite(PHc) == 2 - - set_nsite!(PH, 4) - @test nsite(PH) == 4 - @test nsite(PH1c) == 2 - @test nsite(PHc) == 2 - end - - @testset "Transverse field Ising" begin - N = 32 - sites = siteinds("S=1/2", N) - Random.seed!(432) - psi0 = random_mps(sites) - - os = OpSum() - for j in 1:N - j < N && add!(os, -1.0, "Z", j, "Z", j + 1) - add!(os, -1.0, "X", j) + + @testset "DMRGObserver" begin + + # Test that basic constructors work + observer = DMRGObserver() + observer = DMRGObserver(; minsweeps = 2, energy_tol = 1.0e-4) + + # Test in a DMRG calculation + N = 10 + sites = siteinds("S=1/2", N) + Random.seed!(42) + psi0 = random_mps(sites) + + os = OpSum() + for j in 1:(N - 1) + os -= 1, "Sz", j, "Sz", j + 1 + end + for j in 1:N + os -= 0.2, "Sx", j + end + H = MPO(os, sites) + + sweeps = Sweeps(3) + maxdim!(sweeps, 10) + cutoff!(sweeps, 1.0e-12) + + observer = DMRGObserver(["Sz", "Sx"], sites) + + E, psi = dmrg(H, psi0, sweeps; observer = observer, outputlevel = 0) + @test length(measurements(observer)["Sz"]) == 3 + @test length(measurements(observer)["Sx"]) == 3 + @test all(length.(measurements(observer)["Sz"]) .== N) + @test all(length.(measurements(observer)["Sx"]) .== N) + @test length(energies(observer)) == 3 + @test length(truncerrors(observer)) == 3 + @test energies(observer)[end] == E + @test all(truncerrors(observer) .< 1.0e-9) + + orthogonalize!(psi, 1) + m = scalar(dag(psi[1]) * noprime(op(sites, "Sz", 1) * psi[1])) + @test measurements(observer)["Sz"][end][1] ≈ m end - H = MPO(os, sites) - - sweeps = Sweeps(5) - maxdim!(sweeps, 10, 20) - cutoff!(sweeps, 1E-12) - noise!(sweeps, 1E-10) - energy, psi = dmrg(H, psi0, sweeps; outputlevel=0) - - # Exact energy for transverse field Ising model - # with open boundary conditions at criticality - energy_exact = 1.0 - 1.0 / sin(π / (4 * N + 2)) - @test abs((energy - energy_exact) / energy_exact) < 1e-4 - end - - @testset "Compact Sweeps syntax" begin - N = 32 - sites = siteinds("S=1/2", N) - Random.seed!(432) - psi0 = random_mps(sites) - - function ising(N; h=1.0) - os = OpSum() - for j in 1:N - j < N && (os -= ("Z", j, "Z", j + 1)) - os -= h, "X", j - end - return os + + @testset "Sum of MPOs (ProjMPOSum)" begin + N = 10 + sites = siteinds("S=1", N) + + osZ = OpSum() + for j in 1:(N - 1) + osZ += "Sz", j, "Sz", j + 1 + end + HZ = MPO(osZ, sites) + + osXY = OpSum() + for j in 1:(N - 1) + osXY += 0.5, "S+", j, "S-", j + 1 + osXY += 0.5, "S-", j, "S+", j + 1 + end + HXY = MPO(osXY, sites) + + psi = random_mps(sites) + + sweeps = Sweeps(3) + maxdim!(sweeps, 10, 20, 40) + mindim!(sweeps, 1, 10, 10) + cutoff!(sweeps, 1.0e-11) + noise!(sweeps, 1.0e-10) + energy, psi = dmrg([HZ, HXY], psi, sweeps; outputlevel = 0) + @test energy < -12.0 end - h = 1.0 - H = MPO(ising(N; h=h), sites) - energy, psi = dmrg( - H, psi0; nsweeps=5, maxdim=[10, 20], cutoff=1e-12, noise=1e-10, outputlevel=0 - ) + @testset "Excited-state DMRG" begin + N = 10 + weight = 15.0 - energy_exact = 1.0 - 1.0 / sin(π / (4 * N + 2)) - @test abs((energy - energy_exact) / energy_exact) < 1e-4 - end + sites = siteinds("S=1", N) + sites[1] = Index(2, "S=1/2,n=1,Site") + sites[N] = Index(2, "S=1/2,n=$N,Site") - @testset "Transverse field Ising, conserve Sz parity" begin - N = 32 - sites = siteinds("S=1/2", N; conserve_szparity=true) - Random.seed!(432) + os = OpSum() + for j in 1:(N - 1) + os += "Sz", j, "Sz", j + 1 + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + end + H = MPO(os, sites) - state = [isodd(j) ? "↑" : "↓" for j in 1:N] - psi0 = random_mps(sites, state) + psi0i = random_mps(sites; linkdims = 10) - os = OpSum() - for j in 1:N - j < N && add!(os, -1.0, "X", j, "X", j + 1) - add!(os, -1.0, "Z", j) - end - H = MPO(os, sites) - - sweeps = Sweeps(5) - maxdim!(sweeps, 10, 20) - cutoff!(sweeps, 1E-12) - noise!(sweeps, 1E-10) - energy, psi = dmrg(H, psi0, sweeps; outputlevel=0) - - # Exact energy for transverse field Ising model - # with open boundary conditions at criticality - energy_exact = 1.0 - 1.0 / sin(π / (4 * N + 2)) - @test abs((energy - energy_exact) / energy_exact) < 1e-4 - end - - @testset "DMRGObserver" begin - - # Test that basic constructors work - observer = DMRGObserver() - observer = DMRGObserver(; minsweeps=2, energy_tol=1E-4) - - # Test in a DMRG calculation - N = 10 - sites = siteinds("S=1/2", N) - Random.seed!(42) - psi0 = random_mps(sites) - - os = OpSum() - for j in 1:(N - 1) - os -= 1, "Sz", j, "Sz", j + 1 - end - for j in 1:N - os -= 0.2, "Sx", j - end - H = MPO(os, sites) - - sweeps = Sweeps(3) - maxdim!(sweeps, 10) - cutoff!(sweeps, 1E-12) - - observer = DMRGObserver(["Sz", "Sx"], sites) - - E, psi = dmrg(H, psi0, sweeps; observer=observer, outputlevel=0) - @test length(measurements(observer)["Sz"]) == 3 - @test length(measurements(observer)["Sx"]) == 3 - @test all(length.(measurements(observer)["Sz"]) .== N) - @test all(length.(measurements(observer)["Sx"]) .== N) - @test length(energies(observer)) == 3 - @test length(truncerrors(observer)) == 3 - @test energies(observer)[end] == E - @test all(truncerrors(observer) .< 1E-9) - - orthogonalize!(psi, 1) - m = scalar(dag(psi[1]) * noprime(op(sites, "Sz", 1) * psi[1])) - @test measurements(observer)["Sz"][end][1] ≈ m - end - - @testset "Sum of MPOs (ProjMPOSum)" begin - N = 10 - sites = siteinds("S=1", N) - - osZ = OpSum() - for j in 1:(N - 1) - osZ += "Sz", j, "Sz", j + 1 - end - HZ = MPO(osZ, sites) + sweeps = Sweeps(4) + maxdim!(sweeps, 10, 20, 100, 100) + cutoff!(sweeps, 1.0e-11) + noise!(sweeps, 1.0e-10) - osXY = OpSum() - for j in 1:(N - 1) - osXY += 0.5, "S+", j, "S-", j + 1 - osXY += 0.5, "S-", j, "S+", j + 1 - end - HXY = MPO(osXY, sites) - - psi = random_mps(sites) - - sweeps = Sweeps(3) - maxdim!(sweeps, 10, 20, 40) - mindim!(sweeps, 1, 10, 10) - cutoff!(sweeps, 1E-11) - noise!(sweeps, 1E-10) - energy, psi = dmrg([HZ, HXY], psi, sweeps; outputlevel=0) - @test energy < -12.0 - end - - @testset "Excited-state DMRG" begin - N = 10 - weight = 15.0 - - sites = siteinds("S=1", N) - sites[1] = Index(2, "S=1/2,n=1,Site") - sites[N] = Index(2, "S=1/2,n=$N,Site") - - os = OpSum() - for j in 1:(N - 1) - os += "Sz", j, "Sz", j + 1 - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - end - H = MPO(os, sites) - - psi0i = random_mps(sites; linkdims=10) - - sweeps = Sweeps(4) - maxdim!(sweeps, 10, 20, 100, 100) - cutoff!(sweeps, 1E-11) - noise!(sweeps, 1E-10) - - energy0, psi0 = dmrg(H, psi0i, sweeps; outputlevel=0) - @test energy0 < -11.5 - - psi1i = random_mps(sites; linkdims=10) - energy1, psi1 = dmrg(H, [psi0], psi1i, sweeps; outputlevel=0, weight=weight) - - @test energy1 > energy0 - @test energy1 < -11.1 - - @test inner(psi1, psi0) < 1E-5 - end - - @testset "Fermionic Hamiltonian" begin - N = 10 - t1 = 1.0 - t2 = 0.5 - V = 0.2 - s = siteinds("Fermion", N; conserve_qns=true) - - state = fill(1, N) - state[1] = 2 - state[3] = 2 - state[5] = 2 - state[7] = 2 - psi0 = MPS(s, state) - - os = OpSum() - for j in 1:(N - 1) - os -= t1, "Cdag", j, "C", j + 1 - os -= t1, "Cdag", j + 1, "C", j - os += V, "N", j, "N", j + 1 - end - for j in 1:(N - 2) - os -= t2, "Cdag", j, "C", j + 2 - os -= t2, "Cdag", j + 2, "C", j - end - H = MPO(os, s) - - sweeps = Sweeps(5) - maxdim!(sweeps, 10, 20, 100, 100, 200) - cutoff!(sweeps, 1E-8) - noise!(sweeps, 1E-10) - - energy, psi = dmrg(H, psi0, sweeps; outputlevel=0) - @test (-6.5 < energy < -6.4) - end - - @testset "Hubbard model" begin - N = 10 - Npart = 8 - t1 = 1.0 - U = 1.0 - V1 = 0.5 - sites = siteinds("Electron", N; conserve_qns=true) - os = OpSum() - for i in 1:N - os += (U, "Nupdn", i) - end - for b in 1:(N - 1) - os -= t1, "Cdagup", b, "Cup", b + 1 - os -= t1, "Cdagup", b + 1, "Cup", b - os -= t1, "Cdagdn", b, "Cdn", b + 1 - os -= t1, "Cdagdn", b + 1, "Cdn", b - os += V1, "Ntot", b, "Ntot", b + 1 - end - H = MPO(os, sites) - sweeps = Sweeps(6) - maxdim!(sweeps, 50, 100, 200, 400, 800, 800) - cutoff!(sweeps, 1E-10) - state = ["Up", "Dn", "Dn", "Up", "Emp", "Up", "Up", "Emp", "Dn", "Dn"] - psi0 = random_mps(sites, state; linkdims=10) - energy, psi = dmrg(H, psi0, sweeps; outputlevel=0) - @test (-8.02 < energy < -8.01) - end - - @testset "Input Without Ortho Center or Not at 1" begin - N = 6 - sites = siteinds("S=1", N) - - os = OpSum() - for j in 1:(N - 1) - add!(os, "Sz", j, "Sz", j + 1) - add!(os, 0.5, "S+", j, "S-", j + 1) - add!(os, 0.5, "S-", j, "S+", j + 1) - end - H = MPO(os, sites) + energy0, psi0 = dmrg(H, psi0i, sweeps; outputlevel = 0) + @test energy0 < -11.5 + + psi1i = random_mps(sites; linkdims = 10) + energy1, psi1 = dmrg(H, [psi0], psi1i, sweeps; outputlevel = 0, weight = weight) + + @test energy1 > energy0 + @test energy1 < -11.1 - sweeps = Sweeps(1) - maxdim!(sweeps, 10) - cutoff!(sweeps, 1E-11) + @test inner(psi1, psi0) < 1.0e-5 + end - psi0 = random_mps(sites; linkdims=4) + @testset "Fermionic Hamiltonian" begin + N = 10 + t1 = 1.0 + t2 = 0.5 + V = 0.2 + s = siteinds("Fermion", N; conserve_qns = true) + + state = fill(1, N) + state[1] = 2 + state[3] = 2 + state[5] = 2 + state[7] = 2 + psi0 = MPS(s, state) + + os = OpSum() + for j in 1:(N - 1) + os -= t1, "Cdag", j, "C", j + 1 + os -= t1, "Cdag", j + 1, "C", j + os += V, "N", j, "N", j + 1 + end + for j in 1:(N - 2) + os -= t2, "Cdag", j, "C", j + 2 + os -= t2, "Cdag", j + 2, "C", j + end + H = MPO(os, s) + + sweeps = Sweeps(5) + maxdim!(sweeps, 10, 20, 100, 100, 200) + cutoff!(sweeps, 1.0e-8) + noise!(sweeps, 1.0e-10) + + energy, psi = dmrg(H, psi0, sweeps; outputlevel = 0) + @test (-6.5 < energy < -6.4) + end - # Test that input works with wrong ortho center: - orthogonalize!(psi0, 5) - energy, psi = dmrg(H, psi0, sweeps; outputlevel=0) + @testset "Hubbard model" begin + N = 10 + Npart = 8 + t1 = 1.0 + U = 1.0 + V1 = 0.5 + sites = siteinds("Electron", N; conserve_qns = true) + os = OpSum() + for i in 1:N + os += (U, "Nupdn", i) + end + for b in 1:(N - 1) + os -= t1, "Cdagup", b, "Cup", b + 1 + os -= t1, "Cdagup", b + 1, "Cup", b + os -= t1, "Cdagdn", b, "Cdn", b + 1 + os -= t1, "Cdagdn", b + 1, "Cdn", b + os += V1, "Ntot", b, "Ntot", b + 1 + end + H = MPO(os, sites) + sweeps = Sweeps(6) + maxdim!(sweeps, 50, 100, 200, 400, 800, 800) + cutoff!(sweeps, 1.0e-10) + state = ["Up", "Dn", "Dn", "Up", "Emp", "Up", "Up", "Emp", "Dn", "Dn"] + psi0 = random_mps(sites, state; linkdims = 10) + energy, psi = dmrg(H, psi0, sweeps; outputlevel = 0) + @test (-8.02 < energy < -8.01) + end - # Test that input works with no ortho center: - for j in 1:N - psi0[j] = random_itensor(inds(psi0[j])) + @testset "Input Without Ortho Center or Not at 1" begin + N = 6 + sites = siteinds("S=1", N) + + os = OpSum() + for j in 1:(N - 1) + add!(os, "Sz", j, "Sz", j + 1) + add!(os, 0.5, "S+", j, "S-", j + 1) + add!(os, 0.5, "S-", j, "S+", j + 1) + end + H = MPO(os, sites) + + sweeps = Sweeps(1) + maxdim!(sweeps, 10) + cutoff!(sweeps, 1.0e-11) + + psi0 = random_mps(sites; linkdims = 4) + + # Test that input works with wrong ortho center: + orthogonalize!(psi0, 5) + energy, psi = dmrg(H, psi0, sweeps; outputlevel = 0) + + # Test that input works with no ortho center: + for j in 1:N + psi0[j] = random_itensor(inds(psi0[j])) + end + energy, psi = dmrg(H, psi0, sweeps; outputlevel = 0) end - energy, psi = dmrg(H, psi0, sweeps; outputlevel=0) - end end diff --git a/test/base/test_examples.jl b/test/base/test_examples.jl index ead536e..14a0a97 100644 --- a/test/base/test_examples.jl +++ b/test/base/test_examples.jl @@ -3,28 +3,28 @@ using ITensorMPS: ITensorMPS using Suppressor: @capture_out using Test: @test_nowarn, @testset @testset "Example Codes" begin - @testset "DMRG with Observer" begin - @test_nowarn begin - @capture_out begin - include( - joinpath(pkgdir(ITensorMPS), "examples", "dmrg", "1d_ising_with_observer.jl") - ) - end + @testset "DMRG with Observer" begin + @test_nowarn begin + @capture_out begin + include( + joinpath(pkgdir(ITensorMPS), "examples", "dmrg", "1d_ising_with_observer.jl") + ) + end + end end - end - @testset "Package Compile Code" begin - @test_nowarn begin - @capture_out begin - include( - joinpath( - pkgdir(ITensorMPS), - "ext", - "ITensorMPSPackageCompilerExt", - "precompile_itensormps.jl", - ), - ) - end + @testset "Package Compile Code" begin + @test_nowarn begin + @capture_out begin + include( + joinpath( + pkgdir(ITensorMPS), + "ext", + "ITensorMPSPackageCompilerExt", + "precompile_itensormps.jl", + ), + ) + end + end end - end end end diff --git a/test/base/test_exports.jl b/test/base/test_exports.jl index 429b021..d4fb22f 100644 --- a/test/base/test_exports.jl +++ b/test/base/test_exports.jl @@ -3,24 +3,24 @@ using ITensorMPS: ITensorMPS include("utils/TestITensorMPSExportedNames.jl") using Test: @test, @test_broken, @testset @testset "Exports and aliases" begin - @testset "Exports" begin - # @show setdiff(names(ITensorMPS), TestITensorMPSExportedNames.ITENSORMPS_EXPORTED_NAMES) - # @show setdiff(TestITensorMPSExportedNames.ITENSORMPS_EXPORTED_NAMES, names(ITensorMPS)) - @test issetequal( - names(ITensorMPS), TestITensorMPSExportedNames.ITENSORMPS_EXPORTED_NAMES - ) - # Test the names are actually defined, if not we might need to import them - # from ITensors.jl. - for name in TestITensorMPSExportedNames.ITENSORMPS_EXPORTED_NAMES - @test isdefined(ITensorMPS, name) + @testset "Exports" begin + # @show setdiff(names(ITensorMPS), TestITensorMPSExportedNames.ITENSORMPS_EXPORTED_NAMES) + # @show setdiff(TestITensorMPSExportedNames.ITENSORMPS_EXPORTED_NAMES, names(ITensorMPS)) + @test issetequal( + names(ITensorMPS), TestITensorMPSExportedNames.ITENSORMPS_EXPORTED_NAMES + ) + # Test the names are actually defined, if not we might need to import them + # from ITensors.jl. + for name in TestITensorMPSExportedNames.ITENSORMPS_EXPORTED_NAMES + @test isdefined(ITensorMPS, name) + end end - end - @testset "Not exported" begin - for name in - [:AbstractProjMPO, :ProjMPS, :makeL!, :makeR!, :set_terms, :sortmergeterms, :terms] - @test isdefined(ITensorMPS, name) - @test !Base.isexported(ITensorMPS, name) + @testset "Not exported" begin + for name in + [:AbstractProjMPO, :ProjMPS, :makeL!, :makeR!, :set_terms, :sortmergeterms, :terms] + @test isdefined(ITensorMPS, name) + @test !Base.isexported(ITensorMPS, name) + end end - end end end diff --git a/test/base/test_fermions.jl b/test/base/test_fermions.jl index 05eed7c..e368c35 100644 --- a/test/base/test_fermions.jl +++ b/test/base/test_fermions.jl @@ -4,287 +4,287 @@ using ITensors using Test @testset "AutoFermion MPS, MPO, and OpSum" begin - ITensors.enable_auto_fermion() - - @testset "MPS Tests" begin - @testset "Product MPS consistency checks" begin - s = siteinds("Fermion", 3; conserve_qns=true) - - pA = MPS(s, [2, 1, 2]) - TA = ITensor(s[1], s[2], s[3]) - TA[s[1] => 2, s[2] => 1, s[3] => 2] = 1.0 - A = pA[1] * pA[2] * pA[3] - @test norm(A - TA) < 1E-8 - - pB = MPS(s, [1, 2, 2]) - TB = ITensor(s[1], s[2], s[3]) - TB[s[1] => 1, s[2] => 2, s[3] => 2] = 1.0 - B = pB[1] * pB[2] * pB[3] - @test norm(B - TB) < 1E-8 - end - @testset "MPS inner regression test" begin - sites = siteinds("Fermion", 3; conserve_qns=true) - psi = MPS(sites, [2, 2, 1]) - @test inner(psi, psi) ≈ 1.0 - end - @testset "Orthogonalize of Product MPS" begin - N = 3 - - sites = siteinds("Fermion", N; conserve_qns=true) - - state = [1 for n in 1:N] - state[1] = 2 - state[2] = 2 - psi = MPS(sites, state) - psi_fluxes = [flux(psi[n]) for n in 1:N] - - psi_orig = copy(psi) - orthogonalize!(psi, 1) - @test inner(psi_orig, psi) ≈ 1.0 - @test inner(psi, psi_orig) ≈ 1.0 - end - end - - @testset "Fermionic OpSum Tests" begin - @testset "Spinless Fermion Hamiltonian" begin - N = 2 - sites = siteinds("Fermion", N; conserve_qns=true) - t1 = 1.0 - os = OpSum() - for b in 1:(N - 1) - os -= t1, "Cdag", b, "C", b + 1 - os -= t1, "Cdag", b + 1, "C", b - end - H = MPO(os, sites) - - HH = H[1] - for n in 2:N - HH *= H[n] - end - HHc = dag(swapprime(HH, 0, 1)) - @test norm(HHc - HH) < 1E-8 - end - - @testset "Fermion Hamiltonian Matrix Elements" begin - N = 10 - t1 = 0.654 - V1 = 1.23 - - sites = siteinds("Fermion", N; conserve_qns=true) - - os = OpSum() - for b in 1:(N - 1) - os -= t1, "Cdag", b, "C", b + 1 - os -= t1, "Cdag", b + 1, "C", b - os += V1, "N", b, "N", b + 1 - end - H = MPO(os, sites) - - for j in 1:(N - 2) - stateA = [1 for n in 1:N] - stateA[j] = 2 - stateA[N] = 2 # to make MPS bosonic - - stateB = [1 for n in 1:N] - stateB[j + 1] = 2 - stateB[N] = 2 # to make MPS bosonic - - psiA = MPS(sites, stateA) - psiB = MPS(sites, stateB) - - @test inner(psiA', H, psiB) ≈ -t1 - @test inner(psiB', H, psiA) ≈ -t1 - end - - for j in 1:(N - 1) - state = [1 for n in 1:N] - state[j] = 2 - state[j + 1] = 2 - psi = MPS(sites, state) - @test inner(psi', H, psi) ≈ V1 - end - end - - @testset "Fermion Second Neighbor Hopping" begin - N = 4 - t1 = 1.79 - t2 = 0.427 - s = siteinds("Fermion", N; conserve_qns=true) - os = OpSum() - for n in 1:(N - 1) - os -= t1, "Cdag", n, "C", n + 1 - os -= t1, "Cdag", n + 1, "C", n - end - for n in 1:(N - 2) - os -= t2, "Cdag", n, "C", n + 2 - os -= t2, "Cdag", n + 2, "C", n - end - H = MPO(os, s) - - state1 = [1 for n in 1:N] - state1[1] = 2 - state1[4] = 2 - psi1 = MPS(s, state1) - - state2 = [1 for n in 1:N] - state2[2] = 2 - state2[4] = 2 - psi2 = MPS(s, state2) - - state3 = [1 for n in 1:N] - state3[3] = 2 - state3[4] = 2 - psi3 = MPS(s, state3) - - @test inner(psi1', H, psi2) ≈ -t1 - @test inner(psi2', H, psi1) ≈ -t1 - @test inner(psi2', H, psi3) ≈ -t1 - @test inner(psi3', H, psi2) ≈ -t1 - - @test inner(psi1', H, psi3) ≈ -t2 - @test inner(psi3', H, psi1) ≈ -t2 - - # Add stationary particle to site 2, - # hopping over should change sign: - state1[2] = 2 - psi1 = MPS(s, state1) - state3[2] = 2 - psi3 = MPS(s, state3) - @test inner(psi1', H, psi3) ≈ +t2 - @test inner(psi3', H, psi1) ≈ +t2 + ITensors.enable_auto_fermion() + + @testset "MPS Tests" begin + @testset "Product MPS consistency checks" begin + s = siteinds("Fermion", 3; conserve_qns = true) + + pA = MPS(s, [2, 1, 2]) + TA = ITensor(s[1], s[2], s[3]) + TA[s[1] => 2, s[2] => 1, s[3] => 2] = 1.0 + A = pA[1] * pA[2] * pA[3] + @test norm(A - TA) < 1.0e-8 + + pB = MPS(s, [1, 2, 2]) + TB = ITensor(s[1], s[2], s[3]) + TB[s[1] => 1, s[2] => 2, s[3] => 2] = 1.0 + B = pB[1] * pB[2] * pB[3] + @test norm(B - TB) < 1.0e-8 + end + @testset "MPS inner regression test" begin + sites = siteinds("Fermion", 3; conserve_qns = true) + psi = MPS(sites, [2, 2, 1]) + @test inner(psi, psi) ≈ 1.0 + end + @testset "Orthogonalize of Product MPS" begin + N = 3 + + sites = siteinds("Fermion", N; conserve_qns = true) + + state = [1 for n in 1:N] + state[1] = 2 + state[2] = 2 + psi = MPS(sites, state) + psi_fluxes = [flux(psi[n]) for n in 1:N] + + psi_orig = copy(psi) + orthogonalize!(psi, 1) + @test inner(psi_orig, psi) ≈ 1.0 + @test inner(psi, psi_orig) ≈ 1.0 + end end - @testset "OpSum Regression Test" begin - N = 3 - s = siteinds("Fermion", N; conserve_qns=true) - - os = OpSum() - os += "Cdag", 1, "C", 3 - @test_nowarn H = MPO(os, s) - end - end - - @testset "DMRG Tests" begin - @testset "Nearest Neighbor Fermions" begin - N = 8 - t1 = 1.0 - V1 = 4.0 - - s = siteinds("Fermion", N; conserve_qns=true) - - ost = OpSum() - osV = OpSum() - for b in 1:(N - 1) - ost -= t1, "Cdag", b, "C", b + 1 - ost -= t1, "Cdag", b + 1, "C", b - osV += V1, "N", b, "N", b + 1 - end - Ht = MPO(ost, s) - HV = MPO(osV, s) - - state = ["Emp" for n in 1:N] - for i in 1:2:N - state[i] = "Occ" - end - psi0 = MPS(s, state) - - sweeps = Sweeps(3) - maxdim!(sweeps, 20, 20, 40, 80, 200) - cutoff!(sweeps, 1E-6) - - correct_energy = -2.859778 - - energy, psi = dmrg([Ht, HV], psi0, sweeps; outputlevel=0) - @test abs(energy - correct_energy) < 1E-4 - - # Test using SVD within DMRG too: - energy, psi = dmrg([Ht, HV], psi0, sweeps; outputlevel=0, which_decomp="svd") - @test abs(energy - correct_energy) < 1E-4 - - # Test using only eigen decomp: - energy, psi = dmrg([Ht, HV], psi0, sweeps; outputlevel=0, which_decomp="eigen") - @test abs(energy - correct_energy) < 1E-4 + @testset "Fermionic OpSum Tests" begin + @testset "Spinless Fermion Hamiltonian" begin + N = 2 + sites = siteinds("Fermion", N; conserve_qns = true) + t1 = 1.0 + os = OpSum() + for b in 1:(N - 1) + os -= t1, "Cdag", b, "C", b + 1 + os -= t1, "Cdag", b + 1, "C", b + end + H = MPO(os, sites) + + HH = H[1] + for n in 2:N + HH *= H[n] + end + HHc = dag(swapprime(HH, 0, 1)) + @test norm(HHc - HH) < 1.0e-8 + end + + @testset "Fermion Hamiltonian Matrix Elements" begin + N = 10 + t1 = 0.654 + V1 = 1.23 + + sites = siteinds("Fermion", N; conserve_qns = true) + + os = OpSum() + for b in 1:(N - 1) + os -= t1, "Cdag", b, "C", b + 1 + os -= t1, "Cdag", b + 1, "C", b + os += V1, "N", b, "N", b + 1 + end + H = MPO(os, sites) + + for j in 1:(N - 2) + stateA = [1 for n in 1:N] + stateA[j] = 2 + stateA[N] = 2 # to make MPS bosonic + + stateB = [1 for n in 1:N] + stateB[j + 1] = 2 + stateB[N] = 2 # to make MPS bosonic + + psiA = MPS(sites, stateA) + psiB = MPS(sites, stateB) + + @test inner(psiA', H, psiB) ≈ -t1 + @test inner(psiB', H, psiA) ≈ -t1 + end + + for j in 1:(N - 1) + state = [1 for n in 1:N] + state[j] = 2 + state[j + 1] = 2 + psi = MPS(sites, state) + @test inner(psi', H, psi) ≈ V1 + end + end + + @testset "Fermion Second Neighbor Hopping" begin + N = 4 + t1 = 1.79 + t2 = 0.427 + s = siteinds("Fermion", N; conserve_qns = true) + os = OpSum() + for n in 1:(N - 1) + os -= t1, "Cdag", n, "C", n + 1 + os -= t1, "Cdag", n + 1, "C", n + end + for n in 1:(N - 2) + os -= t2, "Cdag", n, "C", n + 2 + os -= t2, "Cdag", n + 2, "C", n + end + H = MPO(os, s) + + state1 = [1 for n in 1:N] + state1[1] = 2 + state1[4] = 2 + psi1 = MPS(s, state1) + + state2 = [1 for n in 1:N] + state2[2] = 2 + state2[4] = 2 + psi2 = MPS(s, state2) + + state3 = [1 for n in 1:N] + state3[3] = 2 + state3[4] = 2 + psi3 = MPS(s, state3) + + @test inner(psi1', H, psi2) ≈ -t1 + @test inner(psi2', H, psi1) ≈ -t1 + @test inner(psi2', H, psi3) ≈ -t1 + @test inner(psi3', H, psi2) ≈ -t1 + + @test inner(psi1', H, psi3) ≈ -t2 + @test inner(psi3', H, psi1) ≈ -t2 + + # Add stationary particle to site 2, + # hopping over should change sign: + state1[2] = 2 + psi1 = MPS(s, state1) + state3[2] = 2 + psi3 = MPS(s, state3) + @test inner(psi1', H, psi3) ≈ +t2 + @test inner(psi3', H, psi1) ≈ +t2 + end + + @testset "OpSum Regression Test" begin + N = 3 + s = siteinds("Fermion", N; conserve_qns = true) + + os = OpSum() + os += "Cdag", 1, "C", 3 + @test_nowarn H = MPO(os, s) + end end - @testset "Further Neighbor and Correlations" begin - N = 8 - t1 = 1.0 - t2 = 0.2 - - s = siteinds("Fermion", N; conserve_qns=true) - - ost = OpSum() - for b in 1:(N - 1) - ost -= t1, "Cdag", b, "C", b + 1 - ost -= t1, "Cdag", b + 1, "C", b - end - for b in 1:(N - 2) - ost -= t2, "Cdag", b, "C", b + 2 - ost -= t2, "Cdag", b + 2, "C", b - end - Ht = MPO(ost, s) - - state = ["Emp" for n in 1:N] - for i in 1:2:N - state[i] = "Occ" - end - psi0 = MPS(s, state) - - sweeps = Sweeps(3) - maxdim!(sweeps, 20, 20, 40, 80, 200) - cutoff!(sweeps, 1E-6) - - energy, psi = dmrg(Ht, psi0, sweeps; outputlevel=0) - - energy_inner = inner(psi', Ht, psi) - - C = correlation_matrix(psi, "Cdag", "C") - C_energy = - sum(j -> -2t1 * C[j, j + 1], 1:(N - 1)) + sum(j -> -2t2 * C[j, j + 2], 1:(N - 2)) - - @test energy_inner ≈ energy - @test C_energy ≈ energy + @testset "DMRG Tests" begin + @testset "Nearest Neighbor Fermions" begin + N = 8 + t1 = 1.0 + V1 = 4.0 + + s = siteinds("Fermion", N; conserve_qns = true) + + ost = OpSum() + osV = OpSum() + for b in 1:(N - 1) + ost -= t1, "Cdag", b, "C", b + 1 + ost -= t1, "Cdag", b + 1, "C", b + osV += V1, "N", b, "N", b + 1 + end + Ht = MPO(ost, s) + HV = MPO(osV, s) + + state = ["Emp" for n in 1:N] + for i in 1:2:N + state[i] = "Occ" + end + psi0 = MPS(s, state) + + sweeps = Sweeps(3) + maxdim!(sweeps, 20, 20, 40, 80, 200) + cutoff!(sweeps, 1.0e-6) + + correct_energy = -2.859778 + + energy, psi = dmrg([Ht, HV], psi0, sweeps; outputlevel = 0) + @test abs(energy - correct_energy) < 1.0e-4 + + # Test using SVD within DMRG too: + energy, psi = dmrg([Ht, HV], psi0, sweeps; outputlevel = 0, which_decomp = "svd") + @test abs(energy - correct_energy) < 1.0e-4 + + # Test using only eigen decomp: + energy, psi = dmrg([Ht, HV], psi0, sweeps; outputlevel = 0, which_decomp = "eigen") + @test abs(energy - correct_energy) < 1.0e-4 + end + + @testset "Further Neighbor and Correlations" begin + N = 8 + t1 = 1.0 + t2 = 0.2 + + s = siteinds("Fermion", N; conserve_qns = true) + + ost = OpSum() + for b in 1:(N - 1) + ost -= t1, "Cdag", b, "C", b + 1 + ost -= t1, "Cdag", b + 1, "C", b + end + for b in 1:(N - 2) + ost -= t2, "Cdag", b, "C", b + 2 + ost -= t2, "Cdag", b + 2, "C", b + end + Ht = MPO(ost, s) + + state = ["Emp" for n in 1:N] + for i in 1:2:N + state[i] = "Occ" + end + psi0 = MPS(s, state) + + sweeps = Sweeps(3) + maxdim!(sweeps, 20, 20, 40, 80, 200) + cutoff!(sweeps, 1.0e-6) + + energy, psi = dmrg(Ht, psi0, sweeps; outputlevel = 0) + + energy_inner = inner(psi', Ht, psi) + + C = correlation_matrix(psi, "Cdag", "C") + C_energy = + sum(j -> -2t1 * C[j, j + 1], 1:(N - 1)) + sum(j -> -2t2 * C[j, j + 2], 1:(N - 2)) + + @test energy_inner ≈ energy + @test C_energy ≈ energy + end end - end - @testset "MPS gate system" begin - @testset "Fermion sites" begin - N = 3 + @testset "MPS gate system" begin + @testset "Fermion sites" begin + N = 3 - s = siteinds("Fermion", N; conserve_qns=true) + s = siteinds("Fermion", N; conserve_qns = true) - # Ground state |000⟩ - ψ000 = MPS(s, "0") + # Ground state |000⟩ + ψ000 = MPS(s, "0") - # Start state |011⟩ - ψ011 = MPS(s, n -> n == 2 || n == 3 ? "1" : "0") + # Start state |011⟩ + ψ011 = MPS(s, n -> n == 2 || n == 3 ? "1" : "0") - # Reference state |110⟩ - ψ110 = MPS(s, n -> n == 1 || n == 2 ? "1" : "0") + # Reference state |110⟩ + ψ110 = MPS(s, n -> n == 1 || n == 2 ? "1" : "0") - function ITensors.op(::OpName"CdagC3", ::SiteType, s1::Index, s2::Index) - return op("Cdag", s1) * op("C", s2) - end + function ITensors.op(::OpName"CdagC3", ::SiteType, s1::Index, s2::Index) + return op("Cdag", s1) * op("C", s2) + end - os = [("CdagC3", 1, 3)] - Os = ops(os, s) + os = [("CdagC3", 1, 3)] + Os = ops(os, s) - # Results in -|110⟩ - ψ1 = product(Os, ψ011; cutoff=1e-15) + # Results in -|110⟩ + ψ1 = product(Os, ψ011; cutoff = 1.0e-15) - @test inner(ψ1, ψ110) == -1 + @test inner(ψ1, ψ110) == -1 - os = OpSum() - os += "Cdag", 1, "C", 3 - H = MPO(os, s) + os = OpSum() + os += "Cdag", 1, "C", 3 + H = MPO(os, s) - # Results in -|110⟩ - ψ2 = noprime(contract(H, ψ011; cutoff=1e-15)) + # Results in -|110⟩ + ψ2 = noprime(contract(H, ψ011; cutoff = 1.0e-15)) - @test inner(ψ2, ψ110) == -1 + @test inner(ψ2, ψ110) == -1 + end end - end - ITensors.disable_auto_fermion() + ITensors.disable_auto_fermion() end end diff --git a/test/base/test_inference.jl b/test/base/test_inference.jl index c408db0..09e7aca 100644 --- a/test/base/test_inference.jl +++ b/test/base/test_inference.jl @@ -3,19 +3,19 @@ using ITensors.NDTensors using Test @testset "dmrg" begin - N = 10 - sites = siteinds("S=1", N) - opsum = OpSum() - for j in 1:(N - 1) - opsum += "Sz", j, "Sz", j + 1 - opsum += 0.5, "S+", j, "S-", j + 1 - opsum += 0.5, "S-", j, "S+", j + 1 - end - H = MPO(opsum, sites) - psi0 = random_mps(sites; linkdims=10) - sweeps = Sweeps(5) - setmaxdim!(sweeps, 10, 20, 100, 100, 200) - setcutoff!(sweeps, 1E-11) - @test @inferred(Tuple{Any,MPS}, dmrg(H, psi0, sweeps; outputlevel=0)) isa - Tuple{Float64,MPS} + N = 10 + sites = siteinds("S=1", N) + opsum = OpSum() + for j in 1:(N - 1) + opsum += "Sz", j, "Sz", j + 1 + opsum += 0.5, "S+", j, "S-", j + 1 + opsum += 0.5, "S-", j, "S+", j + 1 + end + H = MPO(opsum, sites) + psi0 = random_mps(sites; linkdims = 10) + sweeps = Sweeps(5) + setmaxdim!(sweeps, 10, 20, 100, 100, 200) + setcutoff!(sweeps, 1.0e-11) + @test @inferred(Tuple{Any, MPS}, dmrg(H, psi0, sweeps; outputlevel = 0)) isa + Tuple{Float64, MPS} end diff --git a/test/base/test_lattices.jl b/test/base/test_lattices.jl index 0a86ae7..0d809c8 100644 --- a/test/base/test_lattices.jl +++ b/test/base/test_lattices.jl @@ -2,13 +2,13 @@ using ITensors, Test @test LatticeBond(1, 2) == LatticeBond(1, 2, 0.0, 0.0, 0.0, 0.0, "") @testset "Square lattice" begin - sL = square_lattice(3, 4) - @test length(sL) == 17 + sL = square_lattice(3, 4) + @test length(sL) == 17 end @testset "Triangular lattice" begin - tL = triangular_lattice(3, 4) - @test length(tL) == 23 - tL = triangular_lattice(3, 4; yperiodic=true) - @test length(tL) == 28 # inc. periodic vertical bonds + tL = triangular_lattice(3, 4) + @test length(tL) == 23 + tL = triangular_lattice(3, 4; yperiodic = true) + @test length(tL) == 28 # inc. periodic vertical bonds end diff --git a/test/base/test_mpo.jl b/test/base/test_mpo.jl index f8c5eef..cc9502c 100644 --- a/test/base/test_mpo.jl +++ b/test/base/test_mpo.jl @@ -8,856 +8,856 @@ using Test include(joinpath(@__DIR__, "utils", "util.jl")) -function basicRandomMPO(sites; dim=4) - M = MPO(sites) - N = length(M) - links = [Index(dim, "n=$(n-1),Link") for n in 1:(N + 1)] - for n in 1:N - M[n] = random_itensor(links[n], sites[n], sites[n]', links[n + 1]) - end - M[1] *= delta(links[1]) - M[N] *= delta(links[N + 1]) - return M +function basicRandomMPO(sites; dim = 4) + M = MPO(sites) + N = length(M) + links = [Index(dim, "n=$(n - 1),Link") for n in 1:(N + 1)] + for n in 1:N + M[n] = random_itensor(links[n], sites[n], sites[n]', links[n + 1]) + end + M[1] *= delta(links[1]) + M[N] *= delta(links[N + 1]) + return M end @testset "[first]siteinds(::MPO)" begin - N = 5 - s = siteinds("S=1/2", N) - M = random_mpo(s) - v = siteinds(M) - for n in 1:N - @test hassameinds(v[n], (s[n], s[n]')) - end - @test firstsiteinds(M) == s + N = 5 + s = siteinds("S=1/2", N) + M = random_mpo(s) + v = siteinds(M) + for n in 1:N + @test hassameinds(v[n], (s[n], s[n]')) + end + @test firstsiteinds(M) == s end @testset "MPO Basics" begin - N = 6 - sites = [Index(2, "Site,n=$n") for n in 1:N] - @test length(MPO()) == 0 - @test length(MPO(Index{Int}[])) == 0 - O = MPO(sites) - @test length(O) == N - - str = split(sprint(show, O), '\n') - @test endswith(str[1], "MPO") - @test length(str) == length(O) + 2 - - O[1] = ITensor(sites[1], prime(sites[1])) - @test hasind(O[1], sites[1]) - @test hasind(O[1], prime(sites[1])) - P = copy(O) - @test hasind(P[1], sites[1]) - @test hasind(P[1], prime(sites[1])) - # test constructor from Vector{ITensor} - K = random_mpo(sites) - @test ITensorMPS.data(MPO(copy(ITensorMPS.data(K)))) == ITensorMPS.data(K) - - @testset "orthogonalize!" begin - phi = random_mps(sites) - K = random_mpo(sites) - orthogonalize!(phi, 1) - orthogonalize!(K, 1) - orig_inner = ⋅(phi', K, phi) - orthogonalize!(phi, div(N, 2)) - orthogonalize!(K, div(N, 2)) - @test ⋅(phi', K, phi) ≈ orig_inner - end - - @testset "norm MPO" begin - A = random_mpo(sites) - Adag = sim(linkinds, dag(A)) - A² = ITensor(1) - for j in 1:N - A² *= Adag[j] * A[j] - end - @test A²[] ≈ inner(A, A) - @test sqrt(A²[]) ≈ norm(A) - for j in 1:N - A[j] ./= j - end - reset_ortho_lims!(A) - @test norm(A) ≈ 1 / factorial(N) - end - - @testset "lognorm MPO" begin - A = random_mpo(sites) - for j in 1:N - A[j] .*= j - end - reset_ortho_lims!(A) - Adag = sim(linkinds, dag(A)) - A² = ITensor(1) - for j in 1:N - A² *= Adag[j] * A[j] - end - @test A²[] ≈ A ⋅ A - @test 0.5 * log(A²[]) ≈ lognorm(A) - @test lognorm(A) ≈ log(factorial(N)) - end - - @testset "inner " begin - phi = random_mps(sites) - K = random_mpo(sites) - @test maxlinkdim(K) == 1 - psi = random_mps(sites) - phidag = dag(phi) - prime!(phidag) - phiKpsi = phidag[1] * K[1] * psi[1] - for j in 2:N - phiKpsi *= phidag[j] * K[j] * psi[j] - end - @test phiKpsi[] ≈ inner(phi', K, psi) - - badsites = [Index(2, "Site") for n in 1:(N + 1)] - badpsi = random_mps(badsites) - @test_throws DimensionMismatch inner(phi', K, badpsi) - - # make bigger random MPO... - for link_dim in 2:5 - mpo_tensors = ITensor[ITensor() for ii in 1:N] - mps_tensors = ITensor[ITensor() for ii in 1:N] - mps_tensors2 = ITensor[ITensor() for ii in 1:N] - mpo_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:(N - 1)] - mps_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:(N - 1)] - mpo_tensors[1] = random_itensor(mpo_link_inds[1], sites[1], sites[1]') - mps_tensors[1] = random_itensor(mps_link_inds[1], sites[1]) - mps_tensors2[1] = random_itensor(mps_link_inds[1], sites[1]) - for ii in 2:(N - 1) - mpo_tensors[ii] = random_itensor( - mpo_link_inds[ii], mpo_link_inds[ii - 1], sites[ii], sites[ii]' - ) - mps_tensors[ii] = random_itensor( - mps_link_inds[ii], mps_link_inds[ii - 1], sites[ii] - ) - mps_tensors2[ii] = random_itensor( - mps_link_inds[ii], mps_link_inds[ii - 1], sites[ii] - ) - end - mpo_tensors[N] = random_itensor(mpo_link_inds[N - 1], sites[N], sites[N]') - mps_tensors[N] = random_itensor(mps_link_inds[N - 1], sites[N]) - mps_tensors2[N] = random_itensor(mps_link_inds[N - 1], sites[N]) - K = MPO(mpo_tensors, 0, N + 1) - psi = MPS(mps_tensors, 0, N + 1) - phi = MPS(mps_tensors2, 0, N + 1) - orthogonalize!(psi, 1; maxdim=link_dim) - orthogonalize!(K, 1; maxdim=link_dim) - orthogonalize!(phi, 1; normalize=true, maxdim=link_dim) - phidag = dag(phi) - prime!(phidag) - phiKpsi = phidag[1] * K[1] * psi[1] - for j in 2:N - phiKpsi *= phidag[j] * K[j] * psi[j] - end - @test scalar(phiKpsi) ≈ inner(phi', K, psi) - end - end - - @testset "loginner " begin - n = 4 - c = 2 - - s = siteinds("S=1/2", n) - ψ = c .* random_mps(s; linkdims=4) - Φ = c .* random_mps(s; linkdims=4) - K = random_mpo(s) - - @test log(complex(inner(ψ', K, Φ))) ≈ loginner(ψ', K, Φ) - end - - @testset "inner " begin - phi = makeRandomMPS(sites) - - K = makeRandomMPO(sites; chi=2) - J = makeRandomMPO(sites; chi=2) - - psi = makeRandomMPS(sites) - phidag = dag(phi) - prime!(phidag, 2) - Jdag = dag(J) - prime!(Jdag) - for j in eachindex(Jdag) - swapprime!(Jdag[j], 2, 3) - swapprime!(Jdag[j], 1, 2) - swapprime!(Jdag[j], 3, 1) - end - - phiJdagKpsi = phidag[1] * Jdag[1] * K[1] * psi[1] - for j in eachindex(psi)[2:end] - phiJdagKpsi = phiJdagKpsi * phidag[j] * Jdag[j] * K[j] * psi[j] - end - - @test phiJdagKpsi[] ≈ inner(J, phi, K, psi) - - badsites = [Index(2, "Site") for n in 1:(N + 1)] - badpsi = random_mps(badsites) - @test_throws DimensionMismatch inner(J, phi, K, badpsi) - - # generic tags and prime levels - Kgen = replacetags(K, "Site" => "OpOut"; plev=1) - noprime!(replacetags!(Kgen, "Site" => "Kpsi"; plev=0)) - ITensorMPS.sim!(siteinds, Kgen) - - Jgen = replacetags(J, "Site" => "OpOut"; plev=1) - noprime!(replacetags!(Jgen, "Site" => "Jphi"; plev=0)) - ITensorMPS.sim!(siteinds, Jgen) - # make sure operators share site indices - replaceinds!.(Jgen, siteinds(Jgen; tags="OpOut"), siteinds(Kgen; tags="OpOut")) - - # make sure states share site indices with operators - psigen = replace_siteinds(psi, siteinds(Kgen; tags="Kpsi")) - phigen = replace_siteinds(phi, siteinds(Jgen; tags="Jphi")) - - @test phiJdagKpsi[] ≈ inner(Jgen, phigen, Kgen, psigen) - - badpsigen = replacetags(psigen, "Kpsi" => "notKpsi") - badphigen = sim(siteinds, phigen) - badKgen = replacetags(Kgen, "OpOut" => "notOpOut") - badJgen = sim(siteinds, Jgen) - @test_throws ErrorException inner(Jgen, phigen, Kgen, badpsigen) - @test_throws ErrorException inner(Jgen, badphigen, Kgen, psigen) - @test_throws ErrorException inner(Jgen, phigen, badKgen, psigen) - @test_throws ErrorException inner(badJgen, phigen, Kgen, psigen) - end - - @testset "error_contract" begin - phi = makeRandomMPS(sites) - K = makeRandomMPO(sites; chi=2) - - psi = makeRandomMPS(sites) - - dist = sqrt( - abs(1 + (inner(phi, phi) - 2 * real(inner(phi', K, psi))) / inner(K, psi, K, psi)) - ) - @test dist ≈ error_contract(phi, K, psi) - - badsites = [Index(2, "Site") for n in 1:(N + 1)] - badpsi = random_mps(badsites) - # Apply K to phi and check that error_contract is close to 0. - Kphi = contract(K, phi; method="naive", cutoff=1E-8) - @test error_contract(noprime(Kphi), K, phi) ≈ 0.0 atol = 1e-4 - @test error_contract(noprime(Kphi), phi, K) ≈ 0.0 atol = 1e-4 - - @test_throws DimensionMismatch contract(K, badpsi; method="naive", cutoff=1E-8) - @test_throws DimensionMismatch error_contract(phi, K, badpsi) - end - - @testset "contract" begin - phi = random_mps(sites) + N = 6 + sites = [Index(2, "Site,n=$n") for n in 1:N] + @test length(MPO()) == 0 + @test length(MPO(Index{Int}[])) == 0 + O = MPO(sites) + @test length(O) == N + + str = split(sprint(show, O), '\n') + @test endswith(str[1], "MPO") + @test length(str) == length(O) + 2 + + O[1] = ITensor(sites[1], prime(sites[1])) + @test hasind(O[1], sites[1]) + @test hasind(O[1], prime(sites[1])) + P = copy(O) + @test hasind(P[1], sites[1]) + @test hasind(P[1], prime(sites[1])) + # test constructor from Vector{ITensor} K = random_mpo(sites) - @test maxlinkdim(K) == 1 - psi = random_mps(sites) - psi_out = contract(K, psi; maxdim=1) - @test inner(phi', psi_out) ≈ inner(phi', K, psi) - psi_out = contract(psi, K; maxdim=1) - @test inner(phi', psi_out) ≈ inner(phi', K, psi) - psi_out = psi * K - @test inner(phi', psi_out) ≈ inner(phi', K, psi) - @test_throws MethodError contract(K, psi; method="fakemethod") - - badsites = [Index(2, "Site") for n in 1:(N + 1)] - badpsi = random_mps(badsites) - @test_throws DimensionMismatch contract(K, badpsi) - - # make bigger random MPO... - for link_dim in 2:5 - mpo_tensors = ITensor[ITensor() for ii in 1:N] - mps_tensors = ITensor[ITensor() for ii in 1:N] - mps_tensors2 = ITensor[ITensor() for ii in 1:N] - mpo_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:(N - 1)] - mps_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:(N - 1)] - mpo_tensors[1] = random_itensor(mpo_link_inds[1], sites[1], sites[1]') - mps_tensors[1] = random_itensor(mps_link_inds[1], sites[1]) - mps_tensors2[1] = random_itensor(mps_link_inds[1], sites[1]) - for ii in 2:(N - 1) - mpo_tensors[ii] = random_itensor( - mpo_link_inds[ii], mpo_link_inds[ii - 1], sites[ii], sites[ii]' - ) - mps_tensors[ii] = random_itensor( - mps_link_inds[ii], mps_link_inds[ii - 1], sites[ii] - ) - mps_tensors2[ii] = random_itensor( - mps_link_inds[ii], mps_link_inds[ii - 1], sites[ii] + @test ITensorMPS.data(MPO(copy(ITensorMPS.data(K)))) == ITensorMPS.data(K) + + @testset "orthogonalize!" begin + phi = random_mps(sites) + K = random_mpo(sites) + orthogonalize!(phi, 1) + orthogonalize!(K, 1) + orig_inner = ⋅(phi', K, phi) + orthogonalize!(phi, div(N, 2)) + orthogonalize!(K, div(N, 2)) + @test ⋅(phi', K, phi) ≈ orig_inner + end + + @testset "norm MPO" begin + A = random_mpo(sites) + Adag = sim(linkinds, dag(A)) + A² = ITensor(1) + for j in 1:N + A² *= Adag[j] * A[j] + end + @test A²[] ≈ inner(A, A) + @test sqrt(A²[]) ≈ norm(A) + for j in 1:N + A[j] ./= j + end + reset_ortho_lims!(A) + @test norm(A) ≈ 1 / factorial(N) + end + + @testset "lognorm MPO" begin + A = random_mpo(sites) + for j in 1:N + A[j] .*= j + end + reset_ortho_lims!(A) + Adag = sim(linkinds, dag(A)) + A² = ITensor(1) + for j in 1:N + A² *= Adag[j] * A[j] + end + @test A²[] ≈ A ⋅ A + @test 0.5 * log(A²[]) ≈ lognorm(A) + @test lognorm(A) ≈ log(factorial(N)) + end + + @testset "inner " begin + phi = random_mps(sites) + K = random_mpo(sites) + @test maxlinkdim(K) == 1 + psi = random_mps(sites) + phidag = dag(phi) + prime!(phidag) + phiKpsi = phidag[1] * K[1] * psi[1] + for j in 2:N + phiKpsi *= phidag[j] * K[j] * psi[j] + end + @test phiKpsi[] ≈ inner(phi', K, psi) + + badsites = [Index(2, "Site") for n in 1:(N + 1)] + badpsi = random_mps(badsites) + @test_throws DimensionMismatch inner(phi', K, badpsi) + + # make bigger random MPO... + for link_dim in 2:5 + mpo_tensors = ITensor[ITensor() for ii in 1:N] + mps_tensors = ITensor[ITensor() for ii in 1:N] + mps_tensors2 = ITensor[ITensor() for ii in 1:N] + mpo_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:(N - 1)] + mps_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:(N - 1)] + mpo_tensors[1] = random_itensor(mpo_link_inds[1], sites[1], sites[1]') + mps_tensors[1] = random_itensor(mps_link_inds[1], sites[1]) + mps_tensors2[1] = random_itensor(mps_link_inds[1], sites[1]) + for ii in 2:(N - 1) + mpo_tensors[ii] = random_itensor( + mpo_link_inds[ii], mpo_link_inds[ii - 1], sites[ii], sites[ii]' + ) + mps_tensors[ii] = random_itensor( + mps_link_inds[ii], mps_link_inds[ii - 1], sites[ii] + ) + mps_tensors2[ii] = random_itensor( + mps_link_inds[ii], mps_link_inds[ii - 1], sites[ii] + ) + end + mpo_tensors[N] = random_itensor(mpo_link_inds[N - 1], sites[N], sites[N]') + mps_tensors[N] = random_itensor(mps_link_inds[N - 1], sites[N]) + mps_tensors2[N] = random_itensor(mps_link_inds[N - 1], sites[N]) + K = MPO(mpo_tensors, 0, N + 1) + psi = MPS(mps_tensors, 0, N + 1) + phi = MPS(mps_tensors2, 0, N + 1) + orthogonalize!(psi, 1; maxdim = link_dim) + orthogonalize!(K, 1; maxdim = link_dim) + orthogonalize!(phi, 1; normalize = true, maxdim = link_dim) + phidag = dag(phi) + prime!(phidag) + phiKpsi = phidag[1] * K[1] * psi[1] + for j in 2:N + phiKpsi *= phidag[j] * K[j] * psi[j] + end + @test scalar(phiKpsi) ≈ inner(phi', K, psi) + end + end + + @testset "loginner " begin + n = 4 + c = 2 + + s = siteinds("S=1/2", n) + ψ = c .* random_mps(s; linkdims = 4) + Φ = c .* random_mps(s; linkdims = 4) + K = random_mpo(s) + + @test log(complex(inner(ψ', K, Φ))) ≈ loginner(ψ', K, Φ) + end + + @testset "inner " begin + phi = makeRandomMPS(sites) + + K = makeRandomMPO(sites; chi = 2) + J = makeRandomMPO(sites; chi = 2) + + psi = makeRandomMPS(sites) + phidag = dag(phi) + prime!(phidag, 2) + Jdag = dag(J) + prime!(Jdag) + for j in eachindex(Jdag) + swapprime!(Jdag[j], 2, 3) + swapprime!(Jdag[j], 1, 2) + swapprime!(Jdag[j], 3, 1) + end + + phiJdagKpsi = phidag[1] * Jdag[1] * K[1] * psi[1] + for j in eachindex(psi)[2:end] + phiJdagKpsi = phiJdagKpsi * phidag[j] * Jdag[j] * K[j] * psi[j] + end + + @test phiJdagKpsi[] ≈ inner(J, phi, K, psi) + + badsites = [Index(2, "Site") for n in 1:(N + 1)] + badpsi = random_mps(badsites) + @test_throws DimensionMismatch inner(J, phi, K, badpsi) + + # generic tags and prime levels + Kgen = replacetags(K, "Site" => "OpOut"; plev = 1) + noprime!(replacetags!(Kgen, "Site" => "Kpsi"; plev = 0)) + ITensorMPS.sim!(siteinds, Kgen) + + Jgen = replacetags(J, "Site" => "OpOut"; plev = 1) + noprime!(replacetags!(Jgen, "Site" => "Jphi"; plev = 0)) + ITensorMPS.sim!(siteinds, Jgen) + # make sure operators share site indices + replaceinds!.(Jgen, siteinds(Jgen; tags = "OpOut"), siteinds(Kgen; tags = "OpOut")) + + # make sure states share site indices with operators + psigen = replace_siteinds(psi, siteinds(Kgen; tags = "Kpsi")) + phigen = replace_siteinds(phi, siteinds(Jgen; tags = "Jphi")) + + @test phiJdagKpsi[] ≈ inner(Jgen, phigen, Kgen, psigen) + + badpsigen = replacetags(psigen, "Kpsi" => "notKpsi") + badphigen = sim(siteinds, phigen) + badKgen = replacetags(Kgen, "OpOut" => "notOpOut") + badJgen = sim(siteinds, Jgen) + @test_throws ErrorException inner(Jgen, phigen, Kgen, badpsigen) + @test_throws ErrorException inner(Jgen, badphigen, Kgen, psigen) + @test_throws ErrorException inner(Jgen, phigen, badKgen, psigen) + @test_throws ErrorException inner(badJgen, phigen, Kgen, psigen) + end + + @testset "error_contract" begin + phi = makeRandomMPS(sites) + K = makeRandomMPO(sites; chi = 2) + + psi = makeRandomMPS(sites) + + dist = sqrt( + abs(1 + (inner(phi, phi) - 2 * real(inner(phi', K, psi))) / inner(K, psi, K, psi)) ) - end - mpo_tensors[N] = random_itensor(mpo_link_inds[N - 1], sites[N], sites[N]') - mps_tensors[N] = random_itensor(mps_link_inds[N - 1], sites[N]) - mps_tensors2[N] = random_itensor(mps_link_inds[N - 1], sites[N]) - K = MPO(mpo_tensors, 0, N + 1) - psi = MPS(mps_tensors, 0, N + 1) - phi = MPS(mps_tensors2, 0, N + 1) - orthogonalize!(psi, 1; maxdim=link_dim) - orthogonalize!(K, 1; maxdim=link_dim) - orthogonalize!(phi, 1; normalize=true, maxdim=link_dim) - psi_out = contract(deepcopy(K), deepcopy(psi); maxdim=10 * link_dim, cutoff=0.0) - @test inner(phi', psi_out) ≈ inner(phi', K, psi) - end - end - - @testset "add(::MPO, ::MPO)" begin - shsites = siteinds("S=1/2", N) - K = random_mpo(shsites) - L = random_mpo(shsites) - M = add(K, L) - @test length(M) == N - psi = random_mps(shsites) - k_psi = contract(K, psi; maxdim=1) - l_psi = contract(L, psi; maxdim=1) - @test inner(psi', k_psi + l_psi) ≈ ⋅(psi', M, psi) atol = 5e-3 - @test inner(psi', sum([k_psi, l_psi])) ≈ dot(psi', M, psi) atol = 5e-3 - for dim in 2:4 - shsites = siteinds("S=1/2", N) - K = basicRandomMPO(shsites; dim=dim) - L = basicRandomMPO(shsites; dim=dim) - M = K + L - @test length(M) == N - psi = random_mps(shsites) - k_psi = contract(K, psi) - l_psi = contract(L, psi) - @test inner(psi', k_psi + l_psi) ≈ dot(psi', M, psi) atol = 5e-3 - @test inner(psi', sum([k_psi, l_psi])) ≈ inner(psi', M, psi) atol = 5e-3 - psi = random_mps(shsites) - M = add(K, L; cutoff=1E-9) - k_psi = contract(K, psi) - l_psi = contract(L, psi) - @test inner(psi', k_psi + l_psi) ≈ inner(psi', M, psi) atol = 5e-3 - end - end - - @testset "+(::MPO, ::MPO)" begin - conserve_qns = true - s = siteinds("S=1/2", N; conserve_qns=conserve_qns) - - ops = n -> isodd(n) ? "Sz" : "Id" - H₁ = MPO(s, ops) - H₂ = MPO(s, ops) - - H = H₁ + H₂ - - @test inner(H, H) ≈ inner_add(H₁, H₂) - @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) - - α₁ = 2.2 - α₂ = 3.4 + 1.2im - - H = α₁ * H₁ + H₂ - - @test inner(H, H) ≈ inner_add((α₁, H₁), H₂) - @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) - - H = H₁ - H₂ - - @test inner(H, H) ≈ inner_add(H₁, (-1, H₂)) - @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) - - H = α₁ * H₁ - α₂ * H₂ - - @test inner(H, H) ≈ inner_add((α₁, H₁), (-α₂, H₂)) - @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) - end - - @testset "contract(::MPO, ::MPO)" begin - psi = random_mps(sites) - K = random_mpo(sites) - L = random_mpo(sites) - @test maxlinkdim(K) == 1 - @test maxlinkdim(L) == 1 - KL = contract(prime(K), L; maxdim=1) - psi_kl_out = contract(prime(K), contract(L, psi; maxdim=1); maxdim=1) - @test inner(psi'', KL, psi) ≈ inner(psi'', psi_kl_out) atol = 5e-3 - - # where both K and L have differently labelled sites - othersitesk = [Index(2, "Site,aaa") for n in 1:N] - othersitesl = [Index(2, "Site,bbb") for n in 1:N] - K = random_mpo(sites) - L = random_mpo(sites) - for ii in 1:N - replaceind!(K[ii], sites[ii]', othersitesk[ii]) - replaceind!(L[ii], sites[ii]', othersitesl[ii]) - end - KL = contract(K, L; maxdim=1) - psik = random_mps(othersitesk) - psil = random_mps(othersitesl) - psi_kl_out = contract(K, contract(L, psil; maxdim=1); maxdim=1) - @test inner(psik, KL, psil) ≈ inner(psik, psi_kl_out) atol = 5e-3 - - badsites = [Index(2, "Site") for n in 1:(N + 1)] - badL = random_mpo(badsites) - @test_throws DimensionMismatch contract(K, badL) - end - - @testset "*(::MPO, ::MPO)" begin - psi = random_mps(sites) - K = random_mpo(sites) - L = random_mpo(sites) - @test maxlinkdim(K) == 1 - @test maxlinkdim(L) == 1 - KL = *(prime(K), L; maxdim=1) - psi_kl_out = *(prime(K), *(L, psi; maxdim=1); maxdim=1) - @test ⋅(psi'', KL, psi) ≈ dot(psi'', psi_kl_out) atol = 5e-3 - - @test_throws ErrorException K * L - @test_throws ErrorException contract(K, L) - - @test replaceprime(KL, 2 => 1) ≈ apply(K, L; maxdim=1) - @test replaceprime(KL, 2 => 1) ≈ K(L; maxdim=1) - - # where both K and L have differently labelled sites - othersitesk = [Index(2, "Site,aaa") for n in 1:N] - othersitesl = [Index(2, "Site,bbb") for n in 1:N] - K = random_mpo(sites) - L = random_mpo(sites) - for ii in 1:N - replaceind!(K[ii], sites[ii]', othersitesk[ii]) - replaceind!(L[ii], sites[ii]', othersitesl[ii]) - end - KL = *(K, L; maxdim=1) - psik = random_mps(othersitesk) - psil = random_mps(othersitesl) - psi_kl_out = *(K, *(L, psil; maxdim=1); maxdim=1) - @test dot(psik, KL, psil) ≈ psik ⋅ psi_kl_out atol = 5e-3 - - badsites = [Index(2, "Site") for n in 1:(N + 1)] - badL = random_mpo(badsites) - @test_throws DimensionMismatch K * badL - end - - @testset "Multi-arg apply(::MPO...)" begin - ρ1 = (x -> outer(x', x; maxdim=4))(random_mps(sites; linkdims=2)) - ρ2 = (x -> outer(x', x; maxdim=4))(random_mps(sites; linkdims=2)) - ρ3 = (x -> outer(x', x; maxdim=4))(random_mps(sites; linkdims=2)) - @test apply(ρ1, ρ2, ρ3; cutoff=1e-8) ≈ - apply(apply(ρ1, ρ2; cutoff=1e-8), ρ3; cutoff=1e-8) - end - - sites = siteinds("S=1/2", N) - O = MPO(sites, "Sz") - @test length(O) == N # just make sure this works - - @test_throws ArgumentError random_mpo(sites, 2) - @test isnothing(linkind(MPO(fill(ITensor(), N), 0, N + 1), 1)) - - @testset "movesites $N sites" for N in 1:7 - s0 = siteinds("S=1/2", N) - ψ0 = MPO(s0, "Id") - for perm in permutations(1:N) - s = s0[perm] - ψ = random_mpo(s) - ns′ = [findsite(ψ0, i) for i in s] - @test ns′ == perm - ψ′ = movesites(ψ, 1:N .=> ns′) - for n in 1:N - @test hassameinds(siteinds(ψ0, n), siteinds(ψ′, n)) - end - @test @set_warn_order 15 prod(ψ) ≈ prod(ψ′) - end - end - - @testset "Construct MPO from ITensor" begin - N = 5 - s = siteinds("S=1/2", N) - l = [Index(3, "left_$n") for n in 1:2] - r = [Index(3, "right_$n") for n in 1:2] - - sis = [[sₙ', sₙ] for sₙ in s] - - A = random_itensor(s..., prime.(s)...) - ψ = MPO(A, sis; orthocenter=4) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (s[1], s[1]', ls[1])) - @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1])) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == 4 - @test maxlinkdim(ψ) == 16 - - A = random_itensor(s..., prime.(s)...) - ψ = MPO(A, s; orthocenter=4) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (s[1], s[1]', ls[1])) - @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1])) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == 4 - @test maxlinkdim(ψ) == 16 - - ψ0 = MPO(s, "Id") - A = prod(ψ0) - ψ = MPO(A, sis; cutoff=1e-15, orthocenter=3) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (s[1], s[1]', ls[1])) - @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1])) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == 3 - @test maxlinkdim(ψ) == 1 - - # Use matrix - @test_throws ErrorException MPO(s, [1/2 0; 0 1/2]) - @test MPO(s, _ -> [1/2 0; 0 1/2]) ≈ MPO(s, "Id") ./ 2 - - ψ0 = MPO(s, "Id") - A = prod(ψ0) - ψ = MPO(A, s; cutoff=1e-15, orthocenter=3) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (s[1], s[1]', ls[1])) - @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1])) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == 3 - @test maxlinkdim(ψ) == 1 - - A = random_itensor(s..., prime.(s)..., l[1], r[1]) - ψ = MPO(A, sis; leftinds=l[1]) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (l[1], s[1], s[1]', ls[1])) - @test hassameinds(ψ[N], (r[1], s[N], s[N]', ls[N - 1])) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == N - @test maxlinkdim(ψ) == 48 - - A = random_itensor(s..., prime.(s)..., l[1], r[1]) - ψ = MPO(A, s; leftinds=l[1]) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (l[1], s[1], s[1]', ls[1])) - @test hassameinds(ψ[N], (r[1], s[N], s[N]', ls[N - 1])) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == N - @test maxlinkdim(ψ) == 48 - - A = random_itensor(s..., prime.(s)..., l..., r...) - ψ = MPO(A, sis; leftinds=l, orthocenter=2) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (l..., s[1], s[1]', ls[1])) - @test hassameinds(ψ[N], (r..., s[N], s[N]', ls[N - 1])) - @test @set_warn_order 15 prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == 2 - @test maxlinkdim(ψ) == 144 - - A = random_itensor(s..., prime.(s)..., l..., r...) - ψ = MPO(A, s; leftinds=l, orthocenter=2) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (l..., s[1], s[1]', ls[1])) - @test hassameinds(ψ[N], (r..., s[N], s[N]', ls[N - 1])) - @test @set_warn_order 15 prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == 2 - @test maxlinkdim(ψ) == 144 - end - - @testset "Set range of MPO tensors" begin - N = 5 - s = siteinds("S=1/2", N) - ψ0 = random_mpo(s) - - ψ = orthogonalize(ψ0, 2) - A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) - randn!(A) - ϕ = MPO(A, s[2:(N - 1)]; orthocenter=1) - ψ[2:(N - 1)] = ϕ - @test prod(ψ) ≈ ψ[1] * A * ψ[N] - @test maxlinkdim(ψ) == 4 - @test ITensorMPS.orthocenter(ψ) == 2 - - ψ = orthogonalize(ψ0, 1) - A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) - randn!(A) - @test_throws AssertionError ψ[2:(N - 1)] = A - - ψ = orthogonalize(ψ0, 2) - A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) - randn!(A) - ψ[2:(N - 1), orthocenter = 3] = A - @test prod(ψ) ≈ ψ[1] * A * ψ[N] - @test maxlinkdim(ψ) == 4 - @test ITensorMPS.orthocenter(ψ) == 3 - end - - @testset "swapbondsites MPO" begin - N = 5 + @test dist ≈ error_contract(phi, K, psi) + + badsites = [Index(2, "Site") for n in 1:(N + 1)] + badpsi = random_mps(badsites) + # Apply K to phi and check that error_contract is close to 0. + Kphi = contract(K, phi; method = "naive", cutoff = 1.0e-8) + @test error_contract(noprime(Kphi), K, phi) ≈ 0.0 atol = 1.0e-4 + @test error_contract(noprime(Kphi), phi, K) ≈ 0.0 atol = 1.0e-4 + + @test_throws DimensionMismatch contract(K, badpsi; method = "naive", cutoff = 1.0e-8) + @test_throws DimensionMismatch error_contract(phi, K, badpsi) + end + + @testset "contract" begin + phi = random_mps(sites) + K = random_mpo(sites) + @test maxlinkdim(K) == 1 + psi = random_mps(sites) + psi_out = contract(K, psi; maxdim = 1) + @test inner(phi', psi_out) ≈ inner(phi', K, psi) + psi_out = contract(psi, K; maxdim = 1) + @test inner(phi', psi_out) ≈ inner(phi', K, psi) + psi_out = psi * K + @test inner(phi', psi_out) ≈ inner(phi', K, psi) + @test_throws MethodError contract(K, psi; method = "fakemethod") + + badsites = [Index(2, "Site") for n in 1:(N + 1)] + badpsi = random_mps(badsites) + @test_throws DimensionMismatch contract(K, badpsi) + + # make bigger random MPO... + for link_dim in 2:5 + mpo_tensors = ITensor[ITensor() for ii in 1:N] + mps_tensors = ITensor[ITensor() for ii in 1:N] + mps_tensors2 = ITensor[ITensor() for ii in 1:N] + mpo_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:(N - 1)] + mps_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:(N - 1)] + mpo_tensors[1] = random_itensor(mpo_link_inds[1], sites[1], sites[1]') + mps_tensors[1] = random_itensor(mps_link_inds[1], sites[1]) + mps_tensors2[1] = random_itensor(mps_link_inds[1], sites[1]) + for ii in 2:(N - 1) + mpo_tensors[ii] = random_itensor( + mpo_link_inds[ii], mpo_link_inds[ii - 1], sites[ii], sites[ii]' + ) + mps_tensors[ii] = random_itensor( + mps_link_inds[ii], mps_link_inds[ii - 1], sites[ii] + ) + mps_tensors2[ii] = random_itensor( + mps_link_inds[ii], mps_link_inds[ii - 1], sites[ii] + ) + end + mpo_tensors[N] = random_itensor(mpo_link_inds[N - 1], sites[N], sites[N]') + mps_tensors[N] = random_itensor(mps_link_inds[N - 1], sites[N]) + mps_tensors2[N] = random_itensor(mps_link_inds[N - 1], sites[N]) + K = MPO(mpo_tensors, 0, N + 1) + psi = MPS(mps_tensors, 0, N + 1) + phi = MPS(mps_tensors2, 0, N + 1) + orthogonalize!(psi, 1; maxdim = link_dim) + orthogonalize!(K, 1; maxdim = link_dim) + orthogonalize!(phi, 1; normalize = true, maxdim = link_dim) + psi_out = contract(deepcopy(K), deepcopy(psi); maxdim = 10 * link_dim, cutoff = 0.0) + @test inner(phi', psi_out) ≈ inner(phi', K, psi) + end + end + + @testset "add(::MPO, ::MPO)" begin + shsites = siteinds("S=1/2", N) + K = random_mpo(shsites) + L = random_mpo(shsites) + M = add(K, L) + @test length(M) == N + psi = random_mps(shsites) + k_psi = contract(K, psi; maxdim = 1) + l_psi = contract(L, psi; maxdim = 1) + @test inner(psi', k_psi + l_psi) ≈ ⋅(psi', M, psi) atol = 5.0e-3 + @test inner(psi', sum([k_psi, l_psi])) ≈ dot(psi', M, psi) atol = 5.0e-3 + for dim in 2:4 + shsites = siteinds("S=1/2", N) + K = basicRandomMPO(shsites; dim = dim) + L = basicRandomMPO(shsites; dim = dim) + M = K + L + @test length(M) == N + psi = random_mps(shsites) + k_psi = contract(K, psi) + l_psi = contract(L, psi) + @test inner(psi', k_psi + l_psi) ≈ dot(psi', M, psi) atol = 5.0e-3 + @test inner(psi', sum([k_psi, l_psi])) ≈ inner(psi', M, psi) atol = 5.0e-3 + psi = random_mps(shsites) + M = add(K, L; cutoff = 1.0e-9) + k_psi = contract(K, psi) + l_psi = contract(L, psi) + @test inner(psi', k_psi + l_psi) ≈ inner(psi', M, psi) atol = 5.0e-3 + end + end + + @testset "+(::MPO, ::MPO)" begin + conserve_qns = true + s = siteinds("S=1/2", N; conserve_qns = conserve_qns) + + ops = n -> isodd(n) ? "Sz" : "Id" + H₁ = MPO(s, ops) + H₂ = MPO(s, ops) + + H = H₁ + H₂ + + @test inner(H, H) ≈ inner_add(H₁, H₂) + @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) + + α₁ = 2.2 + α₂ = 3.4 + 1.2im + + H = α₁ * H₁ + H₂ + + @test inner(H, H) ≈ inner_add((α₁, H₁), H₂) + @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) + + H = H₁ - H₂ + + @test inner(H, H) ≈ inner_add(H₁, (-1, H₂)) + @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) + + H = α₁ * H₁ - α₂ * H₂ + + @test inner(H, H) ≈ inner_add((α₁, H₁), (-α₂, H₂)) + @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) + end + + @testset "contract(::MPO, ::MPO)" begin + psi = random_mps(sites) + K = random_mpo(sites) + L = random_mpo(sites) + @test maxlinkdim(K) == 1 + @test maxlinkdim(L) == 1 + KL = contract(prime(K), L; maxdim = 1) + psi_kl_out = contract(prime(K), contract(L, psi; maxdim = 1); maxdim = 1) + @test inner(psi'', KL, psi) ≈ inner(psi'', psi_kl_out) atol = 5.0e-3 + + # where both K and L have differently labelled sites + othersitesk = [Index(2, "Site,aaa") for n in 1:N] + othersitesl = [Index(2, "Site,bbb") for n in 1:N] + K = random_mpo(sites) + L = random_mpo(sites) + for ii in 1:N + replaceind!(K[ii], sites[ii]', othersitesk[ii]) + replaceind!(L[ii], sites[ii]', othersitesl[ii]) + end + KL = contract(K, L; maxdim = 1) + psik = random_mps(othersitesk) + psil = random_mps(othersitesl) + psi_kl_out = contract(K, contract(L, psil; maxdim = 1); maxdim = 1) + @test inner(psik, KL, psil) ≈ inner(psik, psi_kl_out) atol = 5.0e-3 + + badsites = [Index(2, "Site") for n in 1:(N + 1)] + badL = random_mpo(badsites) + @test_throws DimensionMismatch contract(K, badL) + end + + @testset "*(::MPO, ::MPO)" begin + psi = random_mps(sites) + K = random_mpo(sites) + L = random_mpo(sites) + @test maxlinkdim(K) == 1 + @test maxlinkdim(L) == 1 + KL = *(prime(K), L; maxdim = 1) + psi_kl_out = *(prime(K), *(L, psi; maxdim = 1); maxdim = 1) + @test ⋅(psi'', KL, psi) ≈ dot(psi'', psi_kl_out) atol = 5.0e-3 + + @test_throws ErrorException K * L + @test_throws ErrorException contract(K, L) + + @test replaceprime(KL, 2 => 1) ≈ apply(K, L; maxdim = 1) + @test replaceprime(KL, 2 => 1) ≈ K(L; maxdim = 1) + + # where both K and L have differently labelled sites + othersitesk = [Index(2, "Site,aaa") for n in 1:N] + othersitesl = [Index(2, "Site,bbb") for n in 1:N] + K = random_mpo(sites) + L = random_mpo(sites) + for ii in 1:N + replaceind!(K[ii], sites[ii]', othersitesk[ii]) + replaceind!(L[ii], sites[ii]', othersitesl[ii]) + end + KL = *(K, L; maxdim = 1) + psik = random_mps(othersitesk) + psil = random_mps(othersitesl) + psi_kl_out = *(K, *(L, psil; maxdim = 1); maxdim = 1) + @test dot(psik, KL, psil) ≈ psik ⋅ psi_kl_out atol = 5.0e-3 + + badsites = [Index(2, "Site") for n in 1:(N + 1)] + badL = random_mpo(badsites) + @test_throws DimensionMismatch K * badL + end + + @testset "Multi-arg apply(::MPO...)" begin + ρ1 = (x -> outer(x', x; maxdim = 4))(random_mps(sites; linkdims = 2)) + ρ2 = (x -> outer(x', x; maxdim = 4))(random_mps(sites; linkdims = 2)) + ρ3 = (x -> outer(x', x; maxdim = 4))(random_mps(sites; linkdims = 2)) + @test apply(ρ1, ρ2, ρ3; cutoff = 1.0e-8) ≈ + apply(apply(ρ1, ρ2; cutoff = 1.0e-8), ρ3; cutoff = 1.0e-8) + end + sites = siteinds("S=1/2", N) - ψ0 = random_mpo(sites) - - # TODO: implement this? - #ψ = replacebond(ψ0, 3, ψ0[3] * ψ0[4]; - # swapsites = true, - # cutoff = 1e-15) - #@test siteind(ψ, 1) == siteind(ψ0, 1) - #@test siteind(ψ, 2) == siteind(ψ0, 2) - #@test siteind(ψ, 4) == siteind(ψ0, 3) - #@test siteind(ψ, 3) == siteind(ψ0, 4) - #@test siteind(ψ, 5) == siteind(ψ0, 5) - #@test prod(ψ) ≈ prod(ψ0) - #@test maxlinkdim(ψ) == 1 - - ψ = swapbondsites(ψ0, 4; cutoff=1e-15) - @test siteind(ψ, 1) == siteind(ψ0, 1) - @test siteind(ψ, 2) == siteind(ψ0, 2) - @test siteind(ψ, 3) == siteind(ψ0, 3) - @test siteind(ψ, 5) == siteind(ψ0, 4) - @test siteind(ψ, 4) == siteind(ψ0, 5) - @test prod(ψ) ≈ prod(ψ0) - @test maxlinkdim(ψ) == 1 - end - - @testset "MPO(::MPS)" begin - i = Index(QN(0, 2) => 1, QN(1, 2) => 1; tags="i") - j = settags(i, "j") - A = random_itensor(ComplexF64, i, j) - M = A' * dag(A) - ψ = MPS(A, [i, j]) - @test prod(ψ) ≈ A - ρ = outer(ψ', ψ) - @test prod(ρ) ≈ M - ρ = projector(ψ; normalize=false) - @test prod(ρ) ≈ M - # Deprecated syntax - ρ = @test_deprecated MPO(ψ) - @test prod(ρ) ≈ M - end - - @testset "outer(::MPS, ::MPS) and projector(::MPS)" begin - N = 40 - s = siteinds("S=1/2", N; conserve_qns=true) - state(n) = isodd(n) ? "Up" : "Dn" - χψ = 3 - ψ = random_mps(ComplexF64, s, state; linkdims=χψ) - χϕ = 4 - ϕ = random_mps(ComplexF64, s, state; linkdims=χϕ) - - ψ[only(ortho_lims(ψ))] *= 2 - - Pψ = projector(ψ; normalize=false, cutoff=1e-8) - Pψᴴ = swapprime(dag(Pψ), 0 => 1) - @test maxlinkdim(Pψ) == χψ^2 - @test sqrt(inner(Pψ, Pψ) + inner(ψ, ψ)^2 - inner(ψ', Pψ, ψ) - inner(ψ', Pψᴴ, ψ)) / - abs(inner(ψ, ψ)) ≈ 0 atol = 1e-5 * N - - normψ = norm(ψ) - Pψ = projector(ψ; cutoff=1e-8) - Pψᴴ = swapprime(dag(Pψ), 0 => 1) - @test maxlinkdim(Pψ) == χψ^2 - @test sqrt( - inner(Pψ, Pψ) * normψ^4 + inner(ψ, ψ)^2 - inner(ψ', Pψ, ψ) * normψ^2 - - inner(ψ', Pψᴴ, ψ) * normψ^2, - ) / abs(inner(ψ, ψ)) ≈ 0 atol = 1e-5 * N - - ψϕ = outer(ψ', ϕ; cutoff=1e-8) - ϕψ = swapprime(dag(ψϕ), 0 => 1) - @test maxlinkdim(ψϕ) == χψ * χϕ - @test sqrt( - inner(ψϕ, ψϕ) + inner(ψ, ψ) * inner(ϕ, ϕ) - inner(ψ', ψϕ, ϕ) - inner(ϕ', ϕψ, ψ) - ) / sqrt(inner(ψ, ψ) * inner(ϕ, ϕ)) ≈ 0 atol = 1e-5 * N - end - - @testset "tr(::MPO)" begin - N = 5 - s = siteinds("S=1/2", N) - H = MPO(s, "Id") - d = dim(s[1]) - @test tr(H) ≈ d^N - end + O = MPO(sites, "Sz") + @test length(O) == N # just make sure this works + + @test_throws ArgumentError random_mpo(sites, 2) + @test isnothing(linkind(MPO(fill(ITensor(), N), 0, N + 1), 1)) + + @testset "movesites $N sites" for N in 1:7 + s0 = siteinds("S=1/2", N) + ψ0 = MPO(s0, "Id") + for perm in permutations(1:N) + s = s0[perm] + ψ = random_mpo(s) + ns′ = [findsite(ψ0, i) for i in s] + @test ns′ == perm + ψ′ = movesites(ψ, 1:N .=> ns′) + for n in 1:N + @test hassameinds(siteinds(ψ0, n), siteinds(ψ′, n)) + end + @test @set_warn_order 15 prod(ψ) ≈ prod(ψ′) + end + end - @testset "tr(::MPO) multiple site indices" begin - N = 6 - s = siteinds("S=1/2", N) - H = MPO(s, "Id") - H2 = MPO([H[j] * H[j + 1] for j in 1:2:(N - 1)]) - d = dim(s[1]) - @test tr(H) ≈ d^N - @test tr(H2) ≈ d^N - end - - @testset "check_hascommonsiteinds checks in DMRG, inner, dot" begin - N = 4 - s1 = siteinds("S=1/2", N) - s2 = siteinds("S=1/2", N) - psi1 = random_mps(s1) - psi2 = random_mps(s2) - H1 = MPO(OpSum() + ("Id", 1), s1) - H2 = MPO(OpSum() + ("Id", 1), s2) - - @test_throws ErrorException inner(psi1, H2, psi1) - @test_throws ErrorException inner(psi1, H2, psi2; make_inds_match=false) - - sweeps = Sweeps(1) - maxdim!(sweeps, 10) - - @test_throws ErrorException dmrg(H2, psi1, sweeps) - @test_throws ErrorException dmrg(H1, [psi2], psi1, sweeps) - @test_throws ErrorException dmrg([H1, H2], psi1, sweeps) - end - - @testset "unsupported kwarg in dot, logdot" begin - N = 6 - sites = [Index(2, "Site,n=$n") for n in 1:N] - K = random_mpo(sites) - L = random_mpo(sites) - @test_throws ErrorException dot(K, L, make_inds_match=true) - @test_throws ErrorException logdot(K, L, make_inds_match=true) - end + @testset "Construct MPO from ITensor" begin + N = 5 + s = siteinds("S=1/2", N) + l = [Index(3, "left_$n") for n in 1:2] + r = [Index(3, "right_$n") for n in 1:2] + + sis = [[sₙ', sₙ] for sₙ in s] + + A = random_itensor(s..., prime.(s)...) + ψ = MPO(A, sis; orthocenter = 4) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (s[1], s[1]', ls[1])) + @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1])) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == 4 + @test maxlinkdim(ψ) == 16 + + A = random_itensor(s..., prime.(s)...) + ψ = MPO(A, s; orthocenter = 4) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (s[1], s[1]', ls[1])) + @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1])) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == 4 + @test maxlinkdim(ψ) == 16 + + ψ0 = MPO(s, "Id") + A = prod(ψ0) + ψ = MPO(A, sis; cutoff = 1.0e-15, orthocenter = 3) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (s[1], s[1]', ls[1])) + @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1])) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == 3 + @test maxlinkdim(ψ) == 1 + + # Use matrix + @test_throws ErrorException MPO(s, [1 / 2 0; 0 1 / 2]) + @test MPO(s, _ -> [1 / 2 0; 0 1 / 2]) ≈ MPO(s, "Id") ./ 2 + + ψ0 = MPO(s, "Id") + A = prod(ψ0) + ψ = MPO(A, s; cutoff = 1.0e-15, orthocenter = 3) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (s[1], s[1]', ls[1])) + @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1])) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == 3 + @test maxlinkdim(ψ) == 1 + + A = random_itensor(s..., prime.(s)..., l[1], r[1]) + ψ = MPO(A, sis; leftinds = l[1]) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (l[1], s[1], s[1]', ls[1])) + @test hassameinds(ψ[N], (r[1], s[N], s[N]', ls[N - 1])) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == N + @test maxlinkdim(ψ) == 48 + + A = random_itensor(s..., prime.(s)..., l[1], r[1]) + ψ = MPO(A, s; leftinds = l[1]) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (l[1], s[1], s[1]', ls[1])) + @test hassameinds(ψ[N], (r[1], s[N], s[N]', ls[N - 1])) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == N + @test maxlinkdim(ψ) == 48 + + A = random_itensor(s..., prime.(s)..., l..., r...) + ψ = MPO(A, sis; leftinds = l, orthocenter = 2) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (l..., s[1], s[1]', ls[1])) + @test hassameinds(ψ[N], (r..., s[N], s[N]', ls[N - 1])) + @test @set_warn_order 15 prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == 2 + @test maxlinkdim(ψ) == 144 + + A = random_itensor(s..., prime.(s)..., l..., r...) + ψ = MPO(A, s; leftinds = l, orthocenter = 2) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (l..., s[1], s[1]', ls[1])) + @test hassameinds(ψ[N], (r..., s[N], s[N]', ls[N - 1])) + @test @set_warn_order 15 prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == 2 + @test maxlinkdim(ψ) == 144 + end - @testset "MPO*MPO contraction with multiple site indices" begin - N = 8 - s = siteinds("S=1/2", N) - a = OpSum() - for j in 1:(N - 1) - a .+= 0.5, "S+", j, "S-", j + 1 - a .+= 0.5, "S-", j, "S+", j + 1 - a .+= "Sz", j, "Sz", j + 1 - end - H = MPO(a, s) - # Create MPO/MPS with pairs of sites merged - H2 = MPO([H[b] * H[b + 1] for b in 1:2:N]) - @test @disable_warn_order prod(H) ≈ prod(H2) - HH = H' * H - H2H2 = H2' * H2 - @test @disable_warn_order prod(HH) ≈ prod(H2H2) - end - - @testset "MPO*MPO contraction with multiple and combined site indices" begin - N = 8 - s = siteinds("S=1/2", N) - a = OpSum() - for j in 1:(N - 1) - a .+= 0.5, "S+", j, "S-", j + 1 - a .+= 0.5, "S-", j, "S+", j + 1 - a .+= "Sz", j, "Sz", j + 1 - end - H = MPO(a, s) - HH = setprime(H' * H, 1; plev=2) - - # Create MPO/MPS with pairs of sites merged - H2 = MPO([H[b] * H[b + 1] for b in 1:2:N]) - @test @disable_warn_order prod(H) ≈ prod(H2) - s = siteinds(H2; plev=1) - C = combiner.(s; tags="X") - H2 .*= C - H2H2 = prime(H2; tags=(!ts"X")) * dag(H2) - @test @disable_warn_order prod(HH) ≈ prod(H2H2) - end - - @testset "Bond dimensions of MPO*MPS in default contract" begin - N = 8 - chi1 = 6 - chi2 = 2 - s = siteinds(2, N) - - A = begin - l = [Index(chi1, "n=$n,Link") for n in 1:N] - M = MPO(N) - M[1] = random_itensor(dag(s[1]), l[1], s'[1]) - for n in 2:(N - 1) - M[n] = random_itensor(dag(s[n]), dag(l[n - 1]), l[n], s'[n]) - end - M[N] = random_itensor(dag(s[N]), dag(l[N - 1]), s'[N]) - nrm = inner(M, M) - for n in 1:N - M[n] ./= (nrm)^(1 / (2N)) - end - truncate!(M; cutoff=1E-10) - M - end - - psi = random_mps(s; linkdims=chi2) - - Apsi = contract(A, psi) - - dims = linkdims(Apsi) - for d in dims - @test d <= chi1 * chi2 - end - - @test apply(A, psi) ≈ noprime(Apsi) - @test ITensors.materialize(Apply(A, psi)) ≈ noprime(Apsi) - @test A(psi) ≈ noprime(Apsi) - @test inner(noprime(Apsi), Apply(A, psi)) ≈ inner(Apsi, Apsi) - end - - @testset "Other MPO contract algorithms" begin - # Regression test - ensure that output of "naive" algorithm is an - # MPO not an MPS - N = 8 - s = siteinds(2, N) - A = random_mpo(s) - B = random_mpo(s) - C = apply(A, B; alg="naive") - @test C isa MPO - end - - @testset "MPO with no link indices" for conserve_qns in [false, true] - s = siteinds("S=1/2", 4; conserve_qns) - H = MPO([op("Id", sn) for sn in s]) - @test linkinds(H) == fill(nothing, length(s) - 1) - @test norm(H) == √(2^length(s)) - - Hortho = orthogonalize(H, 1) - @test Hortho ≈ H - @test linkdims(Hortho) == fill(1, length(s) - 1) - - Htrunc = truncate(H; cutoff=1e-8) - @test Htrunc ≈ H - @test linkdims(Htrunc) == fill(1, length(s) - 1) - - H² = apply(H, H; cutoff=1e-8) - H̃² = MPO([apply(H[n], H[n]) for n in 1:length(s)]) - @test linkdims(H²) == fill(1, length(s) - 1) - @test H² ≈ H̃² - - e, ψ = dmrg(H, random_mps(s, n -> isodd(n) ? "↑" : "↓"); nsweeps=2, outputlevel=0) - @test e ≈ 1 - end - - @testset "consistent precision of apply" for T in - (Float32, Float64, ComplexF32, ComplexF64) - sites = siteinds("S=1/2", 4) - A = randn(T) * convert_leaf_eltype(T, random_mpo(sites)) - B = randn(T) * convert_leaf_eltype(T, random_mpo(sites)) - @test scalartype(apply(A, B)) == T - end - @testset "sample" begin - N = 6 - sites = [Index(2, "Site,n=$n") for n in 1:N] - seed = 623 - rng = StableRNG(seed) - K = random_mps(rng, sites) - L = MPO(K) - result = sample(rng, L) - @test result ≈ [1, 1, 2, 1, 1, 1] - end + @testset "Set range of MPO tensors" begin + N = 5 + s = siteinds("S=1/2", N) + ψ0 = random_mpo(s) + + ψ = orthogonalize(ψ0, 2) + A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) + randn!(A) + ϕ = MPO(A, s[2:(N - 1)]; orthocenter = 1) + ψ[2:(N - 1)] = ϕ + @test prod(ψ) ≈ ψ[1] * A * ψ[N] + @test maxlinkdim(ψ) == 4 + @test ITensorMPS.orthocenter(ψ) == 2 + + ψ = orthogonalize(ψ0, 1) + A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) + randn!(A) + @test_throws AssertionError ψ[2:(N - 1)] = A + + ψ = orthogonalize(ψ0, 2) + A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) + randn!(A) + ψ[2:(N - 1), orthocenter = 3] = A + @test prod(ψ) ≈ ψ[1] * A * ψ[N] + @test maxlinkdim(ψ) == 4 + @test ITensorMPS.orthocenter(ψ) == 3 + end + + @testset "swapbondsites MPO" begin + N = 5 + sites = siteinds("S=1/2", N) + ψ0 = random_mpo(sites) + + # TODO: implement this? + #ψ = replacebond(ψ0, 3, ψ0[3] * ψ0[4]; + # swapsites = true, + # cutoff = 1e-15) + #@test siteind(ψ, 1) == siteind(ψ0, 1) + #@test siteind(ψ, 2) == siteind(ψ0, 2) + #@test siteind(ψ, 4) == siteind(ψ0, 3) + #@test siteind(ψ, 3) == siteind(ψ0, 4) + #@test siteind(ψ, 5) == siteind(ψ0, 5) + #@test prod(ψ) ≈ prod(ψ0) + #@test maxlinkdim(ψ) == 1 + + ψ = swapbondsites(ψ0, 4; cutoff = 1.0e-15) + @test siteind(ψ, 1) == siteind(ψ0, 1) + @test siteind(ψ, 2) == siteind(ψ0, 2) + @test siteind(ψ, 3) == siteind(ψ0, 3) + @test siteind(ψ, 5) == siteind(ψ0, 4) + @test siteind(ψ, 4) == siteind(ψ0, 5) + @test prod(ψ) ≈ prod(ψ0) + @test maxlinkdim(ψ) == 1 + end + + @testset "MPO(::MPS)" begin + i = Index(QN(0, 2) => 1, QN(1, 2) => 1; tags = "i") + j = settags(i, "j") + A = random_itensor(ComplexF64, i, j) + M = A' * dag(A) + ψ = MPS(A, [i, j]) + @test prod(ψ) ≈ A + ρ = outer(ψ', ψ) + @test prod(ρ) ≈ M + ρ = projector(ψ; normalize = false) + @test prod(ρ) ≈ M + # Deprecated syntax + ρ = @test_deprecated MPO(ψ) + @test prod(ρ) ≈ M + end + + @testset "outer(::MPS, ::MPS) and projector(::MPS)" begin + N = 40 + s = siteinds("S=1/2", N; conserve_qns = true) + state(n) = isodd(n) ? "Up" : "Dn" + χψ = 3 + ψ = random_mps(ComplexF64, s, state; linkdims = χψ) + χϕ = 4 + ϕ = random_mps(ComplexF64, s, state; linkdims = χϕ) + + ψ[only(ortho_lims(ψ))] *= 2 + + Pψ = projector(ψ; normalize = false, cutoff = 1.0e-8) + Pψᴴ = swapprime(dag(Pψ), 0 => 1) + @test maxlinkdim(Pψ) == χψ^2 + @test sqrt(inner(Pψ, Pψ) + inner(ψ, ψ)^2 - inner(ψ', Pψ, ψ) - inner(ψ', Pψᴴ, ψ)) / + abs(inner(ψ, ψ)) ≈ 0 atol = 1.0e-5 * N + + normψ = norm(ψ) + Pψ = projector(ψ; cutoff = 1.0e-8) + Pψᴴ = swapprime(dag(Pψ), 0 => 1) + @test maxlinkdim(Pψ) == χψ^2 + @test sqrt( + inner(Pψ, Pψ) * normψ^4 + inner(ψ, ψ)^2 - inner(ψ', Pψ, ψ) * normψ^2 - + inner(ψ', Pψᴴ, ψ) * normψ^2, + ) / abs(inner(ψ, ψ)) ≈ 0 atol = 1.0e-5 * N + + ψϕ = outer(ψ', ϕ; cutoff = 1.0e-8) + ϕψ = swapprime(dag(ψϕ), 0 => 1) + @test maxlinkdim(ψϕ) == χψ * χϕ + @test sqrt( + inner(ψϕ, ψϕ) + inner(ψ, ψ) * inner(ϕ, ϕ) - inner(ψ', ψϕ, ϕ) - inner(ϕ', ϕψ, ψ) + ) / sqrt(inner(ψ, ψ) * inner(ϕ, ϕ)) ≈ 0 atol = 1.0e-5 * N + end + + @testset "tr(::MPO)" begin + N = 5 + s = siteinds("S=1/2", N) + H = MPO(s, "Id") + d = dim(s[1]) + @test tr(H) ≈ d^N + end + + @testset "tr(::MPO) multiple site indices" begin + N = 6 + s = siteinds("S=1/2", N) + H = MPO(s, "Id") + H2 = MPO([H[j] * H[j + 1] for j in 1:2:(N - 1)]) + d = dim(s[1]) + @test tr(H) ≈ d^N + @test tr(H2) ≈ d^N + end + + @testset "check_hascommonsiteinds checks in DMRG, inner, dot" begin + N = 4 + s1 = siteinds("S=1/2", N) + s2 = siteinds("S=1/2", N) + psi1 = random_mps(s1) + psi2 = random_mps(s2) + H1 = MPO(OpSum() + ("Id", 1), s1) + H2 = MPO(OpSum() + ("Id", 1), s2) + + @test_throws ErrorException inner(psi1, H2, psi1) + @test_throws ErrorException inner(psi1, H2, psi2; make_inds_match = false) + + sweeps = Sweeps(1) + maxdim!(sweeps, 10) + + @test_throws ErrorException dmrg(H2, psi1, sweeps) + @test_throws ErrorException dmrg(H1, [psi2], psi1, sweeps) + @test_throws ErrorException dmrg([H1, H2], psi1, sweeps) + end + + @testset "unsupported kwarg in dot, logdot" begin + N = 6 + sites = [Index(2, "Site,n=$n") for n in 1:N] + K = random_mpo(sites) + L = random_mpo(sites) + @test_throws ErrorException dot(K, L, make_inds_match = true) + @test_throws ErrorException logdot(K, L, make_inds_match = true) + end + + @testset "MPO*MPO contraction with multiple site indices" begin + N = 8 + s = siteinds("S=1/2", N) + a = OpSum() + for j in 1:(N - 1) + a .+= 0.5, "S+", j, "S-", j + 1 + a .+= 0.5, "S-", j, "S+", j + 1 + a .+= "Sz", j, "Sz", j + 1 + end + H = MPO(a, s) + # Create MPO/MPS with pairs of sites merged + H2 = MPO([H[b] * H[b + 1] for b in 1:2:N]) + @test @disable_warn_order prod(H) ≈ prod(H2) + HH = H' * H + H2H2 = H2' * H2 + @test @disable_warn_order prod(HH) ≈ prod(H2H2) + end + + @testset "MPO*MPO contraction with multiple and combined site indices" begin + N = 8 + s = siteinds("S=1/2", N) + a = OpSum() + for j in 1:(N - 1) + a .+= 0.5, "S+", j, "S-", j + 1 + a .+= 0.5, "S-", j, "S+", j + 1 + a .+= "Sz", j, "Sz", j + 1 + end + H = MPO(a, s) + HH = setprime(H' * H, 1; plev = 2) + + # Create MPO/MPS with pairs of sites merged + H2 = MPO([H[b] * H[b + 1] for b in 1:2:N]) + @test @disable_warn_order prod(H) ≈ prod(H2) + s = siteinds(H2; plev = 1) + C = combiner.(s; tags = "X") + H2 .*= C + H2H2 = prime(H2; tags = (!ts"X")) * dag(H2) + @test @disable_warn_order prod(HH) ≈ prod(H2H2) + end - @testset "MPO+MPO sum (directsum)" begin - N = 3 - conserve_qns = true - s = siteinds("S=1/2", N; conserve_qns=conserve_qns) + @testset "Bond dimensions of MPO*MPS in default contract" begin + N = 8 + chi1 = 6 + chi2 = 2 + s = siteinds(2, N) + + A = begin + l = [Index(chi1, "n=$n,Link") for n in 1:N] + M = MPO(N) + M[1] = random_itensor(dag(s[1]), l[1], s'[1]) + for n in 2:(N - 1) + M[n] = random_itensor(dag(s[n]), dag(l[n - 1]), l[n], s'[n]) + end + M[N] = random_itensor(dag(s[N]), dag(l[N - 1]), s'[N]) + nrm = inner(M, M) + for n in 1:N + M[n] ./= (nrm)^(1 / (2N)) + end + truncate!(M; cutoff = 1.0e-10) + M + end + + psi = random_mps(s; linkdims = chi2) + + Apsi = contract(A, psi) + + dims = linkdims(Apsi) + for d in dims + @test d <= chi1 * chi2 + end + + @test apply(A, psi) ≈ noprime(Apsi) + @test ITensors.materialize(Apply(A, psi)) ≈ noprime(Apsi) + @test A(psi) ≈ noprime(Apsi) + @test inner(noprime(Apsi), Apply(A, psi)) ≈ inner(Apsi, Apsi) + end + + @testset "Other MPO contract algorithms" begin + # Regression test - ensure that output of "naive" algorithm is an + # MPO not an MPS + N = 8 + s = siteinds(2, N) + A = random_mpo(s) + B = random_mpo(s) + C = apply(A, B; alg = "naive") + @test C isa MPO + end + + @testset "MPO with no link indices" for conserve_qns in [false, true] + s = siteinds("S=1/2", 4; conserve_qns) + H = MPO([op("Id", sn) for sn in s]) + @test linkinds(H) == fill(nothing, length(s) - 1) + @test norm(H) == √(2^length(s)) + + Hortho = orthogonalize(H, 1) + @test Hortho ≈ H + @test linkdims(Hortho) == fill(1, length(s) - 1) + + Htrunc = truncate(H; cutoff = 1.0e-8) + @test Htrunc ≈ H + @test linkdims(Htrunc) == fill(1, length(s) - 1) + + H² = apply(H, H; cutoff = 1.0e-8) + H̃² = MPO([apply(H[n], H[n]) for n in 1:length(s)]) + @test linkdims(H²) == fill(1, length(s) - 1) + @test H² ≈ H̃² - ops = n -> isodd(n) ? "Sz" : "Id" - H₁ = MPO(s, ops) - H₂ = MPO(s, ops) + e, ψ = dmrg(H, random_mps(s, n -> isodd(n) ? "↑" : "↓"); nsweeps = 2, outputlevel = 0) + @test e ≈ 1 + end + + @testset "consistent precision of apply" for T in + (Float32, Float64, ComplexF32, ComplexF64) + sites = siteinds("S=1/2", 4) + A = randn(T) * convert_leaf_eltype(T, random_mpo(sites)) + B = randn(T) * convert_leaf_eltype(T, random_mpo(sites)) + @test scalartype(apply(A, B)) == T + end + @testset "sample" begin + N = 6 + sites = [Index(2, "Site,n=$n") for n in 1:N] + seed = 623 + rng = StableRNG(seed) + K = random_mps(rng, sites) + L = MPO(K) + result = sample(rng, L) + @test result ≈ [1, 1, 2, 1, 1, 1] + end - H = +(H₁, H₂; alg="directsum") - H_ref = +(H₁, H₂; alg="densitymatrix") + @testset "MPO+MPO sum (directsum)" begin + N = 3 + conserve_qns = true + s = siteinds("S=1/2", N; conserve_qns = conserve_qns) - @test typeof(H) == typeof(H₁) - @test H_ref ≈ H - @test inner(H, H) ≈ inner_add(H₁, H₂) - @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) + ops = n -> isodd(n) ? "Sz" : "Id" + H₁ = MPO(s, ops) + H₂ = MPO(s, ops) - α₁ = 2.2 - α₂ = 3.4 + 1.2im + H = +(H₁, H₂; alg = "directsum") + H_ref = +(H₁, H₂; alg = "densitymatrix") - H = +(α₁ * H₁, H₂; alg="directsum") + @test typeof(H) == typeof(H₁) + @test H_ref ≈ H + @test inner(H, H) ≈ inner_add(H₁, H₂) + @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) - @test typeof(H) == typeof(H₁) - @test inner(H, H) ≈ inner_add((α₁, H₁), H₂) - @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) + α₁ = 2.2 + α₂ = 3.4 + 1.2im - H = +(H₁, -H₂; alg="directsum") + H = +(α₁ * H₁, H₂; alg = "directsum") - @test typeof(H) == typeof(H₁) - @test inner(H, H) ≈ inner_add(H₁, (-1, H₂)) - @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) + @test typeof(H) == typeof(H₁) + @test inner(H, H) ≈ inner_add((α₁, H₁), H₂) + @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) - H = +(α₁ * H₁, -α₂ * H₂; alg="directsum") + H = +(H₁, -H₂; alg = "directsum") - @test typeof(H) == typeof(H₁) - @test inner(H, H) ≈ inner_add((α₁, H₁), (-α₂, H₂)) - @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) - end + @test typeof(H) == typeof(H₁) + @test inner(H, H) ≈ inner_add(H₁, (-1, H₂)) + @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) + + H = +(α₁ * H₁, -α₂ * H₂; alg = "directsum") + + @test typeof(H) == typeof(H₁) + @test inner(H, H) ≈ inner_add((α₁, H₁), (-α₂, H₂)) + @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂) + end end end diff --git a/test/base/test_mps.jl b/test/base/test_mps.jl index 47b45c2..eb73b23 100644 --- a/test/base/test_mps.jl +++ b/test/base/test_mps.jl @@ -11,2038 +11,2039 @@ Random.seed!(1234) include(joinpath(@__DIR__, "utils", "util.jl")) @testset "MPS Basics" begin - sites = [Index(2, "Site") for n in 1:10] - psi = MPS(sites) - @test length(psi) == length(psi) - @test length(MPS()) == 0 - @test linkdims(psi) == fill(1, length(psi) - 1) - @test isnothing(flux(psi)) - - psi = MPS(sites; linkdims=3) - @test length(psi) == length(psi) - @test length(MPS()) == 0 - @test linkdims(psi) == fill(3, length(psi) - 1) - @test isnothing(flux(psi)) - - str = split(sprint(show, psi), '\n') - @test endswith(str[1], "MPS") - @test length(str) == length(psi) + 2 - - @test siteind(psi, 2) == sites[2] - @test findfirstsiteind(psi, sites[2]) == 2 - @test findfirstsiteind(psi, sites[4]) == 4 - @test findfirstsiteinds(psi, IndexSet(sites[5])) == 5 - @test hasind(psi[3], linkind(psi, 2)) - @test hasind(psi[3], linkind(psi, 3)) - - @test isnothing(linkind(psi, length(psi))) - @test isnothing(linkind(psi, length(psi) + 1)) - @test isnothing(linkind(psi, 0)) - @test isnothing(linkind(psi, -1)) - @test linkind(psi, 3) == commonind(psi[3], psi[4]) - - psi[1] = ITensor(sites[1]) - @test hasind(psi[1], sites[1]) - - @testset "N=1 MPS" begin - sites1 = [Index(2, "Site,n=1")] - psi = MPS(sites1) - @test length(psi) == 1 - @test siteind(psi, 1) == sites1[1] - @test siteinds(psi)[1] == sites1[1] - end - - @testset "Missing links" begin - psi = MPS([random_itensor(sites[i]) for i in 1:10]) - @test isnothing(linkind(psi, 1)) - @test isnothing(linkind(psi, 5)) + sites = [Index(2, "Site") for n in 1:10] + psi = MPS(sites) + @test length(psi) == length(psi) + @test length(MPS()) == 0 + @test linkdims(psi) == fill(1, length(psi) - 1) + @test isnothing(flux(psi)) + + psi = MPS(sites; linkdims = 3) + @test length(psi) == length(psi) + @test length(MPS()) == 0 + @test linkdims(psi) == fill(3, length(psi) - 1) + @test isnothing(flux(psi)) + + str = split(sprint(show, psi), '\n') + @test endswith(str[1], "MPS") + @test length(str) == length(psi) + 2 + + @test siteind(psi, 2) == sites[2] + @test findfirstsiteind(psi, sites[2]) == 2 + @test findfirstsiteind(psi, sites[4]) == 4 + @test findfirstsiteinds(psi, IndexSet(sites[5])) == 5 + @test hasind(psi[3], linkind(psi, 2)) + @test hasind(psi[3], linkind(psi, 3)) + @test isnothing(linkind(psi, length(psi))) - @test maxlinkdim(psi) == 1 - @test psi ⋅ psi ≈ *(dag(psi)..., psi...)[] - end - - @testset "MPS" begin - @testset "vector of string input" begin - sites = siteinds("S=1/2", 10) - state = fill("", length(sites)) - for j in 1:length(sites) - state[j] = isodd(j) ? "Up" : "Dn" - end - psi = MPS(sites, state) - for j in 1:length(psi) - sign = isodd(j) ? +1.0 : -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - psi = MPS(sites, state) - for j in 1:length(psi) - sign = isodd(j) ? +1.0 : -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - @test_throws DimensionMismatch MPS(sites, fill("", length(psi) - 1)) - @test_throws DimensionMismatch MPS(sites, fill("", length(psi) - 1)) + @test isnothing(linkind(psi, length(psi) + 1)) + @test isnothing(linkind(psi, 0)) + @test isnothing(linkind(psi, -1)) + @test linkind(psi, 3) == commonind(psi[3], psi[4]) + + psi[1] = ITensor(sites[1]) + @test hasind(psi[1], sites[1]) + + @testset "N=1 MPS" begin + sites1 = [Index(2, "Site,n=1")] + psi = MPS(sites1) + @test length(psi) == 1 + @test siteind(psi, 1) == sites1[1] + @test siteinds(psi)[1] == sites1[1] end - @testset "String input" begin - sites = siteinds("S=1/2", 10) - psi = MPS(sites, "Dn") - for j in 1:length(psi) - sign = -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - psi = MPS(sites, "Dn") - for j in 1:length(psi) - sign = -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - - psi = MPS(sites, "X+") - for j in 1:length(psi) - @test (psi[j] * op(sites, "X", j) * dag(prime(psi[j], "Site")))[] ≈ 1.0 - end + @testset "Missing links" begin + psi = MPS([random_itensor(sites[i]) for i in 1:10]) + @test isnothing(linkind(psi, 1)) + @test isnothing(linkind(psi, 5)) + @test isnothing(linkind(psi, length(psi))) + @test maxlinkdim(psi) == 1 + @test psi ⋅ psi ≈ *(dag(psi)..., psi...)[] end - @testset "Int input" begin - sites = siteinds("S=1/2", 10) - psi = MPS(sites, 2) - for j in 1:length(psi) - sign = -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - psi = MPS(sites, 2) - for j in 1:length(psi) - sign = -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - end + @testset "MPS" begin + @testset "vector of string input" begin + sites = siteinds("S=1/2", 10) + state = fill("", length(sites)) + for j in 1:length(sites) + state[j] = isodd(j) ? "Up" : "Dn" + end + psi = MPS(sites, state) + for j in 1:length(psi) + sign = isodd(j) ? +1.0 : -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end + psi = MPS(sites, state) + for j in 1:length(psi) + sign = isodd(j) ? +1.0 : -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end + @test_throws DimensionMismatch MPS(sites, fill("", length(psi) - 1)) + @test_throws DimensionMismatch MPS(sites, fill("", length(psi) - 1)) + end - @testset "vector of int input" begin - sites = siteinds("S=1/2", 10) - state = fill(0, length(sites)) - for j in 1:length(sites) - state[j] = isodd(j) ? 1 : 2 - end - psi = MPS(sites, state) - for j in 1:length(psi) - sign = isodd(j) ? +1.0 : -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - psi = MPS(sites, state) - for j in 1:length(psi) - sign = isodd(j) ? +1.0 : -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - end + @testset "String input" begin + sites = siteinds("S=1/2", 10) + psi = MPS(sites, "Dn") + for j in 1:length(psi) + sign = -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end + psi = MPS(sites, "Dn") + for j in 1:length(psi) + sign = -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end + + psi = MPS(sites, "X+") + for j in 1:length(psi) + @test (psi[j] * op(sites, "X", j) * dag(prime(psi[j], "Site")))[] ≈ 1.0 + end + end - @testset "vector of ivals input" begin - sites = siteinds("S=1/2", 10) - states = fill(0, length(sites)) - for j in 1:length(sites) - states[j] = isodd(j) ? 1 : 2 - end - ivals = [sites[n] => states[n] for n in 1:length(sites)] - psi = MPS(ivals) - for j in 1:length(psi) - sign = isodd(j) ? +1.0 : -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - psi = MPS(ivals) - for j in 1:length(psi) - sign = isodd(j) ? +1.0 : -1.0 - @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 - end - - @testset "ComplexF64 eltype" begin - sites = siteinds("S=1/2", 10) - psi = MPS(ComplexF64, sites, fill(1, length(sites))) - for j in 1:length(psi) - @test eltype(psi[j]) == ComplexF64 + @testset "Int input" begin + sites = siteinds("S=1/2", 10) + psi = MPS(sites, 2) + for j in 1:length(psi) + sign = -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end + psi = MPS(sites, 2) + for j in 1:length(psi) + sign = -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end end - psi = MPS(ComplexF64, sites, fill(1, length(psi))) - for j in 1:length(psi) - @test eltype(psi[j]) == ComplexF64 + + @testset "vector of int input" begin + sites = siteinds("S=1/2", 10) + state = fill(0, length(sites)) + for j in 1:length(sites) + state[j] = isodd(j) ? 1 : 2 + end + psi = MPS(sites, state) + for j in 1:length(psi) + sign = isodd(j) ? +1.0 : -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end + psi = MPS(sites, state) + for j in 1:length(psi) + sign = isodd(j) ? +1.0 : -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end + end + + @testset "vector of ivals input" begin + sites = siteinds("S=1/2", 10) + states = fill(0, length(sites)) + for j in 1:length(sites) + states[j] = isodd(j) ? 1 : 2 + end + ivals = [sites[n] => states[n] for n in 1:length(sites)] + psi = MPS(ivals) + for j in 1:length(psi) + sign = isodd(j) ? +1.0 : -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end + psi = MPS(ivals) + for j in 1:length(psi) + sign = isodd(j) ? +1.0 : -1.0 + @test (psi[j] * op(sites, "Sz", j) * dag(prime(psi[j], "Site")))[] ≈ sign / 2 + end + + @testset "ComplexF64 eltype" begin + sites = siteinds("S=1/2", 10) + psi = MPS(ComplexF64, sites, fill(1, length(sites))) + for j in 1:length(psi) + @test eltype(psi[j]) == ComplexF64 + end + psi = MPS(ComplexF64, sites, fill(1, length(psi))) + for j in 1:length(psi) + @test eltype(psi[j]) == ComplexF64 + end + @test eltype(psi) == ITensor + @test ITensorMPS.promote_itensor_eltype(psi) == ComplexF64 + end + end + + @testset "N=1 case" begin + site = Index(2, "Site,n=1") + psi = MPS([site], [1]) + @test psi[1][1] ≈ 1.0 + @test psi[1][2] ≈ 0.0 + psi = MPS([site], [1]) + @test psi[1][1] ≈ 1.0 + @test psi[1][2] ≈ 0.0 + psi = MPS([site], [2]) + @test psi[1][1] ≈ 0.0 + @test psi[1][2] ≈ 1.0 + psi = MPS([site], [2]) + @test psi[1][1] ≈ 0.0 + @test psi[1][2] ≈ 1.0 end - @test eltype(psi) == ITensor - @test ITensorMPS.promote_itensor_eltype(psi) == ComplexF64 - end end - @testset "N=1 case" begin - site = Index(2, "Site,n=1") - psi = MPS([site], [1]) - @test psi[1][1] ≈ 1.0 - @test psi[1][2] ≈ 0.0 - psi = MPS([site], [1]) - @test psi[1][1] ≈ 1.0 - @test psi[1][2] ≈ 0.0 - psi = MPS([site], [2]) - @test psi[1][1] ≈ 0.0 - @test psi[1][2] ≈ 1.0 - psi = MPS([site], [2]) - @test psi[1][1] ≈ 0.0 - @test psi[1][2] ≈ 1.0 + @testset "random_mps with chi==1" begin + phi = random_mps(sites) + phic = random_mps(ComplexF64, sites) + + @test maxlinkdim(phi) == 1 + @test maxlinkdim(phic) == 1 + + @test hasind(phi[1], sites[1]) + @test norm(phi[1]) ≈ 1.0 + @test norm(phic[1]) ≈ 1.0 + + @test hasind(phi[4], sites[4]) + @test norm(phi[4]) ≈ 1.0 + @test norm(phic[4]) ≈ 1.0 end - end - - @testset "random_mps with chi==1" begin - phi = random_mps(sites) - phic = random_mps(ComplexF64, sites) - - @test maxlinkdim(phi) == 1 - @test maxlinkdim(phic) == 1 - - @test hasind(phi[1], sites[1]) - @test norm(phi[1]) ≈ 1.0 - @test norm(phic[1]) ≈ 1.0 - - @test hasind(phi[4], sites[4]) - @test norm(phi[4]) ≈ 1.0 - @test norm(phic[4]) ≈ 1.0 - end - - @testset "random_mps with chi>1" for linkdims in [1, 4] - phi = random_mps(Float32, sites; linkdims) - @test LinearAlgebra.promote_leaf_eltypes(phi) === Float32 - @test all(x -> eltype(x) === Float32, phi) - @test maxlinkdim(phi) == linkdims - phic = random_mps(ComplexF32, sites; linkdims) - @test LinearAlgebra.promote_leaf_eltypes(phic) === ComplexF32 - @test maxlinkdim(phic) == linkdims - @test all(x -> eltype(x) === ComplexF32, phic) - end - - @testset "random_mps with nonuniform dimensions" begin - _linkdims = [2, 3, 4, 2, 4, 3, 2, 2, 2] - phi = random_mps(sites; linkdims=_linkdims) - @test linkdims(phi) == _linkdims - end - - @testset "QN random_mps" begin - s = siteinds("S=1/2", 5; conserve_qns=true) - ψ = random_mps(s, n -> isodd(n) ? "↑" : "↓"; linkdims=2) - @test linkdims(ψ) == [2, 2, 2, 2] - ψ = random_mps(s, n -> isodd(n) ? "↑" : "↓"; linkdims=[2, 3, 2, 2]) - @test linkdims(ψ) == [2, 3, 2, 2] - end - - @testset "inner different MPS" begin - phi = random_mps(sites) - psi = random_mps(sites) - phipsi = dag(phi[1]) * psi[1] - for j in 2:length(psi) - phipsi *= dag(phi[j]) * psi[j] + + @testset "random_mps with chi>1" for linkdims in [1, 4] + phi = random_mps(Float32, sites; linkdims) + @test LinearAlgebra.promote_leaf_eltypes(phi) === Float32 + @test all(x -> eltype(x) === Float32, phi) + @test maxlinkdim(phi) == linkdims + phic = random_mps(ComplexF32, sites; linkdims) + @test LinearAlgebra.promote_leaf_eltypes(phic) === ComplexF32 + @test maxlinkdim(phic) == linkdims + @test all(x -> eltype(x) === ComplexF32, phic) end - @test phipsi[] ≈ inner(phi, psi) - badsites = [Index(2) for n in 1:(length(psi) + 1)] - badpsi = random_mps(badsites) - @test_throws DimensionMismatch inner(phi, badpsi) - end + @testset "random_mps with nonuniform dimensions" begin + _linkdims = [2, 3, 4, 2, 4, 3, 2, 2, 2] + phi = random_mps(sites; linkdims = _linkdims) + @test linkdims(phi) == _linkdims + end - @testset "loginner" begin - n = 4 - c = 2 + @testset "QN random_mps" begin + s = siteinds("S=1/2", 5; conserve_qns = true) + ψ = random_mps(s, n -> isodd(n) ? "↑" : "↓"; linkdims = 2) + @test linkdims(ψ) == [2, 2, 2, 2] + ψ = random_mps(s, n -> isodd(n) ? "↑" : "↓"; linkdims = [2, 3, 2, 2]) + @test linkdims(ψ) == [2, 3, 2, 2] + end - s = siteinds("S=1/2", n) - ψ = c .* random_mps(s; linkdims=4) - @test exp(loginner(ψ, ψ)) ≈ c^(2n) - @test exp(loginner(ψ, -ψ)) ≈ -c^(2n) + @testset "inner different MPS" begin + phi = random_mps(sites) + psi = random_mps(sites) + phipsi = dag(phi[1]) * psi[1] + for j in 2:length(psi) + phipsi *= dag(phi[j]) * psi[j] + end + @test phipsi[] ≈ inner(phi, psi) - α = randn(ComplexF64) - @test exp(loginner(ψ, α * ψ)) ≈ α * c^(2n) - end + badsites = [Index(2) for n in 1:(length(psi) + 1)] + badpsi = random_mps(badsites) + @test_throws DimensionMismatch inner(phi, badpsi) + end - @testset "broadcasting" begin - psi = random_mps(sites) - orthogonalize!(psi, 1) - @test ortho_lims(psi) == 1:1 - @test dim.(psi) == fill(2, length(psi)) - psi′ = prime.(psi) - @test ortho_lims(psi′) == 1:length(psi′) - @test ortho_lims(psi) == 1:1 - for n in 1:length(psi) - @test prime(psi[n]) == psi′[n] + @testset "loginner" begin + n = 4 + c = 2 + + s = siteinds("S=1/2", n) + ψ = c .* random_mps(s; linkdims = 4) + @test exp(loginner(ψ, ψ)) ≈ c^(2n) + @test exp(loginner(ψ, -ψ)) ≈ -c^(2n) + + α = randn(ComplexF64) + @test exp(loginner(ψ, α * ψ)) ≈ α * c^(2n) + end + + @testset "broadcasting" begin + psi = random_mps(sites) + orthogonalize!(psi, 1) + @test ortho_lims(psi) == 1:1 + @test dim.(psi) == fill(2, length(psi)) + psi′ = prime.(psi) + @test ortho_lims(psi′) == 1:length(psi′) + @test ortho_lims(psi) == 1:1 + for n in 1:length(psi) + @test prime(psi[n]) == psi′[n] + end + psi_copy = copy(psi) + psi_copy .= addtags(psi_copy, "x") + @test ortho_lims(psi_copy) == 1:length(psi_copy) + @test ortho_lims(psi) == 1:1 + for n in 1:length(psi) + @test addtags(psi[n], "x") == psi_copy[n] + end end - psi_copy = copy(psi) - psi_copy .= addtags(psi_copy, "x") - @test ortho_lims(psi_copy) == 1:length(psi_copy) - @test ortho_lims(psi) == 1:1 - for n in 1:length(psi) - @test addtags(psi[n], "x") == psi_copy[n] + + @testset "replace_siteinds" begin + s = siteinds("S=1/2", 4) + x = MPS(s, j -> isodd(j) ? "↑" : "↓") + @test siteinds(x) == s + t = sim.(s) + y = replace_siteinds(x, t) + @test siteinds(y) == t + # Regression test for https://github.com/ITensor/ITensors.jl/issues/1439. + @test siteinds(x) == s end - end - - @testset "replace_siteinds" begin - s = siteinds("S=1/2", 4) - x = MPS(s, j -> isodd(j) ? "↑" : "↓") - @test siteinds(x) == s - t = sim.(s) - y = replace_siteinds(x, t) - @test siteinds(y) == t - # Regression test for https://github.com/ITensor/ITensors.jl/issues/1439. - @test siteinds(x) == s - end - - @testset "copy and deepcopy" begin - s = siteinds("S=1/2", 3) - M1 = random_mps(s; linkdims=3) - @test norm(M1) ≈ 1 - - M2 = deepcopy(M1) - M2[1] .*= 2 # Modifies the tensor data - @test norm(M1) ≈ 1 - @test norm(M2) ≈ 2 - - M3 = copy(M1) - M3[1] *= 3 - @test norm(M1) ≈ 1 - @test norm(M3) ≈ 3 - - M4 = copy(M1) - M4[1] .*= 4 - @test norm(M1) ≈ 4 - @test norm(M4) ≈ 4 - end - - @testset "inner same MPS" begin - psi = random_mps(sites) - psidag = dag(psi) - #ITensors.prime_linkinds!(psidag) - psipsi = psidag[1] * psi[1] - for j in 2:length(psi) - psipsi *= psidag[j] * psi[j] + + @testset "copy and deepcopy" begin + s = siteinds("S=1/2", 3) + M1 = random_mps(s; linkdims = 3) + @test norm(M1) ≈ 1 + + M2 = deepcopy(M1) + M2[1] .*= 2 # Modifies the tensor data + @test norm(M1) ≈ 1 + @test norm(M2) ≈ 2 + + M3 = copy(M1) + M3[1] *= 3 + @test norm(M1) ≈ 1 + @test norm(M3) ≈ 3 + + M4 = copy(M1) + M4[1] .*= 4 + @test norm(M1) ≈ 4 + @test norm(M4) ≈ 4 end - @test psipsi[] ≈ inner(psi, psi) - end - - @testset "norm MPS (eltype=$elt)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ) - psi = random_mps(elt, sites; linkdims=10) - psidag = sim(linkinds, dag(psi)) - psi² = ITensor(1) - for j in 1:length(psi) - psi² *= psidag[j] * psi[j] + + @testset "inner same MPS" begin + psi = random_mps(sites) + psidag = dag(psi) + #ITensors.prime_linkinds!(psidag) + psipsi = psidag[1] * psi[1] + for j in 2:length(psi) + psipsi *= psidag[j] * psi[j] + end + @test psipsi[] ≈ inner(psi, psi) end - @test psi²[] ≈ psi ⋅ psi - @test sqrt(psi²[]) ≈ norm(psi) - psi = random_mps(elt, sites; linkdims=10) - psi .*= 1:length(psi) - @test norm(psi) ≈ factorial(length(psi)) - @test norm(psi) isa real(elt) + @testset "norm MPS (eltype=$elt)" for elt in ( + Float32, Float64, Complex{Float32}, Complex{Float64}, + ) + psi = random_mps(elt, sites; linkdims = 10) + psidag = sim(linkinds, dag(psi)) + psi² = ITensor(1) + for j in 1:length(psi) + psi² *= psidag[j] * psi[j] + end + @test psi²[] ≈ psi ⋅ psi + @test sqrt(psi²[]) ≈ norm(psi) + + psi = random_mps(elt, sites; linkdims = 10) + psi .*= 1:length(psi) + @test norm(psi) ≈ factorial(length(psi)) + @test norm(psi) isa real(elt) + + psi = random_mps(elt, sites; linkdims = 10) + for j in 1:length(psi) + psi[j] .*= j + end + # This fails because it modifies the MPS ITensors + # directly, which ruins the orthogonality + @test norm(psi) ≉ factorial(length(psi)) + reset_ortho_lims!(psi) + @test norm(psi) ≈ factorial(length(psi)) + + psi = random_mps(elt, sites; linkdims = 10) + + norm_psi = norm(psi) + @test norm_psi ≈ 1 + @test norm_psi isa real(elt) + @test isreal(norm_psi) - psi = random_mps(elt, sites; linkdims=10) - for j in 1:length(psi) - psi[j] .*= j + lognorm_psi = lognorm(psi) + @test lognorm_psi isa real(elt) + @test lognorm_psi ≈ 0 atol = eps(real(elt)) * 10 + @test isreal(lognorm_psi) + + psi = psi .* 2 + + norm_psi = norm(psi) + @test norm_psi ≈ 2^length(psi) + @test isreal(norm_psi) + + lognorm_psi = lognorm(psi) + @test lognorm_psi ≈ log(2) * length(psi) + @test isreal(lognorm_psi) end - # This fails because it modifies the MPS ITensors - # directly, which ruins the orthogonality - @test norm(psi) ≉ factorial(length(psi)) - reset_ortho_lims!(psi) - @test norm(psi) ≈ factorial(length(psi)) - - psi = random_mps(elt, sites; linkdims=10) - - norm_psi = norm(psi) - @test norm_psi ≈ 1 - @test norm_psi isa real(elt) - @test isreal(norm_psi) - - lognorm_psi = lognorm(psi) - @test lognorm_psi isa real(elt) - @test lognorm_psi ≈ 0 atol = eps(real(elt)) * 10 - @test isreal(lognorm_psi) - - psi = psi .* 2 - - norm_psi = norm(psi) - @test norm_psi ≈ 2^length(psi) - @test isreal(norm_psi) - - lognorm_psi = lognorm(psi) - @test lognorm_psi ≈ log(2) * length(psi) - @test isreal(lognorm_psi) - end - - @testset "lognorm checking real tolerance error regression test" begin - # Test that lognorm doesn't throw an error when the norm isn't real - # up to a certain tolerance - Random.seed!(1234) - s = siteinds("S=1/2", 10) - ψ = random_mps(ComplexF64, s; linkdims=4) - reset_ortho_lims!(ψ) - @test exp(lognorm(ψ)) ≈ 1 - end - - @testset "normalize/normalize! MPS" begin - psi = random_mps(sites; linkdims=10) - - @test norm(psi) ≈ 1 - @test norm(normalize(psi)) ≈ 1 - - α = 3.5 - phi = α * psi - @test norm(phi) ≈ α - @test norm(normalize(phi)) ≈ 1 - @test norm(psi) ≈ 1 - @test inner(phi, psi) ≈ α - - normalize!(phi) - @test norm(phi) ≈ 1 - @test norm(normalize(phi)) ≈ 1 - @test norm(psi) ≈ 1 - @test inner(phi, psi) ≈ 1 - - # Zero norm - @test norm(0phi) == 0 - @test lognorm(0phi) == -Inf - - zero_phi = 0phi - lognorm_zero_phi = [] - normalize!(zero_phi; (lognorm!)=lognorm_zero_phi) - @test lognorm_zero_phi[1] == -Inf - @test norm(zero_phi) == 0 - @test norm(normalize(0phi)) == 0 - - # Large number of sites - psi = random_mps(siteinds("S=1/2", 1_000); linkdims=10) - - @test norm(psi) ≈ 1.0 - @test lognorm(psi) ≈ 0.0 atol = 1e-15 - - α = 2 - phi = α .* psi - - @test isnan(norm(phi)) - @test lognorm(phi) ≈ length(psi) * log(α) - - phi = normalize(phi) - - @test norm(phi) ≈ 1 - - # Test scaling only a subset of sites - psi = random_mps(siteinds("S=1/2", 10); linkdims=10) - - @test norm(psi) ≈ 1.0 - @test lognorm(psi) ≈ 0.0 atol = 1e-15 - - α = 2 - r = (length(psi) ÷ 2 - 1):(length(psi) ÷ 2 + 1) - phi = copy(psi) - for n in r - phi[n] = α * psi[n] + + @testset "lognorm checking real tolerance error regression test" begin + # Test that lognorm doesn't throw an error when the norm isn't real + # up to a certain tolerance + Random.seed!(1234) + s = siteinds("S=1/2", 10) + ψ = random_mps(ComplexF64, s; linkdims = 4) + reset_ortho_lims!(ψ) + @test exp(lognorm(ψ)) ≈ 1 end - @test norm(phi) ≈ α^length(r) - @test lognorm(phi) ≈ length(r) * log(α) - - phi = normalize(phi) - - @test norm(phi) ≈ 1 - - # Output the lognorm - α = 2 - psi = random_mps(siteinds("S=1/2", 30); linkdims=10) - psi = α .* psi - @test norm(psi) ≈ α^length(psi) - @test lognorm(psi) ≈ length(psi) * log(α) - lognorm_psi = Float64[] - phi = normalize(psi; (lognorm!)=lognorm_psi) - @test lognorm_psi[end] ≈ lognorm(psi) - @test norm(phi) ≈ 1 - @test lognorm(phi) ≈ 0 atol = 1e-14 - end - - @testset "lognorm MPS" begin - psi = random_mps(sites; linkdims=10) - for j in eachindex(psi) - psi[j] .*= j + @testset "normalize/normalize! MPS" begin + psi = random_mps(sites; linkdims = 10) + + @test norm(psi) ≈ 1 + @test norm(normalize(psi)) ≈ 1 + + α = 3.5 + phi = α * psi + @test norm(phi) ≈ α + @test norm(normalize(phi)) ≈ 1 + @test norm(psi) ≈ 1 + @test inner(phi, psi) ≈ α + + normalize!(phi) + @test norm(phi) ≈ 1 + @test norm(normalize(phi)) ≈ 1 + @test norm(psi) ≈ 1 + @test inner(phi, psi) ≈ 1 + + # Zero norm + @test norm(0phi) == 0 + @test lognorm(0phi) == -Inf + + zero_phi = 0phi + lognorm_zero_phi = [] + normalize!(zero_phi; (lognorm!) = lognorm_zero_phi) + @test lognorm_zero_phi[1] == -Inf + @test norm(zero_phi) == 0 + @test norm(normalize(0phi)) == 0 + + # Large number of sites + psi = random_mps(siteinds("S=1/2", 1_000); linkdims = 10) + + @test norm(psi) ≈ 1.0 + @test lognorm(psi) ≈ 0.0 atol = 1.0e-15 + + α = 2 + phi = α .* psi + + @test isnan(norm(phi)) + @test lognorm(phi) ≈ length(psi) * log(α) + + phi = normalize(phi) + + @test norm(phi) ≈ 1 + + # Test scaling only a subset of sites + psi = random_mps(siteinds("S=1/2", 10); linkdims = 10) + + @test norm(psi) ≈ 1.0 + @test lognorm(psi) ≈ 0.0 atol = 1.0e-15 + + α = 2 + r = (length(psi) ÷ 2 - 1):(length(psi) ÷ 2 + 1) + phi = copy(psi) + for n in r + phi[n] = α * psi[n] + end + + @test norm(phi) ≈ α^length(r) + @test lognorm(phi) ≈ length(r) * log(α) + + phi = normalize(phi) + + @test norm(phi) ≈ 1 + + # Output the lognorm + α = 2 + psi = random_mps(siteinds("S=1/2", 30); linkdims = 10) + psi = α .* psi + @test norm(psi) ≈ α^length(psi) + @test lognorm(psi) ≈ length(psi) * log(α) + lognorm_psi = Float64[] + phi = normalize(psi; (lognorm!) = lognorm_psi) + @test lognorm_psi[end] ≈ lognorm(psi) + @test norm(phi) ≈ 1 + @test lognorm(phi) ≈ 0 atol = 1.0e-14 + end + + @testset "lognorm MPS" begin + psi = random_mps(sites; linkdims = 10) + for j in eachindex(psi) + psi[j] .*= j + end + psidag = sim(linkinds, dag(psi)) + psi² = ITensor(1) + for j in eachindex(psi) + psi² *= psidag[j] * psi[j] + end + @test psi²[] ≈ psi ⋅ psi + @test 0.5 * log(psi²[]) ≉ lognorm(psi) + @test lognorm(psi) ≉ log(factorial(length(psi))) + # Need to manually change the orthogonality + # limits back to 1:length(psi) + reset_ortho_lims!(psi) + @test 0.5 * log(psi²[]) ≈ lognorm(psi) + @test lognorm(psi) ≈ log(factorial(length(psi))) end - psidag = sim(linkinds, dag(psi)) - psi² = ITensor(1) - for j in eachindex(psi) - psi² *= psidag[j] * psi[j] + + @testset "scaling MPS" begin + psi = random_mps(sites; linkdims = 4) + twopsidag = 2.0 * dag(psi) + #ITensors.prime_linkinds!(twopsidag) + @test inner(twopsidag, psi) ≈ 2.0 * inner(psi, psi) end - @test psi²[] ≈ psi ⋅ psi - @test 0.5 * log(psi²[]) ≉ lognorm(psi) - @test lognorm(psi) ≉ log(factorial(length(psi))) - # Need to manually change the orthogonality - # limits back to 1:length(psi) - reset_ortho_lims!(psi) - @test 0.5 * log(psi²[]) ≈ lognorm(psi) - @test lognorm(psi) ≈ log(factorial(length(psi))) - end - - @testset "scaling MPS" begin - psi = random_mps(sites; linkdims=4) - twopsidag = 2.0 * dag(psi) - #ITensors.prime_linkinds!(twopsidag) - @test inner(twopsidag, psi) ≈ 2.0 * inner(psi, psi) - end - - @testset "flip sign of MPS" begin - psi = random_mps(sites) - minuspsidag = -dag(psi) - #ITensors.primelinkinds!(minuspsidag) - @test inner(minuspsidag, psi) ≈ -inner(psi, psi) - end - @testset "add MPS" begin - psi = random_mps(sites) - phi = deepcopy(psi) - xi = add(psi, phi) - @test inner(xi, xi) ≈ 4.0 * inner(psi, psi) - # sum of many MPSs - Ks = [random_mps(sites) for i in 1:3] - K12 = add(Ks[1], Ks[2]) - K123 = add(K12, Ks[3]) - @test inner(sum(Ks), K123) ≈ inner(K123, K123) - # https://github.com/ITensor/ITensors.jl/issues/1000 - @test sum([psi]) ≈ psi - end - - @testset "+ MPS" begin - psi = random_mps(sites) - phi = deepcopy(psi) - xi = psi + phi - @test inner(xi, xi) ≈ 4.0 * inner(psi, psi) - # sum of many MPSs - Ks = [random_mps(sites) for i in 1:3] - K12 = Ks[1] + Ks[2] - K123 = K12 + Ks[3] - @test inner(sum(Ks), K123) ≈ inner(K123, K123) - - χ1 = 2 - χ2 = 3 - ψ1 = random_mps(sites; linkdims=χ1) - ψ2 = 0.0 * random_mps(sites; linkdims=χ2) - - ϕ1 = +(ψ1, ψ2; alg="densitymatrix", cutoff=nothing) - for j in 2:7 - @test linkdim(ϕ1, j) == χ1 + χ2 + @testset "flip sign of MPS" begin + psi = random_mps(sites) + minuspsidag = -dag(psi) + #ITensors.primelinkinds!(minuspsidag) + @test inner(minuspsidag, psi) ≈ -inner(psi, psi) end - @test inner(ϕ1, ψ1) + inner(ϕ1, ψ2) ≈ inner(ϕ1, ϕ1) - ϕ2 = +(ψ1, ψ2; alg="directsum") - for j in 1:8 - @test linkdim(ϕ2, j) == χ1 + χ2 + @testset "add MPS" begin + psi = random_mps(sites) + phi = deepcopy(psi) + xi = add(psi, phi) + @test inner(xi, xi) ≈ 4.0 * inner(psi, psi) + # sum of many MPSs + Ks = [random_mps(sites) for i in 1:3] + K12 = add(Ks[1], Ks[2]) + K123 = add(K12, Ks[3]) + @test inner(sum(Ks), K123) ≈ inner(K123, K123) + # https://github.com/ITensor/ITensors.jl/issues/1000 + @test sum([psi]) ≈ psi end - @test inner(ϕ2, ψ1) + inner(ϕ2, ψ2) ≈ inner(ϕ2, ϕ2) - end - @testset "+ MPS with coefficients" begin - Random.seed!(1234) + @testset "+ MPS" begin + psi = random_mps(sites) + phi = deepcopy(psi) + xi = psi + phi + @test inner(xi, xi) ≈ 4.0 * inner(psi, psi) + # sum of many MPSs + Ks = [random_mps(sites) for i in 1:3] + K12 = Ks[1] + Ks[2] + K123 = K12 + Ks[3] + @test inner(sum(Ks), K123) ≈ inner(K123, K123) + + χ1 = 2 + χ2 = 3 + ψ1 = random_mps(sites; linkdims = χ1) + ψ2 = 0.0 * random_mps(sites; linkdims = χ2) + + ϕ1 = +(ψ1, ψ2; alg = "densitymatrix", cutoff = nothing) + for j in 2:7 + @test linkdim(ϕ1, j) == χ1 + χ2 + end + @test inner(ϕ1, ψ1) + inner(ϕ1, ψ2) ≈ inner(ϕ1, ϕ1) - conserve_qns = true + ϕ2 = +(ψ1, ψ2; alg = "directsum") + for j in 1:8 + @test linkdim(ϕ2, j) == χ1 + χ2 + end + @test inner(ϕ2, ψ1) + inner(ϕ2, ψ2) ≈ inner(ϕ2, ϕ2) + end - s = siteinds("S=1/2", 20; conserve_qns=conserve_qns) - state = n -> isodd(n) ? "↑" : "↓" + @testset "+ MPS with coefficients" begin + Random.seed!(1234) - ψ₁ = random_mps(s, state; linkdims=4) - ψ₂ = random_mps(s, state; linkdims=4) - ψ₃ = random_mps(s, state; linkdims=4) + conserve_qns = true - ψ = ψ₁ + ψ₂ + s = siteinds("S=1/2", 20; conserve_qns = conserve_qns) + state = n -> isodd(n) ? "↑" : "↓" - @test inner(ψ, ψ) ≈ inner_add(ψ₁, ψ₂) - @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) + ψ₁ = random_mps(s, state; linkdims = 4) + ψ₂ = random_mps(s, state; linkdims = 4) + ψ₃ = random_mps(s, state; linkdims = 4) - ψ = +(ψ₁, ψ₂; cutoff=0.0) + ψ = ψ₁ + ψ₂ - @test_throws ErrorException ψ₁ + ψ₂' + @test inner(ψ, ψ) ≈ inner_add(ψ₁, ψ₂) + @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) - @test inner(ψ, ψ) ≈ inner_add(ψ₁, ψ₂) - @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) + ψ = +(ψ₁, ψ₂; cutoff = 0.0) - ψ = ψ₁ + (-ψ₂) + @test_throws ErrorException ψ₁ + ψ₂' - @test inner(ψ, ψ) ≈ inner_add((1, ψ₁), (-1, ψ₂)) - @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) + @test inner(ψ, ψ) ≈ inner_add(ψ₁, ψ₂) + @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) - α₁ = 2.2 - α₂ = -4.1 - ψ = +(α₁ * ψ₁, α₂ * ψ₂; cutoff=1e-8) + ψ = ψ₁ + (-ψ₂) - @test inner(ψ, ψ) ≈ inner_add((α₁, ψ₁), (α₂, ψ₂)) - @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) + @test inner(ψ, ψ) ≈ inner_add((1, ψ₁), (-1, ψ₂)) + @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) - α₁ = 2 + 3im - α₂ = -4 + 1im - ψ = α₁ * ψ₁ + α₂ * ψ₂ + α₁ = 2.2 + α₂ = -4.1 + ψ = +(α₁ * ψ₁, α₂ * ψ₂; cutoff = 1.0e-8) - @test inner(ψ, ψ) ≈ inner_add((α₁, ψ₁), (α₂, ψ₂)) - @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) + @test inner(ψ, ψ) ≈ inner_add((α₁, ψ₁), (α₂, ψ₂)) + @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) - α₁ = 2 + 3im - α₂ = -4 + 1im - ψ = α₁ * ψ₁ + α₂ * ψ₂ + ψ₃ + α₁ = 2 + 3im + α₂ = -4 + 1im + ψ = α₁ * ψ₁ + α₂ * ψ₂ - @test inner(ψ, ψ) ≈ inner_add((α₁, ψ₁), (α₂, ψ₂), ψ₃) - @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) + maxlinkdim(ψ₃) + @test inner(ψ, ψ) ≈ inner_add((α₁, ψ₁), (α₂, ψ₂)) + @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) - ψ = ψ₁ - ψ₂ + α₁ = 2 + 3im + α₂ = -4 + 1im + ψ = α₁ * ψ₁ + α₂ * ψ₂ + ψ₃ - @test inner(ψ, ψ) ≈ inner_add(ψ₁, (-1, ψ₂)) - @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) - end + @test inner(ψ, ψ) ≈ inner_add((α₁, ψ₁), (α₂, ψ₂), ψ₃) + @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) + maxlinkdim(ψ₃) - sites = siteinds(2, 10) - psi = MPS(sites) - @test length(psi) == 10 # just make sure this works - @test length(siteinds(psi)) == length(psi) + ψ = ψ₁ - ψ₂ - psi = random_mps(sites) - l0s = linkinds(psi) - orthogonalize!(psi, length(psi) - 1) - ls = linkinds(psi) - for (l0, l) in zip(l0s, ls) - @test tags(l0) == tags(l) - end - @test ITensorMPS.leftlim(psi) == length(psi) - 2 - @test ITensorMPS.rightlim(psi) == length(psi) - orthogonalize!(psi, 2) - @test ITensorMPS.leftlim(psi) == 1 - @test ITensorMPS.rightlim(psi) == 3 - psi = random_mps(sites) - ITensorMPS.setrightlim!(psi, length(psi) + 1) # do this to test qr - # from rightmost tensor - orthogonalize!(psi, div(length(psi), 2)) - @test ITensorMPS.leftlim(psi) == div(length(psi), 2) - 1 - @test ITensorMPS.rightlim(psi) == div(length(psi), 2) + 1 + @test inner(ψ, ψ) ≈ inner_add(ψ₁, (-1, ψ₂)) + @test maxlinkdim(ψ) ≤ maxlinkdim(ψ₁) + maxlinkdim(ψ₂) + end - @test isnothing(linkind(MPS(fill(ITensor(), length(psi)), 0, length(psi) + 1), 1)) + sites = siteinds(2, 10) + psi = MPS(sites) + @test length(psi) == 10 # just make sure this works + @test length(siteinds(psi)) == length(psi) - @testset "replacebond!" begin - # make sure factorization preserves the bond index tags psi = random_mps(sites) - phi = psi[1] * psi[2] - bondindtags = tags(linkind(psi, 1)) - replacebond!(psi, 1, phi) - @test tags(linkind(psi, 1)) == bondindtags - - # check that replacebond! updates llim and rlim properly - orthogonalize!(psi, 5) - phi = psi[5] * psi[6] - replacebond!(psi, 5, phi; ortho="left") - @test ITensorMPS.leftlim(psi) == 5 - @test ITensorMPS.rightlim(psi) == 7 - - phi = psi[5] * psi[6] - replacebond!(psi, 5, phi; ortho="right") - @test ITensorMPS.leftlim(psi) == 4 - @test ITensorMPS.rightlim(psi) == 6 - - ITensorMPS.setleftlim!(psi, 3) - ITensorMPS.setrightlim!(psi, 7) - phi = psi[5] * psi[6] - replacebond!(psi, 5, phi; ortho="left") - @test ITensorMPS.leftlim(psi) == 3 - @test ITensorMPS.rightlim(psi) == 7 - - # check that replacebond! runs with svd kwargs + l0s = linkinds(psi) + orthogonalize!(psi, length(psi) - 1) + ls = linkinds(psi) + for (l0, l) in zip(l0s, ls) + @test tags(l0) == tags(l) + end + @test ITensorMPS.leftlim(psi) == length(psi) - 2 + @test ITensorMPS.rightlim(psi) == length(psi) + orthogonalize!(psi, 2) + @test ITensorMPS.leftlim(psi) == 1 + @test ITensorMPS.rightlim(psi) == 3 psi = random_mps(sites) - phi = psi[1] * psi[2] - replacebond!(psi, 1, phi; ortho="left", which_decomp="svd", use_relative_cutoff=true) - phi = psi[5] * psi[6] - replacebond!( - psi, - 5, - phi; - ortho="right", - which_decomp="svd", - use_absolute_cutoff=true, - min_blockdim=2, - ) - end + ITensorMPS.setrightlim!(psi, length(psi) + 1) # do this to test qr + # from rightmost tensor + orthogonalize!(psi, div(length(psi), 2)) + @test ITensorMPS.leftlim(psi) == div(length(psi), 2) - 1 + @test ITensorMPS.rightlim(psi) == div(length(psi), 2) + 1 + + @test isnothing(linkind(MPS(fill(ITensor(), length(psi)), 0, length(psi) + 1), 1)) + + @testset "replacebond!" begin + # make sure factorization preserves the bond index tags + psi = random_mps(sites) + phi = psi[1] * psi[2] + bondindtags = tags(linkind(psi, 1)) + replacebond!(psi, 1, phi) + @test tags(linkind(psi, 1)) == bondindtags + + # check that replacebond! updates llim and rlim properly + orthogonalize!(psi, 5) + phi = psi[5] * psi[6] + replacebond!(psi, 5, phi; ortho = "left") + @test ITensorMPS.leftlim(psi) == 5 + @test ITensorMPS.rightlim(psi) == 7 + + phi = psi[5] * psi[6] + replacebond!(psi, 5, phi; ortho = "right") + @test ITensorMPS.leftlim(psi) == 4 + @test ITensorMPS.rightlim(psi) == 6 + + ITensorMPS.setleftlim!(psi, 3) + ITensorMPS.setrightlim!(psi, 7) + phi = psi[5] * psi[6] + replacebond!(psi, 5, phi; ortho = "left") + @test ITensorMPS.leftlim(psi) == 3 + @test ITensorMPS.rightlim(psi) == 7 + + # check that replacebond! runs with svd kwargs + psi = random_mps(sites) + phi = psi[1] * psi[2] + replacebond!(psi, 1, phi; ortho = "left", which_decomp = "svd", use_relative_cutoff = true) + phi = psi[5] * psi[6] + replacebond!( + psi, + 5, + phi; + ortho = "right", + which_decomp = "svd", + use_absolute_cutoff = true, + min_blockdim = 2, + ) + end end @testset "orthogonalize! with QNs" begin - sites = siteinds("S=1/2", 8; conserve_qns=true) - init_state = [isodd(n) ? "Up" : "Dn" for n in 1:length(sites)] - psi0 = MPS(sites, init_state) - orthogonalize!(psi0, 4) - @test ITensorMPS.leftlim(psi0) == 3 - @test ITensorMPS.rightlim(psi0) == 5 + sites = siteinds("S=1/2", 8; conserve_qns = true) + init_state = [isodd(n) ? "Up" : "Dn" for n in 1:length(sites)] + psi0 = MPS(sites, init_state) + orthogonalize!(psi0, 4) + @test ITensorMPS.leftlim(psi0) == 3 + @test ITensorMPS.rightlim(psi0) == 5 end # Helper function for making MPS -function basicRandomMPS(N::Int; dim=4) - sites = [Index(2, "Site") for n in 1:N] - M = MPS(sites) - links = [Index(dim, "n=$(n-1),Link") for n in 1:(N + 1)] - for n in 1:N - M[n] = random_itensor(links[n], sites[n], links[n + 1]) - end - M[1] *= delta(links[1]) - M[N] *= delta(links[N + 1]) - M[1] /= sqrt(inner(M, M)) - return M +function basicRandomMPS(N::Int; dim = 4) + sites = [Index(2, "Site") for n in 1:N] + M = MPS(sites) + links = [Index(dim, "n=$(n - 1),Link") for n in 1:(N + 1)] + for n in 1:N + M[n] = random_itensor(links[n], sites[n], links[n + 1]) + end + M[1] *= delta(links[1]) + M[N] *= delta(links[N + 1]) + M[1] /= sqrt(inner(M, M)) + return M end -function test_correlation_matrix(psi::MPS, ops::Vector{Tuple{String,String}}; kwargs...) - N = length(psi) - s = siteinds(psi) - for op in ops - Cpm = correlation_matrix(psi, op[1], op[2]; kwargs...) - # Check using OpSum: - Copsum = 0.0 * Cpm - for i in 1:N, j in 1:N - a = OpSum() - a += op[1], i, op[2], j - Copsum[i, j] = inner(psi', MPO(a, s), psi) - end - @test Cpm ≈ Copsum rtol = 1E-11 +function test_correlation_matrix(psi::MPS, ops::Vector{Tuple{String, String}}; kwargs...) + N = length(psi) + s = siteinds(psi) + for op in ops + Cpm = correlation_matrix(psi, op[1], op[2]; kwargs...) + # Check using OpSum: + Copsum = 0.0 * Cpm + for i in 1:N, j in 1:N + a = OpSum() + a += op[1], i, op[2], j + Copsum[i, j] = inner(psi', MPO(a, s), psi) + end + @test Cpm ≈ Copsum rtol = 1.0e-11 - PM = expect(psi, op[1] * " * " * op[2]) - @test norm(PM - diag(Cpm)) < 1E-8 - end + PM = expect(psi, op[1] * " * " * op[2]) + @test norm(PM - diag(Cpm)) < 1.0e-8 + end + return end @testset "MPS gauging and truncation" begin - @testset "orthogonalize! method" begin - c = 12 - M = basicRandomMPS(30) - orthogonalize!(M, c) - - @test ITensorMPS.leftlim(M) == c - 1 - @test ITensorMPS.rightlim(M) == c + 1 - - # Test for left-orthogonality - L = M[1] * prime(M[1], "Link") - l = linkind(M, 1) - @test norm(L - delta(l, l')) < 1E-12 - for j in 2:(c - 1) - L = L * M[j] * prime(M[j], "Link") - l = linkind(M, j) - @test norm(L - delta(l, l')) < 1E-12 - end + @testset "orthogonalize! method" begin + c = 12 + M = basicRandomMPS(30) + orthogonalize!(M, c) + + @test ITensorMPS.leftlim(M) == c - 1 + @test ITensorMPS.rightlim(M) == c + 1 + + # Test for left-orthogonality + L = M[1] * prime(M[1], "Link") + l = linkind(M, 1) + @test norm(L - delta(l, l')) < 1.0e-12 + for j in 2:(c - 1) + L = L * M[j] * prime(M[j], "Link") + l = linkind(M, j) + @test norm(L - delta(l, l')) < 1.0e-12 + end - # Test for right-orthogonality - R = M[length(M)] * prime(M[length(M)], "Link") - r = linkind(M, length(M) - 1) - @test norm(R - delta(r, r')) < 1E-12 - for j in reverse((c + 1):(length(M) - 1)) - R = R * M[j] * prime(M[j], "Link") - r = linkind(M, j - 1) - @test norm(R - delta(r, r')) < 1E-12 - end + # Test for right-orthogonality + R = M[length(M)] * prime(M[length(M)], "Link") + r = linkind(M, length(M) - 1) + @test norm(R - delta(r, r')) < 1.0e-12 + for j in reverse((c + 1):(length(M) - 1)) + R = R * M[j] * prime(M[j], "Link") + r = linkind(M, j - 1) + @test norm(R - delta(r, r')) < 1.0e-12 + end - @test norm(M[c]) ≈ 1.0 - end + @test norm(M[c]) ≈ 1.0 + end - @testset "truncate! method" begin - M = basicRandomMPS(10; dim=10) - M0 = copy(M) - truncate!(M; maxdim=5) + @testset "truncate! method" begin + M = basicRandomMPS(10; dim = 10) + M0 = copy(M) + truncate!(M; maxdim = 5) + + @test ITensorMPS.rightlim(M) == 2 + + # Test for right-orthogonality + R = M[length(M)] * prime(M[length(M)], "Link") + r = linkind(M, length(M) - 1) + @test norm(R - delta(r, r')) < 1.0e-12 + for j in reverse(2:(length(M) - 1)) + R = R * M[j] * prime(M[j], "Link") + r = linkind(M, j - 1) + @test norm(R - delta(r, r')) < 1.0e-12 + end - @test ITensorMPS.rightlim(M) == 2 + @test inner(M, M0) > 0.1 + end - # Test for right-orthogonality - R = M[length(M)] * prime(M[length(M)], "Link") - r = linkind(M, length(M) - 1) - @test norm(R - delta(r, r')) < 1E-12 - for j in reverse(2:(length(M) - 1)) - R = R * M[j] * prime(M[j], "Link") - r = linkind(M, j - 1) - @test norm(R - delta(r, r')) < 1E-12 + @testset "truncate! with site_range" begin + M = basicRandomMPS(10; dim = 10) + truncate!(M; site_range = 3:7, maxdim = 2) + @test linkdims(M) == [2, 4, 2, 2, 2, 2, 8, 4, 2] end - @test inner(M, M0) > 0.1 - end - - @testset "truncate! with site_range" begin - M = basicRandomMPS(10; dim=10) - truncate!(M; site_range=3:7, maxdim=2) - @test linkdims(M) == [2, 4, 2, 2, 2, 2, 8, 4, 2] - end - - @testset "truncate! with callback" begin - nsites = 10 - nbonds = nsites - 1 - s = siteinds("S=1/2", nsites) - mps_ = random_mps(s; linkdims=10) - truncation_errors = ones(nbonds) * -1.0 - function _callback(; link, truncation_error) - bond_no = last(link) - truncation_errors[bond_no] = truncation_error - return nothing + @testset "truncate! with callback" begin + nsites = 10 + nbonds = nsites - 1 + s = siteinds("S=1/2", nsites) + mps_ = random_mps(s; linkdims = 10) + truncation_errors = ones(nbonds) * -1.0 + function _callback(; link, truncation_error) + bond_no = last(link) + truncation_errors[bond_no] = truncation_error + return nothing + end + truncate!(mps_; maxdim = 3, cutoff = 1.0e-3, callback = _callback) + @test all(truncation_errors .>= 0.0) end - truncate!(mps_; maxdim=3, cutoff=1E-3, callback=_callback) - @test all(truncation_errors .>= 0.0) - end end @testset "Other MPS methods" begin - @testset "sample! method" begin - sites = [Index(3, "Site,n=$n") for n in 1:10] - psi = random_mps(sites; linkdims=3) - nrm2 = inner(psi, psi) - psi[1] *= (1.0 / sqrt(nrm2)) + @testset "sample! method" begin + sites = [Index(3, "Site,n=$n") for n in 1:10] + psi = random_mps(sites; linkdims = 3) + nrm2 = inner(psi, psi) + psi[1] *= (1.0 / sqrt(nrm2)) - s = sample!(psi) + s = sample!(psi) - @test length(s) == length(psi) - for n in 1:length(psi) - @test 1 <= s[n] <= 3 - end + @test length(s) == length(psi) + for n in 1:length(psi) + @test 1 <= s[n] <= 3 + end - # Throws becase not orthogonalized to site 1: - orthogonalize!(psi, 3) - @test_throws ErrorException sample(psi) - - # Throws because not normalized - orthogonalize!(psi, 1) - psi[1] *= (5.0 / norm(psi[1])) - @test_throws ErrorException sample(psi) - - # Works when ortho & normalized: - orthogonalize!(psi, 1) - psi[1] *= (1.0 / norm(psi[1])) - s = sample(psi) - @test length(s) == length(psi) - end - - @testset "random_mps with chi > 1" begin - chi = 8 - sites = siteinds(2, 20) - M = random_mps(sites; linkdims=chi) - - @test ITensorMPS.leftlim(M) == 0 - @test ITensorMPS.rightlim(M) == 2 - - @test norm(M[1]) ≈ 1.0 - - @test maxlinkdim(M) == chi - - # Test for right-orthogonality - R = M[length(M)] * prime(M[length(M)], "Link") - r = linkind(M, length(M) - 1) - @test norm(R - delta(r, r')) < 1E-10 - for j in reverse(2:(length(M) - 1)) - R = R * M[j] * prime(M[j], "Link") - r = linkind(M, j - 1) - @test norm(R - delta(r, r')) < 1E-10 - end + # Throws becase not orthogonalized to site 1: + orthogonalize!(psi, 3) + @test_throws ErrorException sample(psi) - # Complex case - Mc = random_mps(sites; linkdims=chi) - @test inner(Mc, Mc) ≈ 1.0 + 0.0im - end - - @testset "random_mps from initial state (QN case)" begin - chi = 8 - sites = siteinds("S=1/2", 20; conserve_qns=true) - - # Make flux-zero random MPS - state = [isodd(n) ? 1 : 2 for n in 1:length(sites)] - M = random_mps(sites, state; linkdims=chi) - @test flux(M) == QN("Sz", 0) - - @test ITensorMPS.leftlim(M) == 0 - @test ITensorMPS.rightlim(M) == 2 - - @test norm(M[1]) ≈ 1.0 - @test inner(M, M) ≈ 1.0 - - @test maxlinkdim(M) == chi - - # Test making random MPS with different flux - state[1] = 2 - M = random_mps(sites, state; linkdims=chi) - @test flux(M) == QN("Sz", -2) - state[3] = 2 - M = random_mps(sites, state; linkdims=chi) - @test flux(M) == QN("Sz", -4) - end - - @testset "expect Function" begin - N = 8 - s = siteinds("S=1/2", N) - psi = random_mps(ComplexF64, s; linkdims=2) - - eSz = zeros(N) - eSx = zeros(N) - for j in 1:N - orthogonalize!(psi, j) - eSz[j] = real(scalar(dag(prime(psi[j], "Site")) * op("Sz", s[j]) * psi[j])) - eSx[j] = real(scalar(dag(prime(psi[j], "Site")) * op("Sx", s[j]) * psi[j])) - end + # Throws because not normalized + orthogonalize!(psi, 1) + psi[1] *= (5.0 / norm(psi[1])) + @test_throws ErrorException sample(psi) - res = expect(psi, "Sz") - @test res ≈ eSz - - res = expect(psi, "Sz"; sites=2:4) - @test res ≈ eSz[2:4] - - res = expect(psi, "Sz"; sites=[2, 4, 8]) - @test res[1] ≈ eSz[2] - @test res[2] ≈ eSz[4] - @test res[3] ≈ eSz[8] - - res = expect(psi, "Sz", "Sx") - @test res[1] ≈ eSz - @test res[2] ≈ eSx - - res = expect(psi, "Sz"; sites=3) - @test res isa Float64 - @test res ≈ eSz[3] - - res = expect(psi, "Sz", "Sx"; sites=3) - @test res isa Tuple{Float64,Float64} - @test res[1] ≈ eSz[3] - @test res[2] ≈ eSx[3] - - res = expect(psi, ("Sz", "Sx")) - @test res isa Tuple{Vector{Float64},Vector{Float64}} - @test res[1] ≈ eSz - @test res[2] ≈ eSx - - res = expect(psi, ["Sz" "Sx"; "Sx" "Sz"]; sites=3:7) - @test res isa Matrix{Vector{Float64}} - @test res[1, 1] ≈ eSz[3:7] - @test res[2, 1] ≈ eSx[3:7] - @test res[1, 2] ≈ eSx[3:7] - @test res[2, 2] ≈ eSz[3:7] - - # Test that passing zero-norm MPS leads to an error - # (expect not well-defined in that case) - psi0 = copy(psi) - psi0[1] *= zero(Bool) - @test iszero(norm(psi0)) - @test_throws ErrorException expect(psi0, "Sz") - end - - @testset "Expected value and Correlations" begin - m = 2 - - # Non-fermionic real case - spin system with QNs (very restrictive on allowed ops) - s = siteinds("S=1/2", 4; conserve_qns=true) - psi = random_mps(s, n -> isodd(n) ? "Up" : "Dn"; linkdims=m) - test_correlation_matrix(psi, [("S-", "S+"), ("S+", "S-")]) - - @test correlation_matrix(psi, [1/2 0; 0 -1/2], [1/2 0; 0 -1/2]) ≈ - correlation_matrix(psi, "Sz", "Sz") - @test expect(psi, [1/2 0; 0 -1/2]) ≈ expect(psi, "Sz") - @test all(expect(psi, [1/2 0; 0 -1/2], [1/2 0; 0 -1/2]) .≈ expect(psi, "Sz", "Sz")) - @test expect(psi, [[1/2 0; 0 -1/2], [1/2 0; 0 -1/2]]) ≈ expect(psi, ["Sz", "Sz"]) - - s = siteinds("S=1/2", length(s); conserve_qns=false) - psi = random_mps(s, n -> isodd(n) ? "Up" : "Dn"; linkdims=m) - test_correlation_matrix( - psi, - [ - ("Sz", "Sz"), - ("iSy", "iSy"), - ("Sx", "Sx"), - ("Sz", "Sx"), - ("S+", "S+"), - ("S-", "S+"), - ("S+", "S-"), - ("Sx", "S+"), - ("iSy", "iSy"), - ("Sx", "iSy"), - ], - ) - - test_correlation_matrix(psi, [("Sz", "Sz")]; ishermitian=false) - test_correlation_matrix(psi, [("Sz", "Sz")]; ishermitian=true) - test_correlation_matrix(psi, [("Sz", "Sx")]; ishermitian=false) - # This will fail becuase Sz*Sx is not hermitian, so the below diagonla corr matrix - # need to be calculated explicitely. - #test_correlation_matrix(psi,[("Sz", "Sx")];ishermitian=true) - - #Test sites feature - s = siteinds("S=1/2", 8; conserve_qns=false) - psi = random_mps(s, n -> isodd(n) ? "Up" : "Dn"; linkdims=m) - PM = expect(psi, "S+ * S-") - Cpm = correlation_matrix(psi, "S+", "S-") - range = 3:7 - Cpm37 = correlation_matrix(psi, "S+", "S-"; sites=range) - @test norm(Cpm37 - Cpm[range, range]) < 1E-8 - - @test norm(PM[range] - expect(psi, "S+ * S-"; sites=range)) < 1E-8 - - # With start_site, end_site arguments: - s = siteinds("S=1/2", 8) - psi = random_mps(ComplexF64, s; linkdims=m) - ss, es = 3, 6 - Nb = es - ss + 1 - Cpm = correlation_matrix(psi, "S+", "S-"; sites=ss:es) - Czz = correlation_matrix(psi, "Sz", "Sz"; sites=ss:es) - @test size(Cpm) == (Nb, Nb) - # Check using OpSum: - for i in ss:es, j in i:es - a = OpSum() - a += "S+", i, "S-", j - @test inner(psi', MPO(a, s), psi) ≈ Cpm[i - ss + 1, j - ss + 1] + # Works when ortho & normalized: + orthogonalize!(psi, 1) + psi[1] *= (1.0 / norm(psi[1])) + s = sample(psi) + @test length(s) == length(psi) end - # Electron case - s = siteinds("Electron", 8) - psi = random_mps(s; linkdims=m) - test_correlation_matrix( - psi, [("Cdagup", "Cup"), ("Cup", "Cdagup"), ("Cup", "Cdn"), ("Cdagdn", "Cdn")] - ) - - s = siteinds("Electron", 8; conserve_qns=false) - psi = random_mps(s; linkdims=m) - test_correlation_matrix( - psi, - [ - ("Ntot", "Ntot"), - ("Nup", "Nup"), - ("Ndn", "Ndn"), - ("Cdagup", "Cup"), - ("Adagup", "Aup"), - ("Cdn", "Cdagdn"), - ("Adn", "Adagdn"), - ("Sz", "Sz"), - ("S+", "S-"), - ], - ) - # can't test ,("Cdn","Cdn") yet, because OpSum to MPO thinks this is antisymmetric - - #trigger unsupported error - let err = nothing - try - test_correlation_matrix(psi, [("Cup", "Aup")]) - catch err - end - - @test err isa Exception - @test sprint(showerror, err) == - "correlation_matrix: Mixed fermionic and bosonic operators are not supported yet." - end + @testset "random_mps with chi > 1" begin + chi = 8 + sites = siteinds(2, 20) + M = random_mps(sites; linkdims = chi) - # Fermion case - s = siteinds("Fermion", 8) - psi = random_mps(s; linkdims=m) - test_correlation_matrix(psi, [("N", "N"), ("Cdag", "C"), ("C", "Cdag"), ("C", "C")]) - - s = siteinds("Fermion", 8; conserve_qns=false) - psi = random_mps(s; linkdims=m) - test_correlation_matrix(psi, [("N", "N"), ("Cdag", "C"), ("C", "Cdag"), ("C", "C")]) - - # - # Test non-contiguous sites input - # - C = correlation_matrix(psi, "N", "N") - non_contiguous = [1, 3, 8] - Cs = correlation_matrix(psi, "N", "N"; sites=non_contiguous) - for (ni, i) in enumerate(non_contiguous), (nj, j) in enumerate(non_contiguous) - @test Cs[ni, nj] ≈ C[i, j] - end + @test ITensorMPS.leftlim(M) == 0 + @test ITensorMPS.rightlim(M) == 2 + + @test norm(M[1]) ≈ 1.0 - C2 = correlation_matrix(psi, "N", "N"; sites=2) - @test C2 isa Matrix - @test C2[1, 1] ≈ C[2, 2] - end #testset - - @testset "correlation_matrix with dangling bonds" begin - l01 = Index(3) - l̃01 = sim(l01) - l12 = Index(3) - l23 = Index(3) - l34 = Index(3) - l̃34 = sim(l34) - s1 = Index(2, "Qubit") - s2 = Index(2, "Qubit") - s3 = Index(2, "Qubit") - b01 = random_itensor(l01, l̃01) - A1 = random_itensor(l̃01, s1, l12) - A2 = random_itensor(l12, s2, l23) - A3 = random_itensor(l23, s3, l̃34) - b34 = random_itensor(l̃34, l34) - ψ = MPS([b01, A1, A2, A3, b34]) - sites = 1:3 - C = correlation_matrix(ψ, "Z", "Z"; sites=(sites .+ 1)) - siteinds = [l01, s1, s2, s3, l34] - ψψ = inner(ψ, ψ) - zz = map(Iterators.product(sites .+ 1, sites .+ 1)) do I - i, j = I - ZiZj = MPO(OpSum() + ("Z", i, "Z", j), siteinds) - return inner(ψ', ZiZj, ψ) / ψψ + @test maxlinkdim(M) == chi + + # Test for right-orthogonality + R = M[length(M)] * prime(M[length(M)], "Link") + r = linkind(M, length(M) - 1) + @test norm(R - delta(r, r')) < 1.0e-10 + for j in reverse(2:(length(M) - 1)) + R = R * M[j] * prime(M[j], "Link") + r = linkind(M, j - 1) + @test norm(R - delta(r, r')) < 1.0e-10 + end + + # Complex case + Mc = random_mps(sites; linkdims = chi) + @test inner(Mc, Mc) ≈ 1.0 + 0.0im end - for I in eachindex(C) - @test C[I] ≈ zz[I] + + @testset "random_mps from initial state (QN case)" begin + chi = 8 + sites = siteinds("S=1/2", 20; conserve_qns = true) + + # Make flux-zero random MPS + state = [isodd(n) ? 1 : 2 for n in 1:length(sites)] + M = random_mps(sites, state; linkdims = chi) + @test flux(M) == QN("Sz", 0) + + @test ITensorMPS.leftlim(M) == 0 + @test ITensorMPS.rightlim(M) == 2 + + @test norm(M[1]) ≈ 1.0 + @test inner(M, M) ≈ 1.0 + + @test maxlinkdim(M) == chi + + # Test making random MPS with different flux + state[1] = 2 + M = random_mps(sites, state; linkdims = chi) + @test flux(M) == QN("Sz", -2) + state[3] = 2 + M = random_mps(sites, state; linkdims = chi) + @test flux(M) == QN("Sz", -4) end - end - - @testset "expect regression test for in-place modification of input MPS" begin - s = siteinds("S=1/2", 5) - psi = random_mps(s; linkdims=3) - orthogonalize!(psi, 1) - expect_init = expect(psi, "Sz") - norm_scale = 10 - psi[1] *= norm_scale - @test ortho_lims(psi) == 1:1 - @test norm(psi) ≈ norm_scale - expect_Sz = expect(psi, "Sz") - @test all(≤(1 / 2), expect_Sz) - @test expect_Sz ≈ expect_init - @test ortho_lims(psi) == 1:1 - @test norm(psi) ≈ norm_scale - end - - @testset "correlation_matrix regression test for in-place modification of input MPS" begin - s = siteinds("S=1/2", 5) - psi = random_mps(s; linkdims=3) - orthogonalize!(psi, 1) - correlation_matrix_init = correlation_matrix(psi, "Sz", "Sz") - norm_scale = 10 - psi[1] *= norm_scale - @test ortho_lims(psi) == 1:1 - @test norm(psi) ≈ norm_scale - correlation_matrix_SzSz = correlation_matrix(psi, "Sz", "Sz") - @test all(≤(1 / 2), correlation_matrix_SzSz) - @test correlation_matrix_SzSz ≈ correlation_matrix_init - @test ortho_lims(psi) == 1:1 - @test norm(psi) ≈ norm_scale - end - - @testset "swapbondsites" begin - sites = siteinds("S=1/2", 5) - ψ0 = random_mps(sites) - ψ = replacebond(ψ0, 3, ψ0[3] * ψ0[4]; swapsites=true, cutoff=1e-15) - @test siteind(ψ, 1) == siteind(ψ0, 1) - @test siteind(ψ, 2) == siteind(ψ0, 2) - @test siteind(ψ, 4) == siteind(ψ0, 3) - @test siteind(ψ, 3) == siteind(ψ0, 4) - @test siteind(ψ, 5) == siteind(ψ0, 5) - @test prod(ψ) ≈ prod(ψ0) - @test maxlinkdim(ψ) == 1 - - ψ = swapbondsites(ψ0, 4; cutoff=1e-15) - @test siteind(ψ, 1) == siteind(ψ0, 1) - @test siteind(ψ, 2) == siteind(ψ0, 2) - @test siteind(ψ, 3) == siteind(ψ0, 3) - @test siteind(ψ, 5) == siteind(ψ0, 4) - @test siteind(ψ, 4) == siteind(ψ0, 5) - @test prod(ψ) ≈ prod(ψ0) - @test maxlinkdim(ψ) == 1 - end - - @testset "map!" begin - s = siteinds("S=½", 5) - M0 = MPS(s, "↑") - - # Test map! with limits getting set - M = orthogonalize(M0, 1) - @test ITensorMPS.leftlim(M) == 0 - @test ITensorMPS.rightlim(M) == 2 - map!(prime, M) - @test ITensorMPS.leftlim(M) == 0 - @test ITensorMPS.rightlim(M) == length(M0) + 1 - - # Test map! without limits getting set - M = orthogonalize(M0, 1) - map!(prime, M; set_limits=false) - @test ITensorMPS.leftlim(M) == 0 - @test ITensorMPS.rightlim(M) == 2 - - # Test prime! with limits getting set - M = orthogonalize(M0, 1) - @test ITensorMPS.leftlim(M) == 0 - @test ITensorMPS.rightlim(M) == 2 - prime!(M; set_limits=true) - @test ITensorMPS.leftlim(M) == 0 - @test ITensorMPS.rightlim(M) == length(M0) + 1 - - # Test prime! without limits getting set - M = orthogonalize(M0, 1) - prime!(M) - @test ITensorMPS.leftlim(M) == 0 - @test ITensorMPS.rightlim(M) == 2 - end - - @testset "setindex!(::MPS, _, ::Colon)" begin - s = siteinds("S=½", 4) - ψ = random_mps(s) - ϕ = MPS(s, "↑") - orthogonalize!(ϕ, 1) - ψ[:] = ϕ - @test ITensorMPS.orthocenter(ψ) == 1 - @test inner(ψ, ϕ) ≈ 1 - - ψ = random_mps(s) - ϕ = MPS(s, "↑") - orthogonalize!(ϕ, 1) - ψ[:] = ITensorMPS.data(ϕ) - @test ITensorMPS.leftlim(ψ) == 0 - @test ITensorMPS.rightlim(ψ) == length(ψ) + 1 - @test inner(ψ, ϕ) ≈ 1 - end - - @testset "findsite[s](::MPS/MPO, is)" begin - s = siteinds("S=1/2", 5) - ψ = random_mps(s) - l = linkinds(ψ) - - A = random_itensor(s[4]', s[2]', dag(s[4]), dag(s[2])) - - @test findsite(ψ, s[3]) == 3 - @test findsite(ψ, (s[3], s[5])) == 3 - @test findsite(ψ, l[2]) == 2 - @test findsite(ψ, A) == 2 - - @test findsites(ψ, s[3]) == [3] - @test findsites(ψ, (s[4], s[1])) == [1, 4] - @test findsites(ψ, l[2]) == [2, 3] - @test findsites(ψ, (l[2], l[3])) == [2, 3, 4] - @test findsites(ψ, A) == [2, 4] - - M = random_mpo(s) - lM = linkinds(M) - - @test findsite(M, s[4]) == 4 - @test findsite(M, s[4]') == 4 - @test findsite(M, (s[4]', s[4])) == 4 - @test findsite(M, (s[4]', s[3])) == 3 - @test findsite(M, lM[2]) == 2 - @test findsite(M, A) == 2 - - @test findsites(M, s[4]) == [4] - @test findsites(M, s[4]') == [4] - @test findsites(M, (s[4]', s[4])) == [4] - @test findsites(M, (s[4]', s[3])) == [3, 4] - @test findsites(M, (lM[2], lM[3])) == [2, 3, 4] - @test findsites(M, A) == [2, 4] - end - - @testset "[first]siteind[s](::MPS/MPO, j::Int)" begin - s = siteinds("S=1/2", 5) - ψ = random_mps(s) - @test siteind(first, ψ, 3) == s[3] - @test siteind(ψ, 4) == s[4] - @test isnothing(siteind(ψ, 4; plev=1)) - @test siteinds(ψ, 3) == IndexSet(s[3]) - @test siteinds(ψ, 3; plev=1) == IndexSet() - - M = random_mpo(s) - @test noprime(siteind(first, M, 4)) == s[4] - @test siteind(first, M, 4; plev=0) == s[4] - @test siteind(first, M, 4; plev=1) == s[4]' - @test siteind(M, 4) == s[4] - @test siteind(M, 4; plev=0) == s[4] - @test siteind(M, 4; plev=1) == s[4]' - @test isnothing(siteind(M, 4; plev=2)) - @test hassameinds(siteinds(M, 3), (s[3], s[3]')) - @test siteinds(M, 3; plev=1) == IndexSet(s[3]') - @test siteinds(M, 3; plev=0) == IndexSet(s[3]) - @test siteinds(M, 3; tags="n=2") == IndexSet() - end - - @testset "movesites $N sites" for N in 1:4 - s0 = siteinds("S=1/2", N) - for perm in permutations(1:N) - s = s0[perm] - ψ = MPS(s, rand(("↑", "↓"), N)) - ns′ = [findfirst(==(i), s0) for i in s] - @test ns′ == perm - ψ′ = movesites(ψ, 1:N .=> ns′; cutoff=1e-15) - if N == 1 - @test maxlinkdim(ψ′) == 1 - else - @test maxlinkdim(ψ′) == 1 - end - for n in 1:N - @test s0[n] == siteind(ψ′, n) - end - @test prod(ψ) ≈ prod(ψ′) + + @testset "expect Function" begin + N = 8 + s = siteinds("S=1/2", N) + psi = random_mps(ComplexF64, s; linkdims = 2) + + eSz = zeros(N) + eSx = zeros(N) + for j in 1:N + orthogonalize!(psi, j) + eSz[j] = real(scalar(dag(prime(psi[j], "Site")) * op("Sz", s[j]) * psi[j])) + eSx[j] = real(scalar(dag(prime(psi[j], "Site")) * op("Sx", s[j]) * psi[j])) + end + + res = expect(psi, "Sz") + @test res ≈ eSz + + res = expect(psi, "Sz"; sites = 2:4) + @test res ≈ eSz[2:4] + + res = expect(psi, "Sz"; sites = [2, 4, 8]) + @test res[1] ≈ eSz[2] + @test res[2] ≈ eSz[4] + @test res[3] ≈ eSz[8] + + res = expect(psi, "Sz", "Sx") + @test res[1] ≈ eSz + @test res[2] ≈ eSx + + res = expect(psi, "Sz"; sites = 3) + @test res isa Float64 + @test res ≈ eSz[3] + + res = expect(psi, "Sz", "Sx"; sites = 3) + @test res isa Tuple{Float64, Float64} + @test res[1] ≈ eSz[3] + @test res[2] ≈ eSx[3] + + res = expect(psi, ("Sz", "Sx")) + @test res isa Tuple{Vector{Float64}, Vector{Float64}} + @test res[1] ≈ eSz + @test res[2] ≈ eSx + + res = expect(psi, ["Sz" "Sx"; "Sx" "Sz"]; sites = 3:7) + @test res isa Matrix{Vector{Float64}} + @test res[1, 1] ≈ eSz[3:7] + @test res[2, 1] ≈ eSx[3:7] + @test res[1, 2] ≈ eSx[3:7] + @test res[2, 2] ≈ eSz[3:7] + + # Test that passing zero-norm MPS leads to an error + # (expect not well-defined in that case) + psi0 = copy(psi) + psi0[1] *= zero(Bool) + @test iszero(norm(psi0)) + @test_throws ErrorException expect(psi0, "Sz") end - end - - @testset "Construct MPS from ITensor" begin - N = 5 - s = siteinds("S=1/2", N) - l = [Index(3, "left_$n") for n in 1:2] - r = [Index(3, "right_$n") for n in 1:2] - - # - # MPS - # - - A = random_itensor(s...) - ψ = MPS(A, s) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == N - @test maxlinkdim(ψ) == 4 - - ψ0 = MPS(s, "↑") - A = prod(ψ0) - ψ = MPS(A, s; cutoff=1e-15) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == N - @test maxlinkdim(ψ) == 1 - - ψ0 = random_mps(s; linkdims=2) - A = prod(ψ0) - ψ = MPS(A, s; cutoff=1e-15, orthocenter=2) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == 2 - @test maxlinkdim(ψ) == 2 - - A = random_itensor(s..., l[1], r[1]) - ψ = MPS(A, s; leftinds=l[1], orthocenter=3) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (l[1], s[1], ls[1])) - @test hassameinds(ψ[N], (r[1], s[N], ls[N - 1])) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == 3 - @test maxlinkdim(ψ) == 12 - - A = random_itensor(s..., l..., r...) - ψ = MPS(A, s; leftinds=l) - ls = linkinds(ψ) - @test hassameinds(ψ[1], (l..., s[1], ls[1])) - @test hassameinds(ψ[N], (r..., s[N], ls[N - 1])) - @test prod(ψ) ≈ A - @test ITensorMPS.orthocenter(ψ) == N - @test maxlinkdim(ψ) == 36 - - # - # Construct from regular Julia tensor - # - Ajt = randn(2, 2, 2, 2, 2) # Julia tensor - ψ = MPS(Ajt, s) - @test prod(ψ) ≈ ITensor(Ajt, s...) - - Ajv = randn(2^N) # Julia vector - ψ = MPS(Ajv, s) - @test prod(ψ) ≈ ITensor(Ajv, s...) - end - - @testset "Set range of MPS tensors" begin - N = 5 - s = siteinds("S=1/2", N) - ψ0 = random_mps(s; linkdims=3) - - ψ = orthogonalize(ψ0, 2) - A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) - randn!(A) - ϕ = MPS(A, s[2:(N - 1)]; orthocenter=1) - ψ[2:(N - 1)] = ϕ - @test prod(ψ) ≈ ψ[1] * A * ψ[N] - @test maxlinkdim(ψ) == 4 - @test ITensorMPS.orthocenter(ψ) == 2 - - ψ = orthogonalize(ψ0, 1) - A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) - randn!(A) - @test_throws AssertionError ψ[2:(N - 1)] = A - - ψ = orthogonalize(ψ0, 2) - A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) - randn!(A) - ψ[2:(N - 1), orthocenter = 3] = A - @test prod(ψ) ≈ ψ[1] * A * ψ[N] - @test maxlinkdim(ψ) == 4 - @test ITensorMPS.orthocenter(ψ) == 3 - end - - @testset "movesites reverse sites" begin - N = 6 - s = siteinds("S=1/2", N) - ψ0 = random_mps(s) - ψ = movesites(ψ0, 1:N .=> reverse(1:N)) - for n in 1:N - @test siteind(ψ, n) == s[N - n + 1] + + @testset "Expected value and Correlations" begin + m = 2 + + # Non-fermionic real case - spin system with QNs (very restrictive on allowed ops) + s = siteinds("S=1/2", 4; conserve_qns = true) + psi = random_mps(s, n -> isodd(n) ? "Up" : "Dn"; linkdims = m) + test_correlation_matrix(psi, [("S-", "S+"), ("S+", "S-")]) + + @test correlation_matrix(psi, [1 / 2 0; 0 -1 / 2], [1 / 2 0; 0 -1 / 2]) ≈ + correlation_matrix(psi, "Sz", "Sz") + @test expect(psi, [1 / 2 0; 0 -1 / 2]) ≈ expect(psi, "Sz") + @test all(expect(psi, [1 / 2 0; 0 -1 / 2], [1 / 2 0; 0 -1 / 2]) .≈ expect(psi, "Sz", "Sz")) + @test expect(psi, [[1 / 2 0; 0 -1 / 2], [1 / 2 0; 0 -1 / 2]]) ≈ expect(psi, ["Sz", "Sz"]) + + s = siteinds("S=1/2", length(s); conserve_qns = false) + psi = random_mps(s, n -> isodd(n) ? "Up" : "Dn"; linkdims = m) + test_correlation_matrix( + psi, + [ + ("Sz", "Sz"), + ("iSy", "iSy"), + ("Sx", "Sx"), + ("Sz", "Sx"), + ("S+", "S+"), + ("S-", "S+"), + ("S+", "S-"), + ("Sx", "S+"), + ("iSy", "iSy"), + ("Sx", "iSy"), + ], + ) + + test_correlation_matrix(psi, [("Sz", "Sz")]; ishermitian = false) + test_correlation_matrix(psi, [("Sz", "Sz")]; ishermitian = true) + test_correlation_matrix(psi, [("Sz", "Sx")]; ishermitian = false) + # This will fail becuase Sz*Sx is not hermitian, so the below diagonla corr matrix + # need to be calculated explicitely. + #test_correlation_matrix(psi,[("Sz", "Sx")];ishermitian=true) + + #Test sites feature + s = siteinds("S=1/2", 8; conserve_qns = false) + psi = random_mps(s, n -> isodd(n) ? "Up" : "Dn"; linkdims = m) + PM = expect(psi, "S+ * S-") + Cpm = correlation_matrix(psi, "S+", "S-") + range = 3:7 + Cpm37 = correlation_matrix(psi, "S+", "S-"; sites = range) + @test norm(Cpm37 - Cpm[range, range]) < 1.0e-8 + + @test norm(PM[range] - expect(psi, "S+ * S-"; sites = range)) < 1.0e-8 + + # With start_site, end_site arguments: + s = siteinds("S=1/2", 8) + psi = random_mps(ComplexF64, s; linkdims = m) + ss, es = 3, 6 + Nb = es - ss + 1 + Cpm = correlation_matrix(psi, "S+", "S-"; sites = ss:es) + Czz = correlation_matrix(psi, "Sz", "Sz"; sites = ss:es) + @test size(Cpm) == (Nb, Nb) + # Check using OpSum: + for i in ss:es, j in i:es + a = OpSum() + a += "S+", i, "S-", j + @test inner(psi', MPO(a, s), psi) ≈ Cpm[i - ss + 1, j - ss + 1] + end + + # Electron case + s = siteinds("Electron", 8) + psi = random_mps(s; linkdims = m) + test_correlation_matrix( + psi, [("Cdagup", "Cup"), ("Cup", "Cdagup"), ("Cup", "Cdn"), ("Cdagdn", "Cdn")] + ) + + s = siteinds("Electron", 8; conserve_qns = false) + psi = random_mps(s; linkdims = m) + test_correlation_matrix( + psi, + [ + ("Ntot", "Ntot"), + ("Nup", "Nup"), + ("Ndn", "Ndn"), + ("Cdagup", "Cup"), + ("Adagup", "Aup"), + ("Cdn", "Cdagdn"), + ("Adn", "Adagdn"), + ("Sz", "Sz"), + ("S+", "S-"), + ], + ) + # can't test ,("Cdn","Cdn") yet, because OpSum to MPO thinks this is antisymmetric + + #trigger unsupported error + let err = nothing + try + test_correlation_matrix(psi, [("Cup", "Aup")]) + catch err + end + + @test err isa Exception + @test sprint(showerror, err) == + "correlation_matrix: Mixed fermionic and bosonic operators are not supported yet." + end + + # Fermion case + s = siteinds("Fermion", 8) + psi = random_mps(s; linkdims = m) + test_correlation_matrix(psi, [("N", "N"), ("Cdag", "C"), ("C", "Cdag"), ("C", "C")]) + + s = siteinds("Fermion", 8; conserve_qns = false) + psi = random_mps(s; linkdims = m) + test_correlation_matrix(psi, [("N", "N"), ("Cdag", "C"), ("C", "Cdag"), ("C", "C")]) + + # + # Test non-contiguous sites input + # + C = correlation_matrix(psi, "N", "N") + non_contiguous = [1, 3, 8] + Cs = correlation_matrix(psi, "N", "N"; sites = non_contiguous) + for (ni, i) in enumerate(non_contiguous), (nj, j) in enumerate(non_contiguous) + @test Cs[ni, nj] ≈ C[i, j] + end + + C2 = correlation_matrix(psi, "N", "N"; sites = 2) + @test C2 isa Matrix + @test C2[1, 1] ≈ C[2, 2] + end #testset + + @testset "correlation_matrix with dangling bonds" begin + l01 = Index(3) + l̃01 = sim(l01) + l12 = Index(3) + l23 = Index(3) + l34 = Index(3) + l̃34 = sim(l34) + s1 = Index(2, "Qubit") + s2 = Index(2, "Qubit") + s3 = Index(2, "Qubit") + b01 = random_itensor(l01, l̃01) + A1 = random_itensor(l̃01, s1, l12) + A2 = random_itensor(l12, s2, l23) + A3 = random_itensor(l23, s3, l̃34) + b34 = random_itensor(l̃34, l34) + ψ = MPS([b01, A1, A2, A3, b34]) + sites = 1:3 + C = correlation_matrix(ψ, "Z", "Z"; sites = (sites .+ 1)) + siteinds = [l01, s1, s2, s3, l34] + ψψ = inner(ψ, ψ) + zz = map(Iterators.product(sites .+ 1, sites .+ 1)) do I + i, j = I + ZiZj = MPO(OpSum() + ("Z", i, "Z", j), siteinds) + return inner(ψ', ZiZj, ψ) / ψψ + end + for I in eachindex(C) + @test C[I] ≈ zz[I] + end end - end - - @testset "movesites subsets of sites" begin - N = 6 - s = siteinds("S=1/2", N) - ψ = random_mps(s) - - for i in 1:N, j in 1:N - ns = [i, j] - !allunique(ns) && continue - min_ns = minimum(ns) - ns′ = collect(min_ns:(min_ns + length(ns) - 1)) - ψ′ = movesites(ψ, ns .=> ns′; cutoff=1e-15) - @test siteind(ψ′, min_ns) == siteind(ψ, i) - @test siteind(ψ′, min_ns + 1) == siteind(ψ, j) - @test maxlinkdim(ψ′) == 1 - ψ̃ = movesites(ψ′, ns′ .=> ns; cutoff=1e-15) - for n in 1:N - @test siteind(ψ̃, n) == siteind(ψ, n) - end - @test maxlinkdim(ψ̃) == 1 + + @testset "expect regression test for in-place modification of input MPS" begin + s = siteinds("S=1/2", 5) + psi = random_mps(s; linkdims = 3) + orthogonalize!(psi, 1) + expect_init = expect(psi, "Sz") + norm_scale = 10 + psi[1] *= norm_scale + @test ortho_lims(psi) == 1:1 + @test norm(psi) ≈ norm_scale + expect_Sz = expect(psi, "Sz") + @test all(≤(1 / 2), expect_Sz) + @test expect_Sz ≈ expect_init + @test ortho_lims(psi) == 1:1 + @test norm(psi) ≈ norm_scale end - for i in 1:N, j in 1:N, k in 1:N - ns = [i, j, k] - !allunique(ns) && continue - min_ns = minimum(ns) - ns′ = collect(min_ns:(min_ns + length(ns) - 1)) - ψ′ = movesites(ψ, ns .=> ns′; cutoff=1e-15) - @test siteind(ψ′, min_ns) == siteind(ψ, i) - @test siteind(ψ′, min_ns + 1) == siteind(ψ, j) - @test siteind(ψ′, min_ns + 2) == siteind(ψ, k) - @test maxlinkdim(ψ′) == 1 - ψ̃ = movesites(ψ′, ns′ .=> ns; cutoff=1e-15) - for n in 1:N - @test siteind(ψ̃, n) == siteind(ψ, n) - end - @test maxlinkdim(ψ̃) == 1 + @testset "correlation_matrix regression test for in-place modification of input MPS" begin + s = siteinds("S=1/2", 5) + psi = random_mps(s; linkdims = 3) + orthogonalize!(psi, 1) + correlation_matrix_init = correlation_matrix(psi, "Sz", "Sz") + norm_scale = 10 + psi[1] *= norm_scale + @test ortho_lims(psi) == 1:1 + @test norm(psi) ≈ norm_scale + correlation_matrix_SzSz = correlation_matrix(psi, "Sz", "Sz") + @test all(≤(1 / 2), correlation_matrix_SzSz) + @test correlation_matrix_SzSz ≈ correlation_matrix_init + @test ortho_lims(psi) == 1:1 + @test norm(psi) ≈ norm_scale end - for i in 1:N, j in 1:N, k in 1:N, l in 1:N - ns = [i, j, k, l] - !allunique(ns) && continue - min_ns = minimum(ns) - ns′ = collect(min_ns:(min_ns + length(ns) - 1)) - ψ′ = movesites(ψ, ns .=> ns′; cutoff=1e-15) - @test siteind(ψ′, min_ns) == siteind(ψ, i) - @test siteind(ψ′, min_ns + 1) == siteind(ψ, j) - @test siteind(ψ′, min_ns + 2) == siteind(ψ, k) - @test siteind(ψ′, min_ns + 3) == siteind(ψ, l) - @test maxlinkdim(ψ′) == 1 - ψ̃ = movesites(ψ′, ns′ .=> ns; cutoff=1e-15) - for n in 1:N - @test siteind(ψ̃, n) == siteind(ψ, n) - end - @test maxlinkdim(ψ̃) == 1 + @testset "swapbondsites" begin + sites = siteinds("S=1/2", 5) + ψ0 = random_mps(sites) + ψ = replacebond(ψ0, 3, ψ0[3] * ψ0[4]; swapsites = true, cutoff = 1.0e-15) + @test siteind(ψ, 1) == siteind(ψ0, 1) + @test siteind(ψ, 2) == siteind(ψ0, 2) + @test siteind(ψ, 4) == siteind(ψ0, 3) + @test siteind(ψ, 3) == siteind(ψ0, 4) + @test siteind(ψ, 5) == siteind(ψ0, 5) + @test prod(ψ) ≈ prod(ψ0) + @test maxlinkdim(ψ) == 1 + + ψ = swapbondsites(ψ0, 4; cutoff = 1.0e-15) + @test siteind(ψ, 1) == siteind(ψ0, 1) + @test siteind(ψ, 2) == siteind(ψ0, 2) + @test siteind(ψ, 3) == siteind(ψ0, 3) + @test siteind(ψ, 5) == siteind(ψ0, 4) + @test siteind(ψ, 4) == siteind(ψ0, 5) + @test prod(ψ) ≈ prod(ψ0) + @test maxlinkdim(ψ) == 1 end - for i in 1:N, j in 1:N, k in 1:N, l in 1:N, m in 1:N - ns = [i, j, k, l, m] - !allunique(ns) && continue - min_ns = minimum(ns) - ns′ = collect(min_ns:(min_ns + length(ns) - 1)) - ψ′ = movesites(ψ, ns .=> ns′; cutoff=1e-15) - for n in 1:length(ns) - @test siteind(ψ′, min_ns + n - 1) == siteind(ψ, ns[n]) - end - @test maxlinkdim(ψ′) == 1 - ψ̃ = movesites(ψ′, ns′ .=> ns; cutoff=1e-15) - for n in 1:N - @test siteind(ψ̃, n) == siteind(ψ, n) - end - @test maxlinkdim(ψ̃) == 1 + @testset "map!" begin + s = siteinds("S=½", 5) + M0 = MPS(s, "↑") + + # Test map! with limits getting set + M = orthogonalize(M0, 1) + @test ITensorMPS.leftlim(M) == 0 + @test ITensorMPS.rightlim(M) == 2 + map!(prime, M) + @test ITensorMPS.leftlim(M) == 0 + @test ITensorMPS.rightlim(M) == length(M0) + 1 + + # Test map! without limits getting set + M = orthogonalize(M0, 1) + map!(prime, M; set_limits = false) + @test ITensorMPS.leftlim(M) == 0 + @test ITensorMPS.rightlim(M) == 2 + + # Test prime! with limits getting set + M = orthogonalize(M0, 1) + @test ITensorMPS.leftlim(M) == 0 + @test ITensorMPS.rightlim(M) == 2 + prime!(M; set_limits = true) + @test ITensorMPS.leftlim(M) == 0 + @test ITensorMPS.rightlim(M) == length(M0) + 1 + + # Test prime! without limits getting set + M = orthogonalize(M0, 1) + prime!(M) + @test ITensorMPS.leftlim(M) == 0 + @test ITensorMPS.rightlim(M) == 2 end - end - - @testset "movesites tags" begin - N = 4 - s0 = siteinds("S=1/2", N) - ψ0 = random_mps(s0; linkdims=1) - for perm in permutations(1:N) - s = s0[perm] - ns′ = [findfirst(==(i), s0) for i in s] - ψ = movesites(ψ0, 1:N .=> ns′; cutoff=1e-15) - @test ITensorMPS.hasdefaultlinktags(ψ) + + @testset "setindex!(::MPS, _, ::Colon)" begin + s = siteinds("S=½", 4) + ψ = random_mps(s) + ϕ = MPS(s, "↑") + orthogonalize!(ϕ, 1) + ψ[:] = ϕ + @test ITensorMPS.orthocenter(ψ) == 1 + @test inner(ψ, ϕ) ≈ 1 + + ψ = random_mps(s) + ϕ = MPS(s, "↑") + orthogonalize!(ϕ, 1) + ψ[:] = ITensorMPS.data(ϕ) + @test ITensorMPS.leftlim(ψ) == 0 + @test ITensorMPS.rightlim(ψ) == length(ψ) + 1 + @test inner(ψ, ϕ) ≈ 1 end - end - - @testset "product(::Vector{ITensor}, ::MPS)" begin - N = 6 - s = siteinds("Qubit", N) - - I = [op("Id", s, n) for n in 1:N] - X = [op("X", s, n) for n in 1:N] - Y = [op("Y", s, n) for n in 1:N] - Z = [op("Z", s, n) for n in 1:N] - H = [op("H", s, n) for n in 1:N] - CX = [op("CX", s, n, m) for n in 1:N, m in 1:N] - CY = [op("CY", s, n, m) for n in 1:N, m in 1:N] - CZ = [op("CZ", s, n, m) for n in 1:N, m in 1:N] - CCNOT = [op("CCNOT", s, n, m, k) for n in 1:N, m in 1:N, k in 1:N] - CSWAP = [op("CSWAP", s, n, m, k) for n in 1:N, m in 1:N, k in 1:N] - CCCNOT = [op("CCCNOT", s, n, m, k, l) for n in 1:N, m in 1:N, k in 1:N, l in 1:N] - - v0 = [onehot(s[n] => "0") for n in 1:N] - v1 = [onehot(s[n] => "1") for n in 1:N] - - # Single qubit - @test product(I[1], v0[1]) ≈ v0[1] - @test product(I[1], v1[1]) ≈ v1[1] - - @test product(H[1], H[1]) ≈ I[1] - @test product(H[1], v0[1]) ≈ 1 / sqrt(2) * (v0[1] + v1[1]) - @test product(H[1], v1[1]) ≈ 1 / sqrt(2) * (v0[1] - v1[1]) - - @test product(X[1], v0[1]) ≈ v1[1] - @test product(X[1], v1[1]) ≈ v0[1] - - @test product(Y[1], v0[1]) ≈ im * v1[1] - @test product(Y[1], v1[1]) ≈ -im * v0[1] - - @test product(Z[1], v0[1]) ≈ v0[1] - @test product(Z[1], v1[1]) ≈ -v1[1] - - @test product(X[1], X[1]) ≈ I[1] - @test product(Y[1], Y[1]) ≈ I[1] - @test product(Z[1], Z[1]) ≈ I[1] - @test -im * product([Y[1], X[1]], Z[1]) ≈ I[1] - - @test dag(X[1]) ≈ -product([X[1], Y[1]], Y[1]) - @test dag(Y[1]) ≈ -product([Y[1], Y[1]], Y[1]) - @test dag(Z[1]) ≈ -product([Z[1], Y[1]], Y[1]) - - @test product(X[1], Y[1]) - product(Y[1], X[1]) ≈ 2 * im * Z[1] - @test product(Y[1], Z[1]) - product(Z[1], Y[1]) ≈ 2 * im * X[1] - @test product(Z[1], X[1]) - product(X[1], Z[1]) ≈ 2 * im * Y[1] - - @test product([Y[1], X[1]], v0[1]) - product([X[1], Y[1]], v0[1]) ≈ - 2 * im * product(Z[1], v0[1]) - @test product([Y[1], X[1]], v1[1]) - product([X[1], Y[1]], v1[1]) ≈ - 2 * im * product(Z[1], v1[1]) - @test product([Z[1], Y[1]], v0[1]) - product([Y[1], Z[1]], v0[1]) ≈ - 2 * im * product(X[1], v0[1]) - @test product([Z[1], Y[1]], v1[1]) - product([Y[1], Z[1]], v1[1]) ≈ - 2 * im * product(X[1], v1[1]) - @test product([X[1], Z[1]], v0[1]) - product([Z[1], X[1]], v0[1]) ≈ - 2 * im * product(Y[1], v0[1]) - @test product([X[1], Z[1]], v1[1]) - product([Z[1], X[1]], v1[1]) ≈ - 2 * im * product(Y[1], v1[1]) - - # - # 2-qubit - # - - @test product(I[1] * I[2], v0[1] * v0[2]) ≈ v0[1] * v0[2] - - @test product(CX[1, 2], v0[1] * v0[2]) ≈ v0[1] * v0[2] - @test product(CX[1, 2], v0[1] * v1[2]) ≈ v0[1] * v1[2] - @test product(CX[1, 2], v1[1] * v0[2]) ≈ v1[1] * v1[2] - @test product(CX[1, 2], v1[1] * v1[2]) ≈ v1[1] * v0[2] - - @test product(CY[1, 2], v0[1] * v0[2]) ≈ v0[1] * v0[2] - @test product(CY[1, 2], v0[1] * v1[2]) ≈ v0[1] * v1[2] - @test product(CY[1, 2], v1[1] * v0[2]) ≈ im * v1[1] * v1[2] - @test product(CY[1, 2], v1[1] * v1[2]) ≈ -im * v1[1] * v0[2] - - @test product(CZ[1, 2], v0[1] * v0[2]) ≈ v0[1] * v0[2] - @test product(CZ[1, 2], v0[1] * v1[2]) ≈ v0[1] * v1[2] - @test product(CZ[1, 2], v1[1] * v0[2]) ≈ v1[1] * v0[2] - @test product(CZ[1, 2], v1[1] * v1[2]) ≈ -v1[1] * v1[2] - - # - # 3-qubit - # - - @test product(CCNOT[1, 2, 3], v0[1] * v0[2] * v0[3]) ≈ v0[1] * v0[2] * v0[3] - @test product(CCNOT[1, 2, 3], v0[1] * v0[2] * v1[3]) ≈ v0[1] * v0[2] * v1[3] - @test product(CCNOT[1, 2, 3], v0[1] * v1[2] * v0[3]) ≈ v0[1] * v1[2] * v0[3] - @test product(CCNOT[1, 2, 3], v0[1] * v1[2] * v1[3]) ≈ v0[1] * v1[2] * v1[3] - @test product(CCNOT[1, 2, 3], v1[1] * v0[2] * v0[3]) ≈ v1[1] * v0[2] * v0[3] - @test product(CCNOT[1, 2, 3], v1[1] * v0[2] * v1[3]) ≈ v1[1] * v0[2] * v1[3] - @test product(CCNOT[1, 2, 3], v1[1] * v1[2] * v0[3]) ≈ v1[1] * v1[2] * v1[3] - @test product(CCNOT[1, 2, 3], v1[1] * v1[2] * v1[3]) ≈ v1[1] * v1[2] * v0[3] - - @test product(CSWAP[1, 2, 3], v0[1] * v0[2] * v0[3]) ≈ v0[1] * v0[2] * v0[3] - @test product(CSWAP[1, 2, 3], v0[1] * v0[2] * v1[3]) ≈ v0[1] * v0[2] * v1[3] - @test product(CSWAP[1, 2, 3], v0[1] * v1[2] * v0[3]) ≈ v0[1] * v1[2] * v0[3] - @test product(CSWAP[1, 2, 3], v0[1] * v1[2] * v1[3]) ≈ v0[1] * v1[2] * v1[3] - @test product(CSWAP[1, 2, 3], v1[1] * v0[2] * v0[3]) ≈ v1[1] * v0[2] * v0[3] - @test product(CSWAP[1, 2, 3], v1[1] * v0[2] * v1[3]) ≈ v1[1] * v1[2] * v0[3] - @test product(CSWAP[1, 2, 3], v1[1] * v1[2] * v0[3]) ≈ v1[1] * v0[2] * v1[3] - @test product(CSWAP[1, 2, 3], v1[1] * v1[2] * v1[3]) ≈ v1[1] * v1[2] * v1[3] - - # - # Apply to an MPS - # - - ψ = MPS(s, "0") - @test prod(product(X[1], ψ)) ≈ prod(MPS(s, n -> n == 1 ? "1" : "0")) - @test prod(product(X[1], product(X[2], ψ))) ≈ - prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) - @test prod(product(X[1] * X[2], ψ)) ≈ prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) - @test prod(product([X[2], X[1]], ψ)) ≈ prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) - @test prod(product(CX[1, 2], ψ)) ≈ prod(MPS(s, "0")) - @test prod(product(CX[1, 2], product(X[1], ψ))) ≈ - prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) - @test prod(product(product(CX[1, 2], X[1]), ψ)) ≈ - prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) - @test prod(product([X[1], CX[1, 2]], ψ)) ≈ - prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) - - for i in 1:N, j in 1:N - !allunique((i, j)) && continue - # Don't move sites back - CXij_ψ = product([X[i], CX[i, j]], ψ; move_sites_back=false, cutoff=1e-15) - @test maxlinkdim(CXij_ψ) == 1 - @test prod(CXij_ψ) ≈ prod(MPS(s, n -> n == i || n == j ? "1" : "0")) - - # Move sites back - CXij_ψ = product([X[i], CX[i, j]], ψ) - for n in 1:N - @test siteind(CXij_ψ, n) == siteind(ψ, n) - end - @test prod(CXij_ψ) ≈ prod(MPS(s, n -> n == i || n == j ? "1" : "0")) + + @testset "findsite[s](::MPS/MPO, is)" begin + s = siteinds("S=1/2", 5) + ψ = random_mps(s) + l = linkinds(ψ) + + A = random_itensor(s[4]', s[2]', dag(s[4]), dag(s[2])) + + @test findsite(ψ, s[3]) == 3 + @test findsite(ψ, (s[3], s[5])) == 3 + @test findsite(ψ, l[2]) == 2 + @test findsite(ψ, A) == 2 + + @test findsites(ψ, s[3]) == [3] + @test findsites(ψ, (s[4], s[1])) == [1, 4] + @test findsites(ψ, l[2]) == [2, 3] + @test findsites(ψ, (l[2], l[3])) == [2, 3, 4] + @test findsites(ψ, A) == [2, 4] + + M = random_mpo(s) + lM = linkinds(M) + + @test findsite(M, s[4]) == 4 + @test findsite(M, s[4]') == 4 + @test findsite(M, (s[4]', s[4])) == 4 + @test findsite(M, (s[4]', s[3])) == 3 + @test findsite(M, lM[2]) == 2 + @test findsite(M, A) == 2 + + @test findsites(M, s[4]) == [4] + @test findsites(M, s[4]') == [4] + @test findsites(M, (s[4]', s[4])) == [4] + @test findsites(M, (s[4]', s[3])) == [3, 4] + @test findsites(M, (lM[2], lM[3])) == [2, 3, 4] + @test findsites(M, A) == [2, 4] end - for i in 1:N, j in 1:N, k in 1:N - ns = (i, j, k) - !allunique(ns) && continue - # Don't move sites back - CCNOTijk_ψ = product( - [X[j], X[i], CCNOT[ns...]], ψ; move_sites_back=false, cutoff=1e-15 - ) - @test maxlinkdim(CCNOTijk_ψ) == 1 - @test prod(CCNOTijk_ψ) ≈ prod(MPS(s, n -> n ∈ ns ? "1" : "0")) - - # Move sites back - CCNOTijk_ψ = product([X[j], X[i], CCNOT[ns...]], ψ; cutoff=1e-15) - @test maxlinkdim(CCNOTijk_ψ) == 1 - for n in 1:N - @test siteind(CCNOTijk_ψ, n) == siteind(ψ, n) - end - @test prod(CCNOTijk_ψ) ≈ prod(MPS(s, n -> n ∈ ns ? "1" : "0")) + @testset "[first]siteind[s](::MPS/MPO, j::Int)" begin + s = siteinds("S=1/2", 5) + ψ = random_mps(s) + @test siteind(first, ψ, 3) == s[3] + @test siteind(ψ, 4) == s[4] + @test isnothing(siteind(ψ, 4; plev = 1)) + @test siteinds(ψ, 3) == IndexSet(s[3]) + @test siteinds(ψ, 3; plev = 1) == IndexSet() + + M = random_mpo(s) + @test noprime(siteind(first, M, 4)) == s[4] + @test siteind(first, M, 4; plev = 0) == s[4] + @test siteind(first, M, 4; plev = 1) == s[4]' + @test siteind(M, 4) == s[4] + @test siteind(M, 4; plev = 0) == s[4] + @test siteind(M, 4; plev = 1) == s[4]' + @test isnothing(siteind(M, 4; plev = 2)) + @test hassameinds(siteinds(M, 3), (s[3], s[3]')) + @test siteinds(M, 3; plev = 1) == IndexSet(s[3]') + @test siteinds(M, 3; plev = 0) == IndexSet(s[3]) + @test siteinds(M, 3; tags = "n=2") == IndexSet() end - for i in 1:N, j in i:N, k in 1:N, l in k:N - ns = (i, j, k, l) - !allunique(ns) && continue - # Don't move sites back - CCCNOTijkl_ψ = product( - [X[i], X[j], X[k], CCCNOT[ns...]], ψ; move_sites_back=false, cutoff=1e-15 - ) - @test maxlinkdim(CCCNOTijkl_ψ) == 1 - @test prod(CCCNOTijkl_ψ) ≈ prod(MPS(s, n -> n ∈ ns ? "1" : "0")) - - # Move sites back - CCCNOTijkl_ψ = product([X[i], X[j], X[k], CCCNOT[ns...]], ψ; cutoff=1e-15) - @test maxlinkdim(CCCNOTijkl_ψ) == 1 - for n in 1:N - @test siteind(CCCNOTijkl_ψ, n) == siteind(ψ, n) - end - @test prod(CCCNOTijkl_ψ) ≈ prod(MPS(s, n -> n ∈ ns ? "1" : "0")) + @testset "movesites $N sites" for N in 1:4 + s0 = siteinds("S=1/2", N) + for perm in permutations(1:N) + s = s0[perm] + ψ = MPS(s, rand(("↑", "↓"), N)) + ns′ = [findfirst(==(i), s0) for i in s] + @test ns′ == perm + ψ′ = movesites(ψ, 1:N .=> ns′; cutoff = 1.0e-15) + if N == 1 + @test maxlinkdim(ψ′) == 1 + else + @test maxlinkdim(ψ′) == 1 + end + for n in 1:N + @test s0[n] == siteind(ψ′, n) + end + @test prod(ψ) ≈ prod(ψ′) + end end - end - - @testset "product" begin - @testset "Contraction order of operations" begin - s = siteind("Qubit") - Q = SiteType("Qubit") - @test product(ops([s], [("Y", 1), ("X", 1)]), setelt(s => 1)) ≈ - itensor(op("X", Q) * op("Y", Q) * [1; 0], s) - @test product(ops([s], [("Y", 1), ("Z", 1)]), setelt(s => 1)) ≈ - itensor(op("Z", Q) * op("Y", Q) * [1; 0], s) - @test product(ops([s], [("X", 1), ("Y", 1)]), setelt(s => 1)) ≈ - itensor(op("Y", Q) * op("X", Q) * [1; 0], s) + + @testset "Construct MPS from ITensor" begin + N = 5 + s = siteinds("S=1/2", N) + l = [Index(3, "left_$n") for n in 1:2] + r = [Index(3, "right_$n") for n in 1:2] + + # + # MPS + # + + A = random_itensor(s...) + ψ = MPS(A, s) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == N + @test maxlinkdim(ψ) == 4 + + ψ0 = MPS(s, "↑") + A = prod(ψ0) + ψ = MPS(A, s; cutoff = 1.0e-15) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == N + @test maxlinkdim(ψ) == 1 + + ψ0 = random_mps(s; linkdims = 2) + A = prod(ψ0) + ψ = MPS(A, s; cutoff = 1.0e-15, orthocenter = 2) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == 2 + @test maxlinkdim(ψ) == 2 + + A = random_itensor(s..., l[1], r[1]) + ψ = MPS(A, s; leftinds = l[1], orthocenter = 3) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (l[1], s[1], ls[1])) + @test hassameinds(ψ[N], (r[1], s[N], ls[N - 1])) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == 3 + @test maxlinkdim(ψ) == 12 + + A = random_itensor(s..., l..., r...) + ψ = MPS(A, s; leftinds = l) + ls = linkinds(ψ) + @test hassameinds(ψ[1], (l..., s[1], ls[1])) + @test hassameinds(ψ[N], (r..., s[N], ls[N - 1])) + @test prod(ψ) ≈ A + @test ITensorMPS.orthocenter(ψ) == N + @test maxlinkdim(ψ) == 36 + + # + # Construct from regular Julia tensor + # + Ajt = randn(2, 2, 2, 2, 2) # Julia tensor + ψ = MPS(Ajt, s) + @test prod(ψ) ≈ ITensor(Ajt, s...) + + Ajv = randn(2^N) # Julia vector + ψ = MPS(Ajv, s) + @test prod(ψ) ≈ ITensor(Ajv, s...) end - @testset "Simple on-site state evolution" begin - N = 3 + @testset "Set range of MPS tensors" begin + N = 5 + s = siteinds("S=1/2", N) + ψ0 = random_mps(s; linkdims = 3) + + ψ = orthogonalize(ψ0, 2) + A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) + randn!(A) + ϕ = MPS(A, s[2:(N - 1)]; orthocenter = 1) + ψ[2:(N - 1)] = ϕ + @test prod(ψ) ≈ ψ[1] * A * ψ[N] + @test maxlinkdim(ψ) == 4 + @test ITensorMPS.orthocenter(ψ) == 2 + + ψ = orthogonalize(ψ0, 1) + A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) + randn!(A) + @test_throws AssertionError ψ[2:(N - 1)] = A + + ψ = orthogonalize(ψ0, 2) + A = prod(ITensorMPS.data(ψ)[2:(N - 1)]) + randn!(A) + ψ[2:(N - 1), orthocenter = 3] = A + @test prod(ψ) ≈ ψ[1] * A * ψ[N] + @test maxlinkdim(ψ) == 4 + @test ITensorMPS.orthocenter(ψ) == 3 + end - pos = [("Z", 3), ("Y", 2), ("X", 1)] + @testset "movesites reverse sites" begin + N = 6 + s = siteinds("S=1/2", N) + ψ0 = random_mps(s) + ψ = movesites(ψ0, 1:N .=> reverse(1:N)) + for n in 1:N + @test siteind(ψ, n) == s[N - n + 1] + end + end - s = siteinds("Qubit", N) - gates = ops(s, pos) - ψ0 = MPS(s, "0") + @testset "movesites subsets of sites" begin + N = 6 + s = siteinds("S=1/2", N) + ψ = random_mps(s) + + for i in 1:N, j in 1:N + ns = [i, j] + !allunique(ns) && continue + min_ns = minimum(ns) + ns′ = collect(min_ns:(min_ns + length(ns) - 1)) + ψ′ = movesites(ψ, ns .=> ns′; cutoff = 1.0e-15) + @test siteind(ψ′, min_ns) == siteind(ψ, i) + @test siteind(ψ′, min_ns + 1) == siteind(ψ, j) + @test maxlinkdim(ψ′) == 1 + ψ̃ = movesites(ψ′, ns′ .=> ns; cutoff = 1.0e-15) + for n in 1:N + @test siteind(ψ̃, n) == siteind(ψ, n) + end + @test maxlinkdim(ψ̃) == 1 + end - # Apply the gates - ψ = product(gates, ψ0) + for i in 1:N, j in 1:N, k in 1:N + ns = [i, j, k] + !allunique(ns) && continue + min_ns = minimum(ns) + ns′ = collect(min_ns:(min_ns + length(ns) - 1)) + ψ′ = movesites(ψ, ns .=> ns′; cutoff = 1.0e-15) + @test siteind(ψ′, min_ns) == siteind(ψ, i) + @test siteind(ψ′, min_ns + 1) == siteind(ψ, j) + @test siteind(ψ′, min_ns + 2) == siteind(ψ, k) + @test maxlinkdim(ψ′) == 1 + ψ̃ = movesites(ψ′, ns′ .=> ns; cutoff = 1.0e-15) + for n in 1:N + @test siteind(ψ̃, n) == siteind(ψ, n) + end + @test maxlinkdim(ψ̃) == 1 + end - # Move site 1 to position 3 - ψ′ = movesite(ψ, 1 => 3) - @test siteind(ψ′, 1) == s[2] - @test siteind(ψ′, 2) == s[3] - @test siteind(ψ′, 3) == s[1] - @test prod(ψ) ≈ prod(ψ′) + for i in 1:N, j in 1:N, k in 1:N, l in 1:N + ns = [i, j, k, l] + !allunique(ns) && continue + min_ns = minimum(ns) + ns′ = collect(min_ns:(min_ns + length(ns) - 1)) + ψ′ = movesites(ψ, ns .=> ns′; cutoff = 1.0e-15) + @test siteind(ψ′, min_ns) == siteind(ψ, i) + @test siteind(ψ′, min_ns + 1) == siteind(ψ, j) + @test siteind(ψ′, min_ns + 2) == siteind(ψ, k) + @test siteind(ψ′, min_ns + 3) == siteind(ψ, l) + @test maxlinkdim(ψ′) == 1 + ψ̃ = movesites(ψ′, ns′ .=> ns; cutoff = 1.0e-15) + for n in 1:N + @test siteind(ψ̃, n) == siteind(ψ, n) + end + @test maxlinkdim(ψ̃) == 1 + end - # Move the site back - ψ′′ = movesite(ψ′, 3 => 1) - @test siteind(ψ′′, 1) == s[1] - @test siteind(ψ′′, 2) == s[2] - @test siteind(ψ′′, 3) == s[3] - @test prod(ψ) ≈ prod(ψ′′) + for i in 1:N, j in 1:N, k in 1:N, l in 1:N, m in 1:N + ns = [i, j, k, l, m] + !allunique(ns) && continue + min_ns = minimum(ns) + ns′ = collect(min_ns:(min_ns + length(ns) - 1)) + ψ′ = movesites(ψ, ns .=> ns′; cutoff = 1.0e-15) + for n in 1:length(ns) + @test siteind(ψ′, min_ns + n - 1) == siteind(ψ, ns[n]) + end + @test maxlinkdim(ψ′) == 1 + ψ̃ = movesites(ψ′, ns′ .=> ns; cutoff = 1.0e-15) + for n in 1:N + @test siteind(ψ̃, n) == siteind(ψ, n) + end + @test maxlinkdim(ψ̃) == 1 + end end - @testset "More complex evolution" begin - N = 7 - - osX = [("X", n) for n in 1:N] - - osZ = [("Z", n) for n in 1:N] + @testset "movesites tags" begin + N = 4 + s0 = siteinds("S=1/2", N) + ψ0 = random_mps(s0; linkdims = 1) + for perm in permutations(1:N) + s = s0[perm] + ns′ = [findfirst(==(i), s0) for i in s] + ψ = movesites(ψ0, 1:N .=> ns′; cutoff = 1.0e-15) + @test ITensorMPS.hasdefaultlinktags(ψ) + end + end - osSw = [("SWAP", n, n + 1) for n in 1:(N - 2)] + @testset "product(::Vector{ITensor}, ::MPS)" begin + N = 6 + s = siteinds("Qubit", N) + + I = [op("Id", s, n) for n in 1:N] + X = [op("X", s, n) for n in 1:N] + Y = [op("Y", s, n) for n in 1:N] + Z = [op("Z", s, n) for n in 1:N] + H = [op("H", s, n) for n in 1:N] + CX = [op("CX", s, n, m) for n in 1:N, m in 1:N] + CY = [op("CY", s, n, m) for n in 1:N, m in 1:N] + CZ = [op("CZ", s, n, m) for n in 1:N, m in 1:N] + CCNOT = [op("CCNOT", s, n, m, k) for n in 1:N, m in 1:N, k in 1:N] + CSWAP = [op("CSWAP", s, n, m, k) for n in 1:N, m in 1:N, k in 1:N] + CCCNOT = [op("CCCNOT", s, n, m, k, l) for n in 1:N, m in 1:N, k in 1:N, l in 1:N] + + v0 = [onehot(s[n] => "0") for n in 1:N] + v1 = [onehot(s[n] => "1") for n in 1:N] + + # Single qubit + @test product(I[1], v0[1]) ≈ v0[1] + @test product(I[1], v1[1]) ≈ v1[1] + + @test product(H[1], H[1]) ≈ I[1] + @test product(H[1], v0[1]) ≈ 1 / sqrt(2) * (v0[1] + v1[1]) + @test product(H[1], v1[1]) ≈ 1 / sqrt(2) * (v0[1] - v1[1]) + + @test product(X[1], v0[1]) ≈ v1[1] + @test product(X[1], v1[1]) ≈ v0[1] + + @test product(Y[1], v0[1]) ≈ im * v1[1] + @test product(Y[1], v1[1]) ≈ -im * v0[1] + + @test product(Z[1], v0[1]) ≈ v0[1] + @test product(Z[1], v1[1]) ≈ -v1[1] + + @test product(X[1], X[1]) ≈ I[1] + @test product(Y[1], Y[1]) ≈ I[1] + @test product(Z[1], Z[1]) ≈ I[1] + @test -im * product([Y[1], X[1]], Z[1]) ≈ I[1] + + @test dag(X[1]) ≈ -product([X[1], Y[1]], Y[1]) + @test dag(Y[1]) ≈ -product([Y[1], Y[1]], Y[1]) + @test dag(Z[1]) ≈ -product([Z[1], Y[1]], Y[1]) + + @test product(X[1], Y[1]) - product(Y[1], X[1]) ≈ 2 * im * Z[1] + @test product(Y[1], Z[1]) - product(Z[1], Y[1]) ≈ 2 * im * X[1] + @test product(Z[1], X[1]) - product(X[1], Z[1]) ≈ 2 * im * Y[1] + + @test product([Y[1], X[1]], v0[1]) - product([X[1], Y[1]], v0[1]) ≈ + 2 * im * product(Z[1], v0[1]) + @test product([Y[1], X[1]], v1[1]) - product([X[1], Y[1]], v1[1]) ≈ + 2 * im * product(Z[1], v1[1]) + @test product([Z[1], Y[1]], v0[1]) - product([Y[1], Z[1]], v0[1]) ≈ + 2 * im * product(X[1], v0[1]) + @test product([Z[1], Y[1]], v1[1]) - product([Y[1], Z[1]], v1[1]) ≈ + 2 * im * product(X[1], v1[1]) + @test product([X[1], Z[1]], v0[1]) - product([Z[1], X[1]], v0[1]) ≈ + 2 * im * product(Y[1], v0[1]) + @test product([X[1], Z[1]], v1[1]) - product([Z[1], X[1]], v1[1]) ≈ + 2 * im * product(Y[1], v1[1]) + + # + # 2-qubit + # + + @test product(I[1] * I[2], v0[1] * v0[2]) ≈ v0[1] * v0[2] + + @test product(CX[1, 2], v0[1] * v0[2]) ≈ v0[1] * v0[2] + @test product(CX[1, 2], v0[1] * v1[2]) ≈ v0[1] * v1[2] + @test product(CX[1, 2], v1[1] * v0[2]) ≈ v1[1] * v1[2] + @test product(CX[1, 2], v1[1] * v1[2]) ≈ v1[1] * v0[2] + + @test product(CY[1, 2], v0[1] * v0[2]) ≈ v0[1] * v0[2] + @test product(CY[1, 2], v0[1] * v1[2]) ≈ v0[1] * v1[2] + @test product(CY[1, 2], v1[1] * v0[2]) ≈ im * v1[1] * v1[2] + @test product(CY[1, 2], v1[1] * v1[2]) ≈ -im * v1[1] * v0[2] + + @test product(CZ[1, 2], v0[1] * v0[2]) ≈ v0[1] * v0[2] + @test product(CZ[1, 2], v0[1] * v1[2]) ≈ v0[1] * v1[2] + @test product(CZ[1, 2], v1[1] * v0[2]) ≈ v1[1] * v0[2] + @test product(CZ[1, 2], v1[1] * v1[2]) ≈ -v1[1] * v1[2] + + # + # 3-qubit + # + + @test product(CCNOT[1, 2, 3], v0[1] * v0[2] * v0[3]) ≈ v0[1] * v0[2] * v0[3] + @test product(CCNOT[1, 2, 3], v0[1] * v0[2] * v1[3]) ≈ v0[1] * v0[2] * v1[3] + @test product(CCNOT[1, 2, 3], v0[1] * v1[2] * v0[3]) ≈ v0[1] * v1[2] * v0[3] + @test product(CCNOT[1, 2, 3], v0[1] * v1[2] * v1[3]) ≈ v0[1] * v1[2] * v1[3] + @test product(CCNOT[1, 2, 3], v1[1] * v0[2] * v0[3]) ≈ v1[1] * v0[2] * v0[3] + @test product(CCNOT[1, 2, 3], v1[1] * v0[2] * v1[3]) ≈ v1[1] * v0[2] * v1[3] + @test product(CCNOT[1, 2, 3], v1[1] * v1[2] * v0[3]) ≈ v1[1] * v1[2] * v1[3] + @test product(CCNOT[1, 2, 3], v1[1] * v1[2] * v1[3]) ≈ v1[1] * v1[2] * v0[3] + + @test product(CSWAP[1, 2, 3], v0[1] * v0[2] * v0[3]) ≈ v0[1] * v0[2] * v0[3] + @test product(CSWAP[1, 2, 3], v0[1] * v0[2] * v1[3]) ≈ v0[1] * v0[2] * v1[3] + @test product(CSWAP[1, 2, 3], v0[1] * v1[2] * v0[3]) ≈ v0[1] * v1[2] * v0[3] + @test product(CSWAP[1, 2, 3], v0[1] * v1[2] * v1[3]) ≈ v0[1] * v1[2] * v1[3] + @test product(CSWAP[1, 2, 3], v1[1] * v0[2] * v0[3]) ≈ v1[1] * v0[2] * v0[3] + @test product(CSWAP[1, 2, 3], v1[1] * v0[2] * v1[3]) ≈ v1[1] * v1[2] * v0[3] + @test product(CSWAP[1, 2, 3], v1[1] * v1[2] * v0[3]) ≈ v1[1] * v0[2] * v1[3] + @test product(CSWAP[1, 2, 3], v1[1] * v1[2] * v1[3]) ≈ v1[1] * v1[2] * v1[3] + + # + # Apply to an MPS + # + + ψ = MPS(s, "0") + @test prod(product(X[1], ψ)) ≈ prod(MPS(s, n -> n == 1 ? "1" : "0")) + @test prod(product(X[1], product(X[2], ψ))) ≈ + prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) + @test prod(product(X[1] * X[2], ψ)) ≈ prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) + @test prod(product([X[2], X[1]], ψ)) ≈ prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) + @test prod(product(CX[1, 2], ψ)) ≈ prod(MPS(s, "0")) + @test prod(product(CX[1, 2], product(X[1], ψ))) ≈ + prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) + @test prod(product(product(CX[1, 2], X[1]), ψ)) ≈ + prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) + @test prod(product([X[1], CX[1, 2]], ψ)) ≈ + prod(MPS(s, n -> n == 1 || n == 2 ? "1" : "0")) + + for i in 1:N, j in 1:N + !allunique((i, j)) && continue + # Don't move sites back + CXij_ψ = product([X[i], CX[i, j]], ψ; move_sites_back = false, cutoff = 1.0e-15) + @test maxlinkdim(CXij_ψ) == 1 + @test prod(CXij_ψ) ≈ prod(MPS(s, n -> n == i || n == j ? "1" : "0")) + + # Move sites back + CXij_ψ = product([X[i], CX[i, j]], ψ) + for n in 1:N + @test siteind(CXij_ψ, n) == siteind(ψ, n) + end + @test prod(CXij_ψ) ≈ prod(MPS(s, n -> n == i || n == j ? "1" : "0")) + end - osCx = [("CX", n, n + 3) for n in 1:(N - 3)] + for i in 1:N, j in 1:N, k in 1:N + ns = (i, j, k) + !allunique(ns) && continue + # Don't move sites back + CCNOTijk_ψ = product( + [X[j], X[i], CCNOT[ns...]], ψ; move_sites_back = false, cutoff = 1.0e-15 + ) + @test maxlinkdim(CCNOTijk_ψ) == 1 + @test prod(CCNOTijk_ψ) ≈ prod(MPS(s, n -> n ∈ ns ? "1" : "0")) + + # Move sites back + CCNOTijk_ψ = product([X[j], X[i], CCNOT[ns...]], ψ; cutoff = 1.0e-15) + @test maxlinkdim(CCNOTijk_ψ) == 1 + for n in 1:N + @test siteind(CCNOTijk_ψ, n) == siteind(ψ, n) + end + @test prod(CCNOTijk_ψ) ≈ prod(MPS(s, n -> n ∈ ns ? "1" : "0")) + end - osT = [("CCX", n, n + 1, n + 3) for n in 1:(N - 3)] + for i in 1:N, j in i:N, k in 1:N, l in k:N + ns = (i, j, k, l) + !allunique(ns) && continue + # Don't move sites back + CCCNOTijkl_ψ = product( + [X[i], X[j], X[k], CCCNOT[ns...]], ψ; move_sites_back = false, cutoff = 1.0e-15 + ) + @test maxlinkdim(CCCNOTijkl_ψ) == 1 + @test prod(CCCNOTijkl_ψ) ≈ prod(MPS(s, n -> n ∈ ns ? "1" : "0")) + + # Move sites back + CCCNOTijkl_ψ = product([X[i], X[j], X[k], CCCNOT[ns...]], ψ; cutoff = 1.0e-15) + @test maxlinkdim(CCCNOTijkl_ψ) == 1 + for n in 1:N + @test siteind(CCCNOTijkl_ψ, n) == siteind(ψ, n) + end + @test prod(CCCNOTijkl_ψ) ≈ prod(MPS(s, n -> n ∈ ns ? "1" : "0")) + end + end - osRx = [("Rx", n, (θ=π,)) for n in 1:N] + @testset "product" begin + @testset "Contraction order of operations" begin + s = siteind("Qubit") + Q = SiteType("Qubit") + @test product(ops([s], [("Y", 1), ("X", 1)]), setelt(s => 1)) ≈ + itensor(op("X", Q) * op("Y", Q) * [1; 0], s) + @test product(ops([s], [("Y", 1), ("Z", 1)]), setelt(s => 1)) ≈ + itensor(op("Z", Q) * op("Y", Q) * [1; 0], s) + @test product(ops([s], [("X", 1), ("Y", 1)]), setelt(s => 1)) ≈ + itensor(op("Y", Q) * op("X", Q) * [1; 0], s) + end - osXX = [("Rxx", (n, n + 1), (ϕ=π / 8,)) for n in 1:(N - 1)] + @testset "Simple on-site state evolution" begin + N = 3 - #os_noise = [("noise", n, n+2, n+4) for n in 1:N-4] + pos = [("Z", 3), ("Y", 2), ("X", 1)] - os = vcat(osX, osXX, osSw, osRx, osZ, osCx, osT) - s = siteinds("Qubit", N) - gates = ops(os, s) + s = siteinds("Qubit", N) + gates = ops(s, pos) + ψ0 = MPS(s, "0") - @testset "Pure state evolution" begin - ψ0 = MPS(s, "0") - ψ = product(gates, ψ0; cutoff=1e-15) - @test maxlinkdim(ψ) == 8 - prodψ = product(gates, prod(ψ0)) - @test prod(ψ) ≈ prodψ rtol = 1e-12 - end + # Apply the gates + ψ = product(gates, ψ0) - M0 = MPO(s, "Id") - maxdim = prod(dim(siteinds(M0, j)) for j in 1:N) + # Move site 1 to position 3 + ψ′ = movesite(ψ, 1 => 3) + @test siteind(ψ′, 1) == s[2] + @test siteind(ψ′, 2) == s[3] + @test siteind(ψ′, 3) == s[1] + @test prod(ψ) ≈ prod(ψ′) - @testset "Mixed state evolution" begin - M = product(gates, M0; cutoff=1e-15, maxdim=maxdim) - @test maxlinkdim(M) == 24 || maxlinkdim(M) == 25 - sM0 = siteinds(M0) - sM = siteinds(M) - for n in 1:N - @test hassameinds(sM[n], sM0[n]) + # Move the site back + ψ′′ = movesite(ψ′, 3 => 1) + @test siteind(ψ′′, 1) == s[1] + @test siteind(ψ′′, 2) == s[2] + @test siteind(ψ′′, 3) == s[3] + @test prod(ψ) ≈ prod(ψ′′) end - @set_warn_order 15 begin - prodM = product(gates, prod(M0)) - @test prod(M) ≈ prodM rtol = 1e-6 - end - end - - #@testset "Mixed state noisy evolution" begin - # prepend!(os, os_noise) - # gates = ops(os, s) - # M = product(gates, M0; apply_dag = true, - # cutoff = 1e-15, maxdim = maxdim) - # @test maxlinkdim(M) == 64 - # sM0 = siteinds(M0) - # sM = siteinds(M) - # for n in 1:N - # @test hassameinds(sM[n], sM0[n]) - # end - # @set_warn_order 16 begin - # prodM = product(gates, prod(M0); apply_dag = true) - # @test prod(M) ≈ prodM rtol = 1e-7 - # end - #end - - #@testset "Mixed state noisy evolution" begin - # prepend!(os, os_noise) - # gates = ops(os, s) - # M = product(gates, M0; - # apply_dag = true, cutoff = 1e-15, maxdim = maxdim-1) - # @test maxlinkdim(M) == 64 - # sM0 = siteinds(M0) - # sM = siteinds(M) - # for n in 1:N - # @test hassameinds(sM[n], sM0[n]) - # end - # @set_warn_order 16 begin - # prodM = product(gates, prod(M0); apply_dag = true) - # @test prod(M) ≈ prodM rtol = 1e-7 - # end - #end - end + @testset "More complex evolution" begin + N = 7 + + osX = [("X", n) for n in 1:N] + + osZ = [("Z", n) for n in 1:N] + + osSw = [("SWAP", n, n + 1) for n in 1:(N - 2)] + + osCx = [("CX", n, n + 3) for n in 1:(N - 3)] + + osT = [("CCX", n, n + 1, n + 3) for n in 1:(N - 3)] + + osRx = [("Rx", n, (θ = π,)) for n in 1:N] + + osXX = [("Rxx", (n, n + 1), (ϕ = π / 8,)) for n in 1:(N - 1)] + + #os_noise = [("noise", n, n+2, n+4) for n in 1:N-4] + + os = vcat(osX, osXX, osSw, osRx, osZ, osCx, osT) + s = siteinds("Qubit", N) + gates = ops(os, s) + + @testset "Pure state evolution" begin + ψ0 = MPS(s, "0") + ψ = product(gates, ψ0; cutoff = 1.0e-15) + @test maxlinkdim(ψ) == 8 + prodψ = product(gates, prod(ψ0)) + @test prod(ψ) ≈ prodψ rtol = 1.0e-12 + end + + M0 = MPO(s, "Id") + maxdim = prod(dim(siteinds(M0, j)) for j in 1:N) + + @testset "Mixed state evolution" begin + M = product(gates, M0; cutoff = 1.0e-15, maxdim = maxdim) + @test maxlinkdim(M) == 24 || maxlinkdim(M) == 25 + sM0 = siteinds(M0) + sM = siteinds(M) + for n in 1:N + @test hassameinds(sM[n], sM0[n]) + end + @set_warn_order 15 begin + prodM = product(gates, prod(M0)) + @test prod(M) ≈ prodM rtol = 1.0e-6 + end + end + + #@testset "Mixed state noisy evolution" begin + # prepend!(os, os_noise) + # gates = ops(os, s) + # M = product(gates, M0; apply_dag = true, + # cutoff = 1e-15, maxdim = maxdim) + # @test maxlinkdim(M) == 64 + # sM0 = siteinds(M0) + # sM = siteinds(M) + # for n in 1:N + # @test hassameinds(sM[n], sM0[n]) + # end + # @set_warn_order 16 begin + # prodM = product(gates, prod(M0); apply_dag = true) + # @test prod(M) ≈ prodM rtol = 1e-7 + # end + #end + + #@testset "Mixed state noisy evolution" begin + # prepend!(os, os_noise) + # gates = ops(os, s) + # M = product(gates, M0; + # apply_dag = true, cutoff = 1e-15, maxdim = maxdim-1) + # @test maxlinkdim(M) == 64 + # sM0 = siteinds(M0) + # sM = siteinds(M) + # for n in 1:N + # @test hassameinds(sM[n], sM0[n]) + # end + # @set_warn_order 16 begin + # prodM = product(gates, prod(M0); apply_dag = true) + # @test prod(M) ≈ prodM rtol = 1e-7 + # end + #end - @testset "Gate evolution open system" begin - N = 8 - osX = [("X", n) for n in 1:N] - osZ = [("Z", n) for n in 1:N] - osSw = [("SWAP", n, n + 2) for n in 1:(N - 2)] - osCx = [("CX", n, n + 3) for n in 1:(N - 3)] - osT = [("CCX", n, n + 1, n + 3) for n in 1:(N - 3)] - osRx = [("Rx", n, (θ=π,)) for n in 1:N] - os = vcat(osX, osSw, osRx, osZ, osCx, osT) + end - s = siteinds("Qubit", N) - gates = ops(os, s) + @testset "Gate evolution open system" begin + N = 8 + osX = [("X", n) for n in 1:N] + osZ = [("Z", n) for n in 1:N] + osSw = [("SWAP", n, n + 2) for n in 1:(N - 2)] + osCx = [("CX", n, n + 3) for n in 1:(N - 3)] + osT = [("CCX", n, n + 1, n + 3) for n in 1:(N - 3)] + osRx = [("Rx", n, (θ = π,)) for n in 1:N] + os = vcat(osX, osSw, osRx, osZ, osCx, osT) - M0 = MPO(s, "Id") + s = siteinds("Qubit", N) + gates = ops(os, s) - # Apply the gates + M0 = MPO(s, "Id") - s0 = siteinds(M0) + # Apply the gates - M = apply(gates, M0; apply_dag=true, cutoff=1e-15, maxdim=500, svd_alg="qr_iteration") + s0 = siteinds(M0) - s = siteinds(M) - for n in 1:N - @assert hassameinds(s[n], s0[n]) - end + M = apply(gates, M0; apply_dag = true, cutoff = 1.0e-15, maxdim = 500, svd_alg = "qr_iteration") - @set_warn_order 18 begin - prodM = apply(gates, prod(M0); apply_dag=true) - @test prod(M) ≈ prodM rtol = 1e-6 - end - end + s = siteinds(M) + for n in 1:N + @assert hassameinds(s[n], s0[n]) + end - @testset "Gate evolution state" begin - N = 10 + @set_warn_order 18 begin + prodM = apply(gates, prod(M0); apply_dag = true) + @test prod(M) ≈ prodM rtol = 1.0e-6 + end + end - osX = [("X", n) for n in 1:N] - osZ = [("Z", n) for n in 1:N] - osSw = [("SWAP", n, n + 1) for n in 1:(N - 1)] - osCx = [("CX", n, n + 1) for n in 1:(N - 1)] - osT = [("CCX", n, n + 2, n + 4) for n in 1:(N - 4)] - os = vcat(osX, osSw, osZ, osCx, osT) + @testset "Gate evolution state" begin + N = 10 - s = siteinds("Qubit", N) - gates = ops(os, s) + osX = [("X", n) for n in 1:N] + osZ = [("Z", n) for n in 1:N] + osSw = [("SWAP", n, n + 1) for n in 1:(N - 1)] + osCx = [("CX", n, n + 1) for n in 1:(N - 1)] + osT = [("CCX", n, n + 2, n + 4) for n in 1:(N - 4)] + os = vcat(osX, osSw, osZ, osCx, osT) - ψ0 = MPS(s, "0") + s = siteinds("Qubit", N) + gates = ops(os, s) - # Apply the gates - ψ = apply(gates, ψ0; cutoff=1e-15, maxdim=100) + ψ0 = MPS(s, "0") - prodψ = apply(gates, prod(ψ0)) - @test prod(ψ) ≈ prodψ rtol = 1e-4 - end + # Apply the gates + ψ = apply(gates, ψ0; cutoff = 1.0e-15, maxdim = 100) - @testset "With fermions" begin - N = 3 + prodψ = apply(gates, prod(ψ0)) + @test prod(ψ) ≈ prodψ rtol = 1.0e-4 + end - s = siteinds("Fermion", N; conserve_qns=true) + @testset "With fermions" begin + N = 3 - # Ground state |000⟩ - ψ000 = MPS(s, "0") + s = siteinds("Fermion", N; conserve_qns = true) - # Start state |011⟩ - ψ011 = MPS(s, n -> n == 2 || n == 3 ? "1" : "0") + # Ground state |000⟩ + ψ000 = MPS(s, "0") - # Reference state |110⟩ - ψ110 = MPS(s, n -> n == 1 || n == 2 ? "1" : "0") + # Start state |011⟩ + ψ011 = MPS(s, n -> n == 2 || n == 3 ? "1" : "0") - function ITensors.op(::OpName"CdagC1", ::SiteType, s1::Index, s2::Index) - return op("Cdag", s1) * op("C", s2) - end + # Reference state |110⟩ + ψ110 = MPS(s, n -> n == 1 || n == 2 ? "1" : "0") - os = [("CdagC1", 1, 3)] - Os = ops(os, s) + function ITensors.op(::OpName"CdagC1", ::SiteType, s1::Index, s2::Index) + return op("Cdag", s1) * op("C", s2) + end - # Results in -|110⟩ - ψ1 = product(Os, ψ011; cutoff=1e-15) + os = [("CdagC1", 1, 3)] + Os = ops(os, s) - @test inner(ψ1, ψ110) == -1 + # Results in -|110⟩ + ψ1 = product(Os, ψ011; cutoff = 1.0e-15) - a = OpSum() - a += "Cdag", 1, "C", 3 - H = MPO(a, s) + @test inner(ψ1, ψ110) == -1 - # Results in -|110⟩ - ψ2 = noprime(contract(H, ψ011; cutoff=1e-15)) + a = OpSum() + a += "Cdag", 1, "C", 3 + H = MPO(a, s) - @test inner(ψ2, ψ110) == -1 - end + # Results in -|110⟩ + ψ2 = noprime(contract(H, ψ011; cutoff = 1.0e-15)) - @testset "Spinless fermion (gate evolution)" begin - N = 6 - - s = siteinds("Fermion", N; conserve_qns=true) - - # Starting state - ψ0 = MPS(s, n -> isodd(n) ? "0" : "1") - - t = 1.0 - U = 1.0 - opsum = OpSum() - for b in 1:(N - 1) - opsum .-= t, "Cdag", b, "C", b + 1 - opsum .-= t, "Cdag", b + 1, "C", b - opsum .+= U, "N", b, "N", b + 1 - end - H = MPO(opsum, s) - - sweeps = Sweeps(6) - maxdim!(sweeps, 10, 20, 40) - cutoff!(sweeps, 1E-12) - energy, ψ0 = dmrg(H, ψ0, sweeps; outputlevel=0) - - function ITensors.op(::OpName"CdagC2", ::SiteType, s1::Index, s2::Index) - return op("Cdag", s1) * op("C", s2) - end - - function ITensors.op( - ::OpName"CCCC", ::SiteType, s1::Index, s2::Index, s3::Index, s4::Index - ) - return -1 * op("Cdag", s1) * op("Cdag", s2) * op("C", s3) * op("C", s4) - end - - for i in 1:(N - 1), j in (i + 1):N - G1 = op("CdagC2", s, i, j) - - @disable_warn_order begin - G2 = op("Cdag", s, i) - for n in (i + 1):(j - 1) - G2 *= op("F", s, n) - end - G2 *= op("C", s, j) + @test inner(ψ2, ψ110) == -1 end - opsum = OpSum() - opsum += "Cdag", i, "C", j - G3 = MPO(opsum, s) - - A_OP = prod(product(G1, ψ0; cutoff=1e-6)) - - A_OPS = noprime(G2 * prod(ψ0)) - - A_MPO = noprime(prod(contract(G3, ψ0; cutoff=1e-6))) - - @test A_OP ≈ A_OPS - @test A_OP ≈ A_MPO - end + @testset "Spinless fermion (gate evolution)" begin + N = 6 + + s = siteinds("Fermion", N; conserve_qns = true) + + # Starting state + ψ0 = MPS(s, n -> isodd(n) ? "0" : "1") + + t = 1.0 + U = 1.0 + opsum = OpSum() + for b in 1:(N - 1) + opsum .-= t, "Cdag", b, "C", b + 1 + opsum .-= t, "Cdag", b + 1, "C", b + opsum .+= U, "N", b, "N", b + 1 + end + H = MPO(opsum, s) + + sweeps = Sweeps(6) + maxdim!(sweeps, 10, 20, 40) + cutoff!(sweeps, 1.0e-12) + energy, ψ0 = dmrg(H, ψ0, sweeps; outputlevel = 0) + + function ITensors.op(::OpName"CdagC2", ::SiteType, s1::Index, s2::Index) + return op("Cdag", s1) * op("C", s2) + end + + function ITensors.op( + ::OpName"CCCC", ::SiteType, s1::Index, s2::Index, s3::Index, s4::Index + ) + return -1 * op("Cdag", s1) * op("Cdag", s2) * op("C", s3) * op("C", s4) + end + + for i in 1:(N - 1), j in (i + 1):N + G1 = op("CdagC2", s, i, j) + + @disable_warn_order begin + G2 = op("Cdag", s, i) + for n in (i + 1):(j - 1) + G2 *= op("F", s, n) + end + G2 *= op("C", s, j) + end + + opsum = OpSum() + opsum += "Cdag", i, "C", j + G3 = MPO(opsum, s) + + A_OP = prod(product(G1, ψ0; cutoff = 1.0e-6)) + + A_OPS = noprime(G2 * prod(ψ0)) + + A_MPO = noprime(prod(contract(G3, ψ0; cutoff = 1.0e-6))) + + @test A_OP ≈ A_OPS + @test A_OP ≈ A_MPO + end + + for i in 1:(N - 3), j in (i + 1):(N - 2), k in (j + 1):(N - 1), l in (k + 1):N + G1 = op("CCCC", s, i, j, k, l) + @disable_warn_order begin + G2 = -1 * op("Cdag", s, i) + for n in (i + 1):(j - 1) + G2 *= op("F", s, n) + end + G2 *= op("Cdag", s, j) + for n in (j + 1):(k - 1) + G2 *= op("Id", s, n) + end + G2 *= op("C", s, k) + for n in (k + 1):(l - 1) + G2 *= op("F", s, n) + end + G2 *= op("C", s, l) + + opsum = OpSum() + opsum += "Cdag", i, "Cdag", j, "C", k, "C", l + G3 = MPO(opsum, s) + + A_OP = prod(product(G1, ψ0; cutoff = 1.0e-16)) + + A_OPS = noprime(G2 * prod(ψ0)) + + A_MPO = noprime(prod(contract(G3, ψ0; cutoff = 1.0e-16))) + end + @test A_OPS ≈ A_OP rtol = 1.0e-12 + @test A_MPO ≈ A_OP rtol = 1.0e-12 + end + end - for i in 1:(N - 3), j in (i + 1):(N - 2), k in (j + 1):(N - 1), l in (k + 1):N - G1 = op("CCCC", s, i, j, k, l) - @disable_warn_order begin - G2 = -1 * op("Cdag", s, i) - for n in (i + 1):(j - 1) - G2 *= op("F", s, n) - end - G2 *= op("Cdag", s, j) - for n in (j + 1):(k - 1) - G2 *= op("Id", s, n) - end - G2 *= op("C", s, k) - for n in (k + 1):(l - 1) - G2 *= op("F", s, n) - end - G2 *= op("C", s, l) + @testset "Spinful Fermions (Electron) gate evolution" begin + N = 8 + s = siteinds("Electron", N; conserve_qns = true) + ψ0 = random_mps(s, n -> isodd(n) ? "↑" : "↓") + t = 1.0 + U = 1.0 + opsum = OpSum() + for b in 1:(N - 1) + opsum .-= t, "Cdagup", b, "Cup", b + 1 + opsum .-= t, "Cdagup", b + 1, "Cup", b + opsum .-= t, "Cdagdn", b, "Cdn", b + 1 + opsum .-= t, "Cdagdn", b + 1, "Cdn", b + end + for n in 1:N + opsum .+= U, "Nupdn", n + end + H = MPO(opsum, s) + sweeps = Sweeps(6) + maxdim!(sweeps, 10, 20, 40) + cutoff!(sweeps, 1.0e-12) + energy, ψ = dmrg(H, ψ0, sweeps; outputlevel = 0) + + function ITensors.op(::OpName"CCup", ::SiteType"Electron", s1::Index, s2::Index) + return op("Adagup * F", s1) * op("Aup", s2) + end + + for i in 1:(N - 1), j in (i + 1):N + opsum = OpSum() + opsum += "Cdagup", i, "Cup", j + G1 = MPO(opsum, s) + G2 = op("CCup", s, i, j) + A_MPO = prod(noprime(contract(G1, ψ; cutoff = 1.0e-8))) + A_OP = prod(product(G2, ψ; cutoff = 1.0e-8)) + @test A_MPO ≈ A_OP atol = 1.0e-4 + end + end + end - opsum = OpSum() - opsum += "Cdag", i, "Cdag", j, "C", k, "C", l - G3 = MPO(opsum, s) + @testset "dense conversion of MPS" begin + N = 4 + s = siteinds("S=1/2", N; conserve_qns = true) + QM = random_mps(s, ["Up", "Dn", "Up", "Dn"]; linkdims = 4) + qsz1 = scalar(QM[1] * op("Sz", s[1]) * dag(prime(QM[1], "Site"))) - A_OP = prod(product(G1, ψ0; cutoff=1e-16)) + M = dense(QM) + @test !hasqns(M[1]) + sz1 = scalar(M[1] * op("Sz", removeqns(s[1])) * dag(prime(M[1], "Site"))) + @test sz1 ≈ qsz1 + end - A_OPS = noprime(G2 * prod(ψ0)) + @testset "inner of MPS with more than one site Index" begin + s = siteinds("S=½", 4) + sout = addtags.(s, "out") + sin = addtags.(s, "in") + sinds = IndexSet.(sout, sin) + Cs = combiner.(sinds) + cinds = combinedind.(Cs) + ψ = random_mps(cinds) + @test norm(ψ) ≈ 1 + @test inner(ψ, ψ) ≈ 1 + ψ .*= dag.(Cs) + @test norm(ψ) ≈ 1 + @test inner(ψ, ψ) ≈ 1 + end - A_MPO = noprime(prod(contract(G3, ψ0; cutoff=1e-16))) + @testset "inner(::MPS, ::MPO, ::MPS) with more than one site Index" begin + N = 8 + s = siteinds("S=1/2", N) + a = OpSum() + for j in 1:(N - 1) + a .+= 0.5, "S+", j, "S-", j + 1 + a .+= 0.5, "S-", j, "S+", j + 1 + a .+= "Sz", j, "Sz", j + 1 end - @test A_OPS ≈ A_OP rtol = 1e-12 - @test A_MPO ≈ A_OP rtol = 1e-12 - end + H = MPO(a, s) + ψ = random_mps(s, n -> isodd(n) ? "↑" : "↓"; linkdims = 10) + # Create MPO/MPS with pairs of sites merged + H2 = MPO([H[b] * H[b + 1] for b in 1:2:N]) + ψ2 = MPS([ψ[b] * ψ[b + 1] for b in 1:2:N]) + @test inner(ψ, ψ) ≈ inner(ψ2, ψ2) + @test inner(ψ', H, ψ) ≈ inner(ψ2', H2, ψ2) + @test_throws ErrorException inner(ψ2, ψ2') + @test_throws ErrorException inner(ψ2, H2, ψ2) end - @testset "Spinful Fermions (Electron) gate evolution" begin - N = 8 - s = siteinds("Electron", N; conserve_qns=true) - ψ0 = random_mps(s, n -> isodd(n) ? "↑" : "↓") - t = 1.0 - U = 1.0 - opsum = OpSum() - for b in 1:(N - 1) - opsum .-= t, "Cdagup", b, "Cup", b + 1 - opsum .-= t, "Cdagup", b + 1, "Cup", b - opsum .-= t, "Cdagdn", b, "Cdn", b + 1 - opsum .-= t, "Cdagdn", b + 1, "Cdn", b - end - for n in 1:N - opsum .+= U, "Nupdn", n - end - H = MPO(opsum, s) - sweeps = Sweeps(6) - maxdim!(sweeps, 10, 20, 40) - cutoff!(sweeps, 1E-12) - energy, ψ = dmrg(H, ψ0, sweeps; outputlevel=0) - - function ITensors.op(::OpName"CCup", ::SiteType"Electron", s1::Index, s2::Index) - return op("Adagup * F", s1) * op("Aup", s2) - end - - for i in 1:(N - 1), j in (i + 1):N - opsum = OpSum() - opsum += "Cdagup", i, "Cup", j - G1 = MPO(opsum, s) - G2 = op("CCup", s, i, j) - A_MPO = prod(noprime(contract(G1, ψ; cutoff=1e-8))) - A_OP = prod(product(G2, ψ; cutoff=1e-8)) - @test A_MPO ≈ A_OP atol = 1E-4 - end + @testset "orthogonalize! on MPS with no link indices" begin + N = 4 + s = siteinds("S=1/2", N) + ψ = MPS([itensor(randn(ComplexF64, 2), s[n]) for n in 1:N]) + @test all(==(IndexSet()), linkinds(all, ψ)) + ϕ = orthogonalize(ψ, 2) + @test ITensorMPS.hasdefaultlinktags(ϕ) + @test ortho_lims(ϕ) == 2:2 + @test ITensorMPS.dist(ψ, ϕ) ≈ 0 atol = 1.0e-6 + # TODO: use this instead? + # @test lognorm(ψ - ϕ) < -16 + @test norm(ψ - ϕ) ≈ 0 atol = 1.0e-6 end - end - - @testset "dense conversion of MPS" begin - N = 4 - s = siteinds("S=1/2", N; conserve_qns=true) - QM = random_mps(s, ["Up", "Dn", "Up", "Dn"]; linkdims=4) - qsz1 = scalar(QM[1] * op("Sz", s[1]) * dag(prime(QM[1], "Site"))) - - M = dense(QM) - @test !hasqns(M[1]) - sz1 = scalar(M[1] * op("Sz", removeqns(s[1])) * dag(prime(M[1], "Site"))) - @test sz1 ≈ qsz1 - end - - @testset "inner of MPS with more than one site Index" begin - s = siteinds("S=½", 4) - sout = addtags.(s, "out") - sin = addtags.(s, "in") - sinds = IndexSet.(sout, sin) - Cs = combiner.(sinds) - cinds = combinedind.(Cs) - ψ = random_mps(cinds) - @test norm(ψ) ≈ 1 - @test inner(ψ, ψ) ≈ 1 - ψ .*= dag.(Cs) - @test norm(ψ) ≈ 1 - @test inner(ψ, ψ) ≈ 1 - end - - @testset "inner(::MPS, ::MPO, ::MPS) with more than one site Index" begin - N = 8 - s = siteinds("S=1/2", N) - a = OpSum() - for j in 1:(N - 1) - a .+= 0.5, "S+", j, "S-", j + 1 - a .+= 0.5, "S-", j, "S+", j + 1 - a .+= "Sz", j, "Sz", j + 1 + + @testset "MPO from MPS with no link indices" begin + N = 4 + s = siteinds("S=1/2", N) + ψ = MPS([itensor(randn(ComplexF64, 2), s[n]) for n in 1:N]) + ρ = outer(ψ', ψ) + @test !ITensorMPS.hasnolinkinds(ρ) + @test inner(ρ, ρ) ≈ inner(ψ, ψ)^2 + @test inner(ψ', ρ, ψ) ≈ inner(ψ, ψ)^2 + + # Deprecated syntax + @test_deprecated outer(ψ, ψ) + @test_deprecated inner(ψ, ψ') + @test_deprecated inner(ψ, ρ, ψ) + + ρ = @test_deprecated MPO(ψ) + @test !ITensorMPS.hasnolinkinds(ρ) + @test inner(ρ, ρ) ≈ inner(ψ, ψ)^2 + @test inner(ψ', ρ, ψ) ≈ inner(ψ, ψ)^2 + end + + @testset "Truncate MPO with no link indices" begin + N = 4 + s = siteinds("S=1/2", N) + M = MPO([itensor(randn(ComplexF64, 2, 2), s[n]', dag(s[n])) for n in 1:N]) + @test ITensorMPS.hasnolinkinds(M) + Mt = truncate(M; cutoff = 1.0e-15) + @test ITensorMPS.hasdefaultlinktags(Mt) + @test norm(M - Mt) ≈ 0 atol = 1.0e-12 end - H = MPO(a, s) - ψ = random_mps(s, n -> isodd(n) ? "↑" : "↓"; linkdims=10) - # Create MPO/MPS with pairs of sites merged - H2 = MPO([H[b] * H[b + 1] for b in 1:2:N]) - ψ2 = MPS([ψ[b] * ψ[b + 1] for b in 1:2:N]) - @test inner(ψ, ψ) ≈ inner(ψ2, ψ2) - @test inner(ψ', H, ψ) ≈ inner(ψ2', H2, ψ2) - @test_throws ErrorException inner(ψ2, ψ2') - @test_throws ErrorException inner(ψ2, H2, ψ2) - end - - @testset "orthogonalize! on MPS with no link indices" begin - N = 4 - s = siteinds("S=1/2", N) - ψ = MPS([itensor(randn(ComplexF64, 2), s[n]) for n in 1:N]) - @test all(==(IndexSet()), linkinds(all, ψ)) - ϕ = orthogonalize(ψ, 2) - @test ITensorMPS.hasdefaultlinktags(ϕ) - @test ortho_lims(ϕ) == 2:2 - @test ITensorMPS.dist(ψ, ϕ) ≈ 0 atol = 1e-6 - # TODO: use this instead? - # @test lognorm(ψ - ϕ) < -16 - @test norm(ψ - ϕ) ≈ 0 atol = 1e-6 - end - - @testset "MPO from MPS with no link indices" begin - N = 4 - s = siteinds("S=1/2", N) - ψ = MPS([itensor(randn(ComplexF64, 2), s[n]) for n in 1:N]) - ρ = outer(ψ', ψ) - @test !ITensorMPS.hasnolinkinds(ρ) - @test inner(ρ, ρ) ≈ inner(ψ, ψ)^2 - @test inner(ψ', ρ, ψ) ≈ inner(ψ, ψ)^2 - - # Deprecated syntax - @test_deprecated outer(ψ, ψ) - @test_deprecated inner(ψ, ψ') - @test_deprecated inner(ψ, ρ, ψ) - - ρ = @test_deprecated MPO(ψ) - @test !ITensorMPS.hasnolinkinds(ρ) - @test inner(ρ, ρ) ≈ inner(ψ, ψ)^2 - @test inner(ψ', ρ, ψ) ≈ inner(ψ, ψ)^2 - end - - @testset "Truncate MPO with no link indices" begin - N = 4 - s = siteinds("S=1/2", N) - M = MPO([itensor(randn(ComplexF64, 2, 2), s[n]', dag(s[n])) for n in 1:N]) - @test ITensorMPS.hasnolinkinds(M) - Mt = truncate(M; cutoff=1e-15) - @test ITensorMPS.hasdefaultlinktags(Mt) - @test norm(M - Mt) ≈ 0 atol = 1e-12 - end end end diff --git a/test/base/test_qnmpo.jl b/test/base/test_qnmpo.jl index d3c6b7f..ff2791f 100644 --- a/test/base/test_qnmpo.jl +++ b/test/base/test_qnmpo.jl @@ -1,376 +1,376 @@ using ITensors, Test function op_mpo(sites, which_op, j) - left_ops = "Id" - right_ops = "Id" - if has_fermion_string(which_op, sites[j]) - left_ops = "F" - end - ops = [n < j ? left_ops : (n > j ? right_ops : which_op) for n in 1:length(sites)] - return MPO([op(ops[n], sites[n]) for n in 1:length(sites)]) + left_ops = "Id" + right_ops = "Id" + if has_fermion_string(which_op, sites[j]) + left_ops = "F" + end + ops = [n < j ? left_ops : (n > j ? right_ops : which_op) for n in 1:length(sites)] + return MPO([op(ops[n], sites[n]) for n in 1:length(sites)]) end @testset "MPO Basics" begin - N = 6 - sites = [Index(QN(-1) => 1, QN(1) => 1; tags="Site,n=$n") for n in 1:N] - links = [Index(QN() => 1; tags="Links,l=$n") for n in 1:(N - 1)] - @test length(MPO()) == 0 - #O = MPO(sites) - O = MPO(N) - for i in 1:length(O) - O[i] = random_itensor(QN(), sites[i], sites[i]') - end - @test length(O) == N - - O[1] = emptyITensor(sites[1], prime(sites[1])) - @test hasind(O[1], sites[1]) - @test hasind(O[1], prime(sites[1])) - P = copy(O) - @test hasind(P[1], sites[1]) - @test hasind(P[1], prime(sites[1])) - # test constructor from Vector{ITensor} - - K = MPO(N) - K[1] = random_itensor(QN(), dag(sites[1]), sites[1]', links[1]) - for i in 2:(N - 1) - K[i] = random_itensor(QN(), dag(sites[i]), sites[i]', dag(links[i - 1]), links[i]) - end - K[N] = random_itensor(QN(), dag(sites[N]), sites[N]', dag(links[N - 1])) - - J = MPO(N) - J[1] = random_itensor(QN(), dag(sites[1]), sites[1]', links[1]) - for i in 2:(N - 1) - J[i] = random_itensor(QN(), dag(sites[i]), sites[i]', dag(links[i - 1]), links[i]) - end - J[N] = random_itensor(QN(), dag(sites[N]), sites[N]', dag(links[N - 1])) - - L = MPO(N) - L[1] = random_itensor(QN(), dag(sites[1]), sites[1]', links[1]) - for i in 2:(N - 1) - L[i] = random_itensor(QN(), dag(sites[i]), sites[i]', dag(links[i - 1]), links[i]) - end - L[N] = random_itensor(QN(), dag(sites[N]), sites[N]', dag(links[N - 1])) - - @test length(K) == N - @test ITensorMPS.data(MPO(copy(ITensorMPS.data(K)))) == ITensorMPS.data(K) - - phi = MPS(N) - phi[1] = random_itensor(QN(-1), sites[1], links[1]) - for i in 2:(N - 1) - phi[i] = random_itensor(QN(-1), sites[i], dag(links[i - 1]), links[i]) - end - phi[N] = random_itensor(QN(-1), sites[N], dag(links[N - 1])) - - psi = MPS(N) - psi[1] = random_itensor(QN(-1), sites[1], links[1]) - for i in 2:(N - 1) - psi[i] = random_itensor(QN(-1), sites[i], dag(links[i - 1]), links[i]) - end - psi[N] = random_itensor(QN(-1), sites[N], dag(links[N - 1])) - - @testset "orthogonalize!" begin - orthogonalize!(phi, 1) - orthogonalize!(K, 1) - orig_inner = ⋅(phi', K, phi) - orthogonalize!(phi, div(N, 2)) - orthogonalize!(K, div(N, 2)) - @test ⋅(phi', K, phi) ≈ orig_inner - end - - @testset "inner " begin - @test maxlinkdim(K) == 1 - phidag = dag(phi) - prime!(phidag) - phiKpsi = phidag[1] * K[1] * psi[1] - for j in 2:N - phiKpsi *= phidag[j] * K[j] * psi[j] + N = 6 + sites = [Index(QN(-1) => 1, QN(1) => 1; tags = "Site,n=$n") for n in 1:N] + links = [Index(QN() => 1; tags = "Links,l=$n") for n in 1:(N - 1)] + @test length(MPO()) == 0 + #O = MPO(sites) + O = MPO(N) + for i in 1:length(O) + O[i] = random_itensor(QN(), sites[i], sites[i]') end - @test phiKpsi[] ≈ inner(phi', K, psi) - end - - @testset "inner " begin - phidag = dag(phi) - prime!(phidag, 2) - Jdag = dag(J) - prime!(Jdag) - for j in eachindex(Jdag) - swapprime!(Jdag[j], 2, 3) - swapprime!(Jdag[j], 1, 2) - swapprime!(Jdag[j], 3, 1) + @test length(O) == N + + O[1] = emptyITensor(sites[1], prime(sites[1])) + @test hasind(O[1], sites[1]) + @test hasind(O[1], prime(sites[1])) + P = copy(O) + @test hasind(P[1], sites[1]) + @test hasind(P[1], prime(sites[1])) + # test constructor from Vector{ITensor} + + K = MPO(N) + K[1] = random_itensor(QN(), dag(sites[1]), sites[1]', links[1]) + for i in 2:(N - 1) + K[i] = random_itensor(QN(), dag(sites[i]), sites[i]', dag(links[i - 1]), links[i]) end + K[N] = random_itensor(QN(), dag(sites[N]), sites[N]', dag(links[N - 1])) - phiJdagKpsi = phidag[1] * Jdag[1] * K[1] * psi[1] - for j in eachindex(psi)[2:end] - phiJdagKpsi = phiJdagKpsi * phidag[j] * Jdag[j] * K[j] * psi[j] + J = MPO(N) + J[1] = random_itensor(QN(), dag(sites[1]), sites[1]', links[1]) + for i in 2:(N - 1) + J[i] = random_itensor(QN(), dag(sites[i]), sites[i]', dag(links[i - 1]), links[i]) end + J[N] = random_itensor(QN(), dag(sites[N]), sites[N]', dag(links[N - 1])) - @test phiJdagKpsi[] ≈ inner(J, phi, K, psi) - - badsites = [Index(2, "Site") for n in 1:(N + 1)] - badpsi = random_mps(badsites) - @test_throws DimensionMismatch inner(J, phi, K, badpsi) - end - - @testset "error_contract" begin - dist = sqrt( - abs(1 + (inner(phi, phi) - 2 * real(inner(phi', K, psi))) / inner(K, psi, K, psi)) - ) - @test dist ≈ error_contract(phi, K, psi) - end - - @testset "contract" begin - @test maxlinkdim(K) == 1 - psi_out = contract(K, psi; maxdim=1) - @test inner(phi', psi_out) ≈ inner(phi', K, psi) - @test_throws MethodError contract(K, psi; method="fakemethod") - end - - # TODO: implement add for QN MPOs and add this test back - #@testset "add(::MPO, ::MPO)" begin - # shsites = siteinds("S=1/2", N) - # M = add(K, L) - # @test length(M) == N - # k_psi = contract(K, psi, maxdim=1) - # l_psi = contract(L, psi, maxdim=1) - # @test inner(psi', k_psi + l_psi) ≈ ⋅(psi', M, psi) atol=5e-3 - # @test inner(psi', sum([k_psi, l_psi])) ≈ dot(psi', M, psi) atol=5e-3 - # for dim in 2:4 - # shsites = siteinds("S=1/2",N) - # K = basicRandomMPO(N, shsites; dim=dim) - # L = basicRandomMPO(N, shsites; dim=dim) - # M = K + L - # @test length(M) == N - # psi = random_mps(shsites) - # k_psi = contract(K, psi) - # l_psi = contract(L, psi) - # @test inner(psi, k_psi + l_psi) ≈ dot(psi, M, psi) atol=5e-3 - # @test inner(psi, sum([k_psi, l_psi])) ≈ inner(psi, M, psi) atol=5e-3 - # psi = random_mps(shsites) - # M = add(K, L; cutoff=1E-9) - # k_psi = contract(K, psi) - # l_psi = contract(L, psi) - # @test inner(psi, k_psi + l_psi) ≈ inner(psi, M, psi) atol=5e-3 - # end - #end - - @testset "contract(::MPO, ::MPO)" begin - @test maxlinkdim(K) == 1 - @test maxlinkdim(L) == 1 - KL = contract(prime(K), L; maxdim=1) - Lpsi = contract(L, psi; maxdim=1) - psi_kl_out = contract(prime(K), Lpsi; maxdim=1) - @test inner(psi'', KL, psi) ≈ inner(psi'', psi_kl_out) atol = 5e-3 - end - - @testset "contract(::MPO, ::MPO) without truncation" begin - s = siteinds("Electron", 10; conserve_qns=true) - j1, j2 = 2, 4 - Cdagup = op_mpo(s, "Cdagup", j1) - Cdagdn = op_mpo(s, "Cdagdn", j2) - Cdagmpo = apply(Cdagup, Cdagdn; alg="naive", truncate=false) - @test norm(Cdagmpo) ≈ 2^length(s) / 2 - for j in 1:length(s) - if (j == j1) || (j == j2) - @test norm(Cdagmpo[j]) ≈ √2 - else - @test norm(Cdagmpo[j]) ≈ 2 - end + L = MPO(N) + L[1] = random_itensor(QN(), dag(sites[1]), sites[1]', links[1]) + for i in 2:(N - 1) + L[i] = random_itensor(QN(), dag(sites[i]), sites[i]', dag(links[i - 1]), links[i]) end - end - - @testset "*(::MPO, ::MPO)" begin - @test maxlinkdim(K) == 1 - @test maxlinkdim(L) == 1 - KL = *(prime(K), L; maxdim=1) - psi_kl_out = *(prime(K), *(L, psi; maxdim=1); maxdim=1) - @test ⋅(psi'', KL, psi) ≈ dot(psi'', psi_kl_out) atol = 5e-3 - end - - sites = siteinds("S=1/2", N) - O = MPO(sites, "Sz") - @test length(O) == N # just make sure this works - - @test_throws ArgumentError random_mpo(sites, 2) - @test isnothing(linkind(MPO(fill(ITensor(), N), 0, N + 1), 1)) -end + L[N] = random_itensor(QN(), dag(sites[N]), sites[N]', dag(links[N - 1])) -@testset "splitblocks" begin - N = 4 - sites = siteinds("S=1", N; conserve_qns=true) - opsum = OpSum() - for j in 1:(N - 1) - opsum .+= 0.5, "S+", j, "S-", j + 1 - opsum .+= 0.5, "S-", j, "S+", j + 1 - opsum .+= "Sz", j, "Sz", j + 1 - end - H = MPO(opsum, sites; splitblocks=false) - - # Split the tensors to make them more sparse - # Drops zero blocks by default - H̃ = splitblocks(linkinds, H) - - H̃2 = MPO(opsum, sites; splitblocks=true) - - # Defaults to true - H̃3 = MPO(opsum, sites) - - @test prod(H) ≈ prod(H̃) - @test prod(H) ≈ prod(H̃2) - @test prod(H) ≈ prod(H̃3) - - @test nnz(H[1]) == 9 - @test nnz(H[2]) == 18 - @test nnz(H[3]) == 18 - @test nnz(H[4]) == 9 - - @test nnzblocks(H[1]) == 9 - @test nnzblocks(H[2]) == 18 - @test nnzblocks(H[3]) == 18 - @test nnzblocks(H[4]) == 9 - - @test nnz(H̃[1]) == nnzblocks(H̃[1]) == count(≠(0), H[1]) == count(≠(0), H̃[1]) == 9 - @test nnz(H̃[2]) == nnzblocks(H̃[2]) == count(≠(0), H[2]) == count(≠(0), H̃[2]) == 18 - @test nnz(H̃[3]) == nnzblocks(H̃[3]) == count(≠(0), H[3]) == count(≠(0), H̃[3]) == 18 - @test nnz(H̃[4]) == nnzblocks(H̃[4]) == count(≠(0), H[4]) == count(≠(0), H̃[4]) == 9 - - @test nnz(H̃2[1]) == nnzblocks(H̃2[1]) == count(≠(0), H[1]) == count(≠(0), H̃2[1]) == 9 - @test nnz(H̃2[2]) == nnzblocks(H̃2[2]) == count(≠(0), H[2]) == count(≠(0), H̃2[2]) == 18 - @test nnz(H̃2[3]) == nnzblocks(H̃2[3]) == count(≠(0), H[3]) == count(≠(0), H̃2[3]) == 18 - @test nnz(H̃2[4]) == nnzblocks(H̃2[4]) == count(≠(0), H[4]) == count(≠(0), H̃2[4]) == 9 - - @test nnz(H̃3[1]) == nnzblocks(H̃3[1]) == count(≠(0), H[1]) == count(≠(0), H̃3[1]) == 9 - @test nnz(H̃3[2]) == nnzblocks(H̃3[2]) == count(≠(0), H[2]) == count(≠(0), H̃3[2]) == 18 - @test nnz(H̃3[3]) == nnzblocks(H̃3[3]) == count(≠(0), H[3]) == count(≠(0), H̃3[3]) == 18 - @test nnz(H̃3[4]) == nnzblocks(H̃3[4]) == count(≠(0), H[4]) == count(≠(0), H̃3[4]) == 9 -end + @test length(K) == N + @test ITensorMPS.data(MPO(copy(ITensorMPS.data(K)))) == ITensorMPS.data(K) -@testset "MPO operations with one or two sites" begin - for N in 1:4, conserve_szparity in (true, false) - s = siteinds("S=1/2", N; conserve_szparity=conserve_szparity) - a = OpSum() - h = 0.5 - for j in 1:(N - 1) - a .-= 1, "Sx", j, "Sx", j + 1 + phi = MPS(N) + phi[1] = random_itensor(QN(-1), sites[1], links[1]) + for i in 2:(N - 1) + phi[i] = random_itensor(QN(-1), sites[i], dag(links[i - 1]), links[i]) end - for j in 1:N - a .+= h, "Sz", j + phi[N] = random_itensor(QN(-1), sites[N], dag(links[N - 1])) + + psi = MPS(N) + psi[1] = random_itensor(QN(-1), sites[1], links[1]) + for i in 2:(N - 1) + psi[i] = random_itensor(QN(-1), sites[i], dag(links[i - 1]), links[i]) + end + psi[N] = random_itensor(QN(-1), sites[N], dag(links[N - 1])) + + @testset "orthogonalize!" begin + orthogonalize!(phi, 1) + orthogonalize!(K, 1) + orig_inner = ⋅(phi', K, phi) + orthogonalize!(phi, div(N, 2)) + orthogonalize!(K, div(N, 2)) + @test ⋅(phi', K, phi) ≈ orig_inner end - H = MPO(a, s) - if conserve_szparity - ψ = random_mps(s, n -> isodd(n) ? "↑" : "↓") - else - ψ = random_mps(s) + + @testset "inner " begin + @test maxlinkdim(K) == 1 + phidag = dag(phi) + prime!(phidag) + phiKpsi = phidag[1] * K[1] * psi[1] + for j in 2:N + phiKpsi *= phidag[j] * K[j] * psi[j] + end + @test phiKpsi[] ≈ inner(phi', K, psi) + end + + @testset "inner " begin + phidag = dag(phi) + prime!(phidag, 2) + Jdag = dag(J) + prime!(Jdag) + for j in eachindex(Jdag) + swapprime!(Jdag[j], 2, 3) + swapprime!(Jdag[j], 1, 2) + swapprime!(Jdag[j], 3, 1) + end + + phiJdagKpsi = phidag[1] * Jdag[1] * K[1] * psi[1] + for j in eachindex(psi)[2:end] + phiJdagKpsi = phiJdagKpsi * phidag[j] * Jdag[j] * K[j] * psi[j] + end + + @test phiJdagKpsi[] ≈ inner(J, phi, K, psi) + + badsites = [Index(2, "Site") for n in 1:(N + 1)] + badpsi = random_mps(badsites) + @test_throws DimensionMismatch inner(J, phi, K, badpsi) end - # MPO * MPS - Hψ = H * ψ - @test prod(Hψ) ≈ prod(H) * prod(ψ) - - # MPO * MPO - H² = H' * H - @test prod(H²) ≈ prod(H') * prod(H) - - # DMRG - sweeps = Sweeps(3) - maxdim!(sweeps, 10) - if N == 1 - @test_throws ErrorException dmrg( - H, ψ, sweeps; eigsolve_maxiter=10, eigsolve_krylovdim=10, outputlevel=0 - ) - else - e, ψgs = dmrg(H, ψ, sweeps; eigsolve_maxiter=10, eigsolve_krylovdim=10, outputlevel=0) - @test prod(H) * prod(ψgs) ≈ e * prod(ψgs)' - D, V = eigen(prod(H); ishermitian=true) - if hasqns(ψ) - fluxψ = flux(ψ) - d = commonind(D, V) - b = ITensors.findfirstblock(indblock -> ITensors.qn(indblock) == fluxψ, d) - @test e ≈ minimum(storage(D[Block(b, b)])) - else - @test e ≈ minimum(storage(D)) - end + @testset "error_contract" begin + dist = sqrt( + abs(1 + (inner(phi, phi) - 2 * real(inner(phi', K, psi))) / inner(K, psi, K, psi)) + ) + @test dist ≈ error_contract(phi, K, psi) + end + + @testset "contract" begin + @test maxlinkdim(K) == 1 + psi_out = contract(K, psi; maxdim = 1) + @test inner(phi', psi_out) ≈ inner(phi', K, psi) + @test_throws MethodError contract(K, psi; method = "fakemethod") + end + + # TODO: implement add for QN MPOs and add this test back + #@testset "add(::MPO, ::MPO)" begin + # shsites = siteinds("S=1/2", N) + # M = add(K, L) + # @test length(M) == N + # k_psi = contract(K, psi, maxdim=1) + # l_psi = contract(L, psi, maxdim=1) + # @test inner(psi', k_psi + l_psi) ≈ ⋅(psi', M, psi) atol=5e-3 + # @test inner(psi', sum([k_psi, l_psi])) ≈ dot(psi', M, psi) atol=5e-3 + # for dim in 2:4 + # shsites = siteinds("S=1/2",N) + # K = basicRandomMPO(N, shsites; dim=dim) + # L = basicRandomMPO(N, shsites; dim=dim) + # M = K + L + # @test length(M) == N + # psi = random_mps(shsites) + # k_psi = contract(K, psi) + # l_psi = contract(L, psi) + # @test inner(psi, k_psi + l_psi) ≈ dot(psi, M, psi) atol=5e-3 + # @test inner(psi, sum([k_psi, l_psi])) ≈ inner(psi, M, psi) atol=5e-3 + # psi = random_mps(shsites) + # M = add(K, L; cutoff=1E-9) + # k_psi = contract(K, psi) + # l_psi = contract(L, psi) + # @test inner(psi, k_psi + l_psi) ≈ inner(psi, M, psi) atol=5e-3 + # end + #end + + @testset "contract(::MPO, ::MPO)" begin + @test maxlinkdim(K) == 1 + @test maxlinkdim(L) == 1 + KL = contract(prime(K), L; maxdim = 1) + Lpsi = contract(L, psi; maxdim = 1) + psi_kl_out = contract(prime(K), Lpsi; maxdim = 1) + @test inner(psi'', KL, psi) ≈ inner(psi'', psi_kl_out) atol = 5.0e-3 + end + + @testset "contract(::MPO, ::MPO) without truncation" begin + s = siteinds("Electron", 10; conserve_qns = true) + j1, j2 = 2, 4 + Cdagup = op_mpo(s, "Cdagup", j1) + Cdagdn = op_mpo(s, "Cdagdn", j2) + Cdagmpo = apply(Cdagup, Cdagdn; alg = "naive", truncate = false) + @test norm(Cdagmpo) ≈ 2^length(s) / 2 + for j in 1:length(s) + if (j == j1) || (j == j2) + @test norm(Cdagmpo[j]) ≈ √2 + else + @test norm(Cdagmpo[j]) ≈ 2 + end + end + end + + @testset "*(::MPO, ::MPO)" begin + @test maxlinkdim(K) == 1 + @test maxlinkdim(L) == 1 + KL = *(prime(K), L; maxdim = 1) + psi_kl_out = *(prime(K), *(L, psi; maxdim = 1); maxdim = 1) + @test ⋅(psi'', KL, psi) ≈ dot(psi'', psi_kl_out) atol = 5.0e-3 + end + + sites = siteinds("S=1/2", N) + O = MPO(sites, "Sz") + @test length(O) == N # just make sure this works + + @test_throws ArgumentError random_mpo(sites, 2) + @test isnothing(linkind(MPO(fill(ITensor(), N), 0, N + 1), 1)) +end + +@testset "splitblocks" begin + N = 4 + sites = siteinds("S=1", N; conserve_qns = true) + opsum = OpSum() + for j in 1:(N - 1) + opsum .+= 0.5, "S+", j, "S-", j + 1 + opsum .+= 0.5, "S-", j, "S+", j + 1 + opsum .+= "Sz", j, "Sz", j + 1 + end + H = MPO(opsum, sites; splitblocks = false) + + # Split the tensors to make them more sparse + # Drops zero blocks by default + H̃ = splitblocks(linkinds, H) + + H̃2 = MPO(opsum, sites; splitblocks = true) + + # Defaults to true + H̃3 = MPO(opsum, sites) + + @test prod(H) ≈ prod(H̃) + @test prod(H) ≈ prod(H̃2) + @test prod(H) ≈ prod(H̃3) + + @test nnz(H[1]) == 9 + @test nnz(H[2]) == 18 + @test nnz(H[3]) == 18 + @test nnz(H[4]) == 9 + + @test nnzblocks(H[1]) == 9 + @test nnzblocks(H[2]) == 18 + @test nnzblocks(H[3]) == 18 + @test nnzblocks(H[4]) == 9 + + @test nnz(H̃[1]) == nnzblocks(H̃[1]) == count(≠(0), H[1]) == count(≠(0), H̃[1]) == 9 + @test nnz(H̃[2]) == nnzblocks(H̃[2]) == count(≠(0), H[2]) == count(≠(0), H̃[2]) == 18 + @test nnz(H̃[3]) == nnzblocks(H̃[3]) == count(≠(0), H[3]) == count(≠(0), H̃[3]) == 18 + @test nnz(H̃[4]) == nnzblocks(H̃[4]) == count(≠(0), H[4]) == count(≠(0), H̃[4]) == 9 + + @test nnz(H̃2[1]) == nnzblocks(H̃2[1]) == count(≠(0), H[1]) == count(≠(0), H̃2[1]) == 9 + @test nnz(H̃2[2]) == nnzblocks(H̃2[2]) == count(≠(0), H[2]) == count(≠(0), H̃2[2]) == 18 + @test nnz(H̃2[3]) == nnzblocks(H̃2[3]) == count(≠(0), H[3]) == count(≠(0), H̃2[3]) == 18 + @test nnz(H̃2[4]) == nnzblocks(H̃2[4]) == count(≠(0), H[4]) == count(≠(0), H̃2[4]) == 9 + + @test nnz(H̃3[1]) == nnzblocks(H̃3[1]) == count(≠(0), H[1]) == count(≠(0), H̃3[1]) == 9 + @test nnz(H̃3[2]) == nnzblocks(H̃3[2]) == count(≠(0), H[2]) == count(≠(0), H̃3[2]) == 18 + @test nnz(H̃3[3]) == nnzblocks(H̃3[3]) == count(≠(0), H[3]) == count(≠(0), H̃3[3]) == 18 + @test nnz(H̃3[4]) == nnzblocks(H̃3[4]) == count(≠(0), H[4]) == count(≠(0), H̃3[4]) == 9 +end + +@testset "MPO operations with one or two sites" begin + for N in 1:4, conserve_szparity in (true, false) + s = siteinds("S=1/2", N; conserve_szparity = conserve_szparity) + a = OpSum() + h = 0.5 + for j in 1:(N - 1) + a .-= 1, "Sx", j, "Sx", j + 1 + end + for j in 1:N + a .+= h, "Sz", j + end + H = MPO(a, s) + if conserve_szparity + ψ = random_mps(s, n -> isodd(n) ? "↑" : "↓") + else + ψ = random_mps(s) + end + + # MPO * MPS + Hψ = H * ψ + @test prod(Hψ) ≈ prod(H) * prod(ψ) + + # MPO * MPO + H² = H' * H + @test prod(H²) ≈ prod(H') * prod(H) + + # DMRG + sweeps = Sweeps(3) + maxdim!(sweeps, 10) + if N == 1 + @test_throws ErrorException dmrg( + H, ψ, sweeps; eigsolve_maxiter = 10, eigsolve_krylovdim = 10, outputlevel = 0 + ) + else + e, ψgs = dmrg(H, ψ, sweeps; eigsolve_maxiter = 10, eigsolve_krylovdim = 10, outputlevel = 0) + @test prod(H) * prod(ψgs) ≈ e * prod(ψgs)' + D, V = eigen(prod(H); ishermitian = true) + if hasqns(ψ) + fluxψ = flux(ψ) + d = commonind(D, V) + b = ITensors.findfirstblock(indblock -> ITensors.qn(indblock) == fluxψ, d) + @test e ≈ minimum(storage(D[Block(b, b)])) + else + @test e ≈ minimum(storage(D)) + end + end end - end end # # Build up Hamiltonians with non trival QN spaces in the link indices and further neighbour interactions. # -function make_heisenberg_opsum(sites, NNN::Int64; J::Float64=1.0, kwargs...)::MPO - N = length(sites) - @assert N >= NNN - opsum = OpSum() - for dj in 1:NNN - f = J / dj - for j in 1:(N - dj) - add!(opsum, f, "Sz", j, "Sz", j + dj) - add!(opsum, f * 0.5, "S+", j, "S-", j + dj) - add!(opsum, f * 0.5, "S-", j, "S+", j + dj) +function make_heisenberg_opsum(sites, NNN::Int64; J::Float64 = 1.0, kwargs...)::MPO + N = length(sites) + @assert N >= NNN + opsum = OpSum() + for dj in 1:NNN + f = J / dj + for j in 1:(N - dj) + add!(opsum, f, "Sz", j, "Sz", j + dj) + add!(opsum, f * 0.5, "S+", j, "S-", j + dj) + add!(opsum, f * 0.5, "S-", j, "S+", j + dj) + end end - end - return MPO(opsum, sites; kwargs...) + return MPO(opsum, sites; kwargs...) end function make_hubbard_opsum( - sites, NNN::Int64; U::Float64=1.0, t::Float64=1.0, V::Float64=0.5, kwargs... -)::MPO - N = length(sites) - @assert(N >= NNN) - os = OpSum() - for i in 1:N - os += (U, "Nupdn", i) - end - for dn in 1:NNN - tj, Vj = t / dn, V / dn - for n in 1:(N - dn) - os -= tj, "Cdagup", n, "Cup", n + dn - os -= tj, "Cdagup", n + dn, "Cup", n - os -= tj, "Cdagdn", n, "Cdn", n + dn - os -= tj, "Cdagdn", n + dn, "Cdn", n - os += Vj, "Ntot", n, "Ntot", n + dn + sites, NNN::Int64; U::Float64 = 1.0, t::Float64 = 1.0, V::Float64 = 0.5, kwargs... + )::MPO + N = length(sites) + @assert(N >= NNN) + os = OpSum() + for i in 1:N + os += (U, "Nupdn", i) end - end - return MPO(os, sites; kwargs...) + for dn in 1:NNN + tj, Vj = t / dn, V / dn + for n in 1:(N - dn) + os -= tj, "Cdagup", n, "Cup", n + dn + os -= tj, "Cdagup", n + dn, "Cup", n + os -= tj, "Cdagdn", n, "Cdn", n + dn + os -= tj, "Cdagdn", n + dn, "Cdn", n + os += Vj, "Ntot", n, "Ntot", n + dn + end + end + return MPO(os, sites; kwargs...) end test_combos = [(make_heisenberg_opsum, "S=1/2"), (make_hubbard_opsum, "Electron")] @testset "QR/QL MPO tensors with complex block structures, H=$(test_combo[1])" for test_combo in - test_combos - - N, NNN = 10, 7 #10 lattice site, up 7th neight interactions - sites = siteinds(test_combo[2], N; conserve_qns=true) - H = test_combo[1](sites, NNN) - for n in 1:(N - 1) - W = H[n] - @test flux(W) == QN("Sz", 0) - ilr = filterinds(W; tags="l=$n")[1] - ilq = noncommoninds(W, ilr) - Q, R, q = qr(W, ilq) - @test flux(Q) == QN("Sz", 0) #qr should move all flux on W (0 in this case) onto R - @test flux(R) == QN("Sz", 0) #this effectively removes all flux between Q and R in thie case. - @test W ≈ Q * R atol = 1e-13 - # blocksparse - diag is not supported so we must convert Q*Q_dagger to dense. - # Also fails with error in permutedims so below we use norm(a-b)≈ 0.0 instead. - # @test dense(Q*dag(prime(Q, q))) ≈ δ(Float64, q, q') atol = 1e-13 - @test norm(dense(Q * dag(prime(Q, q))) - δ(Float64, q, q')) ≈ 0.0 atol = 1e-13 - - R, Q, q = ITensors.rq(W, ilr) - @test flux(Q) == QN("Sz", 0) - @test flux(R) == QN("Sz", 0) - @test W ≈ Q * R atol = 1e-13 - @test norm(dense(Q * dag(prime(Q, q))) - δ(Float64, q, q')) ≈ 0.0 atol = 1e-13 - - Q, L, q = ITensors.ql(W, ilq) - @test flux(Q) == QN("Sz", 0) - @test flux(L) == QN("Sz", 0) - @test W ≈ Q * L atol = 1e-13 - @test norm(dense(Q * dag(prime(Q, q))) - δ(Float64, q, q')) ≈ 0.0 atol = 1e-13 - - L, Q, q = ITensors.lq(W, ilr) - @test flux(Q) == QN("Sz", 0) - @test flux(L) == QN("Sz", 0) - @test W ≈ Q * L atol = 1e-13 - @test norm(dense(Q * dag(prime(Q, q))) - δ(Float64, q, q')) ≈ 0.0 atol = 1e-13 - end + test_combos + + N, NNN = 10, 7 #10 lattice site, up 7th neight interactions + sites = siteinds(test_combo[2], N; conserve_qns = true) + H = test_combo[1](sites, NNN) + for n in 1:(N - 1) + W = H[n] + @test flux(W) == QN("Sz", 0) + ilr = filterinds(W; tags = "l=$n")[1] + ilq = noncommoninds(W, ilr) + Q, R, q = qr(W, ilq) + @test flux(Q) == QN("Sz", 0) #qr should move all flux on W (0 in this case) onto R + @test flux(R) == QN("Sz", 0) #this effectively removes all flux between Q and R in thie case. + @test W ≈ Q * R atol = 1.0e-13 + # blocksparse - diag is not supported so we must convert Q*Q_dagger to dense. + # Also fails with error in permutedims so below we use norm(a-b)≈ 0.0 instead. + # @test dense(Q*dag(prime(Q, q))) ≈ δ(Float64, q, q') atol = 1e-13 + @test norm(dense(Q * dag(prime(Q, q))) - δ(Float64, q, q')) ≈ 0.0 atol = 1.0e-13 + + R, Q, q = ITensors.rq(W, ilr) + @test flux(Q) == QN("Sz", 0) + @test flux(R) == QN("Sz", 0) + @test W ≈ Q * R atol = 1.0e-13 + @test norm(dense(Q * dag(prime(Q, q))) - δ(Float64, q, q')) ≈ 0.0 atol = 1.0e-13 + + Q, L, q = ITensors.ql(W, ilq) + @test flux(Q) == QN("Sz", 0) + @test flux(L) == QN("Sz", 0) + @test W ≈ Q * L atol = 1.0e-13 + @test norm(dense(Q * dag(prime(Q, q))) - δ(Float64, q, q')) ≈ 0.0 atol = 1.0e-13 + + L, Q, q = ITensors.lq(W, ilr) + @test flux(Q) == QN("Sz", 0) + @test flux(L) == QN("Sz", 0) + @test W ≈ Q * L atol = 1.0e-13 + @test norm(dense(Q * dag(prime(Q, q))) - δ(Float64, q, q')) ≈ 0.0 atol = 1.0e-13 + end end diff --git a/test/base/test_readme.jl b/test/base/test_readme.jl index e3b8b94..a0d242f 100644 --- a/test/base/test_readme.jl +++ b/test/base/test_readme.jl @@ -1,99 +1,99 @@ using ITensorMPS, ITensors, Test @testset "README Examples" begin - @testset "ITensor Basics" begin - i = Index(3) - j = Index(5) - k = Index(2) - l = Index(7) - - A = ITensor(i, j, k) - B = ITensor(j, l) - - A[i => 1, j => 1, k => 1] = 11.1 - A[i => 2, j => 1, k => 2] = -21.2 - A[k => 1, i => 3, j => 1] = 31.1 # can provide Index values in any order - # ... - - # Contract over shared index j - C = A * B - - @test hasinds(C, i, k, l) == true - - D = random_itensor(k, j, i) # ITensor with random elements - - # Add two ITensors - # must have same set of indices - # but can be in any order - R = A + D - end - - @testset "SVD of a Matrix" begin - i = Index(10) - j = Index(20) - M = random_itensor(i, j) - U, S, V = svd(M, i) - @test norm(M - U * S * V) < 1E-12 - end - - @testset "SVD of a Tensor" begin - i = Index(4, "i") - j = Index(4, "j") - k = Index(4, "k") - l = Index(4, "l") - T = random_itensor(i, j, k, l) - U, S, V = svd(T, i, k) - @test hasinds(U, i, k) - @test hasinds(V, j, l) - @test norm(T - U * S * V) < 1E-12 - end - - @testset "Making Tensor Indices" begin - i = Index(3) # Index of dimension 3 - @test dim(i) == 3 # dim(i) = 3 - - ci = copy(i) - @test ci == i # true - - j = Index(5, "j") # Index with a tag "j" - - @test j != i # false - - s = Index(2, "n=1,Site") # Index with two tags, - # "Site" and "n=1" - @test hastags(s, "Site") # hastags(s,"Site") = true - @test hastags(s, "n=1") # hastags(s,"n=1") = true - - i1 = prime(i) # i1 has a "prime level" of 1 - # but otherwise same properties as i - @test i1 != i # false, prime levels do not match - end - - @testset "DMRG" begin - N = 100 - sites = siteinds("S=1", N) - - # Input operator terms which define - # a Hamiltonian matrix, and convert - # these terms to an MPO tensor network - opsum = OpSum() - for j in 1:(N - 1) - add!(opsum, "Sz", j, "Sz", j + 1) - add!(opsum, 0.5, "S+", j, "S-", j + 1) - add!(opsum, 0.5, "S-", j, "S+", j + 1) + @testset "ITensor Basics" begin + i = Index(3) + j = Index(5) + k = Index(2) + l = Index(7) + + A = ITensor(i, j, k) + B = ITensor(j, l) + + A[i => 1, j => 1, k => 1] = 11.1 + A[i => 2, j => 1, k => 2] = -21.2 + A[k => 1, i => 3, j => 1] = 31.1 # can provide Index values in any order + # ... + + # Contract over shared index j + C = A * B + + @test hasinds(C, i, k, l) == true + + D = random_itensor(k, j, i) # ITensor with random elements + + # Add two ITensors + # must have same set of indices + # but can be in any order + R = A + D + end + + @testset "SVD of a Matrix" begin + i = Index(10) + j = Index(20) + M = random_itensor(i, j) + U, S, V = svd(M, i) + @test norm(M - U * S * V) < 1.0e-12 end - H = MPO(opsum, sites) - # Create an initial random matrix product state - psi0 = random_mps(sites) + @testset "SVD of a Tensor" begin + i = Index(4, "i") + j = Index(4, "j") + k = Index(4, "k") + l = Index(4, "l") + T = random_itensor(i, j, k, l) + U, S, V = svd(T, i, k) + @test hasinds(U, i, k) + @test hasinds(V, j, l) + @test norm(T - U * S * V) < 1.0e-12 + end + + @testset "Making Tensor Indices" begin + i = Index(3) # Index of dimension 3 + @test dim(i) == 3 # dim(i) = 3 + + ci = copy(i) + @test ci == i # true + + j = Index(5, "j") # Index with a tag "j" - sweeps = Sweeps(2) - maxdim!(sweeps, 10, 20, 100, 100, 200) - cutoff!(sweeps, 1E-10) + @test j != i # false - # Run the DMRG algorithm, returning energy - # (dominant eigenvalue) and optimized MPS - energy, psi = dmrg(H, psi0, sweeps; outputlevel=0) - #println("Final energy = $energy") - end + s = Index(2, "n=1,Site") # Index with two tags, + # "Site" and "n=1" + @test hastags(s, "Site") # hastags(s,"Site") = true + @test hastags(s, "n=1") # hastags(s,"n=1") = true + + i1 = prime(i) # i1 has a "prime level" of 1 + # but otherwise same properties as i + @test i1 != i # false, prime levels do not match + end + + @testset "DMRG" begin + N = 100 + sites = siteinds("S=1", N) + + # Input operator terms which define + # a Hamiltonian matrix, and convert + # these terms to an MPO tensor network + opsum = OpSum() + for j in 1:(N - 1) + add!(opsum, "Sz", j, "Sz", j + 1) + add!(opsum, 0.5, "S+", j, "S-", j + 1) + add!(opsum, 0.5, "S-", j, "S+", j + 1) + end + H = MPO(opsum, sites) + + # Create an initial random matrix product state + psi0 = random_mps(sites) + + sweeps = Sweeps(2) + maxdim!(sweeps, 10, 20, 100, 100, 200) + cutoff!(sweeps, 1.0e-10) + + # Run the DMRG algorithm, returning energy + # (dominant eigenvalue) and optimized MPS + energy, psi = dmrg(H, psi0, sweeps; outputlevel = 0) + #println("Final energy = $energy") + end end diff --git a/test/base/test_readwrite.jl b/test/base/test_readwrite.jl index 74679c0..d405640 100644 --- a/test/base/test_readwrite.jl +++ b/test/base/test_readwrite.jl @@ -4,37 +4,37 @@ using ITensorMPS, ITensors, HDF5, Test include(joinpath(@__DIR__, "utils", "util.jl")) @testset "HDF5 Read and Write" begin - @testset "MPO/MPS" begin - N = 6 - sites = siteinds("S=1/2", N) - - # MPO - mpo = makeRandomMPO(sites) - - h5open("data.h5", "w") do fo - write(fo, "mpo", mpo) - end - - h5open("data.h5", "r") do fi - rmpo = read(fi, "mpo", MPO) - @test prod([norm(rmpo[i] - mpo[i]) / norm(mpo[i]) < 1E-10 for i in 1:N]) - end - - # MPS - mps = makeRandomMPS(sites) - h5open("data.h5", "w") do fo - write(fo, "mps", mps) - end - - h5open("data.h5", "r") do fi - rmps = read(fi, "mps", MPS) - @test prod([norm(rmps[i] - mps[i]) / norm(mps[i]) < 1E-10 for i in 1:N]) + @testset "MPO/MPS" begin + N = 6 + sites = siteinds("S=1/2", N) + + # MPO + mpo = makeRandomMPO(sites) + + h5open("data.h5", "w") do fo + write(fo, "mpo", mpo) + end + + h5open("data.h5", "r") do fi + rmpo = read(fi, "mpo", MPO) + @test prod([norm(rmpo[i] - mpo[i]) / norm(mpo[i]) < 1.0e-10 for i in 1:N]) + end + + # MPS + mps = makeRandomMPS(sites) + h5open("data.h5", "w") do fo + write(fo, "mps", mps) + end + + h5open("data.h5", "r") do fi + rmps = read(fi, "mps", MPS) + @test prod([norm(rmps[i] - mps[i]) / norm(mps[i]) < 1.0e-10 for i in 1:N]) + end end - end - # - # Clean up the test hdf5 file - # - rm("data.h5"; force=true) + # + # Clean up the test hdf5 file + # + rm("data.h5"; force = true) end end diff --git a/test/base/test_solvers/runtests.jl b/test/base/test_solvers/runtests.jl index e6e99d3..90d3941 100644 --- a/test/base/test_solvers/runtests.jl +++ b/test/base/test_solvers/runtests.jl @@ -3,12 +3,12 @@ using Test: @testset using ITensorMPS: ITensorMPS test_path = @__DIR__ test_files = filter(readdir(test_path)) do file - return startswith("test_")(file) && endswith(".jl")(file) + return startswith("test_")(file) && endswith(".jl")(file) end @testset "$test_path" begin - @testset "$filename" for filename in test_files - println("Running $filename") - @time include(joinpath(test_path, filename)) - end + @testset "$filename" for filename in test_files + println("Running $filename") + @time include(joinpath(test_path, filename)) + end end end diff --git a/test/base/test_solvers/test_contract.jl b/test/base/test_solvers/test_contract.jl index 8fc0390..4732216 100644 --- a/test/base/test_solvers/test_contract.jl +++ b/test/base/test_solvers/test_contract.jl @@ -4,62 +4,62 @@ using ITensorMPS: MPO, OpSum, apply, contract, inner, random_mps, siteinds, trun using StableRNGs: StableRNG using Test: @test, @test_throws, @testset @testset "Contract MPO (eltype=$elt, conserve_qns=$conserve_qns)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ), - conserve_qns in [false, true] + Float32, Float64, Complex{Float32}, Complex{Float64}, + ), + conserve_qns in [false, true] - N = 20 - s = siteinds("S=1/2", N; conserve_qns) - rng = StableRNG(1234) - psi = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims=8) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - for j in 1:(N - 2) - os += 0.5, "S+", j, "S-", j + 2 - os += 0.5, "S-", j, "S+", j + 2 - os += "Sz", j, "Sz", j + 2 - end - H = MPO(elt, os, s) - @testset "apply (standard indices, nsite=2)" begin - Hpsi = apply(H, psi; alg="fit", nsweeps=2) - @test_throws ErrorException apply(H, psi; alg="fit") - @test ITensors.scalartype(Hpsi) == elt - @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = 10 * √eps(real(elt)) - end - @testset "contract (non-standard indices)" begin - # Change "top" indices of MPO to be a different set - t = siteinds("S=1/2", N; conserve_qns) - Ht = deepcopy(H) - psit = deepcopy(psi) - for j in 1:N - Ht[j] *= delta(elt, dag(s[j])', t[j]) - psit[j] *= delta(elt, dag(s[j]), t[j]) + N = 20 + s = siteinds("S=1/2", N; conserve_qns) + rng = StableRNG(1234) + psi = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims = 8) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 end + for j in 1:(N - 2) + os += 0.5, "S+", j, "S-", j + 2 + os += 0.5, "S-", j, "S+", j + 2 + os += "Sz", j, "Sz", j + 2 + end + H = MPO(elt, os, s) + @testset "apply (standard indices, nsite=2)" begin + Hpsi = apply(H, psi; alg = "fit", nsweeps = 2) + @test_throws ErrorException apply(H, psi; alg = "fit") + @test ITensors.scalartype(Hpsi) == elt + @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = 10 * √eps(real(elt)) + end + @testset "contract (non-standard indices)" begin + # Change "top" indices of MPO to be a different set + t = siteinds("S=1/2", N; conserve_qns) + Ht = deepcopy(H) + psit = deepcopy(psi) + for j in 1:N + Ht[j] *= delta(elt, dag(s[j])', t[j]) + psit[j] *= delta(elt, dag(s[j]), t[j]) + end - # Test with nsweeps=2 - Hpsit = contract(Ht, psi; alg="fit", nsweeps=2) - @test ITensors.scalartype(Hpsit) == elt - @test inner(psit, Hpsit) ≈ inner(psit, Ht, psi) rtol = 10 * √eps(real(elt)) + # Test with nsweeps=2 + Hpsit = contract(Ht, psi; alg = "fit", nsweeps = 2) + @test ITensors.scalartype(Hpsit) == elt + @test inner(psit, Hpsit) ≈ inner(psit, Ht, psi) rtol = 10 * √eps(real(elt)) - # Test with less good initial guess MPS not equal to psi - psit_guess = copy(psit) - truncate!(psit_guess; maxdim=2) - Hpsit = contract(Ht, psi; alg="fit", nsweeps=4, init=psit_guess) - @test ITensors.scalartype(Hpsit) == elt - @test inner(psit, Hpsit) ≈ inner(psit, Ht, psi) rtol = 20 * √eps(real(elt)) - end - @testset "apply (standard indices, nsite=1)" begin - # Test with nsite=1 - Hpsi_guess = apply(H, psi; alg="naive", cutoff=1e-4) - Hpsi = apply(H, psi; alg="fit", init=Hpsi_guess, nsite=1, nsweeps=2) - @test ITensors.scalartype(Hpsi) == elt - scale(::Type{Float32}) = 10^2 - scale(::Type{Float64}) = 10^6 - @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = √eps(real(elt)) * scale(real(elt)) - end + # Test with less good initial guess MPS not equal to psi + psit_guess = copy(psit) + truncate!(psit_guess; maxdim = 2) + Hpsit = contract(Ht, psi; alg = "fit", nsweeps = 4, init = psit_guess) + @test ITensors.scalartype(Hpsit) == elt + @test inner(psit, Hpsit) ≈ inner(psit, Ht, psi) rtol = 20 * √eps(real(elt)) + end + @testset "apply (standard indices, nsite=1)" begin + # Test with nsite=1 + Hpsi_guess = apply(H, psi; alg = "naive", cutoff = 1.0e-4) + Hpsi = apply(H, psi; alg = "fit", init = Hpsi_guess, nsite = 1, nsweeps = 2) + @test ITensors.scalartype(Hpsi) == elt + scale(::Type{Float32}) = 10^2 + scale(::Type{Float64}) = 10^6 + @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = √eps(real(elt)) * scale(real(elt)) + end end end diff --git a/test/base/test_solvers/test_dmrg.jl b/test/base/test_solvers/test_dmrg.jl index d48afb6..58bf37f 100644 --- a/test/base/test_solvers/test_dmrg.jl +++ b/test/base/test_solvers/test_dmrg.jl @@ -4,34 +4,34 @@ using ITensorMPS: Experimental, MPO, OpSum, dmrg, inner, random_mps, siteinds using StableRNGs: StableRNG using Test: @test, @test_throws, @testset @testset "DMRG (eltype=$elt, nsite=$nsite, conserve_qns=$conserve_qns)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ), - nsite in [1, 2], - conserve_qns in [false, true] + Float32, Float64, Complex{Float32}, Complex{Float64}, + ), + nsite in [1, 2], + conserve_qns in [false, true] - N = 10 - cutoff = eps(real(elt)) * 10^4 - s = siteinds("S=1/2", N; conserve_qns) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(elt, os, s) - rng = StableRNG(1234) - psi = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims=20) - nsweeps = 10 - maxdim = [10, 20, 40, 100] - @test_throws ErrorException Experimental.dmrg(H, psi; maxdim, cutoff, nsite) - e, psi = Experimental.dmrg( - H, psi; nsweeps, maxdim, cutoff, nsite, updater_kwargs=(; krylovdim=3, maxiter=1) - ) - @test inner(psi', H, psi) ≈ e - e2, psi2 = dmrg(H, psi; nsweeps, maxdim, cutoff, outputlevel=0) - @test ITensors.scalartype(psi2) == elt - @test e2 isa real(elt) - @test e ≈ e2 rtol = √(eps(real(elt))) * 10 - @test inner(psi', H, psi) ≈ inner(psi2', H, psi2) rtol = √(eps(real(elt))) * 10 + N = 10 + cutoff = eps(real(elt)) * 10^4 + s = siteinds("S=1/2", N; conserve_qns) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = MPO(elt, os, s) + rng = StableRNG(1234) + psi = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims = 20) + nsweeps = 10 + maxdim = [10, 20, 40, 100] + @test_throws ErrorException Experimental.dmrg(H, psi; maxdim, cutoff, nsite) + e, psi = Experimental.dmrg( + H, psi; nsweeps, maxdim, cutoff, nsite, updater_kwargs = (; krylovdim = 3, maxiter = 1) + ) + @test inner(psi', H, psi) ≈ e + e2, psi2 = dmrg(H, psi; nsweeps, maxdim, cutoff, outputlevel = 0) + @test ITensors.scalartype(psi2) == elt + @test e2 isa real(elt) + @test e ≈ e2 rtol = √(eps(real(elt))) * 10 + @test inner(psi', H, psi) ≈ inner(psi2', H, psi2) rtol = √(eps(real(elt))) * 10 end end diff --git a/test/base/test_solvers/test_dmrg_x.jl b/test/base/test_solvers/test_dmrg_x.jl index ee6b015..7816596 100644 --- a/test/base/test_solvers/test_dmrg_x.jl +++ b/test/base/test_solvers/test_dmrg_x.jl @@ -5,51 +5,51 @@ using Random: Random using StableRNGs: StableRNG using Test: @test, @test_throws, @testset @testset "DMRG-X (eltype=$elt, conserve_qns=$conserve_qns)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ), - conserve_qns in [false, true] + Float32, Float64, Complex{Float32}, Complex{Float64}, + ), + conserve_qns in [false, true] - function heisenberg(n; h=zeros(n)) - os = OpSum() - for j in 1:(n - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + function heisenberg(n; h = zeros(n)) + os = OpSum() + for j in 1:(n - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + for j in 1:n + if h[j] ≠ 0 + os -= h[j], "Sz", j + end + end + return os end - for j in 1:n - if h[j] ≠ 0 - os -= h[j], "Sz", j - end - end - return os - end - n = 10 - s = siteinds("S=1/2", n; conserve_qns) - Random.seed!(12) - W = 12 - # Random fields h ∈ [-W, W] - rng = StableRNG(1234) - h = W * (2 * rand(rng, real(elt), n) .- 1) - H = MPO(elt, heisenberg(n; h), s) - initstate = rand(rng, ["↑", "↓"], n) - ψ = MPS(elt, s, initstate) - @test_throws ErrorException dmrg_x(H, ψ; nsite=2, maxdim=20, cutoff=1e-10) - dmrg_x_kwargs = (; nsweeps=20, normalize=true, maxdim=20, cutoff=1e-10, outputlevel=0) - e, ϕ = dmrg_x(H, ψ; nsite=2, dmrg_x_kwargs...) - @test ITensors.scalartype(ϕ) == elt - @test inner(ϕ', H, ϕ) / inner(ϕ, ϕ) ≈ e - @test inner(H, ψ, H, ψ) ≉ inner(ψ', H, ψ)^2 rtol = √eps(real(elt)) - @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ', H, ϕ) / inner(ϕ, ϕ) rtol = 1e-1 - @test inner(H, ϕ, H, ϕ) ≈ inner(ϕ', H, ϕ)^2 rtol = √eps(real(elt)) - ẽ, ϕ̃ = dmrg_x(ProjMPO(H), ϕ; nsite=1, dmrg_x_kwargs...) - @test ITensors.scalartype(ϕ̃) == elt - @test inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) ≈ ẽ - @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) rtol = 1e-1 - scale(::Type{Float32}) = 10^2 - scale(::Type{Float64}) = 10^5 - @test inner(H, ϕ̃, H, ϕ̃) ≈ inner(ϕ̃', H, ϕ̃)^2 rtol = - √(eps(real(elt))) * scale(real(elt)) - # Sometimes broken, sometimes not - # @test abs(loginner(ϕ̃, ϕ) / n) ≈ 0.0 atol = 1e-6 + n = 10 + s = siteinds("S=1/2", n; conserve_qns) + Random.seed!(12) + W = 12 + # Random fields h ∈ [-W, W] + rng = StableRNG(1234) + h = W * (2 * rand(rng, real(elt), n) .- 1) + H = MPO(elt, heisenberg(n; h), s) + initstate = rand(rng, ["↑", "↓"], n) + ψ = MPS(elt, s, initstate) + @test_throws ErrorException dmrg_x(H, ψ; nsite = 2, maxdim = 20, cutoff = 1.0e-10) + dmrg_x_kwargs = (; nsweeps = 20, normalize = true, maxdim = 20, cutoff = 1.0e-10, outputlevel = 0) + e, ϕ = dmrg_x(H, ψ; nsite = 2, dmrg_x_kwargs...) + @test ITensors.scalartype(ϕ) == elt + @test inner(ϕ', H, ϕ) / inner(ϕ, ϕ) ≈ e + @test inner(H, ψ, H, ψ) ≉ inner(ψ', H, ψ)^2 rtol = √eps(real(elt)) + @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ', H, ϕ) / inner(ϕ, ϕ) rtol = 1.0e-1 + @test inner(H, ϕ, H, ϕ) ≈ inner(ϕ', H, ϕ)^2 rtol = √eps(real(elt)) + ẽ, ϕ̃ = dmrg_x(ProjMPO(H), ϕ; nsite = 1, dmrg_x_kwargs...) + @test ITensors.scalartype(ϕ̃) == elt + @test inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) ≈ ẽ + @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) rtol = 1.0e-1 + scale(::Type{Float32}) = 10^2 + scale(::Type{Float64}) = 10^5 + @test inner(H, ϕ̃, H, ϕ̃) ≈ inner(ϕ̃', H, ϕ̃)^2 rtol = + √(eps(real(elt))) * scale(real(elt)) + # Sometimes broken, sometimes not + # @test abs(loginner(ϕ̃, ϕ) / n) ≈ 0.0 atol = 1e-6 end end diff --git a/test/base/test_solvers/test_examples.jl b/test/base/test_solvers/test_examples.jl index a497f54..1da2caf 100644 --- a/test/base/test_solvers/test_examples.jl +++ b/test/base/test_solvers/test_examples.jl @@ -3,13 +3,13 @@ using ITensorMPS: ITensorMPS using Suppressor: @suppress using Test: @testset @testset "Run examples" begin - examples_files = [ - "01_tdvp.jl", "02_dmrg-x.jl", "03_tdvp_time_dependent.jl", "04_tdvp_observers.jl" - ] - examples_path = joinpath(pkgdir(ITensorMPS), "examples", "solvers") - @testset "Running example file $f" for f in examples_files - println("Running example file $f") - @suppress include(joinpath(examples_path, f)) - end + examples_files = [ + "01_tdvp.jl", "02_dmrg-x.jl", "03_tdvp_time_dependent.jl", "04_tdvp_observers.jl", + ] + examples_path = joinpath(pkgdir(ITensorMPS), "examples", "solvers") + @testset "Running example file $f" for f in examples_files + println("Running example file $f") + @suppress include(joinpath(examples_path, f)) + end end end diff --git a/test/base/test_solvers/test_expand.jl b/test/base/test_solvers/test_expand.jl index 9a7b477..5faf066 100644 --- a/test/base/test_solvers/test_expand.jl +++ b/test/base/test_solvers/test_expand.jl @@ -1,95 +1,95 @@ @eval module $(gensym()) using ITensors: scalartype using ITensorMPS: - OpSum, MPO, MPS, expand, inner, linkdims, maxlinkdim, random_mps, siteinds, tdvp + OpSum, MPO, MPS, expand, inner, linkdims, maxlinkdim, random_mps, siteinds, tdvp using ITensorMPS.Experimental: dmrg using LinearAlgebra: normalize using StableRNGs: StableRNG using Test: @test, @testset const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) @testset "expand (eltype=$elt)" for elt in elts - @testset "expand (alg=\"orthogonalize\", conserve_qns=$conserve_qns, eltype=$elt)" for conserve_qns in - ( - false, true - ) - n = 6 - s = siteinds("S=1/2", n; conserve_qns) - rng = StableRNG(1234) - state = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims=4) - reference = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims=2) - state_expanded = expand(state, [reference]; alg="orthogonalize") - @test scalartype(state_expanded) === elt - @test inner(state_expanded, state) ≈ inner(state, state) - @test inner(state_expanded, reference) ≈ inner(state, reference) - end - @testset "expand (alg=\"global_krylov\", conserve_qns=$conserve_qns, eltype=$elt)" for conserve_qns in - ( - false, true - ) - n = 10 - s = siteinds("S=1/2", n; conserve_qns) - opsum = OpSum() - for j in 1:(n - 1) - opsum += 0.5, "S+", j, "S-", j + 1 - opsum += 0.5, "S-", j, "S+", j + 1 - opsum += "Sz", j, "Sz", j + 1 + @testset "expand (alg=\"orthogonalize\", conserve_qns=$conserve_qns, eltype=$elt)" for conserve_qns in + ( + false, true, + ) + n = 6 + s = siteinds("S=1/2", n; conserve_qns) + rng = StableRNG(1234) + state = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims = 4) + reference = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims = 2) + state_expanded = expand(state, [reference]; alg = "orthogonalize") + @test scalartype(state_expanded) === elt + @test inner(state_expanded, state) ≈ inner(state, state) + @test inner(state_expanded, reference) ≈ inner(state, reference) end - operator = MPO(elt, opsum, s) - state = MPS(elt, s, j -> isodd(j) ? "↑" : "↓") - state_expanded = expand(state, operator; alg="global_krylov") - @test scalartype(state_expanded) === elt - @test maxlinkdim(state_expanded) > 1 - @test inner(state_expanded, state) ≈ inner(state, state) - end - @testset "Decoupled ladder (alg=\"global_krylov\", eltype=$elt)" begin - nx = 10 - ny = 2 - n = nx * ny - s = siteinds("S=1/2", n) - opsum = OpSum() - for j in 1:2:(n - 2) - opsum += 1 / 2, "S+", j, "S-", j + 2 - opsum += 1 / 2, "S-", j, "S+", j + 2 - opsum += "Sz", j, "Sz", j + 2 + @testset "expand (alg=\"global_krylov\", conserve_qns=$conserve_qns, eltype=$elt)" for conserve_qns in + ( + false, true, + ) + n = 10 + s = siteinds("S=1/2", n; conserve_qns) + opsum = OpSum() + for j in 1:(n - 1) + opsum += 0.5, "S+", j, "S-", j + 1 + opsum += 0.5, "S-", j, "S+", j + 1 + opsum += "Sz", j, "Sz", j + 1 + end + operator = MPO(elt, opsum, s) + state = MPS(elt, s, j -> isodd(j) ? "↑" : "↓") + state_expanded = expand(state, operator; alg = "global_krylov") + @test scalartype(state_expanded) === elt + @test maxlinkdim(state_expanded) > 1 + @test inner(state_expanded, state) ≈ inner(state, state) end - for j in 2:2:(n - 2) - opsum += 1 / 2, "S+", j, "S-", j + 2 - opsum += 1 / 2, "S-", j, "S+", j + 2 - opsum += "Sz", j, "Sz", j + 2 + @testset "Decoupled ladder (alg=\"global_krylov\", eltype=$elt)" begin + nx = 10 + ny = 2 + n = nx * ny + s = siteinds("S=1/2", n) + opsum = OpSum() + for j in 1:2:(n - 2) + opsum += 1 / 2, "S+", j, "S-", j + 2 + opsum += 1 / 2, "S-", j, "S+", j + 2 + opsum += "Sz", j, "Sz", j + 2 + end + for j in 2:2:(n - 2) + opsum += 1 / 2, "S+", j, "S-", j + 2 + opsum += 1 / 2, "S-", j, "S+", j + 2 + opsum += "Sz", j, "Sz", j + 2 + end + operator = MPO(elt, opsum, s) + rng = StableRNG(1234) + init = random_mps(rng, elt, s; linkdims = 30) + reference_energy, reference_state = dmrg( + operator, + init; + nsweeps = 15, + maxdim = [10, 10, 20, 20, 40, 80, 100], + cutoff = (√(eps(real(elt)))), + noise = (√(eps(real(elt)))), + ) + rng = StableRNG(1234) + state = random_mps(rng, elt, s) + nexpansions = 10 + tau = elt(0.5) + for step in 1:nexpansions + # TODO: Use `fourthroot`/`∜` in Julia 1.10 and above. + state = expand( + state, operator; alg = "global_krylov", krylovdim = 3, cutoff = eps(real(elt))^(1 // 4) + ) + state = tdvp( + operator, + -4tau, + state; + nsteps = 4, + cutoff = 1.0e-5, + updater_kwargs = (; tol = 1.0e-3, krylovdim = 5), + ) + state = normalize(state) + end + @test scalartype(state) === elt + # TODO: Use `fourthroot`/`∜` in Julia 1.10 and above. + @test inner(state', operator, state) ≈ reference_energy rtol = 5 * eps(real(elt))^(1 // 4) end - operator = MPO(elt, opsum, s) - rng = StableRNG(1234) - init = random_mps(rng, elt, s; linkdims=30) - reference_energy, reference_state = dmrg( - operator, - init; - nsweeps=15, - maxdim=[10, 10, 20, 20, 40, 80, 100], - cutoff=(√(eps(real(elt)))), - noise=(√(eps(real(elt)))), - ) - rng = StableRNG(1234) - state = random_mps(rng, elt, s) - nexpansions = 10 - tau = elt(0.5) - for step in 1:nexpansions - # TODO: Use `fourthroot`/`∜` in Julia 1.10 and above. - state = expand( - state, operator; alg="global_krylov", krylovdim=3, cutoff=eps(real(elt))^(1//4) - ) - state = tdvp( - operator, - -4tau, - state; - nsteps=4, - cutoff=1e-5, - updater_kwargs=(; tol=1e-3, krylovdim=5), - ) - state = normalize(state) - end - @test scalartype(state) === elt - # TODO: Use `fourthroot`/`∜` in Julia 1.10 and above. - @test inner(state', operator, state) ≈ reference_energy rtol = 5 * eps(real(elt))^(1//4) - end end end diff --git a/test/base/test_solvers/test_linsolve.jl b/test/base/test_solvers/test_linsolve.jl index 1e91592..5d39e8b 100644 --- a/test/base/test_solvers/test_linsolve.jl +++ b/test/base/test_solvers/test_linsolve.jl @@ -8,38 +8,38 @@ using StableRNGs: StableRNG using Test: @test, @test_throws, @testset using Random: Random @testset "linsolve (eltype=$elt, conserve_qns=$conserve_qns)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ), - conserve_qns in [false, true] + Float32, Float64, Complex{Float32}, Complex{Float64}, + ), + conserve_qns in [false, true] - N = 6 - s = siteinds("S=1/2", N; conserve_qns) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(elt, os, s) - state = [isodd(n) ? "Up" : "Dn" for n in 1:N] - rng = StableRNG(1234) - x_c = random_mps(rng, elt, s, state; linkdims=2) - e, x_c = dmrg(H, x_c; nsweeps=10, cutoff=1e-6, maxdim=20, outputlevel=0) - @test scalartype(x_c) == elt - # Compute `b = H * x_c` - b = apply(H, x_c; cutoff=1e-8) - @test scalartype(b) == elt - # Starting guess - rng = StableRNG(1234) - x0 = x_c + elt(0.05) * random_mps(rng, elt, s, state; linkdims=2) - @test scalartype(x0) == elt - nsweeps = 10 - cutoff = 1e-5 - maxdim = 20 - updater_kwargs = (; tol=1e-4, maxiter=20, krylovdim=30, ishermitian=true) - @test_throws ErrorException linsolve(H, b, x0; cutoff, maxdim, updater_kwargs) - x = linsolve(H, b, x0; nsweeps, cutoff, maxdim, updater_kwargs) - @test scalartype(x) == elt - @test norm(x - x_c) < 1e-2 + N = 6 + s = siteinds("S=1/2", N; conserve_qns) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = MPO(elt, os, s) + state = [isodd(n) ? "Up" : "Dn" for n in 1:N] + rng = StableRNG(1234) + x_c = random_mps(rng, elt, s, state; linkdims = 2) + e, x_c = dmrg(H, x_c; nsweeps = 10, cutoff = 1.0e-6, maxdim = 20, outputlevel = 0) + @test scalartype(x_c) == elt + # Compute `b = H * x_c` + b = apply(H, x_c; cutoff = 1.0e-8) + @test scalartype(b) == elt + # Starting guess + rng = StableRNG(1234) + x0 = x_c + elt(0.05) * random_mps(rng, elt, s, state; linkdims = 2) + @test scalartype(x0) == elt + nsweeps = 10 + cutoff = 1.0e-5 + maxdim = 20 + updater_kwargs = (; tol = 1.0e-4, maxiter = 20, krylovdim = 30, ishermitian = true) + @test_throws ErrorException linsolve(H, b, x0; cutoff, maxdim, updater_kwargs) + x = linsolve(H, b, x0; nsweeps, cutoff, maxdim, updater_kwargs) + @test scalartype(x) == elt + @test norm(x - x_c) < 1.0e-2 end end diff --git a/test/base/test_solvers/test_tdvp.jl b/test/base/test_solvers/test_tdvp.jl index b50c247..75905b6 100644 --- a/test/base/test_solvers/test_tdvp.jl +++ b/test/base/test_solvers/test_tdvp.jl @@ -1,6 +1,6 @@ @eval module $(gensym()) using ITensorMPS: - AbstractObserver, MPO, MPS, OpSum, apply, expect, inner, random_mps, siteinds, tdvp + AbstractObserver, MPO, MPS, OpSum, apply, expect, inner, random_mps, siteinds, tdvp using ITensors: ITensors, ITensor, dag, noprime, op, prime, scalar using KrylovKit: exponentiate using LinearAlgebra: norm @@ -9,337 +9,337 @@ using StableRNGs: StableRNG using Test: @test, @test_throws, @testset const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) @testset "Basic TDVP (eltype=$elt)" for elt in elts - N = 10 - cutoff = eps(real(elt)) * 10^4 - s = siteinds("S=1/2", N) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(elt, os, s) - rng = StableRNG(1234) - ψ0 = random_mps(rng, elt, s; linkdims=10) - time_step = elt(0.1) * im - # Time evolve forward: - ψ1 = tdvp(H, -time_step, ψ0; cutoff, nsite=1) - @test ITensors.scalartype(ψ1) == complex(elt) - #Different backend updaters, default updater_backend = "exponentiate" - @test ψ1 ≈ tdvp(H, -time_step, ψ0; cutoff, nsite=1, updater_backend="applyexp") - @test norm(ψ1) ≈ 1 rtol = √eps(real(elt)) * 10 - ## Should lose fidelity: - #@test abs(inner(ψ0,ψ1)) < 0.9 - # Average energy should be conserved: - @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) rtol = √eps(real(elt)) * 10 - # Time evolve backwards: - ψ2 = tdvp(H, time_step, ψ1; cutoff) - @test ITensors.scalartype(ψ2) == complex(elt) - @test norm(ψ2) ≈ 1 rtol = √eps(real(elt)) * 10 - # Should rotate back to original state: - @test abs(inner(ψ0, ψ2)) > 0.99 + N = 10 + cutoff = eps(real(elt)) * 10^4 + s = siteinds("S=1/2", N) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = MPO(elt, os, s) + rng = StableRNG(1234) + ψ0 = random_mps(rng, elt, s; linkdims = 10) + time_step = elt(0.1) * im + # Time evolve forward: + ψ1 = tdvp(H, -time_step, ψ0; cutoff, nsite = 1) + @test ITensors.scalartype(ψ1) == complex(elt) + #Different backend updaters, default updater_backend = "exponentiate" + @test ψ1 ≈ tdvp(H, -time_step, ψ0; cutoff, nsite = 1, updater_backend = "applyexp") + @test norm(ψ1) ≈ 1 rtol = √eps(real(elt)) * 10 + ## Should lose fidelity: + #@test abs(inner(ψ0,ψ1)) < 0.9 + # Average energy should be conserved: + @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) rtol = √eps(real(elt)) * 10 + # Time evolve backwards: + ψ2 = tdvp(H, time_step, ψ1; cutoff) + @test ITensors.scalartype(ψ2) == complex(elt) + @test norm(ψ2) ≈ 1 rtol = √eps(real(elt)) * 10 + # Should rotate back to original state: + @test abs(inner(ψ0, ψ2)) > 0.99 - @testset "Failure mode tests" begin - total_time = elt(0.1) * im - # matching total time and time step directions - @test_throws "signs agree" tdvp(H, -total_time, ψ0; time_step) - @test_throws "signs agree" tdvp(H, total_time, ψ0; time_step=(-time_step)) - # total time is an integer of time steps - @test_throws "integer" tdvp(H, total_time * 1.5, ψ0; time_step) - end + @testset "Failure mode tests" begin + total_time = elt(0.1) * im + # matching total time and time step directions + @test_throws "signs agree" tdvp(H, -total_time, ψ0; time_step) + @test_throws "signs agree" tdvp(H, total_time, ψ0; time_step = (-time_step)) + # total time is an integer of time steps + @test_throws "integer" tdvp(H, total_time * 1.5, ψ0; time_step) + end end @testset "TDVP: Sum of Hamiltonians (eltype=$elt)" for elt in elts - N = 10 - cutoff = 1e-10 + N = 10 + cutoff = 1.0e-10 - s = siteinds("S=1/2", N) + s = siteinds("S=1/2", N) - os1 = OpSum() - for j in 1:(N - 1) - os1 += 0.5, "S+", j, "S-", j + 1 - os1 += 0.5, "S-", j, "S+", j + 1 - end - os2 = OpSum() - for j in 1:(N - 1) - os2 += "Sz", j, "Sz", j + 1 - end - H1 = MPO(elt, os1, s) - H2 = MPO(elt, os2, s) - Hs = [H1, H2] - rng = StableRNG(1234) - ψ0 = random_mps(rng, elt, s; linkdims=10) - ψ1 = tdvp(Hs, -elt(0.1) * im, ψ0; cutoff, nsite=1) - @test ITensors.scalartype(ψ1) === complex(elt) - @test norm(ψ1) ≈ 1 rtol = √eps(real(elt)) - ## Should lose fidelity: - #@test abs(inner(ψ0,ψ1)) < 0.9 - # Average energy should be conserved: - @test real(sum(H -> inner(ψ1', H, ψ1), Hs)) ≈ sum(H -> inner(ψ0', H, ψ0), Hs) rtol = - 4 * √eps(real(elt)) - # Time evolve backwards: - ψ2 = tdvp(Hs, elt(0.1) * im, ψ1; cutoff) - @test ITensors.scalartype(ψ2) === complex(elt) - @test norm(ψ2) ≈ 1 rtol = √eps(real(elt)) - # Should rotate back to original state: - @test abs(inner(ψ0, ψ2)) > 0.99 + os1 = OpSum() + for j in 1:(N - 1) + os1 += 0.5, "S+", j, "S-", j + 1 + os1 += 0.5, "S-", j, "S+", j + 1 + end + os2 = OpSum() + for j in 1:(N - 1) + os2 += "Sz", j, "Sz", j + 1 + end + H1 = MPO(elt, os1, s) + H2 = MPO(elt, os2, s) + Hs = [H1, H2] + rng = StableRNG(1234) + ψ0 = random_mps(rng, elt, s; linkdims = 10) + ψ1 = tdvp(Hs, -elt(0.1) * im, ψ0; cutoff, nsite = 1) + @test ITensors.scalartype(ψ1) === complex(elt) + @test norm(ψ1) ≈ 1 rtol = √eps(real(elt)) + ## Should lose fidelity: + #@test abs(inner(ψ0,ψ1)) < 0.9 + # Average energy should be conserved: + @test real(sum(H -> inner(ψ1', H, ψ1), Hs)) ≈ sum(H -> inner(ψ0', H, ψ0), Hs) rtol = + 4 * √eps(real(elt)) + # Time evolve backwards: + ψ2 = tdvp(Hs, elt(0.1) * im, ψ1; cutoff) + @test ITensors.scalartype(ψ2) === complex(elt) + @test norm(ψ2) ≈ 1 rtol = √eps(real(elt)) + # Should rotate back to original state: + @test abs(inner(ψ0, ψ2)) > 0.99 end @testset "Custom updater in TDVP" begin - N = 10 - cutoff = 1e-12 - s = siteinds("S=1/2", N) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(os, s) - rng = StableRNG(1234) - ψ0 = random_mps(rng, s; linkdims=10) - function updater(PH, state0; internal_kwargs, kwargs...) - return exponentiate(PH, internal_kwargs.time_step, state0; kwargs...) - end - updater_kwargs = (; - ishermitian=true, tol=1e-12, krylovdim=30, maxiter=100, verbosity=0, eager=true - ) - t = -0.1im - ψ1 = tdvp(H, t, ψ0; updater, updater_kwargs, cutoff, nsite=1) - @test norm(ψ1) ≈ 1 - ## Should lose fidelity: - #@test abs(inner(ψ0,ψ1)) < 0.9 - # Average energy should be conserved: - @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) - # Time evolve backwards: - ψ2 = tdvp(H, +0.1im, ψ1; cutoff) - @test norm(ψ2) ≈ 1 - # Should rotate back to original state: - @test abs(inner(ψ0, ψ2)) > 0.99 + N = 10 + cutoff = 1.0e-12 + s = siteinds("S=1/2", N) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = MPO(os, s) + rng = StableRNG(1234) + ψ0 = random_mps(rng, s; linkdims = 10) + function updater(PH, state0; internal_kwargs, kwargs...) + return exponentiate(PH, internal_kwargs.time_step, state0; kwargs...) + end + updater_kwargs = (; + ishermitian = true, tol = 1.0e-12, krylovdim = 30, maxiter = 100, verbosity = 0, eager = true, + ) + t = -0.1im + ψ1 = tdvp(H, t, ψ0; updater, updater_kwargs, cutoff, nsite = 1) + @test norm(ψ1) ≈ 1 + ## Should lose fidelity: + #@test abs(inner(ψ0,ψ1)) < 0.9 + # Average energy should be conserved: + @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) + # Time evolve backwards: + ψ2 = tdvp(H, +0.1im, ψ1; cutoff) + @test norm(ψ2) ≈ 1 + # Should rotate back to original state: + @test abs(inner(ψ0, ψ2)) > 0.99 end @testset "Accuracy Test" begin - N = 4 - tau = 0.1 - ttotal = 1.0 - cutoff = 1e-12 - s = siteinds("S=1/2", N; conserve_qns=false) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(os, s) - HM = prod(H) - Ut = exp(-im * tau * HM) - state = MPS(s, n -> isodd(n) ? "Up" : "Dn") - state2 = deepcopy(state) - statex = prod(state) - Sz_tdvp = Float64[] - Sz_tdvp2 = Float64[] - Sz_exact = Float64[] - c = div(N, 2) - Szc = op("Sz", s[c]) - Nsteps = Int(ttotal / tau) - for step in 1:Nsteps - statex = noprime(Ut * statex) - statex /= norm(statex) + N = 4 + tau = 0.1 + ttotal = 1.0 + cutoff = 1.0e-12 + s = siteinds("S=1/2", N; conserve_qns = false) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = MPO(os, s) + HM = prod(H) + Ut = exp(-im * tau * HM) + state = MPS(s, n -> isodd(n) ? "Up" : "Dn") + state2 = deepcopy(state) + statex = prod(state) + Sz_tdvp = Float64[] + Sz_tdvp2 = Float64[] + Sz_exact = Float64[] + c = div(N, 2) + Szc = op("Sz", s[c]) + Nsteps = Int(ttotal / tau) + for step in 1:Nsteps + statex = noprime(Ut * statex) + statex /= norm(statex) - state = tdvp( - H, - -im * tau, - state; - cutoff, - normalize=false, - updater_kwargs=(; tol=1e-12, maxiter=500, krylovdim=25), - ) - push!(Sz_tdvp, real(expect(state, "Sz"; sites=c:c)[1])) - state2 = tdvp( - H, - -im * tau, - state2; - cutoff, - normalize=false, - updater_kwargs=(; tol=1e-12, maxiter=500, krylovdim=25), - ) - push!(Sz_tdvp2, real(expect(state2, "Sz"; sites=c:c)[1])) - push!(Sz_exact, real(scalar(dag(prime(statex, s[c])) * Szc * statex))) - F = abs(scalar(dag(statex) * prod(state))) - end - @test norm(Sz_tdvp - Sz_exact) < 1e-5 - @test norm(Sz_tdvp2 - Sz_exact) < 1e-5 + state = tdvp( + H, + -im * tau, + state; + cutoff, + normalize = false, + updater_kwargs = (; tol = 1.0e-12, maxiter = 500, krylovdim = 25), + ) + push!(Sz_tdvp, real(expect(state, "Sz"; sites = c:c)[1])) + state2 = tdvp( + H, + -im * tau, + state2; + cutoff, + normalize = false, + updater_kwargs = (; tol = 1.0e-12, maxiter = 500, krylovdim = 25), + ) + push!(Sz_tdvp2, real(expect(state2, "Sz"; sites = c:c)[1])) + push!(Sz_exact, real(scalar(dag(prime(statex, s[c])) * Szc * statex))) + F = abs(scalar(dag(statex) * prod(state))) + end + @test norm(Sz_tdvp - Sz_exact) < 1.0e-5 + @test norm(Sz_tdvp2 - Sz_exact) < 1.0e-5 end @testset "TEBD Comparison" begin - N = 10 - cutoff = 1e-12 - tau = 0.1 - ttotal = 1.0 - s = siteinds("S=1/2", N; conserve_qns=true) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(os, s) - gates = ITensor[] - for j in 1:(N - 1) - s1 = s[j] - s2 = s[j + 1] - hj = - op("Sz", s1) * op("Sz", s2) + - 1 / 2 * op("S+", s1) * op("S-", s2) + - 1 / 2 * op("S-", s1) * op("S+", s2) - Gj = exp(-1.0im * tau / 2 * hj) - push!(gates, Gj) - end - append!(gates, reverse(gates)) - state = MPS(s, n -> isodd(n) ? "Up" : "Dn") - phi = deepcopy(state) - c = div(N, 2) - # Evolve using TEBD - Nsteps = convert(Int, ceil(abs(ttotal / tau))) - Sz1 = zeros(Nsteps) - En1 = zeros(Nsteps) - Sz2 = zeros(Nsteps) - En2 = zeros(Nsteps) - for step in 1:Nsteps - state = apply(gates, state; cutoff) - nsite = (step <= 3 ? 2 : 1) - phi = tdvp( - H, -tau * im, phi; cutoff, nsite, normalize=true, updater_kwargs=(; krylovdim=15) - ) - Sz1[step] = expect(state, "Sz"; sites=c:c)[1] - Sz2[step] = expect(phi, "Sz"; sites=c:c)[1] - En1[step] = real(inner(state', H, state)) - En2[step] = real(inner(phi', H, phi)) - end - # Evolve using TDVP - function measure_sz(; state, bond, half_sweep) - if bond == 1 && half_sweep == 2 - return expect(state, "Sz"; sites=c) + N = 10 + cutoff = 1.0e-12 + tau = 0.1 + ttotal = 1.0 + s = siteinds("S=1/2", N; conserve_qns = true) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 end - return nothing - end - function measure_en(; state, bond, half_sweep) - if bond == 1 && half_sweep == 2 - return real(inner(state', H, state)) + H = MPO(os, s) + gates = ITensor[] + for j in 1:(N - 1) + s1 = s[j] + s2 = s[j + 1] + hj = + op("Sz", s1) * op("Sz", s2) + + 1 / 2 * op("S+", s1) * op("S-", s2) + + 1 / 2 * op("S-", s1) * op("S+", s2) + Gj = exp(-1.0im * tau / 2 * hj) + push!(gates, Gj) end - return nothing - end - obs = observer("Sz" => measure_sz, "En" => measure_en) + append!(gates, reverse(gates)) + state = MPS(s, n -> isodd(n) ? "Up" : "Dn") + phi = deepcopy(state) + c = div(N, 2) + # Evolve using TEBD + Nsteps = convert(Int, ceil(abs(ttotal / tau))) + Sz1 = zeros(Nsteps) + En1 = zeros(Nsteps) + Sz2 = zeros(Nsteps) + En2 = zeros(Nsteps) + for step in 1:Nsteps + state = apply(gates, state; cutoff) + nsite = (step <= 3 ? 2 : 1) + phi = tdvp( + H, -tau * im, phi; cutoff, nsite, normalize = true, updater_kwargs = (; krylovdim = 15) + ) + Sz1[step] = expect(state, "Sz"; sites = c:c)[1] + Sz2[step] = expect(phi, "Sz"; sites = c:c)[1] + En1[step] = real(inner(state', H, state)) + En2[step] = real(inner(phi', H, phi)) + end + # Evolve using TDVP + function measure_sz(; state, bond, half_sweep) + if bond == 1 && half_sweep == 2 + return expect(state, "Sz"; sites = c) + end + return nothing + end + function measure_en(; state, bond, half_sweep) + if bond == 1 && half_sweep == 2 + return real(inner(state', H, state)) + end + return nothing + end + obs = observer("Sz" => measure_sz, "En" => measure_en) - phi = MPS(s, n -> isodd(n) ? "Up" : "Dn") - phi = tdvp( - H, -im * ttotal, phi; time_step=-im * tau, cutoff, normalize=false, (observer!)=obs - ) - Sz2 = obs.Sz - En2 = obs.En - @test norm(Sz1 - Sz2) < 1e-3 - @test norm(En1 - En2) < 1e-3 + phi = MPS(s, n -> isodd(n) ? "Up" : "Dn") + phi = tdvp( + H, -im * ttotal, phi; time_step = -im * tau, cutoff, normalize = false, (observer!) = obs + ) + Sz2 = obs.Sz + En2 = obs.En + @test norm(Sz1 - Sz2) < 1.0e-3 + @test norm(En1 - En2) < 1.0e-3 end @testset "Imaginary Time Evolution" for reverse_step in [true, false] - N = 10 - cutoff = 1e-12 - tau = 1.0 - ttotal = 50.0 - s = siteinds("S=1/2", N) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(os, s) - rng = StableRNG(1234) - state = random_mps(rng, s; linkdims=2) - state2 = deepcopy(state) - trange = 0.0:tau:ttotal - for (step, t) in enumerate(trange) - nsite = (step <= 10 ? 2 : 1) - state = tdvp( - H, - -tau, - state; - cutoff, - nsite, - reverse_step, - normalize=true, - updater_kwargs=(; krylovdim=15), - ) - state2 = tdvp( - H, - -tau, - state2; - cutoff, - nsite, - reverse_step, - normalize=true, - updater_kwargs=(; krylovdim=15), - ) - end - @test state ≈ state2 rtol = 1e-6 - en1 = inner(state', H, state) - en2 = inner(state2', H, state2) - @test en1 < -4.25 - @test en1 ≈ en2 + N = 10 + cutoff = 1.0e-12 + tau = 1.0 + ttotal = 50.0 + s = siteinds("S=1/2", N) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = MPO(os, s) + rng = StableRNG(1234) + state = random_mps(rng, s; linkdims = 2) + state2 = deepcopy(state) + trange = 0.0:tau:ttotal + for (step, t) in enumerate(trange) + nsite = (step <= 10 ? 2 : 1) + state = tdvp( + H, + -tau, + state; + cutoff, + nsite, + reverse_step, + normalize = true, + updater_kwargs = (; krylovdim = 15), + ) + state2 = tdvp( + H, + -tau, + state2; + cutoff, + nsite, + reverse_step, + normalize = true, + updater_kwargs = (; krylovdim = 15), + ) + end + @test state ≈ state2 rtol = 1.0e-6 + en1 = inner(state', H, state) + en2 = inner(state2', H, state2) + @test en1 < -4.25 + @test en1 ≈ en2 end @testset "Observers" begin - N = 10 - cutoff = 1e-12 - tau = 0.1 - ttotal = 1.0 - s = siteinds("S=1/2", N; conserve_qns=true) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = MPO(os, s) - c = div(N, 2) - # Using Observers.jl - function measure_sz(; state, bond, half_sweep) - if bond == 1 && half_sweep == 2 - return expect(state, "Sz"; sites=c) + N = 10 + cutoff = 1.0e-12 + tau = 0.1 + ttotal = 1.0 + s = siteinds("S=1/2", N; conserve_qns = true) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 end - return nothing - end - function measure_en(; state, bond, half_sweep) - if bond == 1 && half_sweep == 2 - return real(inner(state', H, state)) + H = MPO(os, s) + c = div(N, 2) + # Using Observers.jl + function measure_sz(; state, bond, half_sweep) + if bond == 1 && half_sweep == 2 + return expect(state, "Sz"; sites = c) + end + return nothing end - return nothing - end - function identity_info(; info) - return info - end - obs = observer("Sz" => measure_sz, "En" => measure_en, "info" => identity_info) - step_measure_sz(; state) = expect(state, "Sz"; sites=c) - step_measure_en(; state) = real(inner(state', H, state)) - step_obs = observer("Sz" => step_measure_sz, "En" => step_measure_en) - state = MPS(s, n -> isodd(n) ? "Up" : "Dn") - tdvp( - H, - -im * ttotal, - state; - time_step=-im * tau, - cutoff, - normalize=false, - (observer!)=obs, - (step_observer!)=step_obs, - ) - Sz = filter(!isnothing, obs.Sz) - En = filter(!isnothing, obs.En) - infos = obs.info - Sz_step = step_obs.Sz - En_step = step_obs.En - @test length(Sz) == 10 - @test length(En) == 10 - @test length(Sz_step) == 10 - @test length(En_step) == 10 - @test Sz ≈ Sz_step - @test En ≈ En_step - @test all(x -> x.info.converged == 1, infos) - @test length(values(infos)) == 180 + function measure_en(; state, bond, half_sweep) + if bond == 1 && half_sweep == 2 + return real(inner(state', H, state)) + end + return nothing + end + function identity_info(; info) + return info + end + obs = observer("Sz" => measure_sz, "En" => measure_en, "info" => identity_info) + step_measure_sz(; state) = expect(state, "Sz"; sites = c) + step_measure_en(; state) = real(inner(state', H, state)) + step_obs = observer("Sz" => step_measure_sz, "En" => step_measure_en) + state = MPS(s, n -> isodd(n) ? "Up" : "Dn") + tdvp( + H, + -im * ttotal, + state; + time_step = -im * tau, + cutoff, + normalize = false, + (observer!) = obs, + (step_observer!) = step_obs, + ) + Sz = filter(!isnothing, obs.Sz) + En = filter(!isnothing, obs.En) + infos = obs.info + Sz_step = step_obs.Sz + En_step = step_obs.En + @test length(Sz) == 10 + @test length(En) == 10 + @test length(Sz_step) == 10 + @test length(En_step) == 10 + @test Sz ≈ Sz_step + @test En ≈ En_step + @test all(x -> x.info.converged == 1, infos) + @test length(values(infos)) == 180 end end diff --git a/test/base/test_solvers/test_tdvp_time_dependent.jl b/test/base/test_solvers/test_tdvp_time_dependent.jl index 5fcc972..54724bf 100644 --- a/test/base/test_solvers/test_tdvp_time_dependent.jl +++ b/test/base/test_solvers/test_tdvp_time_dependent.jl @@ -1,118 +1,118 @@ @eval module $(gensym()) using ITensors: ITensors, Index, QN, contract, scalartype using ITensorMPS: - ITensorMPS, - MPO, - MPS, - ProjMPO, - ProjMPOSum, - TimeDependentSum, - position!, - random_mps, - siteinds, - tdvp + ITensorMPS, + MPO, + MPS, + ProjMPO, + ProjMPOSum, + TimeDependentSum, + position!, + random_mps, + siteinds, + tdvp using LinearAlgebra: norm using StableRNGs: StableRNG using Test: @test, @test_skip, @testset include(joinpath(pkgdir(ITensorMPS), "examples", "solvers", "03_models.jl")) include(joinpath(pkgdir(ITensorMPS), "examples", "solvers", "03_updaters.jl")) @testset "TDVP with ODE local updater" begin - @testset "TimeDependentSum (eltype=$elt)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ), - conserve_qns in [false, true] + @testset "TimeDependentSum (eltype=$elt)" for elt in ( + Float32, Float64, Complex{Float32}, Complex{Float64}, + ), + conserve_qns in [false, true] - n = 4 - s = siteinds("S=1/2", 4; conserve_qns) - H = MPO(elt, s, "I") - H⃗ = (H, H) - region = 2:3 - rng = StableRNG(1234) - ψ = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims=2) - H⃗ᵣ = ProjMPO.(H⃗) - map(Hᵣ -> position!(Hᵣ, ψ, first(region)), H⃗ᵣ) - ∑Hᵣ = ProjMPOSum(collect(H⃗)) - position!(∑Hᵣ, ψ, first(region)) - f⃗ₜ = (t -> sin(elt(0.1) * t), t -> cos(elt(0.2) * t)) - α = elt(0.5) - ∑Hₜ = α * TimeDependentSum(f⃗ₜ, ITensors.terms(∑Hᵣ)) - t₀ = elt(0.5) - ∑Hₜ₀ = ∑Hₜ(t₀) - ψᵣ = reduce(*, map(v -> ψ[v], region)) - Hψ = ∑Hₜ₀(ψᵣ) - @test eltype(Hψ) == elt - @test Hψ ≈ sum(i -> α * f⃗ₜ[i](t₀) * H⃗ᵣ[i](ψᵣ), eachindex(H⃗)) - end - @testset "Time dependent Hamiltonian (eltype=$elt, conserve_qns=$conserve_qns)" for elt in - ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ), - conserve_qns in [false, true] + n = 4 + s = siteinds("S=1/2", 4; conserve_qns) + H = MPO(elt, s, "I") + H⃗ = (H, H) + region = 2:3 + rng = StableRNG(1234) + ψ = random_mps(rng, elt, s, j -> isodd(j) ? "↑" : "↓"; linkdims = 2) + H⃗ᵣ = ProjMPO.(H⃗) + map(Hᵣ -> position!(Hᵣ, ψ, first(region)), H⃗ᵣ) + ∑Hᵣ = ProjMPOSum(collect(H⃗)) + position!(∑Hᵣ, ψ, first(region)) + f⃗ₜ = (t -> sin(elt(0.1) * t), t -> cos(elt(0.2) * t)) + α = elt(0.5) + ∑Hₜ = α * TimeDependentSum(f⃗ₜ, ITensors.terms(∑Hᵣ)) + t₀ = elt(0.5) + ∑Hₜ₀ = ∑Hₜ(t₀) + ψᵣ = reduce(*, map(v -> ψ[v], region)) + Hψ = ∑Hₜ₀(ψᵣ) + @test eltype(Hψ) == elt + @test Hψ ≈ sum(i -> α * f⃗ₜ[i](t₀) * H⃗ᵣ[i](ψᵣ), eachindex(H⃗)) + end + @testset "Time dependent Hamiltonian (eltype=$elt, conserve_qns=$conserve_qns)" for elt in + ( + Float32, Float64, Complex{Float32}, Complex{Float64}, + ), + conserve_qns in [false, true] - n = 4 - J₁ = elt(1) - J₂ = elt(0.1) - ω₁ = real(elt)(0.1) - ω₂ = real(elt)(0.2) - ω⃗ = (ω₁, ω₂) - f⃗ = map(ω -> (t -> cos(ω * t)), ω⃗) - time_step = real(elt)(0.1) - time_stop = real(elt)(1) - nsite = 2 - maxdim = 100 - cutoff = √(eps(real(elt))) - tol = √eps(real(elt)) - s = siteinds("S=1/2", n) - ℋ₁₀ = heisenberg(n; J=J₁, J2=zero(elt)) - ℋ₂₀ = heisenberg(n; J=zero(elt), J2=J₂) - ℋ⃗₀ = (ℋ₁₀, ℋ₂₀) - H⃗₀ = map(ℋ₀ -> MPO(elt, ℋ₀, s), ℋ⃗₀) - ψ₀ = complex.(MPS(elt, s, j -> isodd(j) ? "↑" : "↓")) - ψₜ_ode = tdvp( - -im * TimeDependentSum(f⃗, H⃗₀), - time_stop, - ψ₀; - updater=ode_updater, - updater_kwargs=(; reltol=tol, abstol=tol), - time_step, - maxdim, - cutoff, - nsite, - ) - ψₜ_krylov = tdvp( - -im * TimeDependentSum(f⃗, H⃗₀), - time_stop, - ψ₀; - updater=krylov_updater, - updater_kwargs=(; tol, eager=true), - time_step, - maxdim, - cutoff, - nsite, - ) - ψₜ_full, _ = ode_updater( - -im * TimeDependentSum(f⃗, contract.(H⃗₀)), - contract(ψ₀); - internal_kwargs=(; time_step=time_stop), - reltol=tol, - abstol=tol, - ) + n = 4 + J₁ = elt(1) + J₂ = elt(0.1) + ω₁ = real(elt)(0.1) + ω₂ = real(elt)(0.2) + ω⃗ = (ω₁, ω₂) + f⃗ = map(ω -> (t -> cos(ω * t)), ω⃗) + time_step = real(elt)(0.1) + time_stop = real(elt)(1) + nsite = 2 + maxdim = 100 + cutoff = √(eps(real(elt))) + tol = √eps(real(elt)) + s = siteinds("S=1/2", n) + ℋ₁₀ = heisenberg(n; J = J₁, J2 = zero(elt)) + ℋ₂₀ = heisenberg(n; J = zero(elt), J2 = J₂) + ℋ⃗₀ = (ℋ₁₀, ℋ₂₀) + H⃗₀ = map(ℋ₀ -> MPO(elt, ℋ₀, s), ℋ⃗₀) + ψ₀ = complex.(MPS(elt, s, j -> isodd(j) ? "↑" : "↓")) + ψₜ_ode = tdvp( + -im * TimeDependentSum(f⃗, H⃗₀), + time_stop, + ψ₀; + updater = ode_updater, + updater_kwargs = (; reltol = tol, abstol = tol), + time_step, + maxdim, + cutoff, + nsite, + ) + ψₜ_krylov = tdvp( + -im * TimeDependentSum(f⃗, H⃗₀), + time_stop, + ψ₀; + updater = krylov_updater, + updater_kwargs = (; tol, eager = true), + time_step, + maxdim, + cutoff, + nsite, + ) + ψₜ_full, _ = ode_updater( + -im * TimeDependentSum(f⃗, contract.(H⃗₀)), + contract(ψ₀); + internal_kwargs = (; time_step = time_stop), + reltol = tol, + abstol = tol, + ) - @test scalartype(ψ₀) == complex(elt) - @test scalartype(ψₜ_ode) == complex(elt) - @test scalartype(ψₜ_krylov) == complex(elt) - @test scalartype(ψₜ_full) == complex(elt) - @test norm(ψ₀) ≈ 1 - @test norm(ψₜ_ode) ≈ 1 - @test norm(ψₜ_krylov) ≈ 1 rtol = √(eps(real(elt))) - @test norm(ψₜ_full) ≈ 1 + @test scalartype(ψ₀) == complex(elt) + @test scalartype(ψₜ_ode) == complex(elt) + @test scalartype(ψₜ_krylov) == complex(elt) + @test scalartype(ψₜ_full) == complex(elt) + @test norm(ψ₀) ≈ 1 + @test norm(ψₜ_ode) ≈ 1 + @test norm(ψₜ_krylov) ≈ 1 rtol = √(eps(real(elt))) + @test norm(ψₜ_full) ≈ 1 - ode_err = norm(contract(ψₜ_ode) - ψₜ_full) - krylov_err = norm(contract(ψₜ_krylov) - ψₜ_full) + ode_err = norm(contract(ψₜ_ode) - ψₜ_full) + krylov_err = norm(contract(ψₜ_krylov) - ψₜ_full) - @test krylov_err > ode_err - @test ode_err < √(eps(real(elt))) * 10^4 - @test krylov_err < √(eps(real(elt))) * 10^5 - end + @test krylov_err > ode_err + @test ode_err < √(eps(real(elt))) * 10^4 + @test krylov_err < √(eps(real(elt))) * 10^5 + end end end diff --git a/test/base/test_sweepnext.jl b/test/base/test_sweepnext.jl index c74008c..f6b91a5 100644 --- a/test/base/test_sweepnext.jl +++ b/test/base/test_sweepnext.jl @@ -1,51 +1,51 @@ using ITensors, Test @testset "sweepnext function" begin - @testset "one site" begin - N = 6 - count = 1 - output = [ - (1, 1), - (2, 1), - (3, 1), - (4, 1), - (5, 1), - (6, 1), - (6, 2), - (5, 2), - (4, 2), - (3, 2), - (2, 2), - (1, 2), - ] - for (b, ha) in sweepnext(N; ncenter=1) - @test (b, ha) == output[count] - count += 1 + @testset "one site" begin + N = 6 + count = 1 + output = [ + (1, 1), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), + (6, 2), + (5, 2), + (4, 2), + (3, 2), + (2, 2), + (1, 2), + ] + for (b, ha) in sweepnext(N; ncenter = 1) + @test (b, ha) == output[count] + count += 1 + end + @test count == 2 * N + 1 end - @test count == 2 * N + 1 - end - @testset "two site" begin - N = 6 - count = 1 - output = [ - (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (5, 2), (4, 2), (3, 2), (2, 2), (1, 2) - ] - for (b, ha) in sweepnext(N) - @test (b, ha) == output[count] - count += 1 + @testset "two site" begin + N = 6 + count = 1 + output = [ + (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (5, 2), (4, 2), (3, 2), (2, 2), (1, 2), + ] + for (b, ha) in sweepnext(N) + @test (b, ha) == output[count] + count += 1 + end + @test count == 2 * (N - 1) + 1 end - @test count == 2 * (N - 1) + 1 - end - @testset "three site" begin - N = 6 - count = 1 - output = [(1, 1), (2, 1), (3, 1), (4, 1), (4, 2), (3, 2), (2, 2), (1, 2)] - for (b, ha) in sweepnext(N; ncenter=3) - @test (b, ha) == output[count] - count += 1 + @testset "three site" begin + N = 6 + count = 1 + output = [(1, 1), (2, 1), (3, 1), (4, 1), (4, 2), (3, 2), (2, 2), (1, 2)] + for (b, ha) in sweepnext(N; ncenter = 3) + @test (b, ha) == output[count] + count += 1 + end + @test count == 2 * (N - 2) + 1 end - @test count == 2 * (N - 2) + 1 - end end diff --git a/test/base/test_sweeps.jl b/test/base/test_sweeps.jl index 49afcc6..3a3869c 100644 --- a/test/base/test_sweeps.jl +++ b/test/base/test_sweeps.jl @@ -3,149 +3,149 @@ using Test @testset "Sweeps constructor" begin - #Sweeps - #1 cutoff=1.0E-12, maxdim=50, mindim=10, noise=1.0E-07 - #2 cutoff=1.0E-12, maxdim=100, mindim=20, noise=1.0E-08 - #3 cutoff=1.0E-12, maxdim=200, mindim=20, noise=1.0E-10 - #4 cutoff=1.0E-12, maxdim=400, mindim=20, noise=0.0E+00 - #5 cutoff=1.0E-12, maxdim=800, mindim=20, noise=1.0E-11 - #6 cutoff=1.0E-12, maxdim=800, mindim=20, noise=0.0E+00 - - sweep_args = [ - "maxdim" "mindim" "cutoff" "noise" - 50 10 1e-12 1e-7 - 100 20 1e-12 1e-8 - 200 20 1e-12 1e-10 - 400 20 1e-12 0 - 800 20 1e-12 1e-11 - 800 20 1e-12 0 - ] - - @testset "Don't specify nsweep" begin - nsw = size(sweep_args, 1) - 1 - sw = Sweeps(sweep_args) - - @test nsweep(sw) == nsw - - @test maxdim(sw, 1) == 50 - @test maxdim(sw, 2) == 100 - @test maxdim(sw, 3) == 200 - @test maxdim(sw, 4) == 400 - for n in 5:nsw - @test maxdim(sw, n) == 800 + #Sweeps + #1 cutoff=1.0E-12, maxdim=50, mindim=10, noise=1.0E-07 + #2 cutoff=1.0E-12, maxdim=100, mindim=20, noise=1.0E-08 + #3 cutoff=1.0E-12, maxdim=200, mindim=20, noise=1.0E-10 + #4 cutoff=1.0E-12, maxdim=400, mindim=20, noise=0.0E+00 + #5 cutoff=1.0E-12, maxdim=800, mindim=20, noise=1.0E-11 + #6 cutoff=1.0E-12, maxdim=800, mindim=20, noise=0.0E+00 + + sweep_args = [ + "maxdim" "mindim" "cutoff" "noise" + 50 10 1.0e-12 1.0e-7 + 100 20 1.0e-12 1.0e-8 + 200 20 1.0e-12 1.0e-10 + 400 20 1.0e-12 0 + 800 20 1.0e-12 1.0e-11 + 800 20 1.0e-12 0 + ] + + @testset "Don't specify nsweep" begin + nsw = size(sweep_args, 1) - 1 + sw = Sweeps(sweep_args) + + @test nsweep(sw) == nsw + + @test maxdim(sw, 1) == 50 + @test maxdim(sw, 2) == 100 + @test maxdim(sw, 3) == 200 + @test maxdim(sw, 4) == 400 + for n in 5:nsw + @test maxdim(sw, n) == 800 + end + + @test mindim(sw, 1) == 10 + for n in 2:nsw + @test mindim(sw, n) == 20 + end + + for n in 1:nsw + @test cutoff(sw, n) == 1.0e-12 + end + + @test noise(sw, 1) == 1.0e-7 + @test noise(sw, 2) == 1.0e-8 + @test noise(sw, 3) == 1.0e-10 + @test noise(sw, 4) == 0 + @test noise(sw, 5) == 1.0e-11 + @test noise(sw, 6) == 0 end - @test mindim(sw, 1) == 10 - for n in 2:nsw - @test mindim(sw, n) == 20 + @testset "Specify nsweep, more than data" begin + nsw = 7 + sw = Sweeps(nsw, sweep_args) + + @test nsweep(sw) == nsw + + @test maxdim(sw, 1) == 50 + @test maxdim(sw, 2) == 100 + @test maxdim(sw, 3) == 200 + @test maxdim(sw, 4) == 400 + for n in 5:nsw + @test maxdim(sw, n) == 800 + end + + @test mindim(sw, 1) == 10 + for n in 2:nsw + @test mindim(sw, n) == 20 + end + + for n in 1:nsw + @test cutoff(sw, n) == 1.0e-12 + end + + @test noise(sw, 1) == 1.0e-7 + @test noise(sw, 2) == 1.0e-8 + @test noise(sw, 3) == 1.0e-10 + @test noise(sw, 4) == 0 + @test noise(sw, 5) == 1.0e-11 + @test noise(sw, 6) == 0 + @test noise(sw, 7) == 0 end - for n in 1:nsw - @test cutoff(sw, n) == 1e-12 + @testset "Specify nsweep, less than data" begin + nsw = 5 + sw = Sweeps(nsw, sweep_args) + + @test nsweep(sw) == nsw + + @test maxdim(sw, 1) == 50 + @test maxdim(sw, 2) == 100 + @test maxdim(sw, 3) == 200 + @test maxdim(sw, 4) == 400 + for n in 5:nsw + @test maxdim(sw, n) == 800 + end + + @test mindim(sw, 1) == 10 + for n in 2:nsw + @test mindim(sw, n) == 20 + end + + for n in 1:nsw + @test cutoff(sw, n) == 1.0e-12 + end + + @test noise(sw, 1) == 1.0e-7 + @test noise(sw, 2) == 1.0e-8 + @test noise(sw, 3) == 1.0e-10 + @test noise(sw, 4) == 0 + @test noise(sw, 5) == 1.0e-11 end - @test noise(sw, 1) == 1e-7 - @test noise(sw, 2) == 1e-8 - @test noise(sw, 3) == 1e-10 - @test noise(sw, 4) == 0 - @test noise(sw, 5) == 1e-11 - @test noise(sw, 6) == 0 - end - - @testset "Specify nsweep, more than data" begin - nsw = 7 - sw = Sweeps(nsw, sweep_args) - - @test nsweep(sw) == nsw - - @test maxdim(sw, 1) == 50 - @test maxdim(sw, 2) == 100 - @test maxdim(sw, 3) == 200 - @test maxdim(sw, 4) == 400 - for n in 5:nsw - @test maxdim(sw, n) == 800 + @testset "Variable types of input" begin + sw = Sweeps(5) + setnoise!(sw, 1.0e-8, 0) + @test noise(sw, 1) ≈ 1.0e-8 + @test noise(sw, 2) ≈ 0.0 + @test noise(sw, 3) ≈ 0.0 + setcutoff!(sw, 0, 1.0e-8, 0, 1.0e-12) + @test cutoff(sw, 1) ≈ 0.0 + @test cutoff(sw, 2) ≈ 1.0e-8 + @test cutoff(sw, 3) ≈ 0.0 + @test cutoff(sw, 4) ≈ 1.0e-12 end - @test mindim(sw, 1) == 10 - for n in 2:nsw - @test mindim(sw, n) == 20 + @testset "Keyword args to constructor" begin + sw = Sweeps(5; maxdim = [4, 8, 16], mindim = 1, cutoff = [1.0e-5, 1.0e-8]) + @test maxdim(sw, 1) == 4 + @test maxdim(sw, 2) == 8 + @test maxdim(sw, 3) == 16 + @test maxdim(sw, 4) == 16 + @test maxdim(sw, 5) == 16 + + @test mindim(sw, 1) == 1 + @test mindim(sw, 5) == 1 + + @test cutoff(sw, 1) ≈ 1.0e-5 + @test cutoff(sw, 2) ≈ 1.0e-8 + @test cutoff(sw, 3) ≈ 1.0e-8 + @test cutoff(sw, 4) ≈ 1.0e-8 + @test cutoff(sw, 5) ≈ 1.0e-8 + + sw = Sweeps(5; cutoff = 1.0e-8) + @test maxdim(sw, 1) == typemax(Int) + @test maxdim(sw, 5) == typemax(Int) end - - for n in 1:nsw - @test cutoff(sw, n) == 1e-12 - end - - @test noise(sw, 1) == 1e-7 - @test noise(sw, 2) == 1e-8 - @test noise(sw, 3) == 1e-10 - @test noise(sw, 4) == 0 - @test noise(sw, 5) == 1e-11 - @test noise(sw, 6) == 0 - @test noise(sw, 7) == 0 - end - - @testset "Specify nsweep, less than data" begin - nsw = 5 - sw = Sweeps(nsw, sweep_args) - - @test nsweep(sw) == nsw - - @test maxdim(sw, 1) == 50 - @test maxdim(sw, 2) == 100 - @test maxdim(sw, 3) == 200 - @test maxdim(sw, 4) == 400 - for n in 5:nsw - @test maxdim(sw, n) == 800 - end - - @test mindim(sw, 1) == 10 - for n in 2:nsw - @test mindim(sw, n) == 20 - end - - for n in 1:nsw - @test cutoff(sw, n) == 1e-12 - end - - @test noise(sw, 1) == 1e-7 - @test noise(sw, 2) == 1e-8 - @test noise(sw, 3) == 1e-10 - @test noise(sw, 4) == 0 - @test noise(sw, 5) == 1e-11 - end - - @testset "Variable types of input" begin - sw = Sweeps(5) - setnoise!(sw, 1E-8, 0) - @test noise(sw, 1) ≈ 1E-8 - @test noise(sw, 2) ≈ 0.0 - @test noise(sw, 3) ≈ 0.0 - setcutoff!(sw, 0, 1E-8, 0, 1E-12) - @test cutoff(sw, 1) ≈ 0.0 - @test cutoff(sw, 2) ≈ 1E-8 - @test cutoff(sw, 3) ≈ 0.0 - @test cutoff(sw, 4) ≈ 1E-12 - end - - @testset "Keyword args to constructor" begin - sw = Sweeps(5; maxdim=[4, 8, 16], mindim=1, cutoff=[1E-5, 1E-8]) - @test maxdim(sw, 1) == 4 - @test maxdim(sw, 2) == 8 - @test maxdim(sw, 3) == 16 - @test maxdim(sw, 4) == 16 - @test maxdim(sw, 5) == 16 - - @test mindim(sw, 1) == 1 - @test mindim(sw, 5) == 1 - - @test cutoff(sw, 1) ≈ 1E-5 - @test cutoff(sw, 2) ≈ 1E-8 - @test cutoff(sw, 3) ≈ 1E-8 - @test cutoff(sw, 4) ≈ 1E-8 - @test cutoff(sw, 5) ≈ 1E-8 - - sw = Sweeps(5; cutoff=1E-8) - @test maxdim(sw, 1) == typemax(Int) - @test maxdim(sw, 5) == typemax(Int) - end end diff --git a/test/base/test_symmetrystyle.jl b/test/base/test_symmetrystyle.jl index 66c5823..c9af67c 100644 --- a/test/base/test_symmetrystyle.jl +++ b/test/base/test_symmetrystyle.jl @@ -3,11 +3,11 @@ using ITensors.NDTensors using Test @testset "SymmetryStyle trait" begin - sqn = siteinds("S=1/2", 10; conserve_qns=true) - s = removeqns(sqn) - psi = MPS(s) - psiqn = MPS(sqn) - @test @inferred(ITensors.SymmetryStyle, ITensors.symmetrystyle(psi)) == ITensors.NonQN() - @test @inferred(ITensors.SymmetryStyle, ITensors.symmetrystyle(psiqn)) == - ITensors.HasQNs() + sqn = siteinds("S=1/2", 10; conserve_qns = true) + s = removeqns(sqn) + psi = MPS(s) + psiqn = MPS(sqn) + @test @inferred(ITensors.SymmetryStyle, ITensors.symmetrystyle(psi)) == ITensors.NonQN() + @test @inferred(ITensors.SymmetryStyle, ITensors.symmetrystyle(psiqn)) == + ITensors.HasQNs() end diff --git a/test/base/test_threading.jl b/test/base/test_threading.jl index 28e1408..5c98ba0 100644 --- a/test/base/test_threading.jl +++ b/test/base/test_threading.jl @@ -4,54 +4,54 @@ using Test using LinearAlgebra if isone(Threads.nthreads()) - @warn "Testing block sparse multithreading but only one thread is set!" + @warn "Testing block sparse multithreading but only one thread is set!" end @testset "Threading" begin - blas_num_threads = Compat.get_num_threads() - strided_num_threads = ITensors.NDTensors.Strided.get_num_threads() + blas_num_threads = Compat.get_num_threads() + strided_num_threads = ITensors.NDTensors.Strided.get_num_threads() - BLAS.set_num_threads(1) - ITensors.NDTensors.Strided.set_num_threads(1) + BLAS.set_num_threads(1) + ITensors.NDTensors.Strided.set_num_threads(1) - @testset "Bug fixed in threaded block sparse" begin - maxdim = 10 - nsweeps = 2 - outputlevel = 0 - cutoff = 0.0 - Nx = 4 - Ny = 2 - U = 4.0 - t = 1.0 - N = Nx * Ny - sweeps = Sweeps(nsweeps) - maxdims = min.(maxdim, [100, 200, 400, 800, 2000, 3000, maxdim]) - maxdim!(sweeps, maxdims...) - cutoff!(sweeps, cutoff) - noise!(sweeps, 1e-6, 1e-7, 1e-8, 0.0) - sites = siteinds("Electron", N; conserve_qns=true) - lattice = square_lattice(Nx, Ny; yperiodic=true) - opsum = OpSum() - for b in lattice - opsum .-= t, "Cdagup", b.s1, "Cup", b.s2 - opsum .-= t, "Cdagup", b.s2, "Cup", b.s1 - opsum .-= t, "Cdagdn", b.s1, "Cdn", b.s2 - opsum .-= t, "Cdagdn", b.s2, "Cdn", b.s1 + @testset "Bug fixed in threaded block sparse" begin + maxdim = 10 + nsweeps = 2 + outputlevel = 0 + cutoff = 0.0 + Nx = 4 + Ny = 2 + U = 4.0 + t = 1.0 + N = Nx * Ny + sweeps = Sweeps(nsweeps) + maxdims = min.(maxdim, [100, 200, 400, 800, 2000, 3000, maxdim]) + maxdim!(sweeps, maxdims...) + cutoff!(sweeps, cutoff) + noise!(sweeps, 1.0e-6, 1.0e-7, 1.0e-8, 0.0) + sites = siteinds("Electron", N; conserve_qns = true) + lattice = square_lattice(Nx, Ny; yperiodic = true) + opsum = OpSum() + for b in lattice + opsum .-= t, "Cdagup", b.s1, "Cup", b.s2 + opsum .-= t, "Cdagup", b.s2, "Cup", b.s1 + opsum .-= t, "Cdagdn", b.s1, "Cdn", b.s2 + opsum .-= t, "Cdagdn", b.s2, "Cdn", b.s1 + end + for n in 1:N + opsum .+= U, "Nupdn", n + end + H = MPO(opsum, sites) + Hsplit = splitblocks(linkinds, H) + state = [isodd(n) ? "↑" : "↓" for n in 1:N] + ψ0 = MPS(sites, state) + enabled = ITensors.enable_threaded_blocksparse(true) + energy, _ = dmrg(H, ψ0, sweeps; outputlevel = outputlevel) + energy_split, _ = dmrg(Hsplit, ψ0, sweeps; outputlevel = outputlevel) + @test energy_split ≈ energy + ITensors.enable_threaded_blocksparse(enabled) end - for n in 1:N - opsum .+= U, "Nupdn", n - end - H = MPO(opsum, sites) - Hsplit = splitblocks(linkinds, H) - state = [isodd(n) ? "↑" : "↓" for n in 1:N] - ψ0 = MPS(sites, state) - enabled = ITensors.enable_threaded_blocksparse(true) - energy, _ = dmrg(H, ψ0, sweeps; outputlevel=outputlevel) - energy_split, _ = dmrg(Hsplit, ψ0, sweeps; outputlevel=outputlevel) - @test energy_split ≈ energy - ITensors.enable_threaded_blocksparse(enabled) - end - BLAS.set_num_threads(blas_num_threads) - ITensors.NDTensors.Strided.set_num_threads(strided_num_threads) + BLAS.set_num_threads(blas_num_threads) + ITensors.NDTensors.Strided.set_num_threads(strided_num_threads) end diff --git a/test/base/utils/TestITensorMPSExportedNames.jl b/test/base/utils/TestITensorMPSExportedNames.jl index ba05f9a..7acea6e 100644 --- a/test/base/utils/TestITensorMPSExportedNames.jl +++ b/test/base/utils/TestITensorMPSExportedNames.jl @@ -1,170 +1,170 @@ module TestITensorMPSExportedNames const ITENSORMPS_EXPORTED_NAMES = [ - Symbol("@OpName_str"), - Symbol("@SiteType_str"), - Symbol("@StateName_str"), - Symbol("@TagType_str"), - Symbol("@ValName_str"), - Symbol("@preserve_ortho"), - :AbstractMPS, - :AbstractObserver, - :Apply, - :AutoMPO, - :DMRGMeasurement, - :DMRGObserver, - :ITensorMPS, - :Lattice, - :LatticeBond, - :MPO, - :MPS, - :NoObserver, - :Op, - :OpName, - :OpSum, - :Ops, - :Prod, - :ProjMPO, - :ProjMPOSum, - :ProjMPO_MPS, - :Scaled, - :SiteType, - :Spectrum, - :StateName, - :Sum, - :TagType, - :Sweeps, - :TimeDependentSum, - :Trotter, - :ValName, - :add, - :add!, - :apply, - :applyMPO, - :applympo, - :argsdict, - :checkdone!, - :coefficient, - :common_siteind, - :common_siteinds, - :contract, - :convert_leaf_eltype, - :correlation_matrix, - :cutoff, - :cutoff!, - :disk, - :dmrg, - :dmrg_x, - :dot, - :eigs, - :energies, - :entropy, - :errorMPOprod, - :error_contract, - :error_mpoprod, - :error_mul, - :expand, - :expect, - :findfirstsiteind, - :findfirstsiteinds, - :findsite, - :findsites, - :firstsiteind, - :firstsiteinds, - :get_cutoffs, - :get_maxdims, - :get_mindims, - :get_noises, - :has_fermion_string, - :hassameinds, - :inner, - :isortho, - :linkdim, - :linkdims, - :linkind, - :linkindex, - :linkinds, - :linsolve, - :logdot, - :loginner, - :lognorm, - :lproj, - :maxdim, - :maxdim!, - :maxlinkdim, - :measure!, - :measurements, - :mindim, - :mindim!, - :movesite, - :movesites, - :mul, - :multMPO, - :multmpo, - :noise, - :noise!, - :noiseterm, - :nsite, - :nsweep, - :op, - :ops, - :orthoCenter, - :ortho_lims, - :orthocenter, - :orthogonalize, - :orthogonalize!, - :outer, - :position!, - :product, - :primelinks!, - :productMPS, - :projector, - :promote_itensor_eltype, - :randomMPO, - :randomMPS, - :random_mpo, - :random_mps, - :replace_siteinds, - :replace_siteinds!, - :replacebond, - :replacebond!, - :replaceprime, - :replacesites!, - :reset_ortho_lims!, - :rproj, - :sample, - :sample!, - :set_leftlim!, - :set_ortho_lims!, - :set_rightlim!, - :setcutoff!, - :setmaxdim!, - :setmindim!, - :setnoise!, - :sim!, - :simlinks!, - :siteind, - :siteindex, - :siteinds, - :splitblocks, - :square_lattice, - :state, - :sum, - :swapbondsites, - :sweepnext, - :tdvp, - :tensors, - :to_vec, - :toMPO, - :totalqn, - :tr, - :triangular_lattice, - :truncate, - :truncate!, - :truncerror, - :truncerrors, - :unique_siteind, - :unique_siteinds, - :val, - :⋅, + Symbol("@OpName_str"), + Symbol("@SiteType_str"), + Symbol("@StateName_str"), + Symbol("@TagType_str"), + Symbol("@ValName_str"), + Symbol("@preserve_ortho"), + :AbstractMPS, + :AbstractObserver, + :Apply, + :AutoMPO, + :DMRGMeasurement, + :DMRGObserver, + :ITensorMPS, + :Lattice, + :LatticeBond, + :MPO, + :MPS, + :NoObserver, + :Op, + :OpName, + :OpSum, + :Ops, + :Prod, + :ProjMPO, + :ProjMPOSum, + :ProjMPO_MPS, + :Scaled, + :SiteType, + :Spectrum, + :StateName, + :Sum, + :TagType, + :Sweeps, + :TimeDependentSum, + :Trotter, + :ValName, + :add, + :add!, + :apply, + :applyMPO, + :applympo, + :argsdict, + :checkdone!, + :coefficient, + :common_siteind, + :common_siteinds, + :contract, + :convert_leaf_eltype, + :correlation_matrix, + :cutoff, + :cutoff!, + :disk, + :dmrg, + :dmrg_x, + :dot, + :eigs, + :energies, + :entropy, + :errorMPOprod, + :error_contract, + :error_mpoprod, + :error_mul, + :expand, + :expect, + :findfirstsiteind, + :findfirstsiteinds, + :findsite, + :findsites, + :firstsiteind, + :firstsiteinds, + :get_cutoffs, + :get_maxdims, + :get_mindims, + :get_noises, + :has_fermion_string, + :hassameinds, + :inner, + :isortho, + :linkdim, + :linkdims, + :linkind, + :linkindex, + :linkinds, + :linsolve, + :logdot, + :loginner, + :lognorm, + :lproj, + :maxdim, + :maxdim!, + :maxlinkdim, + :measure!, + :measurements, + :mindim, + :mindim!, + :movesite, + :movesites, + :mul, + :multMPO, + :multmpo, + :noise, + :noise!, + :noiseterm, + :nsite, + :nsweep, + :op, + :ops, + :orthoCenter, + :ortho_lims, + :orthocenter, + :orthogonalize, + :orthogonalize!, + :outer, + :position!, + :product, + :primelinks!, + :productMPS, + :projector, + :promote_itensor_eltype, + :randomMPO, + :randomMPS, + :random_mpo, + :random_mps, + :replace_siteinds, + :replace_siteinds!, + :replacebond, + :replacebond!, + :replaceprime, + :replacesites!, + :reset_ortho_lims!, + :rproj, + :sample, + :sample!, + :set_leftlim!, + :set_ortho_lims!, + :set_rightlim!, + :setcutoff!, + :setmaxdim!, + :setmindim!, + :setnoise!, + :sim!, + :simlinks!, + :siteind, + :siteindex, + :siteinds, + :splitblocks, + :square_lattice, + :state, + :sum, + :swapbondsites, + :sweepnext, + :tdvp, + :tensors, + :to_vec, + :toMPO, + :totalqn, + :tr, + :triangular_lattice, + :truncate, + :truncate!, + :truncerror, + :truncerrors, + :unique_siteind, + :unique_siteinds, + :val, + :⋅, ] end diff --git a/test/base/utils/util.jl b/test/base/utils/util.jl index 6bc27e9..e35ed8d 100644 --- a/test/base/utils/util.jl +++ b/test/base/utils/util.jl @@ -4,53 +4,53 @@ using Random using ITensorMPS: AbstractMPS function fill_trivial_coefficients(ψ) - return ψ isa AbstractMPS ? (1, ψ) : ψ + return ψ isa AbstractMPS ? (1, ψ) : ψ end -function inner_add(α⃗ψ⃗::Tuple{<:Number,<:MPST}...) where {MPST<:AbstractMPS} - Nₘₚₛ = length(α⃗ψ⃗) - α⃗ = first.(α⃗ψ⃗) - ψ⃗ = last.(α⃗ψ⃗) - N⃡ = (conj(α⃗[i]) * α⃗[j] * inner(ψ⃗[i], ψ⃗[j]) for i in 1:Nₘₚₛ, j in 1:Nₘₚₛ) - return sum(N⃡) +function inner_add(α⃗ψ⃗::Tuple{<:Number, <:MPST}...) where {MPST <: AbstractMPS} + Nₘₚₛ = length(α⃗ψ⃗) + α⃗ = first.(α⃗ψ⃗) + ψ⃗ = last.(α⃗ψ⃗) + N⃡ = (conj(α⃗[i]) * α⃗[j] * inner(ψ⃗[i], ψ⃗[j]) for i in 1:Nₘₚₛ, j in 1:Nₘₚₛ) + return sum(N⃡) end inner_add(ψ⃗...) = inner_add(fill_trivial_coefficients.(ψ⃗)...) # TODO: this is no longer needed, use random_mps -function makeRandomMPS(sites; chi::Int=4)::MPS - N = length(sites) - v = Vector{ITensor}(undef, N) - l = [Index(chi, "Link,l=$n") for n in 1:(N - 1)] - for n in 1:N - s = sites[n] - if n == 1 - v[n] = random_itensor(l[n], s) - elseif n == N - v[n] = random_itensor(l[n - 1], s) - else - v[n] = random_itensor(l[n - 1], l[n], s) +function makeRandomMPS(sites; chi::Int = 4)::MPS + N = length(sites) + v = Vector{ITensor}(undef, N) + l = [Index(chi, "Link,l=$n") for n in 1:(N - 1)] + for n in 1:N + s = sites[n] + if n == 1 + v[n] = random_itensor(l[n], s) + elseif n == N + v[n] = random_itensor(l[n - 1], s) + else + v[n] = random_itensor(l[n - 1], l[n], s) + end + normalize!(v[n]) end - normalize!(v[n]) - end - return MPS(v, 0, N + 1) + return MPS(v, 0, N + 1) end -function makeRandomMPO(sites; chi::Int=4)::MPO - N = length(sites) - v = Vector{ITensor}(undef, N) - l = [Index(chi, "Link,l=$n") for n in 1:(N - 1)] - for n in 1:N - s = sites[n] - if n == 1 - v[n] = ITensor(l[n], s, s') - elseif n == N - v[n] = ITensor(l[n - 1], s, s') - else - v[n] = ITensor(l[n - 1], s, s', l[n]) +function makeRandomMPO(sites; chi::Int = 4)::MPO + N = length(sites) + v = Vector{ITensor}(undef, N) + l = [Index(chi, "Link,l=$n") for n in 1:(N - 1)] + for n in 1:N + s = sites[n] + if n == 1 + v[n] = ITensor(l[n], s, s') + elseif n == N + v[n] = ITensor(l[n - 1], s, s') + else + v[n] = ITensor(l[n - 1], s, s', l[n]) + end + randn!(v[n]) + normalize!(v[n]) end - randn!(v[n]) - normalize!(v[n]) - end - return MPO(v, 0, N + 1) + return MPO(v, 0, N + 1) end diff --git a/test/ext/ITensorMPSChainRulesCoreExt/runtests.jl b/test/ext/ITensorMPSChainRulesCoreExt/runtests.jl index c2fe5a8..76d6d69 100644 --- a/test/ext/ITensorMPSChainRulesCoreExt/runtests.jl +++ b/test/ext/ITensorMPSChainRulesCoreExt/runtests.jl @@ -6,11 +6,11 @@ ITensors.BLAS.set_num_threads(1) ITensors.disable_threaded_blocksparse() @testset "$(@__DIR__)" begin - filenames = filter(readdir(@__DIR__)) do f - startswith("test_")(f) && endswith(".jl")(f) - end - @testset "Test $(@__DIR__)/$filename" for filename in filenames - println("Running $(@__DIR__)/$filename") - @time include(filename) - end + filenames = filter(readdir(@__DIR__)) do f + startswith("test_")(f) && endswith(".jl")(f) + end + @testset "Test $(@__DIR__)/$filename" for filename in filenames + println("Running $(@__DIR__)/$filename") + @time include(filename) + end end diff --git a/test/ext/ITensorMPSChainRulesCoreExt/test_chainrules.jl b/test/ext/ITensorMPSChainRulesCoreExt/test_chainrules.jl index b563f08..5515031 100644 --- a/test/ext/ITensorMPSChainRulesCoreExt/test_chainrules.jl +++ b/test/ext/ITensorMPSChainRulesCoreExt/test_chainrules.jl @@ -7,379 +7,379 @@ using Zygote Random.seed!(1234) @testset "ChainRules/Zygote AD tests for MPS/MPO" begin - @testset "issue 936" begin - # https://github.com/ITensor/ITensors.jl/issues/936 - n = 2 - s = siteinds("S=1/2", n) - x = (x -> outer(x', x))(random_mps(s)) - f1 = x -> tr(x) - f2 = x -> 2tr(x) - f3 = x -> -tr(x) - @test f1'(x) ≈ MPO(s, "I") - @test f2'(x) ≈ 2MPO(s, "I") - @test f3'(x) ≈ -MPO(s, "I") - end - - @testset "MPS ($ElType)" for ElType in (Float64, ComplexF64) - Random.seed!(1234) - n = 4 - ϵ = 1e-8 - s = siteinds("S=1/2", n; conserve_qns=true) - function heisenberg(n) - os = OpSum() - for j in 1:(n - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - return os + @testset "issue 936" begin + # https://github.com/ITensor/ITensors.jl/issues/936 + n = 2 + s = siteinds("S=1/2", n) + x = (x -> outer(x', x))(random_mps(s)) + f1 = x -> tr(x) + f2 = x -> 2tr(x) + f3 = x -> -tr(x) + @test f1'(x) ≈ MPO(s, "I") + @test f2'(x) ≈ 2MPO(s, "I") + @test f3'(x) ≈ -MPO(s, "I") end - H = MPO(heisenberg(n), s) - ψ = random_mps(s, n -> isodd(n) ? "Up" : "Dn"; linkdims=2) - - f = x -> inner(x, x) - args = (ψ,) - d_args = gradient(f, args...) - @test norm(d_args[1] - 2 * args[1]) ≈ 0 atol = 1e-13 - - f = x -> inner(x', H, x) - args = (ψ,) - d_args = gradient(f, args...) - @test norm(d_args[1]' - 2 * H * args[1]) ≈ 0 atol = 1e-13 - - f = x -> inner(x', x) - args = (ψ,) - @test_throws ErrorException gradient(f, args...) - - f = x -> inner(x, H, x) - args = (ψ,) - @test_throws ErrorException gradient(f, args...) - - # apply on MPS - s = siteinds("S=1/2", n) - ϕ = random_mps(ElType, s) - ψ = random_mps(ElType, s) - f = function (x) - U = [op("Ry", s[2]; θ=x), op("CX", s[1], s[2]), op("Rx", s[3]; θ=x)] - ψθ = apply(U, ψ) - return abs2(inner(ϕ, ψθ)) - end - θ = 0.5 - ∇f = f'(θ) - ∇num = (f(θ + ϵ) - f(θ)) / ϵ - @test ∇f ≈ ∇num atol = 1e-5 - end - - @testset "MPS rrules" begin - Random.seed!(1234) - s = siteinds("S=1/2", 4) - ψ = random_mps(s) - args = (ψ,) - f = x -> inner(x, x) - # TODO: Need to make MPS type compatible with FiniteDifferences. - #test_rrule(ZygoteRuleConfig(), f, args...; rrule_f=rrule_via_ad, check_inferred=false) - d_args = gradient(f, args...) - @test norm(d_args[1] - 2 * args[1]) ≈ 0 atol = 1e-13 - - args = (ψ,) - f = x -> inner(prime(x), prime(x)) - # TODO: Need to make MPS type compatible with FiniteDifferences. - #test_rrule(ZygoteRuleConfig(), f, args...; rrule_f=rrule_via_ad, check_inferred=false) - d_args = gradient(f, args...) - @test norm(d_args[1] - 2 * args[1]) ≈ 0 atol = 1e-13 - - ψ = random_mps(ComplexF64, s) - ψtensors = ITensorMPS.data(ψ) - ϕ = random_mps(ComplexF64, s) - f = function (x) - ψ̃tensors = [x^j * ψtensors[j] for j in 1:length(ψtensors)] - ψ̃ = MPS(ψ̃tensors) - return abs2(inner(ϕ, ψ̃)) - end - x = 0.5 - ϵ = 1e-10 - @test f'(x) ≈ (f(x + ϵ) - f(x)) / ϵ atol = 1e-6 - - ρ = random_mpo(s) - f = function (x) - ψ̃tensors = [x^j * ψtensors[j] for j in 1:length(ψtensors)] - ψ̃ = MPS(ψ̃tensors) - return real(inner(ψ̃', ρ, ψ̃)) - end - @test f'(x) ≈ (f(x + ϵ) - f(x)) / ϵ atol = 1e-6 - end - - #@testset "MPO rules" begin - # Random.seed!(1234) - # s = siteinds("S=1/2", 2) - # - # #ρ = random_mpo(s) - # #ρtensors = ITensors.data(ρ) - # #ϕ = random_mps(ComplexF64, s) - # #f = function (x) - # # ρ̃tensors = [2 * x * ρtensors[1], log(x) * ρtensors[2]] - # # ρ̃ = MPO(ρ̃tensors) - # # #@show typeof(ρ̃) - # # return real(inner(ϕ', ρ̃, ϕ)) - # #end - # #x = 3.0 - # #ϵ = 1e-8 - # #@show (f(x+ϵ) - f(x)) / ϵ - # #@show f'(x) - # ##@test f'(x) ≈ (f(x+ϵ) - f(x)) / ϵ atol = 1e-6 - # # - # - # #ϕ = random_mpo(s) - # #f = function (x) - # # ψ̃tensors = [2 * x * ψtensors[1], log(x) * ψtensors[2]] - # # ψ̃ = MPS(ψ̃tensors) - # # return abs2(inner(ϕ, ψ̃)) - # #end - # #x = 3.0 - # #ϵ = 1e-8 - # #@test f'(x) ≈ (f(x+ϵ) - f(x)) / ϵ atol = 1e-6 - # - # #ρ = random_mpo(s) - #end - @testset "MPO: apply" begin - Random.seed!(1234) - ϵ = 1e-8 - n = 3 - s = siteinds("Qubit", n) - function ising(n, h) - os = OpSum() - for j in 1:(n - 1) - os -= 1, "Z", j, "Z", j + 1 - os -= h, "X", j - end - os -= h, "X", n - return os - end - H = MPO(ising(n, 1.0), s) - A = random_mpo(s) - ϕ = random_mps(ComplexF64, s; linkdims=10) - - # apply on mpo with apply_dag=true - f = function (x) - U = [op("Ry", s[2]; θ=x), op("CX", s[1], s[2]), op("Rx", s[3]; θ=x)] - Hθ = apply(U, H; apply_dag=true) - return real(inner(ϕ', Hθ, ϕ)) - end - θ = 0.5 - ∇f = f'(θ) - ∇num = (f(θ + ϵ) - f(θ)) / ϵ - @test ∇f ≈ ∇num atol = 1e-5 - - # test that apply on non-Hermitian mpo with apply_dag=true - # throws an error. - f = function (x) - U = [op("Ry", s[2]; θ=x), op("CX", s[1], s[2]), op("Rx", s[3]; θ=x)] - Aθ = apply(U, A; apply_dag=true) - return real(inner(ϕ', Aθ, ϕ)) - end - θ = 0.5 - ∇f = f'(θ) - ∇num = (f(θ + ϵ) - f(θ)) / ϵ - @test ∇f ≈ ∇num atol = 1e-5 - - # apply on Hermitian MPO with apply_dag=false - f = function (x) - U = [op("Ry", s[2]; θ=x), op("CX", s[1], s[2]), op("Rx", s[3]; θ=x)] - Hθ = apply(U, H; apply_dag=false) - return real(inner(ϕ', Hθ, ϕ)) - end - θ = 0.5 - ∇f = f'(θ) - ∇num = (f(θ + ϵ) - f(θ)) / ϵ - @test ∇f ≈ ∇num atol = 1e-5 - - # apply on non-Hermitian MPO with apply_dag=false - f = function (x) - U = [op("Ry", s[2]; θ=x), op("CX", s[1], s[2]), op("Rx", s[3]; θ=x)] - Aθ = apply(U, A; apply_dag=false) - return real(inner(ϕ', Aθ, ϕ)) + + @testset "MPS ($ElType)" for ElType in (Float64, ComplexF64) + Random.seed!(1234) + n = 4 + ϵ = 1.0e-8 + s = siteinds("S=1/2", n; conserve_qns = true) + function heisenberg(n) + os = OpSum() + for j in 1:(n - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + return os + end + H = MPO(heisenberg(n), s) + ψ = random_mps(s, n -> isodd(n) ? "Up" : "Dn"; linkdims = 2) + + f = x -> inner(x, x) + args = (ψ,) + d_args = gradient(f, args...) + @test norm(d_args[1] - 2 * args[1]) ≈ 0 atol = 1.0e-13 + + f = x -> inner(x', H, x) + args = (ψ,) + d_args = gradient(f, args...) + @test norm(d_args[1]' - 2 * H * args[1]) ≈ 0 atol = 1.0e-13 + + f = x -> inner(x', x) + args = (ψ,) + @test_throws ErrorException gradient(f, args...) + + f = x -> inner(x, H, x) + args = (ψ,) + @test_throws ErrorException gradient(f, args...) + + # apply on MPS + s = siteinds("S=1/2", n) + ϕ = random_mps(ElType, s) + ψ = random_mps(ElType, s) + f = function (x) + U = [op("Ry", s[2]; θ = x), op("CX", s[1], s[2]), op("Rx", s[3]; θ = x)] + ψθ = apply(U, ψ) + return abs2(inner(ϕ, ψθ)) + end + θ = 0.5 + ∇f = f'(θ) + ∇num = (f(θ + ϵ) - f(θ)) / ϵ + @test ∇f ≈ ∇num atol = 1.0e-5 end - θ = 0.5 - ∇f = f'(θ) - ∇num = (f(θ + ϵ) - f(θ)) / ϵ - @test ∇f ≈ ∇num atol = 1e-5 - - # multiply two MPOs - V = random_mpo(s) - f = function (x) - U = [op("Ry", s[2]; θ=x), op("CX", s[1], s[2]), op("Rx", s[3]; θ=x)] - Hθ = apply(U, H; apply_dag=false) - X = replaceprime(V' * Hθ, 2 => 1) - return real(inner(ϕ', X, ϕ)) + + @testset "MPS rrules" begin + Random.seed!(1234) + s = siteinds("S=1/2", 4) + ψ = random_mps(s) + args = (ψ,) + f = x -> inner(x, x) + # TODO: Need to make MPS type compatible with FiniteDifferences. + #test_rrule(ZygoteRuleConfig(), f, args...; rrule_f=rrule_via_ad, check_inferred=false) + d_args = gradient(f, args...) + @test norm(d_args[1] - 2 * args[1]) ≈ 0 atol = 1.0e-13 + + args = (ψ,) + f = x -> inner(prime(x), prime(x)) + # TODO: Need to make MPS type compatible with FiniteDifferences. + #test_rrule(ZygoteRuleConfig(), f, args...; rrule_f=rrule_via_ad, check_inferred=false) + d_args = gradient(f, args...) + @test norm(d_args[1] - 2 * args[1]) ≈ 0 atol = 1.0e-13 + + ψ = random_mps(ComplexF64, s) + ψtensors = ITensorMPS.data(ψ) + ϕ = random_mps(ComplexF64, s) + f = function (x) + ψ̃tensors = [x^j * ψtensors[j] for j in 1:length(ψtensors)] + ψ̃ = MPS(ψ̃tensors) + return abs2(inner(ϕ, ψ̃)) + end + x = 0.5 + ϵ = 1.0e-10 + @test f'(x) ≈ (f(x + ϵ) - f(x)) / ϵ atol = 1.0e-6 + + ρ = random_mpo(s) + f = function (x) + ψ̃tensors = [x^j * ψtensors[j] for j in 1:length(ψtensors)] + ψ̃ = MPS(ψ̃tensors) + return real(inner(ψ̃', ρ, ψ̃)) + end + @test f'(x) ≈ (f(x + ϵ) - f(x)) / ϵ atol = 1.0e-6 end - θ = 0.5 - ∇f = f'(θ) - ∇num = (f(θ + ϵ) - f(θ)) / ϵ - @test ∇f ≈ ∇num atol = 1e-5 - - # trace(MPO) - V1 = random_mpo(s) - V2 = random_mpo(s) - f = function (x) - U = [op("Ry", s[2]; θ=x), op("CX", s[1], s[2]), op("Rx", s[3]; θ=x)] - Hθ = apply(U, H; apply_dag=false) - X = V1''' * Hθ'' * V2' * Hθ - return real(tr(X; plev=4 => 0)) + #@testset "MPO rules" begin + # Random.seed!(1234) + # s = siteinds("S=1/2", 2) + # + # #ρ = random_mpo(s) + # #ρtensors = ITensors.data(ρ) + # #ϕ = random_mps(ComplexF64, s) + # #f = function (x) + # # ρ̃tensors = [2 * x * ρtensors[1], log(x) * ρtensors[2]] + # # ρ̃ = MPO(ρ̃tensors) + # # #@show typeof(ρ̃) + # # return real(inner(ϕ', ρ̃, ϕ)) + # #end + # #x = 3.0 + # #ϵ = 1e-8 + # #@show (f(x+ϵ) - f(x)) / ϵ + # #@show f'(x) + # ##@test f'(x) ≈ (f(x+ϵ) - f(x)) / ϵ atol = 1e-6 + # # + # + # #ϕ = random_mpo(s) + # #f = function (x) + # # ψ̃tensors = [2 * x * ψtensors[1], log(x) * ψtensors[2]] + # # ψ̃ = MPS(ψ̃tensors) + # # return abs2(inner(ϕ, ψ̃)) + # #end + # #x = 3.0 + # #ϵ = 1e-8 + # #@test f'(x) ≈ (f(x+ϵ) - f(x)) / ϵ atol = 1e-6 + # + # #ρ = random_mpo(s) + #end + @testset "MPO: apply" begin + Random.seed!(1234) + ϵ = 1.0e-8 + n = 3 + s = siteinds("Qubit", n) + function ising(n, h) + os = OpSum() + for j in 1:(n - 1) + os -= 1, "Z", j, "Z", j + 1 + os -= h, "X", j + end + os -= h, "X", n + return os + end + H = MPO(ising(n, 1.0), s) + A = random_mpo(s) + ϕ = random_mps(ComplexF64, s; linkdims = 10) + + # apply on mpo with apply_dag=true + f = function (x) + U = [op("Ry", s[2]; θ = x), op("CX", s[1], s[2]), op("Rx", s[3]; θ = x)] + Hθ = apply(U, H; apply_dag = true) + return real(inner(ϕ', Hθ, ϕ)) + end + θ = 0.5 + ∇f = f'(θ) + ∇num = (f(θ + ϵ) - f(θ)) / ϵ + @test ∇f ≈ ∇num atol = 1.0e-5 + + # test that apply on non-Hermitian mpo with apply_dag=true + # throws an error. + f = function (x) + U = [op("Ry", s[2]; θ = x), op("CX", s[1], s[2]), op("Rx", s[3]; θ = x)] + Aθ = apply(U, A; apply_dag = true) + return real(inner(ϕ', Aθ, ϕ)) + end + θ = 0.5 + ∇f = f'(θ) + ∇num = (f(θ + ϵ) - f(θ)) / ϵ + @test ∇f ≈ ∇num atol = 1.0e-5 + + # apply on Hermitian MPO with apply_dag=false + f = function (x) + U = [op("Ry", s[2]; θ = x), op("CX", s[1], s[2]), op("Rx", s[3]; θ = x)] + Hθ = apply(U, H; apply_dag = false) + return real(inner(ϕ', Hθ, ϕ)) + end + θ = 0.5 + ∇f = f'(θ) + ∇num = (f(θ + ϵ) - f(θ)) / ϵ + @test ∇f ≈ ∇num atol = 1.0e-5 + + # apply on non-Hermitian MPO with apply_dag=false + f = function (x) + U = [op("Ry", s[2]; θ = x), op("CX", s[1], s[2]), op("Rx", s[3]; θ = x)] + Aθ = apply(U, A; apply_dag = false) + return real(inner(ϕ', Aθ, ϕ)) + end + θ = 0.5 + ∇f = f'(θ) + ∇num = (f(θ + ϵ) - f(θ)) / ϵ + @test ∇f ≈ ∇num atol = 1.0e-5 + + # multiply two MPOs + V = random_mpo(s) + f = function (x) + U = [op("Ry", s[2]; θ = x), op("CX", s[1], s[2]), op("Rx", s[3]; θ = x)] + Hθ = apply(U, H; apply_dag = false) + X = replaceprime(V' * Hθ, 2 => 1) + return real(inner(ϕ', X, ϕ)) + end + + θ = 0.5 + ∇f = f'(θ) + ∇num = (f(θ + ϵ) - f(θ)) / ϵ + @test ∇f ≈ ∇num atol = 1.0e-5 + + # trace(MPO) + V1 = random_mpo(s) + V2 = random_mpo(s) + f = function (x) + U = [op("Ry", s[2]; θ = x), op("CX", s[1], s[2]), op("Rx", s[3]; θ = x)] + Hθ = apply(U, H; apply_dag = false) + X = V1''' * Hθ'' * V2' * Hθ + return real(tr(X; plev = 4 => 0)) + end + + θ = 0.5 + ∇f = f'(θ) + ∇num = (f(θ + ϵ) - f(θ)) / ϵ + @test ∇f ≈ ∇num atol = 1.0e-5 + + # add(MPO, MPO) + f = function (x, y) + z = x + y + return inner(z, z) + end + V1 = random_mpo(s) + V2 = random_mpo(s) + g1, g2 = gradient(f, V1, V2) + @test g1 ≈ 2 * (V1 + V2) + @test g2 ≈ 2 * (V1 + V2) + + # subtract(MPO, MPO) + f = function (x, y) + z = x - y + return inner(z, z) + end + V1 = random_mpo(s) + V2 = random_mpo(s) + g1, g2 = gradient(f, V1, V2) + @test g1 ≈ 2 * (V1 - V2) + @test g2 ≈ -2 * (V1 - V2) end - θ = 0.5 - ∇f = f'(θ) - ∇num = (f(θ + ϵ) - f(θ)) / ϵ - @test ∇f ≈ ∇num atol = 1e-5 + @testset "contract/apply MPOs" begin + n = 2 + s = siteinds("S=1/2", n) + x = (x -> outer(x', x))(random_mps(s; linkdims = 4)) + x_itensor = contract(x) - # add(MPO, MPO) - f = function (x, y) - z = x + y - return inner(z, z) - end - V1 = random_mpo(s) - V2 = random_mpo(s) - g1, g2 = gradient(f, V1, V2) - @test g1 ≈ 2 * (V1 + V2) - @test g2 ≈ 2 * (V1 + V2) - - # subtract(MPO, MPO) - f = function (x, y) - z = x - y - return inner(z, z) - end - V1 = random_mpo(s) - V2 = random_mpo(s) - g1, g2 = gradient(f, V1, V2) - @test g1 ≈ 2 * (V1 - V2) - @test g2 ≈ -2 * (V1 - V2) - end - - @testset "contract/apply MPOs" begin - n = 2 - s = siteinds("S=1/2", n) - x = (x -> outer(x', x))(random_mps(s; linkdims=4)) - x_itensor = contract(x) - - f = x -> tr(apply(x, x)) - @test f(x) ≈ f(x_itensor) - @test contract(f'(x)) ≈ f'(x_itensor) - - f = x -> tr(replaceprime(contract(x', x), 2 => 1)) - @test f(x) ≈ f(x_itensor) - @test contract(f'(x)) ≈ f'(x_itensor) - - f = x -> tr(replaceprime(*(x', x), 2 => 1)) - @test f(x) ≈ f(x_itensor) - @test contract(f'(x)) ≈ f'(x_itensor) - end - - @testset "contract/apply MPOs on MPSs" begin - n = 2 - s = siteinds("S=1/2", n) - x = (x -> outer(x', x))(random_mps(s; linkdims=4)) - x_itensor = contract(x) - y = random_mps(s; linkdims=4) - y_itensor = contract(y) - - f = x -> inner(apply(x, y), apply(x, y)) - g = x -> inner(apply(x, y_itensor), apply(x, y_itensor)) - @test f(x) ≈ g(x_itensor) - @test contract(f'(x)) ≈ g'(x_itensor) - - f = y -> inner(apply(x, y), apply(x, y)) - g = y -> inner(apply(x_itensor, y), apply(x_itensor, y)) - @test f(y) ≈ g(y_itensor) - @test contract(f'(y)) ≈ g'(y_itensor) - - # These tests are broken in Julia v1.12. See: - # https://github.com/ITensor/ITensorMPS.jl/pull/161 - # https://github.com/JuliaLang/julia/issues/59138 - # https://github.com/FluxML/Zygote.jl/issues/1580 - if VERSION ≤ v"1.12-" - f = - x -> - inner(replaceprime(contract(x, y), 2 => 1), replaceprime(contract(x, y), 2 => 1)) - g = - x -> inner( - replaceprime(contract(x, y_itensor), 2 => 1), - replaceprime(contract(x, y_itensor), 2 => 1), - ) - @test f(x) ≈ g(x_itensor) - @test contract(f'(x)) ≈ g'(x_itensor) - - f = - y -> - inner(replaceprime(contract(x, y), 2 => 1), replaceprime(contract(x, y), 2 => 1)) - g = - y -> inner( - replaceprime(contract(x_itensor, y), 2 => 1), - replaceprime(contract(x_itensor, y), 2 => 1), - ) - @test f(y) ≈ g(y_itensor) - @test contract(f'(y)) ≈ g'(y_itensor) - - f = x -> inner(replaceprime(*(x, y), 2 => 1), replaceprime(*(x, y), 2 => 1)) - g = - x -> inner( - replaceprime(*(x, y_itensor), 2 => 1), replaceprime(*(x, y_itensor), 2 => 1) - ) - @test f(x) ≈ g(x_itensor) - @test contract(f'(x)) ≈ g'(x_itensor) - - f = y -> inner(replaceprime(*(x, y), 2 => 1), replaceprime(*(x, y), 2 => 1)) - g = - y -> inner( - replaceprime(*(x_itensor, y), 2 => 1), replaceprime(*(x_itensor, y), 2 => 1) - ) - @test f(y) ≈ g(y_itensor) - @test contract(f'(y)) ≈ g'(y_itensor) - end - end - @testset "Calling apply multiple times (ITensors #924 regression test)" begin - n = 1 - θ = 3.0 - p = 2 - - s = siteinds("S=1/2", n) - - ψ₀ₘₚₛ = MPS(s, "↑") - ψ₀ = contract(ψ₀ₘₚₛ) - - U(θ) = [θ * op("Z", s, 1)] - - function f(θ, ψ) - ψθ = ψ - Uθ = U(θ) - for _ in 1:p - ψθ = apply(Uθ, ψθ) - end - return inner(ψ, ψθ) - end + f = x -> tr(apply(x, x)) + @test f(x) ≈ f(x_itensor) + @test contract(f'(x)) ≈ f'(x_itensor) + + f = x -> tr(replaceprime(contract(x', x), 2 => 1)) + @test f(x) ≈ f(x_itensor) + @test contract(f'(x)) ≈ f'(x_itensor) - function g(θ, ψ) - Uθ = U(θ) - Utot = Uθ - for _ in 2:p - Utot = [Utot; Uθ] - end - ψθ = apply(Utot, ψ) - return inner(ψ, ψθ) + f = x -> tr(replaceprime(*(x', x), 2 => 1)) + @test f(x) ≈ f(x_itensor) + @test contract(f'(x)) ≈ f'(x_itensor) end - f_itensor(θ) = f(θ, ψ₀) - f_mps(θ) = f(θ, ψ₀ₘₚₛ) - g_itensor(θ) = g(θ, ψ₀) - g_mps(θ) = g(θ, ψ₀ₘₚₛ) - - @test f_itensor(θ) ≈ θ^p - @test f_mps(θ) ≈ θ^p - @test f_itensor'(θ) ≈ p * θ^(p - 1) - @test f_mps'(θ) ≈ p * θ^(p - 1) - @test g_itensor(θ) ≈ θ^p - @test g_mps(θ) ≈ θ^p - @test g_itensor'(θ) ≈ p * θ^(p - 1) - @test g_mps'(θ) ≈ p * θ^(p - 1) - end + @testset "contract/apply MPOs on MPSs" begin + n = 2 + s = siteinds("S=1/2", n) + x = (x -> outer(x', x))(random_mps(s; linkdims = 4)) + x_itensor = contract(x) + y = random_mps(s; linkdims = 4) + y_itensor = contract(y) + + f = x -> inner(apply(x, y), apply(x, y)) + g = x -> inner(apply(x, y_itensor), apply(x, y_itensor)) + @test f(x) ≈ g(x_itensor) + @test contract(f'(x)) ≈ g'(x_itensor) + + f = y -> inner(apply(x, y), apply(x, y)) + g = y -> inner(apply(x_itensor, y), apply(x_itensor, y)) + @test f(y) ≈ g(y_itensor) + @test contract(f'(y)) ≈ g'(y_itensor) + + # These tests are broken in Julia v1.12. See: + # https://github.com/ITensor/ITensorMPS.jl/pull/161 + # https://github.com/JuliaLang/julia/issues/59138 + # https://github.com/FluxML/Zygote.jl/issues/1580 + if VERSION ≤ v"1.12-" + f = + x -> + inner(replaceprime(contract(x, y), 2 => 1), replaceprime(contract(x, y), 2 => 1)) + g = + x -> inner( + replaceprime(contract(x, y_itensor), 2 => 1), + replaceprime(contract(x, y_itensor), 2 => 1), + ) + @test f(x) ≈ g(x_itensor) + @test contract(f'(x)) ≈ g'(x_itensor) + + f = + y -> + inner(replaceprime(contract(x, y), 2 => 1), replaceprime(contract(x, y), 2 => 1)) + g = + y -> inner( + replaceprime(contract(x_itensor, y), 2 => 1), + replaceprime(contract(x_itensor, y), 2 => 1), + ) + @test f(y) ≈ g(y_itensor) + @test contract(f'(y)) ≈ g'(y_itensor) + + f = x -> inner(replaceprime(*(x, y), 2 => 1), replaceprime(*(x, y), 2 => 1)) + g = + x -> inner( + replaceprime(*(x, y_itensor), 2 => 1), replaceprime(*(x, y_itensor), 2 => 1) + ) + @test f(x) ≈ g(x_itensor) + @test contract(f'(x)) ≈ g'(x_itensor) + + f = y -> inner(replaceprime(*(x, y), 2 => 1), replaceprime(*(x, y), 2 => 1)) + g = + y -> inner( + replaceprime(*(x_itensor, y), 2 => 1), replaceprime(*(x_itensor, y), 2 => 1) + ) + @test f(y) ≈ g(y_itensor) + @test contract(f'(y)) ≈ g'(y_itensor) + end + end + @testset "Calling apply multiple times (ITensors #924 regression test)" begin + n = 1 + θ = 3.0 + p = 2 + + s = siteinds("S=1/2", n) + + ψ₀ₘₚₛ = MPS(s, "↑") + ψ₀ = contract(ψ₀ₘₚₛ) + + U(θ) = [θ * op("Z", s, 1)] + + function f(θ, ψ) + ψθ = ψ + Uθ = U(θ) + for _ in 1:p + ψθ = apply(Uθ, ψθ) + end + return inner(ψ, ψθ) + end + + function g(θ, ψ) + Uθ = U(θ) + Utot = Uθ + for _ in 2:p + Utot = [Utot; Uθ] + end + ψθ = apply(Utot, ψ) + return inner(ψ, ψθ) + end + + f_itensor(θ) = f(θ, ψ₀) + f_mps(θ) = f(θ, ψ₀ₘₚₛ) + g_itensor(θ) = g(θ, ψ₀) + g_mps(θ) = g(θ, ψ₀ₘₚₛ) + + @test f_itensor(θ) ≈ θ^p + @test f_mps(θ) ≈ θ^p + @test f_itensor'(θ) ≈ p * θ^(p - 1) + @test f_mps'(θ) ≈ p * θ^(p - 1) + @test g_itensor(θ) ≈ θ^p + @test g_mps(θ) ≈ θ^p + @test g_itensor'(θ) ≈ p * θ^(p - 1) + @test g_mps'(θ) ≈ p * θ^(p - 1) + end end diff --git a/test/ext/ITensorMPSChainRulesCoreExt/test_optimization.jl b/test/ext/ITensorMPSChainRulesCoreExt/test_optimization.jl index 46ef4c8..8e116f8 100644 --- a/test/ext/ITensorMPSChainRulesCoreExt/test_optimization.jl +++ b/test/ext/ITensorMPSChainRulesCoreExt/test_optimization.jl @@ -7,255 +7,255 @@ using Zygote include(joinpath(@__DIR__, "utils", "circuit.jl")) @testset "optimization" begin - @testset "Energy minimization" begin - N = 3 - s = siteinds("S=1/2", N; conserve_qns=true) - os = OpSum() - for n in 1:(N - 1) - os .+= 0.5, "S+", n, "S-", n + 1 - os .+= 0.5, "S-", n, "S+", n + 1 - os .+= "Sz", n, "Sz", n + 1 - end - Hmpo = MPO(os, s) - ψ₀mps = random_mps(s, n -> isodd(n) ? "↑" : "↓") - H = prod(Hmpo) - ψ₀ = prod(ψ₀mps) - # The Rayleigh quotient to minimize - function E(H::ITensor, ψ::ITensor) - ψdag = dag(ψ) - return (ψdag' * H * ψ)[] / (ψdag * ψ)[] - end - E(ψ::ITensor) = E(H, ψ) - ∇E(ψ::ITensor) = E'(ψ) - fg(ψ::ITensor) = (E(ψ), ∇E(ψ)) - linesearch = HagerZhangLineSearch(; - c₁=0.1, c₂=0.9, ϵ=1e-6, θ=1 / 2, γ=2 / 3, ρ=5.0, verbosity=0 - ) - algorithm = LBFGS(3; maxiter=20, gradtol=1e-8, linesearch=linesearch) - ψ, fψ, gψ, numfg, normgradhistory = optimize(fg, ψ₀, algorithm) - D, _ = eigen(H; ishermitian=true) - @test E(H, ψ) < E(H, ψ₀) - @test E(H, ψ) ≈ minimum(D) - end - - @testset "Energy minimization (MPS)" begin - N = 4 - χ = 4 - s = siteinds("S=1/2", N; conserve_qns=true) - os = OpSum() - for n in 1:(N - 1) - os .+= 0.5, "S+", n, "S-", n + 1 - os .+= 0.5, "S-", n, "S+", n + 1 - os .+= "Sz", n, "Sz", n + 1 - end - Hmpo = MPO(os, s) - - Random.seed!(1234) - ψ₀mps = random_mps(s, n -> isodd(n) ? "↑" : "↓"; linkdims=χ) - - H = ITensorMPS.data(Hmpo) - ψ₀ = ITensorMPS.data(ψ₀mps) - # The Rayleigh quotient to minimize - function E(H::Vector{ITensor}, ψ::Vector{ITensor}) - N = length(ψ) - ψdag = dag.(addtags.(ψ, "bra"; tags="Link")) - ψ′dag = prime.(ψdag) - e = ITensor(1.0) - for n in 1:N - e = e * ψ′dag[n] * H[n] * ψ[n] - end - norm = ITensor(1.0) - for n in 1:N - norm = norm * ψdag[n] * ψ[n] - end - return e[] / norm[] - end - E(ψ) = E(H, ψ) - ∇E(ψ) = E'(ψ) - fg(ψ) = (E(ψ), ∇E(ψ)) - linesearch = HagerZhangLineSearch(; c₁=0.1, c₂=0.9, ϵ=1e-6, θ=1 / 2, γ=2 / 3, ρ=5.0) - algorithm = LBFGS(5; maxiter=50, gradtol=1e-4, linesearch=linesearch, verbosity=0) - ψ, fψ, gψ, numfg, normgradhistory = optimize(fg, ψ₀, algorithm) - sweeps = Sweeps(5) - setmaxdim!(sweeps, χ) - fψmps, ψmps = dmrg(Hmpo, ψ₀mps, sweeps; outputlevel=0) - @test E(H, ψ) ≈ inner(ψmps', Hmpo, ψmps) / inner(ψmps, ψmps) rtol = 1e-2 - end - - @testset "State preparation (full state)" begin - function Rylayer(N, θ⃗) - return [("Ry", (n,), (θ=θ⃗[n],)) for n in 1:N] - end - - function CXlayer(N) - return [("CX", (n, n + 1)) for n in 1:2:(N - 1)] - end - - # The variational circuit we want to optimize - function variational_circuit(θ⃗) - N = length(θ⃗) - return vcat(Rylayer(N, θ⃗), CXlayer(N)) + @testset "Energy minimization" begin + N = 3 + s = siteinds("S=1/2", N; conserve_qns = true) + os = OpSum() + for n in 1:(N - 1) + os .+= 0.5, "S+", n, "S-", n + 1 + os .+= 0.5, "S-", n, "S+", n + 1 + os .+= "Sz", n, "Sz", n + 1 + end + Hmpo = MPO(os, s) + ψ₀mps = random_mps(s, n -> isodd(n) ? "↑" : "↓") + H = prod(Hmpo) + ψ₀ = prod(ψ₀mps) + # The Rayleigh quotient to minimize + function E(H::ITensor, ψ::ITensor) + ψdag = dag(ψ) + return (ψdag' * H * ψ)[] / (ψdag * ψ)[] + end + E(ψ::ITensor) = E(H, ψ) + ∇E(ψ::ITensor) = E'(ψ) + fg(ψ::ITensor) = (E(ψ), ∇E(ψ)) + linesearch = HagerZhangLineSearch(; + c₁ = 0.1, c₂ = 0.9, ϵ = 1.0e-6, θ = 1 / 2, γ = 2 / 3, ρ = 5.0, verbosity = 0 + ) + algorithm = LBFGS(3; maxiter = 20, gradtol = 1.0e-8, linesearch = linesearch) + ψ, fψ, gψ, numfg, normgradhistory = optimize(fg, ψ₀, algorithm) + D, _ = eigen(H; ishermitian = true) + @test E(H, ψ) < E(H, ψ₀) + @test E(H, ψ) ≈ minimum(D) end - N = 4 - Random.seed!(1234) - θ⃗ = 2π .* rand(N) - gates = variational_circuit(θ⃗) - - s = siteinds("Qubit", N) - ψₘₚₛ = MPS(s, "0") - ψ = prod(ψₘₚₛ) - U = buildcircuit(gates, s) - # Create the target state - Uψ = apply(U, ψ) - - @test inner_circuit(Uψ, U, ψ) ≈ 1 - - function loss(θ⃗) - gates = variational_circuit(θ⃗) - U = buildcircuit(gates, s) - return -abs(inner_circuit(Uψ, U, ψ))^2 + @testset "Energy minimization (MPS)" begin + N = 4 + χ = 4 + s = siteinds("S=1/2", N; conserve_qns = true) + os = OpSum() + for n in 1:(N - 1) + os .+= 0.5, "S+", n, "S-", n + 1 + os .+= 0.5, "S-", n, "S+", n + 1 + os .+= "Sz", n, "Sz", n + 1 + end + Hmpo = MPO(os, s) + + Random.seed!(1234) + ψ₀mps = random_mps(s, n -> isodd(n) ? "↑" : "↓"; linkdims = χ) + + H = ITensorMPS.data(Hmpo) + ψ₀ = ITensorMPS.data(ψ₀mps) + # The Rayleigh quotient to minimize + function E(H::Vector{ITensor}, ψ::Vector{ITensor}) + N = length(ψ) + ψdag = dag.(addtags.(ψ, "bra"; tags = "Link")) + ψ′dag = prime.(ψdag) + e = ITensor(1.0) + for n in 1:N + e = e * ψ′dag[n] * H[n] * ψ[n] + end + norm = ITensor(1.0) + for n in 1:N + norm = norm * ψdag[n] * ψ[n] + end + return e[] / norm[] + end + E(ψ) = E(H, ψ) + ∇E(ψ) = E'(ψ) + fg(ψ) = (E(ψ), ∇E(ψ)) + linesearch = HagerZhangLineSearch(; c₁ = 0.1, c₂ = 0.9, ϵ = 1.0e-6, θ = 1 / 2, γ = 2 / 3, ρ = 5.0) + algorithm = LBFGS(5; maxiter = 50, gradtol = 1.0e-4, linesearch = linesearch, verbosity = 0) + ψ, fψ, gψ, numfg, normgradhistory = optimize(fg, ψ₀, algorithm) + sweeps = Sweeps(5) + setmaxdim!(sweeps, χ) + fψmps, ψmps = dmrg(Hmpo, ψ₀mps, sweeps; outputlevel = 0) + @test E(H, ψ) ≈ inner(ψmps', Hmpo, ψmps) / inner(ψmps, ψmps) rtol = 1.0e-2 end - θ⃗₀ = randn!(copy(θ⃗)) - fg(x) = (loss(x), convert(Vector, loss'(x))) - θ⃗ₒₚₜ, fₒₚₜ, gₒₚₜ, numfg, normgradhistory = optimize(fg, θ⃗₀, GradientDescent()) - @test loss(θ⃗ₒₚₜ) ≈ loss(θ⃗) rtol = 1e-2 - end - - @testset "State preparation (MPS)" begin - for gate in ["Ry"] - nsites = 4 # Number of sites - nlayers = 2 # Layers of gates in the ansatz - gradtol = 1e-3 # Tolerance for stopping gradient descent - - # A layer of the circuit we want to optimize - function layer(nsites, θ⃗) - gate_layer = [(gate, (n,), (θ=θ⃗[n],)) for n in 1:nsites] - CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)] - return [gate_layer; CX_layer] - end - - # The variational circuit we want to optimize - function variational_circuit(nsites, nlayers, θ⃗) - range = 1:nsites - circuit = layer(nsites, θ⃗[range]) - for n in 1:(nlayers - 1) - circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])] + @testset "State preparation (full state)" begin + function Rylayer(N, θ⃗) + return [("Ry", (n,), (θ = θ⃗[n],)) for n in 1:N] end - return circuit - end - - Random.seed!(1234) - θ⃗ᵗᵃʳᵍᵉᵗ = 2π * rand(nsites * nlayers) - 𝒰ᵗᵃʳᵍᵉᵗ = variational_circuit(nsites, nlayers, θ⃗ᵗᵃʳᵍᵉᵗ) - - s = siteinds("Qubit", nsites) - Uᵗᵃʳᵍᵉᵗ = ops(𝒰ᵗᵃʳᵍᵉᵗ, s) - - ψ0 = MPS(s, "0") + function CXlayer(N) + return [("CX", (n, n + 1)) for n in 1:2:(N - 1)] + end - # Create the random target state - ψᵗᵃʳᵍᵉᵗ = apply(Uᵗᵃʳᵍᵉᵗ, ψ0; cutoff=1e-8) + # The variational circuit we want to optimize + function variational_circuit(θ⃗) + N = length(θ⃗) + return vcat(Rylayer(N, θ⃗), CXlayer(N)) + end - # - # The loss function, a function of the gate parameters - # and implicitly depending on the target state: - # - # loss(θ⃗) = -|⟨θ⃗ᵗᵃʳᵍᵉᵗ|U(θ⃗)|0⟩|² = -|⟨θ⃗ᵗᵃʳᵍᵉᵗ|θ⃗⟩|² - # - function loss(θ⃗) - nsites = length(ψ0) - s = siteinds(ψ0) - 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) - Uθ⃗ = ops(𝒰θ⃗, s) - ψθ⃗ = apply(Uθ⃗, ψ0; cutoff=1e-8) - return -abs(inner(ψᵗᵃʳᵍᵉᵗ, ψθ⃗))^2 - end + N = 4 + Random.seed!(1234) + θ⃗ = 2π .* rand(N) + gates = variational_circuit(θ⃗) - θ⃗₀ = randn!(copy(θ⃗ᵗᵃʳᵍᵉᵗ)) + s = siteinds("Qubit", N) + ψₘₚₛ = MPS(s, "0") + ψ = prod(ψₘₚₛ) + U = buildcircuit(gates, s) + # Create the target state + Uψ = apply(U, ψ) - @test loss(θ⃗₀) ≉ loss(θ⃗ᵗᵃʳᵍᵉᵗ) + @test inner_circuit(Uψ, U, ψ) ≈ 1 - loss_∇loss(x) = (loss(x), convert(Vector, loss'(x))) - @show gate - algorithm = LBFGS(; gradtol=gradtol, verbosity=2) - θ⃗ₒₚₜ, lossₒₚₜ, ∇lossₒₚₜ, numfg, normgradhistory = optimize( - loss_∇loss, θ⃗₀, algorithm - ) + function loss(θ⃗) + gates = variational_circuit(θ⃗) + U = buildcircuit(gates, s) + return -abs(inner_circuit(Uψ, U, ψ))^2 + end - @test loss(θ⃗ₒₚₜ) ≈ loss(θ⃗ᵗᵃʳᵍᵉᵗ) rtol = 1e-5 - end - end - - @testset "VQE (MPS)" begin - nsites = 4 # Number of sites - nlayers = 2 # Layers of gates in the ansatz - gradtol = 1e-3 # Tolerance for stopping gradient descent - - # The Hamiltonian we are minimizing - function ising_hamiltonian(nsites; h) - ℋ = OpSum() - for j in 1:(nsites - 1) - ℋ -= 1, "Z", j, "Z", j + 1 - end - for j in 1:nsites - ℋ += h, "X", j - end - return ℋ + θ⃗₀ = randn!(copy(θ⃗)) + fg(x) = (loss(x), convert(Vector, loss'(x))) + θ⃗ₒₚₜ, fₒₚₜ, gₒₚₜ, numfg, normgradhistory = optimize(fg, θ⃗₀, GradientDescent()) + @test loss(θ⃗ₒₚₜ) ≈ loss(θ⃗) rtol = 1.0e-2 end - # A layer of the circuit we want to optimize - function layer(nsites, θ⃗) - RY_layer = [("Ry", (n,), (θ=θ⃗[n],)) for n in 1:nsites] - CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)] - return [RY_layer; CX_layer] + @testset "State preparation (MPS)" begin + for gate in ["Ry"] + nsites = 4 # Number of sites + nlayers = 2 # Layers of gates in the ansatz + gradtol = 1.0e-3 # Tolerance for stopping gradient descent + + # A layer of the circuit we want to optimize + function layer(nsites, θ⃗) + gate_layer = [(gate, (n,), (θ = θ⃗[n],)) for n in 1:nsites] + CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)] + return [gate_layer; CX_layer] + end + + # The variational circuit we want to optimize + function variational_circuit(nsites, nlayers, θ⃗) + range = 1:nsites + circuit = layer(nsites, θ⃗[range]) + for n in 1:(nlayers - 1) + circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])] + end + return circuit + end + + Random.seed!(1234) + + θ⃗ᵗᵃʳᵍᵉᵗ = 2π * rand(nsites * nlayers) + 𝒰ᵗᵃʳᵍᵉᵗ = variational_circuit(nsites, nlayers, θ⃗ᵗᵃʳᵍᵉᵗ) + + s = siteinds("Qubit", nsites) + Uᵗᵃʳᵍᵉᵗ = ops(𝒰ᵗᵃʳᵍᵉᵗ, s) + + ψ0 = MPS(s, "0") + + # Create the random target state + ψᵗᵃʳᵍᵉᵗ = apply(Uᵗᵃʳᵍᵉᵗ, ψ0; cutoff = 1.0e-8) + + # + # The loss function, a function of the gate parameters + # and implicitly depending on the target state: + # + # loss(θ⃗) = -|⟨θ⃗ᵗᵃʳᵍᵉᵗ|U(θ⃗)|0⟩|² = -|⟨θ⃗ᵗᵃʳᵍᵉᵗ|θ⃗⟩|² + # + function loss(θ⃗) + nsites = length(ψ0) + s = siteinds(ψ0) + 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) + Uθ⃗ = ops(𝒰θ⃗, s) + ψθ⃗ = apply(Uθ⃗, ψ0; cutoff = 1.0e-8) + return -abs(inner(ψᵗᵃʳᵍᵉᵗ, ψθ⃗))^2 + end + + θ⃗₀ = randn!(copy(θ⃗ᵗᵃʳᵍᵉᵗ)) + + @test loss(θ⃗₀) ≉ loss(θ⃗ᵗᵃʳᵍᵉᵗ) + + loss_∇loss(x) = (loss(x), convert(Vector, loss'(x))) + @show gate + algorithm = LBFGS(; gradtol = gradtol, verbosity = 2) + θ⃗ₒₚₜ, lossₒₚₜ, ∇lossₒₚₜ, numfg, normgradhistory = optimize( + loss_∇loss, θ⃗₀, algorithm + ) + + @test loss(θ⃗ₒₚₜ) ≈ loss(θ⃗ᵗᵃʳᵍᵉᵗ) rtol = 1.0e-5 + end end - # The variational circuit we want to optimize - function variational_circuit(nsites, nlayers, θ⃗) - range = 1:nsites - circuit = layer(nsites, θ⃗[range]) - for n in 1:(nlayers - 1) - circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])] - end - return circuit - end + @testset "VQE (MPS)" begin + nsites = 4 # Number of sites + nlayers = 2 # Layers of gates in the ansatz + gradtol = 1.0e-3 # Tolerance for stopping gradient descent + + # The Hamiltonian we are minimizing + function ising_hamiltonian(nsites; h) + ℋ = OpSum() + for j in 1:(nsites - 1) + ℋ -= 1, "Z", j, "Z", j + 1 + end + for j in 1:nsites + ℋ += h, "X", j + end + return ℋ + end - s = siteinds("Qubit", nsites) - - h = 1.3 - ℋ = ising_hamiltonian(nsites; h=h) - H = MPO(ℋ, s) - ψ0 = MPS(s, "0") - - # - # The loss function, a function of the gate parameters - # and implicitly depending on the Hamiltonian and state: - # - # loss(θ⃗) = ⟨0|U(θ⃗)† H U(θ⃗)|0⟩ = ⟨θ⃗|H|θ⃗⟩ - # - function loss(θ⃗) - nsites = length(ψ0) - s = siteinds(ψ0) - 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) - Uθ⃗ = ops(𝒰θ⃗, s) - ψθ⃗ = apply(Uθ⃗, ψ0; cutoff=1e-8) - return inner(ψθ⃗', H, ψθ⃗; cutoff=1e-8) - end + # A layer of the circuit we want to optimize + function layer(nsites, θ⃗) + RY_layer = [("Ry", (n,), (θ = θ⃗[n],)) for n in 1:nsites] + CX_layer = [("CX", (n, n + 1)) for n in 1:2:(nsites - 1)] + return [RY_layer; CX_layer] + end + + # The variational circuit we want to optimize + function variational_circuit(nsites, nlayers, θ⃗) + range = 1:nsites + circuit = layer(nsites, θ⃗[range]) + for n in 1:(nlayers - 1) + circuit = [circuit; layer(nsites, θ⃗[range .+ n * nsites])] + end + return circuit + end + + s = siteinds("Qubit", nsites) + + h = 1.3 + ℋ = ising_hamiltonian(nsites; h = h) + H = MPO(ℋ, s) + ψ0 = MPS(s, "0") + + # + # The loss function, a function of the gate parameters + # and implicitly depending on the Hamiltonian and state: + # + # loss(θ⃗) = ⟨0|U(θ⃗)† H U(θ⃗)|0⟩ = ⟨θ⃗|H|θ⃗⟩ + # + function loss(θ⃗) + nsites = length(ψ0) + s = siteinds(ψ0) + 𝒰θ⃗ = variational_circuit(nsites, nlayers, θ⃗) + Uθ⃗ = ops(𝒰θ⃗, s) + ψθ⃗ = apply(Uθ⃗, ψ0; cutoff = 1.0e-8) + return inner(ψθ⃗', H, ψθ⃗; cutoff = 1.0e-8) + end - Random.seed!(1234) - θ⃗₀ = 2π * rand(nsites * nlayers) + Random.seed!(1234) + θ⃗₀ = 2π * rand(nsites * nlayers) - loss_∇loss(x) = (loss(x), convert(Vector, loss'(x))) - algorithm = LBFGS(; gradtol=gradtol, verbosity=0) - θ⃗ₒₚₜ, lossₒₚₜ, ∇lossₒₚₜ, numfg, normgradhistory = optimize(loss_∇loss, θ⃗₀, algorithm) + loss_∇loss(x) = (loss(x), convert(Vector, loss'(x))) + algorithm = LBFGS(; gradtol = gradtol, verbosity = 0) + θ⃗ₒₚₜ, lossₒₚₜ, ∇lossₒₚₜ, numfg, normgradhistory = optimize(loss_∇loss, θ⃗₀, algorithm) - sweeps = Sweeps(5) - setmaxdim!(sweeps, 10) - e_dmrg, ψ_dmrg = dmrg(H, ψ0, sweeps; outputlevel=0) + sweeps = Sweeps(5) + setmaxdim!(sweeps, 10) + e_dmrg, ψ_dmrg = dmrg(H, ψ0, sweeps; outputlevel = 0) - @test loss(θ⃗ₒₚₜ) ≈ e_dmrg rtol = 1e-1 - end + @test loss(θ⃗ₒₚₜ) ≈ e_dmrg rtol = 1.0e-1 + end end diff --git a/test/ext/ITensorMPSChainRulesCoreExt/utils/circuit.jl b/test/ext/ITensorMPSChainRulesCoreExt/utils/circuit.jl index 74b4eef..ba3485c 100644 --- a/test/ext/ITensorMPSChainRulesCoreExt/utils/circuit.jl +++ b/test/ext/ITensorMPSChainRulesCoreExt/utils/circuit.jl @@ -3,67 +3,67 @@ using ChainRulesCore: ZeroTangent # Write a custom `rrule` for this. function inner_circuit(ϕ::ITensor, U::Vector{ITensor}, ψ::ITensor) - Uψ = ψ - for u in U - s = commoninds(u, Uψ) - s′ = s' - Uψ = replaceinds(u * Uψ, s′ => s) - end - return (ϕ * Uψ)[] + Uψ = ψ + for u in U + s = commoninds(u, Uψ) + s′ = s' + Uψ = replaceinds(u * Uψ, s′ => s) + end + return (ϕ * Uψ)[] end -name(g::Tuple{String,Vararg}) = g[1] -gate_sites(g::Tuple{<:Any,Tuple{Vararg{Int}},Vararg}) = g[2] -params(g::Tuple{<:Any,<:Any,<:NamedTuple}) = g[3] -params(g::Tuple{<:Any,<:Any}) = NamedTuple() +name(g::Tuple{String, Vararg}) = g[1] +gate_sites(g::Tuple{<:Any, Tuple{Vararg{Int}}, Vararg}) = g[2] +params(g::Tuple{<:Any, <:Any, <:NamedTuple}) = g[3] +params(g::Tuple{<:Any, <:Any}) = NamedTuple() function gate(g::Tuple, s::Vector{<:Index}) - return gate(name(g), params(g), s[collect(gate_sites(g))]) + return gate(name(g), params(g), s[collect(gate_sites(g))]) end function gate(gn::String, params::NamedTuple, s::Vector{<:Index}) - return gate(gate(gn, params), s) + return gate(gate(gn, params), s) end function gate(gn, params::NamedTuple) - return gate(gn; params...) + return gate(gn; params...) end function gate(gn::String, params::NamedTuple) - return gate(Val{Symbol(gn)}(), params) + return gate(Val{Symbol(gn)}(), params) end function gate(g::Matrix, s::Vector{<:Index}) - s = reverse(s) - return itensor(g, s'..., dag(s)...) + s = reverse(s) + return itensor(g, s'..., dag(s)...) end function buildcircuit(gates::Vector, s::Vector{<:Index}) - return [gate(g, s) for g in gates] + return [gate(g, s) for g in gates] end # Gate definitions function gate(gn::Val; params...) - return error("Gate $gn not defined") + return error("Gate $gn not defined") end function gate(::Val{:Ry}; θ) - return [ - cos(θ / 2) -sin(θ / 2) - sin(θ / 2) cos(θ / 2) - ] + return [ + cos(θ / 2) -sin(θ / 2) + sin(θ / 2) cos(θ / 2) + ] end function gate(::Val{:CX}) - return [ - 1 0 0 0 - 0 1 0 0 - 0 0 0 1 - 0 0 1 0 - ] + return [ + 1 0 0 0 + 0 1 0 0 + 0 0 0 1 + 0 0 1 0 + ] end # XXX: For some reason Zygote needs these definitions? Base.reverse(z::ZeroTangent) = z Base.adjoint(::Tuple{Nothing}) = nothing -Base.adjoint(::Tuple{Nothing,Nothing}) = nothing +Base.adjoint(::Tuple{Nothing, Nothing}) = nothing diff --git a/test/ext/ITensorMPSPackageCompilerExt/runtests.jl b/test/ext/ITensorMPSPackageCompilerExt/runtests.jl index cd167f7..12ff98f 100644 --- a/test/ext/ITensorMPSPackageCompilerExt/runtests.jl +++ b/test/ext/ITensorMPSPackageCompilerExt/runtests.jl @@ -4,8 +4,8 @@ using ITensors: ITensors using PackageCompiler: PackageCompiler using Test: @testset, @test @testset "ITensorMPSPackageCompilerExt" begin - # Testing `ITensors.compile` would take too long so we just check - # that `ITensorsPackageCompilerExt` overloads `ITensors.compile`. - @test hasmethod(ITensors.compile, Tuple{ITensors.Algorithm"PackageCompiler"}) + # Testing `ITensors.compile` would take too long so we just check + # that `ITensorsPackageCompilerExt` overloads `ITensors.compile`. + @test hasmethod(ITensors.compile, Tuple{ITensors.Algorithm"PackageCompiler"}) end end diff --git a/test/runtests.jl b/test/runtests.jl index 1c7c6bd..aebfeec 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,8 +6,8 @@ ITensors.BLAS.set_num_threads(1) ITensors.disable_threaded_blocksparse() @testset "$(@__DIR__)" begin - dirs = ["ext/ITensorMPSChainRulesCoreExt", "Ops", "base"] - for dir in dirs - @time include(joinpath(@__DIR__, dir, "runtests.jl")) - end + dirs = ["ext/ITensorMPSChainRulesCoreExt", "Ops", "base"] + for dir in dirs + @time include(joinpath(@__DIR__, dir, "runtests.jl")) + end end diff --git a/test/test_aqua.jl b/test/test_aqua.jl index 649c5b9..bcd84a3 100644 --- a/test/test_aqua.jl +++ b/test/test_aqua.jl @@ -3,5 +3,5 @@ using Aqua: Aqua using Test: @testset @testset "Code quality (Aqua.jl)" begin - Aqua.test_all(ITensorMPS) + Aqua.test_all(ITensorMPS) end