diff --git a/mkdocs.yaml b/mkdocs.yaml index 1c7b64d..6b48ceb 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -103,7 +103,6 @@ plugins: import: - https://docs.python.org/3/objects.inv - https://docs.pydantic.dev/latest/objects.inv - - https://qutip.readthedocs.io/en/qutip-4.7.x/objects.inv - https://pandas.pydata.org/docs/objects.inv - https://matplotlib.org/stable/objects.inv - https://numpy.org/doc/stable/objects.inv diff --git a/pyproject.toml b/pyproject.toml index 7452803..9028f31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,16 @@ name = "oqd-core" version = "0.1.0" requires-python = ">=3.10" readme = "README.md" -license = {text = "Apache 2.0"} -keywords = ["quantum", "computing", "analog", "digital", "compiler", "transpilation", "atomic",] +license = { text = "Apache 2.0" } +keywords = [ + "quantum", + "computing", + "analog", + "digital", + "compiler", + "transpilation", + "atomic", +] classifiers = [ "Development Status :: 3 - Alpha", @@ -33,11 +41,11 @@ dependencies = [ [project.optional-dependencies] docs = [ - "pymdown-extensions", - "mkdocstrings", - "mkdocs-material", - "mkdocstrings-python", - "mdx_truly_sane_lists", + "pymdown-extensions", + "mkdocstrings", + "mkdocs-material", + "mkdocstrings-python", + "mdx_truly_sane_lists", ] test = ["pytest"] diff --git a/src/oqd_core/interface/atomic/circuit.py b/src/oqd_core/interface/atomic/circuit.py index 30956ea..93259a2 100644 --- a/src/oqd_core/interface/atomic/circuit.py +++ b/src/oqd_core/interface/atomic/circuit.py @@ -19,6 +19,7 @@ ######################################################################################## from oqd_core.interface.atomic.system import System +from oqd_core.interface.atomic.protocol import ProtocolSubTypes ######################################################################################## @@ -40,4 +41,4 @@ class AtomicCircuit(TypeReflectBaseModel): """ system: System - protocol: Protocol + protocol: ProtocolSubTypes diff --git a/src/oqd_core/interface/atomic/protocol.py b/src/oqd_core/interface/atomic/protocol.py index ab871e8..d1a6095 100644 --- a/src/oqd_core/interface/atomic/protocol.py +++ b/src/oqd_core/interface/atomic/protocol.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + from typing import List, Union from oqd_compiler_infrastructure import TypeReflectBaseModel @@ -87,7 +89,7 @@ class ParallelProtocol(Protocol): sequence: List of pulses or subprotocols to compose together in a parallel fashion. """ - sequence: List[Union[Pulse, Protocol]] + sequence: List[Union[Pulse, ProtocolSubTypes]] class SequentialProtocol(Protocol): @@ -98,4 +100,7 @@ class SequentialProtocol(Protocol): sequence: List of pulses or subprotocols to compose together in a sequntial fashion. """ - sequence: List[Union[Pulse, Protocol]] + sequence: List[Union[Pulse, ProtocolSubTypes]] + + +ProtocolSubTypes = Union[SequentialProtocol, ParallelProtocol] diff --git a/src/oqd_core/interface/digital/circuit.py b/src/oqd_core/interface/digital/circuit.py index b397454..1297ce7 100644 --- a/src/oqd_core/interface/digital/circuit.py +++ b/src/oqd_core/interface/digital/circuit.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pydantic import field_validator, model_validator, ConfigDict from typing import List, Union from oqd_compiler_infrastructure import VisitableBaseModel @@ -32,15 +33,14 @@ class DigitalCircuit(VisitableBaseModel): + model_config = ConfigDict(extra="forbid") + qreg: List[QuantumRegister] = [] creg: List[ClassicalRegister] = [] declarations: List = [] sequence: List[Union[Gate, Statement]] = [] - class Config: - extra = "forbid" - @field_validator("creg", mode="before") @classmethod def convert_creg(cls, v): diff --git a/src/oqd_core/interface/math.py b/src/oqd_core/interface/math.py index 7aee534..164c569 100644 --- a/src/oqd_core/interface/math.py +++ b/src/oqd_core/interface/math.py @@ -51,6 +51,8 @@ class MathExpr(TypeReflectBaseModel): @classmethod def cast(cls, value: Any): + if isinstance(value, dict): + return value if isinstance(value, MathExpr): return value if isinstance(value, (int, float)): @@ -123,8 +125,23 @@ def is_varname(value: str) -> str: VarName = Annotated[str, AfterValidator(is_varname)] -CastMathExpr = Annotated[MathExpr, BeforeValidator(MathExpr.cast)] -Functions = Literal["sin", "cos", "tan", "exp", "log", "sinh", "cosh", "tanh"] +Functions = Literal[ + "sin", + "cos", + "tan", + "exp", + "log", + "sinh", + "cosh", + "tanh", + "atan", + "acos", + "asin", + "atanh", + "asinh", + "acosh", + "heaviside", +] ######################################################################################## @@ -327,3 +344,5 @@ class MathPow(MathBinaryOp): """ Alias for the union of concrete MathExpr subtypes """ + +CastMathExpr = Annotated[MathExprSubtypes, BeforeValidator(MathExpr.cast)] diff --git a/tests/test_analog/test_serialization.py b/tests/test_analog/test_serialization.py deleted file mode 100644 index ac8e67d..0000000 --- a/tests/test_analog/test_serialization.py +++ /dev/null @@ -1,64 +0,0 @@ -# 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 pytest - -from oqd_core.interface.analog import ( - AnalogCircuit, - AnalogGate, - Annihilation, - Creation, - Identity, - PauliI, - PauliX, - PauliY, - PauliZ, -) - -X, Y, Z, PI, A, C, LI = ( - PauliX(), - PauliY(), - PauliZ(), - PauliI(), - Annihilation(), - Creation(), - Identity(), -) - - -@pytest.mark.parametrize( - "op", - [ - X, - Y @ Y @ X, - X + Y + Z + PI, - A + C + LI, - ], -) -def test_serialize_deserialize_op(op): - assert op.model_validate(op.model_dump()) == op - assert op.model_validate_json(op.model_dump_json()) == op - - -def test_serialize_deserialize_analog_circuit(): - Hx = AnalogGate(hamiltonian=-(np.pi / 4) * X) - ac = AnalogCircuit() - ac.evolve(duration=1, gate=Hx) - ac.evolve(duration=1, gate=Hx) - ac.evolve(duration=1, gate=Hx) - ac.measure() - - assert ac.model_validate(ac.model_dump()) == ac - assert ac.model_validate_json(ac.model_dump_json()) == ac diff --git a/tests/test_serialization.py b/tests/test_serialization.py new file mode 100644 index 0000000..6bbaf6a --- /dev/null +++ b/tests/test_serialization.py @@ -0,0 +1,252 @@ +# 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 pytest + +import numpy as np + +######################################################################################## +from oqd_core.interface.math import MathStr + +from oqd_core.interface.analog import ( + Annihilation, + Creation, + Identity, + PauliI, + PauliX, + PauliY, + PauliZ, + AnalogCircuit, + AnalogGate, +) + +from oqd_core.interface.atomic import ( + AtomicCircuit, + ParallelProtocol, + SequentialProtocol, + Pulse, + Beam, + System, + Ion, + Level, + Transition, + Phonon, +) + + +######################################################################################## + +X, Y, Z, PI, A, C, LI = ( + PauliX(), + PauliY(), + PauliZ(), + PauliI(), + Annihilation(), + Creation(), + Identity(), +) + + +Hx = AnalogGate(hamiltonian=-(np.pi / 4) * X) +analog_circuit = AnalogCircuit() +analog_circuit.evolve(duration=1, gate=Hx) +analog_circuit.evolve(duration=1, gate=Hx) +analog_circuit.evolve(duration=1, gate=Hx) +analog_circuit.measure() + +######################################################################################## + +downstate = Level( + principal=6, + spin=1 / 2, + orbital=0, + nuclear=1 / 2, + spin_orbital=1 / 2, + spin_orbital_nuclear=0, + spin_orbital_nuclear_magnetization=0, + energy=2 * np.pi * 0, +) +upstate = Level( + principal=6, + spin=1 / 2, + orbital=0, + nuclear=1 / 2, + spin_orbital=1 / 2, + spin_orbital_nuclear=1, + spin_orbital_nuclear_magnetization=0, + energy=2 * np.pi * 10, +) +estate = Level( + principal=5, + spin=1 / 2, + orbital=1, + nuclear=1 / 2, + spin_orbital=1 / 2, + spin_orbital_nuclear=1, + spin_orbital_nuclear_magnetization=-1, + energy=2 * np.pi * 100, +) +estate2 = Level( + principal=5, + spin=1 / 2, + orbital=1, + nuclear=1 / 2, + spin_orbital=1 / 2, + spin_orbital_nuclear=1, + spin_orbital_nuclear_magnetization=1, + energy=2 * np.pi * 110, +) + +transitions = [ + Transition( + level1=downstate, + level2=estate, + einsteinA=1, + multipole="E1", + ), + Transition( + level1=downstate, + level2=estate2, + einsteinA=1, + multipole="E1", + ), + Transition( + level1=upstate, + level2=estate, + einsteinA=1, + multipole="E1", + ), + Transition( + level1=upstate, + level2=estate2, + einsteinA=1, + multipole="E1", + ), +] + +ion = Ion( + mass=171, + charge=1, + position=[0, 0, 0], + levels=[downstate, upstate, estate, estate2], + transitions=transitions, +) + +COM_x = Phonon(energy=0.1, eigenvector=[1, 0, 0]) + +system = System( + ions=[ + ion, + ], + modes=[ + COM_x, + ], +) + +beam = Beam( + transition=transitions[0], + rabi=2 * np.pi * 1, + detuning=0, + phase=0, + polarization=[1, 0, 0], + wavevector=[0, 1, 0], + target=0, +) + +beam2 = Beam( + transition=transitions[0], + rabi=2 * np.pi * 5, + detuning=2 * np.pi * 25, + phase=0, + polarization=[1, 0, 0], + wavevector=[0, 1, 0], + target=0, +) + +beam3 = Beam( + transition=transitions[2], + rabi=2 * np.pi * 5, + detuning=2 * np.pi * 25, + phase=0, + polarization=[1, 0, 0], + wavevector=[0, 1, 0], + target=0, +) + +protocol1 = SequentialProtocol( + sequence=[ + Pulse(beam=beam, duration=10), + ] +) +protocol2 = ParallelProtocol( + sequence=[ + Pulse(beam=beam2, duration=10), + Pulse(beam=beam3, duration=10), + ] +) + +protocol = SequentialProtocol( + sequence=[ + protocol1, + protocol2, + ] +) + +atomic_circuit = AtomicCircuit(system=system, protocol=protocol) + +######################################################################################## + + +@pytest.mark.parametrize( + "model", + [ + MathStr(string="1"), + MathStr(string="t"), + MathStr(string="1+2"), + MathStr(string="1*2"), + MathStr(string="1**2"), + MathStr(string="sin(t)"), + MathStr(string="exp(t)"), + X, + Y @ Y @ X, + X + Y + Z + PI, + A + C + LI, + Hx, + analog_circuit, + downstate, + upstate, + estate, + estate2, + transitions[0], + transitions[1], + transitions[2], + transitions[3], + ion, + COM_x, + system, + beam, + beam2, + beam3, + Pulse(beam=beam, duration=10), + Pulse(beam=beam2, duration=10), + Pulse(beam=beam3, duration=10), + protocol1, + protocol2, + protocol, + atomic_circuit, + ], +) +def test_serialize_deserialize(model): + assert model.model_validate(model.model_dump()) == model + assert model.model_validate_json(model.model_dump_json()) == model