Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.git/
.venv/
dist/
__pycache__/
*.pyc
.pytest_cache/
.ruff_cache/
.coverage
htmlcov/
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
version: 2
updates:
- package-ecosystem: pip
- package-ecosystem: uv
directory: "/"
schedule:
interval: weekly
Expand Down
49 changes: 18 additions & 31 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,41 @@ name: build and unit tests

on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
branches: ["main"]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13"]

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
version: "0.10.9"
python-version: "3.13"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing we lose by doing this is the matrix build (testing both 3.12 and 3.13). I'm not sure I'm ok with losing that.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can convert it to 3.12, and keep this.


- name: setup virtual env
run: python3 -m venv .venv
- name: Install dependencies
run: uv sync --all-groups --frozen

- name: setup packages
- name: Audit dependencies
run: |
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
uv export --frozen --no-dev --no-emit-project --no-hashes \
--format requirements-txt --output-file requirements.audit.txt
uv run pip-audit -r requirements.audit.txt

- name: audit dependencies
run: |
. .venv/bin/activate
pip-audit -r requirements.txt
- name: Format check (black)
run: uv run black --check .

- name: format check
run: |
. .venv/bin/activate
black --check . --exclude .venv
- name: Lint (ruff)
run: uv run ruff check .

- name: lint
run: |
. .venv/bin/activate
pylint --ignore=.venv --fail-under=9.75 *.py app/

- name: algorithm tests
run: |
. .venv/bin/activate
pytest -v --cov=. --cov-fail-under=85
- name: Unit tests
run: uv run pytest -v --cov=quantumsolver --cov-fail-under=85
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ quantumsolver.sln
.coverage
.pytest_cache/
htmlcov/
.ruff_cache/

# build artifacts / generated files
dist/
requirements.audit.txt

# editor/OS artifacts
.DS_Store
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
19 changes: 13 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
FROM python:3.12-slim
# ---- build stage: produce a wheel with uv's build backend ----
FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim AS build

WORKDIR /src
COPY . .
RUN uv build --wheel --out-dir /dist

# ---- runtime stage: install the wheel that was just built ----
FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim

RUN groupadd --system quantumsolver \
&& useradd --system --gid quantumsolver --no-create-home quantumsolver

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --from=build /dist/*.whl /tmp/
# Installs the quantumsolver package plus its runtime deps (Flask, qiskit, gunicorn, ...).
RUN uv pip install --system --no-cache /tmp/*.whl && rm /tmp/*.whl

COPY --chown=quantumsolver:quantumsolver app/ app/
COPY --chown=quantumsolver:quantumsolver gunicorn.conf.py .
COPY --chown=quantumsolver:quantumsolver bernstein_vazirani_quantum.py bits.py deutsch_jozsa_quantum.py deutsch_quantum.py sat_quantum.py shors_quantum.py .

# gunicorn.conf.py binds a unix socket here in addition to TCP 27100
RUN mkdir -p /run/quantumsolver && chown quantumsolver:quantumsolver /run/quantumsolver
Expand All @@ -19,4 +26,4 @@ USER quantumsolver

EXPOSE 27100

CMD ["gunicorn", "--config", "gunicorn.conf.py", "app:app"]
CMD ["gunicorn", "--config", "gunicorn.conf.py", "quantumsolver.app:app"]
62 changes: 21 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,50 @@
This is a flask application that will integrates with [Redux](https://github.com/ReduxISU/Redux) to provide qiskit based solvers
for problems.

**Python 3.10–3.13 is required.**
**Python 3.13 is required.** Dependencies and the environment are managed with
[uv](https://docs.astral.sh/uv/).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so why are we forcing python 3.13? It is what we ship in the docker container, true.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessarily forcing 3.13, but i can convert it to 3.12. But yes, everything is shipping with all of the dependencies. One of the nice things about UV is that you don't have to have python installed, UV installs whatever version of python you need to run the project.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessarily forcing 3.13, but i can convert it to 3.12. But yes, everything is shipping with all of the dependencies. One of the nice things about UV is that you don't have to have python installed, UV installs whatever version of python you need to run the project.

Making it 3.13 isn't the point. The point it that I used to be able to say that it was tested with two versions (3.12 and 3.13) and that's not true any more. By removing the matrix tests, it's forced down to a single version. Ultimately, we're going to -ship- one version in the docker to ghcr, but I can't say anything about compatibility with other python versions unless they are tested.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, I’ll add the matrix tests back in this evening


## Getting Started
To run this, create a python virtual environment and "enter" it

```
cd the-location-of-this-README.md
python3 -m venv .venv
```
Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then sync the
environment (uv installs Python 3.13 automatically if it isn't already present):

For Windows:

```
.venv\scripts\Activate.bat
```

For anything else:

```
. .venv/bin/activate
cd the-location-of-this-README.md
uv sync
```

Then install the requirements:
This creates `.venv/`, installs all runtime + dev dependencies, and installs the
`quantumsolver` package itself.

```
pip install -r requirements.txt
```

For development (adds pytest, pylint, pip-audit):
Run the development server. Redux expects the solver on port **27100**:

```
pip install -r requirements-dev.txt
uv run flask --app quantumsolver.app run --port 27100
```

Tell it which app we want:

For Windows:
Or run it under gunicorn (the production WSGI server), binding TCP only:

```
set FLASK_APP=quantumsolver.py
uv run gunicorn --bind '[::]:27100' quantumsolver.app:app
```

For anything else:
```
export FLASK_APP=quantumsolver.py
```
> The committed `gunicorn.conf.py` additionally binds a unix socket at
> `/run/quantumsolver/gunicorn.sock` for nginx. That path is provisioned by systemd in
> production (see `deploy/`), so pass `--config gunicorn.conf.py` locally only if you first
> create it: `sudo mkdir -p /run/quantumsolver && sudo chown "$USER" /run/quantumsolver`.

Now, run it!
## Running the Tests

```
$ flask run
* Serving Flask app 'quantumsolver.py'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
uv run pytest -v
```

## Running the Tests

With the virtual environment active:
Lint (ruff) and format check (black):

```
pytest -v
uv run ruff check .
uv run black --check .
```

## API Endpoints
Expand Down
8 changes: 0 additions & 8 deletions app/__init__.py

This file was deleted.

9 changes: 3 additions & 6 deletions deploy/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ set -euo pipefail

REF="${1:-main}"
DEPLOY_DIR="/home/redux/quantumsolver"
VENV="$DEPLOY_DIR/.venv"
SERVICE="quantumsolver.service"

cd "$DEPLOY_DIR"
Expand All @@ -16,11 +15,9 @@ echo "==> Resetting to ${REF}..."
git reset --hard "origin/${REF}" 2>/dev/null || git reset --hard "${REF}"

echo "==> Syncing dependencies..."
if [ ! -d "$VENV" ]; then
python3 -m venv "$VENV"
"$VENV/bin/pip" install --quiet --upgrade pip
fi
"$VENV/bin/pip" install --quiet --upgrade -r requirements.txt
# Creates/updates .venv from uv.lock (runtime deps only) and installs the
# quantumsolver package. Requires uv on the deploy user's PATH.
uv sync --frozen --no-dev

echo "==> Restarting service..."
sudo /usr/bin/systemctl restart "$SERVICE"
Expand Down
2 changes: 1 addition & 1 deletion deploy/quantumsolver.service
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ RuntimeDirectoryMode=0755

ExecStart=/home/redux/quantumsolver/.venv/bin/gunicorn \
--config /home/redux/quantumsolver/gunicorn.conf.py \
quantumsolver:app
quantumsolver.app:app

Restart=on-failure
RestartSec=5
Expand Down
1 change: 0 additions & 1 deletion gunicorn.conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Gunicorn configuration for quantumsolver."""

# pylint: disable=invalid-name # gunicorn requires lowercase config variable names
import multiprocessing

# Each request blocks for up to 25 s (internal timeout), so sync workers are correct.
Expand Down
43 changes: 40 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,45 @@
[tool.pylint.messages_control]
disable = ["duplicate-code"]
[project]
name = "quantumsolver"
version = "0.1.0"
description = "Qiskit-based quantum solvers for Redux, served as a Flask app."
readme = "README.md"
requires-python = ">=3.13"
license = { file = "LICENSE" }
dependencies = [
"Flask==3.1.3",
"requests==2.34.2",
"qiskit==2.4.1",
"qiskit-aer==0.17.2",
"gunicorn==24.1.1",
]

[dependency-groups]
dev = [
"pytest",
"pytest-cov",
"ruff",
"pip-audit",
"black",
]

[build-system]
requires = ["uv_build>=0.10,<0.11"]
build-backend = "uv_build"

[tool.black]
target-version = ["py313"]

[tool.ruff]
line-length = 88
target-version = "py313"

[tool.ruff.lint]
select = ["E", "F", "I", "W", "B", "UP"]
# black owns line length; don't double-enforce it (it won't wrap comments/strings)
ignore = ["E501"]

[tool.coverage.run]
omit = ["test_*.py", "quantumsolver.py"]
source = ["quantumsolver"]

[tool.coverage.report]
exclude_lines = [
Expand Down
4 changes: 0 additions & 4 deletions quantumsolver.py

This file was deleted.

6 changes: 0 additions & 6 deletions requirements-dev.txt

This file was deleted.

5 changes: 0 additions & 5 deletions requirements.txt

This file was deleted.

1 change: 1 addition & 0 deletions src/quantumsolver/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Qiskit-based quantum solvers for Redux, served as a Flask app."""
8 changes: 8 additions & 0 deletions src/quantumsolver/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""quantum solver flask app initialization"""

from flask import Flask

app = Flask(__name__)

# Imported for its side effect of registering routes on `app`.
from quantumsolver import routes # noqa: E402, F401
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@

import argparse
import json

import requests
from qiskit import QuantumCircuit, qasm2
from qiskit_aer import AerSimulator
from bits import power_of_two_info

from quantumsolver.bits import power_of_two_info


def bv_query(s):
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
"""

import argparse

import requests
from qiskit import QuantumCircuit, qasm2
from qiskit_aer import AerSimulator
from bits import power_of_two_info

from quantumsolver.bits import power_of_two_info


def dj_algorithm(function: QuantumCircuit):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

import argparse

from qiskit import QuantumCircuit, qasm2
from qiskit_aer import AerSimulator

Expand Down
Loading
Loading