diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..cc0720870 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,49 @@ +--- +name: Bug report +about: Something isn't working as expected +title: "" +labels: bug +--- + +## Description + +A clear and concise description of what went wrong, and what you expected to happen instead. + +## Minimal working example (MWE) + +Please include the smallest possible piece of code that reproduces the issue. +Trim away everything that is not needed to trigger the bug (unrelated setup, unused imports, etc). + +```julia +using MPSKit, TensorKit + +# ... +``` + +## Error / output + +Paste the full error message and stacktrace (or the incorrect output), if any. + +``` +paste here +``` + +## Version info + +Please include the versions of MPSKit and its main dependencies. +From the Julia REPL: + +```julia-repl +julia> using Pkg +julia> Pkg.status(["MPSKit", "TensorKit"]) +``` + +Also include the Julia version: + +```julia-repl +julia> versioninfo() +``` + +## Additional context + +Anything else that might help (OS, whether it also happens on the latest `main`, related issues, etc). diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 000000000..c7215fe11 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,27 @@ +--- +name: Question +about: Ask a question about using MPSKit +title: "" +labels: question +--- + +## What are you trying to do? + +Describe the physics or the workflow you are trying to set up. + +## What have you tried? + +If applicable, include the code you have so far, and what result you got (or expected but didn't get). + +```julia +using MPSKit, TensorKit + +# ... +``` + +## Version info (optional but helpful) + +```julia-repl +julia> using Pkg +julia> Pkg.status(["MPSKit", "TensorKit"]) +``` diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..9b4d4812d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +## Description + +Briefly describe what this PR changes and why. + +## Checklist + +- [ ] Tests pass locally (`julia --project=test test/runtests.jl`, or the relevant subset) +- [ ] Documentation updated, if this PR changes public API (docstrings, `docs/src/`) +- [ ] Changelog entry added under `[Unreleased]` in `CHANGELOG.md` diff --git a/docs/src/changelog.md b/CHANGELOG.md similarity index 100% rename from docs/src/changelog.md rename to CHANGELOG.md diff --git a/CITATION.cff b/CITATION.cff index 8592b6a9a..62c16459b 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -12,7 +12,7 @@ authors: orcid: "https://orcid.org/0000-0002-0858-291X" title: "MPSKit" -version: 0.13.11 +version: 0.13.13 doi: 10.5281/zenodo.10654900 -date-released: 2026-05-04 +date-released: 2026-06-09 url: "https://github.com/QuantumKitHub/MPSKit.jl" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..0db85c7b6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,90 @@ +# Contributing to MPSKit.jl + +Thanks for taking the time to contribute! +This document is a short, practical guide to reporting issues, asking questions, and submitting changes. + +## Asking questions and reporting bugs + +Use [GitHub issues](https://github.com/QuantumKitHub/MPSKit.jl/issues) for both. +There are two templates to help you: + +- **Bug report** — for something that doesn't work as expected. +- **Question** — for anything else, from "how do I model X" to "is Y possible". + +A good bug report is one we can act on immediately, and it needs two things: + +1. A minimal working example (MWE): the smallest snippet of code that reproduces the problem. + Strip away everything not needed to trigger the bug — unrelated setup, unused imports, alternative approaches you also tried. +2. Version information for MPSKit, TensorKit, and Julia, obtained with: + + ```julia-repl + julia> using Pkg + julia> Pkg.status(["MPSKit", "TensorKit"]) + ``` + + ```julia-repl + julia> versioninfo() + ``` + +Please also include the full error message and stacktrace, if there is one. + +## Contributing code + +### Development setup + +1. Fork and clone the repository. +2. From the Julia REPL, develop the package against your local checkout: + + ```julia-repl + pkg> dev /path/to/your/clone/MPSKit.jl + ``` + + or, from a fresh environment: + + ```julia-repl + julia> using Pkg; Pkg.develop(path = "/path/to/your/clone/MPSKit.jl") + ``` + +### Running the tests + +The repository root is a Julia workspace (`Project.toml` lists `test`, `docs`, and `examples` as workspace projects), and `test/Project.toml` already points `MPSKit` back at the repo checkout. +From the repository root, run the full suite with: + +``` +julia --project=test test/runtests.jl +``` + +The test suite is organized by topic under `test/` (`algorithms/`, `states/`, `operators/`, `misc/`, `gpu/`), with shared setup code in `test/setup/`. +GPU tests only run when a functional CUDA/cuTENSOR install is detected, so most contributors will only ever exercise the CPU tests. +A `--fast` flag is available for a quicker, reduced run while iterating. + +### Building the documentation + +The docs use Documenter.jl with the DocumenterVitepress backend, so a full render needs Node.js in addition to Julia. + +For routine verification of code examples (fast, no Node required), run the doctests directly: + +``` +julia --project=docs -e 'using Documenter, MPSKit; doctest(MPSKit)' +``` + +For a full local site build (slower, needs `npm`): + +``` +julia --project=docs docs/make.jl +``` + +### Code formatting + +This repository is formatted with [Runic](https://github.com/fredrikekre/Runic.jl). +Formatting is checked automatically on pull requests, and there is a [pre-commit](https://pre-commit.com/) hook (`.pre-commit-config.yaml`) that runs Runic locally if you use pre-commit. +Please format any Julia files you touch before opening a PR. + +### Changelog + +If your change is user-facing (new feature, behavior change, bug fix, deprecation, or removal), add an entry under the `[Unreleased]` section of `CHANGELOG.md`, in the category that matches your change. + +### Opening a pull request + +Small, focused pull requests are easiest to review. +Please describe what the change does and why, link any related issues, and make sure the checklist in the PR template is filled in before requesting review. diff --git a/README.md b/README.md index 215b4145a..437dfd5bd 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ out the [examples](https://QuantumKitHub.github.io/MPSKit.jl/dev/examples/) for use-cases. This package is under active development and new algorithms are added regularly. -Nevertheless, the documentation is quite terse, so feel free to open an issue if you have -any questions. +Questions and general discussion are welcome on [GitHub +Discussions](https://github.com/QuantumKitHub/MPSKit.jl/discussions); if you think you have +found a bug, please open an issue. ## Installation @@ -118,3 +119,19 @@ scatter(g_values, M, xlabel="g", ylabel="M", label="D=$D", title="Magnetization" ``` ![Magnetization](docs/src/assets/README_ising_infinite.png) + +## Citing + +If you use MPSKit.jl in your research, please cite it. +See [`CITATION.cff`](CITATION.cff) for the up-to-date citation metadata, or use the BibTeX entry below. + +```bibtex +@software{mpskitjl, + author = {Devos, Lukas and Van Damme, Maarten and Haegeman, Jutho}, + title = {{MPSKit.jl}}, + version = {v0.13.13}, + doi = {10.5281/zenodo.10654900}, + url = {https://github.com/QuantumKitHub/MPSKit.jl}, + year = {2026} +} +``` diff --git a/docs/.gitignore b/docs/.gitignore index 0587d7400..c5cc3ab56 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,4 +1,5 @@ build/ node_modules/ package-lock.json -Manifest.toml \ No newline at end of file +Manifest.toml +src/changelog.md \ No newline at end of file diff --git a/docs/DOCSTRING_STYLE.md b/docs/DOCSTRING_STYLE.md new file mode 100644 index 000000000..eaaf315ed --- /dev/null +++ b/docs/DOCSTRING_STYLE.md @@ -0,0 +1,215 @@ +# Docstring style guide + +This document defines the conventions for docstrings across MPSKit.jl. +The goal is a single, uniform presentation for all public-facing functionality, so that the API reference reads as one coherent whole. + +These rules apply to **every** docstring in `src/`, exported or not. +Not every symbol needs every section — pick the template that fits (see [Templates](#templates)) — but when a docstring documents arguments, returns, fields, examples, or references, it does so in the one format described here. + +## Quick rules + +- **Section headers use a single hash** (`# Arguments`, `# Returns`), matching Julia Base. +- **Bullet entries** are `` - `name`: description `` — a dash, the name in backticks, a colon, one space, then the description. Add the type (`` `name::Type` ``) only when it is helpful. +- **Cross-references** use `[`name`](@ref)` for internal symbols and `[`name`](@extref Pkg.name)` for symbols in other packages. +- **Type parameter lists** put a space after each comma: `Array{T, N}`, `Union{A, B, C}` — never `Array{T,N}`. +- **Default values** put spaces around the `=`: `tol = 1e-10`, not `tol=1e-10`. +- **Literature references** use `@cite` keys, never inline DOIs or URLs. +- **Examples** that show output are runnable `jldoctest` blocks. +- **Caveats** use `!!! note`; **unstable or experimental** features use `!!! warning`. + +## Section headers + +Use a single hash (`#`) for all section headers inside a docstring. +The canonical section names, in the order they should appear, are: + +1. `# Constructors` — constructor signatures (container types with non-trivial constructors). +2. `# Arguments` — positional arguments. +3. `# Keyword Arguments` — keyword arguments. +4. `# Returns` — the return value(s). +5. `# Fields` — the struct fields, when the raw fields are the public API (algorithm structs; rendered by `$(TYPEDFIELDS)`). +6. `# Properties` — the `getproperty` interface, when it differs from the raw storage (e.g. the gauge views of an MPS container). Use `# Fields` **or** `# Properties`, whichever describes the public surface — not both. +7. `# Notes` — conventions and caveats worth a dedicated block. +8. `# Examples` — runnable examples. +9. `# See also` — related functions; for an algorithm struct, the driver(s) that consume it. +10. `# References` — literature citations. + +Omit any section that does not apply. +Do not use `## Arguments` (double hash), `# Keywords`, or other spellings. +For docstrings long enough to warrant it, split off detail into a `# Extended help` section (a Julia Base convention) so the summary line and first paragraph stay terse. + +## Bullet format + +Document arguments, keyword arguments, returns, and manually-listed properties as bullet lists in this form: + +``` +- `name`: description +- `name::Type`: description +``` + +A dash (not `*`), the name in backticks, a colon **with no leading space**, one trailing space, then the description. + +Include the type in the backtick span **only when it earns its place** — when it constrains what the caller may pass or disambiguates an overloaded name (e.g. `` `O::Union{AbstractMPO, Pair, AbstractTensorMap}` ``). +Omit it when the type is obvious from the name, the surrounding prose, or the default value (e.g. `` `verbosity`: how much information is displayed ``). +Never repeat a type that `$(TYPEDFIELDS)` already renders from the struct definition. + +Give keyword arguments their default in the backtick span when it is informative, with spaces around the `=`: `` - `tol = 1e-10`: convergence tolerance ``. +Always put spaces around `=` when writing a default value in a docstring (both in bullet entries and in signature blocks), even where the underlying code omits them. +Continuation lines of a long description are indented to align under the description text. + +## Cross-references and citations + +- Internal symbols: `` [`find_groundstate`](@ref) ``. +- External symbols: `` [`Householder`](@extref MatrixAlgebraKit.Householder) ``. + Note the parentheses — `@extref` only expands the `[text](@extref target)` form, not `[text][target]`. +- Literature: `[Zauner-Stauber et al. Phys. Rev. B 97 (2018)](@cite zauner-stauber2018)`, with the key defined in the bibliography. + Do not paste raw DOIs or arXiv links. + +## Templates + +Three templates cover the whole package. +Choose by what the symbol is, not by how important it is. + +There are two flavours of type docstring — pick by what the type is. +Algorithm and configuration structs (`A1`) are keyword-configured bags of settings; container/data types (`A2`) hold state and are built through hand-written constructors. + +### Template A1 — algorithm and configuration structs + +For the keyword-configured `@kwdef` structs: every `Algorithm` subtype, and any similar options struct. +Each field carries a per-field string literal so that `$(TYPEDFIELDS)` renders the field documentation, including its type — the doc strings themselves do not repeat the type. + +```julia +""" +$(TYPEDEF) + +One paragraph: what the algorithm does and how. + +# Fields + +$(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`find_groundstate`](@ref) and [`leading_boundary`](@ref). + +# References + +* [Author et al. Journal (Year)](@cite key) +""" +@kwdef struct VUMPS <: Algorithm + "tolerance for convergence criterium" + tol::Float64 = 1e-10 + "maximal amount of iterations" + maxiter::Int = 200 +end +``` + +`$(TYPEDEF)` generates the type signature — do not hand-write it. +The keyword constructor generated by `@kwdef` *is* the field list, so there is no `# Constructors` section; add one only if the type also offers a non-obvious convenience constructor. +`# See also` names the driver function(s) that accept the struct, so the algorithm is discoverable from its own page. +`# References` is optional and only appears when there is literature to cite. + +### Template A2 — container and data types + +For state-holding types with hand-written constructors: `FiniteMPS`, `InfiniteMPS`, `WindowMPS`, the MPO types, and similar. +Here the raw fields are internal; document the public `getproperty` interface under `# Properties`, and the constructors explicitly. + +```julia +""" +$(TYPEDEF) + +Type that represents a finite Matrix Product State. + +# Constructors + FiniteMPS([f, eltype], physicalspaces, maxvirtualspaces; kwargs...) + FiniteMPS([f, eltype], N, physicalspace, maxvirtualspaces; kwargs...) + FiniteMPS(As::Vector{<:GenericMPSTensor}; kwargs...) + +Construct an MPS from physical and virtual spaces, or from a list of tensors `As`. + +# Arguments +- `As`: vector of site tensors +- `f = rand`: initializer for tensor data +- `physicalspaces`: list of physical spaces + +# Keyword Arguments +- `normalize = true`: normalize the constructed state +- `left`: left-most virtual space + +# Properties +- `AL`: left-gauged MPS tensors +- `AR`: right-gauged MPS tensors +- `AC`: center-gauged MPS tensors +- `C`: gauge (bond) tensors + +# Notes +By convention, `AL[i] * C[i] == AC[i] == C[i-1] * AR[i]`. +""" +``` + +Use `$(TYPEDEF)` for the top line here too, so the type signature never drifts from the definition. +The constructors are the user-facing interface and are documented separately: stack their signatures as an indented code block under `# Constructors`, then document their parameters in flat sibling `# Arguments` / `# Keyword Arguments` sections (not nested `### ` sub-headers). + +### Template B — full-contract functions + +Use for the user-facing verbs: `find_groundstate`, `leading_boundary`, `timestep`, `time_evolve`, `expectation_value`, `changebonds`, `approximate`, `correlator`, and the like. + +```julia +""" + funcname(ψ₀, H, [environments]; kwargs...) -> (ψ, environments, ϵ) + +One paragraph describing the operation. + +# Arguments +- `ψ₀::AbstractMPS`: initial guess +- `H::AbstractMPO`: the operator + +# Keyword Arguments +- `tol::Float64 = 1e-10`: convergence tolerance + +# Returns +- `ψ::AbstractMPS`: the converged state +- `ϵ::Float64`: final error estimate + +# Examples +```jldoctest +julia> # runnable example +``` + +# References +* [...](@cite key) +""" +``` + +The top line is an indented, four-space signature; stack multiple overloads as separate signature lines. +Keep the `-> (...)` return annotation on the signature even when a `# Returns` section is present: the signature is the glanceable form, the section is the contract. +Omit any section that does not apply (a function with no keywords has no `# Keyword Arguments`). + +### Template C — lightweight + +Use for simple helpers and most internal functions: a signature and a one- or two-sentence description, no sections. + +```julia +""" + correlator(ψ, O1, O2, i, j) + correlator(ψ, O12, i, j) + +Compute the 2-point correlator `⟨ψ|O1[i]O2[j]|ψ⟩`. +Also accepts a range for `j`. +""" +``` + +## Admonitions + +- `!!! note` for caveats and conventions the reader must know (e.g. gauge conventions). +- `!!! warning` for anything unstable or experimental — everything in `lib/internals`, current GPU support, and any feature that may change. + +## Attachment + +- Prefer a leading `"""..."""` block directly above the definition. +- Use `@doc (@doc a) b` only to alias a genuinely identical docstring onto a sibling. +- A comment between the docstring and the definition silently detaches the docstring; keep them adjacent and put any comment above the docstring. + +## Physics claims + +Any statement about physical behavior, convergence, or the meaning of a result that has not been verified gets a `` comment for the maintainer. +Correctness of physics is the maintainer's call. diff --git a/docs/Project.toml b/docs/Project.toml index dc5e8d2bc..a62ccff45 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -13,6 +13,7 @@ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" +TensorKitTensors = "41b62e7d-e9d1-4e23-942c-79a97adf954b" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" [sources] @@ -22,3 +23,4 @@ MPSKit = {path = ".."} Documenter = "1.11" DocumenterInterLinks = "1" DocumenterVitepress = "0.3" +TensorKitTensors = "0.2" diff --git a/docs/make.jl b/docs/make.jl index 5922e4ee3..0f48ed51e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -38,6 +38,9 @@ links = InterLinks( # include MPSKit in all doctests DocMeta.setdocmeta!(MPSKit, :DocTestSetup, :(using MPSKit, TensorKit); recursive = true) +# root CHANGELOG.md is canonical (visible on GitHub); copy it in for rendering +cp(joinpath(@__DIR__, "..", "CHANGELOG.md"), joinpath(@__DIR__, "src", "changelog.md"); force = true) + makedocs(; sitename = "MPSKit.jl", format = DocumenterVitepress.MarkdownVitepress(; @@ -47,17 +50,50 @@ makedocs(; ), pages = [ "Home" => "index.md", - "Manual" => [ - "man/intro.md", - "man/states.md", - "man/operators.md", - "man/algorithms.md", - # "man/environments.md", - "man/parallelism.md", - "man/lattices.md", + "Tutorials" => [ + "tutorials/installation.md", + "tutorials/first_groundstate.md", + "tutorials/thermodynamic_limit.md", + "tutorials/time_evolution.md", + "tutorials/excitations.md", + "tutorials/using_symmetries.md", + ], + "How-to" => [ + "howto/index.md", + "howto/states.md", + "howto/hamiltonians.md", + "howto/groundstate_algorithms.md", + "howto/bond_dimension.md", + "howto/time_evolution.md", + "howto/observables.md", + "howto/entanglement.md", + "howto/excitations.md", + "howto/parallelism_gpu.md", + ], + "Concepts" => [ + "concepts/vector_spaces.md", + "concepts/matrix_product_states.md", + "concepts/operators_and_hamiltonians.md", + "concepts/algorithm_landscape.md", + "concepts/environments.md", + "concepts/parallelism_model.md", + ], + "Examples" => [ + "Overview" => "examples/index.md", + "Quantum (1+1)d" => quantum_pages, + "Classical (2+0)d" => classic_pages, + ], + "Library" => [ + "lib/public.md", + "lib/states.md", + "lib/operators.md", + "lib/groundstate.md", + "lib/bond_dimension.md", + "lib/time_evolution.md", + "lib/excitations.md", + "lib/observables.md", + "lib/lib.md", ], - "Examples" => "examples/index.md", - "Library" => "lib/lib.md", "References" => "references.md", "Changelog" => "changelog.md", ], diff --git a/docs/src/assets/icons/algorithms.svg b/docs/src/assets/icons/algorithms.svg new file mode 100644 index 000000000..d7b749faf --- /dev/null +++ b/docs/src/assets/icons/algorithms.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/docs/src/assets/icons/fast.svg b/docs/src/assets/icons/fast.svg new file mode 100644 index 000000000..ac12db491 --- /dev/null +++ b/docs/src/assets/icons/fast.svg @@ -0,0 +1,4 @@ + + + diff --git a/docs/src/assets/icons/finite-infinite.svg b/docs/src/assets/icons/finite-infinite.svg new file mode 100644 index 000000000..ba8c7c887 --- /dev/null +++ b/docs/src/assets/icons/finite-infinite.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/icons/symmetry.svg b/docs/src/assets/icons/symmetry.svg new file mode 100644 index 000000000..9d5ee9341 --- /dev/null +++ b/docs/src/assets/icons/symmetry.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/docs/src/assets/mps.svg b/docs/src/assets/mps.svg new file mode 100644 index 000000000..25ee1cb68 --- /dev/null +++ b/docs/src/assets/mps.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/mpskit.bib b/docs/src/assets/mpskit.bib index 296f4194b..f8daacdc3 100644 --- a/docs/src/assets/mpskit.bib +++ b/docs/src/assets/mpskit.bib @@ -614,3 +614,299 @@ @article{zhang2023 url = {https://link.aps.org/doi/10.1103/PhysRevLett.130.151602}, abstract = {We show that the Klein bottle entropy [H.-H. Tu, Phys. Rev. Lett. 119, 261603 (2017)] for conformal field theories perturbed by a relevant operator is a universal function of the dimensionless coupling constant. The universal scaling of the Klein bottle entropy near criticality provides an efficient approach to extract the scaling dimension of lattice operators via data collapse. As paradigmatic examples, we validate the universal scaling of the Klein bottle entropy for Ising and {$\mathbb{Z}$}3 parafermion conformal field theories with various perturbations using numerical simulation with continuous matrix product operator approach.} } + +% --------------------------------------------------------------------------- +% Publications using MPSKit added 2026-07 (from append-candidates review + web search). +% Entries verified to exist (arXiv IDs / DOIs checked). Notes flag any entry whose +% MPSKit citation could not be independently confirmed during review. +% --------------------------------------------------------------------------- + +@misc{ueda2026, + title = {Emergent {{Andreev Reflection}} from a {{Lattice Duality Defect}}}, + author = {Ueda, Atsushi and Numasawa, Tokiro and {De Vos}, Boris and Watanabe, Masataka}, + year = {2026}, + month = jun, + number = {arXiv:2606.23684}, + eprint = {2606.23684}, + primaryclass = {cond-mat.str-el}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2606.23684}, + url = {https://arxiv.org/abs/2606.23684}, + archiveprefix = {arXiv} +} + +@misc{kaplan2026, + title = {Wavelet {{Matrix Product States}} for {{Quantum Fields}}}, + author = {Kaplan, Molly and Tilloy, Antoine}, + year = {2026}, + month = jun, + number = {arXiv:2606.23823}, + eprint = {2606.23823}, + primaryclass = {quant-ph}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2606.23823}, + url = {https://arxiv.org/abs/2606.23823}, + archiveprefix = {arXiv} +} + +@misc{shankar2026, + title = {Finite-{{Element Matrix Product States}} for {{Continuum Models}} in {{One Dimension}}}, + author = {Shankar, Akshay and {Van Acoleyen}, Karel and Haegeman, Jutho}, + year = {2026}, + month = jun, + number = {arXiv:2606.14873}, + eprint = {2606.14873}, + primaryclass = {quant-ph}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2606.14873}, + url = {https://arxiv.org/abs/2606.14873}, + archiveprefix = {arXiv} +} + +@misc{veitas2026, + title = {Fluctuation-Driven Chiral Ferromagnetism}, + author = {Veitas, Rokas and Khalifa, Ahmed and Machado, Francisco and Chatterjee, Shubhayu}, + year = {2026}, + month = may, + number = {arXiv:2605.06852}, + eprint = {2605.06852}, + primaryclass = {cond-mat.str-el}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2605.06852}, + url = {https://arxiv.org/abs/2605.06852}, + archiveprefix = {arXiv} +} + +@misc{vanthilt2026, + title = {Matrix {{Product Operator}} Encodings of the {{Magnus Expansion}} and {{Dyson Series}}}, + author = {Vanthilt, Victor and {Van Damme}, Maarten and Haegeman, Jutho and McCulloch, Ian P. and Vanderstraeten, Laurens}, + year = {2026}, + month = may, + number = {arXiv:2605.21597}, + eprint = {2605.21597}, + primaryclass = {quant-ph}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2605.21597}, + url = {https://arxiv.org/abs/2605.21597}, + archiveprefix = {arXiv} +} + +@misc{zong2026nickelate, + title = {Correlation-Driven Orbital-Selective Fermiology and Superconductivity in the Bilayer Nickelate {{La}}$_3${{Ni}}$_2${{O}}$_7$}, + author = {Zong, Yong-Yue and Yu, Shun-Li and Li, Jian-Xin}, + year = {2026}, + month = may, + number = {arXiv:2605.10101}, + eprint = {2605.10101}, + primaryclass = {cond-mat.str-el}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2605.10101}, + url = {https://arxiv.org/abs/2605.10101}, + archiveprefix = {arXiv} +} + +% NOTE: published PRX; MPSKit citation not independently confirmed during review (uses DMRG). +@article{zong2026pseudogap, + title = {Pseudogap with {{Fermi Arcs}} and {{Fermi Pockets}} in Half-Filled Twisted Transition Metal Dichalcogenides}, + author = {Zong, Yong-Yue and Gu, Zhao-Long and Li, Jian-Xin}, + year = {2026}, + month = jan, + journal = {Physical Review X}, + volume = {16}, + number = {1}, + pages = {011005}, + publisher = {American Physical Society}, + doi = {10.1103/kmn8-y59j}, + url = {https://link.aps.org/doi/10.1103/kmn8-y59j}, + eprint = {2406.11374}, + archiveprefix = {arXiv} +} + +% NOTE: MPSKit appears in the bibliography; primary numerics use ITensor. +@misc{lu2026, + title = {Generalized {{Kramers-Wannier Self-Duality}} in {{Hopf-Ising Models}}}, + author = {Lu, Da-Chuan and Chatterjee, Arkya and Tantivasadakarn, Nathanan}, + year = {2026}, + month = feb, + number = {arXiv:2602.10183}, + eprint = {2602.10183}, + primaryclass = {cond-mat.str-el}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2602.10183}, + url = {https://arxiv.org/abs/2602.10183}, + archiveprefix = {arXiv} +} + +@misc{hormann2026, + title = {Folds of One Curve: The Superradiant Phase Diagram of {{Dicke}} Modes with Interacting Matter}, + author = {H{\"o}rmann, Max}, + year = {2026}, + month = jun, + number = {arXiv:2606.26081}, + eprint = {2606.26081}, + primaryclass = {cond-mat.str-el}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2606.26081}, + url = {https://arxiv.org/abs/2606.26081}, + archiveprefix = {arXiv} +} + +@misc{kadow2026, + title = {Fractionalization from Kinetic Frustration in Doped Two-Dimensional {{SU}}(4) Quantum Magnets}, + author = {Kadow, Wilhelm and Morera, Ivan and Demler, Eugene and Knap, Michael}, + year = {2026}, + month = mar, + number = {arXiv:2603.28871}, + eprint = {2603.28871}, + primaryclass = {cond-mat.str-el}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2603.28871}, + url = {https://arxiv.org/abs/2603.28871}, + archiveprefix = {arXiv} +} + +@misc{basumatary2026, + title = {Cosmological Correlators Using Tensor Networks}, + author = {Basumatary, Ujjwal and Sinha, Aninda and Zhou, Xinan}, + year = {2026}, + month = mar, + number = {arXiv:2603.26090}, + eprint = {2603.26090}, + primaryclass = {hep-th}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2603.26090}, + url = {https://arxiv.org/abs/2603.26090}, + archiveprefix = {arXiv} +} + +@misc{brehmer2026, + title = {{{PEPSKit.jl}}: A {{Julia}} Package for Projected Entangled-Pair State Simulations}, + author = {Brehmer, Paul and Burgelman, Lander and Yue, Zheng-Yuan and Fedorovich, Gleb and Haegeman, Jutho and Devos, Lukas}, + year = {2026}, + month = may, + number = {arXiv:2605.19960}, + eprint = {2605.19960}, + primaryclass = {cond-mat.str-el}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2605.19960}, + url = {https://arxiv.org/abs/2605.19960}, + archiveprefix = {arXiv} +} + +@misc{ritter2026, + title = {Fast Elementwise Operations on Tensor Trains with Alternating Cross Interpolation}, + author = {Ritter, Marc K.}, + year = {2026}, + month = apr, + number = {arXiv:2604.00037}, + eprint = {2604.00037}, + primaryclass = {math.NA}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2604.00037}, + url = {https://arxiv.org/abs/2604.00037}, + archiveprefix = {arXiv} +} + +% NOTE: published SciPost lecture notes; MPSKit citation not independently confirmed during review. +@article{vancraeynestdecuiper2026, + title = {Les {{Houches}} Lecture Notes on Tensor Networks}, + author = {{Vancraeynest-De Cuiper}, Bram and Wiesiolek, Weronika and Verstraete, Frank}, + year = {2026}, + journal = {SciPost Physics Lecture Notes}, + pages = {128}, + issn = {2590-1990}, + doi = {10.21468/SciPostPhysLectNotes.128}, + url = {https://scipost.org/10.21468/SciPostPhysLectNotes.128}, + eprint = {2512.24390}, + archiveprefix = {arXiv} +} + +@misc{staelens2026, + title = {Combining Matrix Product States and Mean-Field Theory to Capture Magnetic Order in Quasi-1D Cuprates}, + author = {Staelens, Quentin and Verraes, Daan and Vrancken, Daan and Braeckevelt, Tom and Haegeman, Jutho and {Van Speybroeck}, Veronique}, + year = {2026}, + month = feb, + number = {arXiv:2602.21695}, + eprint = {2602.21695}, + primaryclass = {cond-mat.str-el}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2602.21695}, + url = {https://arxiv.org/abs/2602.21695}, + archiveprefix = {arXiv} +} + +@misc{yang2026, + title = {Transfer-Matrix Functions for Algebraically Decaying Interactions in Variational Infinite Matrix Product States}, + author = {Yang, Qi}, + year = {2026}, + month = jun, + number = {arXiv:2606.20522}, + eprint = {2606.20522}, + primaryclass = {cond-mat.str-el}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2606.20522}, + url = {https://arxiv.org/abs/2606.20522}, + archiveprefix = {arXiv} +} + +@misc{zemlevskiy2026, + title = {Exclusive Scattering Channels from Entanglement Structure in Real-Time Simulations}, + author = {Zemlevskiy, Nikita A.}, + year = {2026}, + month = mar, + number = {arXiv:2603.15621}, + eprint = {2603.15621}, + primaryclass = {quant-ph}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2603.15621}, + url = {https://arxiv.org/abs/2603.15621}, + archiveprefix = {arXiv} +} + +% NOTE: published Communications Physics; MPSKit citation not independently confirmed during review. +@article{vandamme2025pseudogenerators, + title = {Suppressing Nonperturbative Gauge Errors in the Thermodynamic Limit Using Local Pseudogenerators}, + author = {{Van Damme}, Maarten and Mildenberger, Julius and Grusdt, Fabian and Hauke, Philipp and Halimeh, Jad C.}, + year = {2025}, + month = mar, + journal = {Communications Physics}, + volume = {8}, + number = {1}, + pages = {106}, + publisher = {Springer Nature}, + doi = {10.1038/s42005-025-02035-y}, + url = {https://www.nature.com/articles/s42005-025-02035-y}, + eprint = {2110.08041}, + archiveprefix = {arXiv} +} + +@article{sommer2025, + title = {Higher Berry Curvature from the Wave Function. I. {{Schmidt}} Decomposition and Matrix Product States}, + author = {Sommer, Ophelia Evelyn and Wen, Xueda and Vishwanath, Ashvin}, + year = {2025}, + month = apr, + journal = {Physical Review Letters}, + volume = {134}, + number = {14}, + pages = {146601}, + publisher = {American Physical Society}, + doi = {10.1103/PhysRevLett.134.146601}, + url = {https://link.aps.org/doi/10.1103/PhysRevLett.134.146601}, + eprint = {2405.05316}, + archiveprefix = {arXiv} +} + +% NOTE: lecture notes; MPSKit citation not independently confirmed during review. +@misc{sinha2025, + title = {Lectures on Quantum Field Theory on a Quantum Computer}, + author = {Sinha, Aninda and Basumatary, Ujjwal}, + year = {2025}, + month = dec, + number = {arXiv:2512.02706}, + eprint = {2512.02706}, + primaryclass = {quant-ph}, + publisher = {arXiv}, + doi = {10.48550/arXiv.2512.02706}, + url = {https://arxiv.org/abs/2512.02706}, + archiveprefix = {arXiv} +} diff --git a/docs/src/concepts/algorithm_landscape.md b/docs/src/concepts/algorithm_landscape.md new file mode 100644 index 000000000..d042be916 --- /dev/null +++ b/docs/src/concepts/algorithm_landscape.md @@ -0,0 +1,114 @@ +# [The algorithm landscape](@id concept_algorithm_landscape) + +MPSKit deliberately separates *what* you want to compute from *how* it gets computed. +Entry points such as [`find_groundstate`](@ref), [`timestep`](@ref), [`excitations`](@ref), [`leading_boundary`](@ref), and [`approximate`](@ref) each accept several interchangeable algorithm structs, and the package ships more than a dozen of them. +That flexibility exists because no single algorithm wins everywhere: some only apply to finite or only to infinite systems, some can grow the bond dimension while others cannot, and their relative performance depends on the model at hand. + +This page is the decision guide. +It starts from a table that maps each task onto the algorithm(s) of choice, and then walks through the reasoning behind each row. +It explains *why* you would pick one algorithm over another; for the *how* — the actual calls, keywords, and worked recipes — follow the links into the how-to pages. + +## The decision table + +| Task | Finite system | Infinite system | +|:-----|:--------------|:----------------| +| **Ground state** ([`find_groundstate`](@ref)) | [`DMRG`](@ref) (workhorse, fixed bond dimension); [`DMRG2`](@ref) (grows bond dimension, requires `trscheme`); [`GradientGrassmann`](@ref) (final polish) | [`VUMPS`](@ref) (workhorse, needs a unique ground state); [`IDMRG`](@ref) / [`IDMRG2`](@ref) (two-site requires `trscheme` and a unit cell of at least two sites); [`GradientGrassmann`](@ref) (final polish) | +| **Time evolution** ([`timestep`](@ref) / [`time_evolve`](@ref)) | [`TDVP`](@ref) (fixed bond dimension); [`TDVP2`](@ref) (grows bond dimension, requires `trscheme`); or [`make_time_mpo`](@ref) ([`WI`](@ref) / [`WII`](@ref) / [`TaylorCluster`](@ref)) applied with [`approximate`](@ref) | [`TDVP`](@ref) (no two-site variant exists); or [`make_time_mpo`](@ref) applied with [`approximate`](@ref) | +| **Excitations** ([`excitations`](@ref)) | [`QuasiparticleAnsatz`](@ref) (the only one supporting charged `sector`s); [`FiniteExcited`](@ref) (penalty method); [`ChepigaAnsatz`](@ref) / [`ChepigaAnsatz2`](@ref) (cheap, from ground-state environments) | [`QuasiparticleAnsatz`](@ref) (momentum-resolved, the only choice) | +| **Boundary / statistical mechanics** ([`leading_boundary`](@ref)) | apply the transfer MPO row by row with [`approximate`](@ref) | [`VUMPS`](@ref); [`VOMPS`](@ref) (power method); [`IDMRG`](@ref) / [`IDMRG2`](@ref); [`GradientGrassmann`](@ref) (hermitian, positive transfer matrices) | +| **Compression / approximation** ([`approximate`](@ref), [`changebonds`](@ref)) | [`approximate`](@ref) with [`DMRG`](@ref) / [`DMRG2`](@ref); [`SvdCut`](@ref) via [`changebonds`](@ref) for local truncation | [`approximate`](@ref) with [`IDMRG`](@ref) / [`IDMRG2`](@ref) / [`VOMPS`](@ref); [`SvdCut`](@ref) via [`changebonds`](@ref) for local truncation | + + +A few structural facts hold across the whole table and are worth internalizing early. +Every two-site algorithm (`DMRG2`, `IDMRG2`, `TDVP2`) requires an explicit `trscheme` keyword and can change the bond dimension as it runs; the single-site variants with their default settings cannot. +`IDMRG2` additionally needs a unit cell of at least two sites, and `TDVP2` exists only for finite MPS. +Finally, algorithms compose: the `&` operator chains two algorithms into one, running the first to completion and handing its result to the second, which is how two-site warm-up passes and gradient-descent polishing stages are combined with a workhorse algorithm in a single call. + +## Ground states + +The classic approach is alternating local optimization: [`DMRG`](@ref) sweeps back and forth through a finite chain, optimizing one site while all others are held fixed, which in practice converges to the ground state. +The catch is the fixed bond dimension: a single-site update can never enlarge the virtual spaces, so the precision of the calculation is locked in by the initial state. +This bites hardest when symmetries are involved, because then not just the total bond dimension but its distribution over charge sectors is frozen, and a poor initial distribution cannot be repaired. +[`DMRG2`](@ref) fixes this by optimizing two neighbouring sites jointly and truncating back down, which lets the bond dimension (and its sector distribution) adapt, at a higher cost per sweep. + +For infinite systems, two philosophies compete. +[`IDMRG`](@ref) grows the system from the middle outwards, repeatedly inserting and optimizing new sites until the boundary is no longer felt; [`IDMRG2`](@ref) is its two-site, bond-growing variant. +Because convergence requires the effective system to outgrow the correlation length, IDMRG can be slow to converge for critical systems, where that length diverges. +[`VUMPS`](@ref) instead works with a genuinely uniform state: each local update is followed by a re-gauging step that replaces *every* tensor in the infinite chain with the updated one, so the effect of an update is felt throughout the system immediately. +This often gives VUMPS a higher convergence rate than IDMRG, which is why it is the default infinite-system workhorse. +The price is an injectivity requirement: VUMPS assumes a unique ground state, and it is not the right tool when the state it should converge to is non-injective. +Like DMRG, VUMPS is single-site and cannot alter the bond dimension. + +[`GradientGrassmann`](@ref) approaches the problem from a third direction: the MPS tensors form a Riemannian manifold (a Grassmann manifold), and one can run gradient descent directly on it, for finite and infinite states alike. +Its niche is the tail of the optimization: close to convergence its rate is often the best of the lot, while far from convergence the sweeping algorithms tend to make faster progress. +The practical consequence is the chaining pattern: run a cheap workhorse first, then hand over to gradient descent, e.g. `VUMPS(...) & GradientGrassmann(...)`. +This pattern is baked into `find_groundstate` itself: called with only keywords, it picks `DMRG` for a finite state and `VUMPS` for an infinite one, appends a `GradientGrassmann` stage on infinite states when the requested tolerance is tighter than `1e-4`, and prepends a two-site pass (`DMRG2` or `IDMRG2`) whenever you supply a `trscheme`. +Since gradient descent is also a single-site method, growing the bond dimension remains the job of that two-site pre-pass or of [`changebonds`](@ref). + +For call syntax, keyword tables, and worked chaining examples, see [Ground-state algorithms](@ref howto_groundstate_algorithms). + +## Time evolution + +MPSKit solves the time-dependent Schrödinger equation along two distinct routes, and the choice between them is a genuine trade-off rather than a finite/infinite split. + +The first route, [`TDVP`](@ref), never builds the evolution operator at all. +It projects the Schrödinger equation onto the tangent space of the current MPS, solves the projected equation for a small time step, and repeats. +Its two-site variant [`TDVP2`](@ref) plays the same role as `DMRG2` does for `DMRG`: it lets the bond dimension grow to absorb the entanglement generated by the evolution, at extra cost, and it exists only for finite systems. + +The second route splits the problem in two: first approximate the evolution operator ``\exp(-iH\,dt)`` itself as an MPO using [`make_time_mpo`](@ref) — with [`WI`](@ref), [`WII`](@ref), or [`TaylorCluster`](@ref) as the approximation scheme — and then apply that MPO to the state with [`approximate`](@ref). +The appeal is amortization: for a time-independent Hamiltonian and a fixed step size the MPO is built once and reused for every step, and the accuracy of the operator approximation is controlled independently of the accuracy of its application. + +Both routes accept an `imaginary_evolution` keyword for evolution in imaginary time. +For step-by-step recipes along either route, see [Time evolution](@ref howto_time_evolution). + +## Excitations + +Resolving states deep in the spectrum is generally out of reach, but three families of algorithms target the low-lying part, each with a distinct character. + +The [`QuasiparticleAnsatz`](@ref) is the most broadly applicable: it works for finite and infinite systems, and it is the only algorithm that can target excitations carrying a nontrivial symmetry charge, via the `sector` keyword. +It builds an excited state by replacing a single tensor of the ground-state MPS — summed over all positions on a finite chain, or in a momentum-carrying plane-wave superposition on an infinite one — and solves the resulting eigenvalue problem. +Because the variational class consists of local perturbations on top of the ground state, it is the natural choice for quasiparticle-like excitations, and on infinite systems it is the only option, giving direct access to dispersion relations. + +[`FiniteExcited`](@ref) takes a brute-force approach available only on finite chains: it reruns a full ground-state optimization on a modified Hamiltonian that carries an energy penalty for overlapping with all previously found states. +Each new excited state therefore costs another complete ground-state search, and the orthogonality to earlier states is only approximate (enforced by the penalty `weight`, not exactly). +Its advantage is that it makes no assumption about the *form* of the excited state: since each state is a fully variational `FiniteMPS`, it can in principle capture excitations that a local perturbation of the ground state would describe poorly. + +The [`ChepigaAnsatz`](@ref) (and its two-site refinement [`ChepigaAnsatz2`](@ref)) is the cheapest of the three, also finite-only. +It observes that the gauged ground-state MPS tensors act as isometries projecting the Hamiltonian into a low-energy subspace, so the low-lying spectrum can be read off by diagonalizing the effective Hamiltonian already available from the ground-state environments, with no additional sweeping. +This works best precisely where excitations are hard for the other methods: in critical systems with long-range correlations, where the excitation weight is spread across the whole chain. + +For the call signatures, momentum scans, and sector-targeting recipes, see [Excited states](@ref howto_excitations). + +## Boundaries and statistical mechanics + +MPS algorithms are not limited to Hamiltonian problems. +A two-dimensional classical partition function can be written as an infinite power of a row-to-row transfer MPO, and contracting the network amounts to finding that operator's dominant eigenvector — a boundary MPS. +This is the job of [`leading_boundary`](@ref), which accepts a familiar cast: [`VUMPS`](@ref) and [`IDMRG`](@ref)/[`IDMRG2`](@ref) carry over directly from the ground-state problem, [`GradientGrassmann`](@ref) applies when the transfer MPO is hermitian and positive, and [`VOMPS`](@ref) is a power method specific to this setting, which iteratively approximates the operator-times-state product by a new state of the same bond dimension. + +## Compression and changing bond dimension + +Two mechanisms round out the landscape by manipulating states rather than solving for new ones. +[`approximate`](@ref) variationally fits a new MPS, typically of different bond dimension, to the result of applying an MPO to a state; the sweeping ground-state algorithms (`DMRG`/`DMRG2` for finite, `IDMRG`/`IDMRG2`/`VOMPS` for infinite) double as its optimization engines. +This is the same machinery that applies time-evolution MPOs, and combined with [`SvdCut`](@ref) it yields a globally optimal truncation of a state. +[`changebonds`](@ref), by contrast, performs direct local surgery on a state: truncating with [`SvdCut`](@ref), or expanding with [`OptimalExpand`](@ref), [`RandExpand`](@ref), or [`VUMPSSvdCut`](@ref) so that the single-site algorithms above have room to work with. +The trade-offs between those expansion schemes, and recipes for when to grow, are covered in [Controlling bond dimension](@ref howto_bond_dimension). + +## Where to go next + +The how-to pages turn each row of the table into runnable recipes: [Ground-state algorithms](@ref howto_groundstate_algorithms), [Time evolution](@ref howto_time_evolution), and [Excited states](@ref howto_excitations), with [Controlling bond dimension](@ref howto_bond_dimension) supporting all three. +For the complete signatures, keyword lists, and docstrings of every algorithm named here, see the library reference: [Ground-state algorithms](@ref lib_groundstate), [Time evolution](@ref lib_time_evolution), and [Excitations](@ref lib_excitations). + + diff --git a/docs/src/concepts/environments.md b/docs/src/concepts/environments.md new file mode 100644 index 000000000..6f4ea355c --- /dev/null +++ b/docs/src/concepts/environments.md @@ -0,0 +1,99 @@ +# [Environments](@id concept_environments) + +Almost every MPS algorithm spends most of its time contracting the same tensor network over and over. +In DMRG, optimizing the tensor on one site requires the sum of all Hamiltonian contributions sitting to its left and to its right; in time evolution the same partial contractions reappear at every step. +Recomputing them from scratch each time would be wasteful, because moving attention from one site to a neighbour changes only a little of the network. +The *environment* objects are what let MPSKit avoid that waste. + +This page explains what environments are and why they exist, so that the optional `environments` argument that appears throughout the API stops looking like a mystery. +It is about understanding, not tuning: for the mechanics of a particular algorithm follow the links into the how-to pages. + +## What an environment is + +An environment is a partially contracted piece of a tensor network — the part that does not change when you shift your focus by one site. +Consider the network whose value an algorithm ultimately wants: a state `below` (the bra), an operator, and a state `above` (the ket), all contracted together. +Fixing attention on a single site splits that network into three parts: the tensor at the site itself, everything to its left, and everything to its right. +The left and right parts are exactly the *left environment* and *right environment* of that site. + +The key observation is that these two blocks are shared between neighbouring sites. +The left environment at site `i+1` is the left environment at site `i` with a single extra column contracted onto it. +So once you have paid to build the environment at one site, advancing to the next is cheap: you add one new contribution instead of recontracting the whole chain. +Caching the environments and reusing them across a sweep is what turns an algorithm that would be quadratic in the system size into a linear one. + +In MPSKit these cached blocks live in an environment object, constructed with the exported [`environments`](@ref) function. +The canonical form sandwiches an operator between two states, + +```julia +using MPSKit, MPSKitModels, TensorKit + +state = FiniteMPS(20, ℂ^2, ℂ^10) +H = transverse_field_ising(FiniteChain(20); g = 0.5) +envs = environments(state, H, state) +``` + +while the two-argument form `environments(below, above)` builds the operator-free *overlap* environments between two states. +The individual blocks are then queried with the exported [`leftenv`](@ref) and [`rightenv`](@ref) functions, + +```julia +GL = leftenv(envs, 10, state) # everything to the left of site 10 +GR = rightenv(envs, 10, state) # everything to the right of site 10 +``` + +each of which returns a tensor gauge-compatible with the state tensor at that site, ready to be contracted onto it. + +## Why you rarely build them yourself + +Most of the time you never touch an environment object at all. +The high-level entry points — [`find_groundstate`](@ref), [`timestep`](@ref), [`excitations`](@ref), and the rest — build whatever environments they need internally. +What they also do, uniformly, is accept an *optional* environments argument and return an updated environment object alongside their main result. + +That return value is the reason to care about environments even when you never construct one. +Handing the environments from one call into the next lets the algorithm reuse the cached blocks instead of rebuilding them from nothing. +For iterated procedures such as time evolution — where each step starts from a state only slightly different from the last — feeding the updated environments back in every step avoids repeating work that the previous step already did. +The [Time evolution](@ref howto_time_evolution) how-to shows this threading pattern in a concrete recipe. + +## Finite environments and the `===` cache + +For a finite state the environment object manages its own validity automatically, and understanding how is worthwhile because it comes with one sharp edge. + +When it computes a left environment, the cache records *which* state tensors it contracted to get there — the gauged tensors of `state` up to that site. +On a later query it compares the tensors it would need now against the ones it used before, testing them with Julia's identity operator `===`. +If they are the same objects, the cached block is still valid and is returned immediately. +If some differ, the cache recomputes only the affected part of the network and updates its record. +This is what makes repeated queries during a sweep cheap: the first `leftenv` at a far site pays for the full contraction, and neighbouring queries reuse almost all of it. + +The sharp edge is that `===` tests object identity, not numerical equality. +If you mutate a state tensor *in place* — changing its data while keeping the same object — the cache still sees the same object under `===` and concludes, wrongly, that its stored environment is still valid. +It will then hand back a block computed from the old data. +Building a *new* tensor and assigning it into the state is fine, because that is a different object and the `===` check catches it; only in-place mutation defeats the mechanism. +Because algorithms that use the public API replace tensors rather than mutating them, this is rarely a problem in normal use, but it is the thing to suspect if a hand-written routine that mutates tensors starts returning stale results. + +!!! warning "In-place mutation is invisible to the cache" + The finite-environment cache detects changes by object identity (`===`), so mutating a + state tensor in place leaves the cache convinced its stored environment is still current. + The internal, non-exported helper `MPSKit.poison!(envs, i)` marks the dependencies at + site `i` as stale so the next query recomputes them; needing it is a sign that a tensor + was mutated in place rather than replaced. + +## Infinite environments + +Infinite environments serve the same role but are computed differently, and the difference matters for how you use them. + +A finite chain has genuine boundaries, so its environments can be built by contracting inward from the ends in a finite number of steps. +An infinite chain has no ends. +Its environments are instead the fixed points of the transfer operator — the object you get by contracting one repeating unit cell — and finding a fixed point means solving a linear or eigenvalue problem. +Those problems are solved *iteratively*, to a finite tolerance, rather than by an exact finite contraction. +Building an infinite environment is therefore a small numerical solve, and its result is only as accurate as the tolerance of that solve. + +The precision of that solve is controlled by the `tol`, `maxiter`, and `krylovdim` keyword arguments to [`environments`](@ref), which configure the underlying iterative solver — an Arnoldi eigensolver for a transfer-matrix fixed point, or GMRES for the linear problem that arises with an `InfiniteMPOHamiltonian`. +It is fixed when the environments are built, rather than read from or written to the environment object afterwards. + +The second difference is that infinite environments are **not** recomputed automatically. +The finite cache re-validates itself against the current state on every query; the infinite one does not. +If the state changes, the stored fixed points no longer correspond to it, and there is no automatic re-solve. +Bringing an infinite environment up to date for a changed state is an explicit step, handled internally by the non-exported `MPSKit.recalculate!`.In practice the high-level algorithms perform this recomputation for you as part of their own iteration, which is again why threading the returned environments through successive calls is the efficient pattern. + +## Where to go next + +For the full signatures and docstrings of the environment functions, see [`environments`](@ref), [`leftenv`](@ref), and [`rightenv`](@ref) in the library reference. +For the algorithm-facing side — how ground-state and time-evolution routines consume and return environments — see [Ground-state algorithms](@ref howto_groundstate_algorithms) and [Time evolution](@ref howto_time_evolution). diff --git a/docs/src/man/finite_mps_definition.png b/docs/src/concepts/finite_mps_definition.png similarity index 100% rename from docs/src/man/finite_mps_definition.png rename to docs/src/concepts/finite_mps_definition.png diff --git a/docs/src/concepts/matrix_product_states.md b/docs/src/concepts/matrix_product_states.md new file mode 100644 index 000000000..89eabfa25 --- /dev/null +++ b/docs/src/concepts/matrix_product_states.md @@ -0,0 +1,130 @@ +```@meta +DocTestSetup = quote + using MPSKit, TensorKit +end +``` + +# [Matrix product states](@id concept_matrix_product_states) + +A matrix product state (MPS) represents the wavefunction of a one-dimensional quantum system as a chain of tensors, one per site, contracted along shared *virtual* bonds. +The physical indices carry the local degrees of freedom, while the virtual bonds carry the entanglement between the two halves of the system that meet at that bond. + +```@raw html +A finite MPS drawn as a chain of tensors, each with one physical leg pointing out and virtual legs joining it to its neighbours. +``` + +*The diagram shows a finite MPS as a row of site tensors, each carrying a physical index and linked to its neighbours through virtual bonds; the two ends carry trivial (dimension-one) boundary bonds.* + +This page explains the *gauge freedom* inherent in that representation and the *canonical forms* MPSKit uses to fix it, so that the `AL`, `AR`, `C`, and `AC` you see throughout the API stop looking like arbitrary labels. +It is about understanding rather than construction: for how to *build* a state see [Constructing states](@ref howto_states), and for the full type signatures see the [States](@ref lib_states) reference. + +## Gauge freedom + +The tensors that make up an MPS are not uniquely determined by the physical state they encode. +On any virtual bond you can insert an invertible matrix `C` together with its inverse `C⁻¹`, since their product is the identity and leaves the contracted network unchanged. +Absorbing `C` into the tensor on one side of the bond and `C⁻¹` into the tensor on the other redefines both local tensors while representing exactly the same physical state. + +```@raw html +Inserting C times its inverse on a virtual bond and reabsorbing each factor into the neighbouring tensor, leaving the physical state unchanged. +``` + +*The diagram shows an identity `C · C⁻¹` inserted on a virtual bond, with each factor then absorbed into the tensor on its side of the bond — a change of representation that leaves the physical state untouched.* + +This freedom is not a nuisance to be tolerated; it is a resource. +Because the local tensors can be reshaped at will, we can choose the gauge on every bond to give the tensors especially convenient properties, without ever changing the state they describe. +The two choices below are the ones that matter in practice. + +## Canonical forms + +At each site there are two particularly convenient gauges, the *left*- and *right-canonical* forms. + +In the left-canonical form a site tensor is a **left isometry**: contracting it with its own conjugate over the left virtual and physical indices yields the identity on the right virtual space. +By convention these tensors are called `AL`. + +```jldoctest mps_states +julia> state = FiniteMPS(rand, ComplexF64, 10, ℂ^2, ℂ^4); + +julia> al = state.AL[3]; + +julia> al' * al ≈ id(right_virtualspace(al)) +true +``` + +In the right-canonical form a site tensor is instead a **right isometry**, an identity when contracted over its right virtual and physical indices; these are called `AR`. +The check uses TensorKit's `repartition` to regroup the tensor's indices so that the isometry contraction can be written directly. + +```jldoctest mps_states +julia> ar = state.AR[3]; + +julia> repartition(ar, 1, 2) * repartition(ar, 1, 2)' ≈ id(left_virtualspace(ar)) +true +``` + +The two forms can be mixed: every tensor to the left of a chosen bond is put in the left gauge and every tensor to its right in the right gauge. +The gauge transformation sitting on that one bond can no longer be absorbed without spoiling the isometry property on one side, so it remains as an explicit **center bond tensor** `C`. +`C` is exactly the transformation that relates the left- and right-gauged tensors across its bond. +For convenience a single site tensor can also be left in the *center-site* form `AC`, which is the center tensor absorbed into the neighbouring isometry from either side: + +```jldoctest mps_states +julia> al * state.C[3] ≈ state.AC[3] +true +``` + +Equivalently, absorbing the center tensor on bond `2` into the right isometry at site `3` reproduces the same center-site tensor: + +```jldoctest mps_states +julia> repartition(state.C[2] * repartition(ar, 1, 2), 2, 1) ≈ state.AC[3] +true +``` + +These relations — `AL' * AL = 1`, `AR * AR' = 1`, and `AC = AL · C = C · AR` — hold for any validly gauged MPS, which is why the checks above return `true` even for a random state. + +## Automatic gauge management + +MPS algorithms move through these forms constantly: a DMRG sweep, for instance, carries the center site across the chain, gauging each tensor as it goes. +Doing that bookkeeping by hand would be tedious and error-prone, so the state objects do it for you. +A [`FiniteMPS`](@ref) (and likewise an [`InfiniteMPS`](@ref)) behaves as an automatic gauge manager: querying `state.AL`, `state.AR`, `state.C`, or `state.AC` returns the requested form, computing and caching it on demand and recomputing it when the underlying tensors have changed. +The intended experience is that you never think about how the state is gauged — it is handled automagically. + +!!! warning "In-place mutation defeats the cache" + A `FiniteMPS` detects that a form needs recomputing only when a tensor is *replaced* through an indexing assignment. + Changing a tensor's data in place keeps the same object, so the automatic recomputation is not triggered and stale gauged tensors may be returned. + Assign a new tensor rather than mutating an existing one. + +### The center-gauge overlap insight + +The payoff of the mixed gauge is visible in a computation as basic as the norm. +To compute the overlap of a state with itself, bring any bond into the center gauge. +Everything to the left of that bond is built from left isometries and contracts to the identity, everything to the right is built from right isometries and does the same, and the entire network collapses to the overlap of the center bond tensor `C` with itself. +The overlap is therefore the same whichever bond you pick: + +```jldoctest mps_states +julia> using LinearAlgebra + +julia> d = dot(state, state); + +julia> all(c -> dot(c, c) ≈ d, state.C) +true +``` + +This is not a special trick for the norm; the same collapse-to-the-center reasoning is what makes environments (see [Environments](@ref concept_environments)) and local expectation values cheap to evaluate in the canonical gauge. + +## Finite versus infinite gauging + +The gauge machinery is shared between finite and infinite states, but the way the forms are kept current differs, because the two have very different structure. + +A [`FiniteMPS`](@ref) has genuine boundaries and mutable per-site tensors, so it gauges *lazily*: each form is recomputed only for the tensors it actually depends on, and invalidation is decided by object identity (`===`) — replacing a tensor marks the left-gauged tensors to its right and the right-gauged tensors to its left as stale, leaving the rest cached. +An [`InfiniteMPS`](@ref) instead repeats a finite unit cell periodically, so there is no left or right end to anchor a partial recompute: every tensor lies both to the right and to the left of any change, and all forms are recomputed together whenever a tensor changes. + +## Variants + +Two further state types reuse the same canonical-form vocabulary for more specialized settings: + +- A [`WindowMPS`](@ref) represents a finite window of mutable tensors embedded in an infinite environment on both sides — a finite region living inside two [`InfiniteMPS`](@ref) tails. +- A [`MultilineMPS`](@ref) is a stack of [`InfiniteMPS`](@ref) objects used to represent the two-dimensional networks that arise in boundary-MPS methods. + +## Where to go next + +- To build states from tensors, from spaces, or as product states, see [Constructing states](@ref howto_states). +- For the full type signatures and docstrings, see the [States](@ref lib_states) reference. +- For how the canonical gauge makes contractions cheap, see [Environments](@ref concept_environments). diff --git a/docs/src/man/mps_gauge_freedom.png b/docs/src/concepts/mps_gauge_freedom.png similarity index 100% rename from docs/src/man/mps_gauge_freedom.png rename to docs/src/concepts/mps_gauge_freedom.png diff --git a/docs/src/concepts/operators_and_hamiltonians.md b/docs/src/concepts/operators_and_hamiltonians.md new file mode 100644 index 000000000..469d90b58 --- /dev/null +++ b/docs/src/concepts/operators_and_hamiltonians.md @@ -0,0 +1,149 @@ +# [Operators and Hamiltonians](@id concept_operators_and_hamiltonians) + +Just as a matrix product state factorises a wavefunction into a chain of local tensors, an operator on a one-dimensional system can be factorised in exactly the same way. +The result is a *matrix product operator* (MPO): the operator analogue of an MPS. +This page explains what that factorisation is, why the local tensors are not unique, and — above all — the particular upper-triangular *Jordan-block* structure that lets a sum of local terms be written as a single MPO. +It is about understanding rather than construction: for how to *build* a Hamiltonian see [Building Hamiltonians](@ref howto_hamiltonians), and for the full type signatures see the [Operators](@ref lib_operators) reference. + +## What an MPO is + +An MPO is a collection of local [`MPOTensor`](@ref MPSKit.MPOTensor) objects contracted along a line. +Where an MPS site tensor has one physical leg and two virtual legs, an MPO site tensor has *two* physical legs — one incoming and one outgoing — because it maps states to states, and again two virtual legs that thread the operator together along the chain. + +```@raw html +An MPO drawn as a chain of tensors, each with an incoming and an outgoing physical leg and virtual legs joining it to its neighbours. +``` + +*The diagram shows an MPO as a row of site tensors, each carrying a pair of physical indices (one in, one out) and linked to its neighbours through virtual bonds.* + +As with states, the construction comes in a finite and an infinite flavour. +A [`FiniteMPO`](@ref) is a plain vector of `MPOTensor` objects with trivial (dimension-one) virtual spaces at the two ends, so that the network describes a genuine operator on a finite chain. +An [`InfiniteMPO`](@ref) instead repeats a finite unit cell periodically, and is therefore stored as a periodic array of `MPOTensor` objects rather than an ordinary vector. + +### Gauge non-uniqueness + +The local tensors of an MPO are not uniquely determined by the operator they encode. +Exactly as for an MPS, an invertible gauge transformation can be inserted on any virtual bond and reabsorbed into the two neighbouring tensors without changing the contracted network. +The individual site tensors are therefore defined only up to this virtual-space gauge freedom. + +!!! warning "Element-wise comparison is unsafe" + Because two different sets of local tensors can represent the very same operator, comparing MPOs tensor-by-tensor is not meaningful. + Test for equality through gauge-invariant quantities instead. + +### Products and sums grow the virtual dimension + +MPOs support the usual linear-algebra operations — addition, subtraction, and multiplication, either among themselves or acting on an MPS. +Each such operation combines the virtual spaces of its operands, so the virtual dimension of the result is (generically) the *product* or *sum* of the input dimensions rather than staying fixed. +Composing operators naively therefore makes the representation grow, and the growth compounds under repeated multiplication. +This growth is precisely what motivates the *approximate* algorithms that re-express a product or sum within a bounded virtual dimension; see the [algorithm landscape](@ref concept_algorithm_landscape) for where those methods fit. + +## MPO Hamiltonians and the Jordan-block form + +A quantum Hamiltonian is a *sum* of local terms rather than a single dense operator, yet it too can be written as one MPO. +The trick is a characteristic upper-triangular block structure, so distinctive that the resulting object is usually called a *Jordan-block MPO*. +In MPSKit this is the [`MPOHamiltonian`](@ref) family — [`FiniteMPOHamiltonian`](@ref) and [`InfiniteMPOHamiltonian`](@ref) — and it is what the Hamiltonian constructors assemble under the hood. + +In its most general form, the per-site block matrix ``W`` reads + +```math +W = \begin{pmatrix} +1 & C & D \\ +0 & A & B \\ +0 & 0 & 1 +\end{pmatrix} +``` + +where the corner entries `1` are identity operators and ``A``, ``B``, ``C``, ``D`` are (blocks of) local operators. +The Hamiltonian on ``N`` sites is recovered by contracting one copy of ``W`` per site between two boundary vectors, + +```math +v_L = \begin{pmatrix} 1 & 0 & 0 \end{pmatrix}, +\qquad +v_R = \begin{pmatrix} 0 \\ 0 \\ 1 \end{pmatrix}, +\qquad +H = V_L\, W^{\otimes N}\, V_R . +``` + +### A finite-state automaton + +The upper-triangular shape makes ``W`` behave like a finite-state automaton that reads the chain from left to right. +The boundary vector ``v_L`` starts in the top-left "identity" state; the automaton may stay there (the leading `1`), it may *finish* immediately by placing a single-site term through ``D``, or it may *start* an interaction through ``C``, propagate it across intermediate sites through ``A``, and *close* it through ``B`` into the bottom-right "identity" state selected by ``v_R``. +Every complete left-to-right path through the block matrix contributes one term of the Hamiltonian: + +- ``D`` alone generates the single-site terms, +- ``C \cdot B`` generates the two-site terms, +- ``C \cdot A \cdot B`` generates the three-site terms, +- and in general ``C \cdot A^{k} \cdot B`` generates a term spanning ``k+2`` sites. + +The repeated ``A`` block is what makes longer-range interactions possible at fixed virtual dimension: choosing ``A`` to be (a multiple of) the identity gives every additional site the same weight, while a decaying ``A`` gives geometrically decaying couplings. +A sum of such geometric series can approximate a power-law interaction to any desired accuracy, which is how (exponentially decaying) infinite-range and approximate power-law couplings are represented. + +### The transverse-field Ising Hamiltonian + +For the [transverse-field Ising model](https://en.wikipedia.org/wiki/Transverse-field_Ising_model), + +```math +H = -J \sum_{\langle i, j \rangle} X_i X_j - h \sum_j Z_j , +``` + +the block matrix specialises to + +```math +W = \begin{pmatrix} +1 & X & -hZ \\ +0 & 0 & -JX \\ +0 & 0 & 1 +\end{pmatrix} . +``` + +Here ``D = -hZ`` is the single-site field term, and the nearest-neighbour coupling ``-J X_i X_{i+1}`` is produced by ``C = X`` on one site meeting ``B = -JX`` on the next. +The middle block ``A = 0`` truncates the automaton after two sites, which is exactly what a nearest-neighbour model needs — there are no longer-range paths. + +### Verifying the expansion symbolically + +Because ``H = V_L\, W^{\otimes N}\, V_R`` is just repeated matrix multiplication, the term-generation rule above can be checked with a symbolic algebra system. +Filling ``W`` with abstract symbols ``A``, ``B``, ``C``, ``D`` on each site and expanding the product exposes exactly which combinations survive. + +```@example operators +using Symbolics +L = 4 +# generate W matrices, one per site +@variables A[1:L] B[1:L] C[1:L] D[1:L] +Ws = map(1:L) do l + return [1 C[l] D[l] + 0 A[l] B[l] + 0 0 1] +end + +# left and right boundary vectors +Vₗ = [1, 0, 0]' +Vᵣ = [0, 0, 1] + +# expand the contraction H = V_L W^{⊗L} V_R +expand(Vₗ * prod(Ws) * Vᵣ) +``` + +Reading off the result, the lone ``D`` terms are the single-site contributions, the ``C \cdot B`` products are the two-site terms, the ``C \cdot A \cdot B`` products are the three-site terms, and so on — precisely the automaton paths described above. + +## Sparse and block structure + +Because an [`MPOHamiltonian`](@ref) is an MPO with the extra Jordan-block structure, its virtual space is not a single space but a *direct sum* of spaces, one for each row (or column) of the block matrix ``W``. +The site tensors are therefore stored as [`BlockTensorMap`](@extref BlockTensorKit.BlockTensorMap) objects rather than ordinary dense tensor maps, with each block occupying one cell of the ``W`` matrix. +MPSKit exposes the specialised [`JordanMPOTensor`](@ref) for exactly this layout. + +!!! note "Sparsity is what keeps it efficient" + Most cells of ``W`` are zero — the whole lower-left triangle, and typically much of the interior ``A`` block. + Storing only the non-zero blocks is what makes the Jordan-block representation compact, so the cost tracks the number of distinct interaction terms rather than the nominal size of ``W``. + +The [`JordanMPOTensor`](@ref) type and its internal accessors are implementation detail and may change; treat them as unstable and prefer the public constructors and `@ref`-documented interface. + +## Beyond nearest-neighbour, 1D chains + +The same machinery is not limited to nearest-neighbour couplings or to strictly one-dimensional systems: quasi-1D cylinders and 2D lattices are obtained by snaking the MPO through a multi-dimensional array of physical spaces, and longer-range interactions slot into the ``A`` block as described above. +See [Building Hamiltonians](@ref howto_hamiltonians) for the construction recipes and [MPSKitModels.jl](https://quantumkithub.github.io/MPSKitModels.jl/dev/) for ready-made lattices and models. + +## Where to go next + +- To build Hamiltonians from local terms, in 1D or on lattices, see [Building Hamiltonians](@ref howto_hamiltonians). +- For the full type signatures and docstrings, see the [Operators](@ref lib_operators) reference. +- For the approximate algorithms that keep MPO products and sums bounded, see the [algorithm landscape](@ref concept_algorithm_landscape). diff --git a/docs/src/concepts/parallelism_model.md b/docs/src/concepts/parallelism_model.md new file mode 100644 index 000000000..e8e3bccbc --- /dev/null +++ b/docs/src/concepts/parallelism_model.md @@ -0,0 +1,73 @@ +# [The parallelism model](@id concept_parallelism_model) + +Julia has excellent [parallelism infrastructure](https://julialang.org/blog/2019/07/multithreading/), +but there is a caveat that touches every algorithm in MPSKit: Julia's own threads do not +compose cleanly with the threads that BLAS uses internally for linear algebra. +Since `gemm` (general matrix-matrix multiplication) is a core routine throughout MPSKit, +this interaction has a real effect on performance. + +This page explains the model behind the settings, so that the recipes on +[Parallelism and GPU support](@ref howto_parallelism_gpu) are more than a list of magic +incantations. + +## Julia threads versus BLAS threads + +Much of the confusion here comes from the fact that BLAS threading behaviour is not +consistent between vendors, and that performance depends strongly on the hardware, the +specifics of the problem, and the availability of resources such as total memory and memory +bandwidth. +There is no one-size-fits-all setting, which is why the how-to page frames its advice as +starting points to be measured rather than guarantees. + +The two vendors most commonly used with Julia treat the BLAS thread count differently. +With OpenBLAS (the default), the configured number of BLAS threads is the **total** size of a +single thread pool that is shared by all Julia threads: 4 Julia threads and 4 BLAS threads +means all 4 Julia threads draw from the same pool of 4 BLAS threads. +Setting the BLAS thread count to `1` instead frees OpenBLAS to run its work on the Julia +threads themselves, so that MPSKit's Julia-level parallelism is the thing that scales. + +With [MKL.jl](https://github.com/JuliaLinearAlgebra/MKL.jl), which often outperforms OpenBLAS, +the count is instead the number of threads spawned by **each** Julia thread: 4 Julia threads +with 4 BLAS threads each gives 16 BLAS threads in total. +Getting this wrong oversubscribes the physical cores — more software threads than hardware +can run — which degrades rather than improves performance. + +## Where MPSKit parallelizes + +When Julia is started with more than one thread, MPSKit uses +[OhMyThreads.jl](https://juliafolds2.github.io/OhMyThreads.jl/stable/) to parallelize its +algorithms wherever possible. +In practice this happens where a unit cell (or a chain of sites) lets local updates run +independently: the work is distributed across the sites of the system, with the tensor at +each site updated in parallel. +This is exactly why setting the BLAS thread count to `1` on OpenBLAS tends to help: it keeps +the Julia threads free to work through the sites, rather than contending with a shared BLAS +pool. + +The amount of speedup you can expect therefore tracks how much independent per-site work an +algorithm exposes. + +## Parallelism over symmetry sectors + +There is a second, orthogonal layer of parallelism for tensors that carry an internal +symmetry. +Such tensors are block-diagonal over their symmetry sectors, and the work can be spread +across those blocks. +This is handled by [TensorKit](https://quantumkithub.github.io/TensorKit.jl/stable/) at the +level of the individual tensor operations, below MPSKit's site-level parallelism, so the two +layers are independent of one another. + +## Why memory pressure arises + +The same task-based parallelism that speeds MPSKit up can also drive its memory usage high. +The algorithms spawn tasks in a nested fashion, and each of those tasks allocates and +deallocates a fair amount of memory in a tight loop. +This can produce enough garbage, quickly enough, that the garbage collector cannot keep up; +in the worst case memory is exhausted and an `OutOfMemory` error is thrown before the garbage +can be cleared. + +The most memory-intensive step is reportedly the application of the `derivatives` — the +effective local operators built during the sweeps — which is why the practical mitigation is +to disable MPSKit's multithreading there. +The concrete recipe for doing so is on +[Parallelism and GPU support](@ref howto_parallelism_gpu). diff --git a/docs/src/concepts/vector_spaces.md b/docs/src/concepts/vector_spaces.md new file mode 100644 index 000000000..38beb36da --- /dev/null +++ b/docs/src/concepts/vector_spaces.md @@ -0,0 +1,179 @@ +```@meta +DocTestSetup = quote + using MPSKit, TensorKit +end +``` + +# [TensorKit for MPS users](@id concept_vector_spaces) + +Every tensor in MPSKit is a TensorKit [`TensorMap`](https://quantumkithub.github.io/TensorKit.jl/stable/), and this single choice is what makes the library generic over symmetry. +The same MPS and MPO code runs for a plain complex vector space, an abelian symmetry such as ℤ₂ or U(1), a non-abelian symmetry such as SU(2), and for fermionic or anyonic systems, because the symmetry lives inside the tensor rather than in the algorithms. +This page builds the mental model you need to read that API comfortably: what a `TensorMap` is, how its indices are typed by vector *spaces*, and the index conventions MPSKit adopts for its state and operator tensors. +It is about understanding rather than construction: once the tensors introduced here feel familiar they are put to work in [Matrix product states](@ref concept_matrix_product_states) and [Operators and Hamiltonians](@ref concept_operators_and_hamiltonians). + +## Tensors as linear maps + +The mental shift from a multi-dimensional array to a `TensorMap` is small but important. +An array is a bag of numbers indexed by integer sizes; a `TensorMap` is a **linear map** from one space to another, and its indices carry *types* — vector spaces — rather than bare sizes. +The legs are partitioned into a **codomain** (the outputs of the map) and a **domain** (its inputs), so that a tensor with codomain `W` and domain `V` is read as a map `W ← V`. + +Throughout this page we use a single running example: the two-dimensional complex space of a spin-1/2 degree of freedom. +It is written `ℂ^2` (the `ℂ` is typed `\bbC`), which constructs a [`ComplexSpace`](https://quantumkithub.github.io/TensorKit.jl/stable/) of dimension two. + +```jldoctest vspace +julia> V = ℂ^2 +ℂ^2 + +julia> dim(V) +2 +``` + +A space knows its dimension, and that dimension — not a Julia integer — is what a tensor's legs are built from. + +## Building tensors + +Constructing a tensor mirrors constructing an array, with the `axes`/`size` specifiers replaced by spaces. +The two most common constructors are `rand` and `zeros`, which take a scalar type followed by the codomain and domain. +The domain and codomain can be passed as two separate arguments, or joined with the `←` arrow (typed `\leftarrow`): + +```jldoctest vspace +julia> t = rand(Float64, V ⊗ V, V); + +julia> space(t) +(ℂ^2 ⊗ ℂ^2) ← ℂ^2 + +julia> codomain(t) +(ℂ^2 ⊗ ℂ^2) +``` + +Here `t` is a map from one spin-1/2 space to two of them, built with `⊗` (typed `\otimes`) to combine spaces. +Querying [`space`](https://quantumkithub.github.io/TensorKit.jl/stable/), [`codomain`](https://quantumkithub.github.io/TensorKit.jl/stable/), and [`domain`](https://quantumkithub.github.io/TensorKit.jl/stable/) always prints deterministically, even though the tensor's entries are random. +The `zeros` form takes the codomain and domain as separate positional arguments: + +```jldoctest vspace +julia> z = zeros(ComplexF64, V, V); + +julia> space(z) +ℂ^2 ← ℂ^2 +``` + +## Symmetric tensors: the payoff + +The reason for all of this typing of indices is that the very same interface represents *symmetric* tensors, at no extra cost to the code that uses them. +Instead of `ℂ^2` we hand the constructor a space that has been split into charge **sectors**. +For a ℤ₂ symmetry, `Z2Space(0 => 1, 1 => 1)` is a two-dimensional space whose dimension is distributed as one dimension in the even (charge `0`) sector and one in the odd (charge `1`) sector: + +```jldoctest vspace +julia> V2 = Z2Space(0 => 1, 1 => 1) +Rep[ℤ₂](…) of dim 2: + 0 => 1 + 1 => 1 + +julia> dim(V2) +2 +``` + +A tensor built on this space is a genuinely block-sparse, symmetry-respecting object, yet it is constructed and queried exactly like the plain one above: + +```jldoctest vspace +julia> t3 = rand(Float64, V2 ⊗ V2, V2); + +julia> space(t3) +(Rep[ℤ₂](0 => 1, 1 => 1) ⊗ Rep[ℤ₂](0 => 1, 1 => 1)) ← Rep[ℤ₂](0 => 1, 1 => 1) +``` + +Only the space changed; the tensor stores just the symmetry-allowed blocks and enforces charge conservation for you. +Swapping `Z2Space` for a U(1), SU(2), or fermionic space would be an equally local change, and this is exactly why MPSKit's algorithms never mention a symmetry: it is carried entirely by the spaces. +See [Using symmetries](@ref tutorial_using_symmetries) for the full progression of symmetry types. + +## Reading a partition error + +One feature of `TensorMap`s has no counterpart in plain arrays and is worth meeting deliberately, because it produces an error message that is puzzling the first time. +The partition of legs into codomain and domain is *part of a tensor's type*: two tensors are compatible for addition only when their codomains and domains match, arrows included. +Take a tensor and re-partition it so that every leg sits in the codomain (moving a leg across the `←` also flips its arrow to the dual space): + +```@example vspace +using MPSKit, TensorKit # hide +V = ℂ^2 # hide +t = rand(Float64, V ⊗ V, V) +t2 = permute(t, ((1, 2, 3), ())) +space(t), space(t2) +``` + +`t` and `t2` describe the same legs but with different partitions, so adding them directly fails: + +```@example vspace +try #hide +t + t2 # partitions do not match +catch err; Base.showerror(stderr, err); end #hide +``` + +The fix is [`permute`](https://quantumkithub.github.io/TensorKit.jl/stable/), which regroups the legs into a chosen partition. +Bringing `t2` back to the partition of `t` makes the addition well-defined again: + +```@example vspace +space(t + permute(t2, ((1, 2), (3,)))) +``` + +The lesson is not to avoid re-partitioning but to read such an error as "the same legs, grouped differently" and reach for `permute`. + +## MPSKit's index conventions + +With the `TensorMap` model in hand, we can state the leg conventions MPSKit uses for its own tensors — and, more importantly, *why* it uses them. + +An MPS site tensor has a left virtual space `Vₗ`, one or more physical spaces `P`, and a right virtual space `Vᵣ`, and MPSKit orders them so that the left virtual and physical legs form the codomain while the right virtual leg forms the domain, i.e. `Vₗ ⊗ P ← Vᵣ` (an MPO tensor, with an incoming and an outgoing physical leg, reads `Vₗ ⊗ P ← P ⊗ Vᵣ`). +At first glance this ordering looks arbitrary, but it is chosen to keep the tensor networks **planar**: the legs run left-to-right without any lines having to cross. +Planarity is what lets the algorithms be written without spurious crossings, and this matters most for **fermionic systems**, where every extra line crossing carries a sign and unnecessary crossings would reintroduce a sign problem. + + +### The MPS tensor + +```@raw html +An MPS tensor drawn as a box: a left virtual leg and one or more physical legs on the left, a right virtual leg on the right. +``` + +The diagram encodes the ordering + +```math +V_\ell \otimes P_1 \otimes \cdots \otimes P_{k} \leftarrow V_r, +``` + +i.e. leg 1 is the left virtual space, the physical spaces come next (the picture labels them `physical (2:N-1)`), and the final leg is the right virtual space. +Crucially, an MPS tensor may carry an **arbitrary number of physical legs**, and both [`FiniteMPS`](@ref) and [`InfiniteMPS`](@ref) handle the resulting objects. +This is what allows, for example, boundary tensors in PEPS code, which carry two physical legs. + +### The bond tensor + +```@raw html +A bond tensor drawn as a box with one virtual leg on the left and one virtual leg on the right. +``` + +A bond tensor sits between two MPS site tensors and has only the two virtual legs, ordered + +```math +V_\ell \leftarrow V_r, +``` + +i.e. the left virtual space is the codomain and the right virtual space is the domain. + +### The MPO tensor + +```@raw html +An MPO tensor drawn as a box: a left virtual leg and an outgoing physical leg on the left, an incoming physical leg and a right virtual leg on the right. +``` + +An MPO tensor, used to represent both quantum Hamiltonians and classical statistical-mechanics problems, carries two physical legs (one outgoing, one incoming) and two virtual legs, ordered + +```math +V_\ell \otimes P \leftarrow P \otimes V_r. +``` + +The picture labels these `virtual (1)` and `physical (2)` in the codomain, and `physical (3)` and `virtual (4)` in the domain. + +## Where to go next + +- To see these tensors assembled into states and gauged into canonical form, read [Matrix product states](@ref concept_matrix_product_states). +- For the operator side and the Jordan-block structure of Hamiltonians, read [Operators and Hamiltonians](@ref concept_operators_and_hamiltonians). +- For the full range of symmetry types and when each pays off, see [Using symmetries](@ref tutorial_using_symmetries). +- For the underlying tensor library, consult the [TensorKit documentation](https://quantumkithub.github.io/TensorKit.jl/stable/). +``` diff --git a/docs/src/examples/index.md b/docs/src/examples/index.md index d56cc7397..39ad636ac 100644 --- a/docs/src/examples/index.md +++ b/docs/src/examples/index.md @@ -1,15 +1,91 @@ # Examples +This gallery collects the full worked examples that ship with MPSKit.jl. +Each one is a complete, runnable script (also available as a Jupyter notebook, linked from the example page itself) that goes well beyond the short snippets in the how-to guides. + +Within each physics grouping below, the examples are listed roughly in order of increasing difficulty: start at the top if you are new to MPSKit, and work down as you get comfortable with symmetries, infinite systems, and the less common algorithms. + ## Quantum (1+1)d -```@contents -Pages = map(file -> joinpath("quantum1d", file, "index.md"), readdir("quantum1d")) -Depth = 1 -``` +### [The transverse-field Ising model: a complete ground-state study](quantum1d/0.tfim-groundstate/index.md) + +![](quantum1d/0.tfim-groundstate/figure-1.png) + +Assembles the tools from the tutorials into one case study of the TFIM phase transition: finite-ring `DMRG` versus infinite-chain `VUMPS` magnetization curves in a single figure, plus the entanglement entropy and correlation length of the infinite state across the transition. +The natural first example after finishing the tutorial track. +**Level: introductory.** + +### [The Ising CFT spectrum](quantum1d/1.ising-cft/index.md) + +![](quantum1d/1.ising-cft/figure-2.png) + +Extracts the finite-size conformal spectrum of the critical transverse-field Ising chain, first by brute-force `exact_diagonalization` on a small periodic chain, then by extending to larger sizes with finite `DMRG` and the `QuasiparticleAnsatz`, using a translation MPO to assign a momentum label to each state. +No symmetries are used, which makes this a good entry point into the finite-MPS workflow. +**Level: introductory.** + +### [DQPT in the Ising model](quantum1d/3.ising-dqpt/index.md) + +![](quantum1d/3.ising-dqpt/infinite_timeev.png) + +Quenches the transverse-field Ising chain across its critical point and tracks the Loschmidt echo in search of non-analyticities (dynamical quantum phase transitions), on both a finite chain (`TDVP2`/`TDVP`) and directly in the thermodynamic limit (`changebonds` with `OptimalExpand`, then `TDVP`). +A compact introduction to real-time evolution and environment reuse, still without symmetries. +**Level: introductory/intermediate.** + +### [The Haldane gap](quantum1d/2.haldane/index.md) + +![](quantum1d/2.haldane/figure-3.png) + +Computes the Haldane gap of the spin-1 Heisenberg antiferromagnet in two complementary ways: finite-size `DMRG` with the `QuasiparticleAnsatz`, extrapolated over system size, and a direct infinite-chain `VUMPS` calculation with a momentum-resolved excitation scan. +Introduces `SU2Irrep`/`SU2Space` symmetric tensors for both `FiniteMPS` and `InfiniteMPS`. +**Level: intermediate.** + +### [The XXZ model](quantum1d/4.xxz-heisenberg/index.md) + +![](quantum1d/4.xxz-heisenberg/figure-2.png) + +Walks through a ground-state search that first goes wrong: single-site `VUMPS` and `GradientGrassmann` stall on the spin-1/2 Heisenberg antiferromagnet, and `transferplot`/`entanglementplot` reveal a near-non-injective state with several almost-degenerate transfer-matrix eigenvalues. +The fix is a two-site unit cell together with the `SU2Irrep`-symmetric Hamiltonian, after which `IDMRG2` and `VUMPS` converge cleanly. +A useful, diagnostic-driven example for recognizing and debugging convergence failures. +**Level: intermediate.** + +### [Spin 1 Heisenberg model](quantum1d/5.haldane-spt/index.md) + +![](quantum1d/5.haldane-spt/figure-3.png) + +Distinguishes the two symmetry-protected topological phases of the SU(2)-symmetric spin-1 Heisenberg chain by restricting the virtual `SU2Space` to integer or half-integer charges, then compares the two resulting ground states through their energy, transfer-matrix spectrum (`transferplot`), entanglement spectrum (`entanglementplot`), and entanglement entropy (`entropy`). +Builds directly on the symmetry machinery introduced in the Haldane gap example. +**Level: intermediate/advanced.** + +### [Hubbard chain at half filling](quantum1d/6.hubbard/index.md) + +![](quantum1d/6.hubbard/figure-2.png) + +Studies the 1D Hubbard model at half filling with fermionic `InfiniteMPS`, first benchmarking a plain `VUMPS`/`GradientGrassmann` ground-state search against the exact Bethe-ansatz integral for the energy, then imposing the full particle-number and spin symmetry (`U1Irrep`, `SU2Irrep`, with `MPSKit.add_physical_charge` to pin the filling) and constructing the spinon/holon excitation spectrum with the `QuasiparticleAnsatz`. +Combines fermionic symmetry sectors with a more elaborate ground-state recipe (staged bond-dimension growth via `changebonds`/`OptimalExpand`, `SvdCut`, and mixed `VUMPS`/`GradientGrassmann` refinement). +**Level: advanced.** + +### [Finite temperature XY model](quantum1d/7.xy-finiteT/index.md) + +![](quantum1d/7.xy-finiteT/figure-1.png) + +Simulates the finite-temperature XY chain by purifying the infinite-temperature density matrix and evolving it in imaginary time, then compares the resulting partition function and free energy against exact diagonalization and, via BenchmarkFreeFermions.jl, the exact free-fermion solution. +A technical, comparison-heavy example built around `MPSKit.infinite_temperature_density_matrix` and imaginary-time evolution rather than ground-state search. +**Level: advanced.** + +### [1D Bose-Hubbard model](quantum1d/8.bose-hubbard/index.md) + +![](quantum1d/8.bose-hubbard/figure-6.png) + +The most comprehensive example in the gallery: works directly in the thermodynamic limit with a truncated bosonic local Hilbert space, extracts correlation functions and the correlation length as a function of bond dimension, computes the momentum distribution, and maps out the Mott-insulator/superfluid structure of the phase diagram from the ground-state response to an applied phase twist. +Touches most of the ground-state toolbox (`InfiniteMPS`, `find_groundstate`, bond-dimension control, `expectation_value`-based observables) in a single, longer study. +**Level: advanced.** ## Classical (2+0)d -```@contents -Pages = map(file -> joinpath("classic2d", file, "index.md"), readdir("classic2d")) -Depth = 1 -``` \ No newline at end of file +### [The Hard Hexagon model](classic2d/1.hard-hexagon/index.md) + +![](classic2d/1.hard-hexagon/figure-1.png) + +Extracts the central charge of the hard hexagon lattice gas by finding the leading boundary MPS of its transfer matrix with `VUMPS` (using Fibonacci-anyon virtual spaces), then fitting the CFT-predicted scaling relation between entanglement entropy and correlation length as the bond dimension is increased. +The only classical statistical-mechanics example in the gallery, and the only one demonstrating non-abelian anyonic symmetries (`FibonacciAnyon`) in MPSKit. +**Level: advanced.** diff --git a/docs/src/examples/quantum1d/0.tfim-groundstate/figure-1.png b/docs/src/examples/quantum1d/0.tfim-groundstate/figure-1.png new file mode 100644 index 000000000..bfa456242 Binary files /dev/null and b/docs/src/examples/quantum1d/0.tfim-groundstate/figure-1.png differ diff --git a/docs/src/examples/quantum1d/0.tfim-groundstate/figure-2.png b/docs/src/examples/quantum1d/0.tfim-groundstate/figure-2.png new file mode 100644 index 000000000..19e71c3ea Binary files /dev/null and b/docs/src/examples/quantum1d/0.tfim-groundstate/figure-2.png differ diff --git a/docs/src/examples/quantum1d/0.tfim-groundstate/figure-3.png b/docs/src/examples/quantum1d/0.tfim-groundstate/figure-3.png new file mode 100644 index 000000000..31b1c357a Binary files /dev/null and b/docs/src/examples/quantum1d/0.tfim-groundstate/figure-3.png differ diff --git a/docs/src/examples/quantum1d/0.tfim-groundstate/index.md b/docs/src/examples/quantum1d/0.tfim-groundstate/index.md new file mode 100644 index 000000000..40ccedabd --- /dev/null +++ b/docs/src/examples/quantum1d/0.tfim-groundstate/index.md @@ -0,0 +1,199 @@ +```@meta +EditURL = "../../../../../examples/quantum1d/0.tfim-groundstate/main.jl" +``` + +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/0.tfim-groundstate/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/0.tfim-groundstate/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/quantum1d/0.tfim-groundstate) + +# The transverse-field Ising model: a complete ground-state study + +This example is the bridge from the introductory tutorials into the research-grade +gallery. +If you have worked through [Your first ground state](@ref tutorial_first_groundstate) and +[The thermodynamic limit](@ref tutorial_thermodynamic_limit) you already know every +individual tool used here; the goal now is to *assemble* them into one coherent case +study of a genuine quantum phase transition. + +We use the same transverse-field Ising model (TFIM) as the tutorials, on a chain of +spin-1/2 sites: + +```math +H = -J\left(\sum_{\langle i,j\rangle} \sigma^z_i \sigma^z_j + g\sum_i \sigma^x_i\right), +``` + +where the first sum runs over neighbouring pairs, ``J`` sets the energy scale, and the +dimensionless field ``g`` tunes the competition between the ``\sigma^z\sigma^z`` +interaction and the transverse ``\sigma^x`` field. +The model has a quantum critical point at ``g = 1``. + +Rather than looking at a single field value, we will scan ``g`` across the transition and +diagnose it three independent ways, comparing a *finite* chain against a calculation +performed *directly in the thermodynamic limit*: + +1. the order parameter ``|\langle\sigma^z\rangle|``, computed both for a finite chain and + for an infinite chain, in one figure; +2. the entanglement entropy of the infinite state; +3. the correlation length of the infinite state. + +All three should point at the same place — that agreement is the payoff. + +We take the model and lattice from MPSKitModels, the tensor backend from TensorKit, and +Plots for the figures. The Pauli operators `σᶻ`, `σˣ` are re-exported by MPSKitModels. + +````julia +using MPSKit, MPSKitModels, TensorKit, Plots +```` + +## Shared parameters + +We fix a finite chain length `L`, a bond dimension `D` (the accuracy knob, see +[Controlling bond dimension](@ref howto_bond_dimension)), and the set of field values to +scan. +`D` is kept modest so the whole page runs in a couple of minutes; increasing it sharpens +the infinite-state diagnostics below (the finite-chain curve responds to `D` in a less +obvious way, as we will see). + +````julia +L = 16 +D = 8 +g_values = 0.1:0.1:2.0 +```` + +```` +0.1:0.1:2.0 +```` + +## 1. Finite versus infinite magnetization + +We compute the order parameter ``|\langle\sigma^z\rangle|`` two ways at every field value. + +For the **finite** calculation we use an open chain of `L` sites, exactly as in the +tutorial, and optimize with [`DMRG`](@ref). +We average ``\langle\sigma^z_i\rangle`` over the sites and take the absolute value: the +exact finite-`L` ground state is symmetric, but DMRG lands on one of the two +symmetry-broken states with an arbitrary sign (see the discussion in +[Your first ground state](@ref tutorial_first_groundstate)). + +````julia +ψ₀_finite = FiniteMPS(L, ℂ^2, ℂ^D) +M_finite = map(g_values) do g + H = transverse_field_ising(FiniteChain(L); g = g) + ψ, = find_groundstate(ψ₀_finite, H, DMRG(; verbosity = 0)) + return abs(sum(expectation_value(ψ, i => σᶻ()) for i in 1:L)) / L +end; +```` + +For the **infinite** calculation we drop the lattice argument to build the Hamiltonian on +the infinite chain, use an [`InfiniteMPS`](@ref), and optimize with [`VUMPS`](@ref). +We keep every optimized infinite state, because we will reuse them for the entropy and +correlation-length diagnostics below. + +````julia +ψ₀_infinite = InfiniteMPS(ℂ^2, ℂ^D) +states_infinite = map(g_values) do g + H = transverse_field_ising(; g = g) + ψ, = find_groundstate(ψ₀_infinite, H, VUMPS(; verbosity = 0)) + return ψ +end; +```` + +The order parameter of a translation-invariant state is just ``\langle\sigma^z\rangle`` on +a single site of the unit cell; we again take the absolute value, because on the ordered +side the infinite state settles into one of the two symmetry-broken ground states (see +[The thermodynamic limit](@ref tutorial_thermodynamic_limit)). + +````julia +M_infinite = [abs(expectation_value(ψ, 1 => σᶻ())) for ψ in states_infinite]; +```` + +Plotting both curves in a single figure lets us compare them directly. + +````julia +p_magnetization = plot(; + xlabel = "g", ylabel = "|⟨σᶻ⟩|", title = "TFIM order parameter", legend = :bottomleft +) +scatter!(p_magnetization, g_values, M_finite; label = "finite chain, L = $L, D = $D") +scatter!(p_magnetization, g_values, M_infinite; label = "infinite, D = $D") +vline!(p_magnetization, [1.0]; color = "gray", linestyle = :dash, label = "g = 1") +p_magnetization +```` + +![](figure-1.png) + +Both calculations agree deep in either phase, but near the transition they tell very different stories. +The infinite curve stays on its ordered branch essentially up to `g = 1` and then collapses: it locates the critical point cleanly. +The finite-chain curve instead drops to zero far earlier — at this `L` and `D` the variational optimum on the open chain switches from the symmetry-broken branch to the exactly symmetric ground state, whose magnetization vanishes. +Where that switch happens is set by `L` and `D`, not by the physics; the same sweep at `D = 4` in [Your first ground state](@ref tutorial_first_groundstate) puts it elsewhere. +That is the real lesson of this panel: the finite-chain order parameter is dominated by which state the algorithm selects, while the calculation performed directly in the thermodynamic limit pins the transition at `g = 1`. + +## 2. Entanglement entropy across the transition + +Entanglement is a hallmark of criticality: it is bounded away from the critical point but +grows sharply as we approach it. +For an [`InfiniteMPS`](@ref), [`entropy`](@ref) returns the von Neumann entanglement entropy +per bond, one value for each site of the unit cell. +Our unit cell has a single site, so we take the one entry with `only`. + +````julia +S_infinite = [real(only(entropy(ψ))) for ψ in states_infinite] +p_entropy = scatter( + g_values, S_infinite; + xlabel = "g", ylabel = "entanglement entropy S", title = "TFIM entanglement entropy", + legend = false +) +vline!(p_entropy, [1.0]; color = "gray", linestyle = :dash) +p_entropy +```` + +![](figure-2.png) + +The entropy peaks near `g = 1`. +That peak is the entanglement signature of the phase transition: at criticality +correlations become long-ranged and the ground state is at its most entangled, whereas deep +in either phase the state is closer to a simple product and the entropy is small. + +## 3. Correlation length across the transition + +The [`correlation_length`](@ref) measures how far apart two spins can still influence each +other; it is extracted from the transfer-matrix spectrum of the uniform infinite state and +has no finite-chain analogue. +It grows toward criticality, so we plot it on a logarithmic vertical axis to make the +growth visible. + +````julia +ξ_infinite = [correlation_length(ψ) for ψ in states_infinite] +p_xi = scatter( + g_values, ξ_infinite; + xlabel = "g", ylabel = "correlation length ξ", yscale = :log10, + title = "TFIM correlation length", legend = false +) +vline!(p_xi, [1.0]; color = "gray", linestyle = :dash) +p_xi +```` + +![](figure-3.png) + +The correlation length peaks near `g = 1` as well. +At a genuine critical point it would diverge, but a finite bond dimension `D` can only +capture correlations out to a finite range, so what we measure is large-but-capped rather +than infinite — the peak grows and sharpens as `D` is increased. + +## What you now have + +Three independent diagnostics — the order parameter, the entanglement entropy, and the +correlation length — all locate the transition of the transverse-field Ising model near +`g = 1`, and the finite-versus-infinite comparison shows concretely why the thermodynamic +limit is the right place to measure it. + +From here the gallery goes further. +The Ising CFT example extracts the momentum-resolved excitation spectrum right at +criticality and matches it to the predictions of conformal field theory, turning the "there +is a critical point near `g = 1`" of this page into a quantitative fingerprint of *which* +critical theory it is. +Every curve on this page also sharpens if you rerun it at a larger bond dimension `D`. + +--- + +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/docs/src/examples/quantum1d/0.tfim-groundstate/main.ipynb b/docs/src/examples/quantum1d/0.tfim-groundstate/main.ipynb new file mode 100644 index 000000000..12748dde4 --- /dev/null +++ b/docs/src/examples/quantum1d/0.tfim-groundstate/main.ipynb @@ -0,0 +1,310 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# The transverse-field Ising model: a complete ground-state study\n", + "\n", + "This example is the bridge from the introductory tutorials into the research-grade\n", + "gallery.\n", + "If you have worked through Your first ground state and\n", + "The thermodynamic limit you already know every\n", + "individual tool used here; the goal now is to *assemble* them into one coherent case\n", + "study of a genuine quantum phase transition.\n", + "\n", + "We use the same transverse-field Ising model (TFIM) as the tutorials, on a chain of\n", + "spin-1/2 sites:\n", + "\n", + "$$\n", + "H = -J\\left(\\sum_{\\langle i,j\\rangle} \\sigma^z_i \\sigma^z_j + g\\sum_i \\sigma^x_i\\right),\n", + "$$\n", + "\n", + "where the first sum runs over neighbouring pairs, $J$ sets the energy scale, and the\n", + "dimensionless field $g$ tunes the competition between the $\\sigma^z\\sigma^z$\n", + "interaction and the transverse $\\sigma^x$ field.\n", + "The model has a quantum critical point at $g = 1$.\n", + "\n", + "Rather than looking at a single field value, we will scan $g$ across the transition and\n", + "diagnose it three independent ways, comparing a *finite* chain against a calculation\n", + "performed *directly in the thermodynamic limit*:\n", + "\n", + "1. the order parameter $|\\langle\\sigma^z\\rangle|$, computed both for a finite chain and\n", + " for an infinite chain, in one figure;\n", + "2. the entanglement entropy of the infinite state;\n", + "3. the correlation length of the infinite state.\n", + "\n", + "All three should point at the same place — that agreement is the payoff." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We take the model and lattice from MPSKitModels, the tensor backend from TensorKit, and\n", + "Plots for the figures. The Pauli operators `σᶻ`, `σˣ` are re-exported by MPSKitModels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "using MPSKit, MPSKitModels, TensorKit, Plots" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Shared parameters\n", + "\n", + "We fix a finite chain length `L`, a bond dimension `D` (the accuracy knob, see\n", + "Controlling bond dimension), and the set of field values to\n", + "scan.\n", + "`D` is kept modest so the whole page runs in a couple of minutes; increasing it sharpens\n", + "the infinite-state diagnostics below (the finite-chain curve responds to `D` in a less\n", + "obvious way, as we will see)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "L = 16\n", + "D = 8\n", + "g_values = 0.1:0.1:2.0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Finite versus infinite magnetization\n", + "\n", + "We compute the order parameter $|\\langle\\sigma^z\\rangle|$ two ways at every field value.\n", + "\n", + "For the **finite** calculation we use an open chain of `L` sites, exactly as in the\n", + "tutorial, and optimize with `DMRG`.\n", + "We average $\\langle\\sigma^z_i\\rangle$ over the sites and take the absolute value: the\n", + "exact finite-`L` ground state is symmetric, but DMRG lands on one of the two\n", + "symmetry-broken states with an arbitrary sign (see the discussion in\n", + "Your first ground state)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ψ₀_finite = FiniteMPS(L, ℂ^2, ℂ^D)\n", + "M_finite = map(g_values) do g\n", + " H = transverse_field_ising(FiniteChain(L); g = g)\n", + " ψ, = find_groundstate(ψ₀_finite, H, DMRG(; verbosity = 0))\n", + " return abs(sum(expectation_value(ψ, i => σᶻ()) for i in 1:L)) / L\n", + "end;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the **infinite** calculation we drop the lattice argument to build the Hamiltonian on\n", + "the infinite chain, use an `InfiniteMPS`, and optimize with `VUMPS`.\n", + "We keep every optimized infinite state, because we will reuse them for the entropy and\n", + "correlation-length diagnostics below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ψ₀_infinite = InfiniteMPS(ℂ^2, ℂ^D)\n", + "states_infinite = map(g_values) do g\n", + " H = transverse_field_ising(; g = g)\n", + " ψ, = find_groundstate(ψ₀_infinite, H, VUMPS(; verbosity = 0))\n", + " return ψ\n", + "end;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The order parameter of a translation-invariant state is just $\\langle\\sigma^z\\rangle$ on\n", + "a single site of the unit cell; we again take the absolute value, because on the ordered\n", + "side the infinite state settles into one of the two symmetry-broken ground states (see\n", + "The thermodynamic limit)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "M_infinite = [abs(expectation_value(ψ, 1 => σᶻ())) for ψ in states_infinite];" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plotting both curves in a single figure lets us compare them directly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "p_magnetization = plot(;\n", + " xlabel = \"g\", ylabel = \"|⟨σᶻ⟩|\", title = \"TFIM order parameter\", legend = :bottomleft\n", + ")\n", + "scatter!(p_magnetization, g_values, M_finite; label = \"finite chain, L = $L, D = $D\")\n", + "scatter!(p_magnetization, g_values, M_infinite; label = \"infinite, D = $D\")\n", + "vline!(p_magnetization, [1.0]; color = \"gray\", linestyle = :dash, label = \"g = 1\")\n", + "p_magnetization" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Both calculations agree deep in either phase, but near the transition they tell very different stories.\n", + "The infinite curve stays on its ordered branch essentially up to `g = 1` and then collapses: it locates the critical point cleanly.\n", + "The finite-chain curve instead drops to zero far earlier — at this `L` and `D` the variational optimum on the open chain switches from the symmetry-broken branch to the exactly symmetric ground state, whose magnetization vanishes.\n", + "Where that switch happens is set by `L` and `D`, not by the physics; the same sweep at `D = 4` in Your first ground state puts it elsewhere.\n", + "That is the real lesson of this panel: the finite-chain order parameter is dominated by which state the algorithm selects, while the calculation performed directly in the thermodynamic limit pins the transition at `g = 1`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Entanglement entropy across the transition\n", + "\n", + "Entanglement is a hallmark of criticality: it is bounded away from the critical point but\n", + "grows sharply as we approach it.\n", + "For an `InfiniteMPS`, `entropy` returns the von Neumann entanglement entropy\n", + "per bond, one value for each site of the unit cell.\n", + "Our unit cell has a single site, so we take the one entry with `only`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "S_infinite = [real(only(entropy(ψ))) for ψ in states_infinite]\n", + "p_entropy = scatter(\n", + " g_values, S_infinite;\n", + " xlabel = \"g\", ylabel = \"entanglement entropy S\", title = \"TFIM entanglement entropy\",\n", + " legend = false\n", + ")\n", + "vline!(p_entropy, [1.0]; color = \"gray\", linestyle = :dash)\n", + "p_entropy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The entropy peaks near `g = 1`.\n", + "That peak is the entanglement signature of the phase transition: at criticality\n", + "correlations become long-ranged and the ground state is at its most entangled, whereas deep\n", + "in either phase the state is closer to a simple product and the entropy is small." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Correlation length across the transition\n", + "\n", + "The `correlation_length` measures how far apart two spins can still influence each\n", + "other; it is extracted from the transfer-matrix spectrum of the uniform infinite state and\n", + "has no finite-chain analogue.\n", + "It grows toward criticality, so we plot it on a logarithmic vertical axis to make the\n", + "growth visible." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ξ_infinite = [correlation_length(ψ) for ψ in states_infinite]\n", + "p_xi = scatter(\n", + " g_values, ξ_infinite;\n", + " xlabel = \"g\", ylabel = \"correlation length ξ\", yscale = :log10,\n", + " title = \"TFIM correlation length\", legend = false\n", + ")\n", + "vline!(p_xi, [1.0]; color = \"gray\", linestyle = :dash)\n", + "p_xi" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The correlation length peaks near `g = 1` as well.\n", + "At a genuine critical point it would diverge, but a finite bond dimension `D` can only\n", + "capture correlations out to a finite range, so what we measure is large-but-capped rather\n", + "than infinite — the peak grows and sharpens as `D` is increased." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What you now have\n", + "\n", + "Three independent diagnostics — the order parameter, the entanglement entropy, and the\n", + "correlation length — all locate the transition of the transverse-field Ising model near\n", + "`g = 1`, and the finite-versus-infinite comparison shows concretely why the thermodynamic\n", + "limit is the right place to measure it.\n", + "\n", + "From here the gallery goes further.\n", + "The Ising CFT example extracts the momentum-resolved excitation spectrum right at\n", + "criticality and matches it to the predictions of conformal field theory, turning the \"there\n", + "is a critical point near `g = 1`\" of this page into a quantitative fingerprint of *which*\n", + "critical theory it is.\n", + "Every curve on this page also sharpens if you rerun it at a larger bond dimension `D`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.12.6", + "language": "julia", + "name": "julia-1.12" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 3 +} \ No newline at end of file diff --git a/docs/src/examples/quantum1d/1.ising-cft/figure-1.png b/docs/src/examples/quantum1d/1.ising-cft/figure-1.png index ec004e5a0..dc4c4239a 100644 Binary files a/docs/src/examples/quantum1d/1.ising-cft/figure-1.png and b/docs/src/examples/quantum1d/1.ising-cft/figure-1.png differ diff --git a/docs/src/examples/quantum1d/1.ising-cft/figure-3.png b/docs/src/examples/quantum1d/1.ising-cft/figure-3.png index 8f76b9208..8b55ec3c9 100644 Binary files a/docs/src/examples/quantum1d/1.ising-cft/figure-3.png and b/docs/src/examples/quantum1d/1.ising-cft/figure-3.png differ diff --git a/docs/src/examples/quantum1d/1.ising-cft/index.md b/docs/src/examples/quantum1d/1.ising-cft/index.md index f634da5ff..43b819d94 100644 --- a/docs/src/examples/quantum1d/1.ising-cft/index.md +++ b/docs/src/examples/quantum1d/1.ising-cft/index.md @@ -126,24 +126,24 @@ append!(momenta, fix_degeneracies(states[17:18])) ```` 18-element Vector{Float64}: - 1.0963150642957372e-17 - -2.4157081442786943e-17 - 9.150251499481629e-18 - -0.523598775598299 - 0.5235987755982987 - -1.0471975511965979 + -6.835457747908734e-17 + -1.5823223400041525e-17 + -2.117254204162227e-17 + -0.5235987755982994 + 0.5235987755982988 + -1.047197551196598 1.0471975511965976 - 0.5235987755982989 - -0.5235987755982993 - 1.047197551196598 + 0.5235987755982993 + -0.5235987755982985 -1.0471975511965976 - 1.4597368636872088e-17 + 1.0471975511965976 + -1.3705449793358471e-17 + 1.570796326794897 -1.5707963267948966 - 1.5707963267948963 + -1.0471975511965979 1.0471975511965976 - -1.0471975511965976 - -1.570796326794897 - 1.5707963267948963 + 1.5707963267948968 + -1.5707963267948966 ```` We can compute the scaling dimensions $\Delta_n$ of the operators in the CFT from the @@ -177,53 +177,7 @@ can reach higher system sizes. L_mps = 20 H_mps = periodic_boundary_conditions(transverse_field_ising(), L_mps) D = 64 -ψ, envs, δ = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG()); -```` - -```` -[ Info: DMRG init: obj = -1.946908612087e+01 err = 7.7434e-02 -[ Info: DMRG 1: obj = -2.549098951719e+01 err = 8.0439536934e-03 time = 2.57 sec -[ Info: DMRG 2: obj = -2.549098968635e+01 err = 1.0703227324e-06 time = 0.80 sec -[ Info: DMRG 3: obj = -2.549098968636e+01 err = 1.4373447563e-07 time = 0.98 sec -[ Info: DMRG 4: obj = -2.549098968636e+01 err = 1.4665972881e-08 time = 0.42 sec -[ Info: DMRG 5: obj = -2.549098968636e+01 err = 6.8081026722e-09 time = 0.44 sec -[ Info: DMRG 6: obj = -2.549098968636e+01 err = 3.7573810815e-09 time = 0.43 sec -[ Info: DMRG 7: obj = -2.549098968636e+01 err = 2.5698292651e-09 time = 0.43 sec -[ Info: DMRG 8: obj = -2.549098968636e+01 err = 2.0113551709e-09 time = 0.43 sec -[ Info: DMRG 9: obj = -2.549098968636e+01 err = 1.6427286008e-09 time = 0.89 sec -[ Info: DMRG 10: obj = -2.549098968636e+01 err = 1.3479784013e-09 time = 0.55 sec -[ Info: DMRG 11: obj = -2.549098968636e+01 err = 1.2769471445e-09 time = 0.44 sec -[ Info: DMRG 12: obj = -2.549098968636e+01 err = 1.4168057275e-09 time = 0.48 sec -[ Info: DMRG 13: obj = -2.549098968636e+01 err = 1.5595217750e-09 time = 0.42 sec -[ Info: DMRG 14: obj = -2.549098968636e+01 err = 1.6950091915e-09 time = 0.41 sec -[ Info: DMRG 15: obj = -2.549098968636e+01 err = 1.8105744613e-09 time = 0.40 sec -[ Info: DMRG 16: obj = -2.549098968636e+01 err = 1.8924908787e-09 time = 0.96 sec -[ Info: DMRG 17: obj = -2.549098968636e+01 err = 1.9288336151e-09 time = 0.49 sec -[ Info: DMRG 18: obj = -2.549098968636e+01 err = 1.9133807885e-09 time = 0.31 sec -[ Info: DMRG 19: obj = -2.549098968636e+01 err = 1.8713994972e-09 time = 0.38 sec -[ Info: DMRG 20: obj = -2.549098968636e+01 err = 1.7813737815e-09 time = 0.41 sec -[ Info: DMRG 21: obj = -2.549098968636e+01 err = 1.6542099689e-09 time = 0.40 sec -[ Info: DMRG 22: obj = -2.549098968636e+01 err = 1.5039369007e-09 time = 0.40 sec -[ Info: DMRG 23: obj = -2.549098968636e+01 err = 1.3441838671e-09 time = 0.90 sec -[ Info: DMRG 24: obj = -2.549098968636e+01 err = 1.1858446625e-09 time = 0.28 sec -[ Info: DMRG 25: obj = -2.549098968636e+01 err = 1.0362811206e-09 time = 0.34 sec -[ Info: DMRG 26: obj = -2.549098968636e+01 err = 8.9963099646e-10 time = 0.35 sec -[ Info: DMRG 27: obj = -2.549098968636e+01 err = 7.7760121034e-10 time = 0.41 sec -[ Info: DMRG 28: obj = -2.549098968636e+01 err = 6.7030150822e-10 time = 0.40 sec -[ Info: DMRG 29: obj = -2.549098968636e+01 err = 5.7691780289e-10 time = 0.41 sec -[ Info: DMRG 30: obj = -2.549098968636e+01 err = 4.9618146296e-10 time = 0.91 sec -[ Info: DMRG 31: obj = -2.549098968636e+01 err = 4.2666435281e-10 time = 0.36 sec -[ Info: DMRG 32: obj = -2.549098968636e+01 err = 3.6694816435e-10 time = 0.40 sec -[ Info: DMRG 33: obj = -2.549098968636e+01 err = 3.1571200436e-10 time = 0.36 sec -[ Info: DMRG 34: obj = -2.549098968636e+01 err = 2.7176974363e-10 time = 0.38 sec -[ Info: DMRG 35: obj = -2.549098968636e+01 err = 2.3407977700e-10 time = 0.40 sec -[ Info: DMRG 36: obj = -2.549098968636e+01 err = 2.0173966270e-10 time = 0.40 sec -[ Info: DMRG 37: obj = -2.549098968636e+01 err = 1.7397391951e-10 time = 0.40 sec -[ Info: DMRG 38: obj = -2.549098968636e+01 err = 1.5011934910e-10 time = 0.87 sec -[ Info: DMRG 39: obj = -2.549098968636e+01 err = 1.2961022917e-10 time = 0.35 sec -[ Info: DMRG 40: obj = -2.549098968636e+01 err = 1.1196457020e-10 time = 0.38 sec -[ Info: DMRG conv 41: obj = -2.549098968636e+01 err = 9.6771723038e-11 time = 22.14 sec - +ψ, envs, δ = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG(; verbosity = 0)); ```` Excitations on top of the ground state can be found through the use of the quasiparticle diff --git a/docs/src/examples/quantum1d/1.ising-cft/main.ipynb b/docs/src/examples/quantum1d/1.ising-cft/main.ipynb index 54e1b0ed7..e21dc19cc 100644 --- a/docs/src/examples/quantum1d/1.ising-cft/main.ipynb +++ b/docs/src/examples/quantum1d/1.ising-cft/main.ipynb @@ -197,7 +197,7 @@ "L_mps = 20\n", "H_mps = periodic_boundary_conditions(transverse_field_ising(), L_mps)\n", "D = 64\n", - "ψ, envs, δ = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG());" + "ψ, envs, δ = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG(; verbosity = 0));" ] }, { diff --git a/docs/src/examples/quantum1d/3.ising-dqpt/index.md b/docs/src/examples/quantum1d/3.ising-dqpt/index.md index c21cd3b4c..c92c48efa 100644 --- a/docs/src/examples/quantum1d/3.ising-dqpt/index.md +++ b/docs/src/examples/quantum1d/3.ising-dqpt/index.md @@ -35,18 +35,7 @@ First we construct the Hamiltonian in MPO form, and obtain the pre-quenched grou L = 20 H₀ = transverse_field_ising(FiniteChain(L); g = -0.5) ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10) -ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG()); -```` - -```` -[ Info: DMRG init: obj = +9.979013604153e+00 err = 1.4988e-01 -[ Info: DMRG 1: obj = -2.040021714911e+01 err = 6.6274818897e-04 time = 4.21 sec -[ Info: DMRG 2: obj = -2.040021715179e+01 err = 4.7025708686e-07 time = 0.30 sec -[ Info: DMRG 3: obj = -2.040021786572e+01 err = 3.1050733385e-05 time = 0.09 sec -[ Info: DMRG 4: obj = -2.040021786702e+01 err = 1.7208246127e-06 time = 0.04 sec -[ Info: DMRG 5: obj = -2.040021786703e+01 err = 3.5080300899e-08 time = 0.04 sec -[ Info: DMRG conv 6: obj = -2.040021786703e+01 err = 3.6868374475e-11 time = 4.71 sec - +ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)); ```` ## Finite MPS quenching @@ -75,7 +64,7 @@ Putting it all together, we get function finite_sim(L; dt = 0.05, finaltime = 5.0) ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10) H₀ = transverse_field_ising(FiniteChain(L); g = -0.5) - ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG()) + ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)) H₁ = transverse_field_ising(FiniteChain(L); g = -2.0) ψₜ = deepcopy(ψ₀) @@ -107,19 +96,7 @@ Similarly we could start with an initial infinite state and find the pre-quench ````julia ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10]) H₀ = transverse_field_ising(; g = -0.5) -ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS()); -```` - -```` -[ Info: VUMPS init: obj = +4.970192050239e-01 err = 3.8858e-01 -[ Info: VUMPS 1: obj = -1.049521519045e+00 err = 9.6762771022e-02 time = 1.62 sec -[ Info: VUMPS 2: obj = -1.063544398670e+00 err = 1.0462983506e-04 time = 0.02 sec -[ Info: VUMPS 3: obj = -1.063544409966e+00 err = 3.0128180222e-06 time = 0.01 sec -[ Info: VUMPS 4: obj = -1.063544409973e+00 err = 5.4785900416e-08 time = 0.01 sec -[ Info: VUMPS 5: obj = -1.063544409973e+00 err = 3.5329191510e-09 time = 0.01 sec -[ Info: VUMPS 6: obj = -1.063544409973e+00 err = 3.7796484550e-10 time = 0.01 sec -[ Info: VUMPS conv 7: obj = -1.063544409973e+00 err = 2.9001138645e-11 time = 1.69 sec - +ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0)); ```` The dot product of two infinite matrix product states scales as ``\alpha ^N`` where ``α`` is the dominant eigenvalue of the transfer matrix. @@ -130,7 +107,7 @@ dot(ψ₀, ψ₀) ```` ```` -0.9999999999999996 + 3.8955006105253705e-16im +1.0000000000000029 + 1.5212645660908792e-16im ```` so the Loschmidt echo takes on the pleasant form @@ -163,7 +140,7 @@ The final code is ````julia function infinite_sim(dt = 0.05, finaltime = 5.0) ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10]) - ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS()) + ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0)) ψₜ = deepcopy(ψ₀) envs = environments(ψₜ, H₁, ψₜ) diff --git a/docs/src/examples/quantum1d/3.ising-dqpt/main.ipynb b/docs/src/examples/quantum1d/3.ising-dqpt/main.ipynb index c2e843e0b..8ac2cfa3d 100644 --- a/docs/src/examples/quantum1d/3.ising-dqpt/main.ipynb +++ b/docs/src/examples/quantum1d/3.ising-dqpt/main.ipynb @@ -49,7 +49,7 @@ "L = 20\n", "H₀ = transverse_field_ising(FiniteChain(L); g = -0.5)\n", "ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10)\n", - "ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG());" + "ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0));" ] }, { @@ -108,7 +108,7 @@ "function finite_sim(L; dt = 0.05, finaltime = 5.0)\n", " ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10)\n", " H₀ = transverse_field_ising(FiniteChain(L); g = -0.5)\n", - " ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG())\n", + " ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0))\n", "\n", " H₁ = transverse_field_ising(FiniteChain(L); g = -2.0)\n", " ψₜ = deepcopy(ψ₀)\n", @@ -151,7 +151,7 @@ "source": [ "ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10])\n", "H₀ = transverse_field_ising(; g = -0.5)\n", - "ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS());" + "ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0));" ] }, { @@ -241,7 +241,7 @@ "source": [ "function infinite_sim(dt = 0.05, finaltime = 5.0)\n", " ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10])\n", - " ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS())\n", + " ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0))\n", "\n", " ψₜ = deepcopy(ψ₀)\n", " envs = environments(ψₜ, H₁, ψₜ)\n", diff --git a/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-1.png b/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-1.png index f5e4a5ec2..fe9135d84 100644 Binary files a/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-1.png and b/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-1.png differ diff --git a/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-2.png b/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-2.png index bd8dee700..c19958f0d 100644 Binary files a/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-2.png and b/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-2.png differ diff --git a/docs/src/examples/quantum1d/4.xxz-heisenberg/index.md b/docs/src/examples/quantum1d/4.xxz-heisenberg/index.md index a2d2159bf..5ade1179e 100644 --- a/docs/src/examples/quantum1d/4.xxz-heisenberg/index.md +++ b/docs/src/examples/quantum1d/4.xxz-heisenberg/index.md @@ -15,6 +15,13 @@ The necessary packages to follow this tutorial are: using MPSKit, MPSKitModels, TensorKit, Plots ```` +For reproducibility of this page, we fix the seed of the random number generator: + +````julia +using Random +Random.seed!(123); +```` + ## Failure First we should define the Hamiltonian we want to work with. @@ -54,212 +61,12 @@ state = InfiniteMPS(2, 20) The ground state can then be found by calling `find_groundstate`. ````julia -groundstate, cache, delta = find_groundstate(state, H, VUMPS()); +groundstate, cache, delta = find_groundstate(state, H, VUMPS(; verbosity = 1)); ```` ```` -[ Info: VUMPS init: obj = +2.499992657736e-01 err = 2.3659e-03 -[ Info: VUMPS 1: obj = -1.113720211054e-01 err = 3.4044919149e-01 time = 7.00 sec -[ Info: VUMPS 2: obj = -7.257521722207e-02 err = 3.7501747094e-01 time = 0.29 sec -[ Info: VUMPS 3: obj = -4.083234386194e-02 err = 4.0906387666e-01 time = 0.08 sec -[ Info: VUMPS 4: obj = -1.575841447054e-01 err = 3.6917648883e-01 time = 0.03 sec -[ Info: VUMPS 5: obj = -1.988657390936e-01 err = 3.6791056420e-01 time = 0.03 sec -[ Info: VUMPS 6: obj = -3.197413863086e-01 err = 3.3258645953e-01 time = 0.04 sec -[ Info: VUMPS 7: obj = -7.087435895207e-02 err = 3.8002416604e-01 time = 0.03 sec -[ Info: VUMPS 8: obj = -1.207743137267e-01 err = 3.8655168695e-01 time = 0.02 sec -[ Info: VUMPS 9: obj = -2.086746007253e-01 err = 3.8866997285e-01 time = 0.08 sec -[ Info: VUMPS 10: obj = -2.135149067167e-02 err = 3.9998107520e-01 time = 0.02 sec -[ Info: VUMPS 11: obj = +7.890476682466e-02 err = 3.9662516712e-01 time = 0.02 sec -[ Info: VUMPS 12: obj = +8.179378380698e-02 err = 3.6865902322e-01 time = 0.04 sec -[ Info: VUMPS 13: obj = -3.389058286591e-01 err = 3.3592003962e-01 time = 0.07 sec -[ Info: VUMPS 14: obj = -6.936331877678e-02 err = 4.1384132520e-01 time = 0.02 sec -[ Info: VUMPS 15: obj = -2.095570913077e-01 err = 3.7672687907e-01 time = 0.03 sec -[ Info: VUMPS 16: obj = -8.547010074293e-02 err = 4.0253817589e-01 time = 0.02 sec -[ Info: VUMPS 17: obj = -1.633131236641e-01 err = 3.7085752764e-01 time = 0.01 sec -[ Info: VUMPS 18: obj = -2.440483461804e-01 err = 3.5634709811e-01 time = 0.02 sec -[ Info: VUMPS 19: obj = -2.227348354782e-01 err = 3.6670163212e-01 time = 0.07 sec -[ Info: VUMPS 20: obj = -1.647291995688e-01 err = 3.9547116130e-01 time = 0.03 sec -[ Info: VUMPS 21: obj = -2.272417561286e-01 err = 3.6276621476e-01 time = 0.02 sec -[ Info: VUMPS 22: obj = -3.388919076523e-01 err = 3.1579827529e-01 time = 0.02 sec -[ Info: VUMPS 23: obj = -3.915604574764e-01 err = 2.5208735300e-01 time = 0.07 sec -[ Info: VUMPS 24: obj = -2.716952795096e-01 err = 3.4825332817e-01 time = 0.04 sec -[ Info: VUMPS 25: obj = -2.511746372453e-01 err = 3.6715895791e-01 time = 0.03 sec -[ Info: VUMPS 26: obj = -2.731262108428e-02 err = 3.8195721702e-01 time = 0.04 sec -[ Info: VUMPS 27: obj = -8.401004965257e-02 err = 4.1219830163e-01 time = 0.03 sec -[ Info: VUMPS 28: obj = -5.593112558648e-02 err = 3.7991678397e-01 time = 0.07 sec -[ Info: VUMPS 29: obj = -1.039939899261e-01 err = 4.1791684367e-01 time = 0.04 sec -[ Info: VUMPS 30: obj = +3.646055386088e-02 err = 3.6884640674e-01 time = 0.02 sec -[ Info: VUMPS 31: obj = -1.745961021141e-01 err = 3.7601676764e-01 time = 0.02 sec -[ Info: VUMPS 32: obj = +6.528773402406e-03 err = 3.9555076806e-01 time = 0.02 sec -[ Info: VUMPS 33: obj = -6.925592993288e-02 err = 3.7990669074e-01 time = 0.08 sec -[ Info: VUMPS 34: obj = -1.493711283015e-01 err = 3.9233769652e-01 time = 0.02 sec -[ Info: VUMPS 35: obj = -8.418199500312e-02 err = 3.6340380458e-01 time = 0.02 sec -[ Info: VUMPS 36: obj = -2.276410296255e-02 err = 3.7714183538e-01 time = 0.02 sec -[ Info: VUMPS 37: obj = -1.746306414275e-02 err = 4.3071643087e-01 time = 0.03 sec -[ Info: VUMPS 38: obj = -1.078530946206e-01 err = 4.0554246549e-01 time = 0.08 sec -[ Info: VUMPS 39: obj = -4.201824689181e-03 err = 3.8332228055e-01 time = 0.03 sec -[ Info: VUMPS 40: obj = -1.695196954544e-01 err = 4.0646048881e-01 time = 0.03 sec -[ Info: VUMPS 41: obj = -3.991123357010e-01 err = 2.6404691766e-01 time = 0.03 sec -[ Info: VUMPS 42: obj = -3.731927178774e-04 err = 4.0618023904e-01 time = 0.02 sec -[ Info: VUMPS 43: obj = -1.361859934319e-01 err = 3.8083035475e-01 time = 0.07 sec -[ Info: VUMPS 44: obj = -4.351467677687e-02 err = 3.6067072767e-01 time = 0.01 sec -[ Info: VUMPS 45: obj = +2.036960334903e-02 err = 4.0236374802e-01 time = 0.02 sec -[ Info: VUMPS 46: obj = -2.564786609196e-01 err = 3.6550194687e-01 time = 0.02 sec -[ Info: VUMPS 47: obj = -2.794914053758e-02 err = 3.8554065921e-01 time = 0.03 sec -[ Info: VUMPS 48: obj = -1.456926586076e-01 err = 3.8815324507e-01 time = 0.02 sec -[ Info: VUMPS 49: obj = -1.424477861718e-02 err = 3.7358256943e-01 time = 0.06 sec -[ Info: VUMPS 50: obj = -4.121215827673e-02 err = 3.7267277980e-01 time = 0.02 sec -[ Info: VUMPS 51: obj = -3.082552784588e-01 err = 3.3162188490e-01 time = 0.02 sec -[ Info: VUMPS 52: obj = -2.583717562047e-01 err = 3.6414628453e-01 time = 0.03 sec -[ Info: VUMPS 53: obj = +1.080952855580e-01 err = 3.5691712848e-01 time = 0.02 sec -[ Info: VUMPS 54: obj = -2.388241333550e-01 err = 3.6671504144e-01 time = 0.06 sec -[ Info: VUMPS 55: obj = -2.158989432818e-01 err = 3.5252639302e-01 time = 0.02 sec -[ Info: VUMPS 56: obj = -2.846028253281e-01 err = 3.4280944784e-01 time = 0.02 sec -[ Info: VUMPS 57: obj = -2.844144756572e-01 err = 3.5771158117e-01 time = 0.04 sec -[ Info: VUMPS 58: obj = -3.296915296031e-01 err = 3.4124297389e-01 time = 0.07 sec -[ Info: VUMPS 59: obj = -3.428588110821e-01 err = 3.1055543978e-01 time = 0.05 sec -[ Info: VUMPS 60: obj = -3.689645262669e-01 err = 2.6709254795e-01 time = 0.04 sec -[ Info: VUMPS 61: obj = -1.926296113920e-01 err = 3.9051008708e-01 time = 0.04 sec -[ Info: VUMPS 62: obj = -2.083013559580e-01 err = 3.8360309501e-01 time = 0.09 sec -[ Info: VUMPS 63: obj = -2.330751889106e-01 err = 3.5874442855e-01 time = 0.04 sec -[ Info: VUMPS 64: obj = -1.170346482791e-01 err = 3.8356655434e-01 time = 0.03 sec -[ Info: VUMPS 65: obj = -5.200963578316e-02 err = 3.6833174183e-01 time = 0.03 sec -[ Info: VUMPS 66: obj = -2.197891431095e-02 err = 4.0124794245e-01 time = 0.02 sec -[ Info: VUMPS 67: obj = -1.169084572818e-01 err = 3.9681625944e-01 time = 0.07 sec -[ Info: VUMPS 68: obj = -3.187497224181e-01 err = 3.5389927614e-01 time = 0.03 sec -[ Info: VUMPS 69: obj = -5.774837074080e-02 err = 4.0555259098e-01 time = 0.05 sec -[ Info: VUMPS 70: obj = -1.385925677140e-01 err = 3.5902899521e-01 time = 0.02 sec -[ Info: VUMPS 71: obj = -1.618893406115e-01 err = 3.7624779484e-01 time = 0.02 sec -[ Info: VUMPS 72: obj = -1.501259513086e-01 err = 3.9017543257e-01 time = 0.08 sec -[ Info: VUMPS 73: obj = -2.344578406890e-01 err = 3.7629861390e-01 time = 0.02 sec -[ Info: VUMPS 74: obj = -9.559035249277e-02 err = 3.7722338357e-01 time = 0.02 sec -[ Info: VUMPS 75: obj = -3.559605422071e-01 err = 3.0031220801e-01 time = 0.02 sec -[ Info: VUMPS 76: obj = -1.954426659903e-01 err = 3.7999614851e-01 time = 0.02 sec -[ Info: VUMPS 77: obj = -2.469742715213e-01 err = 3.5932526830e-01 time = 0.07 sec -[ Info: VUMPS 78: obj = +1.378130557437e-02 err = 3.7598886038e-01 time = 0.02 sec -[ Info: VUMPS 79: obj = -1.211793491665e-02 err = 3.6307721624e-01 time = 0.02 sec -[ Info: VUMPS 80: obj = -1.280208560452e-01 err = 4.0740026676e-01 time = 0.03 sec -[ Info: VUMPS 81: obj = -2.377846434769e-01 err = 3.7798079849e-01 time = 0.02 sec -[ Info: VUMPS 82: obj = -3.480513403032e-01 err = 3.1094392423e-01 time = 0.06 sec -[ Info: VUMPS 83: obj = -3.231666531453e-01 err = 3.5795237816e-01 time = 0.03 sec -[ Info: VUMPS 84: obj = -3.230617723979e-01 err = 3.5338137372e-01 time = 0.08 sec -[ Info: VUMPS 85: obj = -1.391043781959e-01 err = 3.7699469945e-01 time = 0.02 sec -[ Info: VUMPS 86: obj = -3.434600465182e-01 err = 3.1162050408e-01 time = 0.08 sec -[ Info: VUMPS 87: obj = -2.896970610423e-01 err = 3.4715325464e-01 time = 0.03 sec -[ Info: VUMPS 88: obj = -3.473500689819e-01 err = 3.0203627103e-01 time = 0.03 sec -[ Info: VUMPS 89: obj = -3.920852749773e-01 err = 2.4261851491e-01 time = 0.04 sec -[ Info: VUMPS 90: obj = -2.323120265885e-03 err = 3.9363699208e-01 time = 0.08 sec -[ Info: VUMPS 91: obj = +6.597719659994e-03 err = 3.6444377682e-01 time = 0.02 sec -[ Info: VUMPS 92: obj = +2.842708237132e-02 err = 3.8036913669e-01 time = 0.02 sec -[ Info: VUMPS 93: obj = +8.866987029294e-02 err = 3.6998433554e-01 time = 0.03 sec -[ Info: VUMPS 94: obj = -5.099903063388e-02 err = 3.6527413192e-01 time = 0.02 sec -[ Info: VUMPS 95: obj = -1.724607412056e-01 err = 3.6271311572e-01 time = 0.06 sec -[ Info: VUMPS 96: obj = -1.359620364521e-01 err = 3.7163521224e-01 time = 0.03 sec -[ Info: VUMPS 97: obj = -2.487229212367e-01 err = 3.5559482532e-01 time = 0.03 sec -[ Info: VUMPS 98: obj = -3.363768011366e-01 err = 3.0691942472e-01 time = 0.02 sec -[ Info: VUMPS 99: obj = -2.349190816289e-01 err = 3.8040603712e-01 time = 0.07 sec -[ Info: VUMPS 100: obj = -2.501766365990e-01 err = 3.3899797906e-01 time = 0.02 sec -[ Info: VUMPS 101: obj = -3.944006951029e-01 err = 2.5365404140e-01 time = 0.02 sec -[ Info: VUMPS 102: obj = -2.995031953737e-02 err = 3.9078970987e-01 time = 0.03 sec -[ Info: VUMPS 103: obj = -1.496727054580e-01 err = 3.8887820541e-01 time = 0.03 sec -[ Info: VUMPS 104: obj = -1.651964515037e-01 err = 3.8482074585e-01 time = 0.06 sec -[ Info: VUMPS 105: obj = -2.136080092702e-01 err = 3.7086717496e-01 time = 0.02 sec -[ Info: VUMPS 106: obj = +2.076780492509e-03 err = 3.7971063787e-01 time = 0.02 sec -[ Info: VUMPS 107: obj = -1.801409094443e-01 err = 3.7203262354e-01 time = 0.02 sec -[ Info: VUMPS 108: obj = -2.824910448641e-01 err = 3.5387251865e-01 time = 0.02 sec -[ Info: VUMPS 109: obj = -1.603436420431e-01 err = 3.9841222680e-01 time = 0.07 sec -[ Info: VUMPS 110: obj = +2.717881863352e-02 err = 3.9583169447e-01 time = 0.02 sec -[ Info: VUMPS 111: obj = +3.649162587405e-02 err = 3.2986973402e-01 time = 0.03 sec -[ Info: VUMPS 112: obj = -1.931191472770e-01 err = 3.8832602682e-01 time = 0.02 sec -[ Info: VUMPS 113: obj = -8.595612263509e-02 err = 3.9456614625e-01 time = 0.02 sec -[ Info: VUMPS 114: obj = -2.052894924418e-01 err = 3.7916182480e-01 time = 0.08 sec -[ Info: VUMPS 115: obj = -1.566623408265e-01 err = 3.7572169212e-01 time = 0.02 sec -[ Info: VUMPS 116: obj = -2.697925193842e-01 err = 3.5224872666e-01 time = 0.03 sec -[ Info: VUMPS 117: obj = -1.653547727749e-01 err = 3.6580445508e-01 time = 0.02 sec -[ Info: VUMPS 118: obj = -3.099498644207e-01 err = 3.3813063847e-01 time = 0.02 sec -[ Info: VUMPS 119: obj = -1.249709134848e-01 err = 3.9297526101e-01 time = 0.07 sec -[ Info: VUMPS 120: obj = -1.759792403391e-01 err = 3.9286117967e-01 time = 0.03 sec -[ Info: VUMPS 121: obj = -1.531238685880e-01 err = 3.7741746395e-01 time = 0.02 sec -[ Info: VUMPS 122: obj = -2.607981777457e-02 err = 4.0319496740e-01 time = 0.03 sec -[ Info: VUMPS 123: obj = -6.592234489034e-02 err = 4.0591425677e-01 time = 0.06 sec -[ Info: VUMPS 124: obj = -6.634520512325e-02 err = 3.5661784954e-01 time = 0.02 sec -[ Info: VUMPS 125: obj = -8.713375234031e-02 err = 3.8753642064e-01 time = 0.03 sec -[ Info: VUMPS 126: obj = -1.125202000113e-01 err = 3.9947548666e-01 time = 0.02 sec -[ Info: VUMPS 127: obj = -7.695667111350e-02 err = 3.8370863324e-01 time = 0.02 sec -[ Info: VUMPS 128: obj = -1.626394492619e-01 err = 3.6111268034e-01 time = 0.06 sec -[ Info: VUMPS 129: obj = -3.049948899402e-01 err = 3.4403324168e-01 time = 0.02 sec -[ Info: VUMPS 130: obj = -1.258602194339e-01 err = 3.9449464476e-01 time = 0.03 sec -[ Info: VUMPS 131: obj = -1.104762253188e-01 err = 3.7940891591e-01 time = 0.03 sec -[ Info: VUMPS 132: obj = +1.961617781097e-01 err = 3.5498814846e-01 time = 0.04 sec -[ Info: VUMPS 133: obj = -5.045117087438e-02 err = 3.9852508192e-01 time = 0.06 sec -[ Info: VUMPS 134: obj = -1.456650620925e-01 err = 3.7944737703e-01 time = 0.02 sec -[ Info: VUMPS 135: obj = -8.263613090518e-02 err = 3.9519353274e-01 time = 0.02 sec -[ Info: VUMPS 136: obj = -1.765342573210e-01 err = 3.8095369610e-01 time = 0.02 sec -[ Info: VUMPS 137: obj = -1.324964043868e-01 err = 3.9563235756e-01 time = 0.02 sec -[ Info: VUMPS 138: obj = -5.801528053563e-03 err = 3.9895803654e-01 time = 0.07 sec -[ Info: VUMPS 139: obj = +3.411995954756e-02 err = 3.7915180484e-01 time = 0.02 sec -[ Info: VUMPS 140: obj = -5.243883424994e-02 err = 3.6892061143e-01 time = 0.02 sec -[ Info: VUMPS 141: obj = -2.240214467858e-01 err = 3.6441762460e-01 time = 0.02 sec -[ Info: VUMPS 142: obj = -2.299190776747e-01 err = 3.6831318301e-01 time = 0.03 sec -[ Info: VUMPS 143: obj = -1.957936901383e-01 err = 3.9355611293e-01 time = 0.06 sec -[ Info: VUMPS 144: obj = -3.444003971032e-01 err = 3.1223261647e-01 time = 0.02 sec -[ Info: VUMPS 145: obj = -4.206622251723e-01 err = 1.7712997133e-01 time = 0.03 sec -[ Info: VUMPS 146: obj = -3.152511465401e-01 err = 3.4248220688e-01 time = 0.04 sec -[ Info: VUMPS 147: obj = -2.523905778375e-01 err = 3.7740870895e-01 time = 0.07 sec -[ Info: VUMPS 148: obj = -4.278492688890e-02 err = 3.5715350579e-01 time = 0.02 sec -[ Info: VUMPS 149: obj = -1.324003340205e-01 err = 4.0013050853e-01 time = 0.02 sec -[ Info: VUMPS 150: obj = -1.068985749883e-01 err = 4.0722208139e-01 time = 0.02 sec -[ Info: VUMPS 151: obj = -2.083862397081e-01 err = 3.6731256501e-01 time = 0.02 sec -[ Info: VUMPS 152: obj = -1.440078983653e-01 err = 3.7426441705e-01 time = 0.06 sec -[ Info: VUMPS 153: obj = -2.520133076021e-01 err = 3.7507045206e-01 time = 0.02 sec -[ Info: VUMPS 154: obj = -1.034831359402e-01 err = 3.3452358556e-01 time = 0.02 sec -[ Info: VUMPS 155: obj = -6.618164126260e-02 err = 3.5135937268e-01 time = 0.03 sec -[ Info: VUMPS 156: obj = -8.742999239375e-02 err = 3.8534304068e-01 time = 0.02 sec -[ Info: VUMPS 157: obj = -1.259714263689e-01 err = 3.9638882368e-01 time = 0.07 sec -[ Info: VUMPS 158: obj = -9.181298201303e-02 err = 3.6918229891e-01 time = 0.02 sec -[ Info: VUMPS 159: obj = -3.681891440506e-01 err = 2.7888162226e-01 time = 0.02 sec -[ Info: VUMPS 160: obj = -3.357053644322e-01 err = 3.2800086170e-01 time = 0.03 sec -[ Info: VUMPS 161: obj = -3.215096951929e-03 err = 3.6664749423e-01 time = 0.07 sec -[ Info: VUMPS 162: obj = +2.646903240101e-03 err = 3.8676335475e-01 time = 0.02 sec -[ Info: VUMPS 163: obj = +7.709386278480e-02 err = 3.5056948614e-01 time = 0.01 sec -[ Info: VUMPS 164: obj = -1.446953476068e-01 err = 3.4180912676e-01 time = 0.02 sec -[ Info: VUMPS 165: obj = -3.581100223225e-01 err = 3.1025051513e-01 time = 0.02 sec -[ Info: VUMPS 166: obj = -2.406814929481e-01 err = 3.8072841015e-01 time = 0.07 sec -[ Info: VUMPS 167: obj = -3.531848910098e-01 err = 3.1053005603e-01 time = 0.02 sec -[ Info: VUMPS 168: obj = -3.297669874782e-01 err = 3.2648804299e-01 time = 0.03 sec -[ Info: VUMPS 169: obj = -1.800883981792e-01 err = 3.7239998435e-01 time = 0.03 sec -[ Info: VUMPS 170: obj = -4.056383454871e-02 err = 3.8301203586e-01 time = 0.03 sec -[ Info: VUMPS 171: obj = -4.448126315315e-02 err = 3.9634205142e-01 time = 0.07 sec -[ Info: VUMPS 172: obj = -3.109186702795e-01 err = 3.4936775901e-01 time = 0.02 sec -[ Info: VUMPS 173: obj = -1.687932549901e-01 err = 3.9713516798e-01 time = 0.02 sec -[ Info: VUMPS 174: obj = -1.703811557590e-01 err = 3.7182890670e-01 time = 0.02 sec -[ Info: VUMPS 175: obj = -1.360221590893e-01 err = 3.7750383659e-01 time = 0.02 sec -[ Info: VUMPS 176: obj = -1.746982448560e-01 err = 3.8728329904e-01 time = 0.07 sec -[ Info: VUMPS 177: obj = -9.378022642433e-02 err = 3.9508912967e-01 time = 0.02 sec -[ Info: VUMPS 178: obj = -1.283340267182e-01 err = 3.8629991015e-01 time = 0.02 sec -[ Info: VUMPS 179: obj = -8.976213980219e-02 err = 4.0495071985e-01 time = 0.02 sec -[ Info: VUMPS 180: obj = -2.669351107647e-01 err = 3.3526515364e-01 time = 0.07 sec -[ Info: VUMPS 181: obj = -3.341709529585e-02 err = 3.8011147844e-01 time = 0.02 sec -[ Info: VUMPS 182: obj = -3.109125460864e-01 err = 3.4567156611e-01 time = 0.03 sec -[ Info: VUMPS 183: obj = -2.183171915980e-01 err = 3.7747243244e-01 time = 0.02 sec -[ Info: VUMPS 184: obj = -2.372842132147e-01 err = 3.7374609557e-01 time = 0.03 sec -[ Info: VUMPS 185: obj = -2.543753578738e-01 err = 3.5816792692e-01 time = 0.07 sec -[ Info: VUMPS 186: obj = -2.469096804733e-01 err = 3.7131984627e-01 time = 0.03 sec -[ Info: VUMPS 187: obj = -1.830902139527e-01 err = 3.9043241343e-01 time = 0.02 sec -[ Info: VUMPS 188: obj = -1.574766929467e-01 err = 3.7319600051e-01 time = 0.03 sec -[ Info: VUMPS 189: obj = -6.991335970984e-02 err = 3.9222107611e-01 time = 0.07 sec -[ Info: VUMPS 190: obj = -1.792846142089e-01 err = 3.6356547438e-01 time = 0.02 sec -[ Info: VUMPS 191: obj = -1.225042448135e-01 err = 3.7360289247e-01 time = 0.02 sec -[ Info: VUMPS 192: obj = -2.129551247072e-01 err = 3.8299381445e-01 time = 0.02 sec -[ Info: VUMPS 193: obj = -8.283800637018e-02 err = 3.8500403241e-01 time = 0.02 sec -[ Info: VUMPS 194: obj = -2.897858859525e-01 err = 3.5007431093e-01 time = 0.06 sec -[ Info: VUMPS 195: obj = -5.369820439974e-02 err = 4.1837283102e-01 time = 0.02 sec -[ Info: VUMPS 196: obj = -2.800880298694e-01 err = 3.7189978262e-01 time = 0.03 sec -[ Info: VUMPS 197: obj = -1.669323399699e-01 err = 3.9481332800e-01 time = 0.03 sec -[ Info: VUMPS 198: obj = -6.212304176188e-02 err = 4.2526435790e-01 time = 0.03 sec -[ Info: VUMPS 199: obj = -4.204616169557e-02 err = 3.7541303170e-01 time = 0.07 sec -┌ Warning: VUMPS cancel 200: obj = -1.383333715979e-01 err = 3.7699700566e-01 time = 14.12 sec -└ @ MPSKit /home/ldevos/LocalProjects/MPSKit.jl/src/algorithms/groundstate/vumps.jl:76 +┌ Warning: VUMPS cancel 200: obj = +3.378309339760e-02 err = 3.6686092746e-01 time = 17.68 sec +└ @ MPSKit ~/Projects/MPSKit.jl/.claude/worktrees/docs-improvement-plan/src/algorithms/groundstate/vumps.jl:87 ```` @@ -268,13 +75,12 @@ On its own, that is already quite curious. Maybe we can do better using another algorithm, such as gradient descent. ````julia -groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20)); +groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20, verbosity = 1)); ```` ```` -[ Info: CG: initializing with f = 2.499992657736e-01, ‖∇f‖ = 1.6729e-03 -┌ Warning: CG: not converged to requested tol after 20 iterations and time 8.29 s: f = -4.426111048892e-01, ‖∇f‖ = 5.5480e-03 -└ @ OptimKit ~/.julia/packages/OptimKit/OEwMx/src/cg.jl:174 +┌ Warning: CG: not converged to requested tol after 20 iterations and time 6.96 s: f = -4.425861871014e-01, ‖∇f‖ = 6.7091e-03 +└ @ OptimKit ~/.julia/packages/OptimKit/S0cVY/src/cg.jl:174 ```` @@ -320,113 +126,13 @@ Alternatively, the Hamiltonian can be constructed directly on a two-site unit ce # H2 = repeat(H, 2); -- copies the one-site version H2 = heisenberg_XXX(ComplexF64, Trivial, InfiniteChain(2); spin = 1 // 2) groundstate, envs, delta = find_groundstate( - state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12) + state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12, verbosity = 1) ); ```` ```` -[ Info: VUMPS init: obj = +4.987085387825e-01 err = 7.4815e-02 -[ Info: VUMPS 1: obj = -4.070483296483e-02 err = 3.8421207912e-01 time = 0.42 sec -[ Info: VUMPS 2: obj = -8.588956105873e-01 err = 1.4311389089e-01 time = 0.04 sec -[ Info: VUMPS 3: obj = -8.846913958875e-01 err = 1.6288696714e-02 time = 0.03 sec -[ Info: VUMPS 4: obj = -8.858742904256e-01 err = 6.3271162473e-03 time = 0.03 sec -[ Info: VUMPS 5: obj = -8.860994898111e-01 err = 4.7506122055e-03 time = 0.07 sec -[ Info: VUMPS 6: obj = -8.861800643367e-01 err = 2.9027017342e-03 time = 0.04 sec -[ Info: VUMPS 7: obj = -8.862112267309e-01 err = 2.2025348252e-03 time = 0.05 sec -[ Info: VUMPS 8: obj = -8.862256494201e-01 err = 1.5437281695e-03 time = 0.08 sec -[ Info: VUMPS 9: obj = -8.862322270149e-01 err = 1.2285599184e-03 time = 0.05 sec -[ Info: VUMPS 10: obj = -8.862354505034e-01 err = 9.5416020728e-04 time = 0.09 sec -[ Info: VUMPS 11: obj = -8.862369964136e-01 err = 7.5140552439e-04 time = 0.06 sec -[ Info: VUMPS 12: obj = -8.862377649153e-01 err = 6.2947047843e-04 time = 0.06 sec -[ Info: VUMPS 13: obj = -8.862381482190e-01 err = 5.3567155770e-04 time = 0.08 sec -[ Info: VUMPS 14: obj = -8.862383501305e-01 err = 4.9726432030e-04 time = 0.05 sec -[ Info: VUMPS 15: obj = -8.862384635741e-01 err = 4.7373205998e-04 time = 0.04 sec -[ Info: VUMPS 16: obj = -8.862385365416e-01 err = 4.7834381662e-04 time = 0.08 sec -[ Info: VUMPS 17: obj = -8.862385914623e-01 err = 4.9010937476e-04 time = 0.06 sec -[ Info: VUMPS 18: obj = -8.862386397747e-01 err = 5.1562261789e-04 time = 0.09 sec -[ Info: VUMPS 19: obj = -8.862386887080e-01 err = 5.4370412689e-04 time = 0.07 sec -[ Info: VUMPS 20: obj = -8.862387403517e-01 err = 5.8200261169e-04 time = 0.06 sec -[ Info: VUMPS 21: obj = -8.862388009252e-01 err = 6.1775277631e-04 time = 0.11 sec -[ Info: VUMPS 22: obj = -8.862388688746e-01 err = 6.6580030857e-04 time = 0.06 sec -[ Info: VUMPS 23: obj = -8.862389551333e-01 err = 7.0184437921e-04 time = 0.06 sec -[ Info: VUMPS 24: obj = -8.862390538865e-01 err = 7.5533855251e-04 time = 0.09 sec -[ Info: VUMPS 25: obj = -8.862391870753e-01 err = 7.7812725098e-04 time = 0.08 sec -[ Info: VUMPS 26: obj = -8.862393378986e-01 err = 8.2535209132e-04 time = 0.10 sec -[ Info: VUMPS 27: obj = -8.862395466874e-01 err = 8.0853992627e-04 time = 0.05 sec -[ Info: VUMPS 28: obj = -8.862397673707e-01 err = 8.2479835919e-04 time = 0.06 sec -[ Info: VUMPS 29: obj = -8.862400528237e-01 err = 7.3793510189e-04 time = 0.07 sec -[ Info: VUMPS 30: obj = -8.862403146804e-01 err = 6.9781267895e-04 time = 0.05 sec -[ Info: VUMPS 31: obj = -8.862405935152e-01 err = 5.6033545341e-04 time = 0.06 sec -[ Info: VUMPS 32: obj = -8.862408144890e-01 err = 4.7977846041e-04 time = 0.09 sec -[ Info: VUMPS 33: obj = -8.862410054255e-01 err = 3.7069947399e-04 time = 0.06 sec -[ Info: VUMPS 34: obj = -8.862411519178e-01 err = 3.0069863398e-04 time = 0.08 sec -[ Info: VUMPS 35: obj = -8.862412707892e-01 err = 2.4560744504e-04 time = 0.06 sec -[ Info: VUMPS 36: obj = -8.862413665434e-01 err = 2.0272830003e-04 time = 0.06 sec -[ Info: VUMPS 37: obj = -8.862414453558e-01 err = 1.7669114346e-04 time = 0.09 sec -[ Info: VUMPS 38: obj = -8.862415101392e-01 err = 1.4966363352e-04 time = 0.06 sec -[ Info: VUMPS 39: obj = -8.862415632632e-01 err = 1.3429888391e-04 time = 0.09 sec -[ Info: VUMPS 40: obj = -8.862416064083e-01 err = 1.1453939487e-04 time = 0.06 sec -[ Info: VUMPS 41: obj = -8.862416411106e-01 err = 1.0327940645e-04 time = 0.09 sec -[ Info: VUMPS 42: obj = -8.862416687158e-01 err = 8.8267214320e-05 time = 0.06 sec -[ Info: VUMPS 43: obj = -8.862416904652e-01 err = 7.8860063320e-05 time = 0.06 sec -[ Info: VUMPS 44: obj = -8.862417074438e-01 err = 6.7541649023e-05 time = 0.08 sec -[ Info: VUMPS 45: obj = -8.862417205990e-01 err = 5.9561454565e-05 time = 0.04 sec -[ Info: VUMPS 46: obj = -8.862417307234e-01 err = 5.0969707803e-05 time = 0.08 sec -[ Info: VUMPS 47: obj = -8.862417384749e-01 err = 4.4565392480e-05 time = 0.05 sec -[ Info: VUMPS 48: obj = -8.862417443833e-01 err = 3.8093480996e-05 time = 0.06 sec -[ Info: VUMPS 49: obj = -8.862417488723e-01 err = 3.3125644419e-05 time = 0.07 sec -[ Info: VUMPS 50: obj = -8.862417522738e-01 err = 2.8298971982e-05 time = 0.06 sec -[ Info: VUMPS 51: obj = -8.862417548472e-01 err = 2.4528466837e-05 time = 0.10 sec -[ Info: VUMPS 52: obj = -8.862417567915e-01 err = 2.0959047274e-05 time = 0.04 sec -[ Info: VUMPS 53: obj = -8.862417582599e-01 err = 1.8137306177e-05 time = 0.03 sec -[ Info: VUMPS 54: obj = -8.862417593686e-01 err = 1.5513404897e-05 time = 0.06 sec -[ Info: VUMPS 55: obj = -8.862417602060e-01 err = 1.3420069417e-05 time = 0.05 sec -[ Info: VUMPS 56: obj = -8.862417608389e-01 err = 1.1498110058e-05 time = 0.08 sec -[ Info: VUMPS 57: obj = -8.862417613176e-01 err = 9.9527379200e-06 time = 0.05 sec -[ Info: VUMPS 58: obj = -8.862417616801e-01 err = 8.5468069101e-06 time = 0.09 sec -[ Info: VUMPS 59: obj = -8.862417619550e-01 err = 7.4079343001e-06 time = 0.04 sec -[ Info: VUMPS 60: obj = -8.862417621636e-01 err = 6.3789249797e-06 time = 0.05 sec -[ Info: VUMPS 61: obj = -8.862417623223e-01 err = 5.5392243963e-06 time = 0.08 sec -[ Info: VUMPS 62: obj = -8.862417624431e-01 err = 4.7843861025e-06 time = 0.05 sec -[ Info: VUMPS 63: obj = -8.862417625353e-01 err = 4.1638877981e-06 time = 0.09 sec -[ Info: VUMPS 64: obj = -8.862417626056e-01 err = 3.6081493661e-06 time = 0.06 sec -[ Info: VUMPS 65: obj = -8.862417626595e-01 err = 3.1478809644e-06 time = 0.06 sec -[ Info: VUMPS 66: obj = -8.862417627008e-01 err = 2.7367561575e-06 time = 0.09 sec -[ Info: VUMPS 67: obj = -8.862417627324e-01 err = 2.3936907479e-06 time = 0.06 sec -[ Info: VUMPS 68: obj = -8.862417627568e-01 err = 2.0878342869e-06 time = 0.09 sec -[ Info: VUMPS 69: obj = -8.862417627755e-01 err = 1.8307028023e-06 time = 0.07 sec -[ Info: VUMPS 70: obj = -8.862417627899e-01 err = 1.6017548673e-06 time = 0.04 sec -[ Info: VUMPS 71: obj = -8.862417628011e-01 err = 1.4078813339e-06 time = 0.08 sec -[ Info: VUMPS 72: obj = -8.862417628097e-01 err = 1.2353984722e-06 time = 0.05 sec -[ Info: VUMPS 73: obj = -8.862417628163e-01 err = 1.0883299189e-06 time = 0.09 sec -[ Info: VUMPS 74: obj = -8.862417628215e-01 err = 9.5754695615e-07 time = 0.06 sec -[ Info: VUMPS 75: obj = -8.862417628254e-01 err = 8.4531618087e-07 time = 0.06 sec -[ Info: VUMPS 76: obj = -8.862417628285e-01 err = 7.4553090808e-07 time = 0.08 sec -[ Info: VUMPS 77: obj = -8.862417628309e-01 err = 6.5939726315e-07 time = 0.05 sec -[ Info: VUMPS 78: obj = -8.862417628328e-01 err = 5.8281301292e-07 time = 0.06 sec -[ Info: VUMPS 79: obj = -8.862417628343e-01 err = 5.1635751307e-07 time = 0.05 sec -[ Info: VUMPS 80: obj = -8.862417628354e-01 err = 4.5725918727e-07 time = 0.05 sec -[ Info: VUMPS 81: obj = -8.862417628363e-01 err = 4.0573779114e-07 time = 0.10 sec -[ Info: VUMPS 82: obj = -8.862417628370e-01 err = 3.5990703959e-07 time = 0.05 sec -[ Info: VUMPS 83: obj = -8.862417628375e-01 err = 3.1978939846e-07 time = 0.09 sec -[ Info: VUMPS 84: obj = -8.862417628379e-01 err = 2.8408974871e-07 time = 0.06 sec -[ Info: VUMPS 85: obj = -8.862417628382e-01 err = 2.5273008172e-07 time = 0.06 sec -[ Info: VUMPS 86: obj = -8.862417628385e-01 err = 2.2481213666e-07 time = 0.08 sec -[ Info: VUMPS 87: obj = -8.862417628387e-01 err = 2.0021375141e-07 time = 0.06 sec -[ Info: VUMPS 88: obj = -8.862417628389e-01 err = 1.7830492076e-07 time = 0.08 sec -[ Info: VUMPS 89: obj = -8.862417628390e-01 err = 1.5895093159e-07 time = 0.06 sec -[ Info: VUMPS 90: obj = -8.862417628391e-01 err = 1.4170464909e-07 time = 0.05 sec -[ Info: VUMPS 91: obj = -8.862417628391e-01 err = 1.2643555469e-07 time = 0.08 sec -[ Info: VUMPS 92: obj = -8.862417628392e-01 err = 1.1282240133e-07 time = 0.06 sec -[ Info: VUMPS 93: obj = -8.862417628393e-01 err = 1.0074688630e-07 time = 0.08 sec -[ Info: VUMPS 94: obj = -8.862417628393e-01 err = 8.9975370500e-08 time = 0.04 sec -[ Info: VUMPS 95: obj = -8.862417628393e-01 err = 8.0404798877e-08 time = 0.06 sec -[ Info: VUMPS 96: obj = -8.862417628393e-01 err = 7.1863230951e-08 time = 0.08 sec -[ Info: VUMPS 97: obj = -8.862417628394e-01 err = 6.4263171589e-08 time = 0.05 sec -[ Info: VUMPS 98: obj = -8.862417628394e-01 err = 5.7476665744e-08 time = 0.07 sec -[ Info: VUMPS 99: obj = -8.862417628394e-01 err = 5.1430713814e-08 time = 0.04 sec -┌ Warning: VUMPS cancel 100: obj = -8.862417628394e-01 err = 4.6029104045e-08 time = 6.81 sec -└ @ MPSKit /home/ldevos/LocalProjects/MPSKit.jl/src/algorithms/groundstate/vumps.jl:76 +┌ Warning: VUMPS cancel 100: obj = -8.862417624752e-01 err = 4.2010389211e-06 time = 5.10 sec +└ @ MPSKit ~/Projects/MPSKit.jl/.claude/worktrees/docs-improvement-plan/src/algorithms/groundstate/vumps.jl:87 ```` @@ -435,7 +141,7 @@ The reason behind this becomes more obvious at higher bond dimensions: ````julia groundstate, envs, delta = find_groundstate( - state, H2, IDMRG2(; trscheme = truncrank(50), maxiter = 20, tol = 1.0e-12) + state, H2, IDMRG2(; trscheme = truncrank(50), maxiter = 20, tol = 1.0e-12, verbosity = 1) ); entanglementplot(groundstate) ```` @@ -476,7 +182,7 @@ state = InfiniteMPS([P, P], [V1, V2]); ```` ┌ Warning: Constructing an MPS from tensors that are not full rank -└ @ MPSKit /home/ldevos/LocalProjects/MPSKit.jl/src/states/infinitemps.jl:160 +└ @ MPSKit ~/Projects/MPSKit.jl/.claude/worktrees/docs-improvement-plan/src/states/infinitemps.jl:162 ```` @@ -485,80 +191,12 @@ Even though the bond dimension is higher than in the example without symmetry, c ````julia println(dim(V1)) println(dim(V2)) -groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12)); +groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12, verbosity = 1)); ```` ```` 52 70 -[ Info: VUMPS init: obj = +8.454690130663e-02 err = 3.6812e-01 -[ Info: VUMPS 1: obj = -8.807747096663e-01 err = 7.4524923622e-02 time = 3.77 sec -[ Info: VUMPS 2: obj = -8.858788324414e-01 err = 6.9171953600e-03 time = 0.05 sec -[ Info: VUMPS 3: obj = -8.861621536444e-01 err = 2.6767683452e-03 time = 0.04 sec -[ Info: VUMPS 4: obj = -8.862392626495e-01 err = 1.6032192901e-03 time = 0.04 sec -[ Info: VUMPS 5: obj = -8.862672547653e-01 err = 9.5323528320e-04 time = 0.05 sec -[ Info: VUMPS 6: obj = -8.862784830480e-01 err = 7.0061763044e-04 time = 0.05 sec -[ Info: VUMPS 7: obj = -8.862834114803e-01 err = 5.7030493713e-04 time = 0.06 sec -[ Info: VUMPS 8: obj = -8.862857129161e-01 err = 4.5154675114e-04 time = 0.15 sec -[ Info: VUMPS 9: obj = -8.862868209497e-01 err = 3.5140725914e-04 time = 0.05 sec -[ Info: VUMPS 10: obj = -8.862873648329e-01 err = 2.6806862728e-04 time = 0.13 sec -[ Info: VUMPS 11: obj = -8.862876338388e-01 err = 2.0070574932e-04 time = 0.11 sec -[ Info: VUMPS 12: obj = -8.862877672196e-01 err = 1.4816530119e-04 time = 0.07 sec -[ Info: VUMPS 13: obj = -8.862878333408e-01 err = 1.0821757653e-04 time = 0.15 sec -[ Info: VUMPS 14: obj = -8.862878660733e-01 err = 7.8417469213e-05 time = 0.06 sec -[ Info: VUMPS 15: obj = -8.862878822601e-01 err = 5.6494124158e-05 time = 0.07 sec -[ Info: VUMPS 16: obj = -8.862878902612e-01 err = 4.0537829957e-05 time = 0.06 sec -[ Info: VUMPS 17: obj = -8.862878942156e-01 err = 2.9004225089e-05 time = 0.07 sec -[ Info: VUMPS 18: obj = -8.862878961706e-01 err = 2.0708366147e-05 time = 0.07 sec -[ Info: VUMPS 19: obj = -8.862878971378e-01 err = 1.4762413368e-05 time = 0.13 sec -[ Info: VUMPS 20: obj = -8.862878976166e-01 err = 1.0511055800e-05 time = 0.03 sec -[ Info: VUMPS 21: obj = -8.862878978539e-01 err = 7.4778223881e-06 time = 0.03 sec -[ Info: VUMPS 22: obj = -8.862878979715e-01 err = 5.3158051331e-06 time = 0.07 sec -[ Info: VUMPS 23: obj = -8.862878980299e-01 err = 3.7764425487e-06 time = 0.07 sec -[ Info: VUMPS 24: obj = -8.862878980589e-01 err = 2.6814072095e-06 time = 0.14 sec -[ Info: VUMPS 25: obj = -8.862878980733e-01 err = 1.9030014616e-06 time = 0.06 sec -[ Info: VUMPS 26: obj = -8.862878980805e-01 err = 1.3500577199e-06 time = 0.06 sec -[ Info: VUMPS 27: obj = -8.862878980841e-01 err = 9.5735794398e-07 time = 0.06 sec -[ Info: VUMPS 28: obj = -8.862878980859e-01 err = 6.7863772480e-07 time = 0.06 sec -[ Info: VUMPS 29: obj = -8.862878980867e-01 err = 4.8090393886e-07 time = 0.14 sec -[ Info: VUMPS 30: obj = -8.862878980872e-01 err = 3.4067956729e-07 time = 0.06 sec -[ Info: VUMPS 31: obj = -8.862878980874e-01 err = 2.4127441738e-07 time = 0.05 sec -[ Info: VUMPS 32: obj = -8.862878980875e-01 err = 1.7082422697e-07 time = 0.05 sec -[ Info: VUMPS 33: obj = -8.862878980876e-01 err = 1.2091935762e-07 time = 0.06 sec -[ Info: VUMPS 34: obj = -8.862878980876e-01 err = 8.5574898934e-08 time = 0.05 sec -[ Info: VUMPS 35: obj = -8.862878980876e-01 err = 6.0549094354e-08 time = 0.13 sec -[ Info: VUMPS 36: obj = -8.862878980877e-01 err = 4.2833729305e-08 time = 0.05 sec -[ Info: VUMPS 37: obj = -8.862878980877e-01 err = 3.0296142094e-08 time = 0.06 sec -[ Info: VUMPS 38: obj = -8.862878980877e-01 err = 2.1424846144e-08 time = 0.06 sec -[ Info: VUMPS 39: obj = -8.862878980877e-01 err = 1.5148957750e-08 time = 0.06 sec -[ Info: VUMPS 40: obj = -8.862878980877e-01 err = 1.0709953737e-08 time = 0.12 sec -[ Info: VUMPS 41: obj = -8.862878980877e-01 err = 7.5707891268e-09 time = 0.03 sec -[ Info: VUMPS 42: obj = -8.862878980877e-01 err = 5.3510570418e-09 time = 0.04 sec -[ Info: VUMPS 43: obj = -8.862878980877e-01 err = 3.7817943502e-09 time = 0.07 sec -[ Info: VUMPS 44: obj = -8.862878980877e-01 err = 2.6724193960e-09 time = 0.06 sec -[ Info: VUMPS 45: obj = -8.862878980877e-01 err = 1.8882939261e-09 time = 0.07 sec -[ Info: VUMPS 46: obj = -8.862878980877e-01 err = 1.3341314150e-09 time = 0.11 sec -[ Info: VUMPS 47: obj = -8.862878980877e-01 err = 9.4252990942e-10 time = 0.06 sec -[ Info: VUMPS 48: obj = -8.862878980877e-01 err = 6.6582278922e-10 time = 0.06 sec -[ Info: VUMPS 49: obj = -8.862878980877e-01 err = 4.7032251942e-10 time = 0.06 sec -[ Info: VUMPS 50: obj = -8.862878980877e-01 err = 3.3220639408e-10 time = 0.06 sec -[ Info: VUMPS 51: obj = -8.862878980878e-01 err = 2.3463502230e-10 time = 0.12 sec -[ Info: VUMPS 52: obj = -8.862878980878e-01 err = 1.6571486183e-10 time = 0.05 sec -[ Info: VUMPS 53: obj = -8.862878980878e-01 err = 1.1703442495e-10 time = 0.05 sec -[ Info: VUMPS 54: obj = -8.862878980878e-01 err = 8.2650109655e-11 time = 0.06 sec -[ Info: VUMPS 55: obj = -8.862878980878e-01 err = 5.8367474734e-11 time = 0.05 sec -[ Info: VUMPS 56: obj = -8.862878980878e-01 err = 4.1213417017e-11 time = 0.12 sec -[ Info: VUMPS 57: obj = -8.862878980878e-01 err = 2.9101697547e-11 time = 0.04 sec -[ Info: VUMPS 58: obj = -8.862878980878e-01 err = 2.0551179926e-11 time = 0.06 sec -[ Info: VUMPS 59: obj = -8.862878980878e-01 err = 1.4510549999e-11 time = 0.06 sec -[ Info: VUMPS 60: obj = -8.862878980878e-01 err = 1.0245548104e-11 time = 0.05 sec -[ Info: VUMPS 61: obj = -8.862878980878e-01 err = 7.2325909689e-12 time = 0.06 sec -[ Info: VUMPS 62: obj = -8.862878980878e-01 err = 5.1092588216e-12 time = 0.12 sec -[ Info: VUMPS 63: obj = -8.862878980878e-01 err = 3.6043616497e-12 time = 0.02 sec -[ Info: VUMPS 64: obj = -8.862878980878e-01 err = 2.5462748087e-12 time = 0.04 sec -[ Info: VUMPS 65: obj = -8.862878980878e-01 err = 1.7984804673e-12 time = 0.03 sec -[ Info: VUMPS 66: obj = -8.862878980878e-01 err = 1.2696913652e-12 time = 0.02 sec -[ Info: VUMPS conv 67: obj = -8.862878980879e-01 err = 8.9456922075e-13 time = 8.25 sec ```` diff --git a/docs/src/examples/quantum1d/4.xxz-heisenberg/main.ipynb b/docs/src/examples/quantum1d/4.xxz-heisenberg/main.ipynb index 39316fad3..755a04371 100644 --- a/docs/src/examples/quantum1d/4.xxz-heisenberg/main.ipynb +++ b/docs/src/examples/quantum1d/4.xxz-heisenberg/main.ipynb @@ -2,182 +2,200 @@ "cells": [ { "cell_type": "markdown", + "metadata": {}, "source": [ "# The XXZ model\n", "\n", "In this file we will give step by step instructions on how to analyze the spin 1/2 XXZ model.\n", "The necessary packages to follow this tutorial are:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using MPSKit, MPSKitModels, TensorKit, Plots" - ], + ] + }, + { + "cell_type": "markdown", "metadata": {}, - "execution_count": null + "source": [ + "For reproducibility of this page, we fix the seed of the random number generator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "using Random\n", + "Random.seed!(123);" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Failure\n", "\n", "First we should define the Hamiltonian we want to work with.\n", "Then we specify an initial guess, which we then further optimize.\n", "Working directly in the thermodynamic limit, this is achieved as follows:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "H = heisenberg_XXX(; spin = 1 // 2)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We then need an initial state, which we shall later optimize. In this example we work directly in the thermodynamic limit." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "state = InfiniteMPS(2, 20)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "The ground state can then be found by calling `find_groundstate`." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", - "source": [ - "groundstate, cache, delta = find_groundstate(state, H, VUMPS());" - ], + "execution_count": null, "metadata": {}, - "execution_count": null + "outputs": [], + "source": [ + "groundstate, cache, delta = find_groundstate(state, H, VUMPS(; verbosity = 1));" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "As you can see, VUMPS struggles to converge.\n", "On its own, that is already quite curious.\n", "Maybe we can do better using another algorithm, such as gradient descent." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", - "source": [ - "groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20));" - ], + "execution_count": null, "metadata": {}, - "execution_count": null + "outputs": [], + "source": [ + "groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20, verbosity = 1));" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Convergence is quite slow and even fails after sufficiently many iterations.\n", "To understand why, we can look at the transfer matrix spectrum." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "transferplot(groundstate, groundstate)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We can clearly see multiple eigenvalues close to the unit circle.\n", "Our state is close to being non-injective, and represents the sum of multiple injective states.\n", "This is numerically very problematic, but also indicates that we used an incorrect ansatz to approximate the groundstate.\n", "We should retry with a larger unit cell." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Success\n", "\n", "Let's initialize a different initial state, this time with a 2-site unit cell:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "state = InfiniteMPS(fill(2, 2), fill(20, 2))" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "In MPSKit, we require that the periodicity of the Hamiltonian equals that of the state it is applied to.\n", "This is not a big obstacle, you can simply repeat the original Hamiltonian.\n", "Alternatively, the Hamiltonian can be constructed directly on a two-site unit cell by making use of MPSKitModels.jl's `@mpoham`." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# H2 = repeat(H, 2); -- copies the one-site version\n", "H2 = heisenberg_XXX(ComplexF64, Trivial, InfiniteChain(2); spin = 1 // 2)\n", "groundstate, envs, delta = find_groundstate(\n", - " state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12)\n", + " state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12, verbosity = 1)\n", ");" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We get convergence, but it takes an enormous amount of iterations.\n", "The reason behind this becomes more obvious at higher bond dimensions:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "groundstate, envs, delta = find_groundstate(\n", - " state, H2, IDMRG2(; trscheme = truncrank(50), maxiter = 20, tol = 1.0e-12)\n", + " state, H2, IDMRG2(; trscheme = truncrank(50), maxiter = 20, tol = 1.0e-12, verbosity = 1)\n", ");\n", "entanglementplot(groundstate)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We see that some eigenvalues clearly belong to a group, and are almost degenerate.\n", "This implies 2 things:\n", @@ -185,31 +203,31 @@ "- poor convergence if we cut off within such a subspace\n", "\n", "It are precisely those problems that we can solve by using symmetries." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Symmetries\n", "\n", "The XXZ Heisenberg Hamiltonian is SU(2) symmetric and we can exploit this to greatly speed up the simulation.\n", "\n", "It is cumbersome to construct symmetric Hamiltonians, but luckily SU(2) symmetric XXZ is already implemented:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "H2 = heisenberg_XXX(ComplexF64, SU2Irrep, InfiniteChain(2); spin = 1 // 2);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Our initial state should also be SU(2) symmetric.\n", "It now becomes apparent why we have to use a two-site periodic state.\n", @@ -218,62 +236,61 @@ "The staggering thus happens on the virtual level.\n", "\n", "An alternative constructor for the initial state is" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "P = Rep[SU₂](1 // 2 => 1)\n", "V1 = Rep[SU₂](1 // 2 => 10, 3 // 2 => 5, 5 // 2 => 2)\n", "V2 = Rep[SU₂](0 => 15, 1 => 10, 2 => 5)\n", "state = InfiniteMPS([P, P], [V1, V2]);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Even though the bond dimension is higher than in the example without symmetry, convergence is reached much faster:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "println(dim(V1))\n", "println(dim(V2))\n", - "groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12));" - ], - "metadata": {}, - "execution_count": null + "groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12, verbosity = 1));" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "---\n", "\n", "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" - ], - "metadata": {} + ] } ], - "nbformat_minor": 3, "metadata": { + "kernelspec": { + "display_name": "Julia 1.12.6", + "language": "julia", + "name": "julia-1.12" + }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.12.4" - }, - "kernelspec": { - "name": "julia-1.12", - "display_name": "Julia 1.12.4", - "language": "julia" + "version": "1.12.6" } }, - "nbformat": 4 + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/docs/src/examples/quantum1d/5.haldane-spt/figure-1.png b/docs/src/examples/quantum1d/5.haldane-spt/figure-1.png index f3641018e..31124b5a5 100644 Binary files a/docs/src/examples/quantum1d/5.haldane-spt/figure-1.png and b/docs/src/examples/quantum1d/5.haldane-spt/figure-1.png differ diff --git a/docs/src/examples/quantum1d/5.haldane-spt/figure-2.png b/docs/src/examples/quantum1d/5.haldane-spt/figure-2.png index 6091af86d..f9d22360f 100644 Binary files a/docs/src/examples/quantum1d/5.haldane-spt/figure-2.png and b/docs/src/examples/quantum1d/5.haldane-spt/figure-2.png differ diff --git a/docs/src/examples/quantum1d/5.haldane-spt/figure-3.png b/docs/src/examples/quantum1d/5.haldane-spt/figure-3.png index 8dfdfe224..42c179c9f 100644 Binary files a/docs/src/examples/quantum1d/5.haldane-spt/figure-3.png and b/docs/src/examples/quantum1d/5.haldane-spt/figure-3.png differ diff --git a/docs/src/examples/quantum1d/5.haldane-spt/index.md b/docs/src/examples/quantum1d/5.haldane-spt/index.md index 6f95d9769..28f90de21 100644 --- a/docs/src/examples/quantum1d/5.haldane-spt/index.md +++ b/docs/src/examples/quantum1d/5.haldane-spt/index.md @@ -128,7 +128,7 @@ E_plus = expectation_value(ψ_plus, H) ```` ```` --1.4014193313393009 - 5.2545708620134027e-17im +-1.4014193313393004 - 2.2233521403023605e-17im ```` ````julia @@ -139,7 +139,7 @@ E_minus = expectation_value(ψ_minus, H) ```` ```` --1.401483973963084 - 2.875923408269285e-17im +-1.4014839739630827 + 6.744598315147384e-17im ```` ````julia @@ -188,11 +188,12 @@ println("S_plus = $S_plus") ```` ```` -S_minus + log(2) = 1.5486227235421324 -S_plus = 1.5450323530571919 +S_minus + log(2) = 1.5486227235423025 +S_plus = 1.545032353055433 ```` --- *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/docs/src/examples/quantum1d/5.haldane-spt/main.ipynb b/docs/src/examples/quantum1d/5.haldane-spt/main.ipynb index a5c803849..f85c243c3 100644 --- a/docs/src/examples/quantum1d/5.haldane-spt/main.ipynb +++ b/docs/src/examples/quantum1d/5.haldane-spt/main.ipynb @@ -213,4 +213,4 @@ }, "nbformat": 4, "nbformat_minor": 3 -} +} \ No newline at end of file diff --git a/docs/src/examples/quantum1d/6.hubbard/figure-1.png b/docs/src/examples/quantum1d/6.hubbard/figure-1.png index 87a4f10f8..73f21dcec 100644 Binary files a/docs/src/examples/quantum1d/6.hubbard/figure-1.png and b/docs/src/examples/quantum1d/6.hubbard/figure-1.png differ diff --git a/docs/src/examples/quantum1d/6.hubbard/figure-2.png b/docs/src/examples/quantum1d/6.hubbard/figure-2.png index f28bef941..f6487a68d 100644 Binary files a/docs/src/examples/quantum1d/6.hubbard/figure-2.png and b/docs/src/examples/quantum1d/6.hubbard/figure-2.png differ diff --git a/docs/src/examples/quantum1d/6.hubbard/index.md b/docs/src/examples/quantum1d/6.hubbard/index.md index 38fa9bc75..f4fb35746 100644 --- a/docs/src/examples/quantum1d/6.hubbard/index.md +++ b/docs/src/examples/quantum1d/6.hubbard/index.md @@ -18,7 +18,7 @@ a kinetic term that allows electrons to hop between neighboring sites, and a pot Often, a third term is included which serves as a chemical potential to control the number of electrons in the system. ```math -H = -t \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + U \sum_i n_{i,\uparrow} n_{i,\downarrow} - \mu \sum_{i,\sigma} n_{i,\sigma} +H = -t ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U ∑_i n_{i,↑} n_{i,↓} - μ ∑_{i,σ} n_{i,σ} ``` At half-filling, the system exhibits particle-hole symmetry, which can be made explicit by rewriting the Hamiltonian slightly. @@ -26,13 +26,13 @@ First, we fix the overall energy scale by setting `t = 1`, and then shift the to This results in the following Hamiltonian: ```math -H = - \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + U / 4 \sum_i (1 - 2 n_{i,\uparrow}) (1 - 2 n_{i,\downarrow}) - \mu \sum_{i,\sigma} n_{i,\sigma} +H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U / 4 ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) - μ ∑_{i,σ} n_{i,σ} ``` Finally, setting `\mu = 0` and defining `u = U / 4` we obtain the Hubbard model at half-filling. ```math -H = - \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + u \sum_i (1 - 2 n_{i,\uparrow}) (1 - 2 n_{i,\downarrow}) +H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + u ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) ``` ````julia @@ -44,7 +44,13 @@ using QuadGK: quadgk using Plots using Interpolations using Optim +```` + +For reproducibility of this page, we fix the seed of the random number generator: +````julia +using Random +Random.seed!(123); const t = 1.0 const mu = 0.0 @@ -56,10 +62,10 @@ const U = 3.0 ```` For this case, the ground state energy has an analytic solution, which can be used to benchmark the numerical results. -It follows from Eq. (6.82) in [](). +It follows from Eq. (6.82) in [Essler, Frahm, Göhmann, Klümper & Korepin, The One-Dimensional Hubbard Model](https://doi.org/10.1017/CBO9780511534843). ```math -e(u) = - u - 4 \int_0^{\infty} \frac{d\omega}{\omega} \frac{J_0(\omega) J_1(\omega)}{1 + \exp(2u \omega)} +e(u) = - u - 4 ∫₀^{∞} \frac{dω}{ω} \frac{J₀(ω) J₁(ω)}{1 + \exp(2u ω)} ``` We can easily verify this by comparing the numerical results to the analytic solution. @@ -67,7 +73,7 @@ We can easily verify this by comparing the numerical results to the analytic sol ````julia function hubbard_energy(u; rtol = 1.0e-12) integrandum(ω) = besselj0(ω) * besselj1(ω) / (1 + exp(2u * ω)) / ω - int, err = quadgk(integrandum, 0, Inf; rtol = rtol) + int, err = quadgk(integrandum, 0, Inf; rtol) return -u - 4 * int end @@ -77,7 +83,7 @@ function compute_groundstate( expansionfactor = (1 / 10), expansioniter = 20 ) - verbosity = 2 + verbosity = 0 psi, = find_groundstate(psi, H; tol = svalue * 10, verbosity) for _ in 1:expansioniter D = maximum(x -> dim(left_virtualspace(psi, x)), 1:length(psi)) @@ -115,54 +121,10 @@ Groundstate energy: ```` ```` -[ Info: VUMPS init: obj = -1.450454615857e+00 err = 5.5193e-01 -[ Info: VUMPS conv 7: obj = -4.377048688339e+00 err = 8.8092806195e-03 time = 4.49 sec -[ Info: VUMPS init: obj = -4.377048688339e+00 err = 1.6440e-02 -[ Info: VUMPS conv 6: obj = -4.378747269347e+00 err = 1.3129004135e-04 time = 0.25 sec -[ Info: VUMPS init: obj = -4.378747269347e+00 err = 7.9951e-03 -[ Info: VUMPS conv 6: obj = -4.379161081627e+00 err = 1.5539751336e-04 time = 0.37 sec -[ Info: VUMPS init: obj = -4.379161081627e+00 err = 6.1111e-03 -[ Info: VUMPS conv 5: obj = -4.379452169384e+00 err = 1.6927654674e-04 time = 0.27 sec -[ Info: VUMPS init: obj = -4.379452169384e+00 err = 5.6959e-03 -[ Info: VUMPS conv 4: obj = -4.379651733231e+00 err = 1.8162083040e-04 time = 0.26 sec -[ Info: VUMPS init: obj = -4.379651733231e+00 err = 4.1039e-03 -[ Info: VUMPS conv 4: obj = -4.379735601762e+00 err = 1.3801495045e-04 time = 0.42 sec -[ Info: VUMPS init: obj = -4.379735601762e+00 err = 3.5769e-03 -[ Info: VUMPS conv 3: obj = -4.379797886653e+00 err = 1.3472741143e-04 time = 0.39 sec -[ Info: VUMPS init: obj = -4.379797886653e+00 err = 2.7707e-03 -[ Info: VUMPS conv 2: obj = -4.379838526805e+00 err = 1.7752552389e-04 time = 0.33 sec -[ Info: VUMPS init: obj = -4.379838526805e+00 err = 2.7291e-03 -[ Info: VUMPS conv 3: obj = -4.379878849406e+00 err = 1.9781894590e-04 time = 0.74 sec -[ Info: VUMPS init: obj = -4.379878849406e+00 err = 2.6911e-03 -[ Info: VUMPS conv 3: obj = -4.379929229387e+00 err = 1.7761427615e-04 time = 0.82 sec -[ Info: VUMPS init: obj = -4.379929229387e+00 err = 2.5553e-03 -[ Info: VUMPS conv 3: obj = -4.379968040382e+00 err = 1.8461546636e-04 time = 2.21 sec -[ Info: VUMPS init: obj = -4.379968040382e+00 err = 1.7682e-03 -[ Info: VUMPS conv 2: obj = -4.379986877757e+00 err = 1.9131369028e-04 time = 0.98 sec -[ Info: VUMPS init: obj = -4.379986877757e+00 err = 1.5838e-03 -[ Info: VUMPS conv 2: obj = -4.380001005486e+00 err = 1.9231335759e-04 time = 1.05 sec -[ Info: VUMPS init: obj = -4.380001005486e+00 err = 1.5109e-03 -[ Info: VUMPS conv 2: obj = -4.380013169634e+00 err = 1.5225084116e-04 time = 1.32 sec -[ Info: VUMPS init: obj = -4.380013169634e+00 err = 1.4234e-03 -[ Info: VUMPS conv 2: obj = -4.380024401012e+00 err = 1.7737882775e-04 time = 1.59 sec -[ Info: VUMPS init: obj = -4.380024401012e+00 err = 1.3330e-03 -[ Info: VUMPS conv 2: obj = -4.380038158990e+00 err = 1.5757417636e-04 time = 2.60 sec -[ Info: VUMPS init: obj = -4.380038158990e+00 err = 1.0032e-03 -[ Info: VUMPS conv 1: obj = -4.380043682260e+00 err = 1.6736593859e-04 time = 0.89 sec -[ Info: VUMPS init: obj = -4.380043682260e+00 err = 9.0999e-04 -[ Info: VUMPS conv 1: obj = -4.380048641018e+00 err = 1.8573996574e-04 time = 1.19 sec -[ Info: VUMPS init: obj = -4.380048641018e+00 err = 8.3081e-04 -[ Info: VUMPS conv 1: obj = -4.380053199895e+00 err = 1.8060836975e-04 time = 2.30 sec -[ Info: VUMPS init: obj = -4.380053199895e+00 err = 6.8144e-04 -[ Info: VUMPS conv 1: obj = -4.380057143242e+00 err = 1.8854132138e-04 time = 1.71 sec -[ Info: VUMPS init: obj = -4.380057143242e+00 err = 6.0293e-04 -[ Info: VUMPS conv 1: obj = -4.380060551312e+00 err = 1.8083344266e-04 time = 2.45 sec -[ Info: VUMPS init: obj = -4.379609468445e+00 err = 4.0958e-03 -[ Info: VUMPS conv 19: obj = -4.379763157256e+00 err = 9.9415625365e-06 time = 8.41 sec -[ Info: CG: initializing with f = -4.379763156901e+00, ‖∇f‖ = 3.1520e-05 -[ Info: CG: converged after 158 iterations and time 1.36 m: f = -4.379763361376e+00, ‖∇f‖ = 9.9957e-07 +[ Info: CG: initializing with f = -4.379763091095e+00, ‖∇f‖ = 3.1534e-05 +[ Info: CG: converged after 167 iterations and time 1.75 m: f = -4.379763081900e+00, ‖∇f‖ = 9.7406e-07 ┌ Info: Groundstate energy: -│ * numerical: -2.1899960609769664 +│ * numerical: -2.189996060974577 └ * analytic: -2.190038374277775 ```` @@ -170,7 +132,7 @@ Groundstate energy: ## Symmetries The Hubbard model has a rich symmetry structure, which can be exploited to speed up simulations. -Apart from the fermionic parity, the model also has a $U(1)$ particle number symmetry, along with a $SU(2)$ spin symmetry. +Apart from the fermionic parity, the model also has a ``U(1)`` particle number symmetry, along with a ``SU(2)`` spin symmetry. Explicitly imposing these symmetries on the tensors can greatly reduce the computational cost of the simulation. Naively imposing these symmetries however, is not compatible with our desire to work at half-filling. @@ -196,56 +158,10 @@ Groundstate energy: ```` ```` -[ Info: VUMPS init: obj = +2.092499297284e-01 err = 8.6283e-01 -[ Info: VUMPS conv 1: obj = -4.000000000000e+00 err = 1.4030299342e-15 time = 2.40 sec -[ Info: VUMPS init: obj = -4.000000000000e+00 err = 3.3634e-01 -[ Info: VUMPS conv 4: obj = -4.289650419749e+00 err = 1.8514003381e-04 time = 0.09 sec -[ Info: VUMPS init: obj = -4.289650419749e+00 err = 1.1203e-01 -[ Info: VUMPS conv 6: obj = -4.359865567620e+00 err = 1.0046942911e-04 time = 0.29 sec -[ Info: VUMPS init: obj = -4.359865567619e+00 err = 4.3643e-02 -[ Info: VUMPS conv 6: obj = -4.372880928482e+00 err = 1.3025843115e-04 time = 2.61 sec -[ Info: VUMPS init: obj = -4.372880928482e+00 err = 3.2693e-02 -[ Info: VUMPS conv 4: obj = -4.375236954488e+00 err = 1.1814239608e-04 time = 0.20 sec -[ Info: VUMPS init: obj = -4.375236954488e+00 err = 2.9487e-02 -[ Info: VUMPS conv 7: obj = -4.378159084364e+00 err = 1.1896740056e-04 time = 0.60 sec -[ Info: VUMPS init: obj = -4.378159084364e+00 err = 1.9312e-02 -[ Info: VUMPS conv 5: obj = -4.379272966040e+00 err = 1.5785413165e-04 time = 0.50 sec -[ Info: VUMPS init: obj = -4.379272966040e+00 err = 9.9128e-03 -[ Info: VUMPS conv 4: obj = -4.379592229143e+00 err = 1.5550378745e-04 time = 0.51 sec -[ Info: VUMPS init: obj = -4.379592229143e+00 err = 6.4841e-03 -[ Info: VUMPS conv 4: obj = -4.379819377264e+00 err = 1.7492038571e-04 time = 0.56 sec -[ Info: VUMPS init: obj = -4.379819377264e+00 err = 3.8754e-03 -┌ Warning: VUMPS cancel 10: obj = -4.379964033305e+00 err = 2.1228930049e-04 time = 1.76 sec -└ @ MPSKit ~/Projects/MPSKit.jl/docs/src/algorithms/groundstate/vumps.jl:83 -[ Info: VUMPS init: obj = -4.379964033305e+00 err = 2.8978e-03 -[ Info: VUMPS conv 3: obj = -4.380010384710e+00 err = 1.4775284542e-04 time = 0.88 sec -[ Info: VUMPS init: obj = -4.380010384710e+00 err = 2.0609e-03 -[ Info: VUMPS conv 3: obj = -4.380041751503e+00 err = 1.6327798118e-04 time = 1.81 sec -[ Info: VUMPS init: obj = -4.380041751502e+00 err = 1.2364e-03 -[ Info: VUMPS conv 2: obj = -4.380055778759e+00 err = 1.8366845284e-04 time = 0.83 sec -[ Info: VUMPS init: obj = -4.380055778759e+00 err = 8.5857e-04 -[ Info: VUMPS conv 2: obj = -4.380064749427e+00 err = 1.3905442267e-04 time = 1.14 sec -[ Info: VUMPS init: obj = -4.380064749427e+00 err = 5.2502e-04 -[ Info: VUMPS conv 1: obj = -4.380067974777e+00 err = 1.5646700070e-04 time = 0.79 sec -[ Info: VUMPS init: obj = -4.380067974777e+00 err = 3.3275e-04 -[ Info: VUMPS conv 1: obj = -4.380070351418e+00 err = 1.3123916502e-04 time = 1.05 sec -[ Info: VUMPS init: obj = -4.380070351418e+00 err = 2.0348e-04 -[ Info: VUMPS conv 1: obj = -4.380072125256e+00 err = 1.1119707628e-04 time = 2.15 sec -[ Info: VUMPS init: obj = -4.380072125256e+00 err = 1.3635e-04 -[ Info: VUMPS conv 1: obj = -4.380073467831e+00 err = 8.5045032311e-05 time = 2.22 sec -[ Info: VUMPS init: obj = -4.380073467830e+00 err = 9.7226e-05 -[ Info: VUMPS conv 1: obj = -4.380074455763e+00 err = 6.4430026631e-05 time = 3.60 sec -[ Info: VUMPS init: obj = -4.380074455763e+00 err = 7.3787e-05 -[ Info: VUMPS conv 1: obj = -4.380075159887e+00 err = 6.2144398833e-05 time = 8.05 sec -[ Info: VUMPS init: obj = -4.380075159887e+00 err = 5.9899e-05 -[ Info: VUMPS conv 1: obj = -4.380075661721e+00 err = 4.2515939994e-05 time = 12.11 sec -[ Info: VUMPS init: obj = -4.379308795201e+00 err = 7.9930e-03 -┌ Warning: VUMPS cancel 100: obj = -4.379692711472e+00 err = 1.5979764572e-05 time = 27.91 sec -└ @ MPSKit ~/Projects/MPSKit.jl/docs/src/algorithms/groundstate/vumps.jl:83 [ Info: CG: initializing with f = -4.379692711472e+00, ‖∇f‖ = 5.7923e-05 -[ Info: CG: converged after 13 iterations and time 7.22 s: f = -4.379692712393e+00, ‖∇f‖ = 6.2087e-07 +[ Info: CG: converged after 13 iterations and time 14.35 s: f = -4.379692712393e+00, ‖∇f‖ = 6.2087e-07 ┌ Info: Groundstate energy: -│ * numerical: -2.1900153475144695 +│ * numerical: -2.190015347514472 └ * analytic: -2.190038374277775 ```` @@ -257,7 +173,7 @@ The elementary excitations are known as spinons and holons, which are domain wal The fact that the spin and charge sectors are separate is a phenomenon known as spin-charge separation. The domain walls can be constructed by noticing that there are two equivalent groundstates, which differ by a translation over a single site. -In other words, the groundstates are ``\psi_{AB}` and ``\psi_{BA}``, where ``A`` and ``B`` are the two sites. +In other words, the groundstates are ``\psi_{AB}`` and ``\psi_{BA}``, where ``A`` and ``B`` are the two sites. These excitations can be constructed as follows: ````julia @@ -288,37 +204,37 @@ E_holon, ϕ_holon = excitations( [ Info: Found excitations for momentum = -2.552544031041707 [ Info: Found excitations for momentum = -2.356194490192345 [ Info: Found excitations for momentum = -2.1598449493429825 -[ Info: Found excitations for momentum = -1.7671458676442586 [ Info: Found excitations for momentum = -1.9634954084936207 +[ Info: Found excitations for momentum = -1.7671458676442586 [ Info: Found excitations for momentum = -1.5707963267948966 [ Info: Found excitations for momentum = -1.3744467859455345 -[ Info: Found excitations for momentum = -0.9817477042468103 [ Info: Found excitations for momentum = -1.1780972450961724 +[ Info: Found excitations for momentum = -0.9817477042468103 [ Info: Found excitations for momentum = -0.7853981633974483 [ Info: Found excitations for momentum = -0.5890486225480862 -[ Info: Found excitations for momentum = -0.19634954084936207 [ Info: Found excitations for momentum = -0.39269908169872414 +[ Info: Found excitations for momentum = -0.19634954084936207 [ Info: Found excitations for momentum = 0.0 [ Info: Found excitations for momentum = 0.19634954084936207 [ Info: Found excitations for momentum = 0.39269908169872414 [ Info: Found excitations for momentum = 0.5890486225480862 [ Info: Found excitations for momentum = 0.7853981633974483 [ Info: Found excitations for momentum = 0.9817477042468103 -[ Info: Found excitations for momentum = 1.3744467859455345 [ Info: Found excitations for momentum = 1.1780972450961724 +[ Info: Found excitations for momentum = 1.3744467859455345 [ Info: Found excitations for momentum = 1.5707963267948966 [ Info: Found excitations for momentum = 1.7671458676442586 -[ Info: Found excitations for momentum = 2.356194490192345 [ Info: Found excitations for momentum = 1.9634954084936207 [ Info: Found excitations for momentum = 2.1598449493429825 +[ Info: Found excitations for momentum = 2.356194490192345 [ Info: Found excitations for momentum = 2.552544031041707 [ Info: Found excitations for momentum = 2.748893571891069 [ Info: Found excitations for momentum = 2.945243112740431 [ Info: Found excitations for momentum = 3.141592653589793 [ Info: Found excitations for momentum = -3.141592653589793 +[ Info: Found excitations for momentum = -2.945243112740431 [ Info: Found excitations for momentum = -2.748893571891069 [ Info: Found excitations for momentum = -2.552544031041707 -[ Info: Found excitations for momentum = -2.945243112740431 [ Info: Found excitations for momentum = -2.356194490192345 [ Info: Found excitations for momentum = -2.1598449493429825 [ Info: Found excitations for momentum = -1.9634954084936207 @@ -346,8 +262,8 @@ E_holon, ϕ_holon = excitations( [ Info: Found excitations for momentum = 2.356194490192345 [ Info: Found excitations for momentum = 2.552544031041707 [ Info: Found excitations for momentum = 2.748893571891069 -[ Info: Found excitations for momentum = 3.141592653589793 [ Info: Found excitations for momentum = 2.945243112740431 +[ Info: Found excitations for momentum = 3.141592653589793 ```` @@ -405,7 +321,7 @@ end The plot shows some discrepancies between the numerical and analytic results. First and foremost, we must realize that in the thermodynamic limit, the momentum of a domain wall is actually not well-defined. Concretely, only the difference in momentum between the two groundstates is well-defined, as we can always shift the momentum by multiplying one of the groundstates by a phase. -Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor ``\pi/2``. +Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor ``π/2``. ````julia momenta_shifted = rem2pi.(momenta .- π / 2, RoundNearest) diff --git a/docs/src/examples/quantum1d/6.hubbard/main.ipynb b/docs/src/examples/quantum1d/6.hubbard/main.ipynb index 4371e2364..a204f45d3 100644 --- a/docs/src/examples/quantum1d/6.hubbard/main.ipynb +++ b/docs/src/examples/quantum1d/6.hubbard/main.ipynb @@ -21,7 +21,7 @@ "Often, a third term is included which serves as a chemical potential to control the number of electrons in the system.\n", "\n", "$$\n", - "H = -t \\sum_{\\langle i, j \\rangle, \\sigma} c^{\\dagger}_{i,\\sigma} c_{j,\\sigma} + U \\sum_i n_{i,\\uparrow} n_{i,\\downarrow} - \\mu \\sum_{i,\\sigma} n_{i,\\sigma}\n", + "H = -t ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U ∑_i n_{i,↑} n_{i,↓} - μ ∑_{i,σ} n_{i,σ}\n", "$$\n", "\n", "At half-filling, the system exhibits particle-hole symmetry, which can be made explicit by rewriting the Hamiltonian slightly.\n", @@ -29,13 +29,13 @@ "This results in the following Hamiltonian:\n", "\n", "$$\n", - "H = - \\sum_{\\langle i, j \\rangle, \\sigma} c^{\\dagger}_{i,\\sigma} c_{j,\\sigma} + U / 4 \\sum_i (1 - 2 n_{i,\\uparrow}) (1 - 2 n_{i,\\downarrow}) - \\mu \\sum_{i,\\sigma} n_{i,\\sigma}\n", + "H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U / 4 ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) - μ ∑_{i,σ} n_{i,σ}\n", "$$\n", "\n", "Finally, setting `\\mu = 0` and defining `u = U / 4` we obtain the Hubbard model at half-filling.\n", "\n", "$$\n", - "H = - \\sum_{\\langle i, j \\rangle, \\sigma} c^{\\dagger}_{i,\\sigma} c_{j,\\sigma} + u \\sum_i (1 - 2 n_{i,\\uparrow}) (1 - 2 n_{i,\\downarrow})\n", + "H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + u ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓})\n", "$$" ] }, @@ -52,8 +52,24 @@ "using QuadGK: quadgk\n", "using Plots\n", "using Interpolations\n", - "using Optim\n", - "\n", + "using Optim" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For reproducibility of this page, we fix the seed of the random number generator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "using Random\n", + "Random.seed!(123);\n", "\n", "const t = 1.0\n", "const mu = 0.0\n", @@ -65,10 +81,10 @@ "metadata": {}, "source": [ "For this case, the ground state energy has an analytic solution, which can be used to benchmark the numerical results.\n", - "It follows from Eq. (6.82) in []().\n", + "It follows from Eq. (6.82) in [Essler, Frahm, Göhmann, Klümper & Korepin, The One-Dimensional Hubbard Model](https://doi.org/10.1017/CBO9780511534843).\n", "\n", "$$\n", - "e(u) = - u - 4 \\int_0^{\\infty} \\frac{d\\omega}{\\omega} \\frac{J_0(\\omega) J_1(\\omega)}{1 + \\exp(2u \\omega)}\n", + "e(u) = - u - 4 ∫₀^{∞} \\frac{dω}{ω} \\frac{J₀(ω) J₁(ω)}{1 + \\exp(2u ω)}\n", "$$\n", "\n", "We can easily verify this by comparing the numerical results to the analytic solution." @@ -82,7 +98,7 @@ "source": [ "function hubbard_energy(u; rtol = 1.0e-12)\n", " integrandum(ω) = besselj0(ω) * besselj1(ω) / (1 + exp(2u * ω)) / ω\n", - " int, err = quadgk(integrandum, 0, Inf; rtol = rtol)\n", + " int, err = quadgk(integrandum, 0, Inf; rtol)\n", " return -u - 4 * int\n", "end\n", "\n", @@ -92,7 +108,7 @@ " expansionfactor = (1 / 10),\n", " expansioniter = 20\n", " )\n", - " verbosity = 2\n", + " verbosity = 0\n", " psi, = find_groundstate(psi, H; tol = svalue * 10, verbosity)\n", " for _ in 1:expansioniter\n", " D = maximum(x -> dim(left_virtualspace(psi, x)), 1:length(psi))\n", @@ -178,7 +194,7 @@ "The fact that the spin and charge sectors are separate is a phenomenon known as spin-charge separation.\n", "\n", "The domain walls can be constructed by noticing that there are two equivalent groundstates, which differ by a translation over a single site.\n", - "In other words, the groundstates are $\\psi_{AB}` and $\\psi_{BA}$, where $A$ and $B$ are the two sites.\n", + "In other words, the groundstates are $\\psi_{AB}$ and $\\psi_{BA}$, where $A$ and $B$ are the two sites.\n", "These excitations can be constructed as follows:" ] }, @@ -274,7 +290,7 @@ "The plot shows some discrepancies between the numerical and analytic results.\n", "First and foremost, we must realize that in the thermodynamic limit, the momentum of a domain wall is actually not well-defined.\n", "Concretely, only the difference in momentum between the two groundstates is well-defined, as we can always shift the momentum by multiplying one of the groundstates by a phase.\n", - "Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor $\\pi/2$." + "Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor $π/2$." ] }, { diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-1.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-1.png index da451a216..be692709a 100644 Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-1.png and b/docs/src/examples/quantum1d/7.xy-finiteT/figure-1.png differ diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-2.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-2.png index 94ec71854..508b94015 100644 Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-2.png and b/docs/src/examples/quantum1d/7.xy-finiteT/figure-2.png differ diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-3.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-3.png index 9daa46501..9df578602 100644 Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-3.png and b/docs/src/examples/quantum1d/7.xy-finiteT/figure-3.png differ diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-4.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-4.png index e15988a40..701d842e0 100644 Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-4.png and b/docs/src/examples/quantum1d/7.xy-finiteT/figure-4.png differ diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-5.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-5.png index 733496a0c..cfd09fdbc 100644 Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-5.png and b/docs/src/examples/quantum1d/7.xy-finiteT/figure-5.png differ diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-6.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-6.png index c2680aed3..16501b948 100644 Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-6.png and b/docs/src/examples/quantum1d/7.xy-finiteT/figure-6.png differ diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/index.md b/docs/src/examples/quantum1d/7.xy-finiteT/index.md index 264ebade7..b05342a1e 100644 --- a/docs/src/examples/quantum1d/7.xy-finiteT/index.md +++ b/docs/src/examples/quantum1d/7.xy-finiteT/index.md @@ -66,8 +66,7 @@ The Hamiltonian can be diagonalized in terms of fermionic creation and annihilat E_0 = -\frac{1}{\pi} \text{EllipticE}\left( \sqrt{1 - \gamma^2} \right) ``` -!!! todo - Show the derivation of the ground state energy by diagonalizing the Hamiltonian in terms of fermionic operators. +The derivation, via a Jordan-Wigner transformation to free fermions followed by a Bogoliubov rotation, can be found in [Lieb, Schultz & Mattis, Ann. Phys. 16, 407 (1961)](https://doi.org/10.1016/0003-4916(61)90115-4). ````julia function groundstate_energy(J, N) @@ -125,11 +124,11 @@ println("Exact (N=Inf):\t", groundstate_energy(J, Inf)) ```` ```` -[ Info: DMRG2 1: obj = -5.004084801485e+00 err = 9.7485774328e-01 time = 1.43 min -[ Info: DMRG2 2: obj = -5.004096940647e+00 err = 1.1899230994e-06 time = 1.27 sec -[ Info: DMRG2 3: obj = -5.004096975044e+00 err = 2.2262868216e-09 time = 0.80 sec -[ Info: DMRG2 conv 4: obj = -5.004096975044e+00 err = 1.1612932838e-13 time = 1.47 min -Numerical: -0.15637803047010942 +[ Info: DMRG2 1: obj = -5.004084869795e+00 err = 9.8826020061e-01 time = 1.04 min +[ Info: DMRG2 2: obj = -5.004096937587e+00 err = 1.1541480959e-06 time = 0.42 sec +[ Info: DMRG2 3: obj = -5.004096975044e+00 err = 2.5015824967e-09 time = 0.72 sec +[ Info: DMRG2 conv 4: obj = -5.004096975044e+00 err = 1.8118839762e-13 time = 1.07 min +Numerical: -0.15637803047010948 Exact (N=32): -0.15637803047254015 Exact (N=Inf): -0.15915494309189535 @@ -173,8 +172,7 @@ The resulting expression is Z(\beta) = \prod_{k=1}^{N} \left( 1 + e^{-\beta \epsilon_k} \right)^{1/N} ``` -!!! todo - Show the derivation of the partition function for the XY model. +This expression follows from the same free-fermion diagonalization as the ground-state energy above: each single-particle mode $\epsilon_k$ is independently occupied or empty, giving the usual free-fermion partition function (see again [Lieb, Schultz & Mattis (1961)](https://doi.org/10.1016/0003-4916(61)90115-4)). ````julia function partition_function(β::Number, J::Number, N::Number) @@ -308,9 +306,6 @@ Z(\beta) = In other words, we can compute the partition function at $\beta$ by computing the overlap of two states evolved for $\beta / 2$, as long as the Hamiltonian is Hermitian. Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^\dagger$. -!!! todo - Add a figure to illustrate this trick. - ````julia double_logpartition(ρ₁, ρ₂ = ρ₁) = log(real(dot(ρ₁, ρ₂))) / length(ρ₁) diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/main.ipynb b/docs/src/examples/quantum1d/7.xy-finiteT/main.ipynb index c6a7df3ce..b6633cbb1 100644 --- a/docs/src/examples/quantum1d/7.xy-finiteT/main.ipynb +++ b/docs/src/examples/quantum1d/7.xy-finiteT/main.ipynb @@ -1,8 +1,10 @@ { "cells": [ { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Markdown\n", "using TensorKit\n", @@ -14,12 +16,11 @@ "using Plots\n", "using LinearAlgebra\n", "using BenchmarkFreeFermions" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Finite temperature XY model\n", "\n", @@ -33,19 +34,20 @@ "$$\n", "\n", "Here we will consider the anti-ferromagnetic ($J > 0$) chain, and restrict ourselves to $J = 1/2$." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Parameters" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "J = 1 / 2\n", "T = ComplexF64\n", @@ -63,12 +65,11 @@ " end\n", " end\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Diagonalization of the Hamiltonian\n", "\n", @@ -79,15 +80,14 @@ " E_0 = -\\frac{1}{\\pi} \\text{EllipticE}\\left( \\sqrt{1 - \\gamma^2} \\right)\n", "$$\n", "\n", - "> **Todo**\n", - ">\n", - "> Show the derivation of the ground state energy by diagonalizing the Hamiltonian in terms of fermionic operators." - ], - "metadata": {} + "The derivation, via a Jordan-Wigner transformation to free fermions followed by a Bogoliubov rotation, can be found in [Lieb, Schultz & Mattis, Ann. Phys. 16, 407 (1961)](https://doi.org/10.1016/0003-4916(61)90115-4)." + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "function groundstate_energy(J, N)\n", " isfinite(N) || return -J / π\n", @@ -95,22 +95,22 @@ " ϵ = SingleParticleSpectrum(T)\n", " return Energy(ϵ, Inf, 0) / N\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exact diagonalization\n", "\n", "We can check our results by comparing them to the exact diagonalization of the Hamiltonian." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "N_exact = 6\n", "H = open_boundary_conditions(XY_hamiltonian(T, symmetry; J, N = Inf), N_exact)\n", @@ -121,22 +121,22 @@ "println(\"Numerical:\\t\", minimum(real(vals)))\n", "println(\"Exact (N=$(N_exact)):\\t\", groundstate_energy(J, N_exact))\n", "println(\"Exact (N=Inf):\\t\", groundstate_energy(J, Inf))" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Finite MPS\n", "\n", "If we wish to increase the system size, we can use the finite MPS representation." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "N = 32\n", "H = XY_hamiltonian(T, symmetry; J, N)\n", @@ -150,12 +150,11 @@ "println(\"Numerical:\\t\", real(E_0))\n", "println(\"Exact (N=$N):\\t\", groundstate_energy(J, N))\n", "println(\"Exact (N=Inf):\\t\", groundstate_energy(J, Inf))" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Finite temperature properties\n", "\n", @@ -195,15 +194,14 @@ " Z(\\beta) = \\prod_{k=1}^{N} \\left( 1 + e^{-\\beta \\epsilon_k} \\right)^{1/N}\n", "$$\n", "\n", - "> **Todo**\n", - ">\n", - "> Show the derivation of the partition function for the XY model." - ], - "metadata": {} + "This expression follows from the same free-fermion diagonalization as the ground-state energy above: each single-particle mode $\\epsilon_k$ is independently occupied or empty, giving the usual free-fermion partition function (see again [Lieb, Schultz & Mattis (1961)](https://doi.org/10.1016/0003-4916(61)90115-4))." + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "function partition_function(β::Number, J::Number, N::Number)\n", " T = diagm(1 => J / 2 * ones(N - 1), -1 => J / 2 * ones(N - 1))\n", @@ -220,12 +218,11 @@ "\n", "Z_analytic = partition_function.(βs, J, N);\n", "F_analytic = free_energy.(βs, J, N);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### MPO approach\n", "\n", @@ -235,12 +232,13 @@ "In order to build the time-evolution operator, we can repurpose the `make_time_mpo` function, which constructs the time-evolution operator for the ground state.\n", "However, since we are interested in $e^{-\\beta H}$, instead of $e^{-iH dt}$, we work with $dt = -i \\beta$.\n", "In particular, we can approximate the exponential using a Taylor series through the `TaylorCluster` algorithm." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "expansion_orders = 1:3\n", "\n", @@ -274,22 +272,21 @@ " plot!(p2, βs, F_taylor; label = labels)\n", " plot(p1, p2)\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Some observations:\n", "- The first order approximation fails to capture the behavior of the partition function.\n", "- The higher order approximations are in good agreement with the analytical result, as long as $\\beta$ is not too large.\n", "- The computational cost of the approximations does not depend on $\\beta$, but on the order of the approximation." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "To address the first point, we can have a look at the particular form of the time-evolution operator.\n", "Here we see that for this particular Hamiltonian, all the terms with factors $d\\tau$ are either zero or have trace zero.\n", @@ -312,12 +309,13 @@ "\n", "Therefore, we will exclude the first order approximation from now on.\n", "Zooming in on the differences with the analytical result, we find:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "expansion_orders = 2:3\n", "Z_taylor = Z_taylor[:, 2:end]\n", @@ -340,12 +338,11 @@ " )\n", " plot(p1, p2)\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We can now clearly see that, somewhat unsurprisingly, the error increases the larger $\\beta$ becomes.\n", "Given that we are computing Taylor expansions around $\\beta = 0$, this is to be expected.\n", @@ -360,17 +357,14 @@ "$$\n", "\n", "In other words, we can compute the partition function at $\\beta$ by computing the overlap of two states evolved for $\\beta / 2$, as long as the Hamiltonian is Hermitian.\n", - "Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^\\dagger$.\n", - "\n", - "> **Todo**\n", - ">\n", - "> Add a figure to illustrate this trick." - ], - "metadata": {} + "Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^\\dagger$." + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "double_logpartition(ρ₁, ρ₂ = ρ₁) = log(real(dot(ρ₁, ρ₂))) / length(ρ₁)\n", "\n", @@ -403,12 +397,11 @@ " )\n", " plot(p1, p2)\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### MPO multiplication approach (linear)\n", "\n", @@ -436,12 +429,13 @@ "> In particular, the truncation of the MPO is now happening in the Frobenius norm, rather than the operator norm.\n", "> While for small truncations this might still work, this is not guaranteed to be the case for larger truncations.\n", "> As a result, the truncated object might not be positive semidefinite, spoiling its interpretation as a density matrix." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "Z_mpo_mul = zeros(length(βs))\n", "D_max = 64\n", @@ -489,12 +483,11 @@ " )\n", " plot(p1, p2)\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "This approach clearly improves the accuracy of the results, indicating that we can indeed compute partition functions at larger $\\beta$ values.\n", "However, the computational cost of this approach (at fixed maximal bond dimension) is now linear in $\\beta$, since we need to compute the partition function at each $\\beta$ value.\n", @@ -509,11 +502,11 @@ "The accuracy of the initial density matrix can be improved by increasing the order of the Taylor expansion, but this will result in a larger MPO bond dimension.\n", "On the other hand, if we improve the accuracy of the initial density matrix, we could also increase the step size, which would reduce the number of iterations required to reach a certain $\\beta$ value.\n", "Keeping these parameters in balance is necessary to obtain accurate results, and this might require some trial and error." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### MPO multiplication approach (exponential)\n", "\n", @@ -529,12 +522,13 @@ "$$\n", "\n", "In other words, we can scan a range of exponentially increasing $\\beta$ values by squaring the density matrix at each step." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "βs_exp = 2.0 .^ (-3:3)\n", "Z_analytic_exp = partition_function.(βs_exp, J, N)\n", @@ -582,12 +576,11 @@ " plot!(p2, βs_exp, abs.(F_mpo_mul_exp .- F_analytic_exp); label = \"MPO multiplication exp\")\n", " plot(p1, p2)\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Clearly, the exponential approach allows us to reach larger $\\beta$ values much quicker, but there is again a trade-off.\n", "Since the size of the steps are increasing, we need to be more careful with the accuracy of our approximations.\n", @@ -596,11 +589,11 @@ ">\n", "> Again, using MPS techniques to approximate the multiplication of density matrices might lead to unphysical truncated density matrices.\n", "> Increasing the stepsize could make this happen sooner, so we need to be careful with the maximal bond dimension." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Time evolution approach\n", "\n", @@ -615,12 +608,13 @@ "\n", "The starting point for this approach could be either achieved through one of the techniques we have already discussed, but we can also start from the infinite temperature state directly.\n", "In particular, this state is given by the identity MPO, and we can evolve this state to compute the partition function at any $\\beta$ value." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "Z_tdvp = zeros(length(βs))\n", "\n", @@ -666,43 +660,41 @@ "\n", " plot(p1, p2)\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "> **Note**\n", ">\n", "> We could further improve the accuracy of the TDVP approach by evolving with $(H \\otimes \\mathbb{1} + \\mathbb{1} \\otimes H^\\dagger)$, rather than $H \\otimes \\mathbb{1}$ which is the current implementation.\n", "> This is known to improve the stability of the positive semidefinite property of the density matrix, and could lead to more accurate results." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "---\n", "\n", "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" - ], - "metadata": {} + ] } ], - "nbformat_minor": 3, "metadata": { + "kernelspec": { + "display_name": "Julia 1.12.6", + "language": "julia", + "name": "julia-1.12" + }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.12.4" - }, - "kernelspec": { - "name": "julia-1.12", - "display_name": "Julia 1.12.4", - "language": "julia" + "version": "1.12.6" } }, - "nbformat": 4 + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-1.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-1.png index 358ff7c7f..75d0d9692 100644 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-1.png and b/docs/src/examples/quantum1d/8.bose-hubbard/figure-1.png differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-2.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-2.png index 85b39fd2f..b8ea6a15c 100644 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-2.png and b/docs/src/examples/quantum1d/8.bose-hubbard/figure-2.png differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-3.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-3.png index 85f16f318..f2bfdfaad 100644 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-3.png and b/docs/src/examples/quantum1d/8.bose-hubbard/figure-3.png differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-4.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-4.png index 5a88f7b67..cce59f93a 100644 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-4.png and b/docs/src/examples/quantum1d/8.bose-hubbard/figure-4.png differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-5.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-5.png index 4048167a9..450c0c63f 100644 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-5.png and b/docs/src/examples/quantum1d/8.bose-hubbard/figure-5.png differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-6.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-6.png index 9eec6c12d..54b85d98d 100644 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-6.png and b/docs/src/examples/quantum1d/8.bose-hubbard/figure-6.png differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/index.md b/docs/src/examples/quantum1d/8.bose-hubbard/index.md index 9342b7f9f..53ec267d2 100644 --- a/docs/src/examples/quantum1d/8.bose-hubbard/index.md +++ b/docs/src/examples/quantum1d/8.bose-hubbard/index.md @@ -154,9 +154,9 @@ println("Energy: ", expectation_value(ground_state, hamiltonian)) ```` ```` -[ Info: VUMPS init: obj = +4.962471958690e-01 err = 5.8876e-01 -[ Info: VUMPS conv 50: obj = -6.757777651150e-01 err = 9.9482617748e-07 time = 6.72 sec -Energy: -0.6757777651149999 - 6.246015340721835e-17im +[ Info: VUMPS init: obj = +5.514844342897e-01 err = 5.9161e-01 +[ Info: VUMPS conv 41: obj = -6.757777651163e-01 err = 8.1024260014e-07 time = 0.83 sec +Energy: -0.6757777651162759 + 9.071218207774581e-17im ```` @@ -285,13 +285,13 @@ quasicondensate_density = map(state -> abs2(expectation_value(state, (0,) => a_o ```` 7-element Vector{Float64}: - 0.30974277207656425 - 0.28814775930068737 - 0.2702000951730164 - 0.25712728156142256 - 0.2468538617129866 - 0.2353979140629328 - 0.2279965552408022 + 0.3097427792977986 + 0.2881478005839194 + 0.27020015650045337 + 0.257127287055965 + 0.24685385184650885 + 0.23539786293063855 + 0.22799664604968056 ```` We may now also visualize the momentum distribution function, which is obtained as the @@ -466,3 +466,4 @@ using what we have learnt in this tutorial. --- *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/main.ipynb b/docs/src/examples/quantum1d/8.bose-hubbard/main.ipynb index d4ace3b04..15ee18a06 100644 --- a/docs/src/examples/quantum1d/8.bose-hubbard/main.ipynb +++ b/docs/src/examples/quantum1d/8.bose-hubbard/main.ipynb @@ -1,8 +1,10 @@ { "cells": [ { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Markdown\n", "using MPSKit, MPSKitModels, TensorKit\n", @@ -11,12 +13,11 @@ "\n", "theme(:wong)\n", "default(fontfamily = \"Computer Modern\", label = nothing, dpi = 100, framestyle = :box)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# 1D Bose-Hubbard model\n", "\n", @@ -96,21 +97,21 @@ "`ComplexSpace` (typeset as `\\bbC`). As $D$ is increased,\n", "one increases the amount of entanglement, i.e, quantum correlations that can be captured by\n", "the state." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "cutoff, D = 4, 5\n", "initial_state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "This simply initializes a tensor filled with random entries (check out the documentation for\n", "other useful constructors). Next, we need the creation and annihilation operators. While we\n", @@ -118,72 +119,73 @@ "[`MPSKitModels.jl`](https://github.com/QuantumKitHub/MPSKitModels.jl) instead that has\n", "predefined operators and models for most well-known lattice models. In particular, we can\n", "use `MPSKitModels.a_min` to create the bosonic annihilation operator." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "a_op = a_min(cutoff = cutoff) # creates a bosonic annihilation operator without any symmetries\n", "display(a_op[])\n", "display((a_op' * a_op)[])" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "The [] accessor lets us see the underlying array, and indeed the operators are exactly what\n", "we require. Similarly, the Bose Hubbard model is also predefined in\n", "`MPSKitModels.bose_hubbard_model` (although we will construct our own variant\n", "later on)." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "hamiltonian = bose_hubbard_model(InfiniteChain(1); cutoff = cutoff, U = 1, mu = 0.5, t = 0.2) # It is not strictly required to pass InfiniteChain() and is only included for clarity; one may instead pass FiniteChain(N) as well" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "This has created the Hamiltonian operator as a [matrix product operator](@ref\n", "InfiniteMPOHamiltonian) (MPO) which is a convenient form to use in conjunction with MPS.\n", "Finally, the ground state optimization may be performed with either `iDMRG` or\n", "`VUMPS`. Both should take similar arguments but it is known that VUMPS is typically\n", "more efficient for these systems so we proceed with that." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "ground_state, _, _ = find_groundstate(initial_state, hamiltonian, VUMPS(tol = 1.0e-6, verbosity = 2, maxiter = 200))\n", "println(\"Energy: \", expectation_value(ground_state, hamiltonian))" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "This automatically runs the algorithm until a certain [error measure](@ref\n", "MPSKit.calc_galerkin) falls below the specified tolerance or the maximum iterations is\n", "reached. Let us wrap all this into a convenient function." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "function get_ground_state(mu, t, cutoff, D; kwargs...)\n", " hamiltonian = bose_hubbard_model(InfiniteChain(); cutoff = cutoff, U = 1, mu = mu, t = t)\n", @@ -194,53 +196,52 @@ "end\n", "\n", "ground_state = get_ground_state(0.5, 0.01, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Now that we have the state, we may compute observables using the `expectation_value`\n", "function. It typically expects a `Pair`, `(i1, i2, .., ik) => op` where `op` is a\n", "`TensorMap` or `InfiniteMPO` acting over `k` sites. In case of the Hamiltonian, it is not\n", "necessary to specify the indices as it spans the whole lattice. We can now plot the\n", "correlation function $\\langle \\hat{a}^{\\dagger}_i \\hat{a}_j\\rangle$." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:50), lw = 2, xlabel = \"Site index\", ylabel = \"Correlation function\", yscale = :log10)\n", "hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We see that the correlations drop off exponentially, indicating the existence of a gapped\n", "Mott insulating phase. Let us now shift our parameters to probe other phases." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "ground_state = get_ground_state(0.5, 0.2, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500)\n", "\n", "plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:100), lw = 2, xlabel = \"Site index\", ylabel = \"Correlation function\", yscale = :log10, xscale = :log10)\n", "hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "In this case, the correlation function drops off algebraically and eventually saturates as\n", "$\\lim_{i \\to \\infty}\\langle\\hat{a}_i^{\\dagger} \\hat{a}_j\\rangle ≈ \\langle \\hat{a}_i^{\\dagger}\\rangle \\langle \\hat{a}_j \\rangle = |\\langle a_i \\rangle|^2 \\neq 0$.\n", @@ -256,12 +257,13 @@ "effects. We can see this clearly by increasing the bond dimension. We also see that the\n", "correlation length seems to depend algebraically on the bond dimension as expected from\n", "finite-entanglement scaling arguments." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "cutoff = 4\n", "Ds = 20:5:50\n", @@ -305,32 +307,31 @@ " ylims = [20, 130],\n", " xlims = [15, 60]\n", ")" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "This shows that any finite bond dimension MPS necessarily breaks the symmetry of the system,\n", - "forming a Bose-Einstein condensate which introduces erraneous long-distance behaviour of\n", + "forming a Bose-Einstein condensate which introduces erroneous long-distance behaviour of\n", "correlation functions. In case of finite bond dimension, it is thus reasonable to associate\n", "the finite expectation value of the field operator to the 'quasicondensate' density of the\n", "system which vanishes as $D \\to \\infty$." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "quasicondensate_density = map(state -> abs2(expectation_value(state, (0,) => a_op)), states)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We may now also visualize the momentum distribution function, which is obtained as the\n", "Fourier transform of the single-particle density matrix. Starting from the definition of the\n", @@ -366,12 +367,13 @@ "that is not indicative of the true physics of the system. Since we know this contribution\n", "vanishes in the infinite bond dimension limit, we instead work with\n", "$\\langle \\hat{a}_r^{\\dagger} \\hat{a}_0 \\rangle_c = \\langle \\hat{a}_r^{\\dagger} \\hat{a}_0 \\rangle - |\\langle \\hat{a}\\rangle|^2$." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "ks = range(-0.05, 0.15, 500)\n", "momentum_distribution = map(\n", @@ -385,12 +387,11 @@ ")\n", "momentum_distribution = vcat(momentum_distribution...)'\n", "plot(ks, momentum_distribution, lab = \"D = \" .* string.(permutedims(Ds)), lw = 1.5, xlabel = \"Momentum k\", ylabel = L\"\\langle n_k \\rangle\", ylim = [0, 50])" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We see that the density seems to peak around $k=0$, this time seemingly becoming more\n", "prominent as $D \\to \\infty$ which seems to suggest again that there is a condensate.\n", @@ -425,12 +426,13 @@ "of `MPSKitModels.jl` to see how these models are defined and tweak it as per your needs.\n", "Here we see that applying twisted boundary conditions is equivalent to adding a prefactor of\n", "$e^{\\pm i\\phi}$ in front of the hopping amplitudes." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "function bose_hubbard_model_twisted_bc(\n", " elt::Type{<:Number} = ComplexF64, symmetry::Type{<:Sector} = Trivial,\n", @@ -475,12 +477,11 @@ "superfluid_stiffness_profile(0.2, 0.3, 5, 4) # superfluid\n", "\n", "superfluid_stiffness_profile(0.01, 0.3, 5, 4) # mott insulator" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Now that we know what phases to expect, we can plot the phase diagram by scanning over a\n", "range of parameters. In general, one could do better by performing a bisection algorithm for\n", @@ -490,12 +491,13 @@ "using the quasi-condensate density as an order parameter since extracting the superfluid\n", "density accurately requires a more robust scheme to compute second derivatives which takes\n", "us away from the focus of this tutorial." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "cutoff, D = 4, 10\n", "mus = range(0, 0.75, 40)\n", @@ -512,44 +514,42 @@ "end\n", "\n", "heatmap(ts, mus, order_parameters, xlabel = L\"t/U\", ylabel = L\"\\mu/U\", title = L\"\\langle \\hat{a}_i \\rangle\")" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Although the bond dimension here is quite low, we already see the deformation of the Mott\n", "insulator lobes to give way to the well known BKT transition that happens at commensurate\n", "density. One can go further and estimate the critical exponents using finite-entanglement\n", "scaling procedures on the correlation functions, but these may now be performed with ease\n", "using what we have learnt in this tutorial." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "---\n", "\n", "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" - ], - "metadata": {} + ] } ], - "nbformat_minor": 3, "metadata": { + "kernelspec": { + "display_name": "Julia 1.12.6", + "language": "julia", + "name": "julia-1.12" + }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.12.4" - }, - "kernelspec": { - "name": "julia-1.12", - "display_name": "Julia 1.12.4", - "language": "julia" + "version": "1.12.6" } }, - "nbformat": 4 + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/docs/src/howto/bond_dimension.md b/docs/src/howto/bond_dimension.md new file mode 100644 index 000000000..4f41e2acf --- /dev/null +++ b/docs/src/howto/bond_dimension.md @@ -0,0 +1,293 @@ +# [Controlling bond dimension](@id howto_bond_dimension) + +The examples on this page use MPSKit.jl, TensorKit.jl, and TensorKitTensors.jl. +See [Installation](@ref tutorial_installation) for how to add these packages to your environment. + +Bond dimension is the key knob in every MPS calculation: too small and the ansatz cannot represent the state, too large and computation slows to a crawl. +This page gives concrete recipes for inspecting, growing, and shrinking bond dimension in MPSKit.jl. +All examples share a single namespace: + +```@example bond_dim +using MPSKit, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ +``` + +--- + +## 1. Inspecting the current bond dimension + +MPSKit exposes the virtual spaces through `left_virtualspace` and `right_virtualspace`. +This returns the raw vector spaces, which carry the information about the different sectors, but we can obtain a single number using `dim`: + +```@example bond_dim +L = 10 +ψ = FiniteMPS(L, ℂ^2, ℂ^8) # finite MPS, max bond dim 8 + +# Bond dimension between sites i and i+1 equals dim(left_virtualspace(ψ, i+1)) +# or equivalently dim(right_virtualspace(ψ, i)). +dim(left_virtualspace(ψ, 5)) # bond to the left of site 5 +``` + +```@example bond_dim +# All bond dimensions in one go +[dim(left_virtualspace(ψ, i)) for i in 1:L] +``` + +!!! note + For a `FiniteMPS` the leftmost and rightmost virtual spaces are typically one-dimensional (the trivial boundary space), + so `left_virtualspace(ψ, 1)` and `left_virtualspace(ψ, L+1)` have dimension 1. + +For an `InfiniteMPS` the same call works per unit-cell site: + +```@example bond_dim +ψ_inf = InfiniteMPS(ℂ^2, ℂ^8) +dim(left_virtualspace(ψ_inf, 1)) +``` + +--- + +## 2. Growing bond dimension + +### 2a. Random expansion (no Hamiltonian required) + +[`RandExpand`](@ref) pads the MPS with orthogonal random vectors drawn from the two-site null space. +It does **not** need the Hamiltonian, so it is cheap and works for any MPS type. + +`trscheme` is **mandatory** and controls how many new directions are added. +Use `truncrank(n)` from MatrixAlgebraKit (re-exported by TensorKit) to add at most `n` extra singular values: + +```@example bond_dim +ψ_small = FiniteMPS(L, ℂ^2, ℂ^4) # start with D = 4 +dim(left_virtualspace(ψ_small, 5)) +``` + +```@example bond_dim +ψ_grown = changebonds(ψ_small, RandExpand(; trscheme = truncrank(8))) +dim(left_virtualspace(ψ_grown, 5)) # expanded, but ≤ 4 + 8 = 12 +``` + +The new vectors are orthogonal to the original state, so the state it represents is unchanged (its overlap with the original is 1) while the variational manifold grows. + +For an `InfiniteMPS` the call is identical: + +```@example bond_dim +ψ_inf_small = InfiniteMPS(ℂ^2, ℂ^4) +ψ_inf_grown = changebonds(ψ_inf_small, RandExpand(; trscheme = truncrank(8))) +dim(left_virtualspace(ψ_inf_grown, 1)) +``` + +### 2b. Optimal expansion (requires Hamiltonian) + +[`OptimalExpand`](@ref) selects the dominant contributions of the two-site-updated MPS tensor that are orthogonal to the current state, as described by [Zauner-Stauber et al., Phys. Rev. B 97, 045145 (2018)](https://doi.org/10.1103/PhysRevB.97.045145). +It needs both the state and the Hamiltonian: + +```@example bond_dim +# Build a finite TFIM Hamiltonian manually +J = 1.0; g = 0.5 +lattice = fill(ℂ^2, L) +X = σˣ() +Z = σᶻ() +H = FiniteMPOHamiltonian(lattice, (i, i + 1) => -J * X ⊗ X for i in 1:(L - 1)) + + FiniteMPOHamiltonian(lattice, (i,) => -g * Z for i in 1:L) + +ψ_opt, envs_opt = changebonds(ψ_small, H, OptimalExpand(; trscheme = truncrank(8))) +dim(left_virtualspace(ψ_opt, 5)) +``` + +`OptimalExpand` also works on `InfiniteMPS` with an `InfiniteMPOHamiltonian`. +The environment argument is optional and defaults to a freshly computed set: + +```@example bond_dim +lattice_inf = PeriodicVector([ℂ^2]) +H_inf = InfiniteMPOHamiltonian(lattice_inf, (1, 2) => -J * X ⊗ X, (1,) => -g * Z) + +ψ_inf_opt, _ = changebonds(ψ_inf_small, H_inf, OptimalExpand(; trscheme = truncrank(8))) +dim(left_virtualspace(ψ_inf_opt, 1)) +``` + +!!! note + `OptimalExpand` and `VUMPSSvdCut` (see [§5](#5-growing-during-infinite-mps-optimization)) + both require the Hamiltonian. + Pass environments as the optional fourth argument to avoid recomputing them if you + already have them from a previous `find_groundstate` call. + +--- + +## 3. Reducing bond dimension + +[`SvdCut`](@ref) truncates the bond dimension by an SVD sweep. +It does **not** need the Hamiltonian and is the standard tool for compression. + +```@example bond_dim +# compress ψ_grown (D up to 12) back to at most 6 singular values per bond +ψ_cut = changebonds(ψ_grown, SvdCut(; trscheme = truncrank(6))) +dim(left_virtualspace(ψ_cut, 5)) +``` + +An in-place variant, `changebonds!`, exists for `FiniteMPS` and avoids allocating a copy. +It also accepts a `normalize` keyword (default `true`): + +```@example bond_dim +ψ_inplace = FiniteMPS(L, ℂ^2, ℂ^12) +changebonds!(ψ_inplace, SvdCut(; trscheme = truncrank(6)); normalize = true) +dim(left_virtualspace(ψ_inplace, 5)) +``` + +`SvdCut` also works on `InfiniteMPS` (2-arg form only; no in-place variant): + +```@example bond_dim +ψ_inf_cut = changebonds(ψ_inf_grown, SvdCut(; trscheme = truncrank(6))) +dim(left_virtualspace(ψ_inf_cut, 1)) +``` + +--- + +## 4. Truncation schemes + +Every bond-change algorithm takes a mandatory `trscheme` keyword drawn from **MatrixAlgebraKit** (re-exported by TensorKit). +The main schemes are: + +| Scheme | Meaning | +|:-------|:--------| +| `truncrank(n)` | Keep at most `n` singular values | +| `trunctol(; atol)` | Drop singular values below `atol` times the largest | +| `notrunc()` | Keep all singular values (no truncation) | +| `truncspace(V)` | Keep only singular values whose index fits in the given space `V` | + +Schemes compose with `&` to apply multiple criteria simultaneously. +For example, to keep at most 16 singular values **and** also drop anything below `1e-8`: + +```@example bond_dim +trscheme_combined = trunctol(; atol = 1.0e-8) & truncrank(16) +ψ_combined = changebonds(ψ_grown, SvdCut(; trscheme = trscheme_combined)) +dim(left_virtualspace(ψ_combined, 5)) +``` + +!!! warning + `trscheme` is **required** on every algorithm; there is no default. + Omitting it will throw a `MethodError` at construction time. + +--- + +## 5. Growing during finite MPS optimization + +The two-site DMRG variant, [`DMRG2`](@ref), performs a bond expansion at every sweep step by keeping both sites together in the update. +Pass `trscheme` to control which singular values are retained: + +```@example bond_dim +ψ_dmrg2_start = FiniteMPS(L, ℂ^2, ℂ^2) # start small + +ψ_dmrg2, envs_dmrg2, _ = find_groundstate( + ψ_dmrg2_start, H, + DMRG2(; trscheme = truncrank(16), maxiter = 5) +) +dim(left_virtualspace(ψ_dmrg2, 5)) +``` + +A common pattern is to warm up with `DMRG2` to grow the bond dimension, then refine with single-site `DMRG` for efficiency. +The algorithm chaining operator `&` makes this easy (see [§7](#7-chaining-algorithms)): + +```@example bond_dim +warmup_then_refine = DMRG2(; trscheme = truncrank(16), maxiter = 3) & + DMRG(; maxiter = 20) + +ψ_dmrg2, envs_dmrg2, _ = find_groundstate(ψ_dmrg2_start, H, warmup_then_refine) +dim(left_virtualspace(ψ_dmrg2, 5)) +``` + +The `find_groundstate` convenience function also accepts a `trscheme` keyword that triggers the same warm-up automatically: + +```@example bond_dim +ψ_conv, envs_conv, _ = find_groundstate( + ψ_dmrg2_start, H; + trscheme = truncrank(16), maxiter = 20 +) +dim(left_virtualspace(ψ_conv, 5)) +``` + +The `trscheme` keyword makes `find_groundstate` prepend a `DMRG2` pass before switching to the default `DMRG`. + +TDVP2 also supports `trscheme` for two-site real- or imaginary-time evolution, but that is covered in the time-evolution documentation rather than here. + +--- + +## 6. Growing during infinite MPS optimization + +### IDMRG2 (two-site infinite DMRG) + +[`IDMRG2`](@ref) is the infinite analogue of `DMRG2`. + +!!! warning + `IDMRG2` requires a unit cell of **at least 2 sites**. + Passing a single-site `InfiniteMPS` will throw an `ArgumentError`. + +```@example bond_dim +# 2-site unit cell: lattice, Hamiltonian, and initial state +lattice_2 = PeriodicVector([ℂ^2, ℂ^2]) +H_inf_2 = InfiniteMPOHamiltonian( + lattice_2, + (1, 2) => -J * X ⊗ X, + (2, 3) => -J * X ⊗ X, + (1,) => -g * Z, + (2,) => -g * Z, +) + +ψ_idmrg2_start = InfiniteMPS([ℂ^2, ℂ^2], [ℂ^2, ℂ^2]) + +ψ_idmrg2, _, _ = find_groundstate( + ψ_idmrg2_start, H_inf_2, + IDMRG2(; trscheme = truncrank(16), maxiter = 5) +) +dim(left_virtualspace(ψ_idmrg2, 1)) +``` + +### VUMPSSvdCut + +[`VUMPSSvdCut`](@ref) grows the bond dimension of an `InfiniteMPS` by performing a two-site VUMPS update followed by an SVD truncation. +It requires the Hamiltonian and returns a new state with updated environments: + +```@example bond_dim +ψ_vs, _ = changebonds(ψ_inf_small, H_inf, VUMPSSvdCut(; trscheme = truncrank(16))) +dim(left_virtualspace(ψ_vs, 1)) +``` + +The typical workflow for infinite systems is to grow the bond dimension first (with `VUMPSSvdCut` or `IDMRG2`), then converge with [`VUMPS`](@ref) as a separate step, reusing the expanded state `ψ_vs` from above: + +```@example bond_dim +ψ_vc, = find_groundstate(ψ_vs, H_inf, VUMPS(; maxiter = 10)) +dim(left_virtualspace(ψ_vc, 1)) +``` + +!!! note + Bond-changing algorithms such as `VUMPSSvdCut` are applied through + [`changebonds`](@ref), not `find_groundstate`. Grow the state first, then pass + the result to a ground-state algorithm. + +--- + +## 7. Chaining algorithms + +The `&` operator chains any two algorithms that share the same interface, applying them in sequence. +This works for both ground-state algorithms and `changebonds` algorithms: + +```@example bond_dim +# Expand with random vectors, then compress to a target rank +grow_and_cut = RandExpand(; trscheme = truncrank(12)) & + SvdCut(; trscheme = truncrank(6)) + +ψ_final = changebonds(ψ_small, grow_and_cut) +dim(left_virtualspace(ψ_final, 5)) +``` + +```@example bond_dim +# Alternatively: combine changebonds with a ground-state algorithm +ψ_expanded, envs_expanded = changebonds( + ψ_small, H, OptimalExpand(; trscheme = truncrank(8)) +) +ψ_gs, _, _ = find_groundstate(ψ_expanded, H, DMRG(; maxiter = 10), envs_expanded) +dim(left_virtualspace(ψ_gs, 5)) +``` + +For background on when each algorithm is appropriate and how convergence is assessed, see [Ground-state algorithms](@ref lib_groundstate). +For constructing MPS objects from scratch, see [Constructing states](@ref howto_states). + diff --git a/docs/src/howto/entanglement.md b/docs/src/howto/entanglement.md new file mode 100644 index 000000000..88e446212 --- /dev/null +++ b/docs/src/howto/entanglement.md @@ -0,0 +1,141 @@ +# [Entanglement entropy and spectrum](@id howto_entanglement) + +The examples on this page use MPSKit.jl, TensorKit.jl, and TensorKitTensors.jl. +See [Installation](@ref tutorial_installation) for how to add these packages to your environment. + +This page collects recipes for extracting the entanglement entropy and the entanglement spectrum from the gauge (bond) tensors of an MPS. +For general expectation values and correlators see [Computing observables](@ref howto_observables); for building the state objects used below see [Constructing states](@ref howto_states). +The reference page for these and related functions is [Observables and analysis](@ref lib_observables). + +```@example entanglement +using MPSKit, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ +``` + +--- + +## Setup: a TFIM ground state + +The examples below reuse a spin-1/2 `FiniteMPS` and the transverse-field Ising Hamiltonian, optimized with DMRG so the entanglement structure reflects an actual ground state rather than a random tensor: + +```@example entanglement +L = 8 +ψ0 = FiniteMPS(L, ℂ^2, ℂ^8) + +# single-site Pauli operators +X = σˣ() +Z = σᶻ() + +lattice = fill(ℂ^2, L) +H = FiniteMPOHamiltonian(lattice, (i, i + 1) => -(X ⊗ X) for i in 1:(L - 1)) + + FiniteMPOHamiltonian(lattice, (i,) => -0.5 * Z for i in 1:L) + +ψ, envs, _ = find_groundstate(ψ0, H, DMRG(; maxiter = 10)) +``` + +--- + +## 1. Entanglement entropy at a single cut + +[`entropy`](@ref) returns the von Neumann entanglement entropy across the cut to the right of a given site. +For a `FiniteMPS` the site is a required argument: + +```@example entanglement +entropy(ψ, L ÷ 2) # entropy across the central cut +``` + +--- + +## 2. Entropy profile across every cut + +Collecting `entropy(ψ, i)` over the valid range of sites gives the full entropy profile of the chain: + +```@example entanglement +[entropy(ψ, i) for i in 1:L] +``` + +!!! warning + For `FiniteMPS` the cut site is required and must lie in `1:length(ψ)`. + `site = 0` — a valid default for `InfiniteMPS` and `WindowMPS` (see recipe 5) — throws a `BoundsError` for `FiniteMPS`. + +--- + +## 3. The entanglement spectrum + +[`entanglement_spectrum`](@ref) returns the singular values of the gauge tensor to the right of a site, packaged as a sector-resolved vector: + +```@example entanglement +spectrum = entanglement_spectrum(ψ, L ÷ 2) +``` + +The entropy can equivalently be computed directly from this spectrum with [`entropy`](@ref): + +```@example entanglement +entropy(spectrum) +``` + +```@example entanglement +entropy(ψ, L ÷ 2) ≈ entropy(spectrum) +``` + +Both routes agree, since `entropy(ψ, site)` computes the entropy from exactly this spectrum internally. + +--- + +## 4. Sector-resolved spectrum + +Because the returned spectrum is indexed by symmetry sector, you can inspect the singular values sector by sector. +Use `keys` to list the sectors present at a cut, and index the spectrum with a sector to obtain its singular values: + +```@example entanglement +collect(keys(spectrum)) +``` + +```@example entanglement +spectrum[only(keys(spectrum))] +``` + +For the plain (no explicit symmetry) `FiniteMPS` built above there is a single sector, `Trivial()`, so all singular values live in one block. +`pairs(spectrum)` iterates `sector => values` pairs and is the natural entry point for a symmetric state where multiple sectors are populated at a cut: + +```@example entanglement +collect(pairs(spectrum)) +``` + +--- + +## 5. Entanglement of an infinite MPS + +For `InfiniteMPS`, the cut site defaults to `0`, and `entropy` without a site argument returns one entropy per site in the unit cell: + +```@example entanglement +ψ∞ = InfiniteMPS(ℂ^2, ℂ^8) +entropy(ψ∞) +``` + +```@example entanglement +entanglement_spectrum(ψ∞) # site defaults to 0 +``` + +!!! note + `ψ∞` here is a random `InfiniteMPS`, not a converged ground state, so the values above illustrate the interface rather than any physical entanglement profile. + For a physically meaningful result, compute the entropy of a state obtained from [`find_groundstate`](@ref) (for example via VUMPS). + +!!! note + `WindowMPS` also supports `entropy(ψ, site)` with a required site argument, mirroring the `FiniteMPS` form. + +--- + +## Plotting the spectrum + +MPSKit defines an `entanglementplot` recipe via `RecipesBase`, but does not depend on Plots.jl itself. +To use it, add `using Plots` (or another Plots-backed package) in your own environment: + +```julia +using Plots +entanglementplot(ψ; site = L ÷ 2) +``` + +!!! note + `entanglementplot` is a plotting *recipe*: it only becomes available once `Plots` (or a compatible plotting package) is loaded. + This block is not executed on this page to keep the docs build free of the Plots.jl dependency. diff --git a/docs/src/howto/excitations.md b/docs/src/howto/excitations.md new file mode 100644 index 000000000..b74366987 --- /dev/null +++ b/docs/src/howto/excitations.md @@ -0,0 +1,166 @@ +# [Excited states](@id howto_excitations) + +The examples on this page use MPSKit.jl, MPSKitModels.jl, and TensorKit.jl. +See [Installation](@ref tutorial_installation) for how to add these packages to your environment. + +[`excitations`](@ref) is the single entry point for computing energy eigenstates beyond the ground state. +This page shows how to call it for a gap, a full dispersion relation, a charged excitation, and a handful of excited states on a finite chain. +For what each algorithm actually does and why you would choose one over another, see [Excitations](@ref lib_excitations). +All examples share a single namespace: + +```@example excitations_howto +using MPSKit, MPSKitModels, TensorKit +``` + +--- + +## 1. Get a single excitation gap on an infinite chain + +On an `InfiniteMPS`, [`QuasiparticleAnsatz`](@ref) perturbs every site of the unit cell in a plane-wave superposition with a fixed `momentum`, given as a `Real` in radians per unit cell. +Pass the ground state and (optionally) its environments straight through from [`find_groundstate`](@ref): + +```@example excitations_howto +g = 2.0 +H_inf = transverse_field_ising(; g) +ψ₀_inf = InfiniteMPS(ℂ^2, ℂ^12) +ψ_inf, envs_inf, = find_groundstate(ψ₀_inf, H_inf; verbosity = 0) + +Es_inf, ϕs_inf = excitations(H_inf, QuasiparticleAnsatz(), 0.0, ψ_inf, envs_inf; num = 1) +Es_inf[1] +``` + +The values in `Es_inf` are excitation *gaps* above the ground-state energy density, not total energies: internally the ground-state energy per site is subtracted before diagonalizing. +`ϕs_inf` holds the corresponding quasiparticle states (`num` of them), which behave like normal vectors for `eigsolve`-style post-processing but are not `FiniteMPS`/`InfiniteMPS` objects themselves. +Raise `num` to get more than one state at the same momentum, e.g. `num = 3` for the three lowest excitations at that momentum. + +--- + +## 2. Scan the dispersion relation + +Pass a range (or any vector) of momenta instead of a single number to sweep the whole Brillouin zone in one call: + +```@example excitations_howto +momenta = range(0, π, 5) +Es_disp, ϕs_disp = excitations( + H_inf, QuasiparticleAnsatz(), momenta, ψ_inf, envs_inf; + num = 1, verbosity = 0 +) +size(Es_disp) +``` + +With a vector of `length(momenta)` momenta, `Es_disp` and `ϕs_disp` come back as `(length(momenta), num)` matrices rather than plain vectors — index `Es_disp[:, n]` for the dispersion of the `n`-th branch, or use `vec(Es_disp)` when `num = 1`. +`verbosity = 0` silences the per-momentum `@info` line that this method otherwise prints; raise it to see progress on a longer scan. +Momenta are independent of each other, so this method also accepts `parallel = true` (the default) to distribute them over available threads/workers; pass `parallel = false` to force sequential evaluation. + +--- + +## 3. Target a symmetry sector + +By default the optimization looks for the lowest excitation with trivial (vacuum) total charge, `sector = leftunit(ψ)`. +Passing a different `TensorKit` sector targets a quasiparticle with that charge instead — only `QuasiparticleAnsatz` supports this keyword. +Build the ground state with symmetric tensors first, then request the sector on the excitation call: + +```@example excitations_howto +g = 10.0 +L = 12 +H_Z2 = transverse_field_ising(Z2Irrep, FiniteChain(L); g) +ψ₀_Z2 = FiniteMPS(L, Z2Space(0 => 1, 1 => 1), Z2Space(0 => 8, 1 => 8)) +ψ_Z2, envs_Z2, = find_groundstate(ψ₀_Z2, H_Z2; verbosity = 0) + +Es_triv, = excitations(H_Z2, QuasiparticleAnsatz(), ψ_Z2, envs_Z2; num = 1) +Es_charged, ϕs_charged = excitations( + H_Z2, QuasiparticleAnsatz(), ψ_Z2, envs_Z2; + num = 1, sector = Z2Irrep(1) +) +Es_triv[1], Es_charged[1] +``` + +Here the `Z2Irrep(1)` excitation corresponds to a single flipped spin, the lowest physical excitation of the transverse-field Ising model. +[`ChepigaAnsatz`](@ref)/[`ChepigaAnsatz2`](@ref) do not support charged excitations at all: passing a nontrivial `sector` to either raises an error, and [`FiniteExcited`](@ref) has no `sector` keyword in the first place. + +--- + +## 4. Excited states on a finite chain + +Momentum is not a conserved quantity on a finite chain, so the finite method of `excitations` has no momentum argument; drop it entirely and call `QuasiparticleAnsatz` on the ground state directly: + +```@example excitations_howto +L = 12 +H_fin = transverse_field_ising(FiniteChain(L); g) +ψ₀_fin = FiniteMPS(L, ℂ^2, ℂ^16) +ψ_fin, envs_fin, = find_groundstate(ψ₀_fin, H_fin; verbosity = 0) + +Es_qp, ϕs_qp = excitations(H_fin, QuasiparticleAnsatz(), ψ_fin, envs_fin; num = 1) +Es_qp[1] +``` + +[`FiniteExcited`](@ref) takes a different approach: it repeatedly finds the ground state of `H + weight * Σᵢ |ψᵢ⟩⟨ψᵢ|`, penalizing overlap with the ground state and any excited states already found, and returns full `FiniteMPS` objects instead of quasiparticle states: + +```@example excitations_howto +fe_alg = FiniteExcited(; gsalg = DMRG(; verbosity = 0), weight = 10.0) +Es_fe, ψs_fe = excitations(H_fin, fe_alg, ψ_fin; num = 2) +Es_fe +``` + +Unlike `QuasiparticleAnsatz`, the values in `Es_fe` are total energies of the excited states, directly comparable to `expectation_value(ψ_fin, H_fin)` on the ground state. +Because each call to `FiniteExcited` reruns a full ground-state optimization under the hood, it scales worse with `num` than the other methods here; reach for it when you need excited states of a genuinely different character than the ground state (so the projector penalty, rather than a local perturbation, is what finds them). + +[`ChepigaAnsatz`](@ref) is a cheaper alternative for excitations that are qualitatively similar to the ground state: it diagonalizes the effective Hamiltonian at a single site `pos` (default the middle of the chain) using the ground-state environments, with no extra sweeping: + +```@example excitations_howto +Es_ch, ψs_ch = excitations(H_fin, ChepigaAnsatz(), ψ_fin, envs_fin; num = 1, pos = L ÷ 2) +Es_ch[1] +``` + +[`ChepigaAnsatz2`](@ref) does the same with a two-site block at `pos, pos + 1`, which costs more but is typically more accurate; it truncates the optimized two-site tensor back down with a `trscheme` keyword (`notrunc()` by default): + +```@example excitations_howto +Es_ch2, ψs_ch2 = excitations(H_fin, ChepigaAnsatz2(; trscheme = truncrank(16)), ψ_fin, envs_fin; num = 1) +Es_ch2[1] +``` + +Like `FiniteExcited`, the energies returned by both Chepiga variants are total energies, not gaps. + +--- + +## 5. Check excitation quality + +[`variance`](@ref) accepts a quasiparticle state directly and reports the variance of the energy, with smaller values indicating a better-converged excitation: + +```@example excitations_howto +variance(ϕs_qp[1], H_fin) +``` + +It also works on the infinite quasiparticle states from §1–§3: + +```@example excitations_howto +variance(ϕs_inf[1], H_inf) +``` + +!!! warning "Variance of infinite quasiparticle states" + `variance` on an infinite quasiparticle state carries an unresolved implementation note in `src/algorithms/toolbox.jl` and may be unreliable; verify its output before relying on it as a convergence diagnostic. + It also throws an `ArgumentError` for domain-wall (topological) excitations, where it is not implemented at all. + +To measure other observables on a finite quasiparticle state, convert it to a plain `FiniteMPS` first: + +```@example excitations_howto +excited_state = convert(FiniteMPS, ϕs_qp[1]) +real(expectation_value(excited_state, H_fin)) +``` + +--- + +## 6. Domain-wall excitations + +`excitations` also accepts two *different* ground states, `excitations(H, QuasiparticleAnsatz(), momentum, ψ_left, envs_left, ψ_right, envs_right; ...)`, which builds a quasiparticle that interpolates between them — a domain-wall (topological) excitation rather than a local perturbation on top of a single ground state. +This is real, exported functionality, but it has no dedicated test or example in the repository at the time of writing, and constructing two genuinely distinct, well-converged ground states to feed it (for instance the two symmetry-broken ground states of an ordered phase) is itself nontrivial to set up reliably in a short recipe. + + +--- + +## Where to go next + +For growing the bond dimension of the ground states these recipes start from, see [Controlling bond dimension](@ref howto_bond_dimension). +For background on the quasiparticle ansatz and the other algorithms used here, see [Excitations](@ref lib_excitations). +For general expectation values and correlators, including on the converted excited states from §5, see [Computing observables](@ref howto_observables). +Spectral functions built from these excitations (`propagator`, `DynamicalDMRG`, and related solvers) are a separate topic covered in the "Linear problems and spectral functions" section of the [Public API](@ref public_api) reference. diff --git a/docs/src/howto/groundstate_algorithms.md b/docs/src/howto/groundstate_algorithms.md new file mode 100644 index 000000000..98124df19 --- /dev/null +++ b/docs/src/howto/groundstate_algorithms.md @@ -0,0 +1,202 @@ +# [Ground-state algorithms](@id howto_groundstate_algorithms) + +The examples on this page use MPSKit.jl, MPSKitModels.jl, TensorKit.jl, and TensorKitTensors.jl. +See [Installation](@ref tutorial_installation) for how to add these packages to your environment. + +[`find_groundstate`](@ref) is the single entry point for optimizing an MPS towards the ground state of a Hamiltonian. +This page shows how to pick and configure the algorithm it runs, for both finite and infinite systems. +For what each algorithm actually does and why you would choose one over another, see [Ground-state algorithms](@ref lib_groundstate). +All examples share a single namespace: + +```@example groundstate_algs +using MPSKit, MPSKitModels, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ +``` + +--- + +## 1. Get a ground state with defaults + +Called with just a state and a Hamiltonian, `find_groundstate` inspects the type of the initial state and picks a matching algorithm for you. +For a `FiniteMPS` it runs [`DMRG`](@ref) with the keywords you pass through (`tol`, `maxiter`, `verbosity`): + +```@example groundstate_algs +L = 8 +ψ₀ = FiniteMPS(L, ℂ^2, ℂ^8) +H = transverse_field_ising(FiniteChain(L); g = 0.5) + +ψ, envs, ϵ = find_groundstate(ψ₀, H; tol = 1.0e-8, maxiter = 50, verbosity = 0) +ϵ +``` + +For an `InfiniteMPS` it instead runs [`VUMPS`](@ref), and if the requested `tol` is tighter than `1e-4` it chains a [`GradientGrassmann`](@ref) pass afterwards to polish the last few digits (see [§4](#4-refine-convergence-with-gradientgrassmann)): + +```@example groundstate_algs +ψ₀_inf = InfiniteMPS(ℂ^2, ℂ^6) +H_inf = transverse_field_ising(; g = 0.5) + +ψ_inf, envs_inf, ϵ_inf = find_groundstate(ψ₀_inf, H_inf; verbosity = 0) +ϵ_inf +``` + +Passing a `trscheme` keyword switches on a two-site pre-pass that can grow the bond dimension before the single-site algorithm takes over. +On a `FiniteMPS` this prepends [`DMRG2`](@ref); on an `InfiniteMPS` it prepends [`IDMRG2`](@ref) (which needs a unit cell of at least two sites, see [§3](#3-configure-infinite-system-algorithms)). + +```@example groundstate_algs +ψ_auto, envs_auto, ϵ_auto = find_groundstate( + ψ₀, H; + trscheme = truncrank(16), verbosity = 0 +) +ϵ_auto +``` + +!!! note "When to reach for an explicit algorithm" + The keyword form above covers the common cases. + Reach for an explicit algorithm struct (`DMRG`, `DMRG2`, `VUMPS`, `IDMRG`, `IDMRG2`, `GradientGrassmann`), or a chain of them with `&`, whenever you need finer control than the heuristic provides — the rest of this page shows how. + +--- + +## 2. Configure finite-system DMRG + +Pass a [`DMRG`](@ref) struct explicitly to set `tol`, `maxiter`, and `verbosity` directly: + +```@example groundstate_algs +ψ_dmrg, envs_dmrg, ϵ_dmrg = find_groundstate( + ψ₀, H, + DMRG(; tol = 1.0e-8, maxiter = 50, verbosity = 0) +) +ϵ_dmrg +``` + +`DMRG` updates one site at a time, so with its defaults it cannot change the bond dimension: whatever bond dimension `ψ₀` starts with is what it keeps. +[`DMRG2`](@ref) optimizes two sites at once and truncates back down, which lets it grow (or shrink) the bond dimension as it sweeps, at extra cost per step. +Unlike `DMRG`, `DMRG2` has no default truncation scheme, so `trscheme` is required: + +```@example groundstate_algs +ψ_dmrg2, envs_dmrg2, ϵ_dmrg2 = find_groundstate( + ψ₀, H, + DMRG2(; trscheme = truncrank(16), maxiter = 5, verbosity = 0) +) +ϵ_dmrg2 +``` + +A common pattern is to warm up with `DMRG2` to grow the bond dimension, then refine with the cheaper single-site `DMRG`. +The `&` chaining operator runs the first algorithm to completion, then feeds its result into the second: + +```@example groundstate_algs +warmup_then_refine = DMRG2(; trscheme = truncrank(16), maxiter = 3, verbosity = 0) & + DMRG(; tol = 1.0e-8, maxiter = 30, verbosity = 0) + +ψ_c, envs_c, ϵ_c = find_groundstate(ψ₀, H, warmup_then_refine) +ϵ_c +``` + +For more on choosing `trscheme` and growing bond dimension in general, see [Controlling bond dimension](@ref howto_bond_dimension). + +--- + +## 3. Configure infinite-system algorithms + +[`VUMPS`](@ref) is the default single-site algorithm for an `InfiniteMPS`, and takes the same `tol`/`maxiter`/`verbosity` keywords: + +```@example groundstate_algs +ψ_v, envs_v, ϵ_v = find_groundstate( + ψ₀_inf, H_inf, + VUMPS(; tol = 1.0e-8, maxiter = 50, verbosity = 0) +) +ϵ_v +``` + +[`IDMRG`](@ref) is the infinite analogue of `DMRG`: it grows the system by repeatedly inserting sites in the middle and re-optimizing, until boundary effects wash out. + +```@example groundstate_algs +ψ_i, envs_i, ϵ_i = find_groundstate( + ψ₀_inf, H_inf, + IDMRG(; tol = 1.0e-8, maxiter = 50, verbosity = 0) +) +ϵ_i +``` + +In practice, prefer `VUMPS` unless you specifically need `IDMRG`'s ability to change the bond dimension one site at a time. + +[`IDMRG2`](@ref) is the two-site, bond-dimension-changing variant, and mirrors `DMRG2`: `trscheme` is required, and it needs a unit cell of at least two sites. + +!!! warning "Unit cell size" + `IDMRG2` throws an `ArgumentError` on a single-site `InfiniteMPS`. + Build the initial state and Hamiltonian with a unit cell of two (or more) sites instead. + +```@example groundstate_algs +J = 1.0 +g = 0.5 +X = σˣ() +Z = σᶻ() + +lattice_2 = PeriodicVector([ℂ^2, ℂ^2]) +H_inf_2 = InfiniteMPOHamiltonian( + lattice_2, + (1, 2) => -J * X ⊗ X, + (2, 3) => -J * X ⊗ X, + (1,) => -g * Z, + (2,) => -g * Z, +) +ψ₀_2 = InfiniteMPS([ℂ^2, ℂ^2], [ℂ^2, ℂ^2]) + +ψ_i2, envs_i2, ϵ_i2 = find_groundstate( + ψ₀_2, H_inf_2, + IDMRG2(; trscheme = truncrank(16), maxiter = 5, verbosity = 0) +) +ϵ_i2 +``` + +--- + +## 4. Refine convergence with GradientGrassmann + +[`GradientGrassmann`](@ref) performs Riemannian gradient descent directly on the manifold of (finite or infinite) MPS, using an optimizer from OptimKit (`ConjugateGradient` by default via the `method` keyword). +Chain it after `VUMPS` (or `DMRG`) with `&` to combine both regimes in one call — this is exactly what `find_groundstate`'s heuristic does once `tol` is tighter than `1e-4`: + +```@example groundstate_algs +refine = VUMPS(; tol = 1.0e-6, maxiter = 20, verbosity = 0) & + GradientGrassmann(; tol = 1.0e-10, maxiter = 50, verbosity = 0) + +ψ_g, envs_g, ϵ_g = find_groundstate(ψ₀_inf, H_inf, refine) +ϵ_g +``` + +Since `GradientGrassmann` is also a single-site algorithm, it cannot change the bond dimension either: grow it beforehand with `DMRG2`/`IDMRG2` or the `changebonds` recipes in [Controlling bond dimension](@ref howto_bond_dimension). + +--- + +## 5. Control output and tolerances + +Every algorithm accepts a `verbosity` keyword as a plain integer: + +| `verbosity` | Output | +|:-----------:|:-------| +| `0` | nothing | +| `1` | warnings only | +| `2` | convergence information | +| `3` | per-iteration information (the default) | +| `4` | everything | + +`find_groundstate` returns `(ψ, envs, ϵ)`. +`ϵ` is the final convergence-error measure (a Galerkin residual) of whichever algorithm ran last — it quantifies how well the sweeps converged, not the error in the energy itself. + +The optional third positional argument to `find_groundstate` lets you reuse `envs` from a previous call instead of recomputing it, which is useful when tightening the tolerance on a state you already optimized: + +```@example groundstate_algs +ψ_v2, envs_v2, ϵ_v2 = find_groundstate( + ψ_v, H_inf, + VUMPS(; tol = 1.0e-10, maxiter = 50, verbosity = 0), + envs_v +) +ϵ_v2 +``` + +--- + +## Where to go next + +For growing, shrinking, and inspecting bond dimension during or between these calculations, see [Controlling bond dimension](@ref howto_bond_dimension). +For background on when each algorithm applies and how it relates to the others, see [Ground-state algorithms](@ref lib_groundstate). +To see `find_groundstate` used end to end on a finite chain, start from [Your first ground state](@ref tutorial_first_groundstate); for the infinite-system counterpart, see [The thermodynamic limit](@ref tutorial_thermodynamic_limit). diff --git a/docs/src/howto/hamiltonians.md b/docs/src/howto/hamiltonians.md new file mode 100644 index 000000000..9b8206684 --- /dev/null +++ b/docs/src/howto/hamiltonians.md @@ -0,0 +1,107 @@ +# [Building Hamiltonians](@id howto_hamiltonians) + +The examples on this page use MPSKit.jl, TensorKit.jl, and TensorKitTensors.jl. +See [Installation](@ref tutorial_installation) for how to add these packages to your environment. + +This page collects recipes for constructing MPO Hamiltonians from local operators, for both finite and infinite (translation-invariant) lattices. +It also covers converting an infinite Hamiltonian to finite open or periodic boundary conditions, and carving a finite window out of an infinite Hamiltonian. +For building the matching state objects see [Constructing states](@ref howto_states); for evaluating a Hamiltonian's energy on a state see [Computing observables](@ref howto_observables). +The reference page for the underlying MPO structure is [Operators](@ref lib_operators). + +```@example hamiltonians +using MPSKit, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ +``` + +--- + +## Setup: local operators + +The examples below build the transverse-field Ising model (TFIM), the same flagship model used elsewhere in these docs. +It couples neighbouring spins through `X ⊗ X` and applies a transverse field of strength `g` along `Z`. +The model has a quantum phase transition at `g = 1`, separating an ordered (ferromagnetic) phase at small `g` from a disordered (paramagnetic) phase at large `g`. +The single-site Pauli operators come from [TensorKitTensors.jl](https://github.com/QuantumKitHub/TensorKitTensors.jl), which returns `ComplexF64` `TensorMap`s on the spin-1/2 physical space `ℂ^2`: + +```@example hamiltonians +X = σˣ() +Z = σᶻ() +g = 0.5 +``` + +--- + +## 1. Finite Hamiltonian from local terms + +[`FiniteMPOHamiltonian`](@ref) takes an array of `VectorSpace` objects describing the local Hilbert spaces, followed by any number of `inds => operator` pairs. +A single-site term uses a one-element tuple `(i,) => O`; a nearest-neighbour term uses a two-element tuple `(i, i + 1) => O₁₂`, where `O₁₂` is a two-site operator built with `⊗`: + +```@example hamiltonians +L = 8 +lattice = fill(ℂ^2, L) + +H_finite = FiniteMPOHamiltonian(lattice, (i, i + 1) => -(X ⊗ X) for i in 1:(L - 1)) + + FiniteMPOHamiltonian(lattice, (i,) => -g * Z for i in 1:L) +``` + +Adding the two `FiniteMPOHamiltonian` objects combines the bond terms and the field terms into a single Jordan-block MPO. +Equivalently, all terms can be passed as one call by splatting a single collection of `inds => operator` pairs; see [Operators](@ref lib_operators) for that form. + +!!! note + The index tuples must refer to contiguous sites for the two-site pairs shown here. + See [Operators](@ref lib_operators) for the general, non-nearest-neighbour "expert mode" construction, which is not covered on this task-oriented page. + +--- + +## 2. Infinite (translation-invariant) Hamiltonian + +[`InfiniteMPOHamiltonian`](@ref) uses the same `inds => operator` convention, but the lattice argument is a single unit cell, and site indices wrap around it periodically. +For the 1-site TFIM unit cell, `(1, 2) => O₁₂` couples site 1 to site 2 of the *next* unit cell: + +```@example hamiltonians +unitcell = fill(ℂ^2, 1) +H_inf = InfiniteMPOHamiltonian(unitcell, (1, 2) => -(X ⊗ X), (1,) => -g * Z) +``` + +The resulting operator repeats this single bond-plus-field pattern along the whole infinite chain. +Use it directly with an [`InfiniteMPS`](@ref) in `expectation_value` or `find_groundstate`, exactly as described in [Computing observables](@ref howto_observables). + +!!! tip + Hand-assembling local operators works for any model, but for standard lattice models MPSKitModels.jl provides ready-made Hamiltonian builders and the `@mpoham` macro for a more compact syntax. + See the MPSKitModels.jl documentation for that higher-level interface; it is a separate package from MPSKit and not covered here. + +--- + +## 3. Converting between boundary conditions + +Starting from an `InfiniteMPOHamiltonian`, [`open_boundary_conditions`](@ref) truncates it to a finite chain of length `L` with open ends, and [`periodic_boundary_conditions`](@ref) instead closes it into a finite ring. +In both cases `L` must be a multiple of the unit-cell length: + +```@example hamiltonians +L_finite = 6 # multiple of the 1-site unit cell + +H_open = open_boundary_conditions(H_inf, L_finite) +``` + +```@example hamiltonians +H_periodic = periodic_boundary_conditions(H_inf, L_finite) +``` + +`H_open` is the same finite-chain Hamiltonian you would get from writing out the terms by hand, as in recipe 1 above, restricted to `L_finite` sites. +`H_periodic` additionally couples the last site back to the first, forming a ring. + +!!! note + Both functions return a [`FiniteMPOHamiltonian`](@ref). + There is no boundary-condition keyword on the `FiniteMPOHamiltonian`/`InfiniteMPOHamiltonian` constructors themselves; boundary conditions are chosen by picking which constructor (or conversion function) to call. + +--- + +## 4. A window Hamiltonian + +[`WindowMPOHamiltonian`](@ref) carves a finite interval out of an infinite Hamiltonian while keeping the infinite left and right environments intact. +This is the operator counterpart of a [`WindowMPS`](@ref) (see [Constructing states](@ref howto_states)), and the two are used together to study a finite region embedded in, and coupled to, an infinite bulk: + +```@example hamiltonians +H_window = WindowMPOHamiltonian(H_inf, 1:6) +``` + +The interval `1:6` selects which unit cells of `H_inf` become the mutable finite window; everything outside it is treated as the fixed infinite environment. diff --git a/docs/src/howto/index.md b/docs/src/howto/index.md new file mode 100644 index 000000000..909df5151 --- /dev/null +++ b/docs/src/howto/index.md @@ -0,0 +1,89 @@ +# [How-to guides](@id howto_index) + +These pages are task recipes: short, runnable answers to "how do I do X?". +They assume you already know the basics — if you are new to MPSKit, start with [Your first ground state](@ref tutorial_first_groundstate) instead. +Each recipe below stands on its own, so feel free to jump straight to the one you need. + +## States and operators + +**[Constructing states](@ref howto_states)** — building `FiniteMPS`, `InfiniteMPS`, `WindowMPS`, and `MultilineMPS` objects. +- A finite MPS — random states, initializers and element types, per-site spaces, product states, and wrapping your own site tensors. +- An infinite MPS — single- and multi-site unit cells, from spaces or from tensors. +- A window MPS — a mutable finite region embedded in infinite environments. +- A multiline MPS — stacking `InfiniteMPS` rows for boundary-MPS methods. +- States with symmetries — building MPS with `Rep[G]` graded spaces. + +**[Building Hamiltonians](@ref howto_hamiltonians)** — assembling MPO Hamiltonians from local operators. +- Finite Hamiltonian from local terms — `FiniteMPOHamiltonian` from `inds => operator` pairs. +- Infinite (translation-invariant) Hamiltonian — `InfiniteMPOHamiltonian` on a unit cell. +- Converting between boundary conditions — open vs. periodic finite chains from an infinite Hamiltonian. +- A window Hamiltonian — carving a finite interval out of an infinite Hamiltonian with `WindowMPOHamiltonian`. + +## Finding ground states + +**[Ground-state algorithms](@ref howto_groundstate_algorithms)** — configuring `find_groundstate`. +- Get a ground state with defaults — letting `find_groundstate` pick an algorithm for you. +- Configure finite-system DMRG — explicit `DMRG`/`DMRG2`, and chaining them with `&`. +- Configure infinite-system algorithms — `VUMPS`, `IDMRG`, and `IDMRG2`. +- Refine convergence with GradientGrassmann — Riemannian gradient descent after a cheaper warm-up. +- Control output and tolerances — `verbosity` levels and reusing `envs` between calls. + +**[Controlling bond dimension](@ref howto_bond_dimension)** — inspecting, growing, and shrinking bond dimension. +- Inspecting the current bond dimension — `left_virtualspace`/`right_virtualspace` and `dim`. +- Growing bond dimension — `RandExpand` (no Hamiltonian needed) and `OptimalExpand`. +- Reducing bond dimension — `SvdCut` and the in-place `changebonds!`. +- Truncation schemes — `truncrank`, `trunctol`, `notrunc`, `truncspace`, and combining them with `&`. +- Growing during finite MPS optimization — `DMRG2` and the `trscheme` keyword of `find_groundstate`. +- Growing during infinite MPS optimization — `IDMRG2` and `VUMPSSvdCut`. +- Chaining algorithms — composing bond-change and ground-state algorithms with `&`. + +## Dynamics + +**[Time evolution](@ref howto_time_evolution)** — real- and imaginary-time evolution of an MPS. +- Evolve a state through one time step — `timestep` with `TDVP`. +- Evolve over a time span — `time_evolve` across a vector of time points. +- Grow the bond dimension while evolving — `TDVP2` with a mandatory `trscheme`. +- Evolve an infinite state — single-site `TDVP` on an `InfiniteMPS`. +- Imaginary-time evolution — `imaginary_evolution = true` to cool towards the ground state. +- Build a time-evolution MPO — `make_time_mpo` (`WII`, `TaylorCluster`, `WI`) plus `approximate`. + +## Measurements + +**[Computing observables](@ref howto_observables)** — extracting physical quantities from an MPS. +- Local (one-site) expectation value — `expectation_value(ψ, i => O)`. +- Multi-site (contiguous) expectation value — tensor-product operators on an index tuple. +- Energy (full-MPO expectation value) — `expectation_value(ψ, H)` for a Hamiltonian MPO. +- Two-point correlators — `correlator`, including a full correlation profile over a range. +- Energy variance as a convergence check — `variance` as a diagnostic after a ground-state search. + +**[Entanglement entropy and spectrum](@ref howto_entanglement)** — reading off entanglement from the gauge tensors. +- Entanglement entropy at a single cut — `entropy(ψ, site)`. +- Entropy profile across every cut — collecting `entropy` over all sites. +- The entanglement spectrum — `entanglement_spectrum` as a sector-resolved vector. +- Sector-resolved spectrum — indexing the spectrum by symmetry sector with `keys`/`pairs`. +- Entanglement of an infinite MPS — `entropy`/`entanglement_spectrum` per unit-cell site. +- Plotting the spectrum — the `entanglementplot` recipe (requires Plots.jl). + +## Excitations + +**[Excited states](@ref howto_excitations)** — computing energy eigenstates beyond the ground state. +- Get a single excitation gap on an infinite chain — `QuasiparticleAnsatz` at a fixed momentum. +- Scan the dispersion relation — passing a range of momenta in one call. +- Target a symmetry sector — a charged quasiparticle via the `sector` keyword. +- Excited states on a finite chain — `QuasiparticleAnsatz`, `FiniteExcited`, and the Chepiga ansätze. +- Check excitation quality — `variance` on a quasiparticle state. +- Domain-wall excitations — quasiparticles interpolating between two distinct ground states. + +## Performance and hardware + +**[Parallelism and GPU support](@ref howto_parallelism_gpu)** — tuning how MPSKit uses the hardware. +- Setting BLAS threads — `BLAS.set_num_threads` and the OpenBLAS/MKL difference. +- Setting the MPSKit scheduler — `MPSKit.Defaults.set_scheduler!` with `:serial`/`:greedy`/`:dynamic`. +- Diagnosing the thread layout — ThreadPinning.jl `threadinfo`. +- Reducing memory usage — disabling multithreading to avoid `OutOfMemory`. +- GPU support — the experimental Adapt-based path for moving states onto a GPU. + +## Missing a recipe? + +If the task you're after isn't listed here, please open an issue at [QuantumKitHub/MPSKit.jl](https://github.com/QuantumKitHub/MPSKit.jl/issues) describing what you're trying to do. +Concrete task descriptions make the best new recipes. diff --git a/docs/src/howto/observables.md b/docs/src/howto/observables.md new file mode 100644 index 000000000..a69288767 --- /dev/null +++ b/docs/src/howto/observables.md @@ -0,0 +1,167 @@ +# [Computing observables](@id howto_observables) + +The examples on this page use MPSKit.jl, TensorKit.jl, and TensorKitTensors.jl. +See [Installation](@ref tutorial_installation) for how to add these packages to your environment. + +This page collects recipes for extracting physical quantities from an MPS: local and multi-site expectation values, the energy of a Hamiltonian, two-point correlators, and the energy variance as a convergence diagnostic. +All examples share a single namespace and build on state and operator objects you would have in hand after a ground-state calculation. + +```@example observables +using MPSKit, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ +``` + +For building MPS objects see [Constructing states](@ref howto_states). +For controlling the bond dimension during optimization see [Controlling bond dimension](@ref howto_bond_dimension). +The reference page for ground-state algorithms is [Ground-state algorithms](@ref lib_groundstate). + +--- + +## Setup: state and operators + +The examples below use a spin-1/2 `FiniteMPS` together with the Pauli operators from [TensorKitTensors.jl](https://github.com/QuantumKitHub/TensorKitTensors.jl). +These are `ComplexF64` `TensorMap`s, matching the default element type of the state. + +```@example observables +L = 8 +ψ = FiniteMPS(L, ℂ^2, ℂ^8) # random finite MPS, bond dim ≤ 8 + +# single-site Pauli operators +X = σˣ() +Z = σᶻ() +``` + +The finite TFIM Hamiltonian used in recipes 3 and 5 is built from these: + +```@example observables +lattice = fill(ℂ^2, L) +H = FiniteMPOHamiltonian(lattice, (i, i + 1) => -(X ⊗ X) for i in 1:(L - 1)) + + FiniteMPOHamiltonian(lattice, (i,) => -0.5 * Z for i in 1:L) +``` + +--- + +## 1. Local (one-site) expectation value + +Use `expectation_value(ψ, i => O)` to evaluate ⟨ψ|Oᵢ|ψ⟩ at a single site `i`. +The pair `i => O` identifies the site and the single-site operator. + +```@example observables +expectation_value(ψ, 4 => Z) # ⟨Z⟩ at site 4 +``` + +To compute a local observable at every site, broadcast over the indices: + +```@example observables +[expectation_value(ψ, i => Z) for i in 1:L] +``` + +!!! note + The state `ψ` must be normalised for the expectation value to be meaningful. + A freshly constructed `FiniteMPS` is normalised by default; if you modified + the tensors by hand, call `normalize!(ψ)` first. + +--- + +## 2. Multi-site (contiguous) expectation value + +For a product of operators on a contiguous range of sites, pass a tuple of indices together with a multi-site operator formed by taking tensor products `⊗`: + +```@example observables +# ⟨X₂ X₃⟩ — two-site operator on sites 2 and 3 +expectation_value(ψ, (2, 3) => X ⊗ X) +``` + +The operator `X ⊗ X` is a `{2,2}` `TensorMap` (two incoming, two outgoing legs) matching the two-site index tuple `(2, 3)`. +The tuple must be contiguous; arbitrary non-adjacent index sets are not supported by this form. + +```@example observables +# ⟨Z₁ Z₂ Z₃⟩ — three-site operator +expectation_value(ψ, (1, 2, 3) => Z ⊗ Z ⊗ Z) +``` + +--- + +## 3. Energy (full-MPO expectation value) + +When the operator is an [`AbstractMPO`](@ref) (e.g. a Hamiltonian), pass it directly without an index argument. +MPSKit evaluates the full contraction ⟨ψ|H|ψ⟩: + +```@example observables +E = expectation_value(ψ, H) +``` + +The result is a scalar; for a Hermitian `H` and a normalised `ψ` its imaginary part is zero up to floating-point noise. + +The same form works for `InfiniteMPS` with an `InfiniteMPOHamiltonian`, where the returned value is the energy **per unit cell**. + +!!! note + The full-MPO form automatically computes and caches the environments. + If you already have environments from a prior `find_groundstate` call you can + pass them as a trailing argument to avoid recomputation, but this is optional; + omitting them is always safe and correct. + +--- + +## 4. Two-point correlators + +[`correlator`](@ref) computes ⟨O₁ᵢ O₂ⱼ⟩ for two sites with `i < j`. +The recommended call uses a single two-site operator `O₁₂`: + +```@example observables +# ⟨Z₂ Zⱼ⟩ for a single target site j = 6 +correlator(ψ, Z ⊗ Z, 2, 6) +``` + +!!! warning + `i` must be strictly less than `j`. + Calling `correlator(ψ, O₁₂, i, j)` with `i ≥ j` will throw an error. + +### Correlation profile over a range + +Pass a range as `j` to obtain a vector of correlators — one entry per target site. +This is the efficient route for a full correlation profile: + +```@example observables +# ⟨Z₂ Zⱼ⟩ for j = 3, 4, …, L +corr = correlator(ψ, Z ⊗ Z, 2, 3:L) +``` + +The result is a `Vector` whose `k`-th element corresponds to `j = 3 + k - 1`. + +A common pattern is to normalise the correlator by ⟨Z⟩² to extract the connected part: + +```@example observables +z_mean = expectation_value(ψ, 2 => Z) +connected = [c - z_mean * expectation_value(ψ, j => Z) for (j, c) in zip(3:L, corr)] +``` + +This subtracts the disconnected part ``\langle Z_i\rangle\langle Z_j\rangle`` to leave the connected correlator ``\langle Z_i Z_j\rangle - \langle Z_i\rangle\langle Z_j\rangle``. + +--- + +## 5. Energy variance as a convergence check + +[`variance`](@ref) returns ⟨H²⟩ − ⟨H⟩², which is zero if and only if `ψ` is an exact eigenstate of `H`. +Use it as a quantitative convergence diagnostic after a ground-state search: + +```@example observables +var_E = variance(ψ, H) +``` + +A smaller variance indicates that `ψ` is closer to a true eigenstate. + +After running a ground-state algorithm the variance should have dropped significantly compared to the random starting state above: + +```@example observables +ψ_gs, envs, _ = find_groundstate(ψ, H, DMRG(; maxiter = 10)) +variance(ψ_gs, H) +``` + +!!! note + The `variance` function also accepts an optional pre-computed `envs` argument. + Pass the environments returned by `find_groundstate` to skip recomputation: + + ```julia + variance(ψ_gs, H, envs) + ``` diff --git a/docs/src/howto/parallelism_gpu.md b/docs/src/howto/parallelism_gpu.md new file mode 100644 index 000000000..dd5b6fe9c --- /dev/null +++ b/docs/src/howto/parallelism_gpu.md @@ -0,0 +1,156 @@ +# [Parallelism and GPU support](@id howto_parallelism_gpu) + +This page collects the practical knobs for controlling how MPSKit uses the hardware: +how to set BLAS threads, how to pick the MPSKit multithreading scheduler, how to inspect +the resulting thread layout, and what to do when a calculation runs out of memory. +It closes with a short, experimental note on moving states onto a GPU. + +For the reasoning behind these settings — why Julia threads and BLAS threads interact the +way they do, and where MPSKit actually parallelizes — see +[The parallelism model](@ref concept_parallelism_model). + +!!! note + Threading performance depends heavily on the hardware, the BLAS vendor, the size of + the problem, and the availability of memory and memory bandwidth. + There is no single setting that is optimal everywhere; the recipes below are sensible + starting points, and you should measure on your own machine. + +## Setting the number of BLAS threads + +Most of the heavy linear algebra in MPSKit ends up in BLAS routines (in particular `gemm`, +general matrix-matrix multiplication). +The number of BLAS threads is controlled through `LinearAlgebra.BLAS.set_num_threads`: + +```julia +using LinearAlgebra: BLAS +BLAS.set_num_threads(1) +``` + +With OpenBLAS (the default vendor), `set_num_threads` sets the **total** number of BLAS +threads held in a shared pool across all Julia threads. +When Julia is started with multiple threads, setting this to `1` lets MPSKit drive the +parallelism through its own (Julia-thread) machinery instead, which is often the best +option for OpenBLAS. + +With [MKL.jl](https://github.com/JuliaLinearAlgebra/MKL.jl) the semantics differ: the BLAS +thread count applies **per Julia thread**, so 4 Julia threads with 4 BLAS threads each spawn +16 BLAS threads in total. +In that case you typically want to lower the BLAS thread count to avoid oversubscribing the +physical cores. + +## Setting the MPSKit scheduler + +When Julia runs with multiple threads, MPSKit parallelizes parts of its algorithms through +[OhMyThreads.jl](https://juliafolds2.github.io/OhMyThreads.jl/stable/). +The behaviour is controlled by a global scheduler, set with `MPSKit.Defaults.set_scheduler!`: + +```julia +MPSKit.Defaults.set_scheduler!(:serial) # disable multithreading +MPSKit.Defaults.set_scheduler!(:greedy) # multithreading with greedy load-balancing +MPSKit.Defaults.set_scheduler!(:dynamic) # multithreading with dynamic load-balancing +``` + +`set_scheduler!` also accepts an `OhMyThreads.Scheduler` directly, or a symbol together with +keyword arguments that are forwarded to the corresponding OhMyThreads scheduler. +When left unset, the default is a serial scheduler if Julia was started with a single thread, +and a dynamic scheduler otherwise. +For the full list of schedulers and their keyword arguments, see the +[OhMyThreads.jl documentation](https://juliafolds2.github.io/OhMyThreads.jl/stable/refs/api/#Schedulers). + +## Diagnosing the thread layout + +Because the interaction between Julia threads and BLAS threads is easy to get wrong, it helps +to inspect the actual layout. +[ThreadPinning.jl](https://github.com/carstenbauer/ThreadPinning.jl) provides `threadinfo`, +which reports the Julia threads, their CPU mapping, and the BLAS backend and thread count: + +```julia-repl +julia> Threads.nthreads() +4 + +julia> using ThreadPinning; threadinfo(; blas = true, hints = true) + +System: 8 cores (2-way SMT), 1 sockets, 1 NUMA domains + +| 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 | + +# = Julia thread, # = HT, # = Julia thread on HT, | = Socket separator + +Julia threads: 4 +├ Occupied CPU-threads: 4 +└ Mapping (Thread => CPUID): 1 => 8, 2 => 5, 3 => 9, 4 => 2, + +BLAS: libopenblas64_.so +└ openblas_get_num_threads: 8 + +[ Info: jlthreads != 1 && blasthreads < cputhreads. You should either set BLAS.set_num_threads(1) (recommended!) or at least BLAS.set_num_threads(16). +[ Info: jlthreads < cputhreads. Perhaps increase number of Julia threads to 16? +``` + +Passing `hints = true` makes ThreadPinning emit the advisory messages shown above. +Loading a different BLAS backend changes the report; with MKL, for example, `threadinfo` +reports `libmkl_rt.so` and warns when the per-Julia-thread BLAS thread count exceeds the +available CPU threads per Julia thread: + +```julia-repl +julia> using MKL; threadinfo(; blas = true, hints = true) + +System: 8 cores (2-way SMT), 1 sockets, 1 NUMA domains + +| 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 | + +# = Julia thread, # = HT, # = Julia thread on HT, | = Socket separator + +Julia threads: 4 +├ Occupied CPU-threads: 4 +└ Mapping (Thread => CPUID): 1 => 11, 2 => 12, 3 => 1, 4 => 2, + +BLAS: libmkl_rt.so +├ mkl_get_num_threads: 8 +└ mkl_get_dynamic: true + +┌ Warning: blasthreads_per_jlthread > cputhreads_per_jlthread. You should decrease the number of MKL threads, i.e. BLAS.set_num_threads(4). +└ @ ThreadPinning ~/.julia/packages/ThreadPinning/qV2Cd/src/threadinfo.jl:256 +[ Info: jlthreads < cputhreads. Perhaps increase number of Julia threads to 16? +``` + +## Reducing memory usage + +MPSKit's multithreading spawns tasks in a nested fashion, each allocating and deallocating +memory in a tight loop. +This can put enough pressure on the garbage collector that memory usage climbs and, in the +worst case, an `OutOfMemory` error occurs before the garbage can be cleared. + +If you hit this, the most effective remedy is usually to disable MPSKit's multithreading, +by setting the scheduler to serial: + +```julia +MPSKit.Defaults.set_scheduler!(:serial) +``` + +The `derivatives` (the effective local operators applied during the sweeps) are reported to +be the most memory-intensive part, so this is where switching off multithreading helps most. + +For why this pressure arises, see +[Why memory pressure arises](@ref concept_parallelism_model). + +## GPU support + +!!! warning "Experimental" + GPU support in MPSKit is **experimental and minimal**. + There are no GPU-specific algorithms, kernels, or tuning options; the only surface is + an [Adapt.jl](https://github.com/JuliaGPU/Adapt.jl)-based mechanism for moving a state + or operator onto a different array type. + Treat this as preparatory infrastructure rather than a supported workflow. + +The package extension `MPSKitAdaptExt` defines `Adapt.adapt_structure` for `FiniteMPS`, +`InfiniteMPS`, `MPO`, and `MPOHamiltonian`. +This lets you convert the underlying tensors to a GPU array type with `Adapt.adapt`, after +which the algorithms dispatch through that array type. +A move onto a CUDA array would look like the following: + +```julia +using Adapt, CUDA +ψ_gpu = adapt(CuArray, ψ) +``` + diff --git a/docs/src/howto/states.md b/docs/src/howto/states.md new file mode 100644 index 000000000..3ea69ea04 --- /dev/null +++ b/docs/src/howto/states.md @@ -0,0 +1,267 @@ +# [Constructing states](@id howto_states) + +The examples on this page use MPSKit.jl and TensorKit.jl. +See [Installation](@ref tutorial_installation) for how to add these packages to your environment. + +This page collects recipes for building [`FiniteMPS`](@ref), [`InfiniteMPS`](@ref), [`WindowMPS`](@ref), and [`MultilineMPS`](@ref) objects. +All constructors live in the `MPSKit` namespace; the examples below assume + +```@example howto_states +using MPSKit, TensorKit +``` + +For background on what these types represent and how gauging works, see the [States](@ref lib_states) reference page. + +--- + +## 1. A finite MPS + +### From length, physical space, and maximum bond dimension + +The most common starting point: give the chain length `N`, the local physical `VectorSpace`, and the maximum allowed virtual space. +The constructor fills the tensors with random `ComplexF64` entries and trims the actual bond dimensions to full rank, so passing an over-large `maxVspace` is safe. + +```@example howto_states +L = 10 +d = ℂ^2 # spin-1/2 physical space (dim 2) +D = ℂ^16 # maximum bond dimension + +ψ = FiniteMPS(L, d, D) +``` + +Inspect the resulting virtual spaces with `left_virtualspace` and `right_virtualspace`. +To get the numeric bond dimension at bond `i` use `dim`: + +```@example howto_states +dim(left_virtualspace(ψ, 3)) # bond dimension between sites 2 and 3 +``` + +```@example howto_states +physicalspace(ψ, 1) # local Hilbert space at site 1 +``` + +### Choosing the initializer and element type + +Pass an initializer function (`rand` or `randn`) and an element type as the first two arguments: + +```@example howto_states +ψ_rand = FiniteMPS(rand, ComplexF64, L, d, D) # default — same as FiniteMPS(L, d, D) +ψ_randn = FiniteMPS(randn, ComplexF64, L, d, D) # normally distributed entries +``` + +The element type sets the scalar type of the tensors, e.g. `ComplexF64` (the default) or `Float64` for a real-valued state. + +### Per-site physical and virtual spaces + +When the physical space varies from site to site — or you want fine control over which bond gets which maximum dimension — pass vectors instead of scalars. +The `maxVspaces` vector must have length `N - 1` (one entry per bond): + +```@example howto_states +Pspaces = [ℂ^2, ℂ^3, ℂ^2, ℂ^3, ℂ^2] # alternating physical spaces +maxVspaces = [ℂ^8, ℂ^8, ℂ^8, ℂ^8] # one per bond (length N-1) + +ψ_het = FiniteMPS(rand, ComplexF64, Pspaces, maxVspaces) +``` + +```@example howto_states +physicalspace(ψ_het, 2) # ℂ^3 +``` + +### A product state (trivial virtual space) + +A product (bond-dimension-1) state has no entanglement: each site carries its own single-site state, independent of the others (with `rand`, a random such state per site). +Achieve this by passing `oneunit(d)` — the one-dimensional unit space of the same symmetry sector — as the maximum virtual space: + +```@example howto_states +ψ_prod = FiniteMPS(rand, ComplexF64, L, ℂ^2, oneunit(ℂ^2)) +dim(left_virtualspace(ψ_prod, 5)) # should be 1 +``` + +!!! note + `oneunit(V)` returns the one-dimensional trivial space matching the symmetry type of `V`. + For plain complex spaces, `oneunit(ℂ^2) == ℂ^1`. + +### From your own site tensors + +If you already have a vector of `TensorMap` objects with the correct index structure (virtual ⊗ physical ← virtual), pass them directly. +The constructor performs a left-to-right QR sweep to bring the state into a canonical form: + +```@example howto_states +# build three-site rank-1 tensors by hand +site_tensors = [rand(ComplexF64, ℂ^1 ⊗ ℂ^2 ← ℂ^1) for _ in 1:L] +ψ_from_tensors = FiniteMPS(site_tensors) +``` + +Set `normalize = true` to also normalize the state during construction (the default is `false` when passing raw tensors): + +```@example howto_states +ψ_normed = FiniteMPS(site_tensors; normalize = true) +``` + +--- + +## 2. An infinite MPS + +### Scalar convenience form + +Provide `d` and `D` as integers or spaces; the constructor builds a single-site unit cell: + +```@example howto_states +ψ_inf = InfiniteMPS(2, 20) # integers → plain ComplexSpace dimensions +``` + +```@example howto_states +ψ_inf2 = InfiniteMPS(ℂ^2, ℂ^20) # same, spelled out as spaces +``` + +### Multi-site unit cell + +Pass vectors of physical and virtual spaces. +The virtual spaces are those to the *right* of the corresponding sites: + +```@example howto_states +ψ_2site = InfiniteMPS([ℂ^2, ℂ^2], [ℂ^20, ℂ^20]) +``` + +```@example howto_states +physicalspace(ψ_2site, 1) +``` + +```@example howto_states +right_virtualspace(ψ_2site, 1) # virtual space to the right of site 1 +``` + +### Choosing element type and initializer + +```@example howto_states +ψ_inf_r = InfiniteMPS(rand, Float64, [ℂ^2], [ℂ^10]) +``` + +### From site tensors + +Tensors must form a valid periodic chain (virtual spaces must match across the unit-cell boundary): + +```@example howto_states +inf_tensors = [rand(ComplexF64, ℂ^4 ⊗ ℂ^2 ← ℂ^4)] +ψ_inf_t = InfiniteMPS(inf_tensors) +``` + +--- + +## 3. A window MPS + +A [`WindowMPS`](@ref) embeds a mutable finite window inside two infinite environments. + +### Slice an existing InfiniteMPS + +The simplest route: pick a region of length `L` from an `InfiniteMPS`. +Both environments are set to the same object (the original infinite state): + +```@example howto_states +ψ_bulk = InfiniteMPS(ℂ^2, ℂ^8) +ψ_win = WindowMPS(ψ_bulk, 6) # window of 6 sites +``` + +```@example howto_states +length(ψ_win) # 6 +``` + +### From space specifications + +Provide the window dimensions together with the infinite environments. +The boundary virtual spaces are taken automatically from `ψₗ`/`ψᵣ`: + +```@example howto_states +ψ_win2 = WindowMPS(rand, ComplexF64, 6, ℂ^2, ℂ^8, ψ_bulk) +``` + +### From a FiniteMPS and two environments + +Build a `FiniteMPS` with matching boundary virtual spaces first, then wrap: + +```@example howto_states +finite_part = FiniteMPS(6, ℂ^2, ℂ^8; left = ℂ^8, right = ℂ^8) +ψ_win3 = WindowMPS(ψ_bulk, finite_part, ψ_bulk) +``` + +!!! warning + When `ψᵣ` is omitted in the outer constructors, the right environment is + **the same object** as the left environment (no copy is made). + If you later evolve the two environments independently, pass `copy(ψ_bulk)` + explicitly as the right argument to avoid aliasing: + + ```julia + ψ_win_safe = WindowMPS(rand, ComplexF64, 6, ℂ^2, ℂ^8, ψ_bulk, copy(ψ_bulk)) + ``` + +--- + +## 4. A multiline MPS + +[`MultilineMPS`](@ref) stacks several [`InfiniteMPS`](@ref) rows and is used in boundary-MPS methods for 2D classical partition functions. + +### From a vector of InfiniteMPS rows + +```@example howto_states +row1 = InfiniteMPS(ℂ^2, ℂ^8) +row2 = InfiniteMPS(ℂ^2, ℂ^8) +ψ_ml = MultilineMPS([row1, row2]) +``` + +Access tensors with Cartesian `[row, col]` indexing: + +```@example howto_states +ψ_ml.AL[1, 1] # left-gauged tensor of row 1, unit-cell site 1 +``` + +### From space matrices + +Pass matrices whose rows correspond to MPS rows and columns to unit-cell sites: + +```@example howto_states +pspaces = fill(ℂ^2, 2, 2) # 2 rows × 2-site unit cell +Dspaces = fill(ℂ^8, 2, 2) +ψ_ml2 = MultilineMPS(pspaces, Dspaces) +``` + +--- + +## 5. States with symmetries + +All constructors accept TensorKit graded spaces. +Pass a `Rep[G]` physical space and a `Rep[G]` maximum virtual space; the constructor automatically selects the consistent fusion channels. + +### Finite MPS with U(1) symmetry + +```@example howto_states +# U(1) spin-1/2: physical space = spin up (charge +1/2) + spin down (charge -1/2) +d_u1 = Rep[U₁](1 // 2 => 1, -1 // 2 => 1) # dim 2 total +# the virtual space must span both charge parities (integer and half-integer): +# with only ±1/2 on each site, the total charge alternates parity bond to bond, +# so a purely half-integer virtual space would starve every even bond +D_u1 = Rep[U₁](0 => 2, 1 // 2 => 2, -1 // 2 => 2, 1 => 1, -1 => 1) + +ψ_u1 = FiniteMPS(rand, ComplexF64, L, d_u1, D_u1) +physicalspace(ψ_u1, 1) +``` + +```@example howto_states +dim(left_virtualspace(ψ_u1, 5)) # actual trimmed bond dimension ≤ dim(D_u1) +``` + +!!! note + The boundary virtual spaces default to `oneunit(spacetype(d_u1))`, i.e. the charge-0 sector. + Use the `left` and `right` keywords to target a different total charge: + + ```julia + # state in total charge-sector +1 (one more up-spin than down-spin) + ψ_charged = FiniteMPS(rand, ComplexF64, L, d_u1, D_u1; + right = Rep[U₁](1 => 1)) + ``` + +### Infinite MPS with U(1) symmetry + +```@example howto_states +ψ_inf_u1 = InfiniteMPS(d_u1, D_u1) +physicalspace(ψ_inf_u1, 1) +``` diff --git a/docs/src/howto/time_evolution.md b/docs/src/howto/time_evolution.md new file mode 100644 index 000000000..f6d85c450 --- /dev/null +++ b/docs/src/howto/time_evolution.md @@ -0,0 +1,155 @@ +# [Time evolution](@id howto_time_evolution) + +The examples on this page use MPSKit.jl, MPSKitModels.jl, TensorKit.jl, and TensorKitTensors.jl. +See [Installation](@ref tutorial_installation) for how to add these packages to your environment. + +MPSKit solves the (real- or imaginary-time) Schrödinger equation `i ∂ψ/∂t = H ψ` in two ways: by projecting the equation onto the MPS tangent space at every step ([`TDVP`](@ref)/[`TDVP2`](@ref)), or by first building an approximate evolution operator as an MPO ([`make_time_mpo`](@ref)) and repeatedly applying it. +This page gives task recipes for both routes. +All examples share a single namespace: + +```@example time_evo +using MPSKit, MPSKitModels, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ +``` + +--- + +## 1. Evolve a state through one time step + +[`timestep`](@ref) advances a state by a single `dt` under a Hamiltonian, using whichever [`TDVP`](@ref)-family algorithm you pass. +Its argument order is state, Hamiltonian, current time, time step, algorithm: + +```@example time_evo +L = 8 +g₀ = 0.5 +H₀ = transverse_field_ising(FiniteChain(L); g = g₀) + +ψ₀ = FiniteMPS(L, ℂ^2, ℂ^8) +ψ₀, = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)) +expectation_value(ψ₀, 4 => σᶻ()) +``` + +Now quench to a different transverse field `g₁` and take a single step: + +```@example time_evo +g₁ = 2.0 +H₁ = transverse_field_ising(FiniteChain(L); g = g₁) + +dt = 0.05 +ψ₁, envs₁ = timestep(ψ₀, H₁, 0.0, dt, TDVP()) +expectation_value(ψ₁, 4 => σᶻ()) +``` + +`timestep` returns the updated state together with an `envs` cache; reuse `envs₁` in the next call to avoid recomputation. +An in-place `timestep!` also exists, but only for finite MPS. + +--- + +## 2. Evolve over a time span + +[`time_evolve`](@ref) steps through an explicit vector of time points instead of a single `dt`, carrying the algorithm and environments through the whole span: + +```@example time_evo +t_span = 0:dt:(4dt) +ψ_span, envs_span = time_evolve(ψ₀, H₁, t_span, TDVP(); verbosity = 0) +expectation_value(ψ_span, 4 => σᶻ()) +``` + +`t_span` need not be uniformly spaced — `time_evolve` steps pairwise between consecutive entries, so any `AbstractVector` of increasing times works. +There is no exported `time_evolve!`; use `time_evolve` and rebind the result. + +--- + +## 3. Grow the bond dimension while evolving + +Single-site `TDVP` cannot change the bond dimension: whatever `ψ₀` starts with is what it keeps. +After a quench, entanglement typically grows and a fixed bond dimension eventually becomes insufficient. +[`TDVP2`](@ref) updates two sites at a time and truncates back down, so it can grow (or shrink) the bond dimension as it evolves. +Unlike `TDVP`, `TDVP2` requires `trscheme` — there is no default: + +```@example time_evo +ψ_tdvp2, envs_tdvp2 = timestep(ψ₀, H₁, 0.0, dt, TDVP2(; trscheme = truncrank(16))) +dim(left_virtualspace(ψ_tdvp2, 4)) +``` + +`TDVP2` only has a finite-MPS method. +For finite systems, an alternative to switching algorithms entirely is single-site `TDVP` with `alg_expand` set to a bond-expansion algorithm such as [`OptimalExpand`](@ref) (Controlled Bond Expansion, "CBE-TDVP"); see [Controlling bond dimension](@ref howto_bond_dimension) for `OptimalExpand` and other `changebonds` recipes. + +--- + +## 4. Evolve an infinite state + +Single-site `TDVP` also works directly on an `InfiniteMPS`: + +```@example time_evo +ψ₀_inf = InfiniteMPS(ℂ^2, ℂ^8) +H₀_inf = transverse_field_ising(; g = g₀) +ψ₀_inf, = find_groundstate(ψ₀_inf, H₀_inf, VUMPS(; verbosity = 0)) + +H₁_inf = transverse_field_ising(; g = g₁) +ψ₁_inf, envs₁_inf = timestep(ψ₀_inf, H₁_inf, 0.0, dt, TDVP()) +expectation_value(ψ₁_inf, 1 => σᶻ()) +``` + +`TDVP2` has no `InfiniteMPS` method, and single-site `TDVP` cannot change the bond dimension on an infinite state either. +Grow the bond dimension beforehand with `changebonds` (e.g. `OptimalExpand` or `VUMPSSvdCut`) — see [Controlling bond dimension](@ref howto_bond_dimension) — then evolve at the fixed, larger bond dimension. + +--- + +## 5. Imaginary-time evolution + +Passing `imaginary_evolution = true` evolves under `exp(-H dt)` instead of `exp(-iH dt)`, using the same real `dt`: + +```@example time_evo +ψ_im, envs_im = timestep(ψ₀, H₁, 0.0, dt, TDVP(); imaginary_evolution = true) +norm(ψ_im) +``` + +Imaginary-time evolution renormalizes the state at every step, so `norm(ψ_im)` stays `1` regardless of how the un-normalized weight would otherwise change. +Repeated imaginary-time steps damp excited-state components faster than the ground state, so this is often used as a (slower) alternative to `find_groundstate` for driving a state towards the ground state of `H₁`. +`time_evolve` accepts the same `imaginary_evolution` keyword for a span of imaginary-time steps. + +--- + +## 6. Build a time-evolution MPO + +When `H` is time-independent and `dt` is fixed, an alternative to repeated `timestep` calls is to build the evolution operator once as an MPO with [`make_time_mpo`](@ref), then apply it repeatedly with [`approximate`](@ref). + +[`WII`](@ref) builds a low-order MPO approximation and works for both finite and infinite Hamiltonians: + +```@example time_evo +O = make_time_mpo(H₁, dt, WII()) +``` + +[`TaylorCluster`](@ref) gives higher-order control via its `N` keyword (and accepts an additional `tol` keyword in `make_time_mpo`): + +```@example time_evo +O_taylor = make_time_mpo(H₁, dt, TaylorCluster(; N = 2); tol = 1.0e-10) +``` + +[`WI`](@ref) is a ready-made first-order `TaylorCluster` constant, so it is used as a value, not called as a constructor: + +```@example time_evo +O_wi = make_time_mpo(H₁, dt, WI) +``` + +Applying the MPO to a finite state uses [`approximate`](@ref) with a finite ground-state-style algorithm such as [`DMRG2`](@ref), which returns a 3-tuple including the final convergence error: + +```@example time_evo +ψ_mpo, envs_mpo, ϵ_mpo = approximate( + ψ₀, (O, ψ₀), DMRG2(; trscheme = truncrank(16), verbosity = 0) +) +expectation_value(ψ_mpo, 4 => σᶻ()) +``` + +Repeat the `approximate` call with the same `O` for successive time steps to build up a longer evolution. +Imaginary-time MPOs are built the same way, by passing `imaginary_evolution = true` to `make_time_mpo`; a real `dt` is promoted internally, so no manual complex conversion is needed. + +--- + +## Where to go next + +For choosing and configuring ground-state algorithms to prepare the pre-quench state, see [Ground-state algorithms](@ref howto_groundstate_algorithms). +For growing or shrinking bond dimension between or during evolution steps, see [Controlling bond dimension](@ref howto_bond_dimension). +For extracting expectation values and correlators from the evolved state, see [Computing observables](@ref howto_observables). +For background on the TDVP and time-evolution-MPO approaches and how they relate, see [Time evolution](@ref lib_time_evolution). diff --git a/docs/src/index.md b/docs/src/index.md index dd86bb1ba..0f47057ef 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -6,14 +6,14 @@ layout: home hero: name: MPSKit.jl text: Matrix product states in Julia - tagline: Efficient and versatile tools for working with matrix product states + tagline: Finite and infinite systems through one interface, with abelian, non-abelian, fermionic, and anyonic symmetries built in. image: src: /logo.svg alt: MPSKit.jl actions: - theme: brand - text: Manual - link: /man/intro + text: Get started + link: /tutorials/installation - theme: alt text: Examples link: /examples/ @@ -22,211 +22,158 @@ hero: link: https://github.com/QuantumKitHub/MPSKit.jl features: - - icon: 🔗 - title: States - details: Construction and manipulation of finite and infinite Matrix Product States (MPS). - - icon: 📏 - title: Observables - details: Calculation of observables and expectation values. - - icon: 🎯 - title: Optimization - details: Various optimization methods for obtaining MPS fixed points. - - icon: ⚛️ - title: Symmetries - details: Support for a wide variety of symmetries, including Abelian, non-Abelian, fermionic and anyonic symmetries. + - icon: + src: /assets/icons/finite-infinite.svg + alt: A finite chain above an infinite one + title: Finite & infinite, one interface + details: Run the same calculation on a finite chain or directly in the thermodynamic limit. FiniteMPS and InfiniteMPS share an API, so switching between them is a one-line change. + - icon: + src: /assets/icons/symmetry.svg + alt: A symmetric hexagon + title: Every symmetry + details: Abelian, non-Abelian, fermionic, and anyonic symmetries out of the box via the TensorKit backend — smaller bond dimensions and exact quantum numbers. + - icon: + src: /assets/icons/algorithms.svg + alt: An energy minimum + title: A complete algorithm suite + details: Ground states with DMRG, VUMPS, and IDMRG; real- and imaginary-time evolution with TDVP; and momentum-resolved excitations via the quasiparticle ansatz. + # REVIEW: make the "faster than ITensors" comparison explicit here once the benchmark results are published. + - icon: + src: /assets/icons/fast.svg + alt: A lightning bolt + title: Fast by design + details: Type-stable code paths and deliberate allocation strategies keep calculations quick out of the box, and non-Abelian symmetries such as SU(2) shrink the tensors you store and contract. --- ``` -## Table of contents - -- [Prerequisites](@ref) -- [States](@ref um_states) -- [Operators](@ref um_operators) -- [Algorithms](@ref um_algorithms) -- [Parallelism in julia](@ref) -- [Lattices](@ref lattices) +MPSKit.jl simulates one-dimensional quantum many-body systems with matrix product states and operators, at finite size or directly in the thermodynamic limit. +Built on the [TensorKit.jl](https://github.com/Jutho/TensorKit.jl) tensor backend, it is aimed at researchers and students who want tensor-network calculations without reimplementing the underlying machinery. ## Installation -MPSKit.jl is a part of the general registry, and can be installed via the package manager -as: +MPSKit.jl is a part of the general registry. +Together with the packages used throughout this documentation, it can be installed via the +package manager as: ``` -pkg> add MPSKit +pkg> add MPSKit TensorKit MPSKitModels TensorKitTensors Plots ``` +- `MPSKit` provides the matrix product state and operator types, together with the + ground-state, time-evolution, and bond-dimension algorithms. +- `TensorKit` supplies the tensor backend (`TensorMap`s and vector spaces) that MPSKit is + built on; it also re-exports the `@tensor` macro for contracting tensors by hand, along + with truncation-scheme constructors such as `truncrank` (from MatrixAlgebraKit). +- `MPSKitModels` collects pre-defined Hamiltonians and local operators for common physical + models. +- `TensorKitTensors` provides ready-made local operators, such as the Pauli operators used throughout the documentation. +- `Plots` is used to visualize results in several of the how-to guides and examples. -## Usage - -To get started with MPSKit, we recommend also including -[TensorKit.jl](https://github.com/Jutho/TensorKit.jl) and -[MPSKitModels.jl](https://github.com/QuantumKitHub/MPSKitModels.jl). The former defines the -tensor backend which is used throughout MPSKit, while the latter includes some common -operators and models. +For a step-by-step walkthrough that sets up a dedicated environment and verifies the installation, see [Installation](@ref tutorial_installation). -```julia -using TensorOperations -using TensorKit -using MPSKit -using LinearAlgebra: norm -``` +## A first calculation -### Finite Matrix Product States +Almost every MPSKit calculation follows the same three steps: build a Hamiltonian, optimize a state, and read off observables. +The transverse-field Ising chain (TFIM) makes each step concrete in a few lines. -```@setup finitemps -using LinearAlgebra -using TensorOperations -using TensorKit -using MPSKit +```@raw html +A matrix product state: a chain of tensors joined by virtual bonds, each with a physical leg ``` -Finite MPS are characterised by a set of tensors, one for each site, which each have 3 legs. -They can be constructed by specifying the virtual spaces and the physical spaces, i.e. the -dimensions of each of the legs. These are then contracted to form the MPS. In MPSKit, they -are represented by `FiniteMPS`, which can be constructed either by passing in the tensors -directly, or by specifying the dimensions of the legs. +A matrix product state is a chain of tensors: the horizontal bonds carry the virtual indices, and the leg hanging off each site is its physical index. -```@example finitemps -d = 2 # physical dimension -D = 5 # virtual dimension -L = 10 # number of sites +### 1. Build a Hamiltonian -mps = FiniteMPS(L, ComplexSpace(d), ComplexSpace(D)) # random MPS with maximal bond dimension D -``` +MPO Hamiltonians are assembled directly from local operators, so an arbitrary model — not just the built-in ones — takes only a couple of lines. +Here the single-site Pauli operators come from TensorKitTensors, and the TFIM is a nearest-neighbour `σᶻσᶻ` coupling plus a transverse `σˣ` field: -The `FiniteMPS` object then handles the gauging of the MPS, which is necessary for many of -the algorithms. This is done automatically when needed, and the user can access the gauged -tensors by getting and setting the `AL`, `AR`, `CR`/`CL` and `AC` fields, which each -represent a vector of these tensors. +```@example index +using MPSKit, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ -```@example finitemps -al = mps.AL[3] # left gauged tensor of the third site -@tensor E[a; b] := al[c, d, b] * conj(al[c, d, a]) -@show isapprox(E, id(right_virtualspace(mps, 3))) -``` -```@example finitemps -ar = mps.AR[3] # right gauged tensor of the third site -@tensor E[a; b] := ar[a, d, c] * conj(ar[b, d, c]) -@show isapprox(E, id(left_virtualspace(mps, 3))) +L = 16 +g = 0.5 +lattice = fill(ℂ^2, L) +H = FiniteMPOHamiltonian(lattice, (i, i + 1) => -(σᶻ() ⊗ σᶻ()) for i in 1:(L - 1)) + + FiniteMPOHamiltonian(lattice, (i,) => -g * σˣ() for i in 1:L) ``` -As the mps will be kept in a gauged form, updating a tensor will also update the gauged -tensors. For example, we can set the tensor of the third site to the identity, and the -gauged tensors will be updated accordingly. +See [Building Hamiltonians](@ref howto_hamiltonians) for infinite lattices, longer-range terms, and boundary conditions. -```@example finitemps -mps.C[3] = id(domain(mps.C[3])) -mps -``` +### 2. Optimize a state -These objects can then be used to compute observables and expectation values. For example, -the expectation value of the identity operator at the third site, which is equal to the norm -of the MPS, can be computed as: +Start from an initial [`FiniteMPS`](@ref) of bond dimension 16 and pass it, together with the Hamiltonian, to [`find_groundstate`](@ref). +The algorithm — here [`DMRG`](@ref) — is an ordinary argument, and its keywords (tolerance, iteration count, verbosity) tune the optimization: -```@example finitemps -N1 = LinearAlgebra.norm(mps) -N2 = expectation_value(mps, 3 => id(physicalspace(mps, 3))) -println("‖mps‖ = $N1") -println(" = $N2") +```@example index +ψ₀ = FiniteMPS(L, ℂ^2, ℂ^16) +ψ, envs, ϵ = find_groundstate(ψ₀, H, DMRG(; tol = 1e-10, verbosity = 0)) +ϵ # final convergence error ``` -Finally, the MPS can be optimized in order to determine groundstates of given Hamiltonians. -Using the pre-defined models in `MPSKitModels`, we can construct the ground state for the -transverse field Ising model: +Choosing a different optimizer such as [`VUMPS`](@ref), or raising the bond dimension, is a one-line change; see [Ground-state algorithms](@ref howto_groundstate_algorithms). -```@example finitemps -J = 1.0 -g = 0.5 -lattice = fill(ComplexSpace(2), 10) -X = TensorMap(ComplexF64[0 1; 1 0], ComplexSpace(2), ComplexSpace(2)) -Z = TensorMap(ComplexF64[1 0; 0 -1], space(X)) -H = FiniteMPOHamiltonian(lattice, (i, i+1) => -J * X ⊗ X for i in 1:length(lattice)-1) + - FiniteMPOHamiltonian(lattice, (i,) => - g * Z for i in 1:length(lattice)) -find_groundstate!(mps, H, DMRG(; maxiter=10)) -E0 = expectation_value(mps, H) -println(" = $real(E0)") -``` +### 3. Read off observables -### Infinite Matrix Product States +Expectation values are a single call. +The ground-state energy is just the Hamiltonian evaluated on the state: -```@setup infinitemps -using LinearAlgebra -using TensorOperations -using TensorKit -using MPSKit +```@example index +E = expectation_value(ψ, H) ``` -Similarly, an infinite MPS can be constructed by specifying the tensors for the unit cell, -characterised by the spaces (dimensions) thereof. +Local operators, correlators, and entanglement measures work the same way. +For instance, the von Neumann [`entropy`](@ref) across each bond traces out the entanglement profile of the chain: -```@example infinitemps -d = 2 # physical dimension -D = 5 # virtual dimension -mps = InfiniteMPS(d, D) # random MPS +```@example index +using Plots +S = [real(entropy(ψ, i)) for i in 1:(L - 1)] +plot( + 1:(L - 1), S; xlabel = "cut position", ylabel = "entanglement entropy", + marker = :circle, legend = false, title = "Entanglement across the chain" +) ``` -The `InfiniteMPS` object then handles the gauging of the MPS, which is necessary for many of -the algorithms. This is done automatically upon creation of the object, and the user can -access the gauged tensors by getting and setting the `AL`, `AR`, `C` and `AC` fields, -which each represent a (periodic) vector of these tensors. +See [Computing observables](@ref howto_observables) and [Entanglement entropy and spectrum](@ref howto_entanglement) for the full set, and [Your first ground state](@ref tutorial_first_groundstate) for a guided walkthrough of this calculation. -```@example infinitemps -al = mps.AL[1] # left gauged tensor of the first site -@tensor E[a; b] := al[c, d, b] * conj(al[c, d, a]) -@show isapprox(E, id(left_virtualspace(mps, 1))) -``` -```@example infinitemps -ar = mps.AR[1] # right gauged tensor of the first site -@tensor E[a; b] := ar[a, d, c] * conj(ar[b, d, c]) -@show isapprox(E, id(right_virtualspace(mps, 2))) -``` +## Beyond this example -As regauging the MPS is not possible without recomputing all the tensors, setting a single -tensor is not supported. Instead, the user should construct a new mps object with the -desired tensor, which will then be gauged upon construction. +The same three steps carry over to harder problems, usually by changing only the vector spaces or the state type: -```@example infinitemps -als = 3 .* mps.AL -mps = InfiniteMPS(als) -``` +- [**The thermodynamic limit**](@ref tutorial_thermodynamic_limit) works at infinite system size: replace `FiniteMPS` with an [`InfiniteMPS`](@ref) and `DMRG` with [`VUMPS`](@ref), and the rest of the code is unchanged. +- [**Using symmetries**](@ref tutorial_using_symmetries) imposes abelian or non-abelian symmetries by swapping the plain `ℂ^2` spaces for symmetric ones (for example an `SU2Space`), which also shrinks the bond dimension; see also the [Haldane gap](examples/quantum1d/2.haldane/index.md) example. +- [**The Hubbard model**](examples/quantum1d/6.hubbard/index.md) treats fermions with the same machinery, through TensorKit's graded vector spaces. -These objects can then be used to compute observables and expectation values. For example, -the norm of the MPS, which is equal to the expectation value of the identity operator can be -computed by: +## Where next -```@example infinitemps -N1 = norm(mps) -N2 = expectation_value(mps, 1 => id(physicalspace(mps, 1))) -println("‖mps‖ = $N1") -println(" = $N2") -``` +- [**Installation**](@ref tutorial_installation) and [**Your first ground state**](@ref tutorial_first_groundstate) open the tutorial track, walking through complete calculations from scratch. +- [**How-to guides**](@ref howto_index) are focused recipes for a known task, such as [constructing states](@ref howto_states), [building Hamiltonians](@ref howto_hamiltonians), and [computing observables](@ref howto_observables). +- [**Concepts**](@ref concept_vector_spaces) explain the ideas behind the library, from [vector spaces and TensorKit](@ref concept_vector_spaces) through [matrix product states](@ref concept_matrix_product_states), [operators and Hamiltonians](@ref concept_operators_and_hamiltonians), and [the algorithm landscape](@ref concept_algorithm_landscape). +- [**The examples gallery**](examples/index.md) collects longer, fully worked case studies across symmetries, infinite systems, and less common algorithms. +- [**The public API**](@ref public_api) is the curated, stable entry point to the full library reference. -!!! note "Normalization of infinite MPS" - Because infinite MPS cannot sensibly be normalized to anything but $1$, the `norm` of - an infinite MPS is always set to be $1$ at construction. If this were not the case, any - observable computed from the MPS would either blow up to infinity or vanish to zero. +## Ecosystem -Finally, the MPS can be optimized in order to determine groundstates of given Hamiltonians. -There are plenty of pre-defined models in `MPSKitModels`, but we can also manually construct -the ground state for the transverse field Ising model: +MPSKit builds on [TensorKit.jl](https://github.com/Jutho/TensorKit.jl), which supplies the tensors and vector spaces and handles the symmetries. +Models and ready-made operators come from [MPSKitModels.jl](https://github.com/QuantumKitHub/MPSKitModels.jl) and [TensorKitTensors.jl](https://github.com/QuantumKitHub/TensorKitTensors.jl). +All of these are part of the [QuantumKitHub](https://github.com/QuantumKitHub) organization; the TensorKit documentation is available [here](https://quantumkithub.github.io/TensorKit.jl/stable/). -```@example infinitemps -J = 1.0 -g = 0.5 -lattice = PeriodicVector([ComplexSpace(2)]) -X = TensorMap(ComplexF64[0 1; 1 0], ComplexSpace(2), ComplexSpace(2)) -Z = TensorMap(ComplexF64[1 0; 0 -1], space(X)) -H = InfiniteMPOHamiltonian(lattice, (1, 2) => -J * X ⊗ X, (1,) => - g * Z) -mps, = find_groundstate(mps, H, VUMPS(; maxiter=10)) -E0 = expectation_value(mps, H) -println(" = $(sum(real(E0)) / length(mps))") -``` +## Community and support -### Additional Resources +Questions and general discussion are welcome on [GitHub Discussions](https://github.com/QuantumKitHub/MPSKit.jl/discussions); bug reports belong on the [issue tracker](https://github.com/QuantumKitHub/MPSKit.jl/issues). +If you would like to contribute, see [CONTRIBUTING.md](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/CONTRIBUTING.md) on GitHub. -For more detailed information on the functionality and capabilities of MPSKit, refer to the -Manual section, or have a look at the [Examples](@ref) page. +## Citing MPSKit -Keep in mind that the documentation is still a work in progress, and that some features may -not be fully documented yet. If you encounter any issues or have questions, please check the -library's [issue tracker](https://github.com/QuantumKitHub/MPSKit.jl/issues) on the GitHub -repository and open a new issue. +If MPSKit.jl is useful for your research, please consider citing it — a citation is the most direct way to support the project and helps others find it. +The package is archived on Zenodo under the DOI [10.5281/zenodo.10654900](https://doi.org/10.5281/zenodo.10654900). +The [`CITATION.cff`](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/CITATION.cff) file in the repository always holds the up-to-date metadata, or you can use the BibTeX entry below: +```bibtex +@software{mpskitjl, + author = {Devos, Lukas and Van Damme, Maarten and Haegeman, Jutho}, + title = {{MPSKit.jl}}, + version = {v0.13.13}, + doi = {10.5281/zenodo.10654900}, + url = {https://github.com/QuantumKitHub/MPSKit.jl}, + year = {2026} +} +``` diff --git a/docs/src/lib/bond_dimension.md b/docs/src/lib/bond_dimension.md new file mode 100644 index 000000000..dfd3d8c7e --- /dev/null +++ b/docs/src/lib/bond_dimension.md @@ -0,0 +1,54 @@ +# [Bond dimension](@id lib_bond_dimension) + +Reference for changing the bond dimension of a state — expanding or truncating its virtual spaces — and for inspecting those virtual spaces directly. +For a task-oriented walkthrough see [Controlling bond dimension](@ref howto_bond_dimension); the full, canonical docstrings for the whole package live in the [Library](@ref lib_index) index. + +## Interface + +```@docs; canonical=false +changebonds +changebonds! +``` + +## Expansion and truncation algorithms + +```@docs; canonical=false +OptimalExpand +RandExpand +SvdCut +VUMPSSvdCut +SketchedExpand +``` + +!!! note + `SketchedExpand` is experimental: it uses randomized controlled bond expansion (CBE), so its reported error estimate is itself randomized, and it is only defined for `FiniteMPS`. + +## Inspecting the virtual spaces + +The bond dimension of an MPS or MPO is the dimension of the virtual space living on a given bond. +The accessors below return that `VectorSpace`, whose `dim` gives the numeric bond dimension. + +```@docs; canonical=false +left_virtualspace +right_virtualspace +physicalspace +``` + + diff --git a/docs/src/lib/excitations.md b/docs/src/lib/excitations.md new file mode 100644 index 000000000..48767b015 --- /dev/null +++ b/docs/src/lib/excitations.md @@ -0,0 +1,37 @@ +# [Excitations](@id lib_excitations) + +Reference for the excitation interface, its algorithms, and the quasiparticle state types it produces. +For a task-oriented walkthrough see the how-to guides. +The full, canonical docstrings for the whole package live in the [Library](@ref lib_index) index. + +## Interface + +```@docs; canonical=false +excitations +``` + +## Algorithms + +```@docs; canonical=false +QuasiparticleAnsatz +FiniteExcited +ChepigaAnsatz +ChepigaAnsatz2 +``` + +## Quasiparticle states + +These are the ansatz states produced by, and passed to, `excitations` on top of a ground state. + +```@docs; canonical=false +QP +LeftGaugedQP +RightGaugedQP +``` + + diff --git a/docs/src/lib/groundstate.md b/docs/src/lib/groundstate.md new file mode 100644 index 000000000..7ec0b3425 --- /dev/null +++ b/docs/src/lib/groundstate.md @@ -0,0 +1,21 @@ +# [Ground-state algorithms](@id lib_groundstate) + +Reference for the ground-state search interface and its algorithms. +For a task-oriented walkthrough see the how-to guides; the full, canonical docstrings for the whole package live in the [Library](@ref lib_index) index. + +## Interface + +```@docs; canonical=false +find_groundstate +``` + +## Algorithms + +```@docs; canonical=false +DMRG +DMRG2 +VUMPS +IDMRG +IDMRG2 +GradientGrassmann +``` diff --git a/docs/src/lib/lib.md b/docs/src/lib/lib.md index b1645c273..7f7b23eb0 100644 --- a/docs/src/lib/lib.md +++ b/docs/src/lib/lib.md @@ -1,4 +1,4 @@ -# Library documentation +# [Library documentation](@id lib_index) ```@autodocs Modules = [MPSKit] diff --git a/docs/src/lib/observables.md b/docs/src/lib/observables.md new file mode 100644 index 000000000..b882596eb --- /dev/null +++ b/docs/src/lib/observables.md @@ -0,0 +1,67 @@ +# [Observables and analysis](@id lib_observables) + +Reference for extracting physical quantities and analysis diagnostics from an MPS. +For a task-oriented walkthrough see the how-to guide [Computing observables](@ref howto_observables). +The full, canonical docstrings for the whole package live in the [Library](@ref lib_index) index. + +## Expectation values + +```@docs; canonical=false +expectation_value +``` + +!!! warning "Multiline environments" + The `expectation_value(::MultilineMPS, ::MultilineMPO, envs...)` method reuses the + passed environments without recomputing them for the operator (see the `# TODO: fix + environments` note in `src/algorithms/expval.jl`). + Results along this code path should be cross-checked against an independent calculation + until the environment handling is finalized. + +## Correlators + +```@docs; canonical=false +correlator +``` + +## Convergence diagnostics + +```@docs; canonical=false +variance +``` + +!!! warning "Variance of infinite quasiparticle states" + The `variance(::InfiniteQP, ::InfiniteMPOHamiltonian, envs)` method carries an + unresolved implementation note in `src/algorithms/toolbox.jl` and may be unreliable. + Verify its output before using it as a convergence diagnostic for quasiparticle states. + +## Transfer matrix and correlation length + +```@docs; canonical=false +correlation_length +marek_gap +transfer_spectrum +transferplot +``` + +## Entanglement + +Entropy and entanglement spectrum are computed from the state's bond/gauge tensors. +See the how-to [Entanglement entropy and spectrum](@ref howto_entanglement) for worked recipes. + +```@docs; canonical=false +entropy +entanglement_spectrum +entanglementplot +``` + + diff --git a/docs/src/lib/operators.md b/docs/src/lib/operators.md new file mode 100644 index 000000000..a6fbe02e3 --- /dev/null +++ b/docs/src/lib/operators.md @@ -0,0 +1,37 @@ +# [Operators](@id lib_operators) + +Reference for matrix product operators and Hamiltonians. +The full, canonical docstrings for the whole package live in the [Library](@ref lib_index) index. + +## Matrix product operators + +```@docs; canonical=false +AbstractMPO +MPO +FiniteMPO +InfiniteMPO +MultilineMPO +``` + +## Hamiltonians + +```@docs; canonical=false +MPOHamiltonian +FiniteMPOHamiltonian +InfiniteMPOHamiltonian +``` + +## Jordan-block MPO tensors + +```@docs; canonical=false +JordanMPOTensor +``` + +## Operator algebra + +```@docs; canonical=false +MultipliedOperator +TimedOperator +UntimedOperator +LazySum +``` diff --git a/docs/src/lib/public.md b/docs/src/lib/public.md new file mode 100644 index 000000000..c8e5fd8b9 --- /dev/null +++ b/docs/src/lib/public.md @@ -0,0 +1,53 @@ +# [Public API](@id public_api) + +This page is the curated, stable public API surface of MPSKit — the symbols that are exported and intended for direct use. +Each entry links to its full docstring in the [Library](@ref lib_index) index. +The category reference pages ([States](@ref lib_states), [Operators](@ref lib_operators), [Ground-state algorithms](@ref lib_groundstate)) group the same docstrings by topic. + +!!! note + Anything not listed here (or marked internal in the [Library](@ref lib_index) index) is + not part of the public API and may change without notice. + +## States + +[`FiniteMPS`](@ref), [`InfiniteMPS`](@ref), [`WindowMPS`](@ref), [`MultilineMPS`](@ref) + +## Operators and Hamiltonians + +[`AbstractMPO`](@ref), [`MPO`](@ref), [`FiniteMPO`](@ref), [`InfiniteMPO`](@ref), [`MultilineMPO`](@ref), [`MPOHamiltonian`](@ref), [`FiniteMPOHamiltonian`](@ref), [`InfiniteMPOHamiltonian`](@ref), [`JordanMPOTensor`](@ref), [`MultipliedOperator`](@ref), [`TimedOperator`](@ref), [`UntimedOperator`](@ref), [`LazySum`](@ref) + +## Environments + +[`environments`](@ref) + +## Ground states and boundaries + +[`find_groundstate`](@ref), [`leading_boundary`](@ref), [`approximate`](@ref), [`VUMPS`](@ref), [`VOMPS`](@ref), [`DMRG`](@ref), [`DMRG2`](@ref), [`IDMRG`](@ref), [`IDMRG2`](@ref), [`GradientGrassmann`](@ref) + +## Bond dimension + +[`changebonds`](@ref), [`OptimalExpand`](@ref), [`RandExpand`](@ref), [`SvdCut`](@ref), [`VUMPSSvdCut`](@ref) + +## Time evolution + +[`time_evolve`](@ref), [`timestep`](@ref), [`make_time_mpo`](@ref), [`TDVP`](@ref), [`TDVP2`](@ref), [`WI`](@ref), [`WII`](@ref), [`TaylorCluster`](@ref) + +## Excitations + +[`excitations`](@ref), [`FiniteExcited`](@ref), [`QuasiparticleAnsatz`](@ref), [`ChepigaAnsatz`](@ref), [`ChepigaAnsatz2`](@ref) + +## Linear problems and spectral functions + +[`propagator`](@ref), [`DynamicalDMRG`](@ref), [`NaiveInvert`](@ref), [`Jeckelmann`](@ref), [`exact_diagonalization`](@ref), [`fidelity_susceptibility`](@ref) + +## Observables and analysis + +[`expectation_value`](@ref), [`correlator`](@ref), [`variance`](@ref), [`correlation_length`](@ref), [`marek_gap`](@ref), [`transfer_spectrum`](@ref), [`entropy`](@ref), [`entanglement_spectrum`](@ref) + +## Boundary conditions + +[`open_boundary_conditions`](@ref), [`periodic_boundary_conditions`](@ref) + +## Utility + +[`PeriodicArray`](@ref), [`PeriodicVector`](@ref), [`PeriodicMatrix`](@ref), [`WindowArray`](@ref), [`left_virtualspace`](@ref), [`right_virtualspace`](@ref), [`physicalspace`](@ref), [`braille`](@ref) diff --git a/docs/src/lib/states.md b/docs/src/lib/states.md new file mode 100644 index 000000000..dc7f14587 --- /dev/null +++ b/docs/src/lib/states.md @@ -0,0 +1,25 @@ +# [States](@id lib_states) + +Reference for the matrix product state types. +The full, canonical docstrings for the whole package live in the [Library](@ref lib_index) index. + +## Matrix product states + +```@docs; canonical=false +FiniteMPS +InfiniteMPS +WindowMPS +MultilineMPS +``` + +## Quasiparticle states + +Excitation ansätze produced by [`excitations`](@ref). +These behave as vectors and are normally obtained from `excitations` rather than constructed directly. + +```@docs; canonical=false +QP +LeftGaugedQP +RightGaugedQP +``` + diff --git a/docs/src/lib/time_evolution.md b/docs/src/lib/time_evolution.md new file mode 100644 index 000000000..87b9ab9f7 --- /dev/null +++ b/docs/src/lib/time_evolution.md @@ -0,0 +1,38 @@ +# [Time evolution](@id lib_time_evolution) + +Reference for the time-evolution drivers and algorithms. +For a task-oriented walkthrough see the how-to guides. +The full, canonical docstrings for the whole package live in the [Library](@ref lib_index) index. + +## Drivers + +```@docs; canonical=false +time_evolve +timestep +timestep! +``` + +## MPS time-evolution algorithms + +```@docs; canonical=false +TDVP +TDVP2 +``` + +## Time-evolution MPOs + +For evolving with an explicitly constructed propagator MPO, e.g. for an [`InfiniteMPS`](@ref), use [`make_time_mpo`](@ref) with one of the expansion algorithms below. + +```@docs; canonical=false +make_time_mpo +TaylorCluster +WI +WII +``` + + diff --git a/docs/src/man/D_100_strided.png b/docs/src/man/D_100_strided.png deleted file mode 100644 index 585beb543..000000000 Binary files a/docs/src/man/D_100_strided.png and /dev/null differ diff --git a/docs/src/man/D_500_blas.png b/docs/src/man/D_500_blas.png deleted file mode 100644 index 6e2938376..000000000 Binary files a/docs/src/man/D_500_blas.png and /dev/null differ diff --git a/docs/src/man/D_500_strided.png b/docs/src/man/D_500_strided.png deleted file mode 100644 index d7a896e8c..000000000 Binary files a/docs/src/man/D_500_strided.png and /dev/null differ diff --git a/docs/src/man/algorithms.md b/docs/src/man/algorithms.md deleted file mode 100644 index 3cf71d52a..000000000 --- a/docs/src/man/algorithms.md +++ /dev/null @@ -1,389 +0,0 @@ -```@meta -DocTestSetup = :(using MPSKit, TensorKit, MPSKitModels) -``` - -# [Algorithms](@id um_algorithms) - -Here is a collection of the algorithms that have been added to MPSKit.jl. -If a particular algorithm is missing, feel free to let us know via an issue, or contribute via a PR. - -## Groundstates - -One of the most prominent use-cases of MPS is to obtain the ground state of a given (quasi-) one-dimensional quantum Hamiltonian. -In MPSKit.jl, this can be achieved through `find_groundstate`: - -```@docs; canonical=false -find_groundstate -``` - -There are a variety of algorithms that have been developed over the years, and many of them have been implemented in MPSKit. -Keep in mind that some of them are exclusive to finite or infinite systems, while others may work for both. -Many of these algorithms have different advantages and disadvantages, and figuring out the optimal algorithm is not always straightforward, since this may strongly depend on the model. -Here, we enumerate some of their properties in hopes of pointing you in the right direction. For convenience, the full list of algorithms is: - -- [DMRG](@ref) -- [DMRG2](@ref) -- [VUMPS](@ref) -- [Gradient descent](@ref) -- [TDVP](@ref) -- [Time evolution MPO](@ref) -- [Quasiparticle Ansatz](@ref) -- [Finite excitations](@ref) -- ["Chepiga Ansatz"](@ref) - -### DMRG - -Probably the most widely used algorithm for optimizing groundstates with MPS is [`DMRG`](@ref) and its variants. -This algorithm sweeps through the system, optimizing a single site or pair of sites while keeping all others fixed. -Since this local problem can be solved efficiently, the global optimal state follows by alternating through the system. -However, because of the single-site nature of this algorithm, this can never alter the bond dimension of the state, such that there is no way of dynamically increasing the precision. -This can become particularly relevant in the cases where symmetries are involved, since then finding a good distribution of charges is also required. -To circumvent this, it is also possible to optimize over two sites at the same time with [`DMRG2`](@ref), followed by a truncation back to the single site states. -This can dynamically change the bond dimension but comes at an increase in cost. - -```@docs; canonical=false -DMRG -DMRG2 -``` - -For infinite systems, a similar approach can be used by dynamically adding new sites to the middle of the system and optimizing over them. -This gradually increases the system size until the boundary effects are no longer felt. -However, because of this approach, for critical systems this algorithm can be quite slow to converge, since the number of steps needs to be larger than the correlation length of the system. -Again, both a single-site and a two-site version are implemented, to have the option to dynamically increase the bonddimension at a higher cost. - -```@docs; canonical=false -IDMRG -IDMRG2 -``` - -### VUMPS - -[`VUMPS`](@ref) is an (I)DMRG inspired algorithm that can be used to variationally find the ground state as a Uniform (infinite) Matrix Product State. -In particular, a local update is followed by a re-gauging procedure that effectively replaces the entire network with the newly updated tensor. -Compared to IDMRG, this often achieves a higher rate of convergence, since updates are felt throughout the system immediately. -Nevertheless, this algorithm only works whenever the state is injective, i.e. there is a unique ground state. -Since VUMPS is a single-site algorithm, it cannot alter the bond dimension. - -```@docs; canonical=false -VUMPS -``` - -### Gradient descent - -Both finite and infinite matrix product states can be parametrized by a set of isometric tensors, -which we can optimize over. -Making use of the geometry of the manifold (a Grassmann manifold), we can greatly outperform naive optimization strategies. -Compared to the other algorithms, quite often the convergence rate in the tail of the optimization procedure is higher, such that often the fastest method combines a different algorithm far from convergence with this algorithm close to convergence. -Since this is again a single-site algorithm, there is no way to alter the bond dimension. - -```@docs; canonical=false -GradientGrassmann -``` - -## Time evolution - -Given a particular state, it can also often be useful to examine the evolution of certain properties over time. -To that end, there are two main approaches to solving the Schrödinger equation in MPSKit. - -```math -i \hbar \frac{d}{dt} \Psi = H \Psi \implies \Psi(t) = \exp{\left(-iH(t - t_0)\right)} \Psi(t_0) -``` - -```@docs; canonical=false -timestep -time_evolve -make_time_mpo -``` - -### TDVP - -The first is focused around approximately solving the equation for a small timestep, and repeating this until the desired evolution is achieved. -This can be achieved by projecting the equation onto the tangent space of the MPS, and then solving the results. -This procedure is commonly referred to as the [`TDVP`](@ref) algorithm, which again has a two-site variant to allow for dynamically altering the bond dimension. - -```@docs; canonical=false -TDVP -TDVP2 -``` - -### Time evolution MPO - -The other approach instead tries to first approximately represent the evolution operator, and only then attempts to apply this operator to the initial state. -Typically the first step happens through [`make_time_mpo`](@ref), while the second can be achieved through [`approximate`](@ref). -Here, there are several algorithms available - -```@docs; canonical=false -WI -WII -TaylorCluster -``` - -## Excitations - -It might also be desirable to obtain information beyond the lowest energy state of a given system, and study the dispersion relation. -While it is typically not feasible to resolve states in the middle of the energy spectrum, there are several ways to target a few of the lowest-lying energy states. - -```@docs; canonical=false -excitations -``` - -```@setup excitations -using TensorKit, MPSKit, MPSKitModels -``` - -### Quasiparticle Ansatz - -The Quasiparticle Ansatz offers an approach to compute low-energy eigenstates in quantum -systems, playing a key role in both finite and infinite systems. It leverages localized -perturbations for approximations, as detailed in [haegeman2013](@cite). - -#### Finite Systems: - -In finite systems, we approximate low-energy states by altering a single tensor in the -Matrix Product State (MPS) for each site, and summing these across all sites. This method -introduces additional gauge freedoms, utilized to ensure orthogonality to the ground state. -Optimizing within this framework translates to solving an eigenvalue problem. For example, -in the transverse field Ising model, we calculate the first excited state as shown in the -provided code snippet, and check the accuracy against theoretical values. Some deviations -are expected, both due to finite-bond-dimension and finite-size effects. - -```@example excitations -# Model parameters -g = 10.0 -L = 16 -H = transverse_field_ising(FiniteChain(L); g) - -# Finding the ground state -ψ₀ = FiniteMPS(L, ℂ^2, ℂ^32) -ψ, = find_groundstate(ψ₀, H; verbosity=0) - -# Computing excitations using the Quasiparticle Ansatz -Es, ϕs = excitations(H, QuasiparticleAnsatz(), ψ; num=1) -isapprox(Es[1], 2(g - 1); rtol=1e-2) -``` - -#### Infinite Systems: - -The ansatz in infinite systems maintains translational invariance by perturbing every site -in the unit cell in a plane-wave superposition, requiring momentum specification. The -[Haldane gap](https://iopscience.iop.org/article/10.1088/0953-8984/1/19/001) computation in -the Heisenberg model illustrates this approach. - -```@example excitations -# Setting up the model and momentum -momentum = π -H = heisenberg_XXX() - -# Ground state computation -ψ₀ = InfiniteMPS(ℂ^3, ℂ^48) -ψ, = find_groundstate(ψ₀, H; verbosity=0) - -# Excitation calculations -Es, ϕs = excitations(H, QuasiparticleAnsatz(), momentum, ψ) -isapprox(Es[1], 0.41047925; atol=1e-4) -``` - -#### Charged excitations: - -When dealing with symmetric systems, the default optimization is for eigenvectors with -trivial total charge. However, quasiparticles with different charges can be obtained using -the sector keyword. For instance, in the transverse field Ising model, we consider an -excitation built up of flipping a single spin, aligning with `Z2Irrep(1)`. - -```@example excitations -g = 10.0 -L = 16 -H = transverse_field_ising(Z2Irrep, FiniteChain(L); g) -ψ₀ = FiniteMPS(L, Z2Space(0 => 1, 1 => 1), Z2Space(0 => 16, 1 => 16)) -ψ, = find_groundstate(ψ₀, H; verbosity=0) -Es, ϕs = excitations(H, QuasiparticleAnsatz(), ψ; num=1, sector=Z2Irrep(1)) -isapprox(Es[1], 2(g - 1); rtol=1e-2) # infinite analytical result -``` - -```@docs; canonical=false -QuasiparticleAnsatz -``` - -### Finite excitations - -For finite systems we can also do something else - find the ground state of the Hamiltonian + -``\\text{weight} \sum_i | \\psi_i ⟩ ⟨ \\psi_i ``. This is also supported by calling - -```@example excitations -# Model parameters -g = 10.0 -L = 16 -H = transverse_field_ising(FiniteChain(L); g) - -# Finding the ground state -ψ₀ = FiniteMPS(L, ℂ^2, ℂ^32) -ψ, = find_groundstate(ψ₀, H; verbosity=0) - -Es, ϕs = excitations(H, FiniteExcited(), ψ; num=1) -isapprox(Es[1], 2(g - 1); rtol=1e-2) -``` - -```@docs; canonical=false -FiniteExcited -``` - -### "Chepiga Ansatz" - -Computing excitations in critical systems poses a significant challenge due to the diverging -correlation length, which requires very large bond dimensions. However, we can leverage this -long-range correlation to effectively identify excitations. In this context, the left/right -gauged MPS, serving as isometries, are effectively projecting the Hamiltonian into the -low-energy sector. This projection method is particularly effective in long-range systems, -where excitations are distributed throughout the entire system. Consequently, the low-lying -energy spectrum can be extracted by diagonalizing the effective Hamiltonian (without any -additional DMRG costs!). The states of these excitations are then represented by the ground -state MPS, with one site substituted by the corresponding eigenvector. This approach is -often referred to as the 'Chepiga ansatz', named after one of the authors of this paper -[chepiga2017](@cite). - -This is supported via the following syntax: - -```@example excitations -g = 10.0 -L = 16 -H = transverse_field_ising(FiniteChain(L); g) -ψ₀ = FiniteMPS(L, ComplexSpace(2), ComplexSpace(32)) -ψ, envs, = find_groundstate(ψ₀, H; verbosity=0) -E₀ = real(sum(expectation_value(ψ, H, envs))) -Es, ϕs = excitations(H, ChepigaAnsatz(), ψ, envs; num=1) -isapprox(Es[1] - E₀, 2(g - 1); rtol=1e-2) # infinite analytical result -``` - -In order to improve the accuracy, a two-site version also exists, which varies two -neighbouring sites: - -```@example excitations -Es, ϕs = excitations(H, ChepigaAnsatz2(), ψ, envs; num=1) -isapprox(Es[1] - E₀, 2(g - 1); rtol=1e-2) # infinite analytical result -``` - -## `changebonds` - -Many of the previously mentioned algorithms do not possess a way to dynamically change to -bond dimension. This is often a problem, as the optimal bond dimension is often not a priori -known, or needs to increase because of entanglement growth throughout the course of a -simulation. [`changebonds`](@ref) exposes a way to change the bond dimension of a given -state. - -```@docs; canonical=false -changebonds -``` - -There are several different algorithms implemented, each having their own advantages and -disadvantages: - -* [`SvdCut`](@ref): The simplest method for changing the bonddimension is found by simply - locally truncating the state using an SVD decomposition. This yields a (locally) optimal - truncation, but clearly cannot be used to increase the bond dimension. Note that a - globally optimal truncation can be obtained by using the [`SvdCut`](@ref) algorithm in - combination with [`approximate`](@ref). Since the output of this method might have a - truncated bonddimension, the new state might not be identical to the input state. - The truncation is controlled through `trscheme`, which dictates how the singular values of - the original state are truncated. - - -* [`OptimalExpand`](@ref): This algorithm is based on the idea of expanding the bond - dimension by investigating the two-site derivative, and adding the most important blocks - which are orthogonal to the current state. From the point of view of a local two-site - update, this procedure is *optimal*, but it requires to evaluate a two-site derivative, - which can be costly when the physical space is large. The state will remain unchanged, but - a one-site scheme will now be able to push the optimization further. The subspace used for - expansion can be truncated through `trscheme`, which dictates how many singular values will - be added. - -* [`RandExpand`](@ref): This algorithm similarly adds blocks orthogonal to the current - state, but does not attempt to select the most important ones, and rather just selects - them at random. The advantage here is that this is much cheaper than the optimal expand, - and if the bond dimension is grown slow enough, this still obtains a very good expansion - scheme. Again, The state will remain unchanged and a one-site scheme will now be able to - push the optimization further. The subspace used for expansion can be truncated through - `trscheme`, which dictates how many orthogonal vectors will be added. - -* [`VUMPSSvdCut`](@ref): This algorithm is based on the [`VUMPS`](@ref) algorithm, and - consists of performing a two-site update, and then truncating the state back down. Because - of the two-site update, this can again become expensive, but the algorithm has the option - of both expanding as well as truncating the bond dimension. Here, `trscheme` controls the - truncation of the full state after the two-site update. - -## Leading boundary - -For statistical mechanics partition functions we want to find the approximate leading -boundary MPS. Again this can be done with VUMPS: - -```julia -th = nonsym_ising_mpo() -ts = InfiniteMPS([ℂ^2],[ℂ^20]); -(ts,envs,_) = leading_boundary(ts,th,VUMPS(maxiter=400,verbosity=false)); -``` - -If the mpo satisfies certain properties (positive and hermitian), it may also be possible to -use GradientGrassmann. - -```@docs; canonical=false -leading_boundary -``` - -## `approximate` - -Often, it is useful to approximate a given MPS by another, typically by one of a different -bond dimension. This is achieved by approximating an application of an MPO to the initial -state, by a new state. - -```@docs; canonical=false -approximate -``` - -## Varia - -What follows is a medley of lesser known (or used) algorithms and don't entirely fit under -one of the above categories. - -### Dynamical DMRG - -Dynamical DMRG has been described in other papers and is a way to find the propagator. The -basic idea is that to calculate ``G(z) = ⟨ V | (H-z)^{-1} | V ⟩ `` , one can variationally -find ``(H-z) |W ⟩ = | V ⟩ `` and then the propagator simply equals ``G(z) = ⟨ V | W ⟩``. - -```@docs; canonical=false -propagator -DynamicalDMRG -NaiveInvert -Jeckelmann -``` - -### fidelity susceptibility - -The fidelity susceptibility measures how much the ground state changes when tuning a -parameter in your Hamiltonian. Divergences occur at phase transitions, making it a valuable -measure when no order parameter is known. - -```@docs; canonical=false -fidelity_susceptibility -``` - -### Boundary conditions - -You can impose periodic or open boundary conditions on an infinite Hamiltonian, to generate a finite counterpart. -In particular, for periodic boundary conditions we still return an MPO that does not form a closed loop, such that it can be used with regular matrix product states. -This is straightforward to implement but, and while this effectively squares the bond dimension, it is still competitive with more advanced periodic MPS algorithms. - -```@docs; canonical=false -open_boundary_conditions -periodic_boundary_conditions -``` - -### Exact diagonalization - -As a side effect, our code supports exact diagonalization. The idea is to construct a finite -matrix product state with maximal bond dimension, and then optimize the middle site. Because -we never truncate the bond dimension, this single site effectively parametrizes the entire -Hilbert space. - -```@docs; canonical=false -exact_diagonalization -``` diff --git a/docs/src/man/environments.md b/docs/src/man/environments.md deleted file mode 100644 index 6d431e05e..000000000 --- a/docs/src/man/environments.md +++ /dev/null @@ -1,66 +0,0 @@ -# [Environments](@id um_environments) - -In many tensor network algorithms we encounter partially contracted tensor networks. -In DMRG for example, one needs to know the sum of all the Hamiltonian contributions left and right of the site that we want to optimize. -If you then optimize the neighboring site to the right, you only need to add one new contribution to the previous sum of Hamiltonian contributions. - -This kind of information is stored in the environment objects. -The goal is that the user should preferably never have to deal with these objects, but being aware of the inner workings may allow you to write more efficient code. -That is why they are nonetheless included in the manual. - -## Finite Environments - -When you create a state and a Hamiltonian: - -```julia -state = FiniteMPS(rand, ComplexF64, 20, ℂ^2, ℂ^10); -operator = nonsym_ising_ham(); -``` - -an environment object can be created by calling -```julia -envs = environments(state, operator, state) -``` - -The partially contracted mpohamiltonian left of site i can then be queried using: - -```julia -@time leftenv(envs, i, state) -``` - -This may take some time, but a subsequent call should be a lot quicker - -```julia -@time leftenv(envs, i - 1, state) -``` - -Behind the scenes the `envs` stored all tensors it used to calculate leftenv (state.AL[1 .. i]) and when queried again, it checks if the tensors it previously used are identical (using ===). If so, it can simply return the previously stored results. If not, it will recalculate again. If you update a tensor in-place, the caches cannot know using === that the actual tensors have changed. If you do this, you have to call poison!(state,i). - -As an optional argument, many algorithms allow you to pass in an environment object, and they also return an updated one. Therefore, for time evolution code, it is more efficient to give it the updated caches every time step, instead of letting it recalculate. - -## Infinite Environments - -Infinite Environments are very similar : -```julia -state = InfiniteMPS(ℂ^2, ℂ^10) -operator = transverse_field_ising() -envs = environments(state, operator, state) -``` - -There are also some notable differences. Infinite environments typically require solving linear problems or eigenvalue problems iteratively with finite precision. To find out what precision we used we can type: -```julia -(cache.tol,cache.maxiter) -``` - -To recalculate with a different precision : -```julia -cache.tol=1e-8; -recalculate!(cache,state) -``` - -Unlike their finite counterparts, recalculating is not done automatically. To get the environment for a different state one has to recalculate explicitly! -```julia -different_state = InfiniteMPS([ℂ^2],[ℂ^10]); -recalculate!(cache,different_state) -leftenv(cache,3,different_state) -``` diff --git a/docs/src/man/intro.md b/docs/src/man/intro.md deleted file mode 100644 index ee6c3ddeb..000000000 --- a/docs/src/man/intro.md +++ /dev/null @@ -1,93 +0,0 @@ -# Prerequisites - -The following sections describe the prerequisites for using MPSKit. If you are already -familiar with the concepts of MPSKit and TensorKit, you can skip to the [Conventions](@ref) -sections. - -## TensorKit - -```@example tensorkit -using TensorKit -``` - -MPSKit uses the tensors defined in [TensorKit.jl](https://github.com/Jutho/TensorKit.jl) as -its underlying data structure. This is what allows the library to be generic with respect to -the symmetry of the tensors. The main difference with regular multi-dimensional arrays is -the notion of a partition of the dimensions in **incoming** and **outgoing**, which are -respectively called **domain** and **codomain**. In other words, a `TensorMap` can be -interpreted as a linear map from its domain to its codomain. Additionally, as generic -symmetries are supported, in general the structure of the indices are not just integers, but -are given by spaces. - -The general syntax for creating a tensor is similar to the creation of arrays, where the -`axes` or `size` specifiers are replaced with `VectorSpace` objects: -```julia -zeros(scalartype, codomain, domain) -rand(scalartype, codomain ← domain) # ← is the `\leftarrow` operator -``` - -For example, the following creates a random tensor with three legs, each of which has -dimension two, however with different partitions. - -```@example tensorkit -V1 = ℂ^2 # ℂ is the `\bbC` operator, equivalent to ComplexSpace(10) -t1 = rand(Float64, V1 ⊗ V1 ⊗ V1) # all spaces in codomain -t2 = rand(Float64, V1, V1 ⊗ V1) # one space in codomain, two in domain -``` - -We can now no longer trivially add them together: - -```@example tensorkit -try #hide -t1 + t2 # incompatible partition -catch err; Base.showerror(stderr, err); end #hide -``` -But this can be resolved by permutation: - -```@example tensorkit -try #hide -t1 + permute(t2, (1, 2, 3), ()) # incompatible arrows -catch err; Base.showerror(stderr, err); end #hide -``` - -These abstract objects can represent not only plain arrays but also symmetric tensors. The -following creates a symmetric tensor with ℤ₂ symmetry, again with three legs of dimension -two. However, now the dimension two is now split over even and odd sectors of ℤ₂. - -```@example tensorkit -V2 = Z2Space(0 => 1, 1 => 1) -t3 = rand(Float64, V2 ⊗ V2, V2) -``` - -For more information, check out the [TensorKit documentation](https://quantumkithub.github.io/TensorKit.jl/stable/)! - -## Conventions - -The general definition of an MPS tensor is as follows: - -```@raw html -convention MPSTensor -``` - -These tensors are allowed to have an arbitrary number of physical legs, and both `FiniteMPS` -as well as `InfiniteMPS` will be able to handle the resulting objects. This allows for -example for the definition of boundary tensors in PEPS code, which have two physical legs. - -Similarly, the definition of a bond tensor, appearing in between two MPS tensors, is as -follows: - -```@raw html -convention BondTensor -``` - -Finally, the definition of a MPO tensor, which is used to represent statistical mechanics -problems as well as quantum Hamiltonians, is represented as: - -```@raw html -convention MPOTensor -``` - -While this results at first glance in the not very intuitive ordering of spaces as $V_l -\otimes P \leftarrow P \otimes V_r$, this is actually the most natural ordering for keeping -the algorithms planar. In particular, this is relevant for dealing with fermionic systems, -where additional crossings would lead to sign problems. diff --git a/docs/src/man/lattices.md b/docs/src/man/lattices.md deleted file mode 100644 index 7ec0c4ad0..000000000 --- a/docs/src/man/lattices.md +++ /dev/null @@ -1,4 +0,0 @@ -# [Lattices](@id lattices) - -!!! warning - This section is still under construction. Coming soon! \ No newline at end of file diff --git a/docs/src/man/operators.md b/docs/src/man/operators.md deleted file mode 100644 index 05525add2..000000000 --- a/docs/src/man/operators.md +++ /dev/null @@ -1,276 +0,0 @@ -# [Operators](@id um_operators) - -In analogy to how we can define matrix product states as a contraction of local tensors, a -similar construction exist for operators. To that end, a Matrix Product Operator (MPO) is -nothing more than a collection of local [`MPOTensor`](@ref MPSKit.MPOTensor) objects, contracted along a -line. Again, we can distinguish between finite and infinite operators, with the latter being -represented by a periodic array of MPO tensors. - -## FiniteMPO - -Starting off with the simplest case, a basic [`FiniteMPO`](@ref) is a vector of `MPOTensor` objects. -These objects can be created either directly from a vector of `MPOTensor`s, or starting from -a dense operator (a subtype of `AbstractTensorMap`), which is then decomposed into a -product of local tensors. - -```@raw html -MPO -``` - -```@setup operators -using TensorKit, MPSKit, MPSKitModels -``` - -```@example operators -S_x = TensorMap(ComplexF64[0 1; 1 0], ℂ^2 ← ℂ^2) -S_z = TensorMap(ComplexF64[1 0; 0 -1], ℂ^2 ← ℂ^2) -O_xzx = FiniteMPO(S_x ⊗ S_z ⊗ S_x); -``` - -The individual tensors are accessible via regular indexing. Note that the tensors are -internally converted to the `MPOTensor` objects, thus having four indices. In this specific -case, the left- and right virtual spaces are trivial, but this is not a requirement. - -```@example operators -O_xzx[1] -``` - -!!! warning - The local tensors are defined only up to a gauge transformation of the virtual spaces. - This means that the tensors are not uniquely defined, and special care must be taken - when comparing MPOs on an element-wise basis. - -For convenience, a number of utility functions are defined for probing the structure of the -constructed MPO. For example, the spaces can be queried as follows: - -```@example operators -left_virtualspace(O_xzx, 2) -right_virtualspace(O_xzx, 2) -physicalspace(O_xzx, 2) -``` - -MPOs also support a range of linear algebra operations, such as addition, subtraction and -multiplication, either among themselves or with a finite MPS. Here, it is important to note -that these operations will increase the virtual dimension of the resulting MPO or MPS, and -this naive application is thus typically not optimal. For approximate operations that do not -increase the virtual dimension, the more advanced algorithms in the [um_algorithms](@ref) -sections should be used. - -```@example operators -O_xzx² = O_xzx * O_xzx -println("Virtual dimension of O_xzx²: ", left_virtualspace(O_xzx², 2)) -O_xzx_sum = 0.1 * O_xzx + O_xzx² -println("Virtual dimension of O_xzx_sum: ", left_virtualspace(O_xzx_sum, 2)) -``` - -```@example operators -O_xzx_sum * FiniteMPS(3, ℂ^2, ℂ^4) -``` - -!!! note - The virtual spaces of the resulting MPOs typically grow exponentially with the - number of multiplications. Nevertheless, a number of optimizations are in place that - make sure that the virtual spaces do not increase past the maximal virtual space that - is dictated by the requirement of being full-rank tensors. - -## InfiniteMPO - -This construction can again be extended to the infinite case, where the tensors are repeated periodically. -Therefore, an [`InfiniteMPO`](@ref) is simply a `PeriodicVector` of `MPOTensor` objects. -These can only be constructed from vectors of `MPOTensor`s, since it is impossible to create the infinite operators directly. - -```@example operators -mpo = InfiniteMPO(O_xzx[1:2]) -``` - -Otherwise, their behavior is mostly similar to that of their finite counterparts. - -## FiniteMPOHamiltonian - -We can also represent quantum Hamiltonians in the same form. This is done by converting a -sum of local operators into a single MPO operator. The resulting operator has a very -specific structure, and is often referred to as a *Jordan block MPO*. - -This object can be constructed as an MPO by using the [`FiniteMPOHamiltonian`](@ref) constructor, -which takes two crucial pieces of information: - -1. An array of `VectorSpace` objects, which determines the local Hilbert spaces of the - system. The resulting MPO will snake through the array in linear indexing order. - -2. A set of local operators, which are characterised by a number of indices that specify on - which sites the operator acts, along with an operator to define the action. These are - specified as a `inds => operator` pairs, or any other iterable collection thereof. The - `inds` should be tuples of valid indices for the array of `VectorSpace` objects, or a - single integer for single-site operators. - -As a concrete example, we consider the -[Transverse-field Ising model](https://en.wikipedia.org/wiki/Transverse-field_Ising_model) -defined by the Hamiltonian - -```math -H = -J \sum_{\langle i, j \rangle} X_i X_j - h \sum_j Z_j -``` - -```@example operators -J = 1.0 -h = 0.5 -chain = fill(ℂ^2, 3) # a finite chain of 4 sites, each with a 2-dimensional Hilbert space -single_site_operators = [1 => -h * S_z, 2 => -h * S_z, 3 => -h * S_z] -two_site_operators = [(1, 2) => -J * S_x ⊗ S_x, (2, 3) => -J * S_x ⊗ S_x] -H_ising = FiniteMPOHamiltonian(chain, single_site_operators..., two_site_operators...) -``` - -Various alternative constructions are possible, such as using a `Dict` with key-value pairs -that specify the operators, or using generator expressions to simplify the construction. - -```@example operators -H_ising′ = -J * FiniteMPOHamiltonian(chain, - (i, i + 1) => S_x ⊗ S_x for i in 1:(length(chain) - 1)) - - h * FiniteMPOHamiltonian(chain, i => S_z for i in 1:length(chain)) -isapprox(H_ising, H_ising′; atol=1e-6) -``` - -Note that this construction is not limited to nearest-neighbour interactions, or 1D systems. -In particular, it is possible to construct quasi-1D realisations of 2D systems, by using -different arrays of [`VectorSpace`](@extref TensorKit.VectorSpace) objects. -For example, the 2D Ising model on a square lattice can be constructed as follows: - -```@example operators -square = fill(ℂ^2, 3, 3) # a 3x3 square lattice -operators = Dict() - -local_operators = Dict() -for I in eachindex(square) - local_operators[(I,)] = -h * S_z # single site operators still require tuples of indices -end - -# horizontal and vertical interactions are easier using Cartesian indices -horizontal_operators = Dict() -I_horizontal = CartesianIndex(0, 1) -for I in eachindex(IndexCartesian(), square) - if I[2] < size(square, 2) - horizontal_operators[(I, I + I_horizontal)] = -J * S_x ⊗ S_x - end -end - -vertical_operators = Dict() -I_vertical = CartesianIndex(1, 0) -for I in eachindex(IndexCartesian(), square) - if I[1] < size(square, 1) - vertical_operators[(I, I + I_vertical)] = -J * S_x ⊗ S_x - end -end - -H_ising_2d = FiniteMPOHamiltonian(square, local_operators) + - FiniteMPOHamiltonian(square, horizontal_operators) + - FiniteMPOHamiltonian(square, vertical_operators); -``` - -There are various utility functions available for constructing more advanced lattices, for -which the [lattices](@ref) section should be consulted. - -## InfiniteMPOHamiltonian - -Again, this construction can be extended straightforwardly to the infinite case. -To that end, we simply need to specify all interactions per unit cell. -In particular, an [`InfiniteMPOHamiltonian`](@ref) for the Ising model is obtained via - -```@example operators -J = 1.0 -h = 0.5 -infinite_chain = PeriodicVector([ℂ^2]) # an infinite chain of a local 2-dimensional Hilbert space -H_ising_infinite = InfiniteMPOHamiltonian(infinite_chain, 1 => -h * S_z, (1, 2) => -J * S_x ⊗ S_x) -``` - -### Expert mode - -The `MPOHamiltonian` constructor is in fact an automated way of constructing the -aforementioned *Jordan block MPO*. In its most general form, the matrix $W$ takes on the -form of the following block matrix: - -```math -\begin{pmatrix} -1 & C & D \\ -0 & A & B \\ -0 & 0 & 1 -\end{pmatrix} -``` - -which generates all single-site local operators $D$, all two-site operators $CB$, three-site -operators $CAB$, and so on. Additionally, this machinery can also be used to construct -interaction that are of (exponentially decaying) infinite range, and to approximate -power-law interactions. - -In order to illustrate this, consider the following explicit example of the Transverse-field -Ising model: - -```math -W = \begin{pmatrix} -1 & X & -hZ \\ -0 & 0 & -JX \\ -0 & 0 & 1 -\end{pmatrix} -``` - -If we add in the left and right boundary vectors - -```math -v_L = \begin{pmatrix} -1 & 0 & 0 -\end{pmatrix} -, \qquad -v_R = \begin{pmatrix} -0 \\ 0 \\ 1 -\end{pmatrix} -``` - -One can easily check that the Hamiltonian on $N$ sites is given by the contraction - -```math -H = V_L W^{\otimes N} V_R -``` - -We can even verify this symbolically: - -```@example operators -using Symbolics -L = 4 -# generate W matrices -@variables A[1:L] B[1:L] C[1:L] D[1:L] -Ws = map(1:L) do l - return [1 C[l] D[l] - 0 A[l] B[l] - 0 0 1] -end - -# generate boundary vectors -Vₗ = [1, 0, 0]' -Vᵣ = [0, 0, 1] - -# expand the MPO -expand(Vₗ * prod(Ws) * Vᵣ) -``` - -The [`FiniteMPOHamiltonian`](@ref) constructor can also be used to construct the operator from this most -general form, by supplying a vector of [`BlockTensorMap`](@extref BlockTensorKit.BlockTensorMap) objects -to the constructor. Here, the vector specifies the sites in the unit cell, while the blocktensors contain -the rows and columns of the matrix. We can verify this explicitly: - -```@example operators -H_ising[2] # print the blocktensor -``` - -### Working with `MPOHamiltonian` objects - -!!! warning - This part is still a work in progress - -Because of the discussion above, the `FiniteMPOHamiltonian` object is in fact just an `AbstractMPO`, -with some additional structure. This means that similar operations and properties are -available, such as the virtual spaces, or the individual tensors. However, the block -structure of the operator means that now the virtual spaces are not just a single space, but -a collection (direct sum) of spaces, one for each row/column. - -```@example operators -left_virtualspace(H_ising, 1), right_virtualspace(H_ising, 1), physicalspace(H_ising, 1) -``` diff --git a/docs/src/man/parallelism.md b/docs/src/man/parallelism.md deleted file mode 100644 index 683be061c..000000000 --- a/docs/src/man/parallelism.md +++ /dev/null @@ -1,116 +0,0 @@ -# Parallelism in julia - -Julia has great -[parallelism infrastructure](https://julialang.org/blog/2019/07/multithreading/), but there -is a caveat that is relevant for all algorithms implemented in MPSKit. The Julia threads do -not play nicely together with the BLAS threads, which are the threads used for many of the -linear algebra routines, and in particular for `gemm` (general matrix-matrix -multiplication). As this is a core routine in MPSKit, this has a significant impact on the -overall performance. - -## Julia threads vs BLAS threads - -A lot of the confusion stems from the fact that the BLAS threading behaviour is not -consistent between different vendors. Additionally, performance behaviour is severely -dependent on hardware, the specifics of the problem, and the availability of other resources -such as total memory, or memory bandwidth. This means that there is no one size fits all -solution, and that you will have to experiment with the settings to get optimal performance. -Nevertheless, there are some general guidelines that can be followed, which seem to at least -work well in most cases. - -The number of threads that are set by `BLAS.set_num_threads()`, in the case of OpenBLAS (the -default vendor), is equal to the **total number** of BLAS threads that is kept in a pool, -which is then shared by all Julia threads. This means that if you have 4 julia threads and 4 -BLAS threads, then all julia threads will share the same 4 BLAS threads. On the other hand, -using `BLAS.set_num_threads(1)`, OpenBLAS will now utilize the julia threads to run the BLAS -jobs. Thus, for OpenBLAS, very often setting the number of BLAS threads to 1 is the best -option, which will then maximally utilize the julia threading infrastructure of MPSKit. - -In the case of [MKL.jl](), which often outperforms OpenBLAS, the situation is a bit -different. Here, the number of BLAS threads corresponds to the number of threads that are -spawned by **each** julia thread. Thus, if you have 4 julia threads and 4 BLAS threads, then -each julia thread will spawn 4 BLAS threads, for a total of 16 BLAS threads. As such, it -might become necessary to adapt the settings to avoid oversubscription of the cores. - -A careful analysis of the different cases and benefits can be inspected by making use of -[`ThreadPinning.jl`](https://github.com/carstenbauer/ThreadPinning.jl)'s tool -`threadinfo(; blas=true, info=true)`. In particular, the following might demonstrate the -difference between OpenBLAS and MKL: - -```julia-repl -julia> Threads.nthreads() -4 - -julia> using ThreadPinning; threadinfo(; blas=true, hints=true) - -System: 8 cores (2-way SMT), 1 sockets, 1 NUMA domains - -| 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 | - -# = Julia thread, # = HT, # = Julia thread on HT, | = Socket separator - -Julia threads: 4 -├ Occupied CPU-threads: 4 -└ Mapping (Thread => CPUID): 1 => 8, 2 => 5, 3 => 9, 4 => 2, - -BLAS: libopenblas64_.so -└ openblas_get_num_threads: 8 - -[ Info: jlthreads != 1 && blasthreads < cputhreads. You should either set BLAS.set_num_threads(1) (recommended!) or at least BLAS.set_num_threads(16). -[ Info: jlthreads < cputhreads. Perhaps increase number of Julia threads to 16? -julia> using MKL; threadinfo(; blas=true, hints=true) - -System: 8 cores (2-way SMT), 1 sockets, 1 NUMA domains - -| 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 | - -# = Julia thread, # = HT, # = Julia thread on HT, | = Socket separator - -Julia threads: 4 -├ Occupied CPU-threads: 4 -└ Mapping (Thread => CPUID): 1 => 11, 2 => 12, 3 => 1, 4 => 2, - -BLAS: libmkl_rt.so -├ mkl_get_num_threads: 8 -└ mkl_get_dynamic: true - -┌ Warning: blasthreads_per_jlthread > cputhreads_per_jlthread. You should decrease the number of MKL threads, i.e. BLAS.set_num_threads(4). -└ @ ThreadPinning ~/.julia/packages/ThreadPinning/qV2Cd/src/threadinfo.jl:256 -[ Info: jlthreads < cputhreads. Perhaps increase number of Julia threads to 16? -``` - -## MPSKit multithreading - -Within MPSKit, when Julia is started with multiple threads, by default the `OhMyThreads.jl` -machinery will be used to parallelize the code as much as possible. In particular, this mostly -occurs whenever there is a unitcell and local updates can take place at each site in parallel. - -The multithreading behaviour can be controlled through a global `scheduler`, which can be set -using the `MPSKit.Defaults.set_scheduler!(arg; kwargs...)` function. This function accepts -either a `Symbol`, an `OhMyThreads.Scheduler` or keywords to determine a scheduler automatically. - -```julia -MPSKit.Defaults.set_scheduler!(:serial) # disable multithreading -MPSKit.Defaults.set_scheduler!(:greedy) # multithreading with greedy load-balancing -MPSKit.Defaults.set_scheduler!(:dynamic) # default: multithreading with some load-balancing -``` - -For further reference on the available schedulers and finer control, please refer to the -[`OhMyThreads.jl` documentation](https://juliafolds2.github.io/OhMyThreads.jl/stable/) - -## TensorKit multithreading - -Finally, when dealing with tensors that have some internal symmetry, it is also possible to -parallelize over the symmetry sectors. This is handled by TensorKit, and more information -can be found in its documentation (Soon TM). - -## Memory management - -Because of the way julia threads work, it is possible that the total memory usage of your -program becomes rather high. This seems to be because of the fact that MPSKit spawns several -tasks (in a nested way), which each allocate and deallocate quite a bit of memory in a tight -loop. This seems to lead to a situation where the garbage collector is not able to keep up, -and can even fail to clear the garbage before an `OutOfMemory` error occurs. In this case, -often the best thing to do is disable the multithreading of MPSKit, specifically for the -`derivatives`, as this seems to be the most memory intensive part. This is something that is -under investigation, and hopefully will be fixed in the future. diff --git a/docs/src/man/states.md b/docs/src/man/states.md deleted file mode 100644 index 168b2117c..000000000 --- a/docs/src/man/states.md +++ /dev/null @@ -1,174 +0,0 @@ -# [States](@id um_states) - -```@setup states -using MPSKit -using TensorKit -using LinearAlgebra: dot -``` - -## FiniteMPS - -A [`FiniteMPS`](@ref) is - at its core - a chain of mps tensors. - -```@raw html -finite MPS -``` - -### Usage - -A `FiniteMPS` can be created by passing in a vector of tensormaps: - -```@example states -L = 10 -data = [rand(ComplexF64, ℂ^1 ⊗ ℂ^2 ← ℂ^1) for _ in 1:L]; -state = FiniteMPS(data) -``` - -Or alternatively by specifying its structure - -```@example states -max_bond_dimension = ℂ^4 -physical_space = ℂ^2 -state = FiniteMPS(rand, ComplexF64, L, physical_space, max_bond_dimension) -``` - -You can take dot products, renormalize!, expectation values,.... - -### Gauging and canonical forms - -An MPS representation is not unique: for every virtual bond we can insert $C \cdot C^{-1}$ without altering the state. -Then, by redefining the tensors on both sides of the bond to include one factor each, we can change the representation. - -```@raw html -MPS gauge freedom -``` - -There are two particularly convenient choices for the gauge at a site, the so-called left and right canonical form. -For the left canonical form, all tensors to the left of a site are gauged such that they become left-isometries. -By convention, we call these tensors `AL`. - -```@example states -al = state.AL[3] -al' * al ≈ id(right_virtualspace(al)) -``` - -Similarly, the right canonical form turns the tensors into right-isometries. -By convention, these are called `AR`. - -```@example states -ar = state.AR[3] -repartition(ar, 1, 2) * repartition(ar, 1, 2)' ≈ id(left_virtualspace(ar)) -``` - -It is also possible to mix and match these two forms, where all tensors to the left of a given site are in the left gauge, while all tensors to the right are in the right gauge. -In this case, the final gauge transformation tensor can no longer be absorbed, since that would spoil the gauge either to the left or the right. -This center-gauged tensor is called `C`, which is also the gauge transformation to relate left- and right-gauged tensors. -Finally, for convenience it is also possible to leave a single MPS tensor in the center gauge, which we call `AC = AL * C` - -```@example states -c = state.C[3] # to the right of site 3 -c′ = state.C[2] # to the left of site 3 -al * c ≈ state.AC[3] ≈ repartition(c′ * repartition(ar, 1, 2), 2, 1) -``` - -These forms are often used throughout MPS algorithms, and the [`FiniteMPS`](@ref) object acts as an automatic manager for this. -It will automatically compute and cache the different forms, and detect when to recompute whenever needed. -For example, in order to compute the overlap of an MPS with itself, we can choose any site and bring that into the center gauge. -Since then both the left and right side simplify to the identity, this simply becomes the overlap of the gauge tensors: - -```@example states -d = dot(state, state) -all(c -> dot(c, c) ≈ d, state.C) -``` - -### Implementation details - -Behind the scenes, a `FiniteMPS` has 4 fields - -```julia -ALs::Vector{Union{Missing,A}} -ARs::Vector{Union{Missing,A}} -ACs::Vector{Union{Missing,A}} -Cs::Vector{Union{Missing,B}} -``` - -and calling `AL`, `AR`, `C` or `AC` returns lazy views over these vectors that instantiate the tensors whenever they are requested. -Similarly, changing a tensor will poison the `ARs` to the left of that tensor, and the `ALs` to the right. -The idea behind this construction is that one never has to worry about how the state is gauged, as this gets handled automagically. - -!!! warning - While a `FiniteMPS` can automatically detect when to recompute the different gauges, this requires that one of the tensors is set using an indexing operation. - In particular, in-place changes to the different tensors will not trigger the recomputation. - -## InfiniteMPS - -An [`InfiniteMPS`](@ref) can be thought of as being very similar to a finite mps, where the set of tensors is repeated periodically. - -It can also be created by passing in a vector of `TensorMap`s: - -```@example states -data = [rand(ComplexF64, ℂ^4 ⊗ ℂ^2 ← ℂ^4) for _ in 1:2] -state = InfiniteMPS(data) -``` - -or by initializing it from given spaces - -```@example states -phys_spaces = fill(ℂ^2, 2) -virt_spaces = [ℂ^4, ℂ^5] # by convention to the right of a site -state = InfiniteMPS(phys_spaces, virt_spaces) -``` - -Note that the code above creates an `InfiniteMPS` with a two-site unit cell, where the given virtual spaces are located to the right of their respective sites. - -### Gauging and canonical forms - -Much like for `FiniteMPS`, we can again query the gauged tensors `AL`, `AR`, `C` and `AC`. -Here however, the implementation is much easier, since they all have to be recomputed whenever a single tensor changes. -This is a result of periodically repeating the tensors, every `AL` is to the right of the changed site, and every `AR` is to the left. -As a result, the fields are simply - -```julia -AL::PeriodicArray{A,1} -AR::PeriodicArray{A,1} -C::PeriodicArray{B,1} -AC::PeriodicArray{A,1} -``` - -## WindowMPS - -A [`WindowMPS`](@ref) or segment MPS can be seen as a mix between an [`InfiniteMPS`](@ref) and a [`FiniteMPS`](@ref). -It represents a window of mutable tensors (a finite MPS), embedded in an infinite environment (two infinite MPSs). -It can therefore be created accordingly, ensuring that the edges match: - -```@example states -infinite_state = InfiniteMPS(ℂ^2, ℂ^4) -finite_state = FiniteMPS(5, ℂ^2, ℂ^4; left=ℂ^4, right=ℂ^4) -window = WindowMPS(infinite_state, finite_state, infinite_state) -``` - -Algorithms will then act on this window of tensors, while leaving the left and right infinite states invariant. - -## MultilineMPS - -A two-dimensional classical partition function can often be represented by an infinite tensor network. -There are many ways to evaluate such a network, but here we focus on the so-called boundary MPS methods. -These first reduce the problem from contracting a two-dimensional network to the contraction of a one-dimensional MPS, by finding the fixed point of the row-to-row (or column-to-column) transfer matrix. -In these cases however, there might be a non-trivial periodicity in both the horizontal as well as vertical direction. -Therefore, in MPSKit they are represented by [`MultilineMPS`](@ref), which are simply a repeating set of [`InfiniteMPS`](@ref). - -```@example states -state = MultilineMPS(fill(infinite_state, 2)) -``` - -They offer some convenience functionality for using cartesian indexing (row - column): - -You can access properties by calling -```@example states -row = 2 -col = 2 -al = state.AL[row, col]; -``` - -These objects are also used extensively in the context of [PEPSKit.jl](https://github.com/QuantumKitHub/PEPSKit.jl). - diff --git a/docs/src/references.md b/docs/src/references.md index bdfee2cf7..a2670933c 100644 --- a/docs/src/references.md +++ b/docs/src/references.md @@ -6,6 +6,29 @@ Below you can find a list of publications that have made use of MPSKit. If you h this package and wish to have your publication added to this list, please open a pull request or an issue on the [GitHub repository](https://github.com/QuantumKitHub/MPSKit.jl/). +### 2026 + +```@bibliography +Pages = [] +basumatary2026 +brehmer2026 +hormann2026 +kadow2026 +kaplan2026 +lu2026 +ritter2026 +shankar2026 +staelens2026 +ueda2026 +vancraeynestdecuiper2026 +vanthilt2026 +veitas2026 +yang2026 +zemlevskiy2026 +zong2026nickelate +zong2026pseudogap +``` + ### 2025 ```@bibliography @@ -15,10 +38,13 @@ dempsey2025 herviou2025 kirchner2025 linden2025 -mortier2025 maertens2025 +mortier2025 shen2025 +sinha2025 +sommer2025 ueda2025 +vandamme2025pseudogenerators vrancken2025 ``` ### 2024 diff --git a/docs/src/tutorials/excitations.md b/docs/src/tutorials/excitations.md new file mode 100644 index 000000000..4ab533c41 --- /dev/null +++ b/docs/src/tutorials/excitations.md @@ -0,0 +1,109 @@ +# [Quasiparticle excitations](@id tutorial_excitations) + +The previous tutorials ended with a ground state: the lowest-energy state of the transverse-field Ising model, first on a finite chain and then directly in [the thermodynamic limit](@ref tutorial_thermodynamic_limit). +The natural next question is what lies *above* it: how much energy does it cost to excite the system? +For a translation-invariant chain the answer is organized by momentum — for each momentum ``k`` there is a lowest excitation energy ``\Delta E(k)``, and the resulting curve is the **dispersion relation** of the model. +Its minimum over all momenta is the **energy gap**, one of the most basic characterizations of a quantum phase. + +In this tutorial we compute the dispersion relation of the infinite transverse-field Ising chain with MPSKit's quasiparticle ansatz, and finish with a plot of ``\Delta E(k)`` across the Brillouin zone — compared against the exact solution. + +## Loading the packages + +As in the previous tutorials, every code block on this page shares one Julia session, so we load the packages once. + +```@example excitations +using MPSKit, MPSKitModels, TensorKit +using Plots +``` + +## 1. Find the ground state + +Excitations are computed *on top of* a ground state, so the first step is the calculation you already know from [The thermodynamic limit](@ref tutorial_thermodynamic_limit): build the infinite Hamiltonian, make a random `InfiniteMPS`, and converge it with `VUMPS`. + +This time we set the field to `g = 2.0`, deep in the paramagnetic phase, where the model is **gapped**: the lowest excitation costs a finite amount of energy, which is exactly what we want to measure. + +```@example excitations +g = 2.0 +H = transverse_field_ising(; g) +ψ₀ = InfiniteMPS(ℂ^2, ℂ^12) +ψ, envs, ϵ = find_groundstate(ψ₀, H, VUMPS(; verbosity = 0)) +``` + +We keep all three return values this time: the optimized state `ψ` and the environments `envs` both feed directly into the excitation calculation below, so nothing has to be recomputed. + +## 2. One excitation at one momentum + +The **quasiparticle ansatz** builds an excited state directly on top of the uniform ground state. +The idea is simple to picture: take the converged ground state and perturb it locally, replacing the tensor at one site with a new one that we get to optimize. +Because the chain is infinite and translation invariant, we do not place this perturbation at any particular site; instead we superpose it across *all* sites with a plane-wave phase, which gives the excitation a definite momentum ``k``. +Optimizing the perturbation then yields the lowest excited state at that momentum. + +The call is [`excitations`](@ref) with the [`QuasiparticleAnsatz`](@ref) algorithm, a momentum (a real number, in radians per site), and the ground state with its environments. +Let us ask for the excitation at the edge of the Brillouin zone, ``k = \pi``: + +```@example excitations +E, ϕ = excitations(H, QuasiparticleAnsatz(), π, ψ, envs) +E +``` + +Two things to note about the return values: + +- `E` is a *vector* of excitation energies, of length `num` — the keyword controlling how many excitations to compute at this momentum, which defaults to `num = 1`, so here it has a single entry. +- The entries of `E` are energies **above the ground state** — gaps at this momentum, not total energies. The ground-state energy is subtracted internally, so you can read them off directly. + +The second return value `ϕ` holds the corresponding quasiparticle states, which can be used for further post-processing; we will not need them in this tutorial. + +## 3. The full dispersion + +To trace out the whole dispersion relation we simply pass a *range* of momenta instead of a single number. +By symmetry it is enough to scan from ``0`` to ``\pi``, and we use 16 points to keep the runtime modest. + +```@example excitations +momenta = range(0, π, 16) +Es, ϕs = excitations(H, QuasiparticleAnsatz(), momenta, ψ, envs; verbosity = 0) +size(Es) +``` + +With a range of momenta the energies come back as a matrix of size `(length(momenta), num)` — here `(16, 1)`, one row per momentum and one column because we kept the default `num = 1`. +We pass `verbosity = 0` to silence the progress line this method otherwise prints for every momentum. +The momenta are independent of one another, so MPSKit works on them in parallel by default. + +## 4. Plot the dispersion + +Now for the payoff. +This particular model is exactly solvable, so we can plot our numerical dispersion right on top of the known answer: + +```math +\Delta E(k) = 2\sqrt{1 + g^2 - 2 g \cos k}. +``` + +For this Hermitian problem the computed energies come back as real numbers; the `real.(...)` below is a harmless safeguard for the general case, where the eigenvalue solver may return a complex number type with numerically vanishing imaginary parts. + +```@example excitations +k_exact = range(0, π, 200) +ΔE_exact = @. 2 * sqrt(1 + g^2 - 2g * cos(k_exact)) +plot(k_exact, ΔE_exact; label = "exact", xlabel = "momentum k", ylabel = "ΔE(k)", title = "TFIM dispersion (g = $g)") +scatter!(momenta, real.(Es); label = "quasiparticle ansatz (D = 12)") +``` + +The 16 computed points fall right on the exact curve. +The dispersion rises monotonically from ``k = 0`` to ``k = \pi``, so its minimum — the gap — sits at zero momentum, where the exact value is ``\Delta E(0) = 2(g - 1)``. +Our first matrix entry is precisely that point, so we can close with a numerical check: + +```@example excitations +real(Es[1, 1]), 2 * (g - 1) +``` + +A ground state at bond dimension 12 plus a variational quasiparticle on top reproduces the exact gap of the model — that is the quasiparticle ansatz working as intended. + +## Where to go next + +You have computed a full dispersion relation on top of an infinite ground state and read off the energy gap. + +The [`excitations`](@ref) entry point can do considerably more than what we used here: it can target excitations carrying a nontrivial symmetry charge, build topological (domain-wall) excitations that interpolate between two different ground states, and compute excited states of *finite* chains, where momentum is no longer a good quantum number and different algorithms take over. +All of these are recipes in [Excited states](@ref howto_excitations). +For what each excitation algorithm actually does and when to choose it, see the library reference [Excitations](@ref lib_excitations). + + diff --git a/docs/src/tutorials/first_groundstate.md b/docs/src/tutorials/first_groundstate.md new file mode 100644 index 000000000..3b2473b74 --- /dev/null +++ b/docs/src/tutorials/first_groundstate.md @@ -0,0 +1,150 @@ +# [Your first ground state](@id tutorial_first_groundstate) + +This tutorial walks you through a complete MPSKit.jl calculation from start to finish: we build the transverse-field Ising model, find its ground state with DMRG, measure a few physical quantities, and finish with a plot of the magnetization across the model's phase transition. +It assumes only that you are comfortable with basic quantum mechanics and that you have finished [Installation](@ref tutorial_installation), so the packages used below are already available in your environment. + +The transverse-field Ising model (TFIM) is the "hello world" of quantum many-body physics: it is the simplest model that still shows a genuine quantum phase transition, so it is the natural place to learn the tools. +On a chain of ``L`` spin-1/2 sites it is + +```math +H = -J\left(\sum_{\langle i,j\rangle} \sigma^z_i\,\sigma^z_j + g\sum_i \sigma^x_i\right), +``` + +where the first sum runs over neighbouring pairs. +The coupling ``J`` sets the overall energy scale, and the dimensionless field ``g`` tunes the competition between the ferromagnetic ``\sigma^z\sigma^z`` interaction and the transverse ``\sigma^x`` field. + +The ground state of ``H`` lives in a Hilbert space of dimension ``2^L``, which is far too large to store as a plain vector for any interesting ``L``. +A *matrix product state* (MPS) sidesteps this by storing the state as a chain of small tensors, one per site, whose sizes we control directly; this is what makes the calculation below tractable. +The details of that compression are the subject of the concept pages — here we simply use it. + +## Loading the packages + +Every code block on this page shares one Julia session, so we only need to load packages once. +We take the model and lattice from MPSKitModels, the local spin operators from TensorKitTensors, and `Plots` for the final figure. + +```@example first-groundstate +using MPSKit, MPSKitModels, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ +using Plots +``` + +## 1. Build the Hamiltonian + +We work with a chain of `L = 16` sites and fix the field to `g = 0.5` for now. +`transverse_field_ising` assembles the Hamiltonian above; passing `FiniteChain(L)` asks for a finite open chain of `L` sites. + +```@example first-groundstate +L = 16 +H = transverse_field_ising(FiniteChain(L); g = 0.5) +``` + +The returned object is an `MPOHamiltonian`: the Hamiltonian written in matrix-product-operator form, i.e. as a chain of small tensors just like the state it acts on. +You do not need to know its internals to use it — MPSKit's algorithms consume it directly. +For other ways to build Hamiltonians see [Building Hamiltonians](@ref howto_hamiltonians). + +## 2. Build the initial state + +DMRG is an optimization: it needs a starting state to improve. +We create a random `FiniteMPS` with the right structure. + +```@example first-groundstate +D = 4 +ψ₀ = FiniteMPS(L, ℂ^2, ℂ^D) +``` + +The two space arguments describe the two kinds of index every MPS tensor carries: + +- `ℂ^2` is the **physical space** — the local Hilbert space of a single spin-1/2 site, which has dimension 2. +- `ℂ^D` is the **virtual (bond) space** — the internal index linking neighbouring tensors, whose dimension `D` is the *bond dimension*. + +The bond dimension `D` is the accuracy knob of the whole method: a larger `D` lets the MPS capture more entanglement and represent the true ground state more faithfully, at the cost of more computation. +`D = 4` is deliberately small so this tutorial runs quickly; [Controlling bond dimension](@ref howto_bond_dimension) covers how to choose and grow it. + +!!! warning "Pass spaces, not integers" + The physical and virtual arguments must be *vector spaces* (`ℂ^2`, `ℂ^D`, or equivalently `ComplexSpace(2)`), never bare integers. + Writing `FiniteMPS(16, 2, 4)` throws a `MethodError` — this is the single most common beginner mistake. + +## 3. Find the ground state + +Now we run the calculation. +`find_groundstate` takes the starting state, the Hamiltonian, and an algorithm; we pass [`DMRG`](@ref) explicitly so the algorithm is visible. + +```@example first-groundstate +ψ, envs, ϵ = find_groundstate(ψ₀, H, DMRG()) +``` + +DMRG (the density-matrix renormalization group) sweeps back and forth along the chain, locally optimizing each tensor while holding the others fixed, and repeats until the state stops changing. +The lines printed above are the per-iteration convergence log (shown at the default `verbosity`); each reports the sweep number, the current energy, and a convergence measure (the same Galerkin residual returned as `ϵ` below). + +!!! note "The algorithm is optional" + Calling `find_groundstate(ψ₀, H)` with no algorithm argument selects DMRG automatically for a finite input, so the explicit `DMRG()` above is only for clarity. + `DMRG` accepts keywords such as `tol` (default `1e-10`), `maxiter` (default `200`), and `verbosity` (default `3`); we use `verbosity = 0` later to silence the log inside a loop. + +`find_groundstate` returns a triple: + +- `ψ` — the optimized ground-state MPS (a *new* state; `ψ₀` is left untouched, so we can reuse it below). A mutating variant `find_groundstate!` also exists. +- `envs` — the *environments*, cached partial contractions that later measurements can reuse to save work. +- `ϵ` — a convergence-error measure (the Galerkin residual). It quantifies how well the sweeps converged; note that it is **not** the error in the energy. + +## 4. Measure observables + +With a ground state in hand we can extract physical quantities. +The energy is the expectation value of the Hamiltonian itself — pass `H` directly, with no site index: + +```@example first-groundstate +E = expectation_value(ψ, H) +``` + +For a Hermitian `H` and a normalized state this is real up to floating-point noise. + +The order parameter of the TFIM is the local magnetization ``\langle\sigma^z_i\rangle``. +We measure it at every site by pairing each site index with the single-site operator `σᶻ()`: + +```@example first-groundstate +[expectation_value(ψ, i => σᶻ()) for i in 1:L] +``` + +Finally, a good "how converged am I really?" check is the energy variance ``\langle H^2\rangle - \langle H\rangle^2``, which vanishes exactly when `ψ` is a true eigenstate: + +```@example first-groundstate +variance(ψ, H) +``` + +A small variance indicates the state is close to an eigenstate of `H`. +More recipes for observables live in [Computing observables](@ref howto_observables). + +## 5. Magnetization across the transition + +The payoff: we sweep the field `g` from 0 to 2 and, for each value, find the ground state and record its average magnetization. +This traces out the phase transition. + +Each step of the sweep repeats the workflow of Sections 1–4 on the same open chain — only the value of `g` changes. + +```@example first-groundstate +g_values = 0:0.1:2 +M = map(g_values) do g + Hg = transverse_field_ising(FiniteChain(L); g = g) + ψg, = find_groundstate(ψ₀, Hg; verbosity = 0) + return abs(sum(expectation_value(ψg, i => σᶻ()) for i in 1:L)) / L +end +scatter(g_values, M; xlabel = "g", ylabel = "M", label = "D = $D", title = "TFIM magnetization") +``` + +Here we take the **absolute value** of the mean magnetization. +At finite `L` the exact ground state does not break the symmetry: it is the symmetric combination of the two oppositely magnetized states, and its raw magnetization ``\sum_i\langle\sigma^z_i\rangle`` is exactly zero. +DMRG at finite bond dimension, however, converges to one of the two symmetry-broken states instead, because either one carries far less entanglement than their symmetric superposition. +Which sign it lands on is arbitrary — it can differ from run to run and between values of `g` — so taking `abs` makes the order-parameter curve well-defined regardless of the branch. + +The plot shows the magnetization close to 1 deep on the ordered side, then dropping to zero — noticeably *below* the thermodynamic critical point `g = 1` (around `g ≈ 0.6` at these parameters). +Both features follow from how the state is computed rather than from the physics of the transition: +on the ordered side DMRG sits on one symmetry-broken branch, and past the drop it recovers the exactly symmetric ground state, whose magnetization vanishes. +Exactly where the drop lands depends on `L` and `D`, so its location by itself is *not yet* a measurement of the critical point. +The honest way to locate the transition is by performing a scaling analysis, taking the limit of infinite size and bond dimension. + +## Where to go next + +You have run a full MPSKit workflow: build a model, optimize an MPS ground state, measure observables, and scan a parameter. +A natural next step is [The thermodynamic limit](@ref tutorial_thermodynamic_limit): +the same calculation performed directly at infinite system size with an `InfiniteMPS`, which removes the finite-size effects seen above and lets you locate the critical point more cleanly. + +To go deeper on the individual steps, see [Constructing states](@ref howto_states), [Building Hamiltonians](@ref howto_hamiltonians), [Computing observables](@ref howto_observables), [Controlling bond dimension](@ref howto_bond_dimension), and [Entanglement entropy and spectrum](@ref howto_entanglement); the algorithm reference is [Ground-state algorithms](@ref lib_groundstate). diff --git a/docs/src/tutorials/installation.md b/docs/src/tutorials/installation.md new file mode 100644 index 000000000..78b67dc4b --- /dev/null +++ b/docs/src/tutorials/installation.md @@ -0,0 +1,63 @@ +# [Installation](@id tutorial_installation) + +This page walks you through setting up a Julia environment for working with MPSKit.jl, and ends with a small snippet you can run to check that everything works. + +## Prerequisites + +You need a working installation of Julia, version 1.10 or later. +If you don't have Julia yet, install it via [juliaup](https://github.com/JuliaLang/juliaup) or download it directly from [julialang.org](https://julialang.org/downloads/). +This tutorial assumes you are comfortable starting the Julia REPL and typing commands into it, but does not assume any prior experience with Julia's package manager. + +## Set up a project environment + +Before installing any packages, create a dedicated environment for this tutorial. +Working in a fresh, named environment (rather than the global default environment) keeps the exact package versions you use here reproducible, and avoids clashes with other projects on your machine. + +Start Julia, enter the package manager by pressing `]`, and activate a new environment: + +``` +pkg> activate mpskit-tutorial +``` + +Julia will create the environment the first time you add a package to it. + +## Install the packages + +With the environment activated, install MPSKit.jl and the packages used throughout this documentation: + +``` +pkg> add MPSKit TensorKit TensorOperations MPSKitModels TensorKitTensors Plots +``` + +- `MPSKit` provides the matrix product state and operator types, together with the ground-state, time-evolution, and bond-dimension algorithms. +- `TensorKit` supplies the tensor backend (`TensorMap`s and vector spaces) that MPSKit is built on; installing it alongside MPSKit also gives access to truncation-scheme constructors such as `truncrank`, which TensorKit re-exports from MatrixAlgebraKit. +- `TensorOperations` provides the `@tensor` macro used to contract tensors by hand. +- `MPSKitModels` collects pre-defined Hamiltonians (such as the transverse-field Ising model) and lattices for common physical models. +- `TensorKitTensors` provides ready-made local operators, such as the Pauli operators. +- `Plots` is used to visualize results in several of the how-to guides and examples; it is optional if you only intend to run computations without plotting. + +MPSKit.jl is registered in Julia's General registry, so `pkg> add` fetches it directly; you do not need to add any custom registries. + +!!! note "First `using` is slow" + The first time you load these packages with `using`, Julia precompiles them, which can take a minute or two. + Subsequent loads in the same environment are much faster. + +## Verify your setup + +Once the packages have finished installing, exit the package manager (backspace) and run the following in the same environment to check that MPSKit, TensorKit, and MPSKitModels work together. + +```@example verify-install +using MPSKit, TensorKit, MPSKitModels + +H = transverse_field_ising(FiniteChain(8); J = 1.0, g = 0.5) +ψ = FiniteMPS(8, ℂ^2, ℂ^8) +``` + +If this runs without error and prints a `FiniteMPS`, your environment is ready. + +From here, continue with [Your first ground state](@ref tutorial_first_groundstate), which uses this same Hamiltonian and initial state to find the ground state of the transverse-field Ising model with DMRG. + +## Troubleshooting + +- **Long precompilation on first use:** this is expected the first time you `using` a package (or after updating one), especially for a large dependency stack; it is not a sign that anything is wrong. +- **Version resolver conflicts:** if `pkg> add` reports that it cannot find a compatible set of versions, try creating a fresh environment (as above) rather than adding these packages to an existing environment that already has other constraints. diff --git a/docs/src/tutorials/thermodynamic_limit.md b/docs/src/tutorials/thermodynamic_limit.md new file mode 100644 index 000000000..0be68f69e --- /dev/null +++ b/docs/src/tutorials/thermodynamic_limit.md @@ -0,0 +1,134 @@ +# [The thermodynamic limit](@id tutorial_thermodynamic_limit) + +In [Your first ground state](@ref tutorial_first_groundstate) we put the transverse-field Ising model on a finite chain of `L = 16` sites. +That is a perfectly good calculation, but it carries two prices: the open ends of the chain are physically different from its middle (boundary effects), and every quantity we measured still depends on the length `L` (finite-size effects). +To read off the true physics of the model we would have to repeat the calculation at several lengths and extrapolate `L → ∞`. + +MPSKit lets you skip that extrapolation and work *directly* at `L = ∞`. +The trick is translation invariance: instead of storing one tensor per site, we store a single tensor and imagine it repeated forever along the chain — an [`InfiniteMPS`](@ref). +There are no ends, so there are no boundary effects, and there is no `L` to extrapolate. +Best of all, as you are about to see, the code barely changes: the same model, the same workflow, two edits. + +!!! note "Infinite states are always normalized" + An `InfiniteMPS` is normalized to 1 by construction, and you cannot choose otherwise. + Any other normalization would make expectation values either blow up or vanish as the (infinite) chain length is taken to infinity, so per-site quantities are the only ones that make sense here. + +## Loading the packages + +As before, every code block on this page shares one Julia session, so we load the packages once. + +```@example thermodynamic-limit +using MPSKit, MPSKitModels, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ +using Plots +``` + +## 1. Build the Hamiltonian and initial state + +Here are the only two lines that differ from the finite tutorial. + +For the Hamiltonian, we drop the lattice argument. +Where the finite version wrote `transverse_field_ising(FiniteChain(L); g = 0.5)`, we simply omit `FiniteChain(L)`: with no lattice, `transverse_field_ising` builds the Hamiltonian for the infinite chain. + +```@example thermodynamic-limit +H = transverse_field_ising(; g = 0.5) +``` + +For the state, we swap `FiniteMPS` for `InfiniteMPS`. +There is no length to pass, so the constructor takes just the physical and virtual spaces — the physical space `ℂ^2` of a spin-1/2 site and the bond space `ℂ^D` whose dimension `D` is again the accuracy knob. + +```@example thermodynamic-limit +D = 4 +ψ₀ = InfiniteMPS(ℂ^2, ℂ^D) +``` + +That is the whole difference. +The bond dimension means exactly what it did on the finite chain (see [Controlling bond dimension](@ref howto_bond_dimension)), and `ℂ^2`/`ℂ^D` are the same physical/virtual spaces. + +!!! note "`InfiniteMPS` also accepts bare integers" + Unlike `FiniteMPS`, the infinite constructor happily takes plain integers: `InfiniteMPS(2, D)` is equivalent to `InfiniteMPS(ℂ^2, ℂ^D)`. + We stick with the explicit spaces to match the rest of the documentation. + +## 2. Find the ground state + +We optimize with [`VUMPS`](@ref), the infinite-chain workhorse, passing it explicitly so it is visible. + +```@example thermodynamic-limit +ψ, envs, ϵ = find_groundstate(ψ₀, H, VUMPS()) +``` + +The lines printed above are VUMPS's per-iteration convergence log, shown at the default `verbosity`. +VUMPS (the variational uniform matrix product state algorithm) optimizes the single repeated tensor directly in the thermodynamic limit, iterating until it reaches a fixed point. + +The return value has the same shape as on the finite chain: the optimized state `ψ`, the reusable `envs`, and a convergence-error measure `ϵ`. + +!!! note "The algorithm is optional here too" + Just as `find_groundstate(ψ₀, H)` selected DMRG for a finite input, calling it with no algorithm on an *infinite* input selects VUMPS automatically. + `VUMPS` accepts the familiar keywords `tol` (default `1e-10`), `maxiter` (default `200`), and `verbosity` (default `3`); we use `verbosity = 0` later to silence the log inside a loop. + Note there is no `find_groundstate!` for infinite states — VUMPS returns a fresh state and leaves `ψ₀` untouched. + +## 3. Measure observables + +For the default single-site unit cell used here, `expectation_value(ψ, H)` returns the energy of that one-site unit cell, which is exactly the **energy per site**: + +```@example thermodynamic-limit +E = expectation_value(ψ, H) +``` + +The magnetization is the local order parameter ``\langle\sigma^z\rangle``. +Because the state is translation-invariant, every site is identical, so we measure it at site 1 of the unit cell: + +```@example thermodynamic-limit +expectation_value(ψ, 1 => σᶻ()) +``` + +So far these are the same quantities we computed on the finite chain. +The infinite setting also unlocks an observable with no finite-chain analogue: the [`correlation_length`](@ref), extracted from the transfer-matrix spectrum of the uniform state. + +```@example thermodynamic-limit +correlation_length(ψ) +``` + +The correlation length tells us how far apart two spins can still "feel" each other; it is measured in units of the lattice spacing. +It grows as we approach the critical point `g = 1`, where correlations become long-ranged. +We can see this by optimizing a second state right at criticality and comparing: + +```@example thermodynamic-limit +H_crit = transverse_field_ising(; g = 1.0) +ψ_crit, = find_groundstate(ψ₀, H_crit, VUMPS(; verbosity = 0)) +correlation_length(ψ_crit) +``` + +At a genuine critical point the correlation length diverges, but a finite bond dimension `D` can only capture correlations out to a finite range, so what we measure is large but capped rather than infinite. + +## 4. Magnetization across the transition + +As on the finite chain, we finish by sweeping the field `g` and recording the magnetization. +The structure mirrors the finite sweep exactly — only `InfiniteMPS` and `VUMPS` have changed. + +```@example thermodynamic-limit +g_values = 0.1:0.1:2 +M = map(g_values) do g + Hg = transverse_field_ising(; g = g) + ψg, = find_groundstate(ψ₀, Hg, VUMPS(; verbosity = 0)) + return abs(expectation_value(ψg, 1 => σᶻ())) +end +scatter(g_values, M; xlabel = "g", ylabel = "M", label = "D = $D", title = "TFIM magnetization (L = ∞)") +``` + +Compare this with the finite-chain sweep of the previous tutorial, where the magnetization dropped to zero well before `g = 1`, at a point set by the algorithm rather than by the physics. +The infinite curve instead tracks the transition itself: the magnetization stays on its ordered branch all the way up to the critical point and collapses to zero right at `g = 1`. +What little smearing remains around the critical point is a finite-bond-dimension effect, and it shrinks as `D` grows. + +We still take the **absolute value** of the magnetization, but for a subtly different reason than on the finite chain. +On the finite chain the nonzero magnetization was an artifact of the algorithm: the exact ground state there is symmetric, and DMRG landed on a symmetry-broken state only because it carries less entanglement. +In the thermodynamic limit the symmetry breaking is genuine — the two oppositely magnetized states become true ground states — and an infinite MPS at finite bond dimension settles into one of them on the ordered side, landing on a definite nonzero magnetization of either sign; `abs` again puts both branches onto a single order-parameter curve. + +## Where to go next + +You have now run the same TFIM calculation twice — once at finite size, once directly at `L = ∞` — and seen how little the code had to change. + +From here you can go beyond ground states. +A natural next step is to [compute the excitations above this infinite ground state](@ref tutorial_excitations) (the model's quasiparticle spectrum), or to [exploit the symmetries of the model](@ref tutorial_using_symmetries) to make the calculation cheaper and more accurate. + +To go deeper on the individual steps used here, see [Constructing states](@ref howto_states), [Controlling bond dimension](@ref howto_bond_dimension), and [Entanglement entropy and spectrum](@ref howto_entanglement); the algorithm reference is [Ground-state algorithms](@ref lib_groundstate). diff --git a/docs/src/tutorials/time_evolution.md b/docs/src/tutorials/time_evolution.md new file mode 100644 index 000000000..21e8474e4 --- /dev/null +++ b/docs/src/tutorials/time_evolution.md @@ -0,0 +1,134 @@ +# [A quantum quench](@id tutorial_time_evolution) + +The previous tutorials computed ground states — static snapshots of a model at its lowest energy. +This tutorial adds the time axis: we take a state that is *not* an eigenstate of its Hamiltonian and watch it evolve under the Schrödinger equation, + +```math +|\psi(t)\rangle = e^{-iHt}\,|\psi(0)\rangle , +``` + +tracking one local observable as a function of time. +The protocol we use is the simplest and most common one in the field, a *global quench*, and the workhorse algorithm is [`TDVP`](@ref), the time-dependent variational principle, driven one step at a time through [`timestep`](@ref). + +We stay with the transverse-field Ising model from [Your first ground state](@ref tutorial_first_groundstate), so the model-building and ground-state steps below should look familiar. + +## Loading the packages + +Every code block on this page shares one Julia session, so we load the packages once. +As before, the model comes from MPSKitModels, the local spin operators from TensorKitTensors, and `Plots` draws the final figure. + +```@example time-evolution +using MPSKit, MPSKitModels, TensorKit +using TensorKitTensors.SpinOperators: σˣ, σᶻ +using Plots +``` + +## 1. Prepare the initial state + +Time evolution needs a definite starting state, and the standard choice is the ground state of some Hamiltonian. +We take a chain of `L = 12` sites with a transverse field `g₀ = 0.5` — the ordered side of the model — and find its ground state exactly as in [the first tutorial](@ref tutorial_first_groundstate), silencing the convergence log with `verbosity = 0`. +The bond dimension `D = 16` is comfortably large for a ground state of this size; we will see below why time evolution wants more headroom than a ground-state calculation. + +```@example time-evolution +L = 12 +D = 16 +g₀ = 0.5 +H₀ = transverse_field_ising(FiniteChain(L); g = g₀) +ψ₀ = FiniteMPS(L, ℂ^2, ℂ^D) +ψ, = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)) +nothing # hide +``` + +The observable we will track through the evolution is the transverse magnetization ``\langle\sigma^x\rangle`` at the middle of the chain, away from the open ends. +We measure its baseline value in the pre-quench ground state: + +```@example time-evolution +i_mid = L ÷ 2 +real(expectation_value(ψ, i_mid => σˣ())) +``` + +## 2. The quench + +A *global quench* is the sudden change of a parameter of the Hamiltonian, everywhere at once: we prepare the ground state of `H₀`, then at `t = 0` switch the Hamiltonian to a different `H₁` and let the state evolve under it. +Because `ψ` is an eigenstate of `H₀` but not of `H₁`, it is no longer stationary — the quench injects energy into the system, and nontrivial dynamics follows. + +Our quench takes the transverse field from `g₀ = 0.5` all the way across the phase transition to `g₁ = 2.0`: + +```@example time-evolution +g₁ = 2.0 +H₁ = transverse_field_ising(FiniteChain(L); g = g₁) +``` + + +## 3. A single time step + +The elementary move of real-time evolution in MPSKit is [`timestep`](@ref), which advances a state by one small increment `dt`. +Its arguments are, in order: the state, the Hamiltonian to evolve under, the current time, the step size, and the algorithm. +Here we take the very first step, from `t = 0.0` to `t = dt`, with single-site [`TDVP`](@ref): + +```@example time-evolution +dt = 0.05 +ψ_t, envs = timestep(ψ, H₁, 0.0, dt, TDVP()) +real(expectation_value(ψ_t, i_mid => σˣ())) +``` + +TDVP integrates the Schrödinger equation projected onto the space of MPS with the current bond dimension, which is why it slots so naturally into an MPS workflow. + +`timestep` returns two things: + +- `ψ_t` — the evolved state at time `dt` (a new state; `ψ` is left untouched). +- `envs` — the environments, cached partial contractions belonging to the new state and `H₁`. + +The `envs` are the reason evolution loops are cheap to keep running: passing them back into the next `timestep` call lets it start from the cached contractions instead of recomputing them from scratch. + +The transverse magnetization has already moved slightly away from its `t = 0` value — the state is on its way. + +## 4. Evolving in a loop + +Real-time evolution is nothing more than this single step, repeated. +We already took step 1 above, so the loop below performs the remaining steps, up to `n_steps = 40` in total (a final time of `t = 2.0`), recording the transverse magnetization at the middle of the chain after every step. +Note how each iteration feeds the previous `envs` back in as the optional last argument, and how the current time `(n - 1) * dt` advances with the loop. + +```@example time-evolution +n_steps = 40 +times = (0:n_steps) .* dt +m = zeros(n_steps + 1) +m[1] = real(expectation_value(ψ, i_mid => σˣ())) # t = 0, before the quench dynamics +m[2] = real(expectation_value(ψ_t, i_mid => σˣ())) # t = dt, from the single step above +for n in 2:n_steps + global ψ_t, envs + ψ_t, envs = timestep(ψ_t, H₁, (n - 1) * dt, dt, TDVP(), envs) + m[n + 1] = real(expectation_value(ψ_t, i_mid => σˣ())) +end +m[end] +``` + +(The `global` keyword is needed because the loop rebinds `ψ_t` and `envs`, which live outside it; inside a function you would not need it.) + +Writing the loop by hand like this keeps every moving part visible, which is the point of a tutorial. +For production use, [`time_evolve`](@ref) wraps exactly this loop and steps through a whole vector of time points in one call — see [Time evolution](@ref howto_time_evolution). + +## 5. Magnetization over time + +The payoff: the transverse magnetization at the middle of the chain, as a function of time after the quench. + +```@example time-evolution +plot(times, m; + xlabel = "t", ylabel = "⟨σˣ⟩ at site $i_mid", + label = "TDVP, D = $D", title = "TFIM transverse magnetization after a quench") +``` + +The curve shows how the observable responds to the sudden change in the field: starting from its pre-quench value, ``\langle\sigma^x\rangle`` relaxes towards a new value set by `H₁`, with oscillations along the way. + +!!! warning "Fixed bond dimension means finite reach in time" + Single-site TDVP keeps the bond dimension fixed at whatever the initial state has. + After a quench, however, the entanglement of the evolving state grows with time, so a fixed bond dimension can only follow the true dynamics faithfully up to some finite time — beyond it, the simulation quietly loses accuracy rather than failing loudly. + The practical checks and remedies — two-site [`TDVP2`](@ref), which grows the bond dimension as it truncates, and bond-expansion options for single-site TDVP — are collected in [Time evolution](@ref howto_time_evolution). + +## Where to go next + +You have run your first dynamics simulation: prepare a ground state, quench the Hamiltonian, step the state forward in time, and read off an observable at every step. + +The natural reference for everything this page glossed over is the [Time evolution](@ref howto_time_evolution) how-to: evolving over a time span in one call, growing the bond dimension during evolution, imaginary time, and evolving infinite states. +Speaking of which — everything here was done on a finite chain, but `timestep` works just as well on the `InfiniteMPS` states introduced in [The thermodynamic limit](@ref tutorial_thermodynamic_limit). +And for measuring more than a single local magnetization on the evolved states, see [Computing observables](@ref howto_observables). diff --git a/docs/src/tutorials/using_symmetries.md b/docs/src/tutorials/using_symmetries.md new file mode 100644 index 000000000..220dacce3 --- /dev/null +++ b/docs/src/tutorials/using_symmetries.md @@ -0,0 +1,181 @@ + + +# [Using symmetries](@id tutorial_using_symmetries) + +In [Your first ground state](@ref tutorial_first_groundstate) and [The thermodynamic limit](@ref tutorial_thermodynamic_limit) we treated the transverse-field Ising model (TFIM) as a generic spin chain. +But the TFIM is not generic: it has a symmetry, and in this tutorial we teach MPSKit about it. + +Recall the Hamiltonian, + +```math +H = -J\left(\sum_{\langle i,j\rangle} \sigma^z_i\,\sigma^z_j + g\sum_i \sigma^x_i\right), +``` + +and consider the *global spin flip* ``P = \prod_i \sigma^x_i``, which flips every spin at once. +Conjugating by ``P`` sends ``\sigma^z_i \to -\sigma^z_i``, so the interaction term ``\sigma^z_i\sigma^z_j`` picks up two minus signs and is unchanged, while the field term ``\sigma^x_i`` commutes with ``P`` trivially. +Hence ``H`` commutes with ``P``. +Since ``P^2 = 1``, this is a ``\mathbb{Z}_2`` symmetry, and every eigenstate of ``H`` can be labelled by a parity quantum number: *even* (``P = +1``) or *odd* (``P = -1``). + +MPSKit, through the TensorKit tensor backend, can bake this symmetry directly into the tensors of the MPS. +Doing so buys you two things. +First, the tensors become **block-sparse**: at the same total bond dimension the computer multiplies smaller dense blocks, which is faster. +Second, every state you compute carries an explicit **sector label**, so "the lowest odd-parity excitation" becomes something you can ask for directly. +This tutorial demonstrates both, by redoing the finite-chain TFIM calculation once without and once with the symmetry. + +## Loading the packages + +Every code block on this page shares one Julia session, so we load the packages once. +`Z2Irrep` and `Z2Space`, the symmetry-aware building blocks used below, come from TensorKit. + +```@example using-symmetries +using MPSKit, MPSKitModels, TensorKit +``` + +## 1. Recap: the ground state without symmetry + +We start from the workflow of the first tutorial: a chain of `L = 16` sites, a random `FiniteMPS`, and a DMRG ground-state search. +Two small changes from before: we set the field to `g = 2.0`, and we use a total bond dimension of 16. + +Why `g = 2.0`? +This puts us deep in the paramagnetic phase, where the ground state respects the spin-flip symmetry. +That matters for what comes next: an MPS built from symmetric tensors lives in exactly one parity sector and *cannot* spontaneously break the symmetry, so a fair comparison needs a point where the true ground state is symmetric to begin with. + +```@example using-symmetries +L = 16 +H = transverse_field_ising(FiniteChain(L); g = 2.0) +ψ₀ = FiniteMPS(L, ℂ^2, ℂ^16) +ψ, envs, ϵ = find_groundstate(ψ₀, H, DMRG(; verbosity = 0)) +E = expectation_value(ψ, H) +``` + +This is our reference number: the ground-state energy computed with plain, symmetry-oblivious tensors. + +## 2. The same model, with the symmetry made explicit + +To exploit the symmetry we change two lines: the Hamiltonian and the initial state. + +For the Hamiltonian, we pass the symmetry as an extra first argument. +`transverse_field_ising(Z2Irrep, ...)` builds the *same* Hamiltonian as before, but out of tensors that manifestly commute with the spin flip: + +```@example using-symmetries +H_Z2 = transverse_field_ising(Z2Irrep, FiniteChain(L); g = 2.0) +``` + +For the state, the plain spaces `ℂ^2` and `ℂ^16` are replaced by *graded* spaces that keep track of parity: + +```@example using-symmetries +ψ₀_Z2 = FiniteMPS(L, Z2Space(0 => 1, 1 => 1), Z2Space(0 => 8, 1 => 8)) +``` + +The syntax reads as a list of `sector => dimension` pairs, where sector `0` is the even (``P = +1``) irrep of ``\mathbb{Z}_2`` and sector `1` is the odd (``P = -1``) one: + +- The physical space `Z2Space(0 => 1, 1 => 1)` is the familiar two-dimensional spin-1/2 site, now split into its symmetry content: one even state and one odd state. +- The virtual space `Z2Space(0 => 8, 1 => 8)` says the bond carries 8 states of even parity and 8 of odd parity — 16 in total, matching the `ℂ^16` of the plain run, so the two calculations have exactly the same variational power. + +From here the workflow is unchanged: + +```@example using-symmetries +ψ_Z2, envs_Z2, ϵ_Z2 = find_groundstate(ψ₀_Z2, H_Z2, DMRG(; verbosity = 0)) +E_Z2 = expectation_value(ψ_Z2, H_Z2) +``` + +Both runs found the same ground state, and the two energies agree to numerical precision: + +```@example using-symmetries +E, E_Z2 +``` + +!!! note "Same physics, different bookkeeping" + Nothing about the model changed — only the way its tensors are stored. + The symmetric calculation restricts the search to states of definite (here: even) parity, and stores only the tensor blocks the symmetry allows to be nonzero. + +## 3. The payoff, part 1: block-sparse tensors + +Where did the symmetry go? +Into the *structure* of the state. +Ask for the virtual space at the central bond and you no longer get an anonymous `ℂ^16`, but a space that knows its sector decomposition: + +```@example using-symmetries +V = left_virtualspace(ψ_Z2, L ÷ 2) +``` + +Its total dimension is still 16: + +```@example using-symmetries +dim(V) +``` + +The same sector labels show up in every quantity derived from the state. +The entanglement spectrum at the central cut, for instance, now comes back resolved by sector — compare [Entanglement entropy and spectrum](@ref howto_entanglement), where the same call on an unsymmetric state produced a single `Trivial()` block: + +```@example using-symmetries +spectrum = entanglement_spectrum(ψ_Z2, L ÷ 2) +collect(keys(spectrum)) +``` + +Iterating `pairs` gives each sector together with its singular values: + +```@example using-symmetries +collect(pairs(spectrum)) +``` + +This block structure is where the speedup comes from: instead of multiplying one dense 16-dimensional bond index, the computer multiplies two independent blocks of roughly half that size, and the forbidden matrix elements between the sectors are never stored or touched at all. +At bond dimension 16 the difference is negligible, but the saving grows with the bond dimension and with the size of the symmetry group. + +## 4. The payoff, part 2: sectors label the physics + +The sector labels are not just an implementation detail — they classify the eigenstates of ``H``, and MPSKit lets you target a sector directly. + +In the paramagnetic phase the lowest excitation of the TFIM is, roughly speaking, a single flipped spin. +Flipping one spin changes the parity of the state, so this excitation lives in the *odd* sector — a different sector than the (even) ground state. + +The [`excitations`](@ref) function computes excited states on top of a converged ground state; on a finite chain it takes the Hamiltonian, an algorithm, the ground state, and its environments, and returns energies measured *above* the ground state. +By default it searches the trivial (even) sector: + +```@example using-symmetries +Es_even, ϕs_even = excitations(H_Z2, QuasiparticleAnsatz(), ψ_Z2, envs_Z2; num = 1) +Es_even[1] +``` + +The `sector` keyword redirects the search to the odd sector: + +```@example using-symmetries +Es_odd, ϕs_odd = excitations( + H_Z2, QuasiparticleAnsatz(), ψ_Z2, envs_Z2; + num = 1, sector = Z2Irrep(1) +) +Es_odd[1] +``` + +The odd-sector excitation is indeed the lower one: + +```@example using-symmetries +Es_odd[1] < Es_even[1] +``` + +Without the symmetry built into the tensors, this question could not even be posed: the plain calculation of Section 1 has no notion of parity to select on. +More ways to use `excitations` — dispersion relations, other algorithms, infinite chains — are collected in [Excited states](@ref howto_excitations). + +## Where to go next + +You have run the flagship TFIM calculation with its ``\mathbb{Z}_2`` symmetry made explicit: the same physics at the same total bond dimension, but with block-sparse tensors and sector labels on everything the calculation produces. + +The same syntax scales up to larger symmetry groups, where the payoff grows. +For a U(1) symmetry (particle number, magnetization) the graded spaces list integer or half-integer charges instead of parities — worked constructions are in [Constructing states](@ref howto_states), Section 9. +For non-abelian symmetries such as SU(2) the gains are more dramatic still, because each symmetric block then represents an entire multiplet of states; the spin-1 Haldane chain and XXZ Heisenberg pages in the examples gallery show this in action. + +To continue the tutorial track, [Quasiparticle excitations](@ref tutorial_excitations) develops the excitation calculation of Section 4 into a full dispersion relation; the recipe collection for excited states is [Excited states](@ref howto_excitations), and the one for building symmetric states is [Constructing states](@ref howto_states). + + diff --git a/examples/Cache.toml b/examples/Cache.toml index 76b7cebb6..1183f3fd4 100644 --- a/examples/Cache.toml +++ b/examples/Cache.toml @@ -3,10 +3,11 @@ [quantum1d] "2.haldane" = "c5a0eb70f0930d38053535c659ab39a87121b6ebda040ceadd9deb5f30921315" -"6.hubbard" = "cef140a6224350345735aac889ee7e33724fc0af9ce94f68be47a7e40107c09c" -"7.xy-finiteT" = "0f330a157bea739a43a82a937791c680b2fa6e5e479171ee5ef318d1fdae7bcf" -"8.bose-hubbard" = "a060ac3e29a9169e9420f7125cdb020c83b26a380508ac85f108bff1562bc70c" -"3.ising-dqpt" = "a2900eed23de7655f600943fae72d1dc35f87f33d11947f707d302ce245399fe" -"5.haldane-spt" = "28eb0350bd7e721866ccc1c14744e78ae5c650fda81e5f0eefb91de40ee4a76a" -"4.xxz-heisenberg" = "9033fb104c3f658ccb1b8e335b986b7abda59e0b5359be086d3b62e1cc32ebd6" -"1.ising-cft" = "3d34757eee7b95120b746c5e30e713203a876a87353ecd9937798f29183c9916" +"6.hubbard" = "50cb64102c2e3a61600df279a9cae45cd3b024e82e6f695d3ed713b0fd3f9283" +"7.xy-finiteT" = "0d21f7556fee8409edb58ab0ea04a77bd90feb2dffec424f0a5962d11dfdfc65" +"8.bose-hubbard" = "ba9ec53721371aab311dadbe05a31a3c95cb1b8a394abb7f81ee97e15b84d503" +"3.ising-dqpt" = "056423a189e1b1d4110bebc4eb6129a957ecdbf8e9a679eca10cf0391494b944" +"5.haldane-spt" = "c8fd3a8d406b9ff3ea9a34b596c5910d81e4eb710b665d7fa04e30f795a46086" +"4.xxz-heisenberg" = "5fa4242290b0a9ae658caf54f6919a8e04c97af80b9fcc6b936c2f45c7c8efdd" +"1.ising-cft" = "ebcae1e501347cb00a46d0889df1355707be77cf962f9ee0d16416393492529b" +"0.tfim-groundstate" = "a87cc89caf8df47285e25f6cec4ff130ea6ed88d4f73d59f8e7dc2e799ece1b0" diff --git a/examples/quantum1d/0.tfim-groundstate/main.jl b/examples/quantum1d/0.tfim-groundstate/main.jl new file mode 100644 index 000000000..09b3ae7e5 --- /dev/null +++ b/examples/quantum1d/0.tfim-groundstate/main.jl @@ -0,0 +1,185 @@ +md""" +# The transverse-field Ising model: a complete ground-state study + +This example is the bridge from the introductory tutorials into the research-grade +gallery. +If you have worked through [Your first ground state](@ref tutorial_first_groundstate) and +[The thermodynamic limit](@ref tutorial_thermodynamic_limit) you already know every +individual tool used here; the goal now is to *assemble* them into one coherent case +study of a genuine quantum phase transition. + +We use the same transverse-field Ising model (TFIM) as the tutorials, on a chain of +spin-1/2 sites: + +```math +H = -J\left(\sum_{\langle i,j\rangle} \sigma^z_i \sigma^z_j + g\sum_i \sigma^x_i\right), +``` + +where the first sum runs over neighbouring pairs, ``J`` sets the energy scale, and the +dimensionless field ``g`` tunes the competition between the ``\sigma^z\sigma^z`` +interaction and the transverse ``\sigma^x`` field. +The model has a quantum critical point at ``g = 1``. + +Rather than looking at a single field value, we will scan ``g`` across the transition and +diagnose it three independent ways, comparing a *finite* chain against a calculation +performed *directly in the thermodynamic limit*: + +1. the order parameter ``|\langle\sigma^z\rangle|``, computed both for a finite chain and + for an infinite chain, in one figure; +2. the entanglement entropy of the infinite state; +3. the correlation length of the infinite state. + +All three should point at the same place — that agreement is the payoff. +""" + +# We take the model and lattice from MPSKitModels, the tensor backend from TensorKit, and +# Plots for the figures. The Pauli operators `σᶻ`, `σˣ` are re-exported by MPSKitModels. + +using MPSKit, MPSKitModels, TensorKit, Plots + +md""" +## Shared parameters + +We fix a finite chain length `L`, a bond dimension `D` (the accuracy knob, see +[Controlling bond dimension](@ref howto_bond_dimension)), and the set of field values to +scan. +`D` is kept modest so the whole page runs in a couple of minutes; increasing it sharpens +the infinite-state diagnostics below (the finite-chain curve responds to `D` in a less +obvious way, as we will see). +""" + +L = 16 +D = 8 +g_values = 0.1:0.1:2.0 + +md""" +## 1. Finite versus infinite magnetization + +We compute the order parameter ``|\langle\sigma^z\rangle|`` two ways at every field value. + +For the **finite** calculation we use an open chain of `L` sites, exactly as in the +tutorial, and optimize with [`DMRG`](@ref). +We average ``\langle\sigma^z_i\rangle`` over the sites and take the absolute value: the +exact finite-`L` ground state is symmetric, but DMRG lands on one of the two +symmetry-broken states with an arbitrary sign (see the discussion in +[Your first ground state](@ref tutorial_first_groundstate)). +""" + +ψ₀_finite = FiniteMPS(L, ℂ^2, ℂ^D) +M_finite = map(g_values) do g + H = transverse_field_ising(FiniteChain(L); g = g) + ψ, = find_groundstate(ψ₀_finite, H, DMRG(; verbosity = 0)) + return abs(sum(expectation_value(ψ, i => σᶻ()) for i in 1:L)) / L +end; + +md""" +For the **infinite** calculation we drop the lattice argument to build the Hamiltonian on +the infinite chain, use an [`InfiniteMPS`](@ref), and optimize with [`VUMPS`](@ref). +We keep every optimized infinite state, because we will reuse them for the entropy and +correlation-length diagnostics below. +""" + +ψ₀_infinite = InfiniteMPS(ℂ^2, ℂ^D) +states_infinite = map(g_values) do g + H = transverse_field_ising(; g = g) + ψ, = find_groundstate(ψ₀_infinite, H, VUMPS(; verbosity = 0)) + return ψ +end; + +md""" +The order parameter of a translation-invariant state is just ``\langle\sigma^z\rangle`` on +a single site of the unit cell; we again take the absolute value, because on the ordered +side the infinite state settles into one of the two symmetry-broken ground states (see +[The thermodynamic limit](@ref tutorial_thermodynamic_limit)). +""" + +M_infinite = [abs(expectation_value(ψ, 1 => σᶻ())) for ψ in states_infinite]; + +md""" +Plotting both curves in a single figure lets us compare them directly. +""" + +p_magnetization = plot(; + xlabel = "g", ylabel = "|⟨σᶻ⟩|", title = "TFIM order parameter", legend = :bottomleft +) +scatter!(p_magnetization, g_values, M_finite; label = "finite chain, L = $L, D = $D") +scatter!(p_magnetization, g_values, M_infinite; label = "infinite, D = $D") +vline!(p_magnetization, [1.0]; color = "gray", linestyle = :dash, label = "g = 1") +p_magnetization + +md""" +Both calculations agree deep in either phase, but near the transition they tell very different stories. +The infinite curve stays on its ordered branch essentially up to `g = 1` and then collapses: it locates the critical point cleanly. +The finite-chain curve instead drops to zero far earlier — at this `L` and `D` the variational optimum on the open chain switches from the symmetry-broken branch to the exactly symmetric ground state, whose magnetization vanishes. +Where that switch happens is set by `L` and `D`, not by the physics; the same sweep at `D = 4` in [Your first ground state](@ref tutorial_first_groundstate) puts it elsewhere. +That is the real lesson of this panel: the finite-chain order parameter is dominated by which state the algorithm selects, while the calculation performed directly in the thermodynamic limit pins the transition at `g = 1`. + +""" + +md""" +## 2. Entanglement entropy across the transition + +Entanglement is a hallmark of criticality: it is bounded away from the critical point but +grows sharply as we approach it. +For an [`InfiniteMPS`](@ref), [`entropy`](@ref) returns the von Neumann entanglement entropy +per bond, one value for each site of the unit cell. +Our unit cell has a single site, so we take the one entry with `only`. +""" + +S_infinite = [real(only(entropy(ψ))) for ψ in states_infinite] +p_entropy = scatter( + g_values, S_infinite; + xlabel = "g", ylabel = "entanglement entropy S", title = "TFIM entanglement entropy", + legend = false +) +vline!(p_entropy, [1.0]; color = "gray", linestyle = :dash) +p_entropy + +md""" +The entropy peaks near `g = 1`. +That peak is the entanglement signature of the phase transition: at criticality +correlations become long-ranged and the ground state is at its most entangled, whereas deep +in either phase the state is closer to a simple product and the entropy is small. +""" + +md""" +## 3. Correlation length across the transition + +The [`correlation_length`](@ref) measures how far apart two spins can still influence each +other; it is extracted from the transfer-matrix spectrum of the uniform infinite state and +has no finite-chain analogue. +It grows toward criticality, so we plot it on a logarithmic vertical axis to make the +growth visible. +""" + +ξ_infinite = [correlation_length(ψ) for ψ in states_infinite] +p_xi = scatter( + g_values, ξ_infinite; + xlabel = "g", ylabel = "correlation length ξ", yscale = :log10, + title = "TFIM correlation length", legend = false +) +vline!(p_xi, [1.0]; color = "gray", linestyle = :dash) +p_xi + +md""" +The correlation length peaks near `g = 1` as well. +At a genuine critical point it would diverge, but a finite bond dimension `D` can only +capture correlations out to a finite range, so what we measure is large-but-capped rather +than infinite — the peak grows and sharpens as `D` is increased. +""" + +md""" +## What you now have + +Three independent diagnostics — the order parameter, the entanglement entropy, and the +correlation length — all locate the transition of the transverse-field Ising model near +`g = 1`, and the finite-versus-infinite comparison shows concretely why the thermodynamic +limit is the right place to measure it. + +From here the gallery goes further. +The Ising CFT example extracts the momentum-resolved excitation spectrum right at +criticality and matches it to the predictions of conformal field theory, turning the "there +is a critical point near `g = 1`" of this page into a quantitative fingerprint of *which* +critical theory it is. +Every curve on this page also sharpens if you rerun it at a larger bond dimension `D`. +""" diff --git a/examples/quantum1d/1.ising-cft/main.jl b/examples/quantum1d/1.ising-cft/main.jl index a3f45887e..571e6ec74 100644 --- a/examples/quantum1d/1.ising-cft/main.jl +++ b/examples/quantum1d/1.ising-cft/main.jl @@ -122,7 +122,7 @@ can reach higher system sizes. L_mps = 20 H_mps = periodic_boundary_conditions(transverse_field_ising(), L_mps) D = 64 -ψ, envs, δ = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG()); +ψ, envs, δ = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG(; verbosity = 0)); md""" Excitations on top of the ground state can be found through the use of the quasiparticle diff --git a/examples/quantum1d/3.ising-dqpt/main.jl b/examples/quantum1d/3.ising-dqpt/main.jl index 3d445cf58..5fdd4dd3a 100644 --- a/examples/quantum1d/3.ising-dqpt/main.jl +++ b/examples/quantum1d/3.ising-dqpt/main.jl @@ -28,7 +28,7 @@ First we construct the Hamiltonian in MPO form, and obtain the pre-quenched grou L = 20 H₀ = transverse_field_ising(FiniteChain(L); g = -0.5) ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10) -ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG()); +ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)); md""" ## Finite MPS quenching @@ -57,7 +57,7 @@ Putting it all together, we get function finite_sim(L; dt = 0.05, finaltime = 5.0) ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10) H₀ = transverse_field_ising(FiniteChain(L); g = -0.5) - ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG()) + ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)) H₁ = transverse_field_ising(FiniteChain(L); g = -2.0) ψₜ = deepcopy(ψ₀) @@ -85,7 +85,7 @@ Similarly we could start with an initial infinite state and find the pre-quench ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10]) H₀ = transverse_field_ising(; g = -0.5) -ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS()); +ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0)); md""" The dot product of two infinite matrix product states scales as ``\alpha ^N`` where ``α`` is the dominant eigenvalue of the transfer matrix. @@ -123,7 +123,7 @@ The final code is function infinite_sim(dt = 0.05, finaltime = 5.0) ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10]) - ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS()) + ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0)) ψₜ = deepcopy(ψ₀) envs = environments(ψₜ, H₁, ψₜ) diff --git a/examples/quantum1d/4.xxz-heisenberg/main.jl b/examples/quantum1d/4.xxz-heisenberg/main.jl index 8c684e911..a4b4c3d0d 100644 --- a/examples/quantum1d/4.xxz-heisenberg/main.jl +++ b/examples/quantum1d/4.xxz-heisenberg/main.jl @@ -7,9 +7,10 @@ The necessary packages to follow this tutorial are: using MPSKit, MPSKitModels, TensorKit, Plots -#src # for reproducibility: -#src using Random -#src Random.seed!(123) +# For reproducibility of this page, we fix the seed of the random number generator: + +using Random +Random.seed!(123); md""" ## Failure @@ -31,7 +32,7 @@ md""" The ground state can then be found by calling `find_groundstate`. """ -groundstate, cache, delta = find_groundstate(state, H, VUMPS()); +groundstate, cache, delta = find_groundstate(state, H, VUMPS(; verbosity = 1)); md""" As you can see, VUMPS struggles to converge. @@ -39,7 +40,7 @@ On its own, that is already quite curious. Maybe we can do better using another algorithm, such as gradient descent. """ -groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20)); +groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20, verbosity = 1)); md""" Convergence is quite slow and even fails after sufficiently many iterations. @@ -72,7 +73,7 @@ Alternatively, the Hamiltonian can be constructed directly on a two-site unit ce ## H2 = repeat(H, 2); -- copies the one-site version H2 = heisenberg_XXX(ComplexF64, Trivial, InfiniteChain(2); spin = 1 // 2) groundstate, envs, delta = find_groundstate( - state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12) + state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12, verbosity = 1) ); md""" @@ -81,7 +82,7 @@ The reason behind this becomes more obvious at higher bond dimensions: """ groundstate, envs, delta = find_groundstate( - state, H2, IDMRG2(; trscheme = truncrank(50), maxiter = 20, tol = 1.0e-12) + state, H2, IDMRG2(; trscheme = truncrank(50), maxiter = 20, tol = 1.0e-12, verbosity = 1) ); entanglementplot(groundstate) @@ -125,4 +126,4 @@ Even though the bond dimension is higher than in the example without symmetry, c println(dim(V1)) println(dim(V2)) -groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12)); +groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12, verbosity = 1)); diff --git a/examples/quantum1d/6.hubbard/main.jl b/examples/quantum1d/6.hubbard/main.jl index 090c84324..3dab08047 100644 --- a/examples/quantum1d/6.hubbard/main.jl +++ b/examples/quantum1d/6.hubbard/main.jl @@ -9,7 +9,7 @@ a kinetic term that allows electrons to hop between neighboring sites, and a pot Often, a third term is included which serves as a chemical potential to control the number of electrons in the system. ```math -H = -t \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + U \sum_i n_{i,\uparrow} n_{i,\downarrow} - \mu \sum_{i,\sigma} n_{i,\sigma} +H = -t ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U ∑_i n_{i,↑} n_{i,↓} - μ ∑_{i,σ} n_{i,σ} ``` At half-filling, the system exhibits particle-hole symmetry, which can be made explicit by rewriting the Hamiltonian slightly. @@ -17,13 +17,13 @@ First, we fix the overall energy scale by setting `t = 1`, and then shift the to This results in the following Hamiltonian: ```math -H = - \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + U / 4 \sum_i (1 - 2 n_{i,\uparrow}) (1 - 2 n_{i,\downarrow}) - \mu \sum_{i,\sigma} n_{i,\sigma} +H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U / 4 ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) - μ ∑_{i,σ} n_{i,σ} ``` Finally, setting `\mu = 0` and defining `u = U / 4` we obtain the Hubbard model at half-filling. ```math -H = - \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + u \sum_i (1 - 2 n_{i,\uparrow}) (1 - 2 n_{i,\downarrow}) +H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + u ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) ``` """ @@ -36,9 +36,10 @@ using Plots using Interpolations using Optim -#src # for reproducibility: -#src using Random -#src Random.seed!(123) +# For reproducibility of this page, we fix the seed of the random number generator: + +using Random +Random.seed!(123); const t = 1.0 const mu = 0.0 @@ -46,10 +47,10 @@ const U = 3.0 md""" For this case, the ground state energy has an analytic solution, which can be used to benchmark the numerical results. -It follows from Eq. (6.82) in [](). +It follows from Eq. (6.82) in [Essler, Frahm, Göhmann, Klümper & Korepin, The One-Dimensional Hubbard Model](https://doi.org/10.1017/CBO9780511534843). ```math -e(u) = - u - 4 \int_0^{\infty} \frac{d\omega}{\omega} \frac{J_0(\omega) J_1(\omega)}{1 + \exp(2u \omega)} +e(u) = - u - 4 ∫₀^{∞} \frac{dω}{ω} \frac{J₀(ω) J₁(ω)}{1 + \exp(2u ω)} ``` We can easily verify this by comparing the numerical results to the analytic solution. @@ -57,7 +58,7 @@ We can easily verify this by comparing the numerical results to the analytic sol function hubbard_energy(u; rtol = 1.0e-12) integrandum(ω) = besselj0(ω) * besselj1(ω) / (1 + exp(2u * ω)) / ω - int, err = quadgk(integrandum, 0, Inf; rtol = rtol) + int, err = quadgk(integrandum, 0, Inf; rtol) return -u - 4 * int end @@ -67,7 +68,7 @@ function compute_groundstate( expansionfactor = (1 / 10), expansioniter = 20 ) - verbosity = 2 + verbosity = 0 psi, = find_groundstate(psi, H; tol = svalue * 10, verbosity) for _ in 1:expansioniter D = maximum(x -> dim(left_virtualspace(psi, x)), 1:length(psi)) @@ -107,7 +108,7 @@ md""" ## Symmetries The Hubbard model has a rich symmetry structure, which can be exploited to speed up simulations. -Apart from the fermionic parity, the model also has a $U(1)$ particle number symmetry, along with a $SU(2)$ spin symmetry. +Apart from the fermionic parity, the model also has a ``U(1)`` particle number symmetry, along with a ``SU(2)`` spin symmetry. Explicitly imposing these symmetries on the tensors can greatly reduce the computational cost of the simulation. Naively imposing these symmetries however, is not compatible with our desire to work at half-filling. @@ -139,7 +140,7 @@ The elementary excitations are known as spinons and holons, which are domain wal The fact that the spin and charge sectors are separate is a phenomenon known as spin-charge separation. The domain walls can be constructed by noticing that there are two equivalent groundstates, which differ by a translation over a single site. -In other words, the groundstates are ``\psi_{AB}` and ``\psi_{BA}``, where ``A`` and ``B`` are the two sites. +In other words, the groundstates are ``\psi_{AB}`` and ``\psi_{BA}``, where ``A`` and ``B`` are the two sites. These excitations can be constructed as follows: """ @@ -215,7 +216,7 @@ md""" The plot shows some discrepancies between the numerical and analytic results. First and foremost, we must realize that in the thermodynamic limit, the momentum of a domain wall is actually not well-defined. Concretely, only the difference in momentum between the two groundstates is well-defined, as we can always shift the momentum by multiplying one of the groundstates by a phase. -Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor ``\pi/2``. +Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor ``π/2``. """ momenta_shifted = rem2pi.(momenta .- π / 2, RoundNearest) diff --git a/examples/quantum1d/7.xy-finiteT/main.jl b/examples/quantum1d/7.xy-finiteT/main.jl index 03ad41999..6b66b05c5 100644 --- a/examples/quantum1d/7.xy-finiteT/main.jl +++ b/examples/quantum1d/7.xy-finiteT/main.jl @@ -56,8 +56,7 @@ The Hamiltonian can be diagonalized in terms of fermionic creation and annihilat E_0 = -\frac{1}{\pi} \text{EllipticE}\left( \sqrt{1 - \gamma^2} \right) ``` -!!! todo - Show the derivation of the ground state energy by diagonalizing the Hamiltonian in terms of fermionic operators. +The derivation, via a Jordan-Wigner transformation to free fermions followed by a Bogoliubov rotation, can be found in [Lieb, Schultz & Mattis, Ann. Phys. 16, 407 (1961)](https://doi.org/10.1016/0003-4916(61)90115-4). """ function groundstate_energy(J, N) @@ -141,8 +140,7 @@ The resulting expression is Z(\beta) = \prod_{k=1}^{N} \left( 1 + e^{-\beta \epsilon_k} \right)^{1/N} ``` -!!! todo - Show the derivation of the partition function for the XY model. +This expression follows from the same free-fermion diagonalization as the ground-state energy above: each single-particle mode $\epsilon_k$ is independently occupied or empty, giving the usual free-fermion partition function (see again [Lieb, Schultz & Mattis (1961)](https://doi.org/10.1016/0003-4916(61)90115-4)). """ function partition_function(β::Number, J::Number, N::Number) @@ -274,8 +272,6 @@ Z(\beta) = In other words, we can compute the partition function at $\beta$ by computing the overlap of two states evolved for $\beta / 2$, as long as the Hamiltonian is Hermitian. Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^\dagger$. -!!! todo - Add a figure to illustrate this trick. """ double_logpartition(ρ₁, ρ₂ = ρ₁) = log(real(dot(ρ₁, ρ₂))) / length(ρ₁) diff --git a/src/algorithms/approximate/approximate.jl b/src/algorithms/approximate/approximate.jl index f28e67e6b..a46c37d93 100644 --- a/src/algorithms/approximate/approximate.jl +++ b/src/algorithms/approximate/approximate.jl @@ -8,19 +8,19 @@ Compute an approximation to the application of an operator `O` to the state `ψ` of an MPS `ψ₀`. If only a state `ψ` is supplied instead of the `(O, ψ)` pair, `ψ₀` is approximated directly to `ψ` (i.e. `O` is taken to be the identity). -## Arguments +# Arguments - `ψ₀::AbstractMPS`: initial guess of the approximated state - `(O::AbstractMPO, ψ::AbstractMPS)`: operator `O` and state `ψ` to be approximated - `ψ::AbstractMPS`: state to be approximated directly (without an operator) - `algorithm`: approximation algorithm. See below for a list of available algorithms. - `[environments]`: MPS environment manager -## Keywords +# Keyword Arguments - `tol::Float64`: tolerance for convergence criterium - `maxiter::Int`: maximum amount of iterations - `verbosity::Int`: display progress information -## Algorithms +# Algorithms - `DMRG`: Alternating least square method for maximizing the fidelity with a single-site scheme. - `DMRG2`: Alternating least square method for maximizing the fidelity with a two-site scheme. diff --git a/src/algorithms/changebonds/changebonds.jl b/src/algorithms/changebonds/changebonds.jl index 9dce51c45..c36f9c271 100644 --- a/src/algorithms/changebonds/changebonds.jl +++ b/src/algorithms/changebonds/changebonds.jl @@ -8,6 +8,26 @@ changedbonds! can modify both the provided state and environments, depending on For FiniteMPS, changebonds also modifies the environments. See also: [`SvdCut`](@ref), [`RandExpand`](@ref), [`VUMPSSvdCut`](@ref), [`OptimalExpand`](@ref) + +# Examples +Growing the bond dimension of a product state with [`OptimalExpand`](@ref), which enriches +each bond with directions orthogonal to the current state (using the environments of `H`): + +```jldoctest +julia> X = TensorMap(Float64[0 1; 1 0], ℂ^2, ℂ^2); + +julia> ψ = FiniteMPS(ones(Float64, (ℂ^2)^4)); + +julia> H = FiniteMPOHamiltonian(fill(ℂ^2, 4), ((i, i + 1) => X ⊗ X for i in 1:3)); + +julia> dim(left_virtualspace(ψ, 3)) +1 + +julia> ψ′, envs = changebonds(ψ, H, OptimalExpand(; trscheme = truncrank(4))); + +julia> dim(left_virtualspace(ψ′, 3)) +2 +``` """ changebonds, changebonds! function changebonds end function changebonds! end @@ -16,9 +36,9 @@ function changebonds! end changebond(site, dir, ψ, [H], alg, [envs]) -> ψ changebond!(site, dir, ψ, [H], alg, [envs]) -> ψ -Expand a single bond of `ψ` in place by adding directions orthogonal to the current state, keeping the state in mixed-canonical form around the enriched bond. +Expand a single bond of `ψ` by adding directions orthogonal to the current state, keeping the state in mixed-canonical form around the expanded bond. The sweep direction `dir` is a `Val(:right)` or `Val(:left)` used for dispatch. -For `Val(:right)` the bond `(site, site + 1)` is enriched on the right tensor (`ψ.AR[site + 1]`) with zero weight added at `ψ.AC[site]`, so that a subsequent single-site optimization of `site` sees the new directions; +For `Val(:right)` the bond `(site, site + 1)` is expanded on the right tensor (`ψ.AR[site + 1]`) with zero weight added at `ψ.AC[site]`, so that a subsequent single-site optimization of `site` sees the new directions; for `Val(:left)` the mirror is applied to bond `(site - 1, site)`. See also [`changebonds`](@ref), [`changebonds!`](@ref). diff --git a/src/algorithms/changebonds/optimalexpand.jl b/src/algorithms/changebonds/optimalexpand.jl index 73da47899..5fe4a5e77 100644 --- a/src/algorithms/changebonds/optimalexpand.jl +++ b/src/algorithms/changebonds/optimalexpand.jl @@ -5,15 +5,20 @@ An algorithm that expands the given mps as described in [Zauner-Stauber et al. Phys. Rev. B 97 (2018)](@cite zauner-stauber2018), by selecting the dominant contributions of a two-site updated MPS tensor, orthogonal to the original ψ. -The expansion does not alter the state: the added directions are connected through a zero block, -so that the expanded state is identical to the original one (as required for e.g. TDVP). +The expansion is state-preserving: the added directions are connected through a zero block, +so that the expanded state represents the same physical state as the original one (as required +for e.g. TDVP). !!! note [`changebonds!`](@ref) is only defined for `FiniteMPS`, and modifies both the state and its environment. -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`changebonds`](@ref) and [`changebonds!`](@ref). """ @kwdef struct OptimalExpand{S} <: Algorithm "algorithm used for the singular value decomposition" diff --git a/src/algorithms/changebonds/randexpand.jl b/src/algorithms/changebonds/randexpand.jl index 7445a0e21..7d93cfe92 100644 --- a/src/algorithms/changebonds/randexpand.jl +++ b/src/algorithms/changebonds/randexpand.jl @@ -13,9 +13,13 @@ distributed weights for each state in the two-site space and truncating that. The environments are not used here, but [`changebonds!`](@ref) modifies both the state and environment so they remain consistent. -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`changebonds`](@ref) and [`changebonds!`](@ref). """ @kwdef struct RandExpand{S} <: Algorithm "algorithm used for the singular value decomposition" diff --git a/src/algorithms/changebonds/sketchedexpand.jl b/src/algorithms/changebonds/sketchedexpand.jl index 9e89b3b81..e839ea2fb 100644 --- a/src/algorithms/changebonds/sketchedexpand.jl +++ b/src/algorithms/changebonds/sketchedexpand.jl @@ -15,9 +15,13 @@ The state-preserving behaviour matches [`OptimalExpand`](@ref). as the `alg_expand` strategy of [`DMRG`](@ref). The reported `ϵ_2site` is a randomized estimate, and the folded application does not exploit `JordanMPO` sparsity. -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`changebonds`](@ref) and [`changebonds!`](@ref). """ @kwdef struct SketchedExpand{S} <: Algorithm "algorithm used to orthonormalize the sketched complement (passed as the `alg` of `left_orth!`/`right_orth!`); `nothing` selects QR without oversampling and an SVD-based decomposition otherwise" diff --git a/src/algorithms/changebonds/svdcut.jl b/src/algorithms/changebonds/svdcut.jl index fbbcb757c..a4a3312c9 100644 --- a/src/algorithms/changebonds/svdcut.jl +++ b/src/algorithms/changebonds/svdcut.jl @@ -6,13 +6,15 @@ This is achieved by a sweeping algorithm that locally performs (optimal) truncat changedbonds! is only defined for FiniteMPS and FiniteMPO. -See also [`changebonds(!)`](@ref changebonds) - -## Fields +# Fields $(TYPEDFIELDS) -## References +# See also + +Used as the `algorithm` argument of [`changebonds`](@ref) and [`changebonds!`](@ref). + +# References * [Parker et al. Phys. Rev. B 102 (2020)](@cite parker2020) """ diff --git a/src/algorithms/changebonds/vumpssvd.jl b/src/algorithms/changebonds/vumpssvd.jl index 75655f65a..d2aa4d570 100644 --- a/src/algorithms/changebonds/vumpssvd.jl +++ b/src/algorithms/changebonds/vumpssvd.jl @@ -6,9 +6,13 @@ An algorithm that uses a two-site update step to change the bond dimension of a !!! note [`changebonds!`](@ref) is not defined. -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`changebonds`](@ref). """ @kwdef struct VUMPSSvdCut <: Algorithm "algorithm used for gauging the `InfiniteMPS`" diff --git a/src/algorithms/correlators.jl b/src/algorithms/correlators.jl index 4413ef63c..5ffac26b3 100644 --- a/src/algorithms/correlators.jl +++ b/src/algorithms/correlators.jl @@ -3,7 +3,8 @@ correlator(ψ, O12, i, j) Compute the 2-point correlator <ψ|O1[i]O2[j]|ψ> for inserting `O1` at `i` and `O2` at `j`. -Also accepts ranges for `j`. +Also accepts ranges for `j`. The sites must be ordered as `i < j`; other orderings throw an +`ArgumentError`. """ function correlator end @@ -14,7 +15,7 @@ end function correlator( state::AbstractMPS, O₁::MPOTensor, O₂::MPOTensor, i::Int, js::AbstractRange{Int} ) - first(js) > i || @error "i should be smaller than j ($i, $(first(js)))" + first(js) > i || throw(ArgumentError("i should be smaller than j ($i, $(first(js)))")) S₁ = _firstspace(O₁) isunitspace(S₁) || throw(ArgumentError("O₁ should start with a trivial leg.")) S₂ = _lastspace(O₂) diff --git a/src/algorithms/derivatives/mpo_derivatives.jl b/src/algorithms/derivatives/mpo_derivatives.jl index db78fe768..5bc4bc943 100644 --- a/src/algorithms/derivatives/mpo_derivatives.jl +++ b/src/algorithms/derivatives/mpo_derivatives.jl @@ -1,5 +1,5 @@ """ - struct MPODerivativeOperator{L,O<:Tuple,R} + struct MPODerivativeOperator{L, O <: Tuple, R} Effective local operator obtained from taking the partial derivative of an MPS-MPO-MPS sandwich. """ diff --git a/src/algorithms/excitation/chepigaansatz.jl b/src/algorithms/excitation/chepigaansatz.jl index 68e6efc22..37241ac0a 100644 --- a/src/algorithms/excitation/chepigaansatz.jl +++ b/src/algorithms/excitation/chepigaansatz.jl @@ -3,11 +3,7 @@ $(TYPEDEF) Single-site optimization algorithm for excitations on top of MPS groundstates. -## Fields - -$(TYPEDFIELDS) - -## Constructors +# Constructors ChepigaAnsatz() ChepigaAnsatz(; kwargs...) @@ -16,9 +12,17 @@ $(TYPEDFIELDS) Create a `ChepigaAnsatz` algorithm with the given eigensolver, or by passing the keyword arguments to [`Arnoldi`](@extref KrylovKit.Arnoldi). -## References +# Fields + +$(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`excitations`](@ref). -- [Chepiga et al. Phys. Rev. B 96 (2017)](@cite chepiga2017) +# References + +* [Chepiga et al. Phys. Rev. B 96 (2017)](@cite chepiga2017) """ struct ChepigaAnsatz{A <: KrylovAlgorithm} <: Algorithm "algorithm used for the eigenvalue solvers" @@ -64,26 +68,30 @@ function excitations( end """ - ChepigaAnsatz2 <: Algorithm +$(TYPEDEF) Two-site optimization algorithm for excitations on top of MPS groundstates. -## Fields -- `alg::A = Defaults.eigsolver`: algorithm to use for the eigenvalue problem. -- `trscheme = Defaults.trscheme`: algorithm to use for truncation. - -## Constructors +# Constructors ChepigaAnsatz2() ChepigaAnsatz2(; kwargs...) ChepigaAnsatz2(alg, trscheme) Create a `ChepigaAnsatz2` algorithm with the given eigensolver and truncation, or by passing the -keyword arguments to `Arnoldi`. +keyword arguments to [`Arnoldi`](@extref KrylovKit.Arnoldi). + +# Fields +- `alg`: algorithm used for the eigenvalue problem, defaults to `Arnoldi(; krylovdim = 30, tol = 1.0e-10, eager = true)` +- `trscheme`: truncation scheme used when splitting the optimized two-site tensor, defaults to `notrunc()` + +# See also + +Used as the `algorithm` argument of [`excitations`](@ref). -## References +# References -- [Chepiga et al. Phys. Rev. B 96 (2017)](@cite chepiga2017) +* [Chepiga et al. Phys. Rev. B 96 (2017)](@cite chepiga2017) """ struct ChepigaAnsatz2{A <: KrylovAlgorithm} <: Algorithm alg::A diff --git a/src/algorithms/excitation/dmrgexcitation.jl b/src/algorithms/excitation/dmrgexcitation.jl index 8bdb934c6..fed0d5812 100644 --- a/src/algorithms/excitation/dmrgexcitation.jl +++ b/src/algorithms/excitation/dmrgexcitation.jl @@ -7,9 +7,13 @@ Variational optimization algorithm for excitations of finite MPS by minimizing t H + λᵢ |ψᵢ⟩⟨ψᵢ| ``` -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`excitations`](@ref). """ @kwdef struct FiniteExcited{A} <: Algorithm "optimization algorithm" diff --git a/src/algorithms/excitation/excitations.jl b/src/algorithms/excitation/excitations.jl index eb1247f90..1bcc0bb93 100644 --- a/src/algorithms/excitation/excitations.jl +++ b/src/algorithms/excitation/excitations.jl @@ -1,14 +1,14 @@ """ excitations(H, algorithm::QuasiparticleAnsatz, ψ::FiniteQP, [left_environments], - [right_environments]; num=1) -> (energies, states) + [right_environments]; num = 1) -> (energies, states) excitations(H, algorithm::QuasiparticleAnsatz, ψ::InfiniteQP, [left_environments], - [right_environments]; num=1, solver=Defaults.solver) -> (energies, states) + [right_environments]; num = 1) -> (energies, states) excitations(H, algorithm::FiniteExcited, ψs::NTuple{<:Any, <:FiniteMPS}; - num=1, init=copy(first(ψs))) -> (energies, states) + num = 1, init) -> (energies, states) excitations(H, algorithm::ChepigaAnsatz, ψ::FiniteMPS, [envs]; - num=1, pos=length(ψ)÷2) -> (energies, states) + num = 1, pos = length(ψ) ÷ 2) -> (energies, states) excitations(H, algorithm::ChepigaAnsatz2, ψ::FiniteMPS, [envs]; - num=1, pos=length(ψ)÷2) -> (energies, states) + num = 1, pos = length(ψ) ÷ 2) -> (energies, states) Compute the first excited states and their energy gap above a ground state. @@ -20,10 +20,10 @@ Compute the first excited states and their energy gap above a ground state. - `[left_environments]`: left ground state environment - `[right_environments]`: right ground state environment -# Keywords +# Keyword Arguments - `num::Int`: number of excited states to compute - `solver`: algorithm for the linear solver of the quasiparticle environments -- `init`: initial excited state guess +- `init`: initial excited state guess; defaults to a copy of the first state in `ψs` - `pos`: position of perturbation """ function excitations end diff --git a/src/algorithms/excitation/quasiparticleexcitation.jl b/src/algorithms/excitation/quasiparticleexcitation.jl index dab59fecd..0a44c4e8f 100644 --- a/src/algorithms/excitation/quasiparticleexcitation.jl +++ b/src/algorithms/excitation/quasiparticleexcitation.jl @@ -7,22 +7,26 @@ $(TYPEDEF) Optimization algorithm for quasi-particle excitations on top of MPS groundstates. -## Fields +# Constructors -$(TYPEDFIELDS) - -## Constructors - QuasiparticleAnsatz() QuasiparticleAnsatz(; kwargs...) QuasiparticleAnsatz(alg) -Create a `QuasiparticleAnsatz` algorithm with the given algorithm, or by passing the -keyword arguments to `Arnoldi`. +Create a `QuasiparticleAnsatz` algorithm with the given eigensolver, or by passing the +keyword arguments to [`Arnoldi`](@extref KrylovKit.Arnoldi). + +# Fields + +$(TYPEDFIELDS) + +# See also -## References +Used as the `algorithm` argument of [`excitations`](@ref). -- [Haegeman et al. Phys. Rev. Let. 111 (2013)](@cite haegeman2013) +# References + +* [Haegeman et al. Phys. Rev. Let. 111 (2013)](@cite haegeman2013) """ struct QuasiparticleAnsatz{A, E} <: Algorithm "algorithm used for the eigenvalue solvers" @@ -69,7 +73,7 @@ end excitations(H, algorithm::QuasiparticleAnsatz, momentum::Union{Number, Vector{<:Number}}, left_ψ::InfiniteMPS, [left_environment], [right_ψ::InfiniteMPS], [right_environment]; - kwargs...) + kwargs...) -> (energies, states) Create and optimize infinite quasiparticle states. @@ -82,11 +86,11 @@ Create and optimize infinite quasiparticle states. - `[right_ψ::InfiniteMPS]`: right ground state - `[right_environment]`: right ground state environment -# Keywords +# Keyword Arguments - `num::Int`: number of excited states to compute - `solver`: algorithm for the linear solver of the quasiparticle environments -- `sector=leftunit(left_ψ)`: charge of the quasiparticle state -- `parallel=true`: enable multi-threading over different momenta +- `sector = leftunit(left_ψ)`: charge of the quasiparticle state +- `parallel = true`: enable multi-threading over different momenta """ function excitations( H, alg::QuasiparticleAnsatz, momentum::Number, lmps::InfiniteMPS, @@ -160,7 +164,7 @@ end """ excitations(H, algorithm::QuasiparticleAnsatz, left_ψ::FiniteMPS, [left_environment], - [right_ψ::FiniteMPS], [right_environment]; kwargs...) + [right_ψ::FiniteMPS], [right_environment]; kwargs...) -> (energies, states) Create and optimize finite quasiparticle states. @@ -172,9 +176,9 @@ Create and optimize finite quasiparticle states. - `[right_ψ::FiniteMPS]`: right ground state - `[right_environment]`: right ground state environment -# Keywords +# Keyword Arguments - `num::Int`: number of excited states to compute -- `sector=leftunit(lmps)`: charge of the quasiparticle state +- `sector = leftunit(lmps)`: charge of the quasiparticle state """ function excitations( H, alg::QuasiparticleAnsatz, lmps::FiniteMPS, @@ -214,7 +218,7 @@ function excitations( num = 1 ) E = effective_excitation_renormalization_energy(H, ϕ₀, lenvs, renvs) - H_eff = EffectiveExcitationHamiltonian(H_eff, lenvs, renvs, E) + H_eff = EffectiveExcitationHamiltonian(H, lenvs, renvs, E) Es, ϕs, convhist = eigsolve(ϕ₀, num, :LM, alg.alg) do ϕ return H_eff(ϕ, alg.alg_environments) diff --git a/src/algorithms/expval.jl b/src/algorithms/expval.jl index d60213af2..f72ed173d 100644 --- a/src/algorithms/expval.jl +++ b/src/algorithms/expval.jl @@ -1,9 +1,9 @@ """ - expectation_value(ψ, O, [environments]) - expectation_value(ψ, inds => O) - expectation_value(ψ, (mpo, site => O), [environments]) + expectation_value(ψ, O, [environments]) -> val + expectation_value(ψ, inds => O) -> val + expectation_value(ψ, (mpo, site => O), [environments]) -> val -Compute the expectation value of an operator `O` on a state `ψ`. +Compute the expectation value of an operator `O` on a state `ψ`, normalized by `⟨ψ|ψ⟩`. Optionally, it is possible to make the computations more efficient by also passing in previously calculated `environments`. @@ -15,12 +15,21 @@ acts, while the operator is either a `AbstractTensorMap` or a `FiniteMPO`. In th the operator is a `AbstractTensorMap` that acts on the physical space of a single site. # Arguments -* `ψ::AbstractMPS` : the state on which to compute the expectation value -* `O::Union{AbstractMPO,Pair,AbstractTensorMap}` : the operator to compute the expectation value of. +- `ψ::AbstractMPS`: the state on which to compute the expectation value +- `O::Union{AbstractMPO, Pair, AbstractTensorMap}`: the operator to compute the expectation value of. This can either be an `AbstractMPO`, a pair of indices and local operator, or a local MPO tensor represented as a `AbstractTensorMap`. -* `environments::AbstractMPSEnvironments` : the environments to use for the calculation. If not given, they will be calculated. +- `environments::AbstractMPSEnvironments`: the environments to use for the calculation. If not given, they will be calculated. Depending on the type of `O`, these will be the environments of the operator `O` or the MPO `mpo`. + +# Returns +- `val::Number`: the (normalized) expectation value `⟨ψ|O|ψ⟩ / ⟨ψ|ψ⟩`. + +!!! note "Infinite operators" + For an infinite state and an infinite operator (e.g. an `InfiniteMPOHamiltonian`), the + return value is the total over one unit cell; divide by `length(ψ)` to obtain a + per-site value. + # Examples ```jldoctest julia> ψ = FiniteMPS(ones(Float64, (ℂ^2)^4)); diff --git a/src/algorithms/groundstate/dmrg.jl b/src/algorithms/groundstate/dmrg.jl index 61f3097a0..9d8a85c22 100644 --- a/src/algorithms/groundstate/dmrg.jl +++ b/src/algorithms/groundstate/dmrg.jl @@ -21,9 +21,13 @@ is `notrunc()` the gauge is a QR decomposition (`alg_orth`, [`Householder`](@ext MatrixAlgebraKit.Householder) by default), otherwise it is a truncated SVD (`alg_svd` with the given `trscheme`). -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`find_groundstate`](@ref) and [`approximate`](@ref). """ struct DMRG{A, F, E, G} <: Algorithm "tolerance for convergence criterium" @@ -64,6 +68,24 @@ function DMRG(; return DMRG(tol, maxiter, verbosity, alg_eigsolve′, finalize, alg_expand, alg_gauge) end +""" + find_groundstate!(ψ, H, algorithm, [environments]) -> (ψ, environments, ϵ) + +In-place version of [`find_groundstate`](@ref): optimize the finite MPS `ψ` for the +Hamiltonian `H`, overwriting the input state instead of working on a copy. +Currently supported for the finite-system algorithms [`DMRG`](@ref) and [`DMRG2`](@ref). + +# Arguments +- `ψ::AbstractFiniteMPS`: initial guess, mutated in place +- `H`: operator for which to find the ground state +- `algorithm`: optimization algorithm +- `[environments]`: MPS environment manager + +# Returns +- `ψ::AbstractFiniteMPS`: converged ground state +- `environments`: environments corresponding to the converged state +- `ϵ::Float64`: final convergence error upon terminating the algorithm +""" function find_groundstate!(ψ::AbstractFiniteMPS, H, alg::DMRG, envs = environments(ψ, H, ψ)) ϵs = map(pos -> calc_galerkin(pos, ψ, H, ψ, envs), 1:length(ψ)) ϵ = maximum(ϵs) @@ -146,9 +168,13 @@ $(TYPEDEF) Two-site DMRG algorithm for finding the dominant eigenvector. -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`find_groundstate`](@ref) and [`approximate`](@ref). """ struct DMRG2{A, S, F} <: Algorithm "tolerance for convergence criterium" diff --git a/src/algorithms/groundstate/find_groundstate.jl b/src/algorithms/groundstate/find_groundstate.jl index 28ebc182d..9d5b203e5 100644 --- a/src/algorithms/groundstate/find_groundstate.jl +++ b/src/algorithms/groundstate/find_groundstate.jl @@ -1,25 +1,62 @@ """ find_groundstate(ψ₀, H, [environments]; kwargs...) -> (ψ, environments, ϵ) - find_groundstate(ψ₀, H, algorithm, environments) -> (ψ, environments, ϵ) + find_groundstate(ψ₀, H, algorithm, [environments]) -> (ψ, environments, ϵ) -Compute the ground state for Hamiltonian `H` with initial guess `ψ`. If not specified, an -optimization algorithm will be attempted based on the supplied keywords. +Compute the ground state for Hamiltonian `H` with initial guess `ψ₀`. If no `algorithm` is +specified, one is selected automatically from the type of `ψ₀` and the supplied keywords +(see the automatic-selection notes below). -## Arguments +# Arguments - `ψ₀::AbstractMPS`: initial guess - `H::AbstractMPO`: operator for which to find the ground state - `[environments]`: MPS environment manager - `algorithm`: optimization algorithm -## Keywords -- `tol::Float64`: tolerance for convergence criterium -- `maxiter::Int`: maximum amount of iterations -- `verbosity::Int`: display progress information +# Keyword Arguments +- `tol::Float64=$(Defaults.tol)`: tolerance for the convergence criterion +- `maxiter::Int=$(Defaults.maxiter)`: maximum number of iterations +- `verbosity::Int=$(Defaults.verbosity)`: display progress information +- `trscheme=nothing`: if supplied, a truncation scheme that enables bond-dimension growth + through a two-site algorithm (see below) -## Returns +# Automatic algorithm selection +When no `algorithm` is passed, the choice depends on the type of `ψ₀`: +- `InfiniteMPS`: [`VUMPS`](@ref) (with its tolerance floored at `1e-4`), refined by + [`GradientGrassmann`](@ref) when `tol < 1e-4`. If `trscheme` is given, an [`IDMRG2`](@ref) + stage is prepended to grow the bond dimension. +- `AbstractFiniteMPS`: [`DMRG`](@ref). If `trscheme` is given, a [`DMRG2`](@ref) stage is + prepended to grow the bond dimension. + +Because single-site [`DMRG`](@ref) preserves the bond dimension of `ψ₀`, passing a +`trscheme` (or an explicit two-site `algorithm`) is the usual way to converge from a +low-bond-dimension initial guess such as a product state. + +# Returns - `ψ::AbstractMPS`: converged ground state - `environments`: environments corresponding to the converged state - `ϵ::Float64`: final convergence error upon terminating the algorithm + +# Examples +Ground state of a 4-site transverse-field Ising chain, `H = -∑ XₖXₖ₊₁ - ∑ Zₖ`, starting +from a product state and letting `DMRG2` grow the bond dimension: + +```jldoctest +julia> X = TensorMap(Float64[0 1; 1 0], ℂ^2, ℂ^2); + +julia> Z = TensorMap(Float64[1 0; 0 -1], ℂ^2, ℂ^2); + +julia> L = 4; lattice = fill(ℂ^2, L); + +julia> H = FiniteMPOHamiltonian(lattice, ((i, i + 1) => -(X ⊗ X) for i in 1:(L - 1))) + + FiniteMPOHamiltonian(lattice, ((i,) => -Z for i in 1:L)); + +julia> ψ₀ = FiniteMPS(ones(Float64, (ℂ^2)^L)); + +julia> ψ, envs, ϵ = find_groundstate(ψ₀, H; verbosity = 0, trscheme = truncrank(16)); + +julia> round(real(expectation_value(ψ, H)); digits = 4) +-4.7588 +``` """ function find_groundstate( ψ::AbstractMPS, H, envs::AbstractMPSEnvironments = environments(ψ, H, ψ); diff --git a/src/algorithms/groundstate/gradient_grassmann.jl b/src/algorithms/groundstate/gradient_grassmann.jl index 049cfaae5..b62113159 100644 --- a/src/algorithms/groundstate/gradient_grassmann.jl +++ b/src/algorithms/groundstate/gradient_grassmann.jl @@ -2,29 +2,31 @@ $(TYPEDEF) Variational gradient-based optimization algorithm that keeps the MPS in left-canonical form, -as points on a Grassmann manifold. The optimization is then a Riemannian gradient descent +as points on a Grassmann manifold. The optimization is then a Riemannian gradient descent with a preconditioner to induce the metric from the Hilbert space inner product. -## Fields +# Constructors + GradientGrassmann(; kwargs...) -$(TYPEDFIELDS) +# Keyword Arguments +- `method = ConjugateGradient`: instance of optimization algorithm, or type of optimization + algorithm to construct +- `finalize!`: finalizer algorithm +- `tol = Defaults.tol`: tolerance for convergence criterium +- `maxiter = Defaults.maxiter`: maximum amount of iterations +- `verbosity = Defaults.verbosity - 1`: level of information display -## References +# Fields -* [Hauru et al. SciPost Phys. 10 (2021)](@cite hauru2021) +$(TYPEDFIELDS) ---- +# See also -## Constructors - GradientGrassmann(; kwargs...) +Used as the `algorithm` argument of [`find_groundstate`](@ref) and [`leading_boundary`](@ref). -### Keywords -- `method=ConjugateGradient`: instance of optimization algorithm, or type of optimization - algorithm to construct -- `finalize!`: finalizer algorithm -- `tol::Float64`: tolerance for convergence criterium -- `maxiter::Int`: maximum amount of iterations -- `verbosity::Int`: level of information display +# References + +* [Hauru et al. SciPost Phys. 10 (2021)](@cite hauru2021) """ struct GradientGrassmann{O <: OptimKit.OptimizationAlgorithm, F} <: Algorithm "optimization algorithm" diff --git a/src/algorithms/groundstate/idmrg.jl b/src/algorithms/groundstate/idmrg.jl index ed83e664a..f7775ad26 100644 --- a/src/algorithms/groundstate/idmrg.jl +++ b/src/algorithms/groundstate/idmrg.jl @@ -3,9 +3,13 @@ $(TYPEDEF) Single site infinite DMRG algorithm for finding the dominant eigenvector. -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`find_groundstate`](@ref), [`leading_boundary`](@ref), and [`approximate`](@ref). """ @kwdef struct IDMRG{A} <: Algorithm "tolerance for convergence criterium" @@ -29,9 +33,13 @@ $(TYPEDEF) Two-site infinite DMRG algorithm for finding the dominant eigenvector. -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`find_groundstate`](@ref), [`leading_boundary`](@ref), and [`approximate`](@ref). """ @kwdef struct IDMRG2{A, S} <: Algorithm "tolerance for convergence criterium" diff --git a/src/algorithms/groundstate/vumps.jl b/src/algorithms/groundstate/vumps.jl index aa1c413cb..0183e94e0 100644 --- a/src/algorithms/groundstate/vumps.jl +++ b/src/algorithms/groundstate/vumps.jl @@ -3,11 +3,15 @@ $(TYPEDEF) Variational optimization algorithm for uniform matrix product states, based on the combination of DMRG with matrix product state tangent space concepts. -## Fields +# Fields $(TYPEDFIELDS) -## References +# See also + +Used as the `algorithm` argument of [`find_groundstate`](@ref) and [`leading_boundary`](@ref). + +# References * [Zauner-Stauber et al. Phys. Rev. B 97 (2018)](@cite zauner-stauber2018) * [Vanderstraeten et al. SciPost Phys. Lect. Notes 7 (2019)](@cite vanderstraeten2019) diff --git a/src/algorithms/propagator/corvector.jl b/src/algorithms/propagator/corvector.jl index 93bfd6637..c8d9c966b 100644 --- a/src/algorithms/propagator/corvector.jl +++ b/src/algorithms/propagator/corvector.jl @@ -11,11 +11,15 @@ $(TYPEDEF) A dynamical DMRG method for calculating dynamical properties and excited states, based on a variational principle for dynamical correlation functions. -## Fields +# Fields $(TYPEDFIELDS) -## References +# See also + +Used as the `algorithm` argument of [`propagator`](@ref). + +# References * [Jeckelmann. Phys. Rev. B 66 (2002)](@cite jeckelmann2002) """ @@ -33,13 +37,14 @@ $(TYPEDFIELDS) end """ - propagator(ψ₀::AbstractFiniteMPS, z::Number, H::MPOHamiltonian, alg::DynamicalDMRG; init=copy(ψ₀)) + propagator(ψ₀::AbstractFiniteMPS, z::Number, H::MPOHamiltonian, alg::DynamicalDMRG; init = copy(ψ₀)) -> (g, ψ) Calculate the action of the propagator ``\\frac{1}{z - H}|ψ₀⟩`` using the dynamical DMRG algorithm. -Returns a tuple `(g, ψ)` where `g` is the approximation of the propagator matrix element -``⟨ψ₀|\\frac{1}{z - H}|ψ₀⟩`` and `ψ` is the MPS approximation of ``\\frac{1}{z - H}|ψ₀⟩``. +# Returns +- `g`: approximation of the propagator matrix element ``⟨ψ₀|\\frac{1}{z - H}|ψ₀⟩`` +- `ψ`: MPS approximation of ``\\frac{1}{z - H}|ψ₀⟩`` """ function propagator end @@ -55,7 +60,9 @@ This algorithm minimizes the following cost function Returns the approximation of ``⟨ψ₀|\\frac{1}{z - H}|ψ₀⟩`` and ``\\frac{1}{z - H}|ψ₀⟩``. -See also [`Jeckelmann`](@ref) for the original approach. +# See also + +[`Jeckelmann`](@ref) for the original approach. """ struct NaiveInvert <: DDMRG_Flavour end @@ -121,9 +128,11 @@ Together with equation (11) from that same paper we can determine the full propa Returns the approximation of ``⟨ψ₀|\\frac{1}{z - H}|ψ₀⟩`` and ``\\frac{1}{z - H}|ψ₀⟩``. -See also [`NaiveInvert`](@ref) for a less costly but less accurate alternative. +# See also + +[`NaiveInvert`](@ref) for a less costly but less accurate alternative. -## References +# References * [Jeckelmann. Phys. Rev. B 66 (2002)](@cite jeckelmann2002) """ diff --git a/src/algorithms/statmech/leading_boundary.jl b/src/algorithms/statmech/leading_boundary.jl index d66088eea..b0ed228d6 100644 --- a/src/algorithms/statmech/leading_boundary.jl +++ b/src/algorithms/statmech/leading_boundary.jl @@ -5,18 +5,18 @@ Compute the leading boundary MPS for operator `O` with initial guess `ψ`. If not specified, an optimization algorithm will be attempted based on the supplied keywords. -## Arguments +# Arguments - `ψ₀::AbstractMPS`: initial guess - `O::AbstractMPO`: operator for which to find the leading_boundary - `[environments]`: MPS environment manager - `algorithm`: optimization algorithm -## Keywords +# Keyword Arguments - `tol::Float64`: tolerance for convergence criterium - `maxiter::Int`: maximum amount of iterations - `verbosity::Int`: display progress information -## Returns +# Returns - `ψ::AbstractMPS`: converged leading boundary MPS - `environments`: environments corresponding to the converged boundary - `ϵ::Float64`: final convergence error upon terminating the algorithm diff --git a/src/algorithms/statmech/vomps.jl b/src/algorithms/statmech/vomps.jl index 9295f2b28..33c94d6c1 100644 --- a/src/algorithms/statmech/vomps.jl +++ b/src/algorithms/statmech/vomps.jl @@ -1,15 +1,19 @@ """ $(TYPEDEF) - + Power method algorithm for finding dominant eigenvectors of infinite MPOs. This method works by iteratively approximating the product of an operator and a state with a new state of the same bond dimension. -## Fields +# Fields $(TYPEDFIELDS) -## References +# See also + +Used as the `algorithm` argument of [`leading_boundary`](@ref) and [`approximate`](@ref). + +# References * [Vanhecke et al. SciPost Phys. Core 4 (2021)](@cite vanhecke2021) """ diff --git a/src/algorithms/timestep/integrators.jl b/src/algorithms/timestep/integrators.jl index 7b2983c34..8cee4bd96 100644 --- a/src/algorithms/timestep/integrators.jl +++ b/src/algorithms/timestep/integrators.jl @@ -1,7 +1,7 @@ """ - integrate(f, y₀, t, dt, alg) + integrate(f, y₀, t, dt, alg) -> y -Integrate the differential equation ``i dy/dt = f(y, t)`` over a time step 'dt' starting from +Integrate the differential equation ``i dy/dt = f(y, t)`` over a time step `dt` starting from ``y(t₀)=y₀``, using the provided algorithm. # Arguments diff --git a/src/algorithms/timestep/taylorcluster.jl b/src/algorithms/timestep/taylorcluster.jl index 9e5943288..b129e9d0c 100644 --- a/src/algorithms/timestep/taylorcluster.jl +++ b/src/algorithms/timestep/taylorcluster.jl @@ -3,11 +3,15 @@ $(TYPEDEF) Algorithm for constructing the `N`th order time evolution MPO using the Taylor cluster expansion. -## Fields +# Fields $(TYPEDFIELDS) -## References +# See also + +Used as the `algorithm` argument of [`make_time_mpo`](@ref). + +# References * [Van Damme et al. SciPost Phys. 17 (2024)](@cite vandamme2024) """ @@ -21,7 +25,7 @@ $(TYPEDFIELDS) end """ - const WI = TaylorCluster(; N=1, extension=false, compression=false) + const WI = TaylorCluster(; N = 1, extension = false, compression = false) First order Taylor expansion for a time-evolution MPO. """ diff --git a/src/algorithms/timestep/tdvp.jl b/src/algorithms/timestep/tdvp.jl index 993782e54..8cec04a05 100644 --- a/src/algorithms/timestep/tdvp.jl +++ b/src/algorithms/timestep/tdvp.jl @@ -4,7 +4,7 @@ $(TYPEDEF) Single site MPS time-evolution algorithm based on the Time-Dependent Variational Principle. For finite MPS, setting `alg_expand` to a bond-expansion algorithm (e.g. [`OptimalExpand`](@ref), -[`SketchedExpand`](@ref)) enriches the bond with directions orthogonal to the current state +[`SketchedExpand`](@ref)) expands the bond with directions orthogonal to the current state ahead of each local integration, recovering Controlled Bond Expansion (CBE) TDVP and lifting the fixed-bond limitation of plain single-site TDVP. A truncating `trscheme` is then required to cut the enlarged bond back down (selecting the truncated-SVD gauge). The expansion is @@ -16,11 +16,15 @@ state-preserving, as required for a consistent time evolution. evolution instead renormalizes at every step, like a ground-state search. CBE is only available for finite MPS. -## Fields +# Fields $(TYPEDFIELDS) -## References +# See also + +Used as the `algorithm` argument of [`timestep`](@ref), [`timestep!`](@ref) and [`time_evolve`](@ref). + +# References * [Haegeman et al. Phys. Rev. Lett. 107 (2011)](@cite haegeman2011) """ @@ -190,11 +194,15 @@ $(TYPEDEF) Two-site MPS time-evolution algorithm based on the Time-Dependent Variational Principle. -## Fields +# Fields $(TYPEDFIELDS) -## References +# See also + +Used as the `algorithm` argument of [`timestep`](@ref), [`timestep!`](@ref) and [`time_evolve`](@ref). + +# References * [Haegeman et al. Phys. Rev. Lett. 107 (2011)](@cite haegeman2011) """ diff --git a/src/algorithms/timestep/time_evolve.jl b/src/algorithms/timestep/time_evolve.jl index 51288abd7..319788689 100644 --- a/src/algorithms/timestep/time_evolve.jl +++ b/src/algorithms/timestep/time_evolve.jl @@ -1,25 +1,30 @@ """ - time_evolve(ψ₀, H, t_span, [alg], [envs]; kwargs...) -> (ψ, envs) - time_evolve!(ψ₀, H, t_span, [alg], [envs]; kwargs...) -> (ψ₀, envs) + time_evolve(ψ₀, H, t_span, alg, [envs]; kwargs...) -> (ψ, envs) + time_evolve!(ψ₀, H, t_span, alg, [envs]; kwargs...) -> (ψ₀, envs) Time-evolve the initial state `ψ₀` with Hamiltonian `H` over a given time span by stepping through each of the time points obtained by iterating t_span. -## Arguments +# Arguments - `ψ₀::AbstractMPS`: initial state - `H::AbstractMPO`: operator that generates the time evolution (can be time-dependent). - `t_span::AbstractVector{<:Number}`: time points over which the time evolution is stepped -- `[alg]`: algorithm to use for the time evolution. Defaults to [`TDVP`](@ref). -- `[envs]`: MPS environment manager +- `alg`: algorithm to use for the time evolution, e.g. [`TDVP`](@ref) or [`TDVP2`](@ref). +- `envs`: MPS environment manager -## Keyword Arguments +# Keyword Arguments -- `verbosity::Int=0`: verbosity level for logging -- `imaginary_evolution::Bool=false`: if true, the time evolution is done with an imaginary time step +- `verbosity::Int = 0`: verbosity level for logging +- `imaginary_evolution::Bool = false`: if true, the time evolution is done with an imaginary time step instead, (i.e. ``\\exp(-Hdt)`` instead of ``\\exp(-iHdt)``). This can be useful for using this function to compute the ground state of a Hamiltonian, or to compute finite-temperature properties of a system. + +# Returns + +- `ψ`: the time-evolved state +- `envs`: the updated environment manager """ function time_evolve end, function time_evolve! end @@ -48,27 +53,53 @@ for (timestep, time_evolve) in zip((:timestep, :timestep!), (:time_evolve, :time end """ - timestep(ψ₀, H, t, dt, [alg], [envs]; kwargs...) -> (ψ, envs) - timestep!(ψ₀, H, t, dt, [alg], [envs]; kwargs...) -> (ψ₀, envs) + timestep(ψ₀, H, t, dt, alg, [envs]; kwargs...) -> (ψ, envs) + timestep!(ψ₀, H, t, dt, alg, [envs]; kwargs...) -> (ψ₀, envs) Time-step the state `ψ₀` with Hamiltonian `H` over a given time step `dt` at time `t`, solving the Schroedinger equation: ``i ∂ψ/∂t = H ψ``. -## Arguments +# Arguments - `ψ₀::AbstractMPS`: initial state - `H::AbstractMPO`: operator that generates the time evolution (can be time-dependent). - `t::Number`: starting time of time-step - `dt::Number`: time-step magnitude -- `[alg]`: algorithm to use for the time evolution. Defaults to [`TDVP`](@ref). -- `[envs]`: MPS environment manager +- `alg`: algorithm to use for the time evolution, e.g. [`TDVP`](@ref) or [`TDVP2`](@ref). +- `envs`: MPS environment manager -## Keyword Arguments +# Keyword Arguments -- `imaginary_evolution::Bool=false`: if true, the time evolution is done with an imaginary time step +- `imaginary_evolution::Bool = false`: if true, the time evolution is done with an imaginary time step instead, (i.e. ``\\exp(-Hdt)`` instead of ``\\exp(-iHdt)``). This can be useful for using this function to compute the ground state of a Hamiltonian, or to compute finite-temperature properties of a system. + +# Returns + +- `ψ`: the time-stepped state +- `envs`: the updated environment manager + +# Examples +Real-time evolution of the `|+···+⟩` product state under a transverse field `H = ∑ Zₖ`. +Each spin precesses independently, so `⟨Xₖ(t)⟩ = cos(2t)`; after a step `dt = 0.1` this is +`cos(0.2) ≈ 0.980067`. The initial state must be complex, since real-time evolution +multiplies by `-i`: + +```jldoctest +julia> X = TensorMap(ComplexF64[0 1; 1 0], ℂ^2, ℂ^2); + +julia> Z = TensorMap(ComplexF64[1 0; 0 -1], ℂ^2, ℂ^2); + +julia> ψ₀ = FiniteMPS(ones(ComplexF64, (ℂ^2)^4)); + +julia> H = FiniteMPOHamiltonian(fill(ℂ^2, 4), ((i,) => Z for i in 1:4)); + +julia> ψ, envs = timestep(ψ₀, H, 0.0, 0.1, TDVP()); + +julia> round(real(expectation_value(ψ, 2 => X)); digits = 6) +0.980067 +``` """ function timestep end, function timestep! end @@ -77,9 +108,9 @@ function timestep end, function timestep! end Construct an `MPO` that approximates ``\\exp(-iHdt)``. -## Keyword Arguments +# Keyword Arguments -- `imaginary_evolution::Bool=false`: if true, the time evolution operator is constructed +- `imaginary_evolution::Bool = false`: if true, the time evolution operator is constructed with an imaginary time step instead, (i.e. ``\\exp(-Hdt)`` instead of ``\\exp(-iHdt)``). This can be useful for using this function to compute the ground state of a Hamiltonian, or to compute finite-temperature properties of a system. diff --git a/src/algorithms/timestep/wii.jl b/src/algorithms/timestep/wii.jl index 5c39500fd..e41405fd2 100644 --- a/src/algorithms/timestep/wii.jl +++ b/src/algorithms/timestep/wii.jl @@ -3,11 +3,15 @@ $(TYPEDEF) Generalization of the Euler approximation of the operator exponential for MPOs. -## Fields +# Fields $(TYPEDFIELDS) -## References +# See also + +Used as the `algorithm` argument of [`make_time_mpo`](@ref). + +# References * [Zaletel et al. Phys. Rev. B 91 (2015)](@cite zaletel2015) * [Paeckel et al. Ann. of Phys. 411 (2019)](@cite paeckel2019) diff --git a/src/algorithms/unionalg.jl b/src/algorithms/unionalg.jl index 3e37f67f4..bdbdd121c 100644 --- a/src/algorithms/unionalg.jl +++ b/src/algorithms/unionalg.jl @@ -1,11 +1,16 @@ """ $(TYPEDEF) -Algorithm wrapper representing the sequential application of two algorithms. +Algorithm wrapper representing the sequential application of two algorithms, as produced by +`alg1 & alg2`. -## Fields +# Fields $(TYPEDFIELDS) + +# See also + +Used as the `algorithm` argument of [`find_groundstate`](@ref) and [`changebonds`](@ref). """ struct UnionAlg{A, B} <: Algorithm "first algorithm" diff --git a/src/environments/finite_envs.jl b/src/environments/finite_envs.jl index cb11cd53f..6605ccc74 100644 --- a/src/environments/finite_envs.jl +++ b/src/environments/finite_envs.jl @@ -97,6 +97,16 @@ function poison!(ca::FiniteEnvironments, ind) end #rightenv[ind] will be contracteable with the tensor on site [ind] +""" + rightenv(envs, site, state) + +Return the right environment stored in `envs` at `site` for the given `state`: the contraction +of everything to the right of `site` in the network the environments were built for. +The result is gauge-compatible with the tensor of `state` at `site` and can be contracted onto +it directly. + +See also [`leftenv`](@ref) and [`environments`](@ref). +""" function rightenv(ca::FiniteEnvironments, ind, state) a = findfirst(i -> !(state.AR[i] === ca.rdependencies[i]), length(state):-1:(ind + 1)) a = isnothing(a) ? nothing : length(state) - a + 1 @@ -114,6 +124,16 @@ function rightenv(ca::FiniteEnvironments, ind, state) return ca.GRs[ind + 1] end +""" + leftenv(envs, site, state) + +Return the left environment stored in `envs` at `site` for the given `state`: the contraction +of everything to the left of `site` in the network the environments were built for. +The result is gauge-compatible with the tensor of `state` at `site` and can be contracted onto +it directly. + +See also [`rightenv`](@ref) and [`environments`](@ref). +""" function leftenv(ca::FiniteEnvironments, ind, state) a = findfirst(i -> !(state.AL[i] === ca.ldependencies[i]), 1:(ind - 1)) diff --git a/src/operators/jordanmpotensor.jl b/src/operators/jordanmpotensor.jl index acea13fe3..773db97ba 100644 --- a/src/operators/jordanmpotensor.jl +++ b/src/operators/jordanmpotensor.jl @@ -1,5 +1,5 @@ """ - JordanMPOTensor{T,S,A} <: AbstractBlockTensorMap{T,S,2,2} +$(TYPEDEF) A single tensor of a matrix product operator (MPO) in upper triangular (Jordan) block form, as used to represent the local tensors of an [`MPOHamiltonian`](@ref). @@ -16,28 +16,27 @@ The virtual (row, column) structure is where `A` is the bulk of interacting operators, `C`/`B` are the operators that start/finish an interaction, `D` is the on-site term, and the diagonal `1`s are identities. -## Representation +# Type parameters +- `T <: Number`: the `scalartype` of the tensors. +- `S`: the `spacetype` of the tensors. +- `A <: DenseVector{T}`: the storage type of the underlying tensors. -Rather than storing the dense block matrix, the genuine operators and the identities are kept separately: +# Properties +The reduced-leg `A`, `B`, `C` and `D` blocks are exposed as properties (`W.A`, `W.B`, `W.C`, +`W.D`), reconstructed on demand from the stored `tensors` and `scalars`. + +# Notes +Rather than storing the dense block matrix, the genuine operators and the identities are kept +separately: - `tensors::SparseBlockTensorMap` holds the non-identity operators over the *full* virtual space (so `A`, `B`, `C` and `D` all live at their `(row, 1, 1, col)` position). -- `scalars::Dict{CartesianIndex{4},T}` holds the scalar multiples of the identity, keyed by +- `scalars::Dict{CartesianIndex{4}, T}` holds the scalar multiples of the identity, keyed by their `(row, 1, 1, col)` virtual position; the diagonal corner `1`s are stored here as well. An index is never present in both `tensors` and `scalars`. -This keeps the ubiquitous identity blocks free of dense storage and lets identities be materialized lazily only when needed. - -## Type parameters - -- `T <: Number`: the `scalartype` of the tensors. -- `S`: the `spacetype` of the tensors. -- `A <: DenseVector{T}`: the storage type of the underlying tensors. - -## Block accessors - -The reduced-leg `A`, `B`, `C` and `D` blocks are exposed as properties (`W.A`, `W.B`, `W.C`, `W.D`), -reconstructed on demand from `tensors` and `scalars`. +This keeps the ubiquitous identity blocks free of dense storage and lets identities be +materialized lazily only when needed. """ struct JordanMPOTensor{ T <: Number, S, A <: DenseVector{T}, diff --git a/src/operators/lazysum.jl b/src/operators/lazysum.jl index 49af40ba4..b2c415ccb 100644 --- a/src/operators/lazysum.jl +++ b/src/operators/lazysum.jl @@ -1,19 +1,19 @@ """ - LazySum{O} <: AbstractVector{O} +$(TYPEDEF) -Type that represents a lazy sum i.e explicit summation is only done when needed. -This type is basically an `AbstractVector` with some extra functionality to calculate things efficiently. +Type that represents a lazy sum, i.e. explicit summation is only done when needed. +This type is basically an `AbstractVector` with some extra functionality to calculate things +efficiently. -## Fields -- ops -- Vector of summable objects - ---- - -## Constructors +# Constructors LazySum(x::Vector) + LazySum(ops::AbstractVector, fs::AbstractVector) +# Fields +$(TYPEDFIELDS) """ struct LazySum{O} <: AbstractVector{O} + "vector of summable objects" ops::Vector{O} end diff --git a/src/operators/mpohamiltonian.jl b/src/operators/mpohamiltonian.jl index cc7e42b15..e2eae8c6a 100644 --- a/src/operators/mpohamiltonian.jl +++ b/src/operators/mpohamiltonian.jl @@ -1,10 +1,9 @@ """ - MPOHamiltonian(lattice::AbstractArray{<:VectorSpace}, local_operators...) - MPOHamiltonian(lattice::AbstractArray{<:VectorSpace}) - MPOHamiltonian(x::AbstractArray{<:Any,3}) +$(TYPEDEF) -MPO representation of a Hamiltonian. This is a specific form of an [`AbstractMPO`](@ref), where -all the sites are represented by an upper triangular block matrix of the following form: +MPO representation of a Hamiltonian. +This is a specific form of an [`AbstractMPO`](@ref), where all the sites are represented by an +upper triangular block matrix of the following form: ```math \\begin{pmatrix} @@ -16,17 +15,44 @@ all the sites are represented by an upper triangular block matrix of the followi where `A`, `B`, `C`, and `D` are `MPOTensor`s, or (sparse) blocks thereof. -## Examples +# Constructors -For example, constructing a nearest-neighbour Hamiltonian would look like this: +The finite and infinite variants, [`FiniteMPOHamiltonian`](@ref) and +[`InfiniteMPOHamiltonian`](@ref), are constructed from a lattice of physical spaces together +with a set of `inds => operator` pairs describing the local terms: -```julia -lattice = fill(ℂ^2, 10) -H = MPOHamiltonian(lattice, (i, i+1) => O for i in 1:length(lattice)-1) + FiniteMPOHamiltonian(lattice::AbstractArray{<:VectorSpace}, local_operators...) + InfiniteMPOHamiltonian(lattice::AbstractArray{<:VectorSpace}, local_operators...) + +# Properties + +- `A`: bulk block of interacting operators at each site +- `B`: operators that finish an interaction +- `C`: operators that start an interaction +- `D`: on-site terms + +# Examples +A nearest-neighbour term is a two-element index tuple `(i, i + 1) => O₁₂`; an on-site term +is a one-element tuple `(i,) => O`. For the finite variant the lattice lists every site; for +the infinite variant it is a single unit cell and indices wrap around it periodically. + +```jldoctest +julia> X = TensorMap(Float64[0 1; 1 0], ℂ^2, ℂ^2); + +julia> Hf = FiniteMPOHamiltonian(fill(ℂ^2, 3), ((i, i + 1) => X ⊗ X for i in 1:2)); + +julia> Hf isa FiniteMPOHamiltonian, length(Hf) +(true, 3) + +julia> Hi = InfiniteMPOHamiltonian(fill(ℂ^2, 1), (1, 2) => X ⊗ X, (1,) => X); + +julia> Hi isa InfiniteMPOHamiltonian, length(Hi) +(true, 1) ``` -See also [`instantiate_operator`](@ref), which is responsible for instantiating the local -operators in a form that is compatible with this constructor. +# See also +[`instantiate_operator`](@ref) is responsible for instantiating the local operators in a form +that is compatible with this constructor. """ struct MPOHamiltonian{TO <: JordanMPOTensor, V <: AbstractVector{TO}} <: AbstractMPO{TO} W::V @@ -62,8 +88,8 @@ end FiniteMPOHamiltonian(Ws::Vector{<:AbstractMatrix}) Create a `FiniteMPOHamiltonian` from a vector of matrices, such that `Ws[i][j, k]` represents -the operator at site `i`, left level `j` and right level `k`. Here, the entries can be -either `MPOTensor`, `Missing` or `Number`. +the operator at site `i`, left level `j` and right level `k`. +Here, the entries can be either `MPOTensor`, `Missing` or `Number`. """ function FiniteMPOHamiltonian(Ws::Vector{<:AbstractMatrix}) T = promote_type(_split_mpoham_types.(Ws)...) @@ -151,11 +177,11 @@ function FiniteMPOHamiltonian{O}(W_mats::Vector{<:AbstractMatrix}) where {O <: J end """ - InfiniteMPOHamiltonian(Ws::Vector{<:Matrix}) + InfiniteMPOHamiltonian(Ws::Vector{<:AbstractMatrix}) -Create a `InfiniteMPOHamiltonian` from a vector of matrices, such that `Ws[i][j, k]` represents -the the operator at site `i`, left level `j` and right level `k`. Here, the entries can be -either `MPOTensor`, `Missing` or `Number`. +Create an `InfiniteMPOHamiltonian` from a vector of matrices, such that `Ws[i][j, k]` +represents the operator at site `i`, left level `j` and right level `k`. +Here, the entries can be either `MPOTensor`, `Missing` or `Number`. """ function InfiniteMPOHamiltonian(Ws::Vector{<:AbstractMatrix}) T = promote_type(_split_mpoham_types.(Ws)...) diff --git a/src/operators/multilinempo.jl b/src/operators/multilinempo.jl index d9c09aa06..e23032b50 100644 --- a/src/operators/multilinempo.jl +++ b/src/operators/multilinempo.jl @@ -6,10 +6,11 @@ Type that represents multiple lines of `MPO` objects. # Constructors - MultilineMPO(mpos::AbstractVector{<:Union{SparseMPO,DenseMPO}}) + MultilineMPO(mpos::AbstractVector{<:Union{SparseMPO, DenseMPO}}) MultilineMPO(Os::AbstractMatrix{<:MPOTensor}) -See also: [`Multiline`](@ref), [`AbstractMPO`](@ref) +# See also +[`Multiline`](@ref), [`AbstractMPO`](@ref) """ const MultilineMPO = Multiline{<:AbstractMPO} diff --git a/src/operators/windowhamiltonian.jl b/src/operators/windowhamiltonian.jl index a0833aa56..8fadae00d 100644 --- a/src/operators/windowhamiltonian.jl +++ b/src/operators/windowhamiltonian.jl @@ -10,12 +10,7 @@ an infinite Hamiltonian to the right. Acts similar to just a finite Hamiltonian, but we "remember" the boundary Hamiltonians. -## Fields - -$(TYPEDFIELDS) - -## Constructors - +# Constructors WindowMPOHamiltonian(ham::InfiniteMPOHamiltonian, interval::UnitRange) Construct a `WindowMPOHamiltonian` by carving a finite `interval` out of an infinite @@ -23,6 +18,9 @@ Hamiltonian `ham`. The finite window consists of the sites in `interval`, while the left and right environments are copies of `ham` whose unit cells are circshifted so that they line up with the window boundaries. + +# Fields +$(TYPEDFIELDS) """ struct WindowMPOHamiltonian{O} <: AbstractMPO{O} "Hamiltonian acting on the infinite environment to the left of the window" @@ -42,6 +40,8 @@ function WindowMPOHamiltonian(ham::InfiniteMPOHamiltonian, interval::UnitRange) return WindowMPOHamiltonian(left_ham, finite_ham, right_ham) end +Base.isfinite(::Type{<:WindowMPOHamiltonian}) = true + Base.parent(h::WindowMPOHamiltonian) = h.finite_ham Base.copy(h::WindowMPOHamiltonian) = WindowMPOHamiltonian(copy(h.left_ham), copy(h.finite_ham), copy(h.right_ham)) Base.conj(h::WindowMPOHamiltonian) = WindowMPOHamiltonian(conj(h.left_ham), conj(h.finite_ham), conj(h.right_ham)) diff --git a/src/states/finitemps.jl b/src/states/finitemps.jl index 6f65dbbe5..62db94a67 100644 --- a/src/states/finitemps.jl +++ b/src/states/finitemps.jl @@ -1,61 +1,70 @@ """ - FiniteMPS{A<:GenericMPSTensor,B<:MPSBondTensor} <: AbstractFiniteMPS +$(TYPEDEF) Type that represents a finite Matrix Product State. -## Properties -- `AL` -- left-gauged MPS tensors -- `AR` -- right-gauged MPS tensors -- `AC` -- center-gauged MPS tensors -- `C` -- gauge tensors -- `center` -- location of the gauge center +# Constructors + FiniteMPS([f, eltype], physicalspaces::Vector{<:Union{S, CompositeSpace{S}}}, + maxvirtualspaces::Union{S, Vector{S}}; + normalize = true, left = unitspace(S), right = unitspace(S)) where {S <: ElementarySpace} + FiniteMPS([f, eltype], N::Int, physicalspace::Union{S, CompositeSpace{S}}, + maxvirtualspaces::Union{S, Vector{S}}; + normalize = true, left = unitspace(S), right = unitspace(S)) where {S <: ElementarySpace} + FiniteMPS(As::Vector{<:GenericMPSTensor}; normalize = false, overwrite = false) -The center property returns `center::HalfInt` that indicates the location of the MPS center: +Construct an MPS via a specification of physical and virtual spaces, or from a list of +tensors `As`. All cases reduce to the latter. In particular, a state with a non-trivial +total charge can be constructed by passing a non-trivially charged vector space as the +`left` or `right` virtual spaces. + +# Arguments +- `As`: vector of site tensors +- `f = rand`: initializer function for the tensor data +- `eltype = ComplexF64`: scalar type of the tensors +- `physicalspaces`: list of physical spaces +- `N`: number of sites +- `physicalspace`: local physical space, repeated for every site +- `maxvirtualspaces`: maximal virtual space(s), truncated to what symmetry allows + +# Keyword Arguments +- `normalize`: normalize the constructed state +- `overwrite = false`: overwrite the given input tensors +- `left = unitspace(S)`: left-most virtual space +- `right = unitspace(S)`: right-most virtual space + +# Properties +- `AL`: left-gauged MPS tensors +- `AR`: right-gauged MPS tensors +- `AC`: center-gauged MPS tensors +- `C`: gauge (bond) tensors +- `center`: location of the gauge center + +The `center` property returns a `center::HalfInt` that indicates the location of the MPS center: - `isinteger(center)` → `center` is a whole number and indicates the location of the first `AC` tensor present in the underlying `ψ.ACs` field. - `ishalfodd(center)` → `center` is a half-odd-integer, meaning that there are no `AC` tensors, and indicating between which sites the bond tensor lives. -e.g `mps.center = 7/2` means that the bond tensor is to the right of the 3rd site and can be accessed via `mps.C[3]`. +For example, `mps.center = 7/2` means that the bond tensor is to the right of the 3rd site and can be accessed via `mps.C[3]`. -## Notes +# Notes By convention, we have that: - `AL[i] * C[i]` = `AC[i]` = `C[i-1] * AR[i]` - `AL[i]' * AL[i] = 1` - `AR[i] * AR[i]' = 1` ---- - -## Constructors - FiniteMPS([f, eltype], physicalspaces::Vector{<:Union{S,CompositeSpace{S}}}, - maxvirtualspaces::Union{S,Vector{S}}; - normalize=true, left=unitspace(S), right=unitspace(S)) where {S<:ElementarySpace} - FiniteMPS([f, eltype], N::Int, physicalspace::Union{S,CompositeSpace{S}}, - maxvirtualspaces::Union{S,Vector{S}}; - normalize=true, left=unitspace(S), right=unitspace(S)) where {S<:ElementarySpace} - FiniteMPS(As::Vector{<:GenericMPSTensor}; normalize=false, overwrite=false) - -Construct an MPS via a specification of physical and virtual spaces, or from a list of -tensors `As`. All cases reduce to the latter. In particular, a state with a non-trivial -total charge can be constructed by passing a non-trivially charged vector space as the -`left` or `right` virtual spaces. +# Examples +Building a 3-site spin-1/2 MPS from a dense array and checking that its left-gauged tensors +are isometries (the state is kept in canonical form even though the raw data is not +normalized): -### Arguments -- `As::Vector{<:GenericMPSTensor}`: vector of site tensors +```jldoctest +julia> ψ = FiniteMPS(ones(Float64, (ℂ^2)^3)); -- `f::Function=rand`: initializer function for tensor data -- `eltype::Type{<:Number}=ComplexF64`: scalar type of tensors +julia> length(ψ) +3 -- `physicalspaces::Vector{<:Union{S, CompositeSpace{S}}`: list of physical spaces -- `N::Int`: number of sites -- `physicalspace::Union{S,CompositeSpace{S}}`: local physical space - -- `virtualspaces::Vector{<:Union{S, CompositeSpace{S}}`: list of virtual spaces -- `maxvirtualspace::S`: maximum virtual space - -### Keywords -- `normalize=true`: normalize the constructed state -- `overwrite=false`: overwrite the given input tensors -- `left=unitspace(S)`: left-most virtual space -- `right=unitspace(S)`: right-most virtual space +julia> ψ.AL[1]' * ψ.AL[1] ≈ id(left_virtualspace(ψ, 2)) +true +``` """ struct FiniteMPS{A <: GenericMPSTensor, B <: MPSBondTensor} <: AbstractFiniteMPS ALs::Vector{Union{Missing, A}} @@ -182,7 +191,7 @@ Return the location of the MPS center. - `isinteger(center)` → `center` is a whole number and indicates the location of the first `AC` tensor present in `ψ.ACs` - `ishalfodd(center)` → `center` is a half-odd-integer, meaning that there are no `AC` tensors, and indicating between which sites the bond tensor lives. -## Example +# Examples ```julia ψ = FiniteMPS(3, ℂ^2, ℂ^16) ψ.center # returns 7/2, bond tensor is to the right of the 3rd site diff --git a/src/states/infinitemps.jl b/src/states/infinitemps.jl index 33636e53d..aead98a0d 100644 --- a/src/states/infinitemps.jl +++ b/src/states/infinitemps.jl @@ -1,47 +1,60 @@ """ - InfiniteMPS{A<:GenericMPSTensor,B<:MPSBondTensor} <: AbtractMPS +$(TYPEDEF) Type that represents an infinite Matrix Product State. -## Fields -- `AL` -- left-gauged MPS tensors -- `AR` -- right-gauged MPS tensors -- `AC` -- center-gauged MPS tensors -- `C` -- gauge tensors +# Constructors + InfiniteMPS([f, eltype], physicalspaces::Vector{<:Union{S, CompositeSpace{S}}}, + virtualspaces::Vector{<:Union{S, CompositeSpace{S}}}; + kwargs...) where {S <: ElementarySpace} + InfiniteMPS(As::AbstractVector{<:GenericMPSTensor}; kwargs...) + InfiniteMPS(ALs::AbstractVector{<:GenericMPSTensor}, C₀::MPSBondTensor; kwargs...) + +Construct an MPS via a specification of physical and virtual spaces, or from a list of +tensors `As`, or a list of left-gauged tensors `ALs`. + +# Arguments +- `As`: vector of site tensors +- `ALs`: vector of left-gauged site tensors +- `C₀`: initial gauge tensor +- `f = rand`: initializer function for the tensor data +- `eltype = ComplexF64`: scalar type of the tensors +- `physicalspaces`: list of physical spaces +- `virtualspaces`: list of virtual spaces + +# Keyword Arguments +- `tol`: gauge fixing tolerance +- `maxiter`: gauge fixing maximum iterations -## Notes +# Properties +- `AL`: left-gauged MPS tensors +- `AR`: right-gauged MPS tensors +- `AC`: center-gauged MPS tensors +- `C`: gauge (bond) tensors + +# Notes By convention, we have that: - `AL[i] * C[i]` = `AC[i]` = `C[i-1] * AR[i]` - `AL[i]' * AL[i] = 1` - `AR[i] * AR[i]' = 1` ---- +# Examples +A one-site unit cell built from an explicit `(V_left ⊗ P ← V_right)` tensor. Here the bond +dimension is one, so this is the `|+⟩` product state, for which `⟨X⟩ = 1`: -## Constructors - InfiniteMPS([f, eltype], physicalspaces::Vector{<:Union{S, CompositeSpace{S}}, - virtualspaces::Vector{<:Union{S, CompositeSpace{S}}; - kwargs...) where {S<:ElementarySpace} - InfiniteMPS(As::AbstractVector{<:GenericMPSTensor}; kwargs...) - InfiniteMPS(ALs::AbstractVector{<:GenericMPSTensor}, C₀::MPSBondTensor; - kwargs...) +```jldoctest +julia> A = TensorMap(ones(Float64, 2, 1), ℂ^1 ⊗ ℂ^2, ℂ^1); -Construct an MPS via a specification of physical and virtual spaces, or from a list of -tensors `As`, or a list of left-gauged tensors `ALs`. +julia> ψ = InfiniteMPS([A]); -### Arguments -- `As::AbstractVector{<:GenericMPSTensor}`: vector of site tensors -- `ALs::AbstractVector{<:GenericMPSTensor}`: vector of left-gauged site tensors -- `C₀::MPSBondTensor`: initial gauge tensor +julia> length(ψ) +1 -- `f::Function=rand`: initializer function for tensor data -- `eltype::Type{<:Number}=ComplexF64`: scalar type of tensors +julia> X = TensorMap(Float64[0 1; 1 0], ℂ^2, ℂ^2); -- `physicalspaces::AbstractVector{<:Union{S, CompositeSpace{S}}`: list of physical spaces -- `virtualspaces::AbstractVector{<:Union{S, CompositeSpace{S}}`: list of virtual spaces - -### Keywords -- `tol`: gauge fixing tolerance -- `maxiter`: gauge fixing maximum iterations +julia> round(real(expectation_value(ψ, 1 => X)); digits = 6) +1.0 +``` """ struct InfiniteMPS{A <: GenericMPSTensor, B <: MPSBondTensor} <: AbstractMPS AL::PeriodicVector{A} diff --git a/src/states/multilinemps.jl b/src/states/multilinemps.jl index a090b67cd..aae028a98 100644 --- a/src/states/multilinemps.jl +++ b/src/states/multilinemps.jl @@ -5,18 +5,24 @@ const MultilineMPS = Multiline{<:InfiniteMPS} @doc """ const MultilineMPS = Multiline{<:InfiniteMPS} -Type that represents multiple lines of `InfiniteMPS` objects. +Type that represents multiple lines of [`InfiniteMPS`](@ref) objects. # Constructors MultilineMPS(mpss::AbstractVector{<:InfiniteMPS}) - MultilineMPS([f, eltype], physicalspaces::Matrix{<:Union{S, CompositeSpace{S}}, - virtualspaces::Matrix{<:Union{S, CompositeSpace{S}}) where - {S<:ElementarySpace} + MultilineMPS([f, eltype], physicalspaces::Matrix{<:Union{S, CompositeSpace{S}}}, + virtualspaces::Matrix{<:Union{S, CompositeSpace{S}}}) where {S <: ElementarySpace} MultilineMPS(As::AbstractMatrix{<:GenericMPSTensor}; kwargs...) MultilineMPS(ALs::AbstractMatrix{<:GenericMPSTensor}, C₀::AbstractVector{<:MPSBondTensor}; kwargs...) -See also: [`Multiline`](@ref) +# Properties +- `AL`: left-gauged MPS tensors +- `AR`: right-gauged MPS tensors +- `AC`: center-gauged MPS tensors +- `C`: gauge (bond) tensors + +# See also +[`Multiline`](@ref) """ function MultilineMPS end diff --git a/src/states/ortho.jl b/src/states/ortho.jl index 0b4cbd6ca..6322b366c 100644 --- a/src/states/ortho.jl +++ b/src/states/ortho.jl @@ -8,7 +8,7 @@ $(TYPEDEF) Algorithm for bringing an `InfiniteMPS` into the left-canonical form. -## Fields +# Fields $(TYPEDFIELDS) """ @@ -33,7 +33,7 @@ $(TYPEDEF) Algorithm for bringing an `InfiniteMPS` into the right-canonical form. -## Fields +# Fields $(TYPEDFIELDS) """ @@ -58,7 +58,7 @@ $(TYPEDEF) Algorithm for bringing an `InfiniteMPS` into the mixed-canonical form. -## Fields +# Fields $(TYPEDFIELDS) """ @@ -153,7 +153,7 @@ end Bring updated `AC` and `C` tensors back into a consistent set of left or right canonical tensors. This minimizes `∥AC_i - AL_i * C_i∥` or `∥AC_i - C_{i-1} * AR_i∥`. -The `alg` is passed on to `left_orth!` and `right_orth!`, and can be used to control the kind of +The `alg` is passed on to `left_orth!` and `right_orth!`, and can be used to control the kind of factorization used. By default, this is set to a (positive) QR/LQ, even though the optimal algorithm would use a polar decompositions instead, sacrificing a bit of performance for accuracy. diff --git a/src/states/quasiparticle_state.jl b/src/states/quasiparticle_state.jl index 83ef7553c..6926d5631 100644 --- a/src/states/quasiparticle_state.jl +++ b/src/states/quasiparticle_state.jl @@ -4,6 +4,35 @@ I think it makes sense to see these things as an actual state instead of return This will allow us to plot energy density (finite qp) and measure observables. =# +""" +$(TYPEDEF) + +Left-gauged quasiparticle excitation ansatz on top of a matrix product state ground state. +The excitation is parametrized through the left-gauge nullspace of the ground-state tensors, +and the object behaves as a vector so it can be handed directly to the iterative eigensolvers +used by [`excitations`](@ref). + +For a `FiniteMPS` ground state this represents a finite (localized) quasiparticle; for an +`InfiniteMPS` ground state it represents a momentum eigenstate with the given `momentum`. +When `left_gs !== right_gs` the ansatz describes a domain wall between the two ground states. + +# Constructors + LeftGaugedQP(datfun, left_gs, right_gs = left_gs; sector, momentum = 0.0) + +These states are normally produced by [`excitations`](@ref) with a +[`QuasiparticleAnsatz`](@ref) rather than constructed directly. When constructing manually, +`datfun` initializes the variational tensors (e.g. `rand`/`randn`), `sector` selects the +charge sector of the excitation, and `momentum` sets the momentum for infinite ground states. + +# Fields +- `left_gs`, `right_gs`: the ground state(s) the excitation lives on; distinct values yield a domain wall. +- `VLs`: left-nullspace tensors of the ground-state `AL` (satisfying `AL' * VL == 0`). +- `Xs`: the variational parameters of the ansatz. +- `momentum`: the excitation momentum (used for infinite ground states). + +# See also +[`RightGaugedQP`](@ref), [`QP`](@ref) +""" struct LeftGaugedQP{S, T1, T2, E <: Number} # !(left_gs === right_gs) => domain wall excitation left_gs::S @@ -15,6 +44,26 @@ struct LeftGaugedQP{S, T1, T2, E <: Number} momentum::E end +""" +$(TYPEDEF) + +Right-gauged counterpart of [`LeftGaugedQP`](@ref): the same quasiparticle excitation ansatz, +but parametrized through the right-gauge nullspace of the ground-state tensors. It is most +often obtained via `convert(RightGaugedQP, ϕ)` from a `LeftGaugedQP` rather than constructed +directly. + +# Constructors + RightGaugedQP(datfun, left_gs, right_gs = left_gs; sector, momentum = 0.0) + +# Fields +- `left_gs`, `right_gs`: the ground state(s) the excitation lives on; distinct values yield a domain wall. +- `Xs`: the variational parameters of the ansatz. +- `VRs`: right-nullspace tensors of the ground-state `AR`. +- `momentum`: the excitation momentum (used for infinite ground states). + +# See also +[`LeftGaugedQP`](@ref), [`QP`](@ref) +""" struct RightGaugedQP{S, T1, T2, E <: Number} # !(left_gs === right_gs) => domain wall excitation left_gs::S @@ -207,6 +256,15 @@ function Base.convert( end # gauge independent code +""" + QP{S, T1, T2} + +Union of the quasiparticle excitation ansätze [`LeftGaugedQP`](@ref) and +[`RightGaugedQP`](@ref). It is used for dispatch and to share their gauge-independent +interface; it is not a concrete type and cannot be constructed on its own. The internal +aliases `FiniteQP` and `InfiniteQP` further restrict the ground-state type to `FiniteMPS` +or `InfiniteMPS` respectively. +""" const QP{S, T1, T2} = Union{LeftGaugedQP{S, T1, T2}, RightGaugedQP{S, T1, T2}} const FiniteQP{S <: FiniteMPS, T1, T2} = QP{S, T1, T2} const InfiniteQP{S <: InfiniteMPS, T1, T2} = QP{S, T1, T2} diff --git a/src/states/windowmps.jl b/src/states/windowmps.jl index e32ac35d2..9a072604d 100644 --- a/src/states/windowmps.jl +++ b/src/states/windowmps.jl @@ -1,39 +1,36 @@ """ - WindowMPS{A<:GenericMPSTensor,B<:MPSBondTensor} <: AbstractFiniteMPS +$(TYPEDEF) Type that represents a finite Matrix Product State embedded in an infinite Matrix Product State. -## Fields - -- `left_gs::InfiniteMPS` -- left infinite environment -- `window::FiniteMPS` -- finite window Matrix Product State -- `right_gs::InfiniteMPS` -- right infinite environment - ---- - -## Constructors - +# Constructors WindowMPS(left_gs::InfiniteMPS, window_state::FiniteMPS, [right_gs::InfiniteMPS]) WindowMPS(left_gs::InfiniteMPS, window_tensors::AbstractVector, [right_gs::InfiniteMPS]) - WindowMPS([f, eltype], physicalspaces::Vector{<:Union{S, CompositeSpace{S}}, - virtualspaces::Vector{<:Union{S, CompositeSpace{S}}, left_gs::InfiniteMPS, + WindowMPS([f, eltype], physicalspaces::Vector{<:Union{S, CompositeSpace{S}}}, + virtualspaces::Vector{<:Union{S, CompositeSpace{S}}}, left_gs::InfiniteMPS, [right_gs::InfiniteMPS]) - WindowMPS([f, eltype], physicalspaces::Vector{<:Union{S,CompositeSpace{S}}}, + WindowMPS([f, eltype], physicalspaces::Vector{<:Union{S, CompositeSpace{S}}}, maxvirtualspace::S, left_gs::InfiniteMPS, [right_gs::InfiniteMPS]) + WindowMPS(ψ::InfiniteMPS, L::Int) Construct a WindowMPS via a specification of left and right infinite environment, and either a window state or a vector of tensors to construct the window. Alternatively, it is possible to supply the same arguments as for the constructor of [`FiniteMPS`](@ref), followed by a -left (and right) environment to construct the WindowMPS in one step. +left (and right) environment to construct the WindowMPS in one step. Finally, a WindowMPS can +be constructed from an `InfiniteMPS` by promoting a region of length `L` to a `FiniteMPS`. !!! note By default, the right environment is chosen to be equal to the left, however no copy is made. In this case, changing the left state will also affect the right state. - WindowMPS(state::InfiniteMPS, L::Int) - -Construct a WindowMPS from an InfiniteMPS, by promoting a region of length `L` to a -`FiniteMPS`. +# Properties +- `left_gs::InfiniteMPS`: left infinite environment +- `window::FiniteMPS`: finite window Matrix Product State +- `right_gs::InfiniteMPS`: right infinite environment +- `AL`: left-gauged MPS tensors +- `AR`: right-gauged MPS tensors +- `AC`: center-gauged MPS tensors +- `C`: gauge (bond) tensors """ struct WindowMPS{A <: GenericMPSTensor, B <: MPSBondTensor} <: AbstractFiniteMPS left_gs::InfiniteMPS{A, B} diff --git a/src/utility/dynamictols.jl b/src/utility/dynamictols.jl index 7a67a9a01..39ac6d6d9 100644 --- a/src/utility/dynamictols.jl +++ b/src/utility/dynamictols.jl @@ -23,11 +23,13 @@ $(TYPEDEF) Algorithm wrapper with dynamically adjusted tolerances. -## Fields +# Fields $(TYPEDFIELDS) -See also [`updatetol`](@ref). +# See also + +[`updatetol`](@ref) """ struct DynamicTol{A} <: Algorithm "parent algorithm" diff --git a/src/utility/multiline.jl b/src/utility/multiline.jl index 53b02cfc6..cef1a0f6d 100644 --- a/src/utility/multiline.jl +++ b/src/utility/multiline.jl @@ -1,13 +1,15 @@ """ - struct Multiline{T} +$(TYPEDEF) Object that represents multiple lines of objects of type `T`. Typically used to represent multiple lines of `InfiniteMPS` (`MultilineMPS`) or MPO (`Multiline{<:AbstractMPO}`). # Fields -- `data::PeriodicArray{T,1}`: the data of the multiline object +- `data::PeriodicArray{T, 1}`: the data of the multiline object -See also: [`MultilineMPS`](@ref) and [`MultilineMPO`](@ref) +# See also + +[`MultilineMPS`](@ref) and [`MultilineMPO`](@ref) """ struct Multiline{T} data::PeriodicArray{T, 1} diff --git a/src/utility/periodicarray.jl b/src/utility/periodicarray.jl index 14ce75b65..3963d46ba 100644 --- a/src/utility/periodicarray.jl +++ b/src/utility/periodicarray.jl @@ -1,10 +1,10 @@ """ - PeriodicArray{T,N} <: AbstractArray{T,N} +$(TYPEDEF) Array wrapper with periodic boundary conditions. # Fields -- `data::Array{T,N}`: the data of the array +- `data::Array{T, N}`: the data of the array # Examples ```jldoctest @@ -24,7 +24,9 @@ A[-1, 1], A[1, 1], A[4, 5] (1, 1, 3) ``` -See also [`PeriodicVector`](@ref), [`PeriodicMatrix`](@ref) +# See also + +[`PeriodicVector`](@ref), [`PeriodicMatrix`](@ref) """ struct PeriodicArray{T, N} <: AbstractArray{T, N} data::Array{T, N} @@ -42,7 +44,7 @@ end PeriodicVector{T} One-dimensional dense array with elements of type `T` and periodic boundary conditions. -Alias for [`PeriodicArray{T,1}`](@ref). +Alias for [`PeriodicArray{T, 1}`](@ref). """ const PeriodicVector{T} = PeriodicArray{T, 1} PeriodicVector(data::AbstractVector{T}) where {T} = PeriodicVector{T}(data) @@ -51,7 +53,7 @@ PeriodicVector(data::AbstractVector{T}) where {T} = PeriodicVector{T}(data) PeriodicMatrix{T} Two-dimensional dense array with elements of type `T` and periodic boundary conditions. -Alias for [`PeriodicArray{T,2}`](@ref). +Alias for [`PeriodicArray{T, 2}`](@ref). """ const PeriodicMatrix{T} = PeriodicArray{T, 2} PeriodicMatrix(data::AbstractMatrix{T}) where {T} = PeriodicMatrix{T}(data) diff --git a/src/utility/plotting.jl b/src/utility/plotting.jl index d94f90c37..6808bd344 100644 --- a/src/utility/plotting.jl +++ b/src/utility/plotting.jl @@ -1,18 +1,18 @@ """ - entanglementplot(state; site=0[, kwargs...]) + entanglementplot(state; site = 0[, kwargs...]) -Plot the [entanglement spectrum](@ref entanglement_spectrum) of a given MPS `state`. +Plot the [entanglement spectrum](@ref entanglement_spectrum) of a given MPS `state`. # Arguments - `state`: the MPS for which to compute the entanglement spectrum. # Keyword Arguments -- `site::Int=0`: MPS index for multisite unit cells. The spectrum is computed for the bond +- `site::Int = 0`: MPS index for multisite unit cells. The spectrum is computed for the bond between `site` and `site + 1`. -- `expand_symmetry::Logical=false`: add quantum dimension degeneracies. -- `sortby=maximum`: the method of sorting the sectors. -- `sector_margin=1//10`: the amount of whitespace between sectors. -- `sector_formatter=string`: how to convert sectors to strings. +- `expand_symmetry = false`: add quantum dimension degeneracies. +- `sortby = maximum`: the method of sorting the sectors. +- `sector_margin = 1 // 10`: the amount of whitespace between sectors. +- `sector_formatter = string`: how to convert sectors to strings. - `kwargs...`: other kwargs are passed on to the plotting backend. !!! note @@ -89,20 +89,20 @@ function entanglementplot end end """ - transferplot(above, below=above; sectors=[], transferkwargs=(;)[, kwargs...]) + transferplot(above, below = above; sectors = [], transferkwargs = (;)[, kwargs...]) Plot the partial transfer matrix spectrum of two InfiniteMPS's. # Arguments - `above::InfiniteMPS`: above mps for [`transfer_spectrum`](@ref). -- `below::InfiniteMPS=above`: below mps for [`transfer_spectrum`](@ref). +- `below::InfiniteMPS = above`: below mps for [`transfer_spectrum`](@ref). # Keyword Arguments -- `sectors=[]`: vector of sectors for which to compute the spectrum. +- `sectors = []`: vector of sectors for which to compute the spectrum. - `transferkwargs`: kwargs for call to [`transfer_spectrum`](@ref). - `kwargs`: other kwargs are passed on to the plotting backend. -- `thetaorigin=0`: origin of the angle range. -- `sector_formatter=string`: how to convert sectors to strings. +- `thetaorigin = 0`: origin of the angle range. +- `sector_formatter = string`: how to convert sectors to strings. !!! note You will need to manually import [Plots.jl](https://github.com/JuliaPlots/Plots.jl) to