From 9866d4f0121f3e5c9b3b830b9bda52af06a46138 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Wed, 9 Aug 2023 13:13:16 +0200 Subject: [PATCH] init --- .github/workflows/ci.yaml | 42 +++++++++ .gitignore | 160 +++++++++++++++++++++++++++++++ LICENSE | 28 ++++++ Makefile | 10 ++ README.md | 25 +++++ pyproject.toml | 8 ++ requirements.txt | 8 ++ usecase/__init__.py | 1 + usecase/use_max.ipynb | 194 ++++++++++++++++++++++++++++++++++++++ usecase/use_max.py | 9 ++ usecase/use_max_test.py | 12 +++ 11 files changed, 497 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 usecase/__init__.py create mode 100644 usecase/use_max.ipynb create mode 100644 usecase/use_max.py create mode 100644 usecase/use_max_test.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..5014a62 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + black: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Check source code formatting + uses: psf/black@stable + + pytest: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11"] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Python project and dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run pytest + id: pytest + run: pytest --doctest-modules diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2dc53ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bfec939 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, QuBRA Benchmarking Project + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b3a96e5 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +ci: format test + +all: format test lint + +format: + black --quiet --check . +test: + pytest --doctest-modules -q +lint: + ruff . diff --git a/README.md b/README.md new file mode 100644 index 0000000..084e98e --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# usecase-template [![CI](../../actions/workflows/ci.yaml/badge.svg?branch=main)](../../actions/workflows/ci.yaml) +An example qubrabench use-case + +## Usage +Add your code in folder `usecase/`. + +Recommended structure: +``` +. +├── requirements.txt # add all dependencies here +└── usecase +    ├── algorithm.py # source code for your implementation +    ├── algorithm_test.py # unittests for above code +    └── algorithm.ipynb # notebook with executions of the algorithm, example usages, plots, visualizations etc. +``` + +## Testing + +If you want to edit the project or run tests, first install all dependencies using: + +```sh +pip install -r requirements.txt +``` + +Then run `pytest` to run all tests. You can alternatively run `make` to run all tests run by the CI. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5dc7f79 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[tool.pytest.ini_options] +markers = [] + +[tool.ruff] +line-length = 100000 # 88 enforced by black, ignore for comments. + +[tool.mypy] +plugins = ["numpy.typing.mypy_plugin"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..58a394b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +qubrabench@git+https://github.com/qubrabench/qubrabench.git#egg=cbe51984534654a4092fb0f839158c4d4447a610 +numpy~=1.24 +matplotlib~=3.7 +black[jupyter] +pytest~=7.3 +ruff~=0.0 +mypy~=1.2 +pandas-stubs~=2.0 diff --git a/usecase/__init__.py b/usecase/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/usecase/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/usecase/use_max.ipynb b/usecase/use_max.ipynb new file mode 100644 index 0000000..0b7165d --- /dev/null +++ b/usecase/use_max.ipynb @@ -0,0 +1,194 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "from dataclasses import asdict\n", + "\n", + "from use_max import max_value_of_function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Finding the maximum of a quadratic using Quantum max-finding\n", + "\n", + "We demonstrate using the qubrabench `max` function by using it to find the maximum of the following quadratic\n", + "$$f(x) = 4 x - x^2$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def quadratic(x: float):\n", + " return 4 * x - x**2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%psource max_value_of_function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Find `x` that maximizes the above quadratic, in range [-10, 10]\n", + "x_best, stats = max_value_of_function(np.linspace(-10, 10, num=1000), quadratic)\n", + "x_best" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can aggregate the costs of the quantum algorithm by combining the classical and quantum query counts with an appropriate \"scaling factor\". In this example, we assume that each quantum query is _twice_ as expensive as a classical one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cq = 2 # multiplier for quantum queries\n", + "classical_cost = stats.classical_actual_queries\n", + "quantum_cost = (\n", + " stats.quantum_expected_classical_queries\n", + " + cq * stats.quantum_expected_quantum_queries\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "classical_cost, quantum_cost" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Benchmarking\n", + "We can now benchmark the max-finding for increasingly large search spaces. We vary the number of samples from $2^4$ till $2^{18}$, in the range $[-10, 10]$.\n", + "\n", + "We collect all this data into a pandas DataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = []\n", + "for pwr in range(4, 19):\n", + " for _ in range(2): # repeat twice\n", + " # search over 2**pwr uniformly distributed values in [-10, 10]\n", + " n_samples = 2**pwr\n", + " x_best, stats = max_value_of_function(np.linspace(-10, 10, num=n_samples), quadratic)\n", + " \n", + " # process stats\n", + " stats = asdict(stats)\n", + " stats[\"n\"] = n_samples\n", + " data.append(stats)\n", + "data = pd.DataFrame([list(row.values()) for row in data], columns=list(data[0].keys()))\n", + "data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qubrabench.utils.plotting_strategy import PlottingStrategy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pdoc PlottingStrategy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Plotter(PlottingStrategy):\n", + " def __init__(self):\n", + " self.colors[\"\"] = \"blue\"\n", + "\n", + " def get_plot_group_column_names(self):\n", + " return []\n", + "\n", + " def get_data_group_column_names(self):\n", + " return []\n", + "\n", + " def compute_aggregates(self, data, *, quantum_factor):\n", + " # compute combined query costs of quantum search\n", + " c = data[\"quantum_expected_classical_queries\"]\n", + " q = data[\"quantum_expected_quantum_queries\"]\n", + " data[\"classical_cost\"] = data[\"classical_actual_queries\"]\n", + " data[\"quantum_cost\"] = c + quantum_factor * q\n", + " return data\n", + "\n", + " def x_axis_column(self):\n", + " return \"n\"\n", + "\n", + " def x_axis_label(self):\n", + " return \"$n$\"\n", + "\n", + " def y_axis_label(self):\n", + " return \"Queries\"\n", + "\n", + " def get_column_names_to_plot(self):\n", + " return {\n", + " \"classical_cost\": (\"Classical Queries\", \"o\"),\n", + " \"quantum_cost\": (\"Quantum Queries\", \"x\"),\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Plotter().plot(data, quantum_factor=2)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python3", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/usecase/use_max.py b/usecase/use_max.py new file mode 100644 index 0000000..d4b982f --- /dev/null +++ b/usecase/use_max.py @@ -0,0 +1,9 @@ +from qubrabench.algorithms.max import max +from qubrabench.stats import QueryStats + + +def max_value_of_function(it, f): + stats = QueryStats() + result = max(it, key=f, error=1e-5, stats=stats) + + return result, stats diff --git a/usecase/use_max_test.py b/usecase/use_max_test.py new file mode 100644 index 0000000..440b032 --- /dev/null +++ b/usecase/use_max_test.py @@ -0,0 +1,12 @@ +from .use_max import max_value_of_function +import numpy as np +import pytest + + +def test_use_max_quadratic(): + def f(x: float): + return 4 * x - x**2 + + domain = np.linspace(-10, 10, num=10000) + result, _ = max_value_of_function(domain, f) + assert result == pytest.approx(2, rel=1e-3)