├── .github
├── ISSUE_TEMPLATE
│ ├── BUG_REPORT.yaml
│ └── FEATURE_REQUEST.yaml
├── dependabot.yml
└── workflows
│ ├── README.md
│ ├── citation.yml
│ ├── coverage.yml
│ ├── docs.yml
│ ├── lint.yml
│ ├── release.yml
│ ├── test_development_versions.yml
│ ├── test_latest_versions.yml
│ └── test_minimum_versions.yml
├── .gitignore
├── .mergify.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── SECURITY.md
├── docs
├── _static
│ ├── .gitkeep
│ └── images
│ │ ├── qiskit-dark-logo.svg
│ │ └── qiskit-light-logo.svg
├── _templates
│ └── autosummary
│ │ ├── class.rst
│ │ └── class_without_inherited_members.rst
├── apidocs
│ ├── index.rst
│ ├── qiskit_addon_mpf.backends.quimb_circuit.rst
│ ├── qiskit_addon_mpf.backends.quimb_layers.rst
│ ├── qiskit_addon_mpf.backends.quimb_tebd.rst
│ ├── qiskit_addon_mpf.backends.rst
│ ├── qiskit_addon_mpf.backends.tenpy_layers.rst
│ ├── qiskit_addon_mpf.backends.tenpy_tebd.rst
│ ├── qiskit_addon_mpf.costs.rst
│ ├── qiskit_addon_mpf.dynamic.rst
│ └── qiskit_addon_mpf.static.rst
├── conf.py
├── explanations
│ ├── index.rst
│ └── mpf_stability.ipynb
├── how_tos
│ ├── choose_trotter_steps.ipynb
│ ├── index.rst
│ └── using_approximate_model.ipynb
├── images
│ └── .gitkeep
├── index.rst
├── install.rst
├── release-notes.rst
└── tutorials
│ ├── 01_getting_started.ipynb
│ └── index.rst
├── pyproject.toml
├── qiskit_addon_mpf
├── __init__.py
├── backends
│ ├── __init__.py
│ ├── interface.py
│ ├── quimb_circuit
│ │ ├── __init__.py
│ │ ├── evolver.py
│ │ └── state.py
│ ├── quimb_layers
│ │ ├── __init__.py
│ │ ├── evolver.py
│ │ └── model.py
│ ├── quimb_tebd
│ │ ├── __init__.py
│ │ ├── evolver.py
│ │ └── state.py
│ ├── tenpy_layers
│ │ ├── __init__.py
│ │ ├── evolver.py
│ │ └── model.py
│ └── tenpy_tebd
│ │ ├── __init__.py
│ │ ├── evolver.py
│ │ └── state.py
├── costs
│ ├── __init__.py
│ ├── exact.py
│ ├── frobenius.py
│ ├── lse.py
│ └── sum_of_squares.py
├── dynamic.py
└── static.py
├── releasenotes
├── config.yaml
└── notes
│ ├── .gitkeep
│ ├── 0.2
│ └── dynamic-mpf-cf18e0b4d2bdeaff.yaml
│ ├── 0.3
│ ├── fix-numerical-time-checks-4b0d9f65f704caaa.yaml
│ ├── python-3.13-5e1de7bd55a8f68f.yaml
│ ├── qiskit-2.0-68f026f7c2517f92.yaml
│ ├── quimb-layers-keep-only-odd-bd6c831bb50c18b7.yaml
│ ├── remove-scaling-factor-064e157afdfeadc9.yaml
│ ├── tenpy-conserve-none-f34f73b38b89111c.yaml
│ └── tenpy-layers-N-steps-guard-96e57723af75c8a0.yaml
│ └── reverse-layers-on-mpo-6a6651864b6b3f7a.yaml
├── test
├── README.md
├── __init__.py
├── backends
│ ├── __init__.py
│ ├── quimb_circuit
│ │ ├── __init__.py
│ │ ├── test_e2e.py
│ │ └── test_state.py
│ ├── quimb_layers
│ │ ├── __init__.py
│ │ ├── test_e2e.py
│ │ ├── test_evolver.py
│ │ └── test_model.py
│ ├── quimb_tebd
│ │ ├── __init__.py
│ │ ├── test_e2e.py
│ │ └── test_state.py
│ ├── tenpy_layers
│ │ ├── __init__.py
│ │ ├── test_e2e.py
│ │ ├── test_evolver.py
│ │ └── test_model.py
│ └── tenpy_tebd
│ │ ├── __init__.py
│ │ ├── test_e2e.py
│ │ └── test_state.py
├── costs
│ ├── __init__.py
│ ├── test_exact.py
│ ├── test_lse.py
│ └── test_sum_of_squares.py
├── test_dynamic.py
└── test_static.py
└── tox.ini
/.github/ISSUE_TEMPLATE/BUG_REPORT.yaml:
--------------------------------------------------------------------------------
1 | name: 🐛 Bug report
2 | description: Create a report to help us improve 🤔.
3 | labels: ["bug"]
4 |
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: Thank you for reporting a bug! Before you do so, please ensure that you have tested on the latest released version of the code. If it does, please also use the search to see if there are any other related issues or pull requests.
9 |
10 | - type: textarea
11 | attributes:
12 | label: Environment
13 | description: Please give the actual version number (_e.g._ 0.1.0) if you are using a release version, or the first 7-8 characters of the commit hash if you have installed from `git`. If anything else is relevant, you can add it to the list.
14 | # The trailing spaces on the following lines are to make filling the form
15 | # in easier. The type is 'textarea' rather than three separate 'input's
16 | # to make the resulting issue body less noisy with headings.
17 | value: |
18 | - **qiskit-addon-mpf version**:
19 | - **Python version**:
20 | - **Operating system**:
21 | validations:
22 | required: true
23 |
24 | - type: textarea
25 | attributes:
26 | label: What is happening and why is it wrong?
27 | description: A short description of what is going wrong, in words.
28 | validations:
29 | required: true
30 |
31 | - type: textarea
32 | attributes:
33 | label: How can we reproduce the issue?
34 | description: Give some steps that show the bug. A [minimal working example](https://stackoverflow.com/help/minimal-reproducible-example) of code with output is best. If you are copying in code, please remember to enclose it in triple backticks (` ``` [multiline code goes here] ``` `) so that it [displays correctly](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax).
35 | validations:
36 | required: true
37 |
38 | - type: textarea
39 | attributes:
40 | label: Traceback
41 | description: If your code provided an error traceback, please paste it below, enclosed in triple backticks (` ``` [multiline code goes here] ``` `) so that it [displays correctly](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax).
42 | validations:
43 | required: false
44 |
45 | - type: textarea
46 | attributes:
47 | label: Any suggestions?
48 | description: Not required, but if you have suggestions for how a contributor should fix this, or any problems we should be aware of, let us know.
49 | validations:
50 | required: false
51 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yaml:
--------------------------------------------------------------------------------
1 | name: 🚀 Feature request
2 | description: Suggest an idea for this project 💡!
3 | labels: ["type: feature request"]
4 |
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: Please make sure to browse the opened and closed issues to make sure that this idea has not previously been discussed.
9 |
10 | - type: textarea
11 | attributes:
12 | label: What should we add?
13 | validations:
14 | required: true
15 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | labels: ["dependencies"]
6 | schedule:
7 | interval: "weekly"
8 | - package-ecosystem: "pip"
9 | directory: "/"
10 | labels: ["dependencies"]
11 | versioning-strategy: increase
12 | schedule:
13 | interval: "monthly"
14 |
--------------------------------------------------------------------------------
/.github/workflows/README.md:
--------------------------------------------------------------------------------
1 | # GitHub Actions workflows
2 |
3 | This directory contains a number of workflows for use with [GitHub Actions](https://docs.github.com/actions). They specify what standards should be expected for development of this software, including pull requests. These workflows are designed to work out of the box for any research software prototype, especially those based on [Qiskit](https://qiskit.org/).
4 |
5 | ## Lint check (`lint.yml`)
6 |
7 | This workflow checks that the code is formatted properly and follows the style guide by installing tox and running the [lint environment](/tests/#lint-environment) (`tox -e lint`).
8 |
9 | ## Latest version tests (`test_latest_versions.yml`)
10 |
11 | This workflow installs the latest version of tox and runs [the current repository's tests](/tests/#test-py-environments) under each supported Python version on Linux and under a single Python version on macOS and Windows. This is the primary testing workflow. It runs for all code changes and additionally once per day, to ensure tests continue to pass as new versions of dependencies are released.
12 |
13 | ## Development version tests (`test_development_versions.yml`)
14 |
15 | This workflow installs tox and modifies `pyproject.toml` to use the _development_ versions of certain Qiskit packages, using [extremal-python-dependencies](https://github.com/IBM/extremal-python-dependencies). For all other packages, the latest version is installed. This workflow runs on two versions of Python: the minimum supported version and the maximum supported version. Its purpose is to identify as soon as possible (i.e., before a Qiskit release) when changes in Qiskit will break the current repository. This workflow runs for all code changes, as well as on a timer once per day.
16 |
17 | ## Minimum version tests (`test_minimum_versions.yml`)
18 |
19 | This workflow first installs the minimum supported tox version (the `minversion` specified in [`tox.ini`](/tox.ini)) and then installs the _minimum_ compatible version of each package listed in `pyproject.toml`, using [extremal-python-dependencies](https://github.com/IBM/extremal-python-dependencies). The purpose of this workflow is to make sure the minimum version specifiers in these files are accurate, i.e., that the tests actually pass with these versions. This workflow uses a single Python version, typically the oldest supported version, as the minimum supported versions of each package may not be compatible with the most recent Python release.
20 |
21 | Under the hood, this workflow uses a regular expression to change each `>=` and `~=` specifier in the dependencies to instead be `==`, as pip [does not support](https://github.com/pypa/pip/issues/8085) resolving the minimum versions of packages directly. Unfortunately, this means that the workflow will only install the minimum version of a package if it is _explicitly_ listed in one of the requirements files with a minimum version. For instance, if the only listed dependency is `qiskit>=1.0`, this workflow will install `qiskit==1.0` along with the latest version of each transitive dependency, such as `rustworkx`.
22 |
23 | ## Code coverage (`coverage.yml`)
24 |
25 | This workflow tests the [coverage environment](/tests/#coverage-environment) on a single version of Python by installing tox and running `tox -e coverage`.
26 |
27 | ## Documentation (`docs.yml`)
28 |
29 | This workflow ensures that the [Sphinx](https://www.sphinx-doc.org/) documentation builds successfully. It also publishes the resulting build to [GitHub Pages](https://pages.github.com/) if it is from the appropriate branch (e.g., `main`).
30 |
31 | ## Citation preview (`citation.yml`)
32 |
33 | This workflow is only triggered when the `CITATION.bib` file is changed. It ensures that the file contains only ASCII characters ([escaped codes](https://en.wikibooks.org/wiki/LaTeX/Special_Characters#Escaped_codes) are preferred, as then the `bib` file will work even when `inputenc` is not used). It also compiles a sample LaTeX document which includes the citation in its bibliography and uploads the resulting PDF as an artifact so it can be previewed (e.g., before merging a pull request).
34 |
35 | ## Release (`release.yml`)
36 |
37 | This workflow is triggered by a maintainer pushing a tag that represents a release. It publishes the release to github.com and to [PyPI](https://pypi.org/).
38 |
--------------------------------------------------------------------------------
/.github/workflows/citation.yml:
--------------------------------------------------------------------------------
1 | name: Citation preview
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - 'stable/**'
8 | paths: ['CITATION.bib', '.github/workflows/citation.yml']
9 | pull_request:
10 | branches:
11 | - main
12 | - 'stable/**'
13 | paths: ['CITATION.bib', '.github/workflows/citation.yml']
14 |
15 | jobs:
16 | build-preview:
17 | runs-on: ubuntu-latest
18 | timeout-minutes: 30
19 | steps:
20 | - uses: actions/checkout@v4
21 | - name: Check for non-ASCII characters
22 | run: |
23 | # Fail immediately if there are any non-ASCII characters in
24 | # the BibTeX source. We prefer "escaped codes" rather than
25 | # UTF-8 characters in order to ensure the bibliography will
26 | # display correctly even in documents that do not contain
27 | # \usepackage[utf8]{inputenc}.
28 | if [ -f "CITATION.bib" ]; then
29 | python3 -c 'open("CITATION.bib", encoding="ascii").read()'
30 | fi
31 | - name: Install LaTeX
32 | run: |
33 | if [ -f "CITATION.bib" ]; then
34 | sudo apt-get update
35 | sudo apt-get install -y texlive-latex-base texlive-publishers
36 | fi
37 | - name: Run LaTeX
38 | run: |
39 | if [ -f "CITATION.bib" ]; then
40 | arr=(${GITHUB_REPOSITORY//\// })
41 | export REPO=${arr[1]}
42 | cat <<- EOF > citation-preview.tex
43 | \documentclass[preprint,aps,physrev,notitlepage]{revtex4-2}
44 | \usepackage{hyperref}
45 | \begin{document}
46 | \title{\texttt{$REPO} BibTeX test}
47 | \maketitle
48 | \noindent
49 | \texttt{$REPO}
50 | \cite{$REPO}
51 | \bibliography{CITATION}
52 | \end{document}
53 | EOF
54 | pdflatex citation-preview
55 | fi
56 | - name: Run BibTeX
57 | run: |
58 | if [ -f "CITATION.bib" ]; then
59 | bibtex citation-preview
60 | fi
61 | - name: Re-run LaTeX
62 | run: |
63 | if [ -f "CITATION.bib" ]; then
64 | pdflatex citation-preview
65 | pdflatex citation-preview
66 | fi
67 | - name: Upload PDF
68 | if: always()
69 | uses: actions/upload-artifact@v4
70 | with:
71 | name: citation-preview.pdf
72 | path: citation-preview.pdf
73 |
--------------------------------------------------------------------------------
/.github/workflows/coverage.yml:
--------------------------------------------------------------------------------
1 | name: Coverage
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - 'stable/**'
8 | pull_request:
9 | branches:
10 | - main
11 | - 'stable/**'
12 |
13 | jobs:
14 | coverage:
15 | name: code coverage (${{ matrix.os }}, ${{ matrix.python-version }})
16 | runs-on: ${{ matrix.os }}
17 | timeout-minutes: 30
18 | strategy:
19 | max-parallel: 4
20 | matrix:
21 | os: [ubuntu-latest]
22 | python-version: ["3.10"]
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Set up Python ${{ matrix.python-version }}
26 | uses: actions/setup-python@v5
27 | with:
28 | python-version: ${{ matrix.python-version }}
29 | - name: Install tox
30 | run: |
31 | python -m pip install --upgrade pip
32 | pip install tox coverage
33 | - name: Run coverage
34 | run: |
35 | tox -e coverage
36 | - name: Convert to lcov
37 | if: always()
38 | run: coverage3 lcov -o coveralls.lcov
39 | - name: Upload report to Coveralls
40 | if: always()
41 | uses: coverallsapp/github-action@v2
42 | with:
43 | github-token: ${{ secrets.GITHUB_TOKEN }}
44 | file: coveralls.lcov
45 | format: lcov
46 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Sphinx
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | tags:
7 | - "[0-9]+.[0-9]+.[0-9]+*"
8 | branches:
9 | - main
10 | - 'stable/**'
11 | pull_request:
12 | branches:
13 | - main
14 | - 'stable/**'
15 |
16 | jobs:
17 | build:
18 | name: build docs
19 | runs-on: ubuntu-latest
20 | timeout-minutes: 20
21 | steps:
22 | - uses: actions/checkout@v4
23 | with:
24 | fetch-depth: 0
25 | - uses: actions/setup-python@v5
26 | with:
27 | python-version: '3.10'
28 | - name: Install dependencies
29 | run: |
30 | python -m pip install --upgrade pip
31 | pip install tox
32 | sudo apt-get update
33 | sudo apt-get install -y pandoc
34 | - name: Tell reno to name the upcoming release after the branch we are on
35 | shell: bash
36 | run: |
37 | sed -i.bak -e '/unreleased_version_title:*/d' releasenotes/config.yaml
38 | echo unreleased_version_title: \"Upcoming release \(\`\`${GITHUB_REF_NAME}\`\`\)\" >> releasenotes/config.yaml
39 | - name: Build docs
40 | run: |
41 | tox -e docs
42 | - name: Upload docs artifact for GitHub Pages
43 | uses: actions/upload-pages-artifact@v3
44 | with:
45 | path: docs/_build/html
46 |
47 | # Provide a nicely named artifact that extracts into an
48 | # identically named subdirectory.
49 | - name: Prepare nice docs artifact for humans
50 | if: always()
51 | shell: bash
52 | run: |
53 | mkdir artifact
54 | cp -a docs/_build/html artifact/qiskit-addon-mpf-htmldocs
55 | - name: Upload nice docs artifact
56 | if: always()
57 | uses: actions/upload-artifact@v4
58 | with:
59 | name: qiskit-addon-mpf-htmldocs
60 | path: ./artifact
61 |
62 | deploy:
63 | name: deploy docs
64 | if: ${{ github.ref == 'refs/heads/stable/0.3' }}
65 | needs: build
66 | permissions:
67 | pages: write
68 | id-token: write
69 | environment:
70 | name: github-pages
71 | url: ${{ steps.deployment.outputs.page_url }}
72 | runs-on: ubuntu-latest
73 | steps:
74 | - name: Deploy to GitHub Pages
75 | id: deployment
76 | uses: actions/deploy-pages@v4
77 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - 'stable/**'
8 | pull_request:
9 | branches:
10 | - main
11 | - 'stable/**'
12 |
13 | jobs:
14 | lint:
15 | name: lint check
16 | runs-on: ubuntu-latest
17 | timeout-minutes: 30
18 | steps:
19 | - uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 | - name: Set up Python 3.10
23 | uses: actions/setup-python@v5
24 | with:
25 | python-version: '3.10'
26 | - name: Install tox
27 | run: |
28 | python -m pip install --upgrade pip
29 | pip install tox
30 | - name: Run lint check
31 | run: |
32 | tox -e lint
33 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Publish release
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | tags:
7 | - "[0-9]+.[0-9]+.[0-9]+"
8 |
9 | jobs:
10 |
11 | github:
12 | name: github
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout tag
16 | uses: actions/checkout@v4
17 | with:
18 | ref: ${{ github.ref_name }}
19 | - name: Publish release
20 | uses: ghalactic/github-release-from-tag@v5
21 | if: github.ref_type == 'tag'
22 | with:
23 | prerelease: false
24 | token: ${{ secrets.GITHUB_TOKEN }}
25 | generateReleaseNotes: "true"
26 |
27 | pypi:
28 | name: pypi
29 | runs-on: ubuntu-latest
30 | needs: github
31 | environment:
32 | name: pypi
33 | url: https://pypi.org/p/qiskit-addon-mpf
34 | permissions:
35 | id-token: write
36 | steps:
37 | - name: Checkout tag
38 | uses: actions/checkout@v4
39 | with:
40 | ref: ${{ github.ref_name }}
41 | - name: Install `build` tool
42 | run: |
43 | python -m pip install --upgrade pip
44 | pip install build
45 | - name: Build distribution
46 | run: |
47 | python -m build
48 | - name: Publish release to PyPI
49 | uses: pypa/gh-action-pypi-publish@release/v1
50 |
--------------------------------------------------------------------------------
/.github/workflows/test_development_versions.yml:
--------------------------------------------------------------------------------
1 | name: Development version tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - 'stable/**'
8 | pull_request:
9 | branches:
10 | - main
11 | - 'stable/**'
12 | schedule:
13 | - cron: '0 1 * * *'
14 |
15 | jobs:
16 | tests:
17 | name: development version tests (${{ matrix.os }}, ${{ matrix.python-version }})
18 | runs-on: ${{ matrix.os }}
19 | timeout-minutes: 30
20 | strategy:
21 | max-parallel: 4
22 | matrix:
23 | os: [ubuntu-latest]
24 | python-version: [3.9, 3.13]
25 | steps:
26 | - uses: actions/checkout@v4
27 | - name: Set up Python ${{ matrix.python-version }}
28 | uses: actions/setup-python@v5
29 | with:
30 | python-version: ${{ matrix.python-version }}
31 | - name: Upgrade pip
32 | run: |
33 | python -m pip install --upgrade pip
34 | - name: Install tools from pypi
35 | run: |
36 | python -m pip install tox build extremal-python-dependencies==0.0.3
37 | - name: Build Qiskit SDK development wheel
38 | run: |
39 | git clone https://github.com/Qiskit/qiskit
40 | cd qiskit
41 | git rev-parse HEAD
42 | python -m build --wheel
43 | - name: Build qiskit-ibm-runtime development wheel
44 | run: |
45 | git clone https://github.com/Qiskit/qiskit-ibm-runtime
46 | cd qiskit-ibm-runtime
47 | git rev-parse HEAD
48 | python -m build --wheel
49 | - name: Build qiskit-addon-utils development wheel
50 | run: |
51 | git clone https://github.com/Qiskit/qiskit-addon-utils
52 | cd qiskit-addon-utils
53 | git rev-parse HEAD
54 | # Unpin qiskit<2
55 | extremal-python-dependencies pin-dependencies --inplace qiskit
56 | python -m build --wheel
57 | - name: Pin development versions
58 | shell: bash
59 | run: >-
60 | extremal-python-dependencies pin-dependencies --inplace
61 | "qiskit @ file:$(echo qiskit/dist/*.whl)"
62 | "qiskit-ibm-runtime @ file:$(echo qiskit-ibm-runtime/dist/*.whl)"
63 | "qiskit-addon-utils @ file:$(echo qiskit-addon-utils/dist/*.whl)"
64 | - name: Test using tox environment
65 | run: |
66 | tox -e py,notebook,doctest --parallel --parallel-no-spinner
67 |
--------------------------------------------------------------------------------
/.github/workflows/test_latest_versions.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - 'stable/**'
8 | pull_request:
9 | branches:
10 | - main
11 | - 'stable/**'
12 | schedule:
13 | - cron: '0 1 * * *'
14 |
15 | jobs:
16 | tests:
17 | name: latest version tests (${{ matrix.os }}, ${{ matrix.python-version }})
18 | runs-on: ${{ matrix.os }}
19 | timeout-minutes: 30
20 | strategy:
21 | max-parallel: 4
22 | matrix:
23 | os: [ubuntu-latest]
24 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
25 | include:
26 | - os: macos-latest
27 | python-version: "3.13"
28 | - os: windows-latest
29 | python-version: "3.13"
30 | steps:
31 | - uses: actions/checkout@v4
32 | - name: Set up Python ${{ matrix.python-version }}
33 | uses: actions/setup-python@v5
34 | with:
35 | python-version: ${{ matrix.python-version }}
36 | - name: Install dependencies
37 | run: |
38 | python -m pip install --upgrade pip
39 | pip install tox
40 | - name: Test using tox environment
41 | shell: bash
42 | run: |
43 | tox -e py,notebook,doctest --parallel --parallel-no-spinner
44 |
--------------------------------------------------------------------------------
/.github/workflows/test_minimum_versions.yml:
--------------------------------------------------------------------------------
1 | name: Minimum version tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - 'stable/**'
8 | pull_request:
9 | branches:
10 | - main
11 | - 'stable/**'
12 |
13 | jobs:
14 | tests:
15 | name: minimum version tests (${{ matrix.os }}, ${{ matrix.python-version }})
16 | runs-on: ${{ matrix.os }}
17 | timeout-minutes: 30
18 | strategy:
19 | max-parallel: 4
20 | matrix:
21 | os: [ubuntu-latest]
22 | python-version: [3.9]
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Set up Python ${{ matrix.python-version }}
26 | uses: actions/setup-python@v5
27 | with:
28 | python-version: ${{ matrix.python-version }}
29 | - name: Install dependencies (minimum versions)
30 | shell: bash
31 | run: |
32 | python -m pip install --upgrade pip
33 | python -m pip install extremal-python-dependencies==0.0.3
34 | pip install "tox==$(extremal-python-dependencies get-tox-minversion)"
35 | extremal-python-dependencies pin-dependencies-to-minimum --inplace
36 | - name: Test using tox environment
37 | run: |
38 | tox -e py,notebook,doctest --parallel --parallel-no-spinner
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 | docs/stubs/
74 |
75 | # PyBuilder
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | .python-version
87 |
88 | # pipenv
89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
92 | # install all needed dependencies.
93 | #Pipfile.lock
94 |
95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
96 | __pypackages__/
97 |
98 | # Celery stuff
99 | celerybeat-schedule
100 | celerybeat.pid
101 |
102 | # SageMath parsed files
103 | *.sage.py
104 |
105 | # Environments
106 | .env
107 | .venv
108 | env/
109 | venv/
110 | ENV/
111 | env.bak/
112 | venv.bak/
113 |
114 | # Spyder project settings
115 | .spyderproject
116 | .spyproject
117 |
118 | # Rope project settings
119 | .ropeproject
120 |
121 | # mkdocs documentation
122 | /site
123 |
124 | # mypy
125 | .mypy_cache/
126 | .dmypy.json
127 | dmypy.json
128 |
129 | # Pyre type checker
130 | .pyre/
131 |
--------------------------------------------------------------------------------
/.mergify.yml:
--------------------------------------------------------------------------------
1 | pull_request_rules:
2 | - name: backport
3 | conditions:
4 | - label=stable backport potential
5 | actions:
6 | backport:
7 | branches:
8 | - stable/0.3
9 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | All members of this project agree to adhere to Qiskit's [code of conduct](https://github.com/Qiskit/qiskit/blob/main/CODE_OF_CONDUCT.md).
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Developer guide
2 |
3 | Development of the `qiskit-addon-mpf` package takes place [on GitHub](https://github.com/Qiskit/qiskit-addon-mpf).
4 | The [Contributing to Qiskit](https://github.com/Qiskit/qiskit/blob/main/CONTRIBUTING.md) guide may serve as a
5 | useful starting point, as this package builds on [Qiskit].
6 |
7 | This package is written in [Python] and uses [tox] as a testing framework. A description of the available
8 | `tox` test environments is located at [`test/README.md`](test/README.md). These environments are used in the
9 | CI workflows, which are described at [`.github/workflows/README.md`](.github/workflows/README.md).
10 |
11 | Project configuration, including information about dependencies, is stored in [`pyproject.toml`](pyproject.toml).
12 |
13 | We use [Sphinx] for documentation and [reno] for release notes.
14 | We use [Google style docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html),
15 | except we omit the type of each argument, as type information is redundant with Python
16 | [type hints](https://docs.python.org/3/library/typing.html).
17 |
18 | We require 100% coverage in all new code.
19 | In rare cases where it is not possible to test a code block, we mark it with ``# pragma: no cover`` so that
20 | the ``coverage`` tests will pass.
21 |
22 | [Qiskit]: https://www.ibm.com/quantum/qiskit
23 | [Python]: https://www.python.org/
24 | [tox]: https://github.com/tox-dev/tox
25 | [Sphinx]: https://www.sphinx-doc.org/
26 | [reno]: https://docs.openstack.org/reno/
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | [](https://github.com/Qiskit/qiskit-addon-mpf/releases)
5 | 
6 | [](https://www.python.org/)
7 | [](https://github.com/Qiskit/qiskit)
8 |
9 | [](https://qiskit.github.io/qiskit-addon-mpf/)
10 |
11 | [](LICENSE.txt)
12 | [](https://pypi.org/project/qiskit-addon-mpf/)
13 | [](https://github.com/Qiskit/qiskit-addon-mpf/actions/workflows/test_latest_versions.yml)
14 | [](https://coveralls.io/github/Qiskit/qiskit-addon-mpf?branch=main)
15 |
16 |
17 | # Qiskit addon: multi-product formulas (MPF)
18 |
19 | ### Table of contents
20 |
21 | * [About](#about)
22 | * [Documentation](#documentation)
23 | * [Installation](#installation)
24 | * [Deprecation Policy](#deprecation-policy)
25 | * [Contributing](#contributing)
26 | * [License](#license)
27 | * [References](#references)
28 |
29 | ----------------------------------------------------------------------------------------------------
30 |
31 | ### About
32 |
33 | [Qiskit addons](https://quantum.cloud.ibm.com/docs/guides/addons) are a collection of modular tools for building utility-scale workloads powered by Qiskit.
34 |
35 | This package contains the Qiskit addon for multi-product formulas (MPFs).
36 | These can be used to reduce the Trotter error of Hamiltonian dynamics.
37 |
38 | This package currently contains the following main entry points for users:
39 |
40 | - `qiskit_addon_mpf.static` for working with static MPFs [1-2](#references)
41 | - `qiskit_addon_mpf.dynamic` for working with dynamic MPFs [2-3](#references)
42 |
43 | ----------------------------------------------------------------------------------------------------
44 |
45 | ### Documentation
46 |
47 | All documentation is available at https://qiskit.github.io/qiskit-addon-mpf/.
48 |
49 | ----------------------------------------------------------------------------------------------------
50 |
51 | ### Installation
52 |
53 | We encourage installing this package via `pip`, when possible:
54 |
55 | ```bash
56 | pip install 'qiskit-addon-mpf'
57 | ```
58 |
59 | For more installation information refer to these [installation instructions](docs/install.rst).
60 |
61 | #### Optional dependencies
62 |
63 | The `qiskit-addon-mpf` package has a number of optional dependencies which enable certain features.
64 | The dynamic MPF feature (see [2-3](#references)) is one such example.
65 | You can install the related optional dependencies like so:
66 |
67 | ```bash
68 | pip install 'qiskit-addon-mpf[dynamic]'
69 | ```
70 |
71 | > [!IMPORTANT]
72 | > The optional dependency [TeNPy](https://github.com/tenpy/tenpy) was previously offered under a GPLv3 license.
73 | > As of the release of [v1.0.4](https://github.com/tenpy/tenpy/releases/tag/v1.0.4) on October 2nd, 2024, it has been offered under the Apache v2 license.
74 | > The license of this package is only compatible with Apache-licensed versions of TeNPy.
75 |
76 | ----------------------------------------------------------------------------------------------------
77 |
78 | ### Deprecation Policy
79 |
80 | We follow [semantic versioning](https://semver.org/) and are guided by the principles in
81 | [Qiskit's deprecation policy](https://github.com/Qiskit/qiskit/blob/main/DEPRECATION.md).
82 | We may occasionally make breaking changes in order to improve the user experience.
83 | When possible, we will keep old interfaces and mark them as deprecated, as long as they can co-exist with the
84 | new ones.
85 | Each substantial improvement, breaking change, or deprecation will be documented in the
86 | [release notes](https://qiskit.github.io/qiskit-addon-mpf/release-notes.html).
87 |
88 | ----------------------------------------------------------------------------------------------------
89 |
90 | ### Contributing
91 |
92 | The source code is available [on GitHub](https://github.com/Qiskit/qiskit-addon-mpf).
93 |
94 | The developer guide is located at [CONTRIBUTING.md](https://github.com/Qiskit/qiskit-addon-mpf/blob/main/CONTRIBUTING.md)
95 | in the root of this project's repository.
96 | By participating, you are expected to uphold Qiskit's [code of conduct](https://github.com/Qiskit/qiskit/blob/main/CODE_OF_CONDUCT.md).
97 |
98 | We use [GitHub issues](https://github.com/Qiskit/qiskit-addon-mpf/issues/new/choose) for tracking requests and bugs.
99 |
100 | ----------------------------------------------------------------------------------------------------
101 |
102 | ### References
103 |
104 | 1. A. Carrera Vazquez, D. J. Egger, D. Ochsner, and S. Wörner, [Well-conditioned multi-product formulas for hardware-friendly Hamiltonian simulation](https://quantum-journal.org/papers/q-2023-07-25-1067/), Quantum 7, 1067 (2023).
105 | 2. S. Zhuk, N. Robertson, and S. Bravyi, [Trotter error bounds and dynamic multi-product formulas for Hamiltonian simulation](https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.6.033309), Phys. Rev. Research 6, 033309 (2024).
106 | 3. N. Robertson, et al. [Tensor Network enhanced Dynamic Multiproduct Formulas](https://arxiv.org/abs/2407.17405v2), arXiv:2407.17405v2 [quant-ph].
107 |
108 | ----------------------------------------------------------------------------------------------------
109 |
110 | ### License
111 |
112 | [Apache License 2.0](LICENSE.txt)
113 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | The package supports one minor version release at a time, both for bug and security fixes.
6 | For example, if the most recent release is 0.2.1, then the 0.2.x release series is currently supported.
7 |
8 | ## Reporting a Vulnerability
9 |
10 | To report vulnerabilities, you can privately report a potential security issue
11 | via the GitHub security vulnerabilities feature. This can be done here:
12 |
13 | https://github.com/Qiskit/qiskit-addon-mpf/security/advisories
14 |
15 | Please do **not** open a public issue about a potential security vulnerability.
16 |
17 | You can find more details on the security vulnerability feature in the GitHub
18 | documentation here:
19 |
20 | https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability
21 |
--------------------------------------------------------------------------------
/docs/_static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qiskit/qiskit-addon-mpf/eff6388c59b43d849cac044114fc814c2a577dde/docs/_static/.gitkeep
--------------------------------------------------------------------------------
/docs/_templates/autosummary/class.rst:
--------------------------------------------------------------------------------
1 | {#
2 | We show all the class's methods and attributes on the same page. By default, we document
3 | all methods, including those defined by parent classes.
4 | -#}
5 |
6 | {{ objname | escape | underline }}
7 |
8 | .. currentmodule:: {{ module }}
9 |
10 | .. autoclass:: {{ objname }}
11 | :no-members:
12 | :no-inherited-members:
13 | :no-special-members:
14 | :show-inheritance:
15 |
16 | {% block attributes_summary %}
17 | {% if attributes %}
18 | .. rubric:: Attributes
19 | {% for item in attributes %}
20 | .. autoattribute:: {{ item }}
21 | {%- endfor %}
22 | {% endif %}
23 | {% endblock -%}
24 |
25 | {% block methods_summary %}
26 | {% set wanted_methods = (methods | reject('==', '__init__') | list) %}
27 | {% if wanted_methods %}
28 | .. rubric:: Methods
29 | {% for item in wanted_methods %}
30 | .. automethod:: {{ item }}
31 | {%- endfor %}
32 | {% endif %}
33 | {% endblock %}
34 |
--------------------------------------------------------------------------------
/docs/_templates/autosummary/class_without_inherited_members.rst:
--------------------------------------------------------------------------------
1 | {#
2 | We show all the class's methods and attributes on the same page. By default, we document
3 | all methods, including those defined by parent classes.
4 | -#}
5 |
6 | {{ objname | escape | underline }}
7 |
8 | .. currentmodule:: {{ module }}
9 |
10 | .. autoclass:: {{ objname }}
11 | :no-members:
12 | :no-inherited-members:
13 | :no-special-members:
14 | :show-inheritance:
15 |
16 | {% block attributes_summary %}
17 | {% set wanted_attributes = (attributes | reject('in', inherited_members) | list) %}
18 | {% if wanted_attributes %}
19 | .. rubric:: Attributes
20 | {% for item in wanted_attributes %}
21 | .. autoattribute:: {{ item }}
22 | {%- endfor %}
23 | {% endif %}
24 | {% endblock -%}
25 |
26 | {% block methods_summary %}
27 | {% set wanted_methods = (methods | reject('==', '__init__') | reject('in', inherited_members) | list) %}
28 | {% if wanted_methods %}
29 | .. rubric:: Methods
30 | {% for item in wanted_methods %}
31 | .. automethod:: {{ item }}
32 | {%- endfor %}
33 | {% endif %}
34 | {% endblock %}
35 |
--------------------------------------------------------------------------------
/docs/apidocs/index.rst:
--------------------------------------------------------------------------------
1 | **********************************
2 | ``qiskit-addon-mpf`` API reference
3 | **********************************
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 |
8 | qiskit_addon_mpf.static
9 | qiskit_addon_mpf.dynamic
10 | qiskit_addon_mpf.costs
11 | qiskit_addon_mpf.backends
12 |
--------------------------------------------------------------------------------
/docs/apidocs/qiskit_addon_mpf.backends.quimb_circuit.rst:
--------------------------------------------------------------------------------
1 | ============================================================================
2 | Quimb circuit-based backend (:mod:`qiskit_addon_mpf.backends.quimb_circuit`)
3 | ============================================================================
4 |
5 | .. automodule:: qiskit_addon_mpf.backends.quimb_circuit
6 | :no-members:
7 | :no-inherited-members:
8 | :no-special-members:
9 |
--------------------------------------------------------------------------------
/docs/apidocs/qiskit_addon_mpf.backends.quimb_layers.rst:
--------------------------------------------------------------------------------
1 | =========================================================================
2 | Quimb layer-based backend (:mod:`qiskit_addon_mpf.backends.quimb_layers`)
3 | =========================================================================
4 |
5 | .. automodule:: qiskit_addon_mpf.backends.quimb_layers
6 | :no-members:
7 | :no-inherited-members:
8 | :no-special-members:
9 |
--------------------------------------------------------------------------------
/docs/apidocs/qiskit_addon_mpf.backends.quimb_tebd.rst:
--------------------------------------------------------------------------------
1 | ================================================================
2 | Quimb TEBD backend (:mod:`qiskit_addon_mpf.backends.quimb_tebd`)
3 | ================================================================
4 |
5 | .. automodule:: qiskit_addon_mpf.backends.quimb_tebd
6 | :no-members:
7 | :no-inherited-members:
8 | :no-special-members:
9 |
--------------------------------------------------------------------------------
/docs/apidocs/qiskit_addon_mpf.backends.rst:
--------------------------------------------------------------------------------
1 | ===========================================
2 | Backends (:mod:`qiskit_addon_mpf.backends`)
3 | ===========================================
4 |
5 | .. automodule:: qiskit_addon_mpf.backends
6 | :no-members:
7 | :no-inherited-members:
8 | :no-special-members:
9 |
--------------------------------------------------------------------------------
/docs/apidocs/qiskit_addon_mpf.backends.tenpy_layers.rst:
--------------------------------------------------------------------------------
1 | =========================================================================
2 | TeNPy layer-based backend (:mod:`qiskit_addon_mpf.backends.tenpy_layers`)
3 | =========================================================================
4 |
5 | .. automodule:: qiskit_addon_mpf.backends.tenpy_layers
6 | :no-members:
7 | :no-inherited-members:
8 | :no-special-members:
9 |
--------------------------------------------------------------------------------
/docs/apidocs/qiskit_addon_mpf.backends.tenpy_tebd.rst:
--------------------------------------------------------------------------------
1 | ================================================================
2 | TeNPy TEBD backend (:mod:`qiskit_addon_mpf.backends.tenpy_tebd`)
3 | ================================================================
4 |
5 | .. automodule:: qiskit_addon_mpf.backends.tenpy_tebd
6 | :no-members:
7 | :no-inherited-members:
8 | :no-special-members:
9 |
--------------------------------------------------------------------------------
/docs/apidocs/qiskit_addon_mpf.costs.rst:
--------------------------------------------------------------------------------
1 | ==============================================
2 | Cost Functions (:mod:`qiskit_addon_mpf.costs`)
3 | ==============================================
4 |
5 | .. automodule:: qiskit_addon_mpf.costs
6 | :no-members:
7 | :no-inherited-members:
8 | :no-special-members:
9 |
--------------------------------------------------------------------------------
/docs/apidocs/qiskit_addon_mpf.dynamic.rst:
--------------------------------------------------------------------------------
1 | ==============================================
2 | Dynamic MPFs (:mod:`qiskit_addon_mpf.dynamic`)
3 | ==============================================
4 |
5 | .. automodule:: qiskit_addon_mpf.dynamic
6 | :no-members:
7 | :no-inherited-members:
8 | :no-special-members:
9 |
--------------------------------------------------------------------------------
/docs/apidocs/qiskit_addon_mpf.static.rst:
--------------------------------------------------------------------------------
1 | ============================================
2 | Static MPFs (:mod:`qiskit_addon_mpf.static`)
3 | ============================================
4 |
5 | .. automodule:: qiskit_addon_mpf.static
6 | :no-members:
7 | :no-inherited-members:
8 | :no-special-members:
9 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | import inspect
14 | import os
15 | import re
16 | import sys
17 | from importlib.metadata import version as metadata_version
18 |
19 | # The following line is required for autodoc to be able to find and import the code whose API should
20 | # be documented.
21 | sys.path.insert(0, os.path.abspath(".."))
22 |
23 | project = "Qiskit addon: multi-product formulas (MPF)"
24 | project_copyright = "2024, Qiskit addons team"
25 | description = "Reducing the Trotter error of Hamiltonian dynamics with multi-product formulas"
26 | author = "Qiskit addons team"
27 | language = "en"
28 | release = metadata_version("qiskit-addon-mpf")
29 |
30 | html_theme = "qiskit-ecosystem"
31 |
32 | # This allows including custom CSS and HTML templates.
33 | html_theme_options = {
34 | "dark_logo": "images/qiskit-dark-logo.svg",
35 | "light_logo": "images/qiskit-light-logo.svg",
36 | "sidebar_qiskit_ecosystem_member": False,
37 | }
38 | html_static_path = ["_static"]
39 | templates_path = ["_templates"]
40 |
41 | # Sphinx should ignore these patterns when building.
42 | exclude_patterns = [
43 | "_build",
44 | "_ecosystem_build",
45 | "_qiskit_build",
46 | "_pytorch_build",
47 | "**.ipynb_checkpoints",
48 | "jupyter_execute",
49 | ]
50 |
51 | extensions = [
52 | "sphinx.ext.napoleon",
53 | "sphinx.ext.autodoc",
54 | "sphinx.ext.autosummary",
55 | "sphinx.ext.doctest",
56 | "sphinx.ext.mathjax",
57 | "sphinx.ext.linkcode",
58 | "sphinx.ext.intersphinx",
59 | "matplotlib.sphinxext.plot_directive",
60 | "sphinx_copybutton",
61 | "sphinx_reredirects",
62 | "reno.sphinxext",
63 | "nbsphinx",
64 | "qiskit_sphinx_theme",
65 | "pytest_doctestplus.sphinx.doctestplus",
66 | ]
67 |
68 | html_last_updated_fmt = "%Y/%m/%d"
69 | html_title = f"{project} {release}"
70 |
71 | # This allows RST files to put `|version|` in their file and
72 | # have it updated with the release set in conf.py.
73 | rst_prolog = f"""
74 | .. |version| replace:: {release}
75 | """
76 |
77 | # Options for autodoc. These reflect the values from Qiskit SDK and Runtime.
78 | autosummary_generate = True
79 | autosummary_generate_overwrite = False
80 | autoclass_content = "both"
81 | autodoc_typehints = "description"
82 | autodoc_default_options = {
83 | "inherited-members": None,
84 | "show-inheritance": True,
85 | }
86 | napoleon_google_docstring = True
87 | napoleon_numpy_docstring = False
88 |
89 |
90 | # This adds numbers to the captions for figures, tables,
91 | # and code blocks.
92 | numfig = True
93 | numfig_format = {"table": "Table %s"}
94 |
95 | # Settings for Jupyter notebooks.
96 | nbsphinx_execute = "never"
97 |
98 | add_module_names = False
99 |
100 | modindex_common_prefix = ["qiskit_addon_mpf."]
101 |
102 | intersphinx_mapping = {
103 | "python": ("https://docs.python.org/3", None),
104 | "numpy": ("https://numpy.org/doc/stable/", None),
105 | "scipy": ("https://docs.scipy.org/doc/scipy/", None),
106 | "cvxpy": ("https://www.cvxpy.org/", None),
107 | "qiskit": ("https://quantum.cloud.ibm.com/docs/api/qiskit/", None),
108 | "rustworkx": ("https://www.rustworkx.org/", None),
109 | "qiskit_addon_utils": ("https://qiskit.github.io/qiskit-addon-utils/", None),
110 | "quimb": ("https://quimb.readthedocs.io/en/latest/", None),
111 | "tenpy": ("https://tenpy.readthedocs.io/en/latest/", None),
112 | }
113 |
114 | plot_working_directory = "."
115 | plot_html_show_source_link = False
116 |
117 | # ----------------------------------------------------------------------------------
118 | # Redirects
119 | # ----------------------------------------------------------------------------------
120 |
121 | _inlined_apis = [
122 | ("qiskit_addon_mpf.static", "LSE"),
123 | ("qiskit_addon_mpf.static", "setup_lse"),
124 | ("qiskit_addon_mpf.static", "setup_exact_model"),
125 | ("qiskit_addon_mpf.static", "setup_approximate_model"),
126 | ]
127 |
128 | redirects = {
129 | "apidocs/qiskit_addon_mpf": "./index.html",
130 | **{
131 | f"stubs/{module}.{name}": f"../apidocs/{module}.html#{module}.{name}"
132 | for module, name in _inlined_apis
133 | },
134 | }
135 |
136 | # ----------------------------------------------------------------------------------
137 | # Source code links
138 | # ----------------------------------------------------------------------------------
139 |
140 |
141 | def determine_github_branch() -> str:
142 | """Determine the GitHub branch name to use for source code links.
143 |
144 | We need to decide whether to use `stable/` vs. `main` for dev builds.
145 | Refer to https://docs.github.com/en/actions/learn-github-actions/variables
146 | for how we determine this with GitHub Actions.
147 | """
148 | # If CI env vars not set, default to `main`. This is relevant for local builds.
149 | if "GITHUB_REF_NAME" not in os.environ:
150 | return "main"
151 |
152 | # PR workflows set the branch they're merging into.
153 | if base_ref := os.environ.get("GITHUB_BASE_REF"):
154 | return base_ref
155 |
156 | ref_name = os.environ["GITHUB_REF_NAME"]
157 |
158 | # Check if the ref_name is a tag like `1.0.0` or `1.0.0rc1`. If so, we need
159 | # to transform it to a Git branch like `stable/1.0`.
160 | version_without_patch = re.match(r"(\d+\.\d+)", ref_name)
161 | return f"stable/{version_without_patch.group()}" if version_without_patch else ref_name
162 |
163 |
164 | GITHUB_BRANCH = determine_github_branch()
165 |
166 |
167 | def linkcode_resolve(domain, info):
168 | if domain != "py":
169 | return None
170 |
171 | module_name = info["module"]
172 | module = sys.modules.get(module_name)
173 | if module is None or "qiskit_addon_mpf" not in module_name:
174 | return None
175 |
176 | def is_valid_code_object(obj):
177 | return inspect.isclass(obj) or inspect.ismethod(obj) or inspect.isfunction(obj)
178 |
179 | obj = module
180 | for part in info["fullname"].split("."):
181 | try:
182 | obj = getattr(obj, part)
183 | except AttributeError:
184 | return None
185 | if not is_valid_code_object(obj):
186 | return None
187 |
188 | # Unwrap decorators. This requires they used `functools.wrap()`.
189 | while hasattr(obj, "__wrapped__"):
190 | obj = obj.__wrapped__
191 | if not is_valid_code_object(obj):
192 | return None
193 |
194 | try:
195 | full_file_name = inspect.getsourcefile(obj)
196 | except TypeError:
197 | return None
198 | if full_file_name is None or "/qiskit_addon_mpf/" not in full_file_name:
199 | return None
200 | file_name = full_file_name.split("/qiskit_addon_mpf/")[-1]
201 |
202 | try:
203 | source, lineno = inspect.getsourcelines(obj)
204 | except (OSError, TypeError):
205 | linespec = ""
206 | else:
207 | ending_lineno = lineno + len(source) - 1
208 | linespec = f"#L{lineno}-L{ending_lineno}"
209 | return f"https://github.com/Qiskit/qiskit-addon-mpf/tree/{GITHUB_BRANCH}/qiskit_addon_mpf/{file_name}{linespec}"
210 |
--------------------------------------------------------------------------------
/docs/explanations/index.rst:
--------------------------------------------------------------------------------
1 | ############
2 | Explanations
3 | ############
4 |
5 | This page summarizes additional explanatory content.
6 |
7 | .. toctree::
8 | :maxdepth: 1
9 | :glob:
10 |
11 | *
12 |
13 |
--------------------------------------------------------------------------------
/docs/how_tos/index.rst:
--------------------------------------------------------------------------------
1 | #############
2 | How-To Guides
3 | #############
4 |
5 | This page summarizes the available how-to guides.
6 |
7 | .. toctree::
8 | :maxdepth: 1
9 | :glob:
10 |
11 | *
12 |
13 |
--------------------------------------------------------------------------------
/docs/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qiskit/qiskit-addon-mpf/eff6388c59b43d849cac044114fc814c2a577dde/docs/images/.gitkeep
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | ##########################################
2 | Qiskit addon: multi-product formulas (MPF)
3 | ##########################################
4 |
5 | `Qiskit addons `_ are a collection of modular tools for building utility-scale workloads powered by Qiskit.
6 |
7 | This package contains the Qiskit addon for multi-product formulas (MPFs).
8 | These can be used to reduce the Trotter error of Hamiltonian dynamics.
9 |
10 | This package currently contains the following main entry points for users:
11 |
12 | - ``qiskit_addon_mpf.static`` for working with static MPFs [`1-2 <#references>`_].
13 | - ``qiskit_addon_mpf.dynamic`` for working with dynamic MPFs [`2-3 <#references>`_].
14 |
15 | Documentation
16 | -------------
17 |
18 | All documentation is available `here `_.
19 |
20 | Installation
21 | ------------
22 |
23 | We encourage installing this package via ``pip``, when possible:
24 |
25 | .. code-block:: bash
26 |
27 | pip install 'qiskit-addon-mpf'
28 |
29 |
30 | For more installation information refer to the `installation instructions `_ in the documentation.
31 |
32 | Optional dependencies
33 | +++++++++++++++++++++
34 |
35 | The ``qiskit-addon-mpf`` package has a number of optional dependencies which enable certain features.
36 | The dynamic MPF feature (see [`2-3 <#references>`_]) is one such example.
37 | You can install the related optional dependencies like so:
38 |
39 | .. code-block:: bash
40 |
41 | pip install 'qiskit-addon-mpf[dynamic]'
42 |
43 | .. caution::
44 | The optional dependency `TeNPy `_ was previously offered under a
45 | GPLv3 license.
46 | As of the release of `v1.0.4 `_ on October
47 | 2nd, 2024, it has been offered under the Apache v2 license.
48 | The license of this package is only compatible with Apache-licensed versions of TeNPy.
49 |
50 |
51 | Deprecation Policy
52 | ------------------
53 |
54 | We follow `semantic versioning `_ and are guided by the principles in
55 | `Qiskit's deprecation policy `_.
56 | We may occasionally make breaking changes in order to improve the user experience.
57 | When possible, we will keep old interfaces and mark them as deprecated, as long as they can co-exist with the
58 | new ones.
59 | Each substantial improvement, breaking change, or deprecation will be documented in the
60 | `release notes `_.
61 |
62 | Contributing
63 | ------------
64 |
65 | The source code is available `on GitHub `_.
66 |
67 | The developer guide is located at `CONTRIBUTING.md `_
68 | in the root of this project's repository.
69 | By participating, you are expected to uphold Qiskit's `code of conduct `_.
70 |
71 | We use `GitHub issues `_ for tracking requests and bugs.
72 |
73 | References
74 | ----------
75 |
76 | 1. A. Carrera Vazquez, D. J. Egger, D. Ochsner, and S. Wörner, `Well-conditioned multi-product formulas for hardware-friendly Hamiltonian simulation `_, Quantum 7, 1067 (2023).
77 | 2. S. Zhuk, N. Robertson, and S. Bravyi, `Trotter error bounds and dynamic multi-product formulas for Hamiltonian simulation `_, Phys. Rev. Research 6, 033309 (2024).
78 | 3. N. Robertson, et al. `Tensor Network enhanced Dynamic Multiproduct Formulas `_, arXiv:2407.17405v2 [quant-ph].
79 |
80 | License
81 | -------
82 |
83 | `Apache License 2.0 `_
84 |
85 |
86 | .. toctree::
87 | :hidden:
88 |
89 | Documentation Home
90 | Installation Instructions
91 | Tutorials
92 | How-To Guides
93 | Explanations
94 | API Reference
95 | GitHub
96 | Release Notes
97 |
--------------------------------------------------------------------------------
/docs/install.rst:
--------------------------------------------------------------------------------
1 | Installation Instructions
2 | =========================
3 |
4 | Let's see how to install the package. The first
5 | thing to do is choose how you're going to run and install the
6 | packages. There are two primary ways to do this:
7 |
8 | - :ref:`Option 1`
9 | - :ref:`Option 2`
10 |
11 | Pre-Installation
12 | ^^^^^^^^^^^^^^^^
13 |
14 | First, create a minimal environment with only Python installed in it. We recommend using `Python virtual environments `__.
15 |
16 | .. code:: sh
17 |
18 | python3 -m venv /path/to/virtual/environment
19 |
20 | Activate your new environment.
21 |
22 | .. code:: sh
23 |
24 | source /path/to/virtual/environment/bin/activate
25 |
26 | Note: If you are using Windows, use the following commands in PowerShell:
27 |
28 | .. code:: pwsh
29 |
30 | python3 -m venv c:\path\to\virtual\environment
31 | c:\path\to\virtual\environment\Scripts\Activate.ps1
32 |
33 |
34 | .. _Option 1:
35 |
36 | Option 1: Install from PyPI
37 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
38 |
39 | The most straightforward way to install the ``qiskit-addon-mpf`` package is via ``PyPI``.
40 |
41 | .. code:: sh
42 |
43 | pip install 'qiskit-addon-mpf'
44 |
45 |
46 | .. _Option 2:
47 |
48 | Option 2: Install from Source
49 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
50 |
51 | Users who wish to develop in the repository or run the notebooks locally may want to install from source.
52 |
53 | If so, the first step is to clone the ``qiskit-addon-mpf`` repository.
54 |
55 | .. code:: sh
56 |
57 | git clone git@github.com:Qiskit/qiskit-addon-mpf.git
58 |
59 | Next, upgrade ``pip`` and enter the repository.
60 |
61 | .. code:: sh
62 |
63 | pip install --upgrade pip
64 | cd qiskit-addon-mpf
65 |
66 | The next step is to install ``qiskit-addon-mpf`` to the virtual environment. If you plan on running the notebooks, install the
67 | notebook dependencies in order to run all the visualizations in the notebooks. If you plan on developing in the repository, you
68 | may want to install the ``dev`` dependencies.
69 |
70 | Adjust the options below to suit your needs.
71 |
72 | .. code:: sh
73 |
74 | pip install tox notebook -e '.[notebook-dependencies,dev]'
75 |
76 | If you installed the notebook dependencies, you can get started by running the notebooks in the docs.
77 |
78 | .. code::
79 |
80 | cd docs/
81 | jupyter lab
82 |
--------------------------------------------------------------------------------
/docs/release-notes.rst:
--------------------------------------------------------------------------------
1 | .. _release notes:
2 |
3 | .. release-notes:: Release Notes
4 |
--------------------------------------------------------------------------------
/docs/tutorials/index.rst:
--------------------------------------------------------------------------------
1 | #########
2 | Tutorials
3 | #########
4 |
5 | This page summarizes the available tutorials.
6 |
7 | .. toctree::
8 | :maxdepth: 1
9 | :glob:
10 |
11 | *
12 |
13 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["hatchling>=1.27.0"]
3 | build-backend = "hatchling.build"
4 |
5 | [project]
6 | name = "qiskit-addon-mpf"
7 | version = "0.3.0"
8 | readme = "README.md"
9 | description = "Reducing the Trotter error of Hamiltonian dynamics with multi-product formulas"
10 | license = "Apache-2.0"
11 | license-files = ["LICENSE.txt"]
12 | classifiers = [
13 | "Intended Audience :: Developers",
14 | "Intended Audience :: Science/Research",
15 | "License :: OSI Approved :: Apache Software License",
16 | "Natural Language :: English",
17 | "Operating System :: MacOS",
18 | "Operating System :: POSIX :: Linux",
19 | "Operating System :: Microsoft :: Windows",
20 | "Programming Language :: Python :: 3.9",
21 | "Programming Language :: Python :: 3.10",
22 | "Programming Language :: Python :: 3.11",
23 | "Programming Language :: Python :: 3.12",
24 | "Programming Language :: Python :: 3.13",
25 | "Topic :: Scientific/Engineering :: Physics",
26 | ]
27 |
28 | requires-python = ">=3.9"
29 |
30 | dependencies = [
31 | "numpy>=1.23",
32 | "cvxpy>=1.6",
33 | "qiskit>=1.2, <3",
34 | ]
35 |
36 | [project.optional-dependencies]
37 | quimb = [
38 | "quimb>=1.10.0, <2",
39 | "qiskit-quimb",
40 | ]
41 | tenpy = [
42 | "physics-tenpy>=1.0.4, <2",
43 | ]
44 | dynamic = [
45 | "qiskit-addon-mpf[quimb,tenpy]",
46 | ]
47 | dev = [
48 | "qiskit-addon-mpf[test,doctest,nbtest,lint,docs]",
49 | "tox>=4.4.3",
50 | ]
51 | basetest = [
52 | "pytest>=8.0",
53 | "pytest-cov>=5.0",
54 | "pytest-subtests>=0.13",
55 | "qiskit-addon-mpf[dynamic]",
56 | "qiskit-addon-utils",
57 | ]
58 | test = [
59 | "qiskit-addon-mpf[basetest]",
60 | ]
61 | doctest = [
62 | "qiskit-addon-mpf[basetest,notebook-dependencies]",
63 | "pytest-doctestplus>=1.2.1",
64 | ]
65 | nbtest = [
66 | "qiskit-addon-mpf[basetest]",
67 | "nbmake>=1.5.0",
68 | ]
69 | style = [
70 | "ruff==0.11.8",
71 | "nbqa>=1.8.5",
72 | "typos>=1.20.0",
73 | ]
74 | lint = [
75 | "qiskit-addon-mpf[style]",
76 | "mypy==1.15.0",
77 | "pylint==3.3.6",
78 | "reno>=4.1",
79 | "toml>=0.9.6",
80 | ]
81 | notebook-dependencies = [
82 | "qiskit-addon-mpf",
83 | "rustworkx[graphviz]>=0.15",
84 | "matplotlib",
85 | "ipywidgets",
86 | "pylatexenc",
87 | ]
88 | docs = [
89 | "qiskit-addon-mpf[doctest]",
90 | "qiskit-sphinx-theme~=2.0.0",
91 | "jupyter-sphinx",
92 | "sphinx-design",
93 | "sphinx-autodoc-typehints",
94 | "sphinx-copybutton",
95 | "sphinx_reredirects",
96 | "nbsphinx>=0.9.4",
97 | "reno>=4.1",
98 | ]
99 |
100 | [tool.coverage.run]
101 | branch = true
102 | parallel = true
103 |
104 | [tool.coverage.report]
105 | fail_under = 100
106 | show_missing = true
107 |
108 | [tool.hatch.build.targets.wheel]
109 | only-include = [
110 | "qiskit_addon_mpf",
111 | ]
112 |
113 | [tool.hatch.metadata]
114 | allow-direct-references = true
115 |
116 | [tool.mypy]
117 | python_version = 3.9
118 | show_error_codes = true
119 | warn_return_any = true
120 | warn_unused_configs = true
121 | ignore_missing_imports = true
122 |
123 | [tool.pylint.main]
124 | py-version = "3.9"
125 |
126 | [tool.pylint."messages control"]
127 | disable = ["all"]
128 | enable = [
129 | "reimported",
130 | "no-else-raise",
131 | "redefined-argument-from-local",
132 | "redefined-builtin",
133 | "raise-missing-from",
134 | "cyclic-import",
135 | "unused-argument",
136 | "attribute-defined-outside-init",
137 | "no-else-return",
138 | ]
139 |
140 | [tool.pytest.ini_options]
141 | testpaths = ["./qiskit_addon_mpf/", "./test/"]
142 |
143 | [tool.ruff]
144 | line-length = 100
145 | src = ["qiskit_addon_mpf", "test"]
146 | target-version = "py39"
147 |
148 | [tool.ruff.lint]
149 | select = [
150 | "I", # isort
151 | "E", # pycodestyle
152 | "W", # pycodestyle
153 | "D", # pydocstyle
154 | "F", # pyflakes
155 | "RUF", # ruff
156 | "UP", # pyupgrade
157 | "SIM", # flake8-simplify
158 | "B", # flake8-bugbear
159 | "A", # flake8-builtins
160 | ]
161 | ignore = [
162 | "E501", # line too long
163 | ]
164 |
165 | [tool.ruff.lint.pylint]
166 | max-args = 6
167 |
168 | [tool.ruff.lint.extend-per-file-ignores]
169 | "test/**.py" = [
170 | "D", # pydocstyle
171 | ]
172 | "docs/**/*" = [
173 | "E402", # module level import not at top of file
174 | "D", # pydocstyle
175 | ]
176 |
177 | [tool.ruff.lint.flake8-copyright]
178 | notice-rgx = """
179 | # This code is a Qiskit project.
180 | #
181 | # \\(C\\) Copyright IBM \\d{4}((,\\s)\\d{4})*\\.
182 | #
183 | # This code is licensed under the Apache License, Version 2\\.0\\. You may
184 | # obtain a copy of this license in the LICENSE\\.txt file in the root directory
185 | # of this source tree or at http\\:\\/\\/www\\.apache\\.org\\/licenses\\/LICENSE\\-2\\.0\\.
186 | #
187 | # Any modifications or derivative works of this code must retain this
188 | # copyright notice, and modified files need to carry a notice indicating
189 | # that they have been altered from the originals\\.
190 | """
191 |
192 | [tool.ruff.lint.pydocstyle]
193 | convention = "google"
194 |
195 | [tool.typos.default.extend-words]
196 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | # Warning: this module is not documented and it does not have an RST file.
14 | # If we ever publicly expose interfaces users can import from this module,
15 | # we should set up its RST file.
16 | """Multi-product formulas."""
17 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Optional backends for the :class:`.DynamicMPF` algorithm.
14 |
15 | .. currentmodule:: qiskit_addon_mpf.backends
16 |
17 | Availability
18 | ------------
19 |
20 | Whether a certain backend can be used depends on the availability of the underlying tensor network
21 | library. This can easily be asserted at runtime using the following indicators:
22 |
23 | .. autoclass:: HAS_QUIMB
24 |
25 | .. autoclass:: HAS_TENPY
26 |
27 | Backends
28 | --------
29 |
30 | Depending on the availability (see above), the following backends are available:
31 |
32 | .. autosummary::
33 | :toctree:
34 |
35 | quimb_tebd
36 | quimb_layers
37 | quimb_circuit
38 | tenpy_tebd
39 | tenpy_layers
40 |
41 | Interface
42 | ---------
43 |
44 | The interface implemented by any one of these optional backends is made up of the following classes:
45 |
46 | .. autoclass:: Evolver
47 |
48 | .. autoclass:: State
49 | """
50 |
51 | from qiskit.utils import LazyImportTester as _LazyImportTester
52 |
53 | from .interface import Evolver, State
54 |
55 | HAS_QUIMB = _LazyImportTester("quimb", install="pip install qiskit-addon-mpf[quimb]")
56 | """Indicates whether the optional :external:mod:`quimb` dependency is installed."""
57 |
58 | HAS_TENPY = _LazyImportTester("tenpy", install="pip install qiskit-addon-mpf[tenpy]")
59 | """Indicates whether the optional :external:mod:`tenpy` dependency is installed."""
60 |
61 | __all__ = [
62 | "Evolver",
63 | "State",
64 | ]
65 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/interface.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """The interface for :class:`.DynamicMPF` backends."""
14 |
15 | from __future__ import annotations
16 |
17 | from abc import ABC, abstractmethod
18 | from typing import Any
19 |
20 |
21 | class Evolver(ABC):
22 | """The interface for the time-evolution algorithms used within :class:`.DynamicMPF`.
23 |
24 | This time-evolution interface is used by the :attr:`.DynamicMPF.lhs` and :attr:`.DynamicMPF.rhs`
25 | and should time-evolve a :class:`.State` object under its hood. The exact mechanism of the
26 | algorithm is described in more detail in :class:`.DynamicMPF`, :class:`.State`, and
27 | :func:`.setup_dynamic_lse`.
28 | """
29 |
30 | @abstractmethod
31 | def step(self) -> None:
32 | """Perform a single time step of this time-evolution algorithm.
33 |
34 | This should act on the internally referenced :class:`.State` object (for which no name is
35 | prescribed by this interface). Whether this time-evolution algorithm instance should evolve
36 | the :class:`.State` from the left- or right-hand side, depends on the value of
37 | :attr:`.conjugate`.
38 | """
39 |
40 | @property
41 | @abstractmethod
42 | def evolved_time(self) -> float:
43 | """Returns the current evolution time."""
44 |
45 | @property
46 | @abstractmethod
47 | def conjugate(self) -> bool:
48 | """Returns whether this time-evolver instance acts on the right-hand side."""
49 |
50 | @conjugate.setter
51 | @abstractmethod
52 | def conjugate(self, conjugate: bool) -> None: ... # pragma: no cover
53 |
54 |
55 | class State(ABC):
56 | """The interface for the :attr:`.DynamicMPF.evolution_state`.
57 |
58 | This time-evolution state is shared between the LHS and RHS :class:`.Evolver` instances of the
59 | :class:`.DynamicMPF` instance. In most cases where a concrete backend implementing this
60 | interface is based on tensor networks, this state will be a matrix product operator (MPO).
61 | This is because most time-evolution algorithms would normally evolve a matrix product state
62 | (MPS) as shown pictorially below, where time evolution blocks (``U#``) are successively applied
63 | to a 1-dimensional MPS (``S#``). Here, the tensor network grows towards the right as time goes
64 | on.
65 |
66 | .. code-block::
67 |
68 | MPS Evolution
69 |
70 | S0┄┄┲━━━━┱┄┄┄┄┄┄┄┄┲━━━━┱┄
71 | │ ┃ U1 ┃ ┃ U5 ┃
72 | S1┄┄┺━━━━┹┄┲━━━━┱┄┺━━━━┹┄
73 | │ ┃ U3 ┃
74 | S2┄┄┲━━━━┱┄┺━━━━┹┄┲━━━━┱┄ ...
75 | │ ┃ U2 ┃ ┃ U6 ┃
76 | S3┄┄┺━━━━┹┄┲━━━━┱┄┺━━━━┹┄
77 | │ ┃ U4 ┃
78 | S4┄┄┄┄┄┄┄┄┄┺━━━━┹┄┄┄┄┄┄┄┄
79 |
80 | However, in our case, we want two time-evolution engines to share a single state. In order to
81 | achieve that, we can have one of them evolve the state from the right (just as before, ``U#``),
82 | but have the second one evolve the state from the left (``V#``). This requires the state to also
83 | have bonds going of in that direction, rendering it a 2-dimensional MPO (``M#``) rather than the
84 | 1-dimensional MPS from before.
85 |
86 | .. code-block::
87 |
88 | MPO Evolution
89 |
90 | ┄┲━━━━┱┄┄┄┄┄┄┄┄┲━━━━┱┄┄M0┄┄┲━━━━┱┄┄┄┄┄┄┄┄┲━━━━┱┄
91 | ┃ V5 ┃ ┃ V1 ┃ │ ┃ U1 ┃ ┃ U5 ┃
92 | ┄┺━━━━┹┄┲━━━━┱┄┺━━━━┹┄┄M1┄┄┺━━━━┹┄┲━━━━┱┄┺━━━━┹┄
93 | ┃ V3 ┃ │ ┃ U3 ┃
94 | ... ┄┲━━━━┱┄┺━━━━┹┄┲━━━━┱┄┄M2┄┄┲━━━━┱┄┺━━━━┹┄┲━━━━┱┄ ...
95 | ┃ V6 ┃ ┃ V2 ┃ │ ┃ U2 ┃ ┃ U6 ┃
96 | ┄┺━━━━┹┄┲━━━━┱┄┺━━━━┹┄┄M3┄┄┺━━━━┹┄┲━━━━┱┄┺━━━━┹┄
97 | ┃ V4 ┃ │ ┃ U4 ┃
98 | ┄┄┄┄┄┄┄┄┺━━━━┹┄┄┄┄┄┄┄┄┄M4┄┄┄┄┄┄┄┄┄┺━━━━┹┄┄┄┄┄┄┄┄
99 | """
100 |
101 | @abstractmethod
102 | def overlap(self, initial_state: Any) -> complex:
103 | """Compute the overlap of this state with the provided initial state.
104 |
105 | .. warning::
106 | A concrete implementation of this method should raise a :class:`TypeError` if the
107 | provided ``initial_state`` object is not supported by the implementing backend.
108 |
109 | Args:
110 | initial_state: the initial state with which to compute the overlap.
111 |
112 | Raises:
113 | TypeError: if the provided initial state has an incompatible type.
114 |
115 | Returns:
116 | The overlap of this state with the provided one.
117 | """
118 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/quimb_circuit/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A circuit-based time-evolution backend using :external:mod:`quimb`.
14 |
15 | .. currentmodule:: qiskit_addon_mpf.backends.quimb_circuit
16 |
17 | .. warning::
18 | This backend is only available if the optional dependencies have been installed:
19 |
20 | .. code-block::
21 |
22 | pip install "qiskit-addon-mpf[quimb]"
23 |
24 | .. autosummary::
25 | :toctree: ../stubs/
26 | :nosignatures:
27 | :template: autosummary/class_without_inherited_members.rst
28 |
29 | CircuitEvolver
30 | CircuitState
31 |
32 |
33 | Underlying method
34 | -----------------
35 |
36 | Quimb boasts direct support for the simulation of quantum circuits in the form of its tensor-network
37 | based :external:class:`quimb.tensor.Circuit` representation. We can leverage this, to bypass any
38 | explicit time-evolution algorithm and instead directly encode the time-evolution in a
39 | :external:class:`~qiskit.circuit.QuantumCircuit` and use :external:mod:`quimb` to compute the
40 | overlap between two such circuits. For more information, check out their guide on
41 | :external:std:doc:`tensor-circuit`.
42 |
43 |
44 | Code example
45 | ------------
46 |
47 | This section shows a simple example to get you started with using this backend. The example shows
48 | how to create the three factory functions required for the :func:`.setup_dynamic_lse`.
49 |
50 | The :class:`.IdentityStateFactory` protocol is already fulfilled by the
51 | :class:`~.quimb_circuit.CircuitState` constructor, rendering the ``identity_factory`` argument
52 | trivial:
53 |
54 | >>> from qiskit_addon_mpf.backends.quimb_circuit import CircuitState
55 | >>> identity_factory = CircuitState
56 |
57 | The setup of the :class:`~.quimb_circuit.CircuitEvolver` is slightly more involved. It requires a
58 | **parameterized** :external:class:`~qiskit.circuit.QuantumCircuit` object as its input where the
59 | :class:`~qiskit.circuit.Parameter` should take the place of the Trotter methods time step (``dt``).
60 |
61 | To show how such a parameterized Trotter circuit template is constructed, we reuse the same
62 | Hamiltonian and second-order Suzuki-Trotter formula as in :mod:`.quimb_layers`.
63 |
64 | >>> from qiskit.quantum_info import SparsePauliOp
65 | >>> hamil = SparsePauliOp.from_sparse_list(
66 | ... [("ZZ", (i, i+1), 1.0) for i in range(0, 9, 2)] +
67 | ... [("Z", (i,), 0.5) for i in range(10)] +
68 | ... [("ZZ", (i, i+1), 1.0) for i in range(1, 9, 2)] +
69 | ... [("X", (i,), 0.25) for i in range(10)],
70 | ... num_qubits=10,
71 | ... )
72 |
73 | But this time, we specify a :class:`~qiskit.circuit.Parameter` as the ``time`` argument when
74 | constructing the actual circuits.
75 |
76 | >>> from functools import partial
77 | >>> from qiskit.circuit import Parameter
78 | >>> from qiskit.synthesis import SuzukiTrotter
79 | >>> from qiskit_addon_mpf.backends.quimb_circuit import CircuitEvolver
80 | >>> from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit
81 | >>> dt = Parameter("dt")
82 | >>> suzuki_2 = generate_time_evolution_circuit(hamil, time=dt, synthesis=SuzukiTrotter(order=2))
83 | >>> approx_evolver_factory = partial(CircuitEvolver, circuit=suzuki_2)
84 |
85 | .. caution::
86 | It is **necessary** that the name of the :class:`~qiskit.circuit.Parameter` is ``dt``!
87 |
88 | We can choose a higher order Trotter formula for the ``exact_evolver_factory``. But note, that we
89 | must once again use a parameterized circuit, even if we immediately bind its parameter when
90 | constructing the ``partial`` function.
91 |
92 | >>> suzuki_4 = generate_time_evolution_circuit(hamil, time=dt, synthesis=SuzukiTrotter(order=4))
93 | >>> exact_evolver_factory = partial(CircuitEvolver, circuit=suzuki_4, dt=0.05)
94 |
95 | These factory functions may now be used to run the :func:`.setup_dynamic_lse`. Refer to its
96 | documentation for more details on that.
97 | """
98 |
99 | # ruff: noqa: E402
100 | from .. import HAS_QUIMB
101 |
102 | HAS_QUIMB.require_now(__name__)
103 |
104 | from .evolver import CircuitEvolver
105 | from .state import CircuitState
106 |
107 | __all__ = [
108 | "CircuitEvolver",
109 | "CircuitState",
110 | ]
111 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/quimb_circuit/evolver.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A time-evolution engine based on quantum circuits."""
14 |
15 | from __future__ import annotations
16 |
17 | from qiskit.circuit import QuantumCircuit
18 | from qiskit_quimb import quimb_circuit
19 |
20 | from .. import Evolver
21 | from .state import CircuitState
22 |
23 |
24 | class CircuitEvolver(Evolver):
25 | """A time-evolution engine based on quantum circuits.
26 |
27 | This algorithm performs time-evolution by means of successively applying a quantum circuit
28 | corresponding to a single Trotter step to its internal state. More specifically, it builds out a
29 | tensor network in the :class:`~.quimb_circuit.CircuitState`. As required by the
30 | :class:`.DynamicMPF` algorithm, it tracks a left- and right-hand side of the time-evolution for
31 | computing the overlap of two circuits. Depending on :attr:`conjugate`, an instance of this
32 | engine will apply the quantum gates of its template circuit to the corresponding side (see
33 | :mod:`.quimb_circuit` for more details).
34 | """
35 |
36 | def __init__(self, evolution_state: CircuitState, circuit: QuantumCircuit, dt: float) -> None:
37 | """Initialize a :class:`CircuitEvolver` instance.
38 |
39 | Args:
40 | evolution_state: a reference to the time-evolution state.
41 | circuit: the template circuit encoding the time-evolution of a single Trotter step. This
42 | circuit **must** be parameterized (see :external:class:`~qiskit.circuit.Parameter`
43 | in place of the Trotter methods time step. This parameter must be named ``dt``.
44 | dt: the time step that will be used and later bound to the
45 | :external:class:`~qiskit.circuit.Parameter` of the ``circuit`` object.
46 | """
47 | self.evolution_state = evolution_state
48 | """The time-evolution state (see also :attr:`.DynamicMPF.evolution_state`)."""
49 | self.evolved_time = 0
50 | self.dt = dt
51 | self.circuit = quimb_circuit(circuit.assign_parameters({"dt": dt}, inplace=False))
52 | """The parameterized :external:class:`~qiskit.circuit.QuantumCircuit` describing the Trotter
53 | step."""
54 | self._conjugate = False
55 |
56 | @property
57 | def conjugate(self) -> bool:
58 | """Returns whether this time-evolver instance acts on the right-hand side."""
59 | return self._conjugate
60 |
61 | @conjugate.setter
62 | def conjugate(self, conjugate: bool) -> None:
63 | self._conjugate = conjugate
64 |
65 | @property
66 | def evolved_time(self) -> float:
67 | """Returns the current evolution time."""
68 | return self._evolved_time
69 |
70 | @evolved_time.setter
71 | def evolved_time(self, evolved_time: float) -> None:
72 | self._evolved_time = evolved_time
73 |
74 | def step(self) -> None:
75 | """Perform a single time step of TEBD.
76 |
77 | This will apply the gates of the :attr:`circuit` to the :attr:`evolution_state`. If
78 | :attr:`conjugate` is ``True``, it applies to :attr:`.CircuitState.lhs`, otherwise to
79 | :attr:`.CircuitState.rhs`.
80 | """
81 | self.evolved_time += self.dt
82 |
83 | if self.conjugate:
84 | if self.evolution_state.lhs is None:
85 | self.evolution_state.lhs = self.circuit.copy()
86 | else:
87 | self.evolution_state.lhs.apply_gates(self.circuit.gates)
88 | else:
89 | if self.evolution_state.rhs is None:
90 | self.evolution_state.rhs = self.circuit.copy()
91 | else:
92 | self.evolution_state.rhs.apply_gates(self.circuit.gates)
93 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/quimb_circuit/state.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A circuit-based MPO-like time-evolution state based on quimb."""
14 |
15 | from __future__ import annotations
16 |
17 | from typing import Any
18 |
19 | import numpy as np
20 | from qiskit.circuit import QuantumCircuit
21 | from qiskit_quimb import quimb_circuit
22 | from quimb.tensor import Circuit, TensorNetwork
23 |
24 | from .. import State
25 |
26 |
27 | class CircuitState(State):
28 | """An MPO-like representation of a time-evolution state based on quantum circuits.
29 |
30 | This time-evolution state can be evolved on its left- and right-hand side as required by the
31 | :class:`.DynamicMPF` algorithm.
32 | """
33 |
34 | def __init__(self) -> None:
35 | """Initialize a :class:`CircuitState` instance."""
36 | self.lhs: Circuit | None = None
37 | """The left-hand side circuit in form of a tensor network."""
38 | self.rhs: Circuit | None = None
39 | """The right-hand side circuit in form of a tensor network."""
40 |
41 | def overlap(self, initial_state: Any) -> complex:
42 | """Compute the overlap of this state with the provided initial state.
43 |
44 | .. warning::
45 | This implementation only supports instances of
46 | :external:class:`qiskit.circuit.QuantumCircuit` for ``initial_state``.
47 |
48 | Args:
49 | initial_state: the initial state with which to compute the overlap.
50 |
51 | Raises:
52 | TypeError: if the provided initial state has an incompatible type.
53 |
54 | Returns:
55 | The overlap of this state with the provided one.
56 | """
57 | if not isinstance(initial_state, QuantumCircuit):
58 | raise TypeError(
59 | "CircuitState.overlap is only implemented for qiskit.QuantumCircuit! "
60 | "But not for states of type '%s'",
61 | type(initial_state),
62 | )
63 |
64 | if self.lhs is None or self.rhs is None:
65 | raise RuntimeError("You must evolve the state before an overlap can be computed!")
66 |
67 | lhs = quimb_circuit(initial_state)
68 | lhs.apply_gates(self.lhs.gates)
69 |
70 | rhs = quimb_circuit(initial_state)
71 | rhs.apply_gates(self.rhs.gates)
72 |
73 | # TODO: find a good way to inject arguments into .contract() below
74 | # For example, specifcying backend="jax" would allow us to run this on a GPU (if available
75 | # and installed properly).
76 | ovlp = TensorNetwork((lhs.psi.H, rhs.psi)).contract()
77 |
78 | return float(np.abs(ovlp) ** 2)
79 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/quimb_layers/evolver.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A layer-wise time-evolver using quimb."""
14 |
15 | from __future__ import annotations
16 |
17 | from quimb.tensor import MatrixProductOperator, MatrixProductState
18 |
19 | from .. import quimb_tebd
20 | from .model import LayerModel
21 |
22 |
23 | class LayerwiseEvolver(quimb_tebd.TEBDEvolver):
24 | """A special case of the :class:`~.quimb_tebd.TEBDEvolver` based on layer-wise evolution models.
25 |
26 | As also explained in :mod:`.quimb_layers`, this implementation extracts the alternating even/odd
27 | bond updates implemented inside of the original :external:class:`quimb.tensor.TEBD` to become
28 | the end users responsibility. It does so, by replacing the single Hamiltonian provided to the
29 | :class:`~.quimb_tebd.TEBDEvolver` instance with a sequence of :class:`~.quimb_layers.LayerModel`
30 | instances. Every single instance of these encodes a single **layer** of interactions. These
31 | should enforce the alternating updates of even and odd bonds of the underlying tensor network.
32 |
33 | The motivation for this more complicated interface is that is provides a lot more flexbility and
34 | enables users to define custom Trotter product formulas rather than being limited to the ones
35 | implemented by ``quimb`` directly.
36 | """
37 |
38 | def __init__(
39 | self,
40 | evolution_state: quimb_tebd.MPOState | MatrixProductState,
41 | layers: list[LayerModel],
42 | *args,
43 | **kwargs,
44 | ) -> None:
45 | """Initialize a :class:`LayerwiseEvolver` instance.
46 |
47 | Args:
48 | evolution_state: forwarded to :class:`~.quimb_tebd.TEBDEvolver`. Please refer to its
49 | documentation for more details.
50 | layers: the list of models describing single layers of interactions. See above as well
51 | as the explanations provided in :mod:`.quimb_layers`.
52 | args: any further positional arguments will be forwarded to the
53 | :class:`~.quimb_tebd.TEBDEvolver` constructor.
54 | kwargs: any further keyword arguments will be forwarded to the
55 | :class:`~.quimb_tebd.TEBDEvolver` constructor.
56 | """
57 | super().__init__(evolution_state, layers[0], *args, **kwargs)
58 | self.layers = layers
59 | """The layers of interactions used to implement the time-evolution."""
60 | self._reverse_layers = isinstance(evolution_state, MatrixProductOperator)
61 |
62 | @property
63 | def reverse_layers(self) -> bool:
64 | """Whether to reverse the layers.
65 |
66 | This defaults to ``True`` when ``self.psi`` is an MPO and to ``False`` when it is an MPS.
67 | This is necessary because the middle-out MPO contraction applies the circuits to an identity
68 | initial state and contracts with the circuits intended initial state from the outside.
69 | Therefore, the layers must be reversed to ensure the same circuit is computed as if it were
70 | applied on top of the initial state.
71 | """
72 | return self._reverse_layers
73 |
74 | @reverse_layers.setter
75 | def reverse_layers(self, reverse_layers: bool) -> None:
76 | self._reverse_layers = reverse_layers
77 |
78 | def step(self) -> None:
79 | # pylint: disable=attribute-defined-outside-init
80 | # NOTE: Somehow, pylint does not properly pick up on the attributes defined in the external
81 | # base classes.
82 | """Perform a single time step of TEBD.
83 |
84 | This will iterate over the :attr:`layers` and apply their interaction to the internal state.
85 | """
86 | dt = self._dt
87 |
88 | # NOTE: support for MatrixProductState objects is only added for testing/debugging purposes!
89 | # This is not meant for consumption by end-users of the `qiskit_addon_mpf.dynamic` module
90 | # and its use is highly discouraged.
91 | is_mps = isinstance(self._pt, MatrixProductState)
92 |
93 | layer_order = range(len(self.layers))
94 | if self.reverse_layers:
95 | layer_order = reversed(layer_order) # type: ignore[assignment]
96 |
97 | for layer_idx in layer_order:
98 | layer = self.layers[layer_idx]
99 | self.H = layer
100 | for i in range(self.L):
101 | sites = (i, (i + 1) % self.L)
102 | gate = self._get_gate_from_ham(1.0, sites)
103 | if gate is None:
104 | continue
105 | if is_mps:
106 | self._pt.gate_split_(gate, sites, **self.split_opts)
107 | else:
108 | self._pt.gate_split_(gate, sites, conj=self.conjugate, **self.split_opts)
109 |
110 | self.t += dt
111 | self._err += float("NaN")
112 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/quimb_layers/model.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A quimb-based model for describing a single layer of interactions."""
14 |
15 | from __future__ import annotations
16 |
17 | from typing import cast
18 |
19 | import numpy as np
20 | from qiskit.circuit import QuantumCircuit
21 | from quimb.gen.operators import pauli
22 | from quimb.tensor import LocalHam1D
23 |
24 |
25 | class LayerModel(LocalHam1D):
26 | """A model for representing a layer of time-evolution interactions.
27 |
28 | Essentially, this class is a simple wrapper of :external:class:`quimb.tensor.LocalHam1D`. Its
29 | main purpose is to provide a simple interface for constructing a Quimb-compatible Hamiltonian
30 | from Qiskit objects.
31 | """
32 |
33 | def get_gate_expm(self, where: tuple[int, int], x: float) -> np.ndarray | None:
34 | """Get the local term at the sites ``where``, matrix exponentiated by ``x``.
35 |
36 | Args:
37 | where: the pair of site indices of the local term to get. This identifies the bond
38 | index.
39 | x: the value with which to matrix exponentiate the interaction term.
40 |
41 | Returns:
42 | The interaction in terms of an array or ``None`` if this layer has no interaction on
43 | this bond.
44 | """
45 | try:
46 | return cast(np.ndarray, self._expm_cached(self.get_gate(where), x))
47 | except KeyError:
48 | return None
49 |
50 | @classmethod
51 | def from_quantum_circuit(
52 | cls,
53 | circuit: QuantumCircuit,
54 | *,
55 | keep_only_odd: bool | None = None,
56 | **kwargs,
57 | ) -> LayerModel:
58 | """Construct a :class:`LayerModel` from a :external:class:`~qiskit.circuit.QuantumCircuit`.
59 |
60 | You can see an example of this function in action in the docs of :mod:`quimb_layers`.
61 |
62 | Args:
63 | circuit: the quantum circuit to parse.
64 | keep_only_odd: whether to keep only interactions on bonds with odd indices.
65 | kwargs: any additional keyword arguments to pass to the :class:`LayerModel` constructor.
66 |
67 | Returns:
68 | A new LayerModel instance.
69 |
70 | Raises:
71 | NotImplementedError: if an unsupported quantum gate is encountered.
72 | """
73 | H2: dict[tuple[int, int] | None, np.ndarray] = {}
74 | H1: dict[int, np.ndarray] = {}
75 | paulis_cache: dict[str, np.ndarray] = {}
76 |
77 | for instruction in circuit.data:
78 | op = instruction.operation
79 | sites = tuple(circuit.find_bit(qubit)[0] for qubit in instruction.qubits)
80 |
81 | # NOTE: the hard-coded scaling factors below account for the Pauli matrix conversion
82 | if op.name in {"rxx", "ryy", "rzz"}:
83 | term = paulis_cache.get(op.name, None)
84 | if term is None:
85 | p = op.name[-1]
86 | paulis_cache[op.name] = pauli(p) & pauli(p)
87 | term = paulis_cache[op.name]
88 | if sites in H2:
89 | H2[sites] += 0.5 * op.params[0] * term
90 | else:
91 | H2[sites] = 0.5 * op.params[0] * term
92 | elif op.name == "xx_plus_yy":
93 | term = paulis_cache.get(op.name, None)
94 | if term is None:
95 | paulis_cache["rxx"] = pauli("X") & pauli("X")
96 | paulis_cache["ryy"] = pauli("Y") & pauli("Y")
97 | paulis_cache[op.name] = paulis_cache["rxx"] + paulis_cache["ryy"]
98 | term = paulis_cache[op.name]
99 | if sites in H2:
100 | H2[sites] += 0.25 * op.params[0] * term
101 | else:
102 | H2[sites] = 0.25 * op.params[0] * term
103 | elif op.name in {"rx", "ry", "rz"}:
104 | term = paulis_cache.get(op.name, None)
105 | if term is None:
106 | p = op.name[-1]
107 | paulis_cache[op.name] = pauli(p)
108 | term = paulis_cache[op.name]
109 | if sites[0] in H1:
110 | H1[sites[0]] += 0.5 * op.params[0] * term
111 | else:
112 | H1[sites[0]] = 0.5 * op.params[0] * term
113 | else:
114 | raise NotImplementedError(f"Cannot handle gate of type {op.name}")
115 |
116 | if len(H2) == 0:
117 | H2[None] = np.zeros((4, 4))
118 |
119 | ret = cls(
120 | circuit.num_qubits,
121 | H2,
122 | H1,
123 | **kwargs,
124 | )
125 |
126 | if keep_only_odd is not None:
127 | # NOTE: if `keep_only_odd` was specified, that means we explicitly overwrite those H_bond
128 | # values with `None` which we do not want to keep. In the case of (for example) coupling
129 | # layers, this should have no effect since those bonds were `None` to begin with. However,
130 | # in the case of onsite layers, this will remove half of the bonds ensuring that we split
131 | # the bond updates into even and odd parts (as required by the TEBD algorithm).
132 | for i in range(0 if keep_only_odd else 1, circuit.num_qubits, 2):
133 | _ = ret.terms.pop((i - 1, i), None)
134 |
135 | return ret
136 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/quimb_tebd/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A :external:mod:`quimb`-based TEBD backend.
14 |
15 | .. currentmodule:: qiskit_addon_mpf.backends.quimb_tebd
16 |
17 | .. warning::
18 | This backend is only available if the optional dependencies have been installed:
19 |
20 | .. code-block::
21 |
22 | pip install "qiskit-addon-mpf[quimb]"
23 |
24 | .. autosummary::
25 | :toctree: ../stubs/
26 | :nosignatures:
27 | :template: autosummary/class_without_inherited_members.rst
28 |
29 | TEBDEvolver
30 | MPOState
31 |
32 | Underlying method
33 | -----------------
34 |
35 | This module provides a time-evolution backend for computing dynamic MPF coefficients based on the
36 | time-evolving block decimation (TEBD) algorithm [1] implemented in the :external:mod:`quimb` tensor
37 | network library.
38 |
39 | The classes provided by this module serve two purposes:
40 |
41 | 1. Connecting :external:mod:`quimb`'s implementation to the interface set out by
42 | :mod:`qiskit_addon_mpf.backends`.
43 | 2. Extending :external:mod:`quimb`'s TEBD implementation to handle an internal MPO (rather than
44 | MPS) state (see also :class:`.State` for more details).
45 |
46 |
47 | In the simplest sense, this module provides a straight-forward extension of the TEBD algorithm to
48 | evolve an internal MPO state.
49 | As such, if you wish to use this backend for your dynamic MPF algorithm, you must encode the
50 | Hamiltonian that you wish to time-evolve, in a :external:mod:`quimb`-native form. To be more
51 | concrete, the :class:`~qiskit_addon_mpf.backends.quimb_tebd.TEBDEvolver` class (which is a subclass
52 | of :external:class:`quimb.tensor.TEBD`) works with a Hamiltonian in the form of a
53 | :external:class:`quimb.tensor.LocalHam1D`. Quimb provides a number of convenience methods for
54 | constructing such Hamiltonians in its :external:mod:`quimb.tensor.tensor_builder` module.
55 | If none of those fulfill your needs, you can consider using the
56 | :class:`~qiskit_addon_mpf.backends.quimb_layers.LayerModel` class which implements some conversion
57 | methods from Qiskit-native objects.
58 |
59 | Code example
60 | ------------
61 |
62 | This section shows a simple example to get you started with using this backend. The example shows
63 | how to create the three factory functions required for the :func:`.setup_dynamic_lse`.
64 |
65 | First, we create the ``identity_factory`` which has to match the :class:`.IdentityStateFactory`
66 | protocol. We do so simply by using the :external:func:`quimb.tensor.MPO_identity` function and
67 | wrapping the resulting :external:class:`quimb.tensor.MatrixProductOperator` with our custom
68 | :class:`~.quimb_tebd.MPOState` interface.
69 |
70 | >>> from qiskit_addon_mpf.backends.quimb_tebd import MPOState
71 | >>> from quimb.tensor import MPO_identity
72 | >>> num_qubits = 10
73 | >>> identity_factory = lambda: MPOState(MPO_identity(num_qubits))
74 |
75 | Next, before being able to define the :class:`.ExactEvolverFactory` and
76 | :class:`.ApproxEvolverFactory` protocols, we must define the Hamiltonian which we would like to
77 | time-evolve. Here, we simply choose one of :external:mod:`quimb`'s convenience methods.
78 |
79 | >>> from quimb.tensor import ham_1d_heis
80 | >>> hamil = ham_1d_heis(num_qubits, 0.8, 0.3, cyclic=False)
81 |
82 | We can now construct the exact and approximate time-evolution instance factories. To do so, we can
83 | simply use :func:`functools.partial` to bind the pre-defined values of the
84 | :class:`~qiskit_addon_mpf.backends.quimb_tebd.TEBDEvolver` initializer, reducing it to the correct
85 | interface as expected by the :class:`.ExactEvolverFactory` and :class:`.ApproxEvolverFactory`
86 | protocols, respectively.
87 |
88 | >>> from functools import partial
89 | >>> from qiskit_addon_mpf.backends.quimb_tebd import TEBDEvolver
90 | >>> exact_evolver_factory = partial(
91 | ... TEBDEvolver,
92 | ... H=hamil,
93 | ... dt=0.05,
94 | ... order=4,
95 | ... )
96 |
97 | Notice, how we have fixed the ``dt`` value to a small time step and have used a higher-order
98 | Suzuki-Trotter decomposition to mimic the exact time evolution above.
99 |
100 | Below, we do not fix the ``dt`` value and use only a second-order Suzuki-Trotter formula for the
101 | approximate time evolution. Additionally, we also specify some truncation settings.
102 |
103 | >>> approx_evolver_factory = partial(
104 | ... TEBDEvolver,
105 | ... H=hamil,
106 | ... order=2,
107 | ... split_opts={"max_bond": 10, "cutoff": 1e-5},
108 | ... )
109 |
110 | Of course, you are not limited to the examples shown here, and we encourage you to play around with
111 | the other settings provided by the :external:class:`quimb.tensor.TEBD` implementation.
112 |
113 | Limitations
114 | -----------
115 |
116 | Finally, we point out a few known limitations on what kind of Hamiltonians can be treated by this
117 | backend:
118 |
119 | * all interactions must be 1-dimensional
120 | * the interactions must be acylic
121 |
122 | Resources
123 | ---------
124 |
125 | [1]: https://en.wikipedia.org/wiki/Time-evolving_block_decimation
126 | """
127 |
128 | # ruff: noqa: E402
129 | from .. import HAS_QUIMB
130 |
131 | HAS_QUIMB.require_now(__name__)
132 |
133 | from .evolver import TEBDEvolver
134 | from .state import MPOState
135 |
136 | __all__ = [
137 | "MPOState",
138 | "TEBDEvolver",
139 | ]
140 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/tenpy_layers/model.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024, 2025.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A TeNPy-based model for describing a single layer of interactions."""
14 |
15 | from __future__ import annotations
16 |
17 | from collections import defaultdict
18 | from typing import cast
19 |
20 | from qiskit.circuit import QuantumCircuit
21 | from tenpy.models import CouplingMPOModel, NearestNeighborModel
22 | from tenpy.networks import Site, SpinHalfSite
23 | from tenpy.tools.params import Config
24 |
25 |
26 | class LayerModel(CouplingMPOModel, NearestNeighborModel):
27 | """A model for representing a layer of time-evolution interactions.
28 |
29 | Essentially, this class is a simple wrapper of
30 | :external:class:`tenpy.models.model.CouplingMPOModel` and
31 | :external:class:`tenpy.models.model.NearestNeighborModel`. Its main purpose is to provide a
32 | simple interface for constructing a TeNPy-compatible Hamiltonian from Qiskit objects.
33 | """
34 |
35 | def init_sites(self, model_params: Config) -> Site:
36 | """Initializes the sites of this Hamiltonian.
37 |
38 | See :external:meth:`~tenpy.models.model.CouplingMPOModel.init_sites` for more details.
39 |
40 | Args:
41 | model_params: the model parameters.
42 |
43 | Returns:
44 | The site to be used internally.
45 | """
46 | # WARN: we use our own default to NOT sort charges (contrary to TeNPy default: `True`)
47 | sort_charge = model_params.get("sort_charge", False, bool)
48 | conserve = model_params.get("conserve", "Sz", str)
49 | return SpinHalfSite(conserve=conserve, sort_charge=sort_charge)
50 |
51 | def init_terms(self, model_params: Config) -> None:
52 | """Initializes the terms of this Hamiltonian.
53 |
54 | See :external:meth:`~tenpy.models.model.CouplingMPOModel.init_terms` for more details.
55 |
56 | Args:
57 | model_params: the model parameters.
58 | """
59 | coupling_terms = model_params.get("coupling_terms", {})
60 | onsite_terms = model_params.get("onsite_terms", {})
61 |
62 | for category, terms in coupling_terms.items():
63 | for term in terms:
64 | self.add_coupling_term(*term, category=category)
65 |
66 | for category, terms in onsite_terms.items():
67 | for term in terms:
68 | self.add_onsite_term(*term, category=category)
69 |
70 | def calc_H_bond(self, tol_zero: float = 1e-15) -> list:
71 | """Calculate the interaction Hamiltonian based on the coupling and onsite terms.
72 |
73 | Essentially, this class overwrites
74 | :external:meth:`~tenpy.models.model.CouplingModel.calc_H_bond` and takes care of removing
75 | even or odd bond interaction Hamiltonians depending on the value of ``keep_only_odd`` (see
76 | :mod:`.tenpy_layers` for more details).
77 |
78 | Args:
79 | tol_zero: the threshold for values considered to be zero.
80 |
81 | Returns:
82 | The list of interaction Hamiltonians for all bonds.
83 | """
84 | H_bond = super().calc_H_bond(tol_zero=tol_zero)
85 |
86 | keep_only_odd = self.options.get("keep_only_odd", None, bool)
87 | if keep_only_odd is None:
88 | # return H_bond unchanged
89 | return cast(list, H_bond)
90 |
91 | # NOTE: if `keep_only_odd` was specified, that means we explicitly overwrite those H_bond
92 | # values with `None` which we do not want to keep. In the case of (for example) coupling
93 | # layers, this should have no effect since those bonds were `None` to begin with. However,
94 | # in the case of onsite layers, this will remove half of the bonds ensuring that we split
95 | # the bond updates into even and odd parts (as required by the TEBD algorithm).
96 | for i in range(0 if keep_only_odd else 1, self.lat.N_sites, 2):
97 | H_bond[i] = None
98 |
99 | return cast(list, H_bond)
100 |
101 | @classmethod
102 | def from_quantum_circuit(
103 | cls,
104 | circuit: QuantumCircuit,
105 | **kwargs,
106 | ) -> LayerModel:
107 | """Construct a :class:`LayerModel` from a :external:class:`~qiskit.circuit.QuantumCircuit`.
108 |
109 | You can see an example of this function in action in the docs of :mod:`tenpy_layers`.
110 |
111 | .. note::
112 | By default, TeNPy tries to enforce spin-conservation and, thus, some operations may not
113 | be available. If you encounter an error stating that some operator (e.g. ``Sx``) is not
114 | available, try specifying ``conserve="None"``. If that still does not work, converting
115 | your specific ``QuantumCircuit`` is currently not possible using this implementation.
116 |
117 | Args:
118 | circuit: the quantum circuit to parse.
119 | kwargs: any additional keyword arguments to pass to the :class:`LayerModel` constructor.
120 |
121 | Returns:
122 | A new LayerModel instance.
123 |
124 | Raises:
125 | NotImplementedError: if an unsupported quantum gate is encountered.
126 | """
127 | coupling_terms = defaultdict(list)
128 | onsite_terms = defaultdict(list)
129 |
130 | for instruction in circuit.data:
131 | op = instruction.operation
132 | sites = [circuit.find_bit(qubit)[0] for qubit in instruction.qubits]
133 |
134 | # NOTE: the hard-coded scaling factors below account for the Pauli matrix conversion
135 | if op.name in {"rxx", "ryy", "rzz"}:
136 | s_p = f"S{op.name[-1]}"
137 | coupling_terms[f"{s_p}_i {s_p}_j"].append(
138 | (
139 | 2.0 * op.params[0],
140 | *sites,
141 | s_p,
142 | s_p,
143 | "Id",
144 | )
145 | )
146 | elif op.name == "xx_plus_yy":
147 | coupling_terms["Sp_i Sm_j"].append((0.5 * op.params[0], *sites, "Sp", "Sm", "Id"))
148 | coupling_terms["Sp_i Sm_j"].append((0.5 * op.params[0], *sites, "Sm", "Sp", "Id"))
149 | elif op.name in {"rx", "ry", "rz"}:
150 | s_p = f"S{op.name[-1]}"
151 | onsite_terms[s_p].append((op.params[0], *sites, s_p))
152 | else:
153 | raise NotImplementedError(f"Cannot handle gate of type {op.name}")
154 |
155 | return cls(
156 | {
157 | "L": circuit.num_qubits,
158 | "coupling_terms": coupling_terms,
159 | "onsite_terms": onsite_terms,
160 | **kwargs,
161 | }
162 | )
163 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/tenpy_tebd/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A :external:mod:`tenpy`-based TEBD backend.
14 |
15 | .. currentmodule:: qiskit_addon_mpf.backends.tenpy_tebd
16 |
17 | .. caution::
18 | The optional dependency `TeNPy `_ was previously offered under a
19 | GPLv3 license.
20 | As of the release of `v1.0.4 `_ on October
21 | 2nd, 2024, it has been offered under the Apache v2 license.
22 | The license of this package is only compatible with Apache-licensed versions of TeNPy.
23 |
24 | .. warning::
25 | This backend is only available if the optional dependencies have been installed:
26 |
27 | .. code-block::
28 |
29 | pip install "qiskit-addon-mpf[tenpy]"
30 |
31 | .. autosummary::
32 | :toctree: ../stubs/
33 | :nosignatures:
34 | :template: autosummary/class_without_inherited_members.rst
35 |
36 | TEBDEvolver
37 | MPOState
38 | MPS_neel_state
39 |
40 | Underlying method
41 | -----------------
42 |
43 | This module provides a time-evolution backend for computing dynamic MPF coefficients based on the
44 | time-evolving block decimation (TEBD) algorithm [1] implemented in the :external:mod:`tenpy` tensor
45 | network library.
46 |
47 | The classes provided by this module serve two purposes:
48 |
49 | 1. Connecting :external:mod:`tenpy`'s implementation to the interface set out by
50 | :mod:`qiskit_addon_mpf.backends`.
51 | 2. Extending :external:mod:`tenpy`'s TEBD implementation to handle an internal MPO (rather than
52 | MPS) state (see also :class:`.State` for more details).
53 |
54 |
55 | In the simplest sense, this module provides a straight-forward extension of the TEBD algorithm to
56 | evolve an internal MPO state.
57 | As such, if you wish to use this backend for your dynamic MPF algorithm, you must encode the
58 | Hamiltonian that you wish to time-evolve, in a :external:mod:`tenpy`-native form. To be more
59 | concrete, the :class:`~qiskit_addon_mpf.backends.tenpy_tebd.TEBDEvolver` class (which is a subclass
60 | of :external:class:`tenpy.algorithms.tebd.TEBDEngine`) works with a Hamiltonian in the form of a
61 | :external:class:`~tenpy.models.model.Model`. TeNPy provides a number of convenience methods for
62 | constructing such Hamiltonians in its :external:mod:`tenpy.models` module.
63 | If none of those fulfill your needs, you can consider using the
64 | :class:`~qiskit_addon_mpf.backends.tenpy_layers.LayerModel` class which implements some conversion
65 | methods from Qiskit-native objects.
66 |
67 | Code example
68 | ------------
69 |
70 | This section shows a simple example to get you started with using this backend. The example shows
71 | how to create the three factory functions required for the :func:`.setup_dynamic_lse`.
72 |
73 | First of all, we define the Hamiltonian which we would like to time-evolve. Here, we simply choose
74 | one of :external:mod:`tenpy`'s convenience methods.
75 |
76 | >>> from tenpy.models import XXZChain2
77 | >>> hamil = XXZChain2(
78 | ... {
79 | ... "L": 10,
80 | ... "Jz": 0.8,
81 | ... "Jxx": 0.7,
82 | ... "hz": 0.3,
83 | ... "bc_MPS": "finite",
84 | ... "sort_charge": False,
85 | ... }
86 | ... )
87 |
88 | Next, we can create the ``identity_factory`` which has to match the :class:`.IdentityStateFactory`
89 | protocol. We do so by using the :func:`~.tenpy_tebd.MPOState.initialize_from_lattice` convenience
90 | method which takes the lattice underlying the Hamiltonian which we just defined as its only input.
91 |
92 | >>> from functools import partial
93 | >>> from qiskit_addon_mpf.backends.tenpy_tebd import MPOState
94 | >>> identity_factory = partial(MPOState.initialize_from_lattice, hamil.lat),
95 |
96 | We can now construct the :class:`.ExactEvolverFactory` and :class:`.ApproxEvolverFactory`
97 | time-evolution instance factories. To do so, we can simply bind the pre-defined values of the
98 | :class:`~qiskit_addon_mpf.backends.tenpy_tebd.TEBDEvolver` initializer, reducing it to the correct
99 | interface as expected by the respective function protocols.
100 |
101 | >>> from qiskit_addon_mpf.backends.tenpy_tebd import TEBDEvolver
102 | >>> exact_evolver_factory = partial(
103 | ... TEBDEvolver,
104 | ... model=hamil,
105 | ... dt=0.05,
106 | ... options={
107 | ... "order": 4,
108 | ... "preserve_norm": False,
109 | ... },
110 | ... )
111 |
112 | Notice, how we have fixed the ``dt`` value to a small time step and have used a higher-order
113 | Suzuki-Trotter decomposition to mimic the exact time-evolution above.
114 |
115 | Below, we do not fix the ``dt`` value and use only a second-order Suzuki-Trotter formula for the
116 | approximate time-evolution. Additionally, we also specify some truncation settings.
117 |
118 | >>> approx_evolver_factory = partial(
119 | ... TEBDEvolver,
120 | ... model=hamil,
121 | ... options={
122 | ... "order": 2,
123 | ... "preserve_norm": False,
124 | ... "trunc_params": {
125 | ... "chi_max": 10,
126 | ... "svd_min": 1e-5,
127 | ... "trunc_cut": None,
128 | ... },
129 | ... },
130 | ... )
131 |
132 | Of course, you are not limited to the examples shown here, and we encourage you to play around with
133 | the other settings provided by TeNPy's :external:class:`~tenpy.algorithms.tebd.TEBDEngine`
134 | implementation.
135 |
136 | Limitations
137 | -----------
138 |
139 | Finally, we point out a few known limitations on what kind of Hamiltonians can be treated by this
140 | backend:
141 |
142 | * all interactions must be 1-dimensional
143 | * the interactions must use finite boundary conditions
144 |
145 | Resources
146 | ---------
147 |
148 | [1]: https://en.wikipedia.org/wiki/Time-evolving_block_decimation
149 | """
150 |
151 | # ruff: noqa: E402
152 | from .. import HAS_TENPY
153 |
154 | HAS_TENPY.require_now(__name__)
155 |
156 | from .evolver import TEBDEvolver
157 | from .state import MPOState, MPS_neel_state
158 |
159 | __all__ = [
160 | "MPOState",
161 | "MPS_neel_state",
162 | "TEBDEvolver",
163 | ]
164 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/tenpy_tebd/evolver.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A tenpy-based TEBD algorithm for evolving an internal MPO."""
14 |
15 | from __future__ import annotations
16 |
17 | import logging
18 | from typing import cast
19 |
20 | from tenpy import TEBDEngine, svd_theta
21 | from tenpy.linalg import np_conserved as npc
22 | from tenpy.networks import MPS
23 |
24 | from .. import Evolver
25 |
26 | LOGGER = logging.getLogger(__name__)
27 |
28 |
29 | class TEBDEvolver(TEBDEngine, Evolver):
30 | """A TEBD algorithm for evolving an internal MPO.
31 |
32 | As discussed in more detail in :mod:`~qiskit_addon_mpf.backends.tenpy_tebd`, this extension of
33 | TeNPy's existing :external:class:`~tenpy.algorithms.tebd.TEBDEngine` implementation time-evolves
34 | an internal matrix product operator (MPO) rather than the conventional matrix product state
35 | (MPS).
36 |
37 | More concretely, the internal object is expected to be an :class:`~.tenpy_tebd.MPOState`.
38 |
39 | .. warning::
40 | The API of this class is actually much larger than shown here, because it inherits additional
41 | methods from the :external:class:`~tenpy.algorithms.tebd.TEBDEngine` base class. However, we
42 | do not duplicate that API here.
43 | """
44 |
45 | def __init__(self, *args, dt: float = 0.1, **kwargs) -> None:
46 | """Initialize a :class:`TEBDEvolver` instance.
47 |
48 | Args:
49 | args: any positional arguments will be forwarded to the
50 | :external:class:`~tenpy.algorithms.tebd.TEBDEngine` constructor.
51 | dt: the time step to be used by this time-evolution instance.
52 | kwargs: any further keyword arguments will be forwarded to the
53 | :external:class:`~tenpy.algorithms.tebd.TEBDEngine` constructor.
54 | """
55 | super().__init__(*args, **kwargs)
56 | self.dt = dt
57 | """The time step to be used by this time-evolution instance."""
58 | self._conjugate = False
59 |
60 | @property
61 | def evolved_time(self) -> float:
62 | """Returns the current evolution time."""
63 | return self._evolved_time
64 |
65 | @evolved_time.setter
66 | def evolved_time(self, evolved_time: float) -> None:
67 | self._evolved_time = evolved_time
68 |
69 | @property
70 | def conjugate(self) -> bool:
71 | """Returns whether this time-evolver instance acts on the right-hand side."""
72 | return self._conjugate
73 |
74 | @conjugate.setter
75 | def conjugate(self, conjugate: bool) -> None:
76 | self._conjugate = conjugate
77 |
78 | def step(self) -> None:
79 | """Perform a single time step of TEBD.
80 |
81 | This essentially calls :external:meth:`~tenpy.algorithms.tebd.TEBDEngine.run_evolution` and
82 | forwards the value of :attr:`dt` that was provided upon construction.
83 | """
84 | self.run_evolution(1, self.dt)
85 |
86 | def update_bond(self, i: int, U_bond: npc.Array) -> float:
87 | """Update the specified bond.
88 |
89 | Overwrites the original (MPS-based) implementation to support an MPO as the shared state.
90 |
91 | Args:
92 | i: the bond index.
93 | U_bond: the bond to update.
94 |
95 | Returns:
96 | The truncation error.
97 | """
98 | i0, i1 = i - 1, i
99 | LOGGER.debug("Update sites (%d, %d)", i0, i1)
100 |
101 | # NOTE: support for MatrixProductState objects is only added for testing/debugging purposes!
102 | # This is not meant for consumption by end-users of the `qiskit_addon_mpf.dynamic` module
103 | # and its use is highly discouraged.
104 | is_mps = isinstance(self.psi, MPS)
105 |
106 | leg_lbl = "v" if is_mps else "w"
107 | left_leg = f"{leg_lbl}L"
108 | right_leg = f"{leg_lbl}R"
109 | p0s = ("p0",) if is_mps else ("p0", "p0*")
110 | p1s = ("p1",) if is_mps else ("p1", "p1*")
111 | ps = ("p",) if is_mps else ("p", "p*")
112 |
113 | # Construct the theta matrix
114 | C0 = self.psi.get_B(i0) if is_mps else self.psi.get_W(i0)
115 | C1 = self.psi.get_B(i1) if is_mps else self.psi.get_W(i1)
116 |
117 | C = npc.tensordot(C0, C1, axes=([right_leg], [left_leg]))
118 | new_labels = [left_leg, *p0s, *p1s, right_leg]
119 | C.iset_leg_labels(new_labels)
120 |
121 | # apply U to C
122 | if self.conjugate:
123 | C = npc.tensordot(U_bond.conj(), C, axes=(["p0", "p1"], ["p0*", "p1*"])) # apply U
124 | else:
125 | C = npc.tensordot(U_bond, C, axes=(["p0*", "p1*"], ["p0", "p1"])) # apply U
126 |
127 | C.itranspose([left_leg, *p0s, *p1s, right_leg])
128 |
129 | theta = C.scale_axis(self.psi.get_SL(i0) if is_mps else self.psi.Ss[i0], left_leg)
130 | # now theta is the same as if we had done
131 | # theta = self.psi.get_theta(i0, n=2)
132 | # theta = npc.tensordot(U_bond, theta, axes=(['p0*', 'p1*'], ['p0', 'p1'])) # apply U
133 | # but also have C which is the same except the missing "S" on the left
134 | # so we don't have to apply inverses of S (see below)
135 |
136 | theta = theta.combine_legs([(left_leg, *p0s), (*p1s, right_leg)], qconj=[+1, -1])
137 |
138 | # Perform the SVD and truncate the wavefunction
139 | U, S, V, trunc_err, renormalize = svd_theta(
140 | theta, self.trunc_params, [None, None], inner_labels=[right_leg, left_leg]
141 | )
142 |
143 | # Split tensor and update matrices
144 | B_R = V.split_legs(1).ireplace_labels(p1s, ps)
145 |
146 | # In general, we want to do the following:
147 | # U = U.iscale_axis(S, 'vR')
148 | # B_L = U.split_legs(0).iscale_axis(self.psi.get_SL(i0)**-1, 'vL')
149 | # B_L = B_L.ireplace_label('p0', 'p')
150 | # i.e. with SL = self.psi.get_SL(i0), we have ``B_L = SL**-1 U S``
151 | #
152 | # However, the inverse of SL is problematic, as it might contain very small singular
153 | # values. Instead, we use ``C == SL**-1 theta == SL**-1 U S V``,
154 | # such that we obtain ``B_L = SL**-1 U S = SL**-1 U S V V^dagger = C V^dagger``
155 | # here, C is the same as theta, but without the `S` on the very left
156 | # (Note: this requires no inverse if the MPS is initially in 'B' canonical form)
157 |
158 | def conj(labels: tuple[str, ...]):
159 | """Conjugates a tuple of leg labels."""
160 | return tuple(lbl[:-1] if lbl[-1] == "*" else lbl + "*" for lbl in labels)
161 |
162 | B_L = npc.tensordot(
163 | C.combine_legs((*p1s, right_leg), pipes=theta.legs[1]),
164 | V.conj(),
165 | axes=[f"({'.'.join(p1s)}.{right_leg})", f"({'.'.join(conj(p1s))}.{right_leg}*)"],
166 | )
167 | B_L.ireplace_labels([f"{left_leg}*", *p0s], [right_leg, *ps])
168 | B_L /= renormalize # re-normalize to = 1
169 |
170 | if is_mps:
171 | self.psi.norm *= renormalize
172 | self.psi.set_SR(i0, S)
173 | self.psi.set_B(i0, B_L, form="B")
174 | self.psi.set_B(i1, B_R, form="B")
175 | else:
176 | self.psi.Ss[i1] = S
177 | self.psi.set_W(i0, B_L)
178 | self.psi.set_W(i1, B_R)
179 |
180 | self._trunc_err_bonds[i0] = self._trunc_err_bonds[i0] + trunc_err
181 |
182 | return cast(float, trunc_err)
183 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/backends/tenpy_tebd/state.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024, 2025.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """A TeNPy-based MPO state."""
14 |
15 | from __future__ import annotations
16 |
17 | from typing import Any, cast
18 |
19 | import numpy as np
20 | from tenpy.linalg import np_conserved as npc
21 | from tenpy.models import Lattice
22 | from tenpy.networks import MPO, MPS
23 |
24 | from .. import State
25 |
26 |
27 | class MPOState(MPO, State):
28 | """A mediator class to make TeNPy's MPO match the :class:`.State` interface.
29 |
30 | This class simply ensures that a :external:class:`tenpy.networks.mpo.MPO` object can work as a
31 | :class:`.State` instance.
32 | """
33 |
34 | @classmethod
35 | def initialize_from_lattice(cls, lat: Lattice, *, conserve: bool = True) -> MPOState:
36 | """Construct an identity :class:`MPOState` instance matching the provided lattice shape.
37 |
38 | Given a lattice, this method constructs a new MPO identity matching the shape of the
39 | lattice.
40 |
41 | Args:
42 | lat: the lattice describing the MPO sites.
43 | conserve: whether to conserve ``Sz``. This is a simplified version of the more elaborate
44 | ``conserve`` property of :class:`~tenpy.networks.site.SpinHalfSite`. The boolean
45 | value simply indicates ``Sz`` (``True``) or ``None`` conservation (``False``)
46 |
47 | Returns:
48 | An identity MPO.
49 | """
50 | # creates a 4d array filled with zeros - shape 1x2x2x1
51 | B = np.zeros([1, 2, 2, 1], dtype=float)
52 | # sets element values of B array to 1
53 | # creates a tensor that represents identity for MPO
54 | B[0, 0, 0, 0] = 1
55 | B[0, 1, 1, 0] = 1
56 |
57 | labels = ["wL", "p", "p*", "wR"]
58 |
59 | if conserve:
60 | # creates a list of tensor leg charge objects encoding charges + conjugations for tensor
61 | # legs (i.e. dimensions)
62 | leg_charge = [
63 | # e.g. charge information for tensor leg / dimension [1] and label ["2*Sz"]
64 | # creates a LegCharge object from the flattened list of charges
65 | # one for each of four legs or dimensions on B
66 | npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1], qconj=1),
67 | npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1, -1], qconj=1),
68 | npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1, -1], qconj=-1),
69 | npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1], qconj=-1),
70 | ]
71 |
72 | B_array = npc.Array.from_ndarray(B, legcharges=leg_charge, labels=labels)
73 | else:
74 | B_array = npc.Array.from_ndarray_trivial(B, labels=labels)
75 |
76 | num_sites = lat.N_sites
77 | # initialize the MPO psi with the wavepacket and an identity operator
78 | psi = cls.from_wavepacket(lat.mps_sites(), [1.0] * num_sites, "Id")
79 |
80 | # set the wavefunction at each site in psi to B_array
81 | for k in range(num_sites):
82 | psi.set_W(k, B_array)
83 |
84 | # srt the bond strengths of psi to a list of lists with all elements as 1.0
85 | psi.Ss = [[1.0]] * num_sites
86 | # psi is now an MPO representing the identity operator
87 | # psi consists of an identical B_array at each site
88 | # psi is a product of local identity operators since the bond dimensions are all 1
89 | return cast(MPOState, psi)
90 |
91 | def overlap(self, initial_state: Any) -> complex:
92 | """Compute the overlap of this state with the provided initial state.
93 |
94 | .. warning::
95 | This implementation only supports instances of
96 | :external:class:`tenpy.networks.mps.MPS` for ``initial_state``.
97 |
98 | Args:
99 | initial_state: the initial state with which to compute the overlap.
100 |
101 | Raises:
102 | TypeError: if the provided initial state has an incompatible type.
103 |
104 | Returns:
105 | The overlap of this state with the provided one.
106 | """
107 | if not isinstance(initial_state, MPS):
108 | raise TypeError(
109 | "MPOState.overlap is only implemented for tenpy.networks.mps.MPS states! "
110 | "But not for states of type '%s'",
111 | type(initial_state),
112 | )
113 |
114 | for k in range(self.L):
115 | self.set_W(k, np.sqrt(2.0) * self.get_W(k))
116 |
117 | overlap = self.expectation_value(initial_state)
118 |
119 | for k in range(self.L):
120 | self.set_W(k, (1.0 / np.sqrt(2.0)) * self.get_W(k))
121 |
122 | return cast(complex, overlap)
123 |
124 |
125 | def MPS_neel_state(lat: Lattice) -> MPS:
126 | """Constructs the Néel state as an MPS.
127 |
128 | Args:
129 | lat: the lattice describing the MPS sites.
130 |
131 | Returns:
132 | A Néel state as an MPS.
133 | """
134 | num_sites = lat.N_sites
135 | product_state = ["up", "down"] * (num_sites // 2) + (num_sites % 2) * ["up"]
136 | initial_state = MPS.from_product_state(lat.mps_sites(), product_state, bc=lat.bc_MPS)
137 | return initial_state
138 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/costs/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Cost functions for MPF coefficients.
14 |
15 | .. currentmodule:: qiskit_addon_mpf.costs
16 |
17 | This module provides a number of optimization problem generator functions, each implementing a
18 | different cost function as the problem's target objective. All of the functions provided by this
19 | module take a linear system of equations (:class:`.LSE`) encoding the parameters of the optimization
20 | problem as their first argument.
21 |
22 | .. autoclass:: LSE
23 |
24 | Optimization problem constructors
25 | ---------------------------------
26 |
27 | .. autofunction:: setup_exact_problem
28 |
29 | .. autofunction:: setup_sum_of_squares_problem
30 |
31 | .. autofunction:: setup_frobenius_problem
32 | """
33 |
34 | from .exact import setup_exact_problem
35 | from .frobenius import setup_frobenius_problem
36 | from .lse import LSE
37 | from .sum_of_squares import setup_sum_of_squares_problem
38 |
39 | __all__ = [
40 | "LSE",
41 | "setup_exact_problem",
42 | "setup_frobenius_problem",
43 | "setup_sum_of_squares_problem",
44 | ]
45 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/costs/exact.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Exact MPF coefficients."""
14 |
15 | from __future__ import annotations
16 |
17 | import cvxpy as cp
18 |
19 | from .lse import LSE
20 |
21 |
22 | def setup_exact_problem(lse: LSE) -> tuple[cp.Problem, cp.Variable]:
23 | r"""Construct a :external:class:`cvxpy.Problem` for finding the exact MPF coefficients.
24 |
25 | .. note::
26 |
27 | The coefficients found via this optimization problem will be identical to the analytical ones
28 | obtained from the :meth:`.LSE.solve` method. This additional interface exists to highlight
29 | the parallel to the other cost functions provided by this module. It also serves educational
30 | purposes for how to approach optimization problems targeting MPF coefficients.
31 |
32 | The optimization problem constructed by this function is defined as follows:
33 |
34 | - the cost function minimizes the L1-norm (:external:class:`~cvxpy.atoms.norm1.norm1`) of the
35 | variables (:attr:`.LSE.x`)
36 | - the constraints correspond to each equation of the :class:`.LSE`:
37 |
38 | .. math::
39 | \sum_j A_{ij} x_j = b_i
40 |
41 |
42 | Here is an example:
43 |
44 | >>> from qiskit_addon_mpf.costs import setup_exact_problem
45 | >>> from qiskit_addon_mpf.static import setup_static_lse
46 | >>> lse = setup_static_lse([1,2,3], order=2, symmetric=True)
47 | >>> problem, coeffs = setup_exact_problem(lse)
48 | >>> print(problem)
49 | minimize norm1(x)
50 | subject to Sum([1. 1. 1.] @ x, None, False) == 1.0
51 | Sum([1. 0.25 0.11111111] @ x, None, False) == 0.0
52 | Sum([1. 0.0625 0.01234568] @ x, None, False) == 0.0
53 |
54 | You can then solve the problem and access the expansion coefficients like so:
55 |
56 | >>> final_cost = problem.solve()
57 | >>> print(coeffs.value) # doctest: +FLOAT_CMP
58 | [ 0.04166667 -1.06666667 2.025 ]
59 |
60 | Args:
61 | lse: the linear system of equations from which to build the model.
62 |
63 | Returns:
64 | The optimization problem and coefficients variable.
65 |
66 | References:
67 | [1]: A. Carrera Vazquez et al., Quantum 7, 1067 (2023).
68 | https://quantum-journal.org/papers/q-2023-07-25-1067/
69 | """
70 | coeffs = lse.x
71 | cost = cp.norm1(coeffs)
72 | constraints = [cp.sum(lse.A[idx] @ coeffs) == b for idx, b in enumerate(lse.b)]
73 | problem = cp.Problem(cp.Minimize(cost), constraints)
74 | return problem, coeffs
75 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/costs/frobenius.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Frobenius norm MPF coefficients."""
14 |
15 | from __future__ import annotations
16 |
17 | import cvxpy as cp
18 |
19 | from .lse import LSE
20 |
21 |
22 | def setup_frobenius_problem(
23 | lse: LSE, *, max_l1_norm: float = 10.0
24 | ) -> tuple[cp.Problem, cp.Variable]:
25 | r"""Construct a :external:class:`cvxpy.Problem` for finding approximate MPF coefficients.
26 |
27 | The optimization problem constructed by this function is defined as follows:
28 |
29 | - the cost function minimizes the following quadratic expression:
30 |
31 | .. math::
32 | 1 + x^T A x - 2 x^T b
33 |
34 | As shown in [1] and [2], this expression arises from the Frobenius norm of the error between
35 | an exact time evolution state and a dynamic MPF. As such, taking the :class:`.LSE` constructed
36 | by :func:`.setup_dynamic_lse` and plugging it into this function will yield Eq. (20) of [1]
37 | (which is identical to Eq. (2) of [2]), which we repeat below
38 |
39 | .. math::
40 | 1 + \sum_{i,j} A_{ij}(t) x_i(t) x_j(t) - 2 \sum_i b_i(t) x_i(t) \, ,
41 |
42 | where $A$ and $b$ of our :class:`.LSE` correspond to the Gram matrix ($M$ in [1] and [2]) and
43 | the overlap vector ($L$ in [1] and [2]), respectively. Additionally, we use $x(t)$ to denote
44 | the MPF variables (or coefficients) rather than $c$ in [1] and [2].
45 |
46 | - two constraints are set:
47 |
48 | 1. the variables must sum to 1: :math:`\sum_i x_i == 1`
49 | 2. the L1-norm (:external:class:`~cvxpy.atoms.norm1.norm1`) of the variables is bounded by
50 | ``max_l1_norm``
51 |
52 | Below is an example which uses the ``lse`` object constructed in the example for
53 | :func:`.setup_dynamic_lse`.
54 |
55 | .. testsetup::
56 | >>> from functools import partial
57 | >>> from qiskit_addon_mpf.backends.quimb_tebd import MPOState, TEBDEvolver
58 | >>> from qiskit_addon_mpf.dynamic import setup_dynamic_lse
59 | >>> from quimb.tensor import ham_1d_heis, MPO_identity, MPS_neel_state
60 | >>> trotter_steps = [3, 4]
61 | >>> time = 0.9
62 | >>> num_qubits = 10
63 | >>> initial_state = MPS_neel_state(num_qubits)
64 | >>> hamil = ham_1d_heis(num_qubits, 0.8, 0.3, cyclic=False)
65 | >>> identity_factory = lambda: MPOState(MPO_identity(num_qubits))
66 | >>> exact_evolver_factory = partial(
67 | ... TEBDEvolver,
68 | ... H=hamil,
69 | ... dt=0.05,
70 | ... order=4,
71 | ... split_opts={"max_bond": 10, "cutoff": 1e-5},
72 | ... )
73 | >>> approx_evolver_factory = partial(
74 | ... TEBDEvolver,
75 | ... H=hamil,
76 | ... order=2,
77 | ... split_opts={"max_bond": 10, "cutoff": 1e-5},
78 | ... )
79 | >>> lse = setup_dynamic_lse(
80 | ... trotter_steps,
81 | ... time,
82 | ... identity_factory,
83 | ... exact_evolver_factory,
84 | ... approx_evolver_factory,
85 | ... initial_state,
86 | ... )
87 |
88 | .. doctest::
89 | >>> from qiskit_addon_mpf.costs import setup_frobenius_problem
90 | >>> problem, coeffs = setup_frobenius_problem(lse, max_l1_norm=3.0)
91 | >>> print(problem) # doctest: +FLOAT_CMP
92 | minimize 1.0 + QuadForm(x, [[1.00 1.00]
93 | [1.00 1.00]]) + -[2.00003171 1.99997911] @ x
94 | subject to Sum(x, None, False) == 1.0
95 | norm1(x) <= 3.0
96 |
97 | You can then solve the problem and access the expansion coefficients like so:
98 |
99 | .. testsetup::
100 | >>> import sys, pytest
101 | >>> if not sys.platform.startswith("linux"):
102 | ... pytest.skip("This doctest only converges to numerically identical values on Linux")
103 |
104 | .. doctest::
105 | >>> final_cost = problem.solve()
106 | >>> print(coeffs.value) # doctest: +FLOAT_CMP
107 | [0.50596416 0.49403584]
108 |
109 | Args:
110 | lse: the linear system of equations from which to build the model.
111 | max_l1_norm: the upper limit to use for the constrain of the L1-norm of the variables.
112 |
113 | Returns:
114 | The optimization problem and coefficients variable.
115 |
116 | References:
117 | [1]: S. Zhuk et al., Phys. Rev. Research 6, 033309 (2024).
118 | https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.6.033309
119 | [2]: N. Robertson et al., arXiv:2407.17405v2 (2024).
120 | https://arxiv.org/abs/2407.17405v2
121 | """
122 | coeffs = lse.x
123 | cost = 1.0 + cp.quad_form(coeffs, lse.A) - 2.0 * lse.b.T @ coeffs
124 | constraints = [cp.sum(coeffs) == 1, cp.norm1(coeffs) <= max_l1_norm]
125 | problem = cp.Problem(cp.Minimize(cost), constraints)
126 | return problem, coeffs
127 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/costs/lse.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Linear system of equations supporting cvxpy variable and parameter objects."""
14 |
15 | from __future__ import annotations
16 |
17 | from typing import NamedTuple, cast
18 |
19 | import cvxpy as cp
20 | import numpy as np
21 |
22 |
23 | class LSE(NamedTuple):
24 | """A :class:`.namedtuple` representing a linear system of equations.
25 |
26 | .. math::
27 | A x = b
28 | """
29 |
30 | A: np.ndarray
31 | """The left hand side of the LSE."""
32 |
33 | b: np.ndarray
34 | """The right hand side of the LSE."""
35 |
36 | @property
37 | def x(self) -> cp.Variable:
38 | """Returns the $x$ :external:class:`~cvxpy.expressions.variable.Variable`."""
39 | return cp.Variable(shape=len(self.b), name="x")
40 |
41 | def solve(self) -> np.ndarray:
42 | r"""Return the solution to this LSE: :math:`x=A^{-1}b`.
43 |
44 | Returns:
45 | The solution to this LSE.
46 |
47 | Raises:
48 | ValueError: if this LSE is parameterized with unassigned values.
49 | ValueError: if this LSE does not include a row ensuring that :math:`\sum_i x_i == 1`
50 | which is a requirement for valid MPF coefficients.
51 | """
52 | if self.A.ndim == 1:
53 | # self.A is a vector of cp.Expression objects
54 | mat_a = np.array([row.value for row in self.A])
55 | if any(row is None for row in mat_a):
56 | raise ValueError(
57 | "This LSE contains unassigned parameter values! Assign a value to them first "
58 | "before trying to solve this LSE again."
59 | )
60 | else:
61 | mat_a = self.A
62 |
63 | vec_b = self.b
64 | ones = [all(row == 1) for row in mat_a]
65 | if not any(ones) or not np.isclose(vec_b[np.where(ones)], 1.0):
66 | raise ValueError(
67 | "This LSE does not enforce the sum of all coefficients to be equal to 1 which is "
68 | "required for valid MPF coefficients. To find valid coefficients for this LSE use "
69 | "one of the non-exact cost functions provided in this module and find its optimal "
70 | "solution."
71 | )
72 |
73 | return cast(np.ndarray, np.linalg.solve(mat_a, vec_b))
74 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/costs/sum_of_squares.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Sum-of-squares MPF coefficients."""
14 |
15 | from __future__ import annotations
16 |
17 | import cvxpy as cp
18 |
19 | from .lse import LSE
20 |
21 |
22 | def setup_sum_of_squares_problem(
23 | lse: LSE, *, max_l1_norm: float = 10.0
24 | ) -> tuple[cp.Problem, cp.Variable]:
25 | r"""Construct a :external:class:`cvxpy.Problem` for finding approximate MPF coefficients.
26 |
27 | The optimization problem constructed by this function is defined as follows:
28 |
29 | - the cost function minimizes the sum of squares
30 | (:external:func:`~cvxpy.atoms.sum_squares.sum_squares`) of the distances to an exact solution
31 | for all equations of the :class:`.LSE`:
32 |
33 | .. math::
34 | \sum_i \left( \sum_j A_{ij} x_j - b_i \right)^2
35 |
36 | - two constraints are set:
37 |
38 | 1. the variables must sum to 1: :math:`\sum_i x_i == 1`
39 | 2. the L1-norm (:external:class:`~cvxpy.atoms.norm1.norm1`) of the variables is bounded by
40 | ``max_l1_norm``
41 |
42 | Here is an example:
43 |
44 | >>> from qiskit_addon_mpf.costs import setup_sum_of_squares_problem
45 | >>> from qiskit_addon_mpf.static import setup_static_lse
46 | >>> lse = setup_static_lse([1,2,3], order=2, symmetric=True)
47 | >>> problem, coeffs = setup_sum_of_squares_problem(lse, max_l1_norm=3.0)
48 | >>> print(problem) # doctest: +FLOAT_CMP
49 | minimize quad_over_lin(Vstack([1. 1. 1.] @ x + -1.0,
50 | [1. 0.25 0.11111111] @ x + -0.0,
51 | [1. 0.0625 0.01234568] @ x + -0.0), 1.0)
52 | subject to Sum(x, None, False) == 1.0
53 | norm1(x) <= 3.0
54 |
55 | You can then solve the problem and access the expansion coefficients like so:
56 |
57 | >>> final_cost = problem.solve()
58 | >>> print(coeffs.value) # doctest: +FLOAT_CMP
59 | [ 0.03513467 -1. 1.96486533]
60 |
61 | Args:
62 | lse: the linear system of equations from which to build the model.
63 | max_l1_norm: the upper limit to use for the constrain of the L1-norm of the variables.
64 |
65 | Returns:
66 | The optimization problem and coefficients variable.
67 |
68 | References:
69 | [1]: S. Zhuk et al., Phys. Rev. Research 6, 033309 (2024).
70 | https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.6.033309
71 | """
72 | coeffs = lse.x
73 | # NOTE: the following list comprehension is required to support parameterized LSE objects
74 | cost = cp.sum_squares(cp.vstack([lse.A[i] @ coeffs - b for i, b in enumerate(lse.b)]))
75 | constraints = [cp.sum(coeffs) == 1, cp.norm1(coeffs) <= max_l1_norm]
76 | problem = cp.Problem(cp.Minimize(cost), constraints)
77 | return problem, coeffs
78 |
--------------------------------------------------------------------------------
/qiskit_addon_mpf/static.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Static MPF coefficients.
14 |
15 | .. currentmodule:: qiskit_addon_mpf.static
16 |
17 | This module provides the generator function for the linear system of equations (:class:`.LSE`) for
18 | computing static (that is, time-independent) MPF coefficients.
19 |
20 | .. autofunction:: setup_static_lse
21 | """
22 |
23 | from __future__ import annotations
24 |
25 | import cvxpy as cp
26 | import numpy as np
27 |
28 | from .costs import LSE
29 |
30 |
31 | def setup_static_lse(
32 | trotter_steps: list[int] | cp.Parameter,
33 | *,
34 | order: int = 1,
35 | symmetric: bool = False,
36 | ) -> LSE:
37 | r"""Return the linear system of equations for computing static MPF coefficients.
38 |
39 | This function constructs the following linear system of equations:
40 |
41 | .. math::
42 | A x = b,
43 |
44 | with
45 |
46 | .. math::
47 | A_{0,j} &= 1 \\
48 | A_{i>0,j} &= k_{j}^{-(\chi + s(i-1))} \\
49 | b_0 &= 1 \\
50 | b_{i>0} &= 0
51 |
52 | where $\\chi$ is the ``order``, $s$ is $2$ if ``symmetric`` is ``True`` and $1$ oterhwise,
53 | $k_{j}$ are the ``trotter_steps``, and $x$ are the variables to solve for.
54 | The indices $i$ and $j$ start at $0$.
55 |
56 | Here is an example:
57 |
58 | >>> from qiskit_addon_mpf.static import setup_static_lse
59 | >>> lse = setup_static_lse([1,2,3], order=2, symmetric=True)
60 | >>> print(lse.A)
61 | [[1. 1. 1. ]
62 | [1. 0.25 0.11111111]
63 | [1. 0.0625 0.01234568]]
64 | >>> print(lse.b)
65 | [1. 0. 0.]
66 |
67 | Args:
68 | trotter_steps: the sequence of trotter steps from which to build $A$. Rather than a list of
69 | integers, this may also be a
70 | :external:class:`~cvxpy.expressions.constants.parameter.Parameter` instance of the
71 | desired size. In this case, the constructed :class:`.LSE` is parameterized whose values
72 | must be assigned before it can be solved.
73 | order: the order of the individual product formulas making up the MPF.
74 | symmetric: whether the individual product formulas making up the MPF are symmetric. For
75 | example, the Lie-Trotter formula is `not` symmetric, while Suzuki-Trotter `is`.
76 |
77 | .. note::
78 | Making use of this value is equivalent to the static MPF coefficient description
79 | provided by [1]. In contrast, [2] disregards the symmetry of the individual product
80 | formulas, effectively always setting ``symmetric=False``.
81 |
82 | Returns:
83 | The :class:`.LSE` to find the static MPF coefficients as described above.
84 |
85 | References:
86 | [1]: A. Carrera Vazquez et al., Quantum 7, 1067 (2023).
87 | https://quantum-journal.org/papers/q-2023-07-25-1067/
88 | [2]: S. Zhuk et al., Phys. Rev. Research 6, 033309 (2024).
89 | https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.6.033309
90 | """
91 | symmetric_factor = 2 if symmetric else 1
92 |
93 | trotter_steps_arr: np.ndarray | cp.Parameter
94 | if isinstance(trotter_steps, cp.Parameter):
95 | assert trotter_steps.ndim == 1
96 | num_trotter_steps = trotter_steps.size
97 | trotter_steps_arr = trotter_steps
98 | else:
99 | num_trotter_steps = len(trotter_steps)
100 | trotter_steps_arr = np.array(trotter_steps)
101 |
102 | mat_a = np.array(
103 | [
104 | 1.0 / trotter_steps_arr ** (0 if k == 0 else (order + symmetric_factor * (k - 1)))
105 | for k in range(num_trotter_steps)
106 | ]
107 | )
108 | vec_b = np.zeros(num_trotter_steps)
109 | vec_b[0] = 1
110 | return LSE(mat_a, vec_b)
111 |
--------------------------------------------------------------------------------
/releasenotes/config.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | encoding: utf8
3 | default_branch: main
4 | unreleased_version_title: "Upcoming release"
5 | earliest_version: 0.1.0
6 |
--------------------------------------------------------------------------------
/releasenotes/notes/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qiskit/qiskit-addon-mpf/eff6388c59b43d849cac044114fc814c2a577dde/releasenotes/notes/.gitkeep
--------------------------------------------------------------------------------
/releasenotes/notes/0.2/dynamic-mpf-cf18e0b4d2bdeaff.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | features:
3 | - |
4 | Adds the ability to compute dynamic (i.e. time-dependent) MPF coefficients.
5 | For more details, refer to :mod:`qiskit_addon_mpf.dynamic`.
6 | upgrade:
7 | - |
8 | The code for the static MPF coefficients has been moved.
9 | It functions the same as before, but you have to update your imports and
10 | function names as summarized in the table below:
11 |
12 | =============================================================== ===================================================================
13 | Old New
14 | =============================================================== ===================================================================
15 | ``from qiskit_addon_mpf.static import setup_lse`` ``from qiskit_addon_mpf.static import setup_static_lse``
16 | ``from qiskit_addon_mpf.static import LSE`` ``from qiskit_addon_mpf.costs import LSE``
17 | ``from qiskit_addon_mpf.static import setup_exact_model`` ``from qiskit_addon_mpf.costs import setup_exact_problem``
18 | ``from qiskit_addon_mpf.static import setup_approximate_model`` ``from qiskit_addon_mpf.costs import setup_sum_of_squares_problem``
19 | =============================================================== ===================================================================
20 |
--------------------------------------------------------------------------------
/releasenotes/notes/0.3/fix-numerical-time-checks-4b0d9f65f704caaa.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | fixes:
3 | - |
4 | Fixes the :meth:`.DynamicMPF.evolve` method when providing ``time`` values
5 | with numerical inaccuracies (e.g. ``time=1.00000001``). Previously this
6 | could cause the LHS and RHS time evolvers to advance too far.
7 | The new default accuracy is 8 decimal places, but it may be configured via
8 | the :attr:`.DynamicMPF.TIME_DECIMALS` value.
9 |
--------------------------------------------------------------------------------
/releasenotes/notes/0.3/python-3.13-5e1de7bd55a8f68f.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | upgrade:
3 | - |
4 | This release adds support for Python 3.13. No code changes were necessary,
5 | so older releases are expected to work on Python 3.13 too.
6 |
--------------------------------------------------------------------------------
/releasenotes/notes/0.3/qiskit-2.0-68f026f7c2517f92.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | upgrade:
3 | - |
4 | This package is now compatible with Qiskit SDK 2.0.
5 |
--------------------------------------------------------------------------------
/releasenotes/notes/0.3/quimb-layers-keep-only-odd-bd6c831bb50c18b7.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | upgrade:
3 | - |
4 | The ``keep_only_odd`` attribute of :class:`.quimb_layers.LayerModel` has
5 | been removed. The internal workings have been refactored to ensure that data
6 | reported by its ``terms`` attribute (which is inherited from the base class)
7 | is already taking the ``keep_only_odd`` argument of the
8 | :meth:`.quimb_layers.LayerModel.from_quantum_circuit` constructor method.
9 |
--------------------------------------------------------------------------------
/releasenotes/notes/0.3/remove-scaling-factor-064e157afdfeadc9.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | upgrade:
3 | - |
4 | The ``scaling_factor`` keyword argument of the ``from_quantum_circuit``
5 | constructor methods has been removed. It is not actually needed and was
6 | merely adding an additional (confusing) re-scaling.
7 |
--------------------------------------------------------------------------------
/releasenotes/notes/0.3/tenpy-conserve-none-f34f73b38b89111c.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | features:
3 | - |
4 | TeNPy can disable ``Sz`` conservation. Previously, the
5 | :meth:`~qiskit_addon_mpf.backends.tenpy_tebd.MPOState.initialize_from_lattice`
6 | method did not support this, but a new keyword argument ``conserve`` has
7 | been added which allows the disabling of ``Sz`` conservation. The default
8 | remains to be ``True``.
9 |
--------------------------------------------------------------------------------
/releasenotes/notes/0.3/tenpy-layers-N-steps-guard-96e57723af75c8a0.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | features:
3 | - |
4 | Guards against unexpected behavior when ``N_steps != 1`` in
5 | :meth:`qiskit_addon_mpf.backends.tenpy_layers.LayerwiseEvolver.evolve`.
6 |
--------------------------------------------------------------------------------
/releasenotes/notes/reverse-layers-on-mpo-6a6651864b6b3f7a.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | fixes:
3 | - |
4 | Fixes the order in which layers are applied to a common MPO state when
5 | computing the dynamic MPF coefficients. Previously, the layers would be
6 | applied in the order given, but when acting on an identity MPO and
7 | contracting with the initial state from the outside, the layers have to be
8 | reversed to produce the correct result.
9 |
10 | This bug only affected circuits that are not symmetric with respect to layer
11 | reversal.
12 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | # Test environments
2 |
3 | This repository's tests and development automation tasks are organized using [tox], a command-line CI frontend for Python projects. tox is typically used during local development and is also invoked from this repository's GitHub Actions [workflows](../.github/workflows/).
4 |
5 | tox can be installed by running `pip install tox`.
6 |
7 | tox is organized around various "environments," each of which is described below. To run _all_ test environments, run `tox` without any arguments:
8 |
9 | ```sh
10 | $ tox
11 | ```
12 |
13 | Environments for this repository are configured in [`tox.ini`] as described below.
14 |
15 | ## Lint environment
16 |
17 | The `lint` environment ensures that the code meets basic coding standards, including
18 |
19 | - [_Black_] formatting style
20 | - Style checking with [ruff], [autoflake], and [pydocstyle]
21 | - [mypy] type annotation checker, as configured by [`.mypy.ini`]
22 |
23 | The _Black_ and mypy passes are applied also to [Jupyter] notebooks (via [nbqa]).
24 |
25 | To run:
26 |
27 | ```sh
28 | $ tox -e lint
29 | ```
30 |
31 | ## Style environment
32 |
33 | The command `tox -e style` will apply automated style fixes. This includes:
34 |
35 | - Automated fixes from [ruff] and [autoflake]
36 | - Reformatting of all files in the repository according to _Black_ style
37 |
38 | ## Test (py##) environments
39 |
40 | The `py##` environments are the main test environments. tox defines one for each version of Python. For instance, the following command will run the tests on Python 3.10, Python 3.11, and Python 3.12:
41 |
42 | ```sh
43 | $ tox -e py310,py311,py312
44 | ```
45 |
46 | These environments execute all tests using [pytest], which supports its own simple style of tests, in addition to [unittest]-style tests.
47 |
48 | ## Notebook environments
49 |
50 | The `notebook` and `py##-notebook` environments invoke [nbmake] to ensure that all Jupyter notebooks in the [`docs/`](/docs/) directory execute successfully.
51 |
52 | ```sh
53 | $ tox -e py310-notebook
54 | ```
55 |
56 | ## Doctest environment
57 |
58 | The `doctest` environment uses [doctest] to execute the code snippets that are embedded into the documentation strings. The tests get run using [pytest].
59 |
60 | ```sh
61 | $ tox -e doctest
62 | ```
63 |
64 | ## Coverage environment
65 |
66 | The `coverage` environment uses [Coverage.py] to ensure that the fraction of code tested by pytest is above some threshold (enforced to be 100% for new modules). A detailed, line-by-line coverage report can be viewed by navigating to `htmlcov/index.html` in a web browser.
67 |
68 | To run:
69 |
70 | ```sh
71 | $ tox -e coverage
72 | ```
73 |
74 | ## Documentation environment
75 |
76 | The `docs` environment builds the [Sphinx] documentation locally.
77 |
78 | For the documentation build to succeed, [pandoc](https://pandoc.org/) must be installed. Pandoc is not available via pip, so must be installed through some other means. Linux users are encouraged to install it through their package manager (e.g., `sudo apt-get install -y pandoc`), while macOS users are encouraged to install it via [Homebrew](https://brew.sh/) (`brew install pandoc`). Full instructions are available on [pandoc's installation page](https://pandoc.org/installing.html).
79 |
80 | To run this environment:
81 |
82 | ```sh
83 | $ tox -e docs
84 | ```
85 |
86 | If the build succeeds, it can be viewed by navigating to `docs/_build/html/index.html` in a web browser.
87 |
88 | [tox]: https://github.com/tox-dev/tox
89 | [`tox.ini`]: ../tox.ini
90 | [mypy]: https://mypy.readthedocs.io/en/stable/
91 | [`.mypy.ini`]: ../.mypy.ini
92 | [nbmake]: https://github.com/treebeardtech/nbmake
93 | [_Black_]: https://github.com/psf/black
94 | [ruff]: https://github.com/charliermarsh/ruff
95 | [autoflake]: https://github.com/PyCQA/autoflake
96 | [pydocstyle]: https://www.pydocstyle.org/en/stable/
97 | [pylint]: https://github.com/PyCQA/pylint
98 | [nbqa]: https://github.com/nbQA-dev/nbQA
99 | [Jupyter]: https://jupyter.org/
100 | [doctest]: https://docs.python.org/3/library/doctest.html
101 | [pytest]: https://docs.pytest.org/
102 | [unittest]: https://docs.python.org/3/library/unittest.html
103 | [Coverage.py]: https://coverage.readthedocs.io/
104 | [Sphinx]: https://www.sphinx-doc.org/
105 |
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
--------------------------------------------------------------------------------
/test/backends/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
--------------------------------------------------------------------------------
/test/backends/quimb_circuit/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
--------------------------------------------------------------------------------
/test/backends/quimb_circuit/test_e2e.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | from functools import partial
14 |
15 | import numpy as np
16 | import pytest
17 | from qiskit.circuit import Parameter, QuantumCircuit
18 | from qiskit.quantum_info import SparsePauliOp
19 | from qiskit.synthesis import SuzukiTrotter
20 | from qiskit_addon_mpf.backends import HAS_QUIMB
21 | from qiskit_addon_mpf.costs import setup_frobenius_problem
22 | from qiskit_addon_mpf.dynamic import setup_dynamic_lse
23 | from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit
24 |
25 | if HAS_QUIMB:
26 | from qiskit_addon_mpf.backends.quimb_circuit import CircuitEvolver, CircuitState
27 |
28 |
29 | @pytest.mark.skipif(not HAS_QUIMB, reason="Quimb is required for these unittests")
30 | class TestEndToEnd:
31 | @pytest.mark.parametrize(
32 | ["time", "expected_A", "expected_b", "expected_coeffs"],
33 | [
34 | (
35 | 0.5,
36 | [[1.0, 0.99961572], [0.99961572, 1.0]],
37 | [0.99939447, 0.99804667],
38 | [2.25365838, -1.25365812],
39 | ),
40 | ],
41 | )
42 | def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs):
43 | np.random.seed(0)
44 |
45 | # constants
46 | L = 6
47 | W = 0.5
48 | epsilon = 0.5
49 |
50 | J = np.random.rand(L - 1) + W * np.ones(L - 1)
51 | # ZZ couplings
52 | Jz = 1.0
53 | # XX and YY couplings
54 | Jxx = epsilon
55 | hz = 0.000000001 * np.array([(-1) ** i for i in range(L)])
56 |
57 | hamil = SparsePauliOp.from_sparse_list(
58 | [("Z", [k], hz[k]) for k in range(L)]
59 | + [("ZZ", [k, k + 1], J[k] * Jz) for k in range(L - 1)]
60 | + [("YY", [k, k + 1], J[k] * Jxx) for k in range(L - 1)]
61 | + [("XX", [k, k + 1], J[k] * Jxx) for k in range(L - 1)],
62 | num_qubits=L,
63 | )
64 |
65 | dt = Parameter("dt")
66 | suz_4 = generate_time_evolution_circuit(hamil, synthesis=SuzukiTrotter(order=4), time=dt)
67 | suz_2 = generate_time_evolution_circuit(hamil, synthesis=SuzukiTrotter(order=2), time=dt)
68 |
69 | initial_state = QuantumCircuit(L)
70 | for k in range(1, L, 2):
71 | initial_state.x(k)
72 |
73 | model = setup_dynamic_lse(
74 | [4, 3],
75 | time,
76 | CircuitState,
77 | partial(CircuitEvolver, circuit=suz_4, dt=0.05),
78 | partial(CircuitEvolver, circuit=suz_2),
79 | initial_state,
80 | )
81 | np.testing.assert_allclose(model.b, expected_b, rtol=1e-4)
82 | np.testing.assert_allclose(model.A, expected_A, rtol=1e-4)
83 |
84 | prob, coeffs = setup_frobenius_problem(model)
85 | prob.solve()
86 | np.testing.assert_allclose(coeffs.value, expected_coeffs, rtol=1e-4)
87 |
--------------------------------------------------------------------------------
/test/backends/quimb_circuit/test_state.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | import pytest
14 | from qiskit.circuit import QuantumCircuit
15 | from qiskit_addon_mpf.backends import HAS_QUIMB
16 |
17 | if HAS_QUIMB:
18 | from qiskit_addon_mpf.backends.quimb_circuit import CircuitState
19 | from quimb.tensor import MPS_neel_state
20 |
21 |
22 | @pytest.mark.skipif(not HAS_QUIMB, reason="Quimb is required for these unittests")
23 | class TestCircuitState:
24 | def test_empty_state_handling(self):
25 | """Test the handling of a non-evolved state."""
26 | state = CircuitState()
27 | circ = QuantumCircuit(5)
28 | with pytest.raises(RuntimeError):
29 | state.overlap(circ)
30 |
31 | def test_unsupported_state(self):
32 | """Test the handling of a non-supported state object."""
33 | state = CircuitState()
34 | neel = MPS_neel_state(5)
35 | with pytest.raises(TypeError):
36 | state.overlap(neel)
37 |
--------------------------------------------------------------------------------
/test/backends/quimb_layers/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
--------------------------------------------------------------------------------
/test/backends/quimb_layers/test_e2e.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | from functools import partial
14 |
15 | import numpy as np
16 | import pytest
17 | from qiskit.circuit import QuantumCircuit
18 | from qiskit.circuit.library import XXPlusYYGate
19 | from qiskit_addon_mpf.backends import HAS_QUIMB
20 | from qiskit_addon_mpf.costs import setup_frobenius_problem
21 | from qiskit_addon_mpf.dynamic import setup_dynamic_lse
22 |
23 | if HAS_QUIMB:
24 | from qiskit_addon_mpf.backends.quimb_layers import LayerModel, LayerwiseEvolver
25 | from qiskit_addon_mpf.backends.quimb_tebd import MPOState, TEBDEvolver
26 | from quimb.tensor import MPO_identity, MPS_neel_state, SpinHam1D
27 |
28 |
29 | def gen_ext_field_layer(n, hz):
30 | qc = QuantumCircuit(n)
31 | for q in range(n):
32 | qc.rz(-hz[q], q)
33 | return qc
34 |
35 |
36 | def trotter_step(qc, q, Jxx, Jz):
37 | qc.rzz(Jz, q, q + 1)
38 | qc.append(XXPlusYYGate(2.0 * Jxx), [q, q + 1])
39 |
40 |
41 | def gen_odd_coupling_layer(n, Jxx, Jz, J):
42 | qc = QuantumCircuit(n)
43 | for q in range(0, n - 1, 2):
44 | trotter_step(qc, q, J[q] * Jxx, J[q] * Jz)
45 | return qc
46 |
47 |
48 | def gen_even_coupling_layer(n, Jxx, Jz, J):
49 | qc = QuantumCircuit(n)
50 | for q in range(1, n - 1, 2):
51 | trotter_step(qc, q, J[q] * Jxx, J[q] * Jz)
52 | return qc
53 |
54 |
55 | @pytest.mark.skipif(not HAS_QUIMB, reason="Quimb is required for these unittests")
56 | class TestEndToEnd:
57 | @pytest.mark.parametrize(
58 | ["time", "expected_A", "expected_b", "expected_coeffs"],
59 | [
60 | (
61 | 0.5,
62 | [[1.0, 0.99975619], [0.99975619, 1.0]],
63 | [0.99952226, 0.99854528],
64 | [2.50358245, -1.50358218],
65 | ),
66 | (
67 | 1.0,
68 | [[1.0, 0.99189288], [0.99189288, 1.0]],
69 | [0.9871936, 0.96466791],
70 | [1.88925267, -0.88925267],
71 | ),
72 | (
73 | 1.5,
74 | [[1.0, 0.95352741], [0.95352741, 1.0]],
75 | [0.90166405, 0.79052836],
76 | [1.69571242, -0.69571242],
77 | ),
78 | ],
79 | )
80 | def test_end_to_end_custom_suzuki(self, time, expected_A, expected_b, expected_coeffs):
81 | np.random.seed(0)
82 |
83 | # constants
84 | L = 10
85 | W = 0.5
86 | epsilon = 0.5
87 |
88 | J = np.random.rand(L - 1) + W * np.ones(L - 1)
89 | # ZZ couplings
90 | Jz = 1.0
91 | # XX and YY couplings
92 | Jxx = epsilon
93 |
94 | # base coupling
95 | # external field
96 | hz = 0.000000001 * np.array([(-1) ** i for i in range(L)])
97 |
98 | # Initialize the builder for a spin 1/2 chain
99 | builder = SpinHam1D(S=1 / 2)
100 |
101 | # Add XX and YY couplings for neighboring sites
102 | for i in range(L - 1):
103 | builder[i, i + 1] += 2.0 * Jxx * J[i], "-", "+"
104 | builder[i, i + 1] += 2.0 * Jxx * J[i], "+", "-"
105 |
106 | # Add ZZ couplings for neighboring sites
107 | for i in range(L - 1):
108 | builder[i, i + 1] += 4.0 * Jz * J[i], "Z", "Z"
109 |
110 | # Add the external Z-field (hz) to each site
111 | for i in range(L):
112 | builder[i] += -2.0 * hz[i], "Z"
113 |
114 | # Build the local Hamiltonian
115 | exact_model = builder.build_local_ham(L)
116 |
117 | # NOTE: below we are building each layer at a time, but we could also have built a single
118 | # Trotter circuit and sliced it using `qiskit_addon_utils.slicing`.
119 | odd_coupling_layer = LayerModel.from_quantum_circuit(
120 | gen_odd_coupling_layer(L, Jxx, Jz, J),
121 | cyclic=False,
122 | )
123 | even_coupling_layer = LayerModel.from_quantum_circuit(
124 | gen_even_coupling_layer(L, Jxx, Jz, 2.0 * J), # factor 2 because its the central layer
125 | cyclic=False,
126 | )
127 | odd_onsite_layer = LayerModel.from_quantum_circuit(
128 | gen_ext_field_layer(L, hz),
129 | keep_only_odd=True,
130 | cyclic=False,
131 | )
132 | even_onsite_layer = LayerModel.from_quantum_circuit(
133 | gen_ext_field_layer(L, hz),
134 | keep_only_odd=False,
135 | cyclic=False,
136 | )
137 | # Our layers combine to form a second-order Suzuki-Trotter formula as follows:
138 | layers = [
139 | odd_coupling_layer,
140 | odd_onsite_layer,
141 | even_onsite_layer,
142 | even_coupling_layer,
143 | even_onsite_layer,
144 | odd_onsite_layer,
145 | odd_coupling_layer,
146 | ]
147 |
148 | split_opts = {
149 | "max_bond": 10,
150 | "cutoff": 1e-5,
151 | "cutoff_mode": "rel",
152 | "method": "svd",
153 | "renorm": False,
154 | }
155 |
156 | initial_state = MPS_neel_state(L)
157 |
158 | model = setup_dynamic_lse(
159 | [4, 3],
160 | time,
161 | lambda: MPOState(MPO_identity(L)),
162 | partial(
163 | TEBDEvolver,
164 | H=exact_model,
165 | dt=0.05,
166 | order=4,
167 | split_opts=split_opts,
168 | ),
169 | partial(
170 | LayerwiseEvolver,
171 | layers=layers,
172 | split_opts=split_opts,
173 | ),
174 | initial_state,
175 | )
176 | np.testing.assert_allclose(model.b, expected_b, rtol=1e-3)
177 | np.testing.assert_allclose(model.A, expected_A, rtol=1e-3)
178 |
179 | prob, coeffs = setup_frobenius_problem(model)
180 | prob.solve()
181 | # NOTE: this particular test converges to fairly different overlaps in the CI on MacOS only.
182 | # Thus, the assertion threshold is so loose.
183 | np.testing.assert_allclose(coeffs.value, expected_coeffs, rtol=1e-1)
184 |
--------------------------------------------------------------------------------
/test/backends/quimb_layers/test_model.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 |
14 | import numpy as np
15 | import pytest
16 | from qiskit.circuit import QuantumCircuit
17 | from qiskit.circuit.library import XXPlusYYGate
18 | from qiskit_addon_mpf.backends import HAS_QUIMB
19 |
20 | if HAS_QUIMB:
21 | from qiskit_addon_mpf.backends.quimb_layers import LayerModel
22 |
23 |
24 | @pytest.mark.skipif(not HAS_QUIMB, reason="Quimb is required for these unittests")
25 | class TestLayerModel:
26 | def test_from_quantum_circuit(self):
27 | L = 6
28 |
29 | qc = QuantumCircuit(L)
30 | for i in range(0, L - 1, 2):
31 | qc.rzz(1.0, i, i + 1)
32 | for i in range(L):
33 | qc.rz(2.0, i)
34 | for i in range(1, L - 1, 2):
35 | qc.append(XXPlusYYGate(1.0), [i, i + 1])
36 |
37 | qc = qc.repeat(2).decompose()
38 |
39 | model = LayerModel.from_quantum_circuit(qc)
40 | expected_terms = {
41 | (0, 1): np.array(
42 | [
43 | [4.0, 0.0, 0.0, 0.0],
44 | [0.0, 0.0, 0.0, 0.0],
45 | [0.0, 0.0, -2.0, 0.0],
46 | [0.0, 0.0, 0.0, -2.0],
47 | ]
48 | ),
49 | (2, 3): np.array(
50 | [
51 | [3.0, 0.0, 0.0, 0.0],
52 | [0.0, -1.0, 0.0, 0.0],
53 | [0.0, 0.0, -1.0, 0.0],
54 | [0.0, 0.0, 0.0, -1.0],
55 | ]
56 | ),
57 | (1, 2): np.array(
58 | [
59 | [2.0, 0.0, 0.0, 0.0],
60 | [0.0, 0.0, 1.0, 0.0],
61 | [0.0, 1.0, 0.0, 0.0],
62 | [0.0, 0.0, 0.0, -2.0],
63 | ]
64 | ),
65 | (4, 5): np.array(
66 | [
67 | [4.0, 0.0, 0.0, 0.0],
68 | [0.0, -2.0, 0.0, 0.0],
69 | [0.0, 0.0, 0.0, 0.0],
70 | [0.0, 0.0, 0.0, -2.0],
71 | ]
72 | ),
73 | (3, 4): np.array(
74 | [
75 | [2.0, 0.0, 0.0, 0.0],
76 | [0.0, 0.0, 1.0, 0.0],
77 | [0.0, 1.0, 0.0, 0.0],
78 | [0.0, 0.0, 0.0, -2.0],
79 | ]
80 | ),
81 | }
82 | for sites in model.terms:
83 | np.testing.assert_allclose(model.terms[sites], expected_terms[sites])
84 |
85 | def test_handling_unsupportedown_gate(self):
86 | qc = QuantumCircuit(1)
87 | qc.h(0)
88 | with pytest.raises(NotImplementedError):
89 | LayerModel.from_quantum_circuit(qc)
90 |
--------------------------------------------------------------------------------
/test/backends/quimb_tebd/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
--------------------------------------------------------------------------------
/test/backends/quimb_tebd/test_e2e.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | from functools import partial
14 |
15 | import numpy as np
16 | import pytest
17 | from qiskit_addon_mpf.backends import HAS_QUIMB
18 | from qiskit_addon_mpf.costs import setup_frobenius_problem
19 | from qiskit_addon_mpf.dynamic import setup_dynamic_lse
20 |
21 | if HAS_QUIMB:
22 | from qiskit_addon_mpf.backends.quimb_tebd import MPOState, TEBDEvolver
23 | from quimb.tensor import MPO_identity, MPS_neel_state, SpinHam1D
24 |
25 |
26 | @pytest.mark.skipif(not HAS_QUIMB, reason="Quimb is required for these unittests")
27 | class TestEndToEnd:
28 | @pytest.mark.parametrize(
29 | ["time", "expected_A", "expected_b", "expected_coeffs"],
30 | [
31 | (
32 | 0.5,
33 | [[1.0, 0.9997562], [0.9997562, 1.0]],
34 | [0.99952645, 0.99854528],
35 | [2.51221012, -1.51220984],
36 | ),
37 | (
38 | 1.0,
39 | [[1.0, 0.99189288], [0.99189288, 1.0]],
40 | [0.9871936, 0.96466791],
41 | [1.88925272, -0.88925272],
42 | ),
43 | (
44 | 1.5,
45 | [[1.0, 0.95352741], [0.95352741, 1.0]],
46 | [0.8887205, 0.79052836],
47 | [1.55645214, -0.55645214],
48 | ),
49 | ],
50 | )
51 | def test_end_to_end_builtin_suzuki(self, time, expected_A, expected_b, expected_coeffs):
52 | np.random.seed(0)
53 |
54 | # constants
55 | L = 10
56 | W = 0.5
57 | epsilon = 0.5
58 |
59 | J = np.random.rand(L - 1) + W * np.ones(L - 1)
60 | # ZZ couplings
61 | Jz = 1.0
62 | # XX and YY couplings
63 | Jxx = epsilon
64 |
65 | # base coupling
66 | # external field
67 | hz = 0.000000001 * np.array([(-1) ** i for i in range(L)])
68 |
69 | # Initialize the builder for a spin 1/2 chain
70 | builder = SpinHam1D(S=1 / 2)
71 |
72 | # Add XX and YY couplings for neighboring sites
73 | for i in range(L - 1):
74 | builder[i, i + 1] += 2.0 * Jxx * J[i], "-", "+"
75 | builder[i, i + 1] += 2.0 * Jxx * J[i], "+", "-"
76 |
77 | # Add ZZ couplings for neighboring sites
78 | for i in range(L - 1):
79 | builder[i, i + 1] += 4.0 * Jz * J[i], "Z", "Z"
80 |
81 | # Add the external Z-field (hz) to each site
82 | for i in range(L):
83 | builder[i] += -2.0 * hz[i], "Z"
84 |
85 | # Build the local Hamiltonian
86 | exact_model = builder.build_local_ham(L)
87 |
88 | split_opts = {
89 | "max_bond": 10,
90 | "cutoff": 1e-5,
91 | "cutoff_mode": "rel",
92 | "method": "svd",
93 | "renorm": False,
94 | }
95 |
96 | initial_state = MPS_neel_state(L)
97 |
98 | model = setup_dynamic_lse(
99 | [4, 3],
100 | time,
101 | lambda: MPOState(MPO_identity(L)),
102 | partial(
103 | TEBDEvolver,
104 | H=exact_model,
105 | dt=0.05,
106 | order=4,
107 | split_opts=split_opts,
108 | ),
109 | partial(
110 | TEBDEvolver,
111 | H=exact_model,
112 | order=2,
113 | split_opts=split_opts,
114 | ),
115 | initial_state,
116 | )
117 | np.testing.assert_allclose(model.b, expected_b, rtol=1.5e-3)
118 | np.testing.assert_allclose(model.A, expected_A, rtol=1e-3)
119 |
120 | prob, coeffs = setup_frobenius_problem(model)
121 | prob.solve()
122 | # NOTE: this particular test converges to fairly different overlaps in the CI on MacOS only.
123 | # Thus, the assertion threshold is so loose.
124 | np.testing.assert_allclose(coeffs.value, expected_coeffs, rtol=0.1)
125 |
--------------------------------------------------------------------------------
/test/backends/quimb_tebd/test_state.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | import pytest
14 | from qiskit.circuit import QuantumCircuit
15 | from qiskit_addon_mpf.backends import HAS_QUIMB
16 |
17 | if HAS_QUIMB:
18 | from qiskit_addon_mpf.backends.quimb_tebd import MPOState
19 | from quimb.tensor import MPO_identity
20 |
21 |
22 | @pytest.mark.skipif(not HAS_QUIMB, reason="Quimb is required for these unittests")
23 | class TestMPOState:
24 | def test_unsupported_state(self):
25 | """Test the handling of a non-supported state object."""
26 | state = MPOState(MPO_identity(5))
27 | circ = QuantumCircuit(5)
28 | with pytest.raises(TypeError):
29 | state.overlap(circ)
30 |
--------------------------------------------------------------------------------
/test/backends/tenpy_layers/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
--------------------------------------------------------------------------------
/test/backends/tenpy_layers/test_e2e.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024, 2025.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | from functools import partial
14 |
15 | import numpy as np
16 | import pytest
17 | from qiskit.circuit import QuantumCircuit
18 | from qiskit.circuit.library import XXPlusYYGate
19 | from qiskit_addon_mpf.backends import HAS_TENPY
20 | from qiskit_addon_mpf.costs import setup_frobenius_problem
21 | from qiskit_addon_mpf.dynamic import setup_dynamic_lse
22 |
23 | if HAS_TENPY:
24 | from qiskit_addon_mpf.backends.tenpy_layers import LayerModel, LayerwiseEvolver
25 | from qiskit_addon_mpf.backends.tenpy_tebd import MPOState, MPS_neel_state, TEBDEvolver
26 | from tenpy.models import XXZChain2
27 | from tenpy.networks.site import SpinHalfSite
28 |
29 |
30 | def gen_ext_field_layer(n, hz):
31 | qc = QuantumCircuit(n)
32 | for q in range(n):
33 | qc.rz(-hz[q], q)
34 | return qc
35 |
36 |
37 | def trotter_step(qc, q, Jxx, Jz):
38 | qc.rzz(Jz, q, q + 1)
39 | qc.append(XXPlusYYGate(2.0 * Jxx), [q, q + 1])
40 |
41 |
42 | def gen_odd_coupling_layer(n, Jxx, Jz, J):
43 | qc = QuantumCircuit(n)
44 | for q in range(0, n - 1, 2):
45 | trotter_step(qc, q, J[q] * Jxx, J[q] * Jz)
46 | return qc
47 |
48 |
49 | def gen_even_coupling_layer(n, Jxx, Jz, J):
50 | qc = QuantumCircuit(n)
51 | for q in range(1, n - 1, 2):
52 | trotter_step(qc, q, J[q] * Jxx, J[q] * Jz)
53 | return qc
54 |
55 |
56 | class ConserveXXZChain2(XXZChain2):
57 | """TeNPy's XXZChain2 hard-codes Sz conservation. This subclass makes it configurable."""
58 |
59 | def init_sites(self, model_params):
60 | conserve = model_params.get("conserve", "Sz", bool)
61 | sort_charge = model_params.get("sort_charge", True, bool)
62 | return SpinHalfSite(conserve=conserve, sort_charge=sort_charge) # use predefined Site
63 |
64 |
65 | @pytest.mark.skipif(not HAS_TENPY, reason="TeNPy is required for these unittests")
66 | class TestEndToEnd:
67 | @pytest.mark.parametrize(
68 | ["time", "expected_A", "expected_b", "expected_coeffs", "conserve"],
69 | [
70 | (
71 | 0.5,
72 | [[1.0, 0.9997562], [0.9997562, 1.0]],
73 | [0.99944125, 0.99857914],
74 | [2.2680617, -1.26806141],
75 | "None",
76 | ),
77 | (
78 | 0.5,
79 | [[1.0, 0.9997562], [0.9997562, 1.0]],
80 | [0.99944125, 0.99857914],
81 | [2.2680558, -1.26805551],
82 | "Sz",
83 | ),
84 | (
85 | 1.0,
86 | [[1.0, 0.99189288], [0.99189288, 1.0]],
87 | [0.98672068, 0.9676077],
88 | [1.67877714, -0.67877714],
89 | "Sz",
90 | ),
91 | (
92 | 1.5,
93 | [[1.0, 0.95352741], [0.95352741, 1.0]],
94 | [0.91217099, 0.68407899],
95 | [2.95404895, -1.95404895],
96 | "Sz",
97 | ),
98 | ],
99 | )
100 | def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs, conserve):
101 | np.random.seed(0)
102 |
103 | # constants
104 | L = 10
105 | W = 0.5
106 | epsilon = 0.5
107 |
108 | J = np.random.rand(L - 1) + W * np.ones(L - 1)
109 | # ZZ couplings
110 | Jz = 1.0
111 | # XX and YY couplings
112 | Jxx = epsilon
113 |
114 | # base coupling
115 | # external field
116 | hz = 0.000000001 * np.array([(-1) ** i for i in range(L)])
117 |
118 | # This is the full model that we want to simulate. It is used for the "exact" time evolution
119 | # (which is approximated via a fourth-order Suzuki-Trotter formula).
120 | exact_model = ConserveXXZChain2(
121 | {
122 | "L": L,
123 | "Jz": 4.0 * Jz * J,
124 | "Jxx": 4.0 * Jxx * J,
125 | "hz": 2.0 * hz,
126 | "bc_MPS": "finite",
127 | "conserve": conserve,
128 | "sort_charge": False,
129 | }
130 | )
131 |
132 | # NOTE: below we are building each layer at a time, but we could also have built a single
133 | # Trotter circuit and sliced it using `qiskit_addon_utils.slicing`.
134 | odd_coupling_layer = LayerModel.from_quantum_circuit(
135 | gen_odd_coupling_layer(L, Jxx, Jz, J),
136 | conserve=conserve,
137 | bc_MPS="finite",
138 | )
139 | even_coupling_layer = LayerModel.from_quantum_circuit(
140 | gen_even_coupling_layer(L, Jxx, Jz, 2.0 * J), # factor 2 because its the central layer
141 | conserve=conserve,
142 | bc_MPS="finite",
143 | )
144 | odd_onsite_layer = LayerModel.from_quantum_circuit(
145 | gen_ext_field_layer(L, hz),
146 | keep_only_odd=True,
147 | conserve=conserve,
148 | bc_MPS="finite",
149 | )
150 | even_onsite_layer = LayerModel.from_quantum_circuit(
151 | gen_ext_field_layer(L, hz),
152 | keep_only_odd=False,
153 | conserve=conserve,
154 | bc_MPS="finite",
155 | )
156 | # Our layers combine to form a second-order Suzuki-Trotter formula as follows:
157 | layers = [
158 | odd_coupling_layer,
159 | odd_onsite_layer,
160 | even_onsite_layer,
161 | even_coupling_layer,
162 | even_onsite_layer,
163 | odd_onsite_layer,
164 | odd_coupling_layer,
165 | ]
166 |
167 | options_common = {
168 | "trunc_params": {
169 | "chi_max": 10,
170 | "svd_min": 1e-5,
171 | "trunc_cut": None,
172 | },
173 | "preserve_norm": False,
174 | }
175 | options_exact = options_common.copy()
176 | options_exact["order"] = 4
177 |
178 | options_approx = options_common.copy()
179 |
180 | initial_state = MPS_neel_state(exact_model.lat)
181 |
182 | model = setup_dynamic_lse(
183 | [4, 3],
184 | time,
185 | partial(MPOState.initialize_from_lattice, exact_model.lat, conserve=conserve == "Sz"),
186 | partial(
187 | TEBDEvolver,
188 | model=exact_model,
189 | dt=0.05,
190 | options=options_exact,
191 | ),
192 | partial(
193 | LayerwiseEvolver,
194 | layers=layers,
195 | options=options_approx,
196 | ),
197 | initial_state,
198 | )
199 | np.testing.assert_allclose(model.b, expected_b, rtol=1e-4)
200 | np.testing.assert_allclose(model.A, expected_A, rtol=1e-4)
201 |
202 | prob, coeffs = setup_frobenius_problem(model)
203 | prob.solve()
204 | np.testing.assert_allclose(coeffs.value, expected_coeffs, rtol=1e-4)
205 |
--------------------------------------------------------------------------------
/test/backends/tenpy_layers/test_model.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 |
14 | import numpy as np
15 | import pytest
16 | from qiskit.circuit import QuantumCircuit
17 | from qiskit.circuit.library import XXPlusYYGate
18 | from qiskit_addon_mpf.backends import HAS_TENPY
19 |
20 | if HAS_TENPY:
21 | from qiskit_addon_mpf.backends.tenpy_layers import LayerModel
22 |
23 |
24 | @pytest.mark.skipif(not HAS_TENPY, reason="Tenpy is required for these unittests")
25 | class TestLayerModel:
26 | def test_from_quantum_circuit(self):
27 | L = 6
28 |
29 | qc = QuantumCircuit(L)
30 | for i in range(0, L - 1, 2):
31 | qc.rzz(1.0, i, i + 1)
32 | for i in range(L):
33 | qc.rz(2.0, i)
34 | for i in range(1, L - 1, 2):
35 | qc.append(XXPlusYYGate(1.0), [i, i + 1])
36 |
37 | qc = qc.repeat(2).decompose()
38 |
39 | model = LayerModel.from_quantum_circuit(qc)
40 | expected_H_bonds = [
41 | np.array(
42 | [
43 | [4.0, 0.0, 0.0, 0.0],
44 | [0.0, 0.0, 0.0, 0.0],
45 | [0.0, 0.0, 0.0, 0.0],
46 | [-2.0, 0.0, 0.0, -2.0],
47 | ]
48 | ),
49 | np.array(
50 | [
51 | [2.0, 0.0, 0.0, 0.0],
52 | [0.0, 0.0, 1.0, 0.0],
53 | [0.0, 1.0, 0.0, 0.0],
54 | [0.0, 0.0, 0.0, -2.0],
55 | ]
56 | ),
57 | np.array(
58 | [
59 | [3.0, 0.0, 0.0, -1.0],
60 | [0.0, 0.0, 0.0, 0.0],
61 | [0.0, 0.0, 0.0, 0.0],
62 | [-1.0, 0.0, 0.0, -1.0],
63 | ]
64 | ),
65 | np.array(
66 | [
67 | [2.0, 0.0, 0.0, 0.0],
68 | [0.0, 0.0, 1.0, 0.0],
69 | [0.0, 1.0, 0.0, 0.0],
70 | [0.0, 0.0, 0.0, -2.0],
71 | ]
72 | ),
73 | np.array(
74 | [
75 | [4.0, 0.0, 0.0, -2.0],
76 | [0.0, 0.0, 0.0, 0.0],
77 | [0.0, 0.0, 0.0, 0.0],
78 | [0.0, 0.0, 0.0, -2.0],
79 | ]
80 | ),
81 | ]
82 | assert model.H_bond[0] is None
83 | for expected, actual in zip(expected_H_bonds, model.H_bond[1:]):
84 | np.testing.assert_allclose(expected, actual.to_ndarray().reshape((4, 4)))
85 |
86 | def test_handling_unsupportedown_gate(self):
87 | qc = QuantumCircuit(1)
88 | qc.h(0)
89 | with pytest.raises(NotImplementedError):
90 | LayerModel.from_quantum_circuit(qc)
91 |
--------------------------------------------------------------------------------
/test/backends/tenpy_tebd/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
--------------------------------------------------------------------------------
/test/backends/tenpy_tebd/test_e2e.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024, 2025.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | from functools import partial
14 |
15 | import numpy as np
16 | import pytest
17 | from qiskit_addon_mpf.backends import HAS_TENPY
18 | from qiskit_addon_mpf.costs import setup_frobenius_problem
19 | from qiskit_addon_mpf.dynamic import setup_dynamic_lse
20 |
21 | if HAS_TENPY:
22 | from qiskit_addon_mpf.backends.tenpy_tebd import MPOState, MPS_neel_state, TEBDEvolver
23 | from tenpy.models import XXZChain2
24 | from tenpy.networks.site import SpinHalfSite
25 |
26 |
27 | class ConserveXXZChain2(XXZChain2):
28 | """TeNPy's XXZChain2 hard-codes Sz conservation. This subclass makes it configurable."""
29 |
30 | def init_sites(self, model_params):
31 | conserve = model_params.get("conserve", "Sz", bool)
32 | sort_charge = model_params.get("sort_charge", True, bool)
33 | return SpinHalfSite(conserve=conserve, sort_charge=sort_charge) # use predefined Site
34 |
35 |
36 | @pytest.mark.skipif(not HAS_TENPY, reason="TeNPy is required for these unittests")
37 | class TestEndToEnd:
38 | @pytest.mark.parametrize(
39 | ["time", "expected_A", "expected_b", "expected_coeffs", "conserve"],
40 | [
41 | (
42 | 0.5,
43 | [[1.0, 0.9997562], [0.9997562, 1.0]],
44 | [0.99944125, 0.99857914],
45 | [2.26806357, -1.26806329],
46 | "None",
47 | ),
48 | (
49 | 0.5,
50 | [[1.0, 0.9997562], [0.9997562, 1.0]],
51 | [0.99944125, 0.99857914],
52 | [2.26805572, -1.26805543],
53 | "Sz",
54 | ),
55 | (
56 | 1.0,
57 | [[1.0, 0.99189288], [0.99189288, 1.0]],
58 | [0.98672068, 0.9676077],
59 | [1.67877713, -0.67877713],
60 | "Sz",
61 | ),
62 | (
63 | 1.5,
64 | [[1.0, 0.95352741], [0.95352741, 1.0]],
65 | [0.94009322, 0.68407899],
66 | [3.25446509, -2.25446509],
67 | "Sz",
68 | ),
69 | ],
70 | )
71 | def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs, conserve):
72 | np.random.seed(0)
73 |
74 | # constants
75 | L = 10
76 | W = 0.5
77 | epsilon = 0.5
78 |
79 | J = np.random.rand(L - 1) + W * np.ones(L - 1)
80 | # ZZ couplings
81 | Jz = 1.0
82 | # XX and YY couplings
83 | Jxx = epsilon
84 |
85 | # base coupling
86 | # external field
87 | hz = 0.000000001 * np.array([(-1) ** i for i in range(L)])
88 |
89 | # This is the full model that we want to simulate. It is used for the "exact" time evolution
90 | # (which is approximated via a fourth-order Suzuki-Trotter formula).
91 | exact_model = ConserveXXZChain2(
92 | {
93 | "L": L,
94 | "Jz": 4.0 * Jz * J,
95 | "Jxx": 4.0 * Jxx * J,
96 | "hz": 2.0 * hz,
97 | "bc_MPS": "finite",
98 | "conserve": conserve,
99 | "sort_charge": False,
100 | }
101 | )
102 |
103 | options_common = {
104 | "trunc_params": {
105 | "chi_max": 10,
106 | "svd_min": 1e-5,
107 | "trunc_cut": None,
108 | },
109 | "preserve_norm": False,
110 | }
111 | options_exact = options_common.copy()
112 | options_exact["order"] = 4
113 |
114 | options_approx = options_common.copy()
115 | options_approx["order"] = 2
116 |
117 | initial_state = MPS_neel_state(exact_model.lat)
118 |
119 | model = setup_dynamic_lse(
120 | [4, 3],
121 | time,
122 | partial(MPOState.initialize_from_lattice, exact_model.lat, conserve=conserve == "Sz"),
123 | partial(
124 | TEBDEvolver,
125 | model=exact_model,
126 | dt=0.05,
127 | options=options_exact,
128 | ),
129 | partial(
130 | TEBDEvolver,
131 | model=exact_model,
132 | options=options_approx,
133 | ),
134 | initial_state,
135 | )
136 | np.testing.assert_allclose(model.b, expected_b, rtol=1e-4)
137 | np.testing.assert_allclose(model.A, expected_A, rtol=1e-4)
138 |
139 | prob, coeffs = setup_frobenius_problem(model)
140 | prob.solve()
141 | np.testing.assert_allclose(coeffs.value, expected_coeffs, rtol=1e-4)
142 |
--------------------------------------------------------------------------------
/test/backends/tenpy_tebd/test_state.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | import pytest
14 | from qiskit.circuit import QuantumCircuit
15 | from qiskit_addon_mpf.backends import HAS_TENPY
16 |
17 | if HAS_TENPY:
18 | from qiskit_addon_mpf.backends.tenpy_tebd import MPOState
19 | from tenpy.models.lattice import Chain
20 | from tenpy.networks.site import SpinHalfSite
21 |
22 |
23 | @pytest.mark.skipif(not HAS_TENPY, reason="Tenpy is required for these unittests")
24 | class TestMPOState:
25 | def test_unsupported_state(self):
26 | """Test the handling of a non-supported state object."""
27 | site = SpinHalfSite()
28 | lattice = Chain(5, site)
29 | state = MPOState.initialize_from_lattice(lattice)
30 | circ = QuantumCircuit(5)
31 | with pytest.raises(TypeError):
32 | state.overlap(circ)
33 |
--------------------------------------------------------------------------------
/test/costs/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
--------------------------------------------------------------------------------
/test/costs/test_exact.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Tests for the ``qiskit_addon_mpf.static.exact`` module."""
14 |
15 | import cvxpy as cp
16 | import numpy as np
17 | import pytest
18 | from qiskit_addon_mpf.costs.exact import setup_exact_problem
19 | from qiskit_addon_mpf.static import setup_static_lse
20 |
21 |
22 | class TestExactCoeffs:
23 | """Tests for the ``qiskit_addon_mpf.static.exact`` module."""
24 |
25 | def test_setup_exact_problem(self, subtests):
26 | """Tests the :meth:`.setup_exact_problem` method."""
27 | trotter_steps = [1, 2, 4]
28 | lse = setup_static_lse(trotter_steps, order=2, symmetric=True)
29 | problem, coeffs = setup_exact_problem(lse)
30 |
31 | with subtests.test(msg="final cost"):
32 | final_cost = problem.solve()
33 | pytest.approx(final_cost, 1.888888888888)
34 |
35 | with subtests.test(msg="optimal coefficients"):
36 | expected = np.array([0.02222222, -0.44444444, 1.42222222])
37 | np.testing.assert_allclose(coeffs.value, expected)
38 |
39 | @pytest.mark.parametrize(
40 | ["trotter_steps", "expected_coeffs", "expected_cost"],
41 | [
42 | # well-conditioned
43 | ([1, 2], [-1.0, 2.0], 3.0),
44 | ([1, 3], [-0.5, 1.5], 2.0),
45 | ([2, 4], [-1.0, 2.0], 3.0),
46 | ([2, 5], [-2 / 3, 5 / 3], 7 / 3),
47 | ([1, 2, 6], [0.2, -1.0, 1.8], 3.0),
48 | ([1, 2, 7], [1 / 6, -4 / 5, 49 / 30], 2.6),
49 | # ill-conditioned
50 | ([6, 7], [-6, 7], 13.0),
51 | ([3, 4, 5, 6, 7], [27 / 8, -128 / 3, 625 / 4, -216, 2401 / 24], 518.3333332),
52 | (
53 | [1, 2, 3, 4, 5, 6, 7],
54 | [1 / 720, -8 / 15, 243 / 16, -1024 / 9, 15625 / 48, -3888 / 10, 117649 / 720],
55 | 1007.22220288,
56 | ),
57 | ],
58 | )
59 | def test_exact_order_1_references(
60 | self, subtests, trotter_steps: list[int], expected_coeffs: list[float], expected_cost: float
61 | ):
62 | """This test ensures the correct behavior of the exact static MPF coefficient model.
63 |
64 | It does so, using the test-cases listed in Appendix E of [1].
65 |
66 | [1]: A. Carrera Vazquez et al., Quantum 7, 1067 (2023).
67 | https://quantum-journal.org/papers/q-2023-07-25-1067/
68 | """
69 | lse = setup_static_lse(trotter_steps, order=1)
70 | problem, coeffs = setup_exact_problem(lse)
71 |
72 | with subtests.test(msg="final cost"):
73 | final_cost = problem.solve()
74 | pytest.approx(final_cost, expected_cost)
75 |
76 | with subtests.test(msg="optimal coefficients"):
77 | np.testing.assert_allclose(coeffs.value, expected_coeffs)
78 |
79 | @pytest.mark.parametrize(
80 | ["order", "trotter_steps", "expected_coeffs", "expected_cost"],
81 | [
82 | (2, [1, 2], [-1.0 / 3.0, 4.0 / 3.0], 5.0 / 3.0),
83 | (2, [1, 2, 4], [1.0 / 45.0, -4.0 / 9.0, 64.00 / 45.0], 85.0 / 45.0),
84 | (2, [1, 2, 6], [1.0 / 105.0, -1.0 / 6.0, 81.0 / 70.0], 4.0 / 3.0),
85 | (4, [1, 2], [-1.0 / 15.0, 16.0 / 15.0], 17.0 / 15.0),
86 | (4, [1, 2, 3], [1 / 336.0, -32 / 105.0, 729 / 560.0], 1.60952381),
87 | (4, [1, 2, 4], [1 / 945.0, -16 / 189.0, 1024 / 945.0], 1.16931217),
88 | ],
89 | )
90 | def test_exact_higher_order_references(
91 | self,
92 | subtests,
93 | order: int,
94 | trotter_steps: list[int],
95 | expected_coeffs: list[float],
96 | expected_cost: float,
97 | ):
98 | """This test ensures the correct behavior for higher order formulas.
99 |
100 | It does so, using some of the test-cases listed in Appendix A of [1].
101 |
102 | [1]: G. H. Low et al, arXiv:1907.11679v2 (2019).
103 | https://arxiv.org/abs/1907.11679v2
104 | """
105 | lse = setup_static_lse(trotter_steps, order=order, symmetric=True)
106 | problem, coeffs = setup_exact_problem(lse)
107 |
108 | with subtests.test(msg="final cost"):
109 | final_cost = problem.solve()
110 | pytest.approx(final_cost, expected_cost)
111 |
112 | with subtests.test(msg="optimal coefficients"):
113 | np.testing.assert_allclose(coeffs.value, expected_coeffs)
114 |
115 | def test_setup_exact_problem_params(self, subtests):
116 | """Tests the :meth:`.setup_exact_problem` method with parameters."""
117 | ks = cp.Parameter(2)
118 | lse = setup_static_lse(ks, order=1)
119 | problem, coeffs = setup_exact_problem(lse)
120 |
121 | for trotter_steps, expected_coeffs, expected_cost in [
122 | ([1, 2], [-1.0, 2.0], 3.0),
123 | ([1, 3], [-0.5, 1.5], 2.0),
124 | ([2, 4], [-1.0, 2.0], 3.0),
125 | ([2, 5], [-2 / 3, 5 / 3], 7 / 3),
126 | ([6, 7], [-6, 7], 13.0),
127 | ]:
128 | ks.value = trotter_steps
129 |
130 | with subtests.test(msg="final cost"):
131 | final_cost = problem.solve()
132 | pytest.approx(final_cost, expected_cost)
133 |
134 | with subtests.test(msg="optimal coefficients"):
135 | np.testing.assert_allclose(coeffs.value, expected_coeffs)
136 |
--------------------------------------------------------------------------------
/test/costs/test_lse.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Tests for the ``qiskit_addon_mpf.costs.LSE`` class."""
14 |
15 | import numpy as np
16 | import pytest
17 | from qiskit_addon_mpf.costs import LSE
18 | from qiskit_addon_mpf.static import setup_static_lse
19 |
20 |
21 | class TestLSE:
22 | """Tests for the ``qiskit_addon_mpf.costs.LSE`` class."""
23 |
24 | def test_lse_solve(self):
25 | """Tests the :meth:`.LSE.solve` method."""
26 | trotter_steps = [1, 2, 4]
27 | lse = setup_static_lse(trotter_steps, order=2, symmetric=True)
28 | coeffs = lse.solve()
29 | expected = np.array([0.022222222, -0.444444444, 1.422222222])
30 | np.testing.assert_allclose(coeffs, expected)
31 |
32 | def test_lse_solve_invalid(self):
33 | """Tests the handling of an invalid model."""
34 | mat_a = np.asarray([[1.0, 0.9], [0.9, 1.0]])
35 | vec_b = np.asarray([0.9, 0.9])
36 | lse = LSE(mat_a, vec_b)
37 | with pytest.raises(ValueError):
38 | lse.solve()
39 |
--------------------------------------------------------------------------------
/test/costs/test_sum_of_squares.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Tests for the ``qiskit_addon_mpf.static.sum_of_squares`` module."""
14 |
15 | from typing import Any, ClassVar
16 |
17 | import cvxpy as cp
18 | import numpy as np
19 | import pytest
20 | from qiskit_addon_mpf.costs.sum_of_squares import setup_sum_of_squares_problem
21 | from qiskit_addon_mpf.static import setup_static_lse
22 |
23 |
24 | class TestSumOfSquaresCoeffs:
25 | """Tests for the ``qiskit_addon_mpf.static.sum_of_squares`` module."""
26 |
27 | CVXPY_SOLVER_SETTINGS: ClassVar[dict[str, Any]] = {
28 | "warm_start": False,
29 | "eps_abs": 1e-7,
30 | "eps_rel": 1e-7,
31 | }
32 |
33 | def test_setup_sum_of_squares_problem(self, subtests):
34 | """Tests the :meth:`.setup_sum_of_squares_problem` method."""
35 | trotter_steps = [1, 2, 4]
36 | lse = setup_static_lse(trotter_steps, order=2, symmetric=True)
37 | problem, coeffs = setup_sum_of_squares_problem(lse)
38 |
39 | with subtests.test(msg="final cost"):
40 | final_cost = problem.solve(**self.CVXPY_SOLVER_SETTINGS)
41 | pytest.approx(final_cost, 0)
42 |
43 | with subtests.test(msg="optimal coefficients"):
44 | expected = np.array([0.02222225, -0.44444444, 1.42222216])
45 | np.testing.assert_allclose(coeffs.value, expected, rtol=1e-4)
46 |
47 | def test_setup_sum_of_squares_problem_max_l1_norm(self, subtests):
48 | """Tests the :meth:`.setup_sum_of_squares_problem` method with ``max_l1_norm``."""
49 | trotter_steps = [1, 2, 4]
50 | lse = setup_static_lse(trotter_steps, order=2, symmetric=True)
51 | problem, coeffs = setup_sum_of_squares_problem(lse, max_l1_norm=1.5)
52 |
53 | with subtests.test(msg="final cost"):
54 | final_cost = problem.solve(**self.CVXPY_SOLVER_SETTINGS)
55 | pytest.approx(final_cost, 0.00035765)
56 |
57 | with subtests.test(msg="optimal coefficients"):
58 | expected = np.array([-0.001143293, -0.2488567, 1.25])
59 | np.testing.assert_allclose(coeffs.value, expected, rtol=1e-4)
60 |
61 | def test_setup_sum_of_squares_problem_params(self, subtests):
62 | """Tests the :meth:`.setup_sum_of_squares_problem` method with parameters."""
63 | ks = cp.Parameter(3)
64 | lse = setup_static_lse(ks, order=2, symmetric=True)
65 | problem, coeffs = setup_sum_of_squares_problem(lse)
66 |
67 | ks.value = [1, 2, 4]
68 |
69 | with subtests.test(msg="final cost"):
70 | final_cost = problem.solve(**self.CVXPY_SOLVER_SETTINGS)
71 | pytest.approx(final_cost, 0)
72 |
73 | with subtests.test(msg="optimal coefficients"):
74 | expected = np.array([0.02222225, -0.44444444, 1.42222216])
75 | np.testing.assert_allclose(coeffs.value, expected, rtol=1e-4)
76 |
--------------------------------------------------------------------------------
/test/test_dynamic.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2025.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | from functools import partial
14 |
15 | import numpy as np
16 | import pytest
17 | from qiskit_addon_mpf.backends import HAS_QUIMB
18 | from qiskit_addon_mpf.costs import setup_frobenius_problem
19 | from qiskit_addon_mpf.dynamic import DynamicMPF, setup_dynamic_lse
20 |
21 | if HAS_QUIMB:
22 | from qiskit_addon_mpf.backends.quimb_tebd import MPOState, TEBDEvolver
23 | from quimb.tensor import MPO_identity, MPS_neel_state, ham_1d_heis
24 |
25 |
26 | @pytest.mark.skipif(not HAS_QUIMB, reason="Quimb is required for this regression test")
27 | class TestDynamic:
28 | @pytest.mark.parametrize(["time", "decimals"], [[1.000000001, None], [1.0001, 3]])
29 | def test_numerical_check_accuracy(self, time, decimals):
30 | """This is a regression test against the evolution time comparisons."""
31 | np.random.seed(0)
32 | L = 4
33 | exact_model = ham_1d_heis(L)
34 |
35 | split_opts = {
36 | "max_bond": 32,
37 | "cutoff": 1e-8,
38 | "cutoff_mode": "rel",
39 | "method": "svd",
40 | "renorm": False,
41 | }
42 |
43 | initial_state = MPS_neel_state(L)
44 |
45 | prev = DynamicMPF.TIME_DECIMALS
46 | if decimals is not None:
47 | DynamicMPF.TIME_DECIMALS = decimals
48 |
49 | model = setup_dynamic_lse(
50 | [2, 3, 4],
51 | time,
52 | lambda: MPOState(MPO_identity(L)),
53 | partial(
54 | TEBDEvolver,
55 | H=exact_model,
56 | dt=0.1,
57 | order=4,
58 | split_opts=split_opts,
59 | ),
60 | partial(
61 | TEBDEvolver,
62 | H=exact_model,
63 | order=2,
64 | split_opts=split_opts,
65 | ),
66 | initial_state,
67 | )
68 |
69 | try:
70 | np.testing.assert_allclose(model.b, [0.999878, 0.999976, 0.999993], rtol=1e-3)
71 | np.testing.assert_allclose(
72 | model.A,
73 | [[1.0, 0.999962, 0.999931], [0.999962, 1.0, 0.999995], [0.999931, 0.999995, 1.0]],
74 | rtol=1e-3,
75 | )
76 |
77 | prob, coeffs = setup_frobenius_problem(model)
78 | prob.solve()
79 | # NOTE: this particular test converges to fairly different overlaps in the CI on MacOS
80 | # only. Thus, the assertion threshold is so loose.
81 | np.testing.assert_allclose(coeffs.value, [-0.494253, 0.643685, 0.850568], rtol=0.1)
82 | finally:
83 | DynamicMPF.TIME_DECIMALS = prev
84 |
85 | def test_numerical_check_fails(self):
86 | """Test that we can overwrite the time precision"""
87 | np.random.seed(0)
88 | L = 4
89 | exact_model = ham_1d_heis(L)
90 |
91 | split_opts = {
92 | "max_bond": 32,
93 | "cutoff": 1e-8,
94 | "cutoff_mode": "rel",
95 | "method": "svd",
96 | "renorm": False,
97 | }
98 |
99 | initial_state = MPS_neel_state(L)
100 |
101 | prev = DynamicMPF.TIME_DECIMALS
102 | DynamicMPF.TIME_DECIMALS = 10
103 | try:
104 | with pytest.raises(RuntimeError):
105 | _ = setup_dynamic_lse(
106 | [2, 3, 4],
107 | 1.000000001,
108 | lambda: MPOState(MPO_identity(L)),
109 | partial(
110 | TEBDEvolver,
111 | H=exact_model,
112 | dt=0.1,
113 | order=4,
114 | split_opts=split_opts,
115 | ),
116 | partial(
117 | TEBDEvolver,
118 | H=exact_model,
119 | order=2,
120 | split_opts=split_opts,
121 | ),
122 | initial_state,
123 | )
124 | finally:
125 | DynamicMPF.TIME_DECIMALS = prev
126 |
--------------------------------------------------------------------------------
/test/test_static.py:
--------------------------------------------------------------------------------
1 | # This code is a Qiskit project.
2 | #
3 | # (C) Copyright IBM 2024.
4 | #
5 | # This code is licensed under the Apache License, Version 2.0. You may
6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8 | #
9 | # Any modifications or derivative works of this code must retain this
10 | # copyright notice, and modified files need to carry a notice indicating
11 | # that they have been altered from the originals.
12 |
13 | """Tests for the ``qiskit_addon_mpf.static.lse`` module."""
14 |
15 | import cvxpy as cp
16 | import numpy as np
17 | import pytest
18 | from qiskit_addon_mpf.static import setup_static_lse
19 |
20 |
21 | class TestLSE:
22 | """Tests for the ``qiskit_addon_mpf.static.lse`` module."""
23 |
24 | def test_setup_static_lse(self, subtests):
25 | """Tests the :meth:` setup_static_lse` method."""
26 | trotter_steps = [1, 2, 4]
27 | expected_b = np.array([1, 0, 0])
28 |
29 | with subtests.test(msg="order=1"):
30 | lse = setup_static_lse(trotter_steps, order=1)
31 | expected_A = np.array([[1.0, 1.0, 1.0], [1.0, 0.5, 0.25], [1.0, 0.25, 0.0625]])
32 | np.testing.assert_allclose(lse.A, expected_A)
33 | np.testing.assert_allclose(lse.b, expected_b)
34 | expected_c = np.array([0.33333333, -2.0, 2.66666667])
35 | np.testing.assert_allclose(lse.solve(), expected_c)
36 |
37 | with subtests.test(msg="order=2"):
38 | lse = setup_static_lse(trotter_steps, order=2, symmetric=True)
39 | expected_A = np.array([[1.0, 1.0, 1.0], [1.0, 0.25, 0.0625], [1.0, 0.0625, 0.00390625]])
40 | np.testing.assert_allclose(lse.A, expected_A)
41 | np.testing.assert_allclose(lse.b, expected_b)
42 | expected_c = np.array([0.022222222, -0.444444444, 1.422222222])
43 | np.testing.assert_allclose(lse.solve(), expected_c)
44 |
45 | def test_lse_with_params(self, subtests):
46 | """Tests the :meth:` setup_static_lse` method with parameters."""
47 | trotter_steps = cp.Parameter(3)
48 |
49 | lse = setup_static_lse(trotter_steps)
50 |
51 | with subtests.test(msg="assert ValueError"), pytest.raises(ValueError):
52 | lse.solve()
53 |
54 | trotter_steps.value = [1, 2, 4]
55 |
56 | expected_A = np.array([[1.0, 1.0, 1.0], [1.0, 0.5, 0.25], [1.0, 0.25, 0.0625]])
57 | for idx, row in enumerate(expected_A):
58 | np.testing.assert_allclose(lse.A[idx].value, row)
59 |
60 | expected_c = np.array([0.33333333, -2.0, 2.66666667])
61 | np.testing.assert_allclose(lse.solve(), expected_c)
62 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | minversion = 4.4.3
3 | envlist = py{39,310,311,312,313}{,-notebook}, lint, coverage, docs, doctest
4 | isolated_build = True
5 |
6 | [testenv]
7 | package = wheel
8 | wheel_build_env = .pkg
9 | parallel_show_output = True
10 | extras =
11 | test
12 | commands =
13 | pytest {posargs}
14 |
15 | [testenv:style]
16 | extras =
17 | style
18 | commands =
19 | ruff format qiskit_addon_mpf/ docs/ test/
20 | ruff check --fix qiskit_addon_mpf/ docs/ test/
21 | nbqa ruff --fix docs/
22 |
23 | [testenv:lint]
24 | image-tester-commit = 7ae965ccf21c39e5170334ec7f4882756883860a
25 | deps =
26 | git+https://github.com/Qiskit/documentation.git@{[testenv:lint]image-tester-commit}\#egg=sphinx-alt-text-validator&subdirectory=scripts/image-tester
27 | basepython = python3.10
28 | extras =
29 | lint
30 | commands =
31 | ruff format --check qiskit_addon_mpf/ docs/ test/
32 | ruff check qiskit_addon_mpf/ docs/ test/
33 | ruff check --preview --select CPY001 --exclude "*.ipynb" qiskit_addon_mpf/ test/
34 | nbqa ruff docs/
35 | mypy qiskit_addon_mpf/
36 | pylint -rn qiskit_addon_mpf/ test/
37 | sphinx-alt-text-validator -f qiskit_addon_mpf
38 | nbqa pylint -rn docs/
39 | typos
40 | reno lint
41 |
42 | [testenv:{,py-,py3-,py39-,py310-,py311-,py312-,py313-}notebook]
43 | extras =
44 | nbtest
45 | notebook-dependencies
46 | commands =
47 | pytest --nbmake --nbmake-timeout=3000 {posargs} docs/
48 |
49 | [testenv:{,py-,py3-,py39-,py310-,py311-,py312-,py313-}doctest]
50 | extras =
51 | test
52 | doctest
53 | commands =
54 | pytest --doctest-plus --doctest-only {posargs}
55 |
56 | [testenv:coverage]
57 | basepython = python3.10
58 | deps =
59 | coverage>=7.4.1
60 | extras =
61 | test
62 | doctest
63 | commands =
64 | pytest --doctest-modules --cov=qiskit_addon_mpf/
65 |
66 | [testenv:docs]
67 | basepython = python3.10
68 | extras =
69 | docs
70 | commands =
71 | sphinx-build -j auto -W -T --keep-going -b html {posargs} {toxinidir}/docs/ {toxinidir}/docs/_build/html
72 | passenv =
73 | CI
74 | GITHUB_BASE_REF
75 | GITHUB_REF_NAME
76 |
77 | [testenv:docs-clean]
78 | skip_install = true
79 | allowlist_externals =
80 | rm
81 | commands =
82 | rm -rf {toxinidir}/docs/stubs/ {toxinidir}/docs/_build/
83 |
--------------------------------------------------------------------------------