Skip to content

Commit

Permalink
Merge pull request #903 from qiboteam/platforms-paths
Browse files Browse the repository at this point in the history
Add support for multiple platform paths
  • Loading branch information
alecandido authored Jun 3, 2024
2 parents 485f416 + 4972ed9 commit 2e6c082
Show file tree
Hide file tree
Showing 15 changed files with 208 additions and 132 deletions.
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

modules = [
{
packages = with pkgs; [pre-commit poethepoet jupyter stdenv.cc.cc.lib zlib];
packages = with pkgs; [pre-commit poethepoet jupyter];

env.QIBOLAB_PLATFORMS = platforms;

Expand Down
127 changes: 16 additions & 111 deletions src/qibolab/__init__.py
Original file line number Diff line number Diff line change
@@ -1,111 +1,16 @@
import importlib.metadata as im
import importlib.util
import os
from pathlib import Path

from qibo import Circuit
from qibo.config import raise_error

from qibolab.execution_parameters import (
AcquisitionType,
AveragingMode,
ExecutionParameters,
)
from qibolab.platform import Platform
from qibolab.serialize import PLATFORM

__version__ = im.version(__package__)

PLATFORMS = "QIBOLAB_PLATFORMS"


def get_platforms_path():
"""Get path to repository containing the platforms.
Path is specified using the environment variable QIBOLAB_PLATFORMS.
"""
profiles = os.environ.get(PLATFORMS)
if profiles is None or not os.path.exists(profiles):
raise_error(RuntimeError, f"Profile directory {profiles} does not exist.")
return Path(profiles)


def create_platform(name) -> Platform:
"""A platform for executing quantum algorithms.
It consists of a quantum processor QPU and a set of controlling instruments.
Args:
name (str): name of the platform. Options are 'tiiq', 'qili' and 'icarusq'.
path (pathlib.Path): path with platform serialization
Returns:
The plaform class.
"""
if name == "dummy" or name == "dummy_couplers":
from qibolab.dummy import create_dummy

return create_dummy(with_couplers=name == "dummy_couplers")

platform = get_platforms_path() / f"{name}"
if not platform.exists():
raise_error(
ValueError,
f"Platform {name} does not exist. Please pick one within the platforms found available {get_available_platforms()}.",
)

spec = importlib.util.spec_from_file_location("platform", platform / PLATFORM)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.create()


def execute_qasm(circuit: str, platform, initial_state=None, nshots=1000):
"""Executes a QASM circuit.
Args:
circuit (str): the QASM circuit.
platform (str): the platform where to execute the circuit.
initial_state (:class:`qibo.models.circuit.Circuit`): Circuit to prepare the initial state.
If ``None`` the default ``|00...0>`` state is used.
nshots (int): Number of shots to sample from the experiment.
Returns:
``MeasurementOutcomes`` object containing the results acquired from the execution.
"""
from qibolab.backends import QibolabBackend

circuit = Circuit.from_qasm(circuit)
return QibolabBackend(platform).execute_circuit(
circuit, initial_state=initial_state, nshots=nshots
)


def get_available_platforms() -> list[str]:
"""Returns the platforms found in the $QIBOLAB_PLATFORMS directory."""
return [
d.name
for d in get_platforms_path().iterdir()
if d.is_dir()
and Path(f"{os.environ.get(PLATFORMS)}/{d.name}/platform.py") in d.iterdir()
]


class MetaBackend:
"""Meta-backend class which takes care of loading the qibolab backend."""

@staticmethod
def load(platform: str):
"""Loads the backend.
Args:
platform (str): Name of the platform to load.
Returns:
qibo.backends.abstract.Backend: The loaded backend.
"""
from qibolab.backends import QibolabBackend

return QibolabBackend(platform=platform)

def list_available(self) -> dict:
"""Lists all the available qibolab platforms."""
return {platform: True for platform in get_available_platforms()}
from .backends import MetaBackend, QibolabBackend, execute_qasm
from .execution_parameters import AcquisitionType, AveragingMode, ExecutionParameters
from .platform import Platform, create_platform
from .version import __version__

__all__ = [
"AcquisitionType",
"AveragingMode",
"ExecutionParameters",
"MetaBackend",
"Platform",
"QibolabBackend",
"create_platform",
"execute_qasm",
"__version__",
]
50 changes: 46 additions & 4 deletions src/qibolab/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,32 @@
from qibo.models import Circuit
from qibo.result import MeasurementOutcomes

from qibolab import ExecutionParameters
from qibolab import __version__ as qibolab_version
from qibolab import create_platform
from qibolab.compilers import Compiler
from qibolab.platform import Platform
from qibolab.execution_parameters import ExecutionParameters
from qibolab.platform import Platform, create_platform
from qibolab.platform.load import available_platforms
from qibolab.version import __version__ as qibolab_version


def execute_qasm(circuit: str, platform, initial_state=None, nshots=1000):
"""Executes a QASM circuit.
Args:
circuit (str): the QASM circuit.
platform (str): the platform where to execute the circuit.
initial_state (:class:`qibo.models.circuit.Circuit`): Circuit to prepare the initial state.
If ``None`` the default ``|00...0>`` state is used.
nshots (int): Number of shots to sample from the experiment.
Returns:
``MeasurementOutcomes`` object containing the results acquired from the execution.
"""
from qibolab.backends import QibolabBackend

circuit = Circuit.from_qasm(circuit)
return QibolabBackend(platform).execute_circuit(
circuit, initial_state=initial_state, nshots=nshots
)


class QibolabBackend(NumpyBackend):
Expand Down Expand Up @@ -146,3 +167,24 @@ def execute_circuits(self, circuits, initial_states=None, nshots=1000):
gate.result.backend = self
gate.result.register_samples(np.array(samples).T)
return results


class MetaBackend:
"""Meta-backend class which takes care of loading the qibolab backend."""

@staticmethod
def load(platform: str):
"""Loads the backend.
Args:
platform (str): Name of the platform to load.
Returns:
qibo.backends.abstract.Backend: The loaded backend.
"""
from qibolab.backends import QibolabBackend

return QibolabBackend(platform=platform)

def list_available(self) -> dict:
"""Lists all the available qibolab platforms."""
return {platform: True for platform in available_platforms()}
10 changes: 7 additions & 3 deletions src/qibolab/instruments/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
import numpy as np
from qibo.config import log

from qibolab import AcquisitionType, AveragingMode, ExecutionParameters
from qibolab.platform import Coupler, Qubit
from qibolab.couplers import Coupler
from qibolab.execution_parameters import (
AcquisitionType,
AveragingMode,
ExecutionParameters,
)
from qibolab.pulses import PulseSequence
from qibolab.qubits import QubitId
from qibolab.qubits import Qubit, QubitId
from qibolab.sweeper import Sweeper
from qibolab.unrolling import Bounds

Expand Down
2 changes: 1 addition & 1 deletion src/qibolab/instruments/rfsoc/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import qibosoq.components.base as rfsoc
import qibosoq.components.pulses as rfsoc_pulses

from qibolab.platform import Qubit
from qibolab.pulses import Pulse, PulseSequence, PulseShape
from qibolab.qubits import Qubit
from qibolab.sweeper import BIAS, DURATION, START, Parameter, Sweeper

HZ_TO_MHZ = 1e-6
Expand Down
3 changes: 2 additions & 1 deletion src/qibolab/instruments/rfsoc/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
from qibosoq import client

from qibolab import AcquisitionType, AveragingMode, ExecutionParameters
from qibolab.couplers import Coupler
from qibolab.instruments.abstract import Controller
from qibolab.instruments.port import Port
from qibolab.platform import Coupler, Qubit
from qibolab.pulses import PulseSequence, PulseType
from qibolab.qubits import Qubit
from qibolab.result import IntegratedResults, SampleResults
from qibolab.sweeper import BIAS, Sweeper

Expand Down
4 changes: 4 additions & 0 deletions src/qibolab/platform/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .load import create_platform
from .platform import Platform, unroll_sequences

__all__ = ["Platform", "create_platform", "unroll_sequences"]
75 changes: 75 additions & 0 deletions src/qibolab/platform/load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import importlib.util
import os
from pathlib import Path

from qibo.config import raise_error

from qibolab.serialize import PLATFORM

from .platform import Platform

PLATFORMS = "QIBOLAB_PLATFORMS"


def _platforms_paths() -> list[Path]:
"""Get path to repository containing the platforms.
Path is specified using the environment variable QIBOLAB_PLATFORMS.
"""
paths = os.environ.get(PLATFORMS)
if paths is None:
raise_error(RuntimeError, f"Platforms path ${PLATFORMS} unset.")

return [Path(p) for p in paths.split(os.pathsep)]


def _search(name: str, paths: list[Path]) -> Path:
"""Search paths for given platform name."""
for path in _platforms_paths():
platform = path / name
if platform.exists():
return platform

raise_error(
ValueError,
f"Platform {name} not found. Check ${PLATFORMS} environment variable.",
)


def _load(platform: Path) -> Platform:
"""Load the platform module."""
module_name = "platform"
spec = importlib.util.spec_from_file_location(module_name, platform / PLATFORM)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.create()


def create_platform(name: str) -> Platform:
"""A platform for executing quantum algorithms.
It consists of a quantum processor QPU and a set of controlling instruments.
Args:
name (str): name of the platform. Options are 'tiiq', 'qili' and 'icarusq'.
path (pathlib.Path): path with platform serialization
Returns:
The plaform class.
"""
if name == "dummy" or name == "dummy_couplers":
from qibolab.dummy import create_dummy

return create_dummy(with_couplers=name == "dummy_couplers")

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


def available_platforms() -> list[str]:
"""Returns the platforms found in the $QIBOLAB_PLATFORMS directory."""
return [
d.name
for platforms in _platforms_paths()
for d in platforms.iterdir()
if d.is_dir()
and Path(f"{os.environ.get(PLATFORMS)}/{d.name}/platform.py") in d.iterdir()
]
14 changes: 7 additions & 7 deletions src/qibolab/platform.py → src/qibolab/platform/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import networkx as nx
from qibo.config import log, raise_error

from .couplers import Coupler
from .execution_parameters import ExecutionParameters
from .instruments.abstract import Controller, Instrument, InstrumentId
from .pulses import Drag, FluxPulse, PulseSequence, ReadoutPulse
from .qubits import Qubit, QubitId, QubitPair, QubitPairId
from .sweeper import Sweeper
from .unrolling import batch
from qibolab.couplers import Coupler
from qibolab.execution_parameters import ExecutionParameters
from qibolab.instruments.abstract import Controller, Instrument, InstrumentId
from qibolab.pulses import Drag, FluxPulse, PulseSequence, ReadoutPulse
from qibolab.qubits import Qubit, QubitId, QubitPair, QubitPairId
from qibolab.sweeper import Sweeper
from qibolab.unrolling import batch

InstrumentMap = Dict[InstrumentId, Instrument]
QubitMap = Dict[QubitId, Qubit]
Expand Down
2 changes: 1 addition & 1 deletion src/qibolab/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from qibolab.couplers import Coupler
from qibolab.kernels import Kernels
from qibolab.native import CouplerNatives, SingleQubitNatives, TwoQubitNatives
from qibolab.platform import (
from qibolab.platform.platform import (
CouplerMap,
InstrumentMap,
Platform,
Expand Down
3 changes: 3 additions & 0 deletions src/qibolab/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import importlib.metadata as im

__version__ = im.version(__package__)
3 changes: 2 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import pytest

from qibolab import PLATFORMS, create_platform
from qibolab.platform import create_platform
from qibolab.platform.load import PLATFORMS

ORIGINAL_PLATFORMS = os.environ.get(PLATFORMS, "")
TESTING_PLATFORM_NAMES = [
Expand Down
2 changes: 1 addition & 1 deletion tests/test_instruments_rfsoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
convert_units_sweeper,
replace_pulse_shape,
)
from qibolab.platform import Qubit
from qibolab.pulses import Drag, Gaussian, Pulse, PulseSequence, PulseType, Rectangular
from qibolab.qubits import Qubit
from qibolab.result import (
AveragedIntegratedResults,
AveragedSampleResults,
Expand Down
Loading

0 comments on commit 2e6c082

Please sign in to comment.