├── .dockerignore ├── .github └── workflows │ ├── build-and-release.yml │ ├── check.yml │ ├── docker.yml │ ├── docs-dev.yml │ ├── docs-release.yml │ ├── docs-stable.yml │ ├── test-latest-versions.yml │ └── test-minimum-versions.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── compose.yaml ├── docs ├── api │ ├── ffsim.contract.rst │ ├── ffsim.linalg.rst │ ├── ffsim.optimize.rst │ ├── ffsim.qiskit.rst │ ├── ffsim.random.rst │ ├── ffsim.rst │ ├── ffsim.testing.rst │ └── index.md ├── conf.py ├── explanations │ ├── double-factorized.ipynb │ ├── hamiltonians.ipynb │ ├── index.md │ ├── lucj.ipynb │ ├── orbital-rotation.ipynb │ ├── qiskit-gate-decompositions.ipynb │ └── state-vectors-and-gates.ipynb ├── how-to-guides │ ├── entanglement-forging.ipynb │ ├── fermion-operator.ipynb │ ├── index.md │ ├── qiskit-circuits-sim.ipynb │ ├── qiskit-lucj.ipynb │ ├── qiskit-merge-orbital-rotations.ipynb │ └── simulate-lucj.ipynb ├── index.md ├── install.md └── tutorials │ ├── double-factorized-trotter.ipynb │ └── index.md ├── pyproject.toml ├── python └── ffsim │ ├── __init__.py │ ├── _lib.pyi │ ├── _slow │ ├── __init__.py │ ├── contract │ │ ├── __init__.py │ │ ├── diag_coulomb.py │ │ └── num_op_sum.py │ ├── fermion_operator.py │ └── gates │ │ ├── __init__.py │ │ ├── diag_coulomb.py │ │ ├── num_op_sum.py │ │ └── orbital_rotation.py │ ├── cistring.py │ ├── contract │ ├── __init__.py │ ├── diag_coulomb.py │ ├── num_op_sum.py │ └── one_body.py │ ├── gates │ ├── __init__.py │ ├── basic_gates.py │ ├── diag_coulomb.py │ ├── num_op_sum.py │ └── orbital_rotation.py │ ├── hamiltonians │ ├── __init__.py │ ├── diagonal_coulomb_hamiltonian.py │ ├── double_factorized_hamiltonian.py │ ├── molecular_hamiltonian.py │ └── single_factorized_hamiltonian.py │ ├── linalg │ ├── __init__.py │ ├── double_factorized_decomposition.py │ ├── givens.py │ ├── linalg.py │ └── predicates.py │ ├── molecular_data.py │ ├── operators │ ├── __init__.py │ ├── common_operators.py │ ├── fermi_hubbard.py │ ├── fermion_action.py │ └── fermion_operator.py │ ├── optimize │ ├── __init__.py │ ├── _util.py │ └── linear_method.py │ ├── protocols │ ├── __init__.py │ ├── apply_unitary_protocol.py │ ├── approximate_equality_protocol.py │ ├── diagonal_protocol.py │ ├── fermion_operator_protocol.py │ ├── linear_operator_protocol.py │ └── trace_protocol.py │ ├── py.typed │ ├── qiskit │ ├── __init__.py │ ├── gates │ │ ├── __init__.py │ │ ├── diag_coulomb.py │ │ ├── diag_coulomb_trotter.py │ │ ├── double_factorized_trotter.py │ │ ├── givens_ansatz.py │ │ ├── num_num_ansatz.py │ │ ├── num_op_sum.py │ │ ├── orbital_rotation.py │ │ ├── slater_determinant.py │ │ └── ucj.py │ ├── jordan_wigner.py │ ├── sampler.py │ ├── sim.py │ ├── transpiler_passes │ │ ├── __init__.py │ │ ├── drop_negligible.py │ │ └── merge_orbital_rotations.py │ ├── transpiler_stages.py │ └── util.py │ ├── random │ ├── __init__.py │ └── random.py │ ├── spin.py │ ├── states │ ├── __init__.py │ ├── bitstring.py │ ├── product_state_sum.py │ ├── rdm.py │ ├── sample_slater.py │ ├── slater.py │ ├── states.py │ └── wick.py │ ├── testing │ ├── __init__.py │ └── testing.py │ ├── trotter │ ├── __init__.py │ ├── _util.py │ ├── diagonal_coulomb.py │ ├── double_factorized.py │ └── qdrift.py │ └── variational │ ├── __init__.py │ ├── givens.py │ ├── hopgate.py │ ├── multireference.py │ ├── num_num.py │ ├── uccsd.py │ ├── ucj_angles_spin_balanced.py │ ├── ucj_spin_balanced.py │ ├── ucj_spin_unbalanced.py │ ├── ucj_spinless.py │ └── util.py ├── src ├── contract │ ├── diag_coulomb.rs │ ├── mod.rs │ └── num_op_sum.rs ├── fermion_operator.rs ├── gates │ ├── diag_coulomb.rs │ ├── mod.rs │ ├── num_op_sum.rs │ ├── orbital_rotation.rs │ └── phase_shift.rs └── lib.rs ├── tests └── python │ ├── __init__.py │ ├── _slow │ ├── __init__.py │ ├── contract │ │ ├── __init__.py │ │ ├── diag_coulomb_test.py │ │ └── num_op_sum_test.py │ └── gates │ │ ├── __init__.py │ │ ├── diag_coulomb_test.py │ │ ├── num_op_sum_test.py │ │ └── orbital_rotation_test.py │ ├── contract │ ├── __init__.py │ ├── diag_coulomb_test.py │ └── num_op_sum_test.py │ ├── gates │ ├── __init__.py │ ├── basic_gates_test.py │ ├── diag_coulomb_test.py │ ├── num_op_sum_test.py │ └── orbital_rotation_test.py │ ├── hamiltonians │ ├── __init__.py │ ├── diagonal_coulomb_hamiltonian_test.py │ ├── double_factorized_hamiltonian_test.py │ ├── molecular_hamiltonian_test.py │ └── single_factorized_hamiltonian_test.py │ ├── linalg │ ├── __init__.py │ ├── double_factorized_decomposition_test.py │ ├── givens_test.py │ └── linalg_test.py │ ├── molecular_data_test.py │ ├── operators │ ├── __init__.py │ ├── common_operators_test.py │ ├── fermi_hubbard_test.py │ ├── fermion_action_test.py │ └── fermion_operator_test.py │ ├── optimize │ ├── __init__.py │ └── linear_method_test.py │ ├── qiskit │ ├── __init__.py │ ├── gates │ │ ├── __init__.py │ │ ├── diag_coulomb_test.py │ │ ├── diag_coulomb_trotter_test.py │ │ ├── double_factorized_trotter_test.py │ │ ├── givens_ansatz_test.py │ │ ├── num_num_ansatz_test.py │ │ ├── num_op_sum_test.py │ │ ├── orbital_rotation_test.py │ │ ├── slater_determinant_test.py │ │ └── ucj_test.py │ ├── jordan_wigner_test.py │ ├── sampler_test.py │ ├── sim_test.py │ ├── transpiler_passes │ │ ├── __init__.py │ │ ├── drop_negligible_test.py │ │ └── merge_orbital_rotations_test.py │ └── util_test.py │ ├── random_test.py │ ├── states │ ├── __init__.py │ ├── bitstring_test.py │ ├── rdm_test.py │ ├── sample_slater_test.py │ ├── slater_test.py │ ├── states_test.py │ └── wick_test.py │ ├── test_data │ └── orbital_rotation-0.npy │ ├── test_no_warnings.py │ ├── testing_test.py │ ├── trotter │ ├── __init__.py │ ├── diag_coulomb_test.py │ ├── double_factorized_test.py │ └── qdrift_test.py │ └── variational │ ├── __init__.py │ ├── givens_test.py │ ├── hopgate_test.py │ ├── multireference_test.py │ ├── num_num_test.py │ ├── uccsd_test.py │ ├── ucj_angles_spin_balanced_test.py │ ├── ucj_spin_balanced_test.py │ ├── ucj_spin_unbalanced_test.py │ └── ucj_spinless_test.py └── tox.ini /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | Dockerfile 4 | 5 | # Below items are from .gitignore 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | pip-wheel-metadata/ 30 | share/python-wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | MANIFEST 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | *.py,cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | docs/api/generated/ 80 | 81 | # PyBuilder 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # IPython 88 | profile_default/ 89 | ipython_config.py 90 | 91 | # pyenv 92 | .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 102 | __pypackages__/ 103 | 104 | # Celery stuff 105 | celerybeat-schedule 106 | celerybeat.pid 107 | 108 | # SageMath parsed files 109 | *.sage.py 110 | 111 | # Environments 112 | .env 113 | .venv 114 | env/ 115 | venv/ 116 | ENV/ 117 | env.bak/ 118 | venv.bak/ 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | 138 | # PyCharm 139 | .idea/ 140 | 141 | # VS Code 142 | .vscode/ 143 | 144 | # cibuildwheel 145 | wheelhouse/ 146 | 147 | # asv 148 | .asv/ 149 | -------------------------------------------------------------------------------- /.github/workflows/build-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload to PyPI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | release: 10 | types: 11 | - published 12 | 13 | jobs: 14 | build_wheels: 15 | name: Build wheels on ${{ matrix.os }} 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest, macos-latest, macos-13] 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | 24 | - name: Build wheels 25 | uses: pypa/cibuildwheel@v2.13.1 26 | 27 | - uses: actions/upload-artifact@v4 28 | with: 29 | name: wheel-${{ matrix.os }} 30 | path: ./wheelhouse/*.whl 31 | 32 | build_sdist: 33 | name: Build source distribution 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v3 37 | 38 | - name: Build sdist 39 | run: pipx run build --sdist 40 | 41 | - uses: actions/upload-artifact@v4 42 | with: 43 | name: sdist 44 | path: dist/*.tar.gz 45 | 46 | upload_pypi: 47 | needs: [build_wheels, build_sdist] 48 | runs-on: ubuntu-latest 49 | environment: pypi 50 | permissions: 51 | id-token: write 52 | if: github.event_name == 'release' && github.event.action == 'published' 53 | # or, alternatively, upload to PyPI on every tag starting with 'v' (remove on: release above to use this) 54 | # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 55 | steps: 56 | - uses: actions/download-artifact@v4 57 | with: 58 | pattern: wheel-* 59 | path: dist 60 | merge-multiple: true 61 | 62 | - uses: actions/download-artifact@v4 63 | with: 64 | name: sdist 65 | path: dist 66 | 67 | - uses: pypa/gh-action-pypi-publish@release/v1 68 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: check 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "*" 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | check: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | tox-env: ["coverage", "type", "lint", "format", "docs"] 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Install dependencies 24 | run: | 25 | sudo apt install -y libopenblas-dev pandoc 26 | - name: Set up Python 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: "3.13" 30 | - name: Install tox 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install tox 34 | - name: Run tox environment 35 | shell: bash 36 | run: | 37 | tox run -e ${{ matrix.tox-env }} 38 | - name: Archive HTML documentation 39 | if: matrix.tox-env == 'docs' 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: docs-html 43 | path: ./docs/_build/html/ 44 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker notebook tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: ['Dockerfile', '.dockerignore', 'compose.yaml', '.github/workflows/docker.yml'] 7 | pull_request: 8 | branches: [ main ] 9 | paths: ['Dockerfile', '.dockerignore', 'compose.yaml', '.github/workflows/docker.yml'] 10 | schedule: 11 | - cron: '0 20 * * 3' 12 | 13 | jobs: 14 | tests: 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 30 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Build image 20 | run: docker compose build 21 | - name: Test notebooks 22 | shell: bash 23 | run: docker compose run notebook "bash" "-c" "pip install pytest nbmake && pytest --nbmake docs" 24 | - name: Test that persistent-volume is writable 25 | shell: bash 26 | run: docker compose run notebook "bash" "-c" "touch persistent-volume/empty-notebook.ipynb" -------------------------------------------------------------------------------- /.github/workflows/docs-dev.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy documentation for development branch 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | build_and_deploy_docs: 11 | permissions: 12 | contents: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: "3.13" 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install tox 23 | sudo apt update 24 | sudo apt install -y libopenblas-dev pandoc 25 | - name: Build docs 26 | run: | 27 | tox run -e docs 28 | - name: Deploy docs 29 | uses: JamesIves/github-pages-deploy-action@v4 30 | with: 31 | folder: docs/_build/html/ 32 | target-folder: dev/ 33 | -------------------------------------------------------------------------------- /.github/workflows/docs-release.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy documentation for release version 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - "*" 8 | 9 | jobs: 10 | build_and_deploy_docs: 11 | permissions: 12 | contents: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: "3.13" 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install tox 23 | sudo apt update 24 | sudo apt install -y libopenblas-dev pandoc 25 | - name: Build docs 26 | run: | 27 | tox run -e docs 28 | - name: Deploy docs 29 | uses: JamesIves/github-pages-deploy-action@v4 30 | with: 31 | folder: docs/_build/html/ 32 | clean-exclude: | 33 | stable/* 34 | dev/* 35 | -------------------------------------------------------------------------------- /.github/workflows/docs-stable.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy documentation for stable version 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - "*" 8 | 9 | jobs: 10 | build_and_deploy_docs: 11 | permissions: 12 | contents: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: "3.13" 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install tox 23 | sudo apt update 24 | sudo apt install -y libopenblas-dev pandoc 25 | - name: Build docs 26 | run: | 27 | tox run -e docs 28 | - name: Set current version 29 | run: | 30 | echo "version=$(git describe --tags)" >> "$GITHUB_ENV" 31 | - name: Deploy docs 32 | uses: JamesIves/github-pages-deploy-action@v4 33 | with: 34 | folder: docs/_build/html/ 35 | target-folder: stable/${{ env.version }} 36 | -------------------------------------------------------------------------------- /.github/workflows/test-latest-versions.yml: -------------------------------------------------------------------------------- 1 | name: test-latest-versions 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "*" 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | test-linux: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Install dependencies 24 | run: | 25 | sudo apt install -y libopenblas-dev 26 | - name: Set up Python 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install tox 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install tox 34 | - name: Run tests 35 | run: | 36 | tox run -e py${{ matrix.python-version }} 37 | test-macos-arm: 38 | runs-on: macos-latest 39 | strategy: 40 | matrix: 41 | python-version: ["3.9", "3.13"] 42 | steps: 43 | - uses: actions/checkout@v3 44 | - name: Set up Python 45 | uses: actions/setup-python@v4 46 | with: 47 | python-version: ${{ matrix.python-version }} 48 | - name: Install tox 49 | run: | 50 | python -m pip install --upgrade pip 51 | pip install tox 52 | - name: Run tests 53 | run: | 54 | tox run -e py${{ matrix.python-version }} 55 | test-macos-intel: 56 | runs-on: macos-13 57 | strategy: 58 | matrix: 59 | python-version: ["3.9", "3.13"] 60 | steps: 61 | - uses: actions/checkout@v3 62 | - name: Set up Python 63 | uses: actions/setup-python@v4 64 | with: 65 | python-version: ${{ matrix.python-version }} 66 | - name: Install tox 67 | run: | 68 | python -m pip install --upgrade pip 69 | pip install tox 70 | - name: Run tests 71 | run: | 72 | tox run -e py${{ matrix.python-version }} 73 | -------------------------------------------------------------------------------- /.github/workflows/test-minimum-versions.yml: -------------------------------------------------------------------------------- 1 | name: test-minimum-versions 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "*" 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | test-linux: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | python-version: ["3.9"] 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Install dependencies 24 | run: | 25 | sudo apt install -y libopenblas-dev 26 | - name: Set up Python 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install tox 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install tox 34 | pip install extremal-python-dependencies==0.0.3 35 | extremal-python-dependencies pin-dependencies-to-minimum --inplace 36 | - name: Run tests 37 | run: | 38 | tox run -e py${{ matrix.python-version }} 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/api/generated/ 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 | 132 | # PyCharm 133 | .idea/ 134 | 135 | # VS Code 136 | .vscode/ 137 | 138 | # cibuildwheel 139 | wheelhouse/ 140 | 141 | # direnv 142 | .envrc 143 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | # Ruff version. 4 | rev: v0.9.9 5 | hooks: 6 | # Run the linter. 7 | - id: ruff 8 | types_or: [python, pyi, jupyter] 9 | args: [--fix] 10 | # Run the formatter. 11 | - id: ruff-format 12 | types_or: [python, pyi, jupyter] 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Developer guide 2 | 3 | The instructions on this page won't work natively on Windows. For ffsim development on Windows, we recommend using [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/) and doing development within the WSL. 4 | 5 | ## Setup 6 | 7 | To set up ffsim for development, install it from source in editable mode along with the development requirements: 8 | 9 | ```bash 10 | pip install -e ".[dev]" 11 | ``` 12 | 13 | To install the git pre-commit hooks, run 14 | 15 | ```bash 16 | pre-commit install 17 | ``` 18 | 19 | ## Rust 20 | 21 | If you add or modify any Rust modules, rebuild them by running the command 22 | 23 | ```bash 24 | maturin develop 25 | ``` 26 | 27 | If you are benchmarking the code, then pass the `--release` flag: 28 | 29 | ```bash 30 | maturin develop --release 31 | ``` 32 | 33 | ## Run code checks using tox 34 | 35 | You can run tests and other code checks using [tox](https://tox.wiki/en/latest/). 36 | To run all checks, simply run 37 | 38 | ```bash 39 | tox 40 | ``` 41 | 42 | To run a specific check, run 43 | 44 | ```bash 45 | tox run -e 46 | ``` 47 | 48 | substituting `` with the name of the tox environment for the check. The following environments are available: 49 | 50 | - `py38`, `py39`, `py310`, `py311`, `py312`: Run tests for a specific Python version 51 | - `coverage`: Code coverage 52 | - `type`: Type check 53 | - `lint`: Lint check 54 | - `format`: Format check 55 | - `docs`: Build documentation 56 | 57 | ## Run code checks directly 58 | 59 | Running the code checks directly using the corresponding software tool can be useful and allows you to: 60 | 61 | - Automatically fix lint and formatting errors. 62 | - Build the documentation without deleting cached files. 63 | 64 | ### Run tests 65 | 66 | ```bash 67 | pytest 68 | ``` 69 | 70 | ### Run type check 71 | 72 | ```bash 73 | mypy 74 | ``` 75 | 76 | ### Fix lint errors 77 | 78 | ```bash 79 | ruff check --fix 80 | ``` 81 | 82 | ### Fix formatting errors 83 | 84 | ```bash 85 | ruff format 86 | ``` 87 | 88 | ### Build documentation 89 | 90 | ```bash 91 | sphinx-build -b html -W docs/ docs/_build/html 92 | ``` 93 | 94 | ## View locally built documentation 95 | 96 | After building the docs using either the [tox command](#run-code-checks-using-tox) or the [sphinx command](#build-documentation), open the file `docs/_build/html/index.html` in your web browser. For rapid iterations, the sphinx command is preferred because it retains cached files. 97 | Building the documentation can consume significant CPU because the tutorial notebooks are executed. 98 | The tox command deletes cached files so it will execute all the notebooks every time, while the sphinx command only executes notebooks if they were modified from the previous run. 99 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ffsim" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [lib] 8 | name = "ffsim" 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | blas = "0.22" 13 | ndarray = { version = "0.15", features = ["rayon"] } 14 | blas-src = { version = "0.10" } 15 | numpy = "0.21" 16 | pyo3 = { version = "0.21", features = [ 17 | "extension-module", 18 | "num-complex", 19 | "abi3-py39", 20 | ] } 21 | num-integer = "0.1" 22 | 23 | [target.'cfg(target_os = "linux")'.dependencies] 24 | blas-src = { version = "0.10", features = ["openblas"] } 25 | openblas-src = { version = "0.10", features = ["system"] } 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/jupyter/minimal-notebook:python-3.11 2 | 3 | LABEL maintainer="Kevin J. Sung " 4 | 5 | # The base notebook sets up a `work` directory "for backwards 6 | # compatibility". We don't need it, so let's just remove it. 7 | RUN rm -rf work 8 | 9 | # Install apt dependencies 10 | USER root 11 | RUN apt update && apt install -y libssl-dev rustc cargo libopenblas-dev pkg-config 12 | USER ${NB_UID} 13 | 14 | # Copy files 15 | COPY . .src/ffsim 16 | 17 | # Fix the permissions of ~/.src and ~/persistent-volume 18 | USER root 19 | RUN fix-permissions .src && \ 20 | mkdir persistent-volume && fix-permissions persistent-volume 21 | USER ${NB_UID} 22 | 23 | # Consolidate the docs into the home directory 24 | RUN mkdir docs && \ 25 | cp -a .src/ffsim/docs docs/ffsim 26 | 27 | # Pip install ffsim 28 | RUN pip install -e ".src/ffsim[dev]" 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ffsim 2 | 3 | 4 | 5 | ffsim is a software library for simulating fermionic quantum circuits that conserve particle number and the Z component of spin. This category includes many quantum circuits used for quantum chemistry simulations. By exploiting the symmetries and using specialized algorithms, ffsim can simulate these circuits much faster than a generic quantum circuit simulator. 6 | 7 | 8 | 9 | ## Documentation 10 | 11 | Documentation is located at the [project website](https://qiskit-community.github.io/ffsim/). 12 | 13 | ## Installation 14 | 15 | 16 | 17 | We recommend installing ffsim using pip when possible: 18 | 19 | ```bash 20 | pip install ffsim 21 | ``` 22 | 23 | This method won't work natively on Windows, however. Refer to the [installation instructions](https://qiskit-community.github.io/ffsim/install.html) for information about using ffsim on Windows, as well as instructions for installing from source and running ffsim in a container. 24 | 25 | 26 | 27 | ## Code example 28 | 29 | 30 | 31 | ```python 32 | import numpy as np 33 | import pyscf 34 | 35 | import ffsim 36 | 37 | # Build an N2 molecule 38 | mol = pyscf.gto.Mole() 39 | mol.build(atom=[["N", (0, 0, 0)], ["N", (1.0, 0, 0)]], basis="6-31g", symmetry="Dooh") 40 | 41 | # Get molecular data 42 | scf = pyscf.scf.RHF(mol).run() 43 | mol_data = ffsim.MolecularData.from_scf(scf, active_space=range(4, mol.nao_nr())) 44 | norb, nelec = mol_data.norb, mol_data.nelec 45 | 46 | # Generate a random orbital rotation 47 | orbital_rotation = ffsim.random.random_unitary(norb, seed=1234) 48 | 49 | # Create the Hartree-Fock state and apply the orbital rotation to it 50 | vec = ffsim.hartree_fock_state(norb, nelec) 51 | vec = ffsim.apply_orbital_rotation(vec, orbital_rotation, norb=norb, nelec=nelec) 52 | 53 | # Convert the Hamiltonian to a Scipy LinearOperator 54 | linop = ffsim.linear_operator(mol_data.hamiltonian, norb=norb, nelec=nelec) 55 | 56 | # Compute the energy of the state 57 | energy = np.vdot(vec, linop @ vec).real 58 | print(energy) # prints -104.17181289596 59 | ``` 60 | 61 | 62 | 63 | ## Citing ffsim 64 | 65 | 66 | 67 | You can cite ffsim using the following BibTeX: 68 | 69 | ```bibtex 70 | @software{ffsim, 71 | author = {{The ffsim developers}}, 72 | title = {{ffsim: Faster simulations of fermionic quantum circuits.}}, 73 | url = {https://github.com/qiskit-community/ffsim} 74 | } 75 | ``` 76 | 77 | 78 | 79 | ## Developer guide 80 | 81 | See the [developer guide](https://github.com/qiskit-community/ffsim/blob/main/CONTRIBUTING.md) for instructions on contributing code to ffsim. 82 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | # This file exists so users can easily get started with ffsim 2 | # in a container using Docker Compose. 3 | 4 | services: 5 | notebook: 6 | build: . 7 | platform: linux/amd64 8 | restart: unless-stopped 9 | environment: 10 | JUPYTER_PORT: 58888 11 | NOTEBOOK_ARGS: "--ServerApp.token=''" 12 | ports: 13 | - 58888:58888 14 | volumes: 15 | - ffsim:/home/jovyan/persistent-volume 16 | 17 | volumes: 18 | ffsim: 19 | -------------------------------------------------------------------------------- /docs/api/ffsim.contract.rst: -------------------------------------------------------------------------------- 1 | ffsim.contract 2 | ============== 3 | 4 | .. automodule:: ffsim.contract 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/api/ffsim.linalg.rst: -------------------------------------------------------------------------------- 1 | ffsim.linalg 2 | ============ 3 | 4 | .. automodule:: ffsim.linalg 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/api/ffsim.optimize.rst: -------------------------------------------------------------------------------- 1 | ffsim.optimize 2 | ============== 3 | 4 | .. automodule:: ffsim.optimize 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/api/ffsim.qiskit.rst: -------------------------------------------------------------------------------- 1 | ffsim.qiskit 2 | ============ 3 | 4 | .. automodule:: ffsim.qiskit 5 | :members: 6 | :special-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/api/ffsim.random.rst: -------------------------------------------------------------------------------- 1 | ffsim.random 2 | ============ 3 | 4 | .. automodule:: ffsim.random 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/api/ffsim.rst: -------------------------------------------------------------------------------- 1 | ffsim 2 | ===== 3 | 4 | .. automodule:: ffsim 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/api/ffsim.testing.rst: -------------------------------------------------------------------------------- 1 | ffsim.testing 2 | ============= 3 | 4 | .. automodule:: ffsim.testing 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # API reference 2 | 3 | ```{toctree} 4 | :maxdepth: 2 5 | 6 | ffsim 7 | ffsim.contract 8 | ffsim.linalg 9 | ffsim.optimize 10 | ffsim.qiskit 11 | ffsim.random 12 | ffsim.testing 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Sphinx configuration.""" 12 | 13 | import importlib.metadata 14 | 15 | project = "ffsim" 16 | copyright = "2023, IBM" 17 | author = "Kevin J. Sung" 18 | release = importlib.metadata.version("ffsim") 19 | 20 | extensions = [ 21 | "sphinx.ext.napoleon", 22 | "sphinx.ext.autodoc", 23 | "sphinx.ext.autosummary", 24 | "sphinx.ext.mathjax", 25 | "sphinx.ext.viewcode", 26 | "sphinx.ext.extlinks", 27 | "sphinx_autodoc_typehints", 28 | "myst_parser", 29 | "nbsphinx", 30 | "qiskit_sphinx_theme", 31 | ] 32 | 33 | exclude_patterns = ["_build", "**.ipynb_checkpoints"] 34 | 35 | # HTML output options 36 | html_theme = "qiskit-ecosystem" 37 | html_title = f"{project} {release}" 38 | html_theme_options = { 39 | "source_repository": "https://github.com/qiskit-community/ffsim/", 40 | "source_branch": "main", 41 | "source_directory": "docs/", 42 | "sidebar_qiskit_ecosystem_member": True, 43 | } 44 | 45 | # nbsphinx options (for tutorials) 46 | nbsphinx_timeout = 300 47 | nbsphinx_execute = "always" 48 | -------------------------------------------------------------------------------- /docs/explanations/index.md: -------------------------------------------------------------------------------- 1 | # Explanations 2 | 3 | ```{toctree} 4 | :maxdepth: 1 5 | 6 | state-vectors-and-gates 7 | hamiltonians 8 | orbital-rotation 9 | double-factorized 10 | lucj 11 | qiskit-gate-decompositions 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/how-to-guides/index.md: -------------------------------------------------------------------------------- 1 | # How-to guides 2 | 3 | ```{toctree} 4 | :maxdepth: 1 5 | 6 | simulate-lucj 7 | entanglement-forging 8 | fermion-operator 9 | qiskit-lucj 10 | qiskit-circuits-sim 11 | qiskit-merge-orbital-rotations 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # ffsim 2 | 3 | ```{include} ../README.md 4 | :start-after: 5 | :end-before: 6 | ``` 7 | 8 | ## Installation 9 | 10 | ```{include} ../README.md 11 | :start-after: 12 | :end-before: 13 | ``` 14 | 15 | ## Code example 16 | 17 | ```{include} ../README.md 18 | :start-after: 19 | :end-before: 20 | ``` 21 | 22 | ## Citing ffsim 23 | 24 | ```{include} ../README.md 25 | :start-after: 26 | :end-before: 27 | ``` 28 | 29 | ## Contents 30 | 31 | ```{toctree} 32 | :maxdepth: 2 33 | 34 | install 35 | tutorials/index 36 | explanations/index 37 | how-to-guides/index 38 | api/index 39 | GitHub 40 | Developer guide 41 | Release notes 42 | Development branch docs 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ffsim is supported directly on Linux and macOS. 4 | 5 | ffsim is not supported directly on Windows. Windows users have two main options: 6 | 7 | - Use [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/). WSL provides a Linux environment where ffsim can be pip installed from PyPI or from source. 8 | - Use ffsim within Docker. See [Use within Docker](#use-within-docker). 9 | 10 | ## Pip install 11 | 12 | ffsim is available on [PyPI](https://pypi.org/project/ffsim/). It can be installed by running 13 | 14 | ```bash 15 | pip install ffsim 16 | ``` 17 | 18 | ## Install from source 19 | 20 | Installing ffsim from source requires the following system dependencies: 21 | 22 | - A Rust compiler. See [these instructions](https://www.rust-lang.org/tools/install). 23 | - A BLAS implementation. 24 | - On macOS, ffsim uses the [Accelerate](https://developer.apple.com/documentation/accelerate) framework that is included with the operating system, so no action is required. 25 | - On Linux, ffsim uses [OpenBLAS](https://www.openblas.net/). You may be able to install it using your system package manager: 26 | - Arch Linux: 27 | 28 | ```bash 29 | sudo pacman -S blas-openblas 30 | ``` 31 | 32 | - Fedora: 33 | 34 | ```bash 35 | sudo dnf install openblas-devel 36 | ``` 37 | 38 | - Ubuntu: 39 | 40 | ```bash 41 | sudo apt install libopenblas-dev 42 | ``` 43 | 44 | Once these dependencies are satisfied, ffsim can be installed by cloning the repository and then using pip to install the package from source. For example: 45 | 46 | ```bash 47 | git clone https://github.com/qiskit-community/ffsim.git 48 | cd ffsim 49 | pip install . 50 | ``` 51 | 52 | ## Use within Docker 53 | 54 | We have provided a [Dockerfile](https://github.com/qiskit-community/ffsim/blob/main/Dockerfile), which can be used to build a [Docker](https://www.docker.com/) image, as well as a [compose.yaml](https://github.com/qiskit-community/ffsim/blob/main/compose.yaml) file, which allows one to use the Docker image with just a few simple commands: 55 | 56 | ```bash 57 | git clone https://github.com/qiskit-community/ffsim.git 58 | cd ffsim 59 | docker compose build 60 | docker compose up 61 | ``` 62 | 63 | Depending on your system configuration, you may need to type `sudo` before each `docker` command. 64 | 65 | Once the container is running, navigate to in a web browser to access the Jupyter Notebook interface. 66 | 67 | The home directory includes a subdirectory named `persistent-volume`. All work you’d like to save should be placed in this directory, as it is the only one that will be saved across different container runs. 68 | -------------------------------------------------------------------------------- /docs/tutorials/index.md: -------------------------------------------------------------------------------- 1 | # Tutorials 2 | 3 | ```{toctree} 4 | :maxdepth: 1 5 | 6 | double-factorized-trotter 7 | ``` 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.0,<2.0"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "ffsim" 7 | requires-python = ">=3.9" 8 | version = "0.0.58.dev" 9 | description = "Faster simulations of fermionic quantum circuits." 10 | readme = "README.md" 11 | license = { file = "LICENSE" } 12 | classifiers = [ 13 | "License :: OSI Approved :: Apache Software License", 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3 :: Only", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "Programming Language :: Rust", 22 | "Topic :: Scientific/Engineering :: Chemistry", 23 | "Topic :: Scientific/Engineering :: Physics", 24 | "Topic :: Software Development :: Libraries :: Python Modules", 25 | ] 26 | dependencies = [ 27 | "numpy", 28 | "opt_einsum", 29 | "orjson", 30 | "pyscf >= 2.9", 31 | "qiskit >= 1.3", 32 | "scipy", 33 | "typing-extensions", 34 | ] 35 | 36 | [project.urls] 37 | Homepage = "https://github.com/qiskit-community/ffsim" 38 | Documentation = "https://qiskit-community.github.io/ffsim/" 39 | 40 | [project.optional-dependencies] 41 | dev = [ 42 | "coverage", 43 | "maturin", 44 | "mypy", 45 | "myst-parser", 46 | "nbmake", 47 | "nbsphinx", 48 | "pre-commit", 49 | "pytest", 50 | "qiskit[visualization]", 51 | "qiskit-sphinx-theme", 52 | "ruff == 0.9.9", 53 | "sphinx", 54 | "sphinx-autodoc-typehints", 55 | ] 56 | 57 | [tool.maturin] 58 | features = ["pyo3/extension-module"] 59 | python-source = "python" 60 | module-name = "ffsim._lib" 61 | 62 | [tool.pytest.ini_options] 63 | testpaths = ["tests"] 64 | 65 | [tool.mypy] 66 | check_untyped_defs = true 67 | ignore_missing_imports = true 68 | files = ["python/**/*.py", "python/**/*.pyi", "tests/**/*.py", "docs/**/*.py"] 69 | 70 | [tool.ruff] 71 | src = ["python"] 72 | include = [ 73 | "pyproject.toml", 74 | "python/**/*.py", 75 | "python/**/*.pyi", 76 | "tests/**/*.py", 77 | "tests/**/*.pyi", 78 | "docs/**/*.py", 79 | "docs/**/*.pyi", 80 | "docs/**/*.ipynb", 81 | ] 82 | 83 | [tool.ruff.lint] 84 | select = ["E", "F", "I", "N", "NPY", "NPY201"] 85 | 86 | [tool.cibuildwheel] 87 | build = "cp39-macosx* cp39-manylinux_x86_64 cp39-manylinux_aarch64" 88 | test-requires = "pytest" 89 | test-command = "pytest {project}/tests" 90 | 91 | [tool.cibuildwheel.linux] 92 | # mirrorlist.centos.org doesn't exist anymore 93 | # Workaround from https://serverfault.com/a/1161847 94 | before-all = "sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo && sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo && yum install -y openssl-devel rust cargo openblas-devel" 95 | -------------------------------------------------------------------------------- /python/ffsim/_lib.pyi: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterator, MutableMapping 2 | 3 | import numpy as np 4 | 5 | class FermionOperator(MutableMapping[tuple[tuple[bool, bool, int], ...], complex]): 6 | def __init__( 7 | self, coeffs: dict[tuple[tuple[bool, bool, int], ...], complex] 8 | ) -> None: ... 9 | def normal_ordered(self) -> "FermionOperator": ... 10 | def conserves_particle_number(self) -> bool: ... 11 | def conserves_spin_z(self) -> bool: ... 12 | def many_body_order(self) -> int: ... 13 | def simplify(self, tol: float = 1e-8) -> None: ... 14 | def copy(self) -> FermionOperator: ... 15 | def __getitem__(self, key: tuple[tuple[bool, bool, int], ...]) -> complex: ... 16 | def __setitem__(self, key: tuple[tuple[bool, bool, int], ...], complex) -> None: ... 17 | def __delitem__(self, key: tuple[tuple[bool, bool, int], ...]) -> None: ... 18 | def __contains__(self, key: object) -> bool: ... 19 | def __iter__(self) -> Iterator[tuple[tuple[bool, bool, int], ...]]: ... 20 | def __len__(self) -> int: ... 21 | def __iadd__(self, other: FermionOperator) -> FermionOperator: ... 22 | def __add__(self, other: FermionOperator) -> FermionOperator: ... 23 | def __isub__(self, other: FermionOperator) -> FermionOperator: ... 24 | def __sub__(self, other: FermionOperator) -> FermionOperator: ... 25 | def __neg__(self) -> FermionOperator: ... 26 | def __itruediv__(self, other: int | float | complex) -> FermionOperator: ... 27 | def __truediv__(self, other: int | float | complex) -> FermionOperator: ... 28 | def __rmul__(self, other: int | float | complex) -> FermionOperator: ... 29 | def __imul__( 30 | self, other: int | float | complex | FermionOperator 31 | ) -> FermionOperator: ... 32 | def __mul__(self, other: FermionOperator) -> FermionOperator: ... 33 | def __pow__(self, other: int) -> FermionOperator: ... 34 | 35 | def apply_phase_shift_in_place( 36 | vec: np.ndarray, phase: complex, indices: np.ndarray 37 | ) -> None: ... 38 | def apply_givens_rotation_in_place( 39 | vec: np.ndarray, c: float, s: complex, slice1: np.ndarray, slice2: np.ndarray 40 | ) -> None: ... 41 | def apply_num_op_sum_evolution_in_place( 42 | vec: np.ndarray, 43 | phases: np.ndarray, 44 | occupations: np.ndarray, 45 | ) -> None: ... 46 | def apply_diag_coulomb_evolution_in_place_num_rep( 47 | vec: np.ndarray, 48 | mat_exp_aa: np.ndarray, 49 | mat_exp_ab: np.ndarray, 50 | mat_exp_bb: np.ndarray, 51 | norb: int, 52 | occupations_a: np.ndarray, 53 | occupations_b: np.ndarray, 54 | ) -> None: ... 55 | def apply_diag_coulomb_evolution_in_place_z_rep( 56 | vec: np.ndarray, 57 | mat_exp_aa: np.ndarray, 58 | mat_exp_ab: np.ndarray, 59 | mat_exp_bb: np.ndarray, 60 | mat_exp_aa_conj: np.ndarray, 61 | mat_exp_ab_conj: np.ndarray, 62 | mat_exp_bb_conj: np.ndarray, 63 | norb: int, 64 | strings_a: np.ndarray, 65 | strings_b: np.ndarray, 66 | ) -> None: ... 67 | def contract_diag_coulomb_into_buffer_num_rep( 68 | vec: np.ndarray, 69 | mat_aa: np.ndarray, 70 | mat_ab: np.ndarray, 71 | mat_bb: np.ndarray, 72 | norb: int, 73 | occupations_a: np.ndarray, 74 | occupations_b: np.ndarray, 75 | out: np.ndarray, 76 | ) -> None: ... 77 | def contract_diag_coulomb_into_buffer_z_rep( 78 | vec: np.ndarray, 79 | mat_aa: np.ndarray, 80 | mat_ab: np.ndarray, 81 | mat_bb: np.ndarray, 82 | norb: int, 83 | strings_a: np.ndarray, 84 | strings_b: np.ndarray, 85 | out: np.ndarray, 86 | ) -> None: ... 87 | def contract_num_op_sum_spin_into_buffer( 88 | vec: np.ndarray, 89 | coeffs: np.ndarray, 90 | occupations: np.ndarray, 91 | out: np.ndarray, 92 | ) -> None: ... 93 | -------------------------------------------------------------------------------- /python/ffsim/_slow/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Python versions of functions that were rewritten in Rust.""" 12 | -------------------------------------------------------------------------------- /python/ffsim/_slow/contract/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /python/ffsim/_slow/contract/diag_coulomb.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | 12 | from __future__ import annotations 13 | 14 | import itertools 15 | 16 | import numpy as np 17 | 18 | 19 | def contract_diag_coulomb_into_buffer_num_rep_slow( 20 | vec: np.ndarray, 21 | mat_aa: np.ndarray, 22 | mat_ab: np.ndarray, 23 | mat_bb: np.ndarray, 24 | norb: int, 25 | occupations_a: np.ndarray, 26 | occupations_b: np.ndarray, 27 | out: np.ndarray, 28 | ) -> None: 29 | dim_a, dim_b = vec.shape 30 | alpha_coeffs = np.zeros((dim_a,), dtype=complex) 31 | beta_coeffs = np.zeros((dim_b,), dtype=complex) 32 | coeff_map = np.zeros((dim_a, norb), dtype=complex) 33 | 34 | for i, occ in enumerate(occupations_b): 35 | coeff = 0 36 | for orb_1, orb_2 in itertools.combinations_with_replacement(occ, 2): 37 | coeff += mat_bb[orb_1, orb_2] 38 | beta_coeffs[i] = coeff 39 | 40 | for i, (row, orbs) in enumerate(zip(coeff_map, occupations_a)): 41 | coeff = 0 42 | for j in range(len(orbs)): 43 | row += mat_ab[orbs[j]] 44 | for k in range(j, len(orbs)): 45 | coeff += mat_aa[orbs[j], orbs[k]] 46 | alpha_coeffs[i] = coeff 47 | 48 | for source, target, alpha_coeff, coeff_map in zip( 49 | vec, out, alpha_coeffs, coeff_map 50 | ): 51 | for j, occ_b in enumerate(occupations_b): 52 | coeff = alpha_coeff + beta_coeffs[j] 53 | for orb_b in occ_b: 54 | coeff += coeff_map[orb_b] 55 | target[j] += coeff * source[j] 56 | 57 | 58 | def contract_diag_coulomb_into_buffer_z_rep_slow( 59 | vec: np.ndarray, 60 | mat_aa: np.ndarray, 61 | mat_ab: np.ndarray, 62 | mat_bb: np.ndarray, 63 | norb: int, 64 | strings_a: np.ndarray, 65 | strings_b: np.ndarray, 66 | out: np.ndarray, 67 | ) -> None: 68 | dim_a, dim_b = vec.shape 69 | alpha_coeffs = np.zeros((dim_a,), dtype=complex) 70 | beta_coeffs = np.zeros((dim_b,), dtype=complex) 71 | coeff_map = np.zeros((dim_a, norb), dtype=complex) 72 | 73 | for i, str0 in enumerate(strings_b): 74 | coeff = 0 75 | for j in range(norb): 76 | sign_j = -1 if str0 >> j & 1 else 1 77 | for k in range(j + 1, norb): 78 | sign_k = -1 if str0 >> k & 1 else 1 79 | coeff += sign_j * sign_k * mat_bb[j, k] 80 | beta_coeffs[i] = coeff 81 | 82 | for i, (row, str0) in enumerate(zip(coeff_map, strings_a)): 83 | coeff = 0 84 | for j in range(norb): 85 | sign_j = -1 if str0 >> j & 1 else 1 86 | row += sign_j * mat_ab[j] 87 | for k in range(j + 1, norb): 88 | sign_k = -1 if str0 >> k & 1 else 1 89 | coeff += sign_j * sign_k * mat_aa[j, k] 90 | alpha_coeffs[i] = coeff 91 | 92 | for source, target, alpha_coeff, coeff_map in zip( 93 | vec, out, alpha_coeffs, coeff_map 94 | ): 95 | for i, str0 in enumerate(strings_b): 96 | coeff = alpha_coeff + beta_coeffs[i] 97 | for j in range(norb): 98 | sign_j = -1 if str0 >> j & 1 else 1 99 | coeff += sign_j * coeff_map[j] 100 | target[i] += 0.25 * coeff * source[i] 101 | -------------------------------------------------------------------------------- /python/ffsim/_slow/contract/num_op_sum.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | 12 | from __future__ import annotations 13 | 14 | import numpy as np 15 | 16 | 17 | def contract_num_op_sum_spin_into_buffer_slow( 18 | vec: np.ndarray, coeffs: np.ndarray, occupations: np.ndarray, out: np.ndarray 19 | ) -> None: 20 | for source_row, target_row, orbs in zip(vec, out, occupations): 21 | coeff = 0 22 | for orb in orbs: 23 | coeff += coeffs[orb] 24 | target_row += coeff * source_row 25 | -------------------------------------------------------------------------------- /python/ffsim/_slow/gates/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /python/ffsim/_slow/gates/num_op_sum.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | from __future__ import annotations 12 | 13 | import numpy as np 14 | 15 | 16 | def apply_num_op_sum_evolution_in_place_slow( 17 | vec: np.ndarray, phases: np.ndarray, occupations: np.ndarray 18 | ) -> None: 19 | """Apply time evolution by a sum of number operators in-place.""" 20 | for row, orbs in zip(vec, occupations): 21 | phase = 1 22 | for orb in orbs: 23 | phase *= phases[orb] 24 | row *= phase 25 | -------------------------------------------------------------------------------- /python/ffsim/_slow/gates/orbital_rotation.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | from __future__ import annotations 12 | 13 | import numpy as np 14 | from scipy.linalg.lapack import zrot 15 | 16 | 17 | def apply_givens_rotation_in_place_slow( 18 | vec: np.ndarray, 19 | c: float, 20 | s: complex, 21 | slice1: np.ndarray, 22 | slice2: np.ndarray, 23 | ) -> None: 24 | """Apply a Givens rotation to slices of a state vector.""" 25 | for i, j in zip(slice1, slice2): 26 | vec[i], vec[j] = zrot(vec[i], vec[j], c, s) 27 | -------------------------------------------------------------------------------- /python/ffsim/cistring.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tools for handling FCI strings.""" 12 | 13 | from __future__ import annotations 14 | 15 | from functools import cache 16 | 17 | import numpy as np 18 | from pyscf.fci import cistring 19 | 20 | 21 | @cache 22 | def make_strings(orbitals: range, nocc: int) -> np.ndarray: 23 | """Cached version of pyscf.fci.cistring.make_strings.""" 24 | return cistring.make_strings(orbitals, nocc) 25 | 26 | 27 | @cache 28 | def gen_occslst(orbitals: range, nocc: int) -> np.ndarray: 29 | """Cached version of pyscf.fci.cistring.gen_occslst.""" 30 | return cistring.gen_occslst(orbitals, nocc).astype(np.uint, copy=False) 31 | 32 | 33 | @cache 34 | def gen_linkstr_index(orbitals: range, nocc: int) -> np.ndarray: 35 | """Cached version of pyscf.fci.cistring.gen_linkstr_index.""" 36 | return cistring.gen_linkstr_index(orbitals, nocc) 37 | 38 | 39 | def init_cache(norb: int, nelec: tuple[int, int]) -> None: 40 | """Initialize cached objects. 41 | 42 | Call this function to prepare ffsim for performing operations with given values 43 | of `norb` and `nelec`. Typically there is no need to call this function, but it 44 | should be called before benchmarking to avoid counting the cost of initializing 45 | cached lookup tables. 46 | 47 | Args: 48 | norb: The number of spatial orbitals. 49 | nelec: The number of alpha and beta electrons. 50 | """ 51 | for nocc in nelec: 52 | make_strings(range(norb), nocc) 53 | gen_occslst(range(norb), nocc) 54 | gen_linkstr_index(range(norb), nocc) 55 | -------------------------------------------------------------------------------- /python/ffsim/contract/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Functions for contracting tensors and constructing linear operators.""" 12 | 13 | from ffsim.contract.diag_coulomb import contract_diag_coulomb, diag_coulomb_linop 14 | from ffsim.contract.num_op_sum import contract_num_op_sum, num_op_sum_linop 15 | from ffsim.contract.one_body import contract_one_body, one_body_linop 16 | 17 | __all__ = [ 18 | "contract_diag_coulomb", 19 | "contract_num_op_sum", 20 | "contract_one_body", 21 | "diag_coulomb_linop", 22 | "num_op_sum_linop", 23 | "one_body_linop", 24 | ] 25 | -------------------------------------------------------------------------------- /python/ffsim/contract/num_op_sum.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Contract linear combination of number operators.""" 12 | 13 | from __future__ import annotations 14 | 15 | import math 16 | 17 | import numpy as np 18 | import scipy.sparse.linalg 19 | 20 | from ffsim._lib import ( 21 | contract_num_op_sum_spin_into_buffer, 22 | ) 23 | from ffsim.cistring import gen_occslst 24 | from ffsim.gates.orbital_rotation import apply_orbital_rotation 25 | from ffsim.states import dim 26 | 27 | 28 | def contract_num_op_sum( 29 | vec: np.ndarray, coeffs: np.ndarray, norb: int, nelec: tuple[int, int] 30 | ): 31 | r"""Contract a linear combination of number operators with a vector. 32 | 33 | A linear combination of number operators has the form 34 | 35 | .. math:: 36 | 37 | \sum_{\sigma, i} \lambda_i n_{\sigma, i} 38 | 39 | where :math:`n_{\sigma, i}` denotes the number operator on orbital :math:`i` 40 | with spin :math:`\sigma` and the :math:`\lambda_i` are real numbers. 41 | 42 | Args: 43 | vec: The state vector to be transformed. 44 | coeffs: The coefficients of the linear combination. 45 | norb: The number of spatial orbitals. 46 | nelec: The number of alpha and beta electrons. 47 | 48 | Returns: 49 | The result of applying the linear combination of number operators on the input 50 | state vector. 51 | """ 52 | vec = vec.astype(complex, copy=False) 53 | n_alpha, n_beta = nelec 54 | 55 | occupations_a = gen_occslst(range(norb), n_alpha) 56 | occupations_b = gen_occslst(range(norb), n_beta) 57 | 58 | dim_a = math.comb(norb, n_alpha) 59 | dim_b = math.comb(norb, n_beta) 60 | vec = vec.reshape((dim_a, dim_b)) 61 | out = np.zeros_like(vec) 62 | # apply alpha 63 | contract_num_op_sum_spin_into_buffer( 64 | vec, coeffs, occupations=occupations_a, out=out 65 | ) 66 | # apply beta 67 | vec = vec.T 68 | out = out.T 69 | contract_num_op_sum_spin_into_buffer( 70 | vec, coeffs, occupations=occupations_b, out=out 71 | ) 72 | 73 | return out.T.reshape(-1) 74 | 75 | 76 | def num_op_sum_linop( 77 | coeffs: np.ndarray, 78 | norb: int, 79 | nelec: tuple[int, int], 80 | *, 81 | orbital_rotation: np.ndarray | None = None, 82 | ) -> scipy.sparse.linalg.LinearOperator: 83 | r"""Convert a (rotated) linear combination of number operators to a linear operator. 84 | 85 | A rotated linear combination of number operators has the form 86 | 87 | .. math:: 88 | 89 | \mathcal{U} 90 | (\sum_{\sigma, i} \lambda_i n_{\sigma, i}) 91 | \mathcal{U}^\dagger 92 | 93 | where :math:`n_{\sigma, i}` denotes the number operator on orbital :math:`i` 94 | with spin :math:`\sigma`, the :math:`\lambda_i` are real numbers, and 95 | :math:`\mathcal{U}` is an optional orbital rotation. 96 | 97 | Args: 98 | coeffs: The coefficients of the linear combination. 99 | norb: The number of spatial orbitals. 100 | nelec: The number of alpha and beta electrons. 101 | orbital_rotation: A unitary matrix describing the optional orbital rotation. 102 | 103 | Returns: 104 | A LinearOperator that implements the action of the linear combination of number 105 | operators. 106 | """ 107 | dim_ = dim(norb, nelec) 108 | 109 | def matvec(vec): 110 | these_coeffs = coeffs 111 | if orbital_rotation is not None: 112 | vec = apply_orbital_rotation( 113 | vec, 114 | orbital_rotation.T.conj(), 115 | norb, 116 | nelec, 117 | ) 118 | vec = contract_num_op_sum(vec, these_coeffs, norb=norb, nelec=nelec) 119 | if orbital_rotation is not None: 120 | vec = apply_orbital_rotation( 121 | vec, 122 | orbital_rotation, 123 | norb, 124 | nelec, 125 | copy=False, 126 | ) 127 | return vec 128 | 129 | return scipy.sparse.linalg.LinearOperator( 130 | (dim_, dim_), matvec=matvec, rmatvec=matvec, dtype=complex 131 | ) 132 | -------------------------------------------------------------------------------- /python/ffsim/contract/one_body.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Contract one-body operator.""" 12 | 13 | from __future__ import annotations 14 | 15 | import numpy as np 16 | import scipy.sparse.linalg 17 | from pyscf.fci.direct_nosym import contract_1e 18 | 19 | from ffsim.cistring import gen_linkstr_index 20 | from ffsim.states import dim 21 | 22 | 23 | def contract_one_body( 24 | vec: np.ndarray, mat: np.ndarray, norb: int, nelec: tuple[int, int] 25 | ) -> np.ndarray: 26 | r"""Contract a one-body tensor with a vector. 27 | 28 | A one-body tensor has the form 29 | 30 | .. math:: 31 | 32 | \sum_{ij} M_{ij} a^\dagger_i a_j 33 | 34 | where :math:`M` is a complex-valued matrix. 35 | 36 | Args: 37 | mat: The one-body tensor. 38 | norb: The number of spatial orbitals. 39 | nelec: The number of alpha and beta electrons. 40 | 41 | Returns: 42 | A LinearOperator that implements the action of the one-body tensor. 43 | """ 44 | n_alpha, n_beta = nelec 45 | link_index_a = gen_linkstr_index(range(norb), n_alpha) 46 | link_index_b = gen_linkstr_index(range(norb), n_beta) 47 | link_index = (link_index_a, link_index_b) 48 | result = contract_1e(mat.real, vec.real, norb, nelec, link_index=link_index).astype( 49 | complex 50 | ) 51 | result += 1j * contract_1e(mat.imag, vec.real, norb, nelec, link_index=link_index) 52 | result += 1j * contract_1e(mat.real, vec.imag, norb, nelec, link_index=link_index) 53 | result -= contract_1e(mat.imag, vec.imag, norb, nelec, link_index=link_index) 54 | return result 55 | 56 | 57 | def one_body_linop( 58 | mat: np.ndarray, norb: int, nelec: tuple[int, int] 59 | ) -> scipy.sparse.linalg.LinearOperator: 60 | r"""Convert a one-body tensor to a linear operator. 61 | 62 | A one-body tensor has the form 63 | 64 | .. math:: 65 | 66 | \sum_{ij} M_{ij} a^\dagger_i a_j 67 | 68 | where :math:`M` is a complex-valued matrix. 69 | 70 | Args: 71 | mat: The one-body tensor. 72 | norb: The number of spatial orbitals. 73 | nelec: The number of alpha and beta electrons. 74 | 75 | Returns: 76 | A LinearOperator that implements the action of the one-body tensor. 77 | """ 78 | dim_ = dim(norb, nelec) 79 | 80 | def matvec(vec: np.ndarray): 81 | return contract_one_body(vec, mat, norb=norb, nelec=nelec) 82 | 83 | def rmatvec(vec: np.ndarray): 84 | return contract_one_body(vec, mat.T.conj(), norb=norb, nelec=nelec) 85 | 86 | return scipy.sparse.linalg.LinearOperator( 87 | shape=(dim_, dim_), matvec=matvec, rmatvec=rmatvec, dtype=complex 88 | ) 89 | -------------------------------------------------------------------------------- /python/ffsim/gates/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Fermionic quantum computation gates.""" 12 | 13 | from ffsim.gates.basic_gates import ( 14 | apply_fsim_gate, 15 | apply_fswap_gate, 16 | apply_givens_rotation, 17 | apply_hop_gate, 18 | apply_num_interaction, 19 | apply_num_num_interaction, 20 | apply_num_op_prod_interaction, 21 | apply_on_site_interaction, 22 | apply_tunneling_interaction, 23 | ) 24 | from ffsim.gates.diag_coulomb import apply_diag_coulomb_evolution 25 | from ffsim.gates.num_op_sum import apply_num_op_sum_evolution 26 | from ffsim.gates.orbital_rotation import apply_orbital_rotation 27 | 28 | __all__ = [ 29 | "apply_diag_coulomb_evolution", 30 | "apply_fsim_gate", 31 | "apply_fswap_gate", 32 | "apply_givens_rotation", 33 | "apply_hop_gate", 34 | "apply_num_interaction", 35 | "apply_num_num_interaction", 36 | "apply_num_op_prod_interaction", 37 | "apply_num_op_sum_evolution", 38 | "apply_on_site_interaction", 39 | "apply_orbital_rotation", 40 | "apply_tunneling_interaction", 41 | ] 42 | -------------------------------------------------------------------------------- /python/ffsim/hamiltonians/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Classes for representing Hamiltonians.""" 12 | 13 | from ffsim.hamiltonians.diagonal_coulomb_hamiltonian import DiagonalCoulombHamiltonian 14 | from ffsim.hamiltonians.double_factorized_hamiltonian import DoubleFactorizedHamiltonian 15 | from ffsim.hamiltonians.molecular_hamiltonian import ( 16 | MolecularHamiltonian, 17 | MolecularHamiltonianSpinless, 18 | ) 19 | from ffsim.hamiltonians.single_factorized_hamiltonian import SingleFactorizedHamiltonian 20 | 21 | __all__ = [ 22 | "DiagonalCoulombHamiltonian", 23 | "DoubleFactorizedHamiltonian", 24 | "MolecularHamiltonian", 25 | "MolecularHamiltonianSpinless", 26 | "SingleFactorizedHamiltonian", 27 | ] 28 | -------------------------------------------------------------------------------- /python/ffsim/linalg/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Linear algebra utilities.""" 12 | 13 | from ffsim.linalg.double_factorized_decomposition import ( 14 | double_factorized, 15 | double_factorized_t2, 16 | double_factorized_t2_alpha_beta, 17 | modified_cholesky, 18 | ) 19 | from ffsim.linalg.givens import ( 20 | GivensRotation, 21 | apply_matrix_to_slices, 22 | givens_decomposition, 23 | ) 24 | from ffsim.linalg.linalg import ( 25 | expm_multiply_taylor, 26 | lup, 27 | match_global_phase, 28 | one_hot, 29 | reduced_matrix, 30 | ) 31 | from ffsim.linalg.predicates import ( 32 | is_antihermitian, 33 | is_hermitian, 34 | is_orthogonal, 35 | is_real_symmetric, 36 | is_special_orthogonal, 37 | is_unitary, 38 | ) 39 | 40 | __all__ = [ 41 | "GivensRotation", 42 | "apply_matrix_to_slices", 43 | "double_factorized", 44 | "double_factorized_t2", 45 | "double_factorized_t2_alpha_beta", 46 | "expm_multiply_taylor", 47 | "givens_decomposition", 48 | "is_antihermitian", 49 | "is_hermitian", 50 | "is_orthogonal", 51 | "is_real_symmetric", 52 | "is_special_orthogonal", 53 | "is_unitary", 54 | "lup", 55 | "match_global_phase", 56 | "modified_cholesky", 57 | "one_hot", 58 | "reduced_matrix", 59 | ] 60 | -------------------------------------------------------------------------------- /python/ffsim/linalg/linalg.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Linear algebra utilities.""" 12 | 13 | from __future__ import annotations 14 | 15 | import cmath 16 | from collections.abc import Sequence 17 | 18 | import numpy as np 19 | import scipy.sparse.linalg 20 | 21 | 22 | def expm_multiply_taylor( 23 | mat: scipy.sparse.linalg.LinearOperator, vec: np.ndarray, tol: float = 1e-12 24 | ) -> np.ndarray: 25 | """Compute expm(mat) @ vec using a Taylor series expansion.""" 26 | result = vec.copy() 27 | term = vec 28 | denominator = 1 29 | while np.linalg.norm(term) > tol: 30 | term = mat @ term / denominator 31 | result += term 32 | denominator += 1 33 | return result 34 | 35 | 36 | def lup(mat: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]: 37 | """Column-pivoted LU decomposition of a matrix. 38 | 39 | The decomposition is: 40 | 41 | .. math:: 42 | 43 | A = L U P 44 | 45 | where L is a lower triangular matrix with unit diagonal elements, 46 | U is upper triangular, and P is a permutation matrix. 47 | """ 48 | p, ell, u = scipy.linalg.lu(mat.T) 49 | d = np.diagonal(u) 50 | ell *= d 51 | u /= d[:, None] 52 | return u.T, ell.T, p.T 53 | 54 | 55 | def reduced_matrix( 56 | mat: scipy.sparse.linalg.LinearOperator, vecs: Sequence[np.ndarray] 57 | ) -> np.ndarray: 58 | r"""Compute reduced matrix within a subspace spanned by some vectors. 59 | 60 | Given a linear operator :math:`A` and a list of vectors :math:`\{v_i\}`, 61 | return the matrix M where :math:`M_{ij} = v_i^\dagger A v_j`. 62 | """ 63 | dim = len(vecs) 64 | result = np.zeros((dim, dim), dtype=complex) 65 | for j, state_j in enumerate(vecs): 66 | mat_state_j = mat @ state_j 67 | for i, state_i in enumerate(vecs): 68 | result[i, j] = np.vdot(state_i, mat_state_j) 69 | return result 70 | 71 | 72 | def match_global_phase(a: np.ndarray, b: np.ndarray) -> tuple[np.ndarray, np.ndarray]: 73 | """Phase the given arrays so that their phases match at one entry. 74 | 75 | Args: 76 | a: A Numpy array. 77 | b: Another Numpy array. 78 | 79 | Returns: 80 | A pair of arrays (a', b') that are equal if and only if a == b * exp(i phi) 81 | for some real number phi. 82 | """ 83 | if a.shape != b.shape: 84 | return a, b 85 | # use the largest entry of one of the matrices to maximize precision 86 | index = max(np.ndindex(*a.shape), key=lambda i: abs(b[i])) 87 | phase_a = cmath.phase(a[index]) 88 | phase_b = cmath.phase(b[index]) 89 | return a * cmath.rect(1, -phase_a), b * cmath.rect(1, -phase_b) 90 | 91 | 92 | def one_hot(shape: int | tuple[int, ...], index, *, dtype=complex): 93 | """Return an array of all zeros except for a one at a specified index. 94 | 95 | Args: 96 | shape: The desired shape of the array. 97 | index: The index at which to place a one. 98 | 99 | Returns: 100 | The one-hot vector. 101 | """ 102 | vec = np.zeros(shape, dtype=dtype) 103 | vec[index] = 1 104 | return vec 105 | -------------------------------------------------------------------------------- /python/ffsim/linalg/predicates.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Linear algebra predicates.""" 12 | 13 | from typing import Union 14 | 15 | import numpy as np 16 | 17 | _bool = Union[bool, np.bool_] 18 | 19 | 20 | def is_hermitian(mat: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8) -> bool: 21 | """Determine if a matrix is approximately Hermitian. 22 | 23 | Args: 24 | mat: The matrix. 25 | rtol: Relative numerical tolerance. 26 | atol: Absolute numerical tolerance. 27 | 28 | Returns: 29 | Whether the matrix is Hermitian within the given tolerance. 30 | """ 31 | m, n = mat.shape 32 | return m == n and np.allclose(mat, mat.T.conj(), rtol=rtol, atol=atol) 33 | 34 | 35 | def is_antihermitian( 36 | mat: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8 37 | ) -> bool: 38 | """Determine if a matrix is approximately anti-Hermitian. 39 | 40 | Args: 41 | mat: The matrix. 42 | rtol: Relative numerical tolerance. 43 | atol: Absolute numerical tolerance. 44 | 45 | Returns: 46 | Whether the matrix is anti-Hermitian within the given tolerance. 47 | """ 48 | m, n = mat.shape 49 | return m == n and np.allclose(mat, -mat.T.conj(), rtol=rtol, atol=atol) 50 | 51 | 52 | def is_real_symmetric( 53 | mat: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8 54 | ) -> _bool: 55 | """Determine if a matrix is real and approximately symmetric. 56 | 57 | Args: 58 | mat: The matrix. 59 | rtol: Relative numerical tolerance. 60 | atol: Absolute numerical tolerance. 61 | 62 | Returns: 63 | Whether the matrix is real and symmetric within the given tolerance. 64 | """ 65 | m, n = mat.shape 66 | return ( 67 | m == n 68 | and np.all(np.isreal(mat)) 69 | and np.allclose(mat, mat.T, rtol=rtol, atol=atol) 70 | ) 71 | 72 | 73 | def is_unitary(mat: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8) -> _bool: 74 | """Determine if a matrix is approximately unitary. 75 | 76 | Args: 77 | mat: The matrix. 78 | rtol: Relative numerical tolerance. 79 | atol: Absolute numerical tolerance. 80 | 81 | Returns: 82 | Whether the matrix is unitary within the given tolerance. 83 | """ 84 | m, n = mat.shape 85 | return m == n and np.allclose(mat @ mat.T.conj(), np.eye(m), rtol=rtol, atol=atol) 86 | 87 | 88 | def is_orthogonal(mat: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8) -> _bool: 89 | """Determine if a matrix is approximately orthogonal. 90 | 91 | Args: 92 | mat: The matrix. 93 | rtol: Relative numerical tolerance. 94 | atol: Absolute numerical tolerance. 95 | 96 | Returns: 97 | Whether the matrix is orthogonal within the given tolerance. 98 | """ 99 | m, n = mat.shape 100 | return ( 101 | m == n 102 | and np.all(np.isreal(mat)) 103 | and np.allclose(mat @ mat.T, np.eye(m), rtol=rtol, atol=atol) 104 | ) 105 | 106 | 107 | def is_special_orthogonal( 108 | mat: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8 109 | ) -> _bool: 110 | """Determine if a matrix is approximately special orthogonal. 111 | 112 | Args: 113 | mat: The matrix. 114 | rtol: Relative numerical tolerance. 115 | atol: Absolute numerical tolerance. 116 | 117 | Returns: 118 | Whether the matrix is special orthogonal within the given tolerance. 119 | """ 120 | m, n = mat.shape 121 | return ( 122 | m == n 123 | and np.all(np.isreal(mat)) 124 | and np.allclose(mat @ mat.T, np.eye(m), rtol=rtol, atol=atol) 125 | and (m == 0 or np.allclose(np.linalg.det(mat), 1, rtol=rtol, atol=atol)) 126 | ) 127 | -------------------------------------------------------------------------------- /python/ffsim/operators/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Operators.""" 12 | 13 | from ffsim.operators.common_operators import number_operator 14 | from ffsim.operators.fermi_hubbard import fermi_hubbard_1d, fermi_hubbard_2d 15 | from ffsim.operators.fermion_action import ( 16 | FermionAction, 17 | cre, 18 | cre_a, 19 | cre_b, 20 | des, 21 | des_a, 22 | des_b, 23 | ) 24 | from ffsim.operators.fermion_operator import FermionOperator 25 | 26 | __all__ = [ 27 | "FermionAction", 28 | "FermionOperator", 29 | "cre", 30 | "cre_a", 31 | "cre_b", 32 | "des", 33 | "des_a", 34 | "des_b", 35 | "fermi_hubbard_1d", 36 | "fermi_hubbard_2d", 37 | "number_operator", 38 | ] 39 | -------------------------------------------------------------------------------- /python/ffsim/operators/common_operators.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Common fermionic operators.""" 12 | 13 | from ffsim._lib import FermionOperator 14 | from ffsim.operators.fermion_action import cre_a, cre_b, des_a, des_b 15 | from ffsim.spin import Spin 16 | 17 | 18 | def number_operator(orb: int, spin: Spin = Spin.ALPHA_AND_BETA) -> FermionOperator: 19 | r"""Occupation number operator. 20 | 21 | The occupation number operator for orbital :math:`p` is defined as 22 | 23 | .. math:: 24 | 25 | n_p = \sum_\sigma a^\dagger_{\sigma, p} a_{\sigma, p} 26 | 27 | Args: 28 | orb: The orbital. 29 | spin: Choice of spin sector(s) to act on. 30 | 31 | - To act on only spin alpha, pass :const:`ffsim.Spin.ALPHA`. 32 | - To act on only spin beta, pass :const:`ffsim.Spin.BETA`. 33 | - To act on both spin alpha and spin beta, pass 34 | :const:`ffsim.Spin.ALPHA_AND_BETA` (this is the default value). 35 | 36 | Returns: 37 | The number operator acting on the specified orbital and spin sector(s). 38 | """ 39 | coeffs: dict[tuple[tuple[bool, bool, int], ...], complex] = {} 40 | if spin & Spin.ALPHA: 41 | coeffs[(cre_a(orb), des_a(orb))] = 1 42 | if spin & Spin.BETA: 43 | coeffs[(cre_b(orb), des_b(orb))] = 1 44 | return FermionOperator(coeffs) 45 | -------------------------------------------------------------------------------- /python/ffsim/operators/fermion_action.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """The FermionAction NamedTuple and construction functions.""" 12 | 13 | from typing import NamedTuple 14 | 15 | 16 | class FermionAction(NamedTuple): 17 | """A fermionic action.""" 18 | 19 | action: bool # False = destroy (annihilate), True = create 20 | spin: bool # False = alpha, True = beta 21 | orb: int # index of the orbital to act on 22 | 23 | 24 | def cre(spin: bool, orb: int) -> FermionAction: 25 | """Create a fermion. 26 | 27 | Args: 28 | spin: The spin of the orbital. False = alpha, True = beta. 29 | orb: The index of the orbital to act on. 30 | """ 31 | return FermionAction(action=True, spin=spin, orb=orb) 32 | 33 | 34 | def des(spin: bool, orb: int) -> FermionAction: 35 | """Destroy a fermion. 36 | 37 | Args: 38 | spin: The spin of the orbital. False = alpha, True = beta. 39 | orb: The index of the orbital to act on. 40 | """ 41 | return FermionAction(action=False, spin=spin, orb=orb) 42 | 43 | 44 | def cre_a(orb: int) -> FermionAction: 45 | """Create a fermion with spin alpha. 46 | 47 | Args: 48 | orb: The index of the orbital to act on. 49 | """ 50 | return cre(False, orb) 51 | 52 | 53 | def des_a(orb: int) -> FermionAction: 54 | """Destroy a fermion with spin alpha. 55 | 56 | Args: 57 | orb: The index of the orbital to act on. 58 | """ 59 | return des(False, orb) 60 | 61 | 62 | def cre_b(orb: int) -> FermionAction: 63 | """Create a fermion with spin beta. 64 | 65 | Args: 66 | orb: The index of the orbital to act on. 67 | """ 68 | return cre(True, orb) 69 | 70 | 71 | def des_b(orb: int) -> FermionAction: 72 | """Destroy a fermion with spin beta. 73 | 74 | Args: 75 | orb: The index of the orbital to act on. 76 | """ 77 | return des(True, orb) 78 | -------------------------------------------------------------------------------- /python/ffsim/operators/fermion_operator.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """The FermionOperator class.""" 12 | 13 | from collections.abc import ItemsView, KeysView, ValuesView 14 | 15 | from ffsim._lib import FermionOperator 16 | 17 | 18 | def keys(self): 19 | return KeysView(self) 20 | 21 | 22 | def values(self): 23 | return ValuesView(self) 24 | 25 | 26 | def items(self): 27 | return ItemsView(self) 28 | 29 | 30 | FermionOperator.keys = keys # type: ignore[method-assign] 31 | FermionOperator.values = values # type: ignore[method-assign] 32 | FermionOperator.items = items # type: ignore[method-assign] 33 | -------------------------------------------------------------------------------- /python/ffsim/optimize/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Optimization algorithms.""" 12 | 13 | from ffsim.optimize.linear_method import minimize_linear_method 14 | 15 | __all__ = [ 16 | "minimize_linear_method", 17 | ] 18 | -------------------------------------------------------------------------------- /python/ffsim/protocols/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Protocols.""" 12 | 13 | from ffsim.protocols.apply_unitary_protocol import SupportsApplyUnitary, apply_unitary 14 | from ffsim.protocols.approximate_equality_protocol import ( 15 | SupportsApproximateEquality, 16 | approx_eq, 17 | ) 18 | from ffsim.protocols.diagonal_protocol import SupportsDiagonal, diag 19 | from ffsim.protocols.fermion_operator_protocol import ( 20 | SupportsFermionOperator, 21 | fermion_operator, 22 | ) 23 | from ffsim.protocols.linear_operator_protocol import ( 24 | SupportsLinearOperator, 25 | linear_operator, 26 | ) 27 | from ffsim.protocols.trace_protocol import SupportsTrace, trace 28 | 29 | __all__ = [ 30 | "SupportsApplyUnitary", 31 | "SupportsApproximateEquality", 32 | "SupportsDiagonal", 33 | "SupportsFermionOperator", 34 | "SupportsLinearOperator", 35 | "SupportsTrace", 36 | "apply_unitary", 37 | "approx_eq", 38 | "diag", 39 | "fermion_operator", 40 | "linear_operator", 41 | "trace", 42 | ] 43 | -------------------------------------------------------------------------------- /python/ffsim/protocols/apply_unitary_protocol.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Apply unitary protocol.""" 12 | 13 | from __future__ import annotations 14 | 15 | from typing import Any, Protocol 16 | 17 | import numpy as np 18 | 19 | 20 | class SupportsApplyUnitary(Protocol): 21 | """An object that can apply a unitary transformation to a vector.""" 22 | 23 | def _apply_unitary_( 24 | self, vec: np.ndarray, norb: int, nelec: int | tuple[int, int], copy: bool 25 | ) -> np.ndarray: 26 | """Apply a unitary transformation to a vector. 27 | 28 | Args: 29 | vec: The vector to apply the unitary transformation to. 30 | norb: The number of spatial orbitals. 31 | nelec: Either a single integer representing the number of fermions for a 32 | spinless system, or a pair of integers storing the numbers of spin alpha 33 | and spin beta fermions. 34 | copy: Whether to copy the vector before operating on it. 35 | 36 | - If `copy=True` then this function always returns a newly allocated 37 | vector and the original vector is left untouched. 38 | - If `copy=False` then this function may still return a newly allocated 39 | vector, but the original vector may have its data overwritten. 40 | It is also possible that the original vector is returned, 41 | modified in-place. 42 | 43 | Returns: 44 | The transformed vector. 45 | """ 46 | 47 | 48 | def apply_unitary( 49 | vec: np.ndarray, 50 | obj: Any, 51 | norb: int, 52 | nelec: int | tuple[int, int], 53 | copy: bool = True, 54 | ) -> np.ndarray: 55 | """Apply a unitary transformation to a vector. 56 | 57 | Args: 58 | vec: The vector to apply the unitary transformation to. 59 | obj: The object with a unitary effect. 60 | norb: The number of spatial orbitals. 61 | nelec: Either a single integer representing the number of fermions for a 62 | spinless system, or a pair of integers storing the numbers of spin alpha 63 | and spin beta fermions. 64 | copy: Whether to copy the vector before operating on it. 65 | 66 | - If `copy=True` then this function always returns a newly allocated 67 | vector and the original vector is left untouched. 68 | - If `copy=False` then this function may still return a newly allocated 69 | vector, but the original vector may have its data overwritten. 70 | It is also possible that the original vector is returned, 71 | modified in-place. 72 | 73 | Returns: 74 | The transformed vector. 75 | """ 76 | method = getattr(obj, "_apply_unitary_", None) 77 | if method is not None: 78 | result = method(vec, norb=norb, nelec=nelec, copy=copy) 79 | if result is not NotImplemented: 80 | return result 81 | raise TypeError( 82 | "ffsim.apply_unitary failed. " 83 | "Object doesn't have a unitary effect.\n" 84 | f"type: {type(obj)}\n" 85 | f"object: {obj!r}\n" 86 | "The object did not have an _apply_unitary_ method that returned " 87 | "a value besides NotImplemented." 88 | ) 89 | -------------------------------------------------------------------------------- /python/ffsim/protocols/approximate_equality_protocol.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Approximate equality protocol.""" 12 | 13 | from __future__ import annotations 14 | 15 | from typing import Any, Protocol 16 | 17 | 18 | class SupportsApproximateEquality(Protocol): 19 | """An object that can be compared approximately.""" 20 | 21 | def _approx_eq_(self, other: Any, rtol: float, atol: float) -> bool: 22 | """Return whether the object is approximately equal to another object. 23 | 24 | See the documentation of `np.isclose`_ for the interpretation of the tolerance 25 | parameters ``rtol`` and ``atol``. 26 | 27 | Args: 28 | other: The object to compare to. 29 | rtol: Relative numerical tolerance. 30 | atol: Absolute numerical tolerance. 31 | 32 | Returns: 33 | True if the objects are approximately equal up to the specified tolerance, 34 | and False otherwise. 35 | 36 | .. _np.isclose: https://numpy.org/doc/stable/reference/generated/numpy.isclose.html 37 | """ 38 | 39 | 40 | def approx_eq(obj: Any, other: Any, rtol: float = 1e-5, atol: float = 1e-8) -> bool: 41 | """Return whether two objects are approximately equal. 42 | 43 | See the documentation of `np.isclose`_ for the interpretation of the tolerance 44 | parameters ``rtol`` and ``atol``. 45 | 46 | Args: 47 | obj: The first object. 48 | other: The object to compare to. 49 | rtol: Relative numerical tolerance. 50 | atol: Absolute numerical tolerance. 51 | 52 | Returns: 53 | True if the objects are approximately equal up to the specified tolerance, 54 | and False otherwise. 55 | 56 | .. _np.isclose: https://numpy.org/doc/stable/reference/generated/numpy.isclose.html 57 | """ 58 | method = getattr(obj, "_approx_eq_", None) 59 | if method is not None: 60 | result = method(other, rtol=rtol, atol=atol) 61 | if result is not NotImplemented: 62 | return result 63 | 64 | method = getattr(other, "_approx_eq_", None) 65 | if method is not None: 66 | result = method(obj, rtol=rtol, atol=atol) 67 | if result is not NotImplemented: 68 | return result 69 | 70 | return obj == other 71 | -------------------------------------------------------------------------------- /python/ffsim/protocols/diagonal_protocol.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Diagonal protocol.""" 12 | 13 | from __future__ import annotations 14 | 15 | from typing import Any, Protocol 16 | 17 | import numpy as np 18 | 19 | from ffsim.protocols.trace_protocol import SupportsTrace 20 | 21 | 22 | class SupportsDiagonal(SupportsTrace, Protocol): 23 | """A linear operator whose diagonal entries can be returned.""" 24 | 25 | def _diag_(self, norb: int, nelec: int | tuple[int, int]) -> np.ndarray: 26 | """Return the diagonal entries of the linear operator. 27 | 28 | Args: 29 | norb: The number of spatial orbitals. 30 | nelec: The number of alpha and beta electrons. 31 | 32 | Returns: 33 | The diagonal entries of the linear operator. 34 | """ 35 | 36 | def _trace_(self, norb: int, nelec: int | tuple[int, int]) -> float: 37 | return np.sum(self._diag_(norb=norb, nelec=nelec)) 38 | 39 | 40 | def diag(obj: Any, norb: int, nelec: int | tuple[int, int]) -> float: 41 | """Return the diagonal entries of the linear operator.""" 42 | method = getattr(obj, "_diag_", None) 43 | if method is not None: 44 | return method(norb=norb, nelec=nelec) 45 | raise TypeError( 46 | f"Could not compute diagonal entries of object of type {type(obj)}.\n" 47 | "The object did not have a _diag_ method that returned its diagonal entries." 48 | ) 49 | -------------------------------------------------------------------------------- /python/ffsim/protocols/fermion_operator_protocol.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """FermionOperator protocol.""" 12 | 13 | from __future__ import annotations 14 | 15 | from typing import Any, Protocol 16 | 17 | from ffsim.operators import FermionOperator 18 | 19 | 20 | class SupportsFermionOperator(Protocol): 21 | """An object that can be converted to a FermionOperator.""" 22 | 23 | def _fermion_operator_(self) -> FermionOperator: 24 | """Return a FermionOperator representing the object. 25 | 26 | Returns: 27 | A FermionOperator representing the object. 28 | """ 29 | 30 | 31 | def fermion_operator(obj: Any) -> FermionOperator: 32 | """Return a FermionOperator representing the object. 33 | 34 | Args: 35 | obj: The object to convert to a LinearOperator. 36 | 37 | Returns: 38 | A FermionOperator representing the object. 39 | """ 40 | method = getattr(obj, "_fermion_operator_", None) 41 | if method is not None: 42 | return method() 43 | 44 | raise TypeError(f"Object of type {type(obj)} has no _fermion_operator_ method.") 45 | -------------------------------------------------------------------------------- /python/ffsim/protocols/trace_protocol.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Trace protocol.""" 12 | 13 | from __future__ import annotations 14 | 15 | from typing import Any, Protocol 16 | 17 | 18 | class SupportsTrace(Protocol): 19 | """A linear operator whose trace can be computed.""" 20 | 21 | def _trace_(self, norb: int, nelec: int | tuple[int, int]) -> float: 22 | """Return the trace of the linear operator. 23 | 24 | Args: 25 | norb: The number of spatial orbitals. 26 | nelec: The number of alpha and beta electrons. 27 | 28 | Returns: 29 | The trace of the linear operator. 30 | """ 31 | 32 | 33 | def trace(obj: Any, norb: int, nelec: int | tuple[int, int]) -> float: 34 | """Return the trace of the linear operator.""" 35 | method = getattr(obj, "_trace_", None) 36 | if method is not None: 37 | return method(norb=norb, nelec=nelec) 38 | raise TypeError( 39 | f"Could not compute trace of object of type {type(obj)}.\n" 40 | "The object did not have a _trace_ method that returned the trace." 41 | ) 42 | -------------------------------------------------------------------------------- /python/ffsim/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/ffsim/65df78bfa4991ed7a2d76f3219575ea89b787c71/python/ffsim/py.typed -------------------------------------------------------------------------------- /python/ffsim/qiskit/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Code that uses Qiskit, e.g. for constructing quantum circuits.""" 12 | 13 | from qiskit.transpiler import PassManager 14 | 15 | from ffsim.qiskit.gates import ( 16 | DiagCoulombEvolutionJW, 17 | DiagCoulombEvolutionSpinlessJW, 18 | GivensAnsatzOpJW, 19 | GivensAnsatzOpSpinlessJW, 20 | NumNumAnsatzOpSpinBalancedJW, 21 | NumOpSumEvolutionJW, 22 | NumOpSumEvolutionSpinlessJW, 23 | OrbitalRotationJW, 24 | OrbitalRotationSpinlessJW, 25 | PrepareHartreeFockJW, 26 | PrepareHartreeFockSpinlessJW, 27 | PrepareSlaterDeterminantJW, 28 | PrepareSlaterDeterminantSpinlessJW, 29 | SimulateTrotterDiagCoulombSplitOpJW, 30 | SimulateTrotterDoubleFactorizedJW, 31 | UCJOpSpinBalancedJW, 32 | UCJOpSpinlessJW, 33 | UCJOpSpinUnbalancedJW, 34 | ) 35 | from ffsim.qiskit.jordan_wigner import jordan_wigner 36 | from ffsim.qiskit.sampler import FfsimSampler 37 | from ffsim.qiskit.sim import final_state_vector 38 | from ffsim.qiskit.transpiler_passes import DropNegligible, MergeOrbitalRotations 39 | from ffsim.qiskit.transpiler_stages import pre_init_passes 40 | from ffsim.qiskit.util import ffsim_vec_to_qiskit_vec, qiskit_vec_to_ffsim_vec 41 | 42 | PRE_INIT = PassManager(list(pre_init_passes())) 43 | """Pass manager recommended for the Qiskit transpiler ``pre_init`` stage. 44 | 45 | See :func:`pre_init_passes` for a description of the transpiler passes included in this 46 | pass manager. 47 | """ 48 | 49 | 50 | __all__ = [ 51 | "DiagCoulombEvolutionJW", 52 | "DiagCoulombEvolutionSpinlessJW", 53 | "DropNegligible", 54 | "FfsimSampler", 55 | "GivensAnsatzOpJW", 56 | "GivensAnsatzOpSpinlessJW", 57 | "MergeOrbitalRotations", 58 | "NumNumAnsatzOpSpinBalancedJW", 59 | "NumOpSumEvolutionJW", 60 | "NumOpSumEvolutionSpinlessJW", 61 | "OrbitalRotationJW", 62 | "OrbitalRotationSpinlessJW", 63 | "PRE_INIT", 64 | "PrepareHartreeFockJW", 65 | "PrepareHartreeFockSpinlessJW", 66 | "PrepareSlaterDeterminantJW", 67 | "PrepareSlaterDeterminantSpinlessJW", 68 | "SimulateTrotterDiagCoulombSplitOpJW", 69 | "SimulateTrotterDoubleFactorizedJW", 70 | "UCJOpSpinBalancedJW", 71 | "UCJOpSpinUnbalancedJW", 72 | "UCJOpSpinlessJW", 73 | "ffsim_vec_to_qiskit_vec", 74 | "final_state_vector", 75 | "jordan_wigner", 76 | "pre_init_passes", 77 | "qiskit_vec_to_ffsim_vec", 78 | ] 79 | -------------------------------------------------------------------------------- /python/ffsim/qiskit/gates/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Qiskit fermionic quantum gates.""" 12 | 13 | from ffsim.qiskit.gates.diag_coulomb import ( 14 | DiagCoulombEvolutionJW, 15 | DiagCoulombEvolutionSpinlessJW, 16 | ) 17 | from ffsim.qiskit.gates.diag_coulomb_trotter import SimulateTrotterDiagCoulombSplitOpJW 18 | from ffsim.qiskit.gates.double_factorized_trotter import ( 19 | SimulateTrotterDoubleFactorizedJW, 20 | ) 21 | from ffsim.qiskit.gates.givens_ansatz import ( 22 | GivensAnsatzOpJW, 23 | GivensAnsatzOpSpinlessJW, 24 | ) 25 | from ffsim.qiskit.gates.num_num_ansatz import NumNumAnsatzOpSpinBalancedJW 26 | from ffsim.qiskit.gates.num_op_sum import ( 27 | NumOpSumEvolutionJW, 28 | NumOpSumEvolutionSpinlessJW, 29 | ) 30 | from ffsim.qiskit.gates.orbital_rotation import ( 31 | OrbitalRotationJW, 32 | OrbitalRotationSpinlessJW, 33 | ) 34 | from ffsim.qiskit.gates.slater_determinant import ( 35 | PrepareHartreeFockJW, 36 | PrepareHartreeFockSpinlessJW, 37 | PrepareSlaterDeterminantJW, 38 | PrepareSlaterDeterminantSpinlessJW, 39 | ) 40 | from ffsim.qiskit.gates.ucj import ( 41 | UCJOpSpinBalancedJW, 42 | UCJOpSpinlessJW, 43 | UCJOpSpinUnbalancedJW, 44 | ) 45 | 46 | __all__ = [ 47 | "DiagCoulombEvolutionJW", 48 | "DiagCoulombEvolutionSpinlessJW", 49 | "GivensAnsatzOpJW", 50 | "GivensAnsatzOpSpinlessJW", 51 | "NumNumAnsatzOpSpinBalancedJW", 52 | "NumOpSumEvolutionJW", 53 | "NumOpSumEvolutionSpinlessJW", 54 | "OrbitalRotationJW", 55 | "OrbitalRotationSpinlessJW", 56 | "PrepareHartreeFockJW", 57 | "PrepareHartreeFockSpinlessJW", 58 | "PrepareSlaterDeterminantJW", 59 | "PrepareSlaterDeterminantSpinlessJW", 60 | "SimulateTrotterDiagCoulombSplitOpJW", 61 | "SimulateTrotterDoubleFactorizedJW", 62 | "UCJOpSpinBalancedJW", 63 | "UCJOpSpinUnbalancedJW", 64 | "UCJOpSpinlessJW", 65 | ] 66 | -------------------------------------------------------------------------------- /python/ffsim/qiskit/gates/givens_ansatz.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Givens rotation ansatz gate.""" 12 | 13 | from __future__ import annotations 14 | 15 | import math 16 | from collections.abc import Iterator, Sequence 17 | 18 | import numpy as np 19 | from qiskit.circuit import ( 20 | CircuitInstruction, 21 | Gate, 22 | QuantumCircuit, 23 | QuantumRegister, 24 | Qubit, 25 | ) 26 | from qiskit.circuit.library import PhaseGate, XXPlusYYGate 27 | 28 | from ffsim.variational import GivensAnsatzOp 29 | 30 | 31 | class GivensAnsatzOpJW(Gate): 32 | """Givens rotation ansatz operator under the Jordan-Wigner transformation. 33 | 34 | See :class:`ffsim.GivensAnsatzOp` for a description of this gate's unitary. 35 | """ 36 | 37 | def __init__(self, givens_ansatz_op: GivensAnsatzOp, *, label: str | None = None): 38 | """Create a new Givens ansatz operator gate. 39 | 40 | Args: 41 | givens_ansatz_op: The Givens rotation ansatz operator. 42 | label: The label of the gate. 43 | """ 44 | self.givens_ansatz_op = givens_ansatz_op 45 | super().__init__("givens_ansatz_jw", 2 * givens_ansatz_op.norb, [], label=label) 46 | 47 | def _define(self): 48 | """Gate decomposition.""" 49 | qubits = QuantumRegister(self.num_qubits) 50 | circuit = QuantumCircuit(qubits, name=self.name) 51 | norb = len(qubits) // 2 52 | alpha_qubits = qubits[:norb] 53 | beta_qubits = qubits[norb:] 54 | for instruction in _givens_ansatz_jw(alpha_qubits, self.givens_ansatz_op): 55 | circuit.append(instruction) 56 | for instruction in _givens_ansatz_jw(beta_qubits, self.givens_ansatz_op): 57 | circuit.append(instruction) 58 | self.definition = circuit 59 | 60 | 61 | class GivensAnsatzOpSpinlessJW(Gate): 62 | """Spinless Givens rotation ansatz operator under the Jordan-Wigner transformation. 63 | 64 | Like :class:`GivensAnsatzOpJW` but only acts on a single spin species. 65 | """ 66 | 67 | def __init__(self, givens_ansatz_op: GivensAnsatzOp, *, label: str | None = None): 68 | """Create a new Givens ansatz operator gate. 69 | 70 | Args: 71 | givens_ansatz_op: The Givens rotation ansatz operator. 72 | label: The label of the gate. 73 | """ 74 | self.givens_ansatz_op = givens_ansatz_op 75 | super().__init__( 76 | "givens_ansatz_spinless_jw", givens_ansatz_op.norb, [], label=label 77 | ) 78 | 79 | def _define(self): 80 | """Gate decomposition.""" 81 | qubits = QuantumRegister(self.num_qubits) 82 | circuit = QuantumCircuit(qubits, name=self.name) 83 | for instruction in _givens_ansatz_jw(qubits, self.givens_ansatz_op): 84 | circuit.append(instruction) 85 | self.definition = circuit 86 | 87 | 88 | def _givens_ansatz_jw( 89 | qubits: Sequence[Qubit], givens_ansatz_op: GivensAnsatzOp 90 | ) -> Iterator[CircuitInstruction]: 91 | phis = givens_ansatz_op.phis 92 | if phis is None: 93 | phis = np.zeros(len(givens_ansatz_op.interaction_pairs)) 94 | for (i, j), theta, phi in zip( 95 | givens_ansatz_op.interaction_pairs, givens_ansatz_op.thetas, phis 96 | ): 97 | yield CircuitInstruction( 98 | XXPlusYYGate(2 * theta, phi - 0.5 * math.pi), (qubits[i], qubits[j]) 99 | ) 100 | if givens_ansatz_op.phase_angles is not None: 101 | for i, phase_angle in enumerate(givens_ansatz_op.phase_angles): 102 | yield CircuitInstruction(PhaseGate(phase_angle), (qubits[i],)) 103 | -------------------------------------------------------------------------------- /python/ffsim/qiskit/gates/num_num_ansatz.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Number-number interaction ansatz gate.""" 12 | 13 | from __future__ import annotations 14 | 15 | from collections.abc import Iterator, Sequence 16 | 17 | from qiskit.circuit import ( 18 | CircuitInstruction, 19 | Gate, 20 | QuantumCircuit, 21 | QuantumRegister, 22 | Qubit, 23 | ) 24 | from qiskit.circuit.library import CPhaseGate, PhaseGate 25 | 26 | from ffsim.variational.num_num import NumNumAnsatzOpSpinBalanced 27 | 28 | 29 | class NumNumAnsatzOpSpinBalancedJW(Gate): 30 | r"""Spin-balanced number-number ansatz under the Jordan-Wigner transformation. 31 | 32 | See :class:`NumNumAnsatzOpSpinBalanced` for a description of this gate's unitary. 33 | 34 | This gate assumes that qubits are ordered such that the first `norb` qubits 35 | correspond to the alpha orbitals and the last `norb` qubits correspond to the 36 | beta orbitals. 37 | """ 38 | 39 | def __init__( 40 | self, 41 | num_num_ansatz_op: NumNumAnsatzOpSpinBalanced, 42 | *, 43 | label: str | None = None, 44 | ): 45 | """Create a new number-number ansatz operator gate. 46 | 47 | Args: 48 | num_num_ansatz_op: The number-number ansatz operator. 49 | label: The label of the gate. 50 | """ 51 | self.num_num_ansatz_op = num_num_ansatz_op 52 | super().__init__( 53 | "num_num_ansatz_balanced_jw", 2 * num_num_ansatz_op.norb, [], label=label 54 | ) 55 | 56 | def _define(self): 57 | """Gate decomposition.""" 58 | qubits = QuantumRegister(self.num_qubits) 59 | circuit = QuantumCircuit(qubits, name=self.name) 60 | for instruction in _num_num_ansatz_spin_balanced_jw( 61 | qubits, self.num_num_ansatz_op 62 | ): 63 | circuit.append(instruction) 64 | self.definition = circuit 65 | 66 | 67 | def _num_num_ansatz_spin_balanced_jw( 68 | qubits: Sequence[Qubit], num_num_ansatz_operator: NumNumAnsatzOpSpinBalanced 69 | ) -> Iterator[CircuitInstruction]: 70 | norb = num_num_ansatz_operator.norb 71 | pairs_aa, pairs_ab = num_num_ansatz_operator.interaction_pairs 72 | thetas_aa, thetas_ab = num_num_ansatz_operator.thetas 73 | 74 | # gates that involve a single spin sector 75 | for sigma in range(2): 76 | for (i, j), theta in zip(pairs_aa, thetas_aa): 77 | if i == j: 78 | yield CircuitInstruction( 79 | PhaseGate(0.5 * theta), (qubits[i + sigma * norb],) 80 | ) 81 | else: 82 | yield CircuitInstruction( 83 | CPhaseGate(theta), 84 | (qubits[i + sigma * norb], qubits[j + sigma * norb]), 85 | ) 86 | 87 | # gates that involve both spin sectors 88 | for (i, j), theta in zip(pairs_ab, thetas_ab): 89 | angle = 0.5 * theta if i == j else theta 90 | yield CircuitInstruction(CPhaseGate(angle), (qubits[i], qubits[j + norb])) 91 | yield CircuitInstruction(CPhaseGate(angle), (qubits[j], qubits[i + norb])) 92 | -------------------------------------------------------------------------------- /python/ffsim/qiskit/jordan_wigner.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Jordan-Wigner transformation.""" 12 | 13 | from __future__ import annotations 14 | 15 | import functools 16 | 17 | from qiskit.quantum_info import SparsePauliOp 18 | 19 | from ffsim.operators import FermionOperator 20 | 21 | 22 | def jordan_wigner(op: FermionOperator, norb: int | None = None) -> SparsePauliOp: 23 | r"""Jordan-Wigner transformation. 24 | 25 | Transform a fermion operator to a qubit operator using the Jordan-Wigner 26 | transformation. The Jordan-Wigner transformation maps fermionic annihilation 27 | operators to qubits as follows: 28 | 29 | .. math:: 30 | 31 | a_p \mapsto \frac12 (X_p + iY_p)Z_1 \cdots Z_{p-1} 32 | 33 | In the transformed operator, the first ``norb`` qubits represent spin-up (alpha) 34 | orbitals, and the latter ``norb`` qubits represent spin-down (beta) orbitals. As a 35 | result of this convention, the qubit index that an orbital is mapped to depends on 36 | the total number of spatial orbitals. By default, the total number of spatial 37 | orbitals is automatically determined by the largest-index orbital present in the 38 | operator, but you can manually specify the number using the `norb` argument. 39 | 40 | Args: 41 | op: The fermion operator to transform. 42 | norb: The total number of spatial orbitals. If not specified, it is determined 43 | by the largest-index orbital present in the operator. 44 | 45 | Returns: 46 | The qubit operator as a Qiskit SparsePauliOp. 47 | 48 | Raises: 49 | ValueError: Number of spatial orbitals was negative. 50 | ValueError: Number of spatial orbitals was fewer than the number detected in the 51 | operator. 52 | """ 53 | if norb and norb < 0: 54 | raise ValueError( 55 | f"Number of spatial orbitals must be non-negative. Got {norb}." 56 | ) 57 | if not op: 58 | return SparsePauliOp.from_sparse_list( 59 | [("", [], 0.0)], num_qubits=2 * (norb or 0) 60 | ) 61 | 62 | norb_in_op = 1 + max(orb for term in op for _, _, orb in term) 63 | if norb is None: 64 | norb = norb_in_op 65 | if norb < norb_in_op: 66 | raise ValueError( 67 | "Number of spatial orbitals specified is fewer than the number detected in " 68 | f"the operator. The operator has {norb_in_op} spatial orbitals, but " 69 | f"only {norb} were specified." 70 | ) 71 | 72 | qubit_terms = [SparsePauliOp.from_sparse_list([("", [], 0.0)], num_qubits=2 * norb)] 73 | for term, coeff in op.items(): 74 | qubit_op = SparsePauliOp.from_sparse_list( 75 | [("", [], coeff)], num_qubits=2 * norb 76 | ) 77 | for action, spin, orb in term: 78 | qubit_op @= _qubit_action(action, orb + spin * norb, norb) 79 | qubit_terms.append(qubit_op) 80 | 81 | return SparsePauliOp.sum(qubit_terms).simplify() 82 | 83 | 84 | @functools.cache 85 | def _qubit_action(action: bool, qubit: int, norb: int): 86 | qubits = list(range(qubit + 1)) 87 | return SparsePauliOp.from_sparse_list( 88 | [ 89 | ("Z" * qubit + "X", qubits, 0.5), 90 | ("Z" * qubit + "Y", qubits, -0.5j if action else 0.5j), 91 | ], 92 | num_qubits=2 * norb, 93 | ) 94 | -------------------------------------------------------------------------------- /python/ffsim/qiskit/transpiler_passes/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Qiskit transpiler passes for fermionic quantum circuits.""" 12 | 13 | from ffsim.qiskit.transpiler_passes.drop_negligible import DropNegligible 14 | from ffsim.qiskit.transpiler_passes.merge_orbital_rotations import MergeOrbitalRotations 15 | 16 | __all__ = [ 17 | "DropNegligible", 18 | "MergeOrbitalRotations", 19 | ] 20 | -------------------------------------------------------------------------------- /python/ffsim/qiskit/transpiler_passes/drop_negligible.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Transpiler pass to drop gates with negligible effects.""" 12 | 13 | from __future__ import annotations 14 | 15 | import numpy as np 16 | from qiskit.circuit.library import ( 17 | CPhaseGate, 18 | PhaseGate, 19 | RXGate, 20 | RXXGate, 21 | RYGate, 22 | RYYGate, 23 | RZGate, 24 | RZZGate, 25 | XXMinusYYGate, 26 | XXPlusYYGate, 27 | ) 28 | from qiskit.dagcircuit import DAGCircuit 29 | from qiskit.transpiler.basepasses import TransformationPass 30 | 31 | # List of Gate classes with the property that if the gate's parameters are all 32 | # (close to) zero then the gate has (close to) no effect. 33 | DROP_NEGLIGIBLE_GATE_CLASSES = ( 34 | CPhaseGate, 35 | PhaseGate, 36 | RXGate, 37 | RYGate, 38 | RZGate, 39 | RXXGate, 40 | RYYGate, 41 | RZZGate, 42 | XXPlusYYGate, 43 | XXMinusYYGate, 44 | ) 45 | 46 | 47 | class DropNegligible(TransformationPass): 48 | """Drop gates with negligible effects.""" 49 | 50 | def __init__(self, atol: float = 1e-8) -> None: 51 | """Initialize the transpiler pass. 52 | 53 | Args: 54 | atol: Absolute numerical tolerance for determining whether a gate's effect 55 | is negligible. 56 | """ 57 | self.atol = atol 58 | super().__init__() 59 | 60 | def run(self, dag: DAGCircuit) -> DAGCircuit: 61 | for node in dag.op_nodes(): 62 | if not isinstance(node.op, DROP_NEGLIGIBLE_GATE_CLASSES): 63 | continue 64 | if not all( 65 | isinstance(param, (int, float, complex)) for param in node.op.params 66 | ): 67 | continue 68 | if np.allclose(node.op.params, 0, atol=self.atol): 69 | dag.remove_op_node(node) 70 | return dag 71 | -------------------------------------------------------------------------------- /python/ffsim/qiskit/transpiler_stages.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tools for constructing Qiskit transpiler pass managers and stages.""" 12 | 13 | from __future__ import annotations 14 | 15 | from collections.abc import Iterator 16 | 17 | from qiskit.transpiler.basepasses import BasePass 18 | from qiskit.transpiler.passes import Decompose 19 | 20 | from ffsim.qiskit.transpiler_passes import MergeOrbitalRotations 21 | 22 | 23 | def pre_init_passes() -> Iterator[BasePass]: 24 | """Yield transpiler passes recommended for the Qiskit transpiler ``pre_init`` stage. 25 | 26 | The following transpiler passes are yielded: 27 | 28 | - `Decompose`_ pass that decomposes :class:`PrepareHartreeFockJW` and 29 | :class:`UCJOperatorJW` gates to expose the underlying 30 | :class:`PrepareSlaterDeterminantJW` and :class:`OrbitalRotationJW` gates. 31 | - :class:`MergeOrbitalRotations` pass to merge the Slater determinant preparation 32 | and orbital rotation gates. 33 | 34 | .. _Decompose: https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.passes.Decompose#decompose 35 | """ 36 | yield Decompose( 37 | [ 38 | "hartree_fock_jw", 39 | "hartree_fock_spinless_jw", 40 | "ucj_jw", 41 | "ucj_balanced_jw", 42 | "ucj_spinless_jw", 43 | "ucj_unbalanced_jw", 44 | ] 45 | ) 46 | yield MergeOrbitalRotations() 47 | -------------------------------------------------------------------------------- /python/ffsim/qiskit/util.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | from __future__ import annotations 12 | 13 | from functools import cache 14 | 15 | import numpy as np 16 | 17 | from ffsim import states 18 | from ffsim.cistring import make_strings 19 | 20 | 21 | def qiskit_vec_to_ffsim_vec( 22 | vec: np.ndarray, norb: int, nelec: int | tuple[int, int] 23 | ) -> np.ndarray: 24 | """Convert a Qiskit state vector to an ffsim state vector. 25 | 26 | Args: 27 | vec: A state vector in Qiskit format. It should be a one-dimensional vector 28 | of length ``2 ** (2 * norb)`` in the spinful case, and ``2 ** norb`` in the 29 | spinless case. 30 | norb: The number of spatial orbitals. 31 | nelec: Either a single integer representing the number of fermions for a 32 | spinless system, or a pair of integers storing the numbers of spin alpha 33 | and spin beta fermions. 34 | """ 35 | n_qubits = norb if isinstance(nelec, int) else 2 * norb 36 | assert vec.shape == (1 << n_qubits,) 37 | return vec[_ffsim_indices(norb, nelec)] 38 | 39 | 40 | def ffsim_vec_to_qiskit_vec( 41 | vec: np.ndarray, norb: int, nelec: int | tuple[int, int] 42 | ) -> np.ndarray: 43 | """Convert an ffsim state vector to a Qiskit state vector. 44 | 45 | Args: 46 | vec: A state vector in ffsim/PySCF format. It should be a one-dimensional vector 47 | of length ``comb(norb, n_alpha) * comb(norb, n_beta)`` in the spinful case, 48 | and ``comb(norb, nelec)`` in the spinless case. 49 | norb: The number of spatial orbitals. 50 | nelec: Either a single integer representing the number of fermions for a 51 | spinless system, or a pair of integers storing the numbers of spin alpha 52 | and spin beta fermions. 53 | """ 54 | n_qubits = norb if isinstance(nelec, int) else 2 * norb 55 | assert vec.shape == (states.dim(norb, nelec),) 56 | qiskit_vec = np.zeros(1 << n_qubits, dtype=vec.dtype) 57 | qiskit_vec[_ffsim_indices(norb, nelec)] = vec 58 | return qiskit_vec 59 | 60 | 61 | @cache 62 | def _ffsim_indices(norb: int, nelec: int | tuple[int, int]) -> np.ndarray: 63 | if isinstance(nelec, int): 64 | return make_strings(range(norb), nelec) 65 | n_alpha, n_beta = nelec 66 | strings_a = make_strings(range(norb), n_alpha) 67 | strings_b = make_strings(range(norb), n_beta) << norb 68 | # Compute [a + b for a, b in product(strings_a, strings_b)] 69 | return (strings_a.reshape(-1, 1) + strings_b).reshape(-1) 70 | -------------------------------------------------------------------------------- /python/ffsim/random/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Random sampling utilities.""" 12 | 13 | from ffsim.random.random import ( 14 | random_antihermitian, 15 | random_density_matrix, 16 | random_diagonal_coulomb_hamiltonian, 17 | random_double_factorized_hamiltonian, 18 | random_fermion_hamiltonian, 19 | random_fermion_operator, 20 | random_hermitian, 21 | random_molecular_hamiltonian, 22 | random_molecular_hamiltonian_spinless, 23 | random_orthogonal, 24 | random_real_symmetric_matrix, 25 | random_special_orthogonal, 26 | random_state_vector, 27 | random_t2_amplitudes, 28 | random_two_body_tensor, 29 | random_uccsd_restricted, 30 | random_ucj_op_spin_balanced, 31 | random_ucj_op_spin_unbalanced, 32 | random_ucj_op_spinless, 33 | random_unitary, 34 | ) 35 | 36 | __all__ = [ 37 | "random_antihermitian", 38 | "random_density_matrix", 39 | "random_diagonal_coulomb_hamiltonian", 40 | "random_double_factorized_hamiltonian", 41 | "random_fermion_hamiltonian", 42 | "random_fermion_operator", 43 | "random_hermitian", 44 | "random_molecular_hamiltonian", 45 | "random_molecular_hamiltonian_spinless", 46 | "random_orthogonal", 47 | "random_real_symmetric_matrix", 48 | "random_special_orthogonal", 49 | "random_state_vector", 50 | "random_t2_amplitudes", 51 | "random_two_body_tensor", 52 | "random_uccsd_restricted", 53 | "random_ucj_op_spin_balanced", 54 | "random_ucj_op_spin_unbalanced", 55 | "random_ucj_op_spinless", 56 | "random_unitary", 57 | ] 58 | -------------------------------------------------------------------------------- /python/ffsim/spin.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Enumeration for indicating alpha, beta, or both spins.""" 12 | 13 | from __future__ import annotations 14 | 15 | from enum import Flag, auto 16 | from typing import TypeVar 17 | 18 | 19 | class Spin(Flag): 20 | """Enumeration for indicating alpha, beta, or both spins.""" 21 | 22 | ALPHA = auto() 23 | """Use this to indicate spin alpha.""" 24 | 25 | BETA = auto() 26 | """Use this to indicate spin beta.""" 27 | 28 | ALPHA_AND_BETA = ALPHA | BETA 29 | """Use this to indicate both spin alpha and spin beta.""" 30 | 31 | 32 | T = TypeVar("T") 33 | 34 | 35 | def pair_for_spin(obj: T, spin: Spin) -> tuple[T | None, T | None]: 36 | """Create a pair of objects for a given spin setting. 37 | 38 | Given an object, return a pair where each element of the pair is either the object 39 | or `None`, depending on the specified `spin`. The first element of the pair 40 | corresponds to spin alpha and the second corresponds to spin beta. 41 | """ 42 | if spin is Spin.ALPHA: 43 | return (obj, None) 44 | if spin is Spin.BETA: 45 | return (None, obj) 46 | return (obj, obj) 47 | -------------------------------------------------------------------------------- /python/ffsim/states/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """States.""" 12 | 13 | from ffsim.states.bitstring import ( 14 | BitstringType, 15 | addresses_to_strings, 16 | strings_to_addresses, 17 | ) 18 | from ffsim.states.product_state_sum import ProductStateSum 19 | from ffsim.states.rdm import rdms 20 | from ffsim.states.sample_slater import sample_slater_determinant 21 | from ffsim.states.slater import ( 22 | hartree_fock_state, 23 | slater_determinant, 24 | slater_determinant_amplitudes, 25 | slater_determinant_rdms, 26 | ) 27 | from ffsim.states.states import ( 28 | StateVector, 29 | dim, 30 | dims, 31 | sample_state_vector, 32 | spin_square, 33 | spinful_to_spinless_rdm1, 34 | spinful_to_spinless_rdm2, 35 | spinful_to_spinless_vec, 36 | ) 37 | from ffsim.states.wick import expectation_one_body_power, expectation_one_body_product 38 | 39 | __all__ = [ 40 | "BitstringType", 41 | "ProductStateSum", 42 | "StateVector", 43 | "addresses_to_strings", 44 | "dim", 45 | "dims", 46 | "expectation_one_body_power", 47 | "expectation_one_body_product", 48 | "hartree_fock_state", 49 | "rdms", 50 | "sample_slater_determinant", 51 | "sample_state_vector", 52 | "slater_determinant", 53 | "slater_determinant_amplitudes", 54 | "slater_determinant_rdms", 55 | "spin_square", 56 | "spinful_to_spinless_rdm1", 57 | "spinful_to_spinless_rdm2", 58 | "spinful_to_spinless_vec", 59 | "strings_to_addresses", 60 | ] 61 | -------------------------------------------------------------------------------- /python/ffsim/states/product_state_sum.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """The ProductStateSum NamedTuple.""" 12 | 13 | from __future__ import annotations 14 | 15 | from typing import NamedTuple 16 | 17 | import numpy as np 18 | 19 | 20 | class ProductStateSum(NamedTuple): 21 | """A linear combination of product states. 22 | 23 | Given a ProductStateSum ``prod_state_sum``, the full state vector can be 24 | reconstructed as 25 | 26 | .. code-block:: python 27 | 28 | sum( 29 | coeff * np.kron(vec_a, vec_b) 30 | for coeff, (vec_a, vec_b) in zip( 31 | prod_state_sum.coeffs, prod_state_sum.states 32 | ) 33 | ) 34 | """ 35 | 36 | coeffs: np.ndarray 37 | states: list[tuple[np.ndarray, np.ndarray]] 38 | -------------------------------------------------------------------------------- /python/ffsim/testing/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Testing utilities.""" 12 | 13 | from ffsim.testing.testing import ( 14 | assert_allclose_up_to_global_phase, 15 | generate_norb_nelec, 16 | generate_norb_nelec_spin, 17 | generate_norb_nocc, 18 | generate_norb_spin, 19 | random_nelec, 20 | random_occupied_orbitals, 21 | ) 22 | 23 | __all__ = [ 24 | "assert_allclose_up_to_global_phase", 25 | "generate_norb_nelec", 26 | "generate_norb_nelec_spin", 27 | "generate_norb_nocc", 28 | "generate_norb_spin", 29 | "random_nelec", 30 | "random_occupied_orbitals", 31 | ] 32 | -------------------------------------------------------------------------------- /python/ffsim/trotter/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Hamiltonian simulation via Trotter-Suzuki formulas.""" 12 | 13 | from ffsim.trotter.diagonal_coulomb import simulate_trotter_diag_coulomb_split_op 14 | from ffsim.trotter.double_factorized import simulate_trotter_double_factorized 15 | from ffsim.trotter.qdrift import simulate_qdrift_double_factorized 16 | 17 | __all__ = [ 18 | "simulate_qdrift_double_factorized", 19 | "simulate_trotter_diag_coulomb_split_op", 20 | "simulate_trotter_double_factorized", 21 | ] 22 | -------------------------------------------------------------------------------- /python/ffsim/trotter/_util.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Trotter simulation utilities.""" 12 | 13 | from __future__ import annotations 14 | 15 | from collections.abc import Iterator 16 | 17 | 18 | def simulate_trotter_step_iterator( 19 | n_terms: int, time: float, order: int = 0 20 | ) -> Iterator[tuple[int, float]]: 21 | if order == 0: 22 | for i in range(n_terms): 23 | yield i, time 24 | else: 25 | yield from simulate_trotter_step_iterator_symmetric(n_terms, time, order) 26 | 27 | 28 | def simulate_trotter_step_iterator_symmetric( 29 | n_terms: int, time: float, order: int 30 | ) -> Iterator[tuple[int, float]]: 31 | if order == 1: 32 | for i in range(n_terms - 1): 33 | yield i, time / 2 34 | yield n_terms - 1, time 35 | for i in reversed(range(n_terms - 1)): 36 | yield i, time / 2 37 | else: 38 | split_time = time / (4 - 4 ** (1 / (2 * order - 1))) 39 | for _ in range(2): 40 | yield from simulate_trotter_step_iterator_symmetric( 41 | n_terms, split_time, order - 1 42 | ) 43 | yield from simulate_trotter_step_iterator_symmetric( 44 | n_terms, time - 4 * split_time, order - 1 45 | ) 46 | for _ in range(2): 47 | yield from simulate_trotter_step_iterator_symmetric( 48 | n_terms, split_time, order - 1 49 | ) 50 | -------------------------------------------------------------------------------- /python/ffsim/variational/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Variational ansatzes.""" 12 | 13 | from ffsim.variational.givens import GivensAnsatzOp 14 | from ffsim.variational.hopgate import HopGateAnsatzOperator 15 | from ffsim.variational.multireference import ( 16 | multireference_state, 17 | multireference_state_prod, 18 | ) 19 | from ffsim.variational.num_num import NumNumAnsatzOpSpinBalanced 20 | from ffsim.variational.uccsd import UCCSDOpRestrictedReal 21 | from ffsim.variational.ucj_angles_spin_balanced import UCJAnglesOpSpinBalanced 22 | from ffsim.variational.ucj_spin_balanced import UCJOpSpinBalanced 23 | from ffsim.variational.ucj_spin_unbalanced import UCJOpSpinUnbalanced 24 | from ffsim.variational.ucj_spinless import UCJOpSpinless 25 | 26 | __all__ = [ 27 | "GivensAnsatzOp", 28 | "HopGateAnsatzOperator", 29 | "NumNumAnsatzOpSpinBalanced", 30 | "UCCSDOpRestrictedReal", 31 | "UCJAnglesOpSpinBalanced", 32 | "UCJOpSpinBalanced", 33 | "UCJOpSpinUnbalanced", 34 | "UCJOpSpinless", 35 | "multireference_state", 36 | "multireference_state_prod", 37 | ] 38 | -------------------------------------------------------------------------------- /src/contract/mod.rs: -------------------------------------------------------------------------------- 1 | // (C) Copyright IBM 2023 2 | // 3 | // This code is licensed under the Apache License, Version 2.0. You may 4 | // obtain a copy of this license in the LICENSE.txt file in the root directory 5 | // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | // 7 | // Any modifications or derivative works of this code must retain this 8 | // copyright notice, and modified files need to carry a notice indicating 9 | // that they have been altered from the originals. 10 | 11 | pub mod diag_coulomb; 12 | pub mod num_op_sum; 13 | -------------------------------------------------------------------------------- /src/contract/num_op_sum.rs: -------------------------------------------------------------------------------- 1 | // (C) Copyright IBM 2023 2 | // 3 | // This code is licensed under the Apache License, Version 2.0. You may 4 | // obtain a copy of this license in the LICENSE.txt file in the root directory 5 | // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | // 7 | // Any modifications or derivative works of this code must retain this 8 | // copyright notice, and modified files need to carry a notice indicating 9 | // that they have been altered from the originals. 10 | 11 | use ndarray::Zip; 12 | use numpy::Complex64; 13 | use numpy::PyReadonlyArray1; 14 | use numpy::PyReadonlyArray2; 15 | use numpy::PyReadwriteArray2; 16 | use pyo3::prelude::*; 17 | 18 | /// Contract a sum of number operators into a buffer. 19 | #[pyfunction] 20 | pub fn contract_num_op_sum_spin_into_buffer( 21 | vec: PyReadonlyArray2, 22 | coeffs: PyReadonlyArray1, 23 | occupations: PyReadonlyArray2, 24 | mut out: PyReadwriteArray2, 25 | ) { 26 | let vec = vec.as_array(); 27 | let coeffs = coeffs.as_array(); 28 | let occupations = occupations.as_array(); 29 | let mut out = out.as_array_mut(); 30 | 31 | Zip::from(vec.rows()) 32 | .and(out.rows_mut()) 33 | .and(occupations.rows()) 34 | .par_for_each(|source, mut target, orbs| { 35 | let mut coeff = Complex64::new(0.0, 0.0); 36 | orbs.for_each(|&orb| coeff += coeffs[orb]); 37 | target += &(coeff * &source); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/gates/mod.rs: -------------------------------------------------------------------------------- 1 | // (C) Copyright IBM 2023 2 | // 3 | // This code is licensed under the Apache License, Version 2.0. You may 4 | // obtain a copy of this license in the LICENSE.txt file in the root directory 5 | // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | // 7 | // Any modifications or derivative works of this code must retain this 8 | // copyright notice, and modified files need to carry a notice indicating 9 | // that they have been altered from the originals. 10 | 11 | pub mod diag_coulomb; 12 | pub mod num_op_sum; 13 | pub mod orbital_rotation; 14 | pub mod phase_shift; 15 | -------------------------------------------------------------------------------- /src/gates/num_op_sum.rs: -------------------------------------------------------------------------------- 1 | // (C) Copyright IBM 2023 2 | // 3 | // This code is licensed under the Apache License, Version 2.0. You may 4 | // obtain a copy of this license in the LICENSE.txt file in the root directory 5 | // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | // 7 | // Any modifications or derivative works of this code must retain this 8 | // copyright notice, and modified files need to carry a notice indicating 9 | // that they have been altered from the originals. 10 | 11 | use ndarray::Zip; 12 | use numpy::Complex64; 13 | use numpy::PyReadonlyArray1; 14 | use numpy::PyReadonlyArray2; 15 | use numpy::PyReadwriteArray2; 16 | use pyo3::prelude::*; 17 | 18 | /// Apply time evolution by a sum of number operators in-place. 19 | #[pyfunction] 20 | pub fn apply_num_op_sum_evolution_in_place( 21 | mut vec: PyReadwriteArray2, 22 | phases: PyReadonlyArray1, 23 | occupations: PyReadonlyArray2, 24 | ) { 25 | let phases = phases.as_array(); 26 | let mut vec = vec.as_array_mut(); 27 | let occupations = occupations.as_array(); 28 | 29 | Zip::from(vec.rows_mut()) 30 | .and(occupations.rows()) 31 | .par_for_each(|mut row, orbs| { 32 | let mut phase = Complex64::new(1.0, 0.0); 33 | orbs.for_each(|&orb| phase *= phases[orb]); 34 | row *= phase; 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/gates/orbital_rotation.rs: -------------------------------------------------------------------------------- 1 | // (C) Copyright IBM 2023 2 | // 3 | // This code is licensed under the Apache License, Version 2.0. You may 4 | // obtain a copy of this license in the LICENSE.txt file in the root directory 5 | // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | // 7 | // Any modifications or derivative works of this code must retain this 8 | // copyright notice, and modified files need to carry a notice indicating 9 | // that they have been altered from the originals. 10 | 11 | extern crate blas_src; 12 | 13 | use blas::zdrot; 14 | use blas::zscal; 15 | use ndarray::s; 16 | 17 | use ndarray::Zip; 18 | use numpy::Complex64; 19 | use numpy::PyReadonlyArray1; 20 | 21 | use numpy::PyReadwriteArray2; 22 | 23 | use pyo3::prelude::*; 24 | 25 | /// Apply a matrix to slices of a matrix. 26 | #[pyfunction] 27 | pub fn apply_givens_rotation_in_place( 28 | mut vec: PyReadwriteArray2, 29 | c: f64, 30 | s: Complex64, 31 | slice1: PyReadonlyArray1, 32 | slice2: PyReadonlyArray1, 33 | ) { 34 | let mut vec = vec.as_array_mut(); 35 | let slice1 = slice1.as_array(); 36 | let slice2 = slice2.as_array(); 37 | let shape = vec.shape(); 38 | let dim_b = shape[1] as i32; 39 | let s_abs = s.norm(); 40 | let angle = s.arg(); 41 | let phase = Complex64::new(angle.cos(), angle.sin()); 42 | let phase_conj = phase.conj(); 43 | 44 | Zip::from(&slice1).and(&slice2).for_each(|&i, &j| { 45 | let (mut row_i, mut row_j) = vec.multi_slice_mut((s![i, ..], s![j, ..])); 46 | match row_i.as_slice_mut() { 47 | Some(row_i) => match row_j.as_slice_mut() { 48 | Some(row_j) => unsafe { 49 | zscal(dim_b, phase_conj, row_i, 1); 50 | zdrot(dim_b, row_i, 1, row_j, 1, c, s_abs); 51 | zscal(dim_b, phase, row_i, 1); 52 | }, 53 | None => panic!( 54 | "Failed to convert ArrayBase to slice, possibly because the data was not contiguous and in standard order." 55 | ), 56 | }, 57 | None => panic!( 58 | "Failed to convert ArrayBase to slice, possibly because the data was not contiguous and in standard order." 59 | ), 60 | }; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /src/gates/phase_shift.rs: -------------------------------------------------------------------------------- 1 | // (C) Copyright IBM 2023 2 | // 3 | // This code is licensed under the Apache License, Version 2.0. You may 4 | // obtain a copy of this license in the LICENSE.txt file in the root directory 5 | // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | // 7 | // Any modifications or derivative works of this code must retain this 8 | // copyright notice, and modified files need to carry a notice indicating 9 | // that they have been altered from the originals. 10 | 11 | extern crate blas_src; 12 | 13 | use blas::zscal; 14 | use numpy::Complex64; 15 | use numpy::PyReadonlyArray1; 16 | use numpy::PyReadwriteArray2; 17 | use pyo3::prelude::*; 18 | 19 | /// Apply a phase shift to slices of a state vector. 20 | #[pyfunction] 21 | pub fn apply_phase_shift_in_place( 22 | mut vec: PyReadwriteArray2, 23 | phase: Complex64, 24 | indices: PyReadonlyArray1, 25 | ) { 26 | let mut vec = vec.as_array_mut(); 27 | let indices = indices.as_array(); 28 | let shape = vec.shape(); 29 | let dim_b = shape[1] as i32; 30 | 31 | indices.for_each(|&str0| { 32 | let mut target = vec.row_mut(str0); 33 | match target.as_slice_mut() { 34 | Some(target) => unsafe { 35 | zscal(dim_b, phase, target, 1); 36 | }, 37 | None => panic!( 38 | "Failed to convert ArrayBase to slice, possibly because the data was not contiguous and in standard order." 39 | ), 40 | }; 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // (C) Copyright IBM 2023 2 | // 3 | // This code is licensed under the Apache License, Version 2.0. You may 4 | // obtain a copy of this license in the LICENSE.txt file in the root directory 5 | // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | // 7 | // Any modifications or derivative works of this code must retain this 8 | // copyright notice, and modified files need to carry a notice indicating 9 | // that they have been altered from the originals. 10 | 11 | use pyo3::prelude::*; 12 | 13 | mod contract; 14 | mod fermion_operator; 15 | mod gates; 16 | 17 | /// Python module exposing Rust extensions. 18 | #[pymodule] 19 | fn _lib(m: &Bound<'_, PyModule>) -> PyResult<()> { 20 | m.add_function(wrap_pyfunction!( 21 | gates::phase_shift::apply_phase_shift_in_place, 22 | m 23 | )?)?; 24 | m.add_function(wrap_pyfunction!( 25 | gates::orbital_rotation::apply_givens_rotation_in_place, 26 | m 27 | )?)?; 28 | m.add_function(wrap_pyfunction!( 29 | gates::num_op_sum::apply_num_op_sum_evolution_in_place, 30 | m 31 | )?)?; 32 | m.add_function(wrap_pyfunction!( 33 | gates::diag_coulomb::apply_diag_coulomb_evolution_in_place_num_rep, 34 | m 35 | )?)?; 36 | m.add_function(wrap_pyfunction!( 37 | gates::diag_coulomb::apply_diag_coulomb_evolution_in_place_z_rep, 38 | m 39 | )?)?; 40 | m.add_function(wrap_pyfunction!( 41 | contract::diag_coulomb::contract_diag_coulomb_into_buffer_num_rep, 42 | m 43 | )?)?; 44 | m.add_function(wrap_pyfunction!( 45 | contract::diag_coulomb::contract_diag_coulomb_into_buffer_z_rep, 46 | m 47 | )?)?; 48 | m.add_function(wrap_pyfunction!( 49 | contract::num_op_sum::contract_num_op_sum_spin_into_buffer, 50 | m 51 | )?)?; 52 | m.add_class::()?; 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /tests/python/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/_slow/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/_slow/contract/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/_slow/contract/diag_coulomb_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for gates.""" 12 | 13 | from __future__ import annotations 14 | 15 | import math 16 | 17 | import numpy as np 18 | 19 | import ffsim 20 | from ffsim import cistring 21 | from ffsim._lib import ( 22 | contract_diag_coulomb_into_buffer_num_rep, 23 | contract_diag_coulomb_into_buffer_z_rep, 24 | ) 25 | from ffsim._slow.contract.diag_coulomb import ( 26 | contract_diag_coulomb_into_buffer_num_rep_slow, 27 | contract_diag_coulomb_into_buffer_z_rep_slow, 28 | ) 29 | 30 | 31 | def test_contract_diag_coulomb_into_buffer_num_rep_slow(): 32 | """Test contracting diag Coulomb operator.""" 33 | norb = 5 34 | rng = np.random.default_rng() 35 | for _ in range(5): 36 | n_alpha = rng.integers(1, norb + 1) 37 | n_beta = rng.integers(1, norb + 1) 38 | dim_a = math.comb(norb, n_alpha) 39 | dim_b = math.comb(norb, n_beta) 40 | occupations_a = cistring.gen_occslst(range(norb), n_alpha) 41 | occupations_b = cistring.gen_occslst(range(norb), n_beta) 42 | mat_aa = ffsim.random.random_real_symmetric_matrix(norb, seed=rng) 43 | mat_ab = ffsim.random.random_real_symmetric_matrix(norb, seed=rng) 44 | mat_bb = ffsim.random.random_real_symmetric_matrix(norb, seed=rng) 45 | vec = ffsim.random.random_state_vector(dim_a * dim_b, seed=rng).reshape( 46 | (dim_a, dim_b) 47 | ) 48 | out_slow = np.zeros_like(vec) 49 | out_fast = np.zeros_like(vec) 50 | contract_diag_coulomb_into_buffer_num_rep_slow( 51 | vec, 52 | mat_aa, 53 | mat_ab, 54 | mat_bb, 55 | norb=norb, 56 | occupations_a=occupations_a, 57 | occupations_b=occupations_b, 58 | out=out_slow, 59 | ) 60 | contract_diag_coulomb_into_buffer_num_rep( 61 | vec, 62 | mat_aa, 63 | mat_ab, 64 | mat_bb, 65 | norb=norb, 66 | occupations_a=occupations_a, 67 | occupations_b=occupations_b, 68 | out=out_fast, 69 | ) 70 | np.testing.assert_allclose(out_slow, out_fast) 71 | 72 | 73 | def test_contract_diag_coulomb_into_buffer_z_rep_slow(): 74 | """Test contracting diag Coulomb operator.""" 75 | norb = 5 76 | rng = np.random.default_rng() 77 | for _ in range(5): 78 | n_alpha = rng.integers(1, norb + 1) 79 | n_beta = rng.integers(1, norb + 1) 80 | dim_a = math.comb(norb, n_alpha) 81 | dim_b = math.comb(norb, n_beta) 82 | strings_a = cistring.make_strings(range(norb), n_alpha) 83 | strings_b = cistring.make_strings(range(norb), n_beta) 84 | mat_aa = ffsim.random.random_real_symmetric_matrix(norb, seed=rng) 85 | mat_ab = ffsim.random.random_real_symmetric_matrix(norb, seed=rng) 86 | mat_bb = ffsim.random.random_real_symmetric_matrix(norb, seed=rng) 87 | vec = ffsim.random.random_state_vector(dim_a * dim_b, seed=rng).reshape( 88 | (dim_a, dim_b) 89 | ) 90 | out_slow = np.zeros_like(vec) 91 | out_fast = np.zeros_like(vec) 92 | contract_diag_coulomb_into_buffer_z_rep_slow( 93 | vec, 94 | mat_aa, 95 | mat_ab, 96 | mat_bb, 97 | norb=norb, 98 | strings_a=strings_a, 99 | strings_b=strings_b, 100 | out=out_slow, 101 | ) 102 | contract_diag_coulomb_into_buffer_z_rep( 103 | vec, 104 | mat_aa, 105 | mat_ab, 106 | mat_bb, 107 | norb=norb, 108 | strings_a=strings_a, 109 | strings_b=strings_b, 110 | out=out_fast, 111 | ) 112 | np.testing.assert_allclose(out_slow, out_fast) 113 | -------------------------------------------------------------------------------- /tests/python/_slow/contract/num_op_sum_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for gates.""" 12 | 13 | from __future__ import annotations 14 | 15 | import math 16 | 17 | import numpy as np 18 | 19 | import ffsim 20 | from ffsim import cistring 21 | from ffsim._lib import contract_num_op_sum_spin_into_buffer 22 | from ffsim._slow.contract.num_op_sum import contract_num_op_sum_spin_into_buffer_slow 23 | 24 | 25 | def test_contract_num_op_sum_spin_into_buffer_slow(): 26 | """Test applying num op sum evolution.""" 27 | norb = 5 28 | rng = np.random.default_rng() 29 | for _ in range(5): 30 | n_alpha = rng.integers(1, norb + 1) 31 | n_beta = rng.integers(1, norb + 1) 32 | dim_a = math.comb(norb, n_alpha) 33 | dim_b = math.comb(norb, n_beta) 34 | occupations = cistring.gen_occslst(range(norb), n_alpha) 35 | coeffs = rng.uniform(size=norb) 36 | vec = ffsim.random.random_state_vector(dim_a * dim_b, seed=rng).reshape( 37 | (dim_a, dim_b) 38 | ) 39 | out_slow = np.zeros_like(vec) 40 | out_fast = np.zeros_like(vec) 41 | contract_num_op_sum_spin_into_buffer_slow( 42 | vec, coeffs, occupations=occupations, out=out_slow 43 | ) 44 | contract_num_op_sum_spin_into_buffer( 45 | vec, coeffs, occupations=occupations, out=out_fast 46 | ) 47 | np.testing.assert_allclose(out_slow, out_fast) 48 | -------------------------------------------------------------------------------- /tests/python/_slow/gates/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/_slow/gates/num_op_sum_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | from __future__ import annotations 12 | 13 | import math 14 | 15 | import numpy as np 16 | 17 | import ffsim 18 | from ffsim import cistring 19 | from ffsim._lib import apply_num_op_sum_evolution_in_place 20 | from ffsim._slow.gates.num_op_sum import apply_num_op_sum_evolution_in_place_slow 21 | 22 | 23 | def test_apply_num_op_sum_evolution_in_place_slow(): 24 | """Test applying num op sum evolution.""" 25 | norb = 5 26 | rng = np.random.default_rng() 27 | for _ in range(5): 28 | n_alpha = rng.integers(1, norb + 1) 29 | n_beta = rng.integers(1, norb + 1) 30 | dim_a = math.comb(norb, n_alpha) 31 | dim_b = math.comb(norb, n_beta) 32 | occupations = cistring.gen_occslst(range(norb), n_alpha) 33 | exponents = rng.uniform(0, 2 * np.pi, size=norb) 34 | phases = np.exp(1j * exponents) 35 | vec_slow = ffsim.random.random_state_vector(dim_a * dim_b, seed=rng).reshape( 36 | (dim_a, dim_b) 37 | ) 38 | vec_fast = vec_slow.copy() 39 | apply_num_op_sum_evolution_in_place_slow( 40 | vec_slow, phases, occupations=occupations 41 | ) 42 | apply_num_op_sum_evolution_in_place( 43 | vec_fast, 44 | phases, 45 | occupations=occupations, 46 | ) 47 | np.testing.assert_allclose(vec_slow, vec_fast) 48 | -------------------------------------------------------------------------------- /tests/python/_slow/gates/orbital_rotation_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | from __future__ import annotations 12 | 13 | import math 14 | 15 | import numpy as np 16 | 17 | import ffsim 18 | from ffsim._lib import ( 19 | apply_givens_rotation_in_place, 20 | ) 21 | from ffsim._slow.gates.orbital_rotation import apply_givens_rotation_in_place_slow 22 | from ffsim.gates.orbital_rotation import _zero_one_subspace_indices 23 | 24 | 25 | def test_apply_givens_rotation_in_place_slow(): 26 | """Test applying Givens rotation.""" 27 | norb = 5 28 | rng = np.random.default_rng() 29 | for _ in range(5): 30 | n_alpha = rng.integers(1, norb + 1) 31 | n_beta = rng.integers(1, norb + 1) 32 | dim_a = math.comb(norb, n_alpha) 33 | dim_b = math.comb(norb, n_beta) 34 | vec_slow = ffsim.random.random_state_vector(dim_a * dim_b, seed=rng).reshape( 35 | (dim_a, dim_b) 36 | ) 37 | vec_fast = vec_slow.copy() 38 | c = rng.uniform(0, 1) 39 | s = (1j) ** rng.uniform(0, 4) * np.sqrt(1 - c**2) 40 | indices = _zero_one_subspace_indices(norb, n_alpha, (1, 3)) 41 | slice1 = indices[: len(indices) // 2] 42 | slice2 = indices[len(indices) // 2 :] 43 | apply_givens_rotation_in_place_slow(vec_slow, c, s, slice1, slice2) 44 | apply_givens_rotation_in_place(vec_fast, c, s, slice1, slice2) 45 | np.testing.assert_allclose(vec_slow, vec_fast) 46 | -------------------------------------------------------------------------------- /tests/python/contract/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/contract/num_op_sum_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for num op sum contraction.""" 12 | 13 | from __future__ import annotations 14 | 15 | from collections.abc import Sequence 16 | from typing import cast 17 | 18 | import numpy as np 19 | import pytest 20 | 21 | import ffsim 22 | 23 | 24 | @pytest.mark.parametrize("norb", [4, 5]) 25 | def test_contract_num_op_sum(norb: int): 26 | """Test contracting sum of number operators.""" 27 | rng = np.random.default_rng() 28 | for _ in range(50): 29 | n_alpha = int(rng.integers(1, norb + 1)) 30 | n_beta = int(rng.integers(1, norb + 1)) 31 | nelec = (n_alpha, n_beta) 32 | alpha_orbitals = cast(Sequence[int], rng.choice(norb, n_alpha, replace=False)) 33 | beta_orbitals = cast(Sequence[int], rng.choice(norb, n_beta, replace=False)) 34 | occupied_orbitals = (alpha_orbitals, beta_orbitals) 35 | state = ffsim.slater_determinant(norb, occupied_orbitals) 36 | 37 | coeffs = rng.standard_normal(norb) 38 | result = ffsim.contract.contract_num_op_sum( 39 | state, coeffs, norb=norb, nelec=nelec 40 | ) 41 | 42 | eig = 0 43 | for i in range(norb): 44 | for sigma in range(2): 45 | if i in occupied_orbitals[sigma]: 46 | eig += coeffs[i] 47 | expected = eig * state 48 | 49 | np.testing.assert_allclose(result, expected) 50 | 51 | 52 | def test_num_op_sum_to_linop(): 53 | """Test converting a num op sum to a linear operator.""" 54 | norb = 5 55 | rng = np.random.default_rng() 56 | n_alpha = int(rng.integers(1, norb + 1)) 57 | n_beta = int(rng.integers(1, norb + 1)) 58 | nelec = (n_alpha, n_beta) 59 | dim = ffsim.dim(norb, nelec) 60 | 61 | coeffs = rng.standard_normal(norb) 62 | orbital_rotation = ffsim.random.random_unitary(norb, seed=rng) 63 | vec = ffsim.random.random_state_vector(dim, seed=rng) 64 | 65 | linop = ffsim.contract.num_op_sum_linop( 66 | coeffs, norb=norb, nelec=nelec, orbital_rotation=orbital_rotation 67 | ) 68 | result = linop @ vec 69 | 70 | expected = ffsim.apply_orbital_rotation( 71 | vec, orbital_rotation.T.conj(), norb=norb, nelec=nelec 72 | ) 73 | expected = ffsim.contract.contract_num_op_sum( 74 | expected, coeffs, norb=norb, nelec=nelec 75 | ) 76 | expected = ffsim.apply_orbital_rotation( 77 | expected, orbital_rotation, norb=norb, nelec=nelec 78 | ) 79 | 80 | np.testing.assert_allclose(result, expected) 81 | -------------------------------------------------------------------------------- /tests/python/gates/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/gates/num_op_sum_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for diagonal Coulomb evolution.""" 12 | 13 | from __future__ import annotations 14 | 15 | import itertools 16 | 17 | import numpy as np 18 | import pytest 19 | import scipy.linalg 20 | import scipy.sparse.linalg 21 | 22 | import ffsim 23 | from ffsim.spin import pair_for_spin 24 | 25 | 26 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nocc(range(5))) 27 | def test_apply_num_op_sum_evolution_spinless(norb: int, nelec: int): 28 | """Test applying time evolution of sum of number operators, spinless.""" 29 | rng = np.random.default_rng() 30 | coeffs = rng.standard_normal(norb) 31 | time = rng.standard_normal() 32 | for occupied_orbitals in itertools.combinations(range(norb), nelec): 33 | state = ffsim.slater_determinant(norb, occupied_orbitals) 34 | original_state = state.copy() 35 | result = ffsim.apply_num_op_sum_evolution(state, coeffs, time, norb, nelec) 36 | eig = sum(coeffs[list(occupied_orbitals)]) 37 | expected = np.exp(-1j * eig * time) * state 38 | np.testing.assert_allclose(result, expected) 39 | np.testing.assert_allclose(state, original_state) 40 | 41 | 42 | @pytest.mark.parametrize( 43 | "norb, nelec, spin", ffsim.testing.generate_norb_nelec_spin(range(5)) 44 | ) 45 | def test_apply_num_op_sum_evolution_spinful( 46 | norb: int, nelec: tuple[int, int], spin: ffsim.Spin 47 | ): 48 | """Test applying time evolution of sum of number operators.""" 49 | rng = np.random.default_rng() 50 | coeffs = rng.standard_normal(norb) 51 | time = rng.standard_normal() 52 | n_alpha, n_beta = nelec 53 | for alpha_orbitals in itertools.combinations(range(norb), n_alpha): 54 | for beta_orbitals in itertools.combinations(range(norb), n_beta): 55 | occupied_orbitals = (alpha_orbitals, beta_orbitals) 56 | state = ffsim.slater_determinant(norb, occupied_orbitals) 57 | original_state = state.copy() 58 | result = ffsim.apply_num_op_sum_evolution( 59 | state, pair_for_spin(coeffs, spin), time, norb, nelec 60 | ) 61 | eig = 0 62 | if spin & ffsim.Spin.ALPHA: 63 | eig += sum(coeffs[list(occupied_orbitals[0])]) 64 | if spin & ffsim.Spin.BETA: 65 | eig += sum(coeffs[list(occupied_orbitals[1])]) 66 | expected = np.exp(-1j * eig * time) * state 67 | np.testing.assert_allclose(result, expected) 68 | np.testing.assert_allclose(state, original_state) 69 | 70 | 71 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nelec(range(1, 5))) 72 | def test_apply_quadratic_hamiltonian_evolution(norb: int, nelec: tuple[int, int]): 73 | """Test applying time evolution of a quadratic Hamiltonian.""" 74 | rng = np.random.default_rng() 75 | mat = ffsim.random.random_hermitian(norb, seed=rng) 76 | eigs, vecs = scipy.linalg.eigh(mat) 77 | time = rng.standard_normal() 78 | dim = ffsim.dim(norb, nelec) 79 | vec = ffsim.random.random_state_vector(dim, seed=rng) 80 | result = ffsim.apply_num_op_sum_evolution( 81 | vec, eigs, time, norb, nelec, orbital_rotation=vecs 82 | ) 83 | op = ffsim.contract.one_body_linop(mat, norb=norb, nelec=nelec) 84 | expected = scipy.sparse.linalg.expm_multiply( 85 | -1j * time * op, vec, traceA=-1j * time * np.sum(np.abs(mat)) 86 | ) 87 | np.testing.assert_allclose(result, expected) 88 | -------------------------------------------------------------------------------- /tests/python/hamiltonians/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/linalg/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/linalg/givens_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for givens decomposition utilities.""" 12 | 13 | from __future__ import annotations 14 | 15 | from pathlib import Path 16 | 17 | import numpy as np 18 | import pytest 19 | from scipy.linalg.lapack import zrot 20 | 21 | import ffsim 22 | from ffsim.linalg import givens_decomposition 23 | 24 | 25 | @pytest.mark.parametrize("dim", range(6)) 26 | def test_givens_decomposition_definition(dim: int): 27 | """Test Givens decomposition definition.""" 28 | rng = np.random.default_rng() 29 | for _ in range(3): 30 | mat = ffsim.random.random_unitary(dim, seed=rng) 31 | givens_rotations, phase_shifts = givens_decomposition(mat) 32 | reconstructed = np.diag(phase_shifts) 33 | for c, s, i, j in givens_rotations[::-1]: 34 | givens_mat = np.eye(dim, dtype=complex) 35 | givens_mat[np.ix_((i, j), (i, j))] = [ 36 | [c, s], 37 | [-s.conjugate(), c], 38 | ] 39 | reconstructed @= givens_mat.conj() 40 | np.testing.assert_allclose(reconstructed, mat) 41 | assert len(givens_rotations) == dim * (dim - 1) // 2 42 | 43 | 44 | @pytest.mark.parametrize("dim", range(6)) 45 | def test_givens_decomposition_reconstruct(dim: int): 46 | """Test Givens decomposition reconstruction of original matrix.""" 47 | rng = np.random.default_rng() 48 | for _ in range(3): 49 | mat = ffsim.random.random_unitary(dim, seed=rng) 50 | givens_rotations, phase_shifts = givens_decomposition(mat) 51 | reconstructed = np.eye(dim, dtype=complex) 52 | for i, phase_shift in enumerate(phase_shifts): 53 | reconstructed[i] *= phase_shift 54 | for c, s, i, j in givens_rotations[::-1]: 55 | reconstructed[:, j], reconstructed[:, i] = zrot( 56 | reconstructed[:, j], reconstructed[:, i], c, s.conjugate() 57 | ) 58 | np.testing.assert_allclose(reconstructed, mat) 59 | 60 | 61 | @pytest.mark.parametrize("dim", range(6)) 62 | def test_givens_decomposition_identity(dim: int): 63 | """Test Givens decomposition on identity matrix.""" 64 | mat = np.eye(dim) 65 | givens_rotations, phase_shifts = givens_decomposition(mat) 66 | assert all(phase_shifts == 1) 67 | assert len(givens_rotations) == 0 68 | 69 | 70 | @pytest.mark.parametrize("norb", range(6)) 71 | def test_givens_decomposition_no_side_effects(norb: int): 72 | """Test that the Givens decomposition doesn't modify the original matrix.""" 73 | rng = np.random.default_rng() 74 | for _ in range(3): 75 | mat = ffsim.random.random_unitary(norb, seed=rng) 76 | original_mat = mat.copy() 77 | _ = givens_decomposition(mat) 78 | 79 | assert ffsim.linalg.is_unitary(original_mat) 80 | assert ffsim.linalg.is_unitary(mat) 81 | np.testing.assert_allclose(mat, original_mat, atol=1e-12) 82 | 83 | 84 | def test_givens_decomposition_no_side_effects_special_case(): 85 | """Test that the Givens decomposition doesn't modify the original matrix.""" 86 | datadir = Path(__file__).parent.parent / "test_data" 87 | filepath = datadir / "orbital_rotation-0.npy" 88 | 89 | with open(filepath, "rb") as f: 90 | mat = np.load(f) 91 | assert ffsim.linalg.is_unitary(mat, atol=1e-12) 92 | 93 | original_mat = mat.copy() 94 | _ = givens_decomposition(mat) 95 | 96 | assert ffsim.linalg.is_unitary(original_mat) 97 | assert ffsim.linalg.is_unitary(mat) 98 | np.testing.assert_allclose(mat, original_mat, atol=1e-12) 99 | -------------------------------------------------------------------------------- /tests/python/linalg/linalg_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for linear algebra utilities.""" 12 | 13 | from __future__ import annotations 14 | 15 | import itertools 16 | 17 | import numpy as np 18 | import scipy.sparse 19 | 20 | import ffsim 21 | 22 | 23 | def test_lup(): 24 | dim = 5 25 | rng = np.random.default_rng() 26 | mat = rng.standard_normal((dim, dim)) + 1j * rng.standard_normal((dim, dim)) 27 | ell, u, p = ffsim.linalg.lup(mat) 28 | np.testing.assert_allclose(ell @ u @ p, mat) 29 | np.testing.assert_allclose(np.diagonal(ell), np.ones(dim)) 30 | 31 | 32 | def test_reduced_matrix(): 33 | big_dim = 20 34 | small_dim = 5 35 | rng = np.random.default_rng() 36 | mat = scipy.sparse.random(big_dim, big_dim, random_state=rng) 37 | vecs = [ 38 | rng.standard_normal(big_dim) + 1j * rng.standard_normal(big_dim) 39 | for _ in range(small_dim) 40 | ] 41 | reduced_mat = ffsim.linalg.reduced_matrix(mat, vecs) 42 | for i, j in itertools.product(range(small_dim), repeat=2): 43 | actual = reduced_mat[i, j] 44 | expected = np.vdot(vecs[i], mat @ vecs[j]) 45 | np.testing.assert_allclose(actual, expected) 46 | 47 | 48 | def test_match_global_phase(): 49 | a = np.array([[1, 2, 3], [4, 5, 6]]) 50 | b = 1j * a 51 | c, d = ffsim.linalg.match_global_phase(a, b) 52 | np.testing.assert_allclose(c, d) 53 | 54 | a = np.array([[1, 2, 3], [4, 5, 6]]) 55 | b = 2j * a 56 | c, d = ffsim.linalg.match_global_phase(a, b) 57 | np.testing.assert_allclose(2 * c, d) 58 | -------------------------------------------------------------------------------- /tests/python/operators/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/operators/common_operators_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for common operators.""" 12 | 13 | from __future__ import annotations 14 | 15 | import pytest 16 | 17 | import ffsim 18 | 19 | 20 | @pytest.mark.parametrize("orb", range(4)) 21 | def test_number_operator(orb: int): 22 | op = ffsim.number_operator(orb) 23 | assert dict(op) == { 24 | (ffsim.cre_a(orb), ffsim.des_a(orb)): 1, 25 | (ffsim.cre_b(orb), ffsim.des_b(orb)): 1, 26 | } 27 | 28 | op = ffsim.number_operator(orb, spin=ffsim.Spin.ALPHA) 29 | assert dict(op) == { 30 | (ffsim.cre_a(orb), ffsim.des_a(orb)): 1, 31 | } 32 | 33 | op = ffsim.number_operator(orb, spin=ffsim.Spin.BETA) 34 | assert dict(op) == { 35 | (ffsim.cre_b(orb), ffsim.des_b(orb)): 1, 36 | } 37 | -------------------------------------------------------------------------------- /tests/python/operators/fermion_action_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for FermionAction.""" 12 | 13 | from __future__ import annotations 14 | 15 | import ffsim 16 | 17 | 18 | def test_fermion_action(): 19 | assert ( 20 | ffsim.cre_a(0) 21 | == ffsim.cre(False, 0) 22 | == ffsim.FermionAction(action=True, orb=0, spin=False) 23 | == (True, False, 0) 24 | ) 25 | assert ( 26 | ffsim.cre_b(1) 27 | == ffsim.cre(True, 1) 28 | == ffsim.FermionAction(action=True, orb=1, spin=True) 29 | == (True, True, 1) 30 | ) 31 | assert ( 32 | ffsim.des_a(-1) 33 | == ffsim.des(False, -1) 34 | == ffsim.FermionAction(action=False, orb=-1, spin=False) 35 | == (False, False, -1) 36 | ) 37 | assert ( 38 | ffsim.des_b(2) 39 | == ffsim.des(True, 2) 40 | == ffsim.FermionAction(action=False, orb=2, spin=True) 41 | == (False, True, 2) 42 | ) 43 | -------------------------------------------------------------------------------- /tests/python/optimize/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/qiskit/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/qiskit/gates/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/qiskit/gates/diag_coulomb_trotter_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for diagonal Coulomb Trotter evolution gate.""" 12 | 13 | from __future__ import annotations 14 | 15 | import numpy as np 16 | import pytest 17 | from qiskit.quantum_info import Statevector 18 | 19 | import ffsim 20 | 21 | 22 | @pytest.mark.parametrize( 23 | "norb, nelec, time, n_steps, order", 24 | [ 25 | (4, (2, 2), 0.1, 1, 0), 26 | (4, (2, 2), 0.1, 2, 0), 27 | (4, (2, 2), 0.1, 1, 1), 28 | (4, (2, 2), 0.1, 1, 2), 29 | ], 30 | ) 31 | def test_random( 32 | norb: int, 33 | nelec: tuple[int, int], 34 | time: float, 35 | n_steps: int, 36 | order: int, 37 | ): 38 | """Test random gate gives correct output state.""" 39 | rng = np.random.default_rng() 40 | dim = ffsim.dim(norb, nelec) 41 | time = 1.0 42 | for _ in range(3): 43 | hamiltonian = ffsim.random.random_diagonal_coulomb_hamiltonian(norb, seed=rng) 44 | gate = ffsim.qiskit.SimulateTrotterDiagCoulombSplitOpJW( 45 | hamiltonian, time, n_steps=n_steps, order=order 46 | ) 47 | 48 | small_vec = ffsim.random.random_state_vector(dim, seed=rng) 49 | big_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec( 50 | small_vec, norb=norb, nelec=nelec 51 | ) 52 | 53 | statevec = Statevector(big_vec).evolve(gate) 54 | result = ffsim.qiskit.qiskit_vec_to_ffsim_vec( 55 | np.array(statevec), norb=norb, nelec=nelec 56 | ) 57 | 58 | expected = ffsim.simulate_trotter_diag_coulomb_split_op( 59 | small_vec, 60 | hamiltonian, 61 | time=time, 62 | norb=norb, 63 | nelec=nelec, 64 | n_steps=n_steps, 65 | order=order, 66 | ) 67 | 68 | np.testing.assert_allclose(result, expected) 69 | -------------------------------------------------------------------------------- /tests/python/qiskit/gates/double_factorized_trotter_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for double-factorized Trotter evolution gate.""" 12 | 13 | from __future__ import annotations 14 | 15 | import numpy as np 16 | import pytest 17 | from qiskit.quantum_info import Statevector 18 | 19 | import ffsim 20 | 21 | 22 | @pytest.mark.parametrize( 23 | "norb, nelec, time, n_steps, order, z_representation", 24 | [ 25 | (4, (2, 2), 0.1, 1, 0, False), 26 | (4, (2, 2), 0.1, 1, 0, True), 27 | (4, (2, 2), 0.1, 2, 0, False), 28 | (4, (2, 2), 0.1, 2, 0, True), 29 | (4, (2, 2), 0.1, 1, 1, False), 30 | (4, (2, 2), 0.1, 1, 1, True), 31 | (4, (2, 2), 0.1, 1, 2, False), 32 | (4, (2, 2), 0.1, 1, 2, True), 33 | ], 34 | ) 35 | def test_random( 36 | norb: int, 37 | nelec: tuple[int, int], 38 | time: float, 39 | n_steps: int, 40 | order: int, 41 | z_representation: bool, 42 | ): 43 | """Test random gate gives correct output state.""" 44 | rng = np.random.default_rng() 45 | dim = ffsim.dim(norb, nelec) 46 | time = 1.0 47 | for _ in range(3): 48 | hamiltonian = ffsim.random.random_double_factorized_hamiltonian( 49 | norb, rank=norb, z_representation=z_representation, seed=rng 50 | ) 51 | gate = ffsim.qiskit.SimulateTrotterDoubleFactorizedJW( 52 | hamiltonian, time, n_steps=n_steps, order=order 53 | ) 54 | 55 | small_vec = ffsim.random.random_state_vector(dim, seed=rng) 56 | big_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec( 57 | small_vec, norb=norb, nelec=nelec 58 | ) 59 | 60 | statevec = Statevector(big_vec).evolve(gate) 61 | result = ffsim.qiskit.qiskit_vec_to_ffsim_vec( 62 | np.array(statevec), norb=norb, nelec=nelec 63 | ) 64 | 65 | expected = ffsim.simulate_trotter_double_factorized( 66 | small_vec, 67 | hamiltonian, 68 | time=time, 69 | norb=norb, 70 | nelec=nelec, 71 | n_steps=n_steps, 72 | order=order, 73 | ) 74 | 75 | np.testing.assert_allclose(result, expected) 76 | -------------------------------------------------------------------------------- /tests/python/qiskit/gates/givens_ansatz_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for Givens rotation ansatz gate.""" 12 | 13 | from __future__ import annotations 14 | 15 | import numpy as np 16 | import pytest 17 | from qiskit.quantum_info import Statevector 18 | 19 | import ffsim 20 | 21 | 22 | def brickwork(norb: int, n_layers: int): 23 | for i in range(n_layers): 24 | for j in range(i % 2, norb - 1, 2): 25 | yield (j, j + 1) 26 | 27 | 28 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nelec(range(5))) 29 | def test_random_spinful(norb: int, nelec: tuple[int, int]): 30 | """Test random Givens rotation ansatz gives correct output state.""" 31 | rng = np.random.default_rng() 32 | dim = ffsim.dim(norb, nelec) 33 | for _ in range(3): 34 | interaction_pairs = list(brickwork(norb, norb)) 35 | thetas = rng.uniform(-np.pi, np.pi, size=len(interaction_pairs)) 36 | phis = rng.uniform(-np.pi, np.pi, size=len(interaction_pairs)) 37 | phase_angles = rng.uniform(-np.pi, np.pi, size=norb) 38 | 39 | givens_ansatz_op = ffsim.GivensAnsatzOp( 40 | norb, interaction_pairs, thetas=thetas, phis=phis, phase_angles=phase_angles 41 | ) 42 | gate = ffsim.qiskit.GivensAnsatzOpJW(givens_ansatz_op) 43 | 44 | small_vec = ffsim.random.random_state_vector(dim, seed=rng) 45 | big_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec( 46 | small_vec, norb=norb, nelec=nelec 47 | ) 48 | 49 | statevec = Statevector(big_vec).evolve(gate) 50 | result = ffsim.qiskit.qiskit_vec_to_ffsim_vec( 51 | np.array(statevec), norb=norb, nelec=nelec 52 | ) 53 | 54 | expected = ffsim.apply_unitary( 55 | small_vec, givens_ansatz_op, norb=norb, nelec=nelec 56 | ) 57 | 58 | np.testing.assert_allclose(result, expected) 59 | 60 | 61 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nocc(range(5))) 62 | def test_random_spinless(norb: int, nelec: int): 63 | """Test random spinless Givens rotation ansatz gives correct output state.""" 64 | rng = np.random.default_rng() 65 | dim = ffsim.dim(norb, nelec) 66 | for _ in range(3): 67 | interaction_pairs = list(brickwork(norb, norb)) 68 | thetas = rng.uniform(-np.pi, np.pi, size=len(interaction_pairs)) 69 | phis = rng.uniform(-np.pi, np.pi, size=len(interaction_pairs)) 70 | phase_angles = rng.uniform(-np.pi, np.pi, size=norb) 71 | 72 | givens_ansatz_op = ffsim.GivensAnsatzOp( 73 | norb, interaction_pairs, thetas=thetas, phis=phis, phase_angles=phase_angles 74 | ) 75 | gate = ffsim.qiskit.GivensAnsatzOpSpinlessJW(givens_ansatz_op) 76 | assert gate.num_qubits == norb 77 | 78 | small_vec = ffsim.random.random_state_vector(dim, seed=rng) 79 | big_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec( 80 | small_vec, norb=norb, nelec=nelec 81 | ) 82 | 83 | statevec = Statevector(big_vec).evolve(gate) 84 | result = ffsim.qiskit.qiskit_vec_to_ffsim_vec( 85 | np.array(statevec), norb=norb, nelec=nelec 86 | ) 87 | 88 | expected = ffsim.apply_unitary( 89 | small_vec, givens_ansatz_op, norb=norb, nelec=nelec 90 | ) 91 | 92 | np.testing.assert_allclose(result, expected) 93 | -------------------------------------------------------------------------------- /tests/python/qiskit/gates/num_num_ansatz_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for number-number interaction ansatz gate.""" 12 | 13 | from __future__ import annotations 14 | 15 | import itertools 16 | 17 | import numpy as np 18 | import pytest 19 | from qiskit.quantum_info import Statevector 20 | 21 | import ffsim 22 | 23 | 24 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nelec(range(5))) 25 | def test_random_num_num_ansatz(norb: int, nelec: tuple[int, int]): 26 | """Test random number-number interaction ansatz gives correct output state.""" 27 | rng = np.random.default_rng() 28 | dim = ffsim.dim(norb, nelec) 29 | for _ in range(3): 30 | pairs_aa = list(itertools.combinations_with_replacement(range(norb), 2)) 31 | pairs_ab = list(itertools.combinations_with_replacement(range(norb), 2)) 32 | thetas_aa = rng.uniform(-np.pi, np.pi, size=len(pairs_aa)) 33 | thetas_ab = rng.uniform(-np.pi, np.pi, size=len(pairs_ab)) 34 | num_num_ansatz_op = ffsim.NumNumAnsatzOpSpinBalanced( 35 | norb, interaction_pairs=(pairs_aa, pairs_ab), thetas=(thetas_aa, thetas_ab) 36 | ) 37 | gate = ffsim.qiskit.NumNumAnsatzOpSpinBalancedJW(num_num_ansatz_op) 38 | 39 | small_vec = ffsim.random.random_state_vector(dim, seed=rng) 40 | big_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec( 41 | small_vec, norb=norb, nelec=nelec 42 | ) 43 | 44 | statevec = Statevector(big_vec).evolve(gate) 45 | result = ffsim.qiskit.qiskit_vec_to_ffsim_vec( 46 | np.array(statevec), norb=norb, nelec=nelec 47 | ) 48 | 49 | expected = ffsim.apply_unitary( 50 | small_vec, num_num_ansatz_op, norb=norb, nelec=nelec 51 | ) 52 | 53 | np.testing.assert_allclose(result, expected) 54 | -------------------------------------------------------------------------------- /tests/python/qiskit/gates/ucj_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for unitary cluster Jastrow gate.""" 12 | 13 | from __future__ import annotations 14 | 15 | import numpy as np 16 | import pytest 17 | from qiskit.quantum_info import Statevector 18 | 19 | import ffsim 20 | 21 | 22 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nelec(range(5))) 23 | def test_random_ucj_op_spin_unbalanced(norb: int, nelec: tuple[int, int]): 24 | """Test random spin-unbalanced UCJ gate gives correct output state.""" 25 | rng = np.random.default_rng() 26 | n_reps = 3 27 | dim = ffsim.dim(norb, nelec) 28 | for _ in range(3): 29 | ucj_op = ffsim.random.random_ucj_op_spin_unbalanced( 30 | norb, n_reps=n_reps, with_final_orbital_rotation=True, seed=rng 31 | ) 32 | gate = ffsim.qiskit.UCJOpSpinUnbalancedJW(ucj_op) 33 | 34 | small_vec = ffsim.random.random_state_vector(dim, seed=rng) 35 | big_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec( 36 | small_vec, norb=norb, nelec=nelec 37 | ) 38 | 39 | statevec = Statevector(big_vec).evolve(gate) 40 | result = ffsim.qiskit.qiskit_vec_to_ffsim_vec( 41 | np.array(statevec), norb=norb, nelec=nelec 42 | ) 43 | 44 | expected = ffsim.apply_unitary(small_vec, ucj_op, norb=norb, nelec=nelec) 45 | 46 | np.testing.assert_allclose(result, expected) 47 | 48 | 49 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nelec(range(5))) 50 | def test_random_ucj_op_spin_balanced(norb: int, nelec: tuple[int, int]): 51 | """Test random spin-balanced UCJ gate gives correct output state.""" 52 | rng = np.random.default_rng() 53 | n_reps = 3 54 | dim = ffsim.dim(norb, nelec) 55 | for _ in range(3): 56 | ucj_op = ffsim.random.random_ucj_op_spin_balanced( 57 | norb, n_reps=n_reps, with_final_orbital_rotation=True, seed=rng 58 | ) 59 | gate = ffsim.qiskit.UCJOpSpinBalancedJW(ucj_op) 60 | 61 | small_vec = ffsim.random.random_state_vector(dim, seed=rng) 62 | big_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec( 63 | small_vec, norb=norb, nelec=nelec 64 | ) 65 | 66 | statevec = Statevector(big_vec).evolve(gate) 67 | result = ffsim.qiskit.qiskit_vec_to_ffsim_vec( 68 | np.array(statevec), norb=norb, nelec=nelec 69 | ) 70 | 71 | expected = ffsim.apply_unitary(small_vec, ucj_op, norb=norb, nelec=nelec) 72 | 73 | np.testing.assert_allclose(result, expected) 74 | 75 | 76 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nocc(range(5))) 77 | def test_random_ucj_op_spinless(norb: int, nelec: int): 78 | """Test random spin-balanced UCJ gate gives correct output state.""" 79 | rng = np.random.default_rng() 80 | n_reps = 3 81 | dim = ffsim.dim(norb, nelec) 82 | for _ in range(3): 83 | ucj_op = ffsim.random.random_ucj_op_spinless( 84 | norb, n_reps=n_reps, with_final_orbital_rotation=True, seed=rng 85 | ) 86 | gate = ffsim.qiskit.UCJOpSpinlessJW(ucj_op) 87 | 88 | small_vec = ffsim.random.random_state_vector(dim, seed=rng) 89 | big_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec( 90 | small_vec, norb=norb, nelec=nelec 91 | ) 92 | 93 | statevec = Statevector(big_vec).evolve(gate) 94 | result = ffsim.qiskit.qiskit_vec_to_ffsim_vec( 95 | np.array(statevec), norb=norb, nelec=nelec 96 | ) 97 | 98 | expected = ffsim.apply_unitary(small_vec, ucj_op, norb=norb, nelec=nelec) 99 | 100 | np.testing.assert_allclose(result, expected) 101 | -------------------------------------------------------------------------------- /tests/python/qiskit/jordan_wigner_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for Jordan-Wigner transformation.""" 12 | 13 | import numpy as np 14 | import pytest 15 | 16 | import ffsim 17 | import ffsim.random.random 18 | 19 | 20 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nelec(range(5))) 21 | def test_random(norb: int, nelec: tuple[int, int]): 22 | """Test on random fermion Hamiltonian.""" 23 | rng = np.random.default_rng(4482) 24 | op = ffsim.random.random_fermion_hamiltonian(norb, seed=rng) 25 | linop = ffsim.linear_operator(op, norb=norb, nelec=nelec) 26 | vec = ffsim.random.random_state_vector(ffsim.dim(norb, nelec), seed=rng) 27 | expected_result = ffsim.qiskit.ffsim_vec_to_qiskit_vec(linop @ vec, norb, nelec) 28 | 29 | qubit_op = ffsim.qiskit.jordan_wigner(op) 30 | qubit_op_sparse = qubit_op.to_matrix(sparse=True) 31 | actual_result = qubit_op_sparse @ ffsim.qiskit.ffsim_vec_to_qiskit_vec( 32 | vec, norb, nelec 33 | ) 34 | np.testing.assert_allclose(actual_result, expected_result, atol=1e-12) 35 | 36 | qubit_op = ffsim.qiskit.jordan_wigner(op, norb=norb) 37 | qubit_op_sparse = qubit_op.to_matrix(sparse=True) 38 | actual_result = qubit_op_sparse @ ffsim.qiskit.ffsim_vec_to_qiskit_vec( 39 | vec, norb, nelec 40 | ) 41 | np.testing.assert_allclose(actual_result, expected_result, atol=1e-12) 42 | 43 | 44 | def test_more_norb(): 45 | """Test specifying more spatial orbitals than are present in the operator.""" 46 | op = ffsim.FermionOperator( 47 | { 48 | (ffsim.cre_a(1), ffsim.cre_b(1)): 1.0, 49 | (ffsim.cre_a(2), ffsim.cre_b(2)): 1.0, 50 | } 51 | ) 52 | qubit_op = ffsim.qiskit.jordan_wigner(op, norb=5) 53 | assert sorted(qubit_op.to_sparse_list()) == sorted( 54 | [ 55 | ("YZZZZX", [1, 2, 3, 4, 5, 6], np.complex128(-0.25j)), 56 | ("YZZZZY", [1, 2, 3, 4, 5, 6], np.complex128(-0.25 + 0j)), 57 | ("XZZZZX", [1, 2, 3, 4, 5, 6], np.complex128(0.25 + 0j)), 58 | ("XZZZZY", [1, 2, 3, 4, 5, 6], np.complex128(-0.25j)), 59 | ("YZZZZX", [2, 3, 4, 5, 6, 7], np.complex128(-0.25j)), 60 | ("YZZZZY", [2, 3, 4, 5, 6, 7], np.complex128(-0.25 + 0j)), 61 | ("XZZZZX", [2, 3, 4, 5, 6, 7], np.complex128(0.25 + 0j)), 62 | ("XZZZZY", [2, 3, 4, 5, 6, 7], np.complex128(-0.25j)), 63 | ] 64 | ) 65 | 66 | 67 | def test_bad_norb(): 68 | """Test passing bad number of spatial orbitals raises errors.""" 69 | op = ffsim.FermionOperator({(ffsim.cre_a(3),): 1.0}) 70 | with pytest.raises(ValueError, match="non-negative"): 71 | _ = ffsim.qiskit.jordan_wigner(op, norb=-1) 72 | with pytest.raises(ValueError, match="fewer"): 73 | _ = ffsim.qiskit.jordan_wigner(op, norb=3) 74 | 75 | 76 | def test_hubbard(): 77 | """Test on Hubbard model""" 78 | rng = np.random.default_rng(7431) 79 | norb_x = 2 80 | norb_y = 2 81 | norb = norb_x * norb_y 82 | nelec = (norb // 2, norb // 2) 83 | op = ffsim.fermi_hubbard_2d( 84 | norb_x=norb_x, 85 | norb_y=norb_y, 86 | tunneling=rng.uniform(-10, 10), 87 | interaction=rng.uniform(-10, 10), 88 | chemical_potential=rng.uniform(-10, 10), 89 | nearest_neighbor_interaction=rng.uniform(-10, 10), 90 | periodic=False, 91 | ) 92 | linop = ffsim.linear_operator(op, norb=norb, nelec=nelec) 93 | vec = ffsim.random.random_state_vector(ffsim.dim(norb, nelec), seed=rng) 94 | qubit_op = ffsim.qiskit.jordan_wigner(op) 95 | qubit_op_sparse = qubit_op.to_matrix(sparse=True) 96 | actual_result = qubit_op_sparse @ ffsim.qiskit.ffsim_vec_to_qiskit_vec( 97 | vec, norb, nelec 98 | ) 99 | expected_result = ffsim.qiskit.ffsim_vec_to_qiskit_vec(linop @ vec, norb, nelec) 100 | np.testing.assert_allclose(actual_result, expected_result) 101 | -------------------------------------------------------------------------------- /tests/python/qiskit/transpiler_passes/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/qiskit/transpiler_passes/drop_negligible_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for DropNegligible transpiler pass.""" 12 | 13 | import numpy as np 14 | from qiskit.circuit import Parameter, QuantumCircuit, QuantumRegister 15 | from qiskit.circuit.library import ( 16 | CPhaseGate, 17 | RXGate, 18 | RXXGate, 19 | RYGate, 20 | RYYGate, 21 | RZGate, 22 | RZZGate, 23 | XXMinusYYGate, 24 | XXPlusYYGate, 25 | ) 26 | from qiskit.quantum_info import Operator 27 | from qiskit.transpiler import PassManager 28 | 29 | import ffsim 30 | 31 | 32 | def test_drops_negligible_gates(): 33 | """Test that negligible gates are dropped.""" 34 | qubits = QuantumRegister(2) 35 | circuit = QuantumCircuit(qubits) 36 | a, b = qubits 37 | circuit.append(CPhaseGate(1e-5), [a, b]) 38 | circuit.append(CPhaseGate(1e-8), [a, b]) 39 | circuit.append(RXGate(1e-5), [a]) 40 | circuit.append(RXGate(1e-8), [a]) 41 | circuit.append(RYGate(1e-5), [a]) 42 | circuit.append(RYGate(1e-8), [a]) 43 | circuit.append(RZGate(1e-5), [a]) 44 | circuit.append(RZGate(1e-8), [a]) 45 | circuit.append(RXXGate(1e-5), [a, b]) 46 | circuit.append(RXXGate(1e-8), [a, b]) 47 | circuit.append(RYYGate(1e-5), [a, b]) 48 | circuit.append(RYYGate(1e-8), [a, b]) 49 | circuit.append(RZZGate(1e-5), [a, b]) 50 | circuit.append(RZZGate(1e-8), [a, b]) 51 | circuit.append(XXPlusYYGate(1e-5, 1e-8), [a, b]) 52 | circuit.append(XXPlusYYGate(1e-8, 1e-8), [a, b]) 53 | circuit.append(XXMinusYYGate(1e-5, 1e-8), [a, b]) 54 | circuit.append(XXMinusYYGate(1e-8, 1e-8), [a, b]) 55 | pass_manager = PassManager([ffsim.qiskit.DropNegligible()]) 56 | transpiled = pass_manager.run(circuit) 57 | assert circuit.count_ops()["cp"] == 2 58 | assert transpiled.count_ops()["cp"] == 1 59 | assert circuit.count_ops()["rx"] == 2 60 | assert transpiled.count_ops()["rx"] == 1 61 | assert circuit.count_ops()["ry"] == 2 62 | assert transpiled.count_ops()["ry"] == 1 63 | assert circuit.count_ops()["rz"] == 2 64 | assert transpiled.count_ops()["rz"] == 1 65 | assert circuit.count_ops()["rxx"] == 2 66 | assert transpiled.count_ops()["rxx"] == 1 67 | assert circuit.count_ops()["ryy"] == 2 68 | assert transpiled.count_ops()["ryy"] == 1 69 | assert circuit.count_ops()["rzz"] == 2 70 | assert transpiled.count_ops()["rzz"] == 1 71 | assert circuit.count_ops()["xx_plus_yy"] == 2 72 | assert transpiled.count_ops()["xx_plus_yy"] == 1 73 | assert circuit.count_ops()["xx_minus_yy"] == 2 74 | assert transpiled.count_ops()["xx_minus_yy"] == 1 75 | np.testing.assert_allclose( 76 | np.array(Operator(circuit)), np.array(Operator(transpiled)), atol=1e-7 77 | ) 78 | 79 | 80 | def test_handles_parameters(): 81 | """Test that gates with parameters are ignored gracefully.""" 82 | qubits = QuantumRegister(2) 83 | circuit = QuantumCircuit(qubits) 84 | a, b = qubits 85 | theta = Parameter("theta") 86 | circuit.append(CPhaseGate(theta), [a, b]) 87 | circuit.append(CPhaseGate(1e-5), [a, b]) 88 | circuit.append(CPhaseGate(1e-8), [a, b]) 89 | pass_manager = PassManager([ffsim.qiskit.DropNegligible()]) 90 | transpiled = pass_manager.run(circuit) 91 | assert circuit.count_ops()["cp"] == 3 92 | assert transpiled.count_ops()["cp"] == 2 93 | 94 | 95 | def test_handles_number_types(): 96 | """Test that gates with different types of numbers are handled correctly.""" 97 | qubits = QuantumRegister(2) 98 | circuit = QuantumCircuit(qubits) 99 | a, b = qubits 100 | circuit.append(CPhaseGate(np.float32(1e-6)), [a, b]) 101 | circuit.append(CPhaseGate(1e-3), [a, b]) 102 | circuit.append(CPhaseGate(1e-8), [a, b]) 103 | pass_manager = PassManager([ffsim.qiskit.DropNegligible(atol=1e-5)]) 104 | transpiled = pass_manager.run(circuit) 105 | assert circuit.count_ops()["cp"] == 3 106 | assert transpiled.count_ops()["cp"] == 1 107 | -------------------------------------------------------------------------------- /tests/python/qiskit/util_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for Qiskit utilities.""" 12 | 13 | import numpy as np 14 | 15 | import ffsim 16 | 17 | 18 | def test_ffsim_to_qiskit_roundtrip(): 19 | """Test converting statevector between ffsim and Qiskit gives consistent results.""" 20 | norb = 5 21 | nelec = 3, 2 22 | big_dim = 2 ** (2 * norb) 23 | small_dim = ffsim.dim(norb, nelec) 24 | rng = np.random.default_rng(9940) 25 | ffsim_vec = ffsim.random.random_state_vector(small_dim, seed=rng) 26 | qiskit_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec(ffsim_vec, norb=norb, nelec=nelec) 27 | assert qiskit_vec.shape == (big_dim,) 28 | ffsim_vec_again = ffsim.qiskit.qiskit_vec_to_ffsim_vec( 29 | qiskit_vec, norb=norb, nelec=nelec 30 | ) 31 | assert ffsim_vec_again.shape == (small_dim,) 32 | np.testing.assert_array_equal(ffsim_vec, ffsim_vec_again) 33 | -------------------------------------------------------------------------------- /tests/python/states/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/states/wick_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Test Wick's theorem utilities.""" 12 | 13 | from __future__ import annotations 14 | 15 | import numpy as np 16 | import pytest 17 | import scipy.linalg 18 | import scipy.sparse.linalg 19 | 20 | import ffsim 21 | from ffsim.states.wick import expectation_one_body_power, expectation_one_body_product 22 | 23 | 24 | @pytest.mark.parametrize( 25 | "norb, occupied_orbitals", 26 | [ 27 | (4, ([0, 1], [0, 1])), 28 | (3, ([0], [1, 2])), 29 | (2, ([], [0])), 30 | ], 31 | ) 32 | def test_expectation_product(norb: int, occupied_orbitals: tuple[list[int], list[int]]): 33 | """Test expectation product.""" 34 | occ_a, occ_b = occupied_orbitals 35 | nelec = len(occ_a), len(occ_b) 36 | dim = ffsim.dim(norb, nelec) 37 | 38 | rng = np.random.default_rng() 39 | 40 | # generate random one-body tensors 41 | n_tensors = 6 42 | one_body_tensors = [] 43 | linops = [] 44 | for _ in range(n_tensors): 45 | one_body_tensor = rng.standard_normal((norb, norb)).astype(complex) 46 | one_body_tensor += 1j * rng.standard_normal((norb, norb)) 47 | linop = ffsim.contract.one_body_linop(one_body_tensor, norb=norb, nelec=nelec) 48 | one_body_tensors.append( 49 | scipy.linalg.block_diag(one_body_tensor, one_body_tensor) 50 | ) 51 | linops.append(linop) 52 | 53 | # generate a random Slater determinant 54 | orbital_rotation = ffsim.random.random_unitary(norb, seed=rng) 55 | rdm = scipy.linalg.block_diag( 56 | *ffsim.slater_determinant_rdms( 57 | norb, occupied_orbitals, orbital_rotation=orbital_rotation 58 | ) 59 | ) 60 | 61 | # get the full statevector 62 | vec = ffsim.slater_determinant( 63 | norb, occupied_orbitals, orbital_rotation=orbital_rotation 64 | ) 65 | 66 | product_op = scipy.sparse.linalg.LinearOperator( 67 | shape=(dim, dim), matvec=lambda x: x 68 | ) 69 | for i in range(n_tensors): 70 | product_op = product_op @ linops[i] 71 | computed = expectation_one_body_product(rdm, one_body_tensors[: i + 1]) 72 | target = np.vdot(vec, product_op @ vec) 73 | np.testing.assert_allclose(computed, target) 74 | 75 | 76 | def test_expectation_power(): 77 | """Test expectation power.""" 78 | norb = 4 79 | occupied_orbitals = ([0, 2], [1, 3]) 80 | 81 | occ_a, occ_b = occupied_orbitals 82 | nelec = len(occ_a), len(occ_b) 83 | dim = ffsim.dim(norb, nelec) 84 | 85 | rng = np.random.default_rng() 86 | 87 | # generate a random one-body tensor 88 | one_body_tensor = rng.standard_normal((norb, norb)).astype(complex) 89 | one_body_tensor += 1j * rng.standard_normal((norb, norb)) 90 | linop = ffsim.contract.one_body_linop(one_body_tensor, norb=norb, nelec=nelec) 91 | one_body_tensor = scipy.linalg.block_diag(one_body_tensor, one_body_tensor) 92 | 93 | # generate a random Slater determinant 94 | orbital_rotation = ffsim.random.random_unitary(norb, seed=rng) 95 | rdm = scipy.linalg.block_diag( 96 | *ffsim.slater_determinant_rdms( 97 | norb, occupied_orbitals, orbital_rotation=orbital_rotation 98 | ) 99 | ) 100 | 101 | # get the full statevector 102 | vec = ffsim.slater_determinant( 103 | norb, occupied_orbitals, orbital_rotation=orbital_rotation 104 | ) 105 | 106 | powered_op = scipy.sparse.linalg.LinearOperator( 107 | shape=(dim, dim), matvec=lambda x: x 108 | ) 109 | for power in range(4): 110 | computed = expectation_one_body_power(rdm, one_body_tensor, power) 111 | target = np.vdot(vec, powered_op @ vec) 112 | np.testing.assert_allclose(computed, target) 113 | powered_op = powered_op @ linop 114 | -------------------------------------------------------------------------------- /tests/python/test_data/orbital_rotation-0.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/ffsim/65df78bfa4991ed7a2d76f3219575ea89b787c71/tests/python/test_data/orbital_rotation-0.npy -------------------------------------------------------------------------------- /tests/python/test_no_warnings.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2025. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Test that no warnings are raised in Python files.""" 12 | 13 | import warnings 14 | from pathlib import Path 15 | 16 | import pytest 17 | 18 | 19 | def test_no_warnings(): 20 | """Test that no warnings are raised in Python files.""" 21 | all_warnings = [] 22 | for filepath in Path("python").glob("**/*.py"): 23 | with warnings.catch_warnings(record=True) as caught_warnings: 24 | compile(filepath.read_text(), filepath, "exec") 25 | all_warnings.extend(caught_warnings) 26 | if all_warnings: 27 | error_messages = [ 28 | f"{w.filename}:{w.lineno}: {w.category.__name__}: {w.message}" 29 | for w in all_warnings 30 | ] 31 | pytest.fail("Encountered warnings:\n" + "\n".join(error_messages)) 32 | -------------------------------------------------------------------------------- /tests/python/testing_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for testing utilities.""" 12 | 13 | from __future__ import annotations 14 | 15 | import numpy as np 16 | import pytest 17 | 18 | import ffsim 19 | 20 | 21 | def test_assert_allclose_up_to_global_phase(): 22 | rng = np.random.default_rng() 23 | shape = (2, 3, 4) 24 | a = rng.standard_normal(shape).astype(complex) 25 | a += 1j + rng.standard_normal(shape) 26 | b = a * (1j) ** 1.5 27 | 28 | ffsim.testing.assert_allclose_up_to_global_phase(a, b) 29 | 30 | b[0, 0, 0] *= 1.1 31 | with pytest.raises(AssertionError): 32 | ffsim.testing.assert_allclose_up_to_global_phase(a, b) 33 | -------------------------------------------------------------------------------- /tests/python/trotter/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/trotter/double_factorized_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for double-factorized Trotter simulation.""" 12 | 13 | from __future__ import annotations 14 | 15 | import numpy as np 16 | import pytest 17 | import scipy.sparse.linalg 18 | 19 | import ffsim 20 | 21 | 22 | @pytest.mark.parametrize( 23 | "norb, nelec, time, n_steps, order, z_representation, atol", 24 | [ 25 | (3, (1, 1), 0.1, 20, 0, False, 1e-2), 26 | (3, (1, 1), 0.1, 10, 1, True, 3e-3), 27 | (4, (2, 1), 0.1, 1, 2, False, 3e-3), 28 | (4, (1, 2), 0.1, 3, 2, True, 3e-3), 29 | (4, (2, 2), 0.1, 4, 1, False, 4e-3), 30 | (5, (3, 2), 0.1, 10, 1, True, 3e-3), 31 | ], 32 | ) 33 | def test_random( 34 | norb: int, 35 | nelec: tuple[int, int], 36 | time: float, 37 | n_steps: int, 38 | order: int, 39 | z_representation: bool, 40 | atol: float, 41 | ): 42 | """Test random Hamiltonian.""" 43 | rng = np.random.default_rng(2488) 44 | 45 | # generate random Hamiltonian 46 | dim = ffsim.dim(norb, nelec) 47 | hamiltonian = ffsim.random.random_double_factorized_hamiltonian( 48 | norb, rank=norb, z_representation=z_representation, seed=rng 49 | ) 50 | linop = ffsim.linear_operator(hamiltonian, norb=norb, nelec=nelec) 51 | 52 | # generate initial state 53 | dim = ffsim.dim(norb, nelec) 54 | initial_state = ffsim.random.random_state_vector(dim, seed=rng) 55 | original_state = initial_state.copy() 56 | 57 | # compute exact state 58 | exact_state = scipy.sparse.linalg.expm_multiply( 59 | -1j * time * linop, 60 | initial_state, 61 | traceA=-1j * time, 62 | ) 63 | 64 | # make sure time is not too small 65 | assert abs(np.vdot(exact_state, initial_state)) < 0.95 66 | 67 | # simulate 68 | final_state = ffsim.simulate_trotter_double_factorized( 69 | initial_state, 70 | hamiltonian, 71 | time, 72 | norb=norb, 73 | nelec=nelec, 74 | n_steps=n_steps, 75 | order=order, 76 | ) 77 | 78 | # check that initial state was not modified 79 | np.testing.assert_allclose(initial_state, original_state) 80 | 81 | # check agreement 82 | np.testing.assert_allclose(np.linalg.norm(final_state), 1.0) 83 | np.testing.assert_allclose(final_state, exact_state, atol=atol) 84 | -------------------------------------------------------------------------------- /tests/python/variational/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | -------------------------------------------------------------------------------- /tests/python/variational/hopgate_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2023. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for hop gate ansatz.""" 12 | 13 | import itertools 14 | import math 15 | 16 | import numpy as np 17 | 18 | import ffsim 19 | 20 | 21 | def test_parameters_roundtrip(): 22 | """Test converting to and back from parameters gives consistent results.""" 23 | norb = 5 24 | n_reps = 2 25 | rng = np.random.default_rng() 26 | 27 | def ncycles(iterable, n): 28 | "Returns the sequence elements n times" 29 | return itertools.chain.from_iterable(itertools.repeat(tuple(iterable), n)) 30 | 31 | pairs = itertools.combinations(range(norb), 2) 32 | interaction_pairs = list(ncycles(pairs, n_reps)) 33 | thetas = rng.uniform(-np.pi, np.pi, size=len(interaction_pairs)) 34 | final_orbital_rotation = ffsim.random.random_unitary(norb) 35 | 36 | operator = ffsim.HopGateAnsatzOperator( 37 | norb=norb, 38 | interaction_pairs=interaction_pairs, 39 | thetas=thetas, 40 | final_orbital_rotation=final_orbital_rotation, 41 | ) 42 | assert len(operator.to_parameters()) == n_reps * math.comb(norb, 2) + norb**2 43 | roundtripped = ffsim.HopGateAnsatzOperator.from_parameters( 44 | operator.to_parameters(), 45 | norb=norb, 46 | interaction_pairs=interaction_pairs, 47 | with_final_orbital_rotation=True, 48 | ) 49 | assert ffsim.approx_eq(roundtripped, operator) 50 | -------------------------------------------------------------------------------- /tests/python/variational/multireference_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for multireference states.""" 12 | 13 | import numpy as np 14 | 15 | import ffsim 16 | import ffsim.random.random 17 | 18 | 19 | def test_multireference_state_prod(): 20 | """Test multireference state for product operator.""" 21 | rng = np.random.default_rng(30314) 22 | 23 | norb = 8 24 | nelec = (4, 4) 25 | 26 | def brickwork(norb: int, n_layers: int): 27 | for i in range(n_layers): 28 | for j in range(i % 2, norb - 1, 2): 29 | yield (j, j + 1) 30 | 31 | n_layers = norb 32 | interaction_pairs = list(brickwork(norb, n_layers)) 33 | 34 | for _ in range(5): 35 | thetas = rng.uniform(-np.pi, np.pi, size=len(interaction_pairs)) 36 | operator = ffsim.HopGateAnsatzOperator(norb, interaction_pairs, thetas) 37 | 38 | mol_hamiltonian = ffsim.random.random_molecular_hamiltonian( 39 | norb, seed=rng, dtype=float 40 | ) 41 | reference_occupations = [ 42 | ((0, 1, 2, 3), (1, 2, 3, 4)), 43 | ((0, 1, 2, 4), (2, 3, 4, 6)), 44 | ((1, 2, 4, 5), (2, 3, 4, 7)), 45 | ] 46 | 47 | energy, prod_state_sum = ffsim.multireference_state_prod( 48 | mol_hamiltonian, 49 | (operator, operator), 50 | reference_occupations, 51 | norb=norb, 52 | nelec=nelec, 53 | ) 54 | reconstructed_state = np.tensordot( 55 | prod_state_sum.coeffs, 56 | [np.kron(vec_a, vec_b) for vec_a, vec_b in prod_state_sum.states], 57 | axes=1, 58 | ) 59 | expected_energy, state = ffsim.multireference_state( 60 | mol_hamiltonian, 61 | operator, 62 | reference_occupations, 63 | norb=norb, 64 | nelec=nelec, 65 | ) 66 | 67 | np.testing.assert_allclose(energy, expected_energy) 68 | ffsim.testing.assert_allclose_up_to_global_phase(reconstructed_state, state) 69 | -------------------------------------------------------------------------------- /tests/python/variational/num_num_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for number-number interaction ansatz.""" 12 | 13 | from __future__ import annotations 14 | 15 | import itertools 16 | 17 | import numpy as np 18 | import pytest 19 | 20 | import ffsim 21 | 22 | 23 | def test_parameters_roundtrip(): 24 | """Test converting to and back from parameters gives consistent results.""" 25 | norb = 5 26 | rng = np.random.default_rng() 27 | 28 | pairs_aa = list(itertools.combinations(range(norb), 2))[:norb] 29 | pairs_ab = list(itertools.combinations(range(norb), 2))[-norb:] 30 | thetas_aa = rng.uniform(-np.pi, np.pi, size=len(pairs_aa)) 31 | thetas_ab = rng.uniform(-np.pi, np.pi, size=len(pairs_ab)) 32 | 33 | operator = ffsim.NumNumAnsatzOpSpinBalanced( 34 | norb=norb, 35 | interaction_pairs=(pairs_aa, pairs_ab), 36 | thetas=(thetas_aa, thetas_ab), 37 | ) 38 | assert ( 39 | ffsim.NumNumAnsatzOpSpinBalanced.n_params( 40 | interaction_pairs=(pairs_aa, pairs_ab) 41 | ) 42 | == len(operator.to_parameters()) 43 | == 2 * norb 44 | ) 45 | roundtripped = ffsim.NumNumAnsatzOpSpinBalanced.from_parameters( 46 | operator.to_parameters(), 47 | norb=norb, 48 | interaction_pairs=(pairs_aa, pairs_ab), 49 | ) 50 | assert ffsim.approx_eq(roundtripped, operator) 51 | 52 | 53 | @pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nelec(range(5))) 54 | def test_diag_coulomb_mats(norb: int, nelec: tuple[int, int]): 55 | """Test initialization from diagonal Coulomb matrices.""" 56 | rng = np.random.default_rng() 57 | mat_aa = ffsim.random.random_real_symmetric_matrix(norb, seed=rng) 58 | mat_ab = ffsim.random.random_real_symmetric_matrix(norb, seed=rng) 59 | operator = ffsim.NumNumAnsatzOpSpinBalanced.from_diag_coulomb_mats((mat_aa, mat_ab)) 60 | vec = ffsim.random.random_state_vector(ffsim.dim(norb, nelec), seed=rng) 61 | actual = ffsim.apply_unitary(vec, operator, norb=norb, nelec=nelec) 62 | expected = ffsim.apply_diag_coulomb_evolution( 63 | vec, (mat_aa, mat_ab, mat_aa), time=-1.0, norb=norb, nelec=nelec 64 | ) 65 | np.testing.assert_allclose(actual, expected) 66 | 67 | 68 | def test_incorrect_num_params(): 69 | """Test that passing incorrect number of parameters throws an error.""" 70 | norb = 5 71 | pairs_aa = [(0, 1), (2, 3)] 72 | pairs_ab = [(0, 1), (2, 3)] 73 | with pytest.raises(ValueError, match="number"): 74 | _ = ffsim.NumNumAnsatzOpSpinBalanced( 75 | norb=norb, 76 | interaction_pairs=(pairs_aa, pairs_ab), 77 | thetas=(np.zeros(len(pairs_aa) + 1), np.zeros(len(pairs_ab))), 78 | ) 79 | with pytest.raises(ValueError, match="number"): 80 | _ = ffsim.NumNumAnsatzOpSpinBalanced( 81 | norb=norb, 82 | interaction_pairs=(pairs_aa, pairs_ab), 83 | thetas=(np.zeros(len(pairs_aa)), np.zeros(len(pairs_ab) - 1)), 84 | ) 85 | 86 | 87 | def test_invalid_pairs(): 88 | """Test that passing invalid pairs throws an error.""" 89 | norb = 5 90 | good_pairs = [(0, 1), (2, 3)] 91 | bad_pairs = [(1, 0), (2, 3)] 92 | with pytest.raises(ValueError, match="triangular"): 93 | _ = ffsim.NumNumAnsatzOpSpinBalanced( 94 | norb=norb, 95 | interaction_pairs=(bad_pairs, good_pairs), 96 | thetas=(np.zeros(len(bad_pairs)), np.zeros(len(good_pairs))), 97 | ) 98 | with pytest.raises(ValueError, match="triangular"): 99 | _ = ffsim.NumNumAnsatzOpSpinBalanced( 100 | norb=norb, 101 | interaction_pairs=(good_pairs, bad_pairs), 102 | thetas=(np.zeros(len(good_pairs)), np.zeros(len(bad_pairs))), 103 | ) 104 | -------------------------------------------------------------------------------- /tests/python/variational/uccsd_test.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright IBM 2024. 2 | # 3 | # This code is licensed under the Apache License, Version 2.0. You may 4 | # obtain a copy of this license in the LICENSE.txt file in the root directory 5 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # Any modifications or derivative works of this code must retain this 8 | # copyright notice, and modified files need to carry a notice indicating 9 | # that they have been altered from the originals. 10 | 11 | """Tests for UCCSD ansatz.""" 12 | 13 | import dataclasses 14 | 15 | import numpy as np 16 | 17 | import ffsim 18 | 19 | 20 | def test_norb(): 21 | """Test norb property.""" 22 | rng = np.random.default_rng(4878) 23 | norb = 5 24 | nocc = 3 25 | operator = ffsim.random.random_uccsd_restricted(norb, nocc, real=True, seed=rng) 26 | assert operator.norb == norb 27 | 28 | 29 | def test_n_params(): 30 | """Test computing number of parameters.""" 31 | rng = np.random.default_rng(4878) 32 | norb = 5 33 | nocc = 3 34 | for with_final_orbital_rotation in [False, True]: 35 | operator = ffsim.random.random_uccsd_restricted( 36 | norb, 37 | nocc, 38 | with_final_orbital_rotation=with_final_orbital_rotation, 39 | real=True, 40 | seed=rng, 41 | ) 42 | actual = ffsim.UCCSDOpRestrictedReal.n_params( 43 | norb, nocc, with_final_orbital_rotation=with_final_orbital_rotation 44 | ) 45 | expected = len(operator.to_parameters()) 46 | assert actual == expected 47 | 48 | 49 | def test_parameters_roundtrip(): 50 | """Test parameters roundtrip.""" 51 | rng = np.random.default_rng(4623) 52 | norb = 5 53 | nocc = 3 54 | for with_final_orbital_rotation in [False, True]: 55 | operator = ffsim.random.random_uccsd_restricted( 56 | norb, 57 | nocc, 58 | with_final_orbital_rotation=with_final_orbital_rotation, 59 | real=True, 60 | seed=rng, 61 | ) 62 | roundtripped = ffsim.UCCSDOpRestrictedReal.from_parameters( 63 | operator.to_parameters(), 64 | norb=norb, 65 | nocc=nocc, 66 | with_final_orbital_rotation=with_final_orbital_rotation, 67 | ) 68 | assert ffsim.approx_eq(roundtripped, operator) 69 | 70 | 71 | def test_approx_eq(): 72 | """Test approximate equality.""" 73 | rng = np.random.default_rng(4623) 74 | norb = 5 75 | nocc = 3 76 | for with_final_orbital_rotation in [False, True]: 77 | operator = ffsim.random.random_uccsd_restricted( 78 | norb, 79 | nocc, 80 | with_final_orbital_rotation=with_final_orbital_rotation, 81 | real=True, 82 | seed=rng, 83 | ) 84 | roundtripped = ffsim.UCCSDOpRestrictedReal.from_parameters( 85 | operator.to_parameters(), 86 | norb=norb, 87 | nocc=nocc, 88 | with_final_orbital_rotation=with_final_orbital_rotation, 89 | ) 90 | assert ffsim.approx_eq(operator, roundtripped) 91 | assert not ffsim.approx_eq( 92 | operator, dataclasses.replace(operator, t1=2 * operator.t1) 93 | ) 94 | assert not ffsim.approx_eq( 95 | operator, dataclasses.replace(operator, t2=2 * operator.t2) 96 | ) 97 | 98 | 99 | def test_apply_unitary(): 100 | """Test unitary.""" 101 | rng = np.random.default_rng(4623) 102 | norb = 5 103 | nocc = 3 104 | vec = ffsim.random.random_state_vector(ffsim.dim(norb, (nocc, nocc)), seed=rng) 105 | for with_final_orbital_rotation in [False, True]: 106 | operator = ffsim.random.random_uccsd_restricted( 107 | norb, 108 | nocc, 109 | with_final_orbital_rotation=with_final_orbital_rotation, 110 | real=True, 111 | seed=rng, 112 | ) 113 | result = ffsim.apply_unitary(vec, operator, norb=norb, nelec=(nocc, nocc)) 114 | np.testing.assert_allclose(np.linalg.norm(result), 1.0) 115 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 4.6.0 3 | env_list = 4 | py{39,310,311,312} 5 | coverage 6 | type 7 | lint 8 | format 9 | docs 10 | 11 | [testenv] 12 | description = run tests 13 | package = wheel 14 | wheel_build_env = .pkg 15 | extras = 16 | dev 17 | commands = 18 | pytest {posargs} 19 | 20 | [testenv:coverage] 21 | description = check test coverage 22 | extras = 23 | dev 24 | commands = 25 | coverage run --source ffsim -m pytest {posargs} 26 | coverage report --fail-under=80 27 | 28 | [testenv:type] 29 | description = run type check 30 | extras = 31 | dev 32 | commands = 33 | mypy 34 | 35 | [testenv:lint] 36 | description = check for lint 37 | extras = 38 | dev 39 | allowlist_externals = cargo 40 | commands = 41 | ruff check 42 | cargo clippy --all -- -D warnings 43 | 44 | [testenv:format] 45 | description = check formatting 46 | extras = 47 | dev 48 | allowlist_externals = cargo 49 | commands = 50 | ruff format --check 51 | cargo fmt --all -- --check 52 | 53 | [testenv:docs] 54 | description = build docs 55 | extras = 56 | dev 57 | setenv = 58 | SPHINX_APIDOC_OPTIONS = members,show-inheritance 59 | commands = 60 | python -c 'import shutil, pathlib; shutil.rmtree(pathlib.Path("docs") / "stubs", ignore_errors=True)' 61 | python -c 'import shutil, pathlib; shutil.rmtree(pathlib.Path("docs") / "_build" / "html" / ".doctrees", ignore_errors=True)' 62 | sphinx-build -b html -W {posargs} docs/ docs/_build/html 63 | --------------------------------------------------------------------------------