diff --git a/pyproject.toml b/pyproject.toml index 82791b37..bbf3e129 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "oqd-core" -version = "0.1.0" +version = "0.2.0dev1" requires-python = ">=3.10" readme = "README.md" license = { text = "Apache 2.0" } @@ -64,10 +64,7 @@ fixable = ["ALL"] oqd-compiler-infrastructure = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure" } [dependency-groups] -dev = [ - "jupyter>=1.1.1", - "pre-commit>=4.1.0", -] +dev = ["jupyter>=1.1.1", "pre-commit>=4.1.0", "ruff>=0.13.1"] [project.urls] diff --git a/src/oqd_core/backend.py b/src/oqd_core/backend.py new file mode 100644 index 00000000..65d6dcf0 --- /dev/null +++ b/src/oqd_core/backend.py @@ -0,0 +1,79 @@ +# 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. + + +import inspect +import warnings +from abc import ABC, abstractmethod +from typing import Any + +######################################################################################## + +__all__ = [ + "MetaBackendRegistry", + "BackendRegistry", + "BackendBase", +] + +######################################################################################## + + +class MetaBackendRegistry(type): + def __new__(cls, clsname, superclasses, attributedict): + attributedict["backends"] = dict() + return super().__new__(cls, clsname, superclasses, attributedict) + + def register(cls, backend): + if not issubclass(backend, BackendBase): + raise TypeError("You may only register subclasses of BackendBase.") + + if backend.__name__ in cls.backends.keys(): + warnings.warn( + f"Overwriting previously registered backend `{backend.__name__}` of the same name.", + UserWarning, + stacklevel=2, + ) + + cls.backends[backend.__name__] = backend + + +class BackendRegistry(metaclass=MetaBackendRegistry): + pass + + +class BackendBase(ABC): + @abstractmethod + def run(self, program, args): + pass + + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + + args = inspect.getfullargspec(cls.run) + + if "program" not in args.annotations: + warnings.warn( + f"Misisng type hint for argument `program` in run method of {cls.__name__}. Defaults to Any." + ) + + cls.run.__annotations__["program"] = Any + + if "args" not in args.annotations: + warnings.warn( + f"Misisng type hint for argument `args` in run method of {cls.__name__}. Defaults to Any." + ) + + cls.run.__annotations__["args"] = Any + + BackendRegistry.register(cls) diff --git a/src/oqd_core/backend/__init__.py b/src/oqd_core/backend/__init__.py deleted file mode 100644 index 084ae799..00000000 --- a/src/oqd_core/backend/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. diff --git a/src/oqd_core/backend/base.py b/src/oqd_core/backend/base.py deleted file mode 100644 index 1637d6d6..00000000 --- a/src/oqd_core/backend/base.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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. - - -from abc import ABC, abstractmethod - -######################################################################################## - - -class BackendBase(ABC): - @abstractmethod - def run(self, task): - pass - - pass diff --git a/src/oqd_core/backend/metric.py b/src/oqd_core/backend/metric.py deleted file mode 100644 index eedcc3ef..00000000 --- a/src/oqd_core/backend/metric.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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. - -from typing import List, Union - -from oqd_compiler_infrastructure import VisitableBaseModel -from pydantic.types import NonNegativeInt - -######################################################################################## -from oqd_core.interface.analog.operator import OperatorSubtypes - -######################################################################################## - -__all__ = [ - "Expectation", - "EntanglementEntropyRenyi", - "EntanglementEntropyVN", -] - -######################################################################################## - - -class Expectation(VisitableBaseModel): - operator: OperatorSubtypes - - -class EntanglementEntropyVN(VisitableBaseModel): - qreg: List[NonNegativeInt] = [] - qmode: List[NonNegativeInt] = [] - - -class EntanglementEntropyRenyi(VisitableBaseModel): - alpha: NonNegativeInt = 1 - qreg: List[NonNegativeInt] = [] - qmode: List[NonNegativeInt] = [] - - -Metric = Union[EntanglementEntropyVN, EntanglementEntropyRenyi, Expectation] diff --git a/src/oqd_core/backend/task.py b/src/oqd_core/backend/task.py deleted file mode 100644 index ac3d59ea..00000000 --- a/src/oqd_core/backend/task.py +++ /dev/null @@ -1,184 +0,0 @@ -# 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. - -from dataclasses import dataclass, field -from typing import Annotated, Dict, List, Literal, Union - -import numpy as np -from oqd_compiler_infrastructure import TypeReflectBaseModel -from pydantic import BaseModel, BeforeValidator - -from oqd_core.backend.metric import Metric - -######################################################################################## -from oqd_core.interface.analog.operation import AnalogCircuit -from oqd_core.interface.atomic.circuit import AtomicCircuit -from oqd_core.interface.digital.circuit import DigitalCircuit - -######################################################################################## - - -class ComplexFloat(TypeReflectBaseModel): - """ - Class representing a complex number - - Attributes: - real (float): real part of the complex number. - imag (float): imaginary part of the complex number. - """ - - real: float - imag: float - - @classmethod - def cast(cls, x): - if isinstance(x, ComplexFloat): - return x - if isinstance(x, complex): - return cls(real=x.real, imag=x.imag) - raise TypeError("Invalid type for argument x") - - def __add__(self, other): - if isinstance(other, ComplexFloat): - self.real += other.real - self.imag += other.imag - return self - - elif isinstance(other, (float, int)): - self.real += other - return self - - def __mul__(self, other): - if isinstance(other, (float, int)): - self.real *= other - self.imag *= other - return self - elif isinstance(other, ComplexFloat): - real = self.real * other.real - self.imag * self.imag - imag = self.real * other.imag + self.imag * self.real - return ComplexFloat(real=real, imag=imag) - else: - raise TypeError - - def __radd__(self, other): - return self + other - - def __rmul__(self, other): - return self * other - - -CastComplexFloat = Annotated[ComplexFloat, BeforeValidator(ComplexFloat.cast)] - -######################################################################################## - - -class TaskArgsBase(TypeReflectBaseModel): - layer: Literal["digital", "analog", "atomic"] - - -class TaskResultsBase(TypeReflectBaseModel): - layer: Literal["digital", "analog", "atomic"] - - -######################################################################################## - - -@dataclass -class DataAnalog: - times: np.array = field(default_factory=lambda: np.empty(0)) - state: np.array = field(default_factory=lambda: np.empty(0)) - metrics: dict[str, List[Union[float, int]]] = field(default_factory=dict) - shots: np.array = field(default_factory=lambda: np.empty(0)) - - -class TaskArgsAnalog(TaskArgsBase): - layer: Literal["analog"] = "analog" - n_shots: Union[int, None] = 10 - fock_cutoff: int = 4 - dt: float = 0.1 - metrics: Dict[str, Metric] = {} - - -class TaskResultAnalog(TaskResultsBase): - """ - Contains the results of a simulation/actual experiment. - - Attributes: - layer (str): the layer of the experiment (analog, atomic) - times (List[float]): number of discrete times - state (List[ComplexFloat]): Final quantum state after evolution - metrics (Dict): metrics which have been computed for the experiment. This does not require any Measure instruction in the analog layer. - counts (Dict[str, int]): counts of different states after the experiment. This requires Measure instruction in the analog layer. - runtime (float): time taken for the simulation/experiment. - """ - - layer: Literal["analog"] = "analog" - times: List[float] = [] - state: List[CastComplexFloat] = [] - metrics: Dict[str, List[Union[float, int]]] = {} - counts: Dict[str, int] = {} - runtime: float = None - - -# todo: uncomment when Digital layer is further developed. -class TaskArgsDigital(BaseModel): - layer: Literal["digital"] = "digital" - repetitions: int = 10 - - -class TaskResultDigital(BaseModel): - layer: Literal["digital"] = "digital" - counts: dict[str, int] = {} - state: List[CastComplexFloat] = [] - - -# - -######################################################################################## - - -# todo: move to TriCal package -class TaskArgsAtomic(BaseModel): - layer: Literal["atomic"] = "atomic" - n_shots: int = 10 - fock_trunc: int = 4 - dt: float = 0.1 - - -class TaskResultAtomic(BaseModel): - layer: Literal["atomic"] = "atomic" - # Hardware results - collated_state_readout: dict[int, int] = {} - state_readout: dict[int, int] = {} - - -######################################################################################## - - -class Task(TypeReflectBaseModel): - """ - Class representing a task to run a quantum experiment with some arguments - - Attributes: - program (Union[AnalogCircuit, DigitalCircuit, AtomicCircuit]): Quantum experiment to run - args (Union[analog_sim.base.TaskArgsAnalogSimulator, TaskArgsDigital, TaskArgsAtomic]): Arguments for the quantum experiment - """ - - program: Union[AnalogCircuit, DigitalCircuit, AtomicCircuit] - args: Union[TaskArgsAnalog, TaskArgsDigital, TaskArgsAtomic] - - -TaskResult = Union[TaskResultAnalog, TaskResultAtomic, TaskResultDigital] - -######################################################################################## diff --git a/src/oqd_core/compiler/analog/passes/__init__.py b/src/oqd_core/compiler/analog/passes/__init__.py index 3b53023a..17ad3021 100644 --- a/src/oqd_core/compiler/analog/passes/__init__.py +++ b/src/oqd_core/compiler/analog/passes/__init__.py @@ -13,12 +13,11 @@ # limitations under the License. from .analysis import analysis_canonical_hamiltonian_dim, analysis_term_index -from .assign import assign_analog_circuit_dim, verify_analog_args_dim +from .assign import assign_analog_circuit_dim from .canonicalize import analog_operator_canonicalization __all__ = [ "assign_analog_circuit_dim", - "verify_analog_args_dim", "analog_operator_canonicalization", "analysis_canonical_hamiltonian_dim", "analysis_term_index", diff --git a/src/oqd_core/compiler/analog/passes/assign.py b/src/oqd_core/compiler/analog/passes/assign.py index 96eef144..8ae1e811 100644 --- a/src/oqd_core/compiler/analog/passes/assign.py +++ b/src/oqd_core/compiler/analog/passes/assign.py @@ -17,7 +17,6 @@ ######################################################################################## from oqd_core.compiler.analog.rewrite.assign import AssignAnalogIRDim from oqd_core.compiler.analog.verify.task import ( - VerifyAnalogArgsDim, VerifyAnalogCircuitDim, ) @@ -25,7 +24,6 @@ __all__ = [ "assign_analog_circuit_dim", - "verify_analog_args_dim", ] ######################################################################################## @@ -51,20 +49,3 @@ def assign_analog_circuit_dim(model): ) )(assigned_model) return assigned_model - - -def verify_analog_args_dim(model, n_qreg, n_qmode): - """ - This pass checks whether the assigned n_qreg and n_qmode in AnalogCircuit match the n_qreg and n_qmode - in any Operators (like the Operator inside Expectation) in TaskArgsAnalog - - Args: - model (TaskArgsAnalog): - - Returns: - model (TaskArgsAnalog): - - Assumptions: - All [`Operator`][oqd_core.interface.analog.operator.Operator] inside TaskArgsAnalog must be canonicalized - """ - Post(VerifyAnalogArgsDim(n_qreg=n_qreg, n_qmode=n_qmode))(model) diff --git a/src/oqd_core/compiler/analog/rewrite/canonicalize.py b/src/oqd_core/compiler/analog/rewrite/canonicalize.py index b5f45347..5fef7b3b 100644 --- a/src/oqd_core/compiler/analog/rewrite/canonicalize.py +++ b/src/oqd_core/compiler/analog/rewrite/canonicalize.py @@ -386,9 +386,6 @@ def __init__(self): def map_AnalogGate(self, model): self.op_add_root = False - def map_Expectation(self, model): - self.op_add_root = False - def map_Operator(self, model: Operator): if not self.op_add_root: self.op_add_root = True diff --git a/src/oqd_core/compiler/analog/verify/__init__.py b/src/oqd_core/compiler/analog/verify/__init__.py index 36d70158..34852b58 100644 --- a/src/oqd_core/compiler/analog/verify/__init__.py +++ b/src/oqd_core/compiler/analog/verify/__init__.py @@ -25,7 +25,6 @@ ) from .operator import VerifyHilberSpaceDim from .task import ( - VerifyAnalogArgsDim, VerifyAnalogCircuitDim, ) @@ -40,6 +39,5 @@ "CanVerSortedOrder", "CanVerScaleTerm", "VerifyAnalogCircuitDim", - "VerifyAnalogArgsDim", "VerifyHilberSpaceDim", ] diff --git a/src/oqd_core/compiler/analog/verify/canonicalize.py b/src/oqd_core/compiler/analog/verify/canonicalize.py index 5be663cf..0199230e 100644 --- a/src/oqd_core/compiler/analog/verify/canonicalize.py +++ b/src/oqd_core/compiler/analog/verify/canonicalize.py @@ -385,9 +385,6 @@ def __init__(self): def map_AnalogGate(self, model): self._single_term_scaling_needed = False - def map_Expectation(self, model): - self._single_term_scaling_needed = False - def map_OperatorScalarMul(self, model: OperatorScalarMul): self._single_term_scaling_needed = True pass diff --git a/src/oqd_core/compiler/analog/verify/operator.py b/src/oqd_core/compiler/analog/verify/operator.py index 94affb00..6efb8732 100644 --- a/src/oqd_core/compiler/analog/verify/operator.py +++ b/src/oqd_core/compiler/analog/verify/operator.py @@ -71,9 +71,6 @@ def _reset(self): def map_AnalogGate(self, model): self._reset() - def map_Expectation(self, model): - self._reset() - def _get_dim(self, model): if isinstance(model, Pauli): return (1, 0) @@ -103,14 +100,14 @@ def map_OperatorAdd(self, model): assert self._term_dim == new, "Incorrect Hilbert space dimension" if isinstance(model.op1, Union[OperatorTerminal, OperatorMul]): - assert self._term_dim == self._get_dim(model.op1), ( - "Incorrect Hilbert space dimension" - ) + assert self._term_dim == self._get_dim( + model.op1 + ), "Incorrect Hilbert space dimension" elif isinstance(model.op1, OperatorScalarMul): if isinstance(model.op1.op, Union[OperatorTerminal, OperatorMul]): - assert self._term_dim == self._get_dim(model.op1.op), ( - "Incorrect Hilbert space dimension" - ) + assert self._term_dim == self._get_dim( + model.op1.op + ), "Incorrect Hilbert space dimension" if not isinstance(model.op1, OperatorAdd): self._final_add_term = True diff --git a/src/oqd_core/compiler/analog/verify/task.py b/src/oqd_core/compiler/analog/verify/task.py index b2f7e9f9..6534af7e 100644 --- a/src/oqd_core/compiler/analog/verify/task.py +++ b/src/oqd_core/compiler/analog/verify/task.py @@ -14,7 +14,6 @@ from oqd_compiler_infrastructure import RewriteRule -from oqd_core.backend.metric import Expectation from oqd_core.compiler.analog.passes.analysis import analysis_canonical_hamiltonian_dim ######################################################################################## @@ -24,7 +23,6 @@ __all__ = [ "VerifyAnalogCircuitDim", - "VerifyAnalogArgsDim", ] ######################################################################################## @@ -51,32 +49,6 @@ def __init__(self, n_qreg, n_qmode): self._dim: tuple = (n_qreg, n_qmode) def map_AnalogGate(self, model: AnalogGate): - assert self._dim == analysis_canonical_hamiltonian_dim(model.hamiltonian), ( - "Inconsistent Hilbert space dimension between Analog Gates" - ) - - -class VerifyAnalogArgsDim(RewriteRule): - """ - Checks whether hilbert space dimensions are consistent between Expectation in args - and whether they match the n_qreg and n_qmode given as input. - - Args: - model (VisitableBaseModel): - The rule only verfies Expectation inside TaskArgsAnalog in Analog layer - - Returns: - model (VisitableBaseModel): unchanged - - Assumptions: - All [`Operator`][oqd_core.interface.analog.operator.Operator] inside [`AnalogCircuit`][oqd_core.interface.analog.operations.AnalogCircuit] are canonicalized - """ - - def __init__(self, n_qreg, n_qmode): - super().__init__() - self._dim: tuple = (n_qreg, n_qmode) - - def map_Expectation(self, model: Expectation): - assert self._dim == analysis_canonical_hamiltonian_dim(model.operator), ( - "Inconsistent Hilbert space dimension in Expectation metric" - ) + assert self._dim == analysis_canonical_hamiltonian_dim( + model.hamiltonian + ), "Inconsistent Hilbert space dimension between Analog Gates" diff --git a/src/oqd_core/interface/analog/__init__.py b/src/oqd_core/interface/analog/__init__.py index f3be5146..5901a49a 100644 --- a/src/oqd_core/interface/analog/__init__.py +++ b/src/oqd_core/interface/analog/__init__.py @@ -31,7 +31,7 @@ OperatorMul, OperatorScalarMul, OperatorSub, - OperatorSubtypes, + OperatorSubTypes, OperatorTerminal, Pauli, PauliI, @@ -43,7 +43,7 @@ ) __all__ = [ - "OperatorSubtypes", + "OperatorSubTypes", "Operator", "OperatorTerminal", "Pauli", diff --git a/src/oqd_core/interface/analog/operation.py b/src/oqd_core/interface/analog/operation.py index 8b8e7f00..90394d8b 100644 --- a/src/oqd_core/interface/analog/operation.py +++ b/src/oqd_core/interface/analog/operation.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Literal, Union +from __future__ import annotations -# %% -from oqd_compiler_infrastructure import TypeReflectBaseModel, VisitableBaseModel +from typing import Annotated, List, Optional, Union + +from oqd_compiler_infrastructure import TypeReflectBaseModel +from pydantic import Discriminator from pydantic.types import NonNegativeInt -from oqd_core.interface.analog.operator import OperatorSubtypes +from oqd_core.interface.analog.operator import OperatorSubTypes __all__ = [ "AnalogCircuit", @@ -33,24 +35,26 @@ ######################################################################################## -class AnalogGate(TypeReflectBaseModel): +class AnalogOperation(TypeReflectBaseModel): """ - Class representing an analog gate composed of Hamiltonian terms and dissipation terms - - Attributes: - hamiltonian (Operator): Hamiltonian terms of the gate + Class representing an analog operation applied to the quantum system """ - hamiltonian: OperatorSubtypes + pass + + +######################################################################################## -# %% -class AnalogOperation(VisitableBaseModel): +class AnalogGate(TypeReflectBaseModel): """ - Class representing an analog operation applied to the quantum system + Class representing an analog gate composed of Hamiltonian terms and dissipation terms + + Attributes: + hamiltonian (Operator): Hamiltonian terms of the gate """ - pass + hamiltonian: OperatorSubTypes class Evolve(AnalogOperation): @@ -62,9 +66,11 @@ class Evolve(AnalogOperation): gate (AnalogGate): Analog gate to evolve by """ - key: Literal["evolve"] = "evolve" duration: float - gate: Union[AnalogGate, str] + gate: AnalogGate + + +######################################################################################## class Measure(AnalogOperation): @@ -72,7 +78,7 @@ class Measure(AnalogOperation): Class representing a measurement in the analog circuit """ - key: Literal["measure"] = "measure" + targets: Optional[List[int]] = None class Initialize(AnalogOperation): @@ -80,13 +86,20 @@ class Initialize(AnalogOperation): Class representing a initialization in the analog circuit """ - key: Literal["initialize"] = "initialize" + targets: Optional[List[int]] = None +######################################################################################## + """ -Union of classes +Union of classes """ -Statement = Union[Measure, Evolve, Initialize] +AnalogOperationSubTypes = Annotated[ + Union[Measure, Evolve, Initialize], Discriminator(discriminator="class_") +] + + +######################################################################################## class AnalogCircuit(AnalogOperation): @@ -98,7 +111,7 @@ class AnalogCircuit(AnalogOperation): """ - sequence: List[Statement] = [] + sequence: List[AnalogOperationSubTypes] = [] n_qreg: Union[NonNegativeInt, None] = None n_qmode: Union[NonNegativeInt, None] = None @@ -106,8 +119,8 @@ class AnalogCircuit(AnalogOperation): def evolve(self, gate: AnalogGate, duration: float): self.sequence.append(Evolve(duration=duration, gate=gate)) - def initialize(self): - self.sequence.append(Initialize()) + def initialize(self, targets=None): + self.sequence.append(Initialize(targets=targets)) - def measure(self): - self.sequence.append(Measure()) + def measure(self, targets=None): + self.sequence.append(Measure(targets=targets)) diff --git a/src/oqd_core/interface/analog/operator.py b/src/oqd_core/interface/analog/operator.py index fc79af37..0583b3cb 100644 --- a/src/oqd_core/interface/analog/operator.py +++ b/src/oqd_core/interface/analog/operator.py @@ -14,14 +14,15 @@ from __future__ import annotations -from typing import Union +from typing import Annotated, Union from oqd_compiler_infrastructure import TypeReflectBaseModel +from pydantic import Discriminator ######################################################################################## from oqd_core.interface.math import ( MathExpr, - MathExprSubtypes, + MathExprSubTypes, MathImag, MathMul, MathNum, @@ -49,7 +50,7 @@ "OperatorMul", "OperatorScalarMul", "OperatorKron", - "OperatorSubtypes", + "OperatorSubTypes", ] @@ -223,8 +224,8 @@ class OperatorScalarMul(Operator): expr (MathExpr): [`MathExpr`][oqd_core.interface.math.MathExpr] to multiply by """ - op: OperatorSubtypes - expr: MathExprSubtypes + op: OperatorSubTypes + expr: MathExprSubTypes class OperatorBinaryOp(Operator): @@ -244,8 +245,8 @@ class OperatorAdd(OperatorBinaryOp): op2 (Operator): Right hand side [`Operator`][oqd_core.interface.analog.operator.Operator] """ - op1: OperatorSubtypes - op2: OperatorSubtypes + op1: OperatorSubTypes + op2: OperatorSubTypes class OperatorSub(OperatorBinaryOp): @@ -257,8 +258,8 @@ class OperatorSub(OperatorBinaryOp): op2 (Operator): Right hand side [`Operator`][oqd_core.interface.analog.operator.Operator] """ - op1: OperatorSubtypes - op2: OperatorSubtypes + op1: OperatorSubTypes + op2: OperatorSubTypes class OperatorMul(OperatorBinaryOp): @@ -270,8 +271,8 @@ class OperatorMul(OperatorBinaryOp): op2 (Operator): Right hand side [`Operator`][oqd_core.interface.analog.operator.Operator] """ - op1: OperatorSubtypes - op2: OperatorSubtypes + op1: OperatorSubTypes + op2: OperatorSubTypes class OperatorKron(OperatorBinaryOp): @@ -283,25 +284,28 @@ class OperatorKron(OperatorBinaryOp): op2 (Operator): Right hand side [`Operator`][oqd_core.interface.analog.operator.Operator] """ - op1: OperatorSubtypes - op2: OperatorSubtypes + op1: OperatorSubTypes + op2: OperatorSubTypes ######################################################################################## -OperatorSubtypes = Union[ - PauliI, - PauliX, - PauliY, - PauliZ, - Creation, - Annihilation, - Identity, - OperatorAdd, - OperatorSub, - OperatorMul, - OperatorScalarMul, - OperatorKron, +OperatorSubTypes = Annotated[ + Union[ + PauliI, + PauliX, + PauliY, + PauliZ, + Creation, + Annihilation, + Identity, + OperatorAdd, + OperatorSub, + OperatorMul, + OperatorScalarMul, + OperatorKron, + ], + Discriminator(discriminator="class_"), ] """ Alias for the union of concrete Operator subtypes diff --git a/src/oqd_core/interface/atomic/protocol.py b/src/oqd_core/interface/atomic/protocol.py index e05afde1..efced645 100644 --- a/src/oqd_core/interface/atomic/protocol.py +++ b/src/oqd_core/interface/atomic/protocol.py @@ -14,10 +14,10 @@ from __future__ import annotations -from typing import List, Tuple, Union +from typing import Annotated, List, Tuple, Union from oqd_compiler_infrastructure import TypeReflectBaseModel -from pydantic import conlist +from pydantic import Discriminator, conlist from oqd_core.interface.atomic.system import Transition from oqd_core.interface.math import CastMathExpr, ConstantMathExpr @@ -103,7 +103,7 @@ class ParallelProtocol(Protocol): sequence: List of pulses or subprotocols to compose together in a parallel fashion. """ - sequence: List[Union[PulseSubTypes, ProtocolSubTypes]] + sequence: List[ProtocolPulseSubTypes] class SequentialProtocol(Protocol): @@ -114,8 +114,16 @@ class SequentialProtocol(Protocol): sequence: List of pulses or subprotocols to compose together in a sequntial fashion. """ - sequence: List[Union[PulseSubTypes, ProtocolSubTypes]] + sequence: List[ProtocolPulseSubTypes] -ProtocolSubTypes = Union[SequentialProtocol, ParallelProtocol] -PulseSubTypes = Union[Pulse, MeasurePulse] +ProtocolSubTypes = Annotated[ + Union[SequentialProtocol, ParallelProtocol], Discriminator(discriminator="class_") +] +PulseSubTypes = Annotated[ + Union[Pulse, MeasurePulse], Discriminator(discriminator="class_") +] +ProtocolPulseSubTypes = Annotated[ + Union[SequentialProtocol, ParallelProtocol, Pulse, MeasurePulse], + Discriminator(discriminator="class_"), +] diff --git a/src/oqd_core/interface/math.py b/src/oqd_core/interface/math.py index 7d936399..724f8569 100644 --- a/src/oqd_core/interface/math.py +++ b/src/oqd_core/interface/math.py @@ -48,7 +48,7 @@ "MathMul", "MathDiv", "MathPow", - "MathExprSubtypes", + "MathExprSubTypes", "ConstantMathExpr", "CastMathExpr", ] @@ -339,27 +339,25 @@ class MathPow(MathBinaryOp): ######################################################################################## -MathExprSubtypes = Annotated[ +MathExprSubTypes = Annotated[ Union[ - Annotated[MathNum, Tag("MathNum")], - Annotated[MathVar, Tag("MathVar")], - Annotated[MathImag, Tag("MathImag")], - Annotated[MathFunc, Tag("MathFunc")], - Annotated[MathAdd, Tag("MathAdd")], - Annotated[MathSub, Tag("MathSub")], - Annotated[MathMul, Tag("MathMul")], - Annotated[MathDiv, Tag("MathDiv")], - Annotated[MathPow, Tag("MathPow")], + MathNum, + MathVar, + MathImag, + MathFunc, + MathAdd, + MathSub, + MathMul, + MathDiv, + MathPow, ], - Discriminator( - lambda v: v["class_"] if isinstance(v, dict) else getattr(v, "class_") - ), + Discriminator(discriminator="class_"), ] """ Alias for the union of concrete MathExpr subtypes """ -CastMathExpr = Annotated[MathExprSubtypes, BeforeValidator(MathExpr.cast)] +CastMathExpr = Annotated[MathExprSubTypes, BeforeValidator(MathExpr.cast)] """ Annotated type that cast typical numeric python types to MathExpr """ diff --git a/uv.lock b/uv.lock index 790b68f2..447e6f97 100644 --- a/uv.lock +++ b/uv.lock @@ -1461,14 +1461,14 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure#b744a07a769e3a4107a128acb662586156608526" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure#bba1bb47c3fdde9c10be1ab373abf3a223b4c339" } dependencies = [ { name = "pydantic" }, ] [[package]] name = "oqd-core" -version = "0.1.0" +version = "0.2.0.dev1" source = { editable = "." } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -1495,6 +1495,7 @@ tests = [ dev = [ { name = "jupyter" }, { name = "pre-commit" }, + { name = "ruff" }, ] [package.metadata] @@ -1515,6 +1516,7 @@ requires-dist = [ dev = [ { name = "jupyter", specifier = ">=1.1.1" }, { name = "pre-commit", specifier = ">=4.1.0" }, + { name = "ruff", specifier = ">=0.13.1" }, ] [[package]] @@ -2204,6 +2206,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523 }, ] +[[package]] +name = "ruff" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/33/c8e89216845615d14d2d42ba2bee404e7206a8db782f33400754f3799f05/ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51", size = 5397987 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/41/ca37e340938f45cfb8557a97a5c347e718ef34702546b174e5300dbb1f28/ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b", size = 12304308 }, + { url = "https://files.pythonhosted.org/packages/ff/84/ba378ef4129415066c3e1c80d84e539a0d52feb250685091f874804f28af/ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334", size = 12937258 }, + { url = "https://files.pythonhosted.org/packages/8d/b6/ec5e4559ae0ad955515c176910d6d7c93edcbc0ed1a3195a41179c58431d/ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae", size = 12214554 }, + { url = "https://files.pythonhosted.org/packages/70/d6/cb3e3b4f03b9b0c4d4d8f06126d34b3394f6b4d764912fe80a1300696ef6/ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e", size = 12448181 }, + { url = "https://files.pythonhosted.org/packages/d2/ea/bf60cb46d7ade706a246cd3fb99e4cfe854efa3dfbe530d049c684da24ff/ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389", size = 12104599 }, + { url = "https://files.pythonhosted.org/packages/2d/3e/05f72f4c3d3a69e65d55a13e1dd1ade76c106d8546e7e54501d31f1dc54a/ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c", size = 13791178 }, + { url = "https://files.pythonhosted.org/packages/81/e7/01b1fc403dd45d6cfe600725270ecc6a8f8a48a55bc6521ad820ed3ceaf8/ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0", size = 14814474 }, + { url = "https://files.pythonhosted.org/packages/fa/92/d9e183d4ed6185a8df2ce9faa3f22e80e95b5f88d9cc3d86a6d94331da3f/ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36", size = 14217531 }, + { url = "https://files.pythonhosted.org/packages/3b/4a/6ddb1b11d60888be224d721e01bdd2d81faaf1720592858ab8bac3600466/ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38", size = 13265267 }, + { url = "https://files.pythonhosted.org/packages/81/98/3f1d18a8d9ea33ef2ad508f0417fcb182c99b23258ec5e53d15db8289809/ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a", size = 13243120 }, + { url = "https://files.pythonhosted.org/packages/8d/86/b6ce62ce9c12765fa6c65078d1938d2490b2b1d9273d0de384952b43c490/ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783", size = 13443084 }, + { url = "https://files.pythonhosted.org/packages/a1/6e/af7943466a41338d04503fb5a81b2fd07251bd272f546622e5b1599a7976/ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a", size = 12295105 }, + { url = "https://files.pythonhosted.org/packages/3f/97/0249b9a24f0f3ebd12f007e81c87cec6d311de566885e9309fcbac5b24cc/ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700", size = 12072284 }, + { url = "https://files.pythonhosted.org/packages/f6/85/0b64693b2c99d62ae65236ef74508ba39c3febd01466ef7f354885e5050c/ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae", size = 12970314 }, + { url = "https://files.pythonhosted.org/packages/96/fc/342e9f28179915d28b3747b7654f932ca472afbf7090fc0c4011e802f494/ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317", size = 13422360 }, + { url = "https://files.pythonhosted.org/packages/37/54/6177a0dc10bce6f43e392a2192e6018755473283d0cf43cc7e6afc182aea/ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0", size = 12178448 }, + { url = "https://files.pythonhosted.org/packages/64/51/c6a3a33d9938007b8bdc8ca852ecc8d810a407fb513ab08e34af12dc7c24/ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5", size = 13286458 }, + { url = "https://files.pythonhosted.org/packages/fd/04/afc078a12cf68592345b1e2d6ecdff837d286bac023d7a22c54c7a698c5b/ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a", size = 12437893 }, +] + [[package]] name = "scipy" version = "1.15.3"