diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7cce43d..0af67d5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,13 +1,42 @@ name: Documentation on: - push: - branches: - - main + pull_request: + branches: [ "main" ] + workflow_call: + inputs: + test-run-id: + required: true + type: string permissions: contents: write jobs: + build: + runs-on: ubuntu-latest + if: github.ref != 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + - name: Install poetry + run: pipx install poetry + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: 'poetry' + - name: Install dependencies + run: poetry install --with docs + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - name: Setting up Cache + uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .ci-cache + restore-keys: | + mkdocs-material- + - name: Build Docs + run: poetry run mkdocs build deploy: runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Configure Git Credentials @@ -20,10 +49,10 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.11" - cache: 'poetry' # caching pip dependencies + cache: 'poetry' - name: Install dependencies run: poetry install --with docs - - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV # (3)! + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - name: Setting up Cache uses: actions/cache@v4 with: @@ -31,5 +60,11 @@ jobs: path: .ci-cache restore-keys: | mkdocs-material- + - name: Download a single artifact + uses: actions/download-artifact@v4 + with: + name: coverage-report + run-id: ${{ inputs.test-run-id }} + path: docs/coverage/ - name: Deploy run: poetry run mkdocs gh-deploy --force \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..ee6e247 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,57 @@ +name: Publish +on: + push: + branches: [ "main" ] +permissions: + contents: write +jobs: + test: + name: Trigger Tests + uses: ./.github/workflows/test.yml + quality: + name: Trigger Quality + uses: ./.github/workflows/quality.yml + documentation: + needs: test + name: Trigger Documentation + uses: ./.github/workflows/docs.yml + with: + test-run-id: ${{ github.run_id }} + release: + needs: [test, quality, documentation] + name: Release + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install poetry + run: pipx install poetry + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: 'poetry' + - name: Compare tag and version + run: | + if [ "$(git tag)" = "$(poetry version --short)" ]; then + echo "The tag and the version are the same, nothing to do." + echo "DORELEASE=false" >> $GITHUB_ENV + else + echo "DORELEASE=true" >> $GITHUB_ENV + fi + - name: Create Release & Tag + if: env.DORELEASE == 'true' + run: | + gh release create "$(poetry version --short)" \ + --repo="$GITHUB_REPOSITORY" \ + --title="${GITHUB_REPOSITORY#*/} $(poetry version --short)" \ + --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish to PyPI + if: env.DORELEASE == 'true' + run: | + poetry install --with dev + poetry config pypi-token.pypi ${{ secrets.PYPI_API_TOKEN }} + poetry build + poetry publish \ No newline at end of file diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index c8fc756..f27516d 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -1,13 +1,12 @@ name: Quality on: - push: - branches: [ "main" ] pull_request: branches: [ "main" ] + workflow_call: permissions: contents: read jobs: - build: + quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -17,10 +16,12 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.11" - cache: 'poetry' # caching pip dependencies + cache: 'poetry' - name: Install dependencies run: poetry install --with dev - name: Lint with flake8 run: | poetry run flake8 qml_essentials poetry run flake8 tests + - name: Running license check + run: poetry run licensecheck --zero --license mit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d1cc555..df3d9cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,15 +1,21 @@ name: Testing on: - push: - branches: [ "main" ] pull_request: branches: [ "main" ] + workflow_call: permissions: - contents: read + contents: write jobs: - build: + test: runs-on: ubuntu-latest steps: + # - name: Install Act dependencies + # if: ${{ env.ACT }} + # run: | + # apt update -qq > /dev/null + # apt install apt-utils -y -qq > /dev/null + # apt remove python3 + # apt install python3.11 python3-venv pipx -y - uses: actions/checkout@v4 - name: Install poetry run: pipx install poetry @@ -17,10 +23,16 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.11" - cache: 'poetry' # caching pip dependencies + cache: 'poetry' - name: Install dependencies run: poetry install --with dev - name: Test with pytest run: | poetry run coverage run -m pytest poetry run coverage report -m + poetry run coverage html + - name: Share coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: htmlcov/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9f14a6..d2d7d91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to QUAFEL +# Contributing to QML-Essentials :tada: Welcome! :tada: @@ -7,7 +7,7 @@ Start of by.. 1. Creating an issue using one of the templates (Bug Report, Feature Request) - let's discuss what's going wrong or what should be added - can you contribute with code? Great! Go ahead! :rocket: -2. Forking the repository and working on your stuff +2. Forking the repository and working on your stuff. See the sections below for details on how to set things up. 3. Creating a pull request to the main repository ## Setup @@ -24,23 +24,27 @@ poetry run pre-commit autoupdate poetry run pre-commit install ``` +Currently the only purpose of the hook is to run Black on commit which will do some code formatting for you. +However be aware, that this might reject your commit and you have to re-do the commit. + ## Testing We do our testing with Pytest. Corresponding tests can be triggered as follows: ``` poetry run pytest ``` +There are Github action pipelines in place, that will do linting and testing once you open a pull request. +However, it's a good idea to run tests and linting (either Black or Flake8) locally before pushing. ## Packaging -Building and packaging requires some extra steps (assuming Poetry): -- `poetry run devpi use https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org` -- `poetry run devpi login alice --password=456` -- `poetry run devpi use alice/quantum` -- `poetry config repositories.quantum https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum` -- `poetry config http-basic.quantum alice 456` (or remove password for interactive prompt) -- `poetry version (major|minor|patch|premajor|preminor|prepatch)` as explained [here](https://python-poetry.org/docs/cli/#version) -- `poetry publish --build -r quantum` +Packaging is done automagically using Github actions. +This action is triggered when a new version is being detected in the `pyprojec.toml` file. +This works by comparing the output of `poetry version --short` against `git tag` and triggering the publishing process if those differ. +Publishing includes +- setting the git tag equal to the version specified in `pyproject.toml` +- creating a release with the current git tag and automatically generated release notes +- publishing the package to PyPI using the stored credentials ## Re-Installing Package @@ -59,13 +63,8 @@ We use MkDocs for our documentation. To run a server locally, run: ``` poetry run mkdocs serve ``` +This will automatically trigger a rebuild each time you make changes. +See the [MkDocs Documentation](https://cirkiters.github.io/qml-essentials/usage/) for more details. -If you make changes to the documentation in the meantime, trigger a build by running -``` -poetry run mkdocs build -``` - -For pushing to Github pages: -``` -poetry run mkdocs gh-deploy -``` \ No newline at end of file +Publishing (and building) the documentation is done automagically using Github actions. +This action is triggered when a new release is made. \ No newline at end of file diff --git a/README.md b/README.md index 0f6872d..1c13e75 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,25 @@ # QML Essentials -[![version](https://img.shields.io/badge/version-0.1.12-green.svg)](https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum/) [![Pipx Status](https://servers.stroblme.de/api/badge/3/uptime/72?color=%2331c754&labelColor=%233f4850)](https://servers.stroblme.de/status/open) [![Quality](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml) [![Testing](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml) [![Documentation](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml) +[![version](https://img.shields.io/badge/version-0.1.12-green.svg)](https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum/) [![Quality](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml) [![Testing](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml) [![Documentation](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml) -## :scroll: About +## 📜 About This repo contains some of the commonly used Ansaetze and coding stuff required for working with QML and Data-Reuploading models. There are also dedicated classes to calculate entanglement and expressiblity of a provided model as well as its Fourier coefficients. -## :rocket: Getting Started +## 🚀 Getting Started -You can find installation instructions and documentation on the corresponding [Github Page](https://cirkiters.github.io/qml-essentials/). +``` +pip install qml-essentials +``` +or +``` +poetry add qml-essentials +``` -## :construction: Contributing +To install our package from [PyPI](https://pypi.org/project/qml-essentials/). +You can find details on how to use it and further documentation on the corresponding [Github Page](https://cirkiters.github.io/qml-essentials/). + +## 🚧 Contributing Contributions are very welcome! See [Contribution Guidelines](https://github.com/cirKITers/qml-essentials/blob/main/CONTRIBUTING.md). \ No newline at end of file diff --git a/docs/ansaetze.md b/docs/ansaetze.md new file mode 100644 index 0000000..ce86e3e --- /dev/null +++ b/docs/ansaetze.md @@ -0,0 +1,13 @@ +# Ansaetze + +.. or Ansatzes as preferred by the english community. +Anyway, we got various of the most-used Ansaetze implemented in this package. + +You can load them manually by +```python +from qml_essentials.ansaetze import Ansaetze +print(Ansaetze.get_available()) +``` + +However, usually you just want reference to them (by name) when instantiating a model. +To get an overview of all the available Ansaetze, checkout the [references](https://cirkiters.github.io/qml-essentials/references/). \ No newline at end of file diff --git a/docs/coefficients.md b/docs/coefficients.md new file mode 100644 index 0000000..55c8070 --- /dev/null +++ b/docs/coefficients.md @@ -0,0 +1,18 @@ +# Coefficients + +A characteristic property of any Fourier model are its coefficients. +Our package can, given a model, calculate the corresponding coefficients by utilizing the [Pennylane Fourier Coefficients](https://docs.pennylane.ai/en/stable/_modules/pennylane/fourier/coefficients.html) method. + +In the simplest case, this could look as follows: +```python +from qml_essentials.model import Model +from qml_essentials.coefficients import Coefficients + +model = Model( + n_qubits=2 + n_layers=1 + circuit_type="HardwareEfficient", + ) + +coeffs = Coefficients.sample_coefficients(model) +``` \ No newline at end of file diff --git a/docs/entanglement.md b/docs/entanglement.md new file mode 100644 index 0000000..4fc414b --- /dev/null +++ b/docs/entanglement.md @@ -0,0 +1,30 @@ +# Entanglement + +As one of the fundamental aspects of quantum computing, entanglement plays also an important role in quantum machine learning. +Our package offers methods for calculating the entangling capability of a particular model. +Currently, only the "Meyer-Wallach" measure is implemented, but other will be added soon! + +In the simplest case, this could look as follows: +```python +from qml_essentials.model import Model +from qml_essentials.entanglement import Entanglement + +model = Model( + n_qubits=2, + n_layers=1, + circuit_type="HardwareEfficient", + ) + +ent_cap = Entanglement.meyer_wallach( + model, n_samples=1000, seed=1000 +) +``` + +Note, that every function in this class accepts keyword-arguments which are being passed to the model call, so you could e.g. enable caching by +```python +ent_cap = Entanglement.meyer_wallach( + model, n_samples=1000, seed=1000, cache=True +) +``` + +If you set `n_samples=None`, we will use the currently stored parameters of the model to estimate the degree of entanglement. \ No newline at end of file diff --git a/docs/expressibility.md b/docs/expressibility.md new file mode 100644 index 0000000..b3c4834 --- /dev/null +++ b/docs/expressibility.md @@ -0,0 +1,36 @@ +# Expressibility + +Our package includes tool to estimate the expressiblity of a particularly chosen Ansatz. + +```python +model = Model( + n_qubits=2, + n_layers=1, + circuit_type=HardwareEfficient, +) + +input_domain, bins, dist_circuit = Expressibility.state_fidelities( + n_bins=10, + n_samples=200, + n_input_samples=5, + seed=1000, + model=model, +) +``` + +Note that `state_fidelities` accepts keyword arguments that are being passed to the model call. +This allows you to utilize e.g. caching. + +Next, you can calculate the Haar integral (as reference), by +```python +input_domain, dist_haar = Expressibility.haar_integral( + n_qubits=2, + n_bins=10, + cache=False, +) +``` + +Finally, the Kullback-Leibler divergence allows you to see how well the particular circuit performs compared to the Haar integral: +```python +kl_dist = Expressibility.kullback_leibler_divergence(dist_circuit, dist_haar).mean() +``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index b30b4dc..0818360 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,10 @@ -[![Pipx Status](https://servers.stroblme.de/api/badge/3/uptime/72?color=%2331c754&labelColor=%233f4850)](https://servers.stroblme.de/status/open) [![Lint and Pytest](https://github.com/cirKITers/qml-essentials/actions/workflows/python-app.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/python-app.yml) [![Page Build](https://github.com/cirKITers/qml-essentials/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/pages/pages-build-deployment) +[![version](https://img.shields.io/badge/version-0.1.12-green.svg)](https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum/) [![Quality](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml) [![Testing](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml) [![Documentation](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml) + This repo contains some of the commonly used Ansaetze and coding stuff required for working with QML and Data-Reuploading models. There are also dedicated classes to calculate entanglement and expressiblity of a provided model as well as its Fourier coefficients. -## Installation - -The package is available at [this index](https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum). - -There are two installation methods: - -### [Poetry](https://python-poetry.org/) - -- `poetry source add --priority=supplemental quantum https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum/+simple/` -- `poetry add --source quantum qml-essentials` - -### pip +Curious? :rocket: Get started by heading to the [setup page](setup.md). -- `pip install --index-url https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum/+simple/ qml-essentials` +If you want to contribute, please refer to our [CONTRIBUTING guide](https://github.com/cirKITers/qml-essentials/blob/main/CONTRIBUTING.md) on Github. +Oh and if you want to know what's being tested, checkout our [coverage report](coverage/index.html). \ No newline at end of file diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 0000000..cd5d323 --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,14 @@ +# Setup + +This package is available via [PyPi](https://pypi.org/project/qml-essentials/). +Installing this package is as simple as with any other package: + +`pip install qml-essentials` + +Or, if you prefer poetry: + +`poetry add qml-essentials` + +Once you set things up, go ahead and checkout [how to use qml-essentials](usage.md). + +If you want to contribute to this project, a more detailled explaination is available in the [CONTRIBUTING guide](https://github.com/cirKITers/qml-essentials/blob/main/CONTRIBUTING.md). \ No newline at end of file diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..53ac59a --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,72 @@ +# Usage + +Central component of our package is the Fourier model which you can import with +```python +from qml_essentials.model import Model +``` + +In the simplest scenario, one would instantiate such a model with $2$ qubits and a single layer using the "Hardware Efficient" ansatz by: +```python +model = Model( + n_qubits=2 + n_layers=1 + circuit_type="HardwareEfficient" +) +``` + +You can take a look at your model, by simply calling +```python +print(model) +``` + +Calling the model without any (`None`) values for the `params` and `inputs` argument, will implicitly call the model with the recently (or initial) parameters and `0`s as input. + +In the following we will describe some concepts of this class. +For a more detailled reference on the methods and arguments that are available, please see the [references page](https://cirkiters.github.io/qml-essentials/references/#model). + +## The essentials + +There is much more to this package, than just providing a Fourier model. +You can calculate the [Expressibility](expressibility.md) or [Entangling Capability](entanglement.md) besides the [Coefficients](coefficients.md) which are unique to this kind of QML interpretation. +Also checkout the available [Ansaetze](ansaetze.md) that we provide with this package. + +## Output Shape + +The output shape is determined by the `output_qubit` argument, provided in the instantiation of the model. +When set to -1 all qubits are measured which will result in the shape being of size $n$ by default (depending on the execution type, see below). + +If `force_mean` flag is set when calling the model, the output is averaged to a single value (while keeping the batch dimension). + +## Execution Type + +Our model be simulated in different ways by setting the `execution_type` property, when calling the model, to: +- `exp_val`: Returns the expectation value between $0$ and $1$ +- `density`: Calculates the density matrix +- `probs`: Simulates the model with the number of shots, set by `model.shots` + +## Noise + +Noise can be added to the model by providing a `noise_params` argument, when calling the model, which is a dictionary with following keys +- `BitFlip` +- `PhaseFlip` +- `AmplitudeDamping` +- `PhaseDamping` +- `DepolarizingChannel` +with values between $0$ and $1$. + +This will apply the corresponding noise in each layer with the provided factor. + +## Caching + +To speed up calculation, you can add `cache=True` when calling the model. +The result of the model call will then be stored in a numpy format in a folder `.cache`. +Each result is being identified by a md5 hash that is a representation of the following model properties: +- number of qubits +- number of layers +- ansatz +- data-reuploading flag +- parameters +- noise parameters +- execution type +- inputs +- output qubit(s) \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 91193e0..4c667a3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,4 +1,14 @@ site_name: QML Essentials +nav: + - index.md + - setup.md + - usage.md + - Advanced: + - ansaetze.md + - coefficients.md + - entanglement.md + - expressibility.md + - references.md theme: name: material highlightjs: true diff --git a/poetry.lock b/poetry.lock index 87256cb..e0b42ca 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,6 +11,25 @@ files = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + [[package]] name = "autograd" version = "1.7.0" @@ -139,6 +158,32 @@ files = [ {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, ] +[[package]] +name = "cattrs" +version = "24.1.2" +description = "Composable complex class support for attrs and dataclasses." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cattrs-24.1.2-py3-none-any.whl", hash = "sha256:67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0"}, + {file = "cattrs-24.1.2.tar.gz", hash = "sha256:8028cfe1ff5382df59dd36474a86e02d817b06eaf8af84555441bac915d2ef85"}, +] + +[package.dependencies] +attrs = ">=23.1.0" +exceptiongroup = {version = ">=1.1.1", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.1.0,<4.6.3 || >4.6.3", markers = "python_version < \"3.11\""} + +[package.extras] +bson = ["pymongo (>=4.4.0)"] +cbor2 = ["cbor2 (>=5.4.6)"] +msgpack = ["msgpack (>=1.0.5)"] +msgspec = ["msgspec (>=0.18.5)"] +orjson = ["orjson (>=3.9.2)"] +pyyaml = ["pyyaml (>=6.0)"] +tomlkit = ["tomlkit (>=0.11.8)"] +ujson = ["ujson (>=5.7.0)"] + [[package]] name = "certifi" version = "2024.8.30" @@ -451,6 +496,21 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "fhconfparser" +version = "2024.1" +description = "Provides a config language independent way to read a config file." +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "fhconfparser-2024.1-py3-none-any.whl", hash = "sha256:f6048cb646e69a3422a581bc0102150c2b79fe7ff26b82233e5ef52f72820e3e"}, + {file = "fhconfparser-2024.1.tar.gz", hash = "sha256:de8af019f0071e614d523985e1d93e0fce20a409d1c64dead03b1b665d4b2e4d"}, +] + +[package.dependencies] +attrs = ">=23.2.0,<24" +tomli = ">=2.0.1,<3" + [[package]] name = "filelock" version = "3.16.1" @@ -608,6 +668,48 @@ files = [ docs = ["sphinx (==5.3.0)", "sphinx-rtd-theme (==1.0.0)"] mypy = ["mypy"] +[[package]] +name = "licensecheck" +version = "2024.3" +description = "Output the licenses used by dependencies and check if these are compatible with the project license" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "licensecheck-2024.3-py3-none-any.whl", hash = "sha256:0baef4c1865e0325a35ff25ed12a0c7094035b7dcfbab9a1abfe43d7735adebe"}, + {file = "licensecheck-2024.3.tar.gz", hash = "sha256:e838e1c87a7ede553df376ad35a69d7c4b02676df0fba9dd1c6a6866eb0e0ee5"}, +] + +[package.dependencies] +appdirs = ">=1.4.4,<2" +fhconfparser = ">=2024.1,<2026" +loguru = ">=0.7.2,<2" +markdown = ">=3.6,<4" +packaging = ">=24.0,<25" +requests = ">=2.31.0,<3" +requests-cache = ">=1.2.0,<2" +requirements-parser = ">=0.11.0,<2" +rich = ">=13.7.1,<14" +tomli = ">=2.0.1,<3" +uv = ">=0.3.3,<2" + +[[package]] +name = "loguru" +version = "0.7.2" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = ">=3.5" +files = [ + {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, + {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] + [[package]] name = "markdown" version = "3.7" @@ -640,6 +742,30 @@ markdown = ">=3.0" [package.extras] tests = ["pytest"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "2.1.5" @@ -720,6 +846,17 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mergedeep" version = "1.3.4" @@ -795,13 +932,13 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-material" -version = "9.5.36" +version = "9.5.38" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.36-py3-none-any.whl", hash = "sha256:36734c1fd9404bea74236242ba3359b267fc930c7233b9fd086b0898825d0ac9"}, - {file = "mkdocs_material-9.5.36.tar.gz", hash = "sha256:140456f761320f72b399effc073fa3f8aac744c77b0970797c201cae2f6c967f"}, + {file = "mkdocs_material-9.5.38-py3-none-any.whl", hash = "sha256:d4779051d52ba9f1e7e344b34de95449c7c366c212b388e4a2db9a3db043c228"}, + {file = "mkdocs_material-9.5.38.tar.gz", hash = "sha256:1843c5171ad6b489550aeaf7358e5b7128cc03ddcf0fb4d91d19aa1e691a63b8"}, ] [package.dependencies] @@ -1182,13 +1319,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.10.1" +version = "10.10.2" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.10.1-py3-none-any.whl", hash = "sha256:6c74ea6c2e2285186a241417480fc2d3cc52941b3ec2dced4014c84dc78c5493"}, - {file = "pymdown_extensions-10.10.1.tar.gz", hash = "sha256:ad277ee4739ced051c3b6328d22ce782358a3bec39bc6ca52815ccbf44f7acdc"}, + {file = "pymdown_extensions-10.10.2-py3-none-any.whl", hash = "sha256:513a9e9432b197cf0539356c8f1fc376e0d10b70ad150cadeb649a5628aacd45"}, + {file = "pymdown_extensions-10.10.2.tar.gz", hash = "sha256:65d82324ef2497931bc858c8320540c6264ab0d9a292707edb61f4fe0cd56633"}, ] [package.dependencies] @@ -1445,6 +1582,69 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-cache" +version = "1.2.1" +description = "A persistent cache for python requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests_cache-1.2.1-py3-none-any.whl", hash = "sha256:1285151cddf5331067baa82598afe2d47c7495a1334bfe7a7d329b43e9fd3603"}, + {file = "requests_cache-1.2.1.tar.gz", hash = "sha256:68abc986fdc5b8d0911318fbb5f7c80eebcd4d01bfacc6685ecf8876052511d1"}, +] + +[package.dependencies] +attrs = ">=21.2" +cattrs = ">=22.2" +platformdirs = ">=2.5" +requests = ">=2.22" +url-normalize = ">=1.4" +urllib3 = ">=1.25.5" + +[package.extras] +all = ["boto3 (>=1.15)", "botocore (>=1.18)", "itsdangerous (>=2.0)", "pymongo (>=3)", "pyyaml (>=6.0.1)", "redis (>=3)", "ujson (>=5.4)"] +bson = ["bson (>=0.5)"] +docs = ["furo (>=2023.3,<2024.0)", "linkify-it-py (>=2.0,<3.0)", "myst-parser (>=1.0,<2.0)", "sphinx (>=5.0.2,<6.0.0)", "sphinx-autodoc-typehints (>=1.19)", "sphinx-automodapi (>=0.14)", "sphinx-copybutton (>=0.5)", "sphinx-design (>=0.2)", "sphinx-notfound-page (>=0.8)", "sphinxcontrib-apidoc (>=0.3)", "sphinxext-opengraph (>=0.9)"] +dynamodb = ["boto3 (>=1.15)", "botocore (>=1.18)"] +json = ["ujson (>=5.4)"] +mongodb = ["pymongo (>=3)"] +redis = ["redis (>=3)"] +security = ["itsdangerous (>=2.0)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "requirements-parser" +version = "0.11.0" +description = "This is a small Python module for parsing Pip requirement files." +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "requirements_parser-0.11.0-py3-none-any.whl", hash = "sha256:50379eb50311834386c2568263ae5225d7b9d0867fb55cf4ecc93959de2c2684"}, + {file = "requirements_parser-0.11.0.tar.gz", hash = "sha256:35f36dc969d14830bf459803da84f314dc3d17c802592e9e970f63d0359e5920"}, +] + +[package.dependencies] +packaging = ">=23.2" +types-setuptools = ">=69.1.0" + +[[package]] +name = "rich" +version = "13.8.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"}, + {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "rustworkx" version = "0.15.1" @@ -1577,6 +1777,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "types-setuptools" +version = "75.1.0.20240917" +description = "Typing stubs for setuptools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-setuptools-75.1.0.20240917.tar.gz", hash = "sha256:12f12a165e7ed383f31def705e5c0fa1c26215dd466b0af34bd042f7d5331f55"}, + {file = "types_setuptools-75.1.0.20240917-py3-none-any.whl", hash = "sha256:06f78307e68d1bbde6938072c57b81cf8a99bc84bd6dc7e4c5014730b097dc0c"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -1588,6 +1799,20 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "url-normalize" +version = "1.4.3" +description = "URL normalization for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"}, + {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, +] + +[package.dependencies] +six = "*" + [[package]] name = "urllib3" version = "2.2.3" @@ -1605,6 +1830,33 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "uv" +version = "0.4.16" +description = "An extremely fast Python package and project manager, written in Rust." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uv-0.4.16-py3-none-linux_armv6l.whl", hash = "sha256:9de9bfd82d5ec1b0180976b1e5db389c7f13e59a2b08037faa93fef474c63517"}, + {file = "uv-0.4.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d501b14f491057c102e2f6be92e5a1da973453b893fd727a552908fe8a8a1061"}, + {file = "uv-0.4.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:97529f45c0720cafa6870ae3d9a43449c34f6c762505249dcd033ca6d7b121ec"}, + {file = "uv-0.4.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c390d0887e0bc918d96660460a89101368af28815c40ea26795ab801651d128e"}, + {file = "uv-0.4.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c54b1725836e5a84168f705a395e21353bdbb2d47e77d645cb0622a77defcf04"}, + {file = "uv-0.4.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43c7339114431565679f42d3c85b4c7ba5dfdf1d9ad5f89682c1177828161602"}, + {file = "uv-0.4.16-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5ee1c25c8296d932fa2f0629ad6d1b9b04e9f5f0a0f1e90e64d488d13861e533"}, + {file = "uv-0.4.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68390b39b36ddbfe48033f308f4e983879b49ce345de2105e5cf3d3baa22dfea"}, + {file = "uv-0.4.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c92a1a2bf541a3f65b5b2502ca51f8709e8ac8bb85846c87c65d343e66ede622"}, + {file = "uv-0.4.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:050715938e78c6d69d9bdd6a9bd536c92c9f516ac0ca252726c546e8dc7af30d"}, + {file = "uv-0.4.16-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:29fdf36b2e4de02e676bb2ae3ca25bccb97d457f8bbb5c5a58fc4f223df1e235"}, + {file = "uv-0.4.16-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:8147b2998bf9eb743d872de3e469bbe71622126be54ca377bfc0028042bfdad2"}, + {file = "uv-0.4.16-py3-none-musllinux_1_1_i686.whl", hash = "sha256:87505d25163f6fe0afd85c7952ab66593aa1ecc77a41f65e910760e90bd53b4f"}, + {file = "uv-0.4.16-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:2a566febc7cbe76e42ad83352c28dd2fe64290e6809f1dfd07f3f158ea5cc68d"}, + {file = "uv-0.4.16-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d1712f1c0df309f7682d7e40783ab55927cc1e7108e43847b2a0b795ea855c45"}, + {file = "uv-0.4.16-py3-none-win32.whl", hash = "sha256:1497dbb3a1b41c6c407e0dc7c6b40ca012796b3f9370f0dcbe4edf4dc098a2ec"}, + {file = "uv-0.4.16-py3-none-win_amd64.whl", hash = "sha256:136f4b1f8d3a6f2e7f87d009cc4b75be1e52b8b9837ee97600fdd3b2db960a53"}, + {file = "uv-0.4.16.tar.gz", hash = "sha256:2144995a87b161d063bd4ef8294b1e948677bd90d01f8394d0e3fca037bb847f"}, +] + [[package]] name = "virtualenv" version = "20.26.5" @@ -1667,6 +1919,20 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +optional = false +python-versions = ">=3.5" +files = [ + {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, + {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, +] + +[package.extras] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] + [[package]] name = "zipp" version = "3.20.2" @@ -1689,4 +1955,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.12" -content-hash = "3f3867376fff1247cddd9aa3b622b07b778848e6a14cbdc965809c054a6a75df" +content-hash = "c4e66eb9170b5fef8c286be0c8b510ce12cb4f0171087b355fe1e318ccfe28de" diff --git a/pyproject.toml b/pyproject.toml index 8dc11d8..75dbb23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ pytest = "^8.2.2" flake8 = "^7.1.1" pre-commit = "^3.8.0" coverage = "^7.6.1" +licensecheck = "^2024.3" [tool.poetry.group.docs] optional = true @@ -31,4 +32,11 @@ markdown-include = "^0.8.1" [build-system] requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" \ No newline at end of file +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +markers = [ + "expensive: Tests that take a long time to run", + "unittest: Tests with an assertion to check the output of a particular uni", + "smoketest: Tests that just run a method and check if it not fails", +] \ No newline at end of file diff --git a/tests/test_coefficients.py b/tests/test_coefficients.py index 1801b2b..22bf3d6 100644 --- a/tests/test_coefficients.py +++ b/tests/test_coefficients.py @@ -3,10 +3,12 @@ import numpy as np import logging +import pytest logger = logging.getLogger(__name__) +@pytest.mark.unittest def test_coefficients() -> None: test_cases = [ { @@ -54,7 +56,3 @@ def test_coefficients() -> None: assert np.isclose( np.sum(coeffs).imag, 0.0, rtol=1.0e-5 ), "Imaginary part is not zero" - - -if __name__ == "__main__": - test_coefficients() diff --git a/tests/test_entanglement.py b/tests/test_entanglement.py index f4242e6..8fd4752 100644 --- a/tests/test_entanglement.py +++ b/tests/test_entanglement.py @@ -3,10 +3,12 @@ import logging import math +import pytest logger = logging.getLogger(__name__) +@pytest.mark.unittest def test_entanglement() -> None: test_cases = [ { @@ -43,7 +45,3 @@ def test_entanglement() -> None: ), f"Entangling capacity is not {test_case['result']}\ for circuit ansatz {test_case['circuit_type']}.\ Was {ent_cap} instead" - - -if __name__ == "__main__": - test_entanglement() diff --git a/tests/test_expressiblity.py b/tests/test_expressiblity.py index 5072300..9722cd8 100644 --- a/tests/test_expressiblity.py +++ b/tests/test_expressiblity.py @@ -3,10 +3,12 @@ import logging import math +import pytest logger = logging.getLogger(__name__) +@pytest.mark.unittest def test_divergence() -> None: test_cases = [ { @@ -38,6 +40,8 @@ def test_divergence() -> None: ), "Distance between two identical haar measures not equal." +@pytest.mark.unittest +@pytest.mark.expensive def test_expressibility() -> None: test_cases = [ { @@ -93,8 +97,3 @@ def test_expressibility() -> None: ), f"Expressibility is not {test_case['result']}\ for circuit ansatz {test_case['circuit_type']}.\ Was {kl_dist} instead" - - -if __name__ == "__main__": - test_divergence() - test_expressibility() diff --git a/tests/test_model.py b/tests/test_model.py index 50573a3..5d2636f 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -12,6 +12,7 @@ logger = logging.getLogger(__name__) +@pytest.mark.unittest def test_parameters() -> None: test_cases = [ { @@ -114,6 +115,7 @@ def test_parameters() -> None: str(model) +@pytest.mark.unittest def test_cache() -> None: # Stupid try removing caches try: @@ -164,6 +166,7 @@ def test_cache() -> None: ), "Cached result and calcualted result is not equal." +@pytest.mark.smoketest def test_initialization() -> None: test_cases = [ { @@ -203,6 +206,7 @@ def test_initialization() -> None: ) +@pytest.mark.smoketest def test_ansaetze() -> None: ansatz_cases = Ansaetze.get_available() @@ -231,6 +235,7 @@ def test_ansaetze() -> None: ) +@pytest.mark.unittest def test_available_ansaetze() -> None: ansatze = set(Ansaetze.get_available()) @@ -241,6 +246,7 @@ def test_available_ansaetze() -> None: assert actual_ansaetze == ansatze +@pytest.mark.unittest def test_multi_input() -> None: input_cases = [ np.random.rand(1), @@ -290,6 +296,7 @@ def test_multi_input() -> None: assert len(out.shape) == 0, "expected one elemental output for empty input" +@pytest.mark.smoketest def test_dru() -> None: dru_cases = [False, True] @@ -313,6 +320,7 @@ def test_dru() -> None: ) +@pytest.mark.unittest def test_local_state() -> None: test_cases = [ { @@ -394,6 +402,7 @@ def test_local_state() -> None: assert model.execution_type == test_case["execution_type"] +@pytest.mark.unittest def test_local_and_global_meas() -> None: test_cases = [ { @@ -512,6 +521,7 @@ def test_local_and_global_meas() -> None: for test case {test_case}" +@pytest.mark.unittest def test_parity() -> None: model_a = Model( n_qubits=2, @@ -534,16 +544,3 @@ def test_parity() -> None: assert not np.allclose( result_a, result_b ), f"Models should be different! Got {result_a} and {result_b}" - - -if __name__ == "__main__": - test_parameters() - test_cache() - test_initialization() - test_ansaetze() - test_available_ansaetze() - test_multi_input() - test_dru() - test_local_state() - test_local_and_global_meas() - test_parity()