├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── doc_checks.yml │ ├── format.yml │ ├── publish.yml │ ├── publish_docs.yml │ └── tests.yaml ├── .gitignore ├── CITATION.cff ├── LICENSE.txt ├── README.md ├── docs ├── Makefile ├── README.md ├── _static │ ├── css │ │ └── custom.css │ ├── favicon.png │ ├── logo.png │ ├── qkm │ │ └── QKM_scheme_pic.jpg │ ├── qnn │ │ └── qnn.svg │ ├── qrc │ │ ├── QRC_user_guide.png │ │ ├── qrc_classification_transparent.png │ │ └── qrc_regression_transparent.png │ ├── schematic.png │ └── util │ │ └── executor.png ├── _templates │ ├── class.rst │ ├── class_with_call.rst │ └── function.rst ├── conf.py ├── examples │ ├── example_kernel_digit_classification.nblink │ ├── example_kernel_grid_search.nblink │ ├── example_qnn_backend_mitigation.nblink │ ├── example_qnn_ode_solver.nblink │ ├── example_quantum_bayesian_optimization.nblink │ └── examples_index.rst ├── index.rst ├── install │ └── install.rst ├── make.bat ├── modules │ └── classes.rst ├── qiskit_settings.conf ├── spelling_wordlist.txt └── user_guide │ ├── encoding_circuits.rst │ ├── executor.rst │ ├── kernel_methods.rst │ ├── observables.rst │ ├── quantum_neural_networks.rst │ ├── quantum_reservoir_computing.rst │ └── user_guide_index.rst ├── examples ├── encoding_circuits │ ├── layered_encoding_circuit.ipynb │ ├── pruning_example.ipynb │ └── various_encoding_circuit.ipynb ├── executor │ └── example_executor_qiskit.ipynb ├── integration │ └── mlflow.ipynb ├── kernel │ ├── example_fidelity_kernel.ipynb │ ├── example_projected_kernel.ipynb │ ├── example_regularization_mitigation.ipynb │ ├── qgpc_workflow.ipynb │ ├── qgpr_optimization_workflow.ipynb │ └── qgpr_workflow.ipynb ├── qnn │ ├── classification_example.ipynb │ ├── example_adam.ipynb │ ├── example_minibatch.ipynb │ ├── example_qcnn_encoding_circuit.ipynb │ ├── ode_example.ipynb │ ├── regression_example.ipynb │ └── regression_with_variance.ipynb ├── qrc │ ├── qrc_classification.ipynb │ └── qrc_regression.ipynb └── tutorials │ ├── images │ ├── QNN_ode_workflow.png │ ├── encoding_circuit.png │ ├── pipeline.png │ ├── projected_quantum_kernel.png │ └── quantum_bo.png │ ├── kernel_digit_classification.ipynb │ ├── kernel_grid_search.ipynb │ ├── kernel_regression.ipynb │ ├── qnn_backend_mitigation.ipynb │ ├── qnn_ode_solver.ipynb │ └── quantum_bayesian_optimization.ipynb ├── install_lowest_dependencies.py ├── output.png ├── pyproject.toml ├── src └── squlearn │ ├── __init__.py │ ├── encoding_circuit │ ├── __init__.py │ ├── circuit_library │ │ ├── __init__.py │ │ ├── chebyshev_pqc.py │ │ ├── chebyshev_rx.py │ │ ├── chebyshev_tower.py │ │ ├── highdim_encoding_circuit.py │ │ ├── hubregtsen_encoding_circuit.py │ │ ├── kyriienko_nonlinear_encoding_circuit.py │ │ ├── multi_control_encoding_circuit.py │ │ ├── param_z_feature_map.py │ │ ├── qcnn_encoding_circuit.py │ │ ├── qiskit_encoding_circuit.py │ │ ├── random_encoding_circuit.py │ │ ├── random_layered_encoding_circuit.py │ │ └── yz_cx_encoding_circuit.py │ ├── encoding_circuit_base.py │ ├── encoding_circuit_derivatives.py │ ├── layered_encoding_circuit.py │ ├── pruned_encoding_circuit.py │ └── transpiled_encoding_circuit.py │ ├── kernel │ ├── __init__.py │ ├── loss │ │ ├── __init__.py │ │ ├── kernel_loss_base.py │ │ ├── negative_log_likelihood.py │ │ └── target_alignment.py │ ├── lowlevel_kernel │ │ ├── __init__.py │ │ ├── fidelity_kernel.py │ │ ├── fidelity_kernel_expectation_value.py │ │ ├── fidelity_kernel_pennylane.py │ │ ├── kernel_matrix_base.py │ │ ├── kernel_optimizer.py │ │ ├── kernel_util.py │ │ ├── projected_quantum_kernel.py │ │ └── regularization.py │ ├── qgpc.py │ ├── qgpr.py │ ├── qkrr.py │ ├── qsvc.py │ └── qsvr.py │ ├── observables │ ├── __init__.py │ ├── observable_base.py │ ├── observable_derivatives.py │ └── observable_implemented │ │ ├── __init__.py │ │ ├── custom_observable.py │ │ ├── ising_hamiltonian.py │ │ ├── single_pauli.py │ │ ├── single_probability.py │ │ ├── summed_paulis.py │ │ └── summed_probabilities.py │ ├── optimizers │ ├── __init__.py │ ├── adam.py │ ├── approximated_gradients.py │ ├── optimizer_base.py │ ├── optimizers_wrapper.py │ └── sglbo.py │ ├── qnn │ ├── __init__.py │ ├── base_qnn.py │ ├── loss │ │ ├── __init__.py │ │ ├── ode_loss.py │ │ ├── parameter_regularization_loss.py │ │ ├── qnn_loss_base.py │ │ ├── squared_loss.py │ │ └── variance_loss.py │ ├── lowlevel_qnn │ │ ├── __init__.py │ │ ├── lowlevel_qnn.py │ │ ├── lowlevel_qnn_base.py │ │ ├── lowlevel_qnn_pennylane.py │ │ └── lowlevel_qnn_qiskit.py │ ├── qnnc.py │ ├── qnnr.py │ └── util │ │ ├── __init__.py │ │ ├── barren_plateau_analysis.py │ │ └── training.py │ ├── qrc │ ├── __init__.py │ ├── base_qrc.py │ ├── qrc_classifier.py │ └── qrc_regressor.py │ └── util │ ├── __init__.py │ ├── data_preprocessing.py │ ├── decompose_to_std.py │ ├── execution │ ├── __init__.py │ ├── automatic_backend_selection.py │ ├── hqaa │ │ ├── __init__.py │ │ ├── heuristic.py │ │ └── parser.py │ ├── parallel_estimator.py │ └── parallel_sampler.py │ ├── executor.py │ ├── optree │ ├── __init__.py │ ├── optree.py │ ├── optree_derivative.py │ └── optree_evaluate.py │ ├── pennylane │ ├── __init__.py │ ├── pennylane_circuit.py │ └── pennylane_gates.py │ └── qfi.py └── tests ├── encoding_circuit ├── test_layered_encoding_circuit.py └── test_random_encoding_circuits.py ├── kernel ├── matrix │ ├── test_fidelity_quantum_kernel.py │ ├── test_kernel_optimizer.py │ └── test_projected_quantum_kernel.py └── ml │ ├── test_qgpc.py │ ├── test_qgpr.py │ ├── test_qkrr.py │ ├── test_qsvc.py │ └── test_qsvr.py ├── optimizers └── test_sglbo.py ├── qnn ├── test_base_qnn.py ├── test_qnnc.py ├── test_qnnr.py └── test_training.py ├── qrc ├── test_base_qrc.py ├── test_qrc_classifier.py └── test_qrc_regressor.py └── util ├── execution ├── test_automatic_backend_selection.py └── test_parallel_primitives.py ├── optree ├── test_optree_derivative.py └── test_optree_evaluate.py └── test_executor.py /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [squlearn@gmail.com](mailto:squlearn@gmail.com). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | 135 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | If you plan to contribute to this project, please read this section carefully and check that your contribution fits the desired process and style. 3 | 4 | ## Install development version 5 | To contribute to sQUlearn install the package from the source code: 6 | 7 | ```bash 8 | git clone https://github.com/sQUlearn/squlearn.git && cd squlearn 9 | git checkout develop 10 | pip install -e . 11 | ``` 12 | 13 | > Note that editable installing as above might require an updated version of pip `pip install pip --upgrade` 14 | 15 | ## Devtools 16 | Install the recommended tools with 17 | ```bash 18 | pip install -e .[dev] 19 | ``` 20 | 21 | ## Style Guide 22 | ### Code Style 23 | We try to match all of our python code closely to the [PEP8 style guide](https://pep8.org/) with the exception of using a line length of `99`. To ensure this we use the [Black formater](https://black.readthedocs.io/en/stable/index.html) with the configuration specified in [pyproject.toml](https://github.com/sQUlearn/squlearn/blob/main/pyproject.toml). To format your code, run 24 | ```bash 25 | black path/to/folder/or/file.py 26 | ``` 27 | from the top level directory to ensure the use of the `pyproject.toml` configuration file. 28 | 29 | We don't enforce but highly encourage the use of the [pylint linter](https://docs.pylint.org/) to check newly written code and adapt accordingly. To run the linter with the desired configuration run 30 | ```bash 31 | pylint path/to/folder/or/file.py 32 | ``` 33 | from the top level directory to again ensure the use of the `pyproject.toml` configuration file. Running pylint before and after the contribution shouldn't add violations and/or lower the overall code score. 34 | 35 | ### Docstrings 36 | We furthermore desire to use the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for our Docstrings. Generally we at least expect 37 | - A one-line summary, terminated by a period, and 38 | - a description of the class/method. 39 | 40 | For classes we furthermore expect 41 | - A list of attributes. 42 | 43 | For methods we furthermore expect 44 | - A list of arguments, and 45 | - A list of return values. 46 | 47 | ## Git Flow 48 | We use the Git Flow branch structure specified [here](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) except for release branches. 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior... 15 | 16 | ``` 17 | This minimal code example will lead to the bug 18 | ``` 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. Version of sQUlearn, python, which OS, etc. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/doc_checks.yml: -------------------------------------------------------------------------------- 1 | name: Documentation check 2 | on: 3 | - pull_request 4 | 5 | jobs: 6 | doc_checks: 7 | runs-on: ubuntu-latest 8 | env: 9 | QISKIT_SETTINGS: ${{github.workspace}}/docs/qiskit_settings.conf 10 | 11 | steps: 12 | - name: Cancel Workflow Action 13 | uses: styfle/cancel-workflow-action@0.11.0 14 | 15 | - uses: actions/checkout@v2 16 | - uses: sQUlearn/sphinx-action@master 17 | with: 18 | docs-folder: "docs/" 19 | pre-build-command: "pip3 install .[docs]" 20 | build-command: "sphinx-build -b html . _build -W --keep-going" 21 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: Formatting check 2 | on: 3 | - pull_request 4 | 5 | jobs: 6 | black: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Cancel Workflow Action 11 | uses: styfle/cancel-workflow-action@0.11.0 12 | 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: 3.9 17 | 18 | - name: Install dependencies 19 | run: pip install black[jupyter] 20 | 21 | - uses: actions/checkout@v2 22 | 23 | - name: Run Black 24 | run: | 25 | black -l 99 src --check 26 | black -l 99 tests --check 27 | black -l 99 --ipynb examples --check 28 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | pypi-publish: 8 | name: Upload release to PyPI 9 | runs-on: ubuntu-latest 10 | environment: 11 | name: release 12 | url: https://pypi.org/p/squlearn 13 | permissions: 14 | id-token: write 15 | steps: 16 | - name: Set up Python 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: 3.9 20 | 21 | - name: Install dependencies 22 | run: pip install flit 23 | 24 | - uses: actions/checkout@v2 25 | 26 | - name: Build 27 | run: flit build 28 | 29 | - name: Publish package distributions to PyPI 30 | uses: pypa/gh-action-pypi-publish@release/v1 31 | -------------------------------------------------------------------------------- /.github/workflows/publish_docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish to GH Pages 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | publish-pages: 9 | name: Publish to GH Pages 10 | runs-on: ubuntu-latest 11 | env: 12 | QISKIT_SETTINGS: ${{github.workspace}}/docs/qiskit_settings.conf 13 | steps: 14 | - name: Checkout Source Code 15 | uses: actions/checkout@v2 16 | - name: Checkout GH Pages 17 | uses: actions/checkout@v2 18 | with: 19 | repository: sQUlearn/squlearn.github.io 20 | fetch-depth: 0 21 | path: "docs/_build" 22 | token: ${{ secrets.PUSH_DOCS_TOKEN }} 23 | - name: Build Docs 24 | uses: sQUlearn/sphinx-action@master 25 | with: 26 | docs-folder: "docs/" 27 | pre-build-command: "pip3 install .[docs]" 28 | build-command: "sphinx-build -E -b html . _build -W" 29 | - name: Commit Files 30 | run: | 31 | cd docs/_build 32 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 33 | git config --global user.name "github-actions[bot]" 34 | git add . 35 | git commit -m "update documentation ${{ github.event.release.tag_name }}" 36 | - name: Tag 37 | run: | 38 | cd docs/_build 39 | git tag -a "${{ github.event.release.tag_name }}" -m "${{ github.event.release.name}}" 40 | if: github.event_name == 'release' 41 | - name: Push Changes 42 | uses: ad-m/github-push-action@master 43 | with: 44 | github_token: ${{ secrets.PUSH_DOCS_TOKEN }} 45 | directory: docs/_build 46 | repository: sQUlearn/squlearn.github.io 47 | force: true 48 | tags: true 49 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | pull_request: 4 | schedule: 5 | - cron: '23 1 * * *' 6 | 7 | jobs: 8 | pytest_minimal_requirements: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Set up Python 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: '3.9' 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | python -m venv venv_minimal 21 | source venv_minimal/bin/activate 22 | pip install toml 23 | python3 install_lowest_dependencies.py 24 | - name: Test with pytest 25 | run: | 26 | source venv_minimal/bin/activate 27 | pip install pytest 28 | pytest tests/ 29 | - name: Test examples 30 | run: | 31 | source venv_minimal/bin/activate 32 | pip install nbmake 33 | pytest --nbmake --nbmake-timeout 600 examples/ 34 | - name: clean 35 | run: | 36 | rm -rf venv_minimal 37 | 38 | pytest_qiskit_0_46: 39 | runs-on: ubuntu-latest 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Set up Python 44 | uses: actions/setup-python@v5 45 | with: 46 | python-version: '3.9' 47 | - name: Install dependencies 48 | run: | 49 | python -m pip install --upgrade pip 50 | python -m venv venv_qiskit_0_46 51 | source venv_qiskit_0_46/bin/activate 52 | pip install .[examples] qiskit==0.46.3 qiskit-ibm-runtime==0.20.0 53 | - name: Test with pytest 54 | run: | 55 | source venv_qiskit_0_46/bin/activate 56 | pip install pytest 57 | pytest tests/ 58 | - name: Test examples 59 | run: | 60 | source venv_qiskit_0_46/bin/activate 61 | pip install nbmake 62 | pytest --nbmake --nbmake-timeout 600 examples/ 63 | - name: clean 64 | run: | 65 | rm -rf venv_qiskit_0_46 66 | 67 | pytest_qiskit_1: 68 | runs-on: ubuntu-latest 69 | 70 | steps: 71 | - uses: actions/checkout@v4 72 | - name: Set up Python 73 | uses: actions/setup-python@v5 74 | with: 75 | python-version: '3.9' 76 | - name: Install dependencies 77 | run: | 78 | python -m pip install --upgrade pip 79 | python -m venv venv_qiskit_1 80 | source venv_qiskit_1/bin/activate 81 | pip install .[examples] qiskit==1.1.2 qiskit-ibm-runtime==0.27.1 82 | - name: Test with pytest 83 | run: | 84 | source venv_qiskit_1/bin/activate 85 | pip install pytest 86 | pytest tests/ 87 | - name: Test examples 88 | run: | 89 | source venv_qiskit_1/bin/activate 90 | pip install nbmake 91 | pytest --nbmake --nbmake-timeout 600 examples/ 92 | - name: clean 93 | run: | 94 | rm -rf venv_qiskit_1 95 | 96 | pytest_latest: 97 | runs-on: ubuntu-latest 98 | 99 | steps: 100 | - uses: actions/checkout@v4 101 | - name: Set up Python 102 | uses: actions/setup-python@v5 103 | with: 104 | python-version: | 105 | 3.12 106 | - name: Install dependencies 107 | run: | 108 | python -m pip install --upgrade pip 109 | python -m venv venv_latest 110 | source venv_latest/bin/activate 111 | pip install .[examples] 112 | - name: Test with pytest 113 | run: | 114 | source venv_latest/bin/activate 115 | pip install pytest 116 | pytest tests/ 117 | - name: Test examples 118 | run: | 119 | source venv_latest/bin/activate 120 | pip install nbmake 121 | pytest --nbmake --nbmake-timeout 600 examples/ 122 | - name: clean 123 | run: | 124 | rm -rf venv_latest 125 | -------------------------------------------------------------------------------- /.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 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 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/modules/generated/* 74 | docs/examples/images 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 101 | # This is especially recommended for binary packages to ensure reproducibility, and is more 102 | # commonly ignored for libraries. 103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 104 | #poetry.lock 105 | 106 | # pdm 107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 108 | #pdm.lock 109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 110 | # in version control. 111 | # https://pdm.fming.dev/#use-with-ide 112 | .pdm.toml 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env* 126 | .venv* 127 | env*/ 128 | venv*/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | 164 | # VSCode 165 | .vscode/ 166 | 167 | #Example logs 168 | examples/qnn/*_log.csv 169 | examples/**/*.log 170 | 171 | #Example caches 172 | **/_cache 173 | examples/integration/mlruns 174 | 175 | # Genereated doc images 176 | docs/examples/images/* -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as follows." 3 | title: sQUlearn 4 | authors: 5 | - family-names: sQUlearn Contributors 6 | url: https://squlearn.github.io 7 | license: Apache-2.0 8 | repository-code: https://github.com/sQUlearn/squlearn 9 | preferred-citation: 10 | type: article 11 | status: preprint 12 | title: "sQUlearn - A Python Library for Quantum Machine Learning" 13 | authors: 14 | - family-names: Kreplin 15 | given-names: David A. 16 | orcid: https://orcid.org/0000-0002-8129-6864 17 | affiliation: Fraunhofer Institute for Manufacturing Engineering and Automation IPA 18 | - family-names: Willmann 19 | given-names: Moritz 20 | orcid: https://orcid.org/0009-0000-8099-425X 21 | affiliation: Fraunhofer Institute for Manufacturing Engineering and Automation IPA 22 | - family-names: Schnabel 23 | given-names: Jan 24 | orcid: https://orcid.org/0000-0003-3485-0479 25 | affiliation: Fraunhofer Institute for Manufacturing Engineering and Automation IPA 26 | - family-names: Rapp 27 | given-names: Frederic 28 | orcid: https://orcid.org/0009-0008-6240-8390 29 | affiliation: Fraunhofer Institute for Manufacturing Engineering and Automation IPA 30 | - family-names: Hagelüken 31 | given-names: Manuel 32 | orcid: https://orcid.org/0009-0007-9852-3190 33 | affilliation: Fraunhofer Institute for Manufacturing Engineering and Automation IPA 34 | - family-names: Roth 35 | given-names: Marco 36 | orcid: https://orcid.org/0000-0002-1276-5655 37 | affiliation: Fraunhofer Institute for Manufacturing Engineering and Automation IPA 38 | year: 2023 39 | month: 11 40 | doi: 10.48550/arXiv.2311.08990 41 | url: https://arxiv.org/abs/2311.08990 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | sQUlearn 3 |

4 | 5 | sQUlearn is a user-friendly, NISQ-ready Python library for quantum machine learning (QML), designed for seamless integration with classical machine learning tools like scikit-learn. The library's dual-layer architecture serves both QML researchers and practitioners, enabling efficient prototyping, experimentation, and pipelining. sQUlearn provides a comprehensive tool set that includes both quantum kernel methods and quantum neural networks, along with features like customizable data encoding strategies, automated execution handling, and specialized kernel regularization techniques. By focusing on NISQ-compatibility and end-to-end automation, sQUlearn aims to bridge the gap between current quantum computing capabilities and practical machine learning applications. 6 | 7 | sQUlearn offers scikit-learn compatible high-level interfaces for various kernel methods, QNNs and quantum reservoir computing. They build on top of the low-level interfaces of the QNN engine and the quantum kernel engine. The executor is used to run experiments on simulated and real backends of the Qiskit or PennyLane frameworks. 8 | 9 |

10 | sQUlearn schematic 11 |

12 | 13 | --- 14 | 15 | ## Prerequisites 16 | 17 | The package requires **at least Python 3.9**. 18 | ## Install sQUlearn 19 | 20 | ### Stable Release 21 | 22 | To install the stable release version of sQUlearn, run the following command: 23 | ```bash 24 | pip install squlearn 25 | ``` 26 | 27 | Alternatively, you can install sQUlearn directly from GitHub via 28 | ```bash 29 | pip install git+ssh://git@github.com:sQUlearn/squlearn.git 30 | ``` 31 | 32 | ## Examples 33 | There are several more elaborate examples available in the folder ``./examples`` which display the features of this package. 34 | Tutorials for beginners can be found at ``./examples/tutorials``. 35 | 36 | To install the required packages, run 37 | ```bash 38 | pip install .[examples] 39 | ``` 40 | 41 | ## Contribute to sQUlearn 42 | Thanks for considering contributing to sQUlearn! Please read our [contribution guidelines](https://github.com/sQUlearn/squlearn/blob/main/.github/CONTRIBUTING.md) before you submit a pull request. 43 | 44 | --- 45 | ## License 46 | 47 | sQUlearn is released under the [Apache License 2.0](https://github.com/sQUlearn/squlearn/blob/main/LICENSE.txt) 48 | 49 | ## Cite sQUlearn 50 | If you use sQUlearn in your work, please cite our paper: 51 | 52 | > Kreplin, D. A., Willmann, M., Schnabel, J., Rapp, F., Hagelüken, M., & Roth, M. (2023). sQUlearn - A Python Library for Quantum Machine Learning. [https://doi.org/10.48550/arXiv.2311.08990](https://doi.org/10.48550/arXiv.2311.08990) 53 | 54 | ## Contact 55 | This project is maintained by the quantum computing group at the Fraunhofer Institute for Manufacturing Engineering and Automation IPA. 56 | 57 | [http://www.ipa.fraunhofer.de/quantum](http://www.ipa.fraunhofer.de/quantum) 58 | 59 | For general questions regarding sQUlearn, use the [GitHub Discussions](https://github.com/sQUlearn/squlearn/discussions) or feel free to contact [sQUlearn@gmail.com](mailto:sQUlearn@gmail.com). 60 | 61 | --- 62 | ## Acknowledgements 63 | 64 | This project was supported by the German Federal Ministry of Economic Affairs and Climate Action through the projects AutoQML (grant no. 01MQ22002A) and AQUAS (grant no. 01MQ22003D), as well as the German Federal Ministry of Education and Research through the project H2Giga Degrad-EL3 (grant no. 03HY110D). 65 | 66 | --- 67 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS = 7 | SPHINXBUILD = sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Requirements: 2 | 3 | ```bash 4 | pip install sphinx 5 | pip install sphinx-rtd-theme 6 | ``` 7 | 8 | Command for generating the documentation: 9 | 10 | ```bash 11 | sphinx-build.exe -b html . _build/ 12 | ``` 13 | 14 | Or execute in the windows shell: 15 | 16 | ```bash 17 | make.bat html 18 | ``` 19 | 20 | Spell checking with 21 | ```bash 22 | make.bat spelling 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | @import 'theme.css'; 2 | 3 | .rst-content .note .admonition-title, .rst-content .note .wy-alert-title, .rst-content .seealso .admonition-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .admonition-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .wy-alert.wy-alert-info .wy-alert-title { 4 | background: #6fd3dd; 5 | } 6 | 7 | .rst-content .note, .rst-content .seealso, .rst-content .wy-alert-info.admonition, .rst-content .wy-alert-info.admonition-todo, .rst-content .wy-alert-info.attention, .rst-content .wy-alert-info.caution, .rst-content .wy-alert-info.danger, .rst-content .wy-alert-info.error, .rst-content .wy-alert-info.hint, .rst-content .wy-alert-info.important, .rst-content .wy-alert-info.tip, .rst-content .wy-alert-info.warning, .wy-alert.wy-alert-info { 8 | background: #e7f7f9; 9 | } 10 | 11 | .rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{ 12 | color:#0fb6c7 13 | } 14 | 15 | .wy-tray-container li.wy-tray-item-info{ 16 | background:#0fb6c7 17 | } 18 | 19 | .btn-info{ 20 | background-color:#0fb6c7!important 21 | } 22 | 23 | .btn-info:hover{ 24 | background-color:#0C919F!important 25 | } 26 | 27 | .btn-link{ 28 | color:#0fb6c7; 29 | } 30 | 31 | .wy-dropdown-menu>dd>a:hover{ 32 | background:#0fb6c7; 33 | } 34 | 35 | .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{ 36 | background:#0fb6c7; 37 | } 38 | 39 | .wy-inline-validate.wy-inline-validate-info .wy-input-context{ 40 | color:#0fb6c7 41 | } 42 | 43 | a{ 44 | color:#0fb6c7; 45 | } 46 | 47 | .wy-text-info{ 48 | color:#0fb6c7!important 49 | } 50 | 51 | .wy-menu-vertical a:active{ 52 | background-color:#0fb6c7; 53 | } 54 | 55 | .wy-side-nav-search{ 56 | background-color:#0fb6c7; 57 | } 58 | 59 | .wy-side-nav-search img{ 60 | background-color:#0fb6c7; 61 | } 62 | 63 | .wy-side-nav-search > div.version { 64 | color: hsla(0, 0%, 100%, 0.7); 65 | } 66 | 67 | .wy-nav .wy-menu-vertical header{ 68 | color:#0fb6c7 69 | } 70 | 71 | .wy-menu-vertical header, .wy-menu-vertical p.caption { 72 | color: #0C919F; 73 | } 74 | 75 | .wy-nav .wy-menu-vertical a:hover{ 76 | background-color:#0fb6c7; 77 | } 78 | 79 | .wy-nav-side { 80 | background: #293133; 81 | } 82 | 83 | .wy-nav-top{ 84 | background:#0fb6c7; 85 | } 86 | 87 | .wy-nav-top img{ 88 | background-color:#0fb6c7; 89 | } 90 | 91 | .rst-versions a{ 92 | color:#0fb6c7; 93 | } 94 | 95 | .rst-content a code,.rst-content a tt{ 96 | color:#0fb6c7 97 | } 98 | 99 | html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{ 100 | color:#0fb6c7; 101 | } 102 | 103 | html.writer-html4 .rst-content dl:not(.docutils) > dt, html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt { 104 | background: #e7f7f9; 105 | border-top: 3px solid #6fd3dd; 106 | } 107 | -------------------------------------------------------------------------------- /docs/_static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/docs/_static/favicon.png -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/docs/_static/logo.png -------------------------------------------------------------------------------- /docs/_static/qkm/QKM_scheme_pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/docs/_static/qkm/QKM_scheme_pic.jpg -------------------------------------------------------------------------------- /docs/_static/qrc/QRC_user_guide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/docs/_static/qrc/QRC_user_guide.png -------------------------------------------------------------------------------- /docs/_static/qrc/qrc_classification_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/docs/_static/qrc/qrc_classification_transparent.png -------------------------------------------------------------------------------- /docs/_static/qrc/qrc_regression_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/docs/_static/qrc/qrc_regression_transparent.png -------------------------------------------------------------------------------- /docs/_static/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/docs/_static/schematic.png -------------------------------------------------------------------------------- /docs/_static/util/executor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/docs/_static/util/executor.png -------------------------------------------------------------------------------- /docs/_templates/class.rst: -------------------------------------------------------------------------------- 1 | :mod:`{{module}}`.\ :spelling:word:`{{objname}}` 2 | {{ underline }}================================= 3 | 4 | .. currentmodule:: {{ module }} 5 | 6 | .. autoclass:: {{ objname }} 7 | -------------------------------------------------------------------------------- /docs/_templates/class_with_call.rst: -------------------------------------------------------------------------------- 1 | .. 2 | The empty line below should not be removed. It is added such that the `rst_prolog` 3 | is added before the :mod: directive. Otherwise, the rendering will show as a 4 | paragraph instead of a header. 5 | 6 | :mod:`{{module}}`.\ :spelling:word:`{{objname}}` 7 | {{ underline }}================================= 8 | 9 | .. currentmodule:: {{ module }} 10 | 11 | .. autoclass:: {{ objname }} 12 | 13 | {% block methods %} 14 | .. automethod:: __call__ 15 | {% endblock %} 16 | 17 | .. raw:: html 18 | 19 |
20 | -------------------------------------------------------------------------------- /docs/_templates/function.rst: -------------------------------------------------------------------------------- 1 | :mod:`{{module}}`.\ :spelling:word:`{{objname}}` 2 | {{ underline }}================================= 3 | 4 | .. currentmodule:: {{ module }} 5 | 6 | .. autofunction:: {{ objname }} 7 | 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath("..")) 17 | 18 | from squlearn import __version__ as squlearn_version 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = "sQUlearn" 23 | copyright = "2023, Fraunhofer IPA" 24 | author = "Fraunhofer IPA" 25 | 26 | # The full version, including alpha/beta/rc tags 27 | release = squlearn_version 28 | version = squlearn_version 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named "sphinx.ext.*") or your custom 34 | # ones. 35 | 36 | extensions = [ 37 | "sphinx.ext.autodoc", 38 | "sphinx.ext.autosummary", 39 | "sphinx.ext.intersphinx", 40 | "sphinx.ext.napoleon", 41 | "sphinxcontrib.spelling", 42 | "sphinx_sitemap", 43 | "matplotlib.sphinxext.plot_directive", 44 | "myst_parser", 45 | "nbsphinx", 46 | "nbsphinx_link", 47 | "jupyter_sphinx", 48 | ] 49 | 50 | source_suffix = { 51 | ".rst": "restructuredtext", 52 | ".md": "markdown", 53 | } 54 | 55 | autodoc_default_options = {"members": True, "inherited-members": True} 56 | 57 | 58 | # Skip property members --> They should be defined in Attributes 59 | def skip_property_member(app, what, name, obj, skip, options): 60 | if isinstance(obj, property): 61 | return True 62 | 63 | 64 | def setup(app): 65 | app.connect("autodoc-skip-member", skip_property_member) 66 | 67 | 68 | # Add any paths that contain templates here, relative to this directory. 69 | templates_path = ["_templates"] 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This pattern also affects html_static_path and html_extra_path. 74 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "README.md", "**.ipynb_checkpoints"] 75 | 76 | # generate autosummary even if no references 77 | autosummary_generate = True 78 | 79 | # -- Options for HTML output ------------------------------------------------- 80 | 81 | # The theme to use for HTML and HTML Help pages. See the documentation for 82 | # a list of builtin themes. 83 | # 84 | html_theme = "sphinx_rtd_theme" 85 | 86 | # Add any paths that contain custom static files (such as style sheets) here, 87 | # relative to this directory. They are copied after the builtin static files, 88 | # so a file named "default.css" will overwrite the builtin "default.css". 89 | html_static_path = ["_static"] 90 | html_logo = "_static/logo.png" 91 | html_favicon = "_static/favicon.png" 92 | html_theme_options = { 93 | "logo_only": True, 94 | "display_version": True, 95 | } 96 | html_css_files = [ 97 | "css/custom.css", 98 | ] 99 | 100 | latex_engine = "xelatex" 101 | latex_elements = { 102 | "preamble": r""" 103 | \usepackage{braket} 104 | """, 105 | } 106 | 107 | # intersphinx 108 | intersphinx_mapping = { 109 | "pennylane": ("https://docs.pennylane.ai/en/stable/", None), 110 | "qiskit": ("https://docs.quantum.ibm.com/api/qiskit/", None), 111 | "qiskit-aer": ("https://qiskit.github.io/qiskit-aer/", None), 112 | "qiskit-ibm-runtime": ("https://docs.quantum.ibm.com/api/qiskit-ibm-runtime/", None), 113 | "scipy": ("https://docs.scipy.org/doc/scipy/", None), 114 | "sklearn": ("https://scikit-learn.org/1.4/", None), 115 | } 116 | 117 | suppress_warnings = ["myst.header", "config.cache"] 118 | 119 | # base URL for sphinx_sitemap 120 | html_baseurl = "https://squlearn.github.io/" 121 | sitemap_url_scheme = "{link}" 122 | -------------------------------------------------------------------------------- /docs/examples/example_kernel_digit_classification.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../examples/tutorials/kernel_digit_classification.ipynb", 3 | "extra-media": [ 4 | "../../examples/tutorials/images" 5 | ] 6 | } -------------------------------------------------------------------------------- /docs/examples/example_kernel_grid_search.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../examples/tutorials/kernel_grid_search.ipynb" 3 | } -------------------------------------------------------------------------------- /docs/examples/example_qnn_backend_mitigation.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../examples/tutorials/qnn_backend_mitigation.ipynb", 3 | "extra-media": [ 4 | "../../examples/tutorials/images" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /docs/examples/example_qnn_ode_solver.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../examples/tutorials/qnn_ode_solver.ipynb", 3 | "extra-media": [ 4 | "../../examples/tutorials/images" 5 | ] 6 | } -------------------------------------------------------------------------------- /docs/examples/example_quantum_bayesian_optimization.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../examples/tutorials/quantum_bayesian_optimization.ipynb", 3 | "extra-media": [ 4 | "../../examples/tutorials/images" 5 | ] 6 | } -------------------------------------------------------------------------------- /docs/examples/examples_index.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Examples 4 | ======== 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | :glob: 9 | 10 | example_kernel_digit_classification 11 | example_kernel_grid_search 12 | example_quantum_bayesian_optimization 13 | example_qnn_backend_mitigation 14 | example_qnn_ode_solver -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. sQUlearn documentation master file, created by 2 | sphinx-quickstart on Thu Apr 27 16:53:35 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to the sQUlearn documentation! 7 | ====================================== 8 | 9 | .. include:: ../README.md 10 | :parser: myst_parser.sphinx_ 11 | :start-line: 4 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | :caption: Contents: 16 | 17 | install/install 18 | user_guide/user_guide_index 19 | modules/classes 20 | examples/examples_index 21 | -------------------------------------------------------------------------------- /docs/install/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installation 4 | ============ 5 | 6 | Prerequisites 7 | ------------- 8 | 9 | sQUlearn requires a recent python3 (>=3.9) installation. 10 | Additionally the following python packages are necessary: :: 11 | 12 | bayesian-optimization>=1.4.3,<2 13 | dill>=0.3.4 14 | mapomatic>=0.10.0 15 | networkx>=3.0 16 | numpy>=1.20 17 | pennylane>=0.34.0 18 | qiskit>=0.45.0 19 | qiskit-aer>=0.12.0 20 | qiskit-algorithms>=0.3.0 21 | qiskit-ibm-runtime>=0.18.0 22 | qiskit-machine-learning>=0.7.0 23 | scipy>=1.8.0 24 | scikit-learn>=1.2.0,<1.4.2 25 | tqdm>=4.1.0 26 | 27 | The packages are automatically installed when installing sQUlearn with pip. 28 | 29 | Stable Release 30 | -------------- 31 | 32 | To install the stable release version of sQUlearn, run the following command: 33 | 34 | .. code-block:: bash 35 | 36 | pip install squlearn 37 | 38 | 39 | Bleeding-edge version 40 | --------------------- 41 | 42 | To install the latest master version: 43 | 44 | .. code-block:: bash 45 | 46 | pip install git+https://github.com/sQUlearn/squlearn.git 47 | 48 | 49 | Development version 50 | ------------------- 51 | 52 | To install the latest development version: 53 | 54 | .. code-block:: bash 55 | 56 | pip install git+https://github.com/sQUlearn/squlearn.git@develop 57 | 58 | 59 | 60 | Installation with optional dependencies: 61 | ---------------------------------------- 62 | 63 | There are several optional dependencies that can be installed with sQUlearn. 64 | To install sQUlearn with the dependencies usefull for development, run the following command: 65 | 66 | .. code-block:: bash 67 | 68 | pip install squlearn[dev] 69 | 70 | To install sQUlearn with the dependencies necessary to run all examples, run the following command: 71 | 72 | .. code-block:: bash 73 | 74 | pip install squlearn[examples] 75 | 76 | And to install sQUlearn with the dependencies necessary to build the documentation, 77 | run the following command: 78 | 79 | .. code-block:: bash 80 | 81 | pip install squlearn[docs] -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set MODDIR=.\modules\generated 12 | set BUILDDIR=_build 13 | set QISKIT_SETTINGS=%CD%\qiskit_settings.conf 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.https://www.sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | if "%1" == "" goto help 29 | if "%1" == "clean" goto clean 30 | 31 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 32 | goto end 33 | 34 | :help 35 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 36 | goto end 37 | 38 | :clean 39 | %SPHINXBUILD% -M clean %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 40 | if exist %MODDIR% ( 41 | echo Removing %MODDIR% 42 | @RD /S /Q %MODDIR% 43 | ) 44 | 45 | :end 46 | popd 47 | -------------------------------------------------------------------------------- /docs/qiskit_settings.conf: -------------------------------------------------------------------------------- 1 | [default] 2 | circuit_drawer = mpl 3 | circuit_mpl_style = clifford 4 | -------------------------------------------------------------------------------- /docs/spelling_wordlist.txt: -------------------------------------------------------------------------------- 1 | Aer 2 | ansatz 3 | backend 4 | backends 5 | Broughton 6 | Chebyshev 7 | Cholesky 8 | CNOT 9 | cov 10 | Hadamard 11 | Hamiltonian 12 | Hamiltonians 13 | Hubregtsen 14 | hyperparameter 15 | hyperparameters 16 | Ising 17 | Laplacian 18 | Matern 19 | Mohseni 20 | multiclass 21 | ndarray 22 | np 23 | opflow 24 | parameterized 25 | Pauli 26 | Paulis 27 | pre 28 | prefactor 29 | preprocessing 30 | pretrained 31 | QFI 32 | Qiskit 33 | qubit 34 | qubits 35 | Rapp 36 | RBF 37 | regressors 38 | runtime 39 | Rx 40 | Rxx 41 | Ry 42 | Ryy 43 | Rz 44 | Rzx 45 | Rzz 46 | scikit 47 | scipy 48 | solvability 49 | squlearn 50 | sQUlearn 51 | transpilation 52 | transpile 53 | transpiled 54 | transpiler 55 | transpiling 56 | untrainability 57 | -------------------------------------------------------------------------------- /docs/user_guide/observables.rst: -------------------------------------------------------------------------------- 1 | .. _user_guide_observables: 2 | 3 | ################################## 4 | Observables for expectation values 5 | ################################## 6 | 7 | .. currentmodule:: squlearn.observables 8 | 9 | Observables play a crucial role in computing the expectation value in conjunction with a 10 | wave function. Currently, observables constructed purely from the Pauli group 11 | :math:`\{\hat{X},\hat{Y},\hat{Z},\hat{I}\}` are supported. In the context of sQUlearn, 12 | observables are mandatory inputs for Quantum Neural Networks (QNNs) and can be 13 | employed in the Projected Quantum Kernel program. 14 | All operators follow the Base Class :class:`ObservableBase`. sQulearn features several 15 | predefined observables, but it is also possible to simply construct custom observables. 16 | 17 | Implemented observables. 18 | ---------------------------------- 19 | 20 | The following predefined observables are available in the module :class:`squlearn.observables`: 21 | 22 | .. autosummary:: 23 | :nosignatures: 24 | 25 | SinglePauli 26 | SummedPaulis 27 | SingleProbability 28 | SummedProbabilities 29 | IsingHamiltonian 30 | CustomObservable 31 | 32 | The observables are simply constructed by initializing the associated class. 33 | 34 | **Example: Summed Pauli observable** 35 | 36 | .. jupyter-execute:: 37 | 38 | from squlearn.observables import SummedPaulis 39 | 40 | op = SummedPaulis(num_qubits=2) 41 | print(op) 42 | 43 | Custom observables 44 | -------------------------- 45 | 46 | 47 | sQUlearn features several options to construct custom observables. 48 | With the class :class:`CustomObservable`, it is possible to construct observables from a string 49 | containing the letters of the Pauli matrices (``"X"``, ``"Y"``, ``"Z"``, ``"I"``). 50 | The resulting observable can be multiplied by a parameter by setting ``parameterized=True``. 51 | Furthermore, observables can be added by ``+`` or multiplied by ``*`` with each other. 52 | This allows the creation of arbitrary observables. 53 | 54 | **Example: Custom observable** 55 | 56 | .. jupyter-execute:: 57 | 58 | from squlearn.observables import CustomObservable 59 | 60 | ob1 = CustomObservable(num_qubits=2, operator_string="IX",parameterized=True) 61 | ob2 = CustomObservable(num_qubits=2, operator_string="ZY",parameterized=True) 62 | added_ob = ob1 + ob2 63 | print("Added observable:\n",added_ob,"\n\n") 64 | squared_ob = added_ob*added_ob 65 | print("Squared observable:\n",squared_ob) 66 | 67 | **Example: More complex custom observable** 68 | 69 | It is also possible to construct more complex observables by supplying a list of strings 70 | containing the Pauli matrices. The observable is then constructed by adding the single observables 71 | together. 72 | 73 | .. jupyter-execute:: 74 | 75 | from squlearn.observables import CustomObservable 76 | 77 | # It is also possible to add trainable parameters: 78 | ob = CustomObservable(num_qubits=4, operator_string=["ZIZZ", "XIXI"], parameterized=True) 79 | print("Custom observable with multiple operators:\n",ob) 80 | 81 | 82 | Note that in Qiskit, the qubits are counted from the right to the left as in the computational 83 | basis! 84 | 85 | Mapping observables to real qubits 86 | ------------------------------------ 87 | 88 | 89 | When running on a backend, the number of physical qubits may change 90 | from the number of qubits the definition of the observable. 91 | If it is necessary, it is possible to adjust the observable to the physical qubits. 92 | This is achieved by providing a map from the qubits of the observable to the 93 | physical qubits utilized for example in the feature map via the function :meth:`set_qubit_map`. 94 | The map can be for example obtained in the transpiled encoding circuit. 95 | 96 | **Example: Use the mapping from the transpiled encoding circuit** 97 | 98 | .. jupyter-execute:: 99 | 100 | from squlearn.encoding_circuit import ChebyshevRx,TranspiledEncodingCircuit 101 | from squlearn.observables import SummedPaulis 102 | from qiskit_ibm_runtime.fake_provider import FakeManilaV2 103 | fm = TranspiledEncodingCircuit(ChebyshevRx(3,1),backend=FakeManilaV2(),initial_layout=[0,1,4]) 104 | ob = SummedPaulis(num_qubits=3, op_str="Z") 105 | print("Observable before mapping:\n",ob,"\n\n") 106 | ob.set_map(fm.qubit_map, fm.num_physical_qubits) 107 | print("Observable after mapping:\n",ob) 108 | 109 | 110 | Derivatives of the observable 111 | ---------------------------------------------- 112 | 113 | .. currentmodule:: squlearn.observables.observable_derivatives 114 | 115 | In sQUlearn it is also possible to calculate derivatives of observables 116 | as for example needed during the training of the QNN. 117 | This is possible with the class :class:`ObservableDerivatives`. 118 | The derivatives are calculated with respect to the parameters of the observable. 119 | 120 | 121 | **Example: first-order derivatives of the Ising Hamiltonian** 122 | 123 | .. jupyter-execute:: 124 | 125 | from squlearn.observables import IsingHamiltonian 126 | from squlearn.observables.observable_derivatives import ObservableDerivatives 127 | ob = IsingHamiltonian(num_qubits=3) 128 | print("Observable:\n", ob,"\n\n") 129 | print("Gradient of the observable:\n", ObservableDerivatives(ob).get_derivative("dop")) 130 | 131 | 132 | **Example: higher-order derivative of the cubed SummedPaulis observable** 133 | 134 | Furthermore, arbitrary derivatives can be computed by supplying a tuple, although the 135 | higher-order derivatives are zero for linear parameters. 136 | To achieve this, the function :meth:`ObservableDerivatives.get_derivative` can be used with a tuple 137 | of parameters :meth:`ObservableDerivatives.parameter_vector`. 138 | 139 | .. jupyter-execute:: 140 | 141 | from squlearn.observables import SummedPaulis 142 | from squlearn.observables.observable_derivatives import ObservableDerivatives 143 | 144 | # Build cubed SummedPaulis observable 145 | op = SummedPaulis(num_qubits=2) 146 | op3 = op*op*op 147 | print("Cubed operator:\n",op3) 148 | 149 | # Get the Hessian from a tuple 150 | deriv = ObservableDerivatives(op3) 151 | print("Second-order derivative w.r.t. p[0]:\n", 152 | deriv.get_derivative((deriv.parameter_vector[0],deriv.parameter_vector[0]))) 153 | 154 | 155 | **Example: Squared summed Pauli Observable** 156 | 157 | It is also possible to calculate the squared observable, which is needed for the 158 | calculation of the variance of the observable. 159 | 160 | .. jupyter-execute:: 161 | 162 | from squlearn.observables import SummedPaulis 163 | from squlearn.observables.observable_derivatives import ObservableDerivatives 164 | op = SummedPaulis(num_qubits=3) 165 | print(ObservableDerivatives(op).get_operator_squared()) 166 | 167 | 168 | -------------------------------------------------------------------------------- /docs/user_guide/user_guide_index.rst: -------------------------------------------------------------------------------- 1 | .. _user_guide: 2 | 3 | User Guide 4 | ============ 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | executor 10 | observables 11 | encoding_circuits 12 | kernel_methods 13 | quantum_neural_networks 14 | quantum_reservoir_computing 15 | -------------------------------------------------------------------------------- /examples/tutorials/images/QNN_ode_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/examples/tutorials/images/QNN_ode_workflow.png -------------------------------------------------------------------------------- /examples/tutorials/images/encoding_circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/examples/tutorials/images/encoding_circuit.png -------------------------------------------------------------------------------- /examples/tutorials/images/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/examples/tutorials/images/pipeline.png -------------------------------------------------------------------------------- /examples/tutorials/images/projected_quantum_kernel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/examples/tutorials/images/projected_quantum_kernel.png -------------------------------------------------------------------------------- /examples/tutorials/images/quantum_bo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/examples/tutorials/images/quantum_bo.png -------------------------------------------------------------------------------- /install_lowest_dependencies.py: -------------------------------------------------------------------------------- 1 | """This script installs the package with the lowest dependencies.""" 2 | 3 | import re 4 | import subprocess 5 | import sys 6 | from typing import List, Optional, Tuple 7 | 8 | import toml 9 | 10 | # Load the pyproject.toml 11 | pyproject = toml.load("pyproject.toml") 12 | 13 | # Extract dependencies 14 | dependencies: List[str] = pyproject.get("project", {}).get("dependencies", []) + pyproject.get( 15 | "project", {} 16 | ).get("optional-dependencies", {}).get("examples", []) 17 | 18 | 19 | # Function to get exactly the minimal specified version 20 | def get_lowest_version(dependency_string: str) -> str: 21 | """Get the lowest version of a dependency.""" 22 | pattern = re.compile(r"([\w-]+)(?:>=(\d*(?:\.\d*(?:\.\d*)?)?))?") 23 | match = pattern.match(dependency_string) 24 | if match: 25 | groups: Tuple[Optional[str], Optional[str]] = match.groups() 26 | if groups[1]: 27 | return "==".join(groups) 28 | return groups[0] 29 | return dependency_string 30 | 31 | 32 | # Install the main package without dependencies 33 | subprocess.run([sys.executable, "-m", "pip", "install", ".", "--no-deps"], check=True) 34 | 35 | # Get the lowest version of pennylane 36 | PENNYLANE_VERSION = None 37 | for dependency in dependencies: 38 | if dependency.startswith("pennylane"): 39 | PENNYLANE_VERSION = get_lowest_version(dependency).split("==")[1] 40 | break 41 | 42 | dependencies = [get_lowest_version(dependency) for dependency in dependencies] 43 | if PENNYLANE_VERSION: 44 | dependencies.append(f"pennylane-lightning=={PENNYLANE_VERSION}") 45 | subprocess.run([sys.executable, "-m", "pip", "install"] + dependencies, check=True) 46 | -------------------------------------------------------------------------------- /output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/output.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "squlearn" 7 | readme = "README.md" 8 | authors = [ 9 | {name = "David Kreplin", email = "david.kreplin@ipa.fraunhofer.de"}, 10 | {name = "Moritz Willmann", email = "moritz.willmann@ipa.fraunhofer.de"}, 11 | {name = "Jan Schnabel", email = "jan.schnabel@ipa.fraunhofer.de"}, 12 | {name = "Frederic Rapp", email = "frederic.rapp@ipa.fraunhofer.de"}, 13 | {name = "Manuel Hagelüken", email = "manuel.hagelueken@ipa.fraunhofer.de"}, 14 | {name = "Marco Roth", email = "marco.roth@ipa.fraunhofer.de"}, 15 | ] 16 | maintainers = [ 17 | {name = "David Kreplin", email = "david.kreplin@ipa.fraunhofer.de"}, 18 | {name = "Moritz Willmann", email = "moritz.willmann@ipa.fraunhofer.de"}, 19 | ] 20 | license = {file = "LICENSE.txt"} 21 | classifiers = [ 22 | "License :: OSI Approved :: Apache Software License", 23 | "Programming Language :: Python", 24 | "Programming Language :: Python :: 3", 25 | ] 26 | keywords = ["quantum", "machine learning", "qml"] 27 | dependencies = [ 28 | "bayesian-optimization>=1.4.3,<2", 29 | "dill>=0.3.4", 30 | "mapomatic>=0.10.0", 31 | "networkx>=3.0", 32 | "numpy>=1.20", 33 | "pennylane>=0.34.0", 34 | "qiskit>=0.45.0,<2", 35 | "qiskit-aer>=0.12.0", 36 | "qiskit-algorithms>=0.3.0", 37 | "qiskit-ibm-runtime>=0.18.0", 38 | "qiskit-machine-learning>=0.7.0", 39 | "scipy>=1.8.0", 40 | "scikit-learn>=1.2.0", 41 | "tqdm>=4.1.0", 42 | ] 43 | requires-python = ">=3.9,<3.13" 44 | dynamic = ["version", "description"] 45 | 46 | [project.optional-dependencies] 47 | dev = [ 48 | "black", 49 | "flit", 50 | "nbmake", 51 | "pylint", 52 | "pytest", 53 | ] 54 | examples = [ 55 | "jupyter", 56 | "matplotlib>=3.5.1", 57 | "mlflow", 58 | "pandas", 59 | "pylatexenc>=2.10", 60 | "seaborn", 61 | "scikit-optimize", 62 | ] 63 | docs = [ 64 | "ipykernel", 65 | "jupyter-sphinx", 66 | "matplotlib>=3.5.1", 67 | "myst-parser", 68 | "nbsphinx", 69 | "nbsphinx_link", 70 | "pylatexenc>=2.10", 71 | "sphinx", 72 | "sphinxcontrib-spelling", 73 | "sphinx-rtd-theme<3.0.0", 74 | "sphinx-sitemap", 75 | ] 76 | 77 | [project.urls] 78 | Homepage = "https://github.com/sQUlearn/squlearn" 79 | 80 | [tool.black] 81 | line-length=99 82 | 83 | [tool.pylint] 84 | max-line-length=99 85 | good-names=["X", "y", "i", "j"] 86 | 87 | [tool.pylint.main] 88 | extension-pkg-allow-list = [ 89 | "numpy", 90 | ] 91 | 92 | [tool.pytest.ini_options] 93 | addopts = [ 94 | "--import-mode=importlib", 95 | "--ignore=examples/tutorials/qnn_backend_mitigation.ipynb", 96 | "--ignore=examples/tutorials/qnn_ode_solver.ipynb", 97 | ] 98 | -------------------------------------------------------------------------------- /src/squlearn/__init__.py: -------------------------------------------------------------------------------- 1 | """A library for quantum machine learning following the scikit-learn standard.""" 2 | 3 | from .util import Executor 4 | from . import observables, encoding_circuit, kernel, optimizers, qnn, util 5 | 6 | __version__ = "0.8.3" 7 | 8 | __all__ = [ 9 | "Executor", 10 | "observables", 11 | "encoding_circuit", 12 | "kernel", 13 | "optimizers", 14 | "qnn", 15 | "util", 16 | ] 17 | -------------------------------------------------------------------------------- /src/squlearn/encoding_circuit/__init__.py: -------------------------------------------------------------------------------- 1 | from .pruned_encoding_circuit import PrunedEncodingCircuit, automated_pruning, pruning_from_QFI 2 | from .layered_encoding_circuit import LayeredEncodingCircuit 3 | from .transpiled_encoding_circuit import TranspiledEncodingCircuit 4 | from .encoding_circuit_derivatives import EncodingCircuitDerivatives 5 | from .circuit_library.qcnn_encoding_circuit import QCNNEncodingCircuit 6 | from .circuit_library.yz_cx_encoding_circuit import YZ_CX_EncodingCircuit 7 | from .circuit_library.highdim_encoding_circuit import HighDimEncodingCircuit 8 | from .circuit_library.hubregtsen_encoding_circuit import HubregtsenEncodingCircuit 9 | from .circuit_library.chebyshev_tower import ChebyshevTower 10 | from .circuit_library.chebyshev_pqc import ChebyshevPQC 11 | from .circuit_library.multi_control_encoding_circuit import MultiControlEncodingCircuit 12 | from .circuit_library.chebyshev_rx import ChebyshevRx 13 | from .circuit_library.param_z_feature_map import ParamZFeatureMap 14 | from .circuit_library.qiskit_encoding_circuit import QiskitEncodingCircuit 15 | from .circuit_library.random_encoding_circuit import RandomEncodingCircuit 16 | from .circuit_library.random_layered_encoding_circuit import RandomLayeredEncodingCircuit 17 | from .circuit_library.kyriienko_nonlinear_encoding_circuit import KyriienkoEncodingCircuit 18 | 19 | __all__ = [ 20 | "PrunedEncodingCircuit", 21 | "TranspiledEncodingCircuit", 22 | "EncodingCircuitDerivatives", 23 | "QCNNEncodingCircuit", 24 | "automated_pruning", 25 | "pruning_from_QFI", 26 | "LayeredEncodingCircuit", 27 | "YZ_CX_EncodingCircuit", 28 | "HighDimEncodingCircuit", 29 | "HubregtsenEncodingCircuit", 30 | "ChebyshevTower", 31 | "ChebyshevPQC", 32 | "MultiControlEncodingCircuit", 33 | "ChebyshevRx", 34 | "ParamZFeatureMap", 35 | "QiskitEncodingCircuit", 36 | "RandomEncodingCircuit", 37 | "RandomLayeredEncodingCircuit", 38 | "KyriienkoEncodingCircuit", 39 | ] 40 | -------------------------------------------------------------------------------- /src/squlearn/encoding_circuit/circuit_library/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/src/squlearn/encoding_circuit/circuit_library/__init__.py -------------------------------------------------------------------------------- /src/squlearn/encoding_circuit/circuit_library/multi_control_encoding_circuit.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | 4 | from qiskit.circuit import ParameterVector 5 | from qiskit.circuit import QuantumCircuit 6 | 7 | from ..encoding_circuit_base import EncodingCircuitBase 8 | 9 | 10 | class MultiControlEncodingCircuit(EncodingCircuitBase): 11 | """ 12 | Encoding circuit with HZ encoding followed by controlled Rx, Ry Rz rotations. 13 | 14 | **Example for 4 qubits, a 2 dimensional feature vector and 1 layer:** 15 | 16 | .. plot:: 17 | 18 | from squlearn.encoding_circuit import MultiControlEncodingCircuit 19 | pqc = MultiControlEncodingCircuit(4, 2, 1) 20 | pqc.draw(output="mpl", style={'fontsize':15,'subfontsize': 10}) 21 | plt.tight_layout() 22 | 23 | The circuit is repeated for the number of layers. 24 | The circuit is closed by default, i.e. the last qubit is entangled with the first one. 25 | The encoding can be optionally repeated at the end to make the previous rotations not 26 | redundant in a fidelity kernel setting. 27 | 28 | Args: 29 | num_qubits (int): Number of qubits of the MultiControlEncodingCircuit encoding circuit 30 | num_features (int): Dimension of the feature vector 31 | num_layers (int): Number of layers (default: 1) 32 | closed (bool): If true, the last and the first qubit are entangled; 33 | not necessarily hardware efficient! (default: true) 34 | final_encoding (bool): If True, the encoding is repeated at the end (default: False) 35 | """ 36 | 37 | def __init__( 38 | self, 39 | num_qubits: int, 40 | num_features: int, 41 | num_layers: int = 1, 42 | closed: bool = True, 43 | final_encoding=False, 44 | ) -> None: 45 | super().__init__(num_qubits, num_features) 46 | 47 | if self.num_qubits < 2: 48 | raise ValueError("MultiControlEncodingCircuit requires at least two qubits.") 49 | 50 | self.num_layers = num_layers 51 | self.closed = closed 52 | self.final_encoding = final_encoding 53 | 54 | @property 55 | def num_parameters(self) -> int: 56 | """The number of trainable parameters of the MultiControlEncodingCircuit encoding circuit.""" 57 | num_param = 3 * (self.num_qubits - 1) * self.num_layers 58 | if self.closed: 59 | num_param += 3 * self.num_layers 60 | return num_param 61 | 62 | @property 63 | def parameter_bounds(self) -> np.ndarray: 64 | """The bounds of the trainable parameters of the MultiControlEncodingCircuit encoding circuit.""" 65 | return np.array([[-2.0 * np.pi, 2.0 * np.pi]] * self.num_parameters) 66 | 67 | def get_params(self, deep: bool = True) -> dict: 68 | """ 69 | Returns hyper-parameters and their values of the MultiControlEncodingCircuit encoding circuit 70 | 71 | Args: 72 | deep (bool): If True, also the parameters for 73 | contained objects are returned (default=True). 74 | 75 | Return: 76 | Dictionary with hyper-parameters and values. 77 | """ 78 | params = super().get_params() 79 | params["num_layers"] = self.num_layers 80 | params["closed"] = self.closed 81 | params["final_encoding"] = self.final_encoding 82 | return params 83 | 84 | def get_circuit( 85 | self, 86 | features: Union[ParameterVector, np.ndarray], 87 | parameters: Union[ParameterVector, np.ndarray], 88 | ) -> QuantumCircuit: 89 | """ 90 | Returns the circuit of the MultiControlEncodingCircuit encoding circuit 91 | 92 | Args: 93 | features (Union[ParameterVector,np.ndarray]): Input vector of the features 94 | from which the gate inputs are obtained. 95 | param_vec (Union[ParameterVector,np.ndarray]): Input vector of the parameters 96 | from which the gate inputs are obtained. 97 | 98 | Return: 99 | Returns the circuit in Qiskit's QuantumCircuit format 100 | """ 101 | 102 | if self.num_qubits < 2: 103 | raise ValueError("MultiControlEncodingCircuit requires at least two qubits.") 104 | 105 | nfeature = len(features) 106 | nparam = len(parameters) 107 | QC = QuantumCircuit(self.num_qubits) 108 | index_offset = 0 109 | feature_offset = 0 110 | 111 | for layer in range(self.num_layers): 112 | # First ZZ-encoding circuit 113 | QC.h(range(self.num_qubits)) 114 | for i in range(self.num_qubits): 115 | QC.rz(features[feature_offset % nfeature], i) 116 | feature_offset += 1 117 | 118 | if self.closed: 119 | istop = self.num_qubits 120 | else: 121 | istop = self.num_qubits - 1 122 | 123 | for i in range(0, istop, 2): 124 | QC.crx(parameters[index_offset % nparam], i, (i + 1) % self.num_qubits) 125 | index_offset += 1 126 | QC.cry(parameters[index_offset % nparam], i, (i + 1) % self.num_qubits) 127 | index_offset += 1 128 | QC.crz(parameters[index_offset % nparam], i, (i + 1) % self.num_qubits) 129 | index_offset += 1 130 | 131 | if self.num_qubits >= 2: 132 | if self.closed: 133 | istop = self.num_qubits 134 | else: 135 | istop = self.num_qubits - 1 136 | 137 | for i in range(1, istop, 2): 138 | QC.crx(parameters[index_offset % nparam], i, (i + 1) % self.num_qubits) 139 | index_offset += 1 140 | QC.cry(parameters[index_offset % nparam], i, (i + 1) % self.num_qubits) 141 | index_offset += 1 142 | QC.crz(parameters[index_offset % nparam], i, (i + 1) % self.num_qubits) 143 | index_offset += 1 144 | 145 | if self.final_encoding: 146 | for i in range(self.num_qubits): 147 | QC.rz(features[feature_offset % nfeature], i) 148 | feature_offset += 1 149 | return QC 150 | -------------------------------------------------------------------------------- /src/squlearn/encoding_circuit/circuit_library/param_z_feature_map.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | 4 | from qiskit.circuit import QuantumCircuit 5 | from qiskit.circuit import ParameterVector 6 | from ..encoding_circuit_base import EncodingCircuitBase 7 | 8 | 9 | class ParamZFeatureMap(EncodingCircuitBase): 10 | """ 11 | Parameterized Z feature map with optional CNOT gates between the default layers. 12 | 13 | This encoding circuit is based on Qiskit's :class:`qiskit.circuit.library.ZFeatureMap`. 14 | 15 | **Example for 4 qubits, a 2 dimensional feature vector and 2 layers with entangling:** 16 | 17 | .. plot:: 18 | 19 | from squlearn.encoding_circuit import ParamZFeatureMap 20 | pqc = ParamZFeatureMap(4, 2, num_layers=2, entangling=True) 21 | plt = pqc.draw(output="mpl", style={'fontsize':15,'subfontsize': 10}) 22 | plt.tight_layout() 23 | 24 | Args: 25 | num_qubits (int): Number of qubits 26 | num_features (int): Dimension of the feature vector 27 | num_layers (int): Number of layers of the encoding circuit 28 | entangling (bool): If true, entangling gates are added between the layers 29 | 30 | """ 31 | 32 | def __init__( 33 | self, num_qubits: int, num_features: int, num_layers: int = 2, entangling: bool = False 34 | ) -> None: 35 | super().__init__(num_qubits, num_features) 36 | self._num_layers = num_layers 37 | self._entangling = entangling 38 | 39 | @property 40 | def num_parameters(self) -> int: 41 | """The number of trainable parameters of the encoding circuit.""" 42 | return max(self._num_qubits, self._num_features) * self._num_layers 43 | 44 | def get_params(self, deep: bool = True) -> dict: 45 | """ 46 | Returns hyper-parameters and their values of the parameterized Z feature map. 47 | 48 | Args: 49 | deep (bool): If True, also the parameters for 50 | contained objects are returned (default=True). 51 | 52 | Return: 53 | Dictionary with hyper-parameters and values. 54 | """ 55 | params = super().get_params() 56 | params["num_layers"] = self._num_layers 57 | params["entangling"] = self._entangling 58 | return params 59 | 60 | @property 61 | def num_layers(self) -> int: 62 | """The number of layers of the encoding circuit.""" 63 | return self._num_layers 64 | 65 | def get_circuit( 66 | self, 67 | features: Union[ParameterVector, np.ndarray], 68 | parameters: Union[ParameterVector, np.ndarray], 69 | ) -> QuantumCircuit: 70 | """ 71 | Returns the circuit of the parameterized Z feature map. 72 | 73 | Args: 74 | features (Union[ParameterVector,np.ndarray]): Input vector of the features 75 | from which the gate inputs are obtained. 76 | param_vec (Union[ParameterVector,np.ndarray]): Input vector of the parameters 77 | from which the gate inputs are obtained. 78 | 79 | Return: 80 | The circuit of the parameterized Z feature map in the form of a QuantumCircuit 81 | """ 82 | 83 | num_features = len(features) 84 | num_param = len(parameters) 85 | 86 | circuit = QuantumCircuit(self._num_qubits) 87 | index_offset = 0 88 | for _ in range(self._num_layers): 89 | for i in range(max(self._num_qubits, self._num_features)): 90 | if i < self._num_qubits: 91 | circuit.h(i) 92 | circuit.p( 93 | parameters[index_offset % num_param] * features[i % num_features], 94 | i % self._num_qubits, 95 | ) 96 | index_offset += 1 97 | 98 | if self._entangling: 99 | if self._num_layers % 2 == 0: 100 | for j in range(self._num_qubits - 1): 101 | circuit.cx(j, j + 1) 102 | else: 103 | for j in range(1, self._num_qubits - 1, 2): 104 | circuit.cx(j, j + 1) 105 | 106 | return circuit 107 | -------------------------------------------------------------------------------- /src/squlearn/encoding_circuit/circuit_library/yz_cx_encoding_circuit.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | from qiskit.circuit import QuantumCircuit 4 | from qiskit.circuit import ParameterVector 5 | 6 | from ..encoding_circuit_base import EncodingCircuitBase 7 | 8 | 9 | class YZ_CX_EncodingCircuit(EncodingCircuitBase): 10 | """ 11 | Creates the YZ-CX Encoding Circuit from reference [1]. 12 | 13 | **Example for 4 qubits, a 4 dimensional feature vector, 2 layers and c = 2.0:** 14 | 15 | .. plot:: 16 | 17 | from squlearn.encoding_circuit import YZ_CX_EncodingCircuit 18 | pqc = YZ_CX_EncodingCircuit(4, 4, 2, c=2.0) 19 | plt = pqc.draw(output="mpl", style={'fontsize':15,'subfontsize': 10}) 20 | plt.tight_layout() 21 | 22 | One combination of Ry and Rz is considered as a single layer. 23 | 24 | Args: 25 | num_qubits (int): Number of qubits of the YZ-CX Encoding Circuit encoding circuit 26 | num_features (int): Dimension of the feature vector 27 | num_layers (int): Number of layers (default: 1) 28 | c (float): Prefactor :math:`c` for rescaling the data (default: 1.0) 29 | 30 | References 31 | ---------- 32 | [1]: T. Haug, C. N. Self and M. S. Kim, "Quantum machine learning of large datasets using 33 | randomized measurements", `arxiv:2108.01039v3 (2021). `_ 34 | """ 35 | 36 | def __init__( 37 | self, 38 | num_qubits: int, 39 | num_features: int, 40 | num_layers: int = 1, 41 | closed: bool = True, 42 | c: float = 1.0, 43 | ) -> None: 44 | super().__init__(num_qubits, num_features) 45 | self.closed = closed 46 | 47 | self._num_layers = num_layers 48 | self._c = c 49 | 50 | @property 51 | def num_parameters(self) -> int: 52 | """The number of trainable parameters of the YZ-CX Encoding Circuit encoding circuit.""" 53 | return 2 * self.num_qubits * self._num_layers 54 | 55 | @property 56 | def parameter_bounds(self) -> np.ndarray: 57 | """The bounds of the trainable parameters of the YZ-CX Encoding Circuit encoding circuit.""" 58 | return np.array([[-np.pi, np.pi]] * self.num_parameters) 59 | 60 | @property 61 | def num_layers(self) -> int: 62 | """The number of layers of the YZ-CX Encoding Circuit encoding circuit.""" 63 | return self._num_layers 64 | 65 | @property 66 | def c(self) -> int: 67 | """The prefactor :math:`c` of the YZ-CX Encoding Circuit encoding circuit.""" 68 | return self._c 69 | 70 | def get_params(self, deep: bool = True) -> dict: 71 | """ 72 | Returns hyper-parameters and their values of the YZ-CX Encoding Circuit encoding circuit 73 | 74 | Args: 75 | deep (bool): If True, also the parameters for 76 | contained objects are returned (default=True). 77 | 78 | Return: 79 | Dictionary with hyper-parameters and values. 80 | """ 81 | params = super().get_params() 82 | params["num_layers"] = self._num_layers 83 | params["c"] = self._c 84 | return params 85 | 86 | def get_circuit( 87 | self, 88 | features: Union[ParameterVector, np.ndarray], 89 | parameters: Union[ParameterVector, np.ndarray], 90 | ) -> QuantumCircuit: 91 | """ 92 | Return the circuit of the YZ-CX encoding circuit. 93 | 94 | Args: 95 | features (Union[ParameterVector,np.ndarray]): Input vector of the features 96 | from which the gate inputs are obtained. 97 | param_vec (Union[ParameterVector,np.ndarray]): Input vector of the parameters 98 | from which the gate inputs are obtained. 99 | 100 | Return: 101 | Returns the circuit in qiskit format. 102 | """ 103 | 104 | nfeature = len(features) 105 | nparam = len(parameters) 106 | 107 | # Creates the layers of the encoding circuit 108 | QC = QuantumCircuit(self.num_qubits) 109 | index_offset = 0 110 | feature_offset = 0 111 | for layer in range(self.num_layers): 112 | for i in range(self.num_qubits): 113 | QC.ry( 114 | parameters[index_offset % nparam] 115 | + self.c * features[feature_offset % nfeature], 116 | i, 117 | ) 118 | index_offset += 1 119 | QC.rz( 120 | parameters[index_offset % nparam] 121 | + self.c * features[feature_offset % nfeature], 122 | i, 123 | ) 124 | index_offset += 1 125 | feature_offset += 1 126 | # Entangling layer depends on odd or even layer 127 | if self.num_qubits >= 2: 128 | for i in range(layer % 2, self.num_qubits + self.closed - 1, 2): 129 | QC.cx(i, (i + 1) % self.num_qubits) 130 | return QC 131 | -------------------------------------------------------------------------------- /src/squlearn/kernel/__init__.py: -------------------------------------------------------------------------------- 1 | from .lowlevel_kernel import FidelityKernel, ProjectedQuantumKernel, KernelOptimizer 2 | 3 | from .qgpr import QGPR 4 | from .qgpc import QGPC 5 | from .qkrr import QKRR 6 | from .qsvr import QSVR 7 | from .qsvc import QSVC 8 | 9 | __all__ = [ 10 | "FidelityKernel", 11 | "ProjectedQuantumKernel", 12 | "KernelOptimizer", 13 | "QGPC", 14 | "QGPR", 15 | "QKRR", 16 | "QSVR", 17 | "QSVC", 18 | ] 19 | -------------------------------------------------------------------------------- /src/squlearn/kernel/loss/__init__.py: -------------------------------------------------------------------------------- 1 | from .negative_log_likelihood import NLL 2 | from .target_alignment import TargetAlignment 3 | 4 | __all__ = ["NLL", "TargetAlignment"] 5 | -------------------------------------------------------------------------------- /src/squlearn/kernel/loss/kernel_loss_base.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from abc import ABC, abstractmethod 3 | 4 | from ..lowlevel_kernel.kernel_matrix_base import KernelMatrixBase 5 | 6 | 7 | class KernelLossBase(ABC): 8 | """Empty parent class for a kernel loss function.""" 9 | 10 | def __init__(self) -> None: 11 | self._quantum_kernel = None 12 | 13 | def set_quantum_kernel(self, quantum_kernel: KernelMatrixBase) -> None: 14 | """Set the quantum kernel matrix to be used in the loss. 15 | 16 | Args: 17 | quantum_kernel (KernelMatrixBase): The quantum kernel matrix to be used in the loss. 18 | """ 19 | self._quantum_kernel = quantum_kernel 20 | 21 | @abstractmethod 22 | def compute( 23 | self, 24 | quantum_kernel: KernelMatrixBase, 25 | parameter_values: np.array, 26 | data: np.ndarray, 27 | labels: np.ndarray, 28 | ) -> float: 29 | """Compute the target alignment loss. 30 | 31 | Args: 32 | quantum_kernel (KernelMatrixBase): The quantum kernel matrix to be used in the loss. 33 | parameter_values (np.ndarray): The parameter values for the variational quantum 34 | kernel parameters. 35 | data (np.ndarray): The training data to be used for the kernel matrix. 36 | labels (np.ndarray): The training labels. 37 | 38 | Returns: 39 | float: The loss value. 40 | """ 41 | raise NotImplementedError 42 | -------------------------------------------------------------------------------- /src/squlearn/kernel/loss/negative_log_likelihood.py: -------------------------------------------------------------------------------- 1 | """Negative log likelihood loss function""" 2 | 3 | import scipy 4 | import numpy as np 5 | 6 | from typing import Sequence 7 | from .kernel_loss_base import KernelLossBase 8 | from ..lowlevel_kernel.kernel_matrix_base import KernelMatrixBase 9 | 10 | 11 | class NLL(KernelLossBase): 12 | r""" 13 | Negative log likelihood loss function. 14 | This class can be used to compute the negative log likelihood loss function 15 | for a given quantum kernel 16 | :math:`K_{\theta}` with variational parameters :math:`\theta`. 17 | The definition of the function is taken from Equation 5.8 Chapter 5.4 of Ref. [1]. 18 | 19 | The log-likelihood function is defined as: 20 | 21 | .. math:: 22 | 23 | L(\theta) = -\frac{1}{2} log(|K_{\theta} + \sigmaI|)-\frac{1}{2} y^{T}(K_{\theta} 24 | + \sigmaI)^{-1}y-\frac{n}{2} log(2\pi) 25 | 26 | Args: 27 | sigma: (float), default=0.0: Hyperparameter for the regularization strength. 28 | 29 | References 30 | ---------- 31 | [1]: `Carl E. Rasmussen and Christopher K.I. Williams, 32 | "Gaussian Processes for Machine Learning", 33 | MIT Press 2006 `_ 34 | 35 | Methods: 36 | -------- 37 | """ 38 | 39 | def __init__(self, sigma=0.0): 40 | super().__init__() 41 | self._sigma = sigma 42 | 43 | def compute( 44 | self, 45 | parameter_values: np.ndarray, 46 | data: np.ndarray, 47 | labels: np.ndarray, 48 | ) -> float: 49 | """Compute the negative log likelihood loss function. 50 | 51 | Args: 52 | parameter_values (np.ndarray): The parameter values for the variational quantum 53 | kernel parameters. 54 | data (np.ndarray): The training data to be used for the kernel matrix. 55 | labels (np.ndarray): The training labels. 56 | 57 | Returns: 58 | float: The negative log likelihood loss value. 59 | """ 60 | 61 | if self._quantum_kernel is None: 62 | raise ValueError( 63 | "Quantum kernel is not set, please set the quantum kernel with set_quantum_kernel method" 64 | ) 65 | 66 | # Bind training parameters 67 | self._quantum_kernel.assign_parameters(parameter_values) 68 | 69 | # get estimated kernel matrix 70 | kmatrix = self._quantum_kernel.evaluate(data) 71 | kmatrix = kmatrix + self._sigma * np.eye(kmatrix.shape[0]) 72 | 73 | # Cholesky decomposition since numerically more stable 74 | L = scipy.linalg.cholesky(kmatrix, lower=True) 75 | S1 = scipy.linalg.solve_triangular(L, labels, lower=True) 76 | S2 = scipy.linalg.solve_triangular(L.T, S1, lower=False) 77 | neg_log_lh = ( 78 | np.sum(np.log(np.diagonal(L))) 79 | + 0.5 * labels.T @ S2 80 | + 0.5 * len(data) * np.log(2.0 * np.pi) 81 | ) 82 | neg_log_lh = neg_log_lh.reshape(-1) 83 | 84 | return neg_log_lh 85 | -------------------------------------------------------------------------------- /src/squlearn/kernel/loss/target_alignment.py: -------------------------------------------------------------------------------- 1 | """Target alignment loss function for kernel matrices.""" 2 | 3 | import numpy as np 4 | 5 | from .kernel_loss_base import KernelLossBase 6 | from ..lowlevel_kernel.kernel_matrix_base import KernelMatrixBase 7 | 8 | 9 | class TargetAlignment(KernelLossBase): 10 | r""" 11 | Target alignment loss function. 12 | This class can be used to compute the target alignment for a given quantum kernel 13 | :math:`K_{\theta}` with variational parameters :math:`\theta`. 14 | The definition of the function is taken from Equation (27,28) of [1]. 15 | The target alignment loss is defined as: 16 | 17 | .. math:: 18 | 19 | TA(K_{\theta}) = 20 | \frac{\\um_{i,j} K_{\theta}(x_i, x_j) y_i y_j} 21 | {\sqrt{\sum_{i,j} K_{\theta}(x_i, x_j)^2 \sum_{i,j} y_i^2 y_j^2}} 22 | 23 | Args: 24 | rescale_class_labels (bool): Whether to rescale the class labels to -1 and 1 25 | (default=True). 26 | 27 | References 28 | ----------- 29 | [1]: T. Hubregtsen et al., 30 | "Training Quantum Embedding Kernels on Near-Term Quantum Computers", 31 | `arXiv:2105.02276v1 (2021) `_. 32 | 33 | Methods: 34 | -------- 35 | """ 36 | 37 | def __init__(self, rescale_class_labels=True) -> None: 38 | """ """ 39 | super().__init__() 40 | self._rescale_class_labels = rescale_class_labels 41 | 42 | def compute( 43 | self, 44 | parameter_values: np.ndarray, 45 | data: np.ndarray, 46 | labels: np.ndarray, 47 | ) -> float: 48 | """Compute the target alignment loss. 49 | 50 | Args: 51 | parameter_values (np.ndarray): The parameter values for the variational quantum kernel 52 | parameters. 53 | data (np.ndarray): The training data to be used for the kernel matrix. 54 | labels (np.ndarray): The labels of the training data. 55 | 56 | Returns: 57 | float: The negative target alignment. 58 | """ 59 | 60 | if self._quantum_kernel is None: 61 | raise ValueError( 62 | "Quantum kernel is not set, please set the quantum kernel with set_quantum_kernel method" 63 | ) 64 | 65 | # Bind training parameters 66 | self._quantum_kernel.assign_parameters(parameter_values) 67 | 68 | # Get estimated kernel matrix 69 | kmatrix = self._quantum_kernel.evaluate(data) 70 | if self._rescale_class_labels: 71 | nplus = np.count_nonzero(np.array(labels) == 1) 72 | nminus = len(labels) - nplus 73 | _Y = np.array([y / nplus if y == 1 else y / nminus for y in labels]) 74 | else: 75 | _Y = np.array(labels) 76 | 77 | T = np.outer(_Y, _Y) 78 | inner_product = np.sum(kmatrix * T) 79 | norm = np.sqrt(np.sum(kmatrix * kmatrix) * np.sum(T * T)) 80 | alignment = inner_product / norm 81 | return -alignment 82 | -------------------------------------------------------------------------------- /src/squlearn/kernel/lowlevel_kernel/__init__.py: -------------------------------------------------------------------------------- 1 | from .fidelity_kernel import FidelityKernel 2 | from .projected_quantum_kernel import ProjectedQuantumKernel 3 | from .kernel_optimizer import KernelOptimizer 4 | 5 | __all__ = ["FidelityKernel", "ProjectedQuantumKernel", "KernelOptimizer"] 6 | -------------------------------------------------------------------------------- /src/squlearn/kernel/lowlevel_kernel/kernel_optimizer.py: -------------------------------------------------------------------------------- 1 | """Quantum Kernel Optimizer""" 2 | 3 | import numpy as np 4 | from functools import partial 5 | from typing import Optional, Sequence 6 | 7 | from .kernel_matrix_base import KernelMatrixBase 8 | from ..loss.kernel_loss_base import KernelLossBase 9 | from ...optimizers.optimizer_base import OptimizerBase 10 | 11 | 12 | class KernelOptimizer(KernelMatrixBase): 13 | """ 14 | Quantum kernel optimizer. 15 | This class can be used to optimize the variational parameters of a quantum kernel. 16 | 17 | Args: 18 | loss (KernelLossBase): The loss function to be minimized. 19 | optimizer (OptimizerBase): The optimizer to be used. 20 | quantum_kernel (KernelMatrixBase): The quantum kernel to be optimized. 21 | initial_parameters (Optional[Sequence[float]]): Initial parameters for the optimizer. 22 | """ 23 | 24 | def __init__( 25 | self, 26 | quantum_kernel: KernelMatrixBase, 27 | loss: KernelLossBase = None, 28 | optimizer: OptimizerBase = None, 29 | initial_parameters: Optional[Sequence[float]] = None, 30 | ) -> None: 31 | super().__init__( 32 | encoding_circuit=quantum_kernel.encoding_circuit, 33 | executor=quantum_kernel._executor, 34 | initial_parameters=initial_parameters, 35 | parameter_seed=quantum_kernel._parameter_seed, 36 | regularization=quantum_kernel._regularization, 37 | ) 38 | self._quantum_kernel = quantum_kernel 39 | self._loss = loss 40 | self._optimizer = optimizer 41 | self._initial_parameters = ( 42 | initial_parameters 43 | if initial_parameters is not None 44 | else self._quantum_kernel.parameters 45 | ) 46 | self._optimal_parameters = None 47 | self._is_trainable = True 48 | self._is_fitted = False 49 | 50 | self._loss.set_quantum_kernel(self._quantum_kernel) 51 | 52 | @property 53 | def is_fitted(self) -> bool: 54 | """Returns whether the quantum kernel has been fitted.""" 55 | return self._is_fitted 56 | 57 | def run_optimization(self, X: np.ndarray, y: np.ndarray = None): 58 | """Run the optimization and return the result. 59 | 60 | Args: 61 | X (np.ndarray): The input data. 62 | y (np.ndarray): The labels. 63 | 64 | Returns: 65 | OptimizeResult: The optimization result. 66 | """ 67 | 68 | if self._is_fitted: 69 | return None 70 | 71 | if self._quantum_kernel.num_parameters == 0: 72 | return None 73 | 74 | # Perform kernel optimization 75 | loss_function = partial(self._loss.compute, data=X, labels=y) 76 | opt_result = self._optimizer.minimize(fun=loss_function, x0=self._initial_parameters) 77 | self._optimal_parameters = opt_result.x 78 | 79 | # Assign optimal parameters to the quantum kernel 80 | self._quantum_kernel.assign_parameters(self._optimal_parameters) 81 | 82 | self._is_fitted = True 83 | 84 | return opt_result 85 | 86 | def assign_parameters(self, parameters: np.ndarray): 87 | """Set the training parameters of the encoding circuit to numerical values 88 | 89 | Args: 90 | parameters (np.ndarray): Array containing numerical values to be assigned to 91 | the trainable parameters of the encoding circuit 92 | """ 93 | self._is_fitted = False 94 | self._quantum_kernel.assign_parameters(parameters) 95 | self._initial_parameters = parameters 96 | 97 | def evaluate(self, x: np.ndarray, y: np.ndarray = None) -> np.ndarray: 98 | """Evaluate the kernel matrix using the current parameters. 99 | 100 | Args: 101 | x (np.ndarray): Vector of training or test data. 102 | y (np.ndarray, optional): Vector of training or test data. 103 | 104 | Returns: 105 | np.ndarray: The evaluated kernel matrix. 106 | """ 107 | return self._quantum_kernel.evaluate(x, y) 108 | 109 | def get_optimal_parameters(self) -> np.ndarray: 110 | """Get the optimal parameters. 111 | 112 | Returns: 113 | np.ndarray: The optimal parameters. 114 | """ 115 | return self._optimal_parameters 116 | 117 | def set_params(self, **params): 118 | """Sets value of the kernel optimizer hyper-parameters. 119 | 120 | Args: 121 | params: Hyper-parameters and their values, e.g. ``num_qubits=2`` 122 | """ 123 | 124 | # Create dictionary of valid parameters 125 | valid_params = self.get_params(deep=True).keys() 126 | for key in params.keys(): 127 | # Check if parameter is valid 128 | if key not in valid_params: 129 | raise ValueError( 130 | f"Invalid parameter {key!r}. " 131 | f"Valid parameters are {sorted(valid_params)!r}." 132 | ) 133 | 134 | if "quantum_kernel" in params: 135 | self._quantum_kernel = params["quantum_kernel"] 136 | self._is_fitted = False 137 | if "loss" in params: 138 | self._loss = params["loss"] 139 | self._is_fitted = False 140 | if "optimizer" in params: 141 | self._optimizer = params["optimizer"] 142 | self._is_fitted = False 143 | if "initial_parameters" in params: 144 | self._initial_parameters = params["initial_parameters"] 145 | self._is_fitted = False 146 | 147 | # Set parameters of the Quantum Kernel and its underlying objects 148 | quantum_kernel_params = self._quantum_kernel.get_params().keys() & params.keys() 149 | if quantum_kernel_params: 150 | self._quantum_kernel.set_params(**{key: params[key] for key in quantum_kernel_params}) 151 | self._is_fitted = False 152 | 153 | return self 154 | 155 | def get_params(self, deep=True) -> dict: 156 | """Returns hyper-parameters and their values of the fidelity kernel. 157 | 158 | Args: 159 | deep (bool): If True, also the parameters for 160 | contained objects are returned (default=True). 161 | 162 | Return: 163 | Dictionary with hyper-parameters and values. 164 | """ 165 | params = {} 166 | params["quantum_kernel"] = self._quantum_kernel 167 | params["loss"] = self._loss 168 | params["optimizer"] = self._optimizer 169 | params["initial_parameters"] = self._initial_parameters 170 | 171 | if deep: 172 | params.update(self._quantum_kernel.get_params(deep=deep)) 173 | 174 | return params 175 | -------------------------------------------------------------------------------- /src/squlearn/kernel/lowlevel_kernel/kernel_util.py: -------------------------------------------------------------------------------- 1 | # kernel util 2 | import numpy as np 3 | 4 | from sklearn.gaussian_process.kernels import Kernel 5 | 6 | from .kernel_matrix_base import KernelMatrixBase 7 | 8 | 9 | def kernel_wrapper(kernel_matrix: KernelMatrixBase): 10 | """ 11 | Wrapper for sQUlearn's KernelMatrixBase to scikit-learn kernel objects. 12 | 13 | Args: 14 | kernel_matrix (KernelMatrixBase) : 15 | Quantum kernel matrix which is to be wrapped into scikit-learn kernel 16 | """ 17 | 18 | class CustomKernel(Kernel): 19 | def __init__(self, kernel_matrix: KernelMatrixBase): 20 | self.kernel_matrix = kernel_matrix 21 | super().__init__() 22 | 23 | def __call__(self, X, Y=None, eval_gradient=False): 24 | if Y is None: 25 | Y = X 26 | kernel_matrix = self.kernel_matrix.evaluate(X, Y) 27 | if eval_gradient: 28 | raise NotImplementedError("Gradient not yet implemented for this kernel.") 29 | else: 30 | return kernel_matrix 31 | 32 | def diag(self, X): 33 | return np.diag(self.kernel_matrix.evaluate(X)) 34 | 35 | @property 36 | def requires_vector_input(self): 37 | return True 38 | 39 | def is_stationary(self): 40 | return self.kernel_matrix.is_stationary() 41 | 42 | return CustomKernel(kernel_matrix) 43 | -------------------------------------------------------------------------------- /src/squlearn/kernel/lowlevel_kernel/regularization.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | 4 | 5 | def thresholding_regularization(gram_matrix): 6 | """ 7 | Thresholding regularization method of a Gram matrix (full or training kernel matrix) 8 | according to this `paper `_ to recover positive 9 | semi-definiteness. This method only changes the negative eigenvalues of the matrix by 10 | setting them to zero. This is done via a full eigenvalue decomposition, adjustment of 11 | the negative eigenvalues and composition of the adjusted spectrum and the original 12 | eigenvectors: 13 | 14 | .. math:: 15 | D = V^T A V 16 | D'_{ij} = \text{max}\lbrace D_{ij},0 \rbrace 17 | R-THR(A) = V D' V^T 18 | 19 | This approach is equivalent to finding the positive semi-definite matrix closest to A 20 | in any unitarily invariant norm. 21 | 22 | Args: 23 | gram_matrix (np.ndarray) : 24 | Gram matrix. 25 | """ 26 | evals, evecs = scipy.linalg.eig(gram_matrix) 27 | reconstruction = evecs @ np.diag(evals.clip(min=0)) @ evecs.T 28 | return np.real(reconstruction) 29 | 30 | 31 | # deprecated regularization technique 32 | def tikhonov_regularization(gram_matrix): 33 | """ 34 | Tikhonov regularization method to recover positive semi-definiteness of a Gram matrix. 35 | In this method the spectrum of the matrix is displaced by its smallest eigenvalue 36 | :math:`\sigma_{min}` if it is negative, by subtracting it from all eigenvalues or 37 | equivalently from the diagonal. 38 | 39 | Args: 40 | gram_matrix (np.ndarray) : 41 | Gram matrix. 42 | """ 43 | evals = scipy.linalg.eigvals(gram_matrix) 44 | shift = np.min(np.real(evals)) 45 | if shift < 0: 46 | gram_matrix -= (shift - 1e-14) * np.identity(gram_matrix.shape[0]) 47 | return gram_matrix 48 | 49 | 50 | def regularize_full_kernel(K_train, K_testtrain, K_test): 51 | """ 52 | Built full Gram matrix from training-, test-train- and test-test kernel matrices 53 | and the regularize full Gram matrix using the `regularize_kernel()` method. 54 | 55 | Args: 56 | K_train (np.ndarray) : 57 | Training kernel matrix of shape (n_train, n_train) 58 | K_testtrain (np.ndarray) : 59 | Test-Train kernel matrix of shape (n_test, n_train) 60 | K_test (np.ndarray) : 61 | Test kernel matrix of shape (n_test, n_test) 62 | """ 63 | gram_matrix_total = np.block([[K_train, K_testtrain.T], [K_testtrain, K_test]]) 64 | reconstruction = thresholding_regularization(gram_matrix_total) 65 | 66 | K_train = reconstruction[: K_train.shape[0], : K_train.shape[1]] 67 | K_testtrain = reconstruction[K_train.shape[0] :, : K_testtrain.shape[1]] 68 | K_test = reconstruction[-K_test.shape[0] :, -K_test.shape[1] :] 69 | 70 | return K_train, K_testtrain, K_test 71 | -------------------------------------------------------------------------------- /src/squlearn/kernel/qgpc.py: -------------------------------------------------------------------------------- 1 | """Quantum Gaussian Process Classification""" 2 | 3 | from sklearn.gaussian_process import GaussianProcessClassifier 4 | 5 | from .lowlevel_kernel.kernel_matrix_base import KernelMatrixBase 6 | from .lowlevel_kernel.kernel_util import kernel_wrapper 7 | 8 | 9 | class QGPC(GaussianProcessClassifier): 10 | """ 11 | Quantum Gaussian process classification (QGPC), that extends the scikit-learn 12 | `sklearn.gaussian_process.GaussianProcessClassifier 13 | `_. 14 | GaussianProcessClassifier class 15 | to use a quantum kernel. 16 | 17 | This class shows how to use a quantum kernel for QGPC. The class inherits its methods 18 | like ``fit`` and ``predict`` from scikit-learn, see the example below. 19 | Read more in the 20 | `scikit-learn user guide 21 | `_. 22 | Additional arguments can be set via ``**kwargs``. 23 | 24 | Args: 25 | quantum_kernel (Union[KernelMatrixBase, str]): The quantum kernel matrix to be used for the GP 26 | (either a fidelity quantum kernel (FQK) 27 | or projected quantum kernel (PQK) must be provided) 28 | **kwargs: Keyword arguments for the quantum kernel matrix, possible arguments can be obtained 29 | by calling ``get_params()``. Can be used to set for example the number of qubits 30 | (``num_qubits=``), or (if supported) the number of layers (``num_layers=``) 31 | of the underlying encoding circuit. 32 | 33 | See Also 34 | -------- 35 | squlearn.kernel.QSVC : Quantum Support Vector classification. 36 | 37 | **Example** 38 | 39 | .. code-block:: 40 | 41 | from sklearn.datasets import load_iris 42 | from squlearn import Executor 43 | from squlearn.encoding_circuit import HubregtsenEncodingCircuit 44 | from squlearn.kernel.lowlevel_kernel import FidelityKernel 45 | from squlearn.kernel import QGPC 46 | X, y = load_iris(return_X_y=True) 47 | 48 | enc_circ = HubregtsenEncodingCircuit(num_qubits=X.shape[1], num_features=X.shape[1], num_layers=2) 49 | q_kernel = FidelityKernel(encoding_circuit=enc_circ, executor=Executor()) 50 | q_kernel.assign_parameters(np.random.rand(enc_circ.num_parameters)) 51 | qgpc_ansatz = QGPC(quantum_kernel=q_kernel) 52 | qgpc_ansatz.fit(X, y) 53 | qgpc_ansatz.score(X, y) 54 | 0.98... 55 | qgpc_ansatz.predict_proba(X[:2,:]) 56 | array([[0.85643716, 0.07037611, 0.07318673], 57 | [0.80314475, 0.09988938, 0.09696586]]) 58 | 59 | Methods: 60 | -------- 61 | """ 62 | 63 | def __init__(self, quantum_kernel: KernelMatrixBase, **kwargs) -> None: 64 | self._quantum_kernel = quantum_kernel 65 | 66 | # Apply kwargs to set_params of quantum kernel 67 | 68 | print("self.quantum_kernel", self.quantum_kernel) 69 | print("kwargs", kwargs) 70 | 71 | quantum_kernel_update_params = self.quantum_kernel.get_params().keys() & kwargs.keys() 72 | if quantum_kernel_update_params: 73 | self.quantum_kernel.set_params( 74 | **{key: kwargs[key] for key in quantum_kernel_update_params} 75 | ) 76 | # remove quantum_kernel_kwargs for SVR initialization 77 | for key in quantum_kernel_update_params: 78 | kwargs.pop(key, None) 79 | 80 | super().__init__(**kwargs) 81 | self.kernel = kernel_wrapper(self._quantum_kernel) 82 | 83 | @classmethod 84 | def _get_param_names(cls): 85 | names = GaussianProcessClassifier._get_param_names() 86 | names.remove("kernel") 87 | names.remove("warm_start") 88 | return names 89 | 90 | def fit(self, X, y): 91 | """Fit Gaussian process classification model. 92 | 93 | Args: 94 | X : array-like of shape (n_samples, n_features) or list of object 95 | Feature vectors or other representations of training data. 96 | 97 | y : array-like of shape (n_samples,) 98 | Target values, must be binary. 99 | 100 | Return: 101 | Returns an instance of self. 102 | """ 103 | if self._quantum_kernel.is_trainable: 104 | self._quantum_kernel.run_optimization(X, y) 105 | return super().fit(X, y) 106 | 107 | def get_params(self, deep: bool = True) -> dict: 108 | """ 109 | Returns hyper-parameters and their values of the QGPC class. 110 | 111 | Args: 112 | deep (bool): If True, also the parameters for 113 | contained objects are returned (default=True). 114 | 115 | Return: 116 | Dictionary with hyper-parameters and values. 117 | """ 118 | params = dict() 119 | 120 | # get parameters from the parent GPC class 121 | for key in self._get_param_names(): 122 | params[key] = getattr(self, key) 123 | 124 | # add qgpc specific parameters 125 | params["quantum_kernel"] = self._quantum_kernel 126 | if deep: 127 | params.update(self._quantum_kernel.get_params(deep=deep)) 128 | 129 | return params 130 | 131 | def set_params(self, **params) -> None: 132 | """ 133 | Sets value of the QGPC hyper-parameters. 134 | 135 | Args: 136 | params: Hyper-parameters and their values, e.g. ``num_qubits=2``. 137 | """ 138 | valid_params = self.get_params(deep=True).keys() 139 | for key in params.keys(): 140 | if key not in valid_params: 141 | raise ValueError( 142 | f"Invalid parameter {key!r}. " 143 | f"Valid parameters are {sorted(valid_params)!r}." 144 | ) 145 | 146 | self_params = self.get_params(deep=False).keys() & params.keys() 147 | for key in self_params: 148 | try: 149 | setattr(self, key, params[key]) 150 | except AttributeError: 151 | setattr(self, "_" + key, params[key]) 152 | 153 | # Set parameters of the Quantum Kernel and its underlying objects 154 | quantum_kernel_params = self._quantum_kernel.get_params().keys() & params.keys() 155 | if quantum_kernel_params: 156 | self._quantum_kernel.set_params(**{key: params[key] for key in quantum_kernel_params}) 157 | return self 158 | 159 | @property 160 | def quantum_kernel(self) -> KernelMatrixBase: 161 | """Returns quantum kernel""" 162 | return self._quantum_kernel 163 | 164 | @quantum_kernel.setter 165 | def quantum_kernel(self, quantum_kernel: KernelMatrixBase): 166 | """Sets quantum kernel""" 167 | self._quantum_kernel = quantum_kernel 168 | self.kernel = kernel_wrapper(quantum_kernel) 169 | -------------------------------------------------------------------------------- /src/squlearn/observables/__init__.py: -------------------------------------------------------------------------------- 1 | from .observable_implemented.summed_probabilities import SummedProbabilities 2 | from .observable_implemented.summed_paulis import SummedPaulis 3 | from .observable_implemented.single_pauli import SinglePauli 4 | from .observable_implemented.single_probability import SingleProbability 5 | from .observable_implemented.custom_observable import ( 6 | CustomObservable, 7 | ) 8 | from .observable_implemented.ising_hamiltonian import IsingHamiltonian 9 | 10 | __all__ = [ 11 | "SinglePauli", 12 | "SummedPaulis", 13 | "SingleProbability", 14 | "SummedProbabilities", 15 | "CustomObservable", 16 | "IsingHamiltonian", 17 | ] 18 | -------------------------------------------------------------------------------- /src/squlearn/observables/observable_implemented/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sQUlearn/squlearn/f21f3ca4a4c90b95d4da763f08c1ff815cb5afdf/src/squlearn/observables/observable_implemented/__init__.py -------------------------------------------------------------------------------- /src/squlearn/observables/observable_implemented/custom_observable.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | 4 | from qiskit.circuit import ParameterVector 5 | from qiskit.quantum_info import SparsePauliOp 6 | 7 | from ..observable_base import ObservableBase 8 | 9 | 10 | class CustomObservable(ObservableBase): 11 | r""" 12 | Class for defining a custom observable. 13 | 14 | The operator is supplied as a string of Pauli operators, e.g. ``operator_string='ZI'`` for 15 | a two qubit operator with a Z operator on the second qubit. 16 | Note that the index of the qubits is reversed, i.e. the first qubit is the last character 17 | in the string, similar to the Qiskit computational state numbering. 18 | 19 | Multiple operators that are summed can be specified by a list of strings, e.g. 20 | ``operator_string=['ZZ', 'XX']``. 21 | 22 | Args: 23 | num_qubits (int): Number of qubits. 24 | operator_string (Union[str, list[str], tuple[str]]): String of operator to measure. 25 | Also list or tuples of strings are allowed for multiple operators. 26 | parameterized (bool): If True, the operator is parameterized. 27 | 28 | Attributes: 29 | ----------- 30 | 31 | Attributes: 32 | num_qubits (int): Number of qubits. 33 | num_parameters (int): Number of trainable parameters in the custom operator. 34 | operator_string (Union[str, list[str], tuple[str]]): String of operator to measure. 35 | parameterized (bool): If True, the operator is parameterized. 36 | 37 | Methods: 38 | -------- 39 | """ 40 | 41 | def __init__( 42 | self, 43 | num_qubits: int, 44 | operator_string: Union[str, list[str], tuple[str]], 45 | parameterized: bool = False, 46 | ) -> None: 47 | super().__init__(num_qubits) 48 | 49 | self.operator_string = operator_string 50 | if isinstance(self.operator_string, str): 51 | self.operator_string = [self.operator_string] 52 | self.parameterized = parameterized 53 | 54 | for s in self.operator_string: 55 | if len(s) != self.num_qubits: 56 | raise ValueError( 57 | "Supplied string has not the same size as the number of qubits, " 58 | + "please add missing identities as 'I'" 59 | ) 60 | for s_ in s: 61 | if s_ not in ["I", "X", "Y", "Z"]: 62 | raise ValueError("Only Pauli operators I, X, Y, Z are allowed.") 63 | 64 | @property 65 | def num_parameters(self): 66 | """Returns the number of trainable parameters in the custom operator""" 67 | if self.parameterized: 68 | return len(self.operator_string) 69 | else: 70 | return 0 71 | 72 | def get_params(self, deep: bool = True) -> dict: 73 | """ 74 | Returns hyper-parameters and their values of the custom operator. 75 | 76 | Args: 77 | deep (bool): If True, also the parameters for 78 | contained objects are returned (default=True). 79 | 80 | Return: 81 | Dictionary with hyper-parameters and values. 82 | """ 83 | params = super().get_params() 84 | params["operator_string"] = self.operator_string 85 | params["parameterized"] = self.parameterized 86 | return params 87 | 88 | def get_pauli(self, parameters: Union[ParameterVector, np.ndarray] = None) -> SparsePauliOp: 89 | """ 90 | Function for generating the SparsePauliOp expression of the custom operator. 91 | 92 | Args: 93 | parameters (Union[ParameterVector, np.ndarray]): Parameters of the custom operator. 94 | 95 | Returns: 96 | SparsePauliOp expression of the specified custom operator. 97 | """ 98 | 99 | op_list = [] 100 | param_list = [] 101 | 102 | if self.parameterized: 103 | nparam = len(parameters) 104 | op_list.append(self.operator_string[0]) 105 | param_list.append(parameters[0 % nparam]) 106 | 107 | ioff = 1 108 | for j in range(1, len(self.operator_string)): 109 | op_list.append(self.operator_string[j]) 110 | param_list.append(parameters[ioff % nparam]) 111 | ioff = ioff + 1 112 | return SparsePauliOp(op_list, param_list) 113 | 114 | else: 115 | op_list.append(self.operator_string[0]) 116 | for j in range(1, len(self.operator_string)): 117 | op_list.append(self.operator_string[j]) 118 | return SparsePauliOp(op_list) 119 | -------------------------------------------------------------------------------- /src/squlearn/observables/observable_implemented/single_pauli.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | 4 | from qiskit.circuit import ParameterVector 5 | from qiskit.quantum_info import SparsePauliOp 6 | 7 | from ..observable_base import ObservableBase 8 | 9 | 10 | class SinglePauli(ObservableBase): 11 | r""" 12 | Observable constructed from a single Pauli operator of a single Qubit. 13 | 14 | **Equation for Z Pauli operator:** 15 | 16 | .. math:: 17 | 18 | \hat{H} = \hat{Z_i} \qquad \text{or} \qquad \hat{H} = \theta\hat{Z_i}~~~~ 19 | \text{ (parameterized)} 20 | 21 | Can be parameterized or not, the four Pauli operators :math:`\hat{X},\hat{Y},\hat{Z}` 22 | and :math:`\hat{I}` are supported. 23 | 24 | Args: 25 | num_qubits (int): Number of qubits. 26 | qubit (int): Qubit on which the Pauli operator acts. 27 | op_str (str): Pauli operator to measure. Must be one of ``'I'``, ``'X'``, ``'Y'``, ``'Z'`` 28 | (default: ``'Z'``). 29 | parameterized (bool): If True, the operator is parameterized (default: False). 30 | 31 | Attributes: 32 | ----------- 33 | 34 | Attributes: 35 | num_qubits (int): Number of qubits. 36 | num_parameters (int): Number of trainable parameters in the single Pauli operator. 37 | qubit (int): Qubit on which the Pauli operator acts. 38 | op_str (str): Pauli operator to measure. 39 | parameterized (bool): If True, the operator is parameterized. 40 | 41 | Methods: 42 | -------- 43 | """ 44 | 45 | def __init__( 46 | self, num_qubits: int, qubit: int = 0, op_str: str = "Z", parameterized: bool = False 47 | ) -> None: 48 | super().__init__(num_qubits) 49 | 50 | self.qubit = qubit 51 | self.op_str = op_str 52 | self.parameterized = parameterized 53 | 54 | if self.op_str not in ["I", "X", "Y", "Z"]: 55 | raise ValueError("Specified operator not supported") 56 | 57 | @property 58 | def num_parameters(self): 59 | """The number of trainable parameters in the single Pauli operator""" 60 | 61 | if self.parameterized: 62 | return 1 63 | else: 64 | return 0 65 | 66 | def get_params(self, deep: bool = True) -> dict: 67 | """ 68 | Returns hyper-parameters and their values of the Single Pauli operator. 69 | 70 | Args: 71 | deep (bool): If True, also the parameters for 72 | contained objects are returned (default=True). 73 | 74 | Return: 75 | Dictionary with hyper-parameters and values. 76 | """ 77 | params = super().get_params() 78 | params["qubit"] = self.qubit 79 | params["op_str"] = self.op_str 80 | params["parameterized"] = self.parameterized 81 | return params 82 | 83 | def get_pauli(self, parameters: Union[ParameterVector, np.ndarray]) -> SparsePauliOp: 84 | """ 85 | Function for generating the SparsePauliOp expression of the single Pauli operator. 86 | 87 | Args: 88 | parameters (Union[ParameterVector, np.ndarray]): Parameters of the single 89 | Pauli operator. 90 | 91 | Return: 92 | SparsePauliOp expression of the specified single Pauli operator. 93 | """ 94 | 95 | i = self.qubit 96 | if 0 > i or self.num_qubits <= i: 97 | raise ValueError("Specified qubit out of range") 98 | 99 | H = "I" * self.num_qubits 100 | if self.parameterized: 101 | return SparsePauliOp([H[(i + 1) :] + self.op_str + H[:i]], [parameters[0]]) 102 | 103 | return SparsePauliOp([H[(i + 1) :] + self.op_str + H[:i]]) 104 | -------------------------------------------------------------------------------- /src/squlearn/observables/observable_implemented/single_probability.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | 4 | from qiskit.circuit import ParameterVector 5 | from qiskit.quantum_info import SparsePauliOp 6 | 7 | from ..observable_base import ObservableBase 8 | 9 | 10 | class SingleProbability(ObservableBase): 11 | r""" 12 | Observable for measuring the probability of being in state 0 or 1 of a specified qubit. 13 | 14 | **Equation as the operator is implemented:** 15 | 16 | .. math:: 17 | 18 | \hat{H} = 0.5(\hat{I}_i+\hat{Z}_i) (= \ket{0}\bra{0}_i) \qquad \text{or} \qquad 19 | \hat{H} = 0.5(\hat{I}_i-\hat{Z}_i) (= \ket{1}\bra{1}_i) 20 | 21 | Operator can be optionally parameterized. 22 | 23 | Args: 24 | num_qubits (int): Number of qubits. 25 | qubit (int): Qubit to measure the probability of. 26 | one_state (bool): If True, measure the probability of being in state 1, otherwise state 0 27 | (default: False). 28 | parameterized (bool): If True, the operator is parameterized (default: false). 29 | 30 | Attributes: 31 | ----------- 32 | 33 | Attributes: 34 | num_qubits (int): Number of qubits. 35 | num_parameters (int): Number of trainable parameters in the single Pauli operator. 36 | qubit (int): Qubit to measure the probability of. 37 | one_state (bool): If True, measure the probability of being in state 1, otherwise state 0. 38 | parameterized (bool): If True, the operator is parameterized. 39 | 40 | Methods: 41 | -------- 42 | """ 43 | 44 | def __init__( 45 | self, 46 | num_qubits: int, 47 | qubit: int = 0, 48 | one_state: bool = False, 49 | parameterized: bool = False, 50 | ) -> None: 51 | super().__init__(num_qubits) 52 | 53 | self.qubit = qubit 54 | self.one_state = one_state 55 | self.parameterized = parameterized 56 | 57 | @property 58 | def num_parameters(self): 59 | """Number of trainable parameters in the single probability operator.""" 60 | 61 | if self.parameterized: 62 | return 1 63 | else: 64 | return 0 65 | 66 | def get_params(self, deep: bool = True) -> dict: 67 | """ 68 | Returns hyper-parameters and their values of the single probability operator. 69 | 70 | Args: 71 | deep (bool): If True, also the parameters for 72 | contained objects are returned (default=True). 73 | 74 | Return: 75 | Dictionary with hyper-parameters and values. 76 | """ 77 | params = super().get_params() 78 | params["qubit"] = self.qubit 79 | params["one_state"] = self.one_state 80 | params["parameterized"] = self.parameterized 81 | return params 82 | 83 | def get_pauli(self, parameters: Union[ParameterVector, np.ndarray] = None) -> SparsePauliOp: 84 | """ 85 | Function for generating the SparsePauliOp expression of the single probability operator. 86 | 87 | Args: 88 | parameters (Union[ParameterVector, np.ndarray]): Parameters of the single 89 | probability operator. 90 | 91 | Return: 92 | SparsePauliOp expression of the specified single probability operator. 93 | """ 94 | 95 | i = self.qubit 96 | if 0 > i or self.num_qubits <= i: 97 | raise ValueError("Specified qubit out of range") 98 | I = "I" * self.num_qubits 99 | Z = I[(i + 1) :] + "Z" + I[:i] 100 | 101 | if self.parameterized: 102 | coeff = 0.5 * parameters[0] 103 | else: 104 | coeff = 0.5 105 | 106 | if self.one_state: 107 | return SparsePauliOp([I, Z], [coeff, -coeff]) 108 | 109 | else: 110 | return SparsePauliOp([I, Z], [coeff, coeff]) 111 | -------------------------------------------------------------------------------- /src/squlearn/observables/observable_implemented/summed_paulis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | 4 | from qiskit.circuit import ParameterVector 5 | from qiskit.quantum_info import SparsePauliOp 6 | 7 | from ..observable_base import ObservableBase 8 | 9 | 10 | class SummedPaulis(ObservableBase): 11 | r""" 12 | Observable for summation of single Pauli operators. 13 | 14 | **Equation for Z Pauli operator:** 15 | 16 | .. math:: 17 | \hat{H} = a\hat{I} + \sum_i b_i \hat{Z}_i 18 | 19 | Multiple Pauli operators can be specified by a tuple of strings, e.g. ``op_str=("X","Z")``: 20 | 21 | .. math:: 22 | \hat{H} = a\hat{I} + \sum_i b_i \hat{X}_i + \sum_i c_i \hat{Z}_i 23 | 24 | The parameter can optionally be equal for the same kind of Pauli operators. 25 | 26 | Args: 27 | num_qubits (int): Number of qubits. 28 | op_str (Union[str,tuple[str]]): String of the Pauli operator that is measured. 29 | Possible values are ``"I"``, ``"X"``, ``"Y"``, ``"Z"``. 30 | Also a tuples of strings are allowed for multiple Pauli 31 | operators (default: ``"Z"``). 32 | full_sum (bool): If False, only one parameter is used for each Pauli operator, 33 | i.e. the sum is :math:`b\sum_i \hat{Z}_i` 34 | instead of :math:`\sum_i b_i \hat{Z}_i` (default: True). 35 | include_identity (bool): If True, the identity operator is included in the sum. 36 | (default: True) 37 | 38 | Attributes: 39 | ----------- 40 | 41 | Attributes: 42 | num_qubits (int): Number of qubits. 43 | num_parameters (int): Number of trainable parameters in the summed Paulis operator. 44 | op_str (Union[str,tuple[str]]): String of the Pauli operator that is measured. 45 | full_sum (bool): If False, only one parameter is used for each Pauli operator. 46 | include_identity (bool): If True, the identity operator is included in the sum. 47 | 48 | Methods: 49 | -------- 50 | """ 51 | 52 | def __init__( 53 | self, 54 | num_qubits: int, 55 | op_str: Union[str, tuple[str]] = "Z", 56 | full_sum: bool = True, 57 | include_identity: bool = True, 58 | ) -> None: 59 | super().__init__(num_qubits) 60 | self.op_str = op_str 61 | self.full_sum = full_sum 62 | self.include_identity = include_identity 63 | 64 | for s in self.op_str: 65 | if s not in ["I", "X", "Y", "Z"]: 66 | raise ValueError("Only Pauli operators I, X, Y, Z are allowed.") 67 | 68 | @property 69 | def num_parameters(self): 70 | """Number of trainable parameters in the summed Pauli operator""" 71 | num_param = 0 72 | if self.include_identity: 73 | num_param += 1 74 | if self.full_sum: 75 | return num_param + len(self.op_str) * self.num_qubits 76 | else: 77 | return num_param + len(self.op_str) 78 | 79 | def get_params(self, deep: bool = True) -> dict: 80 | """ 81 | Returns hyper-parameters and their values of the single summed Pauli operator. 82 | 83 | Args: 84 | deep (bool): If True, also the parameters for 85 | contained objects are returned (default=True). 86 | 87 | Return: 88 | Dictionary with hyper-parameters and values. 89 | """ 90 | params = super().get_params() 91 | params["op_str"] = self.op_str 92 | params["full_sum"] = self.full_sum 93 | params["include_identity"] = self.include_identity 94 | return params 95 | 96 | def get_pauli(self, parameters: Union[ParameterVector, np.ndarray]) -> SparsePauliOp: 97 | """ 98 | Function for generating the PauliOp expression of the summed Paulis operator. 99 | 100 | Args: 101 | parameters (Union[ParameterVector, np.ndarray]): Parameters of the summed 102 | Paulis operator. 103 | 104 | Return: 105 | PauliOp expression of the specified summed Paulis operator. 106 | """ 107 | 108 | def gen_string(i, op_str): 109 | H = "I" * self.num_qubits 110 | H = H[i + 1 :] + op_str + H[:i] 111 | return H 112 | 113 | nparam = len(parameters) 114 | ioff = 0 115 | 116 | op_list = [] 117 | param_list = [] 118 | 119 | if self.include_identity: 120 | op_list.append("I" * self.num_qubits) 121 | param_list.append(parameters[ioff % nparam]) 122 | ioff += 1 123 | 124 | for op_str in self.op_str: 125 | for i in range(self.num_qubits): 126 | op_list.append(gen_string(i, op_str)) 127 | param_list.append(parameters[ioff % nparam]) 128 | if self.full_sum: 129 | ioff += 1 130 | if not self.full_sum: 131 | ioff += 1 132 | 133 | return SparsePauliOp(op_list, np.array(param_list)) 134 | -------------------------------------------------------------------------------- /src/squlearn/observables/observable_implemented/summed_probabilities.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | 4 | from qiskit.circuit import ParameterVector 5 | from qiskit.quantum_info import SparsePauliOp 6 | 7 | from ..observable_base import ObservableBase 8 | 9 | 10 | class SummedProbabilities(ObservableBase): 11 | r""" 12 | Observable for summing single Qubit probabilities of binary states. 13 | 14 | **Equation for a sum of 0-states:** 15 | 16 | .. math:: 17 | \hat{H} = a\hat{I} + \sum_i b_i (\ket{0}\bra{0})_i 18 | 19 | States are implemented by :math:`\ket{0}\bra{0} = 0.5(\hat{I}+\hat{Z})` 20 | and :math:`\ket{1}\bra{1} = 0.5(\hat{I}-\hat{Z})`. 21 | 22 | The parameter can be optionally equal for all states. 23 | 24 | Args: 25 | num_qubits (int): Number of qubits. 26 | one_state (bool): If false the :math:`\ket{0}\bra{0}` state is measured, 27 | if true the :math:`\ket{1}\bra{1}` state is measured (default: False). 28 | full_sum (bool): If False, the parameter is the same for all states, 29 | i.e. the sum is :math:`b\sum_i (\ket{0}\bra{0})_i` 30 | instead of :math:`\sum_i b_i (\ket{0}\bra{0})_i` 31 | (default: True). 32 | include_identity (bool): If True, the identity operator is included in the sum. 33 | (default: True) 34 | 35 | Attributes: 36 | ----------- 37 | 38 | Attributes: 39 | num_qubits (int): Number of qubits. 40 | num_parameters (int): Number of trainable parameters in the summed probabilities operator. 41 | one_state (bool): If false the :math:`\ket{0}\bra{0}` state is measured, 42 | if true the :math:`\ket{1}\bra{1}` state is measured. 43 | full_sum (bool): If False, the parameter is the same for all states. 44 | include_identity (bool): If True, the identity operator is included in the sum. 45 | 46 | Methods: 47 | -------- 48 | """ 49 | 50 | def __init__( 51 | self, 52 | num_qubits: int, 53 | one_state=False, 54 | full_sum: bool = True, 55 | include_identity: bool = True, 56 | ) -> None: 57 | super().__init__(num_qubits) 58 | self.one_state = one_state 59 | self.full_sum = full_sum 60 | self.include_identity = include_identity 61 | 62 | @property 63 | def num_parameters(self): 64 | """The number of trainable parameters in the summed probabilities operator""" 65 | num_param = 0 66 | if self.include_identity: 67 | num_param += 1 68 | if self.full_sum: 69 | num_param += self.num_qubits 70 | else: 71 | num_param += 1 72 | return num_param 73 | 74 | def get_params(self, deep: bool = True) -> dict: 75 | """ 76 | Returns hyper-parameters and their values of the single summed probabilities operator. 77 | 78 | Args: 79 | deep (bool): If True, also the parameters for 80 | contained objects are returned (default=True). 81 | 82 | Return: 83 | Dictionary with hyper-parameters and values. 84 | """ 85 | params = super().get_params() 86 | params["one_state"] = self.one_state 87 | params["full_sum"] = self.full_sum 88 | params["include_identity"] = self.include_identity 89 | return params 90 | 91 | def get_pauli(self, parameters: Union[ParameterVector, np.ndarray] = None) -> SparsePauliOp: 92 | """ 93 | Function for generating the PauliOp expression of the summed probabilities operator. 94 | 95 | Args: 96 | parameters (Union[ParameterVector, np.ndarray]): Parameters of the summed 97 | probabilities operator. 98 | 99 | Returns: 100 | PauliOp expression of the specified summed probabilities operator. 101 | """ 102 | 103 | nparam = len(parameters) 104 | 105 | op_list = [] 106 | coeff_list = [] 107 | 108 | if self.include_identity: 109 | op_list.append("I" * self.num_qubits) 110 | coeff_list.append(parameters[0 % nparam]) 111 | 112 | ioff = 1 113 | for i in range(self.num_qubits): 114 | I = "I" * self.num_qubits 115 | Z = I[(i + 1) :] + "Z" + I[:i] 116 | 117 | if self.one_state: 118 | op_list.append(I) 119 | coeff_list.append(parameters[ioff % nparam] * 0.5) 120 | op_list.append(Z) 121 | coeff_list.append(-parameters[ioff % nparam] * 0.5) 122 | else: 123 | op_list.append(I) 124 | coeff_list.append(parameters[ioff % nparam] * 0.5) 125 | op_list.append(Z) 126 | coeff_list.append(parameters[ioff % nparam] * 0.5) 127 | if self.full_sum: 128 | ioff += 1 129 | 130 | return SparsePauliOp(op_list, coeff_list) 131 | -------------------------------------------------------------------------------- /src/squlearn/optimizers/__init__.py: -------------------------------------------------------------------------------- 1 | """Module for optimizer implementations and wrappers.""" 2 | 3 | from .adam import Adam 4 | from .sglbo import SGLBO 5 | from .optimizers_wrapper import SLSQP, SPSA, LBFGSB 6 | from .approximated_gradients import FiniteDiffGradient, StochasticPerturbationGradient 7 | 8 | __all__ = [ 9 | "Adam", 10 | "SGLBO", 11 | "SLSQP", 12 | "SPSA", 13 | "LBFGSB", 14 | "FiniteDiffGradient", 15 | "StochasticPerturbationGradient", 16 | ] 17 | -------------------------------------------------------------------------------- /src/squlearn/optimizers/approximated_gradients.py: -------------------------------------------------------------------------------- 1 | """Classes for approximated gradients""" 2 | 3 | import numpy as np 4 | 5 | 6 | class ApproxGradientBase: 7 | """Base class for evaluating approximated gradients""" 8 | 9 | def gradient(self, x: np.ndarray) -> np.ndarray: 10 | """Function that calculates the approximated gradient for given input x 11 | 12 | Args: 13 | x (np.ndarray): Input location at which the gradient is calculated 14 | 15 | Returns: 16 | Approximated gradient with the same dimension as x (np.ndarray) 17 | 18 | """ 19 | raise NotImplementedError() 20 | 21 | def __call__(self, x: np.ndarray) -> np.ndarray: 22 | return self.gradient(x) 23 | 24 | 25 | class FiniteDiffGradient(ApproxGradientBase): 26 | """ 27 | Class for evaluating the finite differences gradient. 28 | 29 | Possible implementations are: 30 | 31 | Forward: [f(x+eps)-f(x)]/eps 32 | Backwards: [f(x)-f(x-eps)]/eps 33 | Central (default): [f(x+eps)-f(x-eps)]/2*eps 34 | Five-point: [-f(x+2eps)+8f(x+eps)-8f(x-eps)+f(x-2eps)]/12eps 35 | 36 | Args: 37 | fun (callable): Callable function for the gradient evaluation 38 | eps (float): Step for finite differences 39 | formula (str): type of finite differences. Possible values for type are 40 | 'forward', 'backwards', 'central', and 'five-point' 41 | """ 42 | 43 | def __init__(self, fun: callable, eps: float = 0.01, formula: str = "central") -> None: 44 | self.fun = fun 45 | self.eps = eps 46 | self.formula = formula 47 | 48 | if formula not in ("central", "forward", "backwards", "five-point"): 49 | raise ValueError("Wrong value of formula: " + formula) 50 | 51 | def gradient(self, x: np.ndarray) -> np.ndarray: 52 | """Function that calculates the approximated gradient for given input x 53 | 54 | Args: 55 | x (np.ndarray): Input location at which the gradient is calculated 56 | 57 | Returns: 58 | Approximated gradient with the same dimension as x (np.ndarray) 59 | 60 | """ 61 | if len(x.shape) != 1: 62 | raise ValueError("Unsupported shape of x!") 63 | 64 | if self.formula == "forward": 65 | f0 = self.fun(x) 66 | g = np.zeros(len(x)) 67 | for i in range(len(x)): 68 | dx = np.eye(1, len(x), k=i)[0] * self.eps 69 | g[i] = ((self.fun(x + dx) - f0)) / self.eps # Extract scalar 70 | 71 | elif self.formula == "backwards": 72 | f0 = self.fun(x) 73 | g = np.zeros(len(x)) 74 | for i in range(len(x)): 75 | dx = np.eye(1, len(x), k=i)[0] * self.eps 76 | g[i] = ((f0 - self.fun(x - dx))) / self.eps # Extract scalar 77 | 78 | elif self.formula == "central": 79 | g = np.zeros(len(x)) 80 | for i in range(len(x)): 81 | dx = np.eye(1, len(x), k=i)[0] * self.eps 82 | g[i] = ((self.fun(x + dx) - self.fun(x - dx))) / (2.0 * self.eps) # Extract scalar 83 | 84 | elif self.formula == "five-point": 85 | g = np.zeros(len(x)) 86 | for i in range(len(x)): 87 | dx = np.eye(1, len(x), k=i)[0] * self.eps 88 | g[i] = ( 89 | -1.0 * self.fun(x + 2.0 * dx) 90 | + 8.0 * self.fun(x + 1.0 * dx) 91 | - 8.0 * self.fun(x - 1.0 * dx) 92 | + 1.0 * self.fun(x - 2.0 * dx) 93 | ) / ( 94 | 12.0 * self.eps 95 | ) # Extract scalar 96 | else: 97 | raise ValueError("Wrong value of type: " + self.formula) 98 | 99 | return g 100 | 101 | 102 | class StochasticPerturbationGradient(ApproxGradientBase): 103 | """ 104 | Class for evaluating the stochastic perturbation gradient estimation. 105 | 106 | g_i = f(x+eps*r)-f(x-eps*r)/2*eps*r_i with random vector r 107 | 108 | This is used in the SPSA optimization. 109 | 110 | Args: 111 | fun (callable): Callable function for the gradient evaluation 112 | eps (float): Step for difference 113 | seed (int): Seed for the random vector generation 114 | """ 115 | 116 | def __init__(self, fun: callable, eps: float = 0.1, seed: int = 0) -> None: 117 | self.fun = fun 118 | self.eps = eps 119 | self.rng = np.random.default_rng(seed=seed) 120 | 121 | def set_eps(self, eps) -> None: 122 | """Setter for the eps value (is often dynamically adjusted)""" 123 | self.eps = eps 124 | 125 | def gradient(self, x: np.ndarray) -> np.ndarray: 126 | """Function that calculates the approximated gradient for given input x 127 | 128 | Args: 129 | x (np.ndarray): Input location at which the gradient is calculated 130 | 131 | Returns: 132 | Approximated gradient with the same dimension as x (np.ndarray) 133 | 134 | """ 135 | if len(x.shape) != 1: 136 | raise ValueError("Unsupported shape of x!") 137 | 138 | pert = self.rng.random(len(x)) 139 | 140 | f1 = self.fun(x + self.eps * pert) 141 | f2 = self.fun(x - self.eps * pert) 142 | 143 | return np.divide(f1 - f2, 2.0 * self.eps * pert) 144 | -------------------------------------------------------------------------------- /src/squlearn/optimizers/optimizer_base.py: -------------------------------------------------------------------------------- 1 | """Optimization methods in sQUlearn.""" 2 | 3 | import abc 4 | import numpy as np 5 | 6 | 7 | def default_callback(*args): 8 | """Default callback function.""" 9 | pass 10 | 11 | 12 | class OptimizerResult: # TODO: maybe scipy class? 13 | """Class for holding the final result of the optimization""" 14 | 15 | def __init__(self): 16 | self.x = None 17 | self.nit = 0 18 | self.fun = 0.0 19 | 20 | 21 | class OptimizerBase(abc.ABC): 22 | """Base class for QNN optimizers.""" 23 | 24 | def minimize( 25 | self, 26 | fun: callable, 27 | x0: np.ndarray, 28 | grad: callable = None, 29 | bounds=None, # pylint: disable=invalid-name 30 | ) -> OptimizerResult: 31 | """Function to minimize a given function. 32 | 33 | Args: 34 | fun (callable): Function to minimize. 35 | x0 (numpy.ndarray): Initial guess. 36 | grad (callable): Gradient of the function to minimize. 37 | bounds (sequence): Bounds for the parameters. 38 | 39 | Returns: 40 | Result of the optimization in class:`OptimizerResult` format. 41 | """ 42 | raise NotImplementedError() 43 | 44 | def set_callback(self, callback): 45 | """Set the callback function.""" 46 | self.callback = callback 47 | 48 | 49 | class IterativeMixin: 50 | """Mixin for iteration based optimizers.""" 51 | 52 | def __init__(self): 53 | self.iteration = 0 54 | 55 | 56 | class StepwiseMixin(IterativeMixin): 57 | """Mixin for optimizer for which we can execute single steps.""" 58 | 59 | def step(self, **kwargs): 60 | """Perform one update step.""" 61 | raise NotImplementedError() 62 | 63 | 64 | class SGDMixin(StepwiseMixin, abc.ABC): 65 | """Mixin for stochastic gradient descent based optimizers.""" 66 | 67 | def step(self, **kwargs): 68 | """Perform one update step. 69 | 70 | Args: 71 | x: Current value 72 | grad: Precomputed gradient 73 | 74 | Returns: 75 | Updated x 76 | """ 77 | if "x" in kwargs: 78 | x = kwargs["x"] 79 | else: 80 | raise TypeError("x argument is missing in step function.") 81 | if "grad" in kwargs: 82 | grad = kwargs["grad"] 83 | else: 84 | raise TypeError("grad argument is missing in step function.") 85 | 86 | update = self._get_update(grad) 87 | x_return = x + update 88 | self.iteration += 1 89 | self._update_lr() 90 | return x_return 91 | 92 | def reset(self): 93 | """ 94 | Resets the object to its initial state. 95 | 96 | This function does not take any parameters. 97 | 98 | Returns: 99 | None: This function does not return anything. 100 | """ 101 | pass 102 | 103 | @abc.abstractmethod 104 | def _get_update(self, grad: np.ndarray) -> np.ndarray: 105 | """Function that returns the update for a given gradient.""" 106 | raise NotImplementedError() 107 | 108 | @abc.abstractmethod 109 | def _update_lr(self) -> None: 110 | """Function for updating the learning rate.""" 111 | raise NotImplementedError() 112 | -------------------------------------------------------------------------------- /src/squlearn/qnn/__init__.py: -------------------------------------------------------------------------------- 1 | """QNN module for classification and regression.""" 2 | 3 | from .loss import ConstantLoss, ODELoss, ParameterRegularizationLoss, SquaredLoss, VarianceLoss 4 | from .qnnc import QNNClassifier 5 | from .qnnr import QNNRegressor 6 | 7 | __all__ = [ 8 | "ConstantLoss", 9 | "ODELoss", 10 | "ParameterRegularizationLoss", 11 | "SquaredLoss", 12 | "VarianceLoss", 13 | "QNNClassifier", 14 | "QNNRegressor", 15 | ] 16 | -------------------------------------------------------------------------------- /src/squlearn/qnn/loss/__init__.py: -------------------------------------------------------------------------------- 1 | """Loss functions for QNNs.""" 2 | 3 | from .ode_loss import ODELoss 4 | from .parameter_regularization_loss import ParameterRegularizationLoss 5 | from .qnn_loss_base import ConstantLoss 6 | from .squared_loss import SquaredLoss 7 | from .variance_loss import VarianceLoss 8 | 9 | __all__ = [ 10 | "ConstantLoss", 11 | "ODELoss", 12 | "ParameterRegularizationLoss", 13 | "SquaredLoss", 14 | "VarianceLoss", 15 | ] 16 | -------------------------------------------------------------------------------- /src/squlearn/qnn/loss/squared_loss.py: -------------------------------------------------------------------------------- 1 | """Squared Loss for QNNs.""" 2 | 3 | from typing import Union 4 | 5 | import numpy as np 6 | 7 | from .qnn_loss_base import QNNLossBase 8 | 9 | 10 | class SquaredLoss(QNNLossBase): 11 | """Squared loss for regression.""" 12 | 13 | @property 14 | def loss_variance_available(self) -> bool: 15 | """Returns True since the squared loss function has a variance function.""" 16 | return True 17 | 18 | @property 19 | def loss_args_tuple(self) -> tuple: 20 | """Returns evaluation tuple for the squared loss calculation.""" 21 | return ("f",) 22 | 23 | @property 24 | def variance_args_tuple(self) -> tuple: 25 | """Returns evaluation tuple for the squared loss variance calculation.""" 26 | return ("f", "var") 27 | 28 | @property 29 | def gradient_args_tuple(self) -> tuple: 30 | """Returns evaluation tuple for the squared loss gradient calculation.""" 31 | if self._opt_param_op: 32 | return ("f", "dfdp", "dfdop") 33 | return ("f", "dfdp") 34 | 35 | def value(self, value_dict: dict, **kwargs) -> float: 36 | r"""Calculates the squared loss. 37 | 38 | This function calculates the squared loss between the values in value_dict and ground_truth 39 | as 40 | 41 | .. math:: 42 | \sum_i w_i \left|f\left(x_i\right)-f_ref\left(x_i\right)\right|^2 43 | 44 | Args: 45 | value_dict (dict): Contains calculated values of the model 46 | ground_truth (np.ndarray): The true values :math:`f_ref\left(x_i\right)` 47 | weights (np.ndarray): Weight for each data point, if None all data points count the 48 | same 49 | 50 | Returns: 51 | Loss value 52 | """ 53 | if "ground_truth" not in kwargs: 54 | raise AttributeError("SquaredLoss requires ground_truth.") 55 | ground_truth = kwargs["ground_truth"] 56 | if "weights" in kwargs and kwargs["weights"] is not None: 57 | weights = kwargs["weights"] 58 | else: 59 | weights = np.ones_like(ground_truth) 60 | return np.sum(np.multiply(np.square(value_dict["f"] - ground_truth), weights)) 61 | 62 | def variance(self, value_dict: dict, **kwargs) -> float: 63 | r"""Calculates the approximated variance of the squared loss. 64 | 65 | This function calculates the approximated variance of the squared loss 66 | 67 | .. math:: 68 | 4\sum_i w_i \left|f\left(x_i\right)-f_ref\left(x_i\right)\right|^2 \sigma_f^2(x_i) 69 | 70 | Args: 71 | value_dict (dict): Contains calculated values of the model 72 | ground_truth (np.ndarray): The true values :math:`f_ref\left(x_i\right)` 73 | weights (np.ndarray): Weight for each data point, if None all data points count the 74 | same 75 | 76 | Returns: 77 | Loss value 78 | """ 79 | if "ground_truth" not in kwargs: 80 | raise AttributeError("SquaredLoss requires ground_truth.") 81 | ground_truth = kwargs["ground_truth"] 82 | if "weights" in kwargs and kwargs["weights"] is not None: 83 | weights = kwargs["weights"] 84 | else: 85 | weights = np.ones_like(ground_truth) 86 | 87 | diff_square = np.multiply(weights, np.square(value_dict["f"] - ground_truth)) 88 | return np.sum(4 * np.multiply(diff_square, value_dict["var"])) 89 | 90 | def gradient( 91 | self, value_dict: dict, **kwargs 92 | ) -> Union[np.ndarray, tuple[np.ndarray, np.ndarray]]: 93 | r"""Returns the gradient of the squared loss. 94 | 95 | This function calculates the gradient of the squared loss between the values in value_dict 96 | and ground_truth as 97 | 98 | .. math:: 99 | 2\sum_j \sum_i w_i \left(f\left(x_i\right)-f_ref\left(x_i\right)\right) 100 | \frac{\partial f(x_i)}{\partial p_j} 101 | 102 | Args: 103 | value_dict (dict): Contains calculated values of the model 104 | ground_truth (np.ndarray): The true values :math:`f_ref\left(x_i\right)` 105 | weights (np.ndarray): Weight for each data point, if None all data points count the 106 | same 107 | multiple_output (bool): True if the QNN has multiple outputs 108 | 109 | Returns: 110 | Gradient values 111 | """ 112 | 113 | if "ground_truth" not in kwargs: 114 | raise AttributeError("SquaredLoss requires ground_truth.") 115 | 116 | ground_truth = kwargs["ground_truth"] 117 | if "weights" in kwargs and kwargs["weights"] is not None: 118 | weights = kwargs["weights"] 119 | else: 120 | weights = np.ones_like(ground_truth) 121 | multiple_output = "multiple_output" in kwargs and kwargs["multiple_output"] 122 | 123 | weighted_diff = np.multiply((value_dict["f"] - ground_truth), weights) 124 | 125 | if value_dict["dfdp"].shape[0] == 0: 126 | d_p = np.array([]) 127 | else: 128 | if multiple_output: 129 | d_p = 2.0 * np.einsum("ij,ijk->k", weighted_diff, value_dict["dfdp"]) 130 | else: 131 | d_p = 2.0 * np.einsum("j,jk->k", weighted_diff, value_dict["dfdp"]) 132 | 133 | # Extra code for the cost operator derivatives 134 | if not self._opt_param_op: 135 | return d_p 136 | 137 | if value_dict["dfdop"].shape[0] == 0: 138 | d_op = np.array([]) 139 | else: 140 | if multiple_output: 141 | d_op = 2.0 * np.einsum("ij,ijk->k", weighted_diff, value_dict["dfdop"]) 142 | else: 143 | d_op = 2.0 * np.einsum("j,jk->k", weighted_diff, value_dict["dfdop"]) 144 | return d_p, d_op 145 | -------------------------------------------------------------------------------- /src/squlearn/qnn/loss/variance_loss.py: -------------------------------------------------------------------------------- 1 | """Variance Loss for QNNs.""" 2 | 3 | from collections.abc import Callable 4 | from typing import Union 5 | 6 | import numpy as np 7 | 8 | from .qnn_loss_base import QNNLossBase 9 | 10 | 11 | class VarianceLoss(QNNLossBase): 12 | r"""Variance loss for regression. 13 | 14 | Args: 15 | alpha (float, Callable[[int], float]): Weight value :math:`\alpha` 16 | """ 17 | 18 | def __init__(self, alpha: Union[float, Callable[[int], float]] = 0.005): 19 | super().__init__() 20 | self._alpha = alpha 21 | 22 | @property 23 | def loss_variance_available(self) -> bool: 24 | """Returns True since we neglect the variance of the variance.""" 25 | return True 26 | 27 | @property 28 | def loss_args_tuple(self) -> tuple: 29 | """Returns evaluation tuple for loss calculation.""" 30 | return ("var",) 31 | 32 | @property 33 | def variance_args_tuple(self) -> tuple: 34 | """Returns evaluation tuple for variance calculation.""" 35 | return tuple() 36 | 37 | @property 38 | def gradient_args_tuple(self) -> tuple: 39 | """Returns evaluation tuple for loss gradient calculation.""" 40 | if self._opt_param_op: 41 | return ("var", "dvardp", "dvardop") 42 | return ("var", "dvardp") 43 | 44 | def value(self, value_dict: dict, **kwargs) -> float: 45 | r"""Returns the variance. 46 | 47 | This function returns the weighted variance as 48 | 49 | .. math:: 50 | L_\operatorname{Var} = \alpha \sum_i \operatorname{Var}_i 51 | 52 | Args: 53 | value_dict (dict): Contains calculated values of the model 54 | iteration (int): iteration number, if alpha is a callable function 55 | 56 | Returns: 57 | Loss value 58 | """ 59 | 60 | if callable(self._alpha): 61 | if "iteration" not in kwargs: 62 | raise AttributeError("If alpha is callable, iteration is required.") 63 | alpha = self._alpha(kwargs["iteration"]) 64 | else: 65 | alpha = self._alpha 66 | 67 | return alpha * np.sum(value_dict["var"]) 68 | 69 | def variance(self, value_dict: dict, **kwargs) -> float: 70 | """Returns 0 since we neglect the variance of the variance.""" 71 | return 0.0 72 | 73 | def gradient( 74 | self, value_dict: dict, **kwargs 75 | ) -> Union[np.ndarray, tuple[np.ndarray, np.ndarray]]: 76 | """Returns the gradient of the variance. 77 | 78 | This function calculates the gradient of the variance values in value_dict. 79 | 80 | Args: 81 | value_dict (dict): Contains calculated values of the model 82 | iteration (int): iteration number, if variance_factor is a function 83 | multiple_output (bool): True if the QNN has multiple outputs 84 | 85 | Returns: 86 | Gradient values 87 | """ 88 | if callable(self._alpha): 89 | if "iteration" not in kwargs: 90 | raise AttributeError("If alpha is callable, iteration is required.") 91 | alpha = self._alpha(kwargs["iteration"]) 92 | else: 93 | alpha = self._alpha 94 | 95 | multiple_output = "multiple_output" in kwargs and kwargs["multiple_output"] 96 | if value_dict["dfdp"].shape[0] == 0: 97 | d_p = np.array([]) 98 | else: 99 | if multiple_output: 100 | d_p = alpha * np.sum(value_dict["dvardp"], axis=(0, 1)) 101 | else: 102 | d_p = alpha * np.sum(value_dict["dvardp"], axis=0) 103 | 104 | # Extra code for the cost operator derivatives 105 | if not self._opt_param_op: 106 | return d_p 107 | 108 | if value_dict["dfdop"].shape[0] == 0: 109 | d_op = np.array([]) 110 | else: 111 | if multiple_output: 112 | d_op = alpha * np.sum(value_dict["dvardop"], axis=(0, 1)) 113 | else: 114 | d_op = alpha * np.sum(value_dict["dvardop"], axis=0) 115 | 116 | return d_p, d_op 117 | -------------------------------------------------------------------------------- /src/squlearn/qnn/lowlevel_qnn/__init__.py: -------------------------------------------------------------------------------- 1 | from .lowlevel_qnn import LowLevelQNN 2 | 3 | __all__ = ["LowLevelQNN"] 4 | -------------------------------------------------------------------------------- /src/squlearn/qnn/lowlevel_qnn/lowlevel_qnn.py: -------------------------------------------------------------------------------- 1 | "Low-level QNN Factory." 2 | 3 | from typing import Union 4 | from warnings import warn 5 | 6 | from ...observables.observable_base import ObservableBase 7 | from ...encoding_circuit.encoding_circuit_base import EncodingCircuitBase 8 | from ...util import Executor 9 | 10 | from .lowlevel_qnn_pennylane import LowLevelQNNPennyLane 11 | from .lowlevel_qnn_qiskit import LowLevelQNNQiskit 12 | 13 | 14 | class LowLevelQNN: 15 | """ 16 | Low-level QNN factory, which creates the specific low-level QNN based on the quantum framework. 17 | 18 | Args: 19 | pqc (EncodingCircuitBase): The parameterized quantum circuit. 20 | observable (Union[ObservableBase, list]): The observable(s) to measure. 21 | executor (Executor): The executor for the quantum circuit. 22 | *args: Additional arguments that are passed to the specific QNN. 23 | **kwargs: Additional keyword arguments that are passed to the specific QNN. 24 | 25 | Returns 26 | LowLevelQNNBase: The specific low-level QNN based on the quantum framework. 27 | """ 28 | 29 | def __new__( 30 | cls, 31 | parameterized_quantum_circuit: EncodingCircuitBase, 32 | observable: Union[ObservableBase, list], 33 | executor: Executor, 34 | *args, 35 | **kwargs, 36 | ) -> [LowLevelQNNPennyLane, LowLevelQNNQiskit]: 37 | 38 | if executor.quantum_framework == "pennylane": 39 | if "primitive" in kwargs: 40 | warn("Primitive argument is not supported for PennyLane. Ignoring...") 41 | kwargs.pop("primitive") 42 | return LowLevelQNNPennyLane( 43 | parameterized_quantum_circuit, observable, executor, *args, **kwargs 44 | ) 45 | elif executor.quantum_framework == "qiskit": 46 | return LowLevelQNNQiskit( 47 | parameterized_quantum_circuit, observable, executor, *args, **kwargs 48 | ) 49 | else: 50 | raise RuntimeError("Quantum framework not supported") 51 | -------------------------------------------------------------------------------- /src/squlearn/qnn/lowlevel_qnn/lowlevel_qnn_base.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Union 3 | import numpy as np 4 | import copy 5 | 6 | from qiskit.circuit import ParameterVector 7 | from qiskit.circuit.parametervector import ParameterVectorElement 8 | 9 | from ...observables.observable_base import ObservableBase 10 | from ...encoding_circuit.encoding_circuit_base import EncodingCircuitBase 11 | from ...util import Executor 12 | 13 | 14 | class LowLevelQNNBase(abc.ABC): 15 | """Base class for low-level QNNs. 16 | 17 | Args: 18 | pqc (EncodingCircuitBase): The parameterized quantum circuit. 19 | observable (Union[ObservableBase, list]): The observable(s) to measure. 20 | executor (Executor): The executor for the quantum circuit. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | parameterized_quantum_circuit: EncodingCircuitBase, 26 | observable: Union[ObservableBase, list], 27 | executor: Executor, 28 | ) -> None: 29 | self._pqc = copy.copy(parameterized_quantum_circuit) 30 | self._observable = copy.copy(observable) 31 | self._executor = executor 32 | 33 | @abc.abstractmethod 34 | def set_params(self, **params) -> None: 35 | """Set the parameters of the QNN.""" 36 | raise NotImplementedError 37 | 38 | @abc.abstractmethod 39 | def get_params(self, deep: bool = True) -> dict: 40 | """Get the parameters of the QNN.""" 41 | raise NotImplementedError 42 | 43 | @property 44 | @abc.abstractmethod 45 | def num_qubits(self) -> int: 46 | """Return the number of qubits of the QNN""" 47 | raise NotImplementedError 48 | 49 | @property 50 | @abc.abstractmethod 51 | def num_features(self) -> int: 52 | """Return the dimension of the features of the PQC""" 53 | raise NotImplementedError 54 | 55 | @property 56 | @abc.abstractmethod 57 | def num_parameters(self) -> int: 58 | """Return the number of trainable parameters of the PQC""" 59 | raise NotImplementedError 60 | 61 | @property 62 | def num_operator(self) -> int: 63 | """Return the number outputs""" 64 | raise NotImplementedError 65 | 66 | @property 67 | def num_parameters_observable(self) -> int: 68 | """Return the number of trainable parameters of the expectation value operator""" 69 | raise NotImplementedError 70 | 71 | @property 72 | def multiple_output(self) -> bool: 73 | """Return true if multiple outputs are used""" 74 | raise NotImplementedError 75 | 76 | @abc.abstractmethod 77 | def evaluate( 78 | self, 79 | x: Union[float, np.ndarray], 80 | param: Union[float, np.ndarray], 81 | param_obs: Union[float, np.ndarray], 82 | *values: Union[ 83 | str, 84 | ParameterVector, 85 | ParameterVectorElement, 86 | tuple, 87 | ], 88 | ) -> dict: 89 | """General function for evaluating the output of derivatives of the QNN. 90 | 91 | Evaluation works for given combination of 92 | input features `x` and parameters `param` and `param_op`. 93 | The function includes caching of results 94 | 95 | If `x`, `param`, and/or `param_op` are given as a nested list 96 | (for example multiple sets of parameters), 97 | the values are returned in a nested list. 98 | 99 | Args: 100 | x (np.ndarray): Values of the input feature data. 101 | param (np.ndarray): Parameter values of the PQC parameters 102 | param_op (np.ndarray): Parameter values of the operator parameters 103 | values : Derivatives (or values) of the QNN that are evaluated. Higher order 104 | derivatives are given as tuples of parameters or vectors. 105 | 106 | Results: 107 | Returns a dictionary with the computed values. 108 | The keys of the dictionary are given by the entries in the values tuple 109 | 110 | """ 111 | raise NotImplementedError 112 | 113 | def gradient( 114 | self, 115 | x: Union[float, np.ndarray], 116 | param: Union[float, np.ndarray], 117 | param_obs: Union[float, np.ndarray], 118 | ): 119 | """Return the gradient wrt. trainable parameters of the QNN. 120 | 121 | Args: 122 | x (Union[float, np.ndarray]): Input data. 123 | param (Union[float, np.ndarray]): Parameters of the PQC. 124 | param_obs (Union[float, np.ndarray]): Parameters of the observable. 125 | 126 | Returns: 127 | np.ndarray: Gradient of the QNN. 128 | 129 | """ 130 | return np.concatenate( 131 | ( 132 | self.evaluate(x, param, param_obs, "dfdp")["dfdp"], 133 | self.evaluate(x, param, param_obs, "dfdop")["dfdop"], 134 | ), 135 | axis=None, 136 | ) 137 | 138 | def __call__( 139 | self, 140 | x: Union[float, np.ndarray], 141 | param: Union[float, np.ndarray], 142 | param_obs: Union[float, np.ndarray], 143 | ): 144 | """ 145 | Function for evaluating the QNN. 146 | 147 | Args: 148 | x (Union[float, np.ndarray]): Input data. 149 | param (Union[float, np.ndarray]): Parameters of the PQC. 150 | param_obs (Union[float, np.ndarray]): Parameters of the observable. 151 | 152 | Returns: 153 | np.ndarray: The output of the QNN. 154 | 155 | """ 156 | return self.evaluate(x, param, param_obs, "f")["f"] 157 | -------------------------------------------------------------------------------- /src/squlearn/qnn/util/__init__.py: -------------------------------------------------------------------------------- 1 | """QNN Utilities.""" 2 | 3 | from .training import get_variance_fac, get_lr_decay, ShotsFromRSTD 4 | 5 | __all__ = ["get_variance_fac", "get_lr_decay", "ShotsFromRSTD"] 6 | -------------------------------------------------------------------------------- /src/squlearn/qrc/__init__.py: -------------------------------------------------------------------------------- 1 | """Quantum Reservoir Computing module for classification and regression.""" 2 | 3 | from .qrc_classifier import QRCClassifier 4 | from .qrc_regressor import QRCRegressor 5 | 6 | __all__ = ["QRCClassifier", "QRCRegressor"] 7 | -------------------------------------------------------------------------------- /src/squlearn/qrc/qrc_classifier.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | 4 | from sklearn.base import ClassifierMixin 5 | from sklearn.neural_network import MLPClassifier 6 | from sklearn.linear_model import Perceptron 7 | from sklearn.svm import SVC 8 | 9 | from ..observables.observable_base import ObservableBase 10 | from ..encoding_circuit.encoding_circuit_base import EncodingCircuitBase 11 | 12 | from .base_qrc import BaseQRC 13 | from ..util import Executor 14 | 15 | 16 | class QRCClassifier(BaseQRC, ClassifierMixin): 17 | """Quantum Reservoir Computing for classification. 18 | 19 | This class implements a Quantum Reservoir Computing (QRC) framework designed for 20 | regression tasks. In QRC, data is encoded into a quantum system—referred to as the 21 | quantum reservoir—using an encoding circuit. The state of the quantum reservoir is then 22 | measured using a set of quantum operators, often randomly chosen. The measured values, 23 | also known as expectation values, are used as features for a classical machine learning model 24 | to perform the classification. As a default a simple classification based on a single 25 | perceptron is used. 26 | 27 | Args: 28 | encoding_circuit (EncodingCircuitBase): The encoding circuit to use for encoding the data 29 | into the reservoir. 30 | executor (Executor): Executor instance 31 | ml_model (str): The classical machine learning model to use (default: linear), possible 32 | values are: 33 | 34 | * ``"mlp"`` for a multi-layer perceptron classification model. 35 | * ``"linear"`` for a single layer perceptron. 36 | * ``"kernel"`` for a Support Vector Classifier with a RBF kernel. 37 | 38 | ml_model_options (dict): The options for the machine learning model. Default options of the 39 | sklearn model are used if None. 40 | operators (Union[ObservableBase, list[ObservableBase], str]): Strategy for generating the 41 | operators used to measure the quantum reservoir. Possible values are: 42 | 43 | * ``"random_paulis"`` generates random Pauli operators (default). 44 | * ``"single_paulis"`` generates single qubit Pauli operators. 45 | 46 | Alternatively, a list of ObservableBase objects can be provided. 47 | num_operators (int): The number of random Pauli operators to generate for 48 | ``"operators = random_paulis"`` (default: 100). 49 | operator_seed (int): The seed for the random operator generation for 50 | ``"operators = random_paulis"`` (default: 0). 51 | param_ini (Union[np.ndarray, None]): The parameters for the encoding circuit. 52 | param_op_ini (Union[np.ndarray, None]): The initial parameters for the operators. 53 | parameter_seed (Union[int, None]): The seed for the initial parameter generation if no 54 | parameters are given. 55 | caching (bool): Whether to cache the results of the evaluated expectation values. 56 | 57 | See Also 58 | -------- 59 | squlearn.qrc.QRCRegressor: Quantum Reservoir Computing for Regression. 60 | squlearn.qrc.base_qrc.BaseQRC: Base class for Quantum Reservoir Computing. 61 | 62 | **Example: Classification of Moon example with Quantum Reservoir Computing** 63 | 64 | .. code-block:: python 65 | 66 | from squlearn import Executor 67 | from squlearn.encoding_circuit import HubregtsenEncodingCircuit 68 | from squlearn.qrc import QRCClassifier 69 | from sklearn.datasets import make_moons 70 | from sklearn.model_selection import train_test_split 71 | 72 | X, y = make_moons(n_samples=1000, noise=0.2, random_state=42) 73 | 74 | X_train, X_test, y_train, y_test = train_test_split( 75 | X, y, test_size=0.33, random_state=42) 76 | 77 | clf = QRCClassifier(HubregtsenEncodingCircuit(num_qubits=4, num_features=2), 78 | Executor(), 79 | ml_model="linear", 80 | operators="random_paulis", 81 | num_operators=300, 82 | ) 83 | 84 | clf.fit(X_train, y_train) 85 | y_pred = clf.predict(X_test) 86 | 87 | Methods: 88 | -------- 89 | """ 90 | 91 | def __init__( 92 | self, 93 | encoding_circuit: EncodingCircuitBase, 94 | executor: Executor, 95 | ml_model: str = "linear", 96 | ml_model_options: Union[dict, None] = None, 97 | operators: Union[ObservableBase, list[ObservableBase], str] = "random_paulis", 98 | num_operators: int = 100, 99 | operator_seed: int = 0, 100 | param_ini: Union[np.ndarray, None] = None, 101 | param_op_ini: Union[np.ndarray, None] = None, 102 | parameter_seed: Union[int, None] = 0, 103 | caching: bool = True, 104 | ) -> None: 105 | 106 | super().__init__( 107 | encoding_circuit=encoding_circuit, 108 | executor=executor, 109 | ml_model=ml_model, 110 | ml_model_options=ml_model_options, 111 | operators=operators, 112 | num_operators=num_operators, 113 | operator_seed=operator_seed, 114 | param_ini=param_ini, 115 | param_op_ini=param_op_ini, 116 | parameter_seed=parameter_seed, 117 | caching=caching, 118 | ) 119 | 120 | def _initialize_ml_model(self): 121 | 122 | if self.ml_model_options is None: 123 | self.ml_model_options = {} 124 | 125 | if self.ml_model == "mlp": 126 | self._ml_model = MLPClassifier(**self.ml_model_options) 127 | elif self.ml_model == "linear": 128 | self._ml_model = Perceptron(**self.ml_model_options) 129 | elif self.ml_model == "kernel": 130 | self._ml_model = SVC(**self.ml_model_options) 131 | else: 132 | raise ValueError("Invalid ml_model. Please choose 'mlp', 'linear' or 'kernel'.") 133 | -------------------------------------------------------------------------------- /src/squlearn/qrc/qrc_regressor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Union 3 | 4 | from sklearn.base import RegressorMixin 5 | from sklearn.neural_network import MLPRegressor 6 | from sklearn.linear_model import LinearRegression 7 | from sklearn.kernel_ridge import KernelRidge 8 | 9 | from ..observables.observable_base import ObservableBase 10 | from ..encoding_circuit.encoding_circuit_base import EncodingCircuitBase 11 | 12 | from .base_qrc import BaseQRC 13 | from ..util import Executor 14 | 15 | 16 | class QRCRegressor(BaseQRC, RegressorMixin): 17 | """Quantum Reservoir Computing for regression. 18 | 19 | This class implements a Quantum Reservoir Computing (QRC) framework designed for 20 | regression tasks. In QRC, data is encoded into a quantum system—referred to as the 21 | quantum reservoir—using an encoding circuit. The state of the quantum reservoir is then 22 | measured using a set of quantum operators, often randomly chosen. The measured values, 23 | also known as expectation values, are used as features for a classical machine learning model, 24 | usually a simple linear regression, to perform the regression. 25 | 26 | Args: 27 | encoding_circuit (EncodingCircuitBase): The encoding circuit to use for encoding the data 28 | into the reservoir. 29 | executor (Executor): Executor instance 30 | ml_model (str): The classical machine learning model to use (default: linear). Possible 31 | values are: 32 | 33 | * ``"mlp"`` for a multi-layer perceptron regression model. 34 | * ``"linear"`` for a linear regression model. 35 | * ``"kernel"`` for a kernel ridge regression with a linear kernel. 36 | 37 | ml_model_options (dict): The options for the machine learning model. Default options of the 38 | sklearn model are used if None. 39 | operators (Union[ObservableBase, list[ObservableBase], str]): Strategy for generating the 40 | operators used to measure the quantum reservoir. Possible values are: 41 | 42 | * ``"random_paulis"`` generates random Pauli operators (default). 43 | * ``"single_paulis"`` generates single qubit Pauli operators. 44 | 45 | Alternatively, a list of ObservableBase objects can be provided. 46 | num_operators (int): The number of random Pauli operators to generate for 47 | ``"operators = random_paulis"`` (default: 100). 48 | operator_seed (int): The seed for the random operator generation for 49 | ``"operators = random_paulis"`` (default: 0). 50 | param_ini (Union[np.ndarray, None]): The parameters for the encoding circuit. 51 | param_op_ini (Union[np.ndarray, None]): The initial parameters for the operators. 52 | parameter_seed (Union[int, None]): The seed for the initial parameter generation if no 53 | parameters are given. 54 | caching (bool): Whether to cache the results of the evaluated expectation values. 55 | 56 | See Also 57 | -------- 58 | squlearn.qrc.QRCClassifier: Quantum Reservoir Computing for Classification. 59 | squlearn.qrc.base_qrc.BaseQRC: Base class for Quantum Reservoir Computing. 60 | 61 | **Example: Fitting the logarithm function with Quantum Reservoir Computing** 62 | 63 | .. code-block:: python 64 | 65 | import numpy as np 66 | from squlearn import Executor 67 | from squlearn.encoding_circuit import HubregtsenEncodingCircuit 68 | from squlearn.qrc import QRCRegressor 69 | from sklearn.model_selection import train_test_split 70 | 71 | X, y = np.arange(0.1, 0.9, 0.01), np.log(np.arange(0.1, 0.9, 0.01)) 72 | X_train, X_test, y_train, y_test = train_test_split( 73 | X, y, test_size=0.33, random_state=42) 74 | 75 | reg = QRCRegressor(HubregtsenEncodingCircuit(num_qubits=4, num_features=1), 76 | Executor(), 77 | ml_model="linear", 78 | operators="random_paulis", 79 | num_operators=300, 80 | ) 81 | 82 | reg.fit(X_train, y_train) 83 | y_pred = reg.predict(X_test) 84 | 85 | Methods: 86 | -------- 87 | """ 88 | 89 | def __init__( 90 | self, 91 | encoding_circuit: EncodingCircuitBase, 92 | executor: Executor, 93 | ml_model: str = "linear", 94 | ml_model_options: Union[dict, None] = None, 95 | operators: Union[ObservableBase, list[ObservableBase], str] = "random_paulis", 96 | num_operators: int = 100, 97 | operator_seed: int = 0, 98 | param_ini: Union[np.ndarray, None] = None, 99 | param_op_ini: Union[np.ndarray, None] = None, 100 | parameter_seed: Union[int, None] = 0, 101 | caching: bool = True, 102 | ) -> None: 103 | 104 | super().__init__( 105 | encoding_circuit=encoding_circuit, 106 | executor=executor, 107 | ml_model=ml_model, 108 | ml_model_options=ml_model_options, 109 | operators=operators, 110 | num_operators=num_operators, 111 | operator_seed=operator_seed, 112 | param_ini=param_ini, 113 | param_op_ini=param_op_ini, 114 | parameter_seed=parameter_seed, 115 | caching=caching, 116 | ) 117 | 118 | def _initialize_ml_model(self): 119 | 120 | if self.ml_model_options is None: 121 | self.ml_model_options = {} 122 | 123 | if self.ml_model == "mlp": 124 | self._ml_model = MLPRegressor(**self.ml_model_options) 125 | elif self.ml_model == "linear": 126 | self._ml_model = LinearRegression(**self.ml_model_options) 127 | elif self.ml_model == "kernel": 128 | self._ml_model = KernelRidge(**self.ml_model_options) 129 | else: 130 | raise ValueError("Invalid ml_model. Please choose 'mlp', 'linear' or 'kernel'.") 131 | -------------------------------------------------------------------------------- /src/squlearn/util/__init__.py: -------------------------------------------------------------------------------- 1 | from .executor import Executor 2 | from .optree import OpTree 3 | 4 | __all__ = ["Executor", "OpTree"] 5 | -------------------------------------------------------------------------------- /src/squlearn/util/data_preprocessing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Tuple, Union 3 | 4 | 5 | def adjust_features(x: Union[np.ndarray, float], x_length: int) -> Tuple[np.ndarray, bool]: 6 | """Adjust the feature vector to the form [[]] if necessary. 7 | 8 | Args: 9 | x (np.ndarray): Input array. 10 | x_length (int): Dimension of the input array, e.g. feature dimension. 11 | 12 | Return: 13 | Adjusted feature array and a boolean flag for multiple inputs. 14 | """ 15 | 16 | return _adjust_input(x, x_length, allow_single_array=False) 17 | 18 | 19 | def adjust_parameters(x: np.ndarray, x_length: int) -> Tuple[np.ndarray, bool]: 20 | """Adjust the parameter vector to the form [[]] if necessary. 21 | 22 | In contrast to feature vectors, one dimensional parameters are not considered 23 | as multiple inputs. 24 | 25 | Args: 26 | x (np.ndarray): Input array. 27 | x_length (int): Dimension of the input array, e.g. feature dimension. 28 | 29 | Return: 30 | Adjusted parameter array and a boolean flag for multiple inputs. 31 | """ 32 | 33 | return _adjust_input(x, x_length, allow_single_array=True) 34 | 35 | 36 | def _adjust_input( 37 | x: Union[float, np.ndarray], x_length: int, allow_single_array: bool 38 | ) -> Tuple[np.ndarray, bool]: 39 | """Adjust the input to the form [[]] if necessary. 40 | 41 | If allow_single_array is True, a one dimensional array is not considered as multiple outputs. 42 | 43 | Args: 44 | x (np.ndarray): Input array. 45 | x_length (int): Dimension of the input array, e.g. feature dimension. 46 | allow_single_array (bool): If True, a one dimensional array is not considered as 47 | multiple outputs. 48 | 49 | Return: 50 | Adjusted input array and a boolean flag for multiple inputs. 51 | """ 52 | multiple_inputs = False 53 | error = False 54 | shape = np.shape(x) 55 | 56 | if shape == () and x_length == 1: 57 | # Single floating point number 58 | xx = np.array([[x]]) 59 | elif sum(shape) == 0 and x_length > 0: 60 | # Empty array although x_length not zero 61 | error = True 62 | elif len(shape) == 1: 63 | if x_length == 1: 64 | xx = np.array([np.array([xx]) for xx in x]) 65 | if allow_single_array: 66 | multiple_inputs = shape[0] != 1 67 | else: 68 | multiple_inputs = True 69 | else: 70 | # We have a single multi dimensional x (e.g. parameter vector) 71 | if len(x) == x_length: 72 | xx = np.array([x]) 73 | else: 74 | error = True 75 | elif len(shape) == 2: 76 | if shape[1] == x_length: 77 | xx = x 78 | multiple_inputs = True 79 | else: 80 | error = True 81 | else: 82 | error = True 83 | 84 | if error: 85 | raise ValueError("Wrong format of an input variable.") 86 | 87 | return convert_to_float64(xx), multiple_inputs 88 | 89 | 90 | def convert_to_float64(x: Union[float, np.ndarray, list]) -> np.ndarray: 91 | """Convert to float64 format, raise Error for complex values 92 | 93 | Args: 94 | x (Union[float, np.ndarray]): Data that is converted 95 | 96 | Returns: 97 | Converted numpy float64 array 98 | """ 99 | if not isinstance(x, np.ndarray): 100 | x = np.array(x) 101 | if x.dtype != np.float64: 102 | x = np.real_if_close(x) 103 | if np.iscomplexobj(x): 104 | raise ValueError( 105 | "Only real values for parameters and features are supported in sQUlearn!" 106 | ) 107 | x = np.array(x, dtype=np.float64) 108 | 109 | return x 110 | 111 | 112 | def to_tuple(x: Union[float, np.ndarray, list, tuple], flatten: bool = True) -> Tuple: 113 | """Function for converting data into hashable tuples 114 | 115 | Args: 116 | x (Union[float,np.ndarray,list,tuple]): Input data. 117 | 118 | Return: 119 | Flattened tuple of the input data 120 | """ 121 | 122 | if flatten: 123 | 124 | def recursive_flatten(container): 125 | for i in container: 126 | if isinstance(i, (list, tuple, np.ndarray)): 127 | for j in recursive_flatten(i): 128 | yield j 129 | else: 130 | yield i 131 | 132 | if isinstance(x, float): 133 | return tuple([x]) 134 | elif len(np.shape(x)) == 1: 135 | return tuple(list(x)) 136 | else: 137 | return tuple(recursive_flatten(x)) 138 | 139 | else: 140 | 141 | def array_to_nested_tuple(arr): 142 | if isinstance(arr, (list, tuple, np.ndarray)): 143 | return tuple(array_to_nested_tuple(subarr) for subarr in arr) 144 | else: 145 | return arr 146 | 147 | if isinstance(x, (list, tuple, np.ndarray)): 148 | return array_to_nested_tuple(x) 149 | else: 150 | return tuple([x]) 151 | -------------------------------------------------------------------------------- /src/squlearn/util/decompose_to_std.py: -------------------------------------------------------------------------------- 1 | from qiskit.circuit.library import standard_gates 2 | from qiskit.circuit import QuantumCircuit 3 | from typing import Union 4 | 5 | 6 | def decompose_to_std( 7 | circuit: QuantumCircuit, gate_list: Union[None, list] = None 8 | ) -> QuantumCircuit: 9 | """ 10 | Function to decompose the circuit to standard gates. 11 | 12 | Args: 13 | circuit (QuantumCircuit): The Circuit, which is supposed to be decomposed. 14 | gate_list (Union[None,list]): List of gates, which are considered as standard gates. 15 | If the gate is not in the list, it will be decomposed. If the list is empty, 16 | all gates from `qiskit.circuit.library.standard_gates` and 17 | `["cx","cy","cz","measure"]` are considered as standard gates. 18 | 19 | Returns: 20 | QuantumCircuit: The decomposed circuit. 21 | """ 22 | if not gate_list: 23 | gate_list = [*dir(standard_gates), "cx", "cy", "cz", "measure"] 24 | decompose_names = [ 25 | instruction.operation.name 26 | for instruction in circuit.data 27 | if instruction.operation.name not in gate_list 28 | ] 29 | circuit_new = circuit.decompose(decompose_names) 30 | 31 | while decompose_names and circuit != circuit_new: 32 | circuit = circuit_new 33 | decompose_names = [ 34 | instruction.operation.name 35 | for instruction in circuit.data 36 | if instruction.operation.name not in gate_list 37 | ] 38 | circuit_new = circuit.decompose(decompose_names) 39 | 40 | return circuit_new 41 | -------------------------------------------------------------------------------- /src/squlearn/util/execution/__init__.py: -------------------------------------------------------------------------------- 1 | from .automatic_backend_selection import AutomaticBackendSelection 2 | from .parallel_estimator import ParallelEstimator 3 | from .parallel_sampler import ParallelSampler 4 | 5 | __all__ = [ 6 | "AutomaticBackendSelection", 7 | "ParallelEstimator", 8 | "ParallelSampler", 9 | ] 10 | -------------------------------------------------------------------------------- /src/squlearn/util/execution/hqaa/__init__.py: -------------------------------------------------------------------------------- 1 | from .heuristic import heuristic 2 | from .parser import parse_openqasm 3 | -------------------------------------------------------------------------------- /src/squlearn/util/execution/hqaa/parser.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import re 3 | 4 | 5 | def parse_openqasm(lines: str, number_qubits: int) -> nx.Graph: 6 | """ 7 | Parses OPENQASM code to generate a traffic graph representing single and double qubit gate frequencies. 8 | 9 | Args: 10 | lines (str): The OPENQASM code as a string. 11 | number_qubits (int): The number of qubits in the quantum circuit. 12 | 13 | Returns: 14 | nx.Graph: A NetworkX graph representing the traffic of the quantum circuit, with nodes for qubits and edges indicating gate operations. 15 | """ 16 | traffic = nx.Graph() 17 | 18 | for i in range(0, number_qubits): 19 | traffic.add_node(i, single=0) 20 | for j in range(0, number_qubits): 21 | for k in range(number_qubits - 1, 0, -1): 22 | if j != k: 23 | traffic.add_edge(j, k, double=0) 24 | 25 | # Update the lists for OPENQASM 3 26 | list_s_gates = ["x", "h", "rx", "ry", "rz", "sx"] 27 | list_m_gates = ["cx", "cz", "swap", "ccx", "ecr"] 28 | 29 | for j in range(0, len(list_s_gates)): 30 | for line in lines.split("\n"): 31 | if line.startswith(list_s_gates[j]): 32 | match = re.search(r"q\[(\d+)\]", line) 33 | if match: 34 | number_single_gate = int(match.group(1)) 35 | traffic.nodes[number_single_gate]["single"] += 1 36 | 37 | for k in range(0, len(list_m_gates)): 38 | for line in lines.split("\n"): 39 | if line.startswith(list_m_gates[k]): 40 | match = re.findall(r"q\[(\d+)\]", line) 41 | if match: 42 | qubits = list(map(int, match)) 43 | for i in range(len(qubits) - 1): 44 | traffic[qubits[i]][qubits[i + 1]]["double"] += 1 45 | 46 | # Remove edges with "double" = 0 47 | double_0_new = [(i, j) for i, j, data in traffic.edges(data=True) if data["double"] == 0] 48 | for i, j in double_0_new: 49 | traffic.remove_edge(i, j) 50 | 51 | return traffic 52 | -------------------------------------------------------------------------------- /src/squlearn/util/optree/__init__.py: -------------------------------------------------------------------------------- 1 | from .optree import ( 2 | OpTree, 3 | OpTreeList, 4 | OpTreeSum, 5 | OpTreeCircuit, 6 | OpTreeOperator, 7 | OpTreeContainer, 8 | OpTreeExpectationValue, 9 | OpTreeMeasuredOperator, 10 | OpTreeValue, 11 | ) 12 | 13 | from .optree_evaluate import OpTreeEvaluate 14 | from .optree_derivative import OpTreeDerivative 15 | 16 | __all__ = [ 17 | "OpTree", 18 | "OpTreeEvaluate", 19 | "OpTreeDerivative", 20 | "OpTreeList", 21 | "OpTreeSum", 22 | "OpTreeCircuit", 23 | "OpTreeOperator", 24 | "OpTreeExpectationValue", 25 | "OpTreeMeasuredOperator", 26 | "OpTreeContainer", 27 | "OpTreeValue", 28 | ] 29 | -------------------------------------------------------------------------------- /src/squlearn/util/pennylane/__init__.py: -------------------------------------------------------------------------------- 1 | from .pennylane_circuit import PennyLaneCircuit 2 | from .pennylane_gates import qiskit_pennylane_gate_dict 3 | 4 | __all__ = ["PennyLaneCircuit", "qiskit_pennylane_gate_dict"] 5 | -------------------------------------------------------------------------------- /src/squlearn/util/pennylane/pennylane_gates.py: -------------------------------------------------------------------------------- 1 | import pennylane as qml 2 | 3 | 4 | def RXX(theta, wires): 5 | """RXX gate.""" 6 | return qml.PauliRot(theta, "XX", wires=wires) 7 | 8 | 9 | def RYY(theta, wires): 10 | """RYY gate.""" 11 | return qml.PauliRot(theta, "YY", wires=wires) 12 | 13 | 14 | def RZZ(theta, wires): 15 | """RZZ gate.""" 16 | return qml.PauliRot(theta, "ZZ", wires=wires) 17 | 18 | 19 | def RXZ(theta, wires): 20 | """RXZ gate.""" 21 | return qml.PauliRot(theta, "XZ", wires=wires) 22 | 23 | 24 | def reset(wires): 25 | """Reset gate, implemented by measure and reset.""" 26 | return qml.measure(wires=wires, reset=True) 27 | 28 | 29 | def tdg(wires): 30 | """T-dagger gate.""" 31 | return qml.adjoint(qml.T(wires=wires)) 32 | 33 | 34 | def sdg(wires): 35 | """S-dagger gate.""" 36 | return qml.adjoint(qml.S(wires=wires)) 37 | 38 | 39 | def cs(wires): 40 | """CS gate.""" 41 | if len(wires) != 2: 42 | raise ValueError("CS gate requires two wires.") 43 | return qml.ctrl(qml.S(wires[1]), wires[0]) 44 | 45 | 46 | def csx(wires): 47 | """CSX gate.""" 48 | if len(wires) != 2: 49 | raise ValueError("CSX gate requires two wires.") 50 | return qml.ctrl(qml.SX(wires[1]), wires[0]) 51 | 52 | 53 | # Dictionary of conversion Qiskit gates (from string) to PennyLane gates 54 | qiskit_pennylane_gate_dict = { 55 | "i": qml.Identity, 56 | "h": qml.Hadamard, 57 | "x": qml.PauliX, 58 | "y": qml.PauliY, 59 | "z": qml.PauliZ, 60 | "s": qml.S, 61 | "t": qml.T, 62 | "toffoli": qml.Toffoli, 63 | "sx": qml.SX, 64 | "swap": qml.SWAP, 65 | "iswap": qml.ISWAP, 66 | "cswap": qml.CSWAP, 67 | "ecr": qml.ECR, 68 | "ch": qml.CH, 69 | "rx": qml.RX, 70 | "ry": qml.RY, 71 | "rz": qml.RZ, 72 | "p": qml.PhaseShift, 73 | "cp": qml.ControlledPhaseShift, 74 | "cx": qml.CNOT, 75 | "cnot": qml.CNOT, 76 | "cy": qml.CY, 77 | "cz": qml.CZ, 78 | "crx": qml.CRX, 79 | "cry": qml.CRY, 80 | "crz": qml.CRZ, 81 | "rxx": RXX, 82 | "ryy": RYY, 83 | "rzz": RZZ, 84 | "rxz": RXZ, 85 | "barrier": qml.Barrier, 86 | "u": qml.U3, 87 | "measure": qml.measure, 88 | "reset": reset, 89 | "tdg": tdg, 90 | "sdg": sdg, 91 | "cs": cs, 92 | "csx": csx, 93 | } 94 | -------------------------------------------------------------------------------- /tests/encoding_circuit/test_random_encoding_circuits.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from squlearn.encoding_circuit import RandomLayeredEncodingCircuit, RandomEncodingCircuit 4 | 5 | from qiskit.circuit import ParameterVector 6 | 7 | 8 | class TestRandomEncodingCircuits: 9 | 10 | def test_random_layered_encoding_circuit(self): 11 | """ 12 | Test for the RandomLayeredEncodingCircuit class. 13 | 14 | The test checks if the circuit is generated correctly and uniquly for a given seed 15 | and set of parameters. 16 | """ 17 | 18 | reference1 = [ 19 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[0.7853981633974483])", 20 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[0.7853981633974483])", 21 | "Instruction(name='ry', num_qubits=1, num_clbits=0, params=[ParameterExpression(3.14159265358979*x[0])])", 22 | "Instruction(name='ry', num_qubits=1, num_clbits=0, params=[ParameterExpression(3.14159265358979*x[1])])", 23 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[ParameterExpression(atan(x[2]))])", 24 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[ParameterExpression(atan(x[3]))])", 25 | ] 26 | reference2 = [ 27 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[0.7853981633974483])", 28 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[0.7853981633974483])", 29 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[0.7853981633974483])", 30 | "Instruction(name='rx', num_qubits=1, num_clbits=0, params=[1.5707963267948966])", 31 | "Instruction(name='rx', num_qubits=1, num_clbits=0, params=[1.5707963267948966])", 32 | "Instruction(name='rx', num_qubits=1, num_clbits=0, params=[1.5707963267948966])", 33 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[ParameterExpression(atan(x[0]))])", 34 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[ParameterExpression(atan(x[1]))])", 35 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[ParameterExpression(atan(x[2]))])", 36 | ] 37 | reference3 = [ 38 | "Instruction(name='z', num_qubits=1, num_clbits=0, params=[])", 39 | "Instruction(name='z', num_qubits=1, num_clbits=0, params=[])", 40 | "Instruction(name='z', num_qubits=1, num_clbits=0, params=[])", 41 | "Instruction(name='cx', num_qubits=2, num_clbits=0, params=[])", 42 | "Instruction(name='cx', num_qubits=2, num_clbits=0, params=[])", 43 | "Instruction(name='rx', num_qubits=1, num_clbits=0, params=[ParameterVectorElement(x[0])])", 44 | "Instruction(name='rx', num_qubits=1, num_clbits=0, params=[ParameterVectorElement(x[1])])", 45 | "Instruction(name='rx', num_qubits=1, num_clbits=0, params=[ParameterVectorElement(x[2])])", 46 | ] 47 | 48 | pqc = RandomLayeredEncodingCircuit(num_qubits=2, num_features=4, max_num_layers=3) 49 | x = ParameterVector("x", 4) 50 | check_list1 = [str(op[0]) for op in pqc.get_circuit(x, [])] 51 | assert check_list1 == reference1 52 | 53 | pqc.set_params(num_qubits=3, num_features=3, max_num_layers=3) 54 | x = ParameterVector("x", 3) 55 | check_list2 = [str(op[0]) for op in pqc.get_circuit(x, [])] 56 | assert check_list2 == reference2 57 | 58 | pqc.set_params(seed=1234) 59 | check_list3 = [str(op[0]) for op in pqc.get_circuit(x, [])] 60 | assert check_list3 == reference3 61 | 62 | def test_random_encoding_circuit(self): 63 | """ 64 | Test for the RandomEncodingCircuit class. 65 | 66 | The test checks if the circuit is generated correctly and uniquly for a given seed 67 | and set of parameters. 68 | """ 69 | reference1 = [ 70 | "Instruction(name='z', num_qubits=1, num_clbits=0, params=[])", 71 | "Instruction(name='rx', num_qubits=1, num_clbits=0, params=[ParameterExpression(3.14159265358979*x[3])])", 72 | "Instruction(name='cx', num_qubits=2, num_clbits=0, params=[])", 73 | "Instruction(name='rxx', num_qubits=2, num_clbits=0, params=[ParameterExpression(p[0]*x[1])])", 74 | "Instruction(name='ryy', num_qubits=2, num_clbits=0, params=[ParameterExpression(p[1]*atan(x[0]))])", 75 | "Instruction(name='cz', num_qubits=2, num_clbits=0, params=[])", 76 | "Instruction(name='cy', num_qubits=2, num_clbits=0, params=[])", 77 | "Instruction(name='x', num_qubits=1, num_clbits=0, params=[])", 78 | "Instruction(name='cry', num_qubits=2, num_clbits=0, params=[ParameterExpression(3.14159265358979*x[2])])", 79 | "Instruction(name='rzx', num_qubits=2, num_clbits=0, params=[ParameterExpression(p[2]*atan(x[1]))])", 80 | "Instruction(name='cry', num_qubits=2, num_clbits=0, params=[ParameterExpression(p[3]*atan(x[0]))])", 81 | "Instruction(name='rxx', num_qubits=2, num_clbits=0, params=[ParameterExpression(p[4]*x[2])])", 82 | "Instruction(name='ryy', num_qubits=2, num_clbits=0, params=[ParameterExpression(p[5]*atan(x[3]))])", 83 | ] 84 | reference2 = [ 85 | "Instruction(name='z', num_qubits=1, num_clbits=0, params=[])", 86 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[ParameterExpression(atan(x[2]))])", 87 | "Instruction(name='cx', num_qubits=2, num_clbits=0, params=[])", 88 | "Instruction(name='rzz', num_qubits=2, num_clbits=0, params=[ParameterExpression(p[0]*x[1])])", 89 | "Instruction(name='rx', num_qubits=1, num_clbits=0, params=[ParameterVectorElement(x[0])])", 90 | ] 91 | reference3 = [ 92 | "Instruction(name='rzx', num_qubits=2, num_clbits=0, params=[ParameterExpression(p[0]*atan(x[0]))])", 93 | "Instruction(name='rx', num_qubits=1, num_clbits=0, params=[ParameterVectorElement(x[2])])", 94 | "Instruction(name='rxx', num_qubits=2, num_clbits=0, params=[ParameterVectorElement(p[1])])", 95 | "Instruction(name='z', num_qubits=1, num_clbits=0, params=[])", 96 | "Instruction(name='rz', num_qubits=1, num_clbits=0, params=[ParameterExpression(atan(x[1]))])", 97 | ] 98 | 99 | pqc = RandomEncodingCircuit(num_qubits=2, num_features=4, seed=2) 100 | x = ParameterVector("x", 4) 101 | p = ParameterVector("p", pqc.num_parameters) 102 | check_list1 = [str(op[0]) for op in pqc.get_circuit(x, p)] 103 | assert check_list1 == reference1 104 | 105 | pqc.set_params(num_qubits=3, num_features=3, min_gates=3, max_gates=5) 106 | x = ParameterVector("x", 3) 107 | p = ParameterVector("p", pqc.num_parameters) 108 | check_list2 = [str(op[0]) for op in pqc.get_circuit(x, p)] 109 | assert check_list2 == reference2 110 | 111 | pqc.set_params(seed=1234) 112 | p = ParameterVector("p", pqc.num_parameters) 113 | check_list3 = [str(op[0]) for op in pqc.get_circuit(x, p)] 114 | assert check_list3 == reference3 115 | -------------------------------------------------------------------------------- /tests/kernel/matrix/test_fidelity_quantum_kernel.py: -------------------------------------------------------------------------------- 1 | """Tests for FidelityKernel""" 2 | 3 | import pytest 4 | import numpy as np 5 | import sympy as sp 6 | 7 | from unittest.mock import MagicMock 8 | 9 | from squlearn import Executor 10 | from squlearn.encoding_circuit import LayeredEncodingCircuit 11 | from squlearn.kernel.lowlevel_kernel import FidelityKernel 12 | 13 | 14 | class TestFidelityKernel: 15 | """Test class for FidelityKernels""" 16 | 17 | @pytest.fixture 18 | def setup_single_variable(self): 19 | x, y, p = sp.symbols("x y p") 20 | sympy_K = ( 21 | 0.375 * (1 - sp.cos(p)) ** 2 * sp.cos(x - y) 22 | + 0.125 * (1 - sp.cos(p)) ** 2 * sp.cos(x + y) 23 | - 0.5 * (1 - sp.cos(p)) ** 2 24 | - 1.0 * sp.cos(p) 25 | - 0.3125 * sp.cos(x - y) 26 | - 0.1875 * sp.cos(x + y) 27 | - 0.03125 * sp.cos(-2 * p + x + y) 28 | + 0.125 * sp.cos(-p + x + y) 29 | + 0.375 * sp.cos(p - x + y) 30 | + 0.375 * sp.cos(p + x - y) 31 | + 0.125 * sp.cos(p + x + y) 32 | + 0.03125 * sp.cos(2 * p - x + y) 33 | + 0.03125 * sp.cos(2 * p + x - y) 34 | - 0.03125 * sp.cos(2 * p + x + y) 35 | + 1.5 36 | ) 37 | 38 | sympy_values = { 39 | "K": sympy_K, 40 | "dKdx": sp.diff(sympy_K, x), 41 | "dKdy": sp.diff(sympy_K, y), 42 | "dKdxdx": sp.diff(sp.diff(sympy_K, x), x), 43 | "dKdp": sp.diff(sympy_K, p), 44 | } 45 | 46 | return x, y, p, sympy_values 47 | 48 | @pytest.fixture 49 | def setup_multi_variable(self): 50 | x0, y0, p0 = sp.symbols("x0 y0 p0") 51 | x1, y1, p1 = sp.symbols("x1 y1 p1") 52 | 53 | sympy_K = ( 54 | 0.25 * sp.cos(x0 - y0) 55 | + 0.25 * sp.cos(x1 - y1) 56 | + 0.125 * sp.cos(x0 - x1 - y0 + y1) 57 | + 0.125 * sp.cos(x0 + x1 - y0 - y1) 58 | + 0.25 59 | ) 60 | 61 | sympy_values = { 62 | "K": sympy_K, 63 | "dKdx0": sp.diff(sympy_K, x0), 64 | "dKdx1": sp.diff(sympy_K, x1), 65 | "dKdy0": sp.diff(sympy_K, y0), 66 | "dKdy1": sp.diff(sympy_K, y1), 67 | "dKdp0": sp.diff(sympy_K, p0), 68 | "dKdp1": sp.diff(sympy_K, p1), 69 | } 70 | 71 | return x0, y0, x1, y1, p0, p1, sympy_values 72 | 73 | def create_fidelity_kernel( 74 | self, num_features, initial_parameters, executor, use_expectation, evaluate_duplicates 75 | ): 76 | executor = executor 77 | encoding_circuit = LayeredEncodingCircuit( 78 | num_qubits=num_features, num_features=num_features 79 | ) 80 | 81 | if num_features == 1: 82 | encoding_circuit.Ry("p") # For 1 feature, the FQK analytical kernel is: Rx(x)@Ry(p) 83 | elif num_features == 2: 84 | encoding_circuit.Rx( 85 | "p" 86 | ) # For 2 features, the FQK analytical kernel is: Rx(x0)@Ry(p0) x Rx(x1)@Ry(p1) 87 | encoding_circuit.Rx("x") 88 | 89 | kernel = FidelityKernel( 90 | encoding_circuit, 91 | executor=executor, 92 | caching=False, 93 | use_expectation=use_expectation, 94 | initial_parameters=initial_parameters, 95 | evaluate_duplicates=evaluate_duplicates, 96 | ) 97 | 98 | return kernel 99 | 100 | def test_single_variable_derivatives(self, setup_single_variable): 101 | x, y, p, sympy_values = setup_single_variable 102 | 103 | x_num, y_num = 0.79, -0.31 104 | p_num = -0.63 105 | 106 | subs = {x: x_num, y: y_num, p: p_num} 107 | sympy_num_values = {key: sympy_values[key].evalf(subs=subs) for key in sympy_values} 108 | 109 | kernel = self.create_fidelity_kernel(1, [p_num], Executor("pennylane"), True, "all") 110 | 111 | values = kernel.evaluate_derivatives( 112 | np.array([[x_num]]), np.array([[y_num]]), ["K", "dKdx", "dKdy", "dKdxdx", "dKdp"] 113 | ) 114 | for key in ["K", "dKdx", "dKdy", "dKdxdx", "dKdp"]: 115 | assert np.allclose( 116 | np.array(values[key]).flatten().astype(float), 117 | np.array(sympy_num_values[key]).astype(float), 118 | atol=1e-7, 119 | ) 120 | 121 | def test_multi_variable_derivatives(self, setup_multi_variable): 122 | x0, y0, x1, y1, p0, p1, sympy_values = setup_multi_variable 123 | 124 | x0_num, y0_num = 0.79, -0.31 125 | x1_num, y1_num = 0.9, -1.31 126 | p_num = -0.63 127 | 128 | subs = { 129 | x0: x0_num, 130 | y0: y0_num, 131 | x1: x1_num, 132 | y1: y1_num, 133 | p0: p_num, 134 | p1: p_num, 135 | } 136 | sympy_num_values = { 137 | "K": sympy_values["K"].evalf(subs=subs), 138 | "dKdx": [ 139 | sympy_values["dKdx0"].evalf(subs=subs), 140 | sympy_values["dKdx1"].evalf(subs=subs), 141 | ], 142 | "dKdy": [ 143 | sympy_values["dKdy0"].evalf(subs=subs), 144 | sympy_values["dKdy1"].evalf(subs=subs), 145 | ], 146 | "dKdp": [ 147 | sympy_values["dKdp0"].evalf(subs=subs), 148 | sympy_values["dKdp1"].evalf(subs=subs), 149 | ], 150 | } 151 | 152 | kernel = self.create_fidelity_kernel(2, [p_num, p_num], Executor("pennylane"), True, "all") 153 | 154 | values = kernel.evaluate_derivatives( 155 | np.array([[x0_num, x1_num]]), 156 | np.array([[y0_num, y1_num]]), 157 | ["K", "dKdx", "dKdy", "dKdp"], 158 | ) 159 | 160 | for key in ["K", "dKdx", "dKdy", "dKdp"]: 161 | assert np.allclose( 162 | np.array(values[key]).flatten().astype(float), 163 | np.array(sympy_num_values[key]).astype(float), 164 | atol=1e-7, 165 | ) 166 | 167 | def test_with_and_without_duplicates(self): 168 | np.random.seed(0) 169 | x_train = np.random.rand(10, 2) 170 | p_num = -0.63 171 | 172 | # Check if FidelityKernelExpectationValue and FidelityKernelPennylane give the same results for no duplicates 173 | kernel_without_duplicates_and_expectation_value = self.create_fidelity_kernel( 174 | 2, [p_num, p_num], Executor("pennylane"), True, "none" 175 | ) 176 | 177 | kernel_without_duplicates_and_pure_pennylane = self.create_fidelity_kernel( 178 | 2, [p_num, p_num], Executor("pennylane"), False, "none" 179 | ) 180 | 181 | values_expecation_value = ( 182 | kernel_without_duplicates_and_expectation_value.evaluate_derivatives( 183 | x_train, x_train, "K" 184 | ) 185 | ) 186 | values_pennylane = kernel_without_duplicates_and_pure_pennylane.evaluate(x_train, x_train) 187 | assert np.allclose(values_expecation_value, values_pennylane, atol=1e-7) 188 | -------------------------------------------------------------------------------- /tests/kernel/matrix/test_kernel_optimizer.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from sklearn.datasets import make_regression, make_classification 5 | 6 | from squlearn import Executor 7 | from squlearn.encoding_circuit.circuit_library.yz_cx_encoding_circuit import YZ_CX_EncodingCircuit 8 | from squlearn.kernel import FidelityKernel, ProjectedQuantumKernel, KernelOptimizer 9 | from squlearn.kernel import KernelOptimizer 10 | from squlearn.kernel import QGPR, QGPC, QKRR, QSVC, QSVR 11 | from squlearn.kernel.loss import NLL, TargetAlignment 12 | 13 | from squlearn.optimizers.adam import Adam 14 | 15 | 16 | @pytest.fixture(scope="module") 17 | def setup_kernel_optimizer_for_regressor(): 18 | """Setup kernel optimizer for regression tasks.""" 19 | enc_circ = YZ_CX_EncodingCircuit(num_qubits=4, num_features=4, num_layers=2) 20 | q_kernel = FidelityKernel( 21 | encoding_circuit=enc_circ, 22 | executor=Executor("pennylane"), 23 | parameter_seed=0, 24 | regularization="tikhonov", 25 | ) 26 | nll_loss = NLL() 27 | optimzer = Adam(options={"maxiter": 3, "lr": 0.1}) 28 | kernel_optimizer = KernelOptimizer(quantum_kernel=q_kernel, loss=nll_loss, optimizer=optimzer) 29 | regression_data = make_regression(n_samples=10, n_features=4, random_state=0) 30 | return kernel_optimizer, regression_data 31 | 32 | 33 | @pytest.fixture(scope="module") 34 | def setup_kernel_optimizer_for_classifier(): 35 | """Setup kernel optimizer for classification tasks.""" 36 | enc_circ = YZ_CX_EncodingCircuit(4, num_features=4, num_layers=2, c=1.0) 37 | q_kernel = ProjectedQuantumKernel( 38 | encoding_circuit=enc_circ, 39 | executor=Executor("pennylane"), 40 | parameter_seed=0, 41 | regularization="tikhonov", 42 | ) 43 | nll_loss = TargetAlignment() 44 | optimzer = Adam(options={"maxiter": 3, "lr": 0.1}) 45 | kernel_optimizer = KernelOptimizer(quantum_kernel=q_kernel, loss=nll_loss, optimizer=optimzer) 46 | classification_data = make_classification(n_samples=10, n_features=4, random_state=0) 47 | return kernel_optimizer, classification_data 48 | 49 | 50 | @pytest.mark.parametrize("high_level_class", [QGPR, QKRR, QSVR]) 51 | def test_kernel_optimizer_for_regressor_classes( 52 | setup_kernel_optimizer_for_regressor, 53 | high_level_class, 54 | ): 55 | """Test kernel optimizer for high-level regression methods. 56 | 57 | Args: 58 | setup_kernel_optimizer_for_regressor: A fixture that returns a kernel optimizer and 59 | regression data. 60 | high_level_class: A high-level regression class 61 | """ 62 | kernel_optimizer, regression_data = setup_kernel_optimizer_for_regressor 63 | model = high_level_class(quantum_kernel=kernel_optimizer) 64 | model.fit(regression_data[0], regression_data[1]) 65 | assert kernel_optimizer.get_optimal_parameters() is not None 66 | assert kernel_optimizer._optimizer is not None 67 | assert kernel_optimizer.is_fitted 68 | 69 | 70 | @pytest.mark.parametrize("high_level_class", [QGPC, QSVC]) 71 | def test_kernel_opimizer_for_classification_classes( 72 | setup_kernel_optimizer_for_classifier, 73 | high_level_class, 74 | ): 75 | """Test kernel optimizer for high-level classification methods. 76 | 77 | Args: 78 | setup_kernel_optimizer_for_classifier: A fixture that returns a kernel optimizer and 79 | classification data. 80 | high_level_class: A high-level classification class 81 | """ 82 | kernel_optimizer, classification_data = setup_kernel_optimizer_for_classifier 83 | model = high_level_class(quantum_kernel=kernel_optimizer) 84 | model.fit(classification_data[0], classification_data[1]) 85 | assert kernel_optimizer.get_optimal_parameters() is not None 86 | assert kernel_optimizer._optimizer is not None 87 | assert kernel_optimizer.is_fitted 88 | -------------------------------------------------------------------------------- /tests/kernel/matrix/test_projected_quantum_kernel.py: -------------------------------------------------------------------------------- 1 | """Tests for ProjectedQuantumKernel""" 2 | 3 | import pytest 4 | import numpy as np 5 | import sympy as sp 6 | 7 | from unittest.mock import MagicMock 8 | 9 | from squlearn import Executor 10 | from squlearn.encoding_circuit import LayeredEncodingCircuit 11 | from squlearn.kernel.lowlevel_kernel import ProjectedQuantumKernel 12 | 13 | 14 | class TestProjectedQuantumKernel: 15 | """Test class for ProjectedQuantumKernel""" 16 | 17 | @pytest.fixture 18 | def setup_single_variable(self): 19 | x, y, gamma_sp, p = sp.symbols("x y gamma p") 20 | sympy_K = sp.exp(-2 * gamma_sp * (1 - sp.cos(x - y)) * sp.cos(p) ** 2) 21 | 22 | sympy_values = { 23 | "K": sympy_K, 24 | "dKdx": sp.diff(sympy_K, x), 25 | "dKdy": sp.diff(sympy_K, y), 26 | "dKdxdx": sp.diff(sp.diff(sympy_K, x), x), 27 | "dKdp": sp.diff(sympy_K, p), 28 | } 29 | 30 | return x, y, gamma_sp, p, sympy_values 31 | 32 | @pytest.fixture 33 | def setup_multi_variable(self): 34 | x0, y0, gamma_sp, p0 = sp.symbols("x0 y0 gamma p0") 35 | x1, y1, p1 = sp.symbols("x1 y1 p1") 36 | 37 | sympy_K = sp.exp( 38 | -2.0 39 | * gamma_sp 40 | * ( 41 | -sp.cos(p0) ** 2 * sp.cos(x0 - y0) 42 | + sp.cos(p0) ** 2 43 | - sp.cos(p1) ** 2 * sp.cos(x1 - y1) 44 | + sp.cos(p1) ** 2 45 | ) 46 | ) 47 | 48 | sympy_values = { 49 | "K": sympy_K, 50 | "dKdx0": sp.diff(sympy_K, x0), 51 | "dKdx1": sp.diff(sympy_K, x1), 52 | "dKdy0": sp.diff(sympy_K, y0), 53 | "dKdy1": sp.diff(sympy_K, y1), 54 | "dKdp0": sp.diff(sympy_K, p0), 55 | "dKdp1": sp.diff(sympy_K, p1), 56 | } 57 | 58 | return x0, y0, x1, y1, gamma_sp, p0, p1, sympy_values 59 | 60 | def create_projected_quantum_kernel(self, num_features, gamma_num, initial_parameters): 61 | executor = Executor() 62 | encoding_circuit = LayeredEncodingCircuit( 63 | num_qubits=num_features, num_features=num_features 64 | ) 65 | encoding_circuit.Ry("p") 66 | encoding_circuit.Rx("x") 67 | 68 | kernel = ProjectedQuantumKernel( 69 | encoding_circuit=encoding_circuit, 70 | executor=executor, 71 | outer_kernel="gaussian", 72 | gamma=gamma_num, 73 | initial_parameters=np.array(initial_parameters), 74 | ) 75 | return kernel 76 | 77 | def test_single_variable_derivatives(self, setup_single_variable): 78 | x, y, gamma_sp, p, sympy_values = setup_single_variable 79 | 80 | x_num, y_num = 0.79, -0.31 81 | p_num = -0.63 82 | gamma_num = 0.08 83 | 84 | subs = {x: x_num, y: y_num, gamma_sp: gamma_num, p: p_num} 85 | sympy_num_values = {key: sympy_values[key].evalf(subs=subs) for key in sympy_values} 86 | 87 | kernel = self.create_projected_quantum_kernel(1, gamma_num, [p_num]) 88 | 89 | values = kernel.evaluate_derivatives( 90 | [x_num], [y_num], ["K", "dKdx", "dKdy", "dKdxdx", "dKdp"] 91 | ) 92 | for key in ["K", "dKdx", "dKdy", "dKdxdx", "dKdp"]: 93 | assert np.allclose( 94 | np.array(values[key]).flatten().astype(float), 95 | np.array(sympy_num_values[key]).astype(float), 96 | atol=1e-7, 97 | ) 98 | 99 | def test_multi_variable_derivatives(self, setup_multi_variable): 100 | x0, y0, x1, y1, gamma_sp, p0, p1, sympy_values = setup_multi_variable 101 | 102 | x0_num, y0_num = 0.79, -0.31 103 | x1_num, y1_num = 0.9, -1.31 104 | p_num = -0.63 105 | gamma_num = 0.08 106 | 107 | subs = { 108 | x0: x0_num, 109 | y0: y0_num, 110 | x1: x1_num, 111 | y1: y1_num, 112 | gamma_sp: gamma_num, 113 | p0: p_num, 114 | p1: p_num, 115 | } 116 | sympy_num_values = { 117 | "K": sympy_values["K"].evalf(subs=subs), 118 | "dKdx": [ 119 | sympy_values["dKdx0"].evalf(subs=subs), 120 | sympy_values["dKdx1"].evalf(subs=subs), 121 | ], 122 | "dKdy": [ 123 | sympy_values["dKdy0"].evalf(subs=subs), 124 | sympy_values["dKdy1"].evalf(subs=subs), 125 | ], 126 | "dKdp": [ 127 | sympy_values["dKdp0"].evalf(subs=subs), 128 | sympy_values["dKdp1"].evalf(subs=subs), 129 | ], 130 | } 131 | 132 | kernel = self.create_projected_quantum_kernel(2, gamma_num, [p_num, p_num]) 133 | 134 | values = kernel.evaluate_derivatives( 135 | [[x0_num, x1_num]], [[y0_num, y1_num]], ["K", "dKdx", "dKdy", "dKdp"] 136 | ) 137 | 138 | for key in ["K", "dKdx", "dKdy", "dKdp"]: 139 | assert np.allclose( 140 | np.array(values[key]).flatten().astype(float), 141 | np.array(sympy_num_values[key]).astype(float), 142 | atol=1e-7, 143 | ) 144 | -------------------------------------------------------------------------------- /tests/optimizers/test_sglbo.py: -------------------------------------------------------------------------------- 1 | """Tests for the SGLBO optimizer.""" 2 | 3 | import numpy as np 4 | 5 | from squlearn.optimizers.sglbo import SGLBO 6 | 7 | 8 | # define the function to be optimized 9 | def quadratic(x): 10 | return np.sum(x**2) 11 | 12 | 13 | class TestSGLBO: 14 | """Test class for SGLBO optimizer.""" 15 | 16 | def test_minimize(self): 17 | """Test the minimize method of SGLBO.""" 18 | x0 = np.array([1.0, 2.0]) 19 | 20 | optimizer = SGLBO() 21 | 22 | result = optimizer.minimize(quadratic, x0) 23 | 24 | assert np.isclose(result.fun, 0.0, atol=1e-6) 25 | 26 | def test_step(self): 27 | """Test the step method of SGLBO.""" 28 | 29 | def gradient_function(x): 30 | return 2 * x 31 | 32 | x0 = np.array([2.0]) 33 | 34 | optimizer = SGLBO() 35 | optimizer.x = x0 36 | optimizer.func = quadratic 37 | 38 | result = optimizer.step(x=optimizer.x, grad=gradient_function(optimizer.x)) 39 | 40 | assert result < x0 41 | -------------------------------------------------------------------------------- /tests/qnn/test_base_qnn.py: -------------------------------------------------------------------------------- 1 | """Tests for BaseQNN""" 2 | 3 | import pytest 4 | 5 | import numpy as np 6 | 7 | from squlearn import Executor 8 | from squlearn.observables import IsingHamiltonian 9 | from squlearn.encoding_circuit import ChebyshevPQC 10 | from squlearn.optimizers import SLSQP 11 | from squlearn.qnn import SquaredLoss 12 | from squlearn.qnn.base_qnn import BaseQNN 13 | 14 | 15 | class MockBaseQNN(BaseQNN): 16 | """Mock class for BaseQNN.""" 17 | 18 | def _fit(self, X: np.ndarray, y: np.ndarray, weights: np.ndarray = None) -> None: 19 | pass 20 | 21 | 22 | class TestBaseQNN: 23 | """Test class for BaseQNN.""" 24 | 25 | @pytest.fixture(scope="module") 26 | def qnn_single_op(self) -> MockBaseQNN: 27 | """BaseQNN module with single operator.""" 28 | np.random.seed(42) 29 | executor = Executor() 30 | pqc = ChebyshevPQC(num_qubits=4, num_features=1, num_layers=2) 31 | operator = IsingHamiltonian(num_qubits=4, I="S", Z="S", ZZ="S") 32 | loss = SquaredLoss() 33 | optimizer = SLSQP(options={"maxiter": 2}) 34 | return MockBaseQNN(pqc, operator, executor, loss, optimizer) 35 | 36 | @pytest.fixture(scope="module") 37 | def qnn_multi_op(self) -> MockBaseQNN: 38 | """BaseQNN module with multiple operators.""" 39 | np.random.seed(42) 40 | executor = Executor() 41 | pqc = ChebyshevPQC(num_qubits=4, num_features=1, num_layers=2) 42 | operator = [IsingHamiltonian(num_qubits=4, I="S", Z="S", ZZ="S") for _ in range(5)] 43 | loss = SquaredLoss() 44 | optimizer = SLSQP(options={"maxiter": 2}) 45 | return MockBaseQNN(pqc, operator, executor, loss, optimizer) 46 | 47 | def test_set_params_invalid_param(self, qnn_single_op: MockBaseQNN): 48 | """ 49 | Test if setting an invalid parameter raises a ValueError. 50 | 51 | Args: 52 | qnn_single_op (MockBaseQNN): The MockBaseQNN object to test. 53 | 54 | Returns: 55 | None 56 | """ 57 | with pytest.raises(ValueError): 58 | qnn_single_op.set_params(invalid_param=3) 59 | 60 | def test_set_params_seed(self, qnn_single_op: MockBaseQNN): 61 | """ 62 | Test `set_params` with `parameter_seed`. 63 | 64 | Args: 65 | qnn_single_op (MockBaseQNN): An instance of the `MockBaseQNN` class. 66 | 67 | Returns: 68 | None 69 | """ 70 | qnn_single_op.set_params(parameter_seed=42) 71 | assert qnn_single_op.parameter_seed == 42 72 | 73 | def test_set_params_num_qubits_single_op(self, qnn_single_op: MockBaseQNN): 74 | """ 75 | Test `set_params` with `num_qubits` for single operator. 76 | 77 | Args: 78 | qnn_single_op (MockBaseQNN): An instance of the `MockBaseQNN` class. 79 | 80 | Returns: 81 | None 82 | """ 83 | qnn_single_op.set_params(num_qubits=5) 84 | assert qnn_single_op.encoding_circuit.num_qubits == 5 85 | assert qnn_single_op.operator.num_qubits == 5 86 | assert qnn_single_op._qnn.num_qubits == 5 87 | 88 | def test_set_params_num_qubits_multi_op(self, qnn_multi_op): 89 | """ 90 | Test `set_params` with `num_qubits` for multiple operators. 91 | 92 | Args: 93 | qnn_multi_op (MockBaseQNN): An instance of the `MockBaseQNN` class. 94 | 95 | Returns: 96 | None 97 | """ 98 | qnn_multi_op.set_params(num_qubits=5) 99 | assert qnn_multi_op.encoding_circuit.num_qubits == 5 100 | for operator in qnn_multi_op.operator: 101 | assert operator.num_qubits == 5 102 | assert qnn_multi_op._qnn.num_qubits == 5 103 | 104 | def test_set_params_encoding_circuit(self, qnn_single_op): 105 | """ 106 | Test `set_params` for pqc parameters. 107 | 108 | Args: 109 | qnn_single_op (MockBaseQNN): An instance of the `MockBaseQNN` class. 110 | 111 | Returns: 112 | None 113 | """ 114 | qnn_single_op.set_params(num_layers=3, closed=True) 115 | assert qnn_single_op.encoding_circuit.num_layers == 3 116 | assert qnn_single_op.encoding_circuit.closed 117 | assert qnn_single_op._qnn._pqc.get_params()["num_layers"] == 3 118 | assert qnn_single_op._qnn._pqc.get_params()["closed"] 119 | 120 | def test_set_params_single_operator(self, qnn_single_op): 121 | """ 122 | Test `set_params` for single operator parameters. 123 | 124 | Args: 125 | qnn_single_op (MockBaseQNN): An instance of the `MockBaseQNN` class. 126 | 127 | Returns: 128 | None 129 | """ 130 | qnn_single_op.set_params(X="S", Z="N") 131 | assert qnn_single_op.operator.X == "S" 132 | assert qnn_single_op.operator.Z == "N" 133 | assert qnn_single_op._qnn._observable.X == "S" 134 | assert qnn_single_op._qnn._observable.Z == "N" 135 | 136 | def test_set_params_multi_operator(self, qnn_multi_op): 137 | """ 138 | Test `set_params` for multiple operator parameters. 139 | 140 | Args: 141 | qnn_multi_op (MockBaseQNN): An instance of the `MockBaseQNN` class. 142 | 143 | Returns: 144 | None 145 | """ 146 | qnn_multi_op.set_params(op0__X="S", op3__Z="N") 147 | assert qnn_multi_op.operator[0].X == "S" 148 | assert qnn_multi_op.operator[3].Z == "N" 149 | assert qnn_multi_op._qnn._observable[0].X == "S" 150 | assert qnn_multi_op._qnn._observable[3].Z == "N" 151 | -------------------------------------------------------------------------------- /tests/qrc/test_base_qrc.py: -------------------------------------------------------------------------------- 1 | """Tests for BaseQRC""" 2 | 3 | import pytest 4 | 5 | import numpy as np 6 | 7 | from squlearn import Executor 8 | from squlearn.encoding_circuit import HubregtsenEncodingCircuit 9 | from sklearn.linear_model import LinearRegression 10 | from squlearn.qrc.base_qrc import BaseQRC 11 | 12 | 13 | class MockBaseQRC(BaseQRC): 14 | """Mock class for BaseQRC.""" 15 | 16 | def _initialize_ml_model(self) -> None: 17 | """Initialize the machine learning model, has to be implemented in the child class""" 18 | self._ml_model = LinearRegression() 19 | 20 | 21 | class TestBaseQRC: 22 | """Test class for BaseQRC.""" 23 | 24 | @pytest.fixture(scope="module") 25 | def qrc_class(self) -> MockBaseQRC: 26 | """BaseQRC module.""" 27 | executor = Executor() 28 | encoding_circuit = HubregtsenEncodingCircuit(num_qubits=4, num_features=2) 29 | return MockBaseQRC(encoding_circuit, executor) 30 | 31 | def test_set_params_invalid_param(self, qrc_class: MockBaseQRC): 32 | """ 33 | Test if setting an invalid parameter raises a ValueError. 34 | 35 | Args: 36 | qrc_class (MockBaseQRC): The MockBaseQRC object to test. 37 | 38 | Returns: 39 | None 40 | """ 41 | with pytest.raises(ValueError): 42 | qrc_class.set_params(invalid_param=3) 43 | 44 | def test_set_params_seed(self, qrc_class: MockBaseQRC): 45 | """ 46 | Test `set_params` with `parameter_seed`. 47 | 48 | Args: 49 | qrc_class (MockBaseQRC): An instance of the `MockBaseQRC` class. 50 | 51 | Returns: 52 | None 53 | """ 54 | qrc_class.set_params(parameter_seed=42) 55 | assert qrc_class.parameter_seed == 42 56 | 57 | def test_set_params_num_qubits(self, qrc_class: MockBaseQRC): 58 | """ 59 | Test `set_params` with `num_qubits` for QRC base class. 60 | 61 | Args: 62 | qrc_class (MockBaseQRC): An instance of the `MockBaseQRC` class. 63 | 64 | Returns: 65 | None 66 | """ 67 | qrc_class.set_params(num_qubits=5) 68 | assert qrc_class.encoding_circuit.num_qubits == 5 69 | assert qrc_class._operators[0].num_qubits == 5 70 | assert qrc_class._qnn.num_qubits == 5 71 | 72 | def test_set_params_num_operators(self, qrc_class: MockBaseQRC): 73 | """ 74 | Test `set_params` with `num_operators` for QRC base class 75 | 76 | Args: 77 | qrc_class (MockBaseQRC): An instance of the `MockBaseQRC` class. 78 | 79 | Returns: 80 | None 81 | """ 82 | qrc_class.set_params(num_operators=12) 83 | assert len(qrc_class._operators) == 12 84 | assert qrc_class.num_operators == 12 85 | qrc_class.fit(np.array([[0.1, 0.2], [0.1, 0.2]]), np.array([0.3, 0.4])) 86 | assert qrc_class._ml_model.n_features_in_ == 12 87 | -------------------------------------------------------------------------------- /tests/qrc/test_qrc_classifier.py: -------------------------------------------------------------------------------- 1 | """Tests for QRCClassifier""" 2 | 3 | import pytest 4 | 5 | import numpy as np 6 | from sklearn.datasets import make_classification 7 | from sklearn.preprocessing import MinMaxScaler 8 | 9 | from squlearn import Executor 10 | from squlearn.encoding_circuit import HubregtsenEncodingCircuit 11 | from squlearn.qrc.qrc_classifier import QRCClassifier 12 | 13 | 14 | class TestQRCClassifier: 15 | """Test class for QRCClassifier.""" 16 | 17 | @pytest.fixture(scope="module") 18 | def data(self) -> tuple[np.ndarray, np.ndarray]: 19 | """Test data module.""" 20 | # pylint: disable=unbalanced-tuple-unpacking 21 | X, y = make_classification(n_samples=6, n_features=4, random_state=42) 22 | scl = MinMaxScaler((0.1, 0.9)) 23 | X = scl.fit_transform(X, y) 24 | return X, y 25 | 26 | def qrc_classifier(self, ml_model: str) -> QRCClassifier: 27 | """QNNClassifier module.""" 28 | pqc = HubregtsenEncodingCircuit(num_qubits=4, num_features=4, num_layers=1) 29 | return QRCClassifier(pqc, Executor(), ml_model=ml_model) 30 | 31 | @pytest.mark.parametrize( 32 | "ml_model", 33 | [ 34 | "linear", 35 | "mlp", 36 | "kernel", 37 | ], 38 | ) 39 | def test_fit_predict(self, data, ml_model): 40 | """Tests fit and predict methods of QRCClassifier.""" 41 | X, y = data 42 | qrc_classifier = self.qrc_classifier(ml_model) 43 | 44 | if ml_model == "mlp": 45 | qrc_classifier.set_params(ml_model_options={"random_state": 0, "max_iter": 1000}) 46 | 47 | qrc_classifier.fit(X, y) 48 | values = qrc_classifier.predict(X) 49 | 50 | referece_values = { 51 | "linear": np.array([0, 1, 0, 1, 1, 1]), 52 | "mlp": np.array([0, 1, 0, 1, 0, 1]), 53 | "kernel": np.array([0, 1, 0, 0, 0, 1]), 54 | } 55 | assert np.allclose(values, referece_values[ml_model], atol=1e-7) 56 | -------------------------------------------------------------------------------- /tests/qrc/test_qrc_regressor.py: -------------------------------------------------------------------------------- 1 | """Tests for QRCRegressor""" 2 | 3 | import pytest 4 | 5 | import numpy as np 6 | from sklearn.datasets import make_regression 7 | from sklearn.preprocessing import MinMaxScaler 8 | 9 | from squlearn import Executor 10 | from squlearn.encoding_circuit import HubregtsenEncodingCircuit 11 | from squlearn.qrc.qrc_regressor import QRCRegressor 12 | 13 | 14 | class TestQRCRegressor: 15 | """Test class for QRCRegressor.""" 16 | 17 | @pytest.fixture(scope="module") 18 | def data(self) -> tuple[np.ndarray, np.ndarray]: 19 | """Test data module.""" 20 | # pylint: disable=unbalanced-tuple-unpacking 21 | X, y = make_regression(n_samples=6, n_features=1, random_state=42) 22 | scl = MinMaxScaler((0.1, 0.9)) 23 | X = scl.fit_transform(X, y) 24 | return X, y 25 | 26 | def qrc_regressor(self, ml_model: str) -> QRCRegressor: 27 | """QNNRegressor module.""" 28 | pqc = HubregtsenEncodingCircuit(num_qubits=2, num_features=1, num_layers=1) 29 | return QRCRegressor(pqc, Executor(), ml_model=ml_model) 30 | 31 | @pytest.mark.parametrize( 32 | "ml_model", 33 | [ 34 | "linear", 35 | "mlp", 36 | "kernel", 37 | ], 38 | ) 39 | def test_fit_predict(self, data, ml_model): 40 | """Tests fit and predict methods of QRCRegressor.""" 41 | X, y = data 42 | qrc_regressor = self.qrc_regressor(ml_model) 43 | 44 | if ml_model == "mlp": 45 | qrc_regressor.set_params(ml_model_options={"random_state": 0, "max_iter": 1000}) 46 | 47 | qrc_regressor.fit(X, y) 48 | values = qrc_regressor.predict(X) 49 | 50 | referece_values = { 51 | "linear": np.array( 52 | [2.88509523, -0.80308901, 3.76200899, -1.35995202, 8.84630756, -1.36004739] 53 | ), 54 | "mlp": np.array( 55 | [3.06585455, -0.96776248, 4.0395428, -1.52636054, 8.80042369, -1.52645433] 56 | ), 57 | "kernel": np.array( 58 | [2.87653068, -0.65406699, 3.74209315, -1.13894099, 8.15484098, -1.13902237] 59 | ), 60 | } 61 | 62 | assert np.allclose(values, referece_values[ml_model], atol=1e-7) 63 | -------------------------------------------------------------------------------- /tests/util/execution/test_parallel_primitives.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | 4 | from qiskit.circuit import QuantumCircuit 5 | from qiskit.quantum_info import SparsePauliOp 6 | from qiskit_ibm_runtime.fake_provider import FakeManilaV2 7 | from squlearn.util import Executor 8 | from squlearn.util.executor import BaseEstimatorV1, BaseEstimatorV2, BaseSamplerV1, BaseSamplerV2 9 | 10 | 11 | class TestParallelExecutor: 12 | 13 | @pytest.mark.parametrize("parallel_mode", ["auto", 2]) 14 | def test_parallel_sampler(self, parallel_mode): 15 | """ 16 | Script for testing parallel execution of the sampler primitive. 17 | """ 18 | qc = QuantumCircuit(2) 19 | qc.x([0, 1]) 20 | qc.measure_all() 21 | executor = Executor(FakeManilaV2(), seed=0, shots=10000, qpu_parallelization=parallel_mode) 22 | sampler = executor.get_sampler() 23 | if isinstance(sampler, BaseSamplerV1): 24 | result = sampler.run(qc).result() 25 | assert result.metadata[0]["shots"] == 10000 26 | else: 27 | result = sampler.run([(qc,)]).result() 28 | assert result[0].metadata["shots"] == 10000 29 | 30 | @pytest.mark.parametrize("parallel_mode", ["auto", 2]) 31 | def test_parallel_estimator(self, parallel_mode): 32 | """ 33 | Script for testing parallel execution of the estimator primitive. 34 | """ 35 | qc = QuantumCircuit(2) 36 | qc.x([0, 1]) 37 | obs = SparsePauliOp("ZZ") 38 | executor = Executor(FakeManilaV2(), seed=0, shots=10000, qpu_parallelization=parallel_mode) 39 | estimator = executor.get_estimator() 40 | if isinstance(estimator, BaseEstimatorV1): 41 | result = estimator.run(qc, obs).result() 42 | assert result.metadata[0]["shots"] == 10000 43 | else: 44 | result = estimator.run([(qc, obs)]).result() 45 | assert result[0].metadata["shots"] == 10000 46 | --------------------------------------------------------------------------------