diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..cda0ced --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,29 @@ +* **I'm submitting a ...** + - [ ] bug report + - [ ] feature request + + +* **What is the current behavior?** + + + +* **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem** + + + +* **What is the expected behavior?** + + + +* **What is the motivation / use case for changing the behavior?** + + + +* **Please tell us about your environment:** + + - Version: + - Platform: + - Subsystem: + + +* **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc) \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4aa196e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +* **Please check if the PR fulfills these requirements** +- [ ] The commit message follows our guidelines +- [ ] Tests for the changes have been added (for bug fixes / features) +- [ ] Docs have been added / updated (for bug fixes / features) + + +* **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) + + + +* **What is the current behavior?** (You can also link to an open issue here) + + + +* **What is the new behavior (if this is a feature change)?** + + + +* **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?) + + + +* **Other information**: diff --git a/.github/workflows/check_code_format.yml b/.github/workflows/check_code_format.yml index 0e0da1f..3025518 100644 --- a/.github/workflows/check_code_format.yml +++ b/.github/workflows/check_code_format.yml @@ -1,15 +1,13 @@ -name: Check Code Format +name: Ruff Static Code Analysis on: pull_request jobs: - lint: + ruff_static_code_analysis: + name: Ruff Static Code Analysis runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: psf/black@stable + - uses: astral-sh/ruff-action@v2 with: - options: "--check --verbose" src: "./src" - jupyter: true - version: "~= 24.0" diff --git a/.github/workflows/check_copyright.yml b/.github/workflows/check_copyright.yml new file mode 100644 index 0000000..9ed4c48 --- /dev/null +++ b/.github/workflows/check_copyright.yml @@ -0,0 +1,15 @@ +name: Copyright Header Check + +on: pull_request + +jobs: + copyright_header_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check license & copyright headers + uses: viperproject/check-license-header@v2 + with: + path: + config: .github/workflows/check_copyright_config.json + # strict: true \ No newline at end of file diff --git a/.github/workflows/check_copyright_config.json b/.github/workflows/check_copyright_config.json new file mode 100644 index 0000000..0c6a9d3 --- /dev/null +++ b/.github/workflows/check_copyright_config.json @@ -0,0 +1,13 @@ +[ + { + "include": [ + "src/**", + "tests/**" + ], + "exclude": [ + "docs/**", + "**/*.ini" + ], + "license": ".github/workflows/copyright.txt" + } +] diff --git a/.github/workflows/check_mkdocs_build.yml b/.github/workflows/check_mkdocs_build.yml new file mode 100644 index 0000000..7760f86 --- /dev/null +++ b/.github/workflows/check_mkdocs_build.yml @@ -0,0 +1,24 @@ +name: Check Mkdocs Build + +on: pull_request + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.12 + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v3 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - name: Install uv + uses: astral-sh/setup-uv@v4 + # - run: cp -r examples/ docs/examples/ + - run: uv pip install .[docs] --system + - run: mkdocs build \ No newline at end of file diff --git a/.github/workflows/copyright.txt b/.github/workflows/copyright.txt new file mode 100644 index 0000000..67eb334 --- /dev/null +++ b/.github/workflows/copyright.txt @@ -0,0 +1,13 @@ +# Copyright 2024-2025 Open Quantum Design + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 7047851..d141ebf 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,6 +1,3 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - name: CI on: @@ -8,6 +5,7 @@ on: branches: ["main"] pull_request: branches: ["main"] + workflow_dispatch: permissions: contents: read @@ -21,12 +19,13 @@ jobs: - "3.10" - "3.11" - "3.12" - os: - - ubuntu-latest + - "3.13" + + os: [ubuntu-latest, windows-latest, macos-latest] arch: - x64 name: Python ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} - runs-on: ${{ matrix.os }} + steps: - uses: actions/checkout@v4 - name: Set up Python @@ -34,10 +33,14 @@ jobs: with: python-version: ${{ matrix.version }} cache: "pip" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install .[server] - - name: Run unittest - run: | - python -m unittest discover tests -v + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: Install the project + run: uv pip install .[tests] + + - name: Run tests + run: uv run pytest tests diff --git a/.gitignore b/.gitignore index 32b95e4..fafbe0d 100644 --- a/.gitignore +++ b/.gitignore @@ -163,4 +163,6 @@ cython_debug/ *.sublime-* *.code-workspace -.github/workflows/_*.yml \ No newline at end of file +.github/workflows/_*.yml + +uv.lock \ No newline at end of file diff --git a/README.md b/README.md index 8ae380d..29ceeef 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ Open Quantum Design: Cloud -[![doc](https://img.shields.io/badge/documentation-lightblue)](https://docs.openquantumdesign.org/open-quantum-design-cloud) +[![docs](https://img.shields.io/badge/documentation-lightblue)](https://docs.openquantumdesign.org/open-quantum-design-cloud) [![PyPI Version](https://img.shields.io/pypi/v/oqd-cloud)](https://pypi.org/project/oqd-cloud) [![CI](https://github.com/OpenQuantumDesign/oqd-cloud/actions/workflows/pytest.yml/badge.svg)](https://github.com/OpenQuantumDesign/oqd-cloud/actions/workflows/pytest.yml) ![versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue) -[![black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)](https://opensource.org/licenses/Apache-2.0) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) diff --git a/mkdocs.yaml b/mkdocs.yaml index 6f715c6..9ba3209 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -85,8 +85,6 @@ plugins: import: - https://docs.python.org/3/objects.inv - https://docs.pydantic.dev/latest/objects.inv - - mkdocs-jupyter: - ignore_h1_titles: True markdown_extensions: - attr_list diff --git a/pyproject.toml b/pyproject.toml index 3e7b529..7518e11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,10 +29,7 @@ classifiers = [ dependencies = [ "requests", "pydantic>=2.4", - "numpy~=1.0", - "oqd-compiler-infrastructure", - "oqd-core", - "oqd-analog-emulator" + "oqd-core@git+https://github.com/openquantumdesign/oqd-core", ] [project.optional-dependencies] @@ -41,22 +38,21 @@ docs = [ "mkdocstrings", "mkdocs-material", "mkdocstrings-python", + "mdx_truly_sane_lists" ] -test = ["unittest_prettify"] +tests = ["pytest"] server = [ - "pydantic>=2.4", - "qutip~=5.0.1", "asyncpg", "uvicorn", "python-jose", "passlib", "python-multipart", # - "oqd-compiler-infrastructure", - "oqd-core", - "oqd-analog-emulator", + "oqd-compiler-infrastructure@git+https://github.com/openquantumdesign/oqd-compiler-infrastructure", + "oqd-core@git+https://github.com/openquantumdesign/oqd-core", + "oqd-compiler-infrastructure@git+https://github.com/openquantumdesign/oqd-analog-emulator", ] diff --git a/src/oqd_cloud/__init__.py b/src/oqd_cloud/__init__.py index c5207be..084ae79 100644 --- a/src/oqd_cloud/__init__.py +++ b/src/oqd_cloud/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/oqd_cloud/client.py b/src/oqd_cloud/client.py index c590d7f..7a2467e 100644 --- a/src/oqd_cloud/client.py +++ b/src/oqd_cloud/client.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ from typing import Literal, Optional -from pydantic import BaseModel, ConfigDict -import requests +import requests from oqd_core.backend.task import Task -from oqd_cloud.provider import Provider +from pydantic import BaseModel, ConfigDict +from oqd_cloud.provider import Provider __all__ = ["Job", "Client"] diff --git a/src/oqd_cloud/provider.py b/src/oqd_cloud/provider.py index c9afb26..8bc583e 100644 --- a/src/oqd_cloud/provider.py +++ b/src/oqd_cloud/provider.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/oqd_cloud/server/__init__.py b/src/oqd_cloud/server/__init__.py index c5207be..084ae79 100644 --- a/src/oqd_cloud/server/__init__.py +++ b/src/oqd_cloud/server/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/oqd_cloud/server/app.py b/src/oqd_cloud/server/app.py index 6e8949a..5acef3a 100644 --- a/src/oqd_cloud/server/app.py +++ b/src/oqd_cloud/server/app.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from fastapi import FastAPI from contextlib import asynccontextmanager -######################################################################################## +from fastapi import FastAPI -from oqd_cloud.server.database import engine, Base -from oqd_cloud.server.route import user_router, auth_router, job_router +######################################################################################## +from oqd_cloud.server.database import Base, engine +from oqd_cloud.server.route import auth_router, job_router, user_router ######################################################################################## diff --git a/src/oqd_cloud/server/database.py b/src/oqd_cloud/server/database.py index fbb2e93..751e819 100644 --- a/src/oqd_cloud/server/database.py +++ b/src/oqd_cloud/server/database.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,16 +13,15 @@ # limitations under the License. import os -from typing import Annotated, Optional, List from datetime import datetime +from typing import Annotated, List, Optional from uuid import uuid4 -from fastapi import Depends +from dotenv import load_dotenv +from fastapi import Depends from sqlalchemy import ForeignKey +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship -from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession - -from dotenv import load_dotenv load_dotenv() diff --git a/src/oqd_cloud/server/jobqueue.py b/src/oqd_cloud/server/jobqueue.py index f8e82d1..a0a5398 100644 --- a/src/oqd_cloud/server/jobqueue.py +++ b/src/oqd_cloud/server/jobqueue.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,20 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import asyncio import os +from contextlib import asynccontextmanager from redis import Redis from rq import Queue - from sqlalchemy import select -import asyncio - -from contextlib import asynccontextmanager - ######################################################################################## - -from oqd_cloud.server.database import get_db, JobInDB +from oqd_cloud.server.database import JobInDB, get_db ######################################################################################## diff --git a/src/oqd_cloud/server/main.py b/src/oqd_cloud/server/main.py index b4c8f61..29465e2 100644 --- a/src/oqd_cloud/server/main.py +++ b/src/oqd_cloud/server/main.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/oqd_cloud/server/model.py b/src/oqd_cloud/server/model.py index 628a7a3..9a2212d 100644 --- a/src/oqd_cloud/server/model.py +++ b/src/oqd_cloud/server/model.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/oqd_cloud/server/route/__init__.py b/src/oqd_cloud/server/route/__init__.py index bb60b31..95db880 100644 --- a/src/oqd_cloud/server/route/__init__.py +++ b/src/oqd_cloud/server/route/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .user import user_router from .auth import auth_router from .job import job_router +from .user import user_router + +__all__ = ["user_router", "auth_router", "job_router"] diff --git a/src/oqd_cloud/server/route/auth.py b/src/oqd_cloud/server/route/auth.py index 86bb418..a3c8f33 100644 --- a/src/oqd_cloud/server/route/auth.py +++ b/src/oqd_cloud/server/route/auth.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,17 +18,14 @@ from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm - from jose import JWTError, jwt - from passlib.context import CryptContext - from sqlalchemy import select -######################################################################################## +from oqd_cloud.server.database import UserInDB, db_dependency +######################################################################################## from oqd_cloud.server.model import Token, User -from oqd_cloud.server.database import UserInDB, db_dependency ######################################################################################## @@ -57,7 +54,7 @@ async def current_user(token: Annotated[str, Depends(oauth2_scheme)]): payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM]) username = payload.get("sub") user_id = payload.get("id") - if not username is None and not user_id is None: + if username is not None and user_id is not None: return User(username=username, user_id=user_id) raise JWTError diff --git a/src/oqd_cloud/server/route/job.py b/src/oqd_cloud/server/route/job.py index 740d54c..dd6175e 100644 --- a/src/oqd_cloud/server/route/job.py +++ b/src/oqd_cloud/server/route/job.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,28 +17,23 @@ from fastapi import APIRouter, HTTPException from fastapi import status as http_status -from rq.job import Callback -from rq.job import Job as RQJob - -from sqlalchemy import select - ######################################################################################## - from oqd_analog_emulator.qutip_backend import QutipBackend - -from oqd_core.interface.analog import * from oqd_core.backend.task import Task +from rq.job import Callback +from rq.job import Job as RQJob +from sqlalchemy import select -from oqd_cloud.server.route.auth import user_dependency -from oqd_cloud.server.database import db_dependency, JobInDB +from oqd_cloud.server.database import JobInDB, db_dependency from oqd_cloud.server.jobqueue import ( - redis_client, queue, - report_success, + redis_client, report_failure, report_stopped, + report_success, ) from oqd_cloud.server.model import Job +from oqd_cloud.server.route.auth import user_dependency ######################################################################################## @@ -66,7 +61,7 @@ async def submit_job( if backend == "analog-qutip": try: expt, args = backends[backend].compile(task=task) - except Exception as e: + except Exception: raise Exception("Cannot properly compile to the QutipBackend.") job = queue.enqueue( diff --git a/src/oqd_cloud/server/route/user.py b/src/oqd_cloud/server/route/user.py index 8f2bda2..c752503 100644 --- a/src/oqd_cloud/server/route/user.py +++ b/src/oqd_cloud/server/route/user.py @@ -1,4 +1,4 @@ -# Copyright 2024 Open Quantum Design +# Copyright 2024-2025 Open Quantum Design # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,17 +15,17 @@ from fastapi import APIRouter, HTTPException from fastapi import status as http_status -from rq.job import Job - +# from rq.job import Job from sqlalchemy import select -######################################################################################## - -from oqd_cloud.server.route.auth import user_dependency, pwd_context -from oqd_cloud.server.model import UserRegistrationForm # , Job -from oqd_cloud.server.database import UserInDB, JobInDB, db_dependency +from oqd_cloud.server.database import JobInDB, UserInDB, db_dependency +from oqd_cloud.server.model import ( + Job, # todo: proper import + UserRegistrationForm, # , Job +) -from oqd_cloud.server.model import Job # todo: proper import +######################################################################################## +from oqd_cloud.server.route.auth import pwd_context, user_dependency ######################################################################################## diff --git a/tests/test_client.py b/tests/test_client.py index ee05002..1312220 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,52 +1,50 @@ -# Copyright 2024 Open Quantum Design -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#%% -import numpy as np -import matplotlib.pyplot as plt -import seaborn as sns +# Copyright 2024-2025 Open Quantum Design -#%% -from oqd_core.interface.analog.operator import * -from oqd_core.interface.analog.operation import * -from oqd_core.backend.metric import * -from oqd_core.backend.task import Task, TaskArgsAnalog +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# %% +import matplotlib.pyplot as plt +import numpy as np +import seaborn as sns from oqd_analog_emulator.qutip_backend import QutipBackend +from oqd_core.backend.metric import Expectation +from oqd_core.backend.task import Task, TaskArgsAnalog +from oqd_core.interface.analog.operation import AnalogCircuit, AnalogGate +from oqd_core.interface.analog.operator import PauliX, PauliZ from oqd_cloud.client import Client from oqd_cloud.provider import Provider -#%% +# %% X = PauliX() Z = PauliZ() Z.model_validate_json(Z.model_dump_json()) -Hx = AnalogGate(hamiltonian=(2*X+Z)) +Hx = AnalogGate(hamiltonian=(2 * X + Z)) -#%% -op = (2*X+Z) +# %% +op = 2 * X + Z print(op.model_dump_json()) -#%% +# %% print(Hx) print(Hx.model_dump_json()) print(AnalogGate.model_validate_json(Hx.model_dump_json())) -#%% +# %% circuit = AnalogCircuit() circuit.evolve(duration=10, gate=Hx) @@ -54,10 +52,10 @@ print(circuit.model_dump_json()) print(AnalogCircuit.model_validate_json(circuit.model_dump_json())) -#%% +# %% circuit.model_json_schema() -#%% +# %% # define task args args = TaskArgsAnalog( n_shots=100, @@ -71,14 +69,14 @@ task = Task(program=circuit, args=args) task.model_dump_json() -#%% +# %% backend = QutipBackend() expt, args = backend.compile(task=task) # results = backend.run(experiment=expt, args=args) -a = {'experiment': expt, 'args': args} +a = {"experiment": expt, "args": args} results = backend.run(task=task) -#%% +# %% fig, ax = plt.subplots(1, 1, figsize=[6, 3]) colors = sns.color_palette(palette="crest", n_colors=4) @@ -87,7 +85,7 @@ ax.legend() # plt.show() -#%% +# %% fig, axs = plt.subplots(4, 1, sharex=True, figsize=[5, 9]) state = np.array([basis.real + 1j * basis.imag for basis in results.state]) @@ -113,19 +111,15 @@ # plt.show() -#%% +# %% client = Client() provider = Provider() -client.connect( - provider=provider, - username="ben", - password="pwd" -) +client.connect(provider=provider, username="ben", password="pwd") client.status_report -#%% +# %% print(client.jobs) job = client.submit_job(task=task, backend="analog-qutip") -#%% -client.retrieve_job(job_id=job.job_id) \ No newline at end of file +# %% +client.retrieve_job(job_id=job.job_id)