Skip to content

Commit

Permalink
Merge pull request #1123 from qiboteam/parameters-builder
Browse files Browse the repository at this point in the history
Parameters builder
  • Loading branch information
stavros11 authored Jan 16, 2025
2 parents cbaf2c6 + 3af2909 commit b873571
Show file tree
Hide file tree
Showing 6 changed files with 708 additions and 28 deletions.
507 changes: 504 additions & 3 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ optional = true

[tool.poetry.group.tests.dependencies]
pytest = ">=7.2.2"
setuptools = ">67.0.0"
pytest-cov = "^4.0.0"
pytest-env = ">=0.8.1"
pytest-mock = ">=3.10.0"
Expand Down
142 changes: 137 additions & 5 deletions src/qibolab/_core/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,40 @@
"""

from collections.abc import Callable, Iterable
from typing import Annotated, Any, Union
from typing import Annotated, Any, Optional, Union

from pydantic import BeforeValidator, Field, PlainSerializer, TypeAdapter
from pydantic_core import core_schema

from .components import ChannelConfig, Config
from .components import (
AcquisitionChannel,
AcquisitionConfig,
Channel,
ChannelConfig,
Config,
DcChannel,
DcConfig,
IqChannel,
IqConfig,
IqMixerConfig,
OscillatorConfig,
)
from .execution_parameters import ConfigUpdate, ExecutionParameters
from .identifier import QubitId, QubitPairId
from .native import SingleQubitNatives, TwoQubitNatives
from .identifier import ChannelId, QubitId, QubitPairId
from .instruments.abstract import Instrument, InstrumentId
from .native import Native, NativeContainer, SingleQubitNatives, TwoQubitNatives
from .pulses import Acquisition, Pulse, Readout, Rectangular
from .qubits import Qubit
from .serialize import Model, replace
from .unrolling import Bounds

__all__ = ["ConfigKinds"]
__all__ = [
"ConfigKinds",
"QubitMap",
"InstrumentMap",
"Hardware",
"initialize_parameters",
]


def update_configs(configs: dict[str, Config], updates: list[ConfigUpdate]):
Expand Down Expand Up @@ -202,3 +223,114 @@ def replace(self, update: Update) -> "Parameters":
_setvalue(d, path, val)

return self.model_validate(d)


QubitMap = dict[QubitId, Qubit]
InstrumentMap = dict[InstrumentId, Instrument]


class Hardware(Model):
"""Part of the platform that specifies the hardware configuration."""

instruments: InstrumentMap
qubits: QubitMap
couplers: QubitMap = Field(default_factory=dict)


def _gate_channel(qubit: Qubit, gate: str) -> str:
"""Default channel that a native gate plays on."""
if gate in ("RX", "RX90", "CNOT"):
return qubit.drive
if gate == "RX12":
return qubit.drive_qudits[(1, 2)]
if gate == "MZ":
return qubit.acquisition
if gate in ("CP", "CZ", "iSWAP"):
return qubit.flux


def _gate_sequence(qubit: Qubit, gate: str) -> Native:
"""Default sequence corresponding to a native gate."""
channel = _gate_channel(qubit, gate)
pulse = Pulse(duration=0, amplitude=0, envelope=Rectangular())
if gate != "MZ":
return Native([(channel, pulse)])

return Native(
[(channel, Readout(acquisition=Acquisition(duration=0), probe=pulse))]
)


def _pair_to_qubit(pair: str, qubits: QubitMap) -> Qubit:
"""Get first qubit of a pair given in ``{q0}-{q1}`` format."""
q = tuple(pair.split("-"))[0]
try:
return qubits[q]
except KeyError:
return qubits[int(q)]


def _native_builder(cls, qubit: Qubit, natives: set[str]) -> NativeContainer:
"""Build default native gates for a given qubit or pair.
In case of pair, ``qubit`` is assumed to be the first qubit of the pair,
and a default pulse is added on that qubit, because at this stage we don't
know which qubit is the high frequency one.
"""
return cls(
**{
gate: _gate_sequence(qubit, gate)
for gate in cls.model_fields.keys() & natives
}
)


def _channel_config(id: ChannelId, channel: Channel) -> dict[ChannelId, Config]:
"""Default configs correspondign to a channel."""
if isinstance(channel, DcChannel):
return {id: DcConfig(offset=0)}
if isinstance(channel, AcquisitionChannel):
return {id: AcquisitionConfig(delay=0, smearing=0)}
if isinstance(channel, IqChannel):
configs = {id: IqConfig(frequency=0)}
if channel.lo is not None:
configs[channel.lo] = OscillatorConfig(frequency=0, power=0)
if channel.mixer is not None:
configs[channel.mixer] = IqMixerConfig(frequency=0, power=0)
return configs
return {id: Config()}


def initialize_parameters(
hardware: Hardware,
natives: Optional[set[str]] = None,
pairs: Optional[list[str]] = None,
) -> Parameters:
"""Generates default ``Parameters`` for a given hardware configuration."""
natives = set(natives if natives is not None else ())
configs = {}
for instrument in hardware.instruments.values():
if hasattr(instrument, "channels"):
for id, channel in instrument.channels.items():
configs |= _channel_config(id, channel)

single_qubit = {
q: _native_builder(SingleQubitNatives, qubit, natives - {"CP"})
for q, qubit in hardware.qubits.items()
}
coupler = {
q: _native_builder(SingleQubitNatives, qubit, natives & {"CP"})
for q, qubit in hardware.couplers.items()
}
two_qubit = {
pair: _native_builder(
TwoQubitNatives, _pair_to_qubit(pair, hardware.qubits), natives
)
for pair in (pairs if pairs is not None else ())
}

native_gates = NativeGates(
single_qubit=single_qubit, coupler=coupler, two_qubit=two_qubit
)

return Parameters(settings=Settings(), configs=configs, native_gates=native_gates)
14 changes: 10 additions & 4 deletions src/qibolab/_core/platform/load.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import importlib.util
import os
from pathlib import Path
from typing import Optional
from typing import Optional, Union

from qibo.config import raise_error

from ..parameters import Hardware
from .platform import Platform

__all__ = ["create_platform", "locate_platform"]
Expand Down Expand Up @@ -38,7 +39,7 @@ def _search(name: str, paths: list[Path]) -> Path:
)


def _load(platform: Path) -> Platform:
def _load(platform: Path) -> Union[Platform, Hardware]:
"""Load the platform module."""
module_name = "platform"
spec = importlib.util.spec_from_file_location(module_name, platform / PLATFORM)
Expand Down Expand Up @@ -68,7 +69,6 @@ def create_platform(name: str) -> Platform:
Args:
name (str): name of the platform.
path (pathlib.Path): path with platform serialization
Returns:
The plaform class.
"""
Expand All @@ -77,7 +77,13 @@ def create_platform(name: str) -> Platform:

return create_dummy()

return _load(_search(name, _platforms_paths()))
path = _search(name, _platforms_paths())

hardware = _load(path)
if isinstance(hardware, Platform):
return hardware

return Platform.load(path, **hardware.model_dump())


def available_platforms() -> list[str]:
Expand Down
28 changes: 14 additions & 14 deletions src/qibolab/_core/platform/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@
from ..components.channels import Channel
from ..execution_parameters import ExecutionParameters
from ..identifier import ChannelId, QubitId, QubitPairId, Result
from ..instruments.abstract import Controller, Instrument, InstrumentId
from ..parameters import NativeGates, Parameters, Settings, Update, update_configs
from ..instruments.abstract import Controller
from ..parameters import (
InstrumentMap,
NativeGates,
Parameters,
QubitMap,
Settings,
Update,
update_configs,
)
from ..pulses import PulseId
from ..qubits import Qubit
from ..sequence import PulseSequence
Expand All @@ -21,10 +29,6 @@

__all__ = ["Platform"]

QubitMap = dict[QubitId, Qubit]
QubitPairMap = list[QubitPairId]
InstrumentMap = dict[InstrumentId, Instrument]

NS_TO_SEC = 1e-9
PARAMETERS = "parameters.json"

Expand Down Expand Up @@ -301,17 +305,13 @@ def load(
name: Optional[str] = None,
) -> "Platform":
"""Dump platform."""
if name is None:
name = path.name
if couplers is None:
couplers = {}

parameters = Parameters.model_validate_json((path / PARAMETERS).read_text())
return cls(
name=name,
parameters=Parameters.model_validate_json((path / PARAMETERS).read_text()),
name=name if name is not None else path.name,
parameters=parameters,
instruments=instruments,
qubits=qubits,
couplers=couplers,
couplers=couplers if couplers is not None else {},
)

def dump(self, path: Path):
Expand Down
44 changes: 42 additions & 2 deletions tests/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@

from qibolab._core.components.configs import Config
from qibolab._core.native import Native, TwoQubitNatives
from qibolab._core.parameters import ConfigKinds, Parameters, TwoQubitContainer
from qibolab._core.parameters import (
ConfigKinds,
Hardware,
Parameters,
TwoQubitContainer,
initialize_parameters,
)
from qibolab._core.platform.load import create_platform
from qibolab._core.pulses.pulse import Pulse
from qibolab._core.pulses.pulse import Pulse, Readout


def test_two_qubit_container():
Expand Down Expand Up @@ -104,3 +110,37 @@ def test_update():
assert dummy.settings.nshots == 42
assert dummy.natives.single_qubit[1].RX[0][1].amplitude == -0.123
assert dummy.natives.single_qubit[1].RX[0][1].duration == 456.7


def test_builder():
dummy = create_platform("dummy")

hardware = Hardware(
instruments=dummy.instruments,
qubits=dummy.qubits,
couplers=dummy.couplers,
)
parameters = initialize_parameters(
hardware=hardware, natives=["RX", "MZ", "CZ"], pairs=["0-2"]
)

for q in dummy.qubits:
assert f"{q}/drive" in parameters.configs
assert f"{q}/probe" in parameters.configs
assert f"{q}/acquisition" in parameters.configs
assert f"{q}/drive12" in parameters.configs
assert q in parameters.native_gates.single_qubit
for c in dummy.couplers:
assert f"coupler_{c}/flux" in parameters.configs
assert c in parameters.native_gates.coupler

assert list(parameters.native_gates.two_qubit) == [(0, 2)]
sequence = parameters.native_gates.two_qubit[(0, 2)].CZ
assert sequence[0][0] == "0/flux"
assert isinstance(sequence[0][1], Pulse)
sequence = parameters.native_gates.single_qubit[0].RX
assert sequence[0][0] == "0/drive"
assert isinstance(sequence[0][1], Pulse)
sequence = parameters.native_gates.single_qubit[2].MZ
assert sequence[0][0] == "2/acquisition"
assert isinstance(sequence[0][1], Readout)

0 comments on commit b873571

Please sign in to comment.