Skip to content

Commit

Permalink
Add support for Python 3.12, drop support for Python 3.8 (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
wilfried-huss authored Jan 19, 2024
1 parent e8108b1 commit 9585bbe
Show file tree
Hide file tree
Showing 28 changed files with 444 additions and 495 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ jobs:
- name: Install Poetry
uses: abatilo/actions-poetry@v2
with:
poetry-version: '1.6.1'
poetry-version: '1.7.1'
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
python-version: '3.11'
cache: "poetry"
- name: Check Poetry lock file consistency
run: poetry lock --check
run: poetry check --lock
- name: Install dependencies
run: poetry install --sync
- name: Check version numbers consistency
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/poetry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
poetry-version: [1.6.1]
python-version: ['3.9', '3.10', '3.11', '3.12']
poetry-version: [1.7.1]
os: ["ubuntu-latest"]
steps:
- uses: actions/checkout@v3
Expand All @@ -29,19 +29,19 @@ jobs:
- name: Check shell scripts
uses: ludeeus/[email protected]
- name: Check Poetry lock file status
run: poetry lock --check
run: poetry check --lock
- name: Install coverage tool
run: |
poetry run pip install coverage[toml]
- name: Install examples dependencies (Py3.11)
- name: Install examples dependencies (Python >= 3.11)
run: |
poetry install --only main --extras examples
# tweedledum has no wheel for Python3.11 and the build errors
if: matrix.python-version == '3.11'
- name: Install examples dependencies (~Py3.11)
# tweedledum has no wheel for Python >= 3.11 and the build errors
if: matrix.python-version >= '3.11'
- name: Install examples dependencies (Python < 3.11)
run: |
poetry install --only main --all-extras
if: matrix.python-version != '3.11'
if: matrix.python-version < '3.11'
- name: Run examples
run: |
poetry run examples/run_all.sh -c
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ jobs:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
python-version: '3.11'
- name: Install Poetry
uses: abatilo/actions-poetry@v2
with:
poetry-version: '1.6.1'
poetry-version: '1.7.1'
- name: Install release dependencies
run: pip install -U typer mistletoe
- name: Build packages
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Add support for Python 3.12 (#79)
* Remove support for Python 3.8 (#79)

## qiskit-aqt-provider v1.1.0

* Update to `pydantic` v2 (#66)
Expand Down
6 changes: 3 additions & 3 deletions examples/grover-3-sat.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"""
import tempfile
import textwrap
from typing import Final, Set, Tuple
from typing import Final

from qiskit.circuit.library.phase_oracle import PhaseOracle
from qiskit_algorithms import AmplificationProblem, Grover
Expand All @@ -44,7 +44,7 @@ def is_solution(self, bits: str) -> bool:
return bool(self.func.simulate(*args))


def format_bitstring(bits: str) -> Tuple[bool, ...]:
def format_bitstring(bits: str) -> tuple[bool, ...]:
"""Format a bitstring as tuple of boolean values.
Warning: this reverses the bit order.
Expand Down Expand Up @@ -97,7 +97,7 @@ def format_bitstring(bits: str) -> Tuple[bool, ...]:

# Run the Grover search until all solutions are found
MAX_ITERATIONS: Final = 100
solutions: Set[str] = set()
solutions: set[str] = set()
for _ in range(MAX_ITERATIONS):
solutions.add(grover.amplify(problem).assignment)
if len(solutions) == sat_problem.num_solutions:
Expand Down
6 changes: 3 additions & 3 deletions examples/number_partition.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"""

from dataclasses import dataclass
from typing import Final, List, Set, Union
from typing import Final, Union

import qiskit_algorithms
from qiskit_algorithms.minimum_eigensolvers import QAOA
Expand All @@ -38,7 +38,7 @@
class Success:
# type would be better as tuple[set[int], set[int]] but
# NumberPartition.interpret returns list[list[int]].
partition: List[List[int]]
partition: list[list[int]]

def verify(self) -> bool:
a, b = self.partition
Expand All @@ -49,7 +49,7 @@ class Infeasible:
pass


def solve_partition_problem(num_set: Set[int]) -> Union[Success, Infeasible]:
def solve_partition_problem(num_set: set[int]) -> Union[Success, Infeasible]:
"""Solve a partition problem.
Args:
Expand Down
733 changes: 345 additions & 388 deletions poetry.lock

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ classifiers=[
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering",
]
keywords=["qiskit", "sdk", "quantum"]
Expand All @@ -43,14 +43,14 @@ aqt = "qiskit_aqt_provider.transpiler_plugin:AQTTranslationPlugin"
pytest_qiskit_aqt = "qiskit_aqt_provider.test.fixtures"

[tool.poetry.dependencies]
python = ">=3.8,<3.12"
python = ">=3.9,<3.13"

httpx = ">=0.24.0"
platformdirs = ">=3"
pydantic = ">=2.5.0"
python-dotenv = ">=1"
qiskit = ">=0.45.0"
qiskit-aer = ">=0.11"
qiskit = ">=0.45.1"
qiskit-aer = ">=0.13.2"
qiskit-algorithms = { version = ">=0.2.1", optional = true }
qiskit-optimization = { version = ">=0.6.0", optional = true }
tabulate = ">=0.9.0"
Expand Down Expand Up @@ -112,7 +112,7 @@ enum-field-as-literal = "one"
field-constraints = true
output-model-type = "pydantic_v2.BaseModel"
strict-nullable = true
target-python-version = '3.8'
target-python-version = '3.9'
use-annotated = true
use-double-quotes = true
use-field-description = true
Expand All @@ -138,12 +138,12 @@ reportUnnecessaryTypeIgnoreComment = false

reportDuplicateImport = "error"

pythonVersion = "3.8"
pythonVersion = "3.9"
pythonPlatform = "Linux"

[tool.ruff]
line-length = 100
target-version = 'py38'
target-version = 'py39'
extend-exclude = [
"qiskit_aqt_provider/api_models_generated.py", # generated code
]
Expand Down
11 changes: 6 additions & 5 deletions qiskit_aqt_provider/api_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"""Thin convenience wrappers around generated API models."""

import re
from typing import Any, Dict, List, Literal, Optional, Pattern, Union
from re import Pattern
from typing import Any, Literal, Optional, Union
from uuid import UUID

import httpx
Expand Down Expand Up @@ -60,10 +61,10 @@ def http_client(*, base_url: str, token: str) -> httpx.Client:
return httpx.Client(headers=headers, base_url=base_url, timeout=10.0)


class Workspaces(pdt.RootModel[List[api_models.Workspace]]):
class Workspaces(pdt.RootModel[list[api_models.Workspace]]):
"""List of available workspaces and devices."""

root: List[api_models.Workspace]
root: list[api_models.Workspace]

def filter(
self,
Expand Down Expand Up @@ -146,7 +147,7 @@ def r(*, phi: float, theta: float, qubit: int) -> api_models.OperationModel:
)

@staticmethod
def rxx(*, theta: float, qubits: List[int]) -> api_models.OperationModel:
def rxx(*, theta: float, qubits: list[int]) -> api_models.OperationModel:
"""RXX gate."""
return api_models.OperationModel(
root=api_models.GateRXX(
Expand Down Expand Up @@ -229,7 +230,7 @@ def ongoing(

@staticmethod
def finished(
*, job_id: UUID, workspace_id: str, resource_id: str, results: Dict[str, List[List[int]]]
*, job_id: UUID, workspace_id: str, resource_id: str, results: dict[str, list[list[int]]]
) -> JobResponse:
"""Completed job with the given results."""
return api_models.JobResponseRRFinished(
Expand Down
3 changes: 1 addition & 2 deletions qiskit_aqt_provider/api_models_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
from __future__ import annotations

from enum import Enum
from typing import Dict, List, Literal, Optional, Union
from typing import Annotated, Dict, List, Literal, Optional, Union
from uuid import UUID

from pydantic import BaseModel, ConfigDict, Field, RootModel
from typing_extensions import Annotated


class GateR(BaseModel):
Expand Down
20 changes: 8 additions & 12 deletions qiskit_aqt_provider/aqt_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@
TYPE_CHECKING,
Any,
ClassVar,
DefaultDict,
Dict,
List,
NoReturn,
Optional,
Set,
Union,
)

Expand Down Expand Up @@ -52,7 +48,7 @@ class JobFinished:
"""The job finished successfully."""

status: ClassVar = JobStatus.DONE
results: Dict[int, List[List[int]]]
results: dict[int, list[list[int]]]


@dataclass
Expand Down Expand Up @@ -155,7 +151,7 @@ class AQTJob(JobV1):
def __init__(
self,
backend: "AQTResource",
circuits: List[QuantumCircuit],
circuits: list[QuantumCircuit],
options: AQTOptions,
):
"""Initialize an :class:`AQTJob` instance.
Expand Down Expand Up @@ -371,7 +367,7 @@ def callback(
for circuit_index, circuit in enumerate(self.circuits):
samples = self.status_payload.results[circuit_index]
meas_map = _build_memory_mapping(circuit)
data: Dict[str, Any] = {
data: dict[str, Any] = {
"counts": _format_counts(samples, meas_map),
}

Expand Down Expand Up @@ -410,7 +406,7 @@ def callback(
)


def _build_memory_mapping(circuit: QuantumCircuit) -> Dict[int, Set[int]]:
def _build_memory_mapping(circuit: QuantumCircuit) -> dict[int, set[int]]:
"""Scan the circuit for measurement instructions and collect qubit to classical bits mappings.
Qubits can be mapped to multiple classical bits, possibly in different classical registers.
Expand Down Expand Up @@ -478,7 +474,7 @@ def _build_memory_mapping(circuit: QuantumCircuit) -> Dict[int, Set[int]]:
>>> _build_memory_mapping(qc)
{0: {0, 2}, 1: {1}}
"""
qu2cl: DefaultDict[int, Set[int]] = defaultdict(set)
qu2cl: defaultdict[int, set[int]] = defaultdict(set)

for instruction in circuit.data:
if instruction.operation.name == "measure":
Expand All @@ -489,7 +485,7 @@ def _build_memory_mapping(circuit: QuantumCircuit) -> Dict[int, Set[int]]:


def _shot_to_int(
fluorescence_states: List[int], qubit_to_bit: Optional[Dict[int, Set[int]]] = None
fluorescence_states: list[int], qubit_to_bit: Optional[dict[int, set[int]]] = None
) -> int:
"""Format the detected fluorescence states from a single shot as an integer.
Expand Down Expand Up @@ -595,8 +591,8 @@ def _shot_to_int(


def _format_counts(
samples: List[List[int]], qubit_to_bit: Optional[Dict[int, Set[int]]] = None
) -> Dict[str, int]:
samples: list[list[int]], qubit_to_bit: Optional[dict[int, set[int]]] = None
) -> dict[str, int]:
"""Format all shots results from a circuit evaluation.
The returned dictionary is compatible with Qiskit's `ExperimentResultData`
Expand Down
3 changes: 2 additions & 1 deletion qiskit_aqt_provider/aqt_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

from typing import Any, Iterator, Mapping, Optional
from collections.abc import Iterator, Mapping
from typing import Any, Optional

import pydantic as pdt
from typing_extensions import Self, override
Expand Down
17 changes: 7 additions & 10 deletions qiskit_aqt_provider/aqt_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,15 @@
import re
import warnings
from collections import defaultdict
from collections.abc import Sequence
from dataclasses import dataclass
from operator import attrgetter
from pathlib import Path
from re import Pattern
from typing import (
DefaultDict,
Dict,
Final,
List,
Literal,
Optional,
Pattern,
Sequence,
Union,
overload,
)
Expand Down Expand Up @@ -77,7 +74,7 @@ class BackendsTable(Sequence[AQTResource]):
in IPython/Jupyter notebooks.
"""

def __init__(self, backends: List[AQTResource]):
def __init__(self, backends: list[AQTResource]):
self.backends = backends
self.headers = ["Workspace ID", "Resource ID", "Description", "Resource type"]

Expand Down Expand Up @@ -108,16 +105,16 @@ def _repr_html_(self) -> str:
"""HTML table representation (for IPython/Jupyter)."""
return tabulate(self.table(), headers=self.headers, tablefmt="html") # pragma: no cover

def by_workspace(self) -> Dict[str, List[AQTResource]]:
def by_workspace(self) -> dict[str, list[AQTResource]]:
"""Backends grouped by workspace ID."""
data: DefaultDict[str, List[AQTResource]] = defaultdict(list)
data: defaultdict[str, list[AQTResource]] = defaultdict(list)

for backend in self:
data[backend.resource_id.workspace_id].append(backend)

return dict(data)

def table(self) -> List[List[str]]:
def table(self) -> list[list[str]]:
"""Assemble the data for the printable table."""
table = []
for workspace_id, resources in self.by_workspace().items():
Expand Down Expand Up @@ -244,7 +241,7 @@ def backends(
workspace_pattern=workspace,
)

backends: List[AQTResource] = []
backends: list[AQTResource] = []

# add offline simulators in the default workspace
if (not workspace or workspace.match("default")) and (
Expand Down
Loading

0 comments on commit 9585bbe

Please sign in to comment.