├── .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 | [![Release](https://img.shields.io/pypi/v/qiskit-addon-mpf.svg?label=Release)](https://github.com/Qiskit/qiskit-addon-mpf/releases) 5 | ![Platform](https://img.shields.io/badge/%F0%9F%92%BB%20Platform-Linux%20%7C%20macOS%20%7C%20Windows-informational) 6 | [![Python](https://img.shields.io/pypi/pyversions/qiskit-addon-mpf?label=Python&logo=python)](https://www.python.org/) 7 | [![Qiskit](https://img.shields.io/badge/Qiskit%20-%20%3E%3D1.2%20-%20%236133BD?logo=Qiskit)](https://github.com/Qiskit/qiskit) 8 |
9 | [![Docs (stable)](https://img.shields.io/badge/%F0%9F%93%84%20Docs-stable-blue.svg)](https://qiskit.github.io/qiskit-addon-mpf/) 10 | 11 | [![License](https://img.shields.io/github/license/Qiskit/qiskit-addon-mpf?label=License)](LICENSE.txt) 12 | [![Downloads](https://img.shields.io/pypi/dm/qiskit-addon-mpf.svg?label=Downloads)](https://pypi.org/project/qiskit-addon-mpf/) 13 | [![Tests](https://github.com/Qiskit/qiskit-addon-mpf/actions/workflows/test_latest_versions.yml/badge.svg)](https://github.com/Qiskit/qiskit-addon-mpf/actions/workflows/test_latest_versions.yml) 14 | [![Coverage](https://coveralls.io/repos/github/Qiskit/qiskit-addon-mpf/badge.svg?branch=main)](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 | --------------------------------------------------------------------------------