diff --git a/.buildkite/CUDA_Ext.yml b/.buildkite/CUDA_Ext.yml index b5035f9fe..40a80d6e5 100644 --- a/.buildkite/CUDA_Ext.yml +++ b/.buildkite/CUDA_Ext.yml @@ -4,7 +4,7 @@ steps: setup: version: - "1.10" # oldest - #- "1" # latest + - "1" # latest plugins: - JuliaCI/julia#v1: version: "{{matrix.version}}" @@ -22,12 +22,3 @@ steps: GROUP: "CUDA_Ext" SECRET_CODECOV_TOKEN: "ZfhQu/IcRLqNyZ//ZNs5sjBPaV76IHfU5gui52Qn+Rp8tOurukqgScuyDt+3HQ4R0hJYBw1/Nqg6jmBsvWSc9NEUx8kGsUJFHfN3no0+b+PFxA8oJkWc9EpyIsjht5ZIjlsFWR3f0DpPqMEle/QyWOPcal63CChXR8oAoR+Fz1Bh8GkokLlnC8F9Ugp9xBlu401GCbyZhvLTZnNIgK5yy9q8HBJnBg1cPOhI81J6JvYpEmcIofEzFV/qkfpTUPclu43WNoFX2DZPzbxilf3fsAd5/+nRkRfkNML8KiN4mnmjHxPPbuY8F5zC/PS5ybXtDpfvaMQc01WApXCkZk0ZAQ==;U2FsdGVkX1+eDT7dqCME5+Ox5i8GvWRTQbwiP/VYjapThDbxXFDeSSIC6Opmon+M8go22Bun3bat6Fzie65ang==" timeout_in_minutes: 60 - if: | - // Don't run Buildkite if the commit message includes the text [skip ci], [ci skip], or [no ci] - // Don't run Buildkite for PR draft - // Only run Buildkite when new commits and PR are made to main branch - build.message !~ /\[skip ci\]/ && - build.message !~ /\[ci skip\]/ && - build.message !~ /\[no ci\]/ && - !build.pull_request.draft && - (build.branch =~ /main/ || build.pull_request.base_branch =~ /main/) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index e8de06364..36199fd77 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,16 +1,25 @@ # see: https://github.com/staticfloat/forerunner-buildkite-plugin steps: - label: ":runner: Dynamically launch pipelines" + if: | + // Don't run Buildkite if the commit message includes the text [skip ci], [ci skip], or [no ci] + // Don't run Buildkite for PR draft + // Only run Buildkite when new commits and PR are made to main branch + build.message !~ /\[skip ci\]/ && + build.message !~ /\[ci skip\]/ && + build.message !~ /\[no ci\]/ && + !build.pull_request.draft && + (build.branch =~ /main/ || build.pull_request.base_branch =~ /main/) + agents: + queue: "juliagpu" plugins: - - staticfloat/forerunner: + - staticfloat/forerunner: # CUDA.jl tests watch: - ".buildkite/pipeline.yml" - ".buildkite/CUDA_Ext.yml" - "src/**" - "ext/QuantumToolboxCUDAExt.jl" - "test/runtests.jl" - - "test/cuda_ext.jl" + - "test/ext-test/gpu/**" - "Project.toml" target: ".buildkite/CUDA_Ext.yml" - agents: - queue: "juliagpu" diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index ef9045062..e2f33b540 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -16,13 +16,13 @@ body: label: Code to Reproduce the Bug description: Please provide a minimal working example. Paste your code directly (It will be automatically formatted, so there's no need for backticks) placeholder: "using QuantumToolbox\nprint(qeye(2))" - render: shell + render: julia - type: textarea id: bug-output attributes: label: Code Output description: Please paste the relevant output here (automatically formatted) - placeholder: "Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true\n2×2 Diagonal{ComplexF64, Vector{ComplexF64}}:\n 1.0+0.0im ⋅ \n ⋅ 1.0+0.0im" + placeholder: "Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true\n2×2 Diagonal{ComplexF64, Vector{ComplexF64}}:\n 1.0+0.0im ⋅ \n ⋅ 1.0+0.0im" render: shell - type: textarea id: expected-behaviour @@ -37,7 +37,7 @@ body: attributes: label: Your Environment description: Please use `QuantumToolbox.about()` or `QuantumToolbox.versioninfo()` to get the information about your environment and paste it here (automatically formatted) - placeholder: "Julia Ver. ***\nQuantumToolbox Ver. ***\nLinearSolve Ver. ***\nOrdinaryDiffEqCore Ver. ***\nOS : ***\nWORD_SIZE: ***\nLIBM : ***\nLLVM : ***\nBLAS : ***" + placeholder: "Julia Ver. ***\nQuantumToolbox Ver. ***\nSciMLOperators Ver. ***\nLinearSolve Ver. ***\nOrdinaryDiffEqCore Ver. ***\nOS : ***\nWORD_SIZE: ***\nLIBM : ***\nLLVM : ***\nBLAS : ***" render: shell validations: required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ff6499d68..e0db79d1c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,4 +4,8 @@ updates: - package-ecosystem: "github-actions" directory: "/" # Location of package manifests schedule: - interval: "weekly" \ No newline at end of file + interval: "weekly" + labels: + - "dependencies" + - "Skip ChangeLog" + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f486924a9..db12434e5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,12 @@ ## Checklist Thank you for contributing to `QuantumToolbox.jl`! Please make sure you have finished the following tasks before opening the PR. -- [ ] Please read [Contributor Covenant Code of Conduct](https://github.com/qutip/QuantumToolbox.jl/blob/main/CODE_OF_CONDUCT.md) -- [ ] Any code changes were done in a way that does not break public API -- [ ] Appropriate tests were added. -- [ ] Any code changes should be formatted by running: `julia -e 'using JuliaFormatter; format(".")'` -- [ ] All documentation (in `docs/` folder) related to code changes were updated. +- [ ] Please read [Contributing to Quantum Toolbox in Julia](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing). +- [ ] Any code changes were done in a way that does not break public API. +- [ ] Appropriate tests were added and tested locally by running: `make test`. +- [ ] Any code changes should be `julia` formatted by running: `make format`. +- [ ] All documents (in `docs/` folder) related to code changes were updated and able to build locally by running: `make docs`. +- [ ] (If necessary) the `CHANGELOG.md` should be updated (regarding to the code changes) and built by running: `make changelog`. Request for a review after you have completed all the tasks. If you have not finished them all, you can also open a [Draft Pull Request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) to let the others know this on-going work. @@ -13,6 +14,6 @@ Request for a review after you have completed all the tasks. If you have not fin Describe the proposed change here. ## Related issues or PRs -Please mention the related issues or PRs here. If the PR fixes an issue, use the keyword close/closes/closed/fix/fixes/fixed/resolve/resolves/resolved followed by the issue id, e.g. fix #1234 +Please mention the related issues or PRs here. If the PR fixes an issue, use the keyword close/closes/closed/fix/fixes/fixed/resolve/resolves/resolved followed by the issue id, e.g. fix #[id] -## Additional context \ No newline at end of file +## Additional context diff --git a/.github/workflows/Benchmarks.yml b/.github/workflows/Benchmarks.yml index e73652ebd..6b2247fdc 100644 --- a/.github/workflows/Benchmarks.yml +++ b/.github/workflows/Benchmarks.yml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest if: ${{ !github.event.pull_request.draft }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: julia-actions/setup-julia@v2 with: version: '1' @@ -49,6 +49,7 @@ jobs: Pkg.instantiate(); include("runbenchmarks.jl")' + # this will update benchmarks/data.js in gh-pages branch - name: Parse & Upload Benchmark Results uses: benchmark-action/github-action-benchmark@v1 with: diff --git a/.github/workflows/CI-Julia-nightly.yml b/.github/workflows/CI-Julia-nightly.yml new file mode 100644 index 000000000..79b02246f --- /dev/null +++ b/.github/workflows/CI-Julia-nightly.yml @@ -0,0 +1,61 @@ +name: Runtests (Julia nightly) + +on: + push: + branches: + - 'main' + paths: + - '.github/workflows/CI-Julia-nightly.yml' + - 'src/**' + - 'ext/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'Project.toml' + pull_request: + branches: + - 'main' + paths: + - '.github/workflows/CI-Julia-nightly.yml' + - 'src/**' + - 'ext/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'Project.toml' + types: + - opened + - reopened + - synchronize + - ready_for_review + +jobs: + test: + name: ${{ matrix.os }} - ${{ matrix.arch }} ( ${{ matrix.group }} ) + runs-on: ${{ matrix.os }} + permissions: # needed to allow julia-actions/cache to delete old caches that it has created + actions: write + contents: read + if: ${{ !github.event.pull_request.draft }} + strategy: + fail-fast: false + matrix: + version: + - 'nightly' + os: + - 'ubuntu-latest' + arch: + - 'x64' + group: + - 'Core' + + steps: + - uses: actions/checkout@v5 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + env: + GROUP: ${{ matrix.group }} + JULIA_NUM_THREADS: auto diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0729432fd..9684b88d6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -8,7 +8,9 @@ on: - '.github/workflows/CI.yml' - 'src/**' - 'ext/**' - - 'test/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'test/ext-test/cpu/**' - 'Project.toml' pull_request: branches: @@ -17,7 +19,9 @@ on: - '.github/workflows/CI.yml' - 'src/**' - 'ext/**' - - 'test/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'test/ext-test/cpu/**' - 'Project.toml' types: - opened @@ -27,8 +31,8 @@ on: jobs: test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} ( ${{ matrix.group }} ) - runs-on: ${{ matrix.os }} + name: Julia ${{ matrix.version }} - ${{ matrix.node.os }} - ${{ matrix.node.arch }} ( ${{ matrix.group }} ) + runs-on: ${{ matrix.node.os }} permissions: # needed to allow julia-actions/cache to delete old caches that it has created actions: write contents: read @@ -39,53 +43,53 @@ jobs: # for core tests (latest and oldest supported versions) version: - '1.10' # oldest - # - '1' # latest - os: - - ubuntu-latest - - windows-latest - arch: - - x64 + - '1' # latest + node: + - os: 'ubuntu-latest' + arch: 'x64' + - os: 'windows-latest' + arch: 'x64' + - os: 'macOS-latest' + arch: 'arm64' group: - - Core + - 'Core' include: - # for core tests on macOS (M-series chip) - - version: '1.10' # oldest - os: 'macOS-latest' - arch: 'x64' - group: 'Core' - # - version: '1' # latest - # os: 'macOS-latest' - # arch: 'arm64' - # group: 'Core' - # for core tests (intermediate versions) - # - version: '1.x' - # os: 'ubuntu-latest' - # arch: 'x64' - # group: 'Core' + - version: '1.11' + node: + os: 'ubuntu-latest' + arch: 'x64' + group: 'Core' - # for code quality tests + # for extension tests - version: '1' - os: 'ubuntu-latest' - arch: 'x64' - group: 'Code-Quality' + node: + os: 'ubuntu-latest' + arch: 'x64' + group: 'Makie_Ext' + - version: '1.11' + node: + os: 'ubuntu-latest' + arch: 'x64' + group: 'AutoDiff_Ext' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} - arch: ${{ matrix.arch }} + arch: ${{ matrix.node.arch }} - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 env: GROUP: ${{ matrix.group }} + JULIA_NUM_THREADS: auto - uses: julia-actions/julia-processcoverage@v1 with: directories: src,ext - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: diff --git a/.github/workflows/ChangeLogCheck.yml b/.github/workflows/ChangeLogCheck.yml new file mode 100644 index 000000000..d99dca988 --- /dev/null +++ b/.github/workflows/ChangeLogCheck.yml @@ -0,0 +1,39 @@ +# Enforces the update of the file CHANGELOG.md on every pull request +# Can be skipped with the `Skip ChangeLog` label +name: ChangeLog Update Check +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] + +jobs: + changelog: + runs-on: ubuntu-latest + if: ${{ !github.event.pull_request.draft }} + steps: + # check whether CHANGELOG.md is updated + - uses: dangoslen/changelog-enforcer@v3 + with: + skipLabels: 'Skip ChangeLog' + + # check whether the format of CHANGELOG.md is correct + - uses: actions/checkout@v5 + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + - name: Install and Run Changelog + run: | + julia -e 'import Pkg; Pkg.add("Changelog")' + julia -e 'using Changelog; Changelog.generate(Changelog.CommonMark(), "CHANGELOG.md"; repo = "qutip/QuantumToolbox.jl")' + + - name: CHANGELOG Format Check + run: | + julia -e ' + output = Cmd(`git diff --name-only`) |> read |> String + if output == "" + exit(0) + else + @error "The format of CHANGELOG.md is not correct !!!" + write(stdout, "Please format it by running the following command:\n") + write(stdout, "make changelog") + exit(1) + end' diff --git a/.github/workflows/CleanPreviewDoc.yml b/.github/workflows/CleanPreviewDoc.yml new file mode 100644 index 000000000..dad91e5b5 --- /dev/null +++ b/.github/workflows/CleanPreviewDoc.yml @@ -0,0 +1,31 @@ +name: Cleanup Preview Documentation + +on: + pull_request: + types: [closed] + +permissions: + contents: write + deployments: write + +jobs: + cleanup-preview-doc: + runs-on: ubuntu-latest + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v5 + with: + ref: gh-pages + - name: Delete preview and history + push changes + run: | + if [ -d "previews/PR$PRNUM" ]; then + git config user.name "Documenter.jl" + git config user.email "documenter@juliadocs.github.io" + git rm -rf "previews/PR$PRNUM" + git commit -m "delete preview" + git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree}) + git push --force origin gh-pages-new:gh-pages + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PRNUM: ${{ github.event.number }} diff --git a/.github/workflows/Code-Quality.yml b/.github/workflows/Code-Quality.yml new file mode 100644 index 000000000..8bd92249f --- /dev/null +++ b/.github/workflows/Code-Quality.yml @@ -0,0 +1,61 @@ +name: Code Quality + +on: + push: + branches: + - 'main' + paths: + - '.github/workflows/Code-Quality.yml' + - 'src/**' + - 'ext/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'Project.toml' + pull_request: + branches: + - 'main' + paths: + - '.github/workflows/Code-Quality.yml' + - 'src/**' + - 'ext/**' + - 'test/runtests.jl' + - 'test/core-test/**' + - 'Project.toml' + types: + - opened + - reopened + - synchronize + - ready_for_review + +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + permissions: # needed to allow julia-actions/cache to delete old caches that it has created + actions: write + contents: read + if: ${{ !github.event.pull_request.draft }} + strategy: + fail-fast: false + matrix: + version: + - 'lts' + - '1' + os: + - 'ubuntu-latest' + arch: + - 'x64' + group: + - 'Code-Quality' + + steps: + - uses: actions/checkout@v5 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + env: + GROUP: ${{ matrix.group }} diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index 2b46352d5..e819e6a78 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest if: ${{ !github.event.pull_request.draft }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: julia-actions/setup-julia@v2 with: version: '1' @@ -45,6 +45,6 @@ jobs: write(stdout, output) write(stdout, "-----\n") write(stdout, "Please format them by running the following command:\n") - write(stdout, "julia -e \"using JuliaFormatter; format(\\\".\\\")\"") + write(stdout, "make format") exit(1) end' \ No newline at end of file diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml new file mode 100644 index 000000000..9012b7532 --- /dev/null +++ b/.github/workflows/SpellCheck.yml @@ -0,0 +1,13 @@ +name: Spell Check + +on: [pull_request] + +jobs: + typos-check: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v5 + - name: Check spelling + uses: crate-ci/typos@v1.37.2 \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index beaa3fc02..d7ddeabbd 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -21,15 +21,29 @@ on: - synchronize - ready_for_review + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: write + pages: write + id-token: write + statuses: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: pages + cancel-in-progress: false + jobs: + # Build job build: runs-on: ubuntu-latest - permissions: - contents: write - statuses: write if: ${{ !github.event.pull_request.draft }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: julia-actions/setup-julia@v2 with: version: '1' @@ -37,11 +51,8 @@ jobs: - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-docdeploy@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} - - run: | - julia --project=docs -e ' - using Documenter: DocMeta, doctest - using QuantumToolbox - DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive=true) - doctest(QuantumToolbox)' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key + JULIA_DEBUG: "Documenter" + DATADEPS_ALWAYS_ACCEPT: true + # GKSwstype: "100" # for Plots.jl plots (if you have them) diff --git a/.gitignore b/.gitignore index edb4c3a5e..b07886a94 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,10 @@ *.jl.cov *.jl.mem Manifest.toml -docs/build/ .vscode -*.json + +benchmarks/benchmarks_output.json .ipynb_checkpoints *.ipynb \ No newline at end of file diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 000000000..cb4a74b2f --- /dev/null +++ b/.typos.toml @@ -0,0 +1,4 @@ +[default.extend-words] +ket = "ket" +sme = "sme" +nd = "nd" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..8a8eb735c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,330 @@ +# ChangeLog + +All notable changes to [`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main) + +- Fix `cite()` bibtex output. ([#552]) +- Add `qeye_like` and `qzero_like`, which are synonyms of `one` and `zero`. ([#555]) + +## [v0.36.0] +Release date: 2025-09-29 + +- Add `QuantumToolbox.cite()` for bibtex generator of `QuantumToolbox.jl`. ([#544]) +- Add `sortby` and `rev` keyword arguments to eigensolvers. ([#546]) + +## [v0.35.0] +Release date: 2025-09-03 + +- Add support of `QobjEvo` for `steadystate` (ODE solver only). ([#536]) +- Changes to `SteadyStateODESolver`. ([#537]) + - Introduce the tolerances for `steadystate` terminate condition (two new fields: `terminate_reltol = 1e-5` and `terminate_abstol = 1e-7`) + - Fix keyword argument handling for `SteadyStateODESolver` before passing to `mesolve`. +- Fix incorrect `negativity` and `partial_transpose` for arbitrary subsystem dimension. ([#539]) + +## [v0.34.1] +Release date: 2025-08-23 + +- Improve Bloch sphere rendering for animation. ([#520]) +- Add support to `Enzyme.jl` for `sesolve` and `mesolve`. ([#531]) + +## [v0.34.0] +Release date: 2025-07-29 + +- Improve efficiency of `bloch_redfield_tensor` by avoiding unnecessary conversions. ([#509]) +- Support `SciMLOperators v1.4+`. ([#470]) +- Fix compatibility with `Makie v0.24+`. ([#513]) +- Add `keep_runs_results` option for multi-trajectory solvers to align with `QuTiP`. ([#512]) + - Breaking changes for multi-trajectory solutions: + - the original fields `expect` and `states` now store the results depend on keyword argument `keep_runs_results` (decide whether to store all trajectories results or not). + - remove field `average_expect` + - remove field `runs_expect` + - New statistical analysis functions: + - `average_states` + - `average_expect` + - `std_expect` +- Add support to ForwardDiff.jl for `sesolve` and `mesolve`. ([#515]) +- Add documentation about automatic differentiation. ([#517]) + +## [v0.33.0] +Release date: 2025-07-22 + +- Implement `EnrSpace` and corresponding functionality. ([#500]) +- Check for orthogonality breakdown in `Lanczos` solver for `spectrum`. ([#501]) +- Store both `times` and `times_states` in time evolution solutions. ([#506], [#504]) +- Fix errors in `Julia v1.12`. ([#507]) + +## [v0.32.1] +Release date: 2025-06-24 + +This is a release just for updating documentation. + +## [v0.32.0] +Release date: 2025-06-23 + +- Introduce `Lanczos` solver for `spectrum`. ([#476]) +- Add Bloch-Redfield master equation solver. ([#473]) +- Implement Bloch Sphere rendering and align style with qutip. ([#472], [#480], [#485], [#487], [#489]) +- Add `Base.copy` method for `AbstractQuantumObject`. ([#486]) +- Add documentation for Bloch-Redfield master equation. ([#494]) + +## [v0.31.1] +Release date: 2025-05-16 + +- Introduce `QuantumToolbox.settings` and `auto_tidyup`. ([#460]) + +## [v0.31.0] +Release date: 2025-05-03 + +- Return `sesolve` when `mesolve` allows it. ([#455]) +- Simplify structure of `QuantumObjectType`s. ([#456]) + +## [v0.30.1] +Release date: 2025-04-24 + +- Support different length for `to` and `from` on GeneralDimensions. ([#448]) +- Extend the `Makie.jl` extension to all the other available backends. ([#450]) +- Fix definition of noise derivative in stochastic solvers. ([#453]) + +## [v0.30.0] +Release date: 2025-04-12 + +- Make CUDA conversion more general using Adapt.jl. ([#436], [#437]) +- Make the generation of `fock` states non-mutating to support Zygote.jl. ([#438]) +- Remove Reexport.jl from the dependencies. ([#443]) +- Add support for automatic differentiation for `sesolve` and `mesolve`. ([#440]) + +## [v0.29.1] +Release date: 2025-03-07 + +- Minor changes for GPU matrices element type and word size handling. ([#430]) + +## [v0.29.0] +Release date: 2025-03-07 + +- Add support for `OperatorKet` state input for `mesolve` and `smesolve`. ([#423]) +- Introduce `plot_fock_distribution` to plot the population of a state (ket, bra, or density matrix) in its basis (assumed to be Fock basis). ([#428]) + +## [v0.28.0] +Release date: 2025-02-22 + +- Support for single `AbstractQuantumObject` in `sc_ops` for faster specific method in `ssesolve` and `smesolve`. ([#408]) +- Change save callbacks from `PresetTimeCallback` to `FunctionCallingCallback`. ([#410]) +- Align `eigenstates` and `eigenenergies` to QuTiP. ([#411]) +- Introduce `vector_to_operator` and `operator_to_vector`. ([#413]) +- Introduce some entropy related functions. ([#414], [#416]) + - `entropy_linear` + - `entropy_mutual` + - `entropy_conditional` + - `entropy_relative` +- Fix `entanglement` and introduce `concurrence`. ([#414], [#418], [#419]) +- Introduce some metric functions. ([#414], [#420]) + - `hilbert_dist` + - `hellinger_dist` + - `bures_dist` + - `bures_angle` +- Align `steadystate` ODE solver to other methods and improve GPU support. ([#421]) + +## [v0.27.0] +Release date: 2025-02-14 + +- Rename `sparse_to_dense` as `to_dense` and `dense_to_sparse` as `to_sparse`. ([#392]) +- Fix erroneous definition of the stochastic term in `smesolve`. ([#393]) +- Change name of `MultiSiteOperator` to `multisite_operator`. ([#394]) +- Fix `smesolve` for specifying initial state as density matrix. ([#395]) +- Add more generic solver for `steadystate_floquet` to allow more linear solvers. ([#396]) +- Fix time evolution output when using `saveat` keyword argument. ([#398]) +- Align some attributes of `mcsolve`, `ssesolve` and `smesolve` results with `QuTiP`. ([#402]) +- Improve ensemble generation of `ssesolve` and change parameters handling on stochastic processes. ([#403]) +- Set default trajectories to 500 and rename the keyword argument `ensemble_method` to `ensemblealg`. ([#405]) +- Introduce measurement on `ssesolve` and `smesolve`. ([#404]) + +## [v0.26.0] +Release date: 2025-02-09 + +- Fix CUDA `sparse_to_dense`. ([#386]) +- Improve pseudo inverse spectrum solver. ([#388]) +- Add `smesolve` function for stochastic master equation. ([#389]) + +## [v0.25.2] +Release date: 2025-02-02 + +- Move code quality dependencies to separate environment. ([#380]) +- Add additional normalization of the state during time evolution of `ssesolve`. This improves the numerical stability of the solver. ([#383]) + +## [v0.25.1] +Release date: 2025-01-29 + +- Fix Dynamical Fock Dimension states saving due to wrong saving of dimensions. ([#375]) +- Support a list of observables for `expect`. ([#374], [#376]) +- Add checks for `tlist` in time evolution solvers. The checks are to ensure that `tlist` is not empty, the elements are in increasing order, and the elements are unique. ([#378]) + +## [v0.25.0] +Release date: 2025-01-20 + +- Change the structure of block diagonalization functions, using `BlockDiagonalForm` struct and changing the function name from `bdf` to `block_diagonal_form`. ([#349]) +- Add **GPUArrays** compatibility for `ptrace` function, by using **KernelAbstractions.jl**. ([#350]) +- Introduce `Space`, `Dimensions`, `GeneralDimensions` structures to support wider definitions and operations of `Qobj/QobjEvo`, and potential functionalities in the future. ([#271], [#353], [#360]) +- Improve lazy tensor warning for `SciMLOperators`. ([#370]) +- Change order of `AbstractQuantumObject` data type. For example, from `QuantumObject{DataType,ObjType,DimsType}` to `QuantumObject{ObjType,DimsType,DataType}`. ([#371]) + +## [v0.24.0] +Release date: 2024-12-13 + +- Improve the construction of `QobjEvo`. ([#338], [#339]) +- Support `Base.zero` and `Base.one` for `AbstractQuantumObject`. ([#342], [#346]) +- Introduce visualization and function `plot_wigner` for easy plotting of Wigner functions. ([#86], [#292], [#347]) + +## [v0.23.1] +Release date: 2024-12-06 + +- Update `[compat]` to fix the incompatibility between `QuantumToolbox v0.22.0+` and `DiffEqCallbacks < v4.2.1`. ([#335]) + +## [v0.23.0] +Release date: 2024-12-04 + +- Change `SingleSiteOperator` with the more general `MultiSiteOperator`. ([#324]) +- Make `spectrum` and `correlation` functions align with `Python QuTiP`, introduce spectrum solver `PseudoInverse`, remove spectrum solver `FFTCorrelation`, and introduce `spectrum_correlation_fft`. ([#330]) + +## [v0.22.0] +Release date: 2024-11-20 + +- Change the parameters structure of `sesolve`, `mesolve` and `mcsolve` functions to possibly support automatic differentiation. ([#311]) +- Fix type instability and reduce extra memory allocation in `liouvillian`. ([#315], [#318]) + +## [v0.21.5] +Release date: 2024-11-15 + +- This is a demonstration of how to bump version number and also modify `CHANGELOG.md` before new release. ([#309]) + +## [v0.21.4] +Release date: 2024-11-13 + +- This is just a demonstration about [`Changelog.jl`](https://github.com/JuliaDocs/Changelog.jl). ([#139], [#306]) + + + + +[v0.21.4]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.4 +[v0.21.5]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.21.5 +[v0.22.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.22.0 +[v0.23.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.0 +[v0.23.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.1 +[v0.24.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.24.0 +[v0.25.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.0 +[v0.25.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.1 +[v0.25.2]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.25.2 +[v0.26.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.26.0 +[v0.27.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.27.0 +[v0.28.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.28.0 +[v0.29.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.0 +[v0.29.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.29.1 +[v0.30.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.0 +[v0.30.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.30.1 +[v0.31.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.0 +[v0.31.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.31.1 +[v0.32.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.32.0 +[v0.32.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.32.1 +[v0.33.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.33.0 +[v0.34.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.34.0 +[v0.34.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.34.1 +[v0.35.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.35.0 +[v0.36.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.36.0 +[#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86 +[#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139 +[#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271 +[#292]: https://github.com/qutip/QuantumToolbox.jl/issues/292 +[#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306 +[#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309 +[#311]: https://github.com/qutip/QuantumToolbox.jl/issues/311 +[#315]: https://github.com/qutip/QuantumToolbox.jl/issues/315 +[#318]: https://github.com/qutip/QuantumToolbox.jl/issues/318 +[#324]: https://github.com/qutip/QuantumToolbox.jl/issues/324 +[#330]: https://github.com/qutip/QuantumToolbox.jl/issues/330 +[#335]: https://github.com/qutip/QuantumToolbox.jl/issues/335 +[#338]: https://github.com/qutip/QuantumToolbox.jl/issues/338 +[#339]: https://github.com/qutip/QuantumToolbox.jl/issues/339 +[#342]: https://github.com/qutip/QuantumToolbox.jl/issues/342 +[#346]: https://github.com/qutip/QuantumToolbox.jl/issues/346 +[#347]: https://github.com/qutip/QuantumToolbox.jl/issues/347 +[#349]: https://github.com/qutip/QuantumToolbox.jl/issues/349 +[#350]: https://github.com/qutip/QuantumToolbox.jl/issues/350 +[#353]: https://github.com/qutip/QuantumToolbox.jl/issues/353 +[#360]: https://github.com/qutip/QuantumToolbox.jl/issues/360 +[#370]: https://github.com/qutip/QuantumToolbox.jl/issues/370 +[#371]: https://github.com/qutip/QuantumToolbox.jl/issues/371 +[#374]: https://github.com/qutip/QuantumToolbox.jl/issues/374 +[#375]: https://github.com/qutip/QuantumToolbox.jl/issues/375 +[#376]: https://github.com/qutip/QuantumToolbox.jl/issues/376 +[#378]: https://github.com/qutip/QuantumToolbox.jl/issues/378 +[#380]: https://github.com/qutip/QuantumToolbox.jl/issues/380 +[#383]: https://github.com/qutip/QuantumToolbox.jl/issues/383 +[#386]: https://github.com/qutip/QuantumToolbox.jl/issues/386 +[#388]: https://github.com/qutip/QuantumToolbox.jl/issues/388 +[#389]: https://github.com/qutip/QuantumToolbox.jl/issues/389 +[#392]: https://github.com/qutip/QuantumToolbox.jl/issues/392 +[#393]: https://github.com/qutip/QuantumToolbox.jl/issues/393 +[#394]: https://github.com/qutip/QuantumToolbox.jl/issues/394 +[#395]: https://github.com/qutip/QuantumToolbox.jl/issues/395 +[#396]: https://github.com/qutip/QuantumToolbox.jl/issues/396 +[#398]: https://github.com/qutip/QuantumToolbox.jl/issues/398 +[#402]: https://github.com/qutip/QuantumToolbox.jl/issues/402 +[#403]: https://github.com/qutip/QuantumToolbox.jl/issues/403 +[#404]: https://github.com/qutip/QuantumToolbox.jl/issues/404 +[#405]: https://github.com/qutip/QuantumToolbox.jl/issues/405 +[#408]: https://github.com/qutip/QuantumToolbox.jl/issues/408 +[#410]: https://github.com/qutip/QuantumToolbox.jl/issues/410 +[#411]: https://github.com/qutip/QuantumToolbox.jl/issues/411 +[#413]: https://github.com/qutip/QuantumToolbox.jl/issues/413 +[#414]: https://github.com/qutip/QuantumToolbox.jl/issues/414 +[#416]: https://github.com/qutip/QuantumToolbox.jl/issues/416 +[#418]: https://github.com/qutip/QuantumToolbox.jl/issues/418 +[#419]: https://github.com/qutip/QuantumToolbox.jl/issues/419 +[#420]: https://github.com/qutip/QuantumToolbox.jl/issues/420 +[#421]: https://github.com/qutip/QuantumToolbox.jl/issues/421 +[#423]: https://github.com/qutip/QuantumToolbox.jl/issues/423 +[#428]: https://github.com/qutip/QuantumToolbox.jl/issues/428 +[#430]: https://github.com/qutip/QuantumToolbox.jl/issues/430 +[#436]: https://github.com/qutip/QuantumToolbox.jl/issues/436 +[#437]: https://github.com/qutip/QuantumToolbox.jl/issues/437 +[#438]: https://github.com/qutip/QuantumToolbox.jl/issues/438 +[#440]: https://github.com/qutip/QuantumToolbox.jl/issues/440 +[#443]: https://github.com/qutip/QuantumToolbox.jl/issues/443 +[#448]: https://github.com/qutip/QuantumToolbox.jl/issues/448 +[#450]: https://github.com/qutip/QuantumToolbox.jl/issues/450 +[#453]: https://github.com/qutip/QuantumToolbox.jl/issues/453 +[#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455 +[#456]: https://github.com/qutip/QuantumToolbox.jl/issues/456 +[#460]: https://github.com/qutip/QuantumToolbox.jl/issues/460 +[#470]: https://github.com/qutip/QuantumToolbox.jl/issues/470 +[#472]: https://github.com/qutip/QuantumToolbox.jl/issues/472 +[#473]: https://github.com/qutip/QuantumToolbox.jl/issues/473 +[#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476 +[#480]: https://github.com/qutip/QuantumToolbox.jl/issues/480 +[#485]: https://github.com/qutip/QuantumToolbox.jl/issues/485 +[#486]: https://github.com/qutip/QuantumToolbox.jl/issues/486 +[#487]: https://github.com/qutip/QuantumToolbox.jl/issues/487 +[#489]: https://github.com/qutip/QuantumToolbox.jl/issues/489 +[#494]: https://github.com/qutip/QuantumToolbox.jl/issues/494 +[#500]: https://github.com/qutip/QuantumToolbox.jl/issues/500 +[#501]: https://github.com/qutip/QuantumToolbox.jl/issues/501 +[#504]: https://github.com/qutip/QuantumToolbox.jl/issues/504 +[#506]: https://github.com/qutip/QuantumToolbox.jl/issues/506 +[#507]: https://github.com/qutip/QuantumToolbox.jl/issues/507 +[#509]: https://github.com/qutip/QuantumToolbox.jl/issues/509 +[#512]: https://github.com/qutip/QuantumToolbox.jl/issues/512 +[#513]: https://github.com/qutip/QuantumToolbox.jl/issues/513 +[#515]: https://github.com/qutip/QuantumToolbox.jl/issues/515 +[#517]: https://github.com/qutip/QuantumToolbox.jl/issues/517 +[#520]: https://github.com/qutip/QuantumToolbox.jl/issues/520 +[#531]: https://github.com/qutip/QuantumToolbox.jl/issues/531 +[#536]: https://github.com/qutip/QuantumToolbox.jl/issues/536 +[#537]: https://github.com/qutip/QuantumToolbox.jl/issues/537 +[#539]: https://github.com/qutip/QuantumToolbox.jl/issues/539 +[#544]: https://github.com/qutip/QuantumToolbox.jl/issues/544 +[#546]: https://github.com/qutip/QuantumToolbox.jl/issues/546 +[#552]: https://github.com/qutip/QuantumToolbox.jl/issues/552 +[#555]: https://github.com/qutip/QuantumToolbox.jl/issues/555 diff --git a/CITATION.bib b/CITATION.bib new file mode 100644 index 000000000..265f6af66 --- /dev/null +++ b/CITATION.bib @@ -0,0 +1,13 @@ +@article{QuantumToolbox.jl2025, + title = {Quantum{T}oolbox.jl: {A}n efficient {J}ulia framework for simulating open quantum systems}, + author = {Mercurio, Alberto and Huang, Yi-Te and Cai, Li-Xun and Chen, Yueh-Nan and Savona, Vincenzo and Nori, Franco}, + journal = {{Quantum}}, + issn = {2521-327X}, + publisher = {{Verein zur F{\"{o}}rderung des Open Access Publizierens in den Quantenwissenschaften}}, + volume = {9}, + pages = {1866}, + month = sep, + year = {2025}, + doi = {10.22331/q-2025-09-29-1866}, + url = {https://doi.org/10.22331/q-2025-09-29-1866} +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..60a1f8050 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +JULIA:=julia + +default: help + +setup: + ${JULIA} -e 'import Pkg; Pkg.add(["JuliaFormatter", "Changelog"])' + +format: + ${JULIA} -e 'using JuliaFormatter; format(".")' + +changelog: + ${JULIA} -e 'using Changelog; Changelog.generate(Changelog.CommonMark(), "CHANGELOG.md"; repo = "qutip/QuantumToolbox.jl")' + +test: + ${JULIA} --project -e 'using Pkg; Pkg.update(); Pkg.test()' + +docs: + ${JULIA} --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.update()' + ${JULIA} --project=docs docs/make.jl + +vitepress: + npm --prefix docs run docs:dev + +all: setup format changelog test docs vitepress + +help: + @echo "The following make commands are available:" + @echo " - make setup: install the dependencies for make command" + @echo " - make format: format codes with JuliaFormatter" + @echo " - make changelog: generate changelog" + @echo " - make test: run the tests" + @echo " - make docs: instantiate and build the documentation" + @echo " - make vitepress: start Vitepress site of documentation" + @echo " - make all: run every commands in the above order" + +.PHONY: default setup format changelog test docs vitepress all help diff --git a/Project.toml b/Project.toml index 63533afb2..142d7bf27 100644 --- a/Project.toml +++ b/Project.toml @@ -1,60 +1,71 @@ name = "QuantumToolbox" uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" -authors = ["Alberto Mercurio", "Luca Gravina", "Yi-Te Huang"] -version = "0.13.1" +authors = ["Alberto Mercurio", "Yi-Te Huang"] +version = "0.36.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def" +DiffEqNoiseProcess = "77a26b50-5914-5dd7-bc55-306e6241c503" +Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" IncompleteLU = "40713840-3770-5561-ab4c-a76e7d0d7895" +LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae" OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8" OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -Reexport = "189a3867-3050-52da-a836-e630ba90ab69" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" [weakdeps] CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" +KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" [extensions] QuantumToolboxCUDAExt = "CUDA" +QuantumToolboxChainRulesCoreExt = "ChainRulesCore" +QuantumToolboxGPUArraysExt = ["GPUArrays", "KernelAbstractions"] +QuantumToolboxMakieExt = "Makie" [compat] ArrayInterface = "6, 7" CUDA = "5" +ChainRulesCore = "1" DiffEqBase = "6" -DiffEqCallbacks = "2 - 3.1, 3.8" +DiffEqCallbacks = "4.2.1 - 4" +DiffEqNoiseProcess = "5" +Distributed = "1" FFTW = "1.5" +GPUArrays = "10, 11" Graphs = "1.7" IncompleteLU = "0.2" -LinearAlgebra = "<0.0.1, 1" -LinearSolve = "2" +KernelAbstractions = "0.9.2" +LaTeXStrings = "1.2" +LinearAlgebra = "1" +LinearSolve = "2, 3" +Makie = "0.24" OrdinaryDiffEqCore = "1" OrdinaryDiffEqTsit5 = "1" -Pkg = "<0.0.1, 1" -Random = "<0.0.1, 1" -Reexport = "1" -SciMLBase = "2" -SciMLOperators = "0.3" -SparseArrays = "<0.0.1, 1" +Pkg = "1" +Random = "1" +SciMLBase = "2.105" +SciMLOperators = "1.4" +SparseArrays = "1" SpecialFunctions = "2" StaticArraysCore = "1" -Test = "<0.0.1, 1" +Statistics = "1" +StochasticDiffEq = "6" julia = "1.10" - -[extras] -CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[targets] -test = ["Test"] diff --git a/README.md b/README.md index 369f9cec2..c9935660a 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ # QuantumToolbox.jl -[A. Mercurio](https://github.com/albertomercurio), -[L. Gravina](https://github.com/lgravina1997), +[A. Mercurio](https://github.com/albertomercurio) and [Y.-T. Huang](https://github.com/ytdHuang). -| **Release** | [![Release][release-img]][release-url] [![License][license-img]][license-url] [![DOI][doi-img]][doi-url] [![Downloads][download-img]][download-url] | +| **Release** | [![Release][release-img]][release-url] [![License][license-img]][license-url] [![Cite][cite-img]][cite-url] [![Downloads][download-img]][download-url] | |:-----------------:|:-------------| -| **Runtests** | [![Runtests][runtests-img]][runtests-url] [![Coverage][codecov-img]][codecov-url] [![Aqua QA][aqua-img]][aqua-url] [![JET][jet-img]][jet-url] | +| **Runtests** | [![Runtests][runtests-img]][runtests-url] [![Coverage][codecov-img]][codecov-url] | +| **Code Quality** | [![Code Quality][code-quality-img]][code-quality-url] [![Aqua QA][aqua-img]][aqua-url] [![JET][jet-img]][jet-url] | | **Documentation** | [![Doc-Stable][docs-stable-img]][docs-stable-url] [![Doc-Dev][docs-develop-img]][docs-develop-url] | | **Benchmark** | [![Benchmarks][benchmark-img]][benchmark-url] | | **Support** | [![Unitary Fund](https://img.shields.io/badge/Supported%20By-UNITARY%20FUND-brightgreen.svg?style=for-the-badge)](https://unitary.fund) | @@ -24,8 +24,8 @@ and [Y.-T. Huang](https://github.com/ytdHuang). [license-img]: https://img.shields.io/badge/license-New%20BSD-blue.svg [license-url]: https://opensource.org/licenses/BSD-3-Clause -[doi-img]: https://zenodo.org/badge/DOI/10.5281/zenodo.10822816.svg -[doi-url]: https://doi.org/10.5281/zenodo.10822816 +[cite-img]: https://img.shields.io/badge/cite-Quantum_9%2C_1866_(2025)-blue +[cite-url]: https://doi.org/10.22331/q-2025-09-29-1866 [download-img]: https://img.shields.io/badge/dynamic/json?url=http%3A%2F%2Fjuliapkgstats.com%2Fapi%2Fv1%2Ftotal_downloads%2FQuantumToolbox&query=total_requests&label=Downloads [download-url]: https://juliapkgstats.com/pkg/QuantumToolbox @@ -36,6 +36,9 @@ and [Y.-T. Huang](https://github.com/ytdHuang). [codecov-img]: https://codecov.io/gh/qutip/QuantumToolbox.jl/branch/main/graph/badge.svg [codecov-url]: https://codecov.io/gh/qutip/QuantumToolbox.jl +[code-quality-img]: https://github.com/qutip/QuantumToolbox.jl/actions/workflows/Code-Quality.yml/badge.svg +[code-quality-url]: https://github.com/qutip/QuantumToolbox.jl/actions/workflows/Code-Quality.yml + [aqua-img]: https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg [aqua-url]: https://github.com/JuliaTesting/Aqua.jl @@ -52,30 +55,32 @@ and [Y.-T. Huang](https://github.com/ytdHuang). ## Introduction -[QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge Julia package designed for quantum physics simulations, closely emulating the popular Python [QuTiP](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of Julia with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. +[QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge [`Julia`](https://julialang.org/) package designed for quantum physics simulations, closely emulating the popular Python [`QuTiP`](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of [`Julia`](https://julialang.org/) with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. *With this package, moving from Python to Julia for quantum physics simulations has never been easier*, due to the similar syntax and functionalities. ## Features -QuantumToolbox.jl is equipped with a robust set of features: +`QuantumToolbox.jl` is equipped with a robust set of features: -- **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as QuTiP. -- **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) package. -- **GPU Computing:** Leverage GPU resources for high-performance computing. For example, you run the master equation direclty on the GPU with the same syntax as the CPU case. -- **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. -- **Easy Extension:** Easily extend the package, taking advantage of the Julia language features, like multiple dispatch and metaprogramming. +- **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as `QuTiP`. +- **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [`DifferentialEquations.jl`](https://github.com/SciML/DifferentialEquations.jl) package. +- **GPU Computing:** Leverage GPU resources for high-performance computing. Simulate quantum dynamics directly on the GPU with the same syntax as the CPU case. +- **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. See [here](https://qutip.org/QuantumToolbox.jl/stable/users_guide/cluster) for more information. +- **Differentiable Programming:** Enable gradient-based optimization for quantum algorithms. Compute gradients of quantum dynamics with respect to their parameters using automatic differentiation. See [here](https://qutip.org/QuantumToolbox.jl/stable/users_guide/autodiff) for more information. +- **Easy Extension:** Easily extend the package, taking advantage of the `Julia` language features, like multiple dispatch and metaprogramming. ## Installation -> **_NOTE:_** `QuantumToolbox.jl` requires `Julia 1.10+`. +> [!NOTE] +> `QuantumToolbox.jl` requires `Julia 1.10+`. To install `QuantumToolbox.jl`, run the following commands inside Julia's interactive session (also known as REPL): ```julia using Pkg Pkg.add("QuantumToolbox") ``` -Alternatively, this can also be done in Julia's [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: +Alternatively, this can also be done in `Julia`'s [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: ```julia-repl (1.10) pkg> add QuantumToolbox ``` @@ -90,7 +95,7 @@ QuantumToolbox.about() ## Brief Example -We now provide a brief example to demonstrate the similarity between [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) and [QuTiP](https://github.com/qutip/qutip). +We now provide a brief example to demonstrate the similarity between [`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) and [`QuTiP`](https://github.com/qutip/qutip). Let's consider a quantum harmonic oscillator with a Hamiltonian given by: @@ -144,8 +149,6 @@ We can extract the expectation value of the number operator $\hat{a}^\dagger \ha We can easily pass the computation to the GPU, by simply passing all the `Qobj`s to the GPU: -> **_NOTE:_** The described feature requires `Julia 1.9+`. - ```julia using QuantumToolbox using CUDA @@ -162,3 +165,56 @@ e_ops = [a_gpu' * a_gpu] sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) ``` + +## Performance comparison with other packages + +Here we provide a brief performance comparison between `QuantumToolbox.jl` and other popular quantum physics simulation packages, such as [`QuTiP`](https://github.com/qutip/qutip) (Python), [`dynamiqs`](https://github.com/dynamiqs/dynamiqs) (Python - JAX) and [`QuantumOptics.jl`](https://github.com/qojulia/QuantumOptics.jl) (Julia). We clearly show that `QuantumToolbox.jl` is the fastest package among the four. A detailed code is available [here](https://github.com/albertomercurio/QuantumToolbox.jl-Paper-Figures/blob/main/src/benchmarks/benchmarks.jl). + +![](https://raw.githubusercontent.com/albertomercurio/QuantumToolbox.jl-Paper-Figures/refs/heads/main/figures/benchmarks.svg) + +## Contributing to QuantumToolbox.jl + +You are most welcome to contribute to `QuantumToolbox.jl` development by forking this repository and sending pull requests (PRs), or filing bug reports at the issues page. You can also help out with users' questions, or discuss proposed changes in the [QuTiP discussion group](https://groups.google.com/g/qutip). + +For more information about contribution, including technical advice, please see the [Contributing to Quantum Toolbox in Julia](https://qutip.org/QuantumToolbox.jl/stable/resources/contributing). + +## Cite `QuantumToolbox.jl` +If you like `QuantumToolbox.jl`, we would appreciate it if you starred the repository in order to help us increase its visibility. Furthermore, if you find the framework useful in your research, we would be grateful if you could cite our publication [ [Quantum 9, 1866 (2025)](https://doi.org/10.22331/q-2025-09-29-1866) ] using the following bibtex entry: + +```bib +@article{QuantumToolbox.jl2025, + title = {Quantum{T}oolbox.jl: {A}n efficient {J}ulia framework for simulating open quantum systems}, + author = {Mercurio, Alberto and Huang, Yi-Te and Cai, Li-Xun and Chen, Yueh-Nan and Savona, Vincenzo and Nori, Franco}, + journal = {{Quantum}}, + issn = {2521-327X}, + publisher = {{Verein zur F{\"{o}}rderung des Open Access Publizierens in den Quantenwissenschaften}}, + volume = {9}, + pages = {1866}, + month = sep, + year = {2025}, + doi = {10.22331/q-2025-09-29-1866}, + url = {https://doi.org/10.22331/q-2025-09-29-1866} +} +``` + +## Acknowledgements + +### Fundings + +`QuantumToolbox.jl` is supported by the [Unitary Fund](https://unitary.fund), a grant program for quantum technology projects. + +
+ + Unitary Fund logo + +
+ +### Other Acknowledgements + +We are also grateful to the [Zulip](https://zulip.com) team for providing a free chat service for open-source projects. + +
+ + Zulip logo + +
diff --git a/benchmarks/correlations_and_spectrum.jl b/benchmarks/correlations_and_spectrum.jl index a5eab65a8..d3ae9e916 100644 --- a/benchmarks/correlations_and_spectrum.jl +++ b/benchmarks/correlations_and_spectrum.jl @@ -1,3 +1,9 @@ +function _calculate_fft_spectrum(H, tlist, c_ops, A, B) + corr = correlation_2op_1t(H, nothing, tlist, c_ops, A, B; progress_bar = Val(false)) + ωlist, spec = spectrum_correlation_fft(tlist, corr) + return nothing +end + function benchmark_correlations_and_spectrum!(SUITE) N = 15 ω = 1 @@ -9,11 +15,23 @@ function benchmark_correlations_and_spectrum!(SUITE) c_ops = [sqrt(γ * (nth + 1)) * a, sqrt(γ * nth) * a'] ω_l = range(0, 3, length = 1000) + t_l = range(0, 333 * π, length = 1000) + + PI_solver = PseudoInverse() + + L_solver = Lanczos() SUITE["Correlations and Spectrum"]["FFT Correlation"] = - @benchmarkable spectrum($H, $ω_l, $(a'), $a, $c_ops, solver = FFTCorrelation(), progress_bar = false) + @benchmarkable _calculate_fft_spectrum($H, $t_l, $c_ops, $(a'), $a) + + SUITE["Correlations and Spectrum"]["Spectrum"]["Exponential Series"] = + @benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a) + + SUITE["Correlations and Spectrum"]["Spectrum"]["Pseudo Inverse"] = + @benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a, solver = $PI_solver) - SUITE["Correlations and Spectrum"]["Exponential Series"] = @benchmarkable spectrum($H, $ω_l, $(a'), $a, $c_ops) + SUITE["Correlations and Spectrum"]["Spectrum"]["Lanczos"] = + @benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a, solver = $L_solver) return nothing end diff --git a/benchmarks/eigenvalues.jl b/benchmarks/eigenvalues.jl index d6143b9a2..3fab5302d 100644 --- a/benchmarks/eigenvalues.jl +++ b/benchmarks/eigenvalues.jl @@ -9,14 +9,15 @@ function benchmark_eigenvalues!(SUITE) ωb = 1 g = 0.2 κ = 0.01 - n_thermal = 0.1 + n_th = 0.1 H = ωc * a_d * a + ωb * b_d * b + g * (a + a_d) * (b + b_d) - c_ops = [√((1 + n_thermal) * κ) * a, √κ * b, √(n_thermal * κ) * a_d] + c_ops = [√((1 + n_th) * κ) * a, √κ * b, √(n_th * κ) * a_d] L = liouvillian(H, c_ops) SUITE["Eigenvalues"]["eigenstates"]["dense"] = @benchmarkable eigenstates($L) - SUITE["Eigenvalues"]["eigenstates"]["sparse"] = @benchmarkable eigenstates($L, sparse = true, sigma = 0.01, k = 5) + SUITE["Eigenvalues"]["eigenstates"]["sparse"] = + @benchmarkable eigenstates($L, sparse = true, sigma = 0.01, eigvals = 5) return nothing end diff --git a/benchmarks/runbenchmarks.jl b/benchmarks/runbenchmarks.jl index e921c81a4..a8419c516 100644 --- a/benchmarks/runbenchmarks.jl +++ b/benchmarks/runbenchmarks.jl @@ -1,4 +1,6 @@ using BenchmarkTools +using LinearAlgebra +using SparseArrays using QuantumToolbox using OrdinaryDiffEq using LinearSolve diff --git a/benchmarks/timeevolution.jl b/benchmarks/timeevolution.jl index da9919dc3..189c7beaf 100644 --- a/benchmarks/timeevolution.jl +++ b/benchmarks/timeevolution.jl @@ -47,20 +47,110 @@ function benchmark_timeevolution!(SUITE) $ψ0, $tlist, $c_ops, - n_traj = 100, + ntraj = 100, e_ops = $e_ops, progress_bar = Val(false), - ensemble_method = EnsembleSerial(), + ensemblealg = EnsembleSerial(), ) SUITE["Time Evolution"]["time-independent"]["mcsolve"]["Multithreaded"] = @benchmarkable mcsolve( $H, $ψ0, $tlist, $c_ops, - n_traj = 100, + ntraj = 100, e_ops = $e_ops, progress_bar = Val(false), - ensemble_method = EnsembleThreads(), + ensemblealg = EnsembleThreads(), + ) + + ## Time-dependent evolutions ## + + # Hamiltonian in the lab frame (without drive frame transformation) + H_lab = ωc * a' * a + ωq / 2 * σz + g * (a' * σm + a * σm') + + # Define time-dependent drive terms + coef1(p, t) = p.F * exp(1im * p.ωd * t) + coef2(p, t) = p.F * exp(-1im * p.ωd * t) + p = (F = F, ωd = ωd) + + # Time-dependent Hamiltonian as tuple (lab frame with drive) + H_td = (H_lab, (a, coef1), (a', coef2)) + + # Time-dependent Hamiltonian as QobjEvo + H_td2 = QobjEvo(H_td) + + # Time-dependent Liouvillian + L_td = liouvillian(H_td2) + + tlist_td = range(0, 10 / γ, 100) + + ## sesolve (time-dependent) ## + + SUITE["Time Evolution"]["time-dependent"]["sesolve"]["Tuple"] = + @benchmarkable sesolve($H_td, $ψ0, $tlist_td, e_ops = $e_ops, progress_bar = Val(false), params = $p) + + SUITE["Time Evolution"]["time-dependent"]["sesolve"]["QobjEvo"] = + @benchmarkable sesolve($H_td2, $ψ0, $tlist_td, e_ops = $e_ops, progress_bar = Val(false), params = $p) + + ## mesolve (time-dependent) ## + + SUITE["Time Evolution"]["time-dependent"]["mesolve"]["Tuple"] = + @benchmarkable mesolve($H_td, $ψ0, $tlist_td, $c_ops, e_ops = $e_ops, progress_bar = Val(false), params = $p) + + SUITE["Time Evolution"]["time-dependent"]["mesolve"]["QobjEvo"] = + @benchmarkable mesolve($H_td2, $ψ0, $tlist_td, $c_ops, e_ops = $e_ops, progress_bar = Val(false), params = $p) + + SUITE["Time Evolution"]["time-dependent"]["mesolve"]["Liouvillian"] = + @benchmarkable mesolve($L_td, $ψ0, $tlist_td, $c_ops, e_ops = $e_ops, progress_bar = Val(false), params = $p) + + ## mcsolve (time-dependent) ## + + SUITE["Time Evolution"]["time-dependent"]["mcsolve"]["Tuple"]["Serial"] = @benchmarkable mcsolve( + $H_td, + $ψ0, + $tlist_td, + $c_ops, + ntraj = 100, + e_ops = $e_ops, + progress_bar = Val(false), + params = $p, + ensemblealg = EnsembleSerial(), + ) + + SUITE["Time Evolution"]["time-dependent"]["mcsolve"]["Tuple"]["Multithreaded"] = @benchmarkable mcsolve( + $H_td, + $ψ0, + $tlist_td, + $c_ops, + ntraj = 100, + e_ops = $e_ops, + progress_bar = Val(false), + params = $p, + ensemblealg = EnsembleThreads(), + ) + + SUITE["Time Evolution"]["time-dependent"]["mcsolve"]["QobjEvo"]["Serial"] = @benchmarkable mcsolve( + $H_td2, + $ψ0, + $tlist_td, + $c_ops, + ntraj = 100, + e_ops = $e_ops, + progress_bar = Val(false), + params = $p, + ensemblealg = EnsembleSerial(), + ) + + SUITE["Time Evolution"]["time-dependent"]["mcsolve"]["QobjEvo"]["Multithreaded"] = @benchmarkable mcsolve( + $H_td2, + $ψ0, + $tlist_td, + $c_ops, + ntraj = 100, + e_ops = $e_ops, + progress_bar = Val(false), + params = $p, + ensemblealg = EnsembleThreads(), ) return nothing diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..b3c4f480f --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,5 @@ +build/ +node_modules/ +package-lock.json +Manifest.toml +src/resources/changelog.md \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index 08fe32855..3fc508c90 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,4 +1,15 @@ [deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Changelog = "5217a498-cd5d-4ec6-b8c2-9b85a09b6e3e" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab" +SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[compat] +DocumenterVitepress = "0.2" diff --git a/docs/README.md b/docs/README.md index e7c090ca1..c8d7ae7e8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,18 +1,44 @@ -# How to build documentation locally ? +# How to build documentation and start Vitepress site locally ? ## Working Directory All the commands should be run under the root folder of the package: `/path/to/QuantumToolbox.jl/` -## Build Pkg +The document pages will be generated in the directory: `/path/to/QuantumToolbox.jl/docs/build/1/` (which is ignored by git). + +## Method 1: Run with `make` command +Run the following command to instantiate and build the documentation: +> [!NOTE] +> You need to install `Node.js` and `npm` first. +```shell +make docs +``` + +Run the following command to start a local Vitepress site: +```shell +make vitepress +``` +This will start a local Vitepress site of documentation at [http://localhost:5173](http://localhost:5173) in your computer. + +## Method 2: Run commands manually + +### Build Pkg Run the following command: ```shell julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' ``` > **_NOTE:_** `Pkg.develop(PackageSpec(path=pwd()))` adds the local version of `QuantumToolbox` as dev-dependency instead of pulling from the registered version. -## Build Documentation +### Build Documentation Run the following command: +> [!NOTE] +> You need to install `Node.js` and `npm` first. ```shell julia --project=docs docs/make.jl ``` -The document pages will be generated in the directory: `/path/to/QuantumToolbox.jl/docs/build/` (which is ignored by git). \ No newline at end of file + +### Start a local Vitepress site +Run the following command: +```shell +npm --prefix docs run docs:dev +``` +This will start a local Vitepress site of documentation at [http://localhost:5173](http://localhost:5173) in your computer. \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index fccd116db..d9ac8be98 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -3,70 +3,108 @@ using QuantumToolbox using Documenter +using DocumenterVitepress +using DocumenterCitations +using Changelog -DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive = true) +# Load of packages required to compile the extension documentation +using CairoMakie -const MathEngine = MathJax3( - Dict( - :loader => Dict("load" => ["[tex]/physics"]), - :tex => Dict( - "inlineMath" => [["\$", "\$"], ["\\(", "\\)"]], - "tags" => "ams", - "packages" => ["base", "ams", "autoload", "physics"], - ), - ) +doctest_setup = quote + using LinearAlgebra + using SparseArrays + using QuantumToolbox +end +DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, doctest_setup; recursive = true) + +# some options for `makedocs` +const DRAFT = get(ENV, "DRAFT", false) == "true" # `DRAFT = true` disables cell evaluation +const DOCTEST = get(ENV, "DOCTEST", true) == true # `DOCTEST = false` skips doc tests + +# generate bibliography +bib = CitationBibliography( + joinpath(@__DIR__, "src", "resources", "bibliography.bib"), + style=:authoryear, +) + +# generate changelog +Changelog.generate( + Changelog.Documenter(), + joinpath(@__DIR__, "..", "CHANGELOG.md"), + joinpath(@__DIR__, "src", "resources", "changelog.md"); + repo = "qutip/QuantumToolbox.jl", ) const PAGES = [ + "Home" => "index.md", "Getting Started" => [ - "Introduction" => "index.md", - "Key differences from QuTiP" => "qutip_differences.md", - # "Cite QuantumToolbox.jl" => "cite.md", + "Brief Example" => "getting_started/brief_example.md", + # "Key differences from QuTiP" => "getting_started/qutip_differences.md", + "The Importance of Type-Stability" => "getting_started/type_stability.md", + "Example: Create QuantumToolbox.jl Logo" => "getting_started/logo.md", + "Cite QuantumToolbox.jl" => "getting_started/cite.md", ], "Users Guide" => [ "Basic Operations on Quantum Objects" => [ - "users_guide/QuantumObject/QuantumObject.md", - "users_guide/QuantumObject/QuantumObject_functions.md", + "Quantum Objects (Qobj)" => "users_guide/QuantumObject/QuantumObject.md", + "Functions operating on Qobj" => "users_guide/QuantumObject/QuantumObject_functions.md", ], - "The Importance of Type-Stability" => "users_guide/type_stability.md", "Manipulating States and Operators" => "users_guide/states_and_operators.md", "Tensor Products and Partial Traces" => "users_guide/tensor.md", "Time Evolution and Dynamics" => [ "Introduction" => "users_guide/time_evolution/intro.md", + "Time Evolution Solutions" => "users_guide/time_evolution/solution.md", + "Schrödinger Equation Solver" => "users_guide/time_evolution/sesolve.md", + "Lindblad Master Equation Solver" => "users_guide/time_evolution/mesolve.md", + "Monte Carlo Solver" => "users_guide/time_evolution/mcsolve.md", + "Stochastic Solver" => "users_guide/time_evolution/stochastic.md", + "Solving Problems with Time-dependent Hamiltonians" => "users_guide/time_evolution/time_dependent.md", + "Bloch-Redfield master equation" => "users_guide/time_evolution/brmesolve.md", ], - "Solving for Steady-State Solutions" => [], - "Symmetries" => [], - "Two-time correlation functions" => [], + "Automatic Differentiation" => "users_guide/autodiff.md", + "Intensive parallelization on a Cluster" => "users_guide/cluster.md", + "Hierarchical Equations of Motion" => "users_guide/HEOM.md", + "Solving for Steady-State Solutions" => "users_guide/steadystate.md", + "Two-time correlation functions" => "users_guide/two_time_corr_func.md", + "Plotting on the Bloch Sphere" => "users_guide/plotting_the_bloch_sphere.md", + "QuantumToolbox Settings" => "users_guide/settings.md", "Extensions" => [ - "users_guide/extensions/cuda.md", + "Extension for CUDA.jl" => "users_guide/extensions/cuda.md", + "Extension for the Makie.jl ecosystem" => "users_guide/extensions/cairomakie.md", ], ], - "Tutorials" => [ - "Time Evolution" => [ - "Low Rank Master Equation" => "tutorials/lowrank.md", - ], - "Miscellaneous Tutorials" => [ - "tutorials/logo.md", - ], + "Resources" => [ + "API" => "resources/api.md", + "Bibliography" => "resources/bibliography.md", + "ChangeLog" => "resources/changelog.md", + "Contributing to QuantumToolbox.jl" => "resources/contributing.md", + "Acknowledgements" => "resources/acknowledgements.md", ], - "API" => "api.md", - # "Change Log" => "changelog.md", ] makedocs(; - modules = [QuantumToolbox], - authors = "Alberto Mercurio, Luca Gravina and Yi-Te Huang", + modules = [ + QuantumToolbox, + Base.get_extension(QuantumToolbox, :QuantumToolboxMakieExt), + ], + authors = "Alberto Mercurio and Yi-Te Huang", repo = Remotes.GitHub("qutip", "QuantumToolbox.jl"), sitename = "QuantumToolbox.jl", pages = PAGES, - format = Documenter.HTML(; - prettyurls = get(ENV, "CI", "false") == "true", - canonical = "https://qutip.github.io/QuantumToolbox.jl", - edit_link = "main", - assets = ["assets/favicon.ico"], - mathengine = MathEngine, - size_threshold_ignore = ["api.md"], - ) + format = DocumenterVitepress.MarkdownVitepress( + repo = "github.com/qutip/QuantumToolbox.jl", + devbranch = "main", + devurl = "dev", + ), + draft = DRAFT, + doctest = DOCTEST, + plugins = [bib], ) -deploydocs(; repo = "github.com/qutip/QuantumToolbox.jl", devbranch = "main") +DocumenterVitepress.deploydocs(; + repo = "github.com/qutip/QuantumToolbox.jl", + target = joinpath(@__DIR__, "build"), + devbranch = "main", + branch = "gh-pages", + push_preview = true, +) diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..0ed871854 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,15 @@ +{ + "scripts": { + "docs:dev": "vitepress dev build/.documenter", + "docs:build": "vitepress build build/.documenter", + "docs:preview": "vitepress preview build/.documenter" + }, + "dependencies": { + "@nolebase/vitepress-plugin-enhanced-readabilities": "^2.14.0", + "markdown-it": "^14.1.0", + "markdown-it-footnote": "^4.0.0", + "markdown-it-mathjax3": "^4.3.2", + "vitepress": "^1.6.3", + "vitepress-plugin-tabs": "^0.6.0" + } +} diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts new file mode 100644 index 000000000..18e32b4b5 --- /dev/null +++ b/docs/src/.vitepress/config.mts @@ -0,0 +1,106 @@ +import { defineConfig } from 'vitepress' +import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' +import mathjax3 from "markdown-it-mathjax3"; +import footnote from "markdown-it-footnote"; +import path from 'path' + +function getBaseRepository(base: string): string { + if (!base || base === '/') return '/'; + const parts = base.split('/').filter(Boolean); + return parts.length > 0 ? `/${parts[0]}/` : '/'; +} + +const baseTemp = { + base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! +} + +const navTemp = { + nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS', +} + +const nav = [ + ...navTemp.nav, + { text: 'Tutorials', link: 'https://qutip.org/qutip-julia-tutorials/' }, + { text: 'Benchmarks', link: 'https://qutip.org/QuantumToolbox.jl/benchmarks/' }, + { + component: 'VersionPicker' + } +] + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! + title: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + description: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + lastUpdated: true, + cleanUrls: true, + outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... + head: [ + ['link', { rel: 'icon', href: '/QuantumToolbox.jl/favicon.ico' }], + ['script', {src: `${getBaseRepository(baseTemp.base)}versions.js`}], + // ['script', {src: '/versions.js'], for custom domains, I guess if deploy_url is available. + ['script', {src: `${baseTemp.base}siteinfo.js`}] + ], + + vite: { + define: { + __DEPLOY_ABSPATH__: JSON.stringify('REPLACE_ME_DOCUMENTER_VITEPRESS_DEPLOY_ABSPATH'), + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '../components') + } + }, + optimizeDeps: { + exclude: [ + '@nolebase/vitepress-plugin-enhanced-readabilities/client', + 'vitepress', + '@nolebase/ui', + ], + }, + ssr: { + noExternal: [ + // If there are other packages that need to be processed by Vite, you can add them here. + '@nolebase/vitepress-plugin-enhanced-readabilities', + '@nolebase/ui', + ], + }, + }, + markdown: { + math: true, + + // options for @mdit-vue/plugin-toc + // https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-toc#options + toc: { level: [2, 3, 4] }, // for API page, triggered by: [[toc]] + + config(md) { + md.use(tabsMarkdownPlugin), + md.use(mathjax3), + md.use(footnote) + }, + theme: { + light: "github-light", + dark: "github-dark" + } + }, + themeConfig: { + outline: 'deep', + logo: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + search: { + provider: 'local', + options: { + detailedView: true + } + }, + nav, + sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + editLink: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + socialLinks: [ + { icon: 'github', link: 'REPLACE_ME_DOCUMENTER_VITEPRESS' } + ], + footer: { + message: 'Made with Documenter.jl, VitePress and DocumenterVitepress.jl
Released under the BSD 3-Clause License. Powered by the Julia Programming Language.
', + copyright: `© Copyright ${new Date().getUTCFullYear()} QuTiP.org.` + } + } +}) diff --git a/docs/src/.vitepress/theme/style.css b/docs/src/.vitepress/theme/style.css new file mode 100644 index 000000000..72abe7357 --- /dev/null +++ b/docs/src/.vitepress/theme/style.css @@ -0,0 +1,179 @@ +/* Customize default theme styling by overriding CSS variables: +https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */ +/* Example */ +/* https://github.com/vuejs/vitepress/blob/main/template/.vitepress/theme/style.css */ + +.VPHero .clip { + white-space: pre; + max-width: 600px; +} + +/* Fonts */ +@font-face { + font-family: JuliaMono-Regular; + src: url("https://cdn.jsdelivr.net/gh/cormullion/juliamono/webfonts/JuliaMono-Regular.woff2"); +} + +:root { +/* Typography */ +--vp-font-family-base: "Barlow", "Inter var experimental", "Inter var", + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + +/* Code Snippet font */ +--vp-font-family-mono: JuliaMono-Regular, monospace; +} + +/* Disable contextual alternates (kind of like ligatures but different) in monospace, + which turns `/>` to an up arrow and `|>` (the Julia pipe symbol) to an up arrow as well. */ +.mono-no-substitutions { +font-family: "JuliaMono-Regular", monospace; +font-feature-settings: "calt" off; +} + +.mono-no-substitutions-alt { +font-family: "JuliaMono-Regular", monospace; +font-variant-ligatures: none; +} + +pre, code { +font-family: "JuliaMono-Regular", monospace; +font-feature-settings: "calt" off; +} + +/* Colors */ +:root { + --julia-blue: #4063D8; + --julia-purple: #9558B2; + --julia-red: #CB3C33; + --julia-green: #389826; + + --vp-c-brand: #0087d7; + --vp-c-brand-1: #0890df; + --vp-c-brand-2: #0599ef; + --vp-c-brand-3: #0c9ff4; + --vp-c-brand-light: #0087d7; + --vp-c-brand-dark: #5fd7ff; + --vp-c-brand-dimm: #212425; + + /* Greens */ + --vp-dark-green: #155f3e; /* Main accent green */ + --vp-dark-green-dark: #2b855c; + --vp-dark-green-light: #42d392; + --vp-dark-green-lighter: #35eb9a; + /* Complementary Colors */ + --vp-dark-gray: #1e1e1e; + --vp-dark-gray-soft: #2a2a2a; + --vp-dark-gray-mute: #242424; + --vp-light-gray: #d1d5db; + --vp-tip-bg: rgb(254, 254, 254); + + /* Text Colors */ + --vp-dark-text: #e5e5e5; /* Primary text color */ + --vp-dark-subtext: #c1c1c1; /* Subtle text */ + --vp-source-text: #e5e5e5; + /* custom tip */ + --vp-custom-block-tip-border: var(--vp-c-brand-light); + --vp-custom-block-tip-bg: var(--vp-tip-bg); +} + + /* Component: Button */ +:root { + --vp-button-brand-border: var(--vp-light-gray); + --vp-button-brand-bg: var(--vp-c-brand-light); + --vp-button-brand-hover-border: var(--vp-c-bg-alt); + --vp-button-brand-hover-bg: var(--julia-blue); +} + +/* Component: Home */ +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #9558B2 30%, + #CB3C33 + ); + + --vp-home-hero-image-background-image: none; /* remove the blur background */ + /* (default setting) + --vp-home-hero-image-background-image: linear-gradient( + -145deg, + #9558b282 30%, + #3798269a 30%, + #cb3d33e3 + ); + */ + --vp-home-hero-image-filter: blur(40px); +} + +/* Hero Section */ +:root.dark { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #9558B2 30%, + #CB3C33 + ); + --vp-home-hero-image-background-image: none; /* remove the blur background */ + /* (default setting) + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + var(--vp-dark-green) 30%, + var(--vp-dark-green-light), + var(--vp-dark-gray) 30% + ); + */ + --vp-home-hero-image-filter: blur(56px); +} + +:root.dark { + /* custom tip */ + --vp-custom-block-tip-border: var(--vp-dark-green-dark); + --vp-custom-block-tip-text: var(--vp-dark-subtext); + --vp-custom-block-tip-bg: var(--vp-dark-gray-mute); +} + +/** + * Colors links + * -------------------------------------------------------------------------- */ + +.dark { + --vp-c-brand: var(--vp-dark-green-light); + --vp-button-brand-border: var(--vp-dark-green-lighter); + --vp-button-brand-bg: var(--vp-dark-green); + --vp-c-brand-1: var(--vp-dark-green-light); + --vp-c-brand-2: var(--vp-dark-green-lighter); + --vp-c-brand-3: var(--vp-dark-green); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(72px); + } +} +/* Component: MathJax */ + +mjx-container > svg { + display: block; + margin: auto; +} + +mjx-container { + padding: 0.5rem 0; +} + +mjx-container { + display: inline; + margin: auto 2px -2px; +} + +mjx-container > svg { + margin: auto; + display: inline-block; +} \ No newline at end of file diff --git a/docs/src/assets/logo.png b/docs/src/assets/logo.png old mode 100755 new mode 100644 diff --git a/docs/src/getting_started/brief_example.md b/docs/src/getting_started/brief_example.md new file mode 100644 index 000000000..b2a280753 --- /dev/null +++ b/docs/src/getting_started/brief_example.md @@ -0,0 +1,86 @@ +```@meta +CurrentModule = QuantumToolbox +``` + +# Brief Example + +We now provide a brief example to demonstrate the similarity between [`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) and [`QuTiP`](https://github.com/qutip/qutip). + +## CPU Computation + +Let's consider a quantum harmonic oscillator with a Hamiltonian given by: + +```math +\hat{H} = \omega \hat{a}^\dagger \hat{a} +``` + +where ``\hat{a}`` and ``\hat{a}^\dagger`` are the annihilation and creation operators, respectively. We can define the Hamiltonian as follows: + +```julia +using QuantumToolbox + +N = 20 # cutoff of the Hilbert space dimension +ω = 1.0 # frequency of the harmonic oscillator + +a = destroy(N) # annihilation operator + +H = ω * a' * a +``` + +We now introduce some losses in a thermal environment, described by the Lindblad master equation: + +```math +\frac{d \hat{\rho}}{dt} = -i [\hat{H}, \hat{\rho}] + \gamma \mathcal{D}[\hat{a}] \hat{\rho} +``` + +where ``\hat{\rho}`` is the density matrix, ``\gamma`` is the damping rate, and ``\mathcal{D}[\hat{a}]`` is the Lindblad dissipator, defined as: + +```math +\mathcal{D}[\hat{a}]\hat{\rho} = \hat{a}\hat{\rho}\hat{a}^\dagger - \frac{1}{2}\hat{a}^\dagger\hat{a}\hat{\rho} - \frac{1}{2}\hat{\rho}\hat{a}^\dagger\hat{a} +``` + +!!! note "Lindblad master equation" + See [here](@ref doc-TE:Lindblad-Master-Equation-Solver) for more details about Lindblad master equation. + +We now compute the time evolution of the system using the [`mesolve`](@ref) function, starting from the initial state ``\ket{\psi (0)} = \ket{3}``: + +```julia +γ = 0.1 # damping rate + +ψ0 = fock(N, 3) # initial state + +tlist = range(0, 10, 100) # time list + +c_ops = [sqrt(γ) * a] +e_ops = [a' * a] + +sol = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops) +``` + +We can extract the expectation value of the number operator ``\hat{a}^\dagger \hat{a}`` with the command `sol.expect`, and the states with the command `sol.states`. + +## GPU Computation + +!!! note "Extension for CUDA.jl" + `QuantumToolbox.jl` provides an extension to support GPU computation. To trigger the extension, you need to install and import [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl) together with `QuantumToolbox.jl`. See [here](@ref doc:CUDA) for more details. + +```julia +using QuantumToolbox +using CUDA +CUDA.allowscalar(false) # Avoid unexpected scalar indexing +``` + +We can easily pass the computation to the GPU, by simply passing all the [`QuantumObject`](@ref)s to the GPU: + +```julia +a_gpu = cu(destroy(N)) # The only difference in the code is the cu() function + +H_gpu = ω * a_gpu' * a_gpu + +ψ0_gpu = cu(fock(N, 3)) + +c_ops = [sqrt(γ) * a_gpu] +e_ops = [a_gpu' * a_gpu] + +sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) +``` \ No newline at end of file diff --git a/docs/src/getting_started/cite.md b/docs/src/getting_started/cite.md new file mode 100644 index 000000000..7212e5727 --- /dev/null +++ b/docs/src/getting_started/cite.md @@ -0,0 +1,19 @@ +# [Cite QuantumToolbox.jl](@id doc:Cite) + +If you like `QuantumToolbox.jl`, we would appreciate it if you could cite our publication [ [Quantum 9, 1866 (2025)](https://doi.org/10.22331/q-2025-09-29-1866) ] using the following bibtex entry: + +```bib +@article{QuantumToolbox.jl2025, + title = {Quantum{T}oolbox.jl: {A}n efficient {J}ulia framework for simulating open quantum systems}, + author = {Mercurio, Alberto and Huang, Yi-Te and Cai, Li-Xun and Chen, Yueh-Nan and Savona, Vincenzo and Nori, Franco}, + journal = {{Quantum}}, + issn = {2521-327X}, + publisher = {{Verein zur F{\"{o}}rderung des Open Access Publizierens in den Quantenwissenschaften}}, + volume = {9}, + pages = {1866}, + month = sep, + year = {2025}, + doi = {10.22331/q-2025-09-29-1866}, + url = {https://doi.org/10.22331/q-2025-09-29-1866} +} +``` diff --git a/docs/src/tutorials/logo.md b/docs/src/getting_started/logo.md similarity index 63% rename from docs/src/tutorials/logo.md rename to docs/src/getting_started/logo.md index ac39523e7..ae3093bff 100644 --- a/docs/src/tutorials/logo.md +++ b/docs/src/getting_started/logo.md @@ -1,8 +1,8 @@ -# [Create QuantumToolbox.jl logo](@id doc-tutor:Create-QuantumToolbox.jl-logo) +# [Example: Create QuantumToolbox.jl logo](@id doc:Create-QuantumToolbox.jl-logo) ## Introduction -In this tutorial, we will demonstrate how to create the logo for the **QuantumToolbox.jl** package. The logo represents the Wigner function of the triangular cat state, which is a linear superposition of three coherent states. The resulting Wigner function has a triangular shape that resembles the Julia logo. We will also define a custom colormap that varies based on the value of the Wigner function and the spatial coordinates, such that the three blobs corresponding to the coherent states have different colors (matching the colors of the Julia logo). +In this example, we will demonstrate how to create the logo for the **QuantumToolbox.jl** package. The logo represents the Wigner function of the triangular cat state, which is a linear superposition of three coherent states. The resulting Wigner function has a triangular shape that resembles the Julia logo. We will also define a custom colormap that varies based on the value of the Wigner function and the spatial coordinates, such that the three blobs corresponding to the coherent states have different colors (matching the colors of the Julia logo). ### Triangular Cat State @@ -14,23 +14,23 @@ A cat state, often referred to as a Schrödinger cat state, is a quantum state t where ``| \alpha \rangle`` is a coherent state with amplitude ``\alpha``. -The triangular cat state is a generalization of the standard cat state. It is a superposition of three coherent states with phases ``\theta_0, \theta_1, \theta_2`` separated by ``120^\circ``(or ``2\pi/3``radians): +The triangular cat state is a generalization of the standard cat state. It is a superposition of three coherent states with phases ``\theta_0, \theta_1, \theta_2`` separated by ``120^\circ`` (or ``2\pi/3``radians): ```math | \psi_{\text{tri-cat}} \rangle = \frac{1}{\sqrt{3}} \left( | \alpha_0 \rangle + | \alpha_1 \rangle + | \alpha_2 \rangle \right) ``` -where ``\alpha_j = \rho e^{i\theta_j}``with ``\theta_j = \frac{\pi}{2} + \frac{2\pi j}{3}``and ``j = 0, 1, 2``. +where ``\alpha_j = \rho e^{i\theta_j}`` with ``\theta_j = \frac{\pi}{2} + \frac{2\pi j}{3}`` and ``j = 0, 1, 2``. ### Wigner Function -The Wigner function ``W(x, p)``is a quasi-probability distribution used in quantum mechanics to represent quantum states in phase space. It is defined as: +The Wigner function ``W(x, p)`` is a quasi-probability distribution used in quantum mechanics to represent quantum states in phase space. It is defined as: ```math W(x, p) = \frac{1}{\pi \hbar} \int_{-\infty}^{\infty} \psi^*(x + y) \psi(x - y) e^{2ipy / \hbar} \, dy ``` -where ``\psi(x)``is the wave function of the quantum state, ``x``is the position, ``p``is the momentum, and ``\hbar``is the reduced Planck constant. Unlike classical probability distributions, the Wigner function can take negative values, which indicates non-classical behavior. +where ``\psi(x)`` is the wave function of the quantum state, ``x`` is the position, ``p`` is the momentum, and ``\hbar`` is the reduced Planck constant. Unlike classical probability distributions, the Wigner function can take negative values, which indicates non-classical behavior. ## Generating the Logo @@ -67,25 +67,16 @@ Next, we construct the triangular cat state as a normalized superposition of thr normalize!(ψ) ``` -### Defining the Grid and calculating the Wigner function +### Defining the Grid and plotting the Wigner function -We define the grid for the Wigner function and calculate it using the [`wigner`](@ref) function. We shift the grid in the imaginary direction to ensure that the Wigner function is centered around the origin of the figure. The [`wigner`](@ref) function also supports the `g` scaling factor, which we put here equal to ``2``. +We define the grid for the Wigner function and plot it using the [`plot_wigner`](@ref) function. This, internally calls the [`wigner`](@ref) function for the computation. We shift the grid in the imaginary direction to ensure that the Wigner function is centered around the origin of the figure. The [`wigner`](@ref) function also supports the `g` scaling factor, which we put here equal to ``2``. ```@example logo xvec = range(-ρ, ρ, 500) .* 1.5 yvec = xvec .+ (abs(imag(α1)) - abs(imag(α2))) / 2 -wig = wigner(ψ, xvec, yvec, g = 2) -``` - -### Plotting the Wigner function - -Finally, we plot the Wigner function using the `heatmap` function from the `CairoMakie` package. - -```@example logo -fig = Figure(size = (500, 500), figure_padding = 0) -ax = Axis(fig[1, 1]) -heatmap!(ax, xvec, yvec, wig', colormap = :RdBu, interpolate = true, rasterize = 1) +fig = Figure(size = (250, 250), figure_padding = 0) +fig, ax, hm = plot_wigner(ψ, xvec = xvec, yvec = yvec, g = 2, library = Val(:Makie), location = fig[1,1]) hidespines!(ax) hidexdecorations!(ax) hideydecorations!(ax) @@ -100,7 +91,7 @@ The figure obtained above coulb be already a potential logo for the package. How \frac{d \hat{\rho}}{dt} = -i [\hat{H}, \hat{\rho}] + \gamma \left( 2 \hat{a} \hat{\rho} \hat{a}^\dagger - \hat{a}^\dagger \hat{a} \hat{\rho} - \hat{\rho} \hat{a}^\dagger \hat{a} \right) ``` -where ``\hat{\rho}`` is the density matrix, ``\hat{H} = \omega \hat{a}^\dagger \hat{a}``is the Hamiltonian of the harmonic oscillator (``\hbar = 1``), ``\hat{a}``and ``\hat{a}^\dagger``are the annihilation and creation operators, and ``\gamma``is the damping rate. Thus, we initialize the system in the triangular cat state and evolve it under the Lindblad master equation, using the [`mesolve`](@ref) function. +where ``\hat{\rho}`` is the density matrix, ``\hat{H} = \omega \hat{a}^\dagger \hat{a}`` is the Hamiltonian of the harmonic oscillator (``\hbar = 1``), ``\hat{a}`` and ``\hat{a}^\dagger`` are the annihilation and creation operators, and ``\gamma`` is the damping rate. Thus, we initialize the system in the triangular cat state and evolve it under the [Lindblad master equation](@ref doc-TE:Lindblad-Master-Equation-Solver), using the [`mesolve`](@ref) function. ```@example logo γ = 0.012 @@ -118,12 +109,8 @@ nothing # hide And the Wigner function becomes more uniform: ```@example logo -wig = wigner(sol.states[end], xvec, yvec, g = 2) - -fig = Figure(size = (500, 500), figure_padding = 0) -ax = Axis(fig[1, 1]) - -img_wig = heatmap!(ax, xvec, yvec, wig', colormap = :RdBu, interpolate = true, rasterize = 1) +fig = Figure(size = (250, 250), figure_padding = 0) +fig, ax, hm = plot_wigner(sol.states[end], xvec = xvec, yvec = yvec, g = 2, library = Val(:Makie), location = fig[1,1]) hidespines!(ax) hidexdecorations!(ax) hideydecorations!(ax) @@ -135,7 +122,7 @@ At this stage, we have finished to use the `QuantumToolbox` package. From now on ### Custom Colormap -We define a custom colormap that changes depending on the Wigner function and spatial coordinates. Indeed, we want the three different colormaps, in the regions corresponding to the three coherent states, to match the colors of the Julia logo. We also want the colormap change to be smooth, so we use a Gaussian function to blend the colors. We introduce also a Wigner function dependent transparency to make the logo more appealing. +We define a custom colormap that changes depending on the Wigner function and spatial coordinates. Indeed, we want the three different colormaps, in the regions corresponding to the three coherent states, to match the colors of the Julia logo. We also want the colormap change to be smooth, so we use a Gaussian function to blend the colors. We introduce also a Wigner function dependent transparency to make the logo more appealing. In order to do so, we are going to need the value of the wigner function at each point of the grid, rather than its plot. We will thus call the [`wigner`](@ref) function directly. ```@example logo function set_color_julia(x, y, wig::T, α1, α2, α3, cmap1, cmap2, cmap3, δ) where {T} @@ -156,6 +143,7 @@ function set_color_julia(x, y, wig::T, α1, α2, α3, cmap1, cmap2, cmap3, δ) w return RGBAf(c_tot.r, c_tot.g, c_tot.b, alpha) end +wig = wigner(sol.states[end], xvec, yvec, g = 2) X, Y = meshgrid(xvec, yvec) δ = 1.25 # Smoothing parameter for the Gaussian functions ``` @@ -178,7 +166,7 @@ cmap3 = cgrad(vcat(fill(julia_blue, n_repeats), fill(julia_purple, n_repeats))) ### Normalizing the Wigner function and applying the custom colormap -The colormaps require the input to be in the range ``[0, 1]``. We normalize the Wigner function such that the maximum value is ``1``and the zeros are set to ``0.5``. +The colormaps require the input to be in the range ``[0, 1]``. We normalize the Wigner function such that the maximum value is ``1`` and the zeros are set to ``0.5``. ```@example logo vmax = maximum(wig) @@ -197,7 +185,7 @@ img = set_color_julia.(X, Y, wig_normalized, α1, α2, α3, Ref(cmap1), Ref(cmap Finally, we plot the Wigner function with the custom colormap. ```@example logo -fig = Figure(size = (500, 500), figure_padding = 0, backgroundcolor = :transparent) +fig = Figure(size = (250, 250), figure_padding = 0, backgroundcolor = :transparent) ax = Axis(fig[1, 1], backgroundcolor = :transparent) image!(ax, img', rasterize = 1) hidespines!(ax) @@ -208,4 +196,4 @@ fig ## Conclusion -This tutorial demonstrates how to generate the [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) logo using the package itself and [Makie.jl](https://github.com/MakieOrg/Makie.jl) for visualization. The logo is a visualization of the Wigner function of a triangular cat state, with a custom colormap that highlights the different coherent states with colors matching the Julia logo. +This example demonstrates how to generate the [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) logo using the package itself and [Makie.jl](https://github.com/MakieOrg/Makie.jl) for visualization. The logo is a visualization of the Wigner function of a triangular cat state, with a custom colormap that highlights the different coherent states with colors matching the Julia logo. diff --git a/docs/src/qutip_differences.md b/docs/src/getting_started/qutip_differences.md similarity index 100% rename from docs/src/qutip_differences.md rename to docs/src/getting_started/qutip_differences.md diff --git a/docs/src/getting_started/type_stability.md b/docs/src/getting_started/type_stability.md new file mode 100644 index 000000000..bd22d3481 --- /dev/null +++ b/docs/src/getting_started/type_stability.md @@ -0,0 +1,288 @@ +# [The Importance of Type-Stability](@id doc:Type-Stability) + +You are here because you have probably heard about the excellent performance of Julia compared to other common programming languages like Python. One of the reasons is the Just-In-Time (JIT) compiler of Julia, which is able to generate highly optimized machine code. However, the JIT compiler can only do its job if the code type can be inferred. You can also read the [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/) section in Julia's documentation for more details. Here, we try to explain it briefly, with a focus on the `QuantumToolbox.jl` package. + +!!! note + This page is not a tutorial on `QuantumToolbox.jl`, but rather a general guide to writing Julia code for simulating quantum systems efficiently. If you don't care about the performance of your code, you can skip this page. + +## Basics of type stability + +Let's have a look at the following example: + +```@setup type-stability +using InteractiveUtils +using QuantumToolbox +``` + +```@example type-stability +function foo(x) + if x > 0 + return 1 + else + return -1.0 + end +end +nothing # hide +``` + +The function `foo` apparently seems to be innocent. It takes an argument `x` and returns either `1` or `-1.0` depending on the sign of `x`. However, the return type of `foo` is not clear. If `x` is positive, the return type is `Int`, otherwise it is `Float64`. This is a problem for the JIT compiler, because it has to determine the return type of `foo` at runtime. This is called type instability (even though it is a weak form) and may lead to a significant performance penalty. To avoid this, always aim for type-stable code. This means that the return type of a function should be clear from the types of its arguments. We can check the inferred return type of `foo` using the `@code_warntype` macro: + +```@example type-stability +@code_warntype foo(1) +``` + +The key point is to ensure the return type of a function is clear from the types of its arguments. There are several ways to achieve this, and the best approach depends on the specific problem. For example, one can use the same return type: + +```@example type-stability +function foo(x) + if x > 0 + return 1.0 + else + return -1.0 + end +end +nothing # hide +``` + +Or you can ensure the return type matches the type of the argument: + +```@example type-stability +function foo(x::T) where T + if x > 0 + return T(1) + else + return -T(1) + end +end +nothing # hide +``` + +The latter example is very important because it takes advantage of Julia's multiple dispatch, which is one of the most powerful features of the language. Depending on the type `T` of the argument `x`, the Julia compiler generates a specialized version of `foo` that is optimized for this type. If the input type is an `Int64`, the return type is `Int64`, if `x` is a `Float64`, the return type is `Float64`, and so on. + +```@example type-stability +@show foo(1) +@show foo(-4.4) +@show foo(1//2) +nothing # hide +``` + +!!! note + If you didn't know how to make this function type-stable, it is probably a good idea to read the official Julia documentation, and in particular its [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/) section. + +## Global variables + +Another source of type instability is the use of global variables. In general, it is a good idea to declare global variables as `const` to ensure their type is fixed for the entire program. For example, consider the following function that internally takes a global variable `y`: + +```@example type-stability +y = 2.4 + +function bar(x) + res = zero(x) # this returns the zero of the same type of x + for i in 1:1000 + res += y * x + end + return res +end +nothing # hide +``` + +The Julia compiler cannot infer the type of `res` because it depends on the type of `y`, which is a global variable that can change at any time of the program. We can check it using the `@code_warntype` macro: + +```@example type-stability +@code_warntype bar(3.2) +``` + +While in the last example of the `foo` function we got a weak form of type instability, returning a `Union{Int, Float64}`, in this case the return type of `bar` is `Any`, meaning that the compiler doesn't know anything about the return type. Thus, this function has nothing different from a dynamically typed language like Python. We can benchmark the performance of `bar` using the [BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl) package: + +```@example type-stability +using BenchmarkTools + +@benchmark bar(3.2) +``` + +Here we see a lot of memory allocations and low performances in general. To fix this, we can declare a `const` (constant) variable instead: + +```@example type-stability +const z = 2.4 + +function bar(x) + res = zero(x) # this returns the zero of the same type of x + for i in 1:1000 + res += z * x + end + return res +end + +@benchmark bar(3.2) +``` + +And we can see that the performance has improved significantly. Hence, we highly recommend using global variables as `const`, but only when truly necessary. This choice is problem-dependent, but in the case of `QuantumToolbox.jl`, this can be applied for example in the case of defining the Hilbert space dimensions, static parameters, or the system operators. + +Although it is always a good practice to avoid such kind of type instabilities, in the actual implementation of `QuantumToolbox.jl` (where we mainly deal with linear algebra operations), the compiler may perform only a few runtime dispatches, and the performance penalty may be negligible compared to the heavy linear algebra operations. + +## Vectors vs Tuples vs StaticArrays + +Julia has many ways to represent arrays or lists of general objects. The most common are `Vector`s and `Tuple`s. The former is a dynamic array that can change its size at runtime, while the latter is a fixed-size array that is immutable, and where the type of each element is already known at compile time. For example: + +```@example type-stability +v1 = [1, 2, 3] # Vector of Int64 +v2 = [1.0 + 2.0im, 3.0 + 4.0im] # Vector of ComplexF64 +v3 = [1, "ciao", 3.0] # Vector of Any + +t1 = (1, 2, 3) # Tuple of {Int64, Int64, Int64} +t2 = (1.0 + 2.0im, 3.0 + 4.0im) # Tuple of {ComplexF64, ComplexF64} +t3 = (1, "ciao", 3.0) # Tuple of {Int64, String, Float64} + +@show typeof(v1) +@show typeof(v2) +@show typeof(v3) +@show typeof(t1) +@show typeof(t2) +@show typeof(t3) +nothing # hide +``` + +Thus, we highly recommend using `Vector` only when we are sure that it contains elements of the same type, and only when we don't need to know its size at compile time. On the other hand, `Tuple`s are less flexible but more efficient in terms of performance. A third option is to use the `SVector` type from the [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) package. This is similar to `Vector`, where the elements should have the same type, but it is fixed-size and immutable. One may ask when it is necessary to know the array size at compile time. A practical example is the case of [`ptrace`](@ref), where it internally reshapes the quantum state into a tensor whose dimensions depend on the number of subsystems. We will see this in more detail in the next section. + +## The `QuantumObject` internal structure + +Before making a practical example, let's see the internal structure of the [`QuantumObject`](@ref) type. As an example, we consider the case of three qubits, and we study the internal structure of the ``\hat{\sigma}_x^{(2)}`` operator: + +```@example type-stability +σx_2 = tensor(qeye(2), sigmax(), qeye(2)) +``` + +and its type is + +```@example type-stability +obj_type = typeof(σx_2) +``` + +This is exactly what the Julia compiler sees: it is a [`QuantumObject`](@ref), composed by a field of type `SparseMatrixCSC{ComplexF64, Int64}` (i.e., the 8x8 matrix containing the Pauli matrix, tensored with the identity matrices of the other two qubits). Then, we can also see that it is a [`Operator`](@ref), with `3` subsystems in total. Hence, just looking at the type of the object, the compiler has all the information it needs to generate a specialized version of the functions. + +Let's see more in the details all the internal fields of the [`QuantumObject`](@ref) type: + +```@example type-stability +fieldnames(obj_type) +``` + +```@example type-stability +σx_2.data +``` + +```@example type-stability +σx_2.type +``` + + +```@example type-stability +σx_2.dims +``` + +The `dims` field contains the dimensions of the subsystems (in this case, three subsystems with dimension `2` each). We can see that the type of `dims` is `SVector` instead of `Vector`. As we mentioned before, this is very useful in functions like [`ptrace`](@ref). Let's do a simple example of reshaping an operator internally generated from some `dims` input: + +```@example type-stability +function reshape_operator_data(dims) + op = Qobj(randn(prod(dims), prod(dims)), type=Operator(), dims=dims) + op_dims = op.dims + op_data = op.data + return reshape(op_data, vcat(op_dims, op_dims)...) +end + +typeof(reshape_operator_data([2, 2, 2])) +``` + +Which returns a tensor of size `2x2x2x2x2x2`. Let's check the `@code_warntype`: + +```@example type-stability +@code_warntype reshape_operator_data([2, 2, 2]) +``` + +We got a `Any` type, because the compiler doesn't know the size of the `dims` vector. We can fix this by using a `Tuple` (or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl)): + +```@example type-stability +typeof(reshape_operator_data((2, 2, 2))) +``` + +```@example type-stability +@code_warntype reshape_operator_data((2, 2, 2)) +``` + +Finally, let's look at the benchmarks + +```@example type-stability +@benchmark reshape_operator_data($[2, 2, 2]) +``` + +```@example type-stability +@benchmark reshape_operator_data($((2, 2, 2))) +``` + +Which is an innocuous but huge difference in terms of performance. Hence, we highly recommend using `Tuple` or `SVector` from [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl) when defining the dimensions of a user-defined [`QuantumObject`](@ref). + +## The use of `Val` in some `QuantumToolbox.jl` functions + +In some functions of `QuantumToolbox.jl`, you may find the use of the [`Val`](https://docs.julialang.org/en/v1/base/base/#Base.Val) type in the arguments. This is a trick to pass a value at compile time, and it is very useful to avoid type instabilities. Let's make a very simple example, where we want to create a Fock state ``|j\rangle`` of a given dimension `N`, and we give the possibility to create it as a sparse or dense vector. At first, we can write the function without using `Val`: + +```@example type-stability +using SparseArrays + +function my_fock(N::Int, j::Int = 0; sparse::Bool = false) + if sparse + array = sparsevec([j + 1], [1.0 + 0im], N) + else + array = zeros(ComplexF64, N) + array[j+1] = 1 + end + return QuantumObject(array; type = Ket()) +end +@show my_fock(2, 1) +@show my_fock(2, 1; sparse = true) +nothing # hide +``` + +But it is immediately clear that the return type of this function is not clear, because it depends on the value of the `sparse` argument. We can check it using the `@code_warntype` macro: + +```@example type-stability +@code_warntype my_fock(2, 1) +``` + +```@example type-stability +@code_warntype my_fock(2, 1; sparse = true) +``` + +We can fix this by using the `Val` type, where we enable the multiple dispatch of the function: + +```@example type-stability +getVal(::Val{N}) where N = N +function my_fock_good(N::Int, j::Int = 0; sparse::Val = Val(false)) + if getVal(sparse) + array = zeros(ComplexF64, N) + array[j+1] = 1 + else + array = sparsevec([j + 1], [1.0 + 0im], N) + end + return QuantumObject(array; type = Ket()) +end +@show my_fock_good(2, 1) +@show my_fock_good(2, 1; sparse = Val(true)) +nothing # hide +``` + +And now the return type of the function is clear: + +```@example type-stability +@code_warntype my_fock_good(2, 1) +``` + +```@example type-stability +@code_warntype my_fock_good(2, 1; sparse = Val(true)) +``` + +This is exactly how the current [`fock`](@ref) function is implemented in `QuantumToolbox.jl`. There are many other functions that support this feature, and we highly recommend using it when necessary. + +## Conclusions + +In this page, we have seen the importance of type stability in Julia, and how to write efficient code in the context of `QuantumToolbox.jl`. We have seen that the internal structure of the [`QuantumObject`](@ref) type is already optimized for the compiler, and we have seen some practical examples of how to write efficient code. We have seen that the use of `Vector` should be avoided when the elements don't have the same type, and that the use of `Tuple` or `SVector` is highly recommended when the size of the array is known at compile time. Finally, we have seen the use of `Val` to pass values at compile time, to avoid type instabilities in some functions. +``` + diff --git a/docs/src/index.md b/docs/src/index.md index 149df2998..a8f3baaa8 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,24 +1,65 @@ -```@meta -CurrentModule = QuantumToolbox +```@raw html +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "QuantumToolbox.jl" + tagline: A pure Julia framework designed for high-performance quantum physics simulations + image: + src: /logo.png + alt: QuantumToolbox + actions: + - theme: brand + text: Getting Started + link: /getting_started/brief_example + - theme: alt + text: Users Guide + link: /users_guide/QuantumObject/QuantumObject + - theme: alt + text: Tutorials + link: https://qutip.org/qutip-julia-tutorials/ + - theme: alt + text: API + link: /resources/api + - theme: alt + text: Cite us + link: /getting_started/cite + - theme: alt + text: View on Github + link: https://github.com/qutip/QuantumToolbox.jl + - theme: alt + text: Visit QuTiP.org + link: https://qutip.org/ + + +features: + - icon: markdown + title: Dynamical Evolution + details: Advanced solvers for time evolution of quantum systems, thanks to the powerful DifferentialEquations.jl package. + link: /users_guide/time_evolution/intro + - icon: + title: GPU Computing + details: Leverage GPU resources for high-performance computing. Simulate quantum dynamics directly on the GPU with the same syntax as the CPU case. + link: /users_guide/extensions/cuda + - icon: + title: Distributed Computing + details: Distribute the computation over multiple nodes (e.g., a cluster). Simulate hundreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. + link: /users_guide/cluster + - icon: + title: Differentiable Programming + details: Enable gradient-based optimization for quantum algorithms. Compute gradients of quantum dynamics with respect to their parameters using automatic differentiation. + link: /users_guide/autodiff +--- ``` -# QuantumToolbox.jl Documentation +# [Introduction](@id doc:Introduction) -[QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge Julia package designed for quantum physics simulations, closely emulating the popular Python [QuTiP](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of Julia with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. +[`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) is a cutting-edge [`Julia`](https://julialang.org/) package designed for quantum physics simulations, closely emulating the popular [`Python QuTiP`](https://github.com/qutip/qutip) package. It uniquely combines the simplicity and power of Julia with advanced features like GPU acceleration and distributed computing, making simulation of quantum systems more accessible and efficient. Taking advantage of the [`Julia`](https://julialang.org/) language features (like multiple dispatch and metaprogramming), [`QuantumToolbox.jl`](https://github.com/qutip/QuantumToolbox.jl) is designed to be easily extendable, allowing users to build upon the existing functionalities. -*With this package, moving from Python to Julia for quantum physics simulations has never been easier*, due to the similar syntax and functionalities. +*__With this package, moving from Python to Julia for quantum physics simulations has never been easier__*, due to the similar syntax and functionalities. -## Features - -QuantumToolbox.jl is equipped with a robust set of features: - -- **Quantum State and Operator Manipulation:** Easily handle quantum states and operators with a rich set of tools, with the same functionalities as QuTiP. -- **Dynamical Evolution:** Advanced solvers for time evolution of quantum systems, thanks to the powerful [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) package. -- **GPU Computing:** Leverage GPU resources for high-performance computing. For example, you run the master equation direclty on the GPU with the same syntax as the CPU case. -- **Distributed Computing:** Distribute the computation over multiple nodes (e.g., a cluster). For example, you can run undreds of quantum trajectories in parallel on a cluster, with, again, the same syntax as the simple case. -- **Easy Extension:** Easily extend the package, taking advantage of the Julia language features, like multiple dispatch and metaprogramming. - -## [Installation](@id doc:Installation) +# [Installation](@id doc:Installation) !!! note "Requirements" `QuantumToolbox.jl` requires `Julia 1.10+`. @@ -28,8 +69,8 @@ To install `QuantumToolbox.jl`, run the following commands inside Julia's intera using Pkg Pkg.add("QuantumToolbox") ``` -Alternatively, this can also be done in Julia's [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: -```julia-REPL +Alternatively, this can also be done in `Julia`'s [Pkg REPL](https://julialang.github.io/Pkg.jl/v1/getting-started/) by pressing the key `]` in the REPL to use the package mode, and then type the following command: +```julia-repl (1.10) pkg> add QuantumToolbox ``` More information about `Julia`'s package manager can be found at [`Pkg.jl`](https://julialang.github.io/Pkg.jl/v1/). @@ -41,78 +82,29 @@ QuantumToolbox.versioninfo() QuantumToolbox.about() ``` -## Brief Example - -We now provide a brief example to demonstrate the similarity between [QuantumToolbox.jl](https://github.com/qutip/QuantumToolbox.jl) and [QuTiP](https://github.com/qutip/qutip). - -Let's consider a quantum harmonic oscillator with a Hamiltonian given by: - -```math -\hat{H} = \omega \hat{a}^\dagger \hat{a} -``` - -where ``\hat{a}`` and ``\hat{a}^\dagger`` are the annihilation and creation operators, respectively. We can define the Hamiltonian as follows: - -```julia -using QuantumToolbox - -N = 20 # cutoff of the Hilbert space dimension -ω = 1.0 # frequency of the harmonic oscillator - -a = destroy(N) # annihilation operator - -H = ω * a' * a -``` - -We now introduce some losses in a thermal environment, described by the Lindblad master equation: - -```math -\frac{d \hat{\rho}}{dt} = -i [\hat{H}, \hat{\rho}] + \gamma \mathcal{D}[\hat{a}] \hat{\rho} -``` - -where ``\hat{\rho}`` is the density matrix, ``\gamma`` is the damping rate, and ``\mathcal{D}[\hat{a}]`` is the Lindblad dissipator, defined as: - -```math -\mathcal{D}[\hat{a}]\hat{\rho} = \hat{a}\hat{\rho}\hat{a}^\dagger - \frac{1}{2}\hat{a}^\dagger\hat{a}\hat{\rho} - \frac{1}{2}\hat{\rho}\hat{a}^\dagger\hat{a} -``` - -We now compute the time evolution of the system using the [`mesolve`](@ref) function, starting from the initial state ``\ket{\psi (0)} = \ket{3}``: - -```julia -γ = 0.1 # damping rate - -ψ0 = fock(N, 3) # initial state - -tlist = range(0, 10, 100) # time list - -c_ops = [sqrt(γ) * a] -e_ops = [a' * a] - -sol = mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops) -``` - -We can extract the expectation value of the number operator ``\hat{a}^\dagger \hat{a}`` with the command `sol.expect`, and the states with the command `sol.states`. - -### Support for GPU calculation - -We can easily pass the computation to the GPU, by simply passing all the `Qobj`s to the GPU: - -!!! compat "Compat" - The described feature requires `Julia 1.9+`. See [CUDA extension](@ref doc:CUDA) for more details. - -```julia -using QuantumToolbox -using CUDA -CUDA.allowscalar(false) # Avoid unexpected scalar indexing - -a_gpu = cu(destroy(N)) # The only difference in the code is the cu() function - -H_gpu = ω * a_gpu' * a_gpu - -ψ0_gpu = cu(fock(N, 3)) - -c_ops = [sqrt(γ) * a_gpu] -e_ops = [a_gpu' * a_gpu] - -sol = mesolve(H_gpu, ψ0_gpu, tlist, c_ops, e_ops = e_ops) -``` +# [Other Useful Packages](@id doc:Other-Useful-Packages) + +In order to get a better experience and take full advantage of `QuantumToolbox`, we recommend the following external packages: + +- Standard `Julia` Libraries: (recommended to also `using` with `QuantumToolbox.jl`) + - [`LinearAlgebra.jl`](https://github.com/JuliaLang/LinearAlgebra.jl) + - [`SparseArrays.jl`](https://github.com/JuliaSparse/SparseArrays.jl) +- Solver `alg`orithms: + - [`DifferentialEquations.jl`](https://github.com/SciML/DifferentialEquations.jl) or [`OrdinaryDiffEq.jl`](https://github.com/SciML/OrdinaryDiffEq.jl) + - [`LinearSolve.jl`](https://github.com/SciML/LinearSolve.jl) +- GPU support: + - [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl) +- Distributed Computing support: + - [`Distributed.jl`](https://github.com/JuliaLang/Distributed.jl) + - [`SlurmClusterManager.jl`](https://github.com/JuliaParallel/SlurmClusterManager.jl) +- Plotting Libraries: + - [`Makie.jl`](https://github.com/MakieOrg/Makie.jl) +- Automatic Differentiation: + - [`SciMLSensitivity.jl`](https://github.com/SciML/SciMLSensitivity.jl) + - [`Zygote.jl`](https://github.com/FluxML/Zygote.jl) + - [`Enzyme.jl`](https://github.com/EnzymeAD/Enzyme.jl) + - [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) +- Packages for other advanced usage: + - [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl) + - [`SciMLOperators.jl`](https://github.com/SciML/SciMLOperators.jl) + - [`DiffEqCallbacks.jl`](https://github.com/SciML/DiffEqCallbacks.jl) diff --git a/docs/src/resources/acknowledgements.md b/docs/src/resources/acknowledgements.md new file mode 100644 index 000000000..7f6161e73 --- /dev/null +++ b/docs/src/resources/acknowledgements.md @@ -0,0 +1,25 @@ +# [Acknowledgements](@id doc:Acknowledgements) + +## [Fundings](@id doc:Fundings) + +`QuantumToolbox.jl` is supported by the [Unitary Fund](https://unitary.fund), a grant program for quantum technology projects. + +```@raw html +
+ + Unitary Fund logo + +
+``` + +## [Other Acknowledgements](@id doc:Other-Acknowledgements) + +We are also grateful to the [Zulip](https://zulip.com) team for providing a free chat service for open-source projects. + +```@raw html +
+ + Zulip logo + +
+``` diff --git a/docs/src/api.md b/docs/src/resources/api.md similarity index 56% rename from docs/src/api.md rename to docs/src/resources/api.md index 20c35bd29..e4e5e901a 100644 --- a/docs/src/api.md +++ b/docs/src/resources/api.md @@ -1,35 +1,44 @@ ```@meta CurrentModule = QuantumToolbox + +DocTestSetup = quote + using LinearAlgebra + using SparseArrays + using QuantumToolbox +end ``` # [API](@id doc-API) -## Contents +**Table of contents** -```@contents -Pages = ["api.md"] -``` +[[toc]] ## [Quantum object (Qobj) and type](@id doc-API:Quantum-object-and-type) ```@docs -BraQuantumObject +Space +EnrSpace +Dimensions +GeneralDimensions +AbstractQuantumObject Bra -KetQuantumObject Ket -OperatorQuantumObject Operator -OperatorBraQuantumObject OperatorBra -OperatorKetQuantumObject OperatorKet -SuperOperatorQuantumObject SuperOperator QuantumObject -OperatorSum -size -eltype -length +QuantumObjectEvolution +Base.size +Base.eltype +Base.length +SciMLOperators.cache_operator +``` + +## [Qobj boolean functions](@id doc-API:Qobj-boolean-functions) + +```@docs isbra isket isoper @@ -40,11 +49,15 @@ LinearAlgebra.ishermitian LinearAlgebra.issymmetric LinearAlgebra.isposdef isunitary +SciMLOperators.iscached +SciMLOperators.isconstant ``` ## [Qobj arithmetic and attributes](@id doc-API:Qobj-arithmetic-and-attributes) ```@docs +Base.zero +Base.one Base.conj LinearAlgebra.transpose LinearAlgebra.adjoint @@ -64,7 +77,7 @@ LinearAlgebra.diag proj ptrace purity -permute +SparseArrays.permute tidyup tidyup! get_data @@ -91,8 +104,8 @@ ket2dm expect variance LinearAlgebra.kron -sparse_to_dense -dense_to_sparse +to_dense +to_sparse vec2mat mat2vec ``` @@ -110,6 +123,8 @@ coherent_dm thermal_dm maximally_mixed_dm rand_dm +enr_fock +enr_thermal_dm spin_state spin_coherent bell_state @@ -140,6 +155,8 @@ QuantumToolbox.momentum phase fdestroy fcreate +enr_destroy +enr_identity tunneling qft eye @@ -154,69 +171,137 @@ lindblad_dissipator ## [Synonyms of functions for Qobj](@id doc-API:Synonyms-of-functions-for-Qobj) ```@docs Qobj +QobjEvo shape isherm trans dag matrix_element unit +tensor +⊗ +qeye +vector_to_operator +operator_to_vector sqrtm logm expm sinm cosm -tensor -⊗ -qeye +qeye_like +qzero_like ``` ## [Time evolution](@id doc-API:Time-evolution) ```@docs +TimeEvolutionProblem TimeEvolutionSol TimeEvolutionMCSol +TimeEvolutionStochasticSol +average_states +average_expect +std_expect sesolveProblem mesolveProblem -lr_mesolveProblem mcsolveProblem mcsolveEnsembleProblem +ssesolveProblem +ssesolveEnsembleProblem +smesolveProblem +smesolveEnsembleProblem sesolve mesolve -lr_mesolve mcsolve +ssesolve +smesolve dfd_mesolve -dsf_mesolve -dsf_mcsolve liouvillian liouvillian_generalized +bloch_redfield_tensor +brterm +brmesolve +``` + +### [Steady State Solvers](@id doc-API:Steady-State-Solvers) + +```@docs steadystate -steadystate_floquet +steadystate_fourier +SteadyStateDirectSolver +SteadyStateEigenSolver +SteadyStateLinearSolver SteadyStateODESolver +SSFloquetEffectiveLiouvillian +``` + +### [Dynamical Shifted Fock method](@id doc-API:Dynamical-Shifted-Fock-method) + +```@docs +dsf_mesolve +dsf_mcsolve +``` + +### [Low-rank time evolution](@id doc-API:Low-rank-time-evolution) + +```@docs +TimeEvolutionLRSol +lr_mesolveProblem +lr_mesolve ``` ## [Correlations and Spectrum](@id doc-API:Correlations-and-Spectrum) ```@docs correlation_3op_2t +correlation_3op_1t correlation_2op_2t correlation_2op_1t +spectrum_correlation_fft spectrum +ExponentialSeries +PseudoInverse +Lanczos ``` -## [Metrics](@id doc-API:Metrics) +## [Entropy and Metrics](@id doc-API:Entropy-and-Metrics) ```@docs entropy_vn +entropy_relative +entropy_linear +entropy_mutual +entropy_conditional entanglement -tracedist +concurrence +negativity fidelity +tracedist +hilbert_dist +hellinger_dist +bures_dist +bures_angle +``` + +## [Spin Lattice](@id doc-API:Spin-Lattice) + +```@docs +Lattice +multisite_operator +DissipativeIsing +``` + +## [Symmetries and Block Diagonalization](@id doc-API:Symmetries-and-Block-Diagonalization) + +```@docs +block_diagonal_form +BlockDiagonalForm ``` ## [Miscellaneous](@id doc-API:Miscellaneous) ```@docs wigner -negativity ``` ## [Linear Maps](@id doc-API:Linear-Maps) @@ -228,16 +313,36 @@ AbstractLinearMap ## [Utility functions](@id doc-API:Utility-functions) ```@docs +QuantumToolbox.settings QuantumToolbox.versioninfo QuantumToolbox.about +QuantumToolbox.cite gaussian -n_th +n_thermal +PhysicalConstants +convert_unit row_major_reshape meshgrid -_calculate_expectation! -_adjM_condition_variational -_adjM_affect! -_adjM_condition_ratio -_pinv! -dBdz! +enr_state_dictionaries +``` + +## [Visualization](@id doc-API:Visualization) + +```@docs +plot_wigner +plot_fock_distribution +``` + +### [Bloch Sphere](@id doc-API:Bloch-Sphere) + +```@docs +Bloch +plot_bloch +render +add_points! +add_vectors! +add_line! +add_arc! +add_states! +clear! ``` diff --git a/docs/src/resources/bibliography.bib b/docs/src/resources/bibliography.bib new file mode 100644 index 000000000..9af3e514b --- /dev/null +++ b/docs/src/resources/bibliography.bib @@ -0,0 +1,141 @@ +@book{Gardiner-Zoller2004, + title = {Quantum Noise}, + ISBN = {9783540223016}, + url = {https://link.springer.com/book/9783540223016}, + publisher = {Springer Berlin, Heidelberg}, + author = {Gardiner, Crispin and Zoller, Peter}, + year = {2004}, + month = aug +} + +@book{Nielsen-Chuang2011, + title = {Quantum Computation and Quantum Information: 10th Anniversary Edition}, + ISBN = {9780511976667}, + DOI = {10.1017/cbo9780511976667}, + publisher = {Cambridge University Press}, + author = {Nielsen, Michael A. and Chuang, Isaac L.}, + year = {2012}, + month = jun +} + +@article{Jozsa1994, + author = {Richard Jozsa}, + title = {Fidelity for Mixed Quantum States}, + journal = {Journal of Modern Optics}, + volume = {41}, + number = {12}, + pages = {2315--2323}, + year = {1994}, + publisher = {Taylor \& Francis}, + doi = {10.1080/09500349414552171} +} + +@article{gravina2024adaptive, + title = {{Adaptive variational low-rank dynamics for open quantum systems}}, + author = {Gravina, Luca and Savona, Vincenzo}, + journal = {Phys. Rev. Res.}, + volume = {6}, + issue = {2}, + pages = {023072}, + numpages = {18}, + year = {2024}, + month = {Apr}, + publisher = {American Physical Society}, + doi = {10.1103/PhysRevResearch.6.023072} +} + +@article{Tanimura1989, + title = {Time Evolution of a Quantum System in Contact with a Nearly Gaussian-Markoffian Noise Bath}, + volume = {58}, + ISSN = {1347-4073}, + DOI = {10.1143/jpsj.58.101}, + number = {1}, + journal = {Journal of the Physical Society of Japan}, + publisher = {Physical Society of Japan}, + author = {Tanimura, Yoshitaka and Kubo, Ryogo}, + year = {1989}, + month = jan, + pages = {101–114} +} + +@article{Huang2023, + doi = {10.1038/s42005-023-01427-2}, + year = {2023}, + month = {Oct}, + publisher = {Nature Portfolio}, + volume = {6}, + number = {1}, + pages = {313}, + author = {Huang, Yi-Te and Kuo, Po-Chen and Lambert, Neill and Cirio, Mauro and Cross, Simon and Yang, Shen-Liang and Nori, Franco and Chen, Yueh-Nan}, + title = {An efficient {J}ulia framework for hierarchical equations of motion in open quantum systems}, + journal = {Communications Physics} +} + +@book{Wiseman2009Quantum, + title={Quantum Measurement and Control}, + ISBN={9781107424159}, + url={http://dx.doi.org/10.1017/CBO9780511813948}, + DOI={10.1017/cbo9780511813948}, + publisher={Cambridge University Press}, + author={Wiseman, Howard M. and Milburn, Gerard J.}, + year={2009}, + month=nov +} + +@article{Vedral-Plenio1998, + title = {Entanglement measures and purification procedures}, + author = {Vedral, V. and Plenio, M. B.}, + journal = {Phys. Rev. A}, + volume = {57}, + issue = {3}, + pages = {1619--1633}, + numpages = {0}, + year = {1998}, + month = {Mar}, + publisher = {American Physical Society}, + doi = {10.1103/PhysRevA.57.1619}, + url = {https://link.aps.org/doi/10.1103/PhysRevA.57.1619} +} + +@article{Spehner2017, + title={Geometric measures of quantum correlations with Bures and Hellinger distances}, + author={D. Spehner and F. Illuminati and M. Orszag and W. Roga}, + year={2017}, + journal={arXiv:1611.03449}, + url={https://arxiv.org/abs/1611.03449}, +} + +@article{Hill-Wootters1997, + title = {Entanglement of a Pair of Quantum Bits}, + author = {Hill, Sam A. and Wootters, William K.}, + journal = {Phys. Rev. Lett.}, + volume = {78}, + issue = {26}, + pages = {5022--5025}, + numpages = {0}, + year = {1997}, + month = {Jun}, + publisher = {American Physical Society}, + doi = {10.1103/PhysRevLett.78.5022}, + url = {https://link.aps.org/doi/10.1103/PhysRevLett.78.5022} +} + +@book{Cohen_Tannoudji_atomphoton, + address = {New York}, + author = {{Cohen-Tannoudji}, C. and {Grynberg}, G. and {Dupont-Roc}, J.}, + publisher = {Wiley}, + timestamp = {2010-12-01T16:20:40.000+0100}, + title = {Atom-Photon Interactions: Basic Processes and Applications }, + year = 1992 +} + +@book{breuer2002, + title = {The Theory of Open Quantum Systems}, + author = {Breuer, Heinz-Peter and Petruccione, Francesco}, + year = {2002}, + publisher = {Oxford university press}, + address = {Oxford New York}, + isbn = {978-0-19-852063-4}, + langid = {english}, + lccn = {530.12} +} diff --git a/docs/src/resources/bibliography.md b/docs/src/resources/bibliography.md new file mode 100644 index 000000000..bbb721388 --- /dev/null +++ b/docs/src/resources/bibliography.md @@ -0,0 +1,10 @@ +# [Bibliography](@id doc-Bibliography) + +```@meta +CurrentModule = QuantumToolbox +``` + +This page is generated by [`DocumenterCitations.jl` with author-year style](https://juliadocs.org/DocumenterCitations.jl/stable/gallery/#author_year_style). + +```@bibliography +``` diff --git a/docs/src/resources/contributing.md b/docs/src/resources/contributing.md new file mode 100644 index 000000000..5518c6998 --- /dev/null +++ b/docs/src/resources/contributing.md @@ -0,0 +1,106 @@ +# [Contributing to Quantum Toolbox in Julia](@id doc-Contribute) + +## [Quick Start](@id doc-Contribute:Quick-Start) + +`QuantumToolbox.jl` is developed using the [`git`](https://git-scm.com/) version-control system, with the [main repository](https://github.com/qutip/QuantumToolbox.jl) hosted in the [qutip organisation on GitHub](https://github.com/qutip). You will need to be familiar with [`git`](https://git-scm.com/) as a tool, and the [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow) workflow for branching and making pull requests. The exact details of environment set-up, build process, and runtests vary by repository are discussed below. In overview, the steps to contribute are: + +- Consider creating an issue on the GitHub page of the relevant repository, describing the change you think should be made and why, so we can discuss details with you and make sure it is appropriate. +- *__If this is your first contribution__*, make a fork of the relevant repository on GitHub (which will be called as `origin`) and clone it to your local computer. Also add our copy as a remote (let's call it `qutip` here): `git remote add qutip https://github.com/qutip/`. +- Start from the `main` branch in your local computer (`git checkout main`), and pull all the changes from the remote (`qutip`) repository (on GitHub) to make sure you have an up-to-date copy: `git pull qutip main`. +- Switch to a new `git` branch in your local computer: `git checkout -b `. +- Make the changes you want. +- Go through the following build processes (if the changes you made relates to any of them) locally in your computer to build the final result so you can check your changes work sensibly: + - Write and make sure all the runtests pass. See [here](@ref doc-Contribute:Runtests) for more details. + - Make sure all the changes match the `Julia` code format (style). See [here](@ref doc-Contribute:Julia-Code-Format) for more details. + - Improve and make sure the documentation can be built successfully. See [here](@ref doc-Contribute:Documentation) for more details. + - Update changelog. See [here](@ref doc-Contribute:Update-ChangeLog) for more details. +- Add the changed files to the `git` staging area `git add ...`, and then create some commits with short-but-descriptive names: `git commit`. +- Push the changes to your fork (`origin`): `git push -u origin `. You won’t be able to push to the remote (`qutip`) repositories directly. +- Go to the GitHub website for the repository you are contributing to, click on the “Pull Requests” tab, click the “New Pull Request” button, and follow the instructions there. + +Once the pull request (PR) is created, some members of the QuTiP admin team will review the code to make sure it is suitable for inclusion in the library, to check the programming, and to ensure everything meets our standards. For some repositories, several automated CI pipelines will run whenever you create or modify a PR. In general, these will basically be the same ones which you can run locally, and all CI pipelines are required to pass online before your changes are merged to the remote `main` branch. There might be some feedbacks and requested changes. You can add more commits to address these, and push them to the branch (``) of your fork (`origin`) to update the PR. + +The rest of this document covers programming standards. + +## [Runtests](@id doc-Contribute:Runtests) + +All the test scripts should be located in the folder `test` in the repository. To run the test, use the following command under the *__root directory of the repository__* you are working on: + +```shell +make test +``` + +This command will automatically rebuild `Julia` and run the script located in `test/runtests.jl` (should cover both the original tests and the new test(s) you add). + +The tests are divided into several test groups, where the group names are defined in the file `test/runtests.jl` with a variable `GROUP`. One can also run the test scripts just for a certain test group by adding an argument `GROUP=` to the `make test` command. For example, to run the tests for group `Core`, one can use the following command: + +```shell +make GROUP=Core test +``` + +### [Test Item Framework for Core tests](@id doc-Contribute:Test-Item-Framework-for-Core-tests) + +The tests in `GROUP=Core` are provided using the [Test Item Framework](https://www.julia-vscode.org/docs/stable/userguide/testitems/), which structures the test codes into `@testitems` and makes it easier to run individually. + +The [VS Code](https://code.visualstudio.com/) and its [Julia extension](https://www.julia-vscode.org/) provides us with options to run individual `@testitems`. It is much easier to find the specific core test that failed since the [Julia extension](https://www.julia-vscode.org/) in [VS Code](https://code.visualstudio.com/) will collect all core test failures and then display them in a structured way, directly at the place in the code where a specific core test failed. See [here](https://www.julia-vscode.org/docs/stable/userguide/testitems/) for more details. + +## [Julia Code Format](@id doc-Contribute:Julia-Code-Format) + +We use [`JuliaFormatter.jl`](https://github.com/domluna/JuliaFormatter.jl) to format all the source codes. The code style and extra formatting options is defined in the file `.JuliaFormatter.toml` in the repository. + +To format the changed codes, use the following command under the *__root directory of the repository__* you are working on: + +!!! note "Requirements" + If this is your first time running `make` command in the local repository you are working on or you just had reinstalled `Julia`, you should run `make setup` first. + +```shell +make format +``` + +## [Documentation](@id doc-Contribute:Documentation) + +All the documentation source files [in markdown (`.md`) format] and build scripts should be located in the folder `docs` in the repository. + +The document pages will be generated in the folder `docs/build/1/` (which is ignored by `git`) in the repository. + +To instantiate and build the documentation, run the following command under the *__root directory of the repository__* you are working on: + +!!! note "Requirements" + You need to install `Node.js` and `npm` first. + +```shell +make docs +``` + +This command will automatically rebuild `Julia` and run the script located in `docs/make.jl` (should be able to build the necessary files for the documentation). + +To read the documentation in a browser, you can run the following command: + +```shell +make vitepress +``` + +This will start a local Vitepress site of documentation at `http://localhost:5173` in your computer. + +## [Update ChangeLog](@id doc-Contribute:Update-ChangeLog) + +The changelog is written in the file `CHANGELOG.md` in the repository. If you add some changes to the repository and made a PR, you should also add some messages or release notes together with the related PRs/issues entries to `CHANGELOG.md`. For example, add a new line in `CHANGELOG.md`: + +```markdown +- some messages to describe the changes. ([#issue-ID], [#PR-ID]) +``` + +See also the [ChangeLog page](@ref ChangeLog) for more examples. + +After that, you can run the following command under the *__root directory of the repository__* you are working on: + +!!! note "Requirements" + If this is your first time running `make` command in the local repository you are working on or you just had reinstalled `Julia`, you should run `make setup` first. + +```shell +make changelog +``` + +This will automatically generate the full URLs for the references to PRs/issues by utilizing [`Changelog.jl`](https://github.com/JuliaDocs/Changelog.jl). + +If the changes you made are not necessary to be recorded in `CHANGELOG.md`, you can add the label `[Skip ChangeLog]` to the PR you made in the GitHub repository. diff --git a/docs/src/tutorials/lowrank.md b/docs/src/tutorials/lowrank.md deleted file mode 100644 index 42a100bd0..000000000 --- a/docs/src/tutorials/lowrank.md +++ /dev/null @@ -1,166 +0,0 @@ -# [Low rank master equation](@id doc-tutor:Low-rank-master-equation) - -We start by importing the packages - -```@example lowrank -using QuantumToolbox -using CairoMakie -CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) -``` - -Define lattice - -```@example lowrank -Nx, Ny = 2, 3 -latt = Lattice(Nx = Nx, Ny = Ny) -``` - -Define lr-space dimensions - -```@example lowrank -N_cut = 2 # Number of states of each mode -N_modes = latt.N # Number of modes -N = N_cut^N_modes # Total number of states -M = Nx * Ny + 1 # Number of states in the LR basis -``` - -Define lr states. Take as initial state all spins up. All other N states are taken as those with miniman Hamming distance to the initial state. - -```@example lowrank -ϕ = Vector{QuantumObject{Vector{ComplexF64},KetQuantumObject}}(undef, M) -ϕ[1] = kron(repeat([basis(2, 0)], N_modes)...) - -global i = 1 -for j in 1:N_modes - global i += 1 - i <= M && (ϕ[i] = mb(sp, j, latt) * ϕ[1]) -end -for k in 1:N_modes-1 - for l in k+1:N_modes - global i += 1 - i <= M && (ϕ[i] = mb(sp, k, latt) * mb(sp, l, latt) * ϕ[1]) - end -end -for i in i+1:M - ϕ[i] = QuantumObject(rand(ComplexF64, size(ϕ[1])[1]), dims = ϕ[1].dims) - normalize!(ϕ[i]) -end -``` - -Define the initial state - -```@example lowrank -z = hcat(broadcast(x -> x.data, ϕ)...) -p0 = 0.0 # Population of the lr states other than the initial state -B = Matrix(Diagonal([1 + 0im; p0 * ones(M - 1)])) -S = z' * z # Overlap matrix -B = B / tr(S * B) # Normalize B - -ρ = QuantumObject(z * B * z', dims = ones(Int, N_modes) * N_cut); # Full density matrix -``` - -Define the Hamiltonian and collapse operators - -```@example lowrank -# Define Hamiltonian and collapse operators -Jx = 0.9 -Jy = 1.04 -Jz = 1.0 -hx = 0.0 -γ = 1 - -Sx = sum([mb(sx, i, latt) for i in 1:latt.N]) -Sy = sum([mb(sy, i, latt) for i in 1:latt.N]) -Sz = sum([mb(sz, i, latt) for i in 1:latt.N]) -SFxx = sum([mb(sx, i, latt) * mb(sx, j, latt) for i in 1:latt.N for j in 1:latt.N]) - -H, c_ops = TFIM(Jx, Jy, Jz, hx, γ, latt; bc = pbc, order = 1) -e_ops = (Sx, Sy, Sz, SFxx) - -tl = LinRange(0, 10, 100); -``` - -### Full evolution - -```@example lowrank -@time mesol = mesolve(H, ρ, tl, c_ops; e_ops = [e_ops...]); -A = Matrix(mesol.states[end].data) -λ = eigvals(Hermitian(A)) -Strue = -sum(λ .* log2.(λ)) / latt.N; -``` - -### Low Rank evolution - -Define functions to be evaluated during the low-rank evolution - -```@example lowrank -function f_purity(p, z, B) - N = p.N - M = p.M - S = p.S - T = p.temp_MM - - mul!(T, S, B) - return tr(T^2) -end - -function f_trace(p, z, B) - N = p.N - M = p.M - S = p.S - T = p.temp_MM - - mul!(T, S, B) - return tr(T) -end - -function f_entropy(p, z, B) - C = p.A0 - σ = p.Bi - - mul!(C, z, sqrt(B)) - mul!(σ, C', C) - λ = eigvals(Hermitian(σ)) - λ = λ[λ.>1e-10] - return -sum(λ .* log2.(λ)) -end; -``` - -Define the options for the low-rank evolution - -```@example lowrank -opt = - LRMesolveOptions(err_max = 1e-3, p0 = 0.0, atol_inv = 1e-6, adj_condition = "variational", Δt = 0.0); - -@time lrsol = lr_mesolve(H, z, B, tl, c_ops; e_ops = e_ops, f_ops = (f_purity, f_entropy, f_trace), opt = opt); -``` - -Plot the results - -```@example lowrank -m_me = real(mesol.expect[3, :]) / Nx / Ny -m_lr = real(lrsol.expvals[3, :]) / Nx / Ny - -fig = Figure(size = (800, 400), fontsize = 15) -ax = Axis(fig[1, 1], xlabel = L"\gamma t", ylabel = L"M_{z}", xlabelsize = 20, ylabelsize = 20) -lines!(ax, tl, m_lr, label = L"LR $[M=M(t)]$", linewidth = 2) -lines!(ax, tl, m_me, label = "Fock", linewidth = 2, linestyle = :dash) -axislegend(ax, position = :rb) - -ax2 = Axis(fig[1, 2], xlabel = L"\gamma t", ylabel = "Value", xlabelsize = 20, ylabelsize = 20) -lines!(ax2, tl, 1 .- real(lrsol.funvals[1, :]), label = L"$1-P$", linewidth = 2) -lines!( - ax2, - tl, - 1 .- real(lrsol.funvals[3, :]), - label = L"$1-\mathrm{Tr}(\rho)$", - linewidth = 2, - linestyle = :dash, - color = :orange, -) -lines!(ax2, tl, real(lrsol.funvals[2, :]) / Nx / Ny, color = :blue, label = L"S", linewidth = 2) -hlines!(ax2, [Strue], color = :blue, linestyle = :dash, linewidth = 2, label = L"S^{\,\mathrm{true}}_{\mathrm{ss}}") -axislegend(ax2, position = :rb) - -fig -``` diff --git a/docs/src/users_guide/HEOM.md b/docs/src/users_guide/HEOM.md new file mode 100644 index 000000000..9b33a4a5e --- /dev/null +++ b/docs/src/users_guide/HEOM.md @@ -0,0 +1,33 @@ +# [Hierarchical Equations of Motion](@id doc:Hierarchical-Equations-of-Motion) + +The hierarchical equations of motion (HEOM) approach was originally developed by [Tanimura1989](@citet) in the context of physical chemistry to "exactly" solve a quantum system (labeled as ``\textrm{s}``) in contact with a bosonic environment, encapsulated in the following total Hamiltonian: + +```math +\hat{H}_{\textrm{total}} = \hat{H}_{\textrm{s}} + \sum_k \omega_k \hat{b}^\dagger_k \hat{b}_k + \hat{V}_{\textrm{s}} \sum_k g_k \left(\hat{b}_k + \hat{b}^\dagger_k\right), +``` + +where ``\hat{b}_k`` (``\hat{b}^\dagger_k``) is the bosonic annihilation (creation) operator associated to the ``k``th mode (with frequency ``\omega_k``), ``\hat{V}_{\textrm{s}}`` refer to the coupling operator acting on the system's degree of freedom, and ``g_k`` are the coupling strengths. + +As in other solutions to this problem, the properties of the bath are encapsulated by its temperature and its spectral density, + +```math +J(\omega) = 2 \pi \sum_k g^2_k \delta(\omega - \omega_k). +``` + +In the HEOM approach, for bosonic baths, one typically chooses a Drude-Lorentz spectral density: + +```math +J_{\textrm{DL}}(\omega) = \frac{4 \Delta W \omega}{\omega^2 + W^2}, +``` + +or an under-damped Brownian motion spectral density, + +```math +J_{\textrm{U}}(\omega)=\frac{2 \Delta^2 W \omega}{(\omega^2 - \omega_0^2)^2 + \omega^2 W^2}. +``` + +Here, ``\Delta`` represents the coupling strength between the system and the bosonic bath with band-width ``W`` and resonance frequency ``\omega_0``. + +We introduce an efficient `Julia` framework for HEOM approach called [`HierarchicalEOM.jl`](https://github.com/qutip/HierarchicalEOM.jl). This package is built upon `QuantumToolbox.jl` and provides a user-friendly and efficient tool to simulate complex open quantum systems based on HEOM approach. For a detailed explanation of this package, we recommend to read its [documentation](https://qutip.org/HierarchicalEOM.jl/) and also the article [Huang2023](@citet). + +Given the spectral density, the HEOM approach requires a decomposition of the bath correlation functions in terms of exponentials. In the [documentation of `HierarchicalEOM.jl`](https://qutip.org/HierarchicalEOM.jl/), we not only describe how this is done for both bosonic and fermionic environments with code examples, but also describe how to solve the time evolution (dynamics), steady-states, and spectra based on HEOM approach. diff --git a/docs/src/users_guide/QuantumObject/QuantumObject.md b/docs/src/users_guide/QuantumObject/QuantumObject.md index 61e9569ff..4abd95e88 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject.md @@ -14,6 +14,9 @@ The key difference between classical and quantum mechanics is the use of operato - `CUDA.CUSPARSE.CuSparseMatrixCSR` (sparse GPU matrix) - and even more ... +!!! note "Support for GPU arrays" + See [CUDA extension](@ref doc:CUDA) for more details. + We can create a [`QuantumObject`](@ref) with a user defined data set by passing an array of data into the [`QuantumObject`](@ref): ```@setup Qobj @@ -42,6 +45,8 @@ Qobj(rand(4, 4)) M = rand(ComplexF64, 4, 4) Qobj(M, dims = [2, 2]) # dims as Vector Qobj(M, dims = (2, 2)) # dims as Tuple (recommended) + +import QuantumToolbox: SVector # or using StaticArrays Qobj(M, dims = SVector(2, 2)) # dims as StaticArrays.SVector (recommended) ``` @@ -49,11 +54,11 @@ Qobj(M, dims = SVector(2, 2)) # dims as StaticArrays.SVector (recommended) Please note that here we put the `dims` as a tuple `(2, 2)`. Although it supports also `Vector` type (`dims = [2, 2]`), it is recommended to use `Tuple` or `SVector` from [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl) to improve performance. For a brief explanation on the impact of the type of `dims`, see the Section [The Importance of Type-Stability](@ref doc:Type-Stability). ```@example Qobj -Qobj(rand(4, 4), type = SuperOperator) +Qobj(rand(4, 4), type = SuperOperator()) ``` !!! note "Difference between `dims` and `size`" - Notice that `type`, `dims`, and `size` will change according to the input `data`. Although `dims` and `size` appear to be the same, `dims` keep tracking the dimension of individual Hilbert spaces of a multipartite system, while `size` does not. We refer the reader to the section [tensor products and partial traces](@ref doc:Tensor-products) for more information. + Notice that `type`, `dims`, and `size` will change according to the input `data`. Although `dims` and `size` appear to be the same, `dims` keep tracking the dimension of individual Hilbert spaces of a multipartite system, while `size` does not. We refer the reader to the section [Tensor Products and Partial Traces](@ref doc:Tensor-products-and-Partial-Traces) for more information. ## States and operators @@ -61,14 +66,16 @@ Manually specifying the data for each quantum object is inefficient. Even more s ### States - [`zero_ket`](@ref): zero ket vector -- [`fock`](@ref) or [`basis`](@ref): fock state ket vector -- [`fock_dm`](@ref): density matrix of a fock state +- [`fock`](@ref) or [`basis`](@ref): Fock state ket vector +- [`fock_dm`](@ref): density matrix of a Fock state - [`coherent`](@ref): coherent state ket vector - [`rand_ket`](@ref): random ket vector - [`coherent_dm`](@ref): density matrix of a coherent state - [`thermal_dm`](@ref): density matrix of a thermal state - [`maximally_mixed_dm`](@ref): density matrix of a maximally mixed state - [`rand_dm`](@ref): random density matrix +- [`enr_fock`](@ref): Fock state in the excitation number restricted (ENR) space +- [`enr_thermal_dm`](@ref): thermal state in the excitation number restricted (ENR) space - [`spin_state`](@ref): spin state - [`spin_coherent`](@ref): coherent spin state - [`bell_state`](@ref): Bell state @@ -103,6 +110,8 @@ Manually specifying the data for each quantum object is inefficient. Even more s - [`spin_J_set`](@ref): a set of Spin-`j` operators ``(S_x, S_y, S_z)`` - [`fdestroy`](@ref): fermion destruction operator - [`fcreate`](@ref): fermion creation operator +- [`enr_destroy`](@ref): destruction operator in the excitation number restricted (ENR) space +- [`enr_identity`](@ref): identity operator in the excitation number restricted (ENR) space - [`commutator`](@ref): commutator or anti-commutator - [`tunneling`](@ref): tunneling operator - [`qft`](@ref): discrete quantum Fourier transform matrix @@ -171,6 +180,7 @@ println(isoper(a)) # operator println(isoperket(a)) # operator-ket println(isoperbra(a)) # operator-bra println(issuper(a)) # super operator +println(isconstant(a)) # time-independent or not println(ishermitian(a)) # Hermitian println(isherm(a)) # synonym of ishermitian(a) println(issymmetric(a)) # symmetric @@ -191,6 +201,8 @@ Vector{Int64}(v_d) ``` ```@example Qobj +using SparseArrays + v_s = SparseVector(v_d) ``` @@ -210,14 +222,14 @@ SparseMatrixCSC{Int64}(x_s) Matrix{Float64}(x_s) ``` -To convert between dense and sparse arrays, one can also use [`dense_to_sparse`](@ref) and [`sparse_to_dense`](@ref): +To convert between dense and sparse arrays, one can also use [`to_sparse`](@ref) and [`to_dense`](@ref): ```@example Qobj -x_d = sparse_to_dense(x_s) +x_d = to_dense(x_s) ``` ```@example Qobj -dense_to_sparse(x_d) +to_sparse(x_d) ``` !!! note "Convert to GPU arrays" diff --git a/docs/src/users_guide/QuantumObject/QuantumObject_functions.md b/docs/src/users_guide/QuantumObject/QuantumObject_functions.md index 698017dce..3a1b16da5 100644 --- a/docs/src/users_guide/QuantumObject/QuantumObject_functions.md +++ b/docs/src/users_guide/QuantumObject/QuantumObject_functions.md @@ -10,6 +10,8 @@ Here is a table that summarizes all the supported linear algebra functions and a | **Description** | **Function call** | **Synonyms** | |:----------------|:------------------|:-------------| +| zero-like array | [`zero(Q)`](@ref zero) | [`qzero_like(Q)`](@ref qzero_like) | +| identity-like matrix | [`one(Q)`](@ref one) | [`qeye_like(Q)`](@ref qeye_like) | | conjugate | [`conj(Q)`](@ref conj) | - | | transpose | [`transpose(Q)`](@ref transpose) | [`trans(Q)`](@ref trans) | | conjugate transposition | [`adjoint(Q)`](@ref adjoint) | [`Q'`](@ref adjoint), [`dag(Q)`](@ref dag) | @@ -41,8 +43,6 @@ Here is a table that summarizes all the supported linear algebra functions and a - [`eigenenergies`](@ref): return eigenenergies (eigenvalues) - [`eigenstates`](@ref): return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors) -- [`eigvals`](@ref): return eigenvalues -- [`eigen`](@ref): using dense eigen solver and return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors) - [`eigsolve`](@ref): using sparse eigen solver and return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors) - [`eigsolve_al`](@ref): using the Arnoldi-Lindblad eigen solver and return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors) diff --git a/docs/src/users_guide/autodiff.md b/docs/src/users_guide/autodiff.md new file mode 100644 index 000000000..058b38cd2 --- /dev/null +++ b/docs/src/users_guide/autodiff.md @@ -0,0 +1,195 @@ +# [Automatic Differentiation](@id doc:autodiff) + +Automatic differentiation (AD) has emerged as a key technique in computational science, enabling exact and efficient computation of derivatives for functions defined by code. Unlike symbolic differentiation, which may produce complex and inefficient expressions, or finite-difference methods, which suffer from numerical instability and poor scalability, AD leverages the chain rule at the level of elementary operations to provide machine-precision gradients with minimal overhead. + +In `QuantumToolbox.jl`, we have introduced preliminary support for automatic differentiation. Many of the core functions are compatible with AD engines such as [`Zygote.jl`](https://github.com/FluxML/Zygote.jl), [`Enzyme.jl`](https://github.com/EnzymeAD/Enzyme.jl) or [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl), allowing users to compute gradients of observables or cost functionals involving the time evolution of open quantum systems. Although `QuantumToolbox.jl` was not originally designed with AD in mind, its architecture—rooted in Julia’s multiple dispatch and generic programming model—facilitated the integration of AD capabilities. Many core functions were already compatible with AD engines out of the box. + +!!! warning "Experimental Functionality" + At present, this functionality is considered experimental and not all parts of the library are AD-compatible. Here we provide a brief overview of the current state of AD support in `QuantumToolbox.jl` and how to use it. + + +## [Forward versus Reverse Mode AD](@id doc:autodiff:forward-versus-reverse) + +Automatic differentiation can be broadly categorized into two modes: forward mode and reverse mode. The choice between these modes depends on the nature of the function being differentiated and the number of inputs and outputs: + +- **Forward Mode AD**: This mode is particularly efficient for functions with many outputs and few inputs. It works by propagating derivatives from the inputs through the computational graph to the outputs. Forward mode is often preferred when the number of input variables is small, as it computes the derivative of each output with respect to each input in a single pass. + +- **Reverse Mode AD**: In contrast, reverse mode is more efficient for functions with many inputs and few outputs. It operates by first computing the function's output and then propagating derivatives backward through the computational graph. This mode is commonly used in machine learning and optimization applications, where the loss function (output) depends on a large number of parameters (inputs). + +Understanding the differences between these two modes can help users choose the most appropriate approach for their specific use case in `QuantumToolbox.jl`. + +## [Differentiate the master equation](@id doc:autodiff:master-equation) + +One of the primary use cases for automatic differentiation in `QuantumToolbox.jl` is the differentiation of the master equation. The master equation describes the time evolution of a quantum system's density matrix under the influence of non-unitary dynamics, such as dissipation and decoherence. Let's consider a set of parameters $\mathbf{p} = (p_1, p_2, \ldots, p_n)$ that influence the system's dynamics. The Hamiltonian and the dissipators will depend on these parameters + +```math +\hat{H} = \hat{H}(\mathbf{p}), \qquad \hat{L}_j = \hat{L}_j(\mathbf{p}), +``` + +Hence, the density matrix will evolve according to the master equation + +```@raw html + +``` +```math +\begin{align} +\frac{d \hat{\rho}(\mathbf{p}, t)}{dt} =& -i[\hat{H}(\mathbf{p}), \hat{\rho}(\mathbf{p}, t)] \\ +&+ \sum_j \hat{L}_j(\mathbf{p}) \hat{\rho}(\mathbf{p}, t) \hat{L}_j(\mathbf{p})^\dagger - \frac{1}{2} \left\{ \hat{L}_j(\mathbf{p})^\dagger \hat{L}_j(\mathbf{p}), \hat{\rho}(\mathbf{p}, t) \right\} \, , +\end{align} \tag{1} +``` + +which depends on the parameters $\mathbf{p}$ and time $t$. + +We now want to compute the expectation value of an observable $\hat{O}$ at time $t$: + +```math +\langle \hat{O}(\mathbf{p}, t) \rangle = \text{Tr}[\hat{O} \hat{\rho}(\mathbf{p}, t)] \, , +``` + +which will also depend on the parameters $\mathbf{p}$ and time $t$. + +Our goal is to compute the derivative of the expectation value with respect to the parameters: + +```math +\frac{\partial \langle \hat{O}(\mathbf{p}, t) \rangle}{\partial p_j} = \frac{\partial}{\partial p_j} \text{Tr}[\hat{O} \hat{\rho}(\mathbf{p}, t)] \, , +``` + +and to achieve this, we can use an AD engine like [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) (forward mode) or [`Zygote.jl`](https://github.com/FluxML/Zygote.jl) (reverse mode). + +Let's apply this to a simple example of a driven-dissipative quantum harmonic oscillator. The Hamiltonian in the drive frame is given by + +```math +\hat{H} = \Delta \hat{a}^\dagger \hat{a} + F \left( \hat{a} + \hat{a}^\dagger \right) \, , +``` + +where $\Delta = \omega_0 - \omega_d$ is the cavity-drive detuning, $F$ is the drive strength, and $\hat{a}$ and $\hat{a}^\dagger$ are the annihilation and creation operators, respectively. The system is subject to a single dissipative channel with a Lindblad operator $\hat{L} = \sqrt{\gamma} \hat{a}$, where $\gamma$ is the dissipation rate. If we start from the ground state $\hat{\rho}(0) = \vert 0 \rangle \langle 0 \vert$, the systems evolves according to the master equation in [Eq. (1)](#eq:master-equation). + +We now want to study the number of photons at the steady state, and how it varies with $\mathbf{p} = (\Delta, F, \gamma)$, namely $\nabla_\mathbf{p} \langle \hat{a}^\dagger \hat{a} \rangle (\mathbf{p}, t \to \infty)$. We can extract an analytical expression, in order to verify the correctness of the AD implementation: + +```math +\langle \hat{a}^\dagger \hat{a} \rangle_\mathrm{ss} = \frac{F^2}{\Delta^2 + \frac{\gamma^2}{4}} \, , +``` + +with the gradient given by + +```math +\nabla_\mathbf{p} \langle \hat{a}^\dagger \hat{a} \rangle_\mathrm{ss} = +\begin{pmatrix} +\frac{-2 F^2 \Delta}{(\Delta^2 + \frac{\gamma^2}{4})^2} \\ +\frac{2 F}{\Delta^2 + \frac{\gamma^2}{4}} \\ +\frac{-F^2 \gamma}{2 (\Delta^2 + \frac{\gamma^2}{4})^2} +\end{pmatrix} \, . +``` + +Although `QuantumToolbox.jl` has the [`steadystate`](@ref) function to directly compute the steady state without explicitly solving the master equation, here we use the [`mesolve`](@ref) function to integrate up to a long time $t_\mathrm{max}$, and then compute the expectation value of the number operator. We will demonstrate how to compute the gradient using both [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) and [`Zygote.jl`](https://github.com/FluxML/Zygote.jl). + +### [Forward Mode AD with ForwardDiff.jl](@id doc:autodiff:forward) + +```@setup autodiff +using QuantumToolbox +``` + +We start by importing [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) and defining the parameters and operators: + +```@example autodiff +using ForwardDiff + +const N = 20 +const a = destroy(N) +const ψ0 = fock(N, 0) +const t_max = 40 +const tlist = range(0, t_max, 100) +``` + +Then, we define a function that take the parameters `p` as an input and returns the expectation value of the number operator at `t_max`. We also define the analytical solution of the steady state photon number and its gradient for comparison: + +```@example autodiff +function my_f_mesolve_direct(p) + H = p[1] * a' * a + p[2] * (a + a') + c_ops = [sqrt(p[3]) * a] + sol = mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false)) + return real(expect(a' * a, sol.states[end])) +end + +# Analytical solution +function my_f_analytical(p) + Δ, F, γ = p + return F^2 / (Δ^2 + γ^2 / 4) +end +function my_grad_analytical(p) + Δ, F, γ = p + return [ + -2 * F^2 * Δ / (Δ^2 + γ^2 / 4)^2, + 2 * F / (Δ^2 + γ^2 / 4), + -F^2 * γ / (2 * (Δ^2 + γ^2 / 4)^2) + ] +end +``` + +The gradient can be computed using `ForwardDiff.gradient`: + +```@example autodiff +Δ = 1.5 +F = 1.5 +γ = 1.5 +params = [Δ, F, γ] + +grad_exact = my_grad_analytical(params) +grad_fd = ForwardDiff.gradient(my_f_mesolve_direct, params) +``` + +and test if the results match: + +```@example autodiff +isapprox(grad_exact, grad_fd; atol = 1e-5) +``` + +### [Reverse Mode AD with Zygote.jl](@id doc:autodiff:reverse) + +Reverse-mode differentiation is significantly more challenging than forward-mode when dealing ODEs, as the complexity arises from the need to propagate gradients backward through the entire time evolution of the quantum state. + +`QuantumToolbox.jl` leverages the advanced capabilities of [`SciMLSensitivity.jl`](https://github.com/SciML/SciMLSensitivity.jl) to handle this complexity. [`SciMLSensitivity.jl`](https://github.com/SciML/SciMLSensitivity.jl) implements sophisticated methods for computing gradients of ODE solutions, such as the adjoint method, which computes gradients by solving an additional "adjoint" ODE backward in time. For more details on the adjoint method and other sensitivity analysis techniques, please refer to the [`SciMLSensitivity.jl` documentation](https://docs.sciml.ai/SciMLSensitivity/stable/). + +In order to reverse-differentiate the master equation, we need to define the operators as [`QuantumObjectEvolution`](@ref) objects, which use [`SciMLOperators.jl`](https://github.com/SciML/SciMLOperators.jl) to represent parameter-dependent operators. + +```@example autodiff +using Zygote +using SciMLSensitivity + +# For SciMLSensitivity.jl +coef_Δ(p, t) = p[1] +coef_F(p, t) = p[2] +coef_γ(p, t) = sqrt(p[3]) +H = QobjEvo(a' * a, coef_Δ) + QobjEvo(a + a', coef_F) +c_ops = [QobjEvo(a, coef_γ)] +const L = liouvillian(H, c_ops) + +function my_f_mesolve(p) + sol = mesolve( + L, + ψ0, + tlist, + progress_bar = Val(false), + params = p, + sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()), + ) + + return real(expect(a' * a, sol.states[end])) +end +``` + +And the gradient can be computed using `Zygote.gradient`: + +```@example autodiff +grad_zygote = Zygote.gradient(my_f_mesolve, params)[1] +``` + +Finally, we can compare the results from [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) and [`Zygote.jl`](https://github.com/FluxML/Zygote.jl): + +```@example autodiff +isapprox(grad_fd, grad_zygote; atol = 1e-5) +``` + +## [Conclusion](@id doc:autodiff:conclusion) + +In this section, we have explored the integration of automatic differentiation into `QuantumToolbox.jl`, enabling users to compute gradients of observables and cost functionals involving the time evolution of open quantum systems. We demonstrated how to differentiate the master equation using both forward mode with [`ForwardDiff.jl`](https://github.com/JuliaDiff/ForwardDiff.jl) and reverse mode with [`Zygote.jl`](https://github.com/FluxML/Zygote.jl), showcasing the flexibility and power of automatic differentiation in quantum computing applications. AD can be applied to other functions in `QuantumToolbox.jl`, although the support is still experimental and not all functions are guaranteed to be compatible. We encourage users to experiment with AD in their quantum simulations and contribute to the ongoing development of this feature. \ No newline at end of file diff --git a/docs/src/users_guide/cluster.md b/docs/src/users_guide/cluster.md new file mode 100644 index 000000000..d36259f03 --- /dev/null +++ b/docs/src/users_guide/cluster.md @@ -0,0 +1,264 @@ +# [Intensive parallelization on a Cluster](@id doc:Intensive-parallelization-on-a-Cluster) + +## Introduction + +In this example, we will demonstrate how to seamlessly perform intensive parallelization on a cluster using the **QuantumToolbox.jl** package. Indeed, thanks to the [**Distributed.jl**](https://docs.julialang.org/en/v1/manual/distributed-computing/) and [**ClusterManagers.jl**](https://github.com/JuliaParallel/ClusterManagers.jl) packages, it is possible to parallelize on a cluster with minimal effort. The following examples are applied to a cluster with the [SLURM](https://slurm.schedmd.com/documentation.html) workload manager, but the same principles can be applied to other workload managers, as the [**ClusterManagers.jl**](https://github.com/JuliaParallel/ClusterManagers.jl) package is very versatile. + +## SLURM batch script + +To submit a batch script to [SLURM](https://slurm.schedmd.com/documentation.html), we start by creating a file named `run.batch` with the following content: + +```bash +#!/bin/bash +#SBATCH --job-name=example +#SBATCH --output=output.out +#SBATCH --account=your_account +#SBATCH --nodes=10 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=72 +#SBATCH --mem=128GB +#SBATCH --time=0:10:00 +#SBATCH --qos=parallel + +# Set PATH to include the directory of your custom Julia installation +export PATH=/home/username/.juliaup/bin:$PATH + +# Now run Julia +julia --project script.jl +``` + +where we have to replace `your_account` with the name of your account. This script will be used to submit the job to the cluster by using the following command in terminal: + +```shell +sbatch run.batch +``` + +Here, we are requesting `10` nodes with `72` threads each (`720` parallel jobs). The `--time` flag specifies the maximum time that the job can run. To see all the available options, you can check the [SLURM documentation](https://slurm.schedmd.com/documentation.html). We also export the path to the custom Julia installation, which is necessary to run the script (replace `username` with your username). Finally, we run the script `script.jl` with the command `julia --project script.jl`. + +In the following, we will consider two examples: + +1. **Parallelization of a Monte Carlo quantum trajectories** +2. **Parallelization of a Master Equation by sweeping over parameters** + +## Monte Carlo Quantum Trajectories + +Let's consider a `2`-dimensional transverse field Ising model with `4x3` spins. The Hamiltonian is given by + +```math +\hat{H} = \frac{J_z}{2} \sum_{\langle i,j \rangle} \hat{\sigma}_i^z \hat{\sigma}_j^z + h_x \sum_i \hat{\sigma}_i^x \, , +``` + +where the sums are over nearest neighbors, and the collapse operators are given by + +```math +\hat{c}_i = \sqrt{\gamma} \hat{\sigma}_i^- \, . +``` + +In this case, the `script.jl` contains the following content: + +```julia +using Distributed +using ClusterManagers + +const SLURM_NUM_TASKS = parse(Int, ENV["SLURM_NTASKS"]) +const SLURM_CPUS_PER_TASK = parse(Int, ENV["SLURM_CPUS_PER_TASK"]) + +exeflags = ["--project=.", "-t $SLURM_CPUS_PER_TASK"] +addprocs(SlurmManager(SLURM_NUM_TASKS); exeflags=exeflags, topology=:master_worker) + + +println("################") +println("Hello! You have $(nworkers()) workers with $(remotecall_fetch(Threads.nthreads, 2)) threads each.") + +println("----------------") + + +println("################") + +flush(stdout) + +@everywhere begin + using QuantumToolbox + using OrdinaryDiffEq + + BLAS.set_num_threads(1) +end + +# Define lattice + +Nx = 4 +Ny = 3 +latt = Lattice(Nx = Nx, Ny = Ny) + +# Define Hamiltonian and collapse operators +Jx = 0.0 +Jy = 0.0 +Jz = 1.0 +hx = 0.2 +hy = 0.0 +hz = 0.0 +γ = 1 + +Sx = mapreduce(i -> multisite_operator(latt, i=>sigmax()), +, 1:latt.N) +Sy = mapreduce(i -> multisite_operator(latt, i=>sigmay()), +, 1:latt.N) +Sz = mapreduce(i -> multisite_operator(latt, i=>sigmaz()), +, 1:latt.N) + +H, c_ops = DissipativeIsing(Jx, Jy, Jz, hx, hy, hz, γ, latt; boundary_condition = Val(:periodic_bc), order = 1) +e_ops = [Sx, Sy, Sz] + +# Time Evolution + +ψ0 = fock(2^latt.N, 0, dims = ntuple(i->2, Val(latt.N))) + +tlist = range(0, 10, 100) + +sol_mc = mcsolve(H, ψ0, tlist, c_ops, e_ops=e_ops, ntraj=5000, ensemblealg=EnsembleSplitThreads()) + +## + +println("FINISH!") + +rmprocs(workers()) +``` + +In this script, we first load the necessary packages for distributed computing on the cluster (`Distributed.jl` and `ClusterManagers.jl`). Thanks to the environment variables (previously defined in the SLURM script), we can define the number of tasks and the number of CPUs per task. Then, we initialize the distributed network with the `addprocs(SlurmManager(SLURM_NUM_TASKS); exeflags=exeflags, topology=:master_worker)` command. We then import the packages with the `@everywhere` macro, meaning to load them in all the workers. Moreover, in order to avoid conflicts between the multithreading of the BLAS library and the native Julia multithreading, we set the number of threads of the BLAS library to 1 with the `BLAS.set_num_threads(1)` command. More information about this can be found [here](https://docs.julialang.org/en/v1/manual/performance-tips/#man-multithreading-linear-algebra). + +With the + +```julia +println("Hello! You have $(nworkers()) workers with $(remotecall_fetch(Threads.nthreads, 2)) threads each.") +``` + +command, we test that the distributed network is correctly initialized. The `remotecall_fetch(Threads.nthreads, 2)` command returns the number of threads of the worker with ID `2`. + +We then write the main part of the script, where we define the lattice through the [`Lattice`](@ref) function. We set the parameters and define the Hamiltonian and collapse operators with the [`DissipativeIsing`](@ref) function. We also define the expectation operators `e_ops` and the initial state `ψ0`. Finally, we perform the Monte Carlo quantum trajectories with the [`mcsolve`](@ref) function. The `ensemblealg=EnsembleSplitThreads()` argument is used to parallelize the Monte Carlo quantum trajectories, by splitting the ensemble of trajectories among the workers. For a more detailed explanation of the different ensemble methods, you can check the [official documentation](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/) of the [**DifferentialEquations.jl**](https://github.com/SciML/DifferentialEquations.jl/) package. Finally, the `rmprocs(workers())` command is used to remove the workers after the computation is finished. + +The output of the script will be printed in the `output.out` file, which contains an output similar to the following: + +``` +################ +Hello! You have 10 workers with 72 threads each. +---------------- +################ + +Progress: [==============================] 100.0% --- Elapsed Time: 0h 00m 21s (ETA: 0h 00m 00s) + +FINISH! +``` + +where we can see that the computation **lasted only 21 seconds**. + +## Master Equation by Sweeping Over Parameters + +In this example, we will consider a driven Jaynes-Cummings model, describing a two-level atom interacting with a driven cavity mode. The Hamiltonian is given by + +```math +\hat{H} = \omega_c \hat{a}^\dagger \hat{a} + \frac{\omega_q}{2} \hat{\sigma}_z + g (\hat{a} \hat{\sigma}_+ + \hat{a}^\dagger \hat{\sigma}_-) + F \cos(\omega_d t) (\hat{a} + \hat{a}^\dagger) \, , +``` + +and the collapse operators are given by + +```math +\hat{c}_1 = \sqrt{\gamma} \hat{a} \, , \quad \hat{c}_2 = \sqrt{\gamma} \hat{\sigma}_- \, . +``` + +The SLURM batch script file is the same as before, but the `script.jl` file now contains the following content: + +```julia +using Distributed +using ClusterManagers + +const SLURM_NUM_TASKS = parse(Int, ENV["SLURM_NTASKS"]) +const SLURM_CPUS_PER_TASK = parse(Int, ENV["SLURM_CPUS_PER_TASK"]) + +exeflags = ["--project=.", "-t $SLURM_CPUS_PER_TASK"] +addprocs(SlurmManager(SLURM_NUM_TASKS); exeflags=exeflags, topology=:master_worker) + + +println("################") +println("Hello! You have $(nworkers()) workers with $(remotecall_fetch(Threads.nthreads, 2)) threads each.") + +println("----------------") + + +println("################") + +flush(stdout) + +@everywhere begin + using QuantumToolbox + using OrdinaryDiffEq + + BLAS.set_num_threads(1) +end + +@everywhere begin + const Nc = 20 + const ωc = 1.0 + const g = 0.05 + const γ = 0.01 + const F = 0.01 + + const a = tensor(destroy(Nc), qeye(2)) + + const σm = tensor(qeye(Nc), sigmam()) + const σp = tensor(qeye(Nc), sigmap()) + + H(ωq) = ωc * a' * a + ωq * tensor(num(Nc), qeye(2)) + g * (a' * σm + a * σp) + + coef(p, t) = p.F * cos(p.ωd * t) # coefficient for the time-dependent term + + const c_ops = [sqrt(γ) * a, sqrt(γ) * σm] + const e_ops = [a' * a] +end + +# Define the ODE problem and the EnsembleProblem generation function + +@everywhere begin + ωq_list = range(ωc - 3*g, ωc + 3*g, 100) + ωd_list = range(ωc - 3*g, ωc + 3*g, 100) + + const iter = collect(Iterators.product(ωq_list, ωd_list)) + + function my_prob_func(prob, i, repeat, channel) + ωq, ωd = iter[i] + H_i = H(ωq) + H_d_i = H_i + QobjEvo(a + a', coef) # Hamiltonian with a driving term + + L = liouvillian(H_d_i, c_ops).data # Make the Liouvillian + + put!(channel, true) # Update the progress bar channel + + remake(prob, f=L, p=(F = F, ωd = ωd)) + end +end + +ωq, ωd = iter[1] +H0 = H(ωq) + QobjEvo(a + a', coef) +ψ0 = tensor(fock(Nc, 0), basis(2, 1)) # Ground State +tlist = range(0, 20 / γ, 1000) + +prob = mesolveProblem(H0, ψ0, tlist, c_ops, e_ops=e_ops, progress_bar=Val(false), params=(F = F, ωd = ωd)) + +### Just to print the progress bar +progr = ProgressBar(length(iter)) +progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1)) +### +ens_prob = EnsembleProblem(prob.prob, prob_func=(prob, i, repeat) -> my_prob_func(prob, i, repeat, progr_channel)) + + +@sync begin + @async while take!(progr_channel) + next!(progr) + end + + @async begin + sol = solve(ens_prob, Tsit5(), EnsembleSplitThreads(), trajectories = length(iter)) + put!(progr_channel, false) + end +end +``` + +We are using the [`mesolveProblem`](@ref) function to define the master equation problem. We added some code to manage the progress bar, which is updated through a `RemoteChannel`. The `prob_func` argument of the `EnsembleProblem` function is used to define the function that generates the problem for each iteration. The `iter` variable contains the product of the `ωq_list` and `ωd_list` lists, which are used to sweep over the parameters. The `sol = solve(ens_prob, Tsit5(), EnsembleDistributed(), trajectories=length(iter))` command is used to solve the problem with the distributed ensemble method. The output of the script will be printed in the `output.out` file, which contains an output similar to the previous example. + + diff --git a/docs/src/users_guide/extensions/cairomakie.md b/docs/src/users_guide/extensions/cairomakie.md new file mode 100644 index 000000000..aa4512f06 --- /dev/null +++ b/docs/src/users_guide/extensions/cairomakie.md @@ -0,0 +1,23 @@ +# [Extension for the Makie.jl ecosystem](@id doc:Makie) + +This is an extension to support visualization (plotting functions) using [`Makie.jl`](https://github.com/MakieOrg/Makie.jl) library. + +This extension will be automatically loaded if user imports both `QuantumToolbox.jl` and [`Makie.jl`](https://github.com/MakieOrg/Makie.jl). It is worth noting that the `Makie.jl` package provides only the engine for plotting, and the user has to import the specific backend. Here we demonstrate the usage of [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie), which will automatically import `Makie.jl`. + +```julia +using QuantumToolbox +using CairoMakie +``` + +To plot with [`CairoMakie.jl`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) library, specify the keyword argument `library = Val(:Makie)` for the plotting functions. + +!!! warning "Beware of type-stability!" + If you want to keep type stability, it is recommended to use `Val(:Makie)` instead of `:Makie`. See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details. + +The supported plotting functions are listed as follows: + +| **Plotting Function** | **Description** | +|:----------------------|:----------------| +| [`plot_wigner`](@ref) | [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) | +| [`plot_fock_distribution`](@ref) | [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution | +| [`plot_bloch`](@ref) | [Plotting on the Bloch Sphere](@ref doc:Plotting-on-the-Bloch-Sphere) | diff --git a/docs/src/users_guide/extensions/cuda.md b/docs/src/users_guide/extensions/cuda.md index e40c90915..eacef8083 100644 --- a/docs/src/users_guide/extensions/cuda.md +++ b/docs/src/users_guide/extensions/cuda.md @@ -4,10 +4,7 @@ This is an extension to support `QuantumObject.data` conversion from standard dense and sparse CPU arrays to GPU ([`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl)) arrays. -!!! note "Requirements" - The [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl) extension for `QuantumToolbox.jl` requires `Julia 1.9+`. - -This extension will be automatically loaded if user imports both `QuantumToolbox` and [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl): +This extension will be automatically loaded if user imports both `QuantumToolbox.jl` and [`CUDA.jl`](https://github.com/JuliaGPU/CUDA.jl): ```julia using QuantumToolbox @@ -39,7 +36,7 @@ V = fock(2, 0) # CPU dense vector ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element Vector{ComplexF64}: 1.0 + 0.0im 0.0 + 0.0im @@ -50,7 +47,7 @@ cu(V) ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element CuArray{ComplexF64, 1, CUDA.DeviceMemory}: 1.0 + 0.0im 0.0 + 0.0im @@ -61,7 +58,7 @@ cu(V; word_size = 32) ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element CuArray{ComplexF32, 1, CUDA.DeviceMemory}: 1.0 + 0.0im 0.0 + 0.0im @@ -72,7 +69,7 @@ M = Qobj([1 2; 3 4]) # CPU dense matrix ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=false +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=false 2×2 Matrix{Int64}: 1 2 3 4 @@ -83,7 +80,7 @@ cu(M) ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=false +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=false 2×2 CuArray{Int64, 2, CUDA.DeviceMemory}: 1 2 3 4 @@ -94,7 +91,7 @@ cu(M; word_size = 32) ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=false +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=false 2×2 CuArray{Int32, 2, CUDA.DeviceMemory}: 1 2 3 4 @@ -107,7 +104,7 @@ V = fock(2, 0; sparse=true) # CPU sparse vector ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element SparseVector{ComplexF64, Int64} with 1 stored entry: [1] = 1.0+0.0im ``` @@ -117,7 +114,7 @@ cu(V) ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element CuSparseVector{ComplexF64, Int32} with 1 stored entry: [1] = 1.0+0.0im ``` @@ -127,7 +124,7 @@ cu(V; word_size = 32) ``` ``` -Quantum Object: type=Ket dims=[2] size=(2,) +Quantum Object: type=Ket() dims=[2] size=(2,) 2-element CuSparseVector{ComplexF32, Int32} with 1 stored entry: [1] = 1.0+0.0im ``` @@ -137,7 +134,7 @@ M = sigmax() # CPU sparse matrix ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries: ⋅ 1.0+0.0im 1.0+0.0im ⋅ @@ -148,7 +145,7 @@ cu(M) ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 CuSparseMatrixCSC{ComplexF64, Int32} with 2 stored entries: ⋅ 1.0+0.0im 1.0+0.0im ⋅ @@ -159,7 +156,7 @@ cu(M; word_size = 32) ``` ``` -Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true +Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true 2×2 CuSparseMatrixCSC{ComplexF32, Int32} with 2 stored entries: ⋅ 1.0+0.0im 1.0+0.0im ⋅ diff --git a/docs/src/users_guide/plotting_the_bloch_sphere.md b/docs/src/users_guide/plotting_the_bloch_sphere.md new file mode 100644 index 000000000..a306d2977 --- /dev/null +++ b/docs/src/users_guide/plotting_the_bloch_sphere.md @@ -0,0 +1,276 @@ +# [Plotting on the Bloch Sphere](@id doc:Plotting-on-the-Bloch-Sphere) + +```@setup Bloch_sphere_rendering +using QuantumToolbox + +using CairoMakie +CairoMakie.enable_only_mime!(MIME"image/svg+xml"()) +``` + +## Introduction + +When studying the dynamics of a two-level system, it's often convenient to visualize the state of the system by plotting the state vector or density matrix on the Bloch sphere. + +In [`QuantumToolbox`](https://qutip.org/QuantumToolbox.jl/), this can be done using the [`Bloch`](@ref) or [`plot_bloch`](@ref) methods that provide same syntax as [QuTiP](https://qutip.readthedocs.io/en/stable/guide/guide-bloch.html). + +## Create a Bloch Sphere + +In [`QuantumToolbox`](https://qutip.org/QuantumToolbox.jl/), creating a [`Bloch`](@ref) sphere is accomplished by calling either: + +!!! note "Import plotting libraries" + Remember to import plotting libraries first. Here, we demonstrate the functionalities with [`CairoMakie.jl`](https://docs.makie.org/stable/explanations/backends/cairomakie.html). + +```@example Bloch_sphere_rendering +b = Bloch() +``` + +which will load an instance of [`Bloch`](@ref). Before getting into the details of these objects, we can simply plot the blank [`Bloch`](@ref) sphere associated with these instances via: + +```@example Bloch_sphere_rendering +fig, _ = render(b) +fig +``` + +See the [API documentation for Bloch sphere](@ref doc-API:Bloch-Sphere) for a full list of other available functions. + +## Add a single data point + +As an example, we can add a single data point via [`add_points!`](@ref): + +```@example Bloch_sphere_rendering +pnt = [1 / sqrt(3), 1 / sqrt(3), 1 / sqrt(3)] +add_points!(b, pnt) +fig, _ = render(b) +fig +``` + +## Add a single vector + +Add a single vector via [`add_vectors!`](@ref): + +```@example Bloch_sphere_rendering +vec = [0, 1, 0] +add_vectors!(b, vec) +fig, _ = render(b) +fig +``` + +## Add a single quantum state + +Add another vector corresponding to the ``|0\rangle`` state: + +```@example Bloch_sphere_rendering +z0 = basis(2, 0) +add_states!(b, z0) +fig, _ = render(b) +fig +``` + +## Add multiple data + +We can also plot multiple points, vectors, and states at the same time by passing arrays instead of individual elements via [`add_points!`](@ref), [`add_vectors!`](@ref), and [`add_states!`](@ref), respectively. Before giving an example, we can use [`clear!`](@ref) to remove the current data from our [`Bloch`](@ref) sphere instead of creating a new instance: + +```@example Bloch_sphere_rendering +clear!(b) +fig, _ = render(b) +fig +``` + +Now on the same [`Bloch`](@ref) sphere, we can plot the three states via [`add_states!`](@ref) associated with the `x`, `y`, and `z` directions: + +```@example Bloch_sphere_rendering +x = basis(2, 0) + basis(2, 1) +y = basis(2, 0) + im * basis(2, 1) +z = basis(2, 0) +add_states!(b, [x, y, z]) +fig, _ = render(b) +fig +``` + +!!! note "State normalization" + The function [`add_states!`](@ref) will automatically normalize the given quantum state(s), while [`add_vectors!`](@ref) does not normalize the given vectors. + +A similar method works for adding vectors: + +```@example Bloch_sphere_rendering +clear!(b) +vecs = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] +add_vectors!(b, vecs) +fig, _ = render(b) +fig +``` + +# Add lines and arcs + +You can also add lines and arcs via [`add_line!`](@ref) and [`add_arc!`](@ref) respectively: + +```@example Bloch_sphere_rendering +add_line!(b, x, y) +add_arc!(b, y, z) +fig, _ = render(b) +fig +``` + +## Add multiple points + +Adding multiple points to the [`Bloch`](@ref) sphere works slightly differently than adding multiple states or vectors. For example, lets add a set of `20` points around the equator (after calling [`clear!`](@ref)): + +```@example Bloch_sphere_rendering +clear!(b) + +th = LinRange(0, 2π, 20) +xp = cos.(th) +yp = sin.(th) +zp = zeros(20) +pnts = [xp, yp, zp] +add_points!(b, pnts) +fig, lscene = render(b) +fig +``` + +Notice that, in contrast to states or vectors, each point remains the same color as the initial point. This is because adding multiple data points using [`add_points!`](@ref) is interpreted, by default, to correspond to a single data point (single qubit state) plotted at different times. This is very useful when visualizing the dynamics of a qubit. If we want to plot additional qubit states we can call additional [`add_points!`](@ref) function: + +```@example Bloch_sphere_rendering +xz = zeros(20) +yz = sin.(th) +zz = cos.(th) +add_points!(b, [xz, yz, zz]) +fig, lscene = render(b) +fig +``` + +The color and shape of the data points is varied automatically by [`Bloch`](@ref). Notice how the color and point markers change for each set of data. Again, we have had to call [`add_points!`](@ref) twice because adding more than one set of multiple data points is not supported by the [`add_points!`](@ref) function. + +What if we want to vary the color of our points. We can tell [`Bloch`](@ref) to vary the color of each point according to the colors listed in the `point_color` field (see [Configuring the Bloch sphere](@ref doc:Configuring-the-Bloch-sphere) below). Again, after [`clear!`](@ref): + +```@example Bloch_sphere_rendering +clear!(b) + +xp = cos.(th) +yp = sin.(th) +zp = zeros(20) +pnts = [xp, yp, zp] +add_points!(b, pnts, meth=:m) # add `meth=:m` to signify 'multi' colored points +fig, lscene = render(b) +fig +``` + +Now, the data points cycle through a variety of predefined colors. Now lets add another set of points, but this time we want the set to be a single color, representing say a qubit going from the ``|0\rangle`` state to the ``|1\rangle`` state in the `y-z` plane: + +```@example Bloch_sphere_rendering +pnts = [xz, yz, zz] +add_points!(b, pnts) # no `meth=:m` +fig, lscene = render(b) +fig +``` + +## [Configuring the Bloch sphere](@id doc:Configuring-the-Bloch-sphere) + +At the end of the last section we saw that the colors and marker shapes of the data plotted on the Bloch sphere are automatically varied according to the number of points and vectors added. But what if you want a different choice of color, or you want your sphere to be purple with different axes labels? Well then you are in luck as the [`Bloch`](@ref) structure has many fields which one can control. Assuming `b = Bloch()`: + +### Data storage + +| **Field** | **Description** | **Default setting** | +|:----------|:----------------|:--------------------| +| `b.points` | Points to plot on the Bloch sphere (3D coordinates) | `Vector{Matrix{Float64}}()` (empty) | +| `b.vectors` | Vectors to plot on the Bloch sphere | `Vector{Vector{Float64}}()` (empty) | +| `b.lines` | Lines to draw on the sphere with each line given as `([start_pt, end_pt], line_format)` | `Vector{Tuple{Vector{Vector{Float64}},String}}()` (empty) | +| `b.arcs` | Arcs to draw on the sphere | `Vector{Vector{Vector{Float64}}}()` (empty) | + +### Properties + +| **Field** | **Description** | **Default setting** | +|:----------|:----------------|:--------------------| +| `b.font_color` | Color of axis labels and text | `"black"` | +| `b.font_size` | Font size for labels | `20` | +| `b.frame_alpha` | Transparency of the wire frame | `0.2` | +| `b.frame_color` | Color of the wire frame | `"gray"` | +| `b.frame_width` | Width of wire frame | `1.0` | +| `b.point_default_color` | Default color cycle for points | `["blue", "red", "green", "#CC6600"]` | +| `b.point_color` | List of colors for Bloch point markers to cycle through | `Union{Nothing,String}[]` | +| `b.point_marker` | List of point marker shapes to cycle through | `[:circle, :rect, :diamond, :utriangle]` | +| `b.point_size` | List of point marker sizes (not all markers look the same size when plotted) | `[5.5, 6.2, 6.5, 7.5]` | +| `b.point_style` | List of marker styles | `Symbol[]` | +| `b.point_alpha` | List of marker transparencies | `Float64[]` | +| `b.sphere_color` | Color of Bloch sphere surface | `0.2` | +| `b.sphere_alpha` | Transparency of sphere surface | `"#FFDDDD"` | +| `b.vector_color` | Colors for vectors | `["green", "#CC6600", "blue", "red"]` | +| `b.vector_width` | Width of vectors | `0.02` | +| `b.vector_tiplength` | Length of vector arrow head | `0.08` | +| `b.vector_tipradius` | Radius of vector arrow head | `0.05` | +| `b.view` | Azimuthal and elevation viewing angles in degrees | `[30, 30]` | +| `b.xlabel` | Labels for x-axis | `[L"x", ""]` (``+x`` and ``-x``) | +| `b.xlpos` | Positions of x-axis labels | `[1.2, -1.2]` | +| `b.ylabel` | Labels for y-axis | `[L"y", ""]` (``+y`` and ``-y``) | +| `b.ylpos` | Positions of y-axis labels | `[1.2, -1.2]` | +| `b.zlabel` | Labels for z-axis | `[L"\|0\rangle", L"\|1\rangle]"` (``+z`` and ``-z``) | +| `b.zlpos` | Positions of z-axis labels | `[1.2, -1.2]` | + +These properties can also be accessed via the `print` command: + +```@example Bloch_sphere_rendering +b = Bloch() +print(b) +``` + +## Animating with the Bloch sphere + +The [`Bloch`](@ref) structure was designed from the outset to generate animations. To animate a set of vectors or data points, the basic idea is: plot the data at time ``t_1``, save the sphere, clear the sphere, plot data at ``t_2``, and so on. The easiest way to animate data on the Bloch sphere is to use the `record` function provided by [`Makie.jl`](https://docs.makie.org/stable/). We will demonstrate this functionality with the following example: the decay of a qubit on the Bloch sphere. + +```@example Bloch_sphere_rendering +# system parameters +ω = 2π +θ = 0.2π +n_th = 0.5 # temperature +γ1 = 0.5 +γ2 = 0.2 + +# operators and the Hamiltonian +sx = sigmax() +sy = sigmay() +sz = sigmaz() +sm = sigmam() +H = ω * (cos(θ) * sz + sin(θ) * sx) + +# collapse operators +c_op_list = ( + √(γ1 * (n_th + 1)) * sm, + √(γ1 * n_th) * sm', + √γ2 * sz +) + +# solving evolution +ψ0 = basis(2, 0) +tlist = LinRange(0, 4, 250) +sol = mesolve(H, ψ0, tlist, c_op_list, e_ops = (sx, sy, sz), progress_bar = Val(false)) +``` + +To animate a set of vectors or data points, we use the `record` function provided by [`Makie.jl`](https://docs.makie.org/stable/): + +```@example Bloch_sphere_rendering +# expectation values +x = real(sol.expect[1,:]) +y = real(sol.expect[2,:]) +z = real(sol.expect[3,:]) + +# create Bloch sphere +b = Bloch() +b.view = [50,30] +fig, lscene = render(b) + +# save animation +record(fig, "qubit_decay.mp4", eachindex(tlist), framerate = 20) do idx + clear!(b) + add_vectors!(b, [sin(θ), 0, cos(θ)]) + add_points!(b, [x[1:idx], y[1:idx], z[1:idx]]) + render(b, location = lscene) +end +nothing # hide +``` + +```@raw html +