Skip to content
Merged
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
44 changes: 17 additions & 27 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: build and unit tests

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

permissions:
contents: read
Expand All @@ -20,36 +20,26 @@ jobs:
- 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:
version: "0.10.9"
python-version: ${{ matrix.python-version }}

- 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"]
61 changes: 24 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,57 @@
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.12+ is required** (CI tests 3.12 and 3.13). Dependencies and the environment are managed
with [uv](https://docs.astral.sh/uv/).

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

```
cd the-location-of-this-README.md
python3 -m venv .venv
```

For Windows:
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):

```
.venv\scripts\Activate.bat
cd the-location-of-this-README.md
uv sync
```

For anything else:

```
. .venv/bin/activate
```
This creates `.venv/`, installs all runtime + dev dependencies, and installs the
`quantumsolver` package itself.

Then install the requirements:
Run the development server. Redux expects the solver on port **27100**:

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

For development (adds pytest, pylint, pip-audit):
Or run it under gunicorn (the production WSGI server), binding TCP only:

```
pip install -r requirements-dev.txt
uv run gunicorn --bind '[::]:27100' quantumsolver.app:app
```

Tell it which app we want:

For Windows:
> 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`.

```
set FLASK_APP=quantumsolver.py
```
## Running the Tests

For anything else:
```
export FLASK_APP=quantumsolver.py
uv run pytest -v
```

Now, run it!
To match CI, run against both supported Python versions:

```
$ 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 --python 3.12 pytest -v
uv run --python 3.13 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
44 changes: 41 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
[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.12"
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 = ["py312", "py313"]

[tool.ruff]
line-length = 88
# minimum supported version — keeps pyupgrade from emitting 3.13-only syntax
target-version = "py312"

[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